1use std::path::{Path, PathBuf};
2
3use anyhow::{Context, Result};
4use serde::Deserialize;
5
6#[derive(Deserialize, Debug, Clone)]
9pub struct AgentConfig {
10 pub agent: AgentSection,
11 pub log: LogSection,
12 #[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 #[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#[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
97fn 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}