package config import ( "fmt" "log" "os" "path/filepath" "sync" ) var ( instance *Config once sync.Once ) type Config struct { ServerPort string DatabasePath string JWTSecret string EncryptionKey string // 独立加密密钥,必须与JWT_SECRET分离 DataDir string KubeDir string LogLevel string // 新增:日志级别 debug/info/warn/error HostKeyDir string // 新增:SSH主机密钥存储目录 } func GetConfig() *Config { once.Do(func() { dataDir := getEnv("DATA_DIR", "./data") jwtSecret := os.Getenv("JWT_SECRET") encKey := os.Getenv("ENCRYPTION_KEY") // 生产环境强制校验:JWT_SECRET和ENCRYPTION_KEY必须设置 if jwtSecret == "" { log.Fatal("[SECURITY] JWT_SECRET environment variable is required. Please set a strong random secret.") } if encKey == "" { log.Fatal("[SECURITY] ENCRYPTION_KEY environment variable is required. Please set a strong 32-byte random key (base64 or hex encoded).") } // ENCRYPTION_KEY 长度校验:至少32字节(256位) if len(encKey) < 32 { log.Fatal(fmt.Sprintf("[SECURITY] ENCRYPTION_KEY must be at least 32 bytes, got %d bytes", len(encKey))) } instance = &Config{ ServerPort: getEnv("SERVER_PORT", "8080"), DatabasePath: databasePath(dataDir), JWTSecret: jwtSecret, EncryptionKey: encKey, DataDir: dataDir, KubeDir: filepath.Join(dataDir, "kubeconfigs"), LogLevel: getEnv("LOG_LEVEL", "info"), HostKeyDir: filepath.Join(dataDir, "known_hosts"), } _ = os.MkdirAll(instance.KubeDir, 0700) _ = os.MkdirAll(instance.HostKeyDir, 0700) }) return instance } func getEnv(key, defaultVal string) string { if val := os.Getenv(key); val != "" { return val } return defaultVal } // databasePath 新名优先;历史命名的数据文件存在且新文件不存在时沿用旧文件 // (数据平滑迁移,不搬移文件、零丢失风险) func databasePath(dataDir string) string { newPath := filepath.Join(dataDir, "lokistack.json") if _, err := os.Stat(newPath); err == nil { return newPath } for _, legacy := range []string{"opsxloki.json", "lokideploy.json"} { legacyPath := filepath.Join(dataDir, legacy) if _, err := os.Stat(legacyPath); err == nil { return legacyPath } } return newPath }