Skip to main content

kanade_shared/
config.rs

1use std::path::{Path, PathBuf};
2
3use anyhow::{Context, Result};
4use serde::Deserialize;
5
6// ─── Agent config ────────────────────────────────────────────────────
7
8#[derive(Deserialize, Debug, Clone)]
9pub struct AgentConfig {
10    pub agent: AgentSection,
11    pub log: LogSection,
12    #[serde(default)]
13    pub inventory: InventorySection,
14}
15
16#[derive(Deserialize, Debug, Clone)]
17pub struct AgentSection {
18    pub id: String,
19    pub nats_url: String,
20    /// Wave / group memberships. Each entry causes the agent to
21    /// subscribe to `commands.group.{name}` on startup. Sprint 4a
22    /// declares groups statically in the agent.toml; a dynamic
23    /// group-membership KV is on the Sprint 4d backlog.
24    #[serde(default)]
25    pub groups: Vec<String>,
26}
27
28#[derive(Deserialize, Debug, Clone)]
29pub struct LogSection {
30    pub path: String,
31    pub level: String,
32}
33
34#[derive(Deserialize, Debug, Clone)]
35pub struct InventorySection {
36    #[serde(default = "default_hw_interval")]
37    pub hw_interval: String,
38    #[serde(default = "default_jitter")]
39    pub jitter: String,
40    #[serde(default = "default_enabled")]
41    pub enabled: bool,
42}
43
44impl Default for InventorySection {
45    fn default() -> Self {
46        Self {
47            hw_interval: default_hw_interval(),
48            jitter: default_jitter(),
49            enabled: default_enabled(),
50        }
51    }
52}
53
54fn default_hw_interval() -> String {
55    "24h".into()
56}
57fn default_jitter() -> String {
58    "10m".into()
59}
60fn default_enabled() -> bool {
61    true
62}
63
64// ─── Backend config ──────────────────────────────────────────────────
65
66#[derive(Deserialize, Debug, Clone)]
67pub struct BackendConfig {
68    pub server: ServerSection,
69    pub nats: NatsSection,
70    pub db: DbSection,
71    pub log: LogSection,
72}
73
74#[derive(Deserialize, Debug, Clone)]
75pub struct ServerSection {
76    pub bind: String,
77}
78
79#[derive(Deserialize, Debug, Clone)]
80pub struct NatsSection {
81    pub url: String,
82}
83
84#[derive(Deserialize, Debug, Clone)]
85pub struct DbSection {
86    pub sqlite_path: String,
87}
88
89// ─── Loader ──────────────────────────────────────────────────────────
90
91fn load_typed<T: serde::de::DeserializeOwned>(path: &Path) -> Result<T> {
92    let mut engine = teravars::Engine::new();
93    let ctx = teravars::system_context();
94    let paths: Vec<PathBuf> = vec![path.to_path_buf()];
95    let merged = teravars::load_merged(&paths, &mut engine, &ctx)
96        .with_context(|| format!("teravars load_merged: {path:?}"))?;
97    let cfg: T = toml::Value::Table(merged.config)
98        .try_into()
99        .with_context(|| format!("decode config from {path:?}"))?;
100    Ok(cfg)
101}
102
103pub fn load_agent_config(path: &Path) -> Result<AgentConfig> {
104    load_typed(path)
105}
106
107pub fn load_backend_config(path: &Path) -> Result<BackendConfig> {
108    load_typed(path)
109}