this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

update other commands to cli/v3

+439 -364
+44 -49
cmd/bluepages/main.go
··· 17 17 "github.com/bluesky-social/indigo/atproto/syntax" 18 18 19 19 "github.com/earthboundkid/versioninfo/v2" 20 - "github.com/urfave/cli/v2" 20 + "github.com/urfave/cli/v3" 21 21 ) 22 22 23 23 func main() { ··· 29 29 30 30 func run(args []string) error { 31 31 32 - app := cli.App{ 32 + app := cli.Command{ 33 33 Name: "bluepages", 34 34 Usage: "atproto identity directory", 35 35 Version: versioninfo.Short(), ··· 38 38 Name: "atp-relay-host", 39 39 Usage: "hostname and port of Relay to subscribe to", 40 40 Value: "wss://bsky.network", 41 - EnvVars: []string{"ATP_RELAY_HOST", "ATP_BGS_HOST"}, 41 + Sources: cli.EnvVars("ATP_RELAY_HOST", "ATP_BGS_HOST"), 42 42 }, 43 43 &cli.StringFlag{ 44 44 Name: "atp-plc-host", 45 45 Usage: "method, hostname, and port of PLC registry", 46 46 Value: "https://plc.directory", 47 - EnvVars: []string{"ATP_PLC_HOST"}, 47 + Sources: cli.EnvVars("ATP_PLC_HOST"), 48 48 }, 49 49 &cli.IntFlag{ 50 50 Name: "plc-rate-limit", 51 51 Usage: "max number of requests per second to PLC registry", 52 52 Value: 300, 53 - EnvVars: []string{"BLUEPAGES_PLC_RATE_LIMIT"}, 53 + Sources: cli.EnvVars("BLUEPAGES_PLC_RATE_LIMIT"), 54 54 }, 55 55 &cli.StringFlag{ 56 56 Name: "redis-url", 57 57 Usage: "redis connection URL: redis://<user>:<pass>@<hostname>:6379/<db>", 58 58 Value: "redis://localhost:6379/0", 59 - EnvVars: []string{"BLUEPAGES_REDIS_URL"}, 59 + Sources: cli.EnvVars("BLUEPAGES_REDIS_URL"), 60 60 }, 61 61 &cli.StringFlag{ 62 62 Name: "log-level", 63 63 Usage: "log verbosity level (eg: warn, info, debug)", 64 - EnvVars: []string{"BLUEPAGES_LOG_LEVEL", "GO_LOG_LEVEL", "LOG_LEVEL"}, 64 + Sources: cli.EnvVars("BLUEPAGES_LOG_LEVEL", "GO_LOG_LEVEL", "LOG_LEVEL"), 65 65 }, 66 66 }, 67 67 Commands: []*cli.Command{ ··· 75 75 Usage: "Specify the local IP/port to bind to", 76 76 Required: false, 77 77 Value: ":6600", 78 - EnvVars: []string{"BLUEPAGES_BIND"}, 78 + Sources: cli.EnvVars("BLUEPAGES_BIND"), 79 79 }, 80 80 &cli.StringFlag{ 81 81 Name: "metrics-listen", 82 82 Usage: "IP or address, and port, to listen on for metrics APIs", 83 83 Value: ":3989", 84 - EnvVars: []string{"BLUEPAGES_METRICS_LISTEN"}, 84 + Sources: cli.EnvVars("BLUEPAGES_METRICS_LISTEN"), 85 85 }, 86 86 &cli.BoolFlag{ 87 87 Name: "disable-firehose-consumer", 88 88 Usage: "don't consume #identity events from firehose", 89 - EnvVars: []string{"BLUEPAGES_DISABLE_FIREHOSE_CONSUMER"}, 89 + Sources: cli.EnvVars("BLUEPAGES_DISABLE_FIREHOSE_CONSUMER"), 90 90 }, 91 91 &cli.BoolFlag{ 92 92 Name: "disable-refresh", 93 93 Usage: "disable the refreshIdentity API endpoint", 94 - EnvVars: []string{"BLUEPAGES_DISABLE_REFRESH"}, 94 + Sources: cli.EnvVars("BLUEPAGES_DISABLE_REFRESH"), 95 95 }, 96 96 &cli.IntFlag{ 97 97 Name: "firehose-parallelism", 98 98 Usage: "number of concurrent firehose workers", 99 99 Value: 4, 100 - EnvVars: []string{"BLUEPAGES_FIREHOSE_PARALLELISM"}, 100 + Sources: cli.EnvVars("BLUEPAGES_FIREHOSE_PARALLELISM"), 101 101 }, 102 102 }, 103 103 }, ··· 111 111 Name: "host", 112 112 Usage: "bluepages server to send request to", 113 113 Value: "http://localhost:6600", 114 - EnvVars: []string{"BLUEPAGES_HOST"}, 114 + Sources: cli.EnvVars("BLUEPAGES_HOST"), 115 115 }, 116 116 }, 117 117 }, ··· 125 125 Name: "host", 126 126 Usage: "bluepages server to send request to", 127 127 Value: "http://localhost:6600", 128 - EnvVars: []string{"BLUEPAGES_HOST"}, 128 + Sources: cli.EnvVars("BLUEPAGES_HOST"), 129 129 }, 130 130 }, 131 131 }, ··· 139 139 Name: "host", 140 140 Usage: "bluepages server to send request to", 141 141 Value: "http://localhost:6600", 142 - EnvVars: []string{"BLUEPAGES_HOST"}, 142 + Sources: cli.EnvVars("BLUEPAGES_HOST"), 143 143 }, 144 144 }, 145 145 }, ··· 153 153 Name: "host", 154 154 Usage: "bluepages server to send request to", 155 155 Value: "http://localhost:6600", 156 - EnvVars: []string{"BLUEPAGES_HOST"}, 156 + Sources: cli.EnvVars("BLUEPAGES_HOST"), 157 157 }, 158 158 }, 159 159 }, 160 160 }, 161 161 } 162 162 163 - return app.Run(args) 163 + return app.Run(context.Background(), args) 164 164 } 165 165 166 - func configLogger(cctx *cli.Context, writer io.Writer) *slog.Logger { 166 + func configLogger(cmd *cli.Command, writer io.Writer) *slog.Logger { 167 167 var level slog.Level 168 - switch strings.ToLower(cctx.String("log-level")) { 168 + switch strings.ToLower(cmd.String("log-level")) { 169 169 case "error": 170 170 level = slog.LevelError 171 171 case "warn": ··· 184 184 return logger 185 185 } 186 186 187 - func configClient(cctx *cli.Context) apidir.APIDirectory { 188 - return apidir.NewAPIDirectory(cctx.String("host")) 187 + func configClient(cmd *cli.Command) apidir.APIDirectory { 188 + return apidir.NewAPIDirectory(cmd.String("host")) 189 189 } 190 190 191 - func runServeCmd(cctx *cli.Context) error { 192 - logger := configLogger(cctx, os.Stdout) 193 - ctx := context.Background() 191 + func runServeCmd(ctx context.Context, cmd *cli.Command) error { 192 + logger := configLogger(cmd, os.Stdout) 194 193 195 194 srv, err := NewServer( 196 195 Config{ 197 196 Logger: logger, 198 - Bind: cctx.String("bind"), 199 - RedisURL: cctx.String("redis-url"), 200 - PLCHost: cctx.String("atp-plc-host"), 201 - PLCRateLimit: cctx.Int("plc-rate-limit"), 202 - DisableRefresh: cctx.Bool("disable-refresh"), 197 + Bind: cmd.String("bind"), 198 + RedisURL: cmd.String("redis-url"), 199 + PLCHost: cmd.String("atp-plc-host"), 200 + PLCRateLimit: cmd.Int("plc-rate-limit"), 201 + DisableRefresh: cmd.Bool("disable-refresh"), 203 202 }, 204 203 ) 205 204 if err != nil { 206 205 return fmt.Errorf("failed to construct server: %v", err) 207 206 } 208 207 209 - if !cctx.Bool("disable-firehose-consumer") { 208 + if !cmd.Bool("disable-firehose-consumer") { 210 209 go func() { 211 - firehoseHost := cctx.String("atp-relay-host") 212 - firehoseParallelism := cctx.Int("firehose-parallelism") 210 + firehoseHost := cmd.String("atp-relay-host") 211 + firehoseParallelism := cmd.Int("firehose-parallelism") 213 212 if err := srv.RunFirehoseConsumer(ctx, firehoseHost, firehoseParallelism); err != nil { 214 213 slog.Error("firehose consumer thread failed", "err", err) 215 214 // NOTE: not crashing or halting process here ··· 228 227 // TODO: what is this tuning for? just cargo-culted it 229 228 runtime.SetBlockProfileRate(10) 230 229 runtime.SetMutexProfileFraction(10) 231 - if err := srv.RunMetrics(cctx.String("metrics-listen")); err != nil { 230 + if err := srv.RunMetrics(cmd.String("metrics-listen")); err != nil { 232 231 slog.Error("failed to start metrics endpoint", "error", err) 233 232 // NOTE: not crashing or halting process here 234 233 } ··· 237 236 return srv.RunAPI() 238 237 } 239 238 240 - func runResolveHandleCmd(cctx *cli.Context) error { 241 - ctx := context.Background() 242 - dir := configClient(cctx) 239 + func runResolveHandleCmd(ctx context.Context, cmd *cli.Command) error { 240 + dir := configClient(cmd) 243 241 244 - s := cctx.Args().First() 242 + s := cmd.Args().First() 245 243 if s == "" { 246 244 return fmt.Errorf("need to provide identifier for resolution") 247 245 } ··· 258 256 return nil 259 257 } 260 258 261 - func runResolveDIDCmd(cctx *cli.Context) error { 262 - ctx := context.Background() 263 - dir := configClient(cctx) 259 + func runResolveDIDCmd(ctx context.Context, cmd *cli.Command) error { 260 + dir := configClient(cmd) 264 261 265 - s := cctx.Args().First() 262 + s := cmd.Args().First() 266 263 if s == "" { 267 264 return fmt.Errorf("need to provide identifier for resolution") 268 265 } ··· 283 280 return nil 284 281 } 285 282 286 - func runLookupCmd(cctx *cli.Context) error { 287 - ctx := context.Background() 288 - dir := configClient(cctx) 283 + func runLookupCmd(ctx context.Context, cmd *cli.Command) error { 284 + dir := configClient(cmd) 289 285 290 - s := cctx.Args().First() 286 + s := cmd.Args().First() 291 287 if s == "" { 292 288 return fmt.Errorf("need to provide identifier for resolution") 293 289 } ··· 309 305 return nil 310 306 } 311 307 312 - func runRefreshCmd(cctx *cli.Context) error { 313 - ctx := context.Background() 314 - dir := configClient(cctx) 308 + func runRefreshCmd(ctx context.Context, cmd *cli.Command) error { 309 + dir := configClient(cmd) 315 310 316 - s := cctx.Args().First() 311 + s := cmd.Args().First() 317 312 if s == "" { 318 313 return fmt.Errorf("need to provide identifier for resolution") 319 314 }
+90 -42
cmd/fakermaker/main.go
··· 17 17 comatproto "github.com/bluesky-social/indigo/api/atproto" 18 18 "github.com/bluesky-social/indigo/fakedata" 19 19 "github.com/bluesky-social/indigo/util/cliutil" 20 + "github.com/bluesky-social/indigo/xrpc" 20 21 21 22 "github.com/earthboundkid/versioninfo/v2" 22 - "github.com/urfave/cli/v2" 23 + "github.com/urfave/cli/v3" 23 24 "golang.org/x/sync/errgroup" 24 25 ) 25 26 ··· 29 30 30 31 func run(args []string) { 31 32 32 - app := cli.App{ 33 + app := cli.Command{ 33 34 Name: "fakermaker", 34 35 Usage: "bluesky fake account/content generator", 35 36 Version: versioninfo.Short(), ··· 40 41 Name: "pds-host", 41 42 Usage: "method, hostname, and port of PDS instance", 42 43 Value: "http://localhost:4849", 43 - EnvVars: []string{"ATP_PDS_HOST"}, 44 + Sources: cli.EnvVars("ATP_PDS_HOST"), 44 45 }, 45 46 &cli.StringFlag{ 46 47 Name: "admin-password", 47 48 Usage: "admin authentication password for PDS", 48 49 Required: true, 49 - EnvVars: []string{"ATP_AUTH_ADMIN_PASSWORD"}, 50 + Sources: cli.EnvVars("ATP_AUTH_ADMIN_PASSWORD"), 50 51 }, 51 52 &cli.IntFlag{ 52 53 Name: "jobs", ··· 196 197 }, 197 198 } 198 199 all := fakedata.MeasureIterations("entire command") 199 - app.RunAndExitOnError() 200 + if err := app.Run(context.Background(), os.Args); err != nil { 201 + fmt.Fprintf(os.Stderr, "%s\n", err.Error()) 202 + os.Exit(1) 203 + } 200 204 all(1) 201 205 } 202 206 203 207 // registers fake accounts with PDS, and spits out JSON-lines to stdout with auth info 204 - func genAccounts(cctx *cli.Context) error { 208 + func genAccounts(ctx context.Context, cmd *cli.Command) error { 205 209 206 210 // establish atproto client, with admin token for auth 207 - xrpcc, err := cliutil.GetXrpcClient(cctx, false) 211 + xrpcc, err := getXrpcClient(cmd, false) 208 212 if err != nil { 209 213 return err 210 214 } 211 - adminToken := cctx.String("admin-password") 215 + adminToken := cmd.String("admin-password") 212 216 if len(adminToken) > 0 { 213 217 xrpcc.AdminToken = &adminToken 214 218 } 215 219 216 - countTotal := cctx.Int("count") 217 - countCelebrities := cctx.Int("count-celebrities") 218 - domainSuffix := cctx.String("domain-suffix") 220 + countTotal := cmd.Int("count") 221 + countCelebrities := cmd.Int("count-celebrities") 222 + domainSuffix := cmd.String("domain-suffix") 219 223 if countCelebrities > countTotal { 220 224 return fmt.Errorf("more celebrities than total accounts!") 221 225 } 222 226 countRegulars := countTotal - countCelebrities 223 227 224 228 var inviteCode *string = nil 225 - if cctx.Bool("use-invite-code") { 229 + if cmd.Bool("use-invite-code") { 226 230 resp, err := comatproto.ServerCreateInviteCodes(context.TODO(), xrpcc, &comatproto.ServerCreateInviteCodes_Input{ 227 231 UseCount: int64(countTotal), 228 232 ForAccounts: nil, ··· 268 272 return nil 269 273 } 270 274 271 - func genProfiles(cctx *cli.Context) error { 272 - catalog, err := fakedata.ReadAccountCatalog(cctx.String("catalog")) 275 + func genProfiles(ctx context.Context, cmd *cli.Command) error { 276 + catalog, err := fakedata.ReadAccountCatalog(cmd.String("catalog")) 273 277 if err != nil { 274 278 return err 275 279 } 276 280 277 - pdsHost := cctx.String("pds-host") 278 - genAvatar := !cctx.Bool("no-avatars") 279 - genBanner := !cctx.Bool("no-banners") 280 - jobs := cctx.Int("jobs") 281 + pdsHost := cmd.String("pds-host") 282 + genAvatar := !cmd.Bool("no-avatars") 283 + genBanner := !cmd.Bool("no-banners") 284 + jobs := cmd.Int("jobs") 281 285 282 286 accChan := make(chan fakedata.AccountContext, len(catalog.Celebs)+len(catalog.Regulars)) 283 287 eg := new(errgroup.Group) ··· 303 307 return eg.Wait() 304 308 } 305 309 306 - func genGraph(cctx *cli.Context) error { 307 - catalog, err := fakedata.ReadAccountCatalog(cctx.String("catalog")) 310 + func genGraph(ctx context.Context, cmd *cli.Command) error { 311 + catalog, err := fakedata.ReadAccountCatalog(cmd.String("catalog")) 308 312 if err != nil { 309 313 return err 310 314 } 311 315 312 - pdsHost := cctx.String("pds-host") 313 - maxFollows := cctx.Int("max-follows") 314 - maxMutes := cctx.Int("max-mutes") 315 - jobs := cctx.Int("jobs") 316 + pdsHost := cmd.String("pds-host") 317 + maxFollows := cmd.Int("max-follows") 318 + maxMutes := cmd.Int("max-mutes") 319 + jobs := cmd.Int("jobs") 316 320 317 321 accChan := make(chan fakedata.AccountContext, len(catalog.Celebs)+len(catalog.Regulars)) 318 322 eg := new(errgroup.Group) ··· 338 342 return eg.Wait() 339 343 } 340 344 341 - func genPosts(cctx *cli.Context) error { 342 - catalog, err := fakedata.ReadAccountCatalog(cctx.String("catalog")) 345 + func genPosts(ctx context.Context, cmd *cli.Command) error { 346 + catalog, err := fakedata.ReadAccountCatalog(cmd.String("catalog")) 343 347 if err != nil { 344 348 return err 345 349 } 346 350 347 - pdsHost := cctx.String("pds-host") 348 - maxPosts := cctx.Int("max-posts") 349 - fracImage := cctx.Float64("frac-image") 350 - fracMention := cctx.Float64("frac-mention") 351 - jobs := cctx.Int("jobs") 351 + pdsHost := cmd.String("pds-host") 352 + maxPosts := cmd.Int("max-posts") 353 + fracImage := cmd.Float64("frac-image") 354 + fracMention := cmd.Float64("frac-mention") 355 + jobs := cmd.Int("jobs") 352 356 353 357 accChan := make(chan fakedata.AccountContext, len(catalog.Celebs)+len(catalog.Regulars)) 354 358 eg := new(errgroup.Group) ··· 374 378 return eg.Wait() 375 379 } 376 380 377 - func genInteractions(cctx *cli.Context) error { 378 - catalog, err := fakedata.ReadAccountCatalog(cctx.String("catalog")) 381 + func genInteractions(ctx context.Context, cmd *cli.Command) error { 382 + catalog, err := fakedata.ReadAccountCatalog(cmd.String("catalog")) 379 383 if err != nil { 380 384 return err 381 385 } 382 386 383 - pdsHost := cctx.String("pds-host") 384 - fracLike := cctx.Float64("frac-like") 385 - fracRepost := cctx.Float64("frac-repost") 386 - fracReply := cctx.Float64("frac-reply") 387 - jobs := cctx.Int("jobs") 387 + pdsHost := cmd.String("pds-host") 388 + fracLike := cmd.Float64("frac-like") 389 + fracRepost := cmd.Float64("frac-repost") 390 + fracReply := cmd.Float64("frac-reply") 391 + jobs := cmd.Int("jobs") 388 392 389 393 accChan := make(chan fakedata.AccountContext, len(catalog.Celebs)+len(catalog.Regulars)) 390 394 eg := new(errgroup.Group) ··· 412 416 return eg.Wait() 413 417 } 414 418 415 - func runBrowsing(cctx *cli.Context) error { 416 - catalog, err := fakedata.ReadAccountCatalog(cctx.String("catalog")) 419 + func runBrowsing(ctx context.Context, cmd *cli.Command) error { 420 + catalog, err := fakedata.ReadAccountCatalog(cmd.String("catalog")) 417 421 if err != nil { 418 422 return err 419 423 } 420 424 421 - pdsHost := cctx.String("pds-host") 422 - jobs := cctx.Int("jobs") 425 + pdsHost := cmd.String("pds-host") 426 + jobs := cmd.Int("jobs") 423 427 424 428 accChan := make(chan fakedata.AccountContext, len(catalog.Celebs)+len(catalog.Regulars)) 425 429 eg := new(errgroup.Group) ··· 444 448 close(accChan) 445 449 return eg.Wait() 446 450 } 451 + 452 + func getXrpcClient(cmd *cli.Command, authreq bool) (*xrpc.Client, error) { 453 + h := "http://localhost:4989" 454 + if pdsurl := cmd.String("pds-host"); pdsurl != "" { 455 + h = pdsurl 456 + } 457 + 458 + auth, err := loadAuthFromEnv(cmd, authreq) 459 + if err != nil { 460 + return nil, fmt.Errorf("loading auth: %w", err) 461 + } 462 + 463 + return &xrpc.Client{ 464 + Client: cliutil.NewHttpClient(), 465 + Host: h, 466 + Auth: auth, 467 + }, nil 468 + } 469 + 470 + func loadAuthFromEnv(cmd *cli.Command, req bool) (*xrpc.AuthInfo, error) { 471 + if a := cmd.String("auth"); a != "" { 472 + if ai, err := cliutil.ReadAuth(a); err != nil && req { 473 + return nil, err 474 + } else { 475 + return ai, nil 476 + } 477 + } 478 + 479 + val := os.Getenv("ATP_AUTH_FILE") 480 + if val == "" { 481 + if req { 482 + return nil, fmt.Errorf("no auth env present, ATP_AUTH_FILE not set") 483 + } 484 + 485 + return nil, nil 486 + } 487 + 488 + var auth xrpc.AuthInfo 489 + if err := json.Unmarshal([]byte(val), &auth); err != nil { 490 + return nil, err 491 + } 492 + 493 + return &auth, nil 494 + }
+98 -102
cmd/hepa/main.go
··· 22 22 "github.com/bluesky-social/indigo/automod/consumer" 23 23 24 24 "github.com/earthboundkid/versioninfo/v2" 25 - "github.com/urfave/cli/v2" 25 + "github.com/urfave/cli/v3" 26 26 "golang.org/x/time/rate" 27 27 ) 28 28 ··· 35 35 36 36 func run(args []string) error { 37 37 38 - app := cli.App{ 38 + app := cli.Command{ 39 39 Name: "hepa", 40 40 Usage: "automod daemon (cleans the atmosphere)", 41 41 Version: versioninfo.Short(), ··· 46 46 Name: "atp-relay-host", 47 47 Usage: "hostname and port of Relay to subscribe to", 48 48 Value: "wss://bsky.network", 49 - EnvVars: []string{"ATP_RELAY_HOST", "ATP_BGS_HOST"}, 49 + Sources: cli.EnvVars("ATP_RELAY_HOST", "ATP_BGS_HOST"), 50 50 }, 51 51 &cli.StringFlag{ 52 52 Name: "atp-plc-host", 53 53 Usage: "method, hostname, and port of PLC registry", 54 54 Value: "https://plc.directory", 55 - EnvVars: []string{"ATP_PLC_HOST"}, 55 + Sources: cli.EnvVars("ATP_PLC_HOST"), 56 56 }, 57 57 &cli.StringFlag{ 58 58 Name: "atp-bsky-host", 59 59 Usage: "method, hostname, and port of bsky API (appview) service. does not use auth", 60 60 Value: "https://public.api.bsky.app", 61 - EnvVars: []string{"ATP_BSKY_HOST"}, 61 + Sources: cli.EnvVars("ATP_BSKY_HOST"), 62 62 }, 63 63 &cli.StringFlag{ 64 64 Name: "atp-ozone-host", 65 65 Usage: "method, hostname, and port of ozone instance. requires ozone-admin-token as well", 66 66 Value: "https://mod.bsky.app", 67 - EnvVars: []string{"ATP_OZONE_HOST", "ATP_MOD_HOST"}, 67 + Sources: cli.EnvVars("ATP_OZONE_HOST", "ATP_MOD_HOST"), 68 68 }, 69 69 &cli.StringFlag{ 70 70 Name: "ozone-did", 71 71 Usage: "DID of account to attribute ozone actions to", 72 - EnvVars: []string{"HEPA_OZONE_DID"}, 72 + Sources: cli.EnvVars("HEPA_OZONE_DID"), 73 73 }, 74 74 &cli.StringFlag{ 75 75 Name: "ozone-admin-token", 76 76 Usage: "admin authentication password for mod service", 77 - EnvVars: []string{"HEPA_OZONE_AUTH_ADMIN_TOKEN", "HEPA_MOD_AUTH_ADMIN_TOKEN"}, 77 + Sources: cli.EnvVars("HEPA_OZONE_AUTH_ADMIN_TOKEN", "HEPA_MOD_AUTH_ADMIN_TOKEN"), 78 78 }, 79 79 &cli.StringFlag{ 80 80 Name: "atp-pds-host", 81 81 Usage: "method, hostname, and port of PDS (or entryway) for admin account info; uses admin auth", 82 82 Value: "https://bsky.social", 83 - EnvVars: []string{"ATP_PDS_HOST"}, 83 + Sources: cli.EnvVars("ATP_PDS_HOST"), 84 84 }, 85 85 &cli.StringFlag{ 86 86 Name: "pds-admin-token", 87 87 Usage: "admin authentication password for PDS (or entryway)", 88 - EnvVars: []string{"HEPA_PDS_AUTH_ADMIN_TOKEN"}, 88 + Sources: cli.EnvVars("HEPA_PDS_AUTH_ADMIN_TOKEN"), 89 89 }, 90 90 &cli.StringFlag{ 91 91 Name: "redis-url", 92 92 Usage: "redis connection URL", 93 93 // redis://<user>:<pass>@localhost:6379/<db> 94 94 // redis://localhost:6379/0 95 - EnvVars: []string{"HEPA_REDIS_URL"}, 95 + Sources: cli.EnvVars("HEPA_REDIS_URL"), 96 96 }, 97 97 &cli.IntFlag{ 98 98 Name: "plc-rate-limit", 99 99 Usage: "max number of requests per second to PLC registry", 100 100 Value: 100, 101 - EnvVars: []string{"HEPA_PLC_RATE_LIMIT"}, 101 + Sources: cli.EnvVars("HEPA_PLC_RATE_LIMIT"), 102 102 }, 103 103 &cli.StringFlag{ 104 104 Name: "sets-json-path", 105 105 Usage: "file path of JSON file containing static sets", 106 - EnvVars: []string{"HEPA_SETS_JSON_PATH"}, 106 + Sources: cli.EnvVars("HEPA_SETS_JSON_PATH"), 107 107 }, 108 108 &cli.StringFlag{ 109 109 Name: "hiveai-api-token", 110 110 Usage: "API token for Hive AI image auto-labeling", 111 - EnvVars: []string{"HIVEAI_API_TOKEN"}, 111 + Sources: cli.EnvVars("HIVEAI_API_TOKEN"), 112 112 }, 113 113 &cli.StringFlag{ 114 114 Name: "abyss-host", 115 115 Usage: "host for abusive image scanning API (scheme, host, port)", 116 - EnvVars: []string{"ABYSS_HOST"}, 116 + Sources: cli.EnvVars("ABYSS_HOST"), 117 117 }, 118 118 &cli.StringFlag{ 119 119 Name: "abyss-password", 120 120 Usage: "admin auth password for abyss API", 121 - EnvVars: []string{"ABYSS_PASSWORD"}, 121 + Sources: cli.EnvVars("ABYSS_PASSWORD"), 122 122 }, 123 123 &cli.StringFlag{ 124 124 Name: "ruleset", 125 125 Usage: "which ruleset config to use: default, no-blobs, only-blobs", 126 - EnvVars: []string{"HEPA_RULESET"}, 126 + Sources: cli.EnvVars("HEPA_RULESET"), 127 127 }, 128 128 &cli.StringFlag{ 129 129 Name: "log-level", 130 130 Usage: "log verbosity level (eg: warn, info, debug)", 131 - EnvVars: []string{"HEPA_LOG_LEVEL", "LOG_LEVEL"}, 131 + Sources: cli.EnvVars("HEPA_LOG_LEVEL", "LOG_LEVEL"), 132 132 }, 133 133 &cli.StringFlag{ 134 134 Name: "ratelimit-bypass", 135 135 Usage: "HTTP header to bypass ratelimits", 136 - EnvVars: []string{"HEPA_RATELIMIT_BYPASS", "RATELIMIT_BYPASS"}, 136 + Sources: cli.EnvVars("HEPA_RATELIMIT_BYPASS", "RATELIMIT_BYPASS"), 137 137 }, 138 138 &cli.IntFlag{ 139 139 Name: "firehose-parallelism", 140 140 Usage: "force a fixed number of parallel firehose workers. default (or 0) for auto-scaling; 200 works for a large instance", 141 - EnvVars: []string{"HEPA_FIREHOSE_PARALLELISM"}, 141 + Sources: cli.EnvVars("HEPA_FIREHOSE_PARALLELISM"), 142 142 }, 143 143 &cli.StringFlag{ 144 144 Name: "prescreen-host", 145 145 Usage: "hostname of prescreen server", 146 - EnvVars: []string{"HEPA_PRESCREEN_HOST"}, 146 + Sources: cli.EnvVars("HEPA_PRESCREEN_HOST"), 147 147 }, 148 148 &cli.StringFlag{ 149 149 Name: "prescreen-token", 150 150 Usage: "secret token for prescreen server", 151 - EnvVars: []string{"HEPA_PRESCREEN_TOKEN"}, 151 + Sources: cli.EnvVars("HEPA_PRESCREEN_TOKEN"), 152 152 }, 153 153 &cli.DurationFlag{ 154 154 Name: "report-dupe-period", 155 155 Usage: "time period within which automod will not re-report an account for the same reasonType", 156 - EnvVars: []string{"HEPA_REPORT_DUPE_PERIOD"}, 156 + Sources: cli.EnvVars("HEPA_REPORT_DUPE_PERIOD"), 157 157 Value: 1 * 24 * time.Hour, 158 158 }, 159 159 &cli.IntFlag{ 160 160 Name: "quota-mod-report-day", 161 161 Usage: "number of reports automod can file per day, for all subjects and types combined (circuit breaker)", 162 - EnvVars: []string{"HEPA_QUOTA_MOD_REPORT_DAY"}, 162 + Sources: cli.EnvVars("HEPA_QUOTA_MOD_REPORT_DAY"), 163 163 Value: 10000, 164 164 }, 165 165 &cli.IntFlag{ 166 166 Name: "quota-mod-takedown-day", 167 167 Usage: "number of takedowns automod can action per day, for all subjects combined (circuit breaker)", 168 - EnvVars: []string{"HEPA_QUOTA_MOD_TAKEDOWN_DAY"}, 168 + Sources: cli.EnvVars("HEPA_QUOTA_MOD_TAKEDOWN_DAY"), 169 169 Value: 200, 170 170 }, 171 171 &cli.IntFlag{ 172 172 Name: "quota-mod-action-day", 173 173 Usage: "number of misc actions automod can do per day, for all subjects combined (circuit breaker)", 174 - EnvVars: []string{"HEPA_QUOTA_MOD_ACTION_DAY"}, 174 + Sources: cli.EnvVars("HEPA_QUOTA_MOD_ACTION_DAY"), 175 175 Value: 2000, 176 176 }, 177 177 &cli.DurationFlag{ 178 178 Name: "record-event-timeout", 179 179 Usage: "total processing time for record events (including setup, rules, and persisting)", 180 - EnvVars: []string{"HEPA_RECORD_EVENT_TIMEOUT"}, 180 + Sources: cli.EnvVars("HEPA_RECORD_EVENT_TIMEOUT"), 181 181 Value: 30 * time.Second, 182 182 }, 183 183 &cli.DurationFlag{ 184 184 Name: "identity-event-timeout", 185 185 Usage: "total processing time for identity and account events (including setup, rules, and persisting)", 186 - EnvVars: []string{"HEPA_IDENTITY_EVENT_TIMEOUT"}, 186 + Sources: cli.EnvVars("HEPA_IDENTITY_EVENT_TIMEOUT"), 187 187 Value: 10 * time.Second, 188 188 }, 189 189 &cli.DurationFlag{ 190 190 Name: "ozone-event-timeout", 191 191 Usage: "total processing time for ozone events (including setup, rules, and persisting)", 192 - EnvVars: []string{"HEPA_OZONE_EVENT_TIMEOUT"}, 192 + Sources: cli.EnvVars("HEPA_OZONE_EVENT_TIMEOUT"), 193 193 Value: 30 * time.Second, 194 194 }, 195 195 } ··· 201 201 captureRecentCmd, 202 202 } 203 203 204 - return app.Run(args) 204 + return app.Run(context.Background(), args) 205 205 } 206 206 207 - func configDirectory(cctx *cli.Context) (identity.Directory, error) { 207 + func configDirectory(cmd *cli.Command) (identity.Directory, error) { 208 208 baseDir := identity.BaseDirectory{ 209 - PLCURL: cctx.String("atp-plc-host"), 209 + PLCURL: cmd.String("atp-plc-host"), 210 210 HTTPClient: http.Client{ 211 211 Timeout: time.Second * 15, 212 212 }, 213 - PLCLimiter: rate.NewLimiter(rate.Limit(cctx.Int("plc-rate-limit")), 1), 213 + PLCLimiter: rate.NewLimiter(rate.Limit(cmd.Int("plc-rate-limit")), 1), 214 214 TryAuthoritativeDNS: true, 215 215 SkipDNSDomainSuffixes: []string{".bsky.social", ".staging.bsky.dev"}, 216 216 } 217 217 var dir identity.Directory 218 - if cctx.String("redis-url") != "" { 219 - rdir, err := redisdir.NewRedisDirectory(&baseDir, cctx.String("redis-url"), time.Hour*24, time.Minute*2, time.Minute*5, 10_000) 218 + if cmd.String("redis-url") != "" { 219 + rdir, err := redisdir.NewRedisDirectory(&baseDir, cmd.String("redis-url"), time.Hour*24, time.Minute*2, time.Minute*5, 10_000) 220 220 if err != nil { 221 221 return nil, err 222 222 } ··· 228 228 return dir, nil 229 229 } 230 230 231 - func configLogger(cctx *cli.Context, writer io.Writer) *slog.Logger { 231 + func configLogger(cmd *cli.Command, writer io.Writer) *slog.Logger { 232 232 var level slog.Level 233 - switch strings.ToLower(cctx.String("log-level")) { 233 + switch strings.ToLower(cmd.String("log-level")) { 234 234 case "error": 235 235 level = slog.LevelError 236 236 case "warn": ··· 257 257 Name: "metrics-listen", 258 258 Usage: "IP or address, and port, to listen on for metrics APIs", 259 259 Value: ":3989", 260 - EnvVars: []string{"HEPA_METRICS_LISTEN"}, 260 + Sources: cli.EnvVars("HEPA_METRICS_LISTEN"), 261 261 }, 262 262 &cli.StringFlag{ 263 263 Name: "slack-webhook-url", 264 264 // eg: https://hooks.slack.com/services/X1234 265 265 Usage: "full URL of slack webhook", 266 - EnvVars: []string{"SLACK_WEBHOOK_URL"}, 266 + Sources: cli.EnvVars("SLACK_WEBHOOK_URL"), 267 267 }, 268 268 }, 269 - Action: func(cctx *cli.Context) error { 270 - ctx := context.Background() 271 - logger := configLogger(cctx, os.Stdout) 269 + Action: func(ctx context.Context, cmd *cli.Command) error { 270 + logger := configLogger(cmd, os.Stdout) 272 271 configOTEL("hepa") 273 272 274 - dir, err := configDirectory(cctx) 273 + dir, err := configDirectory(cmd) 275 274 if err != nil { 276 275 return fmt.Errorf("failed to configure identity directory: %v", err) 277 276 } ··· 280 279 dir, 281 280 Config{ 282 281 Logger: logger, 283 - BskyHost: cctx.String("atp-bsky-host"), 284 - OzoneHost: cctx.String("atp-ozone-host"), 285 - OzoneDID: cctx.String("ozone-did"), 286 - OzoneAdminToken: cctx.String("ozone-admin-token"), 287 - PDSHost: cctx.String("atp-pds-host"), 288 - PDSAdminToken: cctx.String("pds-admin-token"), 289 - SetsFileJSON: cctx.String("sets-json-path"), 290 - RedisURL: cctx.String("redis-url"), 291 - SlackWebhookURL: cctx.String("slack-webhook-url"), 292 - HiveAPIToken: cctx.String("hiveai-api-token"), 293 - AbyssHost: cctx.String("abyss-host"), 294 - AbyssPassword: cctx.String("abyss-password"), 295 - RatelimitBypass: cctx.String("ratelimit-bypass"), 296 - RulesetName: cctx.String("ruleset"), 297 - PreScreenHost: cctx.String("prescreen-host"), 298 - PreScreenToken: cctx.String("prescreen-token"), 299 - ReportDupePeriod: cctx.Duration("report-dupe-period"), 300 - QuotaModReportDay: cctx.Int("quota-mod-report-day"), 301 - QuotaModTakedownDay: cctx.Int("quota-mod-takedown-day"), 302 - QuotaModActionDay: cctx.Int("quota-mod-action-day"), 303 - RecordEventTimeout: cctx.Duration("record-event-timeout"), 304 - IdentityEventTimeout: cctx.Duration("identity-event-timeout"), 305 - OzoneEventTimeout: cctx.Duration("ozone-event-timeout"), 282 + BskyHost: cmd.String("atp-bsky-host"), 283 + OzoneHost: cmd.String("atp-ozone-host"), 284 + OzoneDID: cmd.String("ozone-did"), 285 + OzoneAdminToken: cmd.String("ozone-admin-token"), 286 + PDSHost: cmd.String("atp-pds-host"), 287 + PDSAdminToken: cmd.String("pds-admin-token"), 288 + SetsFileJSON: cmd.String("sets-json-path"), 289 + RedisURL: cmd.String("redis-url"), 290 + SlackWebhookURL: cmd.String("slack-webhook-url"), 291 + HiveAPIToken: cmd.String("hiveai-api-token"), 292 + AbyssHost: cmd.String("abyss-host"), 293 + AbyssPassword: cmd.String("abyss-password"), 294 + RatelimitBypass: cmd.String("ratelimit-bypass"), 295 + RulesetName: cmd.String("ruleset"), 296 + PreScreenHost: cmd.String("prescreen-host"), 297 + PreScreenToken: cmd.String("prescreen-token"), 298 + ReportDupePeriod: cmd.Duration("report-dupe-period"), 299 + QuotaModReportDay: cmd.Int("quota-mod-report-day"), 300 + QuotaModTakedownDay: cmd.Int("quota-mod-takedown-day"), 301 + QuotaModActionDay: cmd.Int("quota-mod-action-day"), 302 + RecordEventTimeout: cmd.Duration("record-event-timeout"), 303 + IdentityEventTimeout: cmd.Duration("identity-event-timeout"), 304 + OzoneEventTimeout: cmd.Duration("ozone-event-timeout"), 306 305 }, 307 306 ) 308 307 if err != nil { ··· 335 334 go func() { 336 335 runtime.SetBlockProfileRate(10) 337 336 runtime.SetMutexProfileFraction(10) 338 - if err := srv.RunMetrics(cctx.String("metrics-listen")); err != nil { 337 + if err := srv.RunMetrics(cmd.String("metrics-listen")); err != nil { 339 338 slog.Error("failed to start metrics endpoint", "error", err) 340 339 panic(fmt.Errorf("failed to start metrics endpoint: %w", err)) 341 340 } 342 341 }() 343 342 344 343 // firehose event consumer (note this is actually mandatory) 345 - relayHost := cctx.String("atp-relay-host") 344 + relayHost := cmd.String("atp-relay-host") 346 345 if relayHost != "" { 347 346 fc := consumer.FirehoseConsumer{ 348 347 Engine: srv.Engine, 349 348 Logger: logger.With("subsystem", "firehose-consumer"), 350 - Host: cctx.String("atp-relay-host"), 351 - Parallelism: cctx.Int("firehose-parallelism"), 349 + Host: cmd.String("atp-relay-host"), 350 + Parallelism: cmd.Int("firehose-parallelism"), 352 351 RedisClient: srv.RedisClient, 353 352 } 354 353 ··· 368 367 } 369 368 370 369 // for simple commands, not long-running daemons 371 - func configEphemeralServer(cctx *cli.Context) (*Server, error) { 370 + func configEphemeralServer(cmd *cli.Command) (*Server, error) { 372 371 // NOTE: using stderr not stdout because some commands print to stdout 373 - logger := configLogger(cctx, os.Stderr) 372 + logger := configLogger(cmd, os.Stderr) 374 373 375 - dir, err := configDirectory(cctx) 374 + dir, err := configDirectory(cmd) 376 375 if err != nil { 377 376 return nil, err 378 377 } ··· 381 380 dir, 382 381 Config{ 383 382 Logger: logger, 384 - BskyHost: cctx.String("atp-bsky-host"), 385 - OzoneHost: cctx.String("atp-ozone-host"), 386 - OzoneDID: cctx.String("ozone-did"), 387 - OzoneAdminToken: cctx.String("ozone-admin-token"), 388 - PDSHost: cctx.String("atp-pds-host"), 389 - PDSAdminToken: cctx.String("pds-admin-token"), 390 - SetsFileJSON: cctx.String("sets-json-path"), 391 - RedisURL: cctx.String("redis-url"), 392 - HiveAPIToken: cctx.String("hiveai-api-token"), 393 - AbyssHost: cctx.String("abyss-host"), 394 - AbyssPassword: cctx.String("abyss-password"), 395 - RatelimitBypass: cctx.String("ratelimit-bypass"), 396 - RulesetName: cctx.String("ruleset"), 397 - PreScreenHost: cctx.String("prescreen-host"), 398 - PreScreenToken: cctx.String("prescreen-token"), 383 + BskyHost: cmd.String("atp-bsky-host"), 384 + OzoneHost: cmd.String("atp-ozone-host"), 385 + OzoneDID: cmd.String("ozone-did"), 386 + OzoneAdminToken: cmd.String("ozone-admin-token"), 387 + PDSHost: cmd.String("atp-pds-host"), 388 + PDSAdminToken: cmd.String("pds-admin-token"), 389 + SetsFileJSON: cmd.String("sets-json-path"), 390 + RedisURL: cmd.String("redis-url"), 391 + HiveAPIToken: cmd.String("hiveai-api-token"), 392 + AbyssHost: cmd.String("abyss-host"), 393 + AbyssPassword: cmd.String("abyss-password"), 394 + RatelimitBypass: cmd.String("ratelimit-bypass"), 395 + RulesetName: cmd.String("ruleset"), 396 + PreScreenHost: cmd.String("prescreen-host"), 397 + PreScreenToken: cmd.String("prescreen-token"), 399 398 }, 400 399 ) 401 400 } ··· 405 404 Usage: "process a single record in isolation", 406 405 ArgsUsage: `<at-uri>`, 407 406 Flags: []cli.Flag{}, 408 - Action: func(cctx *cli.Context) error { 409 - ctx := context.Background() 410 - uriArg := cctx.Args().First() 407 + Action: func(ctx context.Context, cmd *cli.Command) error { 408 + uriArg := cmd.Args().First() 411 409 if uriArg == "" { 412 410 return fmt.Errorf("expected a single AT-URI argument") 413 411 } ··· 416 414 return fmt.Errorf("not a valid AT-URI: %v", err) 417 415 } 418 416 419 - srv, err := configEphemeralServer(cctx) 417 + srv, err := configEphemeralServer(cmd) 420 418 if err != nil { 421 419 return err 422 420 } ··· 436 434 Value: 20, 437 435 }, 438 436 }, 439 - Action: func(cctx *cli.Context) error { 440 - ctx := context.Background() 441 - idArg := cctx.Args().First() 437 + Action: func(ctx context.Context, cmd *cli.Command) error { 438 + idArg := cmd.Args().First() 442 439 if idArg == "" { 443 440 return fmt.Errorf("expected a single AT identifier (handle or DID) argument") 444 441 } ··· 447 444 return fmt.Errorf("not a valid handle or DID: %v", err) 448 445 } 449 446 450 - srv, err := configEphemeralServer(cctx) 447 + srv, err := configEphemeralServer(cmd) 451 448 if err != nil { 452 449 return err 453 450 } 454 451 455 - return capture.FetchAndProcessRecent(ctx, srv.Engine, *atid, cctx.Int("limit")) 452 + return capture.FetchAndProcessRecent(ctx, srv.Engine, *atid, cmd.Int("limit")) 456 453 }, 457 454 } 458 455 ··· 467 464 Value: 20, 468 465 }, 469 466 }, 470 - Action: func(cctx *cli.Context) error { 471 - ctx := context.Background() 472 - idArg := cctx.Args().First() 467 + Action: func(ctx context.Context, cmd *cli.Command) error { 468 + idArg := cmd.Args().First() 473 469 if idArg == "" { 474 470 return fmt.Errorf("expected a single AT identifier (handle or DID) argument") 475 471 } ··· 478 474 return fmt.Errorf("not a valid handle or DID: %v", err) 479 475 } 480 476 481 - srv, err := configEphemeralServer(cctx) 477 + srv, err := configEphemeralServer(cmd) 482 478 if err != nil { 483 479 return err 484 480 } 485 481 486 - cap, err := capture.CaptureRecent(ctx, srv.Engine, *atid, cctx.Int("limit")) 482 + cap, err := capture.CaptureRecent(ctx, srv.Engine, *atid, cmd.Int("limit")) 487 483 if err != nil { 488 484 return err 489 485 }
+17 -19
cmd/netsync/main.go
··· 29 29 "github.com/prometheus/client_golang/prometheus" 30 30 "github.com/prometheus/client_golang/prometheus/promauto" 31 31 "github.com/prometheus/client_golang/prometheus/promhttp" 32 - "github.com/urfave/cli/v2" 32 + "github.com/urfave/cli/v3" 33 33 "golang.org/x/time/rate" 34 34 ) 35 35 36 36 func main() { 37 - app := cli.App{ 37 + app := cli.Command{ 38 38 Name: "netsync", 39 39 Usage: "atproto network cloning tool", 40 40 Version: versioninfo.Short(), ··· 80 80 Name: "magic-header-key", 81 81 Usage: "header key to send with checkout request", 82 82 Value: "", 83 - EnvVars: []string{"MAGIC_HEADER_KEY"}, 83 + Sources: cli.EnvVars("MAGIC_HEADER_KEY"), 84 84 }, 85 85 &cli.StringFlag{ 86 86 Name: "magic-header-val", 87 87 Usage: "header value to send with checkout request", 88 88 Value: "", 89 - EnvVars: []string{"MAGIC_HEADER_VAL"}, 89 + Sources: cli.EnvVars("MAGIC_HEADER_VAL"), 90 90 }, 91 91 } 92 92 ··· 94 94 { 95 95 Name: "retry", 96 96 Usage: "requeue failed repos", 97 - Action: func(cctx *cli.Context) error { 97 + Action: func(ctx context.Context, cmd *cli.Command) error { 98 98 state := &NetsyncState{ 99 - StatePath: cctx.String("state-file"), 99 + StatePath: cmd.String("state-file"), 100 100 } 101 101 102 102 err := state.Resume() ··· 123 123 124 124 app.Action = Netsync 125 125 126 - err := app.Run(os.Args) 127 - if err != nil { 126 + if err := app.Run(context.Background(), os.Args); err != nil { 128 127 log.Fatal(err) 129 128 } 130 129 } ··· 260 259 delete(s.EnqueuedRepos, repo) 261 260 } 262 261 263 - func Netsync(cctx *cli.Context) error { 264 - ctx := cctx.Context 262 + func Netsync(ctx context.Context, cmd *cli.Command) error { 265 263 ctx, cancel := context.WithCancel(ctx) 266 264 defer cancel() 267 265 ··· 270 268 slog.SetDefault(slog.New(logger.Handler())) 271 269 272 270 state := &NetsyncState{ 273 - StatePath: cctx.String("state-file"), 274 - CheckoutPath: cctx.String("checkout-path"), 271 + StatePath: cmd.String("state-file"), 272 + CheckoutPath: cmd.String("checkout-path"), 275 273 276 - outDir: cctx.String("out-dir"), 277 - workerCount: cctx.Int("worker-count"), 278 - limiter: rate.NewLimiter(rate.Limit(cctx.Float64("checkout-limit")), 1), 279 - magicHeaderKey: cctx.String("magic-header-key"), 280 - magicHeaderVal: cctx.String("magic-header-val"), 274 + outDir: cmd.String("out-dir"), 275 + workerCount: cmd.Int("worker-count"), 276 + limiter: rate.NewLimiter(rate.Limit(cmd.Float64("checkout-limit")), 1), 277 + magicHeaderKey: cmd.String("magic-header-key"), 278 + magicHeaderVal: cmd.String("magic-header-val"), 281 279 282 280 exit: make(chan struct{}), 283 281 wg: sync.WaitGroup{}, ··· 317 315 318 316 if err != nil { 319 317 // Read repo list 320 - repoListFile, err := os.Open(cctx.String("repo-list")) 318 + repoListFile, err := os.Open(cmd.String("repo-list")) 321 319 if err != nil { 322 320 return err 323 321 } ··· 341 339 mux.Handle("/metrics", promhttp.Handler()) 342 340 343 341 metricsServer := &http.Server{ 344 - Addr: fmt.Sprintf(":%d", cctx.Int("port")), 342 + Addr: fmt.Sprintf(":%d", cmd.Int("port")), 345 343 Handler: mux, 346 344 } 347 345
+70 -70
cmd/palomar/main.go
··· 20 20 21 21 "github.com/earthboundkid/versioninfo/v2" 22 22 es "github.com/opensearch-project/opensearch-go/v2" 23 - "github.com/urfave/cli/v2" 23 + "github.com/urfave/cli/v3" 24 24 "go.opentelemetry.io/otel" 25 25 "go.opentelemetry.io/otel/attribute" 26 26 "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" ··· 39 39 40 40 func run(args []string) error { 41 41 42 - app := cli.App{ 42 + app := cli.Command{ 43 43 Name: "palomar", 44 44 Usage: "search indexing and query service (using ES or OS)", 45 45 Version: versioninfo.Short(), ··· 49 49 &cli.StringFlag{ 50 50 Name: "elastic-cert-file", 51 51 Usage: "certificate file path", 52 - EnvVars: []string{"ES_CERT_FILE", "ELASTIC_CERT_FILE"}, 52 + Sources: cli.EnvVars("ES_CERT_FILE", "ELASTIC_CERT_FILE"), 53 53 }, 54 54 &cli.BoolFlag{ 55 55 Name: "elastic-insecure-ssl", 56 56 Usage: "if true, disable SSL cert validation", 57 - EnvVars: []string{"ES_INSECURE_SSL"}, 57 + Sources: cli.EnvVars("ES_INSECURE_SSL"), 58 58 }, 59 59 &cli.StringFlag{ 60 60 Name: "elastic-username", 61 61 Usage: "elasticsearch username", 62 62 Value: "admin", 63 - EnvVars: []string{"ES_USERNAME", "ELASTIC_USERNAME"}, 63 + Sources: cli.EnvVars("ES_USERNAME", "ELASTIC_USERNAME"), 64 64 }, 65 65 &cli.StringFlag{ 66 66 Name: "elastic-password", 67 67 Usage: "elasticsearch password", 68 68 Value: "0penSearch-Pal0mar", 69 - EnvVars: []string{"ES_PASSWORD", "ELASTIC_PASSWORD"}, 69 + Sources: cli.EnvVars("ES_PASSWORD", "ELASTIC_PASSWORD"), 70 70 }, 71 71 &cli.StringFlag{ 72 72 Name: "elastic-hosts", 73 73 Usage: "elasticsearch hosts (schema/host/port)", 74 74 Value: "http://localhost:9200", 75 - EnvVars: []string{"ES_HOSTS", "ELASTIC_HOSTS", "OPENSEARCH_URL", "ELASTICSEARCH_URL"}, 75 + Sources: cli.EnvVars("ES_HOSTS", "ELASTIC_HOSTS", "OPENSEARCH_URL", "ELASTICSEARCH_URL"), 76 76 }, 77 77 &cli.StringFlag{ 78 78 Name: "es-post-index", 79 79 Usage: "ES index for 'post' documents", 80 80 Value: "palomar_post", 81 - EnvVars: []string{"ES_POST_INDEX"}, 81 + Sources: cli.EnvVars("ES_POST_INDEX"), 82 82 }, 83 83 &cli.StringFlag{ 84 84 Name: "es-profile-index", 85 85 Usage: "ES index for 'profile' documents", 86 86 Value: "palomar_profile", 87 - EnvVars: []string{"ES_PROFILE_INDEX"}, 87 + Sources: cli.EnvVars("ES_PROFILE_INDEX"), 88 88 }, 89 89 &cli.StringFlag{ 90 90 Name: "atp-relay-host", 91 91 Usage: "hostname and port of Relay to subscribe to", 92 92 Value: "wss://bsky.network", 93 - EnvVars: []string{"ATP_RELAY_HOST", "ATP_BGS_HOST"}, 93 + Sources: cli.EnvVars("ATP_RELAY_HOST", "ATP_BGS_HOST"), 94 94 }, 95 95 &cli.StringFlag{ 96 96 Name: "atp-plc-host", 97 97 Usage: "method, hostname, and port of PLC registry", 98 98 Value: "https://plc.directory", 99 - EnvVars: []string{"ATP_PLC_HOST"}, 99 + Sources: cli.EnvVars("ATP_PLC_HOST"), 100 100 }, 101 101 &cli.IntFlag{ 102 102 Name: "max-metadb-connections", 103 - EnvVars: []string{"MAX_METADB_CONNECTIONS"}, 103 + Sources: cli.EnvVars("MAX_METADB_CONNECTIONS"), 104 104 Value: 40, 105 105 }, 106 106 &cli.StringFlag{ 107 107 Name: "log-level", 108 108 Usage: "log level (debug, info, warn, error)", 109 109 Value: "info", 110 - EnvVars: []string{"GOLOG_LOG_LEVEL", "LOG_LEVEL"}, 110 + Sources: cli.EnvVars("GOLOG_LOG_LEVEL", "LOG_LEVEL"), 111 111 }, 112 112 } 113 113 ··· 118 118 searchProfileCmd, 119 119 } 120 120 121 - return app.Run(args) 121 + return app.Run(context.Background(), args) 122 122 } 123 123 124 124 var runCmd = &cli.Command{ ··· 128 128 &cli.StringFlag{ 129 129 Name: "database-url", 130 130 Value: "sqlite://data/palomar/search.db", 131 - EnvVars: []string{"DATABASE_URL"}, 131 + Sources: cli.EnvVars("DATABASE_URL"), 132 132 }, 133 133 &cli.BoolFlag{ 134 134 Name: "readonly", 135 - EnvVars: []string{"PALOMAR_READONLY", "READONLY"}, 135 + Sources: cli.EnvVars("PALOMAR_READONLY", "READONLY"), 136 136 }, 137 137 &cli.StringFlag{ 138 138 Name: "bind", 139 139 Usage: "IP or address, and port, to listen on for HTTP APIs", 140 140 Value: ":3999", 141 - EnvVars: []string{"PALOMAR_BIND"}, 141 + Sources: cli.EnvVars("PALOMAR_BIND"), 142 142 }, 143 143 &cli.StringFlag{ 144 144 Name: "metrics-listen", 145 145 Usage: "IP or address, and port, to listen on for metrics APIs", 146 146 Value: ":3998", 147 - EnvVars: []string{"PALOMAR_METRICS_LISTEN"}, 147 + Sources: cli.EnvVars("PALOMAR_METRICS_LISTEN"), 148 148 }, 149 149 &cli.IntFlag{ 150 150 Name: "relay-sync-rate-limit", 151 151 Usage: "max repo sync (checkout) requests per second to upstream (Relay)", 152 152 Value: 8, 153 - EnvVars: []string{"PALOMAR_RELAY_SYNC_RATE_LIMIT", "PALOMAR_BGS_SYNC_RATE_LIMIT"}, 153 + Sources: cli.EnvVars("PALOMAR_RELAY_SYNC_RATE_LIMIT", "PALOMAR_BGS_SYNC_RATE_LIMIT"), 154 154 }, 155 155 &cli.IntFlag{ 156 156 Name: "index-max-concurrency", 157 157 Usage: "max number of concurrent index requests (HTTP POST) to search index", 158 158 Value: 20, 159 - EnvVars: []string{"PALOMAR_INDEX_MAX_CONCURRENCY"}, 159 + Sources: cli.EnvVars("PALOMAR_INDEX_MAX_CONCURRENCY"), 160 160 }, 161 161 &cli.IntFlag{ 162 162 Name: "indexing-rate-limit", 163 163 Usage: "max number of documents per second to index", 164 164 Value: 50_000, 165 - EnvVars: []string{"PALOMAR_INDEXING_RATE_LIMIT"}, 165 + Sources: cli.EnvVars("PALOMAR_INDEXING_RATE_LIMIT"), 166 166 }, 167 167 &cli.IntFlag{ 168 168 Name: "plc-rate-limit", 169 169 Usage: "max number of requests per second to PLC registry", 170 170 Value: 100, 171 - EnvVars: []string{"PALOMAR_PLC_RATE_LIMIT"}, 171 + Sources: cli.EnvVars("PALOMAR_PLC_RATE_LIMIT"), 172 172 }, 173 173 &cli.BoolFlag{ 174 174 Name: "discover-repos", 175 175 Usage: "if true, discover repositories from the Relay", 176 - EnvVars: []string{"PALOMAR_DISCOVER_REPOS"}, 176 + Sources: cli.EnvVars("PALOMAR_DISCOVER_REPOS"), 177 177 Value: false, 178 178 }, 179 179 &cli.StringFlag{ 180 180 Name: "pagerank-file", 181 - EnvVars: []string{"PAGERANK_FILE"}, 181 + Sources: cli.EnvVars("PAGERANK_FILE"), 182 182 }, 183 183 &cli.StringFlag{ 184 184 Name: "bulk-posts-file", 185 - EnvVars: []string{"BULK_POSTS_FILE"}, 185 + Sources: cli.EnvVars("BULK_POSTS_FILE"), 186 186 }, 187 187 &cli.StringFlag{ 188 188 Name: "bulk-profiles-file", 189 - EnvVars: []string{"BULK_PROFILES_FILE"}, 189 + Sources: cli.EnvVars("BULK_PROFILES_FILE"), 190 190 }, 191 191 }, 192 - Action: func(cctx *cli.Context) error { 192 + Action: func(ctx context.Context, cmd *cli.Command) error { 193 193 logLevel := slog.LevelInfo 194 - switch cctx.String("log-level") { 194 + switch cmd.String("log-level") { 195 195 case "debug": 196 196 logLevel = slog.LevelDebug 197 197 case "info": ··· 208 208 })) 209 209 slog.SetDefault(logger) 210 210 211 - readonly := cctx.Bool("readonly") 211 + readonly := cmd.Bool("readonly") 212 212 213 213 // Enable OTLP HTTP exporter 214 214 // For relevant environment variables: ··· 245 245 otel.SetTracerProvider(tp) 246 246 } 247 247 248 - escli, err := createEsClient(cctx) 248 + escli, err := createEsClient(cmd) 249 249 if err != nil { 250 250 return fmt.Errorf("failed to get elasticsearch: %w", err) 251 251 } 252 252 253 253 base := identity.BaseDirectory{ 254 - PLCURL: cctx.String("atp-plc-host"), 254 + PLCURL: cmd.String("atp-plc-host"), 255 255 HTTPClient: http.Client{ 256 256 Timeout: time.Second * 15, 257 257 }, 258 - PLCLimiter: rate.NewLimiter(rate.Limit(cctx.Int("plc-rate-limit")), 1), 258 + PLCLimiter: rate.NewLimiter(rate.Limit(cmd.Int("plc-rate-limit")), 1), 259 259 TryAuthoritativeDNS: true, 260 260 SkipDNSDomainSuffixes: []string{".bsky.social"}, 261 261 } ··· 263 263 264 264 apiConfig := search.ServerConfig{ 265 265 Logger: logger, 266 - ProfileIndex: cctx.String("es-profile-index"), 267 - PostIndex: cctx.String("es-post-index"), 266 + ProfileIndex: cmd.String("es-profile-index"), 267 + PostIndex: cmd.String("es-post-index"), 268 268 } 269 269 270 270 srv, err := search.NewServer(escli, &dir, apiConfig) ··· 274 274 275 275 // Configure the indexer if we're not in readonly mode 276 276 if !readonly { 277 - db, err := cliutil.SetupDatabase(cctx.String("database-url"), cctx.Int("max-metadb-connections")) 277 + db, err := cliutil.SetupDatabase(cmd.String("database-url"), cmd.Int("max-metadb-connections")) 278 278 if err != nil { 279 279 return fmt.Errorf("failed to set up database: %w", err) 280 280 } 281 281 282 282 indexerConfig := search.IndexerConfig{ 283 - RelayHost: cctx.String("atp-relay-host"), 284 - ProfileIndex: cctx.String("es-profile-index"), 285 - PostIndex: cctx.String("es-post-index"), 283 + RelayHost: cmd.String("atp-relay-host"), 284 + ProfileIndex: cmd.String("es-profile-index"), 285 + PostIndex: cmd.String("es-post-index"), 286 286 Logger: logger, 287 - RelaySyncRateLimit: cctx.Int("relay-sync-rate-limit"), 288 - IndexMaxConcurrency: cctx.Int("index-max-concurrency"), 289 - DiscoverRepos: cctx.Bool("discover-repos"), 290 - IndexingRateLimit: cctx.Int("indexing-rate-limit"), 287 + RelaySyncRateLimit: cmd.Int("relay-sync-rate-limit"), 288 + IndexMaxConcurrency: cmd.Int("index-max-concurrency"), 289 + DiscoverRepos: cmd.Bool("discover-repos"), 290 + IndexingRateLimit: cmd.Int("indexing-rate-limit"), 291 291 } 292 292 293 293 idx, err := search.NewIndexer(db, escli, &dir, indexerConfig) ··· 299 299 } 300 300 301 301 go func() { 302 - if err := srv.RunMetrics(cctx.String("metrics-listen")); err != nil { 302 + if err := srv.RunMetrics(cmd.String("metrics-listen")); err != nil { 303 303 slog.Error("failed to start metrics endpoint", "error", err) 304 304 panic(fmt.Errorf("failed to start metrics endpoint: %w", err)) 305 305 } 306 306 }() 307 307 308 308 go func() { 309 - srv.RunAPI(cctx.String("bind")) 309 + srv.RunAPI(cmd.String("bind")) 310 310 }() 311 311 312 312 // If we're in readonly mode, just block forever 313 313 if readonly { 314 314 select {} 315 - } else if cctx.String("pagerank-file") != "" && srv.Indexer != nil { 315 + } else if cmd.String("pagerank-file") != "" && srv.Indexer != nil { 316 316 // If we're not in readonly mode, and we have a pagerank file, update pageranks 317 317 ctx := context.Background() 318 - if err := srv.Indexer.BulkIndexPageranks(ctx, cctx.String("pagerank-file")); err != nil { 318 + if err := srv.Indexer.BulkIndexPageranks(ctx, cmd.String("pagerank-file")); err != nil { 319 319 return fmt.Errorf("failed to update pageranks: %w", err) 320 320 } 321 - } else if cctx.String("bulk-posts-file") != "" && srv.Indexer != nil { 321 + } else if cmd.String("bulk-posts-file") != "" && srv.Indexer != nil { 322 322 // If we're not in readonly mode, and we have a bulk posts file, index posts 323 323 ctx := context.Background() 324 - if err := srv.Indexer.BulkIndexPosts(ctx, cctx.String("bulk-posts-file")); err != nil { 324 + if err := srv.Indexer.BulkIndexPosts(ctx, cmd.String("bulk-posts-file")); err != nil { 325 325 return fmt.Errorf("failed to bulk index posts: %w", err) 326 326 } 327 - } else if cctx.String("bulk-profiles-file") != "" && srv.Indexer != nil { 327 + } else if cmd.String("bulk-profiles-file") != "" && srv.Indexer != nil { 328 328 // If we're not in readonly mode, and we have a bulk profiles file, index profiles 329 329 ctx := context.Background() 330 - if err := srv.Indexer.BulkIndexProfiles(ctx, cctx.String("bulk-profiles-file")); err != nil { 330 + if err := srv.Indexer.BulkIndexProfiles(ctx, cmd.String("bulk-profiles-file")); err != nil { 331 331 return fmt.Errorf("failed to bulk index profiles: %w", err) 332 332 } 333 333 } else if srv.Indexer != nil { ··· 348 348 var elasticCheckCmd = &cli.Command{ 349 349 Name: "elastic-check", 350 350 Flags: []cli.Flag{}, 351 - Action: func(cctx *cli.Context) error { 352 - escli, err := createEsClient(cctx) 351 + Action: func(ctx context.Context, cmd *cli.Command) error { 352 + escli, err := createEsClient(cmd) 353 353 if err != nil { 354 354 return err 355 355 } ··· 365 365 } 366 366 slog.Info("opensearch client connected", "client_info", inf) 367 367 368 - resp, err := escli.Indices.Exists([]string{cctx.String("es-profile-index"), cctx.String("es-post-index")}) 368 + resp, err := escli.Indices.Exists([]string{cmd.String("es-profile-index"), cmd.String("es-post-index")}) 369 369 if err != nil { 370 370 return fmt.Errorf("failed to check index existence: %w", err) 371 371 } ··· 392 392 var searchPostCmd = &cli.Command{ 393 393 Name: "search-post", 394 394 Usage: "run a simple query against posts index", 395 - Action: func(cctx *cli.Context) error { 396 - escli, err := createEsClient(cctx) 395 + Action: func(ctx context.Context, cmd *cli.Command) error { 396 + escli, err := createEsClient(cmd) 397 397 if err != nil { 398 398 return err 399 399 } ··· 401 401 context.Background(), 402 402 identity.DefaultDirectory(), // TODO: parse PLC arg 403 403 escli, 404 - cctx.String("es-post-index"), 404 + cmd.String("es-post-index"), 405 405 &search.PostSearchParams{ 406 - Query: strings.Join(cctx.Args().Slice(), " "), 406 + Query: strings.Join(cmd.Args().Slice(), " "), 407 407 Offset: 0, 408 408 Size: 20, 409 409 }, ··· 424 424 Name: "typeahead", 425 425 }, 426 426 }, 427 - Action: func(cctx *cli.Context) error { 428 - escli, err := createEsClient(cctx) 427 + Action: func(ctx context.Context, cmd *cli.Command) error { 428 + escli, err := createEsClient(cmd) 429 429 if err != nil { 430 430 return err 431 431 } 432 - if cctx.Bool("typeahead") { 432 + if cmd.Bool("typeahead") { 433 433 res, err := search.DoSearchProfilesTypeahead( 434 434 context.Background(), 435 435 escli, 436 - cctx.String("es-profile-index"), 436 + cmd.String("es-profile-index"), 437 437 &search.ActorSearchParams{ 438 - Query: strings.Join(cctx.Args().Slice(), " "), 438 + Query: strings.Join(cmd.Args().Slice(), " "), 439 439 Size: 10, 440 440 }, 441 441 ) ··· 448 448 context.Background(), 449 449 identity.DefaultDirectory(), // TODO: parse PLC arg 450 450 escli, 451 - cctx.String("es-profile-index"), 451 + cmd.String("es-profile-index"), 452 452 &search.ActorSearchParams{ 453 - Query: strings.Join(cctx.Args().Slice(), " "), 453 + Query: strings.Join(cmd.Args().Slice(), " "), 454 454 Offset: 0, 455 455 Size: 20, 456 456 }, ··· 464 464 }, 465 465 } 466 466 467 - func createEsClient(cctx *cli.Context) (*es.Client, error) { 467 + func createEsClient(cmd *cli.Command) (*es.Client, error) { 468 468 469 469 addrs := []string{} 470 - if hosts := cctx.String("elastic-hosts"); hosts != "" { 470 + if hosts := cmd.String("elastic-hosts"); hosts != "" { 471 471 addrs = strings.Split(hosts, ",") 472 472 } 473 473 474 - certfi := cctx.String("elastic-cert-file") 474 + certfi := cmd.String("elastic-cert-file") 475 475 var cert []byte 476 476 if certfi != "" { 477 477 b, err := os.ReadFile(certfi) ··· 482 482 cert = b 483 483 } 484 484 485 - insecure := cctx.Bool("elastic-insecure-ssl") 485 + insecure := cmd.Bool("elastic-insecure-ssl") 486 486 487 487 cfg := es.Config{ 488 488 Addresses: addrs, 489 - Username: cctx.String("elastic-username"), 490 - Password: cctx.String("elastic-password"), 489 + Username: cmd.String("elastic-username"), 490 + Password: cmd.String("elastic-password"), 491 491 CACert: cert, 492 492 Transport: &http.Transport{ 493 493 MaxIdleConnsPerHost: 20,
+11 -13
cmd/querycheck/main.go
··· 21 21 "github.com/labstack/echo/v4" 22 22 "github.com/labstack/echo/v4/middleware" 23 23 "github.com/prometheus/client_golang/prometheus/promhttp" 24 - "github.com/urfave/cli/v2" 24 + "github.com/urfave/cli/v3" 25 25 "go.opentelemetry.io/otel/trace" 26 26 ) 27 27 28 28 func main() { 29 - app := cli.App{ 29 + app := cli.Command{ 30 30 Name: "querycheck", 31 31 Usage: "a postgresql query plan checker", 32 32 Version: versioninfo.Short(), ··· 37 37 Name: "postgres-url", 38 38 Usage: "postgres url for storing events", 39 39 Value: "postgres://postgres:postgres@localhost:5432/postgres?sslmode=disable", 40 - EnvVars: []string{"POSTGRES_URL"}, 40 + Sources: cli.EnvVars("POSTGRES_URL"), 41 41 }, 42 42 &cli.IntFlag{ 43 43 Name: "port", 44 44 Usage: "port to serve metrics on", 45 45 Value: 8080, 46 - EnvVars: []string{"PORT"}, 46 + Sources: cli.EnvVars("PORT"), 47 47 }, 48 48 &cli.StringFlag{ 49 49 Name: "auth-token", 50 50 Usage: "auth token for accessing the querycheck api", 51 51 Value: "", 52 - EnvVars: []string{"AUTH_TOKEN"}, 52 + Sources: cli.EnvVars("AUTH_TOKEN"), 53 53 }, 54 54 } 55 55 56 56 app.Action = Querycheck 57 57 58 - err := app.Run(os.Args) 59 - if err != nil { 58 + if err := app.Run(context.Background(), os.Args); err != nil { 60 59 log.Fatal(err) 61 60 } 62 61 } ··· 64 63 var tracer trace.Tracer 65 64 66 65 // Querycheck is the main function for querycheck 67 - func Querycheck(cctx *cli.Context) error { 68 - ctx := cctx.Context 66 + func Querycheck(ctx context.Context, cmd *cli.Command) error { 69 67 ctx, cancel := context.WithCancel(ctx) 70 68 defer cancel() 71 69 ··· 108 106 e.Use(middleware.LoggerWithConfig(middleware.DefaultLoggerConfig)) 109 107 110 108 // Start the query checker 111 - querychecker, err := querycheck.NewQuerychecker(ctx, cctx.String("postgres-url")) 109 + querychecker, err := querycheck.NewQuerychecker(ctx, cmd.String("postgres-url")) 112 110 if err != nil { 113 111 log.Fatalf("failed to create querychecker: %+v\n", err) 114 112 } ··· 129 127 130 128 e.Use(func(next echo.HandlerFunc) echo.HandlerFunc { 131 129 return func(c echo.Context) error { 132 - if cctx.String("auth-token") != "" && c.Request().Header.Get("Authorization") != cctx.String("auth-token") { 130 + if cmd.String("auth-token") != "" && c.Request().Header.Get("Authorization") != cmd.String("auth-token") { 133 131 return c.String(http.StatusUnauthorized, "unauthorized") 134 132 } 135 133 return next(c) ··· 145 143 // Start the metrics server 146 144 wg.Add(1) 147 145 go func() { 148 - logger.Info("starting metrics serverd", "port", cctx.Int("port")) 149 - if err := e.Start(fmt.Sprintf(":%d", cctx.Int("port"))); err != nil { 146 + logger.Info("starting metrics serverd", "port", cmd.Int("port")) 147 + if err := e.Start(fmt.Sprintf(":%d", cmd.Int("port"))); err != nil { 150 148 logger.Error("failed to start metrics server", "err", err) 151 149 } 152 150 wg.Done()
+12 -14
cmd/sonar/main.go
··· 21 21 "github.com/earthboundkid/versioninfo/v2" 22 22 "github.com/gorilla/websocket" 23 23 "github.com/prometheus/client_golang/prometheus/promhttp" 24 - "github.com/urfave/cli/v2" 24 + "github.com/urfave/cli/v3" 25 25 ) 26 26 27 27 func main() { 28 - app := cli.App{ 28 + app := cli.Command{ 29 29 Name: "sonar", 30 30 Usage: "atproto firehose monitoring tool", 31 31 Version: versioninfo.Short(), ··· 36 36 Name: "ws-url", 37 37 Usage: "full websocket path to the ATProto SubscribeRepos XRPC endpoint", 38 38 Value: "wss://bsky.network/xrpc/com.atproto.sync.subscribeRepos", 39 - EnvVars: []string{"SONAR_WS_URL"}, 39 + Sources: cli.EnvVars("SONAR_WS_URL"), 40 40 }, 41 41 &cli.StringFlag{ 42 42 Name: "log-level", 43 43 Usage: "log level", 44 44 Value: "info", 45 - EnvVars: []string{"SONAR_LOG_LEVEL"}, 45 + Sources: cli.EnvVars("SONAR_LOG_LEVEL"), 46 46 }, 47 47 &cli.IntFlag{ 48 48 Name: "port", 49 49 Usage: "listen port for metrics server", 50 50 Value: 8345, 51 - EnvVars: []string{"SONAR_PORT"}, 51 + Sources: cli.EnvVars("SONAR_PORT"), 52 52 }, 53 53 &cli.IntFlag{ 54 54 Name: "max-queue-size", ··· 59 59 Name: "cursor-file", 60 60 Usage: "path to cursor file", 61 61 Value: "sonar_cursor.json", 62 - EnvVars: []string{"SONAR_CURSOR_FILE"}, 62 + Sources: cli.EnvVars("SONAR_CURSOR_FILE"), 63 63 }, 64 64 } 65 65 66 66 app.Action = runSonar 67 67 68 - err := app.Run(os.Args) 69 - if err != nil { 68 + if err := app.Run(context.Background(), os.Args); err != nil { 70 69 log.Fatal(err) 71 70 } 72 71 } 73 72 74 - func runSonar(cctx *cli.Context) error { 75 - ctx := cctx.Context 73 + func runSonar(ctx context.Context, cmd *cli.Command) error { 76 74 ctx, cancel := context.WithCancel(ctx) 77 75 defer cancel() 78 76 ··· 89 87 logger = logger.With("source", "sonar_main") 90 88 logger.Info("starting sonar") 91 89 92 - u, err := url.Parse(cctx.String("ws-url")) 90 + u, err := url.Parse(cmd.String("ws-url")) 93 91 if err != nil { 94 92 log.Fatalf("failed to parse ws-url: %+v", err) 95 93 } 96 94 97 - s, err := NewSonar(logger, cctx.String("cursor-file"), u.String()) 95 + s, err := NewSonar(logger, cmd.String("cursor-file"), u.String()) 98 96 if err != nil { 99 97 log.Fatalf("failed to create sonar: %+v", err) 100 98 } ··· 162 160 mux.Handle("/metrics", promhttp.Handler()) 163 161 164 162 metricServer := &http.Server{ 165 - Addr: fmt.Sprintf(":%d", cctx.Int("port")), 163 + Addr: fmt.Sprintf(":%d", cmd.Int("port")), 166 164 Handler: mux, 167 165 } 168 166 ··· 172 170 defer wg.Done() 173 171 logger = logger.With("source", "metrics_server") 174 172 175 - logger.Info("metrics server listening", "port", cctx.Int("port")) 173 + logger.Info("metrics server listening", "port", cmd.Int("port")) 176 174 177 175 if err := metricServer.ListenAndServe(); err != http.ErrServerClosed { 178 176 log.Fatalf("failed to start metrics server: %+v", err)
+62 -17
cmd/stress/main.go
··· 4 4 "context" 5 5 "crypto/rand" 6 6 "encoding/hex" 7 + "encoding/json" 7 8 "fmt" 8 9 "os" 9 10 "sync" ··· 26 27 blockstore "github.com/ipfs/go-ipfs-blockstore" 27 28 cbor "github.com/ipfs/go-ipld-cbor" 28 29 "github.com/ipld/go-car" 29 - "github.com/urfave/cli/v2" 30 + "github.com/urfave/cli/v3" 30 31 ) 31 32 32 33 func main() { ··· 34 35 } 35 36 36 37 func run(args []string) { 37 - app := cli.App{ 38 + app := cli.Command{ 38 39 Name: "stress", 39 40 Usage: "load generation tool for PDS instances", 40 41 Version: versioninfo.Short(), ··· 45 46 genRepoCmd, 46 47 } 47 48 48 - app.RunAndExitOnError() 49 + if err := app.Run(context.Background(), args); err != nil { 50 + fmt.Fprintf(os.Stderr, "error: %v\n", err) 51 + os.Exit(-1) 52 + } 49 53 } 50 54 51 55 var postingCmd = &cli.Command{ ··· 66 70 Name: "pds-host", 67 71 Usage: "method, hostname, and port of PDS instance", 68 72 Value: "http://localhost:4849", 69 - EnvVars: []string{"ATP_PDS_HOST"}, 73 + Sources: cli.EnvVars("ATP_PDS_HOST"), 70 74 }, 71 75 &cli.StringFlag{ 72 76 Name: "invite", 73 77 }, 74 78 }, 75 - Action: func(cctx *cli.Context) error { 76 - xrpcc, err := cliutil.GetXrpcClient(cctx, false) 79 + Action: func(ctx context.Context, cmd *cli.Command) error { 80 + xrpcc, err := getXrpcClient(cmd, false) 77 81 if err != nil { 78 82 return err 79 83 } 80 84 81 - count := cctx.Int("count") 82 - concurrent := cctx.Int("concurrent") 83 - quiet := cctx.Bool("quiet") 84 - ctx := context.TODO() 85 + count := cmd.Int("count") 86 + concurrent := cmd.Int("concurrent") 87 + quiet := cmd.Bool("quiet") 85 88 86 89 buf := make([]byte, 6) 87 90 rand.Read(buf) 88 91 id := hex.EncodeToString(buf) 89 92 90 93 var invite *string 91 - if inv := cctx.String("invite"); inv != "" { 94 + if inv := cmd.String("invite"); inv != "" { 92 95 invite = &inv 93 96 } 94 97 ··· 165 168 Name: "pds-host", 166 169 Usage: "method, hostname, and port of PDS instance", 167 170 Value: "http://localhost:4849", 168 - EnvVars: []string{"ATP_PDS_HOST"}, 171 + Sources: cli.EnvVars("ATP_PDS_HOST"), 169 172 }, 170 173 }, 171 174 ArgsUsage: "<car-file-path>", 172 - Action: func(cctx *cli.Context) error { 173 - fname := cctx.Args().First() 175 + Action: func(ctx context.Context, cmd *cli.Command) error { 176 + fname := cmd.Args().First() 174 177 if fname == "" { 175 178 return cli.Exit("must provide car file path", 127) 176 179 } 177 180 178 - l := cctx.Int("len") 181 + l := cmd.Int("len") 179 182 180 183 membs := blockstore.NewBlockstore(datastore.NewMapDatastore()) 181 - 182 - ctx := context.Background() 183 184 184 185 r := repo.NewRepo(ctx, "did:plc:foobar", membs) 185 186 ··· 224 225 return nil 225 226 }, 226 227 } 228 + 229 + func getXrpcClient(cmd *cli.Command, authreq bool) (*xrpc.Client, error) { 230 + h := "http://localhost:4989" 231 + if pdsurl := cmd.String("pds-host"); pdsurl != "" { 232 + h = pdsurl 233 + } 234 + 235 + auth, err := loadAuthFromEnv(cmd, authreq) 236 + if err != nil { 237 + return nil, fmt.Errorf("loading auth: %w", err) 238 + } 239 + 240 + return &xrpc.Client{ 241 + Client: cliutil.NewHttpClient(), 242 + Host: h, 243 + Auth: auth, 244 + }, nil 245 + } 246 + 247 + func loadAuthFromEnv(cmd *cli.Command, req bool) (*xrpc.AuthInfo, error) { 248 + if a := cmd.String("auth"); a != "" { 249 + if ai, err := cliutil.ReadAuth(a); err != nil && req { 250 + return nil, err 251 + } else { 252 + return ai, nil 253 + } 254 + } 255 + 256 + val := os.Getenv("ATP_AUTH_FILE") 257 + if val == "" { 258 + if req { 259 + return nil, fmt.Errorf("no auth env present, ATP_AUTH_FILE not set") 260 + } 261 + 262 + return nil, nil 263 + } 264 + 265 + var auth xrpc.AuthInfo 266 + if err := json.Unmarshal([]byte(val), &auth); err != nil { 267 + return nil, err 268 + } 269 + 270 + return &auth, nil 271 + }
+35 -38
cmd/supercollider/main.go
··· 43 43 "github.com/prometheus/client_golang/prometheus" 44 44 "github.com/prometheus/client_golang/prometheus/promauto" 45 45 "github.com/prometheus/client_golang/prometheus/promhttp" 46 - "github.com/urfave/cli/v2" 46 + "github.com/urfave/cli/v3" 47 47 cbg "github.com/whyrusleeping/cbor-gen" 48 48 godid "github.com/whyrusleeping/go-did" 49 49 "golang.org/x/crypto/acme/autocert" ··· 84 84 ctx, cancel := context.WithCancel(ctx) 85 85 defer cancel() 86 86 87 - app := cli.App{ 87 + app := cli.Command{ 88 88 Name: "supercollider", 89 89 Usage: "atproto event noise-maker for Relay load testing", 90 90 Version: versioninfo.Short(), ··· 95 95 Name: "hostname", 96 96 Usage: "hostname of this server (forward *.hostname DNS records to this server)", 97 97 Value: "supercollider.jazco.io", 98 - EnvVars: []string{"SUPERCOLLIDER_HOST"}, 98 + Sources: cli.EnvVars("SUPERCOLLIDER_HOST"), 99 99 }, 100 100 &cli.BoolFlag{ 101 101 Name: "use-ssl", 102 102 Usage: "listen on port 443 and use SSL (needs to be run as root and have external DNS setup)", 103 103 Value: false, 104 - EnvVars: []string{"SUPERCOLLIDER_USE_SSL"}, 104 + Sources: cli.EnvVars("SUPERCOLLIDER_USE_SSL"), 105 105 }, 106 106 &cli.IntFlag{ 107 107 Name: "port", 108 108 Usage: "port for the HTTP(S) server to listen on (defaults to 80 if not using SSL, 443 if using SSL)", 109 - EnvVars: []string{"SUPERCOLLIDER_PORT"}, 109 + Sources: cli.EnvVars("SUPERCOLLIDER_PORT"), 110 110 }, 111 111 112 112 &cli.StringFlag{ 113 113 Name: "key-file", 114 114 Usage: "file to store the private key used to sign events", 115 115 Value: "key.raw", 116 - EnvVars: []string{"KEY_FILE"}, 116 + Sources: cli.EnvVars("KEY_FILE"), 117 117 }, 118 118 } 119 119 ··· 127 127 Name: "num-users", 128 128 Usage: "number of fake users to produce events for", 129 129 Value: 100, 130 - EnvVars: []string{"NUM_USERS"}, 130 + Sources: cli.EnvVars("NUM_USERS"), 131 131 }, 132 132 &cli.IntFlag{ 133 133 Name: "total-events", 134 134 Usage: "total number of events to generate", 135 135 Value: 1_000_000, 136 - EnvVars: []string{"TOTAL_EVENTS"}, 136 + Sources: cli.EnvVars("TOTAL_EVENTS"), 137 137 }, 138 138 &cli.StringFlag{ 139 139 Name: "output-file", 140 140 Usage: "output file for the generated events", 141 141 Value: "events_out.cbor", 142 - EnvVars: []string{"OUTPUT_FILE"}, 142 + Sources: cli.EnvVars("OUTPUT_FILE"), 143 143 }, 144 144 }, app.Flags...), 145 145 }, ··· 152 152 Name: "events-per-second", 153 153 Usage: "maximum number of events to generate per second", 154 154 Value: 300, 155 - EnvVars: []string{"EVENTS_PER_SECOND"}, 155 + Sources: cli.EnvVars("EVENTS_PER_SECOND"), 156 156 }, 157 157 &cli.StringFlag{ 158 158 Name: "input-file", 159 159 Usage: "input file for the generated events (if set, will read events from this file instead of generating them)", 160 160 Value: "events_in.cbor", 161 - EnvVars: []string{"INPUT_FILE"}, 161 + Sources: cli.EnvVars("INPUT_FILE"), 162 162 }, 163 163 }, app.Flags...), 164 164 }, 165 165 } 166 166 167 - err := app.Run(os.Args) 168 - if err != nil { 167 + if err := app.Run(context.Background(), os.Args); err != nil { 169 168 log.Fatal(err) 170 169 } 171 170 } 172 171 173 - func Reload(cctx *cli.Context) error { 174 - ctx := cctx.Context 172 + func Reload(ctx context.Context, cmd *cli.Command) error { 175 173 ctx, cancel := context.WithCancel(ctx) 176 174 defer cancel() 177 175 ··· 203 201 204 202 logger.Info("Starting Supercollider in Reload Mode") 205 203 logger.Info(fmt.Sprintf("Generating %d total events and writing them to %s", 206 - cctx.Int("total-events"), cctx.String("output-file"))) 204 + cmd.Int("total-events"), cmd.String("output-file"))) 207 205 208 206 em := events.NewEventManager(yolopersist.NewYoloPersister()) 209 207 210 208 // Try to read the key from disk 211 - keyBytes, err := os.ReadFile(cctx.String("key-file")) 209 + keyBytes, err := os.ReadFile(cmd.String("key-file")) 212 210 if err != nil { 213 211 logger.Warn("failed to read key from disk, creating new key", "err", err.Error()) 214 212 } ··· 223 221 if err != nil { 224 222 log.Fatalf("failed to serialize privkey: %+v\n", err) 225 223 } 226 - err = os.WriteFile(cctx.String("key-file"), rawKey, 0644) 224 + err = os.WriteFile(cmd.String("key-file"), rawKey, 0644) 227 225 if err != nil { 228 226 log.Fatalf("failed to write privkey to disk: %+v\n", err) 229 227 } ··· 247 245 248 246 // Initialize fake account DIDs 249 247 dids := []string{} 250 - for i := 0; i < cctx.Int("num-users"); i++ { 251 - did := fmt.Sprintf("did:web:%s.%s", petname.Generate(4, "-"), cctx.String("hostname")) 248 + for i := 0; i < cmd.Int("num-users"); i++ { 249 + did := fmt.Sprintf("did:web:%s.%s", petname.Generate(4, "-"), cmd.String("hostname")) 252 250 dids = append(dids, did) 253 251 } 254 252 255 253 // Instantiate Server 256 254 s := &Server{ 257 255 Logger: logger, 258 - EnableSSL: cctx.Bool("use-ssl"), 259 - Host: cctx.String("hostname"), 256 + EnableSSL: cmd.Bool("use-ssl"), 257 + Host: cmd.String("hostname"), 260 258 261 259 RepoManager: repoman, 262 260 MultibaseKey: *vMethod.PublicKeyMultibase, 263 261 Dids: dids, 264 262 265 263 Events: em, 266 - TotalDesiredEvents: cctx.Int("total-events"), 264 + TotalDesiredEvents: cmd.Int("total-events"), 267 265 } 268 266 269 267 repoman.SetEventHandler(s.HandleRepoEvent, false) ··· 281 279 }) 282 280 e.GET("/metrics", echo.WrapHandler(promhttp.Handler())) 283 281 284 - port := cctx.Int("port") 282 + port := cmd.Int("port") 285 283 if port == 0 { 286 - if cctx.Bool("use-ssl") { 284 + if cmd.Bool("use-ssl") { 287 285 port = 443 288 286 } else { 289 287 port = 80 ··· 295 293 // Start a loop to subscribe to events and write them to a file 296 294 go func() { 297 295 defer wg.Done() 298 - outFile := cctx.String("output-file") 296 + outFile := cmd.String("output-file") 299 297 f, err := os.OpenFile(outFile, os.O_CREATE|os.O_WRONLY, 0644) 300 298 if err != nil { 301 299 log.Fatalf("failed to open output file: %+v\n", err) ··· 367 365 368 366 listenAddress := fmt.Sprintf(":%d", port) 369 367 go func() { 370 - if cctx.Bool("use-ssl") { 368 + if cmd.Bool("use-ssl") { 371 369 err = e.StartAutoTLS(listenAddress) 372 370 } else { 373 371 err = e.Start(listenAddress) ··· 383 381 return nil 384 382 } 385 383 386 - func Fire(cctx *cli.Context) error { 387 - ctx := cctx.Context 384 + func Fire(ctx context.Context, cmd *cli.Command) error { 388 385 ctx, cancel := context.WithCancel(ctx) 389 386 defer cancel() 390 387 ··· 417 414 logger.Info("Starting Supercollider in Fire Mode") 418 415 419 416 // Try to read the key from disk 420 - keyBytes, err := os.ReadFile(cctx.String("key-file")) 417 + keyBytes, err := os.ReadFile(cmd.String("key-file")) 421 418 if err != nil { 422 419 logger.Warn("failed to read key from disk, creating new key", "err", err.Error()) 423 420 } ··· 432 429 if err != nil { 433 430 log.Fatalf("failed to serialize privkey: %+v\n", err) 434 431 } 435 - err = os.WriteFile(cctx.String("key-file"), rawKey, 0644) 432 + err = os.WriteFile(cmd.String("key-file"), rawKey, 0644) 436 433 if err != nil { 437 434 log.Fatalf("failed to write privkey to disk: %+v\n", err) 438 435 } ··· 451 448 // Instantiate Server 452 449 s := &Server{ 453 450 Logger: logger, 454 - EnableSSL: cctx.Bool("use-ssl"), 455 - Host: cctx.String("hostname"), 451 + EnableSSL: cmd.Bool("use-ssl"), 452 + Host: cmd.String("hostname"), 456 453 MultibaseKey: *vMethod.PublicKeyMultibase, 457 - MaxEventsPerSecond: cctx.Int("events-per-second"), 458 - PlaybackFile: cctx.String("input-file"), 454 + MaxEventsPerSecond: cmd.Int("events-per-second"), 455 + PlaybackFile: cmd.String("input-file"), 459 456 } 460 457 461 458 // HTTP Server setup and Middleware Plumbing ··· 499 496 e.GET("/xrpc/com.atproto.sync.subscribeRepos", s.HandleSubscribeRepos) 500 497 e.GET("/metrics", echo.WrapHandler(promhttp.Handler())) 501 498 502 - port := cctx.Int("port") 499 + port := cmd.Int("port") 503 500 if port == 0 { 504 - if cctx.Bool("use-ssl") { 501 + if cmd.Bool("use-ssl") { 505 502 port = 443 506 503 } else { 507 504 port = 80 ··· 510 507 511 508 listenAddress := fmt.Sprintf(":%d", port) 512 509 go func() { 513 - if cctx.Bool("use-ssl") { 510 + if cmd.Bool("use-ssl") { 514 511 err = e.StartAutoTLS(listenAddress) 515 512 } else { 516 513 err = e.Start(listenAddress)