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