A container registry that uses the AT Protocol for manifest storage and S3 for blob storage.
0
fork

Configure Feed

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

update indigo repo, fix pds carstore

+58 -44
+2 -2
go.mod
··· 4 4 5 5 require ( 6 6 github.com/aws/aws-sdk-go v1.55.5 7 - github.com/bluesky-social/indigo v0.0.0-20251003000214-3259b215110e 7 + github.com/bluesky-social/indigo v0.0.0-20251014222321-1e8718ae9f33 8 8 github.com/distribution/distribution/v3 v3.0.0 9 9 github.com/distribution/reference v0.6.0 10 10 github.com/golang-jwt/jwt/v5 v5.2.2 ··· 25 25 require ( 26 26 github.com/beorn7/perks v1.0.1 // indirect 27 27 github.com/bshuster-repo/logrus-logstash-hook v1.0.0 // indirect 28 - github.com/carlmjohnson/versioninfo v0.22.5 // indirect 29 28 github.com/cenkalti/backoff/v4 v4.3.0 // indirect 30 29 github.com/cespare/xxhash/v2 v2.3.0 // indirect 31 30 github.com/coreos/go-systemd/v22 v22.5.0 // indirect ··· 33 32 github.com/docker/docker-credential-helpers v0.8.2 // indirect 34 33 github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect 35 34 github.com/docker/go-metrics v0.0.1 // indirect 35 + github.com/earthboundkid/versioninfo/v2 v2.24.1 // indirect 36 36 github.com/felixge/httpsnoop v1.0.4 // indirect 37 37 github.com/go-jose/go-jose/v4 v4.1.2 // indirect 38 38 github.com/go-logr/logr v1.4.2 // indirect
+4 -4
go.sum
··· 16 16 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 17 17 github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYEDvkta6I8/rnYM5gSdSV2tJ6XbZuEtY= 18 18 github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= 19 - github.com/bluesky-social/indigo v0.0.0-20251003000214-3259b215110e h1:IutKPwmbU0LrYqw03EuwJtMdAe67rDTrL1U8S8dicRU= 20 - github.com/bluesky-social/indigo v0.0.0-20251003000214-3259b215110e/go.mod h1:n6QE1NDPFoi7PRbMUZmc2y7FibCqiVU4ePpsvhHUBR8= 19 + github.com/bluesky-social/indigo v0.0.0-20251014222321-1e8718ae9f33 h1:x06Y6VyYUCvqWl2AS4/3NBBbRf8wWNMd3YrI44NTHS8= 20 + github.com/bluesky-social/indigo v0.0.0-20251014222321-1e8718ae9f33/go.mod h1:GuGAU33qKulpZCZNPcUeIQ4RW6KzNvOy7s8MSUXbAng= 21 21 github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= 22 22 github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= 23 23 github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= ··· 28 28 github.com/bsm/gomega v1.26.0/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= 29 29 github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= 30 30 github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= 31 - github.com/carlmjohnson/versioninfo v0.22.5 h1:O00sjOLUAFxYQjlN/bzYTuZiS0y6fWDQjMRvwtKgwwc= 32 - github.com/carlmjohnson/versioninfo v0.22.5/go.mod h1:QT9mph3wcVfISUKd0i9sZfVrPviHuSF+cUtLjm2WSf8= 33 31 github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= 34 32 github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= 35 33 github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= ··· 60 58 github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= 61 59 github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= 62 60 github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= 61 + github.com/earthboundkid/versioninfo/v2 v2.24.1 h1:SJTMHaoUx3GzjjnUO1QzP3ZXK6Ee/nbWyCm58eY3oUg= 62 + github.com/earthboundkid/versioninfo/v2 v2.24.1/go.mod h1:VcWEooDEuyUJnMfbdTh0uFN4cfEIg+kHMuWB2CDCLjw= 63 63 github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= 64 64 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= 65 65 github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
+4 -4
pkg/atproto/client.go
··· 11 11 "net/http" 12 12 "strings" 13 13 14 - "github.com/bluesky-social/indigo/atproto/client" 14 + "github.com/bluesky-social/indigo/atproto/atclient" 15 15 ) 16 16 17 17 // Sentinel errors ··· 26 26 accessToken string // For Basic Auth only 27 27 httpClient *http.Client 28 28 useIndigoClient bool // true if using indigo's OAuth client (handles auth automatically) 29 - indigoClient *client.APIClient // indigo's API client for OAuth requests 29 + indigoClient *atclient.APIClient // indigo's API client for OAuth requests 30 30 } 31 31 32 32 // NewClient creates a new ATProto client for Basic Auth tokens (app passwords) ··· 41 41 42 42 // NewClientWithIndigoClient creates an ATProto client using indigo's API client 43 43 // This uses indigo's native XRPC methods with automatic DPoP handling 44 - func NewClientWithIndigoClient(pdsEndpoint, did string, indigoClient *client.APIClient) *Client { 44 + func NewClientWithIndigoClient(pdsEndpoint, did string, indigoClient *atclient.APIClient) *Client { 45 45 return &Client{ 46 46 pdsEndpoint: pdsEndpoint, 47 47 did: did, ··· 125 125 err := c.indigoClient.Get(ctx, "com.atproto.repo.getRecord", params, &result) 126 126 if err != nil { 127 127 // Check for RecordNotFound error from indigo's APIError type 128 - var apiErr *client.APIError 128 + var apiErr *atclient.APIError 129 129 if errors.As(err, &apiErr) { 130 130 if apiErr.StatusCode == 404 || apiErr.Name == "RecordNotFound" { 131 131 return nil, ErrRecordNotFound
+17
pkg/hold/pds/crew.go
··· 5 5 "fmt" 6 6 "time" 7 7 8 + "github.com/bluesky-social/indigo/repo" 8 9 "github.com/ipfs/go-cid" 9 10 ) 10 11 ··· 40 41 if err != nil { 41 42 return cid.Undef, fmt.Errorf("failed to persist commit: %w", err) 42 43 } 44 + 45 + // Create a new session for the next operation (old session is now closed) 46 + newSession, err := p.carstore.NewDeltaSession(ctx, p.uid, nil) 47 + if err != nil { 48 + return cid.Undef, fmt.Errorf("failed to create new session: %w", err) 49 + } 50 + 51 + // Load repo from the newly committed head (not NewRepo which creates empty MST) 52 + newRepo, err := repo.OpenRepo(ctx, newSession, root) 53 + if err != nil { 54 + return cid.Undef, fmt.Errorf("failed to reload repo after commit: %w", err) 55 + } 56 + 57 + // Update the stored session and repo 58 + p.session = newSession 59 + p.repo = newRepo 43 60 44 61 return recordCID, nil 45 62 }
+6 -6
pkg/hold/pds/keys.go
··· 5 5 "os" 6 6 "path/filepath" 7 7 8 - "github.com/bluesky-social/indigo/atproto/crypto" 8 + "github.com/bluesky-social/indigo/atproto/atcrypto" 9 9 ) 10 10 11 11 // GenerateOrLoadKey generates a new K256 key pair or loads an existing one 12 - func GenerateOrLoadKey(keyPath string) (*crypto.PrivateKeyK256, error) { 12 + func GenerateOrLoadKey(keyPath string) (*atcrypto.PrivateKeyK256, error) { 13 13 // Ensure directory exists 14 14 dir := filepath.Dir(keyPath) 15 15 if err := os.MkdirAll(dir, 0700); err != nil { ··· 27 27 } 28 28 29 29 // generateKey creates a new K256 (secp256k1) key pair using indigo's atcrypto 30 - func generateKey(keyPath string) (*crypto.PrivateKeyK256, error) { 30 + func generateKey(keyPath string) (*atcrypto.PrivateKeyK256, error) { 31 31 // Generate K256 key (secp256k1) using indigo 32 - privateKey, err := crypto.GeneratePrivateKeyK256() 32 + privateKey, err := atcrypto.GeneratePrivateKeyK256() 33 33 if err != nil { 34 34 return nil, fmt.Errorf("failed to generate key: %w", err) 35 35 } ··· 47 47 } 48 48 49 49 // loadKey loads an existing private key from disk 50 - func loadKey(keyPath string) (*crypto.PrivateKeyK256, error) { 50 + func loadKey(keyPath string) (*atcrypto.PrivateKeyK256, error) { 51 51 // Read key bytes 52 52 keyBytes, err := os.ReadFile(keyPath) 53 53 if err != nil { ··· 55 55 } 56 56 57 57 // Try to parse as K256 private key 58 - privateKey, err := crypto.ParsePrivateBytesK256(keyBytes) 58 + privateKey, err := atcrypto.ParsePrivateBytesK256(keyBytes) 59 59 if err != nil { 60 60 // Check if this is an old P-256 PEM key (migration) 61 61 if isPEMFormat(keyBytes) {
+25 -28
pkg/hold/pds/server.go
··· 6 6 "os" 7 7 "path/filepath" 8 8 9 - "github.com/bluesky-social/indigo/atproto/crypto" 9 + "github.com/bluesky-social/indigo/atproto/atcrypto" 10 10 "github.com/bluesky-social/indigo/carstore" 11 11 "github.com/bluesky-social/indigo/models" 12 12 "github.com/bluesky-social/indigo/repo" ··· 21 21 repo *repo.Repo 22 22 dbPath string 23 23 uid models.Uid 24 - signingKey *crypto.PrivateKeyK256 24 + signingKey *atcrypto.PrivateKeyK256 25 25 } 26 26 27 27 // NewHoldPDS creates or opens a hold PDS with SQLite carstore ··· 45 45 return nil, fmt.Errorf("failed to create sqlite store: %w", err) 46 46 } 47 47 48 - cs := sqlStore.CarStore() 48 + // Use SQLiteStore directly, not the CarStore() wrapper 49 + // The wrapper has a bug where GetUserRepoHead checks CarShard.ID which SQLite doesn't populate 50 + cs := sqlStore 49 51 50 52 // For a single-user hold, we use a fixed UID (1) 51 53 uid := models.Uid(1) 52 54 53 - // Try to get existing repo head 54 - _, err = cs.GetUserRepoHead(ctx, uid) 55 + // Check if repo already exists with valid head 56 + head, err := cs.GetUserRepoHead(ctx, uid) 57 + hasValidRepo := (err == nil && head.Defined()) 55 58 56 59 var session *carstore.DeltaSession 57 60 var r *repo.Repo 58 61 62 + // Create a session connected to this user's data in carstore 63 + session, err = cs.NewDeltaSession(ctx, uid, nil) 59 64 if err != nil { 60 - // Repo doesn't exist yet, create new delta session 61 - session, err = cs.NewDeltaSession(ctx, uid, nil) 62 - if err != nil { 63 - return nil, fmt.Errorf("failed to create delta session: %w", err) 64 - } 65 + return nil, fmt.Errorf("failed to create delta session: %w", err) 66 + } 65 67 66 - // Create new repo with session as blockstore (needs pointer) 68 + if !hasValidRepo { 69 + // No valid repo - create new empty repo 67 70 r = repo.NewRepo(ctx, did, session) 68 71 } else { 69 - // TODO: Load existing repo 70 - // For now, just create a new session 71 - session, err = cs.NewDeltaSession(ctx, uid, nil) 72 + // Repo exists with valid head - load from existing head 73 + r, err = repo.OpenRepo(ctx, session, head) 72 74 if err != nil { 73 - return nil, fmt.Errorf("failed to create delta session: %w", err) 75 + return nil, fmt.Errorf("failed to open existing repo: %w", err) 74 76 } 75 - 76 - r = repo.NewRepo(ctx, did, session) 77 77 } 78 78 79 79 return &HoldPDS{ ··· 94 94 } 95 95 96 96 // SigningKey returns the hold's signing key 97 - func (p *HoldPDS) SigningKey() *crypto.PrivateKeyK256 { 97 + func (p *HoldPDS) SigningKey() *atcrypto.PrivateKeyK256 { 98 98 return p.signingKey 99 99 } 100 100 ··· 106 106 107 107 // Check if repo already has commits 108 108 head, err := p.carstore.GetUserRepoHead(ctx, p.uid) 109 - if err == nil { 110 - // Repo exists - check if we need to re-bootstrap due to key change 111 - // If the repo exists but is empty/invalid, we should re-bootstrap 112 - if head.String() == "" || head.String() == "b" { 113 - fmt.Printf("⚠️ Detected invalid repo state, re-bootstrapping...\n") 114 - } else { 115 - fmt.Printf("⏭️ Skipping PDS bootstrap: repo already initialized (head: %s)\n", head.String()[:16]) 116 - return nil 117 - } 109 + if err != nil || !head.Defined() { 110 + // No repo exists yet, bootstrap 111 + fmt.Printf("🚀 Bootstrapping hold PDS with owner: %s\n", ownerDID) 112 + } else { 113 + // Repo exists and is valid 114 + fmt.Printf("⏭️ Skipping PDS bootstrap: repo already initialized (head: %s)\n", head.String()[:16]) 115 + return nil 118 116 } 119 117 120 118 // Add hold owner as first crew member with admin role 121 - fmt.Printf("🚀 Bootstrapping hold PDS with owner: %s\n", ownerDID) 122 119 _, err = p.AddCrewMember(ctx, ownerDID, "admin", []string{"blob:read", "blob:write", "crew:admin"}) 123 120 if err != nil { 124 121 return fmt.Errorf("failed to add owner as crew member: %w", err)