mirror of
https://tangled.org/evan.jarrett.net/at-container-registry
synced 2026-05-12 19:11:28 +00:00
163 lines
5.4 KiB
Go
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
|
|
}
|