diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 46df130..430410c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,11 +72,11 @@ jobs: - name: Set executable run: chmod +x ./harmonic - name: Initial install - run: sudo GITHUB_PATH=$GITHUB_PATH RUST_LOG=harmonic=trace RUST_BACKTRACE=full ./harmonic install linux-multi --extra-conf "access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}" --no-confirm + run: GITHUB_PATH=$GITHUB_PATH RUST_LOG=harmonic=trace RUST_BACKTRACE=full ./harmonic install linux-multi --extra-conf "access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}" --no-confirm - name: Initial uninstall (without a `nix run` first) - run: sudo GITHUB_PATH=$GITHUB_PATH RUST_LOG=harmonic=trace RUST_BACKTRACE=full ./harmonic uninstall --no-confirm + run: GITHUB_PATH=$GITHUB_PATH RUST_LOG=harmonic=trace RUST_BACKTRACE=full ./harmonic uninstall --no-confirm - name: Repeated install - run: sudo GITHUB_PATH=$GITHUB_PATH RUST_LOG=harmonic=trace RUST_BACKTRACE=full ./harmonic install linux-multi --extra-conf "access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}" --no-confirm + run: GITHUB_PATH=$GITHUB_PATH RUST_LOG=harmonic=trace RUST_BACKTRACE=full ./harmonic install linux-multi --extra-conf "access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}" --no-confirm - name: echo $PATH run: echo $PATH - name: Test `nix` @@ -99,7 +99,7 @@ jobs: if: success() || failure() shell: fish --login {0} - name: Repeated uninstall - run: sudo GITHUB_PATH=$GITHUB_PATH RUST_LOG=harmonic=trace RUST_BACKTRACE=full ./harmonic uninstall --no-confirm + run: GITHUB_PATH=$GITHUB_PATH RUST_LOG=harmonic=trace RUST_BACKTRACE=full ./harmonic uninstall --no-confirm run-steam-deck: name: Run Steam Deck (mock) @@ -119,11 +119,11 @@ jobs: sudo chmod +x /bin/steamos-readonly sudo useradd -m deck - name: Initial install - run: sudo GITHUB_PATH=$GITHUB_PATH PATH=$PATH:$HOME/bin RUST_LOG=harmonic=trace RUST_BACKTRACE=full ./harmonic install steam-deck --persistence `pwd`/ci-test-nix --extra-conf "access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}" --no-confirm + run: GITHUB_PATH=$GITHUB_PATH PATH=$PATH:$HOME/bin RUST_LOG=harmonic=trace RUST_BACKTRACE=full ./harmonic install steam-deck --persistence `pwd`/ci-test-nix --extra-conf "access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}" --no-confirm - name: Initial uninstall (without a `nix run` first) - run: sudo GITHUB_PATH=$GITHUB_PATH PATH=$PATH:$HOME/bin RUST_LOG=harmonic=trace RUST_BACKTRACE=full /nix/harmonic uninstall --no-confirm + run: GITHUB_PATH=$GITHUB_PATH PATH=$PATH:$HOME/bin RUST_LOG=harmonic=trace RUST_BACKTRACE=full /nix/harmonic uninstall --no-confirm - name: Repeated install - run: sudo GITHUB_PATH=$GITHUB_PATH PATH=$PATH:$HOME/bin RUST_LOG=harmonic=trace RUST_BACKTRACE=full ./harmonic install steam-deck --persistence `pwd`/ci-test-nix --extra-conf "access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}" --no-confirm + run: GITHUB_PATH=$GITHUB_PATH PATH=$PATH:$HOME/bin RUST_LOG=harmonic=trace RUST_BACKTRACE=full ./harmonic install steam-deck --persistence `pwd`/ci-test-nix --extra-conf "access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}" --no-confirm - name: echo $PATH run: echo $PATH - name: Test `nix` @@ -146,7 +146,7 @@ jobs: if: success() || failure() shell: fish --login {0} - name: Repeated uninstall - run: sudo GITHUB_PATH=$GITHUB_PATH PATH=$PATH:$HOME/bin RUST_LOG=harmonic=trace RUST_BACKTRACE=full /nix/harmonic uninstall --no-confirm + run: GITHUB_PATH=$GITHUB_PATH PATH=$PATH:$HOME/bin RUST_LOG=harmonic=trace RUST_BACKTRACE=full /nix/harmonic uninstall --no-confirm build-x86_64-darwin: name: Build x86_64 Darwin @@ -179,11 +179,11 @@ jobs: - name: Set executable run: chmod +x ./harmonic - name: Initial install - run: sudo GITHUB_PATH=$GITHUB_PATH RUST_LOG=harmonic=trace RUST_BACKTRACE=full ./harmonic install darwin-multi --extra-conf "access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}" --no-confirm + run: GITHUB_PATH=$GITHUB_PATH RUST_LOG=harmonic=trace RUST_BACKTRACE=full ./harmonic install darwin-multi --extra-conf "access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}" --no-confirm - name: Initial uninstall (without a `nix run` first) - run: sudo GITHUB_PATH=$GITHUB_PATH RUST_LOG=harmonic=trace RUST_BACKTRACE=full /nix/harmonic uninstall --no-confirm + run: GITHUB_PATH=$GITHUB_PATH RUST_LOG=harmonic=trace RUST_BACKTRACE=full /nix/harmonic uninstall --no-confirm - name: Repeated install - run: sudo GITHUB_PATH=$GITHUB_PATH RUST_LOG=harmonic=trace RUST_BACKTRACE=full ./harmonic install darwin-multi --extra-conf "access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}" --no-confirm + run: GITHUB_PATH=$GITHUB_PATH RUST_LOG=harmonic=trace RUST_BACKTRACE=full ./harmonic install darwin-multi --extra-conf "access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}" --no-confirm - name: echo $PATH run: echo $PATH - name: Test `nix` @@ -206,5 +206,5 @@ jobs: if: success() || failure() shell: fish --login {0} - name: Repeated uninstall - run: sudo GITHUB_PATH=$GITHUB_PATH RUST_LOG=harmonic=trace RUST_BACKTRACE=full /nix/harmonic uninstall --no-confirm + run: GITHUB_PATH=$GITHUB_PATH RUST_LOG=harmonic=trace RUST_BACKTRACE=full /nix/harmonic uninstall --no-confirm \ No newline at end of file diff --git a/nix-install.sh b/nix-install.sh index b76bca4..79348be 100755 --- a/nix-install.sh +++ b/nix-install.sh @@ -93,11 +93,6 @@ main() { exit 1 fi - local maybe_sudo="" - if [ "$(id -u)" -ne 0 ] && command -v sudo > /dev/null; then - maybe_sudo="sudo" - fi - if [ "$need_tty" = "yes" ] && [ ! -t 0 ]; then # The installer is going to want to ask for confirmation by # reading stdin. This script was piped into `sh` though and @@ -107,9 +102,9 @@ main() { err "Unable to run interactively. Run with -y to accept defaults, --help for additional options" fi - ignore "$maybe_sudo" "$_file" "$@" < /dev/tty + ignore "$_file" "$@" < /dev/tty else - ignore "$maybe_sudo" "$_file" "$@" + ignore "$_file" "$@" fi local _retval=$? diff --git a/src/cli/mod.rs b/src/cli/mod.rs index ce37781..12672d9 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -7,7 +7,9 @@ mod interaction; pub(crate) mod subcommand; use clap::Parser; -use std::process::ExitCode; +use eyre::WrapErr; +use owo_colors::OwoColorize; +use std::{ffi::CString, process::ExitCode}; use tokio::sync::broadcast::{Receiver, Sender}; use self::subcommand::HarmonicSubcommand; @@ -77,5 +79,55 @@ pub(crate) async fn signal_channel() -> eyre::Result<(Sender<()>, Receiver<()>)> } pub fn is_root() -> bool { - nix::unistd::getuid() == nix::unistd::Uid::from_raw(0) + let euid = nix::unistd::Uid::effective(); + tracing::trace!("Running as EUID {euid}"); + euid.is_root() +} + +pub fn ensure_root() -> eyre::Result<()> { + if !is_root() { + eprintln!( + "{}", + "Harmonic needs to run as `root`, attempting to escalate now via `sudo`..." + .yellow() + .dimmed() + ); + let sudo_cstring = CString::new("sudo").wrap_err("Making C string of `sudo`")?; + + let args = std::env::args(); + let mut arg_vec_cstring = vec![]; + arg_vec_cstring.push(sudo_cstring.clone()); + + let mut preserve_env_list = vec![]; + for (key, _value) in std::env::vars() { + let preserve = match key.as_str() { + // Rust logging/backtrace bits we use + "RUST_LOG" | "RUST_BACKTRACE" => true, + // CI + "GITHUB_PATH" => true, + // Our own environments + key if key.starts_with("HARMONIC") => true, + _ => false, + }; + if preserve { + preserve_env_list.push(key); + } + } + + if !preserve_env_list.is_empty() { + arg_vec_cstring.push( + CString::new(format!("--preserve-env={}", preserve_env_list.join(","))) + .wrap_err("Building a `--preserve-env` argument for `sudo`")?, + ); + } + + for arg in args { + arg_vec_cstring.push(CString::new(arg).wrap_err("Making arg into C string")?); + } + + tracing::trace!("Execvp'ing `{sudo_cstring:?}` with args `{arg_vec_cstring:?}`"); + nix::unistd::execvp(&sudo_cstring, &arg_vec_cstring) + .wrap_err("Executing Harmonic as `root` via `sudo`")?; + } + Ok(()) } diff --git a/src/cli/subcommand/install.rs b/src/cli/subcommand/install.rs index 75da6c8..686fdf4 100644 --- a/src/cli/subcommand/install.rs +++ b/src/cli/subcommand/install.rs @@ -5,7 +5,7 @@ use std::{ use crate::{ action::ActionState, - cli::{interaction, is_root, signal_channel, CommandExecute}, + cli::{ensure_root, interaction, signal_channel, CommandExecute}, plan::RECEIPT_LOCATION, planner::Planner, BuiltinPlanner, InstallPlan, @@ -53,11 +53,7 @@ impl CommandExecute for Install { explain, } = self; - if !is_root() { - return Err(eyre!( - "`harmonic install` must be run as `root`, try `sudo harmonic install`" - )); - } + ensure_root()?; let existing_receipt: Option = match Path::new(RECEIPT_LOCATION).exists() { true => { diff --git a/src/cli/subcommand/uninstall.rs b/src/cli/subcommand/uninstall.rs index dc5aef1..28376bd 100644 --- a/src/cli/subcommand/uninstall.rs +++ b/src/cli/subcommand/uninstall.rs @@ -5,7 +5,7 @@ use std::{ }; use crate::{ - cli::{is_root, signal_channel}, + cli::{ensure_root, signal_channel}, plan::RECEIPT_LOCATION, InstallPlan, }; @@ -47,11 +47,7 @@ impl CommandExecute for Uninstall { explain, } = self; - if !is_root() { - return Err(eyre!( - "`harmonic install` must be run as `root`, try `sudo harmonic install`" - )); - } + ensure_root()?; // During install, `harmonic` will store a copy of itself in `/nix/harmonic` // If the user opted to run that particular copy of Harmonic to do this uninstall, @@ -89,6 +85,7 @@ impl CommandExecute for Uninstall { let temp_exe_cstring = CString::new(temp_exe.to_string_lossy().into_owned()) .wrap_err("Making C string of executable path")?; + tracing::trace!("Execv'ing `{temp_exe_cstring:?} {arg_vec_cstring:?}`"); nix::unistd::execv(&temp_exe_cstring, &arg_vec_cstring) .wrap_err("Executing copied Harmonic")?; }