From fe83b35199fa16848cdd2f813730cac05bb2b005 Mon Sep 17 00:00:00 2001 From: Ana Hobden Date: Thu, 8 Dec 2022 08:04:49 -0800 Subject: [PATCH] Fixup Mac `curl $URL | sudo sh -s` (#99) * Fixup Mac curl sh * We not longer require sudo for the script --- Cargo.lock | 39 +++++++++++++++++++ Cargo.toml | 1 + README.md | 2 +- nix-install.sh | 9 ++++- src/cli/interaction.rs | 68 ++++++++++++++++----------------- src/cli/subcommand/install.rs | 2 + src/cli/subcommand/uninstall.rs | 7 +++- 7 files changed, 87 insertions(+), 41 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8786260..a4b5f9e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -492,6 +492,16 @@ dependencies = [ "dirs-sys", ] +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + [[package]] name = "dirs-sys" version = "0.3.7" @@ -503,6 +513,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "dyn-clone" version = "1.0.9" @@ -805,6 +826,7 @@ dependencies = [ "sxd-xpath", "tar", "target-lexicon", + "term", "thiserror", "tokio", "tokio-util", @@ -1595,6 +1617,12 @@ dependencies = [ "base64", ] +[[package]] +name = "rustversion" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" + [[package]] name = "ryu" version = "1.0.11" @@ -1859,6 +1887,17 @@ version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9410d0f6853b1d94f0e519fb95df60f29d2c1eff2d921ffdf01a4c8a3b54f12d" +[[package]] +name = "term" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +dependencies = [ + "dirs-next", + "rustversion", + "winapi", +] + [[package]] name = "termcolor" version = "1.1.3" diff --git a/Cargo.toml b/Cargo.toml index 11145f6..bf994e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,6 +57,7 @@ typetag = "0.2.3" dyn-clone = "1.0.9" rand = "0.8.5" semver = { version = "1.0.14", features = ["serde"] } +term = "0.7.0" [dev-dependencies] eyre = "0.6.8" diff --git a/README.md b/README.md index cebe27f..430dc40 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Harmonic is an opinionated, experimental Nix installer. > Try it on a machine/VM you don't care about! > > ```bash -> curl -L https://install.determinate.systems/nix | sudo sh -s -- install +> curl -L https://install.determinate.systems/nix | sh -s -- install > ``` ## Status diff --git a/nix-install.sh b/nix-install.sh index 79348be..68de276 100755 --- a/nix-install.sh +++ b/nix-install.sh @@ -93,6 +93,11 @@ main() { exit 1 fi + local maybe_sudo="" + if [ "$EUID" -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 @@ -102,9 +107,9 @@ main() { err "Unable to run interactively. Run with -y to accept defaults, --help for additional options" fi - ignore "$_file" "$@" < /dev/tty + ignore "$maybe_sudo" "$_file" "$@" < /dev/tty else - ignore "$_file" "$@" + ignore "$maybe_sudo" "$_file" "$@" fi local _retval=$? diff --git a/src/cli/interaction.rs b/src/cli/interaction.rs index 0a6690f..625a13e 100644 --- a/src/cli/interaction.rs +++ b/src/cli/interaction.rs @@ -1,11 +1,18 @@ +use std::io::{stdin, stdout, BufRead, Write}; + use crossterm::event::{EventStream, KeyCode}; use eyre::{eyre, WrapErr}; use futures::{FutureExt, StreamExt}; use owo_colors::OwoColorize; -use tokio::io::AsyncWriteExt; -pub(crate) async fn confirm(question: impl AsRef) -> eyre::Result { - let mut stdout = tokio::io::stdout(); +// Do not try to get clever! +// +// Mac is extremely janky if you `curl $URL | sudo sh` and the TTY may not be set up right. +// The below method was adopted from Rustup at https://github.com/rust-lang/rustup/blob/3331f34c01474bf216c99a1b1706725708833de1/src/cli/term2.rs#L37 +pub(crate) async fn confirm(question: impl AsRef, default: bool) -> eyre::Result { + let stdout = stdout(); + let mut term = + term::terminfo::TerminfoTerminal::new(stdout).ok_or(eyre!("Couldn't get terminal"))?; let with_confirm = format!( "\ {question}\n\ @@ -18,42 +25,31 @@ pub(crate) async fn confirm(question: impl AsRef) -> eyre::Result { yes = "y".green(), ); - stdout.write_all(with_confirm.as_bytes()).await?; - stdout.flush().await?; + term.write_all(with_confirm.as_bytes())?; + term.flush()?; - // crossterm::terminal::enable_raw_mode()?; - let mut reader = EventStream::new(); + let input = read_line()?; - let retval = loop { - let event = reader.next().fuse().await; - match event { - Some(Ok(event)) => { - if let crossterm::event::Event::Key(key) = event { - match key.code { - KeyCode::Char('y') | KeyCode::Char('Y') => { - stdout - .write_all("Confirmed!\n".green().to_string().as_bytes()) - .await?; - stdout.flush().await?; - break Ok(true); - }, - KeyCode::Char('N') | KeyCode::Char('n') => { - stdout - .write_all("Cancelled!\n".red().to_string().as_bytes()) - .await?; - stdout.flush().await?; - break Ok(false); - }, - KeyCode::Enter | _ => continue, - } - } - }, - Some(Err(err)) => break Err(err).wrap_err("Getting response"), - None => break Err(eyre!("Bailed, no confirmation event")), - } + let r = match &*input.to_lowercase() { + "y" | "yes" => true, + "n" | "no" => false, + "" => default, + _ => false, }; - // crossterm::terminal::disable_raw_mode()?; - retval + + Ok(r) +} + +pub(crate) fn read_line() -> eyre::Result { + let stdin = stdin(); + let stdin = stdin.lock(); + let mut lines = stdin.lines(); + let lines = lines.next().transpose()?; + match lines { + None => Err(eyre!("no lines found from stdin")), + Some(v) => Ok(v), + } + .context("unable to read from stdin for confirmation") } pub(crate) async fn clean_exit_with_message(message: impl AsRef) -> ! { diff --git a/src/cli/subcommand/install.rs b/src/cli/subcommand/install.rs index da5ef93..f8bb4fc 100644 --- a/src/cli/subcommand/install.rs +++ b/src/cli/subcommand/install.rs @@ -111,6 +111,7 @@ impl CommandExecute for Install { install_plan .describe_install(explain) .map_err(|e| eyre!(e))?, + true, ) .await? { @@ -128,6 +129,7 @@ impl CommandExecute for Install { install_plan .describe_uninstall(explain) .map_err(|e| eyre!(e))?, + true, ) .await? { diff --git a/src/cli/subcommand/uninstall.rs b/src/cli/subcommand/uninstall.rs index 548e355..0850ed8 100644 --- a/src/cli/subcommand/uninstall.rs +++ b/src/cli/subcommand/uninstall.rs @@ -53,8 +53,11 @@ impl CommandExecute for Uninstall { let mut plan: InstallPlan = serde_json::from_str(&install_receipt_string)?; if !no_confirm { - if !interaction::confirm(plan.describe_uninstall(explain).map_err(|e| eyre!(e))?) - .await? + if !interaction::confirm( + plan.describe_uninstall(explain).map_err(|e| eyre!(e))?, + true, + ) + .await? { interaction::clean_exit_with_message("Okay, didn't do anything! Bye!").await; }