forked from
tangled.org/core
Monorepo for Tangled
1package knotserver
2
3import (
4 "context"
5 "fmt"
6 "net/http"
7
8 "github.com/bluesky-social/indigo/xrpc"
9 "github.com/urfave/cli/v3"
10 "tangled.org/core/api/tangled"
11 "tangled.org/core/hook"
12 "tangled.org/core/idresolver"
13 "tangled.org/core/jetstream"
14 "tangled.org/core/knotserver/config"
15 "tangled.org/core/knotserver/db"
16 "tangled.org/core/log"
17 "tangled.org/core/notifier"
18 "tangled.org/core/rbac"
19)
20
21func Command() *cli.Command {
22 return &cli.Command{
23 Name: "server",
24 Usage: "run a knot server",
25 Action: Run,
26 Description: `
27 Environment variables:
28 KNOT_SERVER_LISTEN_ADDR (default: 0.0.0.0:5555)
29 KNOT_SERVER_INTERNAL_LISTEN_ADDR (default: 127.0.0.1:5444)
30 KNOT_SERVER_DB_PATH (default: knotserver.db)
31 KNOT_SERVER_HOSTNAME (required)
32 KNOT_SERVER_JETSTREAM_ENDPOINT (default: wss://jetstream1.us-west.bsky.network/subscribe)
33 KNOT_SERVER_OWNER (required)
34 KNOT_SERVER_LOG_DIDS (default: true)
35 KNOT_SERVER_DEV (default: false)
36 KNOT_REPO_SCAN_PATH (default: /home/git)
37 KNOT_REPO_README (comma-separated list)
38 KNOT_REPO_MAIN_BRANCH (default: main)
39 KNOT_GIT_USER_NAME (default: Tangled)
40 KNOT_GIT_USER_EMAIL (default: noreply@tangled.sh)
41 APPVIEW_ENDPOINT (default: https://tangled.sh)
42 `,
43 }
44}
45
46func Run(ctx context.Context, cmd *cli.Command) error {
47 logger := log.FromContext(ctx)
48 logger = log.SubLogger(logger, cmd.Name)
49 ctx = log.IntoContext(ctx, logger)
50
51 c, err := config.Load(ctx)
52 if err != nil {
53 return fmt.Errorf("failed to load config: %w", err)
54 }
55
56 err = hook.Setup(hook.Config(
57 hook.WithScanPath(c.Repo.ScanPath),
58 hook.WithInternalApi(c.Server.InternalListenAddr),
59 ))
60 if err != nil {
61 return fmt.Errorf("failed to setup hooks: %w", err)
62 }
63 logger.Info("successfully finished setting up hooks")
64
65 if c.Server.Dev {
66 logger.Info("running in dev mode, signature verification is disabled")
67 }
68
69 db, err := db.Setup(ctx, c.Server.DBPath)
70 if err != nil {
71 return fmt.Errorf("failed to load db: %w", err)
72 }
73
74 e, err := rbac.NewEnforcer(c.Server.DBPath)
75 if err != nil {
76 return fmt.Errorf("failed to setup rbac enforcer: %w", err)
77 }
78
79 e.E.EnableAutoSave(true)
80
81 jc, err := jetstream.NewJetstreamClient(c.Server.JetstreamEndpoint, "knotserver", []string{
82 tangled.PublicKeyNSID,
83 tangled.KnotMemberNSID,
84 tangled.RepoPullNSID,
85 tangled.RepoCollaboratorNSID,
86 }, nil, log.SubLogger(logger, "jetstream"), db, true, c.Server.LogDids)
87 if err != nil {
88 logger.Error("failed to setup jetstream", "error", err)
89 }
90
91 notifier := notifier.New()
92
93 resolver := idresolver.DefaultResolver(c.Server.PlcUrl)
94
95 go migrateReposOnStartup(ctx, c, db, e, ¬ifier, log.SubLogger(logger, "migrate"))
96
97 mux, err := Setup(ctx, c, db, e, jc, ¬ifier, resolver)
98 if err != nil {
99 return fmt.Errorf("failed to setup server: %w", err)
100 }
101
102 imux := Internal(ctx, c, db, e, ¬ifier, resolver)
103
104 logger.Info("starting internal server", "address", c.Server.InternalListenAddr)
105 go http.ListenAndServe(c.Server.InternalListenAddr, imux)
106
107 // TODO(boltless): too lazy here. should clear this up
108 go func() {
109 input := &tangled.SyncRequestCrawl_Input{
110 Hostname: c.Server.Hostname,
111 }
112 for _, knotmirror := range c.KnotMirrors {
113 xrpcc := xrpc.Client{Host: knotmirror}
114 if err := tangled.SyncRequestCrawl(ctx, &xrpcc, input); err != nil {
115 logger.Error("error requesting crawl", "err", err)
116 } else {
117 logger.Info("crawl requested successfully")
118 }
119 }
120 }()
121
122 logger.Info("starting main server", "address", c.Server.ListenAddr)
123 logger.Error("server error", "error", http.ListenAndServe(c.Server.ListenAddr, mux))
124
125 return nil
126}