// Package config provides centralized path management for the fetch_ml project. package config import ( "os" "path/filepath" ) // PathRegistry provides centralized path management type PathRegistry struct { RootDir string // Repository root (auto-detected or from env) } // NewPathRegistry creates a path registry from root directory. // If root is empty, attempts to auto-detect repository root. func NewPathRegistry(root string) *PathRegistry { if root == "" { root = detectRepoRoot() } return &PathRegistry{RootDir: root} } // Binary paths func (p *PathRegistry) BinDir() string { return filepath.Join(p.RootDir, "bin") } func (p *PathRegistry) APIServerBinary() string { return filepath.Join(p.BinDir(), "api-server") } func (p *PathRegistry) WorkerBinary() string { return filepath.Join(p.BinDir(), "worker") } func (p *PathRegistry) TUIBinary() string { return filepath.Join(p.BinDir(), "tui") } func (p *PathRegistry) DataManagerBinary() string { return filepath.Join(p.BinDir(), "data_manager") } // Data paths func (p *PathRegistry) DataDir() string { return filepath.Join(p.RootDir, "data") } func (p *PathRegistry) ActiveDataDir() string { return filepath.Join(p.DataDir(), "active") } func (p *PathRegistry) JupyterStateDir() string { return filepath.Join(p.DataDir(), "active", "jupyter") } func (p *PathRegistry) ExperimentsDir() string { return filepath.Join(p.DataDir(), "experiments") } func (p *PathRegistry) ProdSmokeDir() string { return filepath.Join(p.DataDir(), "prod-smoke") } // Database paths func (p *PathRegistry) DBDir() string { return filepath.Join(p.RootDir, "db") } func (p *PathRegistry) SQLitePath() string { return filepath.Join(p.DBDir(), "fetch_ml.db") } // Log paths func (p *PathRegistry) LogDir() string { return filepath.Join(p.RootDir, "logs") } func (p *PathRegistry) AuditLogPath() string { return filepath.Join(p.LogDir(), "fetchml-audit.log") } // Config paths func (p *PathRegistry) ConfigDir() string { return filepath.Join(p.RootDir, "configs") } func (p *PathRegistry) APIServerConfig() string { return filepath.Join(p.ConfigDir(), "api", "dev.yaml") } func (p *PathRegistry) WorkerConfigDir() string { return filepath.Join(p.ConfigDir(), "workers") } // Test paths func (p *PathRegistry) TestResultsDir() string { return filepath.Join(p.RootDir, "test_results") } func (p *PathRegistry) TempDir() string { return filepath.Join(p.RootDir, "tmp") } // State file paths (for service persistence) func (p *PathRegistry) JupyterServicesFile() string { return filepath.Join(p.JupyterStateDir(), "fetch_ml_jupyter_services.json") } func (p *PathRegistry) JupyterWorkspacesFile() string { return filepath.Join(p.JupyterStateDir(), "fetch_ml_jupyter_workspaces.json") } // EnsureDir creates directory if it doesn't exist with appropriate permissions. func (p *PathRegistry) EnsureDir(path string) error { return os.MkdirAll(path, 0750) } // EnsureDirSecure creates directory with restricted permissions (for sensitive data). func (p *PathRegistry) EnsureDirSecure(path string) error { return os.MkdirAll(path, 0700) } // FileExists checks if a file exists. func (p *PathRegistry) FileExists(path string) bool { _, err := os.Stat(path) return err == nil } // detectRepoRoot finds repository root by looking for go.mod. // Returns current directory if not found. func detectRepoRoot() string { dir, err := os.Getwd() if err != nil { return "." } // Walk up directory tree looking for go.mod for { if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil { return dir } parent := filepath.Dir(dir) if parent == dir { // Reached root break } dir = parent } return "." } // FromEnv creates PathRegistry with root from FETCHML_ROOT env var, // or auto-detects if env var not set. func FromEnv() *PathRegistry { root := os.Getenv("FETCHML_ROOT") return NewPathRegistry(root) }