package service import ( "os" "path/filepath" "strconv" "strings" ) // Settings configures the Metis service runtime. type Settings struct { BindAddr string InventoryPath string CacheDir string ArtifactDir string HistoryPath string SnapshotsPath string TargetsPath string DefaultFlashHost string FlashHosts []string LocalHost string AllowedGroups []string MaxDeviceBytes int64 } // FromEnv builds service settings with sensible defaults for local dev and in-cluster use. func FromEnv() Settings { dataDir := getenvDefault("METIS_DATA_DIR", "/var/lib/metis") localHost := getenvDefault("METIS_LOCAL_HOST", hostnameOr("unknown")) defaultFlashHost := getenvDefault("METIS_DEFAULT_FLASH_HOST", localHost) flashHosts := splitList(getenvDefault("METIS_FLASH_HOSTS", defaultFlashHost)) return Settings{ BindAddr: getenvDefault("METIS_BIND_ADDR", ":8080"), InventoryPath: getenvDefault("METIS_INVENTORY_PATH", "inventory.titan-rpi4.yaml"), CacheDir: getenvDefault("METIS_CACHE_DIR", filepath.Join(dataDir, "cache")), ArtifactDir: getenvDefault("METIS_ARTIFACT_DIR", filepath.Join(dataDir, "artifacts")), HistoryPath: getenvDefault("METIS_HISTORY_PATH", filepath.Join(dataDir, "history.jsonl")), SnapshotsPath: getenvDefault("METIS_SNAPSHOTS_PATH", filepath.Join(dataDir, "snapshots.json")), TargetsPath: getenvDefault("METIS_TARGETS_PATH", filepath.Join(dataDir, "targets.json")), DefaultFlashHost: defaultFlashHost, FlashHosts: flashHosts, LocalHost: localHost, AllowedGroups: splitList(getenvDefault("METIS_ALLOWED_GROUPS", "admin,maintainer")), MaxDeviceBytes: getenvInt64("METIS_MAX_DEVICE_BYTES", 300000000000), } } func getenvDefault(key, fallback string) string { if value := strings.TrimSpace(os.Getenv(key)); value != "" { return value } return fallback } func getenvInt64(key string, fallback int64) int64 { raw := strings.TrimSpace(os.Getenv(key)) if raw == "" { return fallback } value, err := strconv.ParseInt(raw, 10, 64) if err != nil { return fallback } return value } func splitList(raw string) []string { if strings.TrimSpace(raw) == "" { return nil } parts := strings.Split(raw, ",") result := make([]string, 0, len(parts)) for _, part := range parts { part = strings.TrimSpace(part) if part == "" { continue } result = append(result, part) } return result } func hostnameOr(fallback string) string { name, err := os.Hostname() if err != nil || strings.TrimSpace(name) == "" { return fallback } return strings.TrimSpace(name) }