Go implementation of pdsadmin cli

feat: initial release

Implement a subset of commands

+2
.cobra.yaml
··· 1 + author: QuietEngineer <qtengineer@proton.me> 2 + license: MIT
+15
.github/dependabot.yaml
··· 1 + # To get started with Dependabot version updates, you'll need to specify which 2 + # package ecosystems to update and where the package manifests are located. 3 + # Please see the documentation for all configuration options: 4 + # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 + 6 + version: 2 7 + updates: 8 + - package-ecosystem: "gomod" # See documentation for possible values 9 + directory: "/" # Location of package manifests 10 + schedule: 11 + interval: "weekly" 12 + - package-ecosystem: "github-actions" 13 + directory: "/" 14 + schedule: 15 + interval: "weekly"
+26
.github/workflows/go.yaml
··· 1 + name: go 2 + on: 3 + push: 4 + branches: 5 + - main 6 + pull_request: 7 + branches: 8 + - main 9 + 10 + permissions: 11 + contents: read 12 + 13 + jobs: 14 + build: 15 + runs-on: ubuntu-latest 16 + steps: 17 + - uses: actions/checkout@v4 18 + - name: setup go environment 19 + uses: actions/setup-go@v5 20 + with: 21 + go-version: "1.24" 22 + check-latest: true 23 + - name: build 24 + run: go build -v ./... 25 + - name: test 26 + run: go test -v ./...
+21
.github/workflows/golangci-lint.yaml
··· 1 + name: golangci-lint 2 + on: 3 + push: 4 + branches: 5 + - main 6 + pull_request: 7 + 8 + permissions: 9 + contents: read 10 + 11 + jobs: 12 + golangci: 13 + name: lint 14 + runs-on: ubuntu-latest 15 + steps: 16 + - uses: actions/checkout@v4 17 + - uses: actions/setup-go@v5 18 + with: 19 + go-version: "1.24" 20 + - name: golangci-lint 21 + uses: golangci/golangci-lint-action@v8
+19
.github/workflows/goreleaser.yaml
··· 1 + name: goreleaser 2 + on: 3 + push: 4 + tags: 5 + - "*" 6 + 7 + permissions: 8 + contents: write 9 + 10 + jobs: 11 + goreleaser: 12 + runs-on: ubuntu-latest 13 + steps: 14 + - uses: actions/checkout@v4 15 + - uses: actions/setup-go@v5 16 + - name: run goreleaser 17 + uses: goreleaser/goreleaser-action@v6 18 + with: 19 + args: release --clean
+1
.gitignore
··· 1 + node_modules
+57
.goreleaser.yaml
··· 1 + # This is an example .goreleaser.yml file with some sensible defaults. 2 + # Make sure to check the documentation at https://goreleaser.com 3 + 4 + # The lines below are called `modelines`. See `:help modeline` 5 + # Feel free to remove those if you don't want/need to use them. 6 + # yaml-language-server: $schema=https://goreleaser.com/static/schema.json 7 + # vim: set ts=2 sw=2 tw=0 fo=cnqoj 8 + 9 + version: 2 10 + 11 + before: 12 + hooks: 13 + # You may remove this if you don't use go modules. 14 + - go mod tidy 15 + # you may remove this if you don't need go generate 16 + - go generate ./... 17 + 18 + builds: 19 + - env: 20 + - CGO_ENABLED=0 21 + goos: 22 + - linux 23 + # - windows 24 + # - darwin 25 + goarch: 26 + - amd64 27 + - arm64 28 + 29 + archives: 30 + - formats: [tar.gz] 31 + # this name template makes the OS and Arch compatible with the results of `uname`. 32 + name_template: >- 33 + {{ .ProjectName }}_ 34 + {{- title .Os }}_ 35 + {{- if eq .Arch "amd64" }}x86_64 36 + {{- else if eq .Arch "386" }}i386 37 + {{- else }}{{ .Arch }}{{ end }} 38 + {{- if .Arm }}v{{ .Arm }}{{ end }} 39 + # use zip for windows archives 40 + format_overrides: 41 + - goos: windows 42 + formats: [zip] 43 + 44 + changelog: 45 + sort: asc 46 + filters: 47 + exclude: 48 + - "^docs:" 49 + - "^test:" 50 + - "^chore:" 51 + 52 + release: 53 + footer: >- 54 + 55 + --- 56 + 57 + Released by [GoReleaser](https://github.com/goreleaser/goreleaser).
+27
.tangled/workflows/build_and_test.yaml
··· 1 + when: 2 + - event: ["push", "pull_request"] 3 + branch: ["main", "develop"] 4 + - event: ["manual"] 5 + 6 + dependencies: 7 + nixpkgs: 8 + - go 9 + - golangci-lint 10 + 11 + steps: 12 + - name: format 13 + command: | 14 + gofmt -l . 15 + 16 + - name: lint 17 + command: | 18 + golangci-lint --version 19 + golangci-lint run ./... 20 + 21 + - name: build application 22 + command: | 23 + go build -v ./... 24 + 25 + - name: test application 26 + command: | 27 + go test -v ./...
+13
.tangled/workflows/release.yaml
··· 1 + when: 2 + - event: ["tag"] 3 + branch: ["main"] 4 + 5 + dependencies: 6 + nixpkgs: 7 + - go 8 + - goreleaser 9 + 10 + steps: 11 + - name: create release 12 + command: | 13 + goreleaser release --clean
+202
LICENSE-APACHE.txt
··· 1 + 2 + Apache License 3 + Version 2.0, January 2004 4 + http://www.apache.org/licenses/ 5 + 6 + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 + 8 + 1. Definitions. 9 + 10 + "License" shall mean the terms and conditions for use, reproduction, 11 + and distribution as defined by Sections 1 through 9 of this document. 12 + 13 + "Licensor" shall mean the copyright owner or entity authorized by 14 + the copyright owner that is granting the License. 15 + 16 + "Legal Entity" shall mean the union of the acting entity and all 17 + other entities that control, are controlled by, or are under common 18 + control with that entity. For the purposes of this definition, 19 + "control" means (i) the power, direct or indirect, to cause the 20 + direction or management of such entity, whether by contract or 21 + otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 + outstanding shares, or (iii) beneficial ownership of such entity. 23 + 24 + "You" (or "Your") shall mean an individual or Legal Entity 25 + exercising permissions granted by this License. 26 + 27 + "Source" form shall mean the preferred form for making modifications, 28 + including but not limited to software source code, documentation 29 + source, and configuration files. 30 + 31 + "Object" form shall mean any form resulting from mechanical 32 + transformation or translation of a Source form, including but 33 + not limited to compiled object code, generated documentation, 34 + and conversions to other media types. 35 + 36 + "Work" shall mean the work of authorship, whether in Source or 37 + Object form, made available under the License, as indicated by a 38 + copyright notice that is included in or attached to the work 39 + (an example is provided in the Appendix below). 40 + 41 + "Derivative Works" shall mean any work, whether in Source or Object 42 + form, that is based on (or derived from) the Work and for which the 43 + editorial revisions, annotations, elaborations, or other modifications 44 + represent, as a whole, an original work of authorship. For the purposes 45 + of this License, Derivative Works shall not include works that remain 46 + separable from, or merely link (or bind by name) to the interfaces of, 47 + the Work and Derivative Works thereof. 48 + 49 + "Contribution" shall mean any work of authorship, including 50 + the original version of the Work and any modifications or additions 51 + to that Work or Derivative Works thereof, that is intentionally 52 + submitted to Licensor for inclusion in the Work by the copyright owner 53 + or by an individual or Legal Entity authorized to submit on behalf of 54 + the copyright owner. For the purposes of this definition, "submitted" 55 + means any form of electronic, verbal, or written communication sent 56 + to the Licensor or its representatives, including but not limited to 57 + communication on electronic mailing lists, source code control systems, 58 + and issue tracking systems that are managed by, or on behalf of, the 59 + Licensor for the purpose of discussing and improving the Work, but 60 + excluding communication that is conspicuously marked or otherwise 61 + designated in writing by the copyright owner as "Not a Contribution." 62 + 63 + "Contributor" shall mean Licensor and any individual or Legal Entity 64 + on behalf of whom a Contribution has been received by Licensor and 65 + subsequently incorporated within the Work. 66 + 67 + 2. Grant of Copyright License. Subject to the terms and conditions of 68 + this License, each Contributor hereby grants to You a perpetual, 69 + worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 + copyright license to reproduce, prepare Derivative Works of, 71 + publicly display, publicly perform, sublicense, and distribute the 72 + Work and such Derivative Works in Source or Object form. 73 + 74 + 3. Grant of Patent License. Subject to the terms and conditions of 75 + this License, each Contributor hereby grants to You a perpetual, 76 + worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 + (except as stated in this section) patent license to make, have made, 78 + use, offer to sell, sell, import, and otherwise transfer the Work, 79 + where such license applies only to those patent claims licensable 80 + by such Contributor that are necessarily infringed by their 81 + Contribution(s) alone or by combination of their Contribution(s) 82 + with the Work to which such Contribution(s) was submitted. If You 83 + institute patent litigation against any entity (including a 84 + cross-claim or counterclaim in a lawsuit) alleging that the Work 85 + or a Contribution incorporated within the Work constitutes direct 86 + or contributory patent infringement, then any patent licenses 87 + granted to You under this License for that Work shall terminate 88 + as of the date such litigation is filed. 89 + 90 + 4. Redistribution. You may reproduce and distribute copies of the 91 + Work or Derivative Works thereof in any medium, with or without 92 + modifications, and in Source or Object form, provided that You 93 + meet the following conditions: 94 + 95 + (a) You must give any other recipients of the Work or 96 + Derivative Works a copy of this License; and 97 + 98 + (b) You must cause any modified files to carry prominent notices 99 + stating that You changed the files; and 100 + 101 + (c) You must retain, in the Source form of any Derivative Works 102 + that You distribute, all copyright, patent, trademark, and 103 + attribution notices from the Source form of the Work, 104 + excluding those notices that do not pertain to any part of 105 + the Derivative Works; and 106 + 107 + (d) If the Work includes a "NOTICE" text file as part of its 108 + distribution, then any Derivative Works that You distribute must 109 + include a readable copy of the attribution notices contained 110 + within such NOTICE file, excluding those notices that do not 111 + pertain to any part of the Derivative Works, in at least one 112 + of the following places: within a NOTICE text file distributed 113 + as part of the Derivative Works; within the Source form or 114 + documentation, if provided along with the Derivative Works; or, 115 + within a display generated by the Derivative Works, if and 116 + wherever such third-party notices normally appear. The contents 117 + of the NOTICE file are for informational purposes only and 118 + do not modify the License. You may add Your own attribution 119 + notices within Derivative Works that You distribute, alongside 120 + or as an addendum to the NOTICE text from the Work, provided 121 + that such additional attribution notices cannot be construed 122 + as modifying the License. 123 + 124 + You may add Your own copyright statement to Your modifications and 125 + may provide additional or different license terms and conditions 126 + for use, reproduction, or distribution of Your modifications, or 127 + for any such Derivative Works as a whole, provided Your use, 128 + reproduction, and distribution of the Work otherwise complies with 129 + the conditions stated in this License. 130 + 131 + 5. Submission of Contributions. Unless You explicitly state otherwise, 132 + any Contribution intentionally submitted for inclusion in the Work 133 + by You to the Licensor shall be under the terms and conditions of 134 + this License, without any additional terms or conditions. 135 + Notwithstanding the above, nothing herein shall supersede or modify 136 + the terms of any separate license agreement you may have executed 137 + with Licensor regarding such Contributions. 138 + 139 + 6. Trademarks. This License does not grant permission to use the trade 140 + names, trademarks, service marks, or product names of the Licensor, 141 + except as required for reasonable and customary use in describing the 142 + origin of the Work and reproducing the content of the NOTICE file. 143 + 144 + 7. Disclaimer of Warranty. Unless required by applicable law or 145 + agreed to in writing, Licensor provides the Work (and each 146 + Contributor provides its Contributions) on an "AS IS" BASIS, 147 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 + implied, including, without limitation, any warranties or conditions 149 + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 + PARTICULAR PURPOSE. You are solely responsible for determining the 151 + appropriateness of using or redistributing the Work and assume any 152 + risks associated with Your exercise of permissions under this License. 153 + 154 + 8. Limitation of Liability. In no event and under no legal theory, 155 + whether in tort (including negligence), contract, or otherwise, 156 + unless required by applicable law (such as deliberate and grossly 157 + negligent acts) or agreed to in writing, shall any Contributor be 158 + liable to You for damages, including any direct, indirect, special, 159 + incidental, or consequential damages of any character arising as a 160 + result of this License or out of the use or inability to use the 161 + Work (including but not limited to damages for loss of goodwill, 162 + work stoppage, computer failure or malfunction, or any and all 163 + other commercial damages or losses), even if such Contributor 164 + has been advised of the possibility of such damages. 165 + 166 + 9. Accepting Warranty or Additional Liability. While redistributing 167 + the Work or Derivative Works thereof, You may choose to offer, 168 + and charge a fee for, acceptance of support, warranty, indemnity, 169 + or other liability obligations and/or rights consistent with this 170 + License. However, in accepting such obligations, You may act only 171 + on Your own behalf and on Your sole responsibility, not on behalf 172 + of any other Contributor, and only if You agree to indemnify, 173 + defend, and hold each Contributor harmless for any liability 174 + incurred by, or claims asserted against, such Contributor by reason 175 + of your accepting any such warranty or additional liability. 176 + 177 + END OF TERMS AND CONDITIONS 178 + 179 + APPENDIX: How to apply the Apache License to your work. 180 + 181 + To apply the Apache License to your work, attach the following 182 + boilerplate notice, with the fields enclosed by brackets "[]" 183 + replaced with your own identifying information. (Don't include 184 + the brackets!) The text should be enclosed in the appropriate 185 + comment syntax for the file format. We also recommend that a 186 + file or class name and description of purpose be included on the 187 + same "printed page" as the copyright notice for easier 188 + identification within third-party archives. 189 + 190 + Copyright [yyyy] [name of copyright owner] 191 + 192 + Licensed under the Apache License, Version 2.0 (the "License"); 193 + you may not use this file except in compliance with the License. 194 + You may obtain a copy of the License at 195 + 196 + http://www.apache.org/licenses/LICENSE-2.0 197 + 198 + Unless required by applicable law or agreed to in writing, software 199 + distributed under the License is distributed on an "AS IS" BASIS, 200 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 + See the License for the specific language governing permissions and 202 + limitations under the License.
+21
LICENSE-MIT.txt
··· 1 + The MIT License (MIT) 2 + 3 + Copyright © 2025 QuietEngineer <qtengineer@proton.me> 4 + 5 + Permission is hereby granted, free of charge, to any person obtaining a copy 6 + of this software and associated documentation files (the "Software"), to deal 7 + in the Software without restriction, including without limitation the rights 8 + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 + copies of the Software, and to permit persons to whom the Software is 10 + furnished to do so, subject to the following conditions: 11 + 12 + The above copyright notice and this permission notice shall be included in 13 + all copies or substantial portions of the Software. 14 + 15 + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 + THE SOFTWARE.
+7
LICENSE.txt
··· 1 + Dual MIT/Apache-2.0 License 2 + 3 + Copyright (c) 2025 QuietEngineer <qtengineer@proton.me> 4 + 5 + Except as otherwise noted in individual files, this software is licensed under the MIT license (<http://opensource.org/licenses/MIT>), or the Apache License, Version 2.0 (<http://www.apache.org/licenses/LICENSE-2.0>). 6 + 7 + Downstream projects and end users may chose either license individually, or both together, at their discretion. The motivation for this dual-licensing is the additional software patent assurance provided by Apache 2.0.
+4
README.md
··· 1 + # PDSAdmin 2 + 3 + A go implementation of the pdsadmin shell scripts from [bluesky-social](https://github.com/bluesky-social/pds). 4 + Implemented commands try to be a drop in replacement.
+78
cmd/createInviteCode.go
··· 1 + package cmd 2 + 3 + import ( 4 + "bytes" 5 + "encoding/json" 6 + "fmt" 7 + "io" 8 + "net/http" 9 + "os" 10 + 11 + "github.com/spf13/cobra" 12 + "github.com/spf13/viper" 13 + ) 14 + 15 + type ServerCreateInviteCode_Input struct { 16 + UseCount int `json:"useCount"` 17 + } 18 + 19 + type ServerCreateInviteCode_Output struct { 20 + Code string `json:"code"` 21 + } 22 + 23 + var number *int 24 + 25 + // createInviteCodeCmd represents the createInviteCode command 26 + var createInviteCodeCmd = &cobra.Command{ 27 + Use: "create-invite-code", 28 + Short: "Create a new invite code", 29 + Example: "pdsadmin create-invite-code", 30 + Args: cobra.NoArgs, 31 + Run: func(cmd *cobra.Command, args []string) { 32 + if *number < 1 { 33 + fmt.Println("number must be >=1") 34 + os.Exit(1) 35 + } 36 + body := ServerCreateInviteCode_Input{ 37 + UseCount: *number, 38 + } 39 + jsonBody, err := json.Marshal(body) 40 + if err != nil { 41 + fmt.Printf("could not create json body: %s\n", err) 42 + os.Exit(1) 43 + } 44 + bodyReader := bytes.NewReader(jsonBody) 45 + 46 + req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("https://%s/xrpc/com.atproto.server.createInviteCode", viper.GetString("hostname")), bodyReader) 47 + if err != nil { 48 + fmt.Printf("could not create request: %s\n", err) 49 + os.Exit(1) 50 + } 51 + req.Header.Add("Content-Type", "application/json") 52 + req.SetBasicAuth("admin", viper.GetString("admin_password")) 53 + 54 + client := &http.Client{} 55 + res, err := client.Do(req) 56 + if err != nil { 57 + fmt.Printf("error making http request: %s\n", err) 58 + os.Exit(1) 59 + } 60 + resBody, err := io.ReadAll(res.Body) 61 + if err != nil { 62 + fmt.Printf("could not read response body: %s\n", err) 63 + os.Exit(1) 64 + } 65 + 66 + var inviteCode ServerCreateInviteCode_Output 67 + if err := json.Unmarshal(resBody, &inviteCode); err != nil { 68 + fmt.Printf("could not get invite code: %s\n", err) 69 + os.Exit(1) 70 + } 71 + fmt.Println(inviteCode.Code) 72 + }, 73 + } 74 + 75 + func init() { 76 + rootCmd.AddCommand(createInviteCodeCmd) 77 + number = createInviteCodeCmd.Flags().IntP("number", "n", 1, "number of times the code can be used") 78 + }
+76
cmd/requestCrawl.go
··· 1 + package cmd 2 + 3 + import ( 4 + "bytes" 5 + "encoding/json" 6 + "fmt" 7 + "io" 8 + "net/http" 9 + "os" 10 + "strings" 11 + 12 + "github.com/spf13/cobra" 13 + "github.com/spf13/viper" 14 + ) 15 + 16 + type SyncRequestCrawl_Input struct { 17 + // hostname: Hostname of the current service (eg, PDS) that is requesting to be crawled. 18 + Hostname string `json:"hostname"` 19 + } 20 + 21 + // requestCrawlCmd represents the requestCrawl command 22 + var requestCrawlCmd = &cobra.Command{ 23 + Use: "request-crawl", 24 + Short: "Request a crawl from a relay host", 25 + Example: `pdsadmin request-crawl bsky.network 26 + pdsadmin request-crawl bsky.network,second-relay.example.com`, 27 + Args: cobra.MaximumNArgs(1), 28 + Run: func(cmd *cobra.Command, args []string) { 29 + var relayHosts []string 30 + if len(args) == 1 { 31 + relayHosts = strings.Split(args[0], ",") 32 + } else { 33 + relayHosts = viper.GetStringSlice("crawlers") 34 + } 35 + if len(relayHosts) == 0 { 36 + fmt.Println("ERROR: missing RELAY HOST parameter") 37 + os.Exit(1) 38 + } 39 + 40 + client := &http.Client{} 41 + for _, host := range relayHosts { 42 + if host == "" { 43 + continue 44 + } 45 + fmt.Printf("Requesting crawl from %s\n", host) 46 + if !strings.HasPrefix(host, "https:") && !strings.HasPrefix(host, "http:") { 47 + host = fmt.Sprintf("https://%s", host) 48 + } 49 + body := SyncRequestCrawl_Input{ 50 + Hostname: viper.GetString("hostname"), 51 + } 52 + jsonBody, err := json.Marshal(body) 53 + if err != nil { 54 + fmt.Printf("could not create json body: %s\n", err) 55 + continue 56 + } 57 + bodyReader := bytes.NewReader(jsonBody) 58 + 59 + res, err := client.Post(fmt.Sprintf("%s/xrpc/com.atproto.sync.requestCrawl", host), "application/json", bodyReader) 60 + if err != nil { 61 + fmt.Printf("error making http request: %s\n", err) 62 + continue 63 + } 64 + 65 + if _, err := io.ReadAll(res.Body); err != nil { 66 + fmt.Printf("could not read response body: %s\n", err) 67 + continue 68 + } 69 + } 70 + fmt.Println("done") 71 + }, 72 + } 73 + 74 + func init() { 75 + rootCmd.AddCommand(requestCrawlCmd) 76 + }
+91
cmd/root.go
··· 1 + /* 2 + Copyright © 2025 QuietEngineer <qtengineer@proton.me> 3 + 4 + Permission is hereby granted, free of charge, to any person obtaining a copy 5 + of this software and associated documentation files (the "Software"), to deal 6 + in the Software without restriction, including without limitation the rights 7 + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 + copies of the Software, and to permit persons to whom the Software is 9 + furnished to do so, subject to the following conditions: 10 + 11 + The above copyright notice and this permission notice shall be included in 12 + all copies or substantial portions of the Software. 13 + 14 + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 + THE SOFTWARE. 21 + */ 22 + package cmd 23 + 24 + import ( 25 + "fmt" 26 + "os" 27 + "path/filepath" 28 + 29 + "github.com/spf13/cobra" 30 + "github.com/spf13/viper" 31 + ) 32 + 33 + var cfgFile string 34 + 35 + // rootCmd represents the base command when called without any subcommands 36 + var rootCmd = &cobra.Command{ 37 + Use: "pdsadmin", 38 + Short: "PDSAdmin is a tool for interacting with a PDS you administer.", 39 + Long: `PDSAdmin is a tool for interacting with a PDS you own. 40 + 41 + This implementation tries to be comparable with [bluesky-social's](https://github.com/bluesky-social/pds).`, 42 + Version: "0.0.1", 43 + // Uncomment the following line if your bare application 44 + // has an action associated with it: 45 + // Run: func(cmd *cobra.Command, args []string) { }, 46 + } 47 + 48 + // Execute adds all child commands to the root command and sets flags appropriately. 49 + // This is called by main.main(). It only needs to happen once to the rootCmd. 50 + func Execute() { 51 + err := rootCmd.Execute() 52 + if err != nil { 53 + os.Exit(1) 54 + } 55 + } 56 + 57 + func init() { 58 + cobra.OnInitialize(initConfig) 59 + 60 + // Here you will define your flags and configuration settings. 61 + // Cobra supports persistent flags, which, if defined here, 62 + // will be global for your application. 63 + rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.config/pdsadmin/config.yaml)") 64 + } 65 + 66 + // initConfig reads in config file and ENV variables if set. 67 + func initConfig() { 68 + if cfgFile != "" { 69 + // Use config file from the flag. 70 + viper.SetConfigFile(cfgFile) 71 + } else { 72 + // Find user's config directory 73 + configdir, err := os.UserConfigDir() 74 + cobra.CheckErr(err) 75 + 76 + // Search for config in config subdirectory "pdsadmin" and name "config" (without extension) 77 + // On Linux, possibly $HOME/.config/pdsadmin/config 78 + configPath := filepath.Join(configdir, "pdsadmin") 79 + viper.AddConfigPath(configPath) 80 + viper.SetConfigType("yaml") 81 + viper.SetConfigName("config") 82 + } 83 + 84 + viper.SetEnvPrefix("PDS") 85 + viper.AutomaticEnv() // read in environment variables that match 86 + 87 + // If a config file is found, read it in. 88 + if err := viper.ReadInConfig(); err == nil { 89 + fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed()) 90 + } 91 + }
+26
go.mod
··· 1 + module github.com/QuietEngineer/pdsadmin 2 + 3 + go 1.23.6 4 + 5 + require ( 6 + github.com/spf13/cobra v1.10.1 7 + github.com/spf13/viper v1.20.1 8 + ) 9 + 10 + require ( 11 + github.com/fsnotify/fsnotify v1.9.0 // indirect 12 + github.com/go-viper/mapstructure/v2 v2.4.0 // indirect 13 + github.com/inconshreveable/mousetrap v1.1.0 // indirect 14 + github.com/pelletier/go-toml/v2 v2.2.4 // indirect 15 + github.com/rogpeppe/go-internal v1.13.1 // indirect 16 + github.com/sagikazarmark/locafero v0.10.0 // indirect 17 + github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect 18 + github.com/spf13/afero v1.14.0 // indirect 19 + github.com/spf13/cast v1.9.2 // indirect 20 + github.com/spf13/pflag v1.0.10 // indirect 21 + github.com/subosito/gotenv v1.6.0 // indirect 22 + golang.org/x/sys v0.35.0 // indirect 23 + golang.org/x/text v0.28.0 // indirect 24 + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect 25 + gopkg.in/yaml.v3 v3.0.1 // indirect 26 + )
+55
go.sum
··· 1 + github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= 2 + github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 + github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 + github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= 5 + github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= 6 + github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= 7 + github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= 8 + github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= 9 + github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= 10 + github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 11 + github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 12 + github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 13 + github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 14 + github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 15 + github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 16 + github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 17 + github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 18 + github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 19 + github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 20 + github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 21 + github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= 22 + github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= 23 + github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 24 + github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 25 + github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= 26 + github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= 27 + github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 28 + github.com/sagikazarmark/locafero v0.10.0 h1:FM8Cv6j2KqIhM2ZK7HZjm4mpj9NBktLgowT1aN9q5Cc= 29 + github.com/sagikazarmark/locafero v0.10.0/go.mod h1:Ieo3EUsjifvQu4NZwV5sPd4dwvu0OCgEQV7vjc9yDjw= 30 + github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= 31 + github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= 32 + github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA= 33 + github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo= 34 + github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE= 35 + github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= 36 + github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= 37 + github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= 38 + github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 39 + github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= 40 + github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 41 + github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4= 42 + github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= 43 + github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 44 + github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 45 + github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= 46 + github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= 47 + golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= 48 + golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 49 + golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= 50 + golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= 51 + gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 52 + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 53 + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 54 + gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 55 + gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+28
main.go
··· 1 + /* 2 + Copyright © 2025 QuietEngineer <qtengineer@proton.me> 3 + 4 + Permission is hereby granted, free of charge, to any person obtaining a copy 5 + of this software and associated documentation files (the "Software"), to deal 6 + in the Software without restriction, including without limitation the rights 7 + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 + copies of the Software, and to permit persons to whom the Software is 9 + furnished to do so, subject to the following conditions: 10 + 11 + The above copyright notice and this permission notice shall be included in 12 + all copies or substantial portions of the Software. 13 + 14 + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 + THE SOFTWARE. 21 + */ 22 + package main 23 + 24 + import "github.com/QuietEngineer/pdsadmin/cmd" 25 + 26 + func main() { 27 + cmd.Execute() 28 + }
+4
sample.config.yaml
··· 1 + admin_password: a_secure_password 2 + hostname: pds.example.com # without protocol (https) or path 3 + crawlers: 4 + - bsky.network