kanade_shared/default_paths.rs
1//! Spec §2.11 install layout — OS-aware default paths for config /
2//! mutable data / logs, plus the [`find_config`] fallback chain that
3//! every binary uses to locate its config file.
4//!
5//! Layout
6//!
7//! ```text
8//! Windows Linux
9//! C:\Program Files\Kanade\ /usr/local/bin/
10//! ↑ binaries ↑ binaries
11//!
12//! C:\ProgramData\Kanade\config\ /etc/kanade/
13//! ├─ agent.toml ├─ agent.toml
14//! └─ backend.toml └─ backend.toml
15//!
16//! C:\ProgramData\Kanade\data\ /var/lib/kanade/
17//! ├─ state.db (agent) ├─ state.db
18//! ├─ outbox/ (agent) ├─ outbox/
19//! ├─ staging/ (agent self-update) ├─ staging/
20//! ├─ backend.db (backend) ├─ backend.db
21//! ├─ certs/ ├─ certs/
22//! └─ nats/ (JetStream data) └─ nats/
23//!
24//! C:\ProgramData\Kanade\logs\ /var/log/kanade/
25//! ├─ agent.log ├─ agent.log
26//! ├─ backend.log ├─ backend.log
27//! └─ nats-server.log └─ nats-server.log
28//! ```
29
30use std::path::{Path, PathBuf};
31
32/// `%ProgramData%\Kanade\config\` on Windows, `/etc/kanade/` on Linux.
33pub fn config_dir() -> PathBuf {
34 #[cfg(target_os = "windows")]
35 {
36 program_data().join("Kanade").join("config")
37 }
38 #[cfg(not(target_os = "windows"))]
39 {
40 PathBuf::from("/etc/kanade")
41 }
42}
43
44/// `%ProgramData%\Kanade\data\` on Windows, `/var/lib/kanade/` on Linux.
45pub fn data_dir() -> PathBuf {
46 #[cfg(target_os = "windows")]
47 {
48 program_data().join("Kanade").join("data")
49 }
50 #[cfg(not(target_os = "windows"))]
51 {
52 PathBuf::from("/var/lib/kanade")
53 }
54}
55
56/// `%ProgramData%\Kanade\logs\` on Windows, `/var/log/kanade/` on Linux.
57pub fn log_dir() -> PathBuf {
58 #[cfg(target_os = "windows")]
59 {
60 program_data().join("Kanade").join("logs")
61 }
62 #[cfg(not(target_os = "windows"))]
63 {
64 PathBuf::from("/var/log/kanade")
65 }
66}
67
68#[cfg(target_os = "windows")]
69fn program_data() -> PathBuf {
70 std::env::var_os("ProgramData")
71 .map(PathBuf::from)
72 .unwrap_or_else(|| PathBuf::from(r"C:\ProgramData"))
73}
74
75/// Resolve the config file path through the fallback chain:
76///
77/// 1. `flag` (e.g. `--config <path>`) — honored verbatim, even when the
78/// file does not exist (caller's choice).
79/// 2. `env_var` value (e.g. `KANADE_AGENT_CONFIG`) — honored verbatim
80/// when set to a non-empty string.
81/// 3. OS-standard location `<config_dir>/<basename>` — only used when
82/// the file is actually present.
83///
84/// Returns an error when none of the above produced a usable path; the
85/// message lists every option an operator can take to fix it.
86pub fn find_config(flag: Option<&Path>, env_var: &str, basename: &str) -> anyhow::Result<PathBuf> {
87 if let Some(p) = flag {
88 return Ok(p.to_path_buf());
89 }
90 if let Ok(raw) = std::env::var(env_var)
91 && !raw.is_empty()
92 {
93 return Ok(PathBuf::from(raw));
94 }
95 let std_path = config_dir().join(basename);
96 if std_path.exists() {
97 return Ok(std_path);
98 }
99 Err(anyhow::anyhow!(
100 "config not found — pass `--config <path>`, set `{env_var}`, or place `{basename}` at `{}`",
101 std_path.display(),
102 ))
103}