More scaffolding

Signed-off-by: Ana Hobden <operator@hoverbear.org>
This commit is contained in:
Ana Hobden 2022-09-20 11:42:20 -07:00
parent 38cc094381
commit 8addef670d
23 changed files with 499 additions and 210 deletions

View file

@ -1,6 +1,6 @@
use crate::HarmonicError; use crate::HarmonicError;
use super::{ActionDescription, ActionReceipt, Actionable, Revertable}; use crate::actions::{ActionDescription, ActionReceipt, Actionable, Revertable};
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct ConfigureNixDaemonService {} pub struct ConfigureNixDaemonService {}
@ -13,6 +13,7 @@ impl ConfigureNixDaemonService {
#[async_trait::async_trait] #[async_trait::async_trait]
impl<'a> Actionable<'a> for ConfigureNixDaemonService { impl<'a> Actionable<'a> for ConfigureNixDaemonService {
type Receipt = ConfigureNixDaemonServiceReceipt;
fn description(&self) -> Vec<ActionDescription> { fn description(&self) -> Vec<ActionDescription> {
vec![ vec![
ActionDescription::new( ActionDescription::new(
@ -24,7 +25,7 @@ impl<'a> Actionable<'a> for ConfigureNixDaemonService {
] ]
} }
async fn execute(self) -> Result<ActionReceipt, HarmonicError> { async fn execute(self) -> Result<Self::Receipt, HarmonicError> {
todo!() todo!()
} }
} }

View file

@ -1,6 +1,6 @@
use crate::HarmonicError; use crate::HarmonicError;
use super::{ActionDescription, ActionReceipt, Actionable, Revertable}; use crate::actions::{ActionDescription, ActionReceipt, Actionable, Revertable};
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct ConfigureShellProfile {} pub struct ConfigureShellProfile {}
@ -13,6 +13,7 @@ impl ConfigureShellProfile {
#[async_trait::async_trait] #[async_trait::async_trait]
impl<'a> Actionable<'a> for ConfigureShellProfile { impl<'a> Actionable<'a> for ConfigureShellProfile {
type Receipt = ConfigureShellProfileReceipt;
fn description(&self) -> Vec<ActionDescription> { fn description(&self) -> Vec<ActionDescription> {
vec![ vec![
ActionDescription::new( ActionDescription::new(
@ -24,7 +25,7 @@ impl<'a> Actionable<'a> for ConfigureShellProfile {
] ]
} }
async fn execute(self) -> Result<ActionReceipt, HarmonicError> { async fn execute(self) -> Result<Self::Receipt, HarmonicError> {
todo!() todo!()
} }
} }

View file

@ -5,7 +5,7 @@ use std::{
use crate::HarmonicError; use crate::HarmonicError;
use super::{ActionDescription, ActionReceipt, Actionable, Revertable}; use crate::actions::{ActionDescription, ActionReceipt, Actionable, Revertable};
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct CreateDirectory { pub struct CreateDirectory {
@ -29,6 +29,7 @@ impl CreateDirectory {
#[async_trait::async_trait] #[async_trait::async_trait]
impl<'a> Actionable<'a> for CreateDirectory { impl<'a> Actionable<'a> for CreateDirectory {
type Receipt = CreateDirectoryReceipt;
fn description(&self) -> Vec<ActionDescription> { fn description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new( vec![ActionDescription::new(
format!("Create the directory `/nix`"), format!("Create the directory `/nix`"),
@ -38,7 +39,7 @@ impl<'a> Actionable<'a> for CreateDirectory {
)] )]
} }
async fn execute(self) -> Result<ActionReceipt, HarmonicError> { async fn execute(self) -> Result<CreateDirectoryReceipt, HarmonicError> {
let Self { let Self {
path, path,
user, user,
@ -46,12 +47,12 @@ impl<'a> Actionable<'a> for CreateDirectory {
mode, mode,
} = self; } = self;
todo!(); todo!();
Ok(ActionReceipt::CreateDirectory(CreateDirectoryReceipt { Ok(CreateDirectoryReceipt {
path, path,
user, user,
group, group,
mode, mode,
})) })
} }
} }

View file

@ -1,6 +1,6 @@
use crate::HarmonicError; use crate::HarmonicError;
use super::{ActionDescription, ActionReceipt, Actionable, Revertable}; use crate::actions::{ActionDescription, ActionReceipt, Actionable, Revertable};
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct CreateGroup { pub struct CreateGroup {
@ -16,6 +16,7 @@ impl CreateGroup {
#[async_trait::async_trait] #[async_trait::async_trait]
impl<'a> Actionable<'a> for CreateGroup { impl<'a> Actionable<'a> for CreateGroup {
type Receipt = CreateGroupReceipt;
fn description(&self) -> Vec<ActionDescription> { fn description(&self) -> Vec<ActionDescription> {
let name = &self.name; let name = &self.name;
let uid = &self.uid; let uid = &self.uid;
@ -27,9 +28,9 @@ impl<'a> Actionable<'a> for CreateGroup {
)] )]
} }
async fn execute(self) -> Result<ActionReceipt, HarmonicError> { async fn execute(self) -> Result<Self::Receipt, HarmonicError> {
let Self { name, uid } = self; let Self { name, uid } = self;
Ok(ActionReceipt::CreateGroup(CreateGroupReceipt { name, uid })) Ok(CreateGroupReceipt { name, uid })
} }
} }

View file

@ -1,6 +1,6 @@
use crate::HarmonicError; use crate::HarmonicError;
use super::{ActionDescription, ActionReceipt, Actionable, Revertable}; use crate::actions::{ActionDescription, ActionReceipt, Actionable, Revertable};
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct CreateUser { pub struct CreateUser {
@ -16,6 +16,7 @@ impl CreateUser {
#[async_trait::async_trait] #[async_trait::async_trait]
impl<'a> Actionable<'a> for CreateUser { impl<'a> Actionable<'a> for CreateUser {
type Receipt = CreateUserReceipt;
fn description(&self) -> Vec<ActionDescription> { fn description(&self) -> Vec<ActionDescription> {
let name = &self.name; let name = &self.name;
let uid = &self.uid; let uid = &self.uid;
@ -27,9 +28,9 @@ impl<'a> Actionable<'a> for CreateUser {
)] )]
} }
async fn execute(self) -> Result<ActionReceipt, HarmonicError> { async fn execute(self) -> Result<Self::Receipt, HarmonicError> {
let Self { name, uid } = self; let Self { name, uid } = self;
Ok(ActionReceipt::CreateUser(CreateUserReceipt { name, uid })) Ok(CreateUserReceipt { name, uid })
} }
} }

View file

@ -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<ActionDescription> {
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<Self::Receipt, HarmonicError> {
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<ActionDescription> {
todo!()
}
async fn revert(self) -> Result<(), HarmonicError> {
todo!();
Ok(())
}
}

29
src/actions/base/mod.rs Normal file
View file

@ -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};

View file

@ -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<ActionDescription> {
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<Self::Receipt, HarmonicError> {
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<ActionDescription> {
todo!()
}
async fn revert(self) -> Result<(), HarmonicError> {
todo!();
Ok(())
}
}

View file

@ -1,6 +1,6 @@
use crate::HarmonicError; use crate::HarmonicError;
use super::{ActionDescription, ActionReceipt, Actionable, Revertable}; use crate::actions::{ActionDescription, ActionReceipt, Actionable, Revertable};
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct PlaceChannelConfiguration {} pub struct PlaceChannelConfiguration {}
@ -13,6 +13,7 @@ impl PlaceChannelConfiguration {
#[async_trait::async_trait] #[async_trait::async_trait]
impl<'a> Actionable<'a> for PlaceChannelConfiguration { impl<'a> Actionable<'a> for PlaceChannelConfiguration {
type Receipt = PlaceChannelConfigurationReceipt;
fn description(&self) -> Vec<ActionDescription> { fn description(&self) -> Vec<ActionDescription> {
vec![ vec![
ActionDescription::new( ActionDescription::new(
@ -24,7 +25,7 @@ impl<'a> Actionable<'a> for PlaceChannelConfiguration {
] ]
} }
async fn execute(self) -> Result<ActionReceipt, HarmonicError> { async fn execute(self) -> Result<Self::Receipt, HarmonicError> {
todo!() todo!()
} }
} }

View file

@ -1,6 +1,6 @@
use crate::HarmonicError; use crate::HarmonicError;
use super::{ActionDescription, ActionReceipt, Actionable, Revertable}; use crate::actions::{ActionDescription, ActionReceipt, Actionable, Revertable};
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct PlaceNixConfiguration {} pub struct PlaceNixConfiguration {}
@ -13,6 +13,7 @@ impl PlaceNixConfiguration {
#[async_trait::async_trait] #[async_trait::async_trait]
impl<'a> Actionable<'a> for PlaceNixConfiguration { impl<'a> Actionable<'a> for PlaceNixConfiguration {
type Receipt = PlaceNixConfigurationReceipt;
fn description(&self) -> Vec<ActionDescription> { fn description(&self) -> Vec<ActionDescription> {
vec![ vec![
ActionDescription::new( ActionDescription::new(
@ -24,7 +25,7 @@ impl<'a> Actionable<'a> for PlaceNixConfiguration {
] ]
} }
async fn execute(self) -> Result<ActionReceipt, HarmonicError> { async fn execute(self) -> Result<Self::Receipt, HarmonicError> {
todo!() todo!()
} }
} }

View file

@ -1,6 +1,6 @@
use crate::HarmonicError; use crate::HarmonicError;
use super::{ActionDescription, ActionReceipt, Actionable, Revertable}; use crate::actions::{ActionDescription, ActionReceipt, Actionable, Revertable};
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct SetupDefaultProfile {} pub struct SetupDefaultProfile {}
@ -13,6 +13,7 @@ impl SetupDefaultProfile {
#[async_trait::async_trait] #[async_trait::async_trait]
impl<'a> Actionable<'a> for SetupDefaultProfile { impl<'a> Actionable<'a> for SetupDefaultProfile {
type Receipt = SetupDefaultProfileReceipt;
fn description(&self) -> Vec<ActionDescription> { fn description(&self) -> Vec<ActionDescription> {
vec![ vec![
ActionDescription::new( ActionDescription::new(
@ -24,7 +25,7 @@ impl<'a> Actionable<'a> for SetupDefaultProfile {
] ]
} }
async fn execute(self) -> Result<ActionReceipt, HarmonicError> { async fn execute(self) -> Result<Self::Receipt, HarmonicError> {
todo!() todo!()
} }
} }

View file

@ -1,6 +1,6 @@
use crate::HarmonicError; use crate::HarmonicError;
use super::{ActionDescription, ActionReceipt, Actionable, Revertable}; use crate::actions::{ActionDescription, ActionReceipt, Actionable, Revertable};
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct StartSystemdService {} pub struct StartSystemdService {}
@ -13,6 +13,7 @@ impl StartSystemdService {
#[async_trait::async_trait] #[async_trait::async_trait]
impl<'a> Actionable<'a> for StartSystemdService { impl<'a> Actionable<'a> for StartSystemdService {
type Receipt = StartSystemdServiceReceipt;
fn description(&self) -> Vec<ActionDescription> { fn description(&self) -> Vec<ActionDescription> {
vec![ vec![
ActionDescription::new( ActionDescription::new(
@ -24,7 +25,7 @@ impl<'a> Actionable<'a> for StartSystemdService {
] ]
} }
async fn execute(self) -> Result<ActionReceipt, HarmonicError> { async fn execute(self) -> Result<Self::Receipt, HarmonicError> {
todo!() todo!()
} }
} }

View file

@ -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<CreateUser>,
}
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<ActionDescription> {
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<ActionReceipt, HarmonicError> {
// 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<ActionReceipt>,
}
#[async_trait::async_trait]
impl<'a> Revertable<'a> for CreateUsersReceipt {
fn description(&self) -> Vec<ActionDescription> {
todo!()
}
async fn revert(self) -> Result<(), HarmonicError> {
todo!();
Ok(())
}
}

View file

@ -1,6 +1,6 @@
use crate::{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)] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct ConfigureNix {} pub struct ConfigureNix {}
@ -13,18 +13,19 @@ impl ConfigureNix {
#[async_trait::async_trait] #[async_trait::async_trait]
impl<'a> Actionable<'a> for ConfigureNix { impl<'a> Actionable<'a> for ConfigureNix {
type Receipt = ConfigureNixReceipt;
fn description(&self) -> Vec<ActionDescription> { fn description(&self) -> Vec<ActionDescription> {
vec![ vec![
ActionDescription::new( ActionDescription::new(
"Start the systemd Nix daemon".to_string(), "Configure the Nix daemon".to_string(),
vec![ 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<ActionReceipt, HarmonicError> { async fn execute(self) -> Result<Self::Receipt, HarmonicError> {
todo!() todo!()
} }
} }

View file

@ -1,6 +1,6 @@
use crate::{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)] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct CreateNixTree { pub struct CreateNixTree {
@ -15,6 +15,7 @@ impl CreateNixTree {
#[async_trait::async_trait] #[async_trait::async_trait]
impl<'a> Actionable<'a> for CreateNixTree { impl<'a> Actionable<'a> for CreateNixTree {
type Receipt = CreateNixTreeReceipt;
fn description(&self) -> Vec<ActionDescription> { fn description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new( vec![ActionDescription::new(
format!("Create a directory tree in `/nix`"), format!("Create a directory tree in `/nix`"),
@ -24,9 +25,9 @@ impl<'a> Actionable<'a> for CreateNixTree {
)] )]
} }
async fn execute(self) -> Result<ActionReceipt, HarmonicError> { async fn execute(self) -> Result<Self::Receipt, HarmonicError> {
let Self { settings: _ } = self; let Self { settings: _ } = self;
Ok(ActionReceipt::CreateNixTree(CreateNixTreeReceipt {})) Ok(CreateNixTreeReceipt {})
} }
} }

View file

@ -1,6 +1,6 @@
use crate::HarmonicError; use crate::HarmonicError;
use super::{ActionDescription, ActionReceipt, Actionable, Revertable}; use crate::actions::{ActionDescription, ActionReceipt, Actionable, Revertable};
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct CreateNixTreeDirs {} pub struct CreateNixTreeDirs {}
@ -13,6 +13,7 @@ impl CreateNixTreeDirs {
#[async_trait::async_trait] #[async_trait::async_trait]
impl<'a> Actionable<'a> for CreateNixTreeDirs { impl<'a> Actionable<'a> for CreateNixTreeDirs {
type Receipt = CreateNixTreeDirsReceipt;
fn description(&self) -> Vec<ActionDescription> { fn description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new( vec![ActionDescription::new(
format!("Create a directory tree in `/nix`"), format!("Create a directory tree in `/nix`"),
@ -22,11 +23,9 @@ impl<'a> Actionable<'a> for CreateNixTreeDirs {
)] )]
} }
async fn execute(self) -> Result<ActionReceipt, HarmonicError> { async fn execute(self) -> Result<Self::Receipt, HarmonicError> {
let Self {} = self; let Self {} = self;
Ok(ActionReceipt::CreateNixTreeDirs( Ok(CreateNixTreeDirsReceipt {})
CreateNixTreeDirsReceipt {},
))
} }
} }

View file

@ -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<CreateUser>,
}
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<ActionDescription> {
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<Self::Receipt, HarmonicError> {
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<CreateUserReceipt>,
}
#[async_trait::async_trait]
impl<'a> Revertable<'a> for CreateUsersAndGroupReceipt {
fn description(&self) -> Vec<ActionDescription> {
todo!()
}
async fn revert(self) -> Result<(), HarmonicError> {
todo!();
Ok(())
}
}

15
src/actions/meta/mod.rs Normal file
View file

@ -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};

View file

@ -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<Self, HarmonicError> {
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<ActionDescription> {
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<Self::Receipt, HarmonicError> {
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<ActionDescription> {
todo!()
}
async fn revert(self) -> Result<(), HarmonicError> {
todo!();
Ok(())
}
}

View file

@ -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)] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct StartNixDaemon {} pub struct StartNixDaemon {}
@ -13,6 +13,7 @@ impl StartNixDaemon {
#[async_trait::async_trait] #[async_trait::async_trait]
impl<'a> Actionable<'a> for StartNixDaemon { impl<'a> Actionable<'a> for StartNixDaemon {
type Receipt = StartNixDaemonReceipt;
fn description(&self) -> Vec<ActionDescription> { fn description(&self) -> Vec<ActionDescription> {
vec![ vec![
ActionDescription::new( ActionDescription::new(
@ -24,7 +25,7 @@ impl<'a> Actionable<'a> for StartNixDaemon {
] ]
} }
async fn execute(self) -> Result<ActionReceipt, HarmonicError> { async fn execute(self) -> Result<Self::Receipt, HarmonicError> {
todo!() todo!()
} }
} }

View file

@ -1,43 +1,37 @@
mod configure_nix; pub mod base;
mod configure_nix_daemon_service; pub mod meta;
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 use configure_nix::{ConfigureNix, ConfigureNixReceipt}; use base::{
pub use configure_nix_daemon_service::{
ConfigureNixDaemonService, ConfigureNixDaemonServiceReceipt, ConfigureNixDaemonService, ConfigureNixDaemonServiceReceipt,
}; ConfigureShellProfile, ConfigureShellProfileReceipt,
pub use configure_shell_profile::{ConfigureShellProfile, ConfigureShellProfileReceipt}; CreateDirectory, CreateDirectoryReceipt,
pub use create_directory::{CreateDirectory, CreateDirectoryReceipt}; CreateGroup, CreateGroupReceipt,
pub use create_group::{CreateGroup, CreateGroupReceipt}; CreateUser, CreateUserReceipt,
pub use create_nix_tree::{CreateNixTree, CreateNixTreeReceipt}; FetchNix, FetchNixReceipt,
pub use create_nix_tree_dirs::{CreateNixTreeDirs, CreateNixTreeDirsReceipt}; MoveUnpackedNix, MoveUnpackedNixReceipt,
pub use create_user::{CreateUser, CreateUserReceipt};
pub use create_users::{CreateUsers, CreateUsersReceipt};
pub use place_channel_configuration::{
PlaceChannelConfiguration, PlaceChannelConfigurationReceipt, PlaceChannelConfiguration, PlaceChannelConfigurationReceipt,
PlaceNixConfiguration, PlaceNixConfigurationReceipt,
SetupDefaultProfile, SetupDefaultProfileReceipt,
StartSystemdService, StartSystemdServiceReceipt,
}; };
pub use place_nix_configuration::{PlaceNixConfiguration, PlaceNixConfigurationReceipt}; use meta::{
pub use setup_default_profile::{SetupDefaultProfile, SetupDefaultProfileReceipt}; ConfigureNix, ConfigureNixReceipt,
pub use start_nix_daemon::{StartNixDaemon, StartNixDaemonReceipt}; CreateNixTree, CreateNixTreeReceipt,
pub use start_systemd_service::{StartSystemdService, StartSystemdServiceReceipt}; CreateNixTreeDirs, CreateNixTreeDirsReceipt,
CreateUsersAndGroup, CreateUsersAndGroupReceipt,
StartNixDaemon, StartNixDaemonReceipt,
};
use crate::HarmonicError; use crate::HarmonicError;
use self::meta::{ProvisionNix, ProvisionNixReceipt};
#[async_trait::async_trait] #[async_trait::async_trait]
pub trait Actionable<'a>: serde::de::Deserialize<'a> + serde::Serialize { pub trait Actionable<'a>: serde::de::Deserialize<'a> + serde::Serialize {
type Receipt;
fn description(&self) -> Vec<ActionDescription>; fn description(&self) -> Vec<ActionDescription>;
async fn execute(self) -> Result<ActionReceipt, HarmonicError>; async fn execute(self) -> Result<Self::Receipt, HarmonicError>;
} }
#[async_trait::async_trait] #[async_trait::async_trait]
@ -67,17 +61,20 @@ pub enum Action {
ConfigureNixDaemonService(ConfigureNixDaemonService), ConfigureNixDaemonService(ConfigureNixDaemonService),
ConfigureNix(ConfigureNix), ConfigureNix(ConfigureNix),
ConfigureShellProfile(ConfigureShellProfile), ConfigureShellProfile(ConfigureShellProfile),
CreateDirectory(CreateUser), CreateDirectory(CreateDirectory),
CreateGroup(CreateGroup), CreateGroup(CreateGroup),
CreateNixTreeDirs(CreateNixTreeDirs), CreateNixTreeDirs(CreateNixTreeDirs),
CreateNixTree(CreateNixTree), CreateNixTree(CreateNixTree),
CreateUser(CreateUser), CreateUser(CreateUser),
CreateUsers(CreateUsers), CreateUsersAndGroup(CreateUsersAndGroup),
FetchNix(FetchNix),
MoveUnpackedNix(MoveUnpackedNix),
PlaceChannelConfiguration(PlaceChannelConfiguration), PlaceChannelConfiguration(PlaceChannelConfiguration),
PlaceNixConfiguration(PlaceNixConfiguration), PlaceNixConfiguration(PlaceNixConfiguration),
SetupDefaultProfile(SetupDefaultProfile), SetupDefaultProfile(SetupDefaultProfile),
StartNixDaemon(StartNixDaemon), StartNixDaemon(StartNixDaemon),
StartSystemdService(StartNixDaemon), StartSystemdService(StartSystemdService),
ProvisionNix(ProvisionNix),
} }
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
@ -90,16 +87,20 @@ pub enum ActionReceipt {
CreateNixTreeDirs(CreateNixTreeDirsReceipt), CreateNixTreeDirs(CreateNixTreeDirsReceipt),
CreateNixTree(CreateNixTreeReceipt), CreateNixTree(CreateNixTreeReceipt),
CreateUser(CreateUserReceipt), CreateUser(CreateUserReceipt),
CreateUsers(CreateUsersReceipt), CreateUsersAndGroup(CreateUsersAndGroupReceipt),
FetchNix(FetchNixReceipt),
MoveUnpackedNix(MoveUnpackedNixReceipt),
PlaceChannelConfiguration(PlaceChannelConfigurationReceipt), PlaceChannelConfiguration(PlaceChannelConfigurationReceipt),
PlaceNixConfiguration(PlaceNixConfigurationReceipt), PlaceNixConfiguration(PlaceNixConfigurationReceipt),
SetupDefaultProfile(SetupDefaultProfileReceipt), SetupDefaultProfile(SetupDefaultProfileReceipt),
StartNixDaemon(StartNixDaemonReceipt), StartNixDaemon(StartNixDaemonReceipt),
StartSystemdService(StartNixDaemonReceipt), StartSystemdService(StartSystemdServiceReceipt),
ProvisionNix(ProvisionNixReceipt),
} }
#[async_trait::async_trait] #[async_trait::async_trait]
impl<'a> Actionable<'a> for Action { impl<'a> Actionable<'a> for Action {
type Receipt = ActionReceipt;
fn description(&self) -> Vec<ActionDescription> { fn description(&self) -> Vec<ActionDescription> {
match self { match self {
Action::ConfigureNixDaemonService(i) => i.description(), Action::ConfigureNixDaemonService(i) => i.description(),
@ -110,31 +111,37 @@ impl<'a> Actionable<'a> for Action {
Action::CreateNixTreeDirs(i) => i.description(), Action::CreateNixTreeDirs(i) => i.description(),
Action::CreateNixTree(i) => i.description(), Action::CreateNixTree(i) => i.description(),
Action::CreateUser(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::PlaceChannelConfiguration(i) => i.description(),
Action::PlaceNixConfiguration(i) => i.description(), Action::PlaceNixConfiguration(i) => i.description(),
Action::SetupDefaultProfile(i) => i.description(), Action::SetupDefaultProfile(i) => i.description(),
Action::StartNixDaemon(i) => i.description(), Action::StartNixDaemon(i) => i.description(),
Action::StartSystemdService(i) => i.description(), Action::StartSystemdService(i) => i.description(),
Action::ProvisionNix(i) => i.description(),
} }
} }
async fn execute(self) -> Result<ActionReceipt, HarmonicError> { async fn execute(self) -> Result<Self::Receipt, HarmonicError> {
match self { match self {
Action::ConfigureNixDaemonService(i) => i.execute().await, Action::ConfigureNixDaemonService(i) => i.execute().await.map(ActionReceipt::ConfigureNixDaemonService),
Action::ConfigureNix(i) => i.execute().await, Action::ConfigureNix(i) => i.execute().await.map(ActionReceipt::ConfigureNix),
Action::ConfigureShellProfile(i) => i.execute().await, Action::ConfigureShellProfile(i) => i.execute().await.map(ActionReceipt::ConfigureShellProfile),
Action::CreateDirectory(i) => i.execute().await, Action::CreateDirectory(i) => i.execute().await.map(ActionReceipt::CreateDirectory),
Action::CreateGroup(i) => i.execute().await, Action::CreateGroup(i) => i.execute().await.map(ActionReceipt::CreateGroup),
Action::CreateNixTreeDirs(i) => i.execute().await, Action::CreateNixTreeDirs(i) => i.execute().await.map(ActionReceipt::CreateNixTreeDirs),
Action::CreateNixTree(i) => i.execute().await, Action::CreateNixTree(i) => i.execute().await.map(ActionReceipt::CreateNixTree),
Action::CreateUser(i) => i.execute().await, Action::CreateUser(i) => i.execute().await.map(ActionReceipt::CreateUser),
Action::CreateUsers(i) => i.execute().await, Action::CreateUsersAndGroup(i) => i.execute().await.map(ActionReceipt::CreateUsersAndGroup),
Action::PlaceChannelConfiguration(i) => i.execute().await, Action::FetchNix(i) => i.execute().await.map(ActionReceipt::FetchNix),
Action::PlaceNixConfiguration(i) => i.execute().await, Action::MoveUnpackedNix(i) => i.execute().await.map(ActionReceipt::MoveUnpackedNix),
Action::SetupDefaultProfile(i) => i.execute().await, Action::PlaceChannelConfiguration(i) => i.execute().await.map(ActionReceipt::PlaceChannelConfiguration),
Action::StartNixDaemon(i) => i.execute().await, Action::PlaceNixConfiguration(i) => i.execute().await.map(ActionReceipt::PlaceNixConfiguration),
Action::StartSystemdService(i) => i.execute().await, 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::CreateNixTreeDirs(i) => i.description(),
ActionReceipt::CreateNixTree(i) => i.description(), ActionReceipt::CreateNixTree(i) => i.description(),
ActionReceipt::CreateUser(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::PlaceChannelConfiguration(i) => i.description(),
ActionReceipt::PlaceNixConfiguration(i) => i.description(), ActionReceipt::PlaceNixConfiguration(i) => i.description(),
ActionReceipt::SetupDefaultProfile(i) => i.description(), ActionReceipt::SetupDefaultProfile(i) => i.description(),
ActionReceipt::StartNixDaemon(i) => i.description(), ActionReceipt::StartNixDaemon(i) => i.description(),
ActionReceipt::StartSystemdService(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::CreateNixTreeDirs(i) => i.revert().await,
ActionReceipt::CreateNixTree(i) => i.revert().await, ActionReceipt::CreateNixTree(i) => i.revert().await,
ActionReceipt::CreateUser(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::PlaceChannelConfiguration(i) => i.revert().await,
ActionReceipt::PlaceNixConfiguration(i) => i.revert().await, ActionReceipt::PlaceNixConfiguration(i) => i.revert().await,
ActionReceipt::SetupDefaultProfile(i) => i.revert().await, ActionReceipt::SetupDefaultProfile(i) => i.revert().await,
ActionReceipt::StartNixDaemon(i) => i.revert().await, ActionReceipt::StartNixDaemon(i) => i.revert().await,
ActionReceipt::StartSystemdService(i) => i.revert().await, ActionReceipt::StartSystemdService(i) => i.revert().await,
ActionReceipt::ProvisionNix(i) => i.revert().await,
} }
} }
} }

View file

@ -2,8 +2,12 @@ use serde::{Deserialize, Serialize};
use crate::{ use crate::{
actions::{ actions::{
Action, ActionDescription, ActionReceipt, Actionable, ConfigureNix, CreateNixTree, Action, ActionDescription, ActionReceipt, Actionable, Revertable,
Revertable, StartNixDaemon, meta::{
ConfigureNix,
ProvisionNix,
StartNixDaemon,
},
}, },
settings::InstallSettings, settings::InstallSettings,
HarmonicError, HarmonicError,
@ -79,7 +83,7 @@ impl InstallPlan {
} }
pub async fn new(settings: InstallSettings) -> Result<Self, HarmonicError> { pub async fn new(settings: InstallSettings) -> Result<Self, HarmonicError> {
let actions = vec![ let actions = vec![
Action::CreateNixTree(CreateNixTree::plan(settings.clone())), Action::ProvisionNix(ProvisionNix::plan(settings.clone())?),
Action::ConfigureNix(ConfigureNix::plan(settings.clone())), Action::ConfigureNix(ConfigureNix::plan(settings.clone())),
Action::StartNixDaemon(StartNixDaemon::plan(settings.clone())), Action::StartNixDaemon(StartNixDaemon::plan(settings.clone())),
]; ];

View file

@ -10,6 +10,7 @@ pub struct InstallSettings {
pub(crate) nix_build_group_id: usize, pub(crate) nix_build_group_id: usize,
pub(crate) nix_build_user_prefix: String, pub(crate) nix_build_user_prefix: String,
pub(crate) nix_build_user_id_base: usize, pub(crate) nix_build_user_id_base: usize,
pub(crate) nix_package_url: Url,
} }
impl Default for InstallSettings { impl Default for InstallSettings {
@ -23,6 +24,7 @@ impl Default for InstallSettings {
nix_build_group_id: 3000, nix_build_group_id: 3000,
nix_build_user_prefix: String::from("nixbld"), nix_build_user_prefix: String::from("nixbld"),
nix_build_user_id_base: 3001, 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.nix_build_user_id_base = count;
self self
} }
pub fn nix_package_url(&mut self, url: Url) -> &mut Self {
self.nix_package_url = url;
self
}
} }