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) 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 // Check if the knot knows about any Dids; 56 // if it does, it is already initialized and we can repopulate the 57 // Jetstream subscriptions. 58 dids, err := db.GetAllDids() 59 if err != nil { 60 return nil, fmt.Errorf("failed to get all Dids: %w", err) 61 } 62 63 if len(dids) > 0 { 64 h.knotInitialized = true 65 close(h.init) 66 for _, d := range dids { 67 h.jc.AddDid(d) 68 } 69 } 70 71 err = h.jc.StartJetstream(ctx, h.processMessages) 72 if err != nil { 73 return nil, fmt.Errorf("failed to start jetstream: %w", err) 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}