package config import ( "encoding/json" "fmt" "os" "path/filepath" "strings" ) func envWritebackEnabled() bool { v := strings.ToLower(strings.TrimSpace(os.Getenv("DS2API_ENV_WRITEBACK"))) return v == "1" || v == "true" || v == "yes" || v == "on" } func (s *Store) IsEnvWritebackEnabled() bool { return envWritebackEnabled() } func (s *Store) HasEnvConfigSource() bool { rawCfg := strings.TrimSpace(os.Getenv("DS2API_CONFIG_JSON")) return rawCfg != "" } func (s *Store) ConfigPath() string { return s.path } func writeConfigFile(path string, cfg Config) error { persistCfg := cfg.Clone() persistCfg.ClearAccountTokens() b, err := json.MarshalIndent(persistCfg, "", " ") if err != nil { return err } return writeConfigBytes(path, b) } // overwriteFile writes data to path. If the write fails due to permission // issues (common on HuggingFace Spaces where the file may be owned by root), // it removes the old file and retries. func overwriteFile(path string, b []byte, perm os.FileMode) error { if err := os.WriteFile(path, b, perm); err != nil { if removeErr := os.Remove(path); removeErr == nil { return os.WriteFile(path, b, perm) } return err } return nil } func writeConfigBytes(path string, b []byte) error { dir := filepath.Dir(path) if dir == "." || dir == "" { return overwriteFile(path, b, 0o644) } if err := os.MkdirAll(dir, 0o755); err != nil { return fmt.Errorf("mkdir config dir: %w", err) } // Write to a temp file first, then rename atomically to avoid // leaving a corrupted/truncated config on crash or disk-full. tmp, err := os.CreateTemp(dir, ".ds2api-config-*") if err != nil { return fmt.Errorf("create temp config file: %w", err) } tmpName := tmp.Name() wrote := false defer func() { if !wrote { _ = tmp.Close() _ = os.Remove(tmpName) } }() if _, err := tmp.Write(b); err != nil { return fmt.Errorf("write temp config: %w", err) } if err := tmp.Sync(); err != nil { return fmt.Errorf("sync temp config: %w", err) } if err := tmp.Close(); err != nil { return fmt.Errorf("close temp config: %w", err) } wrote = true if err := os.Rename(tmpName, path); err != nil { // Rename may fail because the existing file has wrong ownership/permissions // (common on HuggingFace Spaces where /data/config.json may be owned by root). // Try removing the old file and renaming again. if removeErr := os.Remove(path); removeErr == nil { if renameErr := os.Rename(tmpName, path); renameErr == nil { return nil } } // Rename still failing (e.g. cross-filesystem); fall back to direct write. _ = os.Remove(tmpName) return overwriteFile(path, b, 0o644) } return nil }