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