Files
at-container-registry/pkg/hold/config.go
2025-10-14 20:25:08 -05:00

163 lines
5.4 KiB
Go

package hold
import (
"fmt"
"os"
"path/filepath"
"time"
"github.com/distribution/distribution/v3/configuration"
)
// Config represents the hold service configuration
type Config struct {
Version string `yaml:"version"`
Storage StorageConfig `yaml:"storage"`
Server ServerConfig `yaml:"server"`
Registration RegistrationConfig `yaml:"registration"`
Database DatabaseConfig `yaml:"database"`
}
// RegistrationConfig defines auto-registration settings
type RegistrationConfig struct {
// OwnerDID is the owner's ATProto DID (from env: HOLD_OWNER)
// If set, auto-registration is enabled
OwnerDID string `yaml:"owner_did"`
// AllowAllCrew controls whether to create a wildcard crew record (from env: HOLD_ALLOW_ALL_CREW)
// If true, creates/maintains a crew record with memberPattern: "*" (allows all authenticated users)
// If false, deletes the wildcard crew record if it exists
AllowAllCrew bool `yaml:"allow_all_crew"`
}
// StorageConfig wraps distribution's storage configuration
type StorageConfig struct {
configuration.Storage `yaml:",inline"`
}
// ServerConfig defines server settings
type ServerConfig struct {
// Addr is the address to listen on (e.g., ":8080")
Addr string `yaml:"addr"`
// PublicURL is the public URL of this hold service (e.g., "https://hold.example.com")
PublicURL string `yaml:"public_url"`
// Public controls whether this hold allows public blob reads without auth (from env: HOLD_PUBLIC)
Public bool `yaml:"public"`
// TestMode uses localhost for OAuth redirects while storing real URL in hold record (from env: TEST_MODE)
TestMode bool `yaml:"test_mode"`
// DisablePresignedURLs forces proxy mode even with S3 configured (for testing) (from env: DISABLE_PRESIGNED_URLS)
DisablePresignedURLs bool `yaml:"disable_presigned_urls"`
// ReadTimeout for HTTP requests
ReadTimeout time.Duration `yaml:"read_timeout"`
// WriteTimeout for HTTP requests
WriteTimeout time.Duration `yaml:"write_timeout"`
}
// DatabaseConfig defines embedded PDS database settings
type DatabaseConfig struct {
// Path is the directory path for carstore (from env: HOLD_DATABASE_DIR)
// If empty, embedded PDS is disabled
Path string `yaml:"path"`
// KeyPath is the path to the signing key (from env: HOLD_KEY_PATH)
// Defaults to {Path}/signing.key
KeyPath string `yaml:"key_path"`
}
// LoadConfigFromEnv loads all configuration from environment variables
func LoadConfigFromEnv() (*Config, error) {
cfg := &Config{
Version: "0.1",
}
// Server configuration
cfg.Server.Addr = getEnvOrDefault("HOLD_SERVER_ADDR", ":8080")
cfg.Server.PublicURL = os.Getenv("HOLD_PUBLIC_URL")
if cfg.Server.PublicURL == "" {
return nil, fmt.Errorf("HOLD_PUBLIC_URL is required")
}
cfg.Server.Public = os.Getenv("HOLD_PUBLIC") == "true"
cfg.Server.TestMode = os.Getenv("TEST_MODE") == "true"
cfg.Server.DisablePresignedURLs = os.Getenv("DISABLE_PRESIGNED_URLS") == "true"
cfg.Server.ReadTimeout = 5 * time.Minute // Increased for large blob uploads
cfg.Server.WriteTimeout = 5 * time.Minute // Increased for large blob uploads
// Registration configuration (optional)
cfg.Registration.OwnerDID = os.Getenv("HOLD_OWNER")
cfg.Registration.AllowAllCrew = os.Getenv("HOLD_ALLOW_ALL_CREW") == "true"
// Database configuration (optional - enables embedded PDS)
// Note: HOLD_DATABASE_DIR is a directory path, carstore creates db.sqlite3 inside it
cfg.Database.Path = getEnvOrDefault("HOLD_DATABASE_DIR", "/var/lib/atcr-hold")
cfg.Database.KeyPath = os.Getenv("HOLD_KEY_PATH")
if cfg.Database.KeyPath == "" && cfg.Database.Path != "" {
// Default: signing key in same directory as carstore
cfg.Database.KeyPath = filepath.Join(cfg.Database.Path, "signing.key")
}
// Storage configuration - build from env vars based on storage type
storageType := getEnvOrDefault("STORAGE_DRIVER", "s3")
var err error
cfg.Storage, err = buildStorageConfig(storageType)
if err != nil {
return nil, fmt.Errorf("failed to build storage config: %w", err)
}
return cfg, nil
}
// buildStorageConfig creates storage configuration based on driver type
func buildStorageConfig(driver string) (StorageConfig, error) {
params := make(map[string]any)
switch driver {
case "s3":
// S3/Storj/Minio configuration from standard AWS env vars
accessKey := os.Getenv("AWS_ACCESS_KEY_ID")
secretKey := os.Getenv("AWS_SECRET_ACCESS_KEY")
region := getEnvOrDefault("AWS_REGION", "us-east-1")
bucket := os.Getenv("S3_BUCKET")
endpoint := os.Getenv("S3_ENDPOINT") // For Storj/Minio
if bucket == "" {
return StorageConfig{}, fmt.Errorf("S3_BUCKET is required for S3 storage")
}
params["accesskey"] = accessKey
params["secretkey"] = secretKey
params["region"] = region
params["bucket"] = bucket
if endpoint != "" {
params["regionendpoint"] = endpoint
}
case "filesystem":
// Filesystem configuration
rootDir := getEnvOrDefault("STORAGE_ROOT_DIR", "/var/lib/atcr/hold")
params["rootdirectory"] = rootDir
default:
return StorageConfig{}, fmt.Errorf("unsupported storage driver: %s", driver)
}
// Build distribution Storage config
storageCfg := configuration.Storage{}
storageCfg[driver] = configuration.Parameters(params)
return StorageConfig{Storage: storageCfg}, nil
}
// getEnvOrDefault gets an environment variable or returns a default value
func getEnvOrDefault(key, defaultValue string) string {
if val := os.Getenv(key); val != "" {
return val
}
return defaultValue
}