Skip to main content

pitchfork_cli/
env.rs

1use once_cell::sync::Lazy;
2pub use std::env::*;
3use std::path::PathBuf;
4
5pub static PITCHFORK_BIN: Lazy<PathBuf> = Lazy::new(|| {
6    current_exe()
7        .and_then(|p| p.canonicalize())
8        .unwrap_or_else(|e| {
9            eprintln!("Warning: Could not determine pitchfork binary path: {e}");
10            args()
11                .next()
12                .map(PathBuf::from)
13                .unwrap_or_else(|| PathBuf::from("pitchfork"))
14        })
15});
16pub static CWD: Lazy<PathBuf> = Lazy::new(|| current_dir().unwrap_or_else(|_| PathBuf::from(".")));
17
18pub static HOME_DIR: Lazy<PathBuf> = Lazy::new(|| {
19    // When running under `sudo`, HOME points to /var/root (macOS) or /root (Linux).
20    // Resolve the *original* user's home via SUDO_USER so all derived paths
21    // (state file, IPC socket, config, logs) remain consistent with the
22    // non-sudo invocation. This prevents a second supervisor instance from
23    // being spawned in a separate directory tree.
24    //
25    // Guard: only honour SUDO_USER when the effective UID is 0 (i.e. we are
26    // actually running as root). SUDO_USER can leak into non-sudo environments
27    // (e.g. inherited env, containers) and would misdirect all state paths.
28    #[cfg(unix)]
29    if nix::unistd::Uid::effective().is_root() {
30        if let Ok(sudo_user) = std::env::var("SUDO_USER") {
31            if let Some(home) = home_dir_for_user(&sudo_user) {
32                return home;
33            }
34        }
35    }
36    dirs::home_dir().unwrap_or_else(|| {
37        eprintln!("Warning: Could not determine home directory");
38        PathBuf::from("/tmp")
39    })
40});
41pub static PITCHFORK_CONFIG_DIR: Lazy<PathBuf> = Lazy::new(|| {
42    var_path("PITCHFORK_CONFIG_DIR").unwrap_or(HOME_DIR.join(".config").join("pitchfork"))
43});
44pub static PITCHFORK_GLOBAL_CONFIG_USER: Lazy<PathBuf> =
45    Lazy::new(|| PITCHFORK_CONFIG_DIR.join("config.toml"));
46pub static PITCHFORK_GLOBAL_CONFIG_SYSTEM: Lazy<PathBuf> =
47    Lazy::new(|| PathBuf::from("/etc/pitchfork/config.toml"));
48pub static PITCHFORK_STATE_DIR: Lazy<PathBuf> = Lazy::new(|| {
49    if let Some(p) = var_path("PITCHFORK_STATE_DIR") {
50        return p;
51    }
52    // Under sudo, dirs::state_dir() would resolve against root's HOME,
53    // bypassing our SUDO_USER correction. Use HOME_DIR directly instead.
54    #[cfg(unix)]
55    if nix::unistd::Uid::effective().is_root() {
56        return HOME_DIR.join(".local").join("state").join("pitchfork");
57    }
58    dirs::state_dir()
59        .unwrap_or_else(|| HOME_DIR.join(".local").join("state"))
60        .join("pitchfork")
61});
62pub static PITCHFORK_STATE_FILE: Lazy<PathBuf> =
63    Lazy::new(|| PITCHFORK_STATE_DIR.join("state.toml"));
64pub static PITCHFORK_LOG: Lazy<log::LevelFilter> =
65    Lazy::new(|| var_log_level("PITCHFORK_LOG").unwrap_or(log::LevelFilter::Info));
66pub static PITCHFORK_LOG_FILE_LEVEL: Lazy<log::LevelFilter> =
67    Lazy::new(|| var_log_level("PITCHFORK_LOG_FILE_LEVEL").unwrap_or(*PITCHFORK_LOG));
68pub static PITCHFORK_LOGS_DIR: Lazy<PathBuf> =
69    Lazy::new(|| var_path("PITCHFORK_LOGS_DIR").unwrap_or(PITCHFORK_STATE_DIR.join("logs")));
70pub static PITCHFORK_LOG_FILE: Lazy<PathBuf> =
71    Lazy::new(|| PITCHFORK_LOGS_DIR.join("pitchfork").join("pitchfork.log"));
72// pub static PITCHFORK_EXEC: Lazy<bool> = Lazy::new(|| var_true("PITCHFORK_EXEC"));
73
74pub static IPC_SOCK_DIR: Lazy<PathBuf> = Lazy::new(|| PITCHFORK_STATE_DIR.join("sock"));
75pub static IPC_SOCK_MAIN: Lazy<PathBuf> = Lazy::new(|| IPC_SOCK_DIR.join("main.sock"));
76
77// Capture the PATH at startup so daemons can find user tools
78pub static ORIGINAL_PATH: Lazy<Option<String>> = Lazy::new(|| var("PATH").ok());
79pub static IPC_JSON: Lazy<bool> = Lazy::new(|| !var_false("IPC_JSON"));
80
81fn var_path(name: &str) -> Option<PathBuf> {
82    var(name).map(PathBuf::from).ok()
83}
84
85fn var_log_level(name: &str) -> Option<log::LevelFilter> {
86    var(name).ok().and_then(|level| level.parse().ok())
87}
88
89fn var_false(name: &str) -> bool {
90    var(name)
91        .map(|val| val.to_lowercase())
92        .map(|val| val == "false" || val == "0")
93        .unwrap_or(false)
94}
95
96// fn var_true(name: &str) -> bool {
97//     var(name)
98//         .map(|val| val.to_lowercase())
99//         .map(|val| val == "true" || val == "1")
100//         .unwrap_or(false)
101// }
102
103/// Look up a user's home directory via the system password database.
104/// Returns `None` if the user does not exist or the lookup fails.
105#[cfg(unix)]
106fn home_dir_for_user(username: &str) -> Option<PathBuf> {
107    nix::unistd::User::from_name(username)
108        .ok()
109        .flatten()
110        .map(|u| u.dir)
111}