141 lines
3.1 KiB
Go
141 lines
3.1 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
)
|
|
|
|
type Config struct {
|
|
// Server
|
|
Port string
|
|
|
|
// Database
|
|
DatabaseURL string
|
|
|
|
// Storage
|
|
RepoRoot string
|
|
ArtifactRoot string
|
|
|
|
// Security
|
|
SessionSecret string // must be 32 or 64 bytes for AES-GCM
|
|
CSRFSecret string // must be 32 bytes
|
|
|
|
// OIDC (optional)
|
|
OIDCIssuer string
|
|
OIDCClientID string
|
|
OIDCClientSecret string
|
|
|
|
// Event bus
|
|
NATSUrl string
|
|
|
|
// GitOps
|
|
GitOpsReconcileInterval int // seconds between periodic drift checks; 0 disables
|
|
|
|
// Federation
|
|
InstanceURL string
|
|
InstanceName string
|
|
|
|
// Artifact signing (Phase 4)
|
|
// PEM-encoded ECDSA P-256 private key. If empty an ephemeral key is generated.
|
|
ArtifactSigningKey string
|
|
|
|
// OCI Registry
|
|
OCIRoot string
|
|
|
|
// SSH server
|
|
SSHHost string // env: SSH_HOST, empty = auto-detect from request/instance URL
|
|
SSHPort string // env: SSH_PORT, default "2222"
|
|
SSHHostKeyPath string // env: SSH_HOST_KEY_PATH, empty = generate ephemeral
|
|
|
|
// Dev
|
|
Debug bool
|
|
}
|
|
|
|
func Load() (*Config, error) {
|
|
repoRoot := getEnv("REPO_ROOT", "/var/lib/forgebucket/repos")
|
|
cfg := &Config{
|
|
Port: getEnv("PORT", "8080"),
|
|
RepoRoot: repoRoot,
|
|
ArtifactRoot: getEnv("ARTIFACT_ROOT", filepath.Join(filepath.Dir(repoRoot), "artifacts")),
|
|
Debug: getEnvBool("DEBUG", false),
|
|
|
|
NATSUrl: getEnv("NATS_URL", ""),
|
|
GitOpsReconcileInterval: getEnvInt("GITOPS_RECONCILE_INTERVAL", 300),
|
|
InstanceURL: getEnv("INSTANCE_URL", ""),
|
|
InstanceName: getEnv("INSTANCE_NAME", "ForgeBucket"),
|
|
}
|
|
|
|
var missing []string
|
|
|
|
cfg.DatabaseURL = requireEnv("DATABASE_URL", &missing)
|
|
cfg.SessionSecret = requireEnv("SESSION_SECRET", &missing)
|
|
cfg.CSRFSecret = requireEnv("CSRF_SECRET", &missing)
|
|
|
|
cfg.SSHHost = os.Getenv("SSH_HOST")
|
|
cfg.SSHPort = getEnv("SSH_PORT", "2222")
|
|
cfg.SSHHostKeyPath = os.Getenv("SSH_HOST_KEY_PATH")
|
|
|
|
// Optional signing key
|
|
cfg.ArtifactSigningKey = os.Getenv("ARTIFACT_SIGNING_KEY")
|
|
cfg.OCIRoot = getEnv("OCI_ROOT", filepath.Join(filepath.Dir(cfg.RepoRoot), "oci"))
|
|
|
|
// Optional OIDC
|
|
cfg.OIDCIssuer = os.Getenv("OIDC_ISSUER")
|
|
cfg.OIDCClientID = os.Getenv("OIDC_CLIENT_ID")
|
|
cfg.OIDCClientSecret = os.Getenv("OIDC_CLIENT_SECRET")
|
|
|
|
if len(missing) > 0 {
|
|
return nil, fmt.Errorf("missing required env vars: %v", missing)
|
|
}
|
|
|
|
if len(cfg.SessionSecret) < 32 {
|
|
return nil, fmt.Errorf("SESSION_SECRET must be at least 32 characters")
|
|
}
|
|
if len(cfg.CSRFSecret) != 32 {
|
|
return nil, fmt.Errorf("CSRF_SECRET must be exactly 32 characters")
|
|
}
|
|
|
|
return cfg, nil
|
|
}
|
|
|
|
func requireEnv(key string, missing *[]string) string {
|
|
v := os.Getenv(key)
|
|
if v == "" {
|
|
*missing = append(*missing, key)
|
|
}
|
|
return v
|
|
}
|
|
|
|
func getEnv(key, fallback string) string {
|
|
if v := os.Getenv(key); v != "" {
|
|
return v
|
|
}
|
|
return fallback
|
|
}
|
|
|
|
func getEnvInt(key string, fallback int) int {
|
|
v := os.Getenv(key)
|
|
if v == "" {
|
|
return fallback
|
|
}
|
|
n, err := strconv.Atoi(v)
|
|
if err != nil {
|
|
return fallback
|
|
}
|
|
return n
|
|
}
|
|
|
|
func getEnvBool(key string, fallback bool) bool {
|
|
v := os.Getenv(key)
|
|
if v == "" {
|
|
return fallback
|
|
}
|
|
b, err := strconv.ParseBool(v)
|
|
if err != nil {
|
|
return fallback
|
|
}
|
|
return b
|
|
}
|