package config import ( "bufio" "os" "path/filepath" "strings" ) const defaultAddr = ":8080" const defaultDataDir = ".platform-data" const defaultStorageBackend = "file" type Config struct { Addr string StorageBackend string MySQLDSN string DataDir string MetadataPath string LogDir string LogBodyBackend string } func Load() Config { loadLocalEnvFiles() addr := os.Getenv("PLATFORM_ADDR") if addr == "" { addr = defaultAddr } dataDir := strings.TrimSpace(os.Getenv("PLATFORM_DATA_DIR")) if dataDir == "" { dataDir = defaultDataDir } metadataPath := strings.TrimSpace(os.Getenv("PLATFORM_METADATA_PATH")) if metadataPath == "" { metadataPath = filepath.Join(dataDir, "metadata.json") } logDir := strings.TrimSpace(os.Getenv("PLATFORM_LOG_DIR")) if logDir == "" { logDir = filepath.Join(dataDir, "logs") } storageBackend := strings.TrimSpace(os.Getenv("PLATFORM_STORAGE_BACKEND")) if storageBackend == "" { storageBackend = defaultStorageBackend } logBodyBackend := strings.TrimSpace(os.Getenv("PLATFORM_LOG_BODY_BACKEND")) return Config{ Addr: addr, StorageBackend: storageBackend, MySQLDSN: strings.TrimSpace(os.Getenv("PLATFORM_MYSQL_DSN")), DataDir: dataDir, MetadataPath: metadataPath, LogDir: logDir, LogBodyBackend: logBodyBackend, } } func loadLocalEnvFiles() { candidates := []string{".env", filepath.Join("platform", ".env")} for _, path := range candidates { loadEnvFile(path) } } func loadEnvFile(path string) { file, err := os.Open(path) if err != nil { return } defer file.Close() scanner := bufio.NewScanner(file) for scanner.Scan() { key, value, ok := parseEnvLine(scanner.Text()) if !ok { continue } if _, exists := os.LookupEnv(key); exists { continue } _ = os.Setenv(key, value) } } func parseEnvLine(line string) (string, string, bool) { line = strings.TrimSpace(line) if line == "" || strings.HasPrefix(line, "#") { return "", "", false } line = strings.TrimSpace(strings.TrimPrefix(line, "export ")) key, value, found := strings.Cut(line, "=") if !found { return "", "", false } key = strings.TrimSpace(key) if key == "" || strings.ContainsAny(key, " \t") { return "", "", false } value = strings.TrimSpace(stripInlineComment(strings.TrimSpace(value))) if len(value) >= 2 { if (value[0] == '"' && value[len(value)-1] == '"') || (value[0] == '\'' && value[len(value)-1] == '\'') { value = value[1 : len(value)-1] } } return key, value, true } func stripInlineComment(value string) string { inSingleQuote := false inDoubleQuote := false for index, char := range value { switch char { case '\'': if !inDoubleQuote { inSingleQuote = !inSingleQuote } case '"': if !inSingleQuote { inDoubleQuote = !inDoubleQuote } case '#': if !inSingleQuote && !inDoubleQuote && index > 0 && value[index-1] == ' ' { return strings.TrimSpace(value[:index]) } } } return value }