use anyhow::{Context, Result}; use serde::Deserialize; use std::path::PathBuf; #[derive(Debug, Clone, Deserialize)] pub struct Config { pub listen: String, pub db_path: PathBuf, pub topology_path: PathBuf, /// MM-local checkout scratch dir (per-sha worktrees live here). pub workdir: PathBuf, /// MM-local releases dir (`releases//` and `current` live here). pub release_root: PathBuf, /// Scratch postgres DB url used by `migration_dry_run`. Sando drops and /// recreates the schema on every run, so do not point this at anything /// you care about. #[serde(default)] pub scratch_db_url: Option, /// Names of cargo bin targets the server crate produces (files under /// `target/release/`). First entry is the primary unit (referenced from /// the systemd unit's ExecStart). Defaults to `["server"]`; MNW ships /// `["makenotwork", "mnw-admin"]`. #[serde(default = "default_bin_names")] pub bin_names: Vec, /// Root for per-gate run logs (`//.log`). /// Served via `GET /logs/{version}/{gate}`. Defaults to `/srv/sando/logs`. #[serde(default = "default_logs_root")] pub logs_root: PathBuf, /// Non-binary contents to stage into each release dir alongside /// `bin_names`. Each entry copies `worktree/` into /// `/`. `required=false` makes a missing source a warn /// (older shas missing one of these don't break sando mid-bisect); /// `required=true` errors. Default is empty — projects opt-in via /// daemon config so the sando code stays project-agnostic. #[serde(default)] pub release_contents: Vec, } /// A directory or file copied from the worktree into the staged release dir. /// Multiple entries with the same `dst` are allowed and merged (used by MNW /// to build `docs/` from three different worktree sources). #[derive(Debug, Clone, Deserialize)] pub struct ReleaseEntry { /// Path relative to the worktree root (e.g. `server/static`). pub src: PathBuf, /// Path relative to the release dir (e.g. `static`). Parent dirs are /// created as needed. pub dst: PathBuf, /// If true, a missing source aborts the build. If false, log warn + skip. #[serde(default)] pub required: bool, } fn default_bin_names() -> Vec { vec!["server".into()] } fn default_logs_root() -> PathBuf { PathBuf::from("/srv/sando/logs") } impl Config { /// Primary binary — the one the systemd unit's ExecStart points at. pub fn primary_bin(&self) -> &str { self.bin_names.first().map(|s| s.as_str()).unwrap_or("server") } pub fn load() -> Result { let path = std::env::var("SANDO_CONFIG").unwrap_or_else(|_| "sando-daemon.toml".into()); let raw = std::fs::read_to_string(&path) .with_context(|| format!("reading daemon config at {path}"))?; Ok(toml::from_str(&raw)?) } #[cfg(test)] pub fn for_tests() -> Self { Self { listen: "127.0.0.1:0".into(), db_path: PathBuf::from(":memory:"), topology_path: PathBuf::from("/tmp/sando-test-topology.toml"), workdir: PathBuf::from("/tmp/sando-test-workdir"), release_root: PathBuf::from("/tmp/sando-test-release-root"), scratch_db_url: None, bin_names: vec!["server".into()], logs_root: PathBuf::from("/tmp/sando-test-logs"), release_contents: Vec::new(), } } }