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 "time" 10 11 "github.com/bluesky-social/indigo/api/atproto" 12 "github.com/bluesky-social/indigo/atproto/syntax" 13 lexutil "github.com/bluesky-social/indigo/lex/util" 14 "github.com/bluesky-social/indigo/xrpc" 15 "github.com/go-chi/chi/v5" 16 "tangled.sh/tangled.sh/core/api/tangled" 17 "tangled.sh/tangled.sh/core/jetstream" 18 "tangled.sh/tangled.sh/core/knotserver/config" 19 "tangled.sh/tangled.sh/core/knotserver/db" 20 "tangled.sh/tangled.sh/core/rbac" 21 "tangled.sh/tangled.sh/core/resolver" 22) 23 24const ( 25 ThisServer = "thisserver" // resource identifier for rbac enforcement 26) 27 28type Handle struct { 29 c *config.Config 30 db *db.DB 31 jc *jetstream.JetstreamClient 32 e *rbac.Enforcer 33 l *slog.Logger 34 clock syntax.TIDClock 35} 36 37func Setup(ctx context.Context, c *config.Config, db *db.DB, e *rbac.Enforcer, jc *jetstream.JetstreamClient, l *slog.Logger) (http.Handler, error) { 38 h := Handle{ 39 c: c, 40 db: db, 41 e: e, 42 l: l, 43 jc: jc, 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 // if this knot does not already have an owner, publish it 52 if _, err := h.db.Owner(); err != nil { 53 l.Info("publishing this knot ...", "owner", h.c.Owner.Did) 54 err = h.Publish() 55 if err != nil { 56 return nil, fmt.Errorf("failed to announce knot: %w", err) 57 } 58 } 59 60 l.Info("this knot has been published", "owner", h.c.Owner.Did) 61 62 err = h.jc.StartJetstream(ctx, h.processMessages) 63 if err != nil { 64 return nil, fmt.Errorf("failed to start jetstream: %w", err) 65 } 66 67 dids, err := db.GetAllDids() 68 if err != nil { 69 return nil, fmt.Errorf("failed to get all Dids: %w", err) 70 } 71 72 for _, d := range dids { 73 h.jc.AddDid(d) 74 } 75 76 r := chi.NewRouter() 77 78 r.Get("/", h.Index) 79 r.Get("/capabilities", h.Capabilities) 80 r.Get("/version", h.Version) 81 r.Route("/{did}", func(r chi.Router) { 82 // Repo routes 83 r.Route("/{name}", func(r chi.Router) { 84 r.Route("/collaborator", func(r chi.Router) { 85 r.Use(h.VerifySignature) 86 r.Post("/add", h.AddRepoCollaborator) 87 }) 88 89 r.Route("/languages", func(r chi.Router) { 90 r.With(h.VerifySignature) 91 r.Get("/", h.RepoLanguages) 92 r.Get("/{ref}", h.RepoLanguages) 93 }) 94 95 r.Get("/", h.RepoIndex) 96 r.Get("/info/refs", h.InfoRefs) 97 r.Post("/git-upload-pack", h.UploadPack) 98 r.Post("/git-receive-pack", h.ReceivePack) 99 r.Get("/compare/{rev1}/{rev2}", h.Compare) // git diff-tree compare of two objects 100 101 r.With(h.VerifySignature).Post("/hidden-ref/{forkRef}/{remoteRef}", h.NewHiddenRef) 102 103 r.Route("/merge", func(r chi.Router) { 104 r.With(h.VerifySignature) 105 r.Post("/", h.Merge) 106 r.Post("/check", h.MergeCheck) 107 }) 108 109 r.Route("/tree/{ref}", func(r chi.Router) { 110 r.Get("/", h.RepoIndex) 111 r.Get("/*", h.RepoTree) 112 }) 113 114 r.Route("/blob/{ref}", func(r chi.Router) { 115 r.Get("/*", h.Blob) 116 }) 117 118 r.Route("/raw/{ref}", func(r chi.Router) { 119 r.Get("/*", h.BlobRaw) 120 }) 121 122 r.Get("/log/{ref}", h.Log) 123 r.Get("/archive/{file}", h.Archive) 124 r.Get("/commit/{ref}", h.Diff) 125 r.Get("/tags", h.Tags) 126 r.Route("/branches", func(r chi.Router) { 127 r.Get("/", h.Branches) 128 r.Get("/{branch}", h.Branch) 129 r.Route("/default", func(r chi.Router) { 130 r.Get("/", h.DefaultBranch) 131 r.With(h.VerifySignature).Put("/", h.SetDefaultBranch) 132 }) 133 }) 134 }) 135 }) 136 137 // Create a new repository. 138 r.Route("/repo", func(r chi.Router) { 139 r.Use(h.VerifySignature) 140 r.Put("/new", h.NewRepo) 141 r.Delete("/", h.RemoveRepo) 142 r.Route("/fork", func(r chi.Router) { 143 r.Post("/", h.RepoFork) 144 r.Post("/sync/{branch}", h.RepoForkSync) 145 r.Get("/sync/{branch}", h.RepoForkAheadBehind) 146 }) 147 }) 148 149 r.Route("/member", func(r chi.Router) { 150 r.Use(h.VerifySignature) 151 r.Put("/add", h.AddMember) 152 }) 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 // Return did of the owner of this knot 161 r.Get("/owner", h.Owner) 162 163 // All public keys on the knot. 164 r.Get("/keys", h.Keys) 165 166 return r, nil 167} 168 169// version is set during build time. 170var version string 171 172func (h *Handle) Version(w http.ResponseWriter, r *http.Request) { 173 if version == "" { 174 info, ok := debug.ReadBuildInfo() 175 if !ok { 176 http.Error(w, "failed to read build info", http.StatusInternalServerError) 177 return 178 } 179 180 var modVer string 181 for _, mod := range info.Deps { 182 if mod.Path == "tangled.sh/tangled.sh/knotserver" { 183 version = mod.Version 184 break 185 } 186 } 187 188 if modVer == "" { 189 version = "unknown" 190 } 191 } 192 193 w.Header().Set("Content-Type", "text/plain") 194 fmt.Fprintf(w, "knotserver/%s", version) 195} 196 197func (h *Handle) Publish() error { 198 ownerDid := h.c.Owner.Did 199 appPassword := h.c.Owner.AppPassword 200 201 res := resolver.DefaultResolver() 202 ident, err := res.ResolveIdent(context.Background(), ownerDid) 203 if err != nil { 204 return err 205 } 206 207 client := xrpc.Client{ 208 Host: ident.PDSEndpoint(), 209 } 210 211 resp, err := atproto.ServerCreateSession(context.Background(), &client, &atproto.ServerCreateSession_Input{ 212 Identifier: ownerDid, 213 Password: appPassword, 214 }) 215 if err != nil { 216 return err 217 } 218 219 authClient := xrpc.Client{ 220 Host: ident.PDSEndpoint(), 221 Auth: &xrpc.AuthInfo{ 222 AccessJwt: resp.AccessJwt, 223 RefreshJwt: resp.RefreshJwt, 224 Handle: resp.Handle, 225 Did: resp.Did, 226 }, 227 } 228 229 rkey := h.TID() 230 231 // write a "knot" record to the owners's pds 232 _, err = atproto.RepoPutRecord(context.Background(), &authClient, &atproto.RepoPutRecord_Input{ 233 Collection: tangled.KnotNSID, 234 Repo: ownerDid, 235 Rkey: rkey, 236 Record: &lexutil.LexiconTypeDecoder{ 237 Val: &tangled.Knot{ 238 CreatedAt: time.Now().Format(time.RFC3339), 239 Host: h.c.Server.Hostname, 240 }, 241 }, 242 }) 243 if err != nil { 244 return err 245 } 246 247 err = h.db.SetOwner(ownerDid, rkey) 248 if err != nil { 249 return err 250 } 251 252 err = h.db.AddDid(ownerDid) 253 if err != nil { 254 return err 255 } 256 257 return nil 258} 259 260func (h *Handle) TID() string { 261 return h.clock.Next().String() 262}