package config import ( "errors" "os" "strconv" "strings" ) const ( defaultResticImage = "restic/restic:0.16.4" defaultJobTTLSeconds = 86400 defaultListenAddr = ":8080" defaultResticSecret = "soteria-restic" serviceNamespacePath = "/var/run/secrets/kubernetes.io/serviceaccount/namespace" ) type Config struct { Namespace string SecretNamespace string ResticImage string ResticRepository string ResticSecretName string ResticBackupArgs []string ResticForgetArgs []string S3Endpoint string S3Region string JobTTLSeconds int32 WorkerServiceAccount string ListenAddr string } func Load() (*Config, error) { cfg := &Config{} cfg.Namespace = getenv("SOTERIA_NAMESPACE") if cfg.Namespace == "" { ns, _ := os.ReadFile(serviceNamespacePath) cfg.Namespace = strings.TrimSpace(string(ns)) } if cfg.Namespace == "" { cfg.Namespace = "soteria" } cfg.SecretNamespace = getenv("SOTERIA_SECRET_NAMESPACE") if cfg.SecretNamespace == "" { cfg.SecretNamespace = cfg.Namespace } cfg.ResticImage = getenvDefault("SOTERIA_RESTIC_IMAGE", defaultResticImage) cfg.ResticRepository = getenv("SOTERIA_RESTIC_REPOSITORY") cfg.ResticSecretName = getenvDefault("SOTERIA_RESTIC_SECRET_NAME", defaultResticSecret) cfg.ResticBackupArgs = strings.Fields(getenv("SOTERIA_RESTIC_BACKUP_ARGS")) cfg.ResticForgetArgs = strings.Fields(getenv("SOTERIA_RESTIC_FORGET_ARGS")) cfg.S3Endpoint = getenv("SOTERIA_S3_ENDPOINT") cfg.S3Region = getenv("SOTERIA_S3_REGION") cfg.WorkerServiceAccount = getenv("SOTERIA_JOB_SERVICE_ACCOUNT") cfg.ListenAddr = getenvDefault("SOTERIA_LISTEN_ADDR", defaultListenAddr) if ttl, ok := getenvInt("SOTERIA_JOB_TTL_SECONDS"); ok { cfg.JobTTLSeconds = int32(ttl) } else { cfg.JobTTLSeconds = defaultJobTTLSeconds } if cfg.ResticRepository == "" { return nil, errors.New("SOTERIA_RESTIC_REPOSITORY is required") } if cfg.ResticSecretName == "" { return nil, errors.New("SOTERIA_RESTIC_SECRET_NAME is required") } if strings.Contains(cfg.ResticRepository, "..") { return nil, errors.New("SOTERIA_RESTIC_REPOSITORY contains invalid path segments") } return cfg, nil } func getenv(key string) string { return strings.TrimSpace(os.Getenv(key)) } func getenvDefault(key, value string) string { if v := getenv(key); v != "" { return v } return value } func getenvInt(key string) (int, bool) { val := getenv(key) if val == "" { return 0, false } parsed, err := strconv.Atoi(val) if err != nil { return 0, false } return parsed, true }