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

lower cached plc hits to 8 hours

evan.jarrett.net 0b22082f 1727801d

verified
Changed files
+32 -33
pkg
appview
jetstream
middleware
auth
hold
pds
+1 -1
go.mod
··· 7 7 github.com/bluesky-social/indigo v0.0.0-20251021193747-543ab1124beb 8 8 github.com/distribution/distribution/v3 v3.0.0 9 9 github.com/distribution/reference v0.6.0 10 + github.com/earthboundkid/versioninfo/v2 v2.24.1 10 11 github.com/go-chi/chi/v5 v5.2.3 11 12 github.com/golang-jwt/jwt/v5 v5.2.2 12 13 github.com/google/uuid v1.6.0 ··· 44 45 github.com/docker/docker-credential-helpers v0.8.2 // indirect 45 46 github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect 46 47 github.com/docker/go-metrics v0.0.1 // indirect 47 - github.com/earthboundkid/versioninfo/v2 v2.24.1 // indirect 48 48 github.com/felixge/httpsnoop v1.0.4 // indirect 49 49 github.com/go-jose/go-jose/v4 v4.1.2 // indirect 50 50 github.com/go-logr/logr v1.4.2 // indirect
+1 -1
pkg/appview/jetstream/processor.go
··· 30 30 func NewProcessor(database *sql.DB, useCache bool) *Processor { 31 31 p := &Processor{ 32 32 db: database, 33 - directory: identity.DefaultDirectory(), 33 + directory: atproto.GetDirectory(), 34 34 useCache: useCache, 35 35 } 36 36
+2 -2
pkg/appview/middleware/registry.go
··· 81 81 82 82 // initATProtoResolver initializes the name resolution middleware 83 83 func initATProtoResolver(ctx context.Context, ns distribution.Namespace, _ driver.StorageDriver, options map[string]any) (distribution.Namespace, error) { 84 - // Use indigo's default directory (includes caching) 85 - directory := identity.DefaultDirectory() 84 + // Use shared directory with 8h cache TTL 85 + directory := atproto.GetDirectory() 86 86 87 87 // Get default hold DID from config (required) 88 88 // Expected format: "did:web:hold01.atcr.io"
+1 -1
pkg/appview/ui.go
··· 128 128 } 129 129 130 130 // StaticSubdir returns an fs.FS for a subdirectory within static/ 131 - func StaticSubdir(name string) http.Handler { 131 + func StaticSubdir(name string) http.Handler { 132 132 sub, err := fs.Sub(staticFS, "static/"+name) 133 133 if err != nil { 134 134 panic(err)
+23 -1
pkg/auth/oauth/client.go
··· 36 36 return &App{ 37 37 clientApp: clientApp, 38 38 baseURL: baseURL, 39 - directory: identity.DefaultDirectory(), 39 + directory: atproto.GetDirectory(), 40 40 }, nil 41 41 } 42 42 ··· 177 177 178 178 return true 179 179 } 180 + 181 + // ResolveDIDToPDS resolves a DID to its PDS endpoint (for reference) 182 + // This is an alternative approach if we don't trust the token's issuer claim 183 + func ResolveDIDToPDS(ctx context.Context, did string) (string, error) { 184 + directory := atproto.GetDirectory() 185 + didParsed, err := syntax.ParseDID(did) 186 + if err != nil { 187 + return "", fmt.Errorf("invalid DID: %w", err) 188 + } 189 + 190 + ident, err := directory.LookupDID(ctx, didParsed) 191 + if err != nil { 192 + return "", fmt.Errorf("failed to resolve DID: %w", err) 193 + } 194 + 195 + pdsEndpoint := ident.PDSEndpoint() 196 + if pdsEndpoint == "" { 197 + return "", fmt.Errorf("no PDS endpoint found for DID") 198 + } 199 + 200 + return pdsEndpoint, nil 201 + }
+1 -1
pkg/auth/session.go
··· 42 42 // NewSessionValidator creates a new ATProto session validator 43 43 func NewSessionValidator() *SessionValidator { 44 44 return &SessionValidator{ 45 - directory: identity.DefaultDirectory(), 45 + directory: atproto.GetDirectory(), 46 46 httpClient: &http.Client{}, 47 47 cache: make(map[string]*CachedSession), 48 48 }
+2 -2
pkg/auth/token/handler.go
··· 9 9 "strings" 10 10 "time" 11 11 12 - "github.com/bluesky-social/indigo/atproto/identity" 13 12 "github.com/bluesky-social/indigo/atproto/syntax" 14 13 15 14 "atcr.io/pkg/appview/db" 15 + "atcr.io/pkg/atproto" 16 16 "atcr.io/pkg/auth" 17 17 ) 18 18 ··· 158 158 // Call post-auth callback for AppView business logic (profile management, etc.) 159 159 if h.postAuthCallback != nil { 160 160 // Resolve PDS endpoint for callback 161 - directory := identity.DefaultDirectory() 161 + directory := atproto.GetDirectory() 162 162 atID, err := syntax.ParseAtIdentifier(username) 163 163 if err == nil { 164 164 ident, err := directory.Lookup(r.Context(), *atID)
+1 -24
pkg/hold/pds/auth.go
··· 14 14 15 15 "atcr.io/pkg/atproto" 16 16 "github.com/bluesky-social/indigo/atproto/atcrypto" 17 - "github.com/bluesky-social/indigo/atproto/identity" 18 17 "github.com/bluesky-social/indigo/atproto/syntax" 19 18 "github.com/golang-jwt/jwt/v5" 20 19 ) ··· 190 189 } 191 190 192 191 return &session, nil 193 - } 194 - 195 - // ResolveDIDToPDS resolves a DID to its PDS endpoint (for reference) 196 - // This is an alternative approach if we don't trust the token's issuer claim 197 - func ResolveDIDToPDS(ctx context.Context, did string) (string, error) { 198 - directory := identity.DefaultDirectory() 199 - didParsed, err := syntax.ParseDID(did) 200 - if err != nil { 201 - return "", fmt.Errorf("invalid DID: %w", err) 202 - } 203 - 204 - ident, err := directory.LookupDID(ctx, didParsed) 205 - if err != nil { 206 - return "", fmt.Errorf("failed to resolve DID: %w", err) 207 - } 208 - 209 - pdsEndpoint := ident.PDSEndpoint() 210 - if pdsEndpoint == "" { 211 - return "", fmt.Errorf("no PDS endpoint found for DID") 212 - } 213 - 214 - return pdsEndpoint, nil 215 192 } 216 193 217 194 // ValidateOwnerOrCrewAdmin validates that the request has valid authentication ··· 509 486 // Returns the atcrypto.PublicKey for signature verification 510 487 func fetchPublicKeyFromDID(ctx context.Context, did string) (atcrypto.PublicKey, error) { 511 488 // Use indigo's identity resolution 512 - directory := identity.DefaultDirectory() 489 + directory := atproto.GetDirectory() 513 490 atID, err := syntax.ParseAtIdentifier(did) 514 491 if err != nil { 515 492 return nil, fmt.Errorf("invalid DID format: %w", err)