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/jetstream" 12 "tangled.sh/tangled.sh/core/knotserver/config" 13 "tangled.sh/tangled.sh/core/knotserver/db" 14 "tangled.sh/tangled.sh/core/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.AddKnot(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("/events", h.Events) 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}