Skip to main content

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}