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