package config import ( "encoding/json" "os" "path/filepath" "strconv" "github.com/joho/godotenv" ) // Config holds runtime configuration type Config struct { S3Bucket string `json:"s3Bucket"` LocalDest string `json:"localDest"` AWSProfile string `json:"awsProfile"` Concurrency int `json:"concurrency"` } // Path returns the cross-OS config file path func Path() string { dir, _ := os.UserConfigDir() return filepath.Join(dir, "field-sync", "config.json") } // Save writes config to disk (atomic) func Save(cfg Config) error { p := Path() if err := os.MkdirAll(filepath.Dir(p), 0755); err != nil { return err } data, _ := json.MarshalIndent(cfg, "", " ") tmp := p + ".tmp" if err := os.WriteFile(tmp, data, 0644); err != nil { return err } return os.Rename(tmp, p) } // LoadFromFile reads from JSON config file (no .env) func LoadFromFile() Config { data, err := os.ReadFile(Path()) if err != nil { return Config{AWSProfile: "cosma", Concurrency: 4, LocalDest: defaultLocalDest()} } var c Config json.Unmarshal(data, &c) if c.Concurrency == 0 { c.Concurrency = 4 } if c.AWSProfile == "" { c.AWSProfile = "cosma" } if c.LocalDest == "" { c.LocalDest = defaultLocalDest() } return c } func defaultLocalDest() string { home, _ := os.UserHomeDir() return filepath.Join(home, "field-sync-data") } // Load reads config from ENV (with optional .env file) — kept for compat func Load(envFile string) Config { _ = godotenv.Load(envFile) // ignore if missing concurrency := 4 if v := os.Getenv("FIELD_SYNC_CONCURRENCY"); v != "" { if n, err := strconv.Atoi(v); err == nil && n > 0 { concurrency = n } } return Config{ S3Bucket: getenv("FIELD_SYNC_S3_BUCKET", ""), LocalDest: getenv("FIELD_SYNC_LOCAL_DEST", os.TempDir()), AWSProfile: getenv("AWS_PROFILE", "cosma"), Concurrency: concurrency, } } func getenv(key, fallback string) string { if v := os.Getenv(key); v != "" { return v } return fallback }