A container registry that uses the AT Protocol for manifest storage and S3 for blob storage. atcr.io
docker container atproto go
80
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix up xrpc endpoints

+74 -10
+3
pkg/hold/pds/did.go
··· 58 58 "https://w3id.org/security/multikey/v1", 59 59 }, 60 60 ID: did, 61 + AlsoKnownAs: []string{ 62 + fmt.Sprintf("at://%s", hostname), 63 + }, 61 64 VerificationMethod: []VerificationMethod{ 62 65 { 63 66 ID: fmt.Sprintf("%s#atproto", did),
+71 -10
pkg/hold/pds/xrpc.go
··· 34 34 35 35 // RegisterHandlers registers all XRPC endpoints 36 36 func (h *XRPCHandler) RegisterHandlers(mux *http.ServeMux) { 37 + // Health check endpoint 38 + mux.HandleFunc("/xrpc/_health", h.HandleHealth) 39 + 37 40 // Standard PDS endpoints 38 41 mux.HandleFunc("/xrpc/com.atproto.server.describeServer", h.HandleDescribeServer) 39 42 mux.HandleFunc("/xrpc/com.atproto.repo.describeRepo", h.HandleDescribeRepo) 40 43 mux.HandleFunc("/xrpc/com.atproto.repo.getRecord", h.HandleGetRecord) 41 44 mux.HandleFunc("/xrpc/com.atproto.repo.listRecords", h.HandleListRecords) 42 45 46 + // Sync endpoints 47 + mux.HandleFunc("/xrpc/com.atproto.sync.listRepos", h.HandleListRepos) 48 + 43 49 // Blob endpoints (wrap existing presigned URL logic) 44 50 mux.HandleFunc("/xrpc/com.atproto.repo.uploadBlob", h.HandleUploadBlob) 45 51 mux.HandleFunc("/xrpc/com.atproto.sync.getBlob", h.HandleGetBlob) ··· 48 54 mux.HandleFunc("/.well-known/did.json", h.HandleDIDDocument) 49 55 } 50 56 57 + // HandleHealth returns health check information 58 + func (h *XRPCHandler) HandleHealth(w http.ResponseWriter, r *http.Request) { 59 + if r.Method != http.MethodGet { 60 + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) 61 + return 62 + } 63 + 64 + response := map[string]interface{}{ 65 + "version": "0.4.999", 66 + } 67 + 68 + w.Header().Set("Content-Type", "application/json") 69 + json.NewEncoder(w).Encode(response) 70 + } 71 + 51 72 // HandleDescribeServer returns server metadata 52 73 func (h *XRPCHandler) HandleDescribeServer(w http.ResponseWriter, r *http.Request) { 53 74 if r.Method != http.MethodGet { ··· 56 77 } 57 78 58 79 response := map[string]interface{}{ 59 - "did": h.pds.DID(), 80 + "did": h.pds.DID(), 60 81 "availableUserDomains": []string{}, 61 - "inviteCodeRequired": false, 62 - "links": map[string]string{}, 82 + "inviteCodeRequired": true, // Single-user PDS, no account creation 63 83 } 64 84 65 85 w.Header().Set("Content-Type", "application/json") ··· 87 107 return 88 108 } 89 109 90 - // Extract handle from did:web (remove "did:web:" prefix) 91 - handle := h.pds.DID() 92 - if len(handle) > 8 && handle[:8] == "did:web:" { 93 - handle = handle[8:] // "did:web:example.com" -> "example.com" 94 - } 95 - 96 110 // TODO: Get actual repo head from carstore 111 + // Note: For did:web, the handle IS the DID (not just hostname) 97 112 response := map[string]interface{}{ 98 113 "did": h.pds.DID(), 99 - "handle": handle, 114 + "handle": h.pds.DID(), 100 115 "didDoc": didDoc, 101 116 "collections": []string{CrewCollection}, 102 117 "handleIsCorrect": true, ··· 253 268 254 269 // Return 302 redirect to presigned URL 255 270 http.Redirect(w, r, downloadURL, http.StatusFound) 271 + } 272 + 273 + // HandleListRepos lists all repositories in this PDS 274 + func (h *XRPCHandler) HandleListRepos(w http.ResponseWriter, r *http.Request) { 275 + if r.Method != http.MethodGet { 276 + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) 277 + return 278 + } 279 + 280 + // Single-user PDS: return just this hold's repo 281 + did := h.pds.DID() 282 + 283 + // Get repo head and rev from carstore 284 + // For a single-user PDS, we use a fixed UID (stored in pds.uid) 285 + head, err := h.pds.carstore.GetUserRepoHead(r.Context(), h.pds.uid) 286 + if err != nil { 287 + // If no repo exists yet, return empty list 288 + response := map[string]interface{}{ 289 + "repos": []interface{}{}, 290 + } 291 + w.Header().Set("Content-Type", "application/json") 292 + json.NewEncoder(w).Encode(response) 293 + return 294 + } 295 + 296 + rev, err := h.pds.carstore.GetUserRepoRev(r.Context(), h.pds.uid) 297 + if err != nil { 298 + http.Error(w, fmt.Sprintf("failed to get repo rev: %v", err), http.StatusInternalServerError) 299 + return 300 + } 301 + 302 + repos := []map[string]interface{}{ 303 + { 304 + "did": did, 305 + "head": head.String(), 306 + "rev": rev, 307 + "active": true, 308 + }, 309 + } 310 + 311 + response := map[string]interface{}{ 312 + "repos": repos, 313 + } 314 + 315 + w.Header().Set("Content-Type", "application/json") 316 + json.NewEncoder(w).Encode(response) 256 317 } 257 318 258 319 // HandleDIDDocument returns the DID document