other revert attempt

Signed-off-by: Ana Hobden <operator@hoverbear.org>
This commit is contained in:
Ana Hobden 2022-09-23 12:26:59 -07:00
parent a44c9eb45f
commit aa069cedf7
23 changed files with 733 additions and 1072 deletions

View file

@ -1,10 +1,11 @@
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use serde::Serialize;
use tokio::process::Command; use tokio::process::Command;
use crate::{execute_command, HarmonicError}; use crate::{execute_command, HarmonicError};
use crate::actions::{ActionDescription, Actionable, Revertable}; use crate::actions::{ActionDescription, Actionable, ActionState, Action};
const SERVICE_SRC: &str = "/nix/var/nix/profiles/default/lib/systemd/system/nix-daemon.service"; const SERVICE_SRC: &str = "/nix/var/nix/profiles/default/lib/systemd/system/nix-daemon.service";
const SOCKET_SRC: &str = "/nix/var/nix/profiles/default/lib/systemd/system/nix-daemon.socket"; const SOCKET_SRC: &str = "/nix/var/nix/profiles/default/lib/systemd/system/nix-daemon.socket";
@ -25,8 +26,8 @@ impl ConfigureNixDaemonService {
} }
#[async_trait::async_trait] #[async_trait::async_trait]
impl<'a> Actionable<'a> for ConfigureNixDaemonService { impl Actionable for ActionState<ConfigureNixDaemonService> {
type Receipt = ConfigureNixDaemonServiceReceipt; type Error = ConfigureNixDaemonServiceError;
fn description(&self) -> Vec<ActionDescription> { fn description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new( vec![ActionDescription::new(
"Configure Nix daemon related settings with systemd".to_string(), "Configure Nix daemon related settings with systemd".to_string(),
@ -40,7 +41,7 @@ impl<'a> Actionable<'a> for ConfigureNixDaemonService {
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn execute(self) -> Result<Self::Receipt, HarmonicError> { async fn execute(&mut self) -> Result<(), Self::Error> {
tracing::info!("Configuring nix daemon service"); tracing::info!("Configuring nix daemon service");
tracing::trace!(src = TMPFILES_SRC, dest = TMPFILES_DEST, "Symlinking"); tracing::trace!(src = TMPFILES_SRC, dest = TMPFILES_DEST, "Symlinking");
@ -68,30 +69,31 @@ impl<'a> Actionable<'a> for ConfigureNixDaemonService {
execute_command(Command::new("systemctl").arg("daemon-reload"), false).await?; execute_command(Command::new("systemctl").arg("daemon-reload"), false).await?;
Ok(Self::Receipt {}) Ok(())
} }
}
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct ConfigureNixDaemonServiceReceipt {}
#[async_trait::async_trait]
impl<'a> Revertable<'a> for ConfigureNixDaemonServiceReceipt {
fn description(&self) -> Vec<ActionDescription> {
vec![
ActionDescription::new(
"Stop the systemd Nix daemon".to_string(),
vec![
"The `nix` command line tool communicates with a running Nix daemon managed by your init system".to_string()
]
),
]
}
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn revert(self) -> Result<(), HarmonicError> { async fn revert(&mut self) -> Result<(), Self::Error> {
todo!(); todo!();
Ok(()) Ok(())
} }
} }
impl From<ActionState<ConfigureNixDaemonService>> for ActionState<Action> {
fn from(v: ActionState<ConfigureNixDaemonService>) -> Self {
match v {
ActionState::Completed(_) => ActionState::Completed(Action::ConfigureNixDaemonService(v)),
ActionState::Planned(_) => ActionState::Planned(Action::ConfigureNixDaemonService(v)),
ActionState::Reverted(_) => ActionState::Reverted(Action::ConfigureNixDaemonService(v)),
}
}
}
#[derive(Debug, thiserror::Error, Serialize)]
pub enum ConfigureNixDaemonServiceError {
}

View file

@ -1,11 +1,12 @@
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use nix::unistd::{chown, Group, User}; use nix::unistd::{chown, Group, User};
use serde::Serialize;
use tokio::fs::create_dir; use tokio::fs::create_dir;
use crate::HarmonicError; use crate::HarmonicError;
use crate::actions::{ActionDescription, Actionable, Revertable}; use crate::actions::{ActionDescription, Actionable, ActionState, Action};
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct CreateDirectory { pub struct CreateDirectory {
@ -46,8 +47,8 @@ impl CreateDirectory {
} }
#[async_trait::async_trait] #[async_trait::async_trait]
impl<'a> Actionable<'a> for CreateDirectory { impl Actionable for ActionState<CreateDirectory> {
type Receipt = CreateDirectoryReceipt; type Error = CreateDirectoryError;
fn description(&self) -> Vec<ActionDescription> { fn description(&self) -> Vec<ActionDescription> {
let Self { let Self {
path, path,
@ -65,7 +66,7 @@ impl<'a> Actionable<'a> for CreateDirectory {
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn execute(self) -> Result<CreateDirectoryReceipt, HarmonicError> { async fn execute(&mut self) -> Result<(), Self::Error> {
let Self { let Self {
path, path,
user, user,
@ -88,45 +89,31 @@ impl<'a> Actionable<'a> for CreateDirectory {
.map_err(|e| HarmonicError::CreateDirectory(path.clone(), e))?; .map_err(|e| HarmonicError::CreateDirectory(path.clone(), e))?;
chown(&path, Some(uid), Some(gid)).map_err(|e| HarmonicError::Chown(path.clone(), e))?; chown(&path, Some(uid), Some(gid)).map_err(|e| HarmonicError::Chown(path.clone(), e))?;
Ok(CreateDirectoryReceipt { Ok(())
path,
user,
group,
mode,
})
} }
}
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct CreateDirectoryReceipt {
path: PathBuf,
user: String,
group: String,
mode: u32,
}
#[async_trait::async_trait]
impl<'a> Revertable<'a> for CreateDirectoryReceipt {
fn description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(
format!("Create the directory `/nix`"),
vec![format!(
"Nix and the Nix daemon require a Nix Store, which will be stored at `/nix`"
)],
)]
}
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn revert(self) -> Result<(), HarmonicError> { async fn revert(&mut self) -> Result<(), Self::Error> {
let Self {
path,
user,
group,
mode,
} = self;
todo!(); todo!();
Ok(()) Ok(())
} }
} }
impl From<ActionState<CreateDirectory>> for ActionState<Action> {
fn from(v: ActionState<CreateDirectory>) -> Self {
match v {
ActionState::Completed(_) => ActionState::Completed(Action::CreateDirectory(v)),
ActionState::Planned(_) => ActionState::Planned(Action::CreateDirectory(v)),
ActionState::Reverted(_) => ActionState::Reverted(Action::CreateDirectory(v)),
}
}
}
#[derive(Debug, thiserror::Error, Serialize)]
pub enum CreateDirectoryError {
}

View file

@ -1,13 +1,14 @@
use nix::unistd::{chown, Group, User}; use nix::unistd::{chown, Group, User};
use serde::Serialize;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use tokio::{ use tokio::{
fs::{create_dir_all, OpenOptions}, fs::{create_dir_all, OpenOptions},
io::AsyncWriteExt, io::AsyncWriteExt,
}; };
use crate::HarmonicError; use crate::{HarmonicError, actions::{ActionState, Action}};
use crate::actions::{ActionDescription, Actionable, Revertable}; use crate::actions::{ActionDescription, Actionable};
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct CreateFile { pub struct CreateFile {
@ -53,8 +54,8 @@ impl CreateFile {
} }
#[async_trait::async_trait] #[async_trait::async_trait]
impl<'a> Actionable<'a> for CreateFile { impl Actionable for ActionState<CreateFile> {
type Receipt = CreateFileReceipt; type Error = CreateFileError;
fn description(&self) -> Vec<ActionDescription> { fn description(&self) -> Vec<ActionDescription> {
let Self { let Self {
path, path,
@ -73,7 +74,7 @@ impl<'a> Actionable<'a> for CreateFile {
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn execute(self) -> Result<CreateFileReceipt, HarmonicError> { async fn execute(&mut self) -> Result<(), Self::Error> {
let Self { let Self {
path, path,
user, user,
@ -107,35 +108,29 @@ impl<'a> Actionable<'a> for CreateFile {
tracing::trace!(path = %path.display(), "Chowning file"); tracing::trace!(path = %path.display(), "Chowning file");
chown(&path, Some(uid), Some(gid)).map_err(|e| HarmonicError::Chown(path.clone(), e))?; chown(&path, Some(uid), Some(gid)).map_err(|e| HarmonicError::Chown(path.clone(), e))?;
Ok(Self::Receipt { Ok(())
path,
user,
group,
mode,
buf,
})
} }
}
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct CreateFileReceipt {
path: PathBuf,
user: String,
group: String,
mode: u32,
buf: String,
}
#[async_trait::async_trait]
impl<'a> Revertable<'a> for CreateFileReceipt {
fn description(&self) -> Vec<ActionDescription> {
todo!()
}
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn revert(self) -> Result<(), HarmonicError> { async fn revert(&mut self) -> Result<(), Self::Error> {
todo!(); todo!();
Ok(()) Ok(())
} }
} }
impl From<ActionState<CreateFile>> for ActionState<Action> {
fn from(v: ActionState<CreateFile>) -> Self {
match v {
ActionState::Completed(_) => ActionState::Completed(Action::CreateFile(v)),
ActionState::Planned(_) => ActionState::Planned(Action::CreateFile(v)),
ActionState::Reverted(_) => ActionState::Reverted(Action::CreateFile(v)),
}
}
}
#[derive(Debug, thiserror::Error, Serialize)]
pub enum CreateFileError {
}

View file

@ -1,8 +1,9 @@
use serde::Serialize;
use tokio::process::Command; use tokio::process::Command;
use crate::{HarmonicError, execute_command}; use crate::{HarmonicError, execute_command};
use crate::actions::{ActionDescription, Actionable, Revertable}; use crate::actions::{ActionDescription, Actionable, ActionState, Action};
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct CreateGroup { pub struct CreateGroup {
@ -18,8 +19,8 @@ impl CreateGroup {
} }
#[async_trait::async_trait] #[async_trait::async_trait]
impl<'a> Actionable<'a> for CreateGroup { impl Actionable for ActionState<CreateGroup> {
type Receipt = CreateGroupReceipt; type Error = CreateOrAppendFileError;
fn description(&self) -> Vec<ActionDescription> { fn description(&self) -> Vec<ActionDescription> {
let Self { name, gid } = &self; let Self { name, gid } = &self;
vec![ActionDescription::new( vec![ActionDescription::new(
@ -31,7 +32,7 @@ impl<'a> Actionable<'a> for CreateGroup {
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn execute(self) -> Result<Self::Receipt, HarmonicError> { async fn execute(&mut self) -> Result<(), Self::Error> {
let Self { name, gid } = self; let Self { name, gid } = self;
execute_command( execute_command(
@ -39,26 +40,27 @@ impl<'a> Actionable<'a> for CreateGroup {
false, false,
).await?; ).await?;
Ok(CreateGroupReceipt { name, gid }) Ok(())
}
}
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct CreateGroupReceipt {
name: String,
gid: usize,
}
#[async_trait::async_trait]
impl<'a> Revertable<'a> for CreateGroupReceipt {
fn description(&self) -> Vec<ActionDescription> {
todo!()
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn revert(self) -> Result<(), HarmonicError> { async fn revert(&mut self) -> Result<(), Self::Error> {
todo!(); todo!();
Ok(()) Ok(())
} }
} }
impl From<ActionState<CreateGroup>> for ActionState<Action> {
fn from(v: ActionState<CreateGroup>) -> Self {
match v {
ActionState::Completed(_) => ActionState::Completed(Action::CreateGroup(v)),
ActionState::Planned(_) => ActionState::Planned(Action::CreateGroup(v)),
ActionState::Reverted(_) => ActionState::Reverted(Action::CreateGroup(v)),
}
}
}
#[derive(Debug, thiserror::Error, Serialize)]
pub enum CreateGroupError {
}

View file

@ -1,4 +1,5 @@
use nix::unistd::{chown, Group, User}; use nix::unistd::{chown, Group, User};
use serde::Serialize;
use std::{ use std::{
io::SeekFrom, io::SeekFrom,
path::{Path, PathBuf}, path::{Path, PathBuf},
@ -8,9 +9,9 @@ use tokio::{
io::{AsyncSeekExt, AsyncWriteExt}, io::{AsyncSeekExt, AsyncWriteExt},
}; };
use crate::HarmonicError; use crate::{HarmonicError, actions::{ActionState, Action}};
use crate::actions::{ActionDescription, Actionable, Revertable}; use crate::actions::{ActionDescription, Actionable};
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct CreateOrAppendFile { pub struct CreateOrAppendFile {
@ -43,8 +44,8 @@ impl CreateOrAppendFile {
} }
#[async_trait::async_trait] #[async_trait::async_trait]
impl<'a> Actionable<'a> for CreateOrAppendFile { impl Actionable for ActionState<CreateOrAppendFile> {
type Receipt = CreateOrAppendFileReceipt; type Error = CreateOrAppendFileError;
fn description(&self) -> Vec<ActionDescription> { fn description(&self) -> Vec<ActionDescription> {
let Self { let Self {
path, path,
@ -62,7 +63,7 @@ impl<'a> Actionable<'a> for CreateOrAppendFile {
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn execute(self) -> Result<CreateOrAppendFileReceipt, HarmonicError> { async fn execute(&mut self) -> Result<(), Self::Error> {
let Self { let Self {
path, path,
user, user,
@ -99,40 +100,30 @@ impl<'a> Actionable<'a> for CreateOrAppendFile {
tracing::trace!(path = %path.display(), "Chowning"); tracing::trace!(path = %path.display(), "Chowning");
chown(&path, Some(uid), Some(gid)).map_err(|e| HarmonicError::Chown(path.clone(), e))?; chown(&path, Some(uid), Some(gid)).map_err(|e| HarmonicError::Chown(path.clone(), e))?;
Ok(Self::Receipt { Ok(())
path,
user,
group,
mode,
buf,
})
} }
}
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct CreateOrAppendFileReceipt {
path: PathBuf,
user: String,
group: String,
mode: u32,
buf: String,
}
#[async_trait::async_trait]
impl<'a> Revertable<'a> for CreateOrAppendFileReceipt {
fn description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(
format!("Create the directory `/nix`"),
vec![format!(
"Nix and the Nix daemon require a Nix Store, which will be stored at `/nix`"
)],
)]
}
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn revert(self) -> Result<(), HarmonicError> { async fn revert(&mut self) -> Result<(), Self::Error> {
todo!(); todo!();
Ok(()) Ok(())
} }
} }
impl From<ActionState<CreateOrAppendFile>> for ActionState<Action> {
fn from(v: ActionState<CreateOrAppendFile>) -> Self {
match v {
ActionState::Completed(_) => ActionState::Completed(Action::CreateOrAppendFile(v)),
ActionState::Planned(_) => ActionState::Planned(Action::CreateOrAppendFile(v)),
ActionState::Reverted(_) => ActionState::Reverted(Action::CreateOrAppendFile(v)),
}
}
}
#[derive(Debug, thiserror::Error, Serialize)]
pub enum CreateOrAppendFileError {
}

View file

@ -1,8 +1,9 @@
use serde::Serialize;
use tokio::process::Command; use tokio::process::Command;
use crate::{HarmonicError, execute_command}; use crate::{HarmonicError, execute_command};
use crate::actions::{ActionDescription, Actionable, Revertable}; use crate::actions::{ActionDescription, Actionable, ActionState, Action};
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct CreateUser { pub struct CreateUser {
@ -19,8 +20,8 @@ impl CreateUser {
} }
#[async_trait::async_trait] #[async_trait::async_trait]
impl<'a> Actionable<'a> for CreateUser { impl Actionable for ActionState<CreateUser> {
type Receipt = CreateUserReceipt; type Error = CreateUserError;
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;
@ -33,7 +34,7 @@ impl<'a> Actionable<'a> for CreateUser {
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn execute(self) -> Result<Self::Receipt, HarmonicError> { async fn execute(&mut self) -> Result<(), Self::Error> {
let Self { name, uid, gid } = self; let Self { name, uid, gid } = self;
execute_command(Command::new("useradd").args([ execute_command(Command::new("useradd").args([
@ -56,27 +57,29 @@ impl<'a> Actionable<'a> for CreateUser {
&name.to_string(), &name.to_string(),
]), false).await?; ]), false).await?;
Ok(CreateUserReceipt { name, uid, gid }) Ok(())
} }
}
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct CreateUserReceipt {
name: String,
uid: usize,
gid: usize,
}
#[async_trait::async_trait]
impl<'a> Revertable<'a> for CreateUserReceipt {
fn description(&self) -> Vec<ActionDescription> {
todo!()
}
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn revert(self) -> Result<(), HarmonicError> { async fn revert(&mut self) -> Result<(), Self::Error> {
todo!(); todo!();
Ok(()) Ok(())
} }
} }
impl From<ActionState<CreateUser>> for ActionState<Action> {
fn from(v: ActionState<CreateUser>) -> Self {
match v {
ActionState::Completed(_) => ActionState::Completed(Action::CreateUser(v)),
ActionState::Planned(_) => ActionState::Planned(Action::CreateUser(v)),
ActionState::Reverted(_) => ActionState::Reverted(Action::CreateUser(v)),
}
}
}
#[derive(Debug, thiserror::Error, Serialize)]
pub enum CreateUserError {
}

View file

@ -2,11 +2,12 @@ use std::path::{PathBuf};
use bytes::Buf; use bytes::Buf;
use reqwest::Url; use reqwest::Url;
use serde::Serialize;
use tokio::task::spawn_blocking; use tokio::task::spawn_blocking;
use crate::HarmonicError; use crate::HarmonicError;
use crate::actions::{ActionDescription, Actionable, Revertable}; use crate::actions::{ActionDescription, Actionable, ActionState, Action};
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct FetchNix { pub struct FetchNix {
@ -16,7 +17,7 @@ pub struct FetchNix {
impl FetchNix { impl FetchNix {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
pub async fn plan(url: Url, destination: PathBuf) -> Result<Self, HarmonicError> { pub async fn plan(url: Url, destination: PathBuf) -> Result<Self, FetchNixError> {
// TODO(@hoverbear): Check URL exists? // TODO(@hoverbear): Check URL exists?
// TODO(@hoverbear): Check tempdir exists // TODO(@hoverbear): Check tempdir exists
@ -25,8 +26,8 @@ impl FetchNix {
} }
#[async_trait::async_trait] #[async_trait::async_trait]
impl<'a> Actionable<'a> for FetchNix { impl Actionable for ActionState<FetchNix> {
type Receipt = FetchNixReceipt; type Error = FetchNixError;
fn description(&self) -> Vec<ActionDescription> { fn description(&self) -> Vec<ActionDescription> {
let Self { url, destination } = &self; let Self { url, destination } = &self;
vec![ActionDescription::new( vec![ActionDescription::new(
@ -39,7 +40,7 @@ impl<'a> Actionable<'a> for FetchNix {
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn execute(self) -> Result<Self::Receipt, HarmonicError> { async fn execute(&mut self) -> Result<(), Self::Error> {
let Self { url, destination } = self; let Self { url, destination } = self;
tracing::trace!(%url, "Fetching url"); tracing::trace!(%url, "Fetching url");
@ -61,26 +62,29 @@ impl<'a> Actionable<'a> for FetchNix {
handle?; handle?;
Ok(FetchNixReceipt { url, destination }) Ok(())
} }
}
#[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!()
}
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn revert(self) -> Result<(), HarmonicError> { async fn revert(&mut self) -> Result<(), Self::Error> {
todo!(); todo!();
Ok(()) Ok(())
} }
} }
impl From<ActionState<FetchNix>> for ActionState<Action> {
fn from(v: ActionState<FetchNix>) -> Self {
match v {
ActionState::Completed(_) => ActionState::Completed(Action::FetchNix(v)),
ActionState::Planned(_) => ActionState::Planned(Action::FetchNix(v)),
ActionState::Reverted(_) => ActionState::Reverted(Action::FetchNix(v)),
}
}
}
#[derive(Debug, thiserror::Error, Serialize)]
pub enum FetchNixError {
}

View file

@ -14,18 +14,18 @@ mod setup_default_profile;
mod start_systemd_unit; mod start_systemd_unit;
pub use configure_nix_daemon_service::{ pub use configure_nix_daemon_service::{
ConfigureNixDaemonService, ConfigureNixDaemonServiceReceipt, ConfigureNixDaemonService, ConfigureNixDaemonServiceError,
}; };
pub use create_directory::{CreateDirectory, CreateDirectoryReceipt}; pub use create_directory::{CreateDirectory, CreateDirectoryError};
pub use create_file::{CreateFile, CreateFileReceipt}; pub use create_file::{CreateFile, CreateFileError};
pub use create_group::{CreateGroup, CreateGroupReceipt}; pub use create_group::{CreateGroup, CreateGroupError};
pub use create_or_append_file::{CreateOrAppendFile, CreateOrAppendFileReceipt}; pub use create_or_append_file::{CreateOrAppendFile, CreateOrAppendFileError};
pub use create_user::{CreateUser, CreateUserReceipt}; pub use create_user::{CreateUser, CreateUserError};
pub use fetch_nix::{FetchNix, FetchNixReceipt}; pub use fetch_nix::{FetchNix, FetchNixError};
pub use move_unpacked_nix::{MoveUnpackedNix, MoveUnpackedNixReceipt}; pub use move_unpacked_nix::{MoveUnpackedNix, MoveUnpackedNixError};
pub use place_channel_configuration::{ pub use place_channel_configuration::{
PlaceChannelConfiguration, PlaceChannelConfigurationReceipt, PlaceChannelConfiguration, PlaceChannelConfigurationError,
}; };
pub use place_nix_configuration::{PlaceNixConfiguration, PlaceNixConfigurationReceipt}; pub use place_nix_configuration::{PlaceNixConfiguration, PlaceNixConfigurationError};
pub use setup_default_profile::{SetupDefaultProfile, SetupDefaultProfileReceipt}; pub use setup_default_profile::{SetupDefaultProfile, SetupDefaultProfileError};
pub use start_systemd_unit::{StartSystemdUnit, StartSystemdUnitReceipt}; pub use start_systemd_unit::{StartSystemdUnit, StartSystemdUnitError};

View file

@ -1,8 +1,10 @@
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use serde::Serialize;
use crate::HarmonicError; use crate::HarmonicError;
use crate::actions::{ActionDescription, Actionable, Revertable}; use crate::actions::{ActionDescription, Actionable, ActionState, Action};
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct MoveUnpackedNix { pub struct MoveUnpackedNix {
@ -18,8 +20,8 @@ impl MoveUnpackedNix {
} }
#[async_trait::async_trait] #[async_trait::async_trait]
impl<'a> Actionable<'a> for MoveUnpackedNix { impl Actionable for ActionState<MoveUnpackedNix> {
type Receipt = MoveUnpackedNixReceipt; type Error = MoveUnpackedNixError;
fn description(&self) -> Vec<ActionDescription> { fn description(&self) -> Vec<ActionDescription> {
let Self { source } = &self; let Self { source } = &self;
vec![ActionDescription::new( vec![ActionDescription::new(
@ -32,7 +34,7 @@ impl<'a> Actionable<'a> for MoveUnpackedNix {
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn execute(self) -> Result<Self::Receipt, HarmonicError> { async fn execute(&mut self) -> Result<(), Self::Error> {
let Self { source } = self; let Self { source } = self;
// TODO(@Hoverbear): I would like to make this less awful // TODO(@Hoverbear): I would like to make this less awful
@ -51,23 +53,29 @@ impl<'a> Actionable<'a> for MoveUnpackedNix {
.await .await
.map_err(|e| HarmonicError::Rename(src, dest.to_owned(), e))?; .map_err(|e| HarmonicError::Rename(src, dest.to_owned(), e))?;
Ok(MoveUnpackedNixReceipt {}) Ok(())
} }
}
#[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!()
}
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn revert(self) -> Result<(), HarmonicError> { async fn revert(&mut self) -> Result<(), Self::Error> {
todo!(); todo!();
Ok(()) Ok(())
} }
} }
impl From<ActionState<MoveUnpackedNix>> for ActionState<Action> {
fn from(v: ActionState<MoveUnpackedNix>) -> Self {
match v {
ActionState::Completed(_) => ActionState::Completed(Action::MoveUnpackedNix(v)),
ActionState::Planned(_) => ActionState::Planned(Action::MoveUnpackedNix(v)),
ActionState::Reverted(_) => ActionState::Reverted(Action::MoveUnpackedNix(v)),
}
}
}
#[derive(Debug, thiserror::Error, Serialize)]
pub enum MoveUnpackedNixError {
}

View file

@ -1,10 +1,11 @@
use reqwest::Url; use reqwest::Url;
use serde::Serialize;
use crate::HarmonicError; use crate::HarmonicError;
use crate::actions::{ActionDescription, Actionable, Revertable}; use crate::actions::{ActionDescription, Actionable, ActionState, Action};
use super::{CreateFile, CreateFileReceipt}; use super::{CreateFile, CreateFileError};
const NIX_CHANNELS_PATH: &str = "/root/.nix-channels"; const NIX_CHANNELS_PATH: &str = "/root/.nix-channels";
@ -32,8 +33,8 @@ impl PlaceChannelConfiguration {
} }
#[async_trait::async_trait] #[async_trait::async_trait]
impl<'a> Actionable<'a> for PlaceChannelConfiguration { impl Actionable for ActionState<PlaceChannelConfiguration> {
type Receipt = PlaceChannelConfigurationReceipt; type Error = PlaceChannelConfigurationError;
fn description(&self) -> Vec<ActionDescription> { fn description(&self) -> Vec<ActionDescription> {
let Self { let Self {
channels, channels,
@ -46,35 +47,38 @@ impl<'a> Actionable<'a> for PlaceChannelConfiguration {
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn execute(self) -> Result<Self::Receipt, HarmonicError> { async fn execute(&mut self) -> Result<(), Self::Error> {
let Self { let Self {
create_file, create_file,
channels, channels,
} = self; } = self;
let create_file = create_file.execute().await?;
Ok(Self::Receipt { create_file.execute().await?;
create_file,
channels, Ok(())
})
} }
}
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct PlaceChannelConfigurationReceipt {
channels: Vec<(String, Url)>,
create_file: CreateFileReceipt,
}
#[async_trait::async_trait]
impl<'a> Revertable<'a> for PlaceChannelConfigurationReceipt {
fn description(&self) -> Vec<ActionDescription> {
todo!()
}
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn revert(self) -> Result<(), HarmonicError> { async fn revert(&mut self) -> Result<(), Self::Error> {
todo!(); todo!();
Ok(()) Ok(())
} }
} }
impl From<ActionState<PlaceChannelConfiguration>> for ActionState<Action> {
fn from(v: ActionState<PlaceChannelConfiguration>) -> Self {
match v {
ActionState::Completed(_) => ActionState::Completed(Action::PlaceChannelConfiguration(v)),
ActionState::Planned(_) => ActionState::Planned(Action::PlaceChannelConfiguration(v)),
ActionState::Reverted(_) => ActionState::Reverted(Action::PlaceChannelConfiguration(v)),
}
}
}
#[derive(Debug, thiserror::Error, Serialize)]
pub enum PlaceChannelConfigurationError {
}

View file

@ -1,8 +1,10 @@
use serde::Serialize;
use crate::HarmonicError; use crate::HarmonicError;
use crate::actions::{ActionDescription, Actionable, Revertable}; use crate::actions::{ActionDescription, Actionable, ActionState, Action};
use super::{CreateFile, CreateFileReceipt, CreateDirectory, CreateDirectoryReceipt}; use super::{CreateFile, CreateFileError, CreateDirectory, CreateDirectoryError};
const NIX_CONF_FOLDER: &str = "/etc/nix"; const NIX_CONF_FOLDER: &str = "/etc/nix";
const NIX_CONF: &str = "/etc/nix/nix.conf"; const NIX_CONF: &str = "/etc/nix/nix.conf";
@ -35,8 +37,9 @@ impl PlaceNixConfiguration {
} }
#[async_trait::async_trait] #[async_trait::async_trait]
impl<'a> Actionable<'a> for PlaceNixConfiguration { impl Actionable for ActionState<PlaceNixConfiguration> {
type Receipt = PlaceNixConfigurationReceipt; type Error = PlaceNixConfigurationError;
fn description(&self) -> Vec<ActionDescription> { fn description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new( vec![ActionDescription::new(
format!("Place the nix configuration in `{NIX_CONF}`"), format!("Place the nix configuration in `{NIX_CONF}`"),
@ -45,30 +48,36 @@ impl<'a> Actionable<'a> for PlaceNixConfiguration {
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn execute(self) -> Result<Self::Receipt, HarmonicError> { async fn execute(&mut self) -> Result<(), Self::Error> {
let Self { create_file, create_directory } = self; let Self { create_file, create_directory } = self;
let create_directory = create_directory.execute().await?;
let create_file = create_file.execute().await?;
Ok(Self::Receipt { create_file, create_directory })
}
}
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] create_directory.execute().await?;
pub struct PlaceNixConfigurationReceipt { create_file.execute().await?;
create_directory: CreateDirectoryReceipt,
create_file: CreateFileReceipt,
}
#[async_trait::async_trait] Ok(())
impl<'a> Revertable<'a> for PlaceNixConfigurationReceipt {
fn description(&self) -> Vec<ActionDescription> {
todo!()
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn revert(self) -> Result<(), HarmonicError> { async fn revert(&mut self) -> Result<(), Self::Error> {
todo!(); todo!();
Ok(()) Ok(())
} }
} }
impl From<ActionState<PlaceNixConfiguration>> for ActionState<Action> {
fn from(v: ActionState<PlaceNixConfiguration>) -> Self {
match v {
ActionState::Completed(_) => ActionState::Completed(Action::PlaceNixConfiguration(v)),
ActionState::Planned(_) => ActionState::Planned(Action::PlaceNixConfiguration(v)),
ActionState::Reverted(_) => ActionState::Reverted(Action::PlaceNixConfiguration(v)),
}
}
}
#[derive(Debug, thiserror::Error, Serialize)]
pub enum PlaceNixConfigurationError {
}

View file

@ -1,9 +1,10 @@
use crate::{execute_command, HarmonicError}; use crate::{execute_command, HarmonicError, actions::{ActionState, Action}};
use glob::glob; use glob::glob;
use serde::Serialize;
use tokio::process::Command; use tokio::process::Command;
use crate::actions::{ActionDescription, Actionable, Revertable}; use crate::actions::{ActionDescription, Actionable};
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct SetupDefaultProfile { pub struct SetupDefaultProfile {
@ -18,8 +19,8 @@ impl SetupDefaultProfile {
} }
#[async_trait::async_trait] #[async_trait::async_trait]
impl<'a> Actionable<'a> for SetupDefaultProfile { impl Actionable for ActionState<SetupDefaultProfile> {
type Receipt = SetupDefaultProfileReceipt; type Error = SetupDefaultProfileError;
fn description(&self) -> Vec<ActionDescription> { fn description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new( vec![ActionDescription::new(
"Setup the default Nix profile".to_string(), "Setup the default Nix profile".to_string(),
@ -28,7 +29,7 @@ impl<'a> Actionable<'a> for SetupDefaultProfile {
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn execute(self) -> Result<Self::Receipt, HarmonicError> { async fn execute(&mut self) -> Result<(), Self::Error> {
let Self { channels } = self; let Self { channels } = self;
tracing::info!("Setting up default profile"); tracing::info!("Setting up default profile");
@ -110,26 +111,30 @@ impl<'a> Actionable<'a> for SetupDefaultProfile {
false => return Err(HarmonicError::CommandFailedStatus(command_str)), false => return Err(HarmonicError::CommandFailedStatus(command_str)),
} }
} }
Ok(Self::Receipt {}) Ok(())
} }
}
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct SetupDefaultProfileReceipt {}
#[async_trait::async_trait]
impl<'a> Revertable<'a> for SetupDefaultProfileReceipt {
fn description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(
"Unset the default Nix profile".to_string(),
vec!["TODO".to_string()],
)]
}
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn revert(self) -> Result<(), HarmonicError> { async fn revert(&mut self) -> Result<(), Self::Error> {
todo!(); todo!();
Ok(()) Ok(())
} }
} }
impl From<ActionState<SetupDefaultProfile>> for ActionState<Action> {
fn from(v: ActionState<SetupDefaultProfile>) -> Self {
match v {
ActionState::Completed(_) => ActionState::Completed(Action::SetupDefaultProfile(v)),
ActionState::Planned(_) => ActionState::Planned(Action::SetupDefaultProfile(v)),
ActionState::Reverted(_) => ActionState::Reverted(Action::SetupDefaultProfile(v)),
}
}
}
#[derive(Debug, thiserror::Error, Serialize)]
pub enum SetupDefaultProfileError {
}

View file

@ -1,8 +1,10 @@
use serde::Serialize;
use tokio::process::Command; use tokio::process::Command;
use crate::actions::meta::StartNixDaemon;
use crate::{execute_command, HarmonicError}; use crate::{execute_command, HarmonicError};
use crate::actions::{ActionDescription, Actionable, Revertable}; use crate::actions::{ActionDescription, Actionable, ActionState, Action, ActionError};
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct StartSystemdUnit { pub struct StartSystemdUnit {
@ -11,56 +13,83 @@ pub struct StartSystemdUnit {
impl StartSystemdUnit { impl StartSystemdUnit {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
pub async fn plan(unit: String) -> Result<Self, HarmonicError> { pub async fn plan(unit: String) -> Result<ActionState<Self>, StartSystemdUnitError> {
Ok(Self { unit }) Ok(ActionState::Planned(Self { unit }))
} }
} }
#[async_trait::async_trait] #[async_trait::async_trait]
impl<'a> Actionable<'a> for StartSystemdUnit { impl Actionable for ActionState<StartSystemdUnit> {
type Receipt = StartSystemdUnitReceipt; type Error = StartSystemdUnitError;
fn description(&self) -> Vec<ActionDescription> { fn description(&self) -> Vec<ActionDescription> {
vec![ match self {
ActionDescription::new( ActionState::Planned(v) => vec![
"Start the systemd Nix service and socket".to_string(), ActionDescription::new(
vec![ "Start the systemd Nix service and socket".to_string(),
"The `nix` command line tool communicates with a running Nix daemon managed by your init system".to_string() vec![
] "The `nix` command line tool communicates with a running Nix daemon managed by your init system".to_string()
), ]
] ),
],
ActionState::Completed(_) => vec![
ActionDescription::new(
"Stop the systemd Nix service and socket".to_string(),
vec![
"The `nix` command line tool communicates with a running Nix daemon managed by your init system".to_string()
]
),
],
ActionState::Reverted(_) => vec![
ActionDescription::new(
"Stopped the systemd Nix service and socket".to_string(),
vec![
"The `nix` command line tool communicates with a running Nix daemon managed by your init system".to_string()
]
),
],
}
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn execute(self) -> Result<Self::Receipt, HarmonicError> { async fn execute(&mut self) -> Result<(), ActionError> {
let Self { unit } = self; let StartSystemdUnit { unit } = match self {
ActionState::Completed(_) => return Err(ActionError::AlreadyExecuted(self.clone().into())),
ActionState::Reverted(_) => return Err(ActionError::AlreadyReverted(self.clone().into())),
ActionState::Planned(v) => v,
};
// TODO(@Hoverbear): Handle proxy vars // TODO(@Hoverbear): Handle proxy vars
execute_command( execute_command(
Command::new("systemctl") Command::new("systemctl")
.arg("enable") .arg("enable")
.arg("--now") .arg("--now")
.arg(format!("{unit}")), .arg(format!("{unit}")),
false,
) )
.await?; .await.map_err(StartSystemdUnitError::Command)?;
Ok(Self::Receipt {}) Ok(())
}
}
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct StartSystemdUnitReceipt {}
#[async_trait::async_trait]
impl<'a> Revertable<'a> for StartSystemdUnitReceipt {
fn description(&self) -> Vec<ActionDescription> {
todo!()
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn revert(self) -> Result<(), HarmonicError> { async fn revert(&mut self) -> Result<(), ActionError> {
todo!(); todo!();
Ok(()) Ok(())
} }
} }
impl From<ActionState<StartSystemdUnit>> for ActionState<Action> {
fn from(v: ActionState<StartSystemdUnit>) -> Self {
match v {
ActionState::Completed(_) => ActionState::Completed(Action::StartSystemdUnit(v)),
ActionState::Planned(_) => ActionState::Planned(Action::StartSystemdUnit(v)),
ActionState::Reverted(_) => ActionState::Reverted(Action::StartSystemdUnit(v)),
}
}
}
#[derive(Debug, thiserror::Error, Serialize)]
pub enum StartSystemdUnitError {
#[error("Failed to execute command")]
#[serde(serialize_with = "crate::serialize_std_io_error_to_display")]
Command(#[source] std::io::Error)
}

View file

@ -1,13 +1,15 @@
use serde::Serialize;
use crate::actions::{ use crate::actions::{
base::{ base::{
ConfigureNixDaemonService, ConfigureNixDaemonServiceReceipt, PlaceNixConfiguration, ConfigureNixDaemonService, ConfigureNixDaemonServiceError, PlaceNixConfiguration,
PlaceNixConfigurationReceipt, SetupDefaultProfile, SetupDefaultProfileReceipt, PlaceChannelConfiguration, PlaceChannelConfigurationReceipt, PlaceNixConfigurationError, SetupDefaultProfile, SetupDefaultProfileError, PlaceChannelConfiguration, PlaceChannelConfigurationError,
}, },
meta::{ConfigureShellProfile, ConfigureShellProfileReceipt}, meta::{ConfigureShellProfile, ConfigureShellProfileError}, ActionState, Action,
}; };
use crate::{HarmonicError, InstallSettings}; use crate::{HarmonicError, InstallSettings};
use crate::actions::{ActionDescription, Actionable, Revertable}; use crate::actions::{ActionDescription, Actionable};
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct ConfigureNix { pub struct ConfigureNix {
@ -20,7 +22,7 @@ pub struct ConfigureNix {
impl ConfigureNix { impl ConfigureNix {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
pub async fn plan(settings: InstallSettings) -> Result<Self, HarmonicError> { pub async fn plan(settings: InstallSettings) -> Result<ActionState<Self>, ConfigureNixError> {
let channels = settings let channels = settings
.channels .channels
.iter() .iter()
@ -51,8 +53,8 @@ impl ConfigureNix {
} }
#[async_trait::async_trait] #[async_trait::async_trait]
impl<'a> Actionable<'a> for ConfigureNix { impl Actionable for ActionState<ConfigureNix> {
type Receipt = ConfigureNixReceipt; type Error = ConfigureNixError;
fn description(&self) -> Vec<ActionDescription> { fn description(&self) -> Vec<ActionDescription> {
let Self { let Self {
setup_default_profile, setup_default_profile,
@ -74,7 +76,7 @@ impl<'a> Actionable<'a> for ConfigureNix {
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn execute(self) -> Result<Self::Receipt, HarmonicError> { async fn execute(&mut self) -> Result<(), Self::Error> {
let Self { let Self {
setup_default_profile, setup_default_profile,
configure_nix_daemon_service, configure_nix_daemon_service,
@ -114,34 +116,27 @@ impl<'a> Actionable<'a> for ConfigureNix {
configure_shell_profile, configure_shell_profile,
}) })
} }
}
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct ConfigureNixReceipt {
setup_default_profile: SetupDefaultProfileReceipt,
configure_shell_profile: Option<ConfigureShellProfileReceipt>,
place_nix_configuration: PlaceNixConfigurationReceipt,
place_channel_configuration: PlaceChannelConfigurationReceipt,
configure_nix_daemon_service: ConfigureNixDaemonServiceReceipt,
}
#[async_trait::async_trait]
impl<'a> Revertable<'a> for ConfigureNixReceipt {
fn description(&self) -> Vec<ActionDescription> {
vec![
ActionDescription::new(
"Stop the systemd Nix daemon".to_string(),
vec![
"The `nix` command line tool communicates with a running Nix daemon managed by your init system".to_string()
]
),
]
}
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn revert(self) -> Result<(), HarmonicError> { async fn revert(&mut self) -> Result<(), Self::Error> {
todo!(); todo!();
Ok(()) Ok(())
} }
} }
impl From<ActionState<ConfigureNix>> for ActionState<Action> {
fn from(v: ActionState<ConfigureNix>) -> Self {
match v {
ActionState::Completed(_) => ActionState::Completed(Action::ConfigureNix(v)),
ActionState::Planned(_) => ActionState::Planned(Action::ConfigureNix(v)),
ActionState::Reverted(_) => ActionState::Reverted(Action::ConfigureNix(v)),
}
}
}
#[derive(Debug, thiserror::Error, Serialize)]
pub enum ConfigureNixError {
}

View file

@ -1,11 +1,12 @@
use std::path::Path; use std::path::Path;
use serde::Serialize;
use tokio::task::JoinSet; use tokio::task::JoinSet;
use crate::HarmonicError; use crate::HarmonicError;
use crate::actions::base::{CreateOrAppendFile, CreateOrAppendFileReceipt}; use crate::actions::base::{CreateOrAppendFile, CreateOrAppendFileError};
use crate::actions::{ActionDescription, Actionable, Revertable}; use crate::actions::{ActionDescription, Actionable, ActionState, Action};
const PROFILE_TARGETS: &[&str] = &[ const PROFILE_TARGETS: &[&str] = &[
"/etc/bashrc", "/etc/bashrc",
@ -24,7 +25,7 @@ pub struct ConfigureShellProfile {
impl ConfigureShellProfile { impl ConfigureShellProfile {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
pub async fn plan() -> Result<Self, HarmonicError> { pub async fn plan() -> Result<ActionState<Self>, ConfigureShellProfileError> {
let mut create_or_append_files = Vec::default(); let mut create_or_append_files = Vec::default();
for profile_target in PROFILE_TARGETS { for profile_target in PROFILE_TARGETS {
let path = Path::new(profile_target); let path = Path::new(profile_target);
@ -54,8 +55,8 @@ impl ConfigureShellProfile {
} }
#[async_trait::async_trait] #[async_trait::async_trait]
impl<'a> Actionable<'a> for ConfigureShellProfile { impl Actionable for ActionState<ConfigureShellProfile> {
type Receipt = ConfigureShellProfileReceipt; type Error = ConfigureShellProfileError;
fn description(&self) -> Vec<ActionDescription> { fn description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new( vec![ActionDescription::new(
"Configure the shell profiles".to_string(), "Configure the shell profiles".to_string(),
@ -64,7 +65,7 @@ impl<'a> Actionable<'a> for ConfigureShellProfile {
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn execute(self) -> Result<Self::Receipt, HarmonicError> { async fn execute(&mut self) -> Result<(), Self::Error> {
let Self { let Self {
create_or_append_files, create_or_append_files,
} = self; } = self;
@ -81,7 +82,7 @@ impl<'a> Actionable<'a> for ConfigureShellProfile {
while let Some(result) = set.join_next().await { while let Some(result) = set.join_next().await {
match result { match result {
Ok(Ok(success)) => successes.push(success), Ok(Ok(())) => (),
Ok(Err(e)) => errors.push(e), Ok(Err(e)) => errors.push(e),
Err(e) => errors.push(e.into()), Err(e) => errors.push(e.into()),
}; };
@ -95,27 +96,29 @@ impl<'a> Actionable<'a> for ConfigureShellProfile {
} }
} }
Ok(Self::Receipt { Ok(())
create_or_append_files: successes,
})
} }
}
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct ConfigureShellProfileReceipt {
create_or_append_files: Vec<CreateOrAppendFileReceipt>,
}
#[async_trait::async_trait]
impl<'a> Revertable<'a> for ConfigureShellProfileReceipt {
fn description(&self) -> Vec<ActionDescription> {
todo!()
}
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn revert(self) -> Result<(), HarmonicError> { async fn revert(&mut self) -> Result<(), Self::Error> {
todo!(); todo!();
Ok(()) Ok(())
} }
} }
impl From<ActionState<ConfigureShellProfile>> for ActionState<Action> {
fn from(v: ActionState<ConfigureShellProfile>) -> Self {
match v {
ActionState::Completed(_) => ActionState::Completed(Action::ConfigureShellProfile(v)),
ActionState::Planned(_) => ActionState::Planned(Action::ConfigureShellProfile(v)),
ActionState::Reverted(_) => ActionState::Reverted(Action::ConfigureShellProfile(v)),
}
}
}
#[derive(Debug, thiserror::Error, Serialize)]
pub enum ConfigureShellProfileError {
}

View file

@ -1,7 +1,9 @@
use serde::Serialize;
use crate::HarmonicError; use crate::HarmonicError;
use crate::actions::base::{CreateDirectory, CreateDirectoryReceipt}; use crate::actions::base::{CreateDirectory, CreateDirectoryError};
use crate::actions::{ActionDescription, Actionable, Revertable}; use crate::actions::{ActionDescription, Actionable, ActionState, Action};
const PATHS: &[&str] = &[ const PATHS: &[&str] = &[
"/nix", "/nix",
@ -41,8 +43,8 @@ impl CreateNixTree {
} }
#[async_trait::async_trait] #[async_trait::async_trait]
impl<'a> Actionable<'a> for CreateNixTree { impl Actionable for ActionState<CreateNixTree> {
type Receipt = CreateNixTreeReceipt; type Error = CreateNixTreeError;
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`"),
@ -54,36 +56,38 @@ impl<'a> Actionable<'a> for CreateNixTree {
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn execute(self) -> Result<Self::Receipt, HarmonicError> { async fn execute(&mut self) -> Result<(), HarmonicError> {
let Self { create_directories } = self; let Self { create_directories } = self;
let mut successes = Vec::with_capacity(create_directories.len()); let mut successes = Vec::with_capacity(create_directories.len());
// Just do sequential since parallizing this will have little benefit // Just do sequential since parallizing this will have little benefit
for create_directory in create_directories { for create_directory in create_directories {
successes.push(create_directory.execute().await?) create_directory.execute().await?
} }
Ok(CreateNixTreeReceipt { Ok(())
create_directories: successes,
})
} }
}
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct CreateNixTreeReceipt {
create_directories: Vec<CreateDirectoryReceipt>,
}
#[async_trait::async_trait]
impl<'a> Revertable<'a> for CreateNixTreeReceipt {
fn description(&self) -> Vec<ActionDescription> {
todo!()
}
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn revert(self) -> Result<(), HarmonicError> { async fn revert(&mut self) -> Result<(), Self::Error> {
todo!(); todo!();
Ok(()) Ok(())
} }
} }
impl From<ActionState<CreateNixTree>> for ActionState<Action> {
fn from(v: ActionState<CreateNixTree>) -> Self {
match v {
ActionState::Completed(_) => ActionState::Completed(Action::CreateNixTree(v)),
ActionState::Planned(_) => ActionState::Planned(Action::CreateNixTree(v)),
ActionState::Reverted(_) => ActionState::Reverted(Action::CreateNixTree(v)),
}
}
}
#[derive(Debug, thiserror::Error, Serialize)]
pub enum CreateNixTreeError {
}

View file

@ -1,9 +1,10 @@
use serde::Serialize;
use tokio::task::JoinSet; use tokio::task::JoinSet;
use crate::{HarmonicError, InstallSettings}; use crate::{HarmonicError, InstallSettings};
use crate::actions::base::{CreateGroup, CreateGroupReceipt, CreateUserReceipt}; use crate::actions::base::{CreateGroup, CreateGroupError, CreateUserError};
use crate::actions::{ActionDescription, Actionable, CreateUser, Revertable}; use crate::actions::{ActionDescription, Actionable, CreateUser, ActionState, Action};
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct CreateUsersAndGroup { pub struct CreateUsersAndGroup {
@ -47,8 +48,8 @@ impl CreateUsersAndGroup {
} }
#[async_trait::async_trait] #[async_trait::async_trait]
impl<'a> Actionable<'a> for CreateUsersAndGroup { impl Actionable for ActionState<CreateUsersAndGroup> {
type Receipt = CreateUsersAndGroupReceipt; type Error = CreateUsersAndGroupError;
fn description(&self) -> Vec<ActionDescription> { fn description(&self) -> Vec<ActionDescription> {
let Self { let Self {
daemon_user_count, daemon_user_count,
@ -72,7 +73,7 @@ impl<'a> Actionable<'a> for CreateUsersAndGroup {
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn execute(self) -> Result<Self::Receipt, HarmonicError> { async fn execute(&mut self) -> Result<(), HarmonicError> {
let Self { let Self {
create_users, create_users,
create_group, create_group,
@ -114,24 +115,29 @@ impl<'a> Actionable<'a> for CreateUsersAndGroup {
create_users: successes, 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!()
}
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn revert(self) -> Result<(), HarmonicError> { async fn revert(&mut self) -> Result<(), Self::Error> {
todo!(); todo!();
Ok(()) Ok(())
} }
} }
impl From<ActionState<CreateUsersAndGroup>> for ActionState<Action> {
fn from(v: ActionState<CreateUsersAndGroup>) -> Self {
match v {
ActionState::Completed(_) => ActionState::Completed(Action::CreateUsersAndGroup(v)),
ActionState::Planned(_) => ActionState::Planned(Action::CreateUsersAndGroup(v)),
ActionState::Reverted(_) => ActionState::Reverted(Action::CreateUsersAndGroup(v)),
}
}
}
#[derive(Debug, thiserror::Error, Serialize)]
pub enum CreateUsersAndGroupError {
}

View file

@ -7,9 +7,9 @@ mod create_users_and_group;
mod provision_nix; mod provision_nix;
mod start_nix_daemon; mod start_nix_daemon;
pub use configure_nix::{ConfigureNix, ConfigureNixReceipt}; pub use configure_nix::{ConfigureNix, ConfigureNixError};
pub use configure_shell_profile::{ConfigureShellProfile, ConfigureShellProfileReceipt}; pub use configure_shell_profile::{ConfigureShellProfile, ConfigureShellProfileError};
pub use create_nix_tree::{CreateNixTree, CreateNixTreeReceipt}; pub use create_nix_tree::{CreateNixTree, CreateNixTreeError};
pub use create_users_and_group::{CreateUsersAndGroup, CreateUsersAndGroupReceipt}; pub use create_users_and_group::{CreateUsersAndGroup, CreateUsersAndGroupError};
pub use provision_nix::{ProvisionNix, ProvisionNixReceipt}; pub use provision_nix::{ProvisionNix, ProvisionNixError};
pub use start_nix_daemon::{StartNixDaemon, StartNixDaemonReceipt}; pub use start_nix_daemon::{StartNixDaemon, StartNixDaemonError};

View file

@ -1,13 +1,14 @@
use serde::Serialize;
use tempdir::TempDir; use tempdir::TempDir;
use crate::actions::base::{FetchNix, FetchNixReceipt, MoveUnpackedNix, MoveUnpackedNixReceipt}; use crate::actions::base::{FetchNix, FetchNixError, MoveUnpackedNix, MoveUnpackedNixError};
use crate::{HarmonicError, InstallSettings}; use crate::{HarmonicError, InstallSettings};
use crate::actions::{ActionDescription, Actionable, Revertable}; use crate::actions::{ActionDescription, Actionable, ActionState, Action};
use super::{ use super::{
CreateNixTree, CreateNixTreeReceipt, CreateNixTree, CreateNixTreeError,
CreateUsersAndGroup, CreateUsersAndGroupReceipt, CreateUsersAndGroup, CreateUsersAndGroupError,
}; };
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
@ -20,8 +21,8 @@ pub struct ProvisionNix {
impl ProvisionNix { impl ProvisionNix {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
pub async fn plan(settings: InstallSettings) -> Result<Self, HarmonicError> { pub async fn plan(settings: InstallSettings) -> Result<ActionState<Self>, ProvisionNixError> {
let tempdir = TempDir::new("nix").map_err(HarmonicError::TempDir)?; let tempdir = TempDir::new("nix").map_err(ProvisionNixError::TempDir)?;
let fetch_nix = FetchNix::plan( let fetch_nix = FetchNix::plan(
settings.nix_package_url.clone(), settings.nix_package_url.clone(),
@ -31,19 +32,24 @@ impl ProvisionNix {
let create_users_and_group = CreateUsersAndGroup::plan(settings.clone()).await?; let create_users_and_group = CreateUsersAndGroup::plan(settings.clone()).await?;
let create_nix_tree = CreateNixTree::plan(settings.force).await?; let create_nix_tree = CreateNixTree::plan(settings.force).await?;
let move_unpacked_nix = MoveUnpackedNix::plan(tempdir.path().to_path_buf()).await?; let move_unpacked_nix = MoveUnpackedNix::plan(tempdir.path().to_path_buf()).await?;
Ok(Self { Ok(ActionState::Planned(Self {
fetch_nix, fetch_nix,
create_users_and_group, create_users_and_group,
create_nix_tree, create_nix_tree,
move_unpacked_nix, move_unpacked_nix,
}) }))
} }
} }
#[async_trait::async_trait] #[async_trait::async_trait]
impl<'a> Actionable<'a> for ProvisionNix { impl Actionable for ActionState<ProvisionNix> {
type Receipt = ProvisionNixReceipt; type Error = ProvisionNixError;
fn description(&self) -> Vec<ActionDescription> { fn description(&self) -> Vec<ActionDescription> {
match self {
ActionState::Completed(action) => action.start_systemd_socket.description(),
ActionState::Planned(action) => action.start_systemd_socket.description(),
ActionState::Reverted(_) => todo!(),
}
let Self { let Self {
fetch_nix, fetch_nix,
create_users_and_group, create_users_and_group,
@ -60,7 +66,7 @@ impl<'a> Actionable<'a> for ProvisionNix {
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn execute(self) -> Result<Self::Receipt, HarmonicError> { async fn execute(&mut self) -> Result<(), Self::Error> {
let Self { let Self {
fetch_nix, fetch_nix,
create_nix_tree, create_nix_tree,
@ -71,39 +77,37 @@ impl<'a> Actionable<'a> for ProvisionNix {
// We fetch nix while doing the rest, then move it over. // We fetch nix while doing the rest, then move it over.
let fetch_nix_handle = tokio::spawn(async move { fetch_nix.execute().await }); let fetch_nix_handle = tokio::spawn(async move { fetch_nix.execute().await });
let create_users_and_group = create_users_and_group.execute().await?; create_users_and_group.execute().await?;
let create_nix_tree = create_nix_tree.execute().await?; create_nix_tree.execute().await?;
let fetch_nix = fetch_nix_handle.await??; fetch_nix_handle.await??;
let move_unpacked_nix = move_unpacked_nix.execute().await?; move_unpacked_nix.execute().await?;
Ok(ProvisionNixReceipt { Ok(())
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!()
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn revert(self) -> Result<(), HarmonicError> { async fn revert(&mut self) -> Result<(), Self::Error> {
todo!(); todo!();
Ok(()) Ok(())
} }
} }
impl From<ActionState<ProvisionNix>> for ActionState<Action> {
fn from(v: ActionState<ProvisionNix>) -> Self {
match v {
ActionState::Completed(_) => ActionState::Completed(Action::ProvisionNix(v)),
ActionState::Planned(_) => ActionState::Planned(Action::ProvisionNix(v)),
ActionState::Reverted(_) => ActionState::Reverted(Action::ProvisionNix(v)),
}
}
}
#[derive(Debug, thiserror::Error, Serialize)]
pub enum ProvisionNixError {
#[error("Failed create tempdir")]
#[serde(serialize_with = "crate::serialize_std_io_error_to_display")]
TempDir(#[source] std::io::Error)
}

View file

@ -1,62 +1,80 @@
use crate::actions::base::{StartSystemdUnit, StartSystemdUnitReceipt}; use serde::Serialize;
use crate::actions::base::{StartSystemdUnit, StartSystemdUnitError};
use crate::HarmonicError; use crate::HarmonicError;
use crate::actions::{ActionDescription, Actionable, Revertable}; use crate::actions::{ActionDescription, Actionable, ActionState, Action, ActionError};
/// This is mostly indirection for supporting non-systemd /// This is mostly indirection for supporting non-systemd
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct StartNixDaemon { pub struct StartNixDaemon {
start_systemd_socket: StartSystemdUnit, start_systemd_socket: ActionState<StartSystemdUnit>,
} }
impl StartNixDaemon { impl StartNixDaemon {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
pub async fn plan() -> Result<Self, HarmonicError> { pub async fn plan() -> Result<ActionState<Self>, StartNixDaemonError> {
let start_systemd_socket = StartSystemdUnit::plan("nix-daemon.socket".into()).await?; let start_systemd_socket = StartSystemdUnit::plan("nix-daemon.socket".into()).await?;
let start_systemd_service = StartSystemdUnit::plan("nix-daemon.service".into()).await?; Ok(ActionState::Planned(Self {
Ok(Self {
start_systemd_socket, start_systemd_socket,
}) }))
} }
} }
#[async_trait::async_trait] #[async_trait::async_trait]
impl<'a> Actionable<'a> for StartNixDaemon { impl Actionable for ActionState<StartNixDaemon> {
type Receipt = StartNixDaemonReceipt; type Error = StartNixDaemonError;
fn description(&self) -> Vec<ActionDescription> { fn description(&self) -> Vec<ActionDescription> {
let Self { let StartNixDaemon { start_systemd_socket } = match self {
start_systemd_socket, ActionState::Completed(v) | ActionState::Reverted(v) | ActionState::Planned(v) => v,
} = &self; };
start_systemd_socket.description() start_systemd_socket.description()
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn execute(self) -> Result<Self::Receipt, HarmonicError> { async fn execute(self) -> Result<Self, ActionError> {
let Self { let StartNixDaemon { start_systemd_socket } = match self {
start_systemd_socket, ActionState::Planned(v) => v,
} = self; ActionState::Completed(_) => return Err(ActionError::AlreadyExecuted(self.clone().into())),
let start_systemd_socket = start_systemd_socket.execute().await?; ActionState::Reverted(_) => return Err(ActionError::AlreadyReverted(self.clone().into())),
Ok(Self::Receipt { };
start_systemd_socket,
})
}
}
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] start_systemd_socket.execute().await?;
pub struct StartNixDaemonReceipt {
start_systemd_socket: StartSystemdUnitReceipt,
}
#[async_trait::async_trait] Ok(Self::Completed(StartNixDaemon {
impl<'a> Revertable<'a> for StartNixDaemonReceipt { start_systemd_socket,
fn description(&self) -> Vec<ActionDescription> { }))
todo!()
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn revert(self) -> Result<(), HarmonicError> { async fn revert(self) -> Result<Self, ActionError> {
todo!(); let StartNixDaemon { start_systemd_socket } = match self {
ActionState::Planned(v) => return Err(ActionError::NotExecuted(self.clone().into())),
ActionState::Completed(v) => v,
ActionState::Reverted(_) => return Err(ActionError::AlreadyReverted(self.clone().into())),
};
Ok(()) start_systemd_socket.revert().await?;
Ok(Self::Reverted(StartNixDaemon {
start_systemd_socket,
}))
} }
} }
impl From<ActionState<StartNixDaemon>> for ActionState<Action> {
fn from(v: ActionState<StartNixDaemon>) -> Self {
match v {
ActionState::Completed(_) => ActionState::Completed(Action::StartNixDaemon(v)),
ActionState::Planned(_) => ActionState::Planned(Action::StartNixDaemon(v)),
ActionState::Reverted(_) => ActionState::Reverted(Action::StartNixDaemon(v)),
}
}
}
#[derive(Debug, thiserror::Error, Serialize)]
pub enum StartNixDaemonError {
#[error(transparent)]
StartSystemdUnit(#[from] StartSystemdUnitError)
}

View file

@ -1,33 +1,45 @@
pub mod base; pub mod base;
pub mod meta; pub mod meta;
use std::{error::Error, fmt::Display};
use base::{ use base::{
ConfigureNixDaemonService, ConfigureNixDaemonServiceReceipt, CreateDirectory, ConfigureNixDaemonService, ConfigureNixDaemonServiceError, CreateDirectory,
CreateDirectoryReceipt, CreateFile, CreateFileReceipt, CreateGroup, CreateGroupReceipt, CreateDirectoryError, CreateFile, CreateFileError, CreateGroup, CreateGroupError,
CreateOrAppendFile, CreateOrAppendFileReceipt, CreateUser, CreateUserReceipt, FetchNix, CreateOrAppendFile, CreateOrAppendFileError, CreateUser, CreateUserError, FetchNix,
FetchNixReceipt, MoveUnpackedNix, MoveUnpackedNixReceipt, PlaceChannelConfiguration, FetchNixError, MoveUnpackedNix, MoveUnpackedNixError, PlaceChannelConfiguration,
PlaceChannelConfigurationReceipt, PlaceNixConfiguration, PlaceNixConfigurationReceipt, PlaceChannelConfigurationError, PlaceNixConfiguration, PlaceNixConfigurationError,
SetupDefaultProfile, SetupDefaultProfileReceipt, SetupDefaultProfile, SetupDefaultProfileError,
}; };
use meta::{ use meta::{
ConfigureNix, ConfigureNixReceipt, ConfigureShellProfile, ConfigureShellProfileReceipt, ConfigureNix, ConfigureNixError, ConfigureShellProfile, ConfigureShellProfileError,
CreateNixTree, CreateNixTreeReceipt, CreateUsersAndGroup, CreateUsersAndGroupReceipt, CreateNixTree, CreateNixTreeError, CreateUsersAndGroup, CreateUsersAndGroupError,
ProvisionNix, ProvisionNixReceipt, StartNixDaemon, StartNixDaemonReceipt, ProvisionNix, ProvisionNixError, StartNixDaemon, StartNixDaemonError,
}; };
use serde::{Deserialize, de::DeserializeOwned, Serialize};
use crate::HarmonicError;
use self::base::{StartSystemdUnit, StartSystemdUnitError};
#[async_trait::async_trait] #[async_trait::async_trait]
pub trait Actionable<'a>: serde::de::Deserialize<'a> + serde::Serialize { pub trait Actionable: DeserializeOwned + Serialize + Into<ActionState<Action>> {
type Receipt; type Error: std::error::Error + std::fmt::Debug + Serialize;
fn description(&self) -> Vec<ActionDescription>; fn description(&self) -> Vec<ActionDescription>;
async fn execute(self) -> Result<Self::Receipt, HarmonicError>;
// They should also have an `async fn plan(args...) -> Result<ActionState<Self>, Self::Error>;`
async fn execute(self) -> Result<Self, ActionError>;
async fn revert(self) -> Result<Self, ActionError>;
} }
#[async_trait::async_trait] #[derive(thiserror::Error, Debug, Serialize, Deserialize, Clone)]
pub trait Revertable<'a>: serde::de::Deserialize<'a> + serde::Serialize { pub enum ActionState<P> where P: Serialize + DeserializeOwned + Clone {
fn description(&self) -> Vec<ActionDescription>; #[serde(bound = "P: DeserializeOwned")]
async fn revert(self) -> Result<(), HarmonicError>; Completed(P),
#[serde(bound = "P: DeserializeOwned")]
Planned(P),
#[serde(bound = "P: DeserializeOwned")]
Reverted(P),
} }
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
@ -48,51 +60,80 @@ impl ActionDescription {
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub enum Action { pub enum Action {
ConfigureNixDaemonService(ConfigureNixDaemonService), ConfigureNixDaemonService(ActionState<ConfigureNixDaemonService>),
ConfigureNix(ConfigureNix), ConfigureNix(ActionState<ConfigureNix>),
ConfigureShellProfile(ConfigureShellProfile), ConfigureShellProfile(ActionState<ConfigureShellProfile>),
CreateDirectory(CreateDirectory), CreateDirectory(ActionState<CreateDirectory>),
CreateFile(CreateFile), CreateFile(ActionState<CreateFile>),
CreateGroup(CreateGroup), CreateGroup(ActionState<CreateGroup>),
CreateOrAppendFile(CreateOrAppendFile), CreateOrAppendFile(ActionState<CreateOrAppendFile>),
CreateNixTree(CreateNixTree), CreateNixTree(ActionState<CreateNixTree>),
CreateUser(CreateUser), CreateUser(ActionState<CreateUser>),
CreateUsersAndGroup(CreateUsersAndGroup), CreateUsersAndGroup(ActionState<CreateUsersAndGroup>),
FetchNix(FetchNix), FetchNix(ActionState<FetchNix>),
MoveUnpackedNix(MoveUnpackedNix), MoveUnpackedNix(ActionState<MoveUnpackedNix>),
PlaceChannelConfiguration(PlaceChannelConfiguration), PlaceChannelConfiguration(ActionState<PlaceChannelConfiguration>),
PlaceNixConfiguration(PlaceNixConfiguration), PlaceNixConfiguration(ActionState<PlaceNixConfiguration>),
SetupDefaultProfile(SetupDefaultProfile), SetupDefaultProfile(ActionState<SetupDefaultProfile>),
StartNixDaemon(StartNixDaemon), StartNixDaemon(ActionState<StartNixDaemon>),
ProvisionNix(ProvisionNix), StartSystemdUnit(ActionState<StartSystemdUnit>),
ProvisionNix(ActionState<ProvisionNix>),
} }
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] #[derive(Debug, thiserror::Error, serde::Serialize)]
pub enum ActionReceipt { pub enum ActionError {
ConfigureNixDaemonService(ConfigureNixDaemonServiceReceipt), #[error("Attempted to revert an unexecuted action")]
ConfigureNix(ConfigureNixReceipt), NotExecuted(ActionState<Action>),
ConfigureShellProfile(ConfigureShellProfileReceipt), #[error("Attempted to execute an already executed action")]
CreateDirectory(CreateDirectoryReceipt), AlreadyExecuted(ActionState<Action>),
CreateFile(CreateFileReceipt), #[error("Attempted to revert an already reverted action")]
CreateGroup(CreateGroupReceipt), AlreadyReverted(ActionState<Action>),
CreateOrAppendFile(CreateOrAppendFileReceipt), #[error(transparent)]
CreateNixTree(CreateNixTreeReceipt), ConfigureNixDaemonService(#[from] ConfigureNixDaemonServiceError),
CreateUser(CreateUserReceipt), #[error(transparent)]
CreateUsersAndGroup(CreateUsersAndGroupReceipt), ConfigureNix(#[from] ConfigureNixError),
FetchNix(FetchNixReceipt), #[error(transparent)]
MoveUnpackedNix(MoveUnpackedNixReceipt), ConfigureShellProfile(#[from] ConfigureShellProfileError),
PlaceChannelConfiguration(PlaceChannelConfigurationReceipt), #[error(transparent)]
PlaceNixConfiguration(PlaceNixConfigurationReceipt), CreateDirectory(#[from] CreateDirectoryError),
SetupDefaultProfile(SetupDefaultProfileReceipt), #[error(transparent)]
StartNixDaemon(StartNixDaemonReceipt), CreateFile(#[from] CreateFileError),
ProvisionNix(ProvisionNixReceipt), #[error(transparent)]
CreateGroup(#[from] CreateGroupError),
#[error(transparent)]
CreateOrAppendFile(#[from] CreateOrAppendFileError),
#[error(transparent)]
CreateNixTree(#[from] CreateNixTreeError),
#[error(transparent)]
CreateUser(#[from] CreateUserError),
#[error(transparent)]
CreateUsersAndGroup(#[from] CreateUsersAndGroupError),
#[error(transparent)]
FetchNix(#[from] FetchNixError),
#[error(transparent)]
MoveUnpackedNix(#[from] MoveUnpackedNixError),
#[error(transparent)]
PlaceChannelConfiguration(#[from] PlaceChannelConfigurationError),
#[error(transparent)]
PlaceNixConfiguration(#[from] PlaceNixConfigurationError),
#[error(transparent)]
SetupDefaultProfile(#[from] SetupDefaultProfileError),
#[error(transparent)]
StartNixDaemon(#[from] StartNixDaemonError),
#[error(transparent)]
StartSystemdUnit(#[from] StartSystemdUnitError),
#[error(transparent)]
ProvisionNix(#[from] ProvisionNixError),
} }
#[async_trait::async_trait] #[async_trait::async_trait]
impl<'a> Actionable<'a> for Action { impl Actionable for ActionState<Action> {
type Receipt = ActionReceipt; type Error = ActionError;
fn description(&self) -> Vec<ActionDescription> { fn description(&self) -> Vec<ActionDescription> {
match self { let inner = match self {
ActionState::Planned(p) | ActionState::Completed(p) | ActionState::Reverted(p) => p,
};
match inner {
Action::ConfigureNixDaemonService(i) => i.description(), Action::ConfigureNixDaemonService(i) => i.description(),
Action::ConfigureNix(i) => i.description(), Action::ConfigureNix(i) => i.description(),
Action::ConfigureShellProfile(i) => i.description(), Action::ConfigureShellProfile(i) => i.description(),
@ -109,92 +150,64 @@ impl<'a> Actionable<'a> for Action {
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::StartSystemdUnit(i) => i.description(),
Action::ProvisionNix(i) => i.description(), Action::ProvisionNix(i) => i.description(),
} }
} }
async fn execute(self) -> Result<Self::Receipt, HarmonicError> { async fn execute(&mut self) -> Result<(), Self::Error> {
match self { let inner = match self {
Action::ConfigureNixDaemonService(i) => i ActionState::Completed(p) => todo!(),
.execute() ActionState::Planned(p) => p,
.await ActionState::Reverted(p) => todo!(),
.map(ActionReceipt::ConfigureNixDaemonService), };
Action::ConfigureNix(i) => i.execute().await.map(ActionReceipt::ConfigureNix), match inner {
Action::ConfigureShellProfile(i) => { Action::ConfigureNixDaemonService(i) => i.execute().await,
i.execute().await.map(ActionReceipt::ConfigureShellProfile) Action::ConfigureNix(i) => i.execute().await,
}, Action::ConfigureShellProfile(i) => i.execute().await,
Action::CreateDirectory(i) => i.execute().await.map(ActionReceipt::CreateDirectory), Action::CreateDirectory(i) => i.execute().await,
Action::CreateFile(i) => i.execute().await.map(ActionReceipt::CreateFile), Action::CreateFile(i) => i.execute().await,
Action::CreateGroup(i) => i.execute().await.map(ActionReceipt::CreateGroup), Action::CreateGroup(i) => i.execute().await,
Action::CreateOrAppendFile(i) => { Action::CreateOrAppendFile(i) => i.execute().await,
i.execute().await.map(ActionReceipt::CreateOrAppendFile) Action::CreateNixTree(i) => i.execute().await,
}, Action::CreateUser(i) => i.execute().await,
Action::CreateNixTree(i) => i.execute().await.map(ActionReceipt::CreateNixTree), Action::CreateUsersAndGroup(i) => i.execute().await,
Action::CreateUser(i) => i.execute().await.map(ActionReceipt::CreateUser), Action::FetchNix(i) => i.execute().await,
Action::CreateUsersAndGroup(i) => { Action::MoveUnpackedNix(i) => i.execute().await,
i.execute().await.map(ActionReceipt::CreateUsersAndGroup) Action::PlaceChannelConfiguration(i) => i.execute().await,
}, Action::PlaceNixConfiguration(i) => i.execute().await,
Action::FetchNix(i) => i.execute().await.map(ActionReceipt::FetchNix), Action::SetupDefaultProfile(i) => i.execute().await,
Action::MoveUnpackedNix(i) => i.execute().await.map(ActionReceipt::MoveUnpackedNix), Action::StartNixDaemon(i) => i.execute().await,
Action::PlaceChannelConfiguration(i) => i Action::StartSystemdUnit(i) => i.execute().await,
.execute() Action::ProvisionNix(i) => i.execute().await,
.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::ProvisionNix(i) => i.execute().await.map(ActionReceipt::ProvisionNix),
}
}
}
#[async_trait::async_trait]
impl<'a> Revertable<'a> for ActionReceipt {
fn description(&self) -> Vec<ActionDescription> {
match self {
ActionReceipt::ConfigureNixDaemonService(i) => i.description(),
ActionReceipt::ConfigureNix(i) => i.description(),
ActionReceipt::ConfigureShellProfile(i) => i.description(),
ActionReceipt::CreateDirectory(i) => i.description(),
ActionReceipt::CreateFile(i) => i.description(),
ActionReceipt::CreateGroup(i) => i.description(),
ActionReceipt::CreateOrAppendFile(i) => i.description(),
ActionReceipt::CreateNixTree(i) => i.description(),
ActionReceipt::CreateUser(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::ProvisionNix(i) => i.description(),
} }
} }
async fn revert(self) -> Result<(), HarmonicError> { async fn revert(&mut self) -> Result<(), Self::Error> {
match self { let inner = match self {
ActionReceipt::ConfigureNixDaemonService(i) => i.revert().await, ActionState::Planned(p) => todo!(),
ActionReceipt::ConfigureNix(i) => i.revert().await, ActionState::Completed(p) => p,
ActionReceipt::ConfigureShellProfile(i) => i.revert().await, ActionState::Reverted(p) => todo!(),
ActionReceipt::CreateDirectory(i) => i.revert().await, };
ActionReceipt::CreateFile(i) => i.revert().await, match inner {
ActionReceipt::CreateGroup(i) => i.revert().await, Action::ConfigureNixDaemonService(i) => i.revert().await,
ActionReceipt::CreateOrAppendFile(i) => i.revert().await, Action::ConfigureNix(i) => i.revert().await,
ActionReceipt::CreateNixTree(i) => i.revert().await, Action::ConfigureShellProfile(i) => i.revert().await,
ActionReceipt::CreateUser(i) => i.revert().await, Action::CreateDirectory(i) => i.revert().await,
ActionReceipt::CreateUsersAndGroup(i) => i.revert().await, Action::CreateFile(i) => i.revert().await,
ActionReceipt::FetchNix(i) => i.revert().await, Action::CreateGroup(i) => i.revert().await,
ActionReceipt::MoveUnpackedNix(i) => i.revert().await, Action::CreateOrAppendFile(i) => i.revert().await,
ActionReceipt::PlaceChannelConfiguration(i) => i.revert().await, Action::CreateNixTree(i) => i.revert().await,
ActionReceipt::PlaceNixConfiguration(i) => i.revert().await, Action::CreateUser(i) => i.revert().await,
ActionReceipt::SetupDefaultProfile(i) => i.revert().await, Action::CreateUsersAndGroup(i) => i.revert().await,
ActionReceipt::StartNixDaemon(i) => i.revert().await, Action::FetchNix(i) => i.revert().await,
ActionReceipt::ProvisionNix(i) => i.revert().await, Action::MoveUnpackedNix(i) => i.revert().await,
Action::PlaceChannelConfiguration(i) => i.revert().await,
Action::PlaceNixConfiguration(i) => i.revert().await,
Action::SetupDefaultProfile(i) => i.revert().await,
Action::StartNixDaemon(i) => i.revert().await,
Action::StartSystemdUnit(i) => i.revert().await,
Action::ProvisionNix(i) => i.revert().await,
} }
} }
} }

View file

@ -14,6 +14,7 @@ use std::{
pub use error::HarmonicError; pub use error::HarmonicError;
pub use plan::InstallPlan; pub use plan::InstallPlan;
use serde::Serializer;
pub use settings::InstallSettings; pub use settings::InstallSettings;
use bytes::Buf; use bytes::Buf;
@ -26,402 +27,6 @@ use tokio::{
task::spawn_blocking, task::spawn_blocking,
}; };
// This uses a Rust builder pattern
#[derive(Debug)]
pub struct Harmonic {
dry_run: bool,
daemon_user_count: usize,
channels: Vec<(String, Url)>,
modify_profile: bool,
nix_build_group_name: String,
nix_build_group_id: usize,
nix_build_user_prefix: String,
nix_build_user_id_base: usize,
}
impl Harmonic {
pub fn dry_run(&mut self, dry_run: bool) -> &mut Self {
self.dry_run = dry_run;
self
}
pub fn daemon_user_count(&mut self, count: usize) -> &mut Self {
self.daemon_user_count = count;
self
}
pub fn channels(&mut self, channels: impl IntoIterator<Item = (String, Url)>) -> &mut Self {
self.channels = channels.into_iter().collect();
self
}
pub fn modify_profile(&mut self, toggle: bool) -> &mut Self {
self.modify_profile = toggle;
self
}
}
impl Harmonic {
#[tracing::instrument(skip_all)]
pub async fn fetch_nix(&self) -> Result<(), HarmonicError> {
tracing::info!("Fetching nix");
// TODO(@hoverbear): architecture specific download
// TODO(@hoverbear): hash check
// TODO(@hoverbear): custom url
let tempdir = TempDir::new("nix").map_err(HarmonicError::TempDir)?;
fetch_url_and_unpack_xz(
"https://releases.nixos.org/nix/nix-2.11.0/nix-2.11.0-x86_64-linux.tar.xz",
tempdir.path(),
self.dry_run,
)
.await?;
let found_nix_path = if !self.dry_run {
// TODO(@Hoverbear): I would like to make this less awful
let found_nix_paths = glob::glob(&format!("{}/nix-*", tempdir.path().display()))?
.collect::<Result<Vec<_>, _>>()?;
assert_eq!(
found_nix_paths.len(),
1,
"Did not expect to find multiple nix paths, please report this"
);
found_nix_paths.into_iter().next().unwrap()
} else {
PathBuf::from("/nix/nix-*")
};
rename(found_nix_path.join("store"), "/nix/store", self.dry_run).await?;
Ok(())
}
#[tracing::instrument(skip_all)]
pub async fn create_group(&self) -> Result<(), HarmonicError> {
tracing::info!("Creating group");
execute_command(
Command::new("groupadd")
.arg("-g")
.arg(self.nix_build_group_id.to_string())
.arg("--system")
.arg(&self.nix_build_group_name),
self.dry_run,
)
.await?;
Ok(())
}
#[tracing::instrument(skip_all)]
pub async fn create_users(&self) -> Result<(), HarmonicError> {
tracing::info!("Creating users");
for index in 1..=self.daemon_user_count {
let user_name = format!("{}{index}", self.nix_build_user_prefix);
let user_id = self.nix_build_user_id_base + index;
execute_command(
Command::new("useradd").args([
"--home-dir",
"/var/empty",
"--comment",
&format!("\"Nix build user {user_id}\""),
"--gid",
&self.nix_build_group_name.to_string(),
"--groups",
&self.nix_build_group_name.to_string(),
"--no-user-group",
"--system",
"--shell",
"/sbin/nologin",
"--uid",
&user_id.to_string(),
"--password",
"\"!\"",
&user_name.to_string(),
]),
self.dry_run,
)
.await?;
}
Ok(())
}
#[tracing::instrument(skip_all)]
pub async fn create_directories(&self) -> Result<(), HarmonicError> {
tracing::info!("Creating directories");
create_directory("/nix", self.dry_run).await?;
set_permissions(
"/nix",
None,
Some("root".to_string()),
Some(self.nix_build_group_name.clone()),
self.dry_run,
)
.await?;
let permissions = Permissions::from_mode(0o0755);
let paths = [
"/nix/var",
"/nix/var/log",
"/nix/var/log/nix",
"/nix/var/log/nix/drvs",
"/nix/var/nix",
"/nix/var/nix/db",
"/nix/var/nix/gcroots",
"/nix/var/nix/gcroots/per-user",
"/nix/var/nix/profiles",
"/nix/var/nix/profiles/per-user",
"/nix/var/nix/temproots",
"/nix/var/nix/userpool",
"/nix/var/nix/daemon-socket",
];
for path in paths {
// We use `create_dir` over `create_dir_all` to ensure we always set permissions right
create_directory(path, self.dry_run).await?;
set_permissions(path, Some(permissions.clone()), None, None, self.dry_run).await?;
}
create_directory("/nix/store", self.dry_run).await?;
set_permissions(
"/nix/store",
Some(Permissions::from_mode(0o1775)),
None,
Some(self.nix_build_group_name.clone()),
self.dry_run,
)
.await?;
create_directory("/etc/nix", self.dry_run).await?;
set_permissions(
"/etc/nix",
Some(Permissions::from_mode(0o0555)),
None,
None,
self.dry_run,
)
.await?;
Ok(())
}
#[tracing::instrument(skip_all)]
pub async fn place_channel_configuration(&self) -> Result<(), HarmonicError> {
tracing::info!("Placing channel configuration");
let buf = self
.channels
.iter()
.map(|(name, url)| format!("{} {}", url, name))
.collect::<Vec<_>>()
.join("\n");
create_file_if_not_exists("/root/.nix-channels", buf, self.dry_run).await?;
set_permissions(
"/root/.nix-channels",
Some(Permissions::from_mode(0o0664)),
None,
None,
self.dry_run,
)
.await
}
#[tracing::instrument(skip_all)]
pub async fn configure_shell_profile(&self) -> Result<(), HarmonicError> {
tracing::info!("Configuring shell profile");
const PROFILE_TARGETS: &[&str] = &[
"/etc/bashrc",
"/etc/profile.d/nix.sh",
"/etc/zshrc",
"/etc/bash.bashrc",
"/etc/zsh/zshrc",
];
const PROFILE_NIX_FILE: &str = "/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh";
for profile_target in PROFILE_TARGETS {
let path = Path::new(profile_target);
let buf = format!(
"\n\
# Nix\n\
if [ -e '{PROFILE_NIX_FILE}' ]; then\n\
. '{PROFILE_NIX_FILE}'\n\
fi\n\
# End Nix\n
\n",
);
create_or_append_file(path, buf, self.dry_run).await?;
}
Ok(())
}
#[tracing::instrument(skip_all)]
pub async fn setup_default_profile(&self) -> Result<(), HarmonicError> {
tracing::info!("Setting up default profile");
// Find an `nix` package
let nix_pkg_glob = "/nix/store/*-nix-*";
let found_nix_pkg = if !self.dry_run {
let mut found_pkg = None;
for entry in glob(nix_pkg_glob).map_err(HarmonicError::GlobPatternError)? {
match entry {
Ok(path) => {
// TODO(@Hoverbear): Should probably ensure is unique
found_pkg = Some(path);
break;
},
Err(_) => continue, /* Ignore it */
};
}
found_pkg
} else {
// This is a mock for dry running.
Some(PathBuf::from(nix_pkg_glob))
};
let nix_pkg = if let Some(nix_pkg) = found_nix_pkg {
nix_pkg
} else {
return Err(HarmonicError::NoNssCacert);
};
execute_command(
Command::new(nix_pkg.join("bin/nix-env"))
.arg("-i")
.arg(&nix_pkg),
self.dry_run,
)
.await?;
// Find an `nss-cacert` package, add it too.
let nss_ca_cert_pkg_glob = "/nix/store/*-nss-cacert-*";
let found_nss_ca_cert_pkg = if !self.dry_run {
let mut found_pkg = None;
for entry in glob(nss_ca_cert_pkg_glob).map_err(HarmonicError::GlobPatternError)? {
match entry {
Ok(path) => {
// TODO(@Hoverbear): Should probably ensure is unique
found_pkg = Some(path);
break;
},
Err(_) => continue, /* Ignore it */
};
}
found_pkg
} else {
// This is a mock for dry running.
Some(PathBuf::from(nss_ca_cert_pkg_glob))
};
if let Some(nss_ca_cert_pkg) = found_nss_ca_cert_pkg {
execute_command(
Command::new(nix_pkg.join("bin/nix-env"))
.arg("-i")
.arg(&nss_ca_cert_pkg),
self.dry_run,
)
.await?;
set_env(
"NIX_SSL_CERT_FILE",
"/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt",
self.dry_run,
);
nss_ca_cert_pkg
} else {
return Err(HarmonicError::NoNssCacert);
};
if !self.channels.is_empty() {
execute_command(
Command::new(nix_pkg.join("bin/nix-channel"))
.arg("--update")
.arg("nixpkgs")
.env(
"NIX_SSL_CERT_FILE",
"/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt",
),
self.dry_run,
)
.await?;
}
Ok(())
}
#[tracing::instrument(skip_all)]
pub async fn place_nix_configuration(&self) -> Result<(), HarmonicError> {
tracing::info!("Placing nix configuration");
let buf = format!(
"\
{extra_conf}\n\
build-users-group = {build_group_name}\n\
",
extra_conf = "", // TODO(@Hoverbear): populate me
build_group_name = self.nix_build_group_name,
);
create_file_if_not_exists("/etc/nix/nix.conf", buf, self.dry_run).await
}
#[tracing::instrument(skip_all)]
pub async fn configure_nix_daemon_service(&self) -> Result<(), HarmonicError> {
tracing::info!("Configuring nix daemon service");
if Path::new("/run/systemd/system").exists() {
const SERVICE_SRC: &str =
"/nix/var/nix/profiles/default/lib/systemd/system/nix-daemon.service";
const SOCKET_SRC: &str =
"/nix/var/nix/profiles/default/lib/systemd/system/nix-daemon.socket";
const TMPFILES_SRC: &str =
"/nix/var/nix/profiles/default//lib/tmpfiles.d/nix-daemon.conf";
const TMPFILES_DEST: &str = "/etc/tmpfiles.d/nix-daemon.conf";
symlink(TMPFILES_SRC, TMPFILES_DEST, self.dry_run).await?;
execute_command(
Command::new("systemd-tmpfiles")
.arg("--create")
.arg("--prefix=/nix/var/nix"),
self.dry_run,
)
.await?;
execute_command(
Command::new("systemctl").arg("link").arg(SERVICE_SRC),
self.dry_run,
)
.await?;
execute_command(
Command::new("systemctl").arg("enable").arg(SOCKET_SRC),
self.dry_run,
)
.await?;
// TODO(@Hoverbear): Handle proxy vars
execute_command(Command::new("systemctl").arg("daemon-reload"), self.dry_run).await?;
execute_command(
Command::new("systemctl")
.arg("start")
.arg("nix-daemon.socket"),
self.dry_run,
)
.await?;
execute_command(
Command::new("systemctl")
.arg("restart")
.arg("nix-daemon.service"),
self.dry_run,
)
.await?;
} else {
return Err(HarmonicError::InitNotSupported);
}
Ok(())
}
}
impl Default for Harmonic {
fn default() -> Self {
Self {
dry_run: true,
channels: vec![(
"nixpkgs".to_string(),
"https://nixos.org/channels/nixpkgs-unstable"
.parse::<Url>()
.unwrap(),
)],
daemon_user_count: 32,
modify_profile: true,
nix_build_group_name: String::from("nixbld"),
nix_build_group_id: 30000,
nix_build_user_prefix: String::from("nixbld"),
nix_build_user_id_base: 30000,
}
}
}
#[tracing::instrument(skip_all, fields( #[tracing::instrument(skip_all, fields(
path = %path.as_ref().display(), path = %path.as_ref().display(),
@ -497,27 +102,15 @@ async fn create_directory(path: impl AsRef<Path>, dry_run: bool) -> Result<(), H
#[tracing::instrument(skip_all, fields(command = %format!("{:?}", command.as_std())))] #[tracing::instrument(skip_all, fields(command = %format!("{:?}", command.as_std())))]
async fn execute_command( async fn execute_command(
command: &mut Command, command: &mut Command,
dry_run: bool, ) -> Result<ExitStatus, std::io::Error> {
) -> Result<ExitStatus, HarmonicError> { tracing::trace!("Executing");
if !dry_run { let command_str = format!("{:?}", command.as_std());
tracing::trace!("Executing"); let status = command
let command_str = format!("{:?}", command.as_std()); .status()
let status = command .await?;
.status() match status.success() {
.await true => Ok(status),
.map_err(|e| HarmonicError::CommandFailedExec(command_str.clone(), e))?; false => Err(std::io::Error::new(std::io::ErrorKind::Other, "Failed status")),
match status.success() {
true => Ok(status),
false => Err(HarmonicError::CommandFailedStatus(command_str)),
}
} else {
tracing::info!("Dry run: Would execute");
// You cannot conjure "good" exit status in Rust without breaking the rules
// So... we conjure one from `true`
Command::new("true")
.status()
.await
.map_err(|e| HarmonicError::CommandFailedExec(String::from("true"), e))
} }
} }
@ -686,3 +279,7 @@ fn set_env(k: impl AsRef<OsStr>, v: impl AsRef<OsStr>, dry_run: bool) {
tracing::info!("Dry run: Would set env"); tracing::info!("Dry run: Would set env");
} }
} }
fn serialize_std_io_error_to_display<S>(err: &std::io::Error, ser: S) -> Result<S::Ok, S::Error> where S: Serializer {
ser.serialize_str(&format!("{err:#}"))
}

View file

@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};
use crate::{ use crate::{
actions::{ actions::{
meta::{ConfigureNix, ProvisionNix, StartNixDaemon}, meta::{ConfigureNix, ProvisionNix, StartNixDaemon},
Action, ActionDescription, ActionReceipt, Actionable, Revertable, Action, ActionDescription, Actionable, ActionState, ActionError,
}, },
settings::InstallSettings, settings::InstallSettings,
HarmonicError, HarmonicError,
@ -30,7 +30,9 @@ pub struct InstallPlan {
* "Start Nix" * "Start Nix"
* start_nix_daemon_service * start_nix_daemon_service
*/ */
actions: Vec<Action>, provision_nix: ActionState<ProvisionNix>,
configure_nix: ActionState<ConfigureNix>,
start_nix_daemon: ActionState<StartNixDaemon>,
} }
impl InstallPlan { impl InstallPlan {
@ -55,68 +57,48 @@ impl InstallPlan {
.map(|(name, url)| format!("{name}={url}")) .map(|(name, url)| format!("{name}={url}"))
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(","), .join(","),
actions = self actions = {
.actions let mut buf = self.provision_nix.description();
.iter() buf.append(&mut self.configure_nix.description());
.flat_map(|action| action.description()) buf.append(&mut self.start_nix_daemon.description());
.map(|desc| { buf.iter()
let ActionDescription { .map(|desc| {
description, let ActionDescription {
explanation, description,
} = desc; explanation,
} = desc;
let mut buf = String::default(); let mut buf = String::default();
buf.push_str(&format!("* {description}\n")); buf.push_str(&format!("* {description}\n"));
if self.settings.explain { if self.settings.explain {
for line in explanation { for line in explanation {
buf.push_str(&format!(" {line}\n")); buf.push_str(&format!(" {line}\n"));
}
} }
} buf
buf })
}) .collect::<Vec<_>>()
.collect::<Vec<_>>() .join("\n")
.join("\n"), },
) )
} }
pub async fn new(settings: InstallSettings) -> Result<Self, HarmonicError> { pub async fn new(settings: InstallSettings) -> Result<Self, ActionError> {
let actions = vec![ Ok(Self {
Action::ProvisionNix(ProvisionNix::plan(settings.clone()).await?), settings: settings.clone(),
Action::ConfigureNix(ConfigureNix::plan(settings.clone()).await?), provision_nix: ProvisionNix::plan(settings.clone()).await?,
Action::StartNixDaemon(StartNixDaemon::plan().await?), configure_nix: ConfigureNix::plan(settings).await?,
]; start_nix_daemon: StartNixDaemon::plan().await?,
Ok(Self { settings, actions }) })
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
pub async fn install(self) -> Result<Receipt, HarmonicError> { pub async fn install(&mut self) -> Result<(), ActionError> {
let mut receipt = Receipt::default();
// This is **deliberately sequential**. // This is **deliberately sequential**.
// Actions which are parallelizable are represented by "group actions" like CreateUsers // Actions which are parallelizable are represented by "group actions" like CreateUsers
// The plan itself represents the concept of the sequence of stages. // The plan itself represents the concept of the sequence of stages.
for action in self.actions { self.provision_nix.execute().await?;
match action.execute().await { self.configure_nix.execute().await?;
Ok(action_receipt) => receipt.actions.push(action_receipt), self.start_nix_daemon.execute().await?;
Err(err) => { Ok(())
return Err(err);
// TODO
// let mut revert_errs = Vec::default();
// for action_receipt in receipt.actions {
// if let Err(err) = action_receipt.revert().await {
// revert_errs.push(err);
// }
// }
// if !revert_errs.is_empty() {
// return Err(HarmonicError::FailedReverts(vec![err], revert_errs));
// }
},
};
}
Ok(receipt)
} }
} }
#[derive(Default, Debug, Serialize, Deserialize)]
pub struct Receipt {
actions: Vec<ActionReceipt>,
}