actually wrap them in a envvar check

This commit is contained in:
Evan Jarrett
2025-10-23 16:43:58 -05:00
parent a8815737fd
commit dd79b8a0ee
11 changed files with 59 additions and 43 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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" {

View File

@@ -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))
}

View File

@@ -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)
}