package config import ( "errors" "os" "strconv" "time" ) type Config struct { Addr string JWTSecret []byte // OMNEX_JWT_SECRET (min 32 octets) — signe les tokens SessionTTL time.Duration // durée de vie JWT + session Redis AllowedOrigins []string // CORS (vitrine + back-office) Env string // "dev" | "prod" DatabaseURL string // OMNEX_DATABASE_URL (DSN PostgreSQL GORM) RedisURL string // OMNEX_REDIS_URL (sessions), ex. redis://:pass@host:6379/0 DemoDomain string // domaine des démos DemoHTTPSPort string // port HTTPS public à afficher dans l'URL des démos (443 = omis, sinon ex. NodePort) Kubeconfig string FrontendImage string BackendImage string LBTelegramImage string AppDownloadsDir string // OMNEX_APP_DOWNLOADS_DIR : répertoire des .apk téléchargeables (voir internal/downloads) // DockerHubUsername/Password : compte Docker Hub authentifié (droits // lecture seule) injecté dans chaque namespace de démo (voir // deploy/chart-gestion/registry-credentials) pour passer le rate-limit // de pull anonyme (100/6h par IP) à 200/6h. "" = pull anonyme (défaut). DockerHubUsername string DockerHubPassword string // Projets vitrine (abonnements de 1 à 12 mois, voir internal/projects). VitrineDomain string // OMNEX_VITRINE_DOMAIN : les projets sont servis sur . VitrineChartsDir string // OMNEX_VITRINE_CHARTS_DIR : charts backend/frontend/postgresql/redis/ingressroute du projet vitrine VitrineBackendImage string // VITRINE_BACKEND_IMAGE, ex. registry/vitrine-backend:1.0.0 VitrineFrontendImage string // VITRINE_FRONTEND_IMAGE // VitrineGraceDays : jours pendant lesquels un projet dont l'abonnement // a expiré reste suspendu (données et sauvegardes conservées) avant sa // suppression définitive. Un renouvellement pendant ce délai le relance. VitrineGraceDays int } // Load lit la config. Fail-secure : secret JWT obligatoire ; en prod base + Redis aussi. func Load() (Config, error) { secret := os.Getenv("OMNEX_JWT_SECRET") if len(secret) < 32 { return Config{}, errors.New("OMNEX_JWT_SECRET manquant ou < 32 octets") } cfg := Config{ Addr: getenv("OMNEX_ADDR", ":8080"), JWTSecret: []byte(secret), SessionTTL: 24 * time.Hour, AllowedOrigins: splitCSV(getenv("OMNEX_ALLOWED_ORIGINS", "http://localhost:5173")), Env: getenv("OMNEX_ENV", "dev"), DatabaseURL: os.Getenv("OMNEX_DATABASE_URL"), RedisURL: os.Getenv("OMNEX_REDIS_URL"), DemoDomain: getenv("OMNEX_DEMO_DOMAIN", "demo.omnex.app"), DemoHTTPSPort: getenv("OMNEX_DEMO_HTTPS_PORT", "443"), Kubeconfig: os.Getenv("KUBECONFIG"), FrontendImage: os.Getenv("FRONTEND_IMAGE_APP"), BackendImage: os.Getenv("BACKEND_IMAGE_APP"), LBTelegramImage: os.Getenv("LBTELEGRAM_IMAGE_APP"), AppDownloadsDir: getenv("OMNEX_APP_DOWNLOADS_DIR", "/app-downloads"), DockerHubUsername: os.Getenv("OMNEX_DOCKERHUB_USERNAME"), DockerHubPassword: os.Getenv("OMNEX_DOCKERHUB_PASSWORD"), VitrineDomain: getenv("OMNEX_VITRINE_DOMAIN", "vitrine-omnex.club"), VitrineChartsDir: getenv("OMNEX_VITRINE_CHARTS_DIR", "/charts-vitrine"), VitrineBackendImage: os.Getenv("VITRINE_BACKEND_IMAGE"), VitrineFrontendImage: os.Getenv("VITRINE_FRONTEND_IMAGE"), VitrineGraceDays: 7, } if v := os.Getenv("OMNEX_VITRINE_GRACE_DAYS"); v != "" { days, err := strconv.Atoi(v) if err != nil || days < 0 || days > 365 { return Config{}, errors.New("OMNEX_VITRINE_GRACE_DAYS doit être un entier entre 0 et 365") } cfg.VitrineGraceDays = days } if cfg.Env == "prod" { if cfg.DatabaseURL == "" { return Config{}, errors.New("OMNEX_DATABASE_URL requis en prod") } if cfg.RedisURL == "" { return Config{}, errors.New("OMNEX_REDIS_URL requis en prod (sessions)") } } return cfg, nil } // Secure indique si les cookies doivent porter l'attribut Secure (HTTPS). func (c Config) Secure() bool { return c.Env == "prod" } func getenv(k, def string) string { if v := os.Getenv(k); v != "" { return v } return def } func splitCSV(s string) []string { var out []string start := 0 for i := 0; i <= len(s); i++ { if i == len(s) || s[i] == ',' { if seg := s[start:i]; seg != "" { out = append(out, seg) } start = i + 1 } } return out }