From 65292566f2f9e6c659f096d1b53af7fed408e229 Mon Sep 17 00:00:00 2001 From: Albert Armea Date: Sun, 4 Jan 2026 20:53:31 -0500 Subject: [PATCH] Standardize on ~/.config/shepherd/config.toml --- crates/shepherd-config/README.md | 4 ++-- crates/shepherd-util/src/paths.rs | 38 +++++++++++++++++++++++++++++++ crates/shepherdd/README.md | 2 +- crates/shepherdd/src/main.rs | 6 ++--- 4 files changed, 44 insertions(+), 6 deletions(-) diff --git a/crates/shepherd-config/README.md b/crates/shepherd-config/README.md index e28168c..6c3c809 100644 --- a/crates/shepherd-config/README.md +++ b/crates/shepherd-config/README.md @@ -86,8 +86,8 @@ max_run_seconds = 3600 # 1 hour use shepherd_config::{load_config, parse_config, Policy}; use std::path::Path; -// Load from file -let policy = load_config("/etc/shepherdd/config.toml")?; +// Load from file (typically ~/.config/shepherd/config.toml) +let policy = load_config("config.toml")?; // Parse from string let toml_content = std::fs::read_to_string("config.toml")?; diff --git a/crates/shepherd-util/src/paths.rs b/crates/shepherd-util/src/paths.rs index 6e26cf0..98aaa63 100644 --- a/crates/shepherd-util/src/paths.rs +++ b/crates/shepherd-util/src/paths.rs @@ -115,6 +115,37 @@ pub fn socket_dir() -> PathBuf { }) } +/// Configuration subdirectory name (uses "shepherd" not "shepherdd") +const CONFIG_APP_DIR: &str = "shepherd"; + +/// Configuration filename +const CONFIG_FILENAME: &str = "config.toml"; + +/// Get the default configuration file path. +/// +/// Returns `$XDG_CONFIG_HOME/shepherd/config.toml` or `~/.config/shepherd/config.toml` +pub fn default_config_path() -> PathBuf { + // Try XDG_CONFIG_HOME first + if let Ok(config_home) = std::env::var("XDG_CONFIG_HOME") { + return PathBuf::from(config_home) + .join(CONFIG_APP_DIR) + .join(CONFIG_FILENAME); + } + + // Fallback to ~/.config/shepherd/config.toml + if let Ok(home) = std::env::var("HOME") { + return PathBuf::from(home) + .join(".config") + .join(CONFIG_APP_DIR) + .join(CONFIG_FILENAME); + } + + // Last resort (unlikely to be valid, but provides a fallback) + PathBuf::from("/etc") + .join(CONFIG_APP_DIR) + .join(CONFIG_FILENAME) +} + #[cfg(test)] mod tests { use super::*; @@ -145,4 +176,11 @@ mod tests { let dir = socket_dir(); assert_eq!(socket.parent().unwrap(), dir); } + + #[test] + fn config_path_contains_shepherd() { + let path = default_config_path(); + assert!(path.to_string_lossy().contains("shepherd")); + assert!(path.to_string_lossy().ends_with("config.toml")); + } } diff --git a/crates/shepherdd/README.md b/crates/shepherdd/README.md index b861e1c..65253b2 100644 --- a/crates/shepherdd/README.md +++ b/crates/shepherdd/README.md @@ -65,7 +65,7 @@ shepherdd --log-level debug | Option | Default | Description | |--------|---------|-------------| -| `-c, --config` | `/etc/shepherdd/config.toml` | Configuration file path | +| `-c, --config` | `~/.config/shepherd/config.toml` | Configuration file path | | `-s, --socket` | From config | IPC socket path | | `-d, --data-dir` | From config | Data directory | | `-l, --log-level` | `info` | Log verbosity | diff --git a/crates/shepherdd/src/main.rs b/crates/shepherdd/src/main.rs index 96816ed..9d93733 100644 --- a/crates/shepherdd/src/main.rs +++ b/crates/shepherdd/src/main.rs @@ -21,7 +21,7 @@ use shepherd_host_api::{HostAdapter, HostEvent, StopMode as HostStopMode, Volume use shepherd_host_linux::{LinuxHost, LinuxVolumeController}; use shepherd_ipc::{IpcServer, ServerMessage}; use shepherd_store::{AuditEvent, AuditEventType, SqliteStore, Store}; -use shepherd_util::{ClientId, MonotonicInstant, RateLimiter}; +use shepherd_util::{default_config_path, ClientId, MonotonicInstant, RateLimiter}; use std::path::PathBuf; use std::sync::Arc; use std::time::Duration; @@ -35,8 +35,8 @@ use tracing_subscriber::EnvFilter; #[command(name = "shepherdd")] #[command(about = "Policy enforcement service for child-focused computing", long_about = None)] struct Args { - /// Configuration file path - #[arg(short, long, default_value = "/etc/shepherdd/config.toml")] + /// Configuration file path (default: ~/.config/shepherd/config.toml) + #[arg(short, long, default_value_os_t = default_config_path())] config: PathBuf, /// Socket path override (or set SHEPHERD_SOCKET env var)