1use crate::prelude::*;
2
3use std::os::unix::fs::{DirBuilderExt as _, PermissionsExt as _};
4
5pub fn make_all() -> Result<()> {
6 create_dir_all_with_permissions(&config_dir(), 0o700)?;
7 create_dir_all_with_permissions(&cache_dir(), 0o700)?;
8 create_dir_all_with_permissions(&runtime_dir(), 0o700)?;
9 create_dir_all_with_permissions(&data_dir(), 0o700)?;
10
11 Ok(())
12}
13
14fn create_dir_all_with_permissions(
15 path: &std::path::Path,
16 mode: u32,
17) -> Result<()> {
18 std::fs::DirBuilder::new()
21 .recursive(true)
22 .mode(mode)
23 .create(path)
24 .map_err(|source| Error::CreateDirectory {
25 source,
26 file: path.to_path_buf(),
27 })?;
28 std::fs::set_permissions(path, std::fs::Permissions::from_mode(mode))
31 .map_err(|source| Error::CreateDirectory {
32 source,
33 file: path.to_path_buf(),
34 })?;
35 Ok(())
36}
37
38pub fn config_file() -> std::path::PathBuf {
39 config_dir().join("config.json")
40}
41
42pub fn db_file(server: &str, email: &str) -> std::path::PathBuf {
43 let server = urlencoding::encode(server).into_owned();
44 cache_dir().join(format!("{server}:{email}.json"))
45}
46
47pub fn pid_file() -> std::path::PathBuf {
48 runtime_dir().join("pidfile")
49}
50
51pub fn agent_stdout_file() -> std::path::PathBuf {
52 data_dir().join("agent.out")
53}
54
55pub fn agent_stderr_file() -> std::path::PathBuf {
56 data_dir().join("agent.err")
57}
58
59pub fn device_id_file() -> std::path::PathBuf {
60 data_dir().join("device_id")
61}
62
63pub fn socket_file() -> std::path::PathBuf {
64 runtime_dir().join("socket")
65}
66
67pub fn ssh_agent_socket_file() -> std::path::PathBuf {
68 runtime_dir().join("ssh-agent-socket")
69}
70
71fn home_dir() -> std::path::PathBuf {
72 std::env::var_os("HOME").map_or_else(
73 || std::path::PathBuf::from("/"),
74 std::path::PathBuf::from,
75 )
76}
77
78#[cfg(target_os = "macos")]
79fn config_dir() -> std::path::PathBuf {
80 home_dir()
81 .join("Library/Application Support")
82 .join(profile())
83}
84
85#[cfg(target_os = "macos")]
86fn cache_dir() -> std::path::PathBuf {
87 home_dir().join("Library/Caches").join(profile())
88}
89
90#[cfg(target_os = "macos")]
91fn data_dir() -> std::path::PathBuf {
92 config_dir()
93}
94
95#[cfg(not(target_os = "macos"))]
96fn xdg_or(env: &str, fallback_rel: &str) -> std::path::PathBuf {
97 std::env::var_os(env)
98 .filter(|v| std::path::Path::new(v).is_absolute())
99 .map_or_else(
100 || home_dir().join(fallback_rel),
101 std::path::PathBuf::from,
102 )
103}
104
105#[cfg(not(target_os = "macos"))]
106fn config_dir() -> std::path::PathBuf {
107 xdg_or("XDG_CONFIG_HOME", ".config").join(profile())
108}
109
110#[cfg(not(target_os = "macos"))]
111fn cache_dir() -> std::path::PathBuf {
112 xdg_or("XDG_CACHE_HOME", ".cache").join(profile())
113}
114
115#[cfg(not(target_os = "macos"))]
116fn data_dir() -> std::path::PathBuf {
117 xdg_or("XDG_DATA_HOME", ".local/share").join(profile())
118}
119
120fn runtime_dir() -> std::path::PathBuf {
121 if let Some(d) = std::env::var_os("XDG_RUNTIME_DIR") {
126 if std::path::Path::new(&d).is_absolute() {
127 return std::path::PathBuf::from(d).join(profile());
128 }
129 }
130 format!(
131 "{}/{}-{}",
132 std::env::temp_dir().to_string_lossy(),
133 profile(),
134 rustix::process::getuid().as_raw()
135 )
136 .into()
137}
138
139pub fn profile() -> String {
140 match std::env::var("BWX_PROFILE") {
141 Ok(profile) if !profile.is_empty() => format!("bwx-{profile}"),
142 _ => "bwx".to_string(),
143 }
144}