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 r.Route("/{did}", func(r chi.Router) { 76 r.Route("/{name}", func(r chi.Router) { 77 // routes for git operations 78 r.Get("/info/refs", h.InfoRefs) 79 r.Post("/git-upload-pack", h.UploadPack) 80 r.Post("/git-receive-pack", h.ReceivePack) 81 }) 82 }) 83 84 // xrpc apis 85 r.Route("/xrpc", func(r chi.Router) { 86 r.Get("/_health", h.Version) 87 r.Mount("/", h.XrpcRouter()) 88 }) 89 90 // Socket that streams git oplogs 91 r.Get("/events", h.Events) 92 93 return r, nil 94} 95 96func (h *Knot) XrpcRouter() http.Handler { 97 logger := tlog.New("knots") 98 99 serviceAuth := serviceauth.NewServiceAuth(h.l, h.resolver, h.c.Server.Did().String()) 100 101 xrpc := &xrpc.Xrpc{ 102 Config: h.c, 103 Db: h.db, 104 Ingester: h.jc, 105 Enforcer: h.e, 106 Logger: logger, 107 Notifier: h.n, 108 Resolver: h.resolver, 109 ServiceAuth: serviceAuth, 110 } 111 return xrpc.Router() 112} 113 114// version is set during build time. 115var version string 116 117func (h *Knot) Version(w http.ResponseWriter, r *http.Request) { 118 if version == "" { 119 info, ok := debug.ReadBuildInfo() 120 if !ok { 121 http.Error(w, "failed to read build info", http.StatusInternalServerError) 122 return 123 } 124 125 var modVer string 126 var sha string 127 var modified bool 128 129 for _, mod := range info.Deps { 130 if mod.Path == "tangled.sh/tangled.sh/knotserver" { 131 modVer = mod.Version 132 break 133 } 134 } 135 136 for _, setting := range info.Settings { 137 switch setting.Key { 138 case "vcs.revision": 139 sha = setting.Value 140 case "vcs.modified": 141 modified = setting.Value == "true" 142 } 143 } 144 145 if modVer == "" { 146 modVer = "unknown" 147 } 148 149 if sha == "" { 150 version = modVer 151 } else if modified { 152 version = fmt.Sprintf("%s (%s with modifications)", modVer, sha) 153 } else { 154 version = fmt.Sprintf("%s (%s)", modVer, sha) 155 } 156 } 157 158 w.Header().Set("Content-Type", "text/plain; charset=utf-8") 159 fmt.Fprintf(w, "knotserver/%s", version) 160} 161 162func (h *Knot) configureOwner() error { 163 cfgOwner := h.c.Server.Owner 164 165 rbacDomain := "thisserver" 166 167 existing, err := h.e.GetKnotUsersByRole("server:owner", rbacDomain) 168 if err != nil { 169 return err 170 } 171 172 switch len(existing) { 173 case 0: 174 // no owner configured, continue 175 case 1: 176 // find existing owner 177 existingOwner := existing[0] 178 179 // no ownership change, this is okay 180 if existingOwner == h.c.Server.Owner { 181 break 182 } 183 184 // remove existing owner 185 if err = h.db.RemoveDid(existingOwner); err != nil { 186 return err 187 } 188 if err = h.e.RemoveKnotOwner(rbacDomain, existingOwner); err != nil { 189 return err 190 } 191 192 default: 193 return fmt.Errorf("more than one owner in DB, try deleting %q and starting over", h.c.Server.DBPath) 194 } 195 196 if err = h.db.AddDid(cfgOwner); err != nil { 197 return fmt.Errorf("failed to add owner to DB: %w", err) 198 } 199 if err := h.e.AddKnotOwner(rbacDomain, cfgOwner); err != nil { 200 return fmt.Errorf("failed to add owner to RBAC: %w", err) 201 } 202 203 return nil 204}