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    /// DEPRECATED in Sprint 5: group membership is now server-managed
21    /// via the `agent_groups` KV bucket. Use
22    /// `kanade agent groups set <pc_id> <group> [<group> ...]` to
23    /// declare membership. The field is still parsed for back-compat
24    /// — old agent.toml files keep loading — but the value is
25    /// logged-and-ignored at startup and will be removed in v0.3.0.
26    #[serde(default)]
27    pub groups: Vec<String>,
28}
29
30#[derive(Deserialize, Debug, Clone)]
31pub struct LogSection {
32    pub path: String,
33    pub level: String,
34}
35
36#[derive(Deserialize, Debug, Clone)]
37pub struct InventorySection {
38    #[serde(default = "default_hw_interval")]
39    pub hw_interval: String,
40    #[serde(default = "default_jitter")]
41    pub jitter: String,
42    #[serde(default = "default_enabled")]
43    pub enabled: bool,
44}
45
46impl Default for InventorySection {
47    fn default() -> Self {
48        Self {
49            hw_interval: default_hw_interval(),
50            jitter: default_jitter(),
51            enabled: default_enabled(),
52        }
53    }
54}
55
56fn default_hw_interval() -> String {
57    "24h".into()
58}
59fn default_jitter() -> String {
60    "10m".into()
61}
62fn default_enabled() -> bool {
63    true
64}
65
66// ─── Backend config ──────────────────────────────────────────────────
67
68#[derive(Deserialize, Debug, Clone)]
69pub struct BackendConfig {
70    pub server: ServerSection,
71    pub nats: NatsSection,
72    pub db: DbSection,
73    pub log: LogSection,
74}
75
76#[derive(Deserialize, Debug, Clone)]
77pub struct ServerSection {
78    pub bind: String,
79}
80
81#[derive(Deserialize, Debug, Clone)]
82pub struct NatsSection {
83    pub url: String,
84}
85
86#[derive(Deserialize, Debug, Clone)]
87pub struct DbSection {
88    pub sqlite_path: String,
89}
90
91// ─── Loader ──────────────────────────────────────────────────────────
92
93fn load_typed<T: serde::de::DeserializeOwned>(path: &Path) -> Result<T> {
94    let mut engine = teravars::Engine::new();
95    let ctx = teravars::system_context();
96    let paths: Vec<PathBuf> = vec![path.to_path_buf()];
97    let merged = teravars::load_merged(&paths, &mut engine, &ctx)
98        .with_context(|| format!("teravars load_merged: {path:?}"))?;
99    let cfg: T = toml::Value::Table(merged.config)
100        .try_into()
101        .with_context(|| format!("decode config from {path:?}"))?;
102    Ok(cfg)
103}
104
105pub fn load_agent_config(path: &Path) -> Result<AgentConfig> {
106    load_typed(path)
107}
108
109pub fn load_backend_config(path: &Path) -> Result<BackendConfig> {
110    load_typed(path)
111}