Vow, uncensorable PDS written in Go

refactor: use cobra

+340 -358
+32
cmd/cocoon/flags.go
··· 1 + package main 2 + 3 + const ( 4 + flagAddr = "addr" 5 + flagDbName = "db-name" 6 + flagDid = "did" 7 + flagHostname = "hostname" 8 + flagRotationKeyPath = "rotation-key-path" 9 + flagJwkPath = "jwk-path" 10 + flagContactEmail = "contact-email" 11 + flagRelays = "relays" 12 + flagAdminPassword = "admin-password" 13 + flagRequireInvite = "require-invite" 14 + flagSmtpUser = "smtp-user" 15 + flagSmtpPass = "smtp-pass" 16 + flagSmtpHost = "smtp-host" 17 + flagSmtpPort = "smtp-port" 18 + flagSmtpEmail = "smtp-email" 19 + flagSmtpName = "smtp-name" 20 + flagIpfsBlobstoreEnabled = "ipfs-blobstore-enabled" 21 + flagIpfsNodeUrl = "ipfs-node-url" 22 + flagIpfsGatewayUrl = "ipfs-gateway-url" 23 + flagIpfsPinningServiceUrl = "ipfs-pinning-service-url" 24 + flagIpfsPinningServiceToken = "ipfs-pinning-service-token" 25 + flagSessionSecret = "session-secret" 26 + flagSessionCookieKey = "session-cookie-key" 27 + flagBlockstoreVariant = "blockstore-variant" 28 + flagFallbackProxy = "fallback-proxy" 29 + flagLogLevel = "log-level" 30 + flagDebug = "debug" 31 + flagMetricsListenAddress = "metrics-listen-address" 32 + )
+267 -313
cmd/cocoon/main.go
··· 1 1 package main 2 2 3 3 import ( 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 + "net/http" 12 + _ "net/http/pprof" 10 13 "os" 11 14 "strings" 12 15 "time" 13 16 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 - "github.com/urfave/cli/v2" 24 + "github.com/prometheus/client_golang/prometheus/promhttp" 25 + "github.com/spf13/cobra" 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 - app := &cli.App{ 31 - Name: "cocoon", 32 - Usage: "An atproto PDS", 33 - Flags: []cli.Flag{ 34 - &cli.StringFlag{ 35 - Name: "addr", 36 - Value: ":8080", 37 - EnvVars: []string{"COCOON_ADDR"}, 38 - }, 39 - &cli.StringFlag{ 40 - Name: "db-name", 41 - Value: "cocoon.db", 42 - EnvVars: []string{"COCOON_DB_NAME"}, 43 - }, 44 - 45 - &cli.StringFlag{ 46 - Name: "did", 47 - EnvVars: []string{"COCOON_DID"}, 48 - }, 49 - &cli.StringFlag{ 50 - Name: "hostname", 51 - EnvVars: []string{"COCOON_HOSTNAME"}, 52 - }, 53 - &cli.StringFlag{ 54 - Name: "rotation-key-path", 55 - EnvVars: []string{"COCOON_ROTATION_KEY_PATH"}, 56 - }, 57 - &cli.StringFlag{ 58 - Name: "jwk-path", 59 - EnvVars: []string{"COCOON_JWK_PATH"}, 60 - }, 61 - &cli.StringFlag{ 62 - Name: "contact-email", 63 - EnvVars: []string{"COCOON_CONTACT_EMAIL"}, 64 - }, 65 - &cli.StringSliceFlag{ 66 - Name: "relays", 67 - EnvVars: []string{"COCOON_RELAYS"}, 68 - }, 69 - &cli.StringFlag{ 70 - Name: "admin-password", 71 - EnvVars: []string{"COCOON_ADMIN_PASSWORD"}, 72 - }, 73 - &cli.BoolFlag{ 74 - Name: "require-invite", 75 - EnvVars: []string{"COCOON_REQUIRE_INVITE"}, 76 - Value: true, 77 - }, 78 - &cli.StringFlag{ 79 - Name: "smtp-user", 80 - EnvVars: []string{"COCOON_SMTP_USER"}, 81 - }, 82 - &cli.StringFlag{ 83 - Name: "smtp-pass", 84 - EnvVars: []string{"COCOON_SMTP_PASS"}, 85 - }, 86 - &cli.StringFlag{ 87 - Name: "smtp-host", 88 - EnvVars: []string{"COCOON_SMTP_HOST"}, 89 - }, 90 - &cli.StringFlag{ 91 - Name: "smtp-port", 92 - EnvVars: []string{"COCOON_SMTP_PORT"}, 93 - }, 94 - &cli.StringFlag{ 95 - Name: "smtp-email", 96 - EnvVars: []string{"COCOON_SMTP_EMAIL"}, 97 - }, 98 - &cli.StringFlag{ 99 - Name: "smtp-name", 100 - EnvVars: []string{"COCOON_SMTP_NAME"}, 101 - }, 102 - &cli.BoolFlag{ 103 - Name: "ipfs-blobstore-enabled", 104 - EnvVars: []string{"COCOON_IPFS_BLOBSTORE_ENABLED"}, 105 - Usage: "Store blobs on IPFS via the Kubo HTTP RPC API instead of SQLite.", 106 - }, 107 - &cli.StringFlag{ 108 - Name: "ipfs-node-url", 109 - EnvVars: []string{"COCOON_IPFS_NODE_URL"}, 110 - Value: "http://127.0.0.1:5001", 111 - Usage: "Base URL of the Kubo (go-ipfs) RPC API used for adding and fetching blobs.", 112 - }, 113 - &cli.StringFlag{ 114 - Name: "ipfs-gateway-url", 115 - EnvVars: []string{"COCOON_IPFS_GATEWAY_URL"}, 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 - }, 118 - &cli.StringFlag{ 119 - Name: "ipfs-pinning-service-url", 120 - EnvVars: []string{"COCOON_IPFS_PINNING_SERVICE_URL"}, 121 - Usage: "Remote IPFS Pinning Service API endpoint (e.g., https://api.pinata.cloud/psa). Leave empty to skip remote pinning.", 122 - }, 123 - &cli.StringFlag{ 124 - Name: "ipfs-pinning-service-token", 125 - EnvVars: []string{"COCOON_IPFS_PINNING_SERVICE_TOKEN"}, 126 - Usage: "Bearer token for authenticating with the remote IPFS pinning service.", 127 - }, 128 - &cli.StringFlag{ 129 - Name: "session-secret", 130 - EnvVars: []string{"COCOON_SESSION_SECRET"}, 131 - }, 132 - &cli.StringFlag{ 133 - Name: "session-cookie-key", 134 - EnvVars: []string{"COCOON_SESSION_COOKIE_KEY"}, 135 - Value: "session", 136 - }, 137 - &cli.StringFlag{ 138 - Name: "blockstore-variant", 139 - EnvVars: []string{"COCOON_BLOCKSTORE_VARIANT"}, 140 - Value: "sqlite", 141 - }, 142 - &cli.StringFlag{ 143 - Name: "fallback-proxy", 144 - EnvVars: []string{"COCOON_FALLBACK_PROXY"}, 145 - }, 146 - telemetry.CLIFlagDebug, 147 - telemetry.CLIFlagMetricsListenAddress, 148 - }, 149 - Commands: []*cli.Command{ 150 - runServe, 151 - runCreateRotationKey, 152 - runCreatePrivateJwk, 153 - runCreateInviteCode, 154 - runResetPassword, 155 - }, 156 - ErrWriter: os.Stdout, 157 - Version: Version, 34 + if err := rootCmd.Execute(); err != nil { 35 + os.Exit(1) 158 36 } 37 + } 159 38 160 - if err := app.Run(os.Args); err != nil { 161 - fmt.Printf("Error: %v\n", err) 162 - } 39 + var rootCmd = &cobra.Command{ 40 + Use: "cocoon", 41 + Short: "An atproto PDS", 42 + Version: Version, 43 + SilenceErrors: true, 44 + SilenceUsage: true, 163 45 } 164 46 165 - var runServe = &cli.Command{ 166 - Name: "run", 167 - Usage: "Start the cocoon PDS", 168 - Flags: []cli.Flag{ 169 - &cli.StringFlag{ 170 - Name: "log-level", 171 - Usage: "Log level: debug, info, warn, error", 172 - EnvVars: []string{"COCOON_LOG_LEVEL", "LOG_LEVEL"}, 173 - Value: "info", 174 - }, 175 - }, 176 - Action: func(cmd *cli.Context) error { 47 + func init() { 48 + pf := rootCmd.PersistentFlags() 49 + 50 + pf.String(flagAddr, ":8080", "Listen address") 51 + pf.String(flagDbName, "cocoon.db", "SQLite database file path") 52 + pf.String(flagDid, "", "DID of this PDS") 53 + pf.String(flagHostname, "", "Public hostname of this PDS") 54 + pf.String(flagRotationKeyPath, "", "Path to the rotation key file") 55 + pf.String(flagJwkPath, "", "Path to the private JWK file") 56 + pf.String(flagContactEmail, "", "Contact e-mail address for this PDS") 57 + pf.StringSlice(flagRelays, []string{}, "Relay URLs to crawl") 58 + pf.String(flagAdminPassword, "", "Admin password") 59 + pf.Bool(flagRequireInvite, true, "Require an invite code to create an account") 60 + pf.String(flagSmtpUser, "", "SMTP username") 61 + pf.String(flagSmtpPass, "", "SMTP password") 62 + pf.String(flagSmtpHost, "", "SMTP host") 63 + pf.String(flagSmtpPort, "", "SMTP port") 64 + pf.String(flagSmtpEmail, "", "SMTP from address") 65 + pf.String(flagSmtpName, "", "SMTP from name") 66 + pf.Bool(flagIpfsBlobstoreEnabled, false, "Store blobs on IPFS via the Kubo HTTP RPC API instead of SQLite") 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 + 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 + pf.String(flagIpfsPinningServiceUrl, "", "Remote IPFS Pinning Service API endpoint (e.g. https://api.pinata.cloud/psa). Leave empty to skip remote pinning") 70 + pf.String(flagIpfsPinningServiceToken, "", "Bearer token for authenticating with the remote IPFS pinning service") 71 + pf.String(flagSessionSecret, "", "Session secret") 72 + pf.String(flagSessionCookieKey, "session", "Session cookie key name") 73 + pf.String(flagBlockstoreVariant, "sqlite", "Blockstore variant (sqlite)") 74 + pf.String(flagFallbackProxy, "", "Fallback proxy URL") 75 + pf.String(flagLogLevel, "info", "Log level: debug, info, warn, error") 76 + pf.Bool(flagDebug, false, "Enable debug logging (shorthand for --log-level=debug)") 77 + pf.String(flagMetricsListenAddress, "0.0.0.0:6009", "Listen address for the Prometheus metrics / pprof endpoint") 78 + 79 + v := viper.New() 80 + v.SetEnvPrefix("COCOON") 81 + v.SetEnvKeyReplacer(strings.NewReplacer("-", "_")) 82 + v.AutomaticEnv() 83 + _ = v.BindEnv(flagDebug, "DEBUG") 84 + _ = v.BindEnv(flagLogLevel, "COCOON_LOG_LEVEL", "LOG_LEVEL") 85 + _ = v.BindEnv(flagMetricsListenAddress, "METRICS_LISTEN_ADDRESS") 86 + _ = v.BindPFlags(pf) 177 87 178 - logger := telemetry.StartLogger(cmd) 179 - telemetry.StartMetrics(cmd) 88 + rootCmd.AddCommand( 89 + newServeCmd(v), 90 + newCreateRotationKeyCmd(), 91 + newCreatePrivateJwkCmd(), 92 + newCreateInviteCodeCmd(v), 93 + newResetPasswordCmd(v), 94 + ) 95 + } 180 96 181 - var level slog.Level 182 - switch strings.ToLower(cmd.String("log-level")) { 97 + func buildLogger(v *viper.Viper) *slog.Logger { 98 + var level slog.Level 99 + if v.GetBool(flagDebug) { 100 + level = slog.LevelDebug 101 + } else { 102 + switch strings.ToLower(v.GetString(flagLogLevel)) { 183 103 case "debug": 184 104 level = slog.LevelDebug 185 - case "info": 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 + } 194 113 195 - s, err := server.New(&server.Args{ 196 - Logger: logger, 197 - LogLevel: level, 198 - Addr: cmd.String("addr"), 199 - DbName: cmd.String("db-name"), 200 - Did: cmd.String("did"), 201 - Hostname: cmd.String("hostname"), 202 - RotationKeyPath: cmd.String("rotation-key-path"), 203 - JwkPath: cmd.String("jwk-path"), 204 - ContactEmail: cmd.String("contact-email"), 205 - Version: Version, 206 - Relays: cmd.StringSlice("relays"), 207 - AdminPassword: cmd.String("admin-password"), 208 - RequireInvite: cmd.Bool("require-invite"), 209 - SmtpUser: cmd.String("smtp-user"), 210 - SmtpPass: cmd.String("smtp-pass"), 211 - SmtpHost: cmd.String("smtp-host"), 212 - SmtpPort: cmd.String("smtp-port"), 213 - SmtpEmail: cmd.String("smtp-email"), 214 - SmtpName: cmd.String("smtp-name"), 215 - IPFSConfig: &server.IPFSConfig{ 216 - BlobstoreEnabled: cmd.Bool("ipfs-blobstore-enabled"), 217 - NodeURL: cmd.String("ipfs-node-url"), 218 - GatewayURL: cmd.String("ipfs-gateway-url"), 219 - PinningServiceURL: cmd.String("ipfs-pinning-service-url"), 220 - PinningServiceToken: cmd.String("ipfs-pinning-service-token"), 221 - }, 222 - SessionSecret: cmd.String("session-secret"), 223 - SessionCookieKey: cmd.String("session-cookie-key"), 224 - BlockstoreVariant: server.MustReturnBlockstoreVariant(cmd.String("blockstore-variant")), 225 - FallbackProxy: cmd.String("fallback-proxy"), 226 - }) 227 - if err != nil { 228 - fmt.Printf("error creating cocoon: %v", err) 229 - return err 230 - } 114 + handler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ 115 + Level: level, 116 + AddSource: true, 117 + }) 118 + logger := slog.New(handler) 119 + slog.SetDefault(logger) 120 + return logger 121 + } 231 122 232 - if err := s.Serve(cmd.Context); err != nil { 233 - fmt.Printf("error starting cocoon: %v", err) 234 - return err 123 + func startMetrics(v *viper.Viper) { 124 + addr := v.GetString(flagMetricsListenAddress) 125 + if addr == "" { 126 + return 127 + } 128 + logger := slog.Default().With("component", "telemetry") 129 + logger.Info("starting metrics server", "address", addr) 130 + mux := http.NewServeMux() 131 + mux.Handle("/metrics", promhttp.Handler()) 132 + mux.Handle("/debug/pprof/", http.DefaultServeMux) 133 + go func() { 134 + if err := http.ListenAndServe(addr, mux); err != nil { 135 + logger.Error("metrics server failed", "err", err) 235 136 } 236 - 237 - return nil 238 - }, 137 + }() 239 138 } 240 139 241 - var runCreateRotationKey = &cli.Command{ 242 - Name: "create-rotation-key", 243 - Usage: "creates a rotation key for your pds", 244 - Flags: []cli.Flag{ 245 - &cli.StringFlag{ 246 - Name: "out", 247 - Required: true, 248 - Usage: "output file for your rotation key", 249 - }, 250 - }, 251 - Action: func(cmd *cli.Context) error { 252 - key, err := atcrypto.GeneratePrivateKeyK256() 253 - if err != nil { 254 - return err 255 - } 140 + func newDb(v *viper.Viper) (*gorm.DB, error) { 141 + dbName := v.GetString(flagDbName) 142 + if dbName == "" { 143 + dbName = "cocoon.db" 144 + } 145 + return gorm.Open(sqlite.Open(dbName), &gorm.Config{}) 146 + } 256 147 257 - bytes := key.Bytes() 148 + func newServeCmd(v *viper.Viper) *cobra.Command { 149 + return &cobra.Command{ 150 + Use: "run", 151 + Short: "Start the cocoon PDS", 152 + RunE: func(cmd *cobra.Command, args []string) error { 153 + logger := buildLogger(v) 154 + startMetrics(v) 258 155 259 - if err := os.WriteFile(cmd.String("out"), bytes, 0644); err != nil { 260 - return err 261 - } 156 + s, err := server.New(&server.Args{ 157 + Logger: logger, 158 + Addr: v.GetString(flagAddr), 159 + DbName: v.GetString(flagDbName), 160 + Did: v.GetString(flagDid), 161 + Hostname: v.GetString(flagHostname), 162 + RotationKeyPath: v.GetString(flagRotationKeyPath), 163 + JwkPath: v.GetString(flagJwkPath), 164 + ContactEmail: v.GetString(flagContactEmail), 165 + Version: Version, 166 + Relays: v.GetStringSlice(flagRelays), 167 + AdminPassword: v.GetString(flagAdminPassword), 168 + RequireInvite: v.GetBool(flagRequireInvite), 169 + SmtpUser: v.GetString(flagSmtpUser), 170 + SmtpPass: v.GetString(flagSmtpPass), 171 + SmtpHost: v.GetString(flagSmtpHost), 172 + SmtpPort: v.GetString(flagSmtpPort), 173 + SmtpEmail: v.GetString(flagSmtpEmail), 174 + SmtpName: v.GetString(flagSmtpName), 175 + IPFSConfig: &server.IPFSConfig{ 176 + BlobstoreEnabled: v.GetBool(flagIpfsBlobstoreEnabled), 177 + NodeURL: v.GetString(flagIpfsNodeUrl), 178 + GatewayURL: v.GetString(flagIpfsGatewayUrl), 179 + PinningServiceURL: v.GetString(flagIpfsPinningServiceUrl), 180 + PinningServiceToken: v.GetString(flagIpfsPinningServiceToken), 181 + }, 182 + SessionSecret: v.GetString(flagSessionSecret), 183 + SessionCookieKey: v.GetString(flagSessionCookieKey), 184 + BlockstoreVariant: server.MustReturnBlockstoreVariant(v.GetString(flagBlockstoreVariant)), 185 + FallbackProxy: v.GetString(flagFallbackProxy), 186 + }) 187 + if err != nil { 188 + return fmt.Errorf("error creating cocoon: %w", err) 189 + } 262 190 263 - return nil 264 - }, 265 - } 191 + if err := s.Serve(context.Background()); err != nil { 192 + return fmt.Errorf("error starting cocoon: %w", err) 193 + } 266 194 267 - var runCreatePrivateJwk = &cli.Command{ 268 - Name: "create-private-jwk", 269 - Usage: "creates a private jwk for your pds", 270 - Flags: []cli.Flag{ 271 - &cli.StringFlag{ 272 - Name: "out", 273 - Required: true, 274 - Usage: "output file for your jwk", 195 + return nil 275 196 }, 276 - }, 277 - Action: func(cmd *cli.Context) error { 278 - privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 279 - if err != nil { 280 - return err 281 - } 197 + } 198 + } 282 199 283 - key, err := jwk.FromRaw(privKey) 284 - if err != nil { 285 - return err 286 - } 200 + func newCreateRotationKeyCmd() *cobra.Command { 201 + cmd := &cobra.Command{ 202 + Use: "create-rotation-key", 203 + Short: "Create a rotation key for your PDS", 204 + RunE: func(cmd *cobra.Command, args []string) error { 205 + out, _ := cmd.Flags().GetString("out") 287 206 288 - kid := fmt.Sprintf("%d", time.Now().Unix()) 207 + key, err := atcrypto.GeneratePrivateKeyK256() 208 + if err != nil { 209 + return err 210 + } 289 211 290 - if err := key.Set(jwk.KeyIDKey, kid); err != nil { 291 - return err 292 - } 212 + if err := os.WriteFile(out, key.Bytes(), 0644); err != nil { 213 + return err 214 + } 293 215 294 - b, err := json.Marshal(key) 295 - if err != nil { 296 - return err 297 - } 216 + fmt.Printf("Rotation key written to %s\n", out) 217 + return nil 218 + }, 219 + } 298 220 299 - if err := os.WriteFile(cmd.String("out"), b, 0644); err != nil { 300 - return err 301 - } 221 + cmd.Flags().StringP("out", "o", "", "Output file for the rotation key (required)") 222 + _ = cmd.MarkFlagRequired("out") 302 223 303 - return nil 304 - }, 224 + return cmd 305 225 } 306 226 307 - var runCreateInviteCode = &cli.Command{ 308 - Name: "create-invite-code", 309 - Usage: "creates an invite code", 310 - Flags: []cli.Flag{ 311 - &cli.StringFlag{ 312 - Name: "for", 313 - Usage: "optional did to assign the invite code to", 314 - }, 315 - &cli.IntFlag{ 316 - Name: "uses", 317 - Usage: "number of times the invite code can be used", 318 - Value: 1, 319 - }, 320 - }, 321 - Action: func(cmd *cli.Context) error { 322 - db, err := newDb(cmd) 323 - if err != nil { 324 - return err 325 - } 227 + func newCreatePrivateJwkCmd() *cobra.Command { 228 + cmd := &cobra.Command{ 229 + Use: "create-private-jwk", 230 + Short: "Create a private JWK for your PDS", 231 + RunE: func(cmd *cobra.Command, args []string) error { 232 + out, _ := cmd.Flags().GetString("out") 326 233 327 - forDid := "did:plc:123" 328 - if cmd.String("for") != "" { 329 - did, err := syntax.ParseDID(cmd.String("for")) 234 + privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 235 + if err != nil { 236 + return err 237 + } 238 + 239 + key, err := jwk.FromRaw(privKey) 330 240 if err != nil { 331 241 return err 332 242 } 333 243 334 - forDid = did.String() 335 - } 244 + if err := key.Set(jwk.KeyIDKey, fmt.Sprintf("%d", time.Now().Unix())); err != nil { 245 + return err 246 + } 336 247 337 - uses := cmd.Int("uses") 248 + b, err := json.Marshal(key) 249 + if err != nil { 250 + return err 251 + } 338 252 339 - code := fmt.Sprintf("%s-%s", helpers.RandomVarchar(8), helpers.RandomVarchar(8)) 253 + if err := os.WriteFile(out, b, 0644); err != nil { 254 + return err 255 + } 340 256 341 - if err := db.Exec("INSERT INTO invite_codes (did, code, remaining_use_count) VALUES (?, ?, ?)", forDid, code, uses).Error; err != nil { 342 - return err 343 - } 257 + fmt.Printf("Private JWK written to %s\n", out) 258 + return nil 259 + }, 260 + } 344 261 345 - fmt.Printf("New invite code created with %d uses: %s\n", uses, code) 262 + cmd.Flags().StringP("out", "o", "", "Output file for the private JWK (required)") 263 + _ = cmd.MarkFlagRequired("out") 346 264 347 - return nil 348 - }, 265 + return cmd 349 266 } 350 267 351 - var runResetPassword = &cli.Command{ 352 - Name: "reset-password", 353 - Usage: "resets a password", 354 - Flags: []cli.Flag{ 355 - &cli.StringFlag{ 356 - Name: "did", 357 - Usage: "did of the user who's password you want to reset", 268 + func newCreateInviteCodeCmd(v *viper.Viper) *cobra.Command { 269 + cmd := &cobra.Command{ 270 + Use: "create-invite-code", 271 + Short: "Create an invite code", 272 + RunE: func(cmd *cobra.Command, args []string) error { 273 + db, err := newDb(v) 274 + if err != nil { 275 + return err 276 + } 277 + 278 + forFlag, _ := cmd.Flags().GetString("for") 279 + uses, _ := cmd.Flags().GetInt("uses") 280 + 281 + forDid := "did:plc:123" 282 + if forFlag != "" { 283 + did, err := syntax.ParseDID(forFlag) 284 + if err != nil { 285 + return fmt.Errorf("invalid DID %q: %w", forFlag, err) 286 + } 287 + forDid = did.String() 288 + } 289 + 290 + code := fmt.Sprintf("%s-%s", helpers.RandomVarchar(8), helpers.RandomVarchar(8)) 291 + 292 + if err := db.Exec( 293 + "INSERT INTO invite_codes (did, code, remaining_use_count) VALUES (?, ?, ?)", 294 + forDid, code, uses, 295 + ).Error; err != nil { 296 + return err 297 + } 298 + 299 + fmt.Printf("New invite code created with %d uses: %s\n", uses, code) 300 + return nil 358 301 }, 359 - }, 360 - Action: func(cmd *cli.Context) error { 361 - db, err := newDb(cmd) 362 - if err != nil { 363 - return err 364 - } 302 + } 365 303 366 - didStr := cmd.String("did") 367 - did, err := syntax.ParseDID(didStr) 368 - if err != nil { 369 - return err 370 - } 304 + cmd.Flags().String("for", "", "Optional DID to assign the invite code to") 305 + cmd.Flags().Int("uses", 1, "Number of times the invite code can be used") 371 306 372 - newPass := fmt.Sprintf("%s-%s", helpers.RandomVarchar(12), helpers.RandomVarchar(12)) 373 - hashed, err := bcrypt.GenerateFromPassword([]byte(newPass), 10) 374 - if err != nil { 375 - return err 376 - } 307 + return cmd 308 + } 377 309 378 - if err := db.Exec("UPDATE repos SET password = ? WHERE did = ?", hashed, did.String()).Error; err != nil { 379 - return err 380 - } 310 + func newResetPasswordCmd(v *viper.Viper) *cobra.Command { 311 + cmd := &cobra.Command{ 312 + Use: "reset-password", 313 + Short: "Reset a user's password", 314 + RunE: func(cmd *cobra.Command, args []string) error { 315 + db, err := newDb(v) 316 + if err != nil { 317 + return err 318 + } 381 319 382 - fmt.Printf("Password for %s has been reset to: %s", did.String(), newPass) 320 + didStr, _ := cmd.Flags().GetString("did") 321 + did, err := syntax.ParseDID(didStr) 322 + if err != nil { 323 + return fmt.Errorf("invalid DID %q: %w", didStr, err) 324 + } 383 325 384 - return nil 385 - }, 386 - } 326 + newPass := fmt.Sprintf("%s-%s", helpers.RandomVarchar(12), helpers.RandomVarchar(12)) 327 + hashed, err := bcrypt.GenerateFromPassword([]byte(newPass), 10) 328 + if err != nil { 329 + return err 330 + } 387 331 388 - func newDb(cmd *cli.Context) (*gorm.DB, error) { 389 - dbName := cmd.String("db-name") 390 - if dbName == "" { 391 - dbName = "cocoon.db" 332 + if err := db.Exec( 333 + "UPDATE repos SET password = ? WHERE did = ?", 334 + hashed, did.String(), 335 + ).Error; err != nil { 336 + return err 337 + } 338 + 339 + fmt.Printf("Password for %s has been reset to: %s\n", did.String(), newPass) 340 + return nil 341 + }, 392 342 } 393 - return gorm.Open(sqlite.Open(dbName), &gorm.Config{}) 343 + 344 + cmd.Flags().String("did", "", "DID of the user whose password to reset (required)") 345 + _ = cmd.MarkFlagRequired("did") 346 + 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 - 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 - github.com/urfave/cli/v2 v2.27.6 31 + github.com/spf13/cobra v1.10.2 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 - 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 + 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 + 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 + 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 + 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 - github.com/russross/blackfriday/v2 v2.1.0 // indirect 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 + github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect 114 118 github.com/spaolacci/murmur3 v1.1.0 // indirect 119 + github.com/spf13/afero v1.15.0 // indirect 120 + github.com/spf13/cast v1.10.0 // indirect 121 + github.com/spf13/pflag v1.0.10 // indirect 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 - 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 + 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 - github.com/bluesky-social/go-util v0.0.0-20251012040650-2ebbf57f5934 h1:btHMur2kTRgWEnCHn6LaI3BE9YRgsqTpwpJ1UdB7VEk= 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 - github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= 29 - github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 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 + github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= 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 + github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= 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 + github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 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 + github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= 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 - 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 + github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc= 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 + github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= 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 + github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= 339 + github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= 340 + github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= 341 + github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= 342 + github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= 343 + github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= 344 + github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 345 + github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= 346 + github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 347 + github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= 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 + github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= 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 - github.com/urfave/cli/v2 v2.27.6 h1:VdRdS98FNhKZ8/Az8B7MTyGQmpIr36O1EHybx/LaZ4g= 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 - github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= 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 + go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= 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 - 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 - 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 - type filteredHandler struct { 226 - level slog.Level 227 - handler slog.Handler 228 - } 229 - 230 - func (h *filteredHandler) Enabled(ctx context.Context, level slog.Level) bool { 231 - return level >= h.level && h.handler.Enabled(ctx, level) 232 - } 233 - 234 - func (h *filteredHandler) Handle(ctx context.Context, r slog.Record) error { 235 - return h.handler.Handle(ctx, r) 236 - } 237 - 238 - func (h *filteredHandler) WithAttrs(attrs []slog.Attr) slog.Handler { 239 - return &filteredHandler{level: h.level, handler: h.handler.WithAttrs(attrs)} 240 - } 241 - 242 - func (h *filteredHandler) WithGroup(name string) slog.Handler { 243 - return &filteredHandler{level: h.level, handler: h.handler.WithGroup(name)} 244 - } 245 - 246 223 func New(args *Args) (*Server, error) { 247 224 if args.Logger == nil { 248 225 args.Logger = slog.Default() 249 - } 250 - 251 - if args.LogLevel != 0 { 252 - args.Logger = slog.New(&filteredHandler{ 253 - level: args.LogLevel, 254 - handler: args.Logger.Handler(), 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 - LogLevel: args.LogLevel, 413 382 Version: args.Version, 414 383 Did: args.Did, 415 384 Hostname: args.Hostname,