Skip to main content

cli/
privilege.rs

1use anyhow::{Context, Result};
2use dialoguer::Confirm;
3use std::io::IsTerminal;
4use std::process::Stdio;
5use std::sync::atomic::{AtomicBool, Ordering};
6
7static AUTHORIZED: AtomicBool = AtomicBool::new(false);
8
9pub async fn ensure_admin(action_count: usize) -> Result<bool> {
10    if AUTHORIZED.load(Ordering::Acquire) {
11        return Ok(true);
12    }
13    if cfg!(windows) {
14        // Windows drivers batch their privileged work through an elevated
15        // PowerShell process. Authorization happens when that batch starts.
16        return Ok(true);
17    }
18    if std::env::var("USER").is_ok_and(|user| user == "root") {
19        AUTHORIZED.store(true, Ordering::Release);
20        return Ok(true);
21    }
22    if tokio::process::Command::new("sudo")
23        .args(["-n", "-v"])
24        .status()
25        .await
26        .is_ok_and(|status| status.success())
27    {
28        AUTHORIZED.store(true, Ordering::Release);
29        return Ok(true);
30    }
31    if !(std::io::stdin().is_terminal() && std::io::stdout().is_terminal()) {
32        eprintln!(
33            "managed system configuration requires administrator privileges; run interactively"
34        );
35        return Ok(false);
36    }
37    let confirmed = Confirm::new()
38        .with_prompt(format!(
39            "Apply {action_count} operation(s) with administrator privileges?"
40        ))
41        .default(false)
42        .interact()?;
43    if !confirmed {
44        return Ok(false);
45    }
46    let status = tokio::process::Command::new("sudo")
47        .arg("-v")
48        .stdin(Stdio::inherit())
49        .stdout(Stdio::inherit())
50        .stderr(Stdio::inherit())
51        .status()
52        .await
53        .context("failed to request administrator privileges")?;
54    if status.success() {
55        AUTHORIZED.store(true, Ordering::Release);
56    }
57    Ok(status.success())
58}