this repo has no description
at main 4.7 kB view raw
1package main 2 3import ( 4 "context" 5 "io" 6 "log/slog" 7 "os" 8 "os/signal" 9 "strings" 10 "syscall" 11 "time" 12 13 _ "github.com/joho/godotenv/autoload" 14 _ "net/http/pprof" 15 16 "github.com/bluesky-social/indigo/atproto/identity" 17 "github.com/bluesky-social/indigo/util/cliutil" 18 "github.com/earthboundkid/versioninfo/v2" 19 "github.com/urfave/cli/v3" 20) 21 22func main() { 23 if err := run(os.Args); err != nil { 24 slog.Error("exiting process", "err", err.Error()) 25 os.Exit(-1) 26 } 27} 28 29func run(args []string) error { 30 31 app := cli.Command{ 32 Name: "scrumble", 33 Usage: "scrumble.social server instance", 34 Version: versioninfo.Short(), 35 } 36 app.Flags = []cli.Flag{ 37 &cli.StringSliceFlag{ 38 Name: "admin-password", 39 Usage: "secret password/token for accessing admin endpoints (multiple values allowed)", 40 Sources: cli.EnvVars("SCRUMBLE_ADMIN_PASSWORD", "SCRUMBLE_ADMIN_KEY"), 41 }, 42 &cli.StringFlag{ 43 Name: "log-level", 44 Usage: "log verbosity level (eg: warn, info, debug)", 45 Sources: cli.EnvVars("SCRUMBLE_LOG_LEVEL", "GO_LOG_LEVEL", "LOG_LEVEL"), 46 }, 47 } 48 app.Commands = []*cli.Command{ 49 &cli.Command{ 50 Name: "serve", 51 Usage: "run the scrumble daemon", 52 Action: runServer, 53 Flags: []cli.Flag{ 54 &cli.StringFlag{ 55 Name: "db-url", 56 Usage: "database connection string for relay database", 57 Value: "sqlite://data/relay/relay.sqlite", 58 Sources: cli.EnvVars("DATABASE_URL"), 59 }, 60 &cli.IntFlag{ 61 Name: "max-db-conn", 62 Usage: "limit on size of database connection pool", 63 Sources: cli.EnvVars("MAX_DB_CONNECTIONS", "MAX_METADB_CONNECTIONS"), 64 Value: 40, 65 }, 66 &cli.StringFlag{ 67 Name: "plc-host", 68 Usage: "method, hostname, and port of PLC registry", 69 Value: "https://plc.directory", 70 Sources: cli.EnvVars("SCRUMBLE_PLC_HOST", "ATP_PLC_HOST"), 71 }, 72 &cli.StringFlag{ 73 Name: "bind", 74 Usage: "IP or address, and port, to listen on for HTTP APIs (including firehose)", 75 Value: ":3000", 76 Sources: cli.EnvVars("SCRUMBLE_BIND"), 77 }, 78 &cli.IntFlag{ 79 Name: "ident-cache-size", 80 Value: 10_000, 81 Usage: "size of in-process identity cache (eg, DID docs)", 82 Sources: cli.EnvVars("SCRUMBLE_IDENT_CACHE_SIZE"), 83 }, 84 &cli.StringFlag{ 85 Name: "metrics-listen", 86 Usage: "IP or address, and port, to listen on for prometheus metrics", 87 Value: ":2471", 88 Sources: cli.EnvVars("SCRUMBLE_METRICS_LISTEN"), 89 }, 90 }, 91 }, 92 } 93 return app.Run(context.Background(), args) 94 95} 96 97func configLogger(cmd *cli.Command, writer io.Writer) *slog.Logger { 98 var level slog.Level 99 switch strings.ToLower(cmd.String("log-level")) { 100 case "error": 101 level = slog.LevelError 102 case "warn": 103 level = slog.LevelWarn 104 case "info": 105 level = slog.LevelInfo 106 case "debug": 107 level = slog.LevelDebug 108 default: 109 level = slog.LevelInfo 110 } 111 logger := slog.New(slog.NewJSONHandler(writer, &slog.HandlerOptions{ 112 Level: level, 113 })) 114 slog.SetDefault(logger) 115 return logger 116} 117 118func runServer(ctx context.Context, cmd *cli.Command) error { 119 logger := configLogger(cmd, os.Stdout) 120 121 // Trap SIGINT to trigger a shutdown. 122 signals := make(chan os.Signal, 1) 123 signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM) 124 125 dburl := cmd.String("db-url") 126 maxConn := cmd.Int("max-db-conn") 127 logger.Info("configuring database", "url", dburl, "maxConn", maxConn) 128 db, err := cliutil.SetupDatabase(dburl, maxConn) 129 if err != nil { 130 return err 131 } 132 133 baseDir := identity.BaseDirectory{ 134 SkipHandleVerification: true, 135 SkipDNSDomainSuffixes: []string{".bsky.social"}, 136 TryAuthoritativeDNS: true, 137 PLCURL: cmd.String("plc-host"), 138 } 139 dir := identity.NewCacheDirectory(&baseDir, cmd.Int("ident-cache-size"), time.Hour*24, time.Minute*2, time.Minute*5) 140 141 srv, err := NewServer(db) 142 if err != nil { 143 return err 144 } 145 srv.dir = &dir 146 147 // start metrics endpoint 148 go func() { 149 if err := srv.StartMetrics(cmd.String("metrics-listen")); err != nil { 150 logger.Error("failed to start metrics endpoint", "err", err) 151 os.Exit(1) 152 } 153 }() 154 155 srvErr := make(chan error, 1) 156 go func() { 157 err := srv.StartHTTP(cmd.String("bind")) 158 srvErr <- err 159 }() 160 161 logger.Info("startup complete") 162 select { 163 case <-signals: 164 logger.Info("received shutdown signal") 165 errs := srv.Shutdown() 166 for err := range errs { 167 logger.Error("error during shutdown", "err", err) 168 } 169 case err := <-srvErr: 170 if err != nil { 171 logger.Error("error during startup", "err", err) 172 } 173 logger.Info("shutting down") 174 errs := srv.Shutdown() 175 for err := range errs { 176 logger.Error("error during shutdown", "err", err) 177 } 178 } 179 180 logger.Info("shutdown complete") 181 return nil 182}