this repo has no description
1package knotserver 2 3import ( 4 "context" 5 "fmt" 6 "log/slog" 7 "net/http" 8 9 "github.com/go-chi/chi/v5" 10 "tangled.org/core/idresolver" 11 "tangled.org/core/jetstream" 12 "tangled.org/core/knotserver/config" 13 "tangled.org/core/knotserver/db" 14 "tangled.org/core/knotserver/xrpc" 15 "tangled.org/core/log" 16 "tangled.org/core/notifier" 17 "tangled.org/core/rbac" 18 "tangled.org/core/xrpc/serviceauth" 19) 20 21type Knot struct { 22 c *config.Config 23 db *db.DB 24 jc *jetstream.JetstreamClient 25 e *rbac.Enforcer 26 l *slog.Logger 27 n *notifier.Notifier 28 resolver *idresolver.Resolver 29} 30 31func Setup(ctx context.Context, c *config.Config, db *db.DB, e *rbac.Enforcer, jc *jetstream.JetstreamClient, n *notifier.Notifier) (http.Handler, error) { 32 h := Knot{ 33 c: c, 34 db: db, 35 e: e, 36 l: log.FromContext(ctx), 37 jc: jc, 38 n: n, 39 resolver: idresolver.DefaultResolver(c.Server.PlcUrl), 40 } 41 42 err := e.AddKnot(rbac.ThisServer) 43 if err != nil { 44 return nil, fmt.Errorf("failed to setup enforcer: %w", err) 45 } 46 47 // configure owner 48 if err = h.configureOwner(); err != nil { 49 return nil, err 50 } 51 h.l.Info("owner set", "did", h.c.Server.Owner) 52 h.jc.AddDid(h.c.Server.Owner) 53 54 // configure known-dids in jetstream consumer 55 dids, err := h.db.GetAllDids() 56 if err != nil { 57 return nil, fmt.Errorf("failed to get all dids: %w", err) 58 } 59 for _, d := range dids { 60 jc.AddDid(d) 61 } 62 63 err = h.jc.StartJetstream(ctx, h.processMessages) 64 if err != nil { 65 return nil, fmt.Errorf("failed to start jetstream: %w", err) 66 } 67 68 return h.Router(), nil 69} 70 71func (h *Knot) Router() http.Handler { 72 r := chi.NewRouter() 73 74 r.Use(h.CORS) 75 r.Use(h.RequestLogger) 76 77 r.Get("/", func(w http.ResponseWriter, r *http.Request) { 78 w.Write([]byte("This is a knot server. More info at https://tangled.sh")) 79 }) 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-archive", h.UploadArchive) 86 r.Post("/git-upload-pack", h.UploadPack) 87 r.Post("/git-receive-pack", h.ReceivePack) 88 // convenience routes 89 r.Get("/archive/{ref}", h.Archive) 90 }) 91 }) 92 93 // xrpc apis 94 r.Mount("/xrpc", h.XrpcRouter()) 95 96 // Socket that streams git oplogs 97 r.Get("/events", h.Events) 98 99 return r 100} 101 102func (h *Knot) XrpcRouter() http.Handler { 103 serviceAuth := serviceauth.NewServiceAuth(h.l, h.resolver, h.c.Server.Did().String()) 104 105 l := log.SubLogger(h.l, "xrpc") 106 107 xrpc := &xrpc.Xrpc{ 108 Config: h.c, 109 Db: h.db, 110 Ingester: h.jc, 111 Enforcer: h.e, 112 Logger: l, 113 Notifier: h.n, 114 Resolver: h.resolver, 115 ServiceAuth: serviceAuth, 116 } 117 118 return xrpc.Router() 119} 120 121func (h *Knot) configureOwner() error { 122 cfgOwner := h.c.Server.Owner 123 124 rbacDomain := "thisserver" 125 126 existing, err := h.e.GetKnotUsersByRole("server:owner", rbacDomain) 127 if err != nil { 128 return err 129 } 130 131 switch len(existing) { 132 case 0: 133 // no owner configured, continue 134 case 1: 135 // find existing owner 136 existingOwner := existing[0] 137 138 // no ownership change, this is okay 139 if existingOwner == h.c.Server.Owner { 140 break 141 } 142 143 // remove existing owner 144 if err = h.db.RemoveDid(existingOwner); err != nil { 145 return err 146 } 147 if err = h.e.RemoveKnotOwner(rbacDomain, existingOwner); err != nil { 148 return err 149 } 150 151 default: 152 return fmt.Errorf("more than one owner in DB, try deleting %q and starting over", h.c.Server.DBPath) 153 } 154 155 if err = h.db.AddDid(cfgOwner); err != nil { 156 return fmt.Errorf("failed to add owner to DB: %w", err) 157 } 158 if err := h.e.AddKnotOwner(rbacDomain, cfgOwner); err != nil { 159 return fmt.Errorf("failed to add owner to RBAC: %w", err) 160 } 161 162 return nil 163}