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