//! Raw configuration schema (as parsed from TOML) use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::path::PathBuf; /// Raw configuration as parsed from TOML #[derive(Debug, Clone, Deserialize, Serialize)] pub struct RawConfig { /// Config schema version pub config_version: u32, /// Global service settings #[serde(default, alias = "daemon")] pub service: RawServiceConfig, /// List of allowed entries #[serde(default)] pub entries: Vec, } /// Service-level settings #[derive(Debug, Clone, Default, Deserialize, Serialize)] pub struct RawServiceConfig { /// IPC socket path (default: $XDG_RUNTIME_DIR/shepherdd/shepherdd.sock) pub socket_path: Option, /// Log directory (default: $XDG_STATE_HOME/shepherdd) pub log_dir: Option, /// Data directory for store (default: $XDG_DATA_HOME/shepherdd) pub data_dir: Option, /// Capture stdout/stderr from child applications to log files /// Files are written to child_log_dir (or log_dir/sessions if not set) #[serde(default)] pub capture_child_output: bool, /// Directory for child application logs (default: log_dir/sessions) pub child_log_dir: Option, /// Default warning thresholds (can be overridden per entry) pub default_warnings: Option>, /// Default max run duration pub default_max_run_seconds: Option, /// Global volume restrictions #[serde(default)] pub volume: Option, } /// Raw entry definition #[derive(Debug, Clone, Deserialize, Serialize)] pub struct RawEntry { /// Unique stable ID pub id: String, /// Display label pub label: String, /// Icon reference (opaque, interpreted by shell) pub icon: Option, /// Entry kind and launch details pub kind: RawEntryKind, /// Availability time windows #[serde(default)] pub availability: Option, /// Time limits #[serde(default)] pub limits: Option, /// Warning configuration #[serde(default)] pub warnings: Option>, /// Volume restrictions for this entry (overrides global) #[serde(default)] pub volume: Option, /// Explicitly disabled #[serde(default)] pub disabled: bool, /// Reason for disabling pub disabled_reason: Option, } /// Raw entry kind #[derive(Debug, Clone, Deserialize, Serialize)] #[serde(tag = "type", rename_all = "snake_case")] pub enum RawEntryKind { Process { /// Command to run (required) command: String, /// Additional command-line arguments #[serde(default)] args: Vec, #[serde(default)] env: HashMap, cwd: Option, }, /// Snap application - uses systemd scope-based process management Snap { /// The snap name (e.g., "mc-installer") snap_name: String, /// Command to run (defaults to snap_name if not specified) command: Option, /// Additional command-line arguments #[serde(default)] args: Vec, /// Additional environment variables #[serde(default)] env: HashMap, }, Vm { driver: String, #[serde(default)] args: HashMap, }, Media { library_id: String, #[serde(default)] args: HashMap, }, Custom { type_name: String, #[serde(default)] payload: Option, }, } /// Availability configuration #[derive(Debug, Clone, Default, Deserialize, Serialize)] pub struct RawAvailability { /// Time windows when entry is available #[serde(default)] pub windows: Vec, /// If true, entry is always available (ignores windows) #[serde(default)] pub always: bool, } /// Time window #[derive(Debug, Clone, Deserialize, Serialize)] pub struct RawTimeWindow { /// Days of week: "weekdays", "weekends", "all", or list like ["mon", "tue", "wed"] pub days: RawDays, /// Start time (HH:MM format) pub start: String, /// End time (HH:MM format) pub end: String, } /// Days specification #[derive(Debug, Clone, Deserialize, Serialize)] #[serde(untagged)] pub enum RawDays { Preset(String), List(Vec), } /// Time limits #[derive(Debug, Clone, Default, Deserialize, Serialize)] pub struct RawLimits { /// Maximum run duration in seconds pub max_run_seconds: Option, /// Daily quota in seconds pub daily_quota_seconds: Option, /// Cooldown after session ends, in seconds pub cooldown_seconds: Option, } /// Warning threshold #[derive(Debug, Clone, Deserialize, Serialize)] pub struct RawWarningThreshold { /// Seconds before expiry pub seconds_before: u64, /// Severity: "info", "warn", "critical" #[serde(default = "default_severity")] pub severity: String, /// Message template pub message: Option, } fn default_severity() -> String { "warn".to_string() } /// Volume control configuration #[derive(Debug, Clone, Default, Deserialize, Serialize)] pub struct RawVolumeConfig { /// Maximum volume percentage allowed (0-100) pub max_volume: Option, /// Minimum volume percentage allowed (0-100) pub min_volume: Option, /// Whether mute toggle is allowed (default: true) #[serde(default = "default_true")] pub allow_mute: bool, /// Whether volume changes are allowed at all (default: true) #[serde(default = "default_true")] pub allow_change: bool, } fn default_true() -> bool { true } #[cfg(test)] mod tests { use super::*; #[test] fn parse_process_entry() { let toml_str = r#" config_version = 1 [[entries]] id = "scummvm" label = "ScummVM" kind = { type = "process", command = "scummvm", args = ["-f"] } [entries.limits] max_run_seconds = 3600 "#; let config: RawConfig = toml::from_str(toml_str).unwrap(); assert_eq!(config.entries.len(), 1); assert_eq!(config.entries[0].id, "scummvm"); } #[test] fn parse_time_windows() { let toml_str = r#" config_version = 1 [[entries]] id = "game" label = "Game" kind = { type = "process", command = "/bin/game" } [entries.availability] [[entries.availability.windows]] days = "weekdays" start = "14:00" end = "18:00" [[entries.availability.windows]] days = ["sat", "sun"] start = "10:00" end = "20:00" "#; let config: RawConfig = toml::from_str(toml_str).unwrap(); let avail = config.entries[0].availability.as_ref().unwrap(); assert_eq!(avail.windows.len(), 2); } }