forked from
tangled.org/core
Monorepo for Tangled — https://tangled.org
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-pack", h.UploadPack)
86 r.Post("/git-receive-pack", h.ReceivePack)
87 // convenience routes
88 r.Get("/archive/{ref}", h.Archive)
89 })
90 })
91
92 // xrpc apis
93 r.Mount("/xrpc", h.XrpcRouter())
94
95 // Socket that streams git oplogs
96 r.Get("/events", h.Events)
97
98 return r
99}
100
101func (h *Knot) XrpcRouter() http.Handler {
102 serviceAuth := serviceauth.NewServiceAuth(h.l, h.resolver, h.c.Server.Did().String())
103
104 l := log.SubLogger(h.l, "xrpc")
105
106 xrpc := &xrpc.Xrpc{
107 Config: h.c,
108 Db: h.db,
109 Ingester: h.jc,
110 Enforcer: h.e,
111 Logger: l,
112 Notifier: h.n,
113 Resolver: h.resolver,
114 ServiceAuth: serviceAuth,
115 }
116
117 return xrpc.Router()
118}
119
120func (h *Knot) configureOwner() error {
121 cfgOwner := h.c.Server.Owner
122
123 rbacDomain := "thisserver"
124
125 existing, err := h.e.GetKnotUsersByRole("server:owner", rbacDomain)
126 if err != nil {
127 return err
128 }
129
130 switch len(existing) {
131 case 0:
132 // no owner configured, continue
133 case 1:
134 // find existing owner
135 existingOwner := existing[0]
136
137 // no ownership change, this is okay
138 if existingOwner == h.c.Server.Owner {
139 break
140 }
141
142 // remove existing owner
143 if err = h.db.RemoveDid(existingOwner); err != nil {
144 return err
145 }
146 if err = h.e.RemoveKnotOwner(rbacDomain, existingOwner); err != nil {
147 return err
148 }
149
150 default:
151 return fmt.Errorf("more than one owner in DB, try deleting %q and starting over", h.c.Server.DBPath)
152 }
153
154 if err = h.db.AddDid(cfgOwner); err != nil {
155 return fmt.Errorf("failed to add owner to DB: %w", err)
156 }
157 if err := h.e.AddKnotOwner(rbacDomain, cfgOwner); err != nil {
158 return fmt.Errorf("failed to add owner to RBAC: %w", err)
159 }
160
161 return nil
162}