forked from
tangled.org/core
fork
Configure Feed
Select the types of activity you want to include in your feed.
Monorepo for Tangled
fork
Configure Feed
Select the types of activity you want to include in your feed.
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)
20
21type Handle 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 // init is a channel that is closed when the knot has been initailized
31 // i.e. when the first user (knot owner) has been added.
32 init chan struct{}
33 knotInitialized bool
34}
35
36func 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) {
37 r := chi.NewRouter()
38
39 h := Handle{
40 c: c,
41 db: db,
42 e: e,
43 l: l,
44 jc: jc,
45 n: n,
46 resolver: idresolver.DefaultResolver(),
47 init: make(chan struct{}),
48 }
49
50 err := e.AddKnot(rbac.ThisServer)
51 if err != nil {
52 return nil, fmt.Errorf("failed to setup enforcer: %w", err)
53 }
54
55 err = h.jc.StartJetstream(ctx, h.processMessages)
56 if err != nil {
57 return nil, fmt.Errorf("failed to start jetstream: %w", err)
58 }
59
60 // Check if the knot knows about any Dids;
61 // if it does, it is already initialized and we can repopulate the
62 // Jetstream subscriptions.
63 dids, err := db.GetAllDids()
64 if err != nil {
65 return nil, fmt.Errorf("failed to get all Dids: %w", err)
66 }
67
68 if len(dids) > 0 {
69 h.knotInitialized = true
70 close(h.init)
71 for _, d := range dids {
72 h.jc.AddDid(d)
73 }
74 }
75
76 r.Get("/", h.Index)
77 r.Get("/capabilities", h.Capabilities)
78 r.Get("/version", h.Version)
79 r.Route("/{did}", func(r chi.Router) {
80 // Repo routes
81 r.Route("/{name}", func(r chi.Router) {
82 r.Route("/collaborator", func(r chi.Router) {
83 r.Use(h.VerifySignature)
84 r.Post("/add", h.AddRepoCollaborator)
85 })
86
87 r.Route("/languages", func(r chi.Router) {
88 r.With(h.VerifySignature)
89 r.Get("/", h.RepoLanguages)
90 r.Get("/{ref}", h.RepoLanguages)
91 })
92
93 r.Get("/", h.RepoIndex)
94 r.Get("/info/refs", h.InfoRefs)
95 r.Post("/git-upload-pack", h.UploadPack)
96 r.Post("/git-receive-pack", h.ReceivePack)
97 r.Get("/compare/{rev1}/{rev2}", h.Compare) // git diff-tree compare of two objects
98
99 r.With(h.VerifySignature).Post("/hidden-ref/{forkRef}/{remoteRef}", h.NewHiddenRef)
100
101 r.Route("/merge", func(r chi.Router) {
102 r.With(h.VerifySignature)
103 r.Post("/", h.Merge)
104 r.Post("/check", h.MergeCheck)
105 })
106
107 r.Route("/tree/{ref}", func(r chi.Router) {
108 r.Get("/", h.RepoIndex)
109 r.Get("/*", h.RepoTree)
110 })
111
112 r.Route("/blob/{ref}", func(r chi.Router) {
113 r.Get("/*", h.Blob)
114 })
115
116 r.Route("/raw/{ref}", func(r chi.Router) {
117 r.Get("/*", h.BlobRaw)
118 })
119
120 r.Get("/log/{ref}", h.Log)
121 r.Get("/archive/{file}", h.Archive)
122 r.Get("/commit/{ref}", h.Diff)
123 r.Get("/tags", h.Tags)
124 r.Route("/branches", func(r chi.Router) {
125 r.Get("/", h.Branches)
126 r.Get("/{branch}", h.Branch)
127 r.Route("/default", func(r chi.Router) {
128 r.Get("/", h.DefaultBranch)
129 r.With(h.VerifySignature).Put("/", h.SetDefaultBranch)
130 })
131 })
132 })
133 })
134
135 // xrpc apis
136 r.Mount("/xrpc", h.XrpcRouter())
137
138 // Create a new repository.
139 r.Route("/repo", func(r chi.Router) {
140 r.Use(h.VerifySignature)
141 r.Put("/new", h.NewRepo)
142 r.Delete("/", h.RemoveRepo)
143 r.Route("/fork", func(r chi.Router) {
144 r.Post("/", h.RepoFork)
145 r.Post("/sync/{branch}", h.RepoForkSync)
146 r.Get("/sync/{branch}", h.RepoForkAheadBehind)
147 })
148 })
149
150 r.Route("/member", func(r chi.Router) {
151 r.Use(h.VerifySignature)
152 r.Put("/add", h.AddMember)
153 })
154
155 // Socket that streams git oplogs
156 r.Get("/events", h.Events)
157
158 // Initialize the knot with an owner and public key.
159 r.With(h.VerifySignature).Post("/init", h.Init)
160
161 // Health check. Used for two-way verification with appview.
162 r.With(h.VerifySignature).Get("/health", h.Health)
163
164 // All public keys on the knot.
165 r.Get("/keys", h.Keys)
166
167 return r, nil
168}
169
170func (h *Handle) XrpcRouter() http.Handler {
171 logger := tlog.New("knots")
172
173 xrpc := &xrpc.Xrpc{
174 Config: h.c,
175 Db: h.db,
176 Ingester: h.jc,
177 Enforcer: h.e,
178 Logger: logger,
179 Notifier: h.n,
180 Resolver: h.resolver,
181 }
182 return xrpc.Router()
183}
184
185// version is set during build time.
186var version string
187
188func (h *Handle) Version(w http.ResponseWriter, r *http.Request) {
189 if version == "" {
190 info, ok := debug.ReadBuildInfo()
191 if !ok {
192 http.Error(w, "failed to read build info", http.StatusInternalServerError)
193 return
194 }
195
196 var modVer string
197 for _, mod := range info.Deps {
198 if mod.Path == "tangled.sh/tangled.sh/knotserver" {
199 version = mod.Version
200 break
201 }
202 }
203
204 if modVer == "" {
205 version = "unknown"
206 }
207 }
208
209 w.Header().Set("Content-Type", "text/plain; charset=utf-8")
210 fmt.Fprintf(w, "knotserver/%s", version)
211}