diff --git a/src/actions/configure_nix_daemon_service.rs b/src/actions/base/configure_nix_daemon_service.rs similarity index 87% rename from src/actions/configure_nix_daemon_service.rs rename to src/actions/base/configure_nix_daemon_service.rs index 8f9b53b..e74f70a 100644 --- a/src/actions/configure_nix_daemon_service.rs +++ b/src/actions/base/configure_nix_daemon_service.rs @@ -1,6 +1,6 @@ use crate::HarmonicError; -use super::{ActionDescription, ActionReceipt, Actionable, Revertable}; +use crate::actions::{ActionDescription, ActionReceipt, Actionable, Revertable}; #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] pub struct ConfigureNixDaemonService {} @@ -13,6 +13,7 @@ impl ConfigureNixDaemonService { #[async_trait::async_trait] impl<'a> Actionable<'a> for ConfigureNixDaemonService { + type Receipt = ConfigureNixDaemonServiceReceipt; fn description(&self) -> Vec { vec![ ActionDescription::new( @@ -24,7 +25,7 @@ impl<'a> Actionable<'a> for ConfigureNixDaemonService { ] } - async fn execute(self) -> Result { + async fn execute(self) -> Result { todo!() } } diff --git a/src/actions/configure_shell_profile.rs b/src/actions/base/configure_shell_profile.rs similarity index 87% rename from src/actions/configure_shell_profile.rs rename to src/actions/base/configure_shell_profile.rs index 81ea188..9dd3823 100644 --- a/src/actions/configure_shell_profile.rs +++ b/src/actions/base/configure_shell_profile.rs @@ -1,6 +1,6 @@ use crate::HarmonicError; -use super::{ActionDescription, ActionReceipt, Actionable, Revertable}; +use crate::actions::{ActionDescription, ActionReceipt, Actionable, Revertable}; #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] pub struct ConfigureShellProfile {} @@ -13,6 +13,7 @@ impl ConfigureShellProfile { #[async_trait::async_trait] impl<'a> Actionable<'a> for ConfigureShellProfile { + type Receipt = ConfigureShellProfileReceipt; fn description(&self) -> Vec { vec![ ActionDescription::new( @@ -24,7 +25,7 @@ impl<'a> Actionable<'a> for ConfigureShellProfile { ] } - async fn execute(self) -> Result { + async fn execute(self) -> Result { todo!() } } diff --git a/src/actions/create_directory.rs b/src/actions/base/create_directory.rs similarity index 88% rename from src/actions/create_directory.rs rename to src/actions/base/create_directory.rs index ad2fcb7..c507708 100644 --- a/src/actions/create_directory.rs +++ b/src/actions/base/create_directory.rs @@ -5,7 +5,7 @@ use std::{ use crate::HarmonicError; -use super::{ActionDescription, ActionReceipt, Actionable, Revertable}; +use crate::actions::{ActionDescription, ActionReceipt, Actionable, Revertable}; #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] pub struct CreateDirectory { @@ -29,6 +29,7 @@ impl CreateDirectory { #[async_trait::async_trait] impl<'a> Actionable<'a> for CreateDirectory { + type Receipt = CreateDirectoryReceipt; fn description(&self) -> Vec { vec![ActionDescription::new( format!("Create the directory `/nix`"), @@ -38,7 +39,7 @@ impl<'a> Actionable<'a> for CreateDirectory { )] } - async fn execute(self) -> Result { + async fn execute(self) -> Result { let Self { path, user, @@ -46,12 +47,12 @@ impl<'a> Actionable<'a> for CreateDirectory { mode, } = self; todo!(); - Ok(ActionReceipt::CreateDirectory(CreateDirectoryReceipt { + Ok(CreateDirectoryReceipt { path, user, group, mode, - })) + }) } } diff --git a/src/actions/create_group.rs b/src/actions/base/create_group.rs similarity index 83% rename from src/actions/create_group.rs rename to src/actions/base/create_group.rs index b28f8ed..3b6a550 100644 --- a/src/actions/create_group.rs +++ b/src/actions/base/create_group.rs @@ -1,6 +1,6 @@ use crate::HarmonicError; -use super::{ActionDescription, ActionReceipt, Actionable, Revertable}; +use crate::actions::{ActionDescription, ActionReceipt, Actionable, Revertable}; #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] pub struct CreateGroup { @@ -16,6 +16,7 @@ impl CreateGroup { #[async_trait::async_trait] impl<'a> Actionable<'a> for CreateGroup { + type Receipt = CreateGroupReceipt; fn description(&self) -> Vec { let name = &self.name; let uid = &self.uid; @@ -27,9 +28,9 @@ impl<'a> Actionable<'a> for CreateGroup { )] } - async fn execute(self) -> Result { + async fn execute(self) -> Result { let Self { name, uid } = self; - Ok(ActionReceipt::CreateGroup(CreateGroupReceipt { name, uid })) + Ok(CreateGroupReceipt { name, uid }) } } diff --git a/src/actions/create_user.rs b/src/actions/base/create_user.rs similarity index 83% rename from src/actions/create_user.rs rename to src/actions/base/create_user.rs index 877b3a3..3fb90f1 100644 --- a/src/actions/create_user.rs +++ b/src/actions/base/create_user.rs @@ -1,6 +1,6 @@ use crate::HarmonicError; -use super::{ActionDescription, ActionReceipt, Actionable, Revertable}; +use crate::actions::{ActionDescription, ActionReceipt, Actionable, Revertable}; #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] pub struct CreateUser { @@ -16,6 +16,7 @@ impl CreateUser { #[async_trait::async_trait] impl<'a> Actionable<'a> for CreateUser { + type Receipt = CreateUserReceipt; fn description(&self) -> Vec { let name = &self.name; let uid = &self.uid; @@ -27,9 +28,9 @@ impl<'a> Actionable<'a> for CreateUser { )] } - async fn execute(self) -> Result { + async fn execute(self) -> Result { let Self { name, uid } = self; - Ok(ActionReceipt::CreateUser(CreateUserReceipt { name, uid })) + Ok(CreateUserReceipt { name, uid }) } } diff --git a/src/actions/base/fetch_nix.rs b/src/actions/base/fetch_nix.rs new file mode 100644 index 0000000..ce3664f --- /dev/null +++ b/src/actions/base/fetch_nix.rs @@ -0,0 +1,59 @@ +use std::path::{Path, PathBuf}; + +use reqwest::Url; + +use crate::HarmonicError; + +use crate::actions::{ActionDescription, ActionReceipt, Actionable, Revertable}; + +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +pub struct FetchNix { + url: Url, + destination: PathBuf, +} + +impl FetchNix { + pub fn plan(url: Url, destination: PathBuf) -> Self { + Self { url, destination } + } +} + +#[async_trait::async_trait] +impl<'a> Actionable<'a> for FetchNix { + type Receipt = FetchNixReceipt; + fn description(&self) -> Vec { + let Self { + url, destination + } = &self; + vec![ActionDescription::new( + format!("Fetch Nix from `{url}`"), + vec![format!( + "Fetch a Nix archive and unpack it to `{}`", destination.display() + )], + )] + } + + async fn execute(self) -> Result { + let Self { url, destination } = self; + Ok(FetchNixReceipt { url, destination }) + } +} + +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +pub struct FetchNixReceipt { + url: Url, + destination: PathBuf, +} + +#[async_trait::async_trait] +impl<'a> Revertable<'a> for FetchNixReceipt { + fn description(&self) -> Vec { + todo!() + } + + async fn revert(self) -> Result<(), HarmonicError> { + todo!(); + + Ok(()) + } +} diff --git a/src/actions/base/mod.rs b/src/actions/base/mod.rs new file mode 100644 index 0000000..f5566a7 --- /dev/null +++ b/src/actions/base/mod.rs @@ -0,0 +1,29 @@ +/*! Actions which do not only call other base plugins. */ + +mod configure_nix_daemon_service; +mod configure_shell_profile; +mod create_directory; +mod create_group; +mod create_user; +mod fetch_nix; +mod move_unpacked_nix; +mod place_channel_configuration; +mod place_nix_configuration; +mod setup_default_profile; +mod start_systemd_service; + +pub use configure_nix_daemon_service::{ + ConfigureNixDaemonService, ConfigureNixDaemonServiceReceipt, +}; +pub use configure_shell_profile::{ConfigureShellProfile, ConfigureShellProfileReceipt}; +pub use create_directory::{CreateDirectory, CreateDirectoryReceipt}; +pub use create_group::{CreateGroup, CreateGroupReceipt}; +pub use create_user::{CreateUser, CreateUserReceipt}; +pub use fetch_nix::{FetchNix, FetchNixReceipt}; +pub use move_unpacked_nix::{MoveUnpackedNix, MoveUnpackedNixReceipt}; +pub use place_channel_configuration::{ + PlaceChannelConfiguration, PlaceChannelConfigurationReceipt, +}; +pub use place_nix_configuration::{PlaceNixConfiguration, PlaceNixConfigurationReceipt}; +pub use setup_default_profile::{SetupDefaultProfile, SetupDefaultProfileReceipt}; +pub use start_systemd_service::{StartSystemdService, StartSystemdServiceReceipt}; \ No newline at end of file diff --git a/src/actions/base/move_unpacked_nix.rs b/src/actions/base/move_unpacked_nix.rs new file mode 100644 index 0000000..7008c8c --- /dev/null +++ b/src/actions/base/move_unpacked_nix.rs @@ -0,0 +1,53 @@ +use std::path::PathBuf; + +use crate::HarmonicError; + +use crate::actions::{ActionDescription, ActionReceipt, Actionable, Revertable}; + +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +pub struct MoveUnpackedNix { + source: PathBuf, +} + +impl MoveUnpackedNix { + pub fn plan(source: PathBuf) -> Self { + Self { source, } + } +} + +#[async_trait::async_trait] +impl<'a> Actionable<'a> for MoveUnpackedNix { + type Receipt = MoveUnpackedNixReceipt; + fn description(&self) -> Vec { + let Self { source } = &self; + vec![ActionDescription::new( + format!("Move the downloaded Nix into `/nix`"), + vec![format!( + "Nix is downloaded to `{}` and should be in `nix`", source.display(), + )], + )] + } + + async fn execute(self) -> Result { + let Self { source } = self; + Ok(MoveUnpackedNixReceipt { }) + } +} + +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +pub struct MoveUnpackedNixReceipt { + +} + +#[async_trait::async_trait] +impl<'a> Revertable<'a> for MoveUnpackedNixReceipt { + fn description(&self) -> Vec { + todo!() + } + + async fn revert(self) -> Result<(), HarmonicError> { + todo!(); + + Ok(()) + } +} diff --git a/src/actions/place_channel_configuration.rs b/src/actions/base/place_channel_configuration.rs similarity index 87% rename from src/actions/place_channel_configuration.rs rename to src/actions/base/place_channel_configuration.rs index 07d499e..26e10a7 100644 --- a/src/actions/place_channel_configuration.rs +++ b/src/actions/base/place_channel_configuration.rs @@ -1,6 +1,6 @@ use crate::HarmonicError; -use super::{ActionDescription, ActionReceipt, Actionable, Revertable}; +use crate::actions::{ActionDescription, ActionReceipt, Actionable, Revertable}; #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] pub struct PlaceChannelConfiguration {} @@ -13,6 +13,7 @@ impl PlaceChannelConfiguration { #[async_trait::async_trait] impl<'a> Actionable<'a> for PlaceChannelConfiguration { + type Receipt = PlaceChannelConfigurationReceipt; fn description(&self) -> Vec { vec![ ActionDescription::new( @@ -24,7 +25,7 @@ impl<'a> Actionable<'a> for PlaceChannelConfiguration { ] } - async fn execute(self) -> Result { + async fn execute(self) -> Result { todo!() } } diff --git a/src/actions/place_nix_configuration.rs b/src/actions/base/place_nix_configuration.rs similarity index 87% rename from src/actions/place_nix_configuration.rs rename to src/actions/base/place_nix_configuration.rs index 758bbe0..d7ec82c 100644 --- a/src/actions/place_nix_configuration.rs +++ b/src/actions/base/place_nix_configuration.rs @@ -1,6 +1,6 @@ use crate::HarmonicError; -use super::{ActionDescription, ActionReceipt, Actionable, Revertable}; +use crate::actions::{ActionDescription, ActionReceipt, Actionable, Revertable}; #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] pub struct PlaceNixConfiguration {} @@ -13,6 +13,7 @@ impl PlaceNixConfiguration { #[async_trait::async_trait] impl<'a> Actionable<'a> for PlaceNixConfiguration { + type Receipt = PlaceNixConfigurationReceipt; fn description(&self) -> Vec { vec![ ActionDescription::new( @@ -24,7 +25,7 @@ impl<'a> Actionable<'a> for PlaceNixConfiguration { ] } - async fn execute(self) -> Result { + async fn execute(self) -> Result { todo!() } } diff --git a/src/actions/setup_default_profile.rs b/src/actions/base/setup_default_profile.rs similarity index 87% rename from src/actions/setup_default_profile.rs rename to src/actions/base/setup_default_profile.rs index 65df72e..62b4db5 100644 --- a/src/actions/setup_default_profile.rs +++ b/src/actions/base/setup_default_profile.rs @@ -1,6 +1,6 @@ use crate::HarmonicError; -use super::{ActionDescription, ActionReceipt, Actionable, Revertable}; +use crate::actions::{ActionDescription, ActionReceipt, Actionable, Revertable}; #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] pub struct SetupDefaultProfile {} @@ -13,6 +13,7 @@ impl SetupDefaultProfile { #[async_trait::async_trait] impl<'a> Actionable<'a> for SetupDefaultProfile { + type Receipt = SetupDefaultProfileReceipt; fn description(&self) -> Vec { vec![ ActionDescription::new( @@ -24,7 +25,7 @@ impl<'a> Actionable<'a> for SetupDefaultProfile { ] } - async fn execute(self) -> Result { + async fn execute(self) -> Result { todo!() } } diff --git a/src/actions/start_systemd_service.rs b/src/actions/base/start_systemd_service.rs similarity index 87% rename from src/actions/start_systemd_service.rs rename to src/actions/base/start_systemd_service.rs index 525737b..924f9ea 100644 --- a/src/actions/start_systemd_service.rs +++ b/src/actions/base/start_systemd_service.rs @@ -1,6 +1,6 @@ use crate::HarmonicError; -use super::{ActionDescription, ActionReceipt, Actionable, Revertable}; +use crate::actions::{ActionDescription, ActionReceipt, Actionable, Revertable}; #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] pub struct StartSystemdService {} @@ -13,6 +13,7 @@ impl StartSystemdService { #[async_trait::async_trait] impl<'a> Actionable<'a> for StartSystemdService { + type Receipt = StartSystemdServiceReceipt; fn description(&self) -> Vec { vec![ ActionDescription::new( @@ -24,7 +25,7 @@ impl<'a> Actionable<'a> for StartSystemdService { ] } - async fn execute(self) -> Result { + async fn execute(self) -> Result { todo!() } } diff --git a/src/actions/create_users.rs b/src/actions/create_users.rs deleted file mode 100644 index bf40151..0000000 --- a/src/actions/create_users.rs +++ /dev/null @@ -1,117 +0,0 @@ -use tokio::task::JoinSet; - -use crate::HarmonicError; - -use super::{ActionDescription, ActionReceipt, Actionable, CreateUser, Revertable}; - -#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] -pub struct CreateUsers { - nix_build_user_prefix: String, - nix_build_user_id_base: usize, - daemon_user_count: usize, - children: Vec, -} - -impl CreateUsers { - pub fn plan( - nix_build_user_prefix: String, - nix_build_user_id_base: usize, - daemon_user_count: usize, - ) -> Self { - let children = (0..daemon_user_count) - .map(|count| { - CreateUser::plan( - format!("{nix_build_user_prefix}{count}"), - nix_build_user_id_base + count, - ) - }) - .collect(); - Self { - nix_build_user_prefix, - nix_build_user_id_base, - daemon_user_count, - children, - } - } -} - -#[async_trait::async_trait] -impl<'a> Actionable<'a> for CreateUsers { - fn description(&self) -> Vec { - let nix_build_user_prefix = &self.nix_build_user_prefix; - let nix_build_user_id_base = &self.nix_build_user_id_base; - let daemon_user_count = &self.daemon_user_count; - vec![ - ActionDescription::new( - format!("Create build users"), - vec![ - format!("The nix daemon requires system users it can act as in order to build"), - format!("This action will create {daemon_user_count} users with prefix `{nix_build_user_prefix}` starting at uid `{nix_build_user_id_base}`"), - ], - ) - ] - } - - async fn execute(self) -> Result { - // TODO(@hoverbear): Abstract this, it will be common - let Self { children, .. } = self; - let mut set = JoinSet::new(); - let mut successes = Vec::with_capacity(children.len()); - let mut errors = Vec::default(); - - for child in children { - let _abort_handle = set.spawn(async move { child.execute().await }); - } - - while let Some(result) = set.join_next().await { - match result { - Ok(Ok(success)) => successes.push(success), - Ok(Err(e)) => errors.push(e), - Err(e) => errors.push(e.into()), - }; - } - - if !errors.is_empty() { - // If we got an error in a child, we need to revert the successful ones: - let mut failed_reverts = Vec::default(); - for success in successes { - match success.revert().await { - Ok(()) => (), - Err(e) => failed_reverts.push(e), - } - } - - if !failed_reverts.is_empty() { - return Err(HarmonicError::FailedReverts(errors, failed_reverts)); - } - - if errors.len() == 1 { - return Err(errors.into_iter().next().unwrap()); - } else { - return Err(HarmonicError::Multiple(errors)); - } - } - - Ok(ActionReceipt::CreateUsers(CreateUsersReceipt { - children: successes, - })) - } -} - -#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] -pub struct CreateUsersReceipt { - children: Vec, -} - -#[async_trait::async_trait] -impl<'a> Revertable<'a> for CreateUsersReceipt { - fn description(&self) -> Vec { - todo!() - } - - async fn revert(self) -> Result<(), HarmonicError> { - todo!(); - - Ok(()) - } -} diff --git a/src/actions/configure_nix.rs b/src/actions/meta/configure_nix.rs similarity index 78% rename from src/actions/configure_nix.rs rename to src/actions/meta/configure_nix.rs index b9f0f03..08699d7 100644 --- a/src/actions/configure_nix.rs +++ b/src/actions/meta/configure_nix.rs @@ -1,6 +1,6 @@ use crate::{HarmonicError, InstallSettings}; -use super::{ActionDescription, ActionReceipt, Actionable, Revertable}; +use crate::actions::{ActionDescription, ActionReceipt, Actionable, Revertable}; #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] pub struct ConfigureNix {} @@ -13,18 +13,19 @@ impl ConfigureNix { #[async_trait::async_trait] impl<'a> Actionable<'a> for ConfigureNix { + type Receipt = ConfigureNixReceipt; fn description(&self) -> Vec { vec![ ActionDescription::new( - "Start the systemd Nix daemon".to_string(), + "Configure the Nix daemon".to_string(), vec![ - "The `nix` command line tool communicates with a running Nix daemon managed by your init system".to_string() + "Blah".to_string() ] ), ] } - async fn execute(self) -> Result { + async fn execute(self) -> Result { todo!() } } diff --git a/src/actions/create_nix_tree.rs b/src/actions/meta/create_nix_tree.rs similarity index 82% rename from src/actions/create_nix_tree.rs rename to src/actions/meta/create_nix_tree.rs index 1f66a98..e992274 100644 --- a/src/actions/create_nix_tree.rs +++ b/src/actions/meta/create_nix_tree.rs @@ -1,6 +1,6 @@ use crate::{HarmonicError, InstallSettings}; -use super::{ActionDescription, ActionReceipt, Actionable, Revertable}; +use crate::actions::{ActionDescription, ActionReceipt, Actionable, Revertable}; #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] pub struct CreateNixTree { @@ -15,6 +15,7 @@ impl CreateNixTree { #[async_trait::async_trait] impl<'a> Actionable<'a> for CreateNixTree { + type Receipt = CreateNixTreeReceipt; fn description(&self) -> Vec { vec![ActionDescription::new( format!("Create a directory tree in `/nix`"), @@ -24,9 +25,9 @@ impl<'a> Actionable<'a> for CreateNixTree { )] } - async fn execute(self) -> Result { + async fn execute(self) -> Result { let Self { settings: _ } = self; - Ok(ActionReceipt::CreateNixTree(CreateNixTreeReceipt {})) + Ok(CreateNixTreeReceipt {}) } } diff --git a/src/actions/create_nix_tree_dirs.rs b/src/actions/meta/create_nix_tree_dirs.rs similarity index 81% rename from src/actions/create_nix_tree_dirs.rs rename to src/actions/meta/create_nix_tree_dirs.rs index 064d1c1..f4745df 100644 --- a/src/actions/create_nix_tree_dirs.rs +++ b/src/actions/meta/create_nix_tree_dirs.rs @@ -1,6 +1,6 @@ use crate::HarmonicError; -use super::{ActionDescription, ActionReceipt, Actionable, Revertable}; +use crate::actions::{ActionDescription, ActionReceipt, Actionable, Revertable}; #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] pub struct CreateNixTreeDirs {} @@ -13,6 +13,7 @@ impl CreateNixTreeDirs { #[async_trait::async_trait] impl<'a> Actionable<'a> for CreateNixTreeDirs { + type Receipt = CreateNixTreeDirsReceipt; fn description(&self) -> Vec { vec![ActionDescription::new( format!("Create a directory tree in `/nix`"), @@ -22,11 +23,9 @@ impl<'a> Actionable<'a> for CreateNixTreeDirs { )] } - async fn execute(self) -> Result { + async fn execute(self) -> Result { let Self {} = self; - Ok(ActionReceipt::CreateNixTreeDirs( - CreateNixTreeDirsReceipt {}, - )) + Ok(CreateNixTreeDirsReceipt {}) } } diff --git a/src/actions/meta/create_users_and_group.rs b/src/actions/meta/create_users_and_group.rs new file mode 100644 index 0000000..a7dcffa --- /dev/null +++ b/src/actions/meta/create_users_and_group.rs @@ -0,0 +1,137 @@ +use tokio::task::JoinSet; + +use crate::{HarmonicError, InstallSettings}; + +use crate::actions::base::{CreateGroup, CreateUserReceipt, CreateGroupReceipt}; +use crate::actions::{ActionDescription, ActionReceipt, Actionable, CreateUser, Revertable}; + +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +pub struct CreateUsersAndGroup { + settings: InstallSettings, + create_group: CreateGroup, + create_users: Vec, +} + +impl CreateUsersAndGroup { + pub fn plan( + settings: InstallSettings + ) -> Self { + let create_group = CreateGroup::plan(settings.nix_build_group_name.clone(), settings.nix_build_group_id); + let create_users = (0..settings.daemon_user_count) + .map(|count| { + CreateUser::plan( + format!("{}{count}", settings.nix_build_user_prefix), + settings.nix_build_user_id_base + count, + ) + }) + .collect(); + Self { + settings, + create_group, + create_users, + } + } +} + +#[async_trait::async_trait] +impl<'a> Actionable<'a> for CreateUsersAndGroup { + type Receipt = CreateUsersAndGroupReceipt; + fn description(&self) -> Vec { + let Self { + create_users: _, + create_group: _, + settings: InstallSettings { + explain: _, + daemon_user_count, + channels: _, + modify_profile: _, + nix_build_group_name, + nix_build_group_id, + nix_build_user_prefix, + nix_build_user_id_base, + nix_package_url, + } + } = &self; + + vec![ + ActionDescription::new( + format!("Create build users and group"), + vec![ + format!("The nix daemon requires system users it can act as in order to build"), + format!("This action will create group `{nix_build_group_name}` with uid `{nix_build_group_id}`"), + format!("This action will create {daemon_user_count} users with prefix `{nix_build_user_prefix}` starting at uid `{nix_build_user_id_base}`"), + ], + ) + ] + } + + async fn execute(self) -> Result { + let Self { create_users, create_group, settings: _ } = self; + + // Create group + let create_group = create_group.execute().await?; + + // Create users + // TODO(@hoverbear): Abstract this, it will be common + let mut set = JoinSet::new(); + let mut successes = Vec::with_capacity(create_users.len()); + let mut errors = Vec::default(); + + for create_user in create_users { + let _abort_handle = set.spawn(async move { create_user.execute().await }); + } + + while let Some(result) = set.join_next().await { + match result { + Ok(Ok(success)) => successes.push(success), + Ok(Err(e)) => errors.push(e), + Err(e) => errors.push(e.into()), + }; + } + + if !errors.is_empty() { + // If we got an error in a child, we need to revert the successful ones: + let mut failed_reverts = Vec::default(); + for success in successes { + match success.revert().await { + Ok(()) => (), + Err(e) => failed_reverts.push(e), + } + } + + if !failed_reverts.is_empty() { + return Err(HarmonicError::FailedReverts(errors, failed_reverts)); + } + + if errors.len() == 1 { + return Err(errors.into_iter().next().unwrap()); + } else { + return Err(HarmonicError::Multiple(errors)); + } + } + + Ok(CreateUsersAndGroupReceipt { + create_group, + create_users: successes, + }) + } +} + +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +pub struct CreateUsersAndGroupReceipt { + create_group: CreateGroupReceipt, + create_users: Vec, +} + +#[async_trait::async_trait] +impl<'a> Revertable<'a> for CreateUsersAndGroupReceipt { + fn description(&self) -> Vec { + todo!() + } + + async fn revert(self) -> Result<(), HarmonicError> { + todo!(); + + Ok(()) + } +} diff --git a/src/actions/meta/mod.rs b/src/actions/meta/mod.rs new file mode 100644 index 0000000..5973154 --- /dev/null +++ b/src/actions/meta/mod.rs @@ -0,0 +1,15 @@ +/*! Actions which only call other base plugins. */ + +mod create_nix_tree; +mod create_nix_tree_dirs; +mod create_users_and_group; +mod configure_nix; +mod start_nix_daemon; +mod provision_nix; + +pub use create_nix_tree::{CreateNixTree, CreateNixTreeReceipt}; +pub use create_nix_tree_dirs::{CreateNixTreeDirs, CreateNixTreeDirsReceipt}; +pub use create_users_and_group::{CreateUsersAndGroup, CreateUsersAndGroupReceipt}; +pub use configure_nix::{ConfigureNix, ConfigureNixReceipt}; +pub use start_nix_daemon::{StartNixDaemon, StartNixDaemonReceipt}; +pub use provision_nix::{ProvisionNix, ProvisionNixReceipt}; \ No newline at end of file diff --git a/src/actions/meta/provision_nix.rs b/src/actions/meta/provision_nix.rs new file mode 100644 index 0000000..9f28cfa --- /dev/null +++ b/src/actions/meta/provision_nix.rs @@ -0,0 +1,79 @@ +use tempdir::TempDir; + +use crate::actions::base::{FetchNix, MoveUnpackedNix, FetchNixReceipt, MoveUnpackedNixReceipt}; +use crate::{HarmonicError, InstallSettings}; + +use crate::actions::{ActionDescription, ActionReceipt, Actionable, Revertable}; + +use super::{CreateUsersAndGroup, CreateNixTree, create_users_and_group, create_nix_tree, CreateUsersAndGroupReceipt, CreateNixTreeReceipt}; + +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +pub struct ProvisionNix { + fetch_nix: FetchNix, + create_users_and_group: CreateUsersAndGroup, + create_nix_tree: CreateNixTree, + move_unpacked_nix: MoveUnpackedNix, +} + +impl ProvisionNix { + pub fn plan(settings: InstallSettings) -> Result { + let tempdir = TempDir::new("nix").map_err(HarmonicError::TempDir)?; + + let fetch_nix = FetchNix::plan(settings.nix_package_url.clone(), tempdir.path().to_path_buf()); + let create_users_and_group = CreateUsersAndGroup::plan(settings.clone()); + let create_nix_tree = CreateNixTree::plan(settings.clone()); + let move_unpacked_nix = MoveUnpackedNix::plan(tempdir.path().to_path_buf()); + Ok(Self { fetch_nix, create_users_and_group, create_nix_tree, move_unpacked_nix }) + } +} + +#[async_trait::async_trait] +impl<'a> Actionable<'a> for ProvisionNix { + type Receipt = ProvisionNixReceipt; + fn description(&self) -> Vec { + let Self { fetch_nix, create_users_and_group, create_nix_tree, move_unpacked_nix } = &self; + + let mut buf = fetch_nix.description(); + buf.append(&mut create_users_and_group.description()); + buf.append(&mut create_nix_tree.description()); + buf.append(&mut move_unpacked_nix.description()); + + buf + } + + async fn execute(self) -> Result { + let Self { fetch_nix, create_nix_tree, create_users_and_group, move_unpacked_nix } = self; + + // We fetch nix while doing the rest, then move it over. + let fetch_nix_handle = tokio::spawn(async move { fetch_nix.execute().await }); + + let create_users_and_group = create_users_and_group.execute().await?; + let create_nix_tree = create_nix_tree.execute().await?; + + let fetch_nix = fetch_nix_handle.await??; + let move_unpacked_nix = move_unpacked_nix.execute().await?; + + Ok(ProvisionNixReceipt { fetch_nix, create_users_and_group, create_nix_tree, move_unpacked_nix }) + } +} + +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +pub struct ProvisionNixReceipt { + fetch_nix: FetchNixReceipt, + create_users_and_group: CreateUsersAndGroupReceipt, + create_nix_tree: CreateNixTreeReceipt, + move_unpacked_nix: MoveUnpackedNixReceipt, +} + +#[async_trait::async_trait] +impl<'a> Revertable<'a> for ProvisionNixReceipt { + fn description(&self) -> Vec { + todo!() + } + + async fn revert(self) -> Result<(), HarmonicError> { + todo!(); + + Ok(()) + } +} diff --git a/src/actions/start_nix_daemon.rs b/src/actions/meta/start_nix_daemon.rs similarity index 84% rename from src/actions/start_nix_daemon.rs rename to src/actions/meta/start_nix_daemon.rs index a41443c..c72e753 100644 --- a/src/actions/start_nix_daemon.rs +++ b/src/actions/meta/start_nix_daemon.rs @@ -1,6 +1,6 @@ -use crate::{settings, HarmonicError, InstallSettings}; +use crate::{HarmonicError, InstallSettings}; -use super::{ActionDescription, ActionReceipt, Actionable, Revertable}; +use crate::actions::{ActionDescription, ActionReceipt, Actionable, Revertable}; #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] pub struct StartNixDaemon {} @@ -13,6 +13,7 @@ impl StartNixDaemon { #[async_trait::async_trait] impl<'a> Actionable<'a> for StartNixDaemon { + type Receipt = StartNixDaemonReceipt; fn description(&self) -> Vec { vec![ ActionDescription::new( @@ -24,7 +25,7 @@ impl<'a> Actionable<'a> for StartNixDaemon { ] } - async fn execute(self) -> Result { + async fn execute(self) -> Result { todo!() } } diff --git a/src/actions/mod.rs b/src/actions/mod.rs index 519826d..5d56765 100644 --- a/src/actions/mod.rs +++ b/src/actions/mod.rs @@ -1,43 +1,37 @@ -mod configure_nix; -mod configure_nix_daemon_service; -mod configure_shell_profile; -mod create_directory; -mod create_group; -mod create_nix_tree; -mod create_nix_tree_dirs; -mod create_user; -mod create_users; -mod place_channel_configuration; -mod place_nix_configuration; -mod setup_default_profile; -mod start_nix_daemon; -mod start_systemd_service; +pub mod base; +pub mod meta; -pub use configure_nix::{ConfigureNix, ConfigureNixReceipt}; -pub use configure_nix_daemon_service::{ +use base::{ ConfigureNixDaemonService, ConfigureNixDaemonServiceReceipt, -}; -pub use configure_shell_profile::{ConfigureShellProfile, ConfigureShellProfileReceipt}; -pub use create_directory::{CreateDirectory, CreateDirectoryReceipt}; -pub use create_group::{CreateGroup, CreateGroupReceipt}; -pub use create_nix_tree::{CreateNixTree, CreateNixTreeReceipt}; -pub use create_nix_tree_dirs::{CreateNixTreeDirs, CreateNixTreeDirsReceipt}; -pub use create_user::{CreateUser, CreateUserReceipt}; -pub use create_users::{CreateUsers, CreateUsersReceipt}; -pub use place_channel_configuration::{ + ConfigureShellProfile, ConfigureShellProfileReceipt, + CreateDirectory, CreateDirectoryReceipt, + CreateGroup, CreateGroupReceipt, + CreateUser, CreateUserReceipt, + FetchNix, FetchNixReceipt, + MoveUnpackedNix, MoveUnpackedNixReceipt, PlaceChannelConfiguration, PlaceChannelConfigurationReceipt, + PlaceNixConfiguration, PlaceNixConfigurationReceipt, + SetupDefaultProfile, SetupDefaultProfileReceipt, + StartSystemdService, StartSystemdServiceReceipt, }; -pub use place_nix_configuration::{PlaceNixConfiguration, PlaceNixConfigurationReceipt}; -pub use setup_default_profile::{SetupDefaultProfile, SetupDefaultProfileReceipt}; -pub use start_nix_daemon::{StartNixDaemon, StartNixDaemonReceipt}; -pub use start_systemd_service::{StartSystemdService, StartSystemdServiceReceipt}; +use meta::{ + ConfigureNix, ConfigureNixReceipt, + CreateNixTree, CreateNixTreeReceipt, + CreateNixTreeDirs, CreateNixTreeDirsReceipt, + CreateUsersAndGroup, CreateUsersAndGroupReceipt, + StartNixDaemon, StartNixDaemonReceipt, +}; + use crate::HarmonicError; +use self::meta::{ProvisionNix, ProvisionNixReceipt}; + #[async_trait::async_trait] pub trait Actionable<'a>: serde::de::Deserialize<'a> + serde::Serialize { + type Receipt; fn description(&self) -> Vec; - async fn execute(self) -> Result; + async fn execute(self) -> Result; } #[async_trait::async_trait] @@ -67,17 +61,20 @@ pub enum Action { ConfigureNixDaemonService(ConfigureNixDaemonService), ConfigureNix(ConfigureNix), ConfigureShellProfile(ConfigureShellProfile), - CreateDirectory(CreateUser), + CreateDirectory(CreateDirectory), CreateGroup(CreateGroup), CreateNixTreeDirs(CreateNixTreeDirs), CreateNixTree(CreateNixTree), CreateUser(CreateUser), - CreateUsers(CreateUsers), + CreateUsersAndGroup(CreateUsersAndGroup), + FetchNix(FetchNix), + MoveUnpackedNix(MoveUnpackedNix), PlaceChannelConfiguration(PlaceChannelConfiguration), PlaceNixConfiguration(PlaceNixConfiguration), SetupDefaultProfile(SetupDefaultProfile), StartNixDaemon(StartNixDaemon), - StartSystemdService(StartNixDaemon), + StartSystemdService(StartSystemdService), + ProvisionNix(ProvisionNix), } #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] @@ -90,16 +87,20 @@ pub enum ActionReceipt { CreateNixTreeDirs(CreateNixTreeDirsReceipt), CreateNixTree(CreateNixTreeReceipt), CreateUser(CreateUserReceipt), - CreateUsers(CreateUsersReceipt), + CreateUsersAndGroup(CreateUsersAndGroupReceipt), + FetchNix(FetchNixReceipt), + MoveUnpackedNix(MoveUnpackedNixReceipt), PlaceChannelConfiguration(PlaceChannelConfigurationReceipt), PlaceNixConfiguration(PlaceNixConfigurationReceipt), SetupDefaultProfile(SetupDefaultProfileReceipt), StartNixDaemon(StartNixDaemonReceipt), - StartSystemdService(StartNixDaemonReceipt), + StartSystemdService(StartSystemdServiceReceipt), + ProvisionNix(ProvisionNixReceipt), } #[async_trait::async_trait] impl<'a> Actionable<'a> for Action { + type Receipt = ActionReceipt; fn description(&self) -> Vec { match self { Action::ConfigureNixDaemonService(i) => i.description(), @@ -110,31 +111,37 @@ impl<'a> Actionable<'a> for Action { Action::CreateNixTreeDirs(i) => i.description(), Action::CreateNixTree(i) => i.description(), Action::CreateUser(i) => i.description(), - Action::CreateUsers(i) => i.description(), + Action::CreateUsersAndGroup(i) => i.description(), + Action::FetchNix(i) => i.description(), + Action::MoveUnpackedNix(i) => i.description(), Action::PlaceChannelConfiguration(i) => i.description(), Action::PlaceNixConfiguration(i) => i.description(), Action::SetupDefaultProfile(i) => i.description(), Action::StartNixDaemon(i) => i.description(), Action::StartSystemdService(i) => i.description(), + Action::ProvisionNix(i) => i.description(), } } - async fn execute(self) -> Result { + async fn execute(self) -> Result { match self { - Action::ConfigureNixDaemonService(i) => i.execute().await, - Action::ConfigureNix(i) => i.execute().await, - Action::ConfigureShellProfile(i) => i.execute().await, - Action::CreateDirectory(i) => i.execute().await, - Action::CreateGroup(i) => i.execute().await, - Action::CreateNixTreeDirs(i) => i.execute().await, - Action::CreateNixTree(i) => i.execute().await, - Action::CreateUser(i) => i.execute().await, - Action::CreateUsers(i) => i.execute().await, - Action::PlaceChannelConfiguration(i) => i.execute().await, - Action::PlaceNixConfiguration(i) => i.execute().await, - Action::SetupDefaultProfile(i) => i.execute().await, - Action::StartNixDaemon(i) => i.execute().await, - Action::StartSystemdService(i) => i.execute().await, + Action::ConfigureNixDaemonService(i) => i.execute().await.map(ActionReceipt::ConfigureNixDaemonService), + Action::ConfigureNix(i) => i.execute().await.map(ActionReceipt::ConfigureNix), + Action::ConfigureShellProfile(i) => i.execute().await.map(ActionReceipt::ConfigureShellProfile), + Action::CreateDirectory(i) => i.execute().await.map(ActionReceipt::CreateDirectory), + Action::CreateGroup(i) => i.execute().await.map(ActionReceipt::CreateGroup), + Action::CreateNixTreeDirs(i) => i.execute().await.map(ActionReceipt::CreateNixTreeDirs), + Action::CreateNixTree(i) => i.execute().await.map(ActionReceipt::CreateNixTree), + Action::CreateUser(i) => i.execute().await.map(ActionReceipt::CreateUser), + Action::CreateUsersAndGroup(i) => i.execute().await.map(ActionReceipt::CreateUsersAndGroup), + Action::FetchNix(i) => i.execute().await.map(ActionReceipt::FetchNix), + Action::MoveUnpackedNix(i) => i.execute().await.map(ActionReceipt::MoveUnpackedNix), + Action::PlaceChannelConfiguration(i) => i.execute().await.map(ActionReceipt::PlaceChannelConfiguration), + Action::PlaceNixConfiguration(i) => i.execute().await.map(ActionReceipt::PlaceNixConfiguration), + Action::SetupDefaultProfile(i) => i.execute().await.map(ActionReceipt::SetupDefaultProfile), + Action::StartNixDaemon(i) => i.execute().await.map(ActionReceipt::StartNixDaemon), + Action::StartSystemdService(i) => i.execute().await.map(ActionReceipt::StartSystemdService), + Action::ProvisionNix(i) => i.execute().await.map(ActionReceipt::ProvisionNix), } } } @@ -151,12 +158,15 @@ impl<'a> Revertable<'a> for ActionReceipt { ActionReceipt::CreateNixTreeDirs(i) => i.description(), ActionReceipt::CreateNixTree(i) => i.description(), ActionReceipt::CreateUser(i) => i.description(), - ActionReceipt::CreateUsers(i) => i.description(), + ActionReceipt::CreateUsersAndGroup(i) => i.description(), + ActionReceipt::FetchNix(i) => i.description(), + ActionReceipt::MoveUnpackedNix(i) => i.description(), ActionReceipt::PlaceChannelConfiguration(i) => i.description(), ActionReceipt::PlaceNixConfiguration(i) => i.description(), ActionReceipt::SetupDefaultProfile(i) => i.description(), ActionReceipt::StartNixDaemon(i) => i.description(), ActionReceipt::StartSystemdService(i) => i.description(), + ActionReceipt::ProvisionNix(i) => i.description(), } } @@ -170,12 +180,15 @@ impl<'a> Revertable<'a> for ActionReceipt { ActionReceipt::CreateNixTreeDirs(i) => i.revert().await, ActionReceipt::CreateNixTree(i) => i.revert().await, ActionReceipt::CreateUser(i) => i.revert().await, - ActionReceipt::CreateUsers(i) => i.revert().await, + ActionReceipt::CreateUsersAndGroup(i) => i.revert().await, + ActionReceipt::FetchNix(i) => i.revert().await, + ActionReceipt::MoveUnpackedNix(i) => i.revert().await, ActionReceipt::PlaceChannelConfiguration(i) => i.revert().await, ActionReceipt::PlaceNixConfiguration(i) => i.revert().await, ActionReceipt::SetupDefaultProfile(i) => i.revert().await, ActionReceipt::StartNixDaemon(i) => i.revert().await, ActionReceipt::StartSystemdService(i) => i.revert().await, + ActionReceipt::ProvisionNix(i) => i.revert().await, } } } diff --git a/src/plan.rs b/src/plan.rs index 1078a07..e89c240 100644 --- a/src/plan.rs +++ b/src/plan.rs @@ -2,8 +2,12 @@ use serde::{Deserialize, Serialize}; use crate::{ actions::{ - Action, ActionDescription, ActionReceipt, Actionable, ConfigureNix, CreateNixTree, - Revertable, StartNixDaemon, + Action, ActionDescription, ActionReceipt, Actionable, Revertable, + meta::{ + ConfigureNix, + ProvisionNix, + StartNixDaemon, + }, }, settings::InstallSettings, HarmonicError, @@ -79,7 +83,7 @@ impl InstallPlan { } pub async fn new(settings: InstallSettings) -> Result { let actions = vec![ - Action::CreateNixTree(CreateNixTree::plan(settings.clone())), + Action::ProvisionNix(ProvisionNix::plan(settings.clone())?), Action::ConfigureNix(ConfigureNix::plan(settings.clone())), Action::StartNixDaemon(StartNixDaemon::plan(settings.clone())), ]; diff --git a/src/settings.rs b/src/settings.rs index 937721b..0537441 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -10,6 +10,7 @@ pub struct InstallSettings { pub(crate) nix_build_group_id: usize, pub(crate) nix_build_user_prefix: String, pub(crate) nix_build_user_id_base: usize, + pub(crate) nix_package_url: Url, } impl Default for InstallSettings { @@ -23,6 +24,7 @@ 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") } } } @@ -67,4 +69,8 @@ impl InstallSettings { self.nix_build_user_id_base = count; self } + pub fn nix_package_url(&mut self, url: Url) -> &mut Self { + self.nix_package_url = url; + self + } }