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}
13
14#[derive(Deserialize, Debug, Clone)]
15pub struct AgentSection {
16    pub id: String,
17    pub nats_url: String,
18    /// DEPRECATED in Sprint 5: group membership is now server-managed
19    /// via the `agent_groups` KV bucket. Use
20    /// `kanade agent groups set <pc_id> <group> [<group> ...]` to
21    /// declare membership. Still parsed for back-compat; the value
22    /// is logged-and-ignored at startup. Field removal is scheduled
23    /// for v0.4.0.
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    /// Number of rotated daily files (incl. today's) to retain.
33    /// Defaults to 14 — covers two weeks of incidents without
34    /// blowing up disk. Set to 0 to disable on-disk logging
35    /// (stdout only).
36    #[serde(default = "default_keep_days")]
37    pub keep_days: usize,
38}
39
40fn default_keep_days() -> usize {
41    14
42}
43
44// ─── Backend config ──────────────────────────────────────────────────
45
46#[derive(Deserialize, Debug, Clone)]
47pub struct BackendConfig {
48    pub server: ServerSection,
49    pub nats: NatsSection,
50    pub db: DbSection,
51    pub log: LogSection,
52}
53
54#[derive(Deserialize, Debug, Clone)]
55pub struct ServerSection {
56    pub bind: String,
57}
58
59#[derive(Deserialize, Debug, Clone)]
60pub struct NatsSection {
61    pub url: String,
62}
63
64#[derive(Deserialize, Debug, Clone)]
65pub struct DbSection {
66    pub sqlite_path: String,
67}
68
69// ─── Loader ──────────────────────────────────────────────────────────
70
71fn load_typed<T: serde::de::DeserializeOwned>(path: &Path) -> Result<T> {
72    let mut engine = teravars::Engine::new();
73    let ctx = teravars::system_context();
74    let paths: Vec<PathBuf> = vec![path.to_path_buf()];
75    let merged = teravars::load_merged(&paths, &mut engine, &ctx)
76        .with_context(|| format!("teravars load_merged: {path:?}"))?;
77    let cfg: T = toml::Value::Table(merged.config)
78        .try_into()
79        .with_context(|| format!("decode config from {path:?}"))?;
80    Ok(cfg)
81}
82
83pub fn load_agent_config(path: &Path) -> Result<AgentConfig> {
84    load_typed(path)
85}
86
87pub fn load_backend_config(path: &Path) -> Result<BackendConfig> {
88    load_typed(path)
89}