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 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}