diff --git a/src/actions/mod.rs b/src/actions/mod.rs index 3fe5118..a1d988b 100644 --- a/src/actions/mod.rs +++ b/src/actions/mod.rs @@ -8,7 +8,6 @@ use base::{ FetchNixError, MoveUnpackedNix, MoveUnpackedNixError, SetupDefaultProfile, SetupDefaultProfileError, }; -use futures::Future; use meta::{ ConfigureNix, ConfigureNixError, ConfigureShellProfile, ConfigureShellProfileError, CreateNixTree, CreateNixTreeError, CreateUsersAndGroup, CreateUsersAndGroupError, diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 938ec46..f9057c6 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -1,10 +1,7 @@ pub(crate) mod arg; pub(crate) mod subcommand; -use crate::{cli::arg::ChannelValue, interaction}; -use clap::{ArgAction, Parser}; -use eyre::eyre; -use harmonic::{InstallPlan, InstallSettings}; +use clap::Parser; use std::process::ExitCode; use self::subcommand::HarmonicSubcommand; @@ -22,140 +19,24 @@ pub(crate) trait CommandExecute { pub(crate) struct HarmonicCli { #[clap(flatten)] pub(crate) instrumentation: arg::Instrumentation, - /// Channel(s) to add by default, pass multiple times for multiple channels - #[clap( - long, - value_parser, - action = clap::ArgAction::Append, - env = "HARMONIC_CHANNEL", - default_value = "nixpkgs=https://nixos.org/channels/nixpkgs-unstable" - )] - pub(crate) channel: Vec, - /// Don't modify the user profile to automatically load nix - #[clap( - long, - action(ArgAction::SetTrue), - default_value = "false", - global = true - )] - pub(crate) no_modify_profile: bool, - /// Number of build users to create - #[clap(long, default_value = "32", env = "HARMONIC_NIX_DAEMON_USER_COUNT")] - pub(crate) daemon_user_count: usize, - #[clap( - long, - action(ArgAction::SetTrue), - default_value = "false", - global = true - )] - pub(crate) explain: bool, - #[clap( - long, - action(ArgAction::SetTrue), - default_value = "false", - global = true - )] - pub(crate) force: bool, + #[clap(subcommand)] - subcommand: Option, + subcommand: HarmonicSubcommand, } #[async_trait::async_trait] impl CommandExecute for HarmonicCli { - #[tracing::instrument(skip_all, fields( - channels = %self.channel.iter().map(|ChannelValue(name, url)| format!("{name} {url}")).collect::>().join(", "), - daemon_user_count = %self.daemon_user_count, - no_modify_profile = %self.no_modify_profile, - explain = %self.explain, - ))] + #[tracing::instrument(skip_all)] async fn execute(self) -> eyre::Result { let Self { instrumentation: _, - daemon_user_count, - channel, - no_modify_profile, - explain, subcommand, - force, } = self; match subcommand { - Some(HarmonicSubcommand::Plan(plan)) => plan.execute().await, - Some(HarmonicSubcommand::Execute(execute)) => execute.execute().await, - Some(HarmonicSubcommand::Uninstall(revert)) => revert.execute().await, - None => { - let mut settings = InstallSettings::default(); - - settings.force(force); - settings.daemon_user_count(daemon_user_count); - settings.channels( - channel - .into_iter() - .map(|ChannelValue(name, url)| (name, url)), - ); - settings.modify_profile(!no_modify_profile); - - let mut plan = InstallPlan::new(settings).await?; - - // TODO(@Hoverbear): Make this smarter - if !interaction::confirm(plan.describe_execute(explain)).await? { - interaction::clean_exit_with_message("Okay, didn't do anything! Bye!").await; - } - - if let Err(err) = plan.install().await { - tracing::error!("{:?}", eyre!(err)); - if !interaction::confirm(plan.describe_revert(explain)).await? { - interaction::clean_exit_with_message("Okay, didn't do anything! Bye!") - .await; - } - plan.revert().await? - } - - Ok(ExitCode::SUCCESS) - }, + HarmonicSubcommand::Plan(plan) => plan.execute().await, + HarmonicSubcommand::Install(install) => install.execute().await, + HarmonicSubcommand::Uninstall(revert) => revert.execute().await, } - // let mut harmonic = Harmonic::default(); - - // harmonic.dry_run(dry_run); - // harmonic.explain(explain); - // harmonic.daemon_user_count(daemon_user_count); - // harmonic.channels( - // channel - // .into_iter() - // .map(|ChannelValue(name, url)| (name, url)), - // ); - // harmonic.modify_profile(!no_modify_profile); - - // // TODO(@Hoverbear): Make this smarter - // if !interaction::confirm( - // "\ - // Ready to install nix?\n\ - // \n\ - // This installer will:\n\ - // \n\ - // * Create a `nixbld` group\n\ - // * Create several `nixbld*` users\n\ - // * Create several Nix related directories\n\ - // * Place channel configurations\n\ - // * Fetch a copy of Nix and unpack it\n\ - // * Configure the shell profiles of various shells\n\ - // * Place a Nix configuration\n\ - // * Configure the Nix daemon to work with your init\ - // ", - // ) - // .await? - // { - // interaction::clean_exit_with_message("Okay, didn't do anything! Bye!").await; - // } - - // harmonic.create_group().await?; - // harmonic.create_users().await?; - // harmonic.create_directories().await?; - // harmonic.place_channel_configuration().await?; - // harmonic.fetch_nix().await?; - // harmonic.configure_shell_profile().await?; - // harmonic.setup_default_profile().await?; - // harmonic.place_nix_configuration().await?; - // harmonic.configure_nix_daemon_service().await?; } } diff --git a/src/cli/subcommand/install.rs b/src/cli/subcommand/install.rs index fd9e433..b23ec42 100644 --- a/src/cli/subcommand/install.rs +++ b/src/cli/subcommand/install.rs @@ -2,13 +2,16 @@ use std::{path::PathBuf, process::ExitCode}; use clap::{ArgAction, Parser}; use eyre::{eyre, WrapErr}; -use harmonic::InstallPlan; +use harmonic::{InstallPlan, InstallSettings}; -use crate::{cli::CommandExecute, interaction}; +use crate::{ + cli::{arg::ChannelValue, CommandExecute}, + interaction, +}; -/// Execute an install using an existing plan +/// Execute an install (possibly using an existing plan) #[derive(Debug, Parser)] -pub(crate) struct Execute { +pub(crate) struct Install { #[clap( long, action(ArgAction::SetTrue), @@ -16,31 +19,83 @@ pub(crate) struct Execute { global = true )] no_confirm: bool, + /// Channel(s) to add by default, pass multiple times for multiple channels + #[clap( + long, + value_parser, + action = clap::ArgAction::Append, + env = "HARMONIC_CHANNEL", + default_value = "nixpkgs=https://nixos.org/channels/nixpkgs-unstable" + )] + pub(crate) channel: Vec, + /// Don't modify the user profile to automatically load nix #[clap( long, action(ArgAction::SetTrue), default_value = "false", global = true )] - explain: bool, - #[clap(default_value = "/dev/stdin")] - plan: PathBuf, + pub(crate) no_modify_profile: bool, + /// Number of build users to create + #[clap(long, default_value = "32", env = "HARMONIC_NIX_DAEMON_USER_COUNT")] + pub(crate) daemon_user_count: usize, + #[clap( + long, + action(ArgAction::SetTrue), + default_value = "false", + global = true + )] + pub(crate) explain: bool, + #[clap( + long, + action(ArgAction::SetTrue), + default_value = "false", + global = true + )] + pub(crate) force: bool, + #[clap( + conflicts_with_all = [ "no_modify_profile", "daemon_user_count", "channel" ], + env = "HARMONIC_PLAN", + )] + plan: Option, } #[async_trait::async_trait] -impl CommandExecute for Execute { +impl CommandExecute for Install { #[tracing::instrument(skip_all, fields())] async fn execute(self) -> eyre::Result { let Self { no_confirm, plan, explain, + channel, + no_modify_profile, + daemon_user_count, + force, } = self; - let install_plan_string = tokio::fs::read_to_string(plan) - .await - .wrap_err("Reading plan")?; - let mut plan: InstallPlan = serde_json::from_str(&install_plan_string)?; + let mut plan = match &plan { + Some(plan_path) => { + let install_plan_string = tokio::fs::read_to_string(&plan_path) + .await + .wrap_err("Reading plan")?; + serde_json::from_str(&install_plan_string)? + }, + None => { + let mut settings = InstallSettings::default()?; + + settings.force(force); + settings.daemon_user_count(daemon_user_count); + settings.channels( + channel + .into_iter() + .map(|ChannelValue(name, url)| (name, url)), + ); + settings.modify_profile(!no_modify_profile); + + InstallPlan::new(settings).await? + }, + }; if !no_confirm { if !interaction::confirm(plan.describe_execute(explain)).await? { diff --git a/src/cli/subcommand/mod.rs b/src/cli/subcommand/mod.rs index 231ac29..881e499 100644 --- a/src/cli/subcommand/mod.rs +++ b/src/cli/subcommand/mod.rs @@ -1,13 +1,13 @@ mod plan; use plan::Plan; mod install; -use install::Execute; +use install::Install; mod uninstall; use uninstall::Uninstall; #[derive(Debug, clap::Subcommand)] pub(crate) enum HarmonicSubcommand { Plan(Plan), - Execute(Execute), + Install(Install), Uninstall(Uninstall), } diff --git a/src/cli/subcommand/plan.rs b/src/cli/subcommand/plan.rs index 7eb9a75..9ab035f 100644 --- a/src/cli/subcommand/plan.rs +++ b/src/cli/subcommand/plan.rs @@ -57,7 +57,7 @@ impl CommandExecute for Plan { plan, } = self; - let mut settings = InstallSettings::default(); + let mut settings = InstallSettings::default()?; settings.force(force); settings.daemon_user_count(daemon_user_count); diff --git a/src/settings.rs b/src/settings.rs index d1f05b0..a601ead 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -14,9 +14,32 @@ pub struct InstallSettings { pub(crate) force: bool, } -impl Default for InstallSettings { - fn default() -> Self { - Self { +impl InstallSettings { + pub fn default() -> Result { + let url; + + use target_lexicon::{Architecture, OperatingSystem}; + match (Architecture::host(), OperatingSystem::host()) { + (Architecture::X86_64, OperatingSystem::Linux) => { + url = "https://releases.nixos.org/nix/nix-2.11.0/nix-2.11.0-x86_64-linux.tar.xz"; + }, + (Architecture::Aarch64(_), OperatingSystem::Linux) => { + url = "https://releases.nixos.org/nix/nix-2.11.0/nix-2.11.0-aarch64-linux.tar.xz"; + }, + (Architecture::X86_64, OperatingSystem::MacOSX { .. }) => { + url = "https://releases.nixos.org/nix/nix-2.11.0/nix-2.11.0-x86_64-darwin.tar.xz"; + }, + (Architecture::Aarch64(_), OperatingSystem::MacOSX { .. }) => { + url = "https://releases.nixos.org/nix/nix-2.11.0/nix-2.11.0-aarch64-darwin.tar.xz"; + }, + _ => { + return Err(InstallSettingsError::UnsupportedArchitecture( + target_lexicon::HOST, + )) + }, + }; + + Ok(Self { daemon_user_count: Default::default(), channels: Default::default(), modify_profile: Default::default(), @@ -24,13 +47,12 @@ impl Default for InstallSettings { nix_build_group_id: 3000, nix_build_user_prefix: String::from("nixbld"), nix_build_user_id_base: 3001, - nix_package_url: - "https://releases.nixos.org/nix/nix-2.11.0/nix-2.11.0-x86_64-linux.tar.xz" - .parse() - .expect("Could not parse default Nix archive url, please report this issue"), + nix_package_url: url + .parse() + .expect("Could not parse default Nix archive url, please report this issue"), extra_conf: Default::default(), force: false, - } + }) } } @@ -83,3 +105,9 @@ impl InstallSettings { self } } + +#[derive(thiserror::Error, Debug)] +pub enum InstallSettingsError { + #[error("Harmonic does not support the `{0}` architecture right now")] + UnsupportedArchitecture(target_lexicon::Triple), +}