forked from tangled.org/core
this repo has no description
1package knotserver 2 3import ( 4 "context" 5 "fmt" 6 "log/slog" 7 "net/http" 8 "runtime/debug" 9 10 "github.com/go-chi/chi/v5" 11 "tangled.sh/tangled.sh/core/idresolver" 12 "tangled.sh/tangled.sh/core/jetstream" 13 "tangled.sh/tangled.sh/core/knotserver/config" 14 "tangled.sh/tangled.sh/core/knotserver/db" 15 "tangled.sh/tangled.sh/core/knotserver/xrpc" 16 tlog "tangled.sh/tangled.sh/core/log" 17 "tangled.sh/tangled.sh/core/notifier" 18 "tangled.sh/tangled.sh/core/rbac" 19 "tangled.sh/tangled.sh/core/xrpc/serviceauth" 20) 21 22type Knot struct { 23 c *config.Config 24 db *db.DB 25 jc *jetstream.JetstreamClient 26 e *rbac.Enforcer 27 l *slog.Logger 28 n *notifier.Notifier 29 resolver *idresolver.Resolver 30} 31 32func Setup(ctx context.Context, c *config.Config, db *db.DB, e *rbac.Enforcer, jc *jetstream.JetstreamClient, l *slog.Logger, n *notifier.Notifier) (http.Handler, error) { 33 r := chi.NewRouter() 34 35 h := Knot{ 36 c: c, 37 db: db, 38 e: e, 39 l: l, 40 jc: jc, 41 n: n, 42 resolver: idresolver.DefaultResolver(), 43 } 44 45 err := e.AddKnot(rbac.ThisServer) 46 if err != nil { 47 return nil, fmt.Errorf("failed to setup enforcer: %w", err) 48 } 49 50 // configure owner 51 if err = h.configureOwner(); err != nil { 52 return nil, err 53 } 54 h.l.Info("owner set", "did", h.c.Server.Owner) 55 h.jc.AddDid(h.c.Server.Owner) 56 57 // configure known-dids in jetstream consumer 58 dids, err := h.db.GetAllDids() 59 if err != nil { 60 return nil, fmt.Errorf("failed to get all dids: %w", err) 61 } 62 for _, d := range dids { 63 jc.AddDid(d) 64 } 65 66 err = h.jc.StartJetstream(ctx, h.processMessages) 67 if err != nil { 68 return nil, fmt.Errorf("failed to start jetstream: %w", err) 69 } 70 71 r.Get("/", func(w http.ResponseWriter, r *http.Request) { 72 w.Write([]byte("This is a knot server. More info at https://tangled.sh")) 73 }) 74 75 owner := func(w http.ResponseWriter, r *http.Request) { 76 w.Write([]byte(h.c.Server.Owner)) 77 } 78 // Deprecated: the sh.tangled.knot.owner xrpc call should be used instead 79 r.Get("/owner", owner) 80 81 r.Route("/{did}", func(r chi.Router) { 82 r.Route("/{name}", func(r chi.Router) { 83 // routes for git operations 84 r.Get("/info/refs", h.InfoRefs) 85 r.Post("/git-upload-pack", h.UploadPack) 86 r.Post("/git-receive-pack", h.ReceivePack) 87 }) 88 }) 89 90 // xrpc apis 91 r.Route("/xrpc", func(r chi.Router) { 92 r.Get("/_health", h.Version) 93 r.Get("/sh.tangled.knot.owner", owner) 94 r.Mount("/", h.XrpcRouter()) 95 }) 96 97 // Socket that streams git oplogs 98 r.Get("/events", h.Events) 99 100 return r, nil 101} 102 103func (h *Knot) XrpcRouter() http.Handler { 104 logger := tlog.New("knots") 105 106 serviceAuth := serviceauth.NewServiceAuth(h.l, h.resolver, h.c.Server.Did().String()) 107 108 xrpc := &xrpc.Xrpc{ 109 Config: h.c, 110 Db: h.db, 111 Ingester: h.jc, 112 Enforcer: h.e, 113 Logger: logger, 114 Notifier: h.n, 115 Resolver: h.resolver, 116 ServiceAuth: serviceAuth, 117 } 118 return xrpc.Router() 119} 120 121// version is set during build time. 122var version string 123 124func (h *Knot) Version(w http.ResponseWriter, r *http.Request) { 125 if version == "" { 126 info, ok := debug.ReadBuildInfo() 127 if !ok { 128 http.Error(w, "failed to read build info", http.StatusInternalServerError) 129 return 130 } 131 132 var modVer string 133 var sha string 134 var modified bool 135 136 for _, mod := range info.Deps { 137 if mod.Path == "tangled.sh/tangled.sh/knotserver" { 138 modVer = mod.Version 139 break 140 } 141 } 142 143 for _, setting := range info.Settings { 144 switch setting.Key { 145 case "vcs.revision": 146 sha = setting.Value 147 case "vcs.modified": 148 modified = setting.Value == "true" 149 } 150 } 151 152 if modVer == "" { 153 modVer = "unknown" 154 } 155 156 if sha == "" { 157 version = modVer 158 } else if modified { 159 version = fmt.Sprintf("%s (%s with modifications)", modVer, sha) 160 } else { 161 version = fmt.Sprintf("%s (%s)", modVer, sha) 162 } 163 } 164 165 w.Header().Set("Content-Type", "text/plain; charset=utf-8") 166 fmt.Fprintf(w, "knotserver/%s", version) 167} 168 169func (h *Knot) configureOwner() error { 170 cfgOwner := h.c.Server.Owner 171 172 rbacDomain := "thisserver" 173 174 existing, err := h.e.GetKnotUsersByRole("server:owner", rbacDomain) 175 if err != nil { 176 return err 177 } 178 179 switch len(existing) { 180 case 0: 181 // no owner configured, continue 182 case 1: 183 // find existing owner 184 existingOwner := existing[0] 185 186 // no ownership change, this is okay 187 if existingOwner == h.c.Server.Owner { 188 break 189 } 190 191 // remove existing owner 192 if err = h.db.RemoveDid(existingOwner); err != nil { 193 return err 194 } 195 if err = h.e.RemoveKnotOwner(rbacDomain, existingOwner); err != nil { 196 return err 197 } 198 199 default: 200 return fmt.Errorf("more than one owner in DB, try deleting %q and starting over", h.c.Server.DBPath) 201 } 202 203 if err = h.db.AddDid(cfgOwner); err != nil { 204 return fmt.Errorf("failed to add owner to DB: %w", err) 205 } 206 if err := h.e.AddKnotOwner(rbacDomain, cfgOwner); err != nil { 207 return fmt.Errorf("failed to add owner to RBAC: %w", err) 208 } 209 210 return nil 211}