this repo has no description
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}