diff --git a/Cargo.lock b/Cargo.lock index 2a3a9e9..530b3aa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -390,6 +390,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "erased-serde" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54558e0ba96fbe24280072642eceb9d7d442e32c7ec0ea9e7ecd7b4ea2cf4e11" +dependencies = [ + "serde", +] + [[package]] name = "event-listener" version = "2.5.3" @@ -558,6 +567,17 @@ dependencies = [ "slab", ] +[[package]] +name = "ghost" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb19fe8de3ea0920d282f7b77dd4227aea6b8b999b42cdf0ca41b2472b14443a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "gimli" version = "0.26.2" @@ -629,6 +649,7 @@ dependencies = [ "tracing", "tracing-error", "tracing-subscriber", + "typetag", "url", "valuable", "walkdir", @@ -762,6 +783,16 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "inventory" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e21e0a36a4dc4b469422ee17f715e8313f4a637675656d6a13637954278c6f55" +dependencies = [ + "ctor", + "ghost", +] + [[package]] name = "iovec" version = "0.1.4" @@ -1652,6 +1683,30 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" +[[package]] +name = "typetag" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23c8909f61359cac318f10dc9a3f9a7ee2155b6511f2a0c973460222ae5a00bb" +dependencies = [ + "erased-serde", + "inventory", + "once_cell", + "serde", + "typetag-impl", +] + +[[package]] +name = "typetag-impl" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd4d8ff674190866d3437d2f725355d49e99e3b1a513bf7ee43dcfe7effc36f4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "unicode-bidi" version = "0.3.8" diff --git a/Cargo.toml b/Cargo.toml index 2109769..b0c1ce6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,3 +34,4 @@ walkdir = "2.3.2" serde = { version = "1.0.144", features = ["derive"] } url = { version = "2.3.1", features = ["serde"] } serde_json = "1.0.85" +typetag = "0.2.3" diff --git a/src/actions/mod.rs b/src/actions/mod.rs index a1d988b..3fe5118 100644 --- a/src/actions/mod.rs +++ b/src/actions/mod.rs @@ -8,6 +8,7 @@ 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/subcommand/install.rs b/src/cli/subcommand/install.rs index da4b205..fd9e433 100644 --- a/src/cli/subcommand/install.rs +++ b/src/cli/subcommand/install.rs @@ -22,7 +22,7 @@ pub(crate) struct Execute { default_value = "false", global = true )] - pub(crate) explain: bool, + explain: bool, #[clap(default_value = "/dev/stdin")] plan: PathBuf, } diff --git a/src/cli/subcommand/plan.rs b/src/cli/subcommand/plan.rs index 291afe7..7eb9a75 100644 --- a/src/cli/subcommand/plan.rs +++ b/src/cli/subcommand/plan.rs @@ -18,7 +18,7 @@ pub(crate) struct Plan { env = "HARMONIC_CHANNEL", default_value = "nixpkgs=https://nixos.org/channels/nixpkgs-unstable" )] - pub(crate) channel: Vec, + channel: Vec, /// Don't modify the user profile to automatically load nix #[clap( long, @@ -26,17 +26,17 @@ pub(crate) struct Plan { default_value = "false", global = true )] - pub(crate) no_modify_profile: bool, + 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, + daemon_user_count: usize, #[clap( long, action(ArgAction::SetTrue), default_value = "false", global = true )] - pub(crate) force: bool, + force: bool, #[clap(default_value = "/dev/stdout")] plan: PathBuf, } diff --git a/src/cli/subcommand/uninstall.rs b/src/cli/subcommand/uninstall.rs index 56dde6c..7ffab3f 100644 --- a/src/cli/subcommand/uninstall.rs +++ b/src/cli/subcommand/uninstall.rs @@ -22,7 +22,7 @@ pub(crate) struct Uninstall { default_value = "false", global = true )] - pub(crate) explain: bool, + explain: bool, #[clap(default_value = "/nix/receipt.json")] receipt: PathBuf, } diff --git a/src/plan.rs b/src/plan.rs index 9712f2a..ef93128 100644 --- a/src/plan.rs +++ b/src/plan.rs @@ -3,7 +3,7 @@ use std::path::PathBuf; use crate::{ actions::{ meta::{ConfigureNix, ProvisionNix, StartNixDaemon}, - ActionDescription, ActionError, Actionable, + Action, ActionDescription, ActionError, Actionable, }, settings::InstallSettings, HarmonicError, @@ -13,52 +13,33 @@ use crate::{ pub struct InstallPlan { settings: InstallSettings, - /** Bootstrap the install - - * There are roughly three phases: - * "Create nix tree"": - * download_nix --------------------------------------> move_downloaded_nix - * create_group -> create_users -> create_directories -> move_downloaded_nix - * place_channel_configuration - * place_nix_configuration - * --- - * "Configure Nix": - * setup_default_profile - * configure_nix_daemon_service - * configure_shell_profile - * --- - * "Start Nix" - * start_nix_daemon_service - */ - provision_nix: ProvisionNix, - configure_nix: ConfigureNix, - start_nix_daemon: StartNixDaemon, + actions: Vec, } impl InstallPlan { pub async fn new(settings: InstallSettings) -> Result { Ok(Self { settings: settings.clone(), - provision_nix: ProvisionNix::plan(settings.clone()) - .await - .map_err(|e| ActionError::from(e))?, - configure_nix: ConfigureNix::plan(settings) - .await - .map_err(|e| ActionError::from(e))?, - start_nix_daemon: StartNixDaemon::plan() - .await - .map_err(|e| ActionError::from(e))?, + actions: vec![ + ProvisionNix::plan(settings.clone()) + .await + .map(Action::from) + .map_err(ActionError::from)?, + ConfigureNix::plan(settings) + .await + .map(Action::from) + .map_err(ActionError::from)?, + StartNixDaemon::plan() + .await + .map(Action::from) + .map_err(ActionError::from)?, + ], }) } #[tracing::instrument(skip_all)] pub fn describe_execute(&self, explain: bool) -> String { - let Self { - settings, - provision_nix, - configure_nix, - start_nix_daemon, - } = self; + let Self { settings, actions } = self; format!( "\ This Nix install is for:\n\ @@ -77,66 +58,53 @@ impl InstallPlan { .map(|(name, url)| format!("{name}={url}")) .collect::>() .join(","), - actions = { - let mut buf = provision_nix.describe_execute(); - buf.append(&mut configure_nix.describe_execute()); - buf.append(&mut start_nix_daemon.describe_execute()); - buf.iter() - .map(|desc| { - let ActionDescription { - description, - explanation, - } = desc; + actions = actions + .iter() + .map(|v| v.describe_execute()) + .flatten() + .map(|desc| { + let ActionDescription { + description, + explanation, + } = desc; - let mut buf = String::default(); - buf.push_str(&format!("* {description}\n")); - if explain { - for line in explanation { - buf.push_str(&format!(" {line}\n")); - } + let mut buf = String::default(); + buf.push_str(&format!("* {description}\n")); + if explain { + for line in explanation { + buf.push_str(&format!(" {line}\n")); } - buf - }) - .collect::>() - .join("\n") - }, + } + buf + }) + .collect::>() + .join("\n"), ) } #[tracing::instrument(skip_all)] pub async fn install(&mut self) -> Result<(), HarmonicError> { + let Self { + actions, + settings: _, + } = self; + // This is **deliberately sequential**. // Actions which are parallelizable are represented by "group actions" like CreateUsers // The plan itself represents the concept of the sequence of stages. - - if let Err(err) = self.provision_nix.execute().await { - write_receipt(self.clone()).await?; - return Err(ActionError::from(err).into()); + for action in actions { + if let Err(err) = action.execute().await { + write_receipt(self.clone()).await?; + return Err(ActionError::from(err).into()); + } } - if let Err(err) = self.configure_nix.execute().await { - write_receipt(self.clone()).await?; - return Err(ActionError::from(err).into()); - } - - if let Err(err) = self.start_nix_daemon.execute().await { - write_receipt(self.clone()).await?; - return Err(ActionError::from(err).into()); - } - - write_receipt(self.clone()).await?; - - Ok(()) + write_receipt(self.clone()).await } #[tracing::instrument(skip_all)] pub fn describe_revert(&self, explain: bool) -> String { - let Self { - settings, - provision_nix, - configure_nix, - start_nix_daemon, - } = self; + let Self { settings, actions } = self; format!( "\ This Nix uninstall is for:\n\ @@ -155,50 +123,45 @@ impl InstallPlan { .map(|(name, url)| format!("{name}={url}")) .collect::>() .join(","), - actions = { - let mut buf = provision_nix.describe_revert(); - buf.append(&mut configure_nix.describe_revert()); - buf.append(&mut start_nix_daemon.describe_revert()); - buf.iter() - .map(|desc| { - let ActionDescription { - description, - explanation, - } = desc; + actions = actions + .iter() + .map(|v| v.describe_revert()) + .flatten() + .map(|desc| { + let ActionDescription { + description, + explanation, + } = desc; - let mut buf = String::default(); - buf.push_str(&format!("* {description}\n")); - if explain { - for line in explanation { - buf.push_str(&format!(" {line}\n")); - } + let mut buf = String::default(); + buf.push_str(&format!("* {description}\n")); + if explain { + for line in explanation { + buf.push_str(&format!(" {line}\n")); } - buf - }) - .collect::>() - .join("\n") - }, + } + buf + }) + .collect::>() + .join("\n"), ) } #[tracing::instrument(skip_all)] pub async fn revert(&mut self) -> Result<(), HarmonicError> { + let Self { + actions, + settings: _, + } = self; + // This is **deliberately sequential**. // Actions which are parallelizable are represented by "group actions" like CreateUsers // The plan itself represents the concept of the sequence of stages. - if let Err(err) = self.start_nix_daemon.revert().await { - write_receipt(self.clone()).await?; - return Err(ActionError::from(err).into()); - } - - if let Err(err) = self.configure_nix.revert().await { - write_receipt(self.clone()).await?; - return Err(ActionError::from(err).into()); - } - - if let Err(err) = self.provision_nix.revert().await { - write_receipt(self.clone()).await?; - return Err(ActionError::from(err).into()); + for action in actions { + if let Err(err) = action.revert().await { + write_receipt(self.clone()).await?; + return Err(ActionError::from(err).into()); + } } Ok(())