From 4c930e8ae5f3d8d7a3dd1c896ac55b6275a56600 Mon Sep 17 00:00:00 2001 From: Evan Jarrett Date: Tue, 14 Oct 2025 23:18:01 -0500 Subject: [PATCH] update indigo repo, fix pds carstore --- go.mod | 4 ++-- go.sum | 8 +++---- pkg/atproto/client.go | 8 +++---- pkg/hold/pds/crew.go | 17 ++++++++++++++ pkg/hold/pds/keys.go | 12 +++++----- pkg/hold/pds/server.go | 53 ++++++++++++++++++++---------------------- 6 files changed, 58 insertions(+), 44 deletions(-) diff --git a/go.mod b/go.mod index 249521f..eae1700 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.24.7 require ( github.com/aws/aws-sdk-go v1.55.5 - github.com/bluesky-social/indigo v0.0.0-20251003000214-3259b215110e + github.com/bluesky-social/indigo v0.0.0-20251014222321-1e8718ae9f33 github.com/distribution/distribution/v3 v3.0.0 github.com/distribution/reference v0.6.0 github.com/golang-jwt/jwt/v5 v5.2.2 @@ -25,7 +25,6 @@ require ( require ( github.com/beorn7/perks v1.0.1 // indirect github.com/bshuster-repo/logrus-logstash-hook v1.0.0 // indirect - github.com/carlmjohnson/versioninfo v0.22.5 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect @@ -33,6 +32,7 @@ require ( github.com/docker/docker-credential-helpers v0.8.2 // indirect github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect github.com/docker/go-metrics v0.0.1 // indirect + github.com/earthboundkid/versioninfo/v2 v2.24.1 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-jose/go-jose/v4 v4.1.2 // indirect github.com/go-logr/logr v1.4.2 // indirect diff --git a/go.sum b/go.sum index aa90135..002ac82 100644 --- a/go.sum +++ b/go.sum @@ -16,8 +16,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYEDvkta6I8/rnYM5gSdSV2tJ6XbZuEtY= github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= -github.com/bluesky-social/indigo v0.0.0-20251003000214-3259b215110e h1:IutKPwmbU0LrYqw03EuwJtMdAe67rDTrL1U8S8dicRU= -github.com/bluesky-social/indigo v0.0.0-20251003000214-3259b215110e/go.mod h1:n6QE1NDPFoi7PRbMUZmc2y7FibCqiVU4ePpsvhHUBR8= +github.com/bluesky-social/indigo v0.0.0-20251014222321-1e8718ae9f33 h1:x06Y6VyYUCvqWl2AS4/3NBBbRf8wWNMd3YrI44NTHS8= +github.com/bluesky-social/indigo v0.0.0-20251014222321-1e8718ae9f33/go.mod h1:GuGAU33qKulpZCZNPcUeIQ4RW6KzNvOy7s8MSUXbAng= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= @@ -28,8 +28,6 @@ github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdb github.com/bsm/gomega v1.26.0/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= -github.com/carlmjohnson/versioninfo v0.22.5 h1:O00sjOLUAFxYQjlN/bzYTuZiS0y6fWDQjMRvwtKgwwc= -github.com/carlmjohnson/versioninfo v0.22.5/go.mod h1:QT9mph3wcVfISUKd0i9sZfVrPviHuSF+cUtLjm2WSf8= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -60,6 +58,8 @@ github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= +github.com/earthboundkid/versioninfo/v2 v2.24.1 h1:SJTMHaoUx3GzjjnUO1QzP3ZXK6Ee/nbWyCm58eY3oUg= +github.com/earthboundkid/versioninfo/v2 v2.24.1/go.mod h1:VcWEooDEuyUJnMfbdTh0uFN4cfEIg+kHMuWB2CDCLjw= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= diff --git a/pkg/atproto/client.go b/pkg/atproto/client.go index d99e752..bb41187 100644 --- a/pkg/atproto/client.go +++ b/pkg/atproto/client.go @@ -11,7 +11,7 @@ import ( "net/http" "strings" - "github.com/bluesky-social/indigo/atproto/client" + "github.com/bluesky-social/indigo/atproto/atclient" ) // Sentinel errors @@ -26,7 +26,7 @@ type Client struct { accessToken string // For Basic Auth only httpClient *http.Client useIndigoClient bool // true if using indigo's OAuth client (handles auth automatically) - indigoClient *client.APIClient // indigo's API client for OAuth requests + indigoClient *atclient.APIClient // indigo's API client for OAuth requests } // NewClient creates a new ATProto client for Basic Auth tokens (app passwords) @@ -41,7 +41,7 @@ func NewClient(pdsEndpoint, did, accessToken string) *Client { // NewClientWithIndigoClient creates an ATProto client using indigo's API client // This uses indigo's native XRPC methods with automatic DPoP handling -func NewClientWithIndigoClient(pdsEndpoint, did string, indigoClient *client.APIClient) *Client { +func NewClientWithIndigoClient(pdsEndpoint, did string, indigoClient *atclient.APIClient) *Client { return &Client{ pdsEndpoint: pdsEndpoint, did: did, @@ -125,7 +125,7 @@ func (c *Client) GetRecord(ctx context.Context, collection, rkey string) (*Recor err := c.indigoClient.Get(ctx, "com.atproto.repo.getRecord", params, &result) if err != nil { // Check for RecordNotFound error from indigo's APIError type - var apiErr *client.APIError + var apiErr *atclient.APIError if errors.As(err, &apiErr) { if apiErr.StatusCode == 404 || apiErr.Name == "RecordNotFound" { return nil, ErrRecordNotFound diff --git a/pkg/hold/pds/crew.go b/pkg/hold/pds/crew.go index 8a86ca9..c0ca8db 100644 --- a/pkg/hold/pds/crew.go +++ b/pkg/hold/pds/crew.go @@ -5,6 +5,7 @@ import ( "fmt" "time" + "github.com/bluesky-social/indigo/repo" "github.com/ipfs/go-cid" ) @@ -41,6 +42,22 @@ func (p *HoldPDS) AddCrewMember(ctx context.Context, memberDID, role string, per return cid.Undef, fmt.Errorf("failed to persist commit: %w", err) } + // Create a new session for the next operation (old session is now closed) + newSession, err := p.carstore.NewDeltaSession(ctx, p.uid, nil) + if err != nil { + return cid.Undef, fmt.Errorf("failed to create new session: %w", err) + } + + // Load repo from the newly committed head (not NewRepo which creates empty MST) + newRepo, err := repo.OpenRepo(ctx, newSession, root) + if err != nil { + return cid.Undef, fmt.Errorf("failed to reload repo after commit: %w", err) + } + + // Update the stored session and repo + p.session = newSession + p.repo = newRepo + return recordCID, nil } diff --git a/pkg/hold/pds/keys.go b/pkg/hold/pds/keys.go index 9dc9900..b1b0df1 100644 --- a/pkg/hold/pds/keys.go +++ b/pkg/hold/pds/keys.go @@ -5,11 +5,11 @@ import ( "os" "path/filepath" - "github.com/bluesky-social/indigo/atproto/crypto" + "github.com/bluesky-social/indigo/atproto/atcrypto" ) // GenerateOrLoadKey generates a new K256 key pair or loads an existing one -func GenerateOrLoadKey(keyPath string) (*crypto.PrivateKeyK256, error) { +func GenerateOrLoadKey(keyPath string) (*atcrypto.PrivateKeyK256, error) { // Ensure directory exists dir := filepath.Dir(keyPath) if err := os.MkdirAll(dir, 0700); err != nil { @@ -27,9 +27,9 @@ func GenerateOrLoadKey(keyPath string) (*crypto.PrivateKeyK256, error) { } // generateKey creates a new K256 (secp256k1) key pair using indigo's atcrypto -func generateKey(keyPath string) (*crypto.PrivateKeyK256, error) { +func generateKey(keyPath string) (*atcrypto.PrivateKeyK256, error) { // Generate K256 key (secp256k1) using indigo - privateKey, err := crypto.GeneratePrivateKeyK256() + privateKey, err := atcrypto.GeneratePrivateKeyK256() if err != nil { return nil, fmt.Errorf("failed to generate key: %w", err) } @@ -47,7 +47,7 @@ func generateKey(keyPath string) (*crypto.PrivateKeyK256, error) { } // loadKey loads an existing private key from disk -func loadKey(keyPath string) (*crypto.PrivateKeyK256, error) { +func loadKey(keyPath string) (*atcrypto.PrivateKeyK256, error) { // Read key bytes keyBytes, err := os.ReadFile(keyPath) if err != nil { @@ -55,7 +55,7 @@ func loadKey(keyPath string) (*crypto.PrivateKeyK256, error) { } // Try to parse as K256 private key - privateKey, err := crypto.ParsePrivateBytesK256(keyBytes) + privateKey, err := atcrypto.ParsePrivateBytesK256(keyBytes) if err != nil { // Check if this is an old P-256 PEM key (migration) if isPEMFormat(keyBytes) { diff --git a/pkg/hold/pds/server.go b/pkg/hold/pds/server.go index 6b0c3fa..43e4977 100644 --- a/pkg/hold/pds/server.go +++ b/pkg/hold/pds/server.go @@ -6,7 +6,7 @@ import ( "os" "path/filepath" - "github.com/bluesky-social/indigo/atproto/crypto" + "github.com/bluesky-social/indigo/atproto/atcrypto" "github.com/bluesky-social/indigo/carstore" "github.com/bluesky-social/indigo/models" "github.com/bluesky-social/indigo/repo" @@ -21,7 +21,7 @@ type HoldPDS struct { repo *repo.Repo dbPath string uid models.Uid - signingKey *crypto.PrivateKeyK256 + signingKey *atcrypto.PrivateKeyK256 } // NewHoldPDS creates or opens a hold PDS with SQLite carstore @@ -45,35 +45,35 @@ func NewHoldPDS(ctx context.Context, did, publicURL, dbPath, keyPath string) (*H return nil, fmt.Errorf("failed to create sqlite store: %w", err) } - cs := sqlStore.CarStore() + // Use SQLiteStore directly, not the CarStore() wrapper + // The wrapper has a bug where GetUserRepoHead checks CarShard.ID which SQLite doesn't populate + cs := sqlStore // For a single-user hold, we use a fixed UID (1) uid := models.Uid(1) - // Try to get existing repo head - _, err = cs.GetUserRepoHead(ctx, uid) + // Check if repo already exists with valid head + head, err := cs.GetUserRepoHead(ctx, uid) + hasValidRepo := (err == nil && head.Defined()) var session *carstore.DeltaSession var r *repo.Repo + // Create a session connected to this user's data in carstore + session, err = cs.NewDeltaSession(ctx, uid, nil) if err != nil { - // Repo doesn't exist yet, create new delta session - session, err = cs.NewDeltaSession(ctx, uid, nil) - if err != nil { - return nil, fmt.Errorf("failed to create delta session: %w", err) - } + return nil, fmt.Errorf("failed to create delta session: %w", err) + } - // Create new repo with session as blockstore (needs pointer) + if !hasValidRepo { + // No valid repo - create new empty repo r = repo.NewRepo(ctx, did, session) } else { - // TODO: Load existing repo - // For now, just create a new session - session, err = cs.NewDeltaSession(ctx, uid, nil) + // Repo exists with valid head - load from existing head + r, err = repo.OpenRepo(ctx, session, head) if err != nil { - return nil, fmt.Errorf("failed to create delta session: %w", err) + return nil, fmt.Errorf("failed to open existing repo: %w", err) } - - r = repo.NewRepo(ctx, did, session) } return &HoldPDS{ @@ -94,7 +94,7 @@ func (p *HoldPDS) DID() string { } // SigningKey returns the hold's signing key -func (p *HoldPDS) SigningKey() *crypto.PrivateKeyK256 { +func (p *HoldPDS) SigningKey() *atcrypto.PrivateKeyK256 { return p.signingKey } @@ -106,19 +106,16 @@ func (p *HoldPDS) Bootstrap(ctx context.Context, ownerDID string) error { // Check if repo already has commits head, err := p.carstore.GetUserRepoHead(ctx, p.uid) - if err == nil { - // Repo exists - check if we need to re-bootstrap due to key change - // If the repo exists but is empty/invalid, we should re-bootstrap - if head.String() == "" || head.String() == "b" { - fmt.Printf("⚠️ Detected invalid repo state, re-bootstrapping...\n") - } else { - fmt.Printf("⏭️ Skipping PDS bootstrap: repo already initialized (head: %s)\n", head.String()[:16]) - return nil - } + if err != nil || !head.Defined() { + // No repo exists yet, bootstrap + fmt.Printf("🚀 Bootstrapping hold PDS with owner: %s\n", ownerDID) + } else { + // Repo exists and is valid + fmt.Printf("⏭️ Skipping PDS bootstrap: repo already initialized (head: %s)\n", head.String()[:16]) + return nil } // Add hold owner as first crew member with admin role - fmt.Printf("🚀 Bootstrapping hold PDS with owner: %s\n", ownerDID) _, err = p.AddCrewMember(ctx, ownerDID, "admin", []string{"blob:read", "blob:write", "crew:admin"}) if err != nil { return fmt.Errorf("failed to add owner as crew member: %w", err)