-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathenv.rs
242 lines (218 loc) · 6.99 KB
/
env.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
//! Information about the environment in which Nix will run
use std::{fmt::Display, path::Path};
use bytesize::ByteSize;
use os_info;
use serde::{Deserialize, Serialize};
use std::process::Command;
use tracing::instrument;
/// The environment in which Nix operates
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct NixEnv {
/// Current user ($USER)
pub current_user: String,
/// Current user groups
pub current_user_groups: Vec<String>,
/// Underlying OS in which Nix runs
pub os: OS,
/// Total disk space of the volume where /nix exists.
///
/// This is either root volume or the dedicated /nix volume.
pub total_disk_space: ByteSize,
/// Total memory
pub total_memory: ByteSize,
}
impl NixEnv {
/// Determine [NixEnv] on the user's system
#[instrument]
pub async fn detect() -> Result<NixEnv, NixEnvError> {
use sysinfo::{DiskExt, SystemExt};
tracing::info!("Detecting Nix environment");
let os = OS::detect().await;
tokio::task::spawn_blocking(|| {
let current_user = std::env::var("USER")?;
let sys = sysinfo::System::new_with_specifics(
sysinfo::RefreshKind::new().with_disks_list().with_memory(),
);
let total_disk_space = to_bytesize(get_nix_disk(&sys)?.total_space());
let total_memory = to_bytesize(sys.total_memory());
let current_user_groups = get_current_user_groups()?;
Ok(NixEnv {
current_user,
current_user_groups,
os,
total_disk_space,
total_memory,
})
})
.await
.unwrap()
}
}
/// Get the current user's groups
fn get_current_user_groups() -> Result<Vec<String>, NixEnvError> {
let output = Command::new("groups")
.output()
.map_err(NixEnvError::GroupsError)?;
let group_info = &String::from_utf8_lossy(&output.stdout);
Ok(group_info
.as_ref()
.split_whitespace()
.map(|v| v.to_string())
.collect())
}
/// Get the disk where /nix exists
fn get_nix_disk(sys: &sysinfo::System) -> Result<&sysinfo::Disk, NixEnvError> {
use sysinfo::{DiskExt, SystemExt};
let by_mount_point: std::collections::HashMap<&Path, &sysinfo::Disk> = sys
.disks()
.iter()
.map(|disk| (disk.mount_point(), disk))
.collect();
// Lookup /nix first, then /.
by_mount_point
.get(Path::new("/nix"))
.copied()
.or_else(|| by_mount_point.get(Path::new("/")).copied())
.ok_or(NixEnvError::NoDisk)
}
/// The system under which Nix is installed and operates
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum OS {
/// On macOS
MacOS {
/// Using nix-darwin
nix_darwin: bool,
arch: MacOSArch,
},
/// On NixOS
NixOS,
/// Nix is individually installed on Linux or macOS
Other(os_info::Type),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum MacOSArch {
Arm64(AppleEmulation),
Other(Option<String>),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum AppleEmulation {
None,
Rosetta,
}
impl AppleEmulation {
pub fn new() -> Self {
use is_proc_translated::is_proc_translated;
if is_proc_translated() {
AppleEmulation::Rosetta
} else {
AppleEmulation::None
}
}
}
impl Default for AppleEmulation {
fn default() -> Self {
Self::new()
}
}
impl MacOSArch {
pub fn from(os_arch: Option<&str>) -> MacOSArch {
match os_arch {
Some("arm64") => MacOSArch::Arm64(AppleEmulation::new()),
other => MacOSArch::Other(other.map(|s| s.to_string())),
}
}
}
// The [Display] instance affects how [OS] is displayed to the app user
impl Display for OS {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
OS::MacOS {
nix_darwin,
arch: _,
} => {
if *nix_darwin {
write!(f, "nix-darwin")
} else {
write!(f, "macOS")
}
}
OS::NixOS => write!(f, "NixOS"),
OS::Other(os_type) => write!(f, "{}", os_type),
}
}
}
impl OS {
pub async fn detect() -> Self {
let os_info = tokio::task::spawn_blocking(os_info::get).await.unwrap();
let os_type = os_info.os_type();
let arch = MacOSArch::from(os_info.architecture());
async fn is_symlink(file_path: &str) -> std::io::Result<bool> {
let metadata = tokio::fs::symlink_metadata(file_path).await?;
Ok(metadata.file_type().is_symlink())
}
match os_type {
os_info::Type::Macos => {
// To detect that we are on NixDarwin, we check if /etc/nix/nix.conf
// is a symlink (which nix-darwin manages like NixOS does)
let nix_darwin = is_symlink("/etc/nix/nix.conf").await.unwrap_or(false);
OS::MacOS { nix_darwin, arch }
}
os_info::Type::NixOS => OS::NixOS,
_ => OS::Other(os_type),
}
}
/// Return the label for nix-darwin or NixOS system
pub fn nix_system_config_label(&self) -> Option<String> {
// TODO: This should return Markdown
match self {
OS::MacOS {
nix_darwin,
arch: _,
} if *nix_darwin => Some("nix-darwin configuration".to_string()),
OS::NixOS => Some("nixos configuration".to_string()),
_ => None,
}
}
/// Return the label for where Nix is configured
pub fn nix_config_label(&self) -> String {
self.nix_system_config_label()
.unwrap_or("/etc/nix/nix.conf".to_string())
}
}
/// Errors while trying to fetch [NixEnv]
#[derive(thiserror::Error, Debug)]
pub enum NixEnvError {
#[error("Cannot find $USER: {0}")]
UserError(#[from] std::env::VarError),
#[error("Failed to fetch groups: {0}")]
GroupsError(std::io::Error),
#[error("Unable to find root disk or /nix volume")]
NoDisk,
}
/// Convert bytes to a closest [ByteSize]
///
/// Useful for displaying disk space and memory which are typically in GBs / TBs
fn to_bytesize(bytes: u64) -> ByteSize {
let kb = bytes / 1024;
let mb = kb / 1024;
let gb = mb / 1024;
if gb > 0 {
ByteSize::gib(gb)
} else if mb > 0 {
ByteSize::mib(mb)
} else if kb > 0 {
ByteSize::kib(kb)
} else {
ByteSize::b(bytes)
}
}
/// Test for [to_bytesize]
#[test]
fn test_to_bytesize() {
assert_eq!(to_bytesize(0), ByteSize::b(0));
assert_eq!(to_bytesize(1), ByteSize::b(1));
assert_eq!(to_bytesize(1023), ByteSize::b(1023));
assert_eq!(to_bytesize(1024), ByteSize::kib(1));
assert_eq!(to_bytesize(1024 * 1024), ByteSize::mib(1));
assert_eq!(to_bytesize(1024 * 1024 * 1024), ByteSize::gib(1));
}