tangled
alpha
login
or
join now
julien.rbrt.fr
/
vow
forked from
hailey.at/cocoon
0
fork
atom
Vow, uncensorable PDS written in Go
0
fork
atom
overview
issues
pulls
pipelines
refactor: use cobra
julien.rbrt.fr
2 weeks ago
5ce7fa86
73d67ee0
+340
-358
5 changed files
expand all
collapse all
unified
split
cmd
cocoon
flags.go
main.go
go.mod
go.sum
server
server.go
+32
cmd/cocoon/flags.go
···
1
1
+
package main
2
2
+
3
3
+
const (
4
4
+
flagAddr = "addr"
5
5
+
flagDbName = "db-name"
6
6
+
flagDid = "did"
7
7
+
flagHostname = "hostname"
8
8
+
flagRotationKeyPath = "rotation-key-path"
9
9
+
flagJwkPath = "jwk-path"
10
10
+
flagContactEmail = "contact-email"
11
11
+
flagRelays = "relays"
12
12
+
flagAdminPassword = "admin-password"
13
13
+
flagRequireInvite = "require-invite"
14
14
+
flagSmtpUser = "smtp-user"
15
15
+
flagSmtpPass = "smtp-pass"
16
16
+
flagSmtpHost = "smtp-host"
17
17
+
flagSmtpPort = "smtp-port"
18
18
+
flagSmtpEmail = "smtp-email"
19
19
+
flagSmtpName = "smtp-name"
20
20
+
flagIpfsBlobstoreEnabled = "ipfs-blobstore-enabled"
21
21
+
flagIpfsNodeUrl = "ipfs-node-url"
22
22
+
flagIpfsGatewayUrl = "ipfs-gateway-url"
23
23
+
flagIpfsPinningServiceUrl = "ipfs-pinning-service-url"
24
24
+
flagIpfsPinningServiceToken = "ipfs-pinning-service-token"
25
25
+
flagSessionSecret = "session-secret"
26
26
+
flagSessionCookieKey = "session-cookie-key"
27
27
+
flagBlockstoreVariant = "blockstore-variant"
28
28
+
flagFallbackProxy = "fallback-proxy"
29
29
+
flagLogLevel = "log-level"
30
30
+
flagDebug = "debug"
31
31
+
flagMetricsListenAddress = "metrics-listen-address"
32
32
+
)
+267
-313
cmd/cocoon/main.go
···
1
1
package main
2
2
3
3
import (
4
4
+
"context"
4
5
"crypto/ecdsa"
5
6
"crypto/elliptic"
6
7
"crypto/rand"
7
8
"encoding/json"
8
9
"fmt"
9
10
"log/slog"
11
11
+
"net/http"
12
12
+
_ "net/http/pprof"
10
13
"os"
11
14
"strings"
12
15
"time"
13
16
14
14
-
"github.com/bluesky-social/go-util/pkg/telemetry"
15
17
"github.com/bluesky-social/indigo/atproto/atcrypto"
16
18
"github.com/bluesky-social/indigo/atproto/syntax"
17
19
"github.com/glebarez/sqlite"
···
19
21
"github.com/haileyok/cocoon/server"
20
22
_ "github.com/joho/godotenv/autoload"
21
23
"github.com/lestrrat-go/jwx/v2/jwk"
22
22
-
"github.com/urfave/cli/v2"
24
24
+
"github.com/prometheus/client_golang/prometheus/promhttp"
25
25
+
"github.com/spf13/cobra"
26
26
+
"github.com/spf13/viper"
23
27
"golang.org/x/crypto/bcrypt"
24
28
"gorm.io/gorm"
25
29
)
···
27
31
var Version = "dev"
28
32
29
33
func main() {
30
30
-
app := &cli.App{
31
31
-
Name: "cocoon",
32
32
-
Usage: "An atproto PDS",
33
33
-
Flags: []cli.Flag{
34
34
-
&cli.StringFlag{
35
35
-
Name: "addr",
36
36
-
Value: ":8080",
37
37
-
EnvVars: []string{"COCOON_ADDR"},
38
38
-
},
39
39
-
&cli.StringFlag{
40
40
-
Name: "db-name",
41
41
-
Value: "cocoon.db",
42
42
-
EnvVars: []string{"COCOON_DB_NAME"},
43
43
-
},
44
44
-
45
45
-
&cli.StringFlag{
46
46
-
Name: "did",
47
47
-
EnvVars: []string{"COCOON_DID"},
48
48
-
},
49
49
-
&cli.StringFlag{
50
50
-
Name: "hostname",
51
51
-
EnvVars: []string{"COCOON_HOSTNAME"},
52
52
-
},
53
53
-
&cli.StringFlag{
54
54
-
Name: "rotation-key-path",
55
55
-
EnvVars: []string{"COCOON_ROTATION_KEY_PATH"},
56
56
-
},
57
57
-
&cli.StringFlag{
58
58
-
Name: "jwk-path",
59
59
-
EnvVars: []string{"COCOON_JWK_PATH"},
60
60
-
},
61
61
-
&cli.StringFlag{
62
62
-
Name: "contact-email",
63
63
-
EnvVars: []string{"COCOON_CONTACT_EMAIL"},
64
64
-
},
65
65
-
&cli.StringSliceFlag{
66
66
-
Name: "relays",
67
67
-
EnvVars: []string{"COCOON_RELAYS"},
68
68
-
},
69
69
-
&cli.StringFlag{
70
70
-
Name: "admin-password",
71
71
-
EnvVars: []string{"COCOON_ADMIN_PASSWORD"},
72
72
-
},
73
73
-
&cli.BoolFlag{
74
74
-
Name: "require-invite",
75
75
-
EnvVars: []string{"COCOON_REQUIRE_INVITE"},
76
76
-
Value: true,
77
77
-
},
78
78
-
&cli.StringFlag{
79
79
-
Name: "smtp-user",
80
80
-
EnvVars: []string{"COCOON_SMTP_USER"},
81
81
-
},
82
82
-
&cli.StringFlag{
83
83
-
Name: "smtp-pass",
84
84
-
EnvVars: []string{"COCOON_SMTP_PASS"},
85
85
-
},
86
86
-
&cli.StringFlag{
87
87
-
Name: "smtp-host",
88
88
-
EnvVars: []string{"COCOON_SMTP_HOST"},
89
89
-
},
90
90
-
&cli.StringFlag{
91
91
-
Name: "smtp-port",
92
92
-
EnvVars: []string{"COCOON_SMTP_PORT"},
93
93
-
},
94
94
-
&cli.StringFlag{
95
95
-
Name: "smtp-email",
96
96
-
EnvVars: []string{"COCOON_SMTP_EMAIL"},
97
97
-
},
98
98
-
&cli.StringFlag{
99
99
-
Name: "smtp-name",
100
100
-
EnvVars: []string{"COCOON_SMTP_NAME"},
101
101
-
},
102
102
-
&cli.BoolFlag{
103
103
-
Name: "ipfs-blobstore-enabled",
104
104
-
EnvVars: []string{"COCOON_IPFS_BLOBSTORE_ENABLED"},
105
105
-
Usage: "Store blobs on IPFS via the Kubo HTTP RPC API instead of SQLite.",
106
106
-
},
107
107
-
&cli.StringFlag{
108
108
-
Name: "ipfs-node-url",
109
109
-
EnvVars: []string{"COCOON_IPFS_NODE_URL"},
110
110
-
Value: "http://127.0.0.1:5001",
111
111
-
Usage: "Base URL of the Kubo (go-ipfs) RPC API used for adding and fetching blobs.",
112
112
-
},
113
113
-
&cli.StringFlag{
114
114
-
Name: "ipfs-gateway-url",
115
115
-
EnvVars: []string{"COCOON_IPFS_GATEWAY_URL"},
116
116
-
Usage: "Public IPFS gateway URL for blob redirects (e.g., https://ipfs.io). When set, getBlob redirects to this URL instead of proxying through the node.",
117
117
-
},
118
118
-
&cli.StringFlag{
119
119
-
Name: "ipfs-pinning-service-url",
120
120
-
EnvVars: []string{"COCOON_IPFS_PINNING_SERVICE_URL"},
121
121
-
Usage: "Remote IPFS Pinning Service API endpoint (e.g., https://api.pinata.cloud/psa). Leave empty to skip remote pinning.",
122
122
-
},
123
123
-
&cli.StringFlag{
124
124
-
Name: "ipfs-pinning-service-token",
125
125
-
EnvVars: []string{"COCOON_IPFS_PINNING_SERVICE_TOKEN"},
126
126
-
Usage: "Bearer token for authenticating with the remote IPFS pinning service.",
127
127
-
},
128
128
-
&cli.StringFlag{
129
129
-
Name: "session-secret",
130
130
-
EnvVars: []string{"COCOON_SESSION_SECRET"},
131
131
-
},
132
132
-
&cli.StringFlag{
133
133
-
Name: "session-cookie-key",
134
134
-
EnvVars: []string{"COCOON_SESSION_COOKIE_KEY"},
135
135
-
Value: "session",
136
136
-
},
137
137
-
&cli.StringFlag{
138
138
-
Name: "blockstore-variant",
139
139
-
EnvVars: []string{"COCOON_BLOCKSTORE_VARIANT"},
140
140
-
Value: "sqlite",
141
141
-
},
142
142
-
&cli.StringFlag{
143
143
-
Name: "fallback-proxy",
144
144
-
EnvVars: []string{"COCOON_FALLBACK_PROXY"},
145
145
-
},
146
146
-
telemetry.CLIFlagDebug,
147
147
-
telemetry.CLIFlagMetricsListenAddress,
148
148
-
},
149
149
-
Commands: []*cli.Command{
150
150
-
runServe,
151
151
-
runCreateRotationKey,
152
152
-
runCreatePrivateJwk,
153
153
-
runCreateInviteCode,
154
154
-
runResetPassword,
155
155
-
},
156
156
-
ErrWriter: os.Stdout,
157
157
-
Version: Version,
34
34
+
if err := rootCmd.Execute(); err != nil {
35
35
+
os.Exit(1)
158
36
}
37
37
+
}
159
38
160
160
-
if err := app.Run(os.Args); err != nil {
161
161
-
fmt.Printf("Error: %v\n", err)
162
162
-
}
39
39
+
var rootCmd = &cobra.Command{
40
40
+
Use: "cocoon",
41
41
+
Short: "An atproto PDS",
42
42
+
Version: Version,
43
43
+
SilenceErrors: true,
44
44
+
SilenceUsage: true,
163
45
}
164
46
165
165
-
var runServe = &cli.Command{
166
166
-
Name: "run",
167
167
-
Usage: "Start the cocoon PDS",
168
168
-
Flags: []cli.Flag{
169
169
-
&cli.StringFlag{
170
170
-
Name: "log-level",
171
171
-
Usage: "Log level: debug, info, warn, error",
172
172
-
EnvVars: []string{"COCOON_LOG_LEVEL", "LOG_LEVEL"},
173
173
-
Value: "info",
174
174
-
},
175
175
-
},
176
176
-
Action: func(cmd *cli.Context) error {
47
47
+
func init() {
48
48
+
pf := rootCmd.PersistentFlags()
49
49
+
50
50
+
pf.String(flagAddr, ":8080", "Listen address")
51
51
+
pf.String(flagDbName, "cocoon.db", "SQLite database file path")
52
52
+
pf.String(flagDid, "", "DID of this PDS")
53
53
+
pf.String(flagHostname, "", "Public hostname of this PDS")
54
54
+
pf.String(flagRotationKeyPath, "", "Path to the rotation key file")
55
55
+
pf.String(flagJwkPath, "", "Path to the private JWK file")
56
56
+
pf.String(flagContactEmail, "", "Contact e-mail address for this PDS")
57
57
+
pf.StringSlice(flagRelays, []string{}, "Relay URLs to crawl")
58
58
+
pf.String(flagAdminPassword, "", "Admin password")
59
59
+
pf.Bool(flagRequireInvite, true, "Require an invite code to create an account")
60
60
+
pf.String(flagSmtpUser, "", "SMTP username")
61
61
+
pf.String(flagSmtpPass, "", "SMTP password")
62
62
+
pf.String(flagSmtpHost, "", "SMTP host")
63
63
+
pf.String(flagSmtpPort, "", "SMTP port")
64
64
+
pf.String(flagSmtpEmail, "", "SMTP from address")
65
65
+
pf.String(flagSmtpName, "", "SMTP from name")
66
66
+
pf.Bool(flagIpfsBlobstoreEnabled, false, "Store blobs on IPFS via the Kubo HTTP RPC API instead of SQLite")
67
67
+
pf.String(flagIpfsNodeUrl, "http://127.0.0.1:5001", "Base URL of the Kubo (go-ipfs) RPC API used for adding and fetching blobs")
68
68
+
pf.String(flagIpfsGatewayUrl, "", "Public IPFS gateway URL for blob redirects (e.g. https://ipfs.io). When set, getBlob redirects to this URL instead of proxying through the node")
69
69
+
pf.String(flagIpfsPinningServiceUrl, "", "Remote IPFS Pinning Service API endpoint (e.g. https://api.pinata.cloud/psa). Leave empty to skip remote pinning")
70
70
+
pf.String(flagIpfsPinningServiceToken, "", "Bearer token for authenticating with the remote IPFS pinning service")
71
71
+
pf.String(flagSessionSecret, "", "Session secret")
72
72
+
pf.String(flagSessionCookieKey, "session", "Session cookie key name")
73
73
+
pf.String(flagBlockstoreVariant, "sqlite", "Blockstore variant (sqlite)")
74
74
+
pf.String(flagFallbackProxy, "", "Fallback proxy URL")
75
75
+
pf.String(flagLogLevel, "info", "Log level: debug, info, warn, error")
76
76
+
pf.Bool(flagDebug, false, "Enable debug logging (shorthand for --log-level=debug)")
77
77
+
pf.String(flagMetricsListenAddress, "0.0.0.0:6009", "Listen address for the Prometheus metrics / pprof endpoint")
78
78
+
79
79
+
v := viper.New()
80
80
+
v.SetEnvPrefix("COCOON")
81
81
+
v.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
82
82
+
v.AutomaticEnv()
83
83
+
_ = v.BindEnv(flagDebug, "DEBUG")
84
84
+
_ = v.BindEnv(flagLogLevel, "COCOON_LOG_LEVEL", "LOG_LEVEL")
85
85
+
_ = v.BindEnv(flagMetricsListenAddress, "METRICS_LISTEN_ADDRESS")
86
86
+
_ = v.BindPFlags(pf)
177
87
178
178
-
logger := telemetry.StartLogger(cmd)
179
179
-
telemetry.StartMetrics(cmd)
88
88
+
rootCmd.AddCommand(
89
89
+
newServeCmd(v),
90
90
+
newCreateRotationKeyCmd(),
91
91
+
newCreatePrivateJwkCmd(),
92
92
+
newCreateInviteCodeCmd(v),
93
93
+
newResetPasswordCmd(v),
94
94
+
)
95
95
+
}
180
96
181
181
-
var level slog.Level
182
182
-
switch strings.ToLower(cmd.String("log-level")) {
97
97
+
func buildLogger(v *viper.Viper) *slog.Logger {
98
98
+
var level slog.Level
99
99
+
if v.GetBool(flagDebug) {
100
100
+
level = slog.LevelDebug
101
101
+
} else {
102
102
+
switch strings.ToLower(v.GetString(flagLogLevel)) {
183
103
case "debug":
184
104
level = slog.LevelDebug
185
185
-
case "info":
186
186
-
level = slog.LevelInfo
187
105
case "warn":
188
106
level = slog.LevelWarn
189
107
case "error":
···
191
109
default:
192
110
level = slog.LevelInfo
193
111
}
112
112
+
}
194
113
195
195
-
s, err := server.New(&server.Args{
196
196
-
Logger: logger,
197
197
-
LogLevel: level,
198
198
-
Addr: cmd.String("addr"),
199
199
-
DbName: cmd.String("db-name"),
200
200
-
Did: cmd.String("did"),
201
201
-
Hostname: cmd.String("hostname"),
202
202
-
RotationKeyPath: cmd.String("rotation-key-path"),
203
203
-
JwkPath: cmd.String("jwk-path"),
204
204
-
ContactEmail: cmd.String("contact-email"),
205
205
-
Version: Version,
206
206
-
Relays: cmd.StringSlice("relays"),
207
207
-
AdminPassword: cmd.String("admin-password"),
208
208
-
RequireInvite: cmd.Bool("require-invite"),
209
209
-
SmtpUser: cmd.String("smtp-user"),
210
210
-
SmtpPass: cmd.String("smtp-pass"),
211
211
-
SmtpHost: cmd.String("smtp-host"),
212
212
-
SmtpPort: cmd.String("smtp-port"),
213
213
-
SmtpEmail: cmd.String("smtp-email"),
214
214
-
SmtpName: cmd.String("smtp-name"),
215
215
-
IPFSConfig: &server.IPFSConfig{
216
216
-
BlobstoreEnabled: cmd.Bool("ipfs-blobstore-enabled"),
217
217
-
NodeURL: cmd.String("ipfs-node-url"),
218
218
-
GatewayURL: cmd.String("ipfs-gateway-url"),
219
219
-
PinningServiceURL: cmd.String("ipfs-pinning-service-url"),
220
220
-
PinningServiceToken: cmd.String("ipfs-pinning-service-token"),
221
221
-
},
222
222
-
SessionSecret: cmd.String("session-secret"),
223
223
-
SessionCookieKey: cmd.String("session-cookie-key"),
224
224
-
BlockstoreVariant: server.MustReturnBlockstoreVariant(cmd.String("blockstore-variant")),
225
225
-
FallbackProxy: cmd.String("fallback-proxy"),
226
226
-
})
227
227
-
if err != nil {
228
228
-
fmt.Printf("error creating cocoon: %v", err)
229
229
-
return err
230
230
-
}
114
114
+
handler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
115
115
+
Level: level,
116
116
+
AddSource: true,
117
117
+
})
118
118
+
logger := slog.New(handler)
119
119
+
slog.SetDefault(logger)
120
120
+
return logger
121
121
+
}
231
122
232
232
-
if err := s.Serve(cmd.Context); err != nil {
233
233
-
fmt.Printf("error starting cocoon: %v", err)
234
234
-
return err
123
123
+
func startMetrics(v *viper.Viper) {
124
124
+
addr := v.GetString(flagMetricsListenAddress)
125
125
+
if addr == "" {
126
126
+
return
127
127
+
}
128
128
+
logger := slog.Default().With("component", "telemetry")
129
129
+
logger.Info("starting metrics server", "address", addr)
130
130
+
mux := http.NewServeMux()
131
131
+
mux.Handle("/metrics", promhttp.Handler())
132
132
+
mux.Handle("/debug/pprof/", http.DefaultServeMux)
133
133
+
go func() {
134
134
+
if err := http.ListenAndServe(addr, mux); err != nil {
135
135
+
logger.Error("metrics server failed", "err", err)
235
136
}
236
236
-
237
237
-
return nil
238
238
-
},
137
137
+
}()
239
138
}
240
139
241
241
-
var runCreateRotationKey = &cli.Command{
242
242
-
Name: "create-rotation-key",
243
243
-
Usage: "creates a rotation key for your pds",
244
244
-
Flags: []cli.Flag{
245
245
-
&cli.StringFlag{
246
246
-
Name: "out",
247
247
-
Required: true,
248
248
-
Usage: "output file for your rotation key",
249
249
-
},
250
250
-
},
251
251
-
Action: func(cmd *cli.Context) error {
252
252
-
key, err := atcrypto.GeneratePrivateKeyK256()
253
253
-
if err != nil {
254
254
-
return err
255
255
-
}
140
140
+
func newDb(v *viper.Viper) (*gorm.DB, error) {
141
141
+
dbName := v.GetString(flagDbName)
142
142
+
if dbName == "" {
143
143
+
dbName = "cocoon.db"
144
144
+
}
145
145
+
return gorm.Open(sqlite.Open(dbName), &gorm.Config{})
146
146
+
}
256
147
257
257
-
bytes := key.Bytes()
148
148
+
func newServeCmd(v *viper.Viper) *cobra.Command {
149
149
+
return &cobra.Command{
150
150
+
Use: "run",
151
151
+
Short: "Start the cocoon PDS",
152
152
+
RunE: func(cmd *cobra.Command, args []string) error {
153
153
+
logger := buildLogger(v)
154
154
+
startMetrics(v)
258
155
259
259
-
if err := os.WriteFile(cmd.String("out"), bytes, 0644); err != nil {
260
260
-
return err
261
261
-
}
156
156
+
s, err := server.New(&server.Args{
157
157
+
Logger: logger,
158
158
+
Addr: v.GetString(flagAddr),
159
159
+
DbName: v.GetString(flagDbName),
160
160
+
Did: v.GetString(flagDid),
161
161
+
Hostname: v.GetString(flagHostname),
162
162
+
RotationKeyPath: v.GetString(flagRotationKeyPath),
163
163
+
JwkPath: v.GetString(flagJwkPath),
164
164
+
ContactEmail: v.GetString(flagContactEmail),
165
165
+
Version: Version,
166
166
+
Relays: v.GetStringSlice(flagRelays),
167
167
+
AdminPassword: v.GetString(flagAdminPassword),
168
168
+
RequireInvite: v.GetBool(flagRequireInvite),
169
169
+
SmtpUser: v.GetString(flagSmtpUser),
170
170
+
SmtpPass: v.GetString(flagSmtpPass),
171
171
+
SmtpHost: v.GetString(flagSmtpHost),
172
172
+
SmtpPort: v.GetString(flagSmtpPort),
173
173
+
SmtpEmail: v.GetString(flagSmtpEmail),
174
174
+
SmtpName: v.GetString(flagSmtpName),
175
175
+
IPFSConfig: &server.IPFSConfig{
176
176
+
BlobstoreEnabled: v.GetBool(flagIpfsBlobstoreEnabled),
177
177
+
NodeURL: v.GetString(flagIpfsNodeUrl),
178
178
+
GatewayURL: v.GetString(flagIpfsGatewayUrl),
179
179
+
PinningServiceURL: v.GetString(flagIpfsPinningServiceUrl),
180
180
+
PinningServiceToken: v.GetString(flagIpfsPinningServiceToken),
181
181
+
},
182
182
+
SessionSecret: v.GetString(flagSessionSecret),
183
183
+
SessionCookieKey: v.GetString(flagSessionCookieKey),
184
184
+
BlockstoreVariant: server.MustReturnBlockstoreVariant(v.GetString(flagBlockstoreVariant)),
185
185
+
FallbackProxy: v.GetString(flagFallbackProxy),
186
186
+
})
187
187
+
if err != nil {
188
188
+
return fmt.Errorf("error creating cocoon: %w", err)
189
189
+
}
262
190
263
263
-
return nil
264
264
-
},
265
265
-
}
191
191
+
if err := s.Serve(context.Background()); err != nil {
192
192
+
return fmt.Errorf("error starting cocoon: %w", err)
193
193
+
}
266
194
267
267
-
var runCreatePrivateJwk = &cli.Command{
268
268
-
Name: "create-private-jwk",
269
269
-
Usage: "creates a private jwk for your pds",
270
270
-
Flags: []cli.Flag{
271
271
-
&cli.StringFlag{
272
272
-
Name: "out",
273
273
-
Required: true,
274
274
-
Usage: "output file for your jwk",
195
195
+
return nil
275
196
},
276
276
-
},
277
277
-
Action: func(cmd *cli.Context) error {
278
278
-
privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
279
279
-
if err != nil {
280
280
-
return err
281
281
-
}
197
197
+
}
198
198
+
}
282
199
283
283
-
key, err := jwk.FromRaw(privKey)
284
284
-
if err != nil {
285
285
-
return err
286
286
-
}
200
200
+
func newCreateRotationKeyCmd() *cobra.Command {
201
201
+
cmd := &cobra.Command{
202
202
+
Use: "create-rotation-key",
203
203
+
Short: "Create a rotation key for your PDS",
204
204
+
RunE: func(cmd *cobra.Command, args []string) error {
205
205
+
out, _ := cmd.Flags().GetString("out")
287
206
288
288
-
kid := fmt.Sprintf("%d", time.Now().Unix())
207
207
+
key, err := atcrypto.GeneratePrivateKeyK256()
208
208
+
if err != nil {
209
209
+
return err
210
210
+
}
289
211
290
290
-
if err := key.Set(jwk.KeyIDKey, kid); err != nil {
291
291
-
return err
292
292
-
}
212
212
+
if err := os.WriteFile(out, key.Bytes(), 0644); err != nil {
213
213
+
return err
214
214
+
}
293
215
294
294
-
b, err := json.Marshal(key)
295
295
-
if err != nil {
296
296
-
return err
297
297
-
}
216
216
+
fmt.Printf("Rotation key written to %s\n", out)
217
217
+
return nil
218
218
+
},
219
219
+
}
298
220
299
299
-
if err := os.WriteFile(cmd.String("out"), b, 0644); err != nil {
300
300
-
return err
301
301
-
}
221
221
+
cmd.Flags().StringP("out", "o", "", "Output file for the rotation key (required)")
222
222
+
_ = cmd.MarkFlagRequired("out")
302
223
303
303
-
return nil
304
304
-
},
224
224
+
return cmd
305
225
}
306
226
307
307
-
var runCreateInviteCode = &cli.Command{
308
308
-
Name: "create-invite-code",
309
309
-
Usage: "creates an invite code",
310
310
-
Flags: []cli.Flag{
311
311
-
&cli.StringFlag{
312
312
-
Name: "for",
313
313
-
Usage: "optional did to assign the invite code to",
314
314
-
},
315
315
-
&cli.IntFlag{
316
316
-
Name: "uses",
317
317
-
Usage: "number of times the invite code can be used",
318
318
-
Value: 1,
319
319
-
},
320
320
-
},
321
321
-
Action: func(cmd *cli.Context) error {
322
322
-
db, err := newDb(cmd)
323
323
-
if err != nil {
324
324
-
return err
325
325
-
}
227
227
+
func newCreatePrivateJwkCmd() *cobra.Command {
228
228
+
cmd := &cobra.Command{
229
229
+
Use: "create-private-jwk",
230
230
+
Short: "Create a private JWK for your PDS",
231
231
+
RunE: func(cmd *cobra.Command, args []string) error {
232
232
+
out, _ := cmd.Flags().GetString("out")
326
233
327
327
-
forDid := "did:plc:123"
328
328
-
if cmd.String("for") != "" {
329
329
-
did, err := syntax.ParseDID(cmd.String("for"))
234
234
+
privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
235
235
+
if err != nil {
236
236
+
return err
237
237
+
}
238
238
+
239
239
+
key, err := jwk.FromRaw(privKey)
330
240
if err != nil {
331
241
return err
332
242
}
333
243
334
334
-
forDid = did.String()
335
335
-
}
244
244
+
if err := key.Set(jwk.KeyIDKey, fmt.Sprintf("%d", time.Now().Unix())); err != nil {
245
245
+
return err
246
246
+
}
336
247
337
337
-
uses := cmd.Int("uses")
248
248
+
b, err := json.Marshal(key)
249
249
+
if err != nil {
250
250
+
return err
251
251
+
}
338
252
339
339
-
code := fmt.Sprintf("%s-%s", helpers.RandomVarchar(8), helpers.RandomVarchar(8))
253
253
+
if err := os.WriteFile(out, b, 0644); err != nil {
254
254
+
return err
255
255
+
}
340
256
341
341
-
if err := db.Exec("INSERT INTO invite_codes (did, code, remaining_use_count) VALUES (?, ?, ?)", forDid, code, uses).Error; err != nil {
342
342
-
return err
343
343
-
}
257
257
+
fmt.Printf("Private JWK written to %s\n", out)
258
258
+
return nil
259
259
+
},
260
260
+
}
344
261
345
345
-
fmt.Printf("New invite code created with %d uses: %s\n", uses, code)
262
262
+
cmd.Flags().StringP("out", "o", "", "Output file for the private JWK (required)")
263
263
+
_ = cmd.MarkFlagRequired("out")
346
264
347
347
-
return nil
348
348
-
},
265
265
+
return cmd
349
266
}
350
267
351
351
-
var runResetPassword = &cli.Command{
352
352
-
Name: "reset-password",
353
353
-
Usage: "resets a password",
354
354
-
Flags: []cli.Flag{
355
355
-
&cli.StringFlag{
356
356
-
Name: "did",
357
357
-
Usage: "did of the user who's password you want to reset",
268
268
+
func newCreateInviteCodeCmd(v *viper.Viper) *cobra.Command {
269
269
+
cmd := &cobra.Command{
270
270
+
Use: "create-invite-code",
271
271
+
Short: "Create an invite code",
272
272
+
RunE: func(cmd *cobra.Command, args []string) error {
273
273
+
db, err := newDb(v)
274
274
+
if err != nil {
275
275
+
return err
276
276
+
}
277
277
+
278
278
+
forFlag, _ := cmd.Flags().GetString("for")
279
279
+
uses, _ := cmd.Flags().GetInt("uses")
280
280
+
281
281
+
forDid := "did:plc:123"
282
282
+
if forFlag != "" {
283
283
+
did, err := syntax.ParseDID(forFlag)
284
284
+
if err != nil {
285
285
+
return fmt.Errorf("invalid DID %q: %w", forFlag, err)
286
286
+
}
287
287
+
forDid = did.String()
288
288
+
}
289
289
+
290
290
+
code := fmt.Sprintf("%s-%s", helpers.RandomVarchar(8), helpers.RandomVarchar(8))
291
291
+
292
292
+
if err := db.Exec(
293
293
+
"INSERT INTO invite_codes (did, code, remaining_use_count) VALUES (?, ?, ?)",
294
294
+
forDid, code, uses,
295
295
+
).Error; err != nil {
296
296
+
return err
297
297
+
}
298
298
+
299
299
+
fmt.Printf("New invite code created with %d uses: %s\n", uses, code)
300
300
+
return nil
358
301
},
359
359
-
},
360
360
-
Action: func(cmd *cli.Context) error {
361
361
-
db, err := newDb(cmd)
362
362
-
if err != nil {
363
363
-
return err
364
364
-
}
302
302
+
}
365
303
366
366
-
didStr := cmd.String("did")
367
367
-
did, err := syntax.ParseDID(didStr)
368
368
-
if err != nil {
369
369
-
return err
370
370
-
}
304
304
+
cmd.Flags().String("for", "", "Optional DID to assign the invite code to")
305
305
+
cmd.Flags().Int("uses", 1, "Number of times the invite code can be used")
371
306
372
372
-
newPass := fmt.Sprintf("%s-%s", helpers.RandomVarchar(12), helpers.RandomVarchar(12))
373
373
-
hashed, err := bcrypt.GenerateFromPassword([]byte(newPass), 10)
374
374
-
if err != nil {
375
375
-
return err
376
376
-
}
307
307
+
return cmd
308
308
+
}
377
309
378
378
-
if err := db.Exec("UPDATE repos SET password = ? WHERE did = ?", hashed, did.String()).Error; err != nil {
379
379
-
return err
380
380
-
}
310
310
+
func newResetPasswordCmd(v *viper.Viper) *cobra.Command {
311
311
+
cmd := &cobra.Command{
312
312
+
Use: "reset-password",
313
313
+
Short: "Reset a user's password",
314
314
+
RunE: func(cmd *cobra.Command, args []string) error {
315
315
+
db, err := newDb(v)
316
316
+
if err != nil {
317
317
+
return err
318
318
+
}
381
319
382
382
-
fmt.Printf("Password for %s has been reset to: %s", did.String(), newPass)
320
320
+
didStr, _ := cmd.Flags().GetString("did")
321
321
+
did, err := syntax.ParseDID(didStr)
322
322
+
if err != nil {
323
323
+
return fmt.Errorf("invalid DID %q: %w", didStr, err)
324
324
+
}
383
325
384
384
-
return nil
385
385
-
},
386
386
-
}
326
326
+
newPass := fmt.Sprintf("%s-%s", helpers.RandomVarchar(12), helpers.RandomVarchar(12))
327
327
+
hashed, err := bcrypt.GenerateFromPassword([]byte(newPass), 10)
328
328
+
if err != nil {
329
329
+
return err
330
330
+
}
387
331
388
388
-
func newDb(cmd *cli.Context) (*gorm.DB, error) {
389
389
-
dbName := cmd.String("db-name")
390
390
-
if dbName == "" {
391
391
-
dbName = "cocoon.db"
332
332
+
if err := db.Exec(
333
333
+
"UPDATE repos SET password = ? WHERE did = ?",
334
334
+
hashed, did.String(),
335
335
+
).Error; err != nil {
336
336
+
return err
337
337
+
}
338
338
+
339
339
+
fmt.Printf("Password for %s has been reset to: %s\n", did.String(), newPass)
340
340
+
return nil
341
341
+
},
392
342
}
393
393
-
return gorm.Open(sqlite.Open(dbName), &gorm.Config{})
343
343
+
344
344
+
cmd.Flags().String("did", "", "DID of the user whose password to reset (required)")
345
345
+
_ = cmd.MarkFlagRequired("did")
346
346
+
347
347
+
return cmd
394
348
}
+13
-5
go.mod
···
4
4
5
5
require (
6
6
github.com/Azure/go-autorest/autorest/to v0.4.1
7
7
-
github.com/bluesky-social/go-util v0.0.0-20251012040650-2ebbf57f5934
8
7
github.com/bluesky-social/indigo v0.0.0-20260203235305-a86f3ae1f8ec
9
8
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792
10
9
github.com/domodwyer/mailyak/v3 v3.6.2
···
29
28
github.com/multiformats/go-multihash v0.2.3
30
29
github.com/prometheus/client_golang v1.23.2
31
30
github.com/samber/slog-echo v1.16.1
32
32
-
github.com/urfave/cli/v2 v2.27.6
31
31
+
github.com/spf13/cobra v1.10.2
32
32
+
github.com/spf13/viper v1.21.0
33
33
github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e
34
34
gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b
35
35
golang.org/x/crypto v0.41.0
···
41
41
github.com/RussellLuo/slidingwindow v0.0.0-20200528002341-535bb99d338b // indirect
42
42
github.com/beorn7/perks v1.0.1 // indirect
43
43
github.com/cespare/xxhash/v2 v2.3.0 // indirect
44
44
-
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
45
44
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
46
45
github.com/dustin/go-humanize v1.0.1 // indirect
47
46
github.com/earthboundkid/versioninfo/v2 v2.24.1 // indirect
48
47
github.com/felixge/httpsnoop v1.0.4 // indirect
48
48
+
github.com/fsnotify/fsnotify v1.9.0 // indirect
49
49
github.com/glebarez/go-sqlite v1.21.2 // indirect
50
50
github.com/go-logr/logr v1.4.2 // indirect
51
51
github.com/go-logr/stdr v1.2.2 // indirect
52
52
github.com/go-playground/locales v0.14.1 // indirect
53
53
github.com/go-playground/universal-translator v0.18.1 // indirect
54
54
+
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
54
55
github.com/goccy/go-json v0.10.2 // indirect
55
56
github.com/gocql/gocql v1.7.0 // indirect
56
57
github.com/gogo/protobuf v1.3.2 // indirect
···
61
62
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
62
63
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
63
64
github.com/hashicorp/golang-lru v1.0.2 // indirect
65
65
+
github.com/inconshreveable/mousetrap v1.1.0 // indirect
64
66
github.com/ipfs/bbloom v0.0.4 // indirect
65
67
github.com/ipfs/go-blockservice v0.5.2 // indirect
66
68
github.com/ipfs/go-datastore v0.6.0 // indirect
···
103
105
github.com/multiformats/go-varint v0.0.7 // indirect
104
106
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
105
107
github.com/opentracing/opentracing-go v1.2.0 // indirect
108
108
+
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
106
109
github.com/polydawn/refmt v0.89.1-0.20221221234430-40501e09de1f // indirect
107
110
github.com/prometheus/client_model v0.6.2 // indirect
108
111
github.com/prometheus/common v0.66.1 // indirect
109
112
github.com/prometheus/procfs v0.16.1 // indirect
110
113
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
111
111
-
github.com/russross/blackfriday/v2 v2.1.0 // indirect
114
114
+
github.com/sagikazarmark/locafero v0.11.0 // indirect
112
115
github.com/samber/lo v1.49.1 // indirect
113
116
github.com/segmentio/asm v1.2.0 // indirect
117
117
+
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
114
118
github.com/spaolacci/murmur3 v1.1.0 // indirect
119
119
+
github.com/spf13/afero v1.15.0 // indirect
120
120
+
github.com/spf13/cast v1.10.0 // indirect
121
121
+
github.com/spf13/pflag v1.0.10 // indirect
122
122
+
github.com/subosito/gotenv v1.6.0 // indirect
115
123
github.com/valyala/bytebufferpool v1.0.0 // indirect
116
124
github.com/valyala/fasttemplate v1.2.2 // indirect
117
117
-
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
118
125
gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 // indirect
119
126
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
120
127
go.opentelemetry.io/otel v1.29.0 // indirect
···
124
131
go.uber.org/multierr v1.11.0 // indirect
125
132
go.uber.org/zap v1.26.0 // indirect
126
133
go.yaml.in/yaml/v2 v2.4.2 // indirect
134
134
+
go.yaml.in/yaml/v3 v3.0.4 // indirect
127
135
golang.org/x/net v0.43.0 // indirect
128
136
golang.org/x/sync v0.16.0 // indirect
129
137
golang.org/x/sys v0.35.0 // indirect
+28
-9
go.sum
···
14
14
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
15
15
github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYEDvkta6I8/rnYM5gSdSV2tJ6XbZuEtY=
16
16
github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k=
17
17
-
github.com/bluesky-social/go-util v0.0.0-20251012040650-2ebbf57f5934 h1:btHMur2kTRgWEnCHn6LaI3BE9YRgsqTpwpJ1UdB7VEk=
18
18
-
github.com/bluesky-social/go-util v0.0.0-20251012040650-2ebbf57f5934/go.mod h1:LWamyZfbQGW7PaVc5jumFfjgrshJ5mXgDUnR6fK7+BI=
19
17
github.com/bluesky-social/indigo v0.0.0-20260203235305-a86f3ae1f8ec h1:fubriMftMNEmb35sF07gDCsdUSEd0+EIDebt/+5oQRU=
20
18
github.com/bluesky-social/indigo v0.0.0-20260203235305-a86f3ae1f8ec/go.mod h1:VG/LeqLGNI3Ew7lsYixajnZGFfWPv144qbUddh+Oyag=
21
19
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
···
25
23
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
26
24
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
27
25
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
28
28
-
github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc=
29
29
-
github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
26
26
+
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
30
27
github.com/cskr/pubsub v1.0.2 h1:vlOzMhl6PFn60gRlTQQsIfVwaPB/B/8MziK8FhEPt/0=
31
28
github.com/cskr/pubsub v1.0.2/go.mod h1:/8MzYXk/NJAz782G8RPkFzXTZVu63VotefPnR9TIRis=
32
29
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
···
46
43
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
47
44
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
48
45
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
46
46
+
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
47
47
+
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
49
48
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
50
49
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
51
50
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
···
65
64
github.com/go-playground/validator v9.31.0+incompatible/go.mod h1:yrEkQXlcI+PugkyDjY2bRrL/UBU4f3rvrgkN3V8JEig=
66
65
github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
67
66
github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
67
67
+
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
68
68
+
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
68
69
github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0=
69
70
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
70
71
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
···
114
115
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
115
116
github.com/huin/goupnp v1.0.3 h1:N8No57ls+MnjlB+JPiCVSOyy/ot7MJTqlo7rn+NYSqQ=
116
117
github.com/huin/goupnp v1.0.3/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y=
118
118
+
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
119
119
+
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
117
120
github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs=
118
121
github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0=
119
122
github.com/ipfs/go-bitswap v0.11.0 h1:j1WVvhDX1yhG32NTC9xfxnqycqYIlhzEzLXG/cU1HyQ=
···
290
293
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
291
294
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
292
295
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
296
296
+
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
297
297
+
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
293
298
github.com/petar/GoLLRB v0.0.0-20210522233825-ae3b015fd3e9 h1:1/WtZae0yGtPq+TI6+Tv1WTxkukpXeMlviSxvL7SRgk=
294
299
github.com/petar/GoLLRB v0.0.0-20210522233825-ae3b015fd3e9/go.mod h1:x3N5drFsm2uilKKuuYo6LdyD8vZAW55sH/9w+pbo1sw=
295
300
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
···
312
317
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
313
318
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
314
319
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
315
315
-
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
316
320
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
321
321
+
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
322
322
+
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
317
323
github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew=
318
324
github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
319
325
github.com/samber/slog-echo v1.16.1 h1:5Q5IUROkFqKcu/qJM/13AP1d3gd1RS+Q/4EvKQU1fuo=
···
325
331
github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
326
332
github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg=
327
333
github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM=
334
334
+
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
335
335
+
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
328
336
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
329
337
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
338
338
+
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
339
339
+
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
340
340
+
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
341
341
+
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
342
342
+
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
343
343
+
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
344
344
+
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
345
345
+
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
346
346
+
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
347
347
+
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
348
348
+
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
330
349
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
331
350
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
332
351
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
···
335
354
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
336
355
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
337
356
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
357
357
+
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
358
358
+
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
338
359
github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
339
339
-
github.com/urfave/cli/v2 v2.27.6 h1:VdRdS98FNhKZ8/Az8B7MTyGQmpIr36O1EHybx/LaZ4g=
340
340
-
github.com/urfave/cli/v2 v2.27.6/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
341
360
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
342
361
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
343
362
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
···
350
369
github.com/whyrusleeping/cbor v0.0.0-20171005072247-63513f603b11/go.mod h1:Wlo/SzPmxVp6vXpGt/zaXhHH0fn4IxgqZc82aKg6bpQ=
351
370
github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e h1:28X54ciEwwUxyHn9yrZfl5ojgF4CBNLWX7LR0rvBkf4=
352
371
github.com/whyrusleeping/cbor-gen v0.2.1-0.20241030202151-b7a6831be65e/go.mod h1:pM99HXyEbSQHcosHc0iW7YFmwnscr+t9Te4ibko05so=
353
353
-
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
354
354
-
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
355
372
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
356
373
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
357
374
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
···
385
402
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
386
403
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
387
404
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
405
405
+
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
406
406
+
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
388
407
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
389
408
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
390
409
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-31
server/server.go
···
102
102
type Args struct {
103
103
Logger *slog.Logger
104
104
105
105
-
LogLevel slog.Level
106
105
Addr string
107
106
DbName string
108
107
Version string
···
132
131
}
133
132
134
133
type config struct {
135
135
-
LogLevel slog.Level
136
134
Version string
137
135
Did string
138
136
Hostname string
···
222
220
return t.templates.ExecuteTemplate(w, name, data)
223
221
}
224
222
225
225
-
type filteredHandler struct {
226
226
-
level slog.Level
227
227
-
handler slog.Handler
228
228
-
}
229
229
-
230
230
-
func (h *filteredHandler) Enabled(ctx context.Context, level slog.Level) bool {
231
231
-
return level >= h.level && h.handler.Enabled(ctx, level)
232
232
-
}
233
233
-
234
234
-
func (h *filteredHandler) Handle(ctx context.Context, r slog.Record) error {
235
235
-
return h.handler.Handle(ctx, r)
236
236
-
}
237
237
-
238
238
-
func (h *filteredHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
239
239
-
return &filteredHandler{level: h.level, handler: h.handler.WithAttrs(attrs)}
240
240
-
}
241
241
-
242
242
-
func (h *filteredHandler) WithGroup(name string) slog.Handler {
243
243
-
return &filteredHandler{level: h.level, handler: h.handler.WithGroup(name)}
244
244
-
}
245
245
-
246
223
func New(args *Args) (*Server, error) {
247
224
if args.Logger == nil {
248
225
args.Logger = slog.Default()
249
249
-
}
250
250
-
251
251
-
if args.LogLevel != 0 {
252
252
-
args.Logger = slog.New(&filteredHandler{
253
253
-
level: args.LogLevel,
254
254
-
handler: args.Logger.Handler(),
255
255
-
})
256
226
}
257
227
258
228
logger := args.Logger.With("name", "New")
···
409
379
plcClient: plcClient,
410
380
privateKey: &pkey,
411
381
config: &config{
412
412
-
LogLevel: args.LogLevel,
413
382
Version: args.Version,
414
383
Did: args.Did,
415
384
Hostname: args.Hostname,