Monorepo for Tangled tangled.org
2

Configure Feed

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

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 knotdb "tangled.org/core/knotserver/db" 16 "tangled.org/core/knotserver/sandbox" 17 "tangled.org/core/log" 18 "tangled.org/core/notifier" 19 "tangled.org/core/rbac" 20) 21 22func Command() *cli.Command { 23 return &cli.Command{ 24 Name: "server", 25 Usage: "run a knot server", 26 Action: Run, 27 Flags: []cli.Flag{ 28 &cli.BoolFlag{ 29 Name: "secure-mode", 30 Usage: "isolate git subprocesses to their own repository directory", 31 }, 32 }, 33 Description: ` 34 Environment variables: 35 KNOT_SERVER_LISTEN_ADDR (default: 0.0.0.0:5555) 36 KNOT_SERVER_INTERNAL_LISTEN_ADDR (default: 127.0.0.1:5444) 37 KNOT_SERVER_DB_PATH (default: knotserver.db) 38 KNOT_SERVER_HOSTNAME (required) 39 KNOT_SERVER_JETSTREAM_ENDPOINT (default: wss://jetstream1.us-west.bsky.network/subscribe) 40 KNOT_SERVER_OWNER (required) 41 KNOT_SERVER_LOG_DIDS (default: true) 42 KNOT_SERVER_DEV (default: false) 43 KNOT_SERVER_SECURE_MODE (default: false) 44 KNOT_REPO_SCAN_PATH (default: /home/git) 45 KNOT_REPO_README (comma-separated list) 46 KNOT_REPO_MAIN_BRANCH (default: main) 47 KNOT_GIT_USER_NAME (default: Tangled) 48 KNOT_GIT_USER_EMAIL (default: noreply@tangled.sh) 49 APPVIEW_ENDPOINT (default: https://tangled.sh) 50 `, 51 } 52} 53 54func Run(ctx context.Context, cmd *cli.Command) error { 55 logger := log.FromContext(ctx) 56 logger = log.SubLogger(logger, cmd.Name) 57 ctx = log.IntoContext(ctx, logger) 58 59 c, err := config.Load(ctx) 60 if err != nil { 61 return fmt.Errorf("failed to load config: %w", err) 62 } 63 64 // CLI flag overrides env var. 65 if cmd.Bool("secure-mode") { 66 c.Server.SecureMode = true 67 } 68 69 if !c.Server.SecureMode { 70 err = hook.Setup(hook.Config( 71 hook.WithScanPath(c.Repo.ScanPath), 72 hook.WithInternalApi(c.Server.InternalListenAddr), 73 )) 74 if err != nil { 75 return fmt.Errorf("failed to setup hooks: %w", err) 76 } 77 logger.Info("successfully finished setting up hooks") 78 } 79 80 if c.Server.Dev { 81 logger.Info("running in dev mode, signature verification is disabled") 82 } 83 84 db, err := knotdb.Setup(ctx, c.Server.DBPath) 85 if err != nil { 86 return fmt.Errorf("failed to load db: %w", err) 87 } 88 89 e, err := rbac.NewEnforcer(c.Server.DBPath) 90 if err != nil { 91 return fmt.Errorf("failed to setup rbac enforcer: %w", err) 92 } 93 94 e.E.EnableAutoSave(true) 95 96 jc, err := jetstream.NewJetstreamClient(c.Server.JetstreamEndpoint, "knotserver", []string{ 97 tangled.PublicKeyNSID, 98 tangled.RepoNSID, 99 tangled.RepoPullNSID, 100 }, nil, log.SubLogger(logger, "jetstream"), db, true, c.Server.LogDids) 101 if err != nil { 102 logger.Error("failed to setup jetstream", "error", err) 103 } 104 105 notifier := notifier.New() 106 107 resolver := idresolver.DefaultResolver(c.Server.PlcUrl) 108 109 if err := BackfillKnotMembers(ctx, db, e, c.Server.Owner, logger); err != nil { 110 logger.Warn("knot member backfill failed, continuing", "err", err) 111 } 112 if err := BackfillCollaborators(ctx, db, e, logger, false); err != nil { 113 logger.Warn("collaborator backfill failed, continuing", "err", err) 114 } 115 116 // probe and initialise the sandbox backend. 117 var sb sandbox.Backend 118 if c.Server.SecureMode { 119 var warn string 120 sb, warn = sandbox.New(func(repoPath string) (uint32, uint32, error) { 121 return sandbox.LookupUIDForRepoPath(c.Repo.ScanPath, repoPath) 122 }) 123 if warn != "" { 124 return fmt.Errorf("secure-mode: %s", warn) 125 } 126 logger.Info("secure-mode: activated", "backend", sb.Name()) 127 128 // refuse to start if any repos have not yet been isolation-migrated. 129 unmigrated, countErr := db.CountUnmigratedRepos() 130 if countErr != nil { 131 return fmt.Errorf("secure-mode: checking unmigrated repos: %w", countErr) 132 } 133 if unmigrated > 0 { 134 return fmt.Errorf( 135 "secure-mode: %d repo(s) have not been isolation-migrated; "+ 136 "run 'knot migrate-isolation' first", 137 unmigrated, 138 ) 139 } 140 } else { 141 sb = &sandbox.NoopBackend{} 142 } 143 144 go func() { 145 migrated := migrateReposOnStartup(ctx, c, db, e, &notifier, log.SubLogger(logger, "migrate")) 146 if err := BackfillCollaborators(ctx, db, e, logger, migrated); err != nil { 147 logger.Warn("collaborator backfill failed, continuing", "err", err) 148 } 149 }() 150 151 mux, err := Setup(ctx, c, db, e, jc, &notifier, resolver, sb) 152 if err != nil { 153 return fmt.Errorf("failed to setup server: %w", err) 154 } 155 156 imux := Internal(ctx, c, db, e, &notifier, resolver) 157 158 logger.Info("starting internal server", "address", c.Server.InternalListenAddr) 159 go http.ListenAndServe(c.Server.InternalListenAddr, imux) 160 161 // TODO(boltless): too lazy here. should clear this up 162 go func() { 163 input := &tangled.SyncRequestCrawl_Input{ 164 Hostname: c.Server.Hostname, 165 } 166 for _, knotmirror := range c.KnotMirrors { 167 xrpcc := xrpc.Client{Host: knotmirror} 168 if err := tangled.SyncRequestCrawl(ctx, &xrpcc, input); err != nil { 169 logger.Error("error requesting crawl", "err", err) 170 } else { 171 logger.Info("crawl requested successfully") 172 } 173 } 174 }() 175 176 logger.Info("starting main server", "address", c.Server.ListenAddr) 177 logger.Error("server error", "error", http.ListenAndServe(c.Server.ListenAddr, mux)) 178 179 return nil 180}