+15
.github/dependabot.yaml
+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
+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
+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
+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
.gitignore
···
1
+
node_modules
+57
.goreleaser.yaml
+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
+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
+13
.tangled/workflows/release.yaml
+202
LICENSE-APACHE.txt
+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
+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
+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
+4
README.md
+78
cmd/createInviteCode.go
+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
+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
+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
+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
+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
+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
+
}