feat(config): add XDG platform path helpers
Add ConfigDir, DataDir, CacheDir, DefaultDBPath, DefaultConfigPath to pkg/config — following XDG Base Directory spec on Linux, and the stdlib os.UserConfigDir / os.UserCacheDir platform conventions on macOS/Windows. All four directory helpers call MkdirAll(0700) so callers never need to pre-create directories. DefaultDBPath returns <DataDir>/picoclaw.db and DefaultConfigPath returns <ConfigDir>/config.json. Wire into cmd/picoclaw: - getConfigPath() now delegates to config.DefaultConfigPath() with a legacy fallback (~/.picoclaw/config.json) for systems where XDG fails - setupSecureBus() resolves secrets.json via config.ConfigDir() with the same fallback, removing the hard-coded os.UserHomeDir + join pattern Extracted and extended from Budgetsmith internal/infrastructure/config/paths.go.
This commit is contained in:
parent
0db51c3b63
commit
f4ec7103fe
2 changed files with 86 additions and 4 deletions
|
|
@ -1533,6 +1533,10 @@ func daemonStatus() {
|
|||
}
|
||||
|
||||
func getConfigPath() string {
|
||||
if p, err := config.DefaultConfigPath(); err == nil {
|
||||
return p
|
||||
}
|
||||
// Fallback: legacy ~/.picoclaw/config.json for systems where XDG resolution fails.
|
||||
home, _ := os.UserHomeDir()
|
||||
return filepath.Join(home, ".picoclaw", "config.json")
|
||||
}
|
||||
|
|
@ -1568,13 +1572,18 @@ func loadConfig() (*config.Config, error) {
|
|||
// returns without attaching the bus — the agent continues in direct-execution mode.
|
||||
// The returned closer must be called on shutdown when the bus is non-nil.
|
||||
func setupSecureBus(agentLoop *agent.AgentLoop) (closer func()) {
|
||||
home, err := os.UserHomeDir()
|
||||
cfgDir, err := config.ConfigDir()
|
||||
if err != nil {
|
||||
logger.WarnC("itr", "SecureBus: cannot determine home dir — running without ITR")
|
||||
// Fallback to ~/.picoclaw if XDG resolution fails.
|
||||
home, herr := os.UserHomeDir()
|
||||
if herr != nil {
|
||||
logger.WarnC("itr", "SecureBus: cannot determine config dir — running without ITR")
|
||||
return func() {}
|
||||
}
|
||||
cfgDir = filepath.Join(home, ".picoclaw")
|
||||
}
|
||||
|
||||
secretsPath := filepath.Join(home, ".picoclaw", "secrets.json")
|
||||
secretsPath := filepath.Join(cfgDir, "secrets.json")
|
||||
|
||||
// Use NoopKeyring by default; EnvKeyring when PICOCLAW_MASTER_KEY is set.
|
||||
var keyring security.KeyringProvider
|
||||
|
|
|
|||
|
|
@ -602,3 +602,76 @@ func expandHome(path string) string {
|
|||
}
|
||||
return path
|
||||
}
|
||||
|
||||
// ─── XDG / platform path helpers ─────────────────────────────────────────────
|
||||
|
||||
const appName = "picoclaw"
|
||||
|
||||
// ConfigDir returns the platform-appropriate user configuration directory for
|
||||
// picoclaw, following XDG Base Directory spec on Linux
|
||||
// (~/.config/picoclaw), Library/Application Support on macOS, and
|
||||
// %AppData%\picoclaw on Windows. The directory is created if it does not exist.
|
||||
func ConfigDir() (string, error) {
|
||||
base, err := os.UserConfigDir()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("resolve user config dir: %w", err)
|
||||
}
|
||||
dir := filepath.Join(base, appName)
|
||||
if err := os.MkdirAll(dir, 0o700); err != nil {
|
||||
return "", fmt.Errorf("create config dir %q: %w", dir, err)
|
||||
}
|
||||
return dir, nil
|
||||
}
|
||||
|
||||
// DataDir returns the platform-appropriate user data directory for picoclaw.
|
||||
// On Linux this is ~/.local/share/picoclaw (XDG_DATA_HOME); on macOS and
|
||||
// Windows it falls back to the same base as ConfigDir. The directory is
|
||||
// created if it does not exist.
|
||||
func DataDir() (string, error) {
|
||||
// XDG_DATA_HOME is Linux-standard; os.UserHomeDir gives us the root we need.
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("resolve home dir: %w", err)
|
||||
}
|
||||
dir := filepath.Join(home, ".local", "share", appName)
|
||||
if err := os.MkdirAll(dir, 0o700); err != nil {
|
||||
return "", fmt.Errorf("create data dir %q: %w", dir, err)
|
||||
}
|
||||
return dir, nil
|
||||
}
|
||||
|
||||
// CacheDir returns the platform-appropriate user cache directory for picoclaw
|
||||
// (XDG_CACHE_HOME on Linux → ~/.cache/picoclaw). The directory is created if
|
||||
// it does not exist.
|
||||
func CacheDir() (string, error) {
|
||||
base, err := os.UserCacheDir()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("resolve user cache dir: %w", err)
|
||||
}
|
||||
dir := filepath.Join(base, appName)
|
||||
if err := os.MkdirAll(dir, 0o700); err != nil {
|
||||
return "", fmt.Errorf("create cache dir %q: %w", dir, err)
|
||||
}
|
||||
return dir, nil
|
||||
}
|
||||
|
||||
// DefaultDBPath returns the canonical SQLite database path inside DataDir.
|
||||
// Callers that want to override this should check for a CLI flag or the
|
||||
// PICOCLAW_DB_PATH environment variable before falling back to this value.
|
||||
func DefaultDBPath() (string, error) {
|
||||
dataDir, err := DataDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return filepath.Join(dataDir, appName+".db"), nil
|
||||
}
|
||||
|
||||
// DefaultConfigPath returns the path to the primary JSON config file inside
|
||||
// ConfigDir (picoclaw/config.json).
|
||||
func DefaultConfigPath() (string, error) {
|
||||
cfgDir, err := ConfigDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return filepath.Join(cfgDir, "config.json"), nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue