1use std::env;
2use std::path::{Path, PathBuf};
3
4use anyhow::Result;
5use nix::unistd::getuid;
6
7pub struct HostEnv {
9 pub uid: u32,
10 pub username: String,
11 pub xdg_runtime_dir: PathBuf,
12 pub wayland_display: Option<String>,
13 pub wayland_socket: Option<PathBuf>,
14 pub pipewire_socket: Option<PathBuf>,
15 pub pulse_dir: Option<PathBuf>,
16 pub dbus_socket: Option<PathBuf>,
17 pub gpu_has_dri: bool,
18 pub gpu_has_nvidia: bool,
19 pub gpu_has_nvidia_uvm: bool,
20 pub host_has_localtime: bool,
21 pub host_has_timezone_file: bool,
22 pub host_has_local_share_themes: bool,
23 pub host_has_local_share_icons: bool,
24 pub host_has_local_share_fonts: bool,
25 pub host_shell: Option<String>,
26 pub host_locale: Option<String>,
27 pub gpg_agent_socket: Option<PathBuf>,
28 pub gpg_home: Option<PathBuf>,
29}
30
31pub fn resolve() -> Result<HostEnv> {
37 let uid = getuid().as_raw();
38 let username = env::var("USER")
39 .or_else(|_| env::var("LOGNAME"))
40 .ok()
41 .filter(|s| !s.is_empty())
42 .unwrap_or_else(|| {
43 nix::unistd::User::from_uid(nix::unistd::Uid::from_raw(uid))
44 .ok()
45 .flatten()
46 .map(|u| u.name)
47 .unwrap_or_else(|| "user".into())
48 });
49
50 let xdg_runtime_dir: PathBuf = env::var_os("XDG_RUNTIME_DIR")
51 .map(PathBuf::from)
52 .unwrap_or_else(|| PathBuf::from(format!("/run/user/{}", uid)));
53
54 let wayland_display = env::var("WAYLAND_DISPLAY").ok();
55 let wayland_socket = wayland_display
56 .as_ref()
57 .map(|d| xdg_runtime_dir.join(d))
58 .filter(|p| p.exists());
59
60 let pipewire_socket = Some(xdg_runtime_dir.join("pipewire-0")).filter(|p| p.exists());
61
62 let pulse_dir = Some(xdg_runtime_dir.join("pulse")).filter(|p| p.join("native").exists());
63
64 let dbus_socket = Some(xdg_runtime_dir.join("bus")).filter(|p| p.exists());
65
66 let gpu_has_dri = Path::new("/dev/dri").exists();
67 let gpu_has_nvidia = Path::new("/dev/nvidiactl").exists();
68 let gpu_has_nvidia_uvm = Path::new("/dev/nvidia-uvm").exists();
69
70 let host_has_localtime = Path::new("/etc/localtime").exists();
71 let host_has_timezone_file = Path::new("/etc/timezone").exists();
72
73 let home = dirs::home_dir().unwrap_or_else(|| PathBuf::from("/root"));
74 let host_has_local_share_themes = home.join(".local/share/themes").exists();
75 let host_has_local_share_icons = home.join(".local/share/icons").exists();
76 let host_has_local_share_fonts = home.join(".local/share/fonts").exists();
77
78 let host_shell = env::var("SHELL").ok().filter(|s| !s.is_empty());
79 let host_locale = env::var("LANG")
80 .ok()
81 .or_else(|| env::var("LC_ALL").ok())
82 .or_else(|| env::var("LC_CTYPE").ok())
83 .filter(|s| !s.is_empty());
84
85 let gpg_home = env::var("GNUPGHOME").ok().map(PathBuf::from).or_else(|| {
86 let fallback = dirs::home_dir()
87 .unwrap_or_else(|| PathBuf::from("/root"))
88 .join(".gnupg");
89 if fallback.exists() {
90 Some(fallback)
91 } else {
92 None
93 }
94 });
95 let gpg_agent_socket = gpg_home.as_ref().and_then(|gpg| {
96 let sock = gpg.join("S.gpg-agent");
97 if sock.exists() { Some(sock) } else { None }
98 });
99
100 Ok(HostEnv {
101 uid,
102 username,
103 xdg_runtime_dir,
104 wayland_display,
105 wayland_socket,
106 pipewire_socket,
107 pulse_dir,
108 dbus_socket,
109 gpu_has_dri,
110 gpu_has_nvidia,
111 gpu_has_nvidia_uvm,
112 host_has_localtime,
113 host_has_timezone_file,
114 host_has_local_share_themes,
115 host_has_local_share_icons,
116 host_has_local_share_fonts,
117 host_shell,
118 host_locale,
119 gpg_agent_socket,
120 gpg_home,
121 })
122}