Skip to main content

podbox/
env.rs

1use std::env;
2use std::path::{Path, PathBuf};
3
4use anyhow::Result;
5use nix::unistd::getuid;
6
7/// Resolved host environment for socket and path detection.
8pub 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
32/// Resolve the host environment.
33///
34/// Reads `WAYLAND_DISPLAY`, `XDG_RUNTIME_DIR` from the environment.
35/// Detects presence of Wayland, PipeWire, PulseAudio, and D-Bus sockets.
36/// Detects timezone files and modern XDG theme/icon/font directories.
37pub 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}