//! What a machine that has never been configured looks like. use std::collections::BTreeMap; use std::path::PathBuf; use crate::config::{Config, Profile, Service, ServiceKind}; /// The profile a directory nobody has bound resolves to. pub const DEFAULT_PROFILE: &str = "default"; /// Directory created under the user configuration directory. const CONFIG_DIR: &str = "didbot"; /// Name of the configuration file itself. const CONFIG_FILE: &str = "stack.toml"; /// The interface a service listens on unless it says otherwise. /// /// Loopback, and an address rather than a name: `localhost` resolves to `::1` /// under systemd-resolved and to `127.0.0.1` elsewhere, and a default that /// means different things on different machines is not a default. pub(crate) fn host() -> String { "127.0.0.1".to_owned() } /// `$XDG_CONFIG_HOME/didbot/stack.toml`. /// /// Configuration rather than state: it is hand-editable, it is worth keeping, /// and losing it changes what this machine does rather than merely costing it /// some accounts. That is the opposite of `hook-agents.json`, which lives /// under the state directory for the opposite reasons. pub fn default_config_path() -> PathBuf { let base = std::env::var_os("XDG_CONFIG_HOME") .map(PathBuf::from) .filter(|path| !path.as_os_str().is_empty()) .or_else(|| { std::env::var_os("HOME") .map(PathBuf::from) .filter(|path| !path.as_os_str().is_empty()) .map(|home| home.join(".config")) }) .unwrap_or_else(|| PathBuf::from(".")); base.join(CONFIG_DIR).join(CONFIG_FILE) } /// One `pds` and one `mcp`, on the ports `scripts/dev-*.sh` use. /// /// These numbers are duplicated from those scripts on purpose, and the /// duplication is checked: `the_builtin_ports_match_the_development_scripts` /// in this crate's tests reads the scripts and compares. A default that /// drifted from what the scripts actually bind would send every hook on an /// unconfigured machine to a port with nothing on it. pub(crate) fn builtin() -> Config { let mut services = BTreeMap::new(); services.insert( "pds".to_owned(), Service { kind: ServiceKind::Pds, port: 3000, host: host(), zone: Some("agents.localhost".to_owned()), names: Some("mineral+creature".to_owned()), pds: None, index: None, query: None, data: None, }, ); services.insert( "mcp".to_owned(), Service { kind: ServiceKind::Mcp, port: 3001, host: host(), pds: Some("pds".to_owned()), index: None, query: None, zone: None, data: None, names: None, }, ); let mut profiles = BTreeMap::new(); profiles.insert( DEFAULT_PROFILE.to_owned(), Profile { pds: "pds".to_owned(), mcp: Some("mcp".to_owned()), // The index, the query service and the canvas are // vibescrobble.com's processes now. `ServiceKind` still names // them, so a developer running both can declare them in their // own profile; what is built in is what this repository can // start. index: None, query: None, web: None, }, ); Config { services, profiles, bind: BTreeMap::new(), debug: false, } } #[cfg(test)] mod tests { use super::*; /// The one duplication in this crate, checked rather than trusted. #[test] fn the_builtin_ports_match_the_development_scripts() { let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")) .parent() .and_then(|crates| crates.parent()) .expect("the workspace root is two levels up") .join("scripts"); let config = builtin(); for (script, variable, service) in [ ("dev-pds.sh", "DIDBOT_PDS_PORT", "pds"), ("dev-mcp.sh", "DIDBOT_MCP_PORT", "mcp"), ] { let source = std::fs::read_to_string(root.join(script)) .unwrap_or_else(|err| panic!("read {script}: {err}")); let expected = config.services[service].port; let needle = format!("${{{variable}:-{expected}}}"); assert!( source.contains(&needle), "{script} does not default {variable} to {expected}; \ the builtin configuration and the script disagree" ); } } }