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/jetstream"
12 "tangled.sh/tangled.sh/core/knotserver/config"
13 "tangled.sh/tangled.sh/core/knotserver/db"
14 "tangled.sh/tangled.sh/core/knotserver/notifier"
15 "tangled.sh/tangled.sh/core/rbac"
16)
17
18const (
19 ThisServer = "thisserver" // resource identifier for rbac enforcement
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
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 init: make(chan struct{}),
47 }
48
49 err := e.AddDomain(ThisServer)
50 if err != nil {
51 return nil, fmt.Errorf("failed to setup enforcer: %w", err)
52 }
53
54 err = h.jc.StartJetstream(ctx, h.processMessages)
55 if err != nil {
56 return nil, fmt.Errorf("failed to start jetstream: %w", err)
57 }
58
59 // Check if the knot knows about any Dids;
60 // if it does, it is already initialized and we can repopulate the
61 // Jetstream subscriptions.
62 dids, err := db.GetAllDids()
63 if err != nil {
64 return nil, fmt.Errorf("failed to get all Dids: %w", err)
65 }
66
67 if len(dids) > 0 {
68 h.knotInitialized = true
69 close(h.init)
70 for _, d := range dids {
71 h.jc.AddDid(d)
72 }
73 }
74
75 r.Get("/", h.Index)
76 r.Get("/capabilities", h.Capabilities)
77 r.Get("/version", h.Version)
78 r.Route("/{did}", func(r chi.Router) {
79 // Repo routes
80 r.Route("/{name}", func(r chi.Router) {
81 r.Route("/collaborator", func(r chi.Router) {
82 r.Use(h.VerifySignature)
83 r.Post("/add", h.AddRepoCollaborator)
84 })
85
86 r.Route("/languages", func(r chi.Router) {
87 r.With(h.VerifySignature)
88 r.Get("/", h.RepoLanguages)
89 r.Get("/{ref}", h.RepoLanguages)
90 })
91
92 r.Get("/", h.RepoIndex)
93 r.Get("/info/refs", h.InfoRefs)
94 r.Post("/git-upload-pack", h.UploadPack)
95 r.Post("/git-receive-pack", h.ReceivePack)
96 r.Get("/compare/{rev1}/{rev2}", h.Compare) // git diff-tree compare of two objects
97
98 r.With(h.VerifySignature).Post("/hidden-ref/{forkRef}/{remoteRef}", h.NewHiddenRef)
99
100 r.Route("/merge", func(r chi.Router) {
101 r.With(h.VerifySignature)
102 r.Post("/", h.Merge)
103 r.Post("/check", h.MergeCheck)
104 })
105
106 r.Route("/tree/{ref}", func(r chi.Router) {
107 r.Get("/", h.RepoIndex)
108 r.Get("/*", h.RepoTree)
109 })
110
111 r.Route("/blob/{ref}", func(r chi.Router) {
112 r.Get("/*", h.Blob)
113 })
114
115 r.Route("/raw/{ref}", func(r chi.Router) {
116 r.Get("/*", h.BlobRaw)
117 })
118
119 r.Get("/log/{ref}", h.Log)
120 r.Get("/archive/{file}", h.Archive)
121 r.Get("/commit/{ref}", h.Diff)
122 r.Get("/tags", h.Tags)
123 r.Route("/branches", func(r chi.Router) {
124 r.Get("/", h.Branches)
125 r.Get("/{branch}", h.Branch)
126 r.Route("/default", func(r chi.Router) {
127 r.Get("/", h.DefaultBranch)
128 r.With(h.VerifySignature).Put("/", h.SetDefaultBranch)
129 })
130 })
131 })
132 })
133
134 // Create a new repository.
135 r.Route("/repo", func(r chi.Router) {
136 r.Use(h.VerifySignature)
137 r.Put("/new", h.NewRepo)
138 r.Delete("/", h.RemoveRepo)
139 r.Route("/fork", func(r chi.Router) {
140 r.Post("/", h.RepoFork)
141 r.Post("/sync/{branch}", h.RepoForkSync)
142 r.Get("/sync/{branch}", h.RepoForkAheadBehind)
143 })
144 })
145
146 r.Route("/member", func(r chi.Router) {
147 r.Use(h.VerifySignature)
148 r.Put("/add", h.AddMember)
149 })
150
151 // Socket that streams git oplogs
152 r.Get("/oplog", h.OpLog)
153
154 // Initialize the knot with an owner and public key.
155 r.With(h.VerifySignature).Post("/init", h.Init)
156
157 // Health check. Used for two-way verification with appview.
158 r.With(h.VerifySignature).Get("/health", h.Health)
159
160 // All public keys on the knot.
161 r.Get("/keys", h.Keys)
162
163 return r, nil
164}
165
166// version is set during build time.
167var version string
168
169func (h *Handle) Version(w http.ResponseWriter, r *http.Request) {
170 if version == "" {
171 info, ok := debug.ReadBuildInfo()
172 if !ok {
173 http.Error(w, "failed to read build info", http.StatusInternalServerError)
174 return
175 }
176
177 var modVer string
178 for _, mod := range info.Deps {
179 if mod.Path == "tangled.sh/tangled.sh/knotserver" {
180 version = mod.Version
181 break
182 }
183 }
184
185 if modVer == "" {
186 version = "unknown"
187 }
188 }
189
190 w.Header().Set("Content-Type", "text/plain")
191 fmt.Fprintf(w, "knotserver/%s", version)
192}