From dd79b8a0ee5ed3165668ba15e5b59aa2eb322bf9 Mon Sep 17 00:00:00 2001 From: Evan Jarrett Date: Thu, 23 Oct 2025 16:43:58 -0500 Subject: [PATCH] actually wrap them in a envvar check --- cmd/hold/main.go | 4 ++-- pkg/hold/config.go | 6 ++++++ pkg/hold/oci/xrpc.go | 10 ++++++---- pkg/hold/oci/xrpc_test.go | 4 ++-- pkg/hold/pds/captain_test.go | 2 +- pkg/hold/pds/did_test.go | 8 ++++---- pkg/hold/pds/server.go | 32 +++++++++++++++++--------------- pkg/hold/pds/server_test.go | 22 +++++++++++----------- pkg/hold/pds/status.go | 6 ++++++ pkg/hold/pds/status_test.go | 4 ++-- pkg/hold/pds/xrpc_test.go | 4 ++-- 11 files changed, 59 insertions(+), 43 deletions(-) diff --git a/cmd/hold/main.go b/cmd/hold/main.go index 04e7ce9..4feabb2 100644 --- a/cmd/hold/main.go +++ b/cmd/hold/main.go @@ -43,7 +43,7 @@ func main() { // Initialize PDS with carstore and keys ctx := context.Background() - holdPDS, err = pds.NewHoldPDS(ctx, holdDID, cfg.Server.PublicURL, cfg.Database.Path, cfg.Database.KeyPath) + holdPDS, err = pds.NewHoldPDS(ctx, holdDID, cfg.Server.PublicURL, cfg.Database.Path, cfg.Database.KeyPath, cfg.Registration.EnableBlueskyPosts) if err != nil { log.Fatalf("Failed to initialize embedded PDS: %v", err) } @@ -103,7 +103,7 @@ func main() { xrpcHandler = pds.NewXRPCHandler(holdPDS, *s3Service, driver, broadcaster, nil) // Create OCI XRPC handler (multipart upload endpoints) - ociHandler = oci.NewXRPCHandler(holdPDS, *s3Service, driver, cfg.Server.DisablePresignedURLs, nil) + ociHandler = oci.NewXRPCHandler(holdPDS, *s3Service, driver, cfg.Server.DisablePresignedURLs, cfg.Registration.EnableBlueskyPosts, nil) } // Setup HTTP routes with chi router diff --git a/pkg/hold/config.go b/pkg/hold/config.go index ccf7306..95cd58c 100644 --- a/pkg/hold/config.go +++ b/pkg/hold/config.go @@ -32,6 +32,11 @@ type RegistrationConfig struct { // ProfileAvatarURL is the URL to download the avatar image from (from env: HOLD_PROFILE_AVATAR) // If set, the avatar will be downloaded and uploaded as a blob during bootstrap ProfileAvatarURL string `yaml:"profile_avatar_url"` + + // EnableBlueskyPosts controls whether to create Bluesky posts for manifest uploads (from env: HOLD_BLUESKY_POSTS_ENABLED) + // If true, creates posts when users push images + // Can be overridden per-hold via captain record's enableManifestPosts field + EnableBlueskyPosts bool `yaml:"enable_bluesky_posts"` } // StorageConfig wraps distribution's storage configuration @@ -96,6 +101,7 @@ func LoadConfigFromEnv() (*Config, error) { cfg.Registration.OwnerDID = os.Getenv("HOLD_OWNER") cfg.Registration.AllowAllCrew = os.Getenv("HOLD_ALLOW_ALL_CREW") == "true" cfg.Registration.ProfileAvatarURL = getEnvOrDefault("HOLD_PROFILE_AVATAR", "https://imgs.blue/evan.jarrett.net/1TpTOdtS60GdJWBYEqtK22y688jajbQ9a5kbYRFtwuqrkBAE") + cfg.Registration.EnableBlueskyPosts = os.Getenv("HOLD_BLUESKY_POSTS_ENABLED") == "true" // Database configuration (optional - enables embedded PDS) // Note: HOLD_DATABASE_DIR is a directory path, carstore creates db.sqlite3 inside it diff --git a/pkg/hold/oci/xrpc.go b/pkg/hold/oci/xrpc.go index 1802cbc..021a7e9 100644 --- a/pkg/hold/oci/xrpc.go +++ b/pkg/hold/oci/xrpc.go @@ -21,10 +21,11 @@ type XRPCHandler struct { MultipartMgr *MultipartManager // Exported for access in route handlers pds *pds.HoldPDS httpClient pds.HTTPClient + enableBlueskyPosts bool } // NewXRPCHandler creates a new OCI XRPC handler -func NewXRPCHandler(holdPDS *pds.HoldPDS, s3Service s3.S3Service, driver storagedriver.StorageDriver, disablePresignedURLs bool, httpClient pds.HTTPClient) *XRPCHandler { +func NewXRPCHandler(holdPDS *pds.HoldPDS, s3Service s3.S3Service, driver storagedriver.StorageDriver, disablePresignedURLs bool, enableBlueskyPosts bool, httpClient pds.HTTPClient) *XRPCHandler { return &XRPCHandler{ driver: driver, disablePresignedURLs: disablePresignedURLs, @@ -32,6 +33,7 @@ func NewXRPCHandler(holdPDS *pds.HoldPDS, s3Service s3.S3Service, driver storage s3Service: s3Service, pds: holdPDS, httpClient: httpClient, + enableBlueskyPosts: enableBlueskyPosts, } } @@ -242,9 +244,9 @@ func (h *XRPCHandler) HandleNotifyManifest(w http.ResponseWriter, r *http.Reques } // Check if manifest posts are enabled - // TODO: Check captain record enableManifestPosts field - // For now, posts are always created - postsEnabled := true + // Controlled by HOLD_BLUESKY_POSTS_ENABLED environment variable + // TODO: Override with captain record enableManifestPosts field if set + postsEnabled := h.enableBlueskyPosts // Create layer records for each blob layersCreated := 0 diff --git a/pkg/hold/oci/xrpc_test.go b/pkg/hold/oci/xrpc_test.go index dfbd4c7..d675526 100644 --- a/pkg/hold/oci/xrpc_test.go +++ b/pkg/hold/oci/xrpc_test.go @@ -97,7 +97,7 @@ func setupTestOCIHandler(t *testing.T) (*XRPCHandler, context.Context) { t.Fatalf("Failed to copy shared signing key: %v", err) } - holdPDS, err := pds.NewHoldPDS(ctx, holdDID, publicURL, dbPath, keyPath) + holdPDS, err := pds.NewHoldPDS(ctx, holdDID, publicURL, dbPath, keyPath, false) if err != nil { t.Fatalf("Failed to create PDS: %v", err) } @@ -126,7 +126,7 @@ func setupTestOCIHandler(t *testing.T) (*XRPCHandler, context.Context) { // Create OCI handler with buffered mode (no S3) mockS3 := s3.S3Service{} - handler := NewXRPCHandler(holdPDS, mockS3, driver, true, mockClient) + handler := NewXRPCHandler(holdPDS, mockS3, driver, true, false, mockClient) return handler, ctx } diff --git a/pkg/hold/pds/captain_test.go b/pkg/hold/pds/captain_test.go index 6fcef82..9dc5e9f 100644 --- a/pkg/hold/pds/captain_test.go +++ b/pkg/hold/pds/captain_test.go @@ -28,7 +28,7 @@ func setupTestPDS(t *testing.T) (*HoldPDS, context.Context) { t.Fatalf("Failed to copy shared signing key: %v", err) } - pds, err := NewHoldPDS(ctx, "did:web:hold.example.com", "https://hold.example.com", dbPath, keyPath) + pds, err := NewHoldPDS(ctx, "did:web:hold.example.com", "https://hold.example.com", dbPath, keyPath, false) if err != nil { t.Fatalf("Failed to create test PDS: %v", err) } diff --git a/pkg/hold/pds/did_test.go b/pkg/hold/pds/did_test.go index d807456..585ec15 100644 --- a/pkg/hold/pds/did_test.go +++ b/pkg/hold/pds/did_test.go @@ -84,7 +84,7 @@ func TestGenerateDIDDocument(t *testing.T) { keyPath := filepath.Join(tmpDir, "signing-key") publicURL := "https://hold.example.com" - pds, err := NewHoldPDS(ctx, "did:web:hold.example.com", publicURL, dbPath, keyPath) + pds, err := NewHoldPDS(ctx, "did:web:hold.example.com", publicURL, dbPath, keyPath, false) if err != nil { t.Fatalf("Failed to create PDS: %v", err) } @@ -183,7 +183,7 @@ func TestGenerateDIDDocument_WithPort(t *testing.T) { keyPath := filepath.Join(tmpDir, "signing-key") publicURL := "https://hold.example.com:8443" - pds, err := NewHoldPDS(ctx, "did:web:hold.example.com:8443", publicURL, dbPath, keyPath) + pds, err := NewHoldPDS(ctx, "did:web:hold.example.com:8443", publicURL, dbPath, keyPath, false) if err != nil { t.Fatalf("Failed to create PDS: %v", err) } @@ -213,7 +213,7 @@ func TestMarshalDIDDocument(t *testing.T) { keyPath := filepath.Join(tmpDir, "signing-key") publicURL := "https://hold.example.com" - pds, err := NewHoldPDS(ctx, "did:web:hold.example.com", publicURL, dbPath, keyPath) + pds, err := NewHoldPDS(ctx, "did:web:hold.example.com", publicURL, dbPath, keyPath, false) if err != nil { t.Fatalf("Failed to create PDS: %v", err) } @@ -261,7 +261,7 @@ func TestGenerateDIDDocument_InvalidURL(t *testing.T) { keyPath := filepath.Join(tmpDir, "signing-key") publicURL := "https://hold.example.com" - pds, err := NewHoldPDS(ctx, "did:web:hold.example.com", publicURL, dbPath, keyPath) + pds, err := NewHoldPDS(ctx, "did:web:hold.example.com", publicURL, dbPath, keyPath, false) if err != nil { t.Fatalf("Failed to create PDS: %v", err) } diff --git a/pkg/hold/pds/server.go b/pkg/hold/pds/server.go index 2d7eed6..9846649 100644 --- a/pkg/hold/pds/server.go +++ b/pkg/hold/pds/server.go @@ -30,17 +30,18 @@ func init() { // HoldPDS is a minimal ATProto PDS implementation for a hold service type HoldPDS struct { - did string - PublicURL string - carstore carstore.CarStore - repomgr *RepoManager - dbPath string - uid models.Uid - signingKey *atcrypto.PrivateKeyK256 + did string + PublicURL string + carstore carstore.CarStore + repomgr *RepoManager + dbPath string + uid models.Uid + signingKey *atcrypto.PrivateKeyK256 + enableBlueskyPosts bool } // NewHoldPDS creates or opens a hold PDS with SQLite carstore -func NewHoldPDS(ctx context.Context, did, publicURL, dbPath, keyPath string) (*HoldPDS, error) { +func NewHoldPDS(ctx context.Context, did, publicURL, dbPath, keyPath string, enableBlueskyPosts bool) (*HoldPDS, error) { // Generate or load signing key signingKey, err := GenerateOrLoadKey(keyPath) if err != nil { @@ -95,13 +96,14 @@ func NewHoldPDS(ctx context.Context, did, publicURL, dbPath, keyPath string) (*H } return &HoldPDS{ - did: did, - PublicURL: publicURL, - carstore: cs, - repomgr: rm, - dbPath: dbPath, - uid: uid, - signingKey: signingKey, + did: did, + PublicURL: publicURL, + carstore: cs, + repomgr: rm, + dbPath: dbPath, + uid: uid, + signingKey: signingKey, + enableBlueskyPosts: enableBlueskyPosts, }, nil } diff --git a/pkg/hold/pds/server_test.go b/pkg/hold/pds/server_test.go index 6f3a847..22c7178 100644 --- a/pkg/hold/pds/server_test.go +++ b/pkg/hold/pds/server_test.go @@ -23,7 +23,7 @@ func TestNewHoldPDS_NewRepo(t *testing.T) { did := "did:web:hold.example.com" publicURL := "https://hold.example.com" - pds, err := NewHoldPDS(ctx, did, publicURL, dbPath, keyPath) + pds, err := NewHoldPDS(ctx, did, publicURL, dbPath, keyPath, false) if err != nil { t.Fatalf("NewHoldPDS failed: %v", err) } @@ -62,7 +62,7 @@ func TestNewHoldPDS_ExistingRepo(t *testing.T) { publicURL := "https://hold.example.com" // Create first PDS instance and bootstrap it - pds1, err := NewHoldPDS(ctx, did, publicURL, dbPath, keyPath) + pds1, err := NewHoldPDS(ctx, did, publicURL, dbPath, keyPath, false) if err != nil { t.Fatalf("First NewHoldPDS failed: %v", err) } @@ -86,7 +86,7 @@ func TestNewHoldPDS_ExistingRepo(t *testing.T) { pds1.Close() // Re-open the same database - pds2, err := NewHoldPDS(ctx, did, publicURL, dbPath, keyPath) + pds2, err := NewHoldPDS(ctx, did, publicURL, dbPath, keyPath, false) if err != nil { t.Fatalf("Second NewHoldPDS failed: %v", err) } @@ -118,7 +118,7 @@ func TestBootstrap_NewRepo(t *testing.T) { dbPath := filepath.Join(tmpDir, "pds.db") keyPath := filepath.Join(tmpDir, "signing-key") - pds, err := NewHoldPDS(ctx, "did:web:hold.example.com", "https://hold.example.com", dbPath, keyPath) + pds, err := NewHoldPDS(ctx, "did:web:hold.example.com", "https://hold.example.com", dbPath, keyPath, false) if err != nil { t.Fatalf("NewHoldPDS failed: %v", err) } @@ -195,7 +195,7 @@ func TestBootstrap_Idempotent(t *testing.T) { dbPath := filepath.Join(tmpDir, "pds.db") keyPath := filepath.Join(tmpDir, "signing-key") - pds, err := NewHoldPDS(ctx, "did:web:hold.example.com", "https://hold.example.com", dbPath, keyPath) + pds, err := NewHoldPDS(ctx, "did:web:hold.example.com", "https://hold.example.com", dbPath, keyPath, false) if err != nil { t.Fatalf("NewHoldPDS failed: %v", err) } @@ -261,7 +261,7 @@ func TestBootstrap_EmptyOwner(t *testing.T) { dbPath := filepath.Join(tmpDir, "pds.db") keyPath := filepath.Join(tmpDir, "signing-key") - pds, err := NewHoldPDS(ctx, "did:web:hold.example.com", "https://hold.example.com", dbPath, keyPath) + pds, err := NewHoldPDS(ctx, "did:web:hold.example.com", "https://hold.example.com", dbPath, keyPath, false) if err != nil { t.Fatalf("NewHoldPDS failed: %v", err) } @@ -294,7 +294,7 @@ func TestLexiconTypeRegistration(t *testing.T) { dbPath := filepath.Join(tmpDir, "pds.db") keyPath := filepath.Join(tmpDir, "signing-key") - pds, err := NewHoldPDS(ctx, "did:web:hold.example.com", "https://hold.example.com", dbPath, keyPath) + pds, err := NewHoldPDS(ctx, "did:web:hold.example.com", "https://hold.example.com", dbPath, keyPath, false) if err != nil { t.Fatalf("NewHoldPDS failed: %v", err) } @@ -344,7 +344,7 @@ func TestBootstrap_DidWebOwner(t *testing.T) { dbPath := filepath.Join(tmpDir, "pds.db") keyPath := filepath.Join(tmpDir, "signing-key") - pds, err := NewHoldPDS(ctx, "did:web:hold01.atcr.io", "https://hold01.atcr.io", dbPath, keyPath) + pds, err := NewHoldPDS(ctx, "did:web:hold01.atcr.io", "https://hold01.atcr.io", dbPath, keyPath, false) if err != nil { t.Fatalf("NewHoldPDS failed: %v", err) } @@ -406,7 +406,7 @@ func TestBootstrap_MixedDIDs(t *testing.T) { // Create hold with did:web holdDID := "did:web:hold.example.com" - pds, err := NewHoldPDS(ctx, holdDID, "https://hold.example.com", dbPath, keyPath) + pds, err := NewHoldPDS(ctx, holdDID, "https://hold.example.com", dbPath, keyPath, false) if err != nil { t.Fatalf("NewHoldPDS failed: %v", err) } @@ -474,7 +474,7 @@ func TestBootstrap_CrewWithoutCaptain(t *testing.T) { dbPath := filepath.Join(tmpDir, "pds.db") keyPath := filepath.Join(tmpDir, "signing-key") - pds, err := NewHoldPDS(ctx, "did:web:hold.example.com", "https://hold.example.com", dbPath, keyPath) + pds, err := NewHoldPDS(ctx, "did:web:hold.example.com", "https://hold.example.com", dbPath, keyPath, false) if err != nil { t.Fatalf("NewHoldPDS failed: %v", err) } @@ -546,7 +546,7 @@ func TestBootstrap_CaptainWithoutCrew(t *testing.T) { dbPath := filepath.Join(tmpDir, "pds.db") keyPath := filepath.Join(tmpDir, "signing-key") - pds, err := NewHoldPDS(ctx, "did:web:hold.example.com", "https://hold.example.com", dbPath, keyPath) + pds, err := NewHoldPDS(ctx, "did:web:hold.example.com", "https://hold.example.com", dbPath, keyPath, false) if err != nil { t.Fatalf("NewHoldPDS failed: %v", err) } diff --git a/pkg/hold/pds/status.go b/pkg/hold/pds/status.go index c745a76..3e51682 100644 --- a/pkg/hold/pds/status.go +++ b/pkg/hold/pds/status.go @@ -17,6 +17,12 @@ const ( // status should be "online" or "offline" // Each call creates a unique post with a TID-based rkey func (p *HoldPDS) SetStatus(ctx context.Context, status string) error { + // Check if Bluesky posts are enabled + if !p.enableBlueskyPosts { + fmt.Printf("Bluesky posts disabled, skipping status post: %s\n", status) + return nil + } + // Format the post text with emoji indicator emoji := "🟢" if status == "offline" { diff --git a/pkg/hold/pds/status_test.go b/pkg/hold/pds/status_test.go index 22e754e..9f094c6 100644 --- a/pkg/hold/pds/status_test.go +++ b/pkg/hold/pds/status_test.go @@ -42,7 +42,7 @@ func TestStatusPost(t *testing.T) { did := "did:web:test.example.com" publicURL := "https://test.example.com" - holdPDS, err := NewHoldPDS(ctx, did, publicURL, dbPath, keyPath) + holdPDS, err := NewHoldPDS(ctx, did, publicURL, dbPath, keyPath, true) if err != nil { t.Fatalf("Failed to create test PDS: %v", err) } @@ -276,7 +276,7 @@ func TestMain(m *testing.M) { // Create one shared, bootstrapped PDS for read-only tests // Use in-memory database for speed sharedCtx = context.Background() - sharedPDS, err = NewHoldPDS(sharedCtx, "did:web:hold.example.com", "https://hold.example.com", ":memory:", sharedTestKeyPath) + sharedPDS, err = NewHoldPDS(sharedCtx, "did:web:hold.example.com", "https://hold.example.com", ":memory:", sharedTestKeyPath, true) if err != nil { panic(fmt.Sprintf("Failed to create shared PDS: %v", err)) } diff --git a/pkg/hold/pds/xrpc_test.go b/pkg/hold/pds/xrpc_test.go index a51eed1..67cccdf 100644 --- a/pkg/hold/pds/xrpc_test.go +++ b/pkg/hold/pds/xrpc_test.go @@ -44,7 +44,7 @@ func setupTestXRPCHandler(t *testing.T) (*XRPCHandler, context.Context) { t.Fatalf("Failed to copy shared signing key: %v", err) } - pds, err := NewHoldPDS(ctx, "did:web:hold.example.com", "https://hold.example.com", dbPath, keyPath) + pds, err := NewHoldPDS(ctx, "did:web:hold.example.com", "https://hold.example.com", dbPath, keyPath, false) if err != nil { t.Fatalf("Failed to create test PDS: %v", err) } @@ -1377,7 +1377,7 @@ func setupTestXRPCHandlerWithBlobs(t *testing.T) (*XRPCHandler, *mockS3Service, t.Fatalf("Failed to copy shared signing key: %v", err) } - pds, err := NewHoldPDS(ctx, "did:web:hold.example.com", "https://hold.example.com", dbPath, keyPath) + pds, err := NewHoldPDS(ctx, "did:web:hold.example.com", "https://hold.example.com", dbPath, keyPath, false) if err != nil { t.Fatalf("Failed to create test PDS: %v", err) }