Logging and error tidying

This commit is contained in:
Ana Hobden 2022-09-27 12:05:24 -07:00
parent 47018ad00c
commit b34a352753
23 changed files with 709 additions and 241 deletions

View file

@ -33,16 +33,21 @@ impl ConfigureNixDaemonService {
#[async_trait::async_trait] #[async_trait::async_trait]
impl Actionable for ConfigureNixDaemonService { impl Actionable for ConfigureNixDaemonService {
type Error = ConfigureNixDaemonServiceError; type Error = ConfigureNixDaemonServiceError;
fn description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new( fn describe_execute(&self) -> Vec<ActionDescription> {
"Configure Nix daemon related settings with systemd".to_string(), if self.action_state == ActionState::Completed {
vec![ vec![]
"Run `systemd-tempfiles --create --prefix=/nix/var/nix`".to_string(), } else {
"Run `systemctl link {SERVICE_SRC}`".to_string(), vec![ActionDescription::new(
"Run `systemctl link {SOCKET_SRC}`".to_string(), "Configure Nix daemon related settings with systemd".to_string(),
"Run `systemctl daemon-reload`".to_string(), vec![
], "Run `systemd-tempfiles --create --prefix=/nix/var/nix`".to_string(),
)] "Run `systemctl link {SERVICE_SRC}`".to_string(),
"Run `systemctl link {SOCKET_SRC}`".to_string(),
"Run `systemctl daemon-reload`".to_string(),
],
)]
}
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
@ -86,6 +91,22 @@ impl Actionable for ConfigureNixDaemonService {
Ok(()) Ok(())
} }
fn describe_revert(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Uncompleted {
vec![]
} else {
vec![ActionDescription::new(
"Unconfigure Nix daemon related settings with systemd".to_string(),
vec![
"Run `systemctl disable {SOCKET_SRC}`".to_string(),
"Run `systemctl disable {SERVICE_SRC}`".to_string(),
"Run `systemd-tempfiles --remove --prefix=/nix/var/nix`".to_string(),
"Run `systemctl daemon-reload`".to_string(),
],
)]
}
}
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn revert(&mut self) -> Result<(), Self::Error> { async fn revert(&mut self) -> Result<(), Self::Error> {
let Self { action_state } = self; let Self { action_state } = self;

View file

@ -50,7 +50,8 @@ impl CreateDirectory {
#[async_trait::async_trait] #[async_trait::async_trait]
impl Actionable for CreateDirectory { impl Actionable for CreateDirectory {
type Error = CreateDirectoryError; type Error = CreateDirectoryError;
fn description(&self) -> Vec<ActionDescription> {
fn describe_execute(&self) -> Vec<ActionDescription> {
let Self { let Self {
path, path,
user, user,
@ -58,13 +59,17 @@ impl Actionable for CreateDirectory {
mode, mode,
action_state: _, action_state: _,
} = &self; } = &self;
vec![ActionDescription::new( if self.action_state == ActionState::Completed {
format!("Create the directory `{}`", path.display()), vec![]
vec![format!( } else {
"Creating directory `{}` owned by `{user}:{group}` with mode `{mode:#o}`", vec![ActionDescription::new(
path.display() format!("Create the directory `{}`", path.display()),
)], vec![format!(
)] "Creating directory `{}` owned by `{user}:{group}` with mode `{mode:#o}`",
path.display()
)],
)]
}
} }
#[tracing::instrument(skip_all, fields( #[tracing::instrument(skip_all, fields(
@ -110,6 +115,25 @@ impl Actionable for CreateDirectory {
Ok(()) Ok(())
} }
fn describe_revert(&self) -> Vec<ActionDescription> {
let Self {
path,
user: _,
group: _,
mode: _,
action_state: _,
} = &self;
if self.action_state == ActionState::Uncompleted {
vec![]
} else {
vec![ActionDescription::new(
format!("Remove the directory `{}`", path.display()),
vec![],
)]
}
}
#[tracing::instrument(skip_all, fields( #[tracing::instrument(skip_all, fields(
path = %self.path.display(), path = %self.path.display(),
user = self.user, user = self.user,

View file

@ -52,7 +52,8 @@ impl CreateFile {
#[async_trait::async_trait] #[async_trait::async_trait]
impl Actionable for CreateFile { impl Actionable for CreateFile {
type Error = CreateFileError; type Error = CreateFileError;
fn description(&self) -> Vec<ActionDescription> {
fn describe_execute(&self) -> Vec<ActionDescription> {
let Self { let Self {
path, path,
user, user,
@ -62,12 +63,16 @@ impl Actionable for CreateFile {
force: _, force: _,
action_state: _, action_state: _,
} = &self; } = &self;
vec![ActionDescription::new( if self.action_state == ActionState::Completed {
format!("Create or overwrite file `{}`", path.display()), vec![]
vec![format!( } else {
"Create or overwrite `{}` owned by `{user}:{group}` with mode `{mode:#o}` with `{buf}`", path.display() vec![ActionDescription::new(
)], format!("Create or overwrite file `{}`", path.display()),
)] vec![format!(
"Create or overwrite `{}` owned by `{user}:{group}` with mode `{mode:#o}` with `{buf}`", path.display()
)],
)]
}
} }
#[tracing::instrument(skip_all, fields( #[tracing::instrument(skip_all, fields(
@ -121,6 +126,29 @@ impl Actionable for CreateFile {
Ok(()) Ok(())
} }
fn describe_revert(&self) -> Vec<ActionDescription> {
let Self {
path,
user: _,
group: _,
mode: _,
buf: _,
force: _,
action_state: _,
} = &self;
if self.action_state == ActionState::Uncompleted {
vec![]
} else {
vec![ActionDescription::new(
format!("Delete file `{}`", path.display()),
vec![format!(
"Delete file `{}`", path.display()
)],
)]
}
}
#[tracing::instrument(skip_all, fields( #[tracing::instrument(skip_all, fields(
path = %self.path.display(), path = %self.path.display(),
user = self.user, user = self.user,

View file

@ -26,18 +26,23 @@ impl CreateGroup {
#[async_trait::async_trait] #[async_trait::async_trait]
impl Actionable for CreateGroup { impl Actionable for CreateGroup {
type Error = CreateGroupError; type Error = CreateGroupError;
fn description(&self) -> Vec<ActionDescription> {
fn describe_execute(&self) -> Vec<ActionDescription> {
let Self { let Self {
name, name,
gid, gid,
action_state: _, action_state: _,
} = &self; } = &self;
vec![ActionDescription::new( if self.action_state == ActionState::Completed {
format!("Create group {name} with GID {gid}"), vec![]
vec![format!( } else {
"The nix daemon requires a system user group its system users can be part of" vec![ActionDescription::new(
)], format!("Create group {name} with GID {gid}"),
)] vec![format!(
"The nix daemon requires a system user group its system users can be part of"
)],
)]
}
} }
#[tracing::instrument(skip_all, fields( #[tracing::instrument(skip_all, fields(
@ -65,6 +70,25 @@ impl Actionable for CreateGroup {
Ok(()) Ok(())
} }
fn describe_revert(&self) -> Vec<ActionDescription> {
let Self {
name,
gid: _,
action_state: _,
} = &self;
if self.action_state == ActionState::Completed {
vec![]
} else {
vec![ActionDescription::new(
format!("Delete group {name}"),
vec![format!(
"The nix daemon requires a system user group its system users can be part of"
)],
)]
}
}
#[tracing::instrument(skip_all, fields( #[tracing::instrument(skip_all, fields(
user = self.name, user = self.name,
gid = self.gid, gid = self.gid,

View file

@ -49,7 +49,8 @@ impl CreateOrAppendFile {
#[async_trait::async_trait] #[async_trait::async_trait]
impl Actionable for CreateOrAppendFile { impl Actionable for CreateOrAppendFile {
type Error = CreateOrAppendFileError; type Error = CreateOrAppendFileError;
fn description(&self) -> Vec<ActionDescription> {
fn describe_execute(&self) -> Vec<ActionDescription> {
let Self { let Self {
path, path,
user, user,
@ -58,12 +59,16 @@ impl Actionable for CreateOrAppendFile {
buf, buf,
action_state: _, action_state: _,
} = &self; } = &self;
vec![ActionDescription::new( if self.action_state == ActionState::Completed {
format!("Create or append file `{}`", path.display()), vec![]
vec![format!( } else {
"Create or append `{}` owned by `{user}:{group}` with mode `{mode:#o}` with `{buf}`", path.display() vec![ActionDescription::new(
)], format!("Create or append file `{}`", path.display()),
)] vec![format!(
"Create or append `{}` owned by `{user}:{group}` with mode `{mode:#o}` with `{buf}`", path.display()
)],
)]
}
} }
#[tracing::instrument(skip_all, fields( #[tracing::instrument(skip_all, fields(
@ -123,6 +128,27 @@ impl Actionable for CreateOrAppendFile {
Ok(()) Ok(())
} }
fn describe_revert(&self) -> Vec<ActionDescription> {
let Self {
path,
user: _,
group: _,
mode: _,
buf,
action_state: _,
} = &self;
if self.action_state == ActionState::Uncompleted {
vec![]
} else {
vec![ActionDescription::new(
format!("Delete Nix related fragment from file `{}`", path.display()),
vec![format!(
"Delete Nix related fragment from file `{}`. Fragment: `{buf}`", path.display()
)],
)]
}
}
#[tracing::instrument(skip_all, fields( #[tracing::instrument(skip_all, fields(
path = %self.path.display(), path = %self.path.display(),
user = self.user, user = self.user,

View file

@ -28,15 +28,20 @@ impl CreateUser {
#[async_trait::async_trait] #[async_trait::async_trait]
impl Actionable for CreateUser { impl Actionable for CreateUser {
type Error = CreateUserError; type Error = CreateUserError;
fn description(&self) -> Vec<ActionDescription> {
let name = &self.name; fn describe_execute(&self) -> Vec<ActionDescription> {
let uid = &self.uid; if self.action_state == ActionState::Completed {
vec![ActionDescription::new( vec![]
format!("Create user {name} with UID {uid}"), } else {
vec![format!( let Self { name, uid, gid, action_state: _ } = self;
"The nix daemon requires system users it can act as in order to build"
)], vec![ActionDescription::new(
)] format!("Create user {name} with UID {uid} with group {gid}"),
vec![format!(
"The nix daemon requires system users it can act as in order to build"
)],
)]
}
} }
#[tracing::instrument(skip_all, fields( #[tracing::instrument(skip_all, fields(
@ -84,6 +89,22 @@ impl Actionable for CreateUser {
Ok(()) Ok(())
} }
fn describe_revert(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Uncompleted {
vec![]
} else {
let Self { name, uid, gid, action_state: _ } = self;
vec![ActionDescription::new(
format!("Delete user {name} with UID {uid} with group {gid}"),
vec![format!(
"The nix daemon requires system users it can act as in order to build"
)],
)]
}
}
#[tracing::instrument(skip_all, fields( #[tracing::instrument(skip_all, fields(
user = self.name, user = self.name,
uid = self.uid, uid = self.uid,

View file

@ -31,19 +31,24 @@ impl FetchNix {
#[async_trait::async_trait] #[async_trait::async_trait]
impl Actionable for FetchNix { impl Actionable for FetchNix {
type Error = FetchNixError; type Error = FetchNixError;
fn description(&self) -> Vec<ActionDescription> {
fn describe_execute(&self) -> Vec<ActionDescription> {
let Self { let Self {
url, url,
dest, dest,
action_state: _, action_state: _,
} = &self; } = &self;
vec![ActionDescription::new( if self.action_state == ActionState::Completed {
format!("Fetch Nix from `{url}`"), vec![]
vec![format!( } else {
"Unpack it to `{}` (moved later)", vec![ActionDescription::new(
dest.display() format!("Fetch Nix from `{url}`"),
)], vec![format!(
)] "Unpack it to `{}` (moved later)",
dest.display()
)],
)]
}
} }
#[tracing::instrument(skip_all, fields( #[tracing::instrument(skip_all, fields(
@ -85,6 +90,14 @@ impl Actionable for FetchNix {
Ok(()) Ok(())
} }
fn describe_revert(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Uncompleted {
vec![]
} else {
vec![/* Deliberately empty -- this is a noop */]
}
}
#[tracing::instrument(skip_all, fields( #[tracing::instrument(skip_all, fields(
url = %self.url, url = %self.url,
dest = %self.dest.display(), dest = %self.dest.display(),
@ -114,8 +127,9 @@ impl From<FetchNix> for Action {
#[derive(Debug, thiserror::Error, Serialize)] #[derive(Debug, thiserror::Error, Serialize)]
pub enum FetchNixError { pub enum FetchNixError {
#[error(transparent)] #[error("Joining spawned async task")]
Join( Join(
#[source]
#[from] #[from]
#[serde(serialize_with = "crate::serialize_error_to_display")] #[serde(serialize_with = "crate::serialize_error_to_display")]
JoinError, JoinError,

View file

@ -26,18 +26,19 @@ impl MoveUnpackedNix {
#[async_trait::async_trait] #[async_trait::async_trait]
impl Actionable for MoveUnpackedNix { impl Actionable for MoveUnpackedNix {
type Error = MoveUnpackedNixError; type Error = MoveUnpackedNixError;
fn description(&self) -> Vec<ActionDescription> {
let Self { fn describe_execute(&self) -> Vec<ActionDescription> {
src, if self.action_state == ActionState::Completed {
action_state: _, vec![]
} = &self; } else {
vec![ActionDescription::new( vec![ActionDescription::new(
format!("Move the downloaded Nix into `/nix`"), format!("Move the downloaded Nix into `/nix`"),
vec![format!( vec![format!(
"Nix is being downloaded to `{}` and should be in `nix`", "Nix is being downloaded to `{}` and should be in `nix`",
src.display(), self.src.display(),
)], )],
)] )]
}
} }
#[tracing::instrument(skip_all, fields( #[tracing::instrument(skip_all, fields(
@ -76,6 +77,15 @@ impl Actionable for MoveUnpackedNix {
Ok(()) Ok(())
} }
fn describe_revert(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Uncompleted {
vec![]
} else {
vec![/* Deliberately empty -- this is a noop */]
}
}
#[tracing::instrument(skip_all, fields( #[tracing::instrument(skip_all, fields(
src = %self.src.display(), src = %self.src.display(),
dest = DEST, dest = DEST,

View file

@ -28,11 +28,19 @@ impl SetupDefaultProfile {
#[async_trait::async_trait] #[async_trait::async_trait]
impl Actionable for SetupDefaultProfile { impl Actionable for SetupDefaultProfile {
type Error = SetupDefaultProfileError; type Error = SetupDefaultProfileError;
fn description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(
"Setup the default Nix profile".to_string(), fn describe_execute(&self) -> Vec<ActionDescription> {
vec!["TODO".to_string()], if self.action_state == ActionState::Completed {
)] vec![]
} else {
vec![ActionDescription::new(
"Setup the default Nix profile".to_string(),
vec![
"TODO".to_string()
]
)]
}
} }
#[tracing::instrument(skip_all, fields( #[tracing::instrument(skip_all, fields(
@ -131,6 +139,19 @@ impl Actionable for SetupDefaultProfile {
Ok(()) Ok(())
} }
fn describe_revert(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Uncompleted {
vec![]
} else {
vec![ActionDescription::new(
"Unset the default Nix profile".to_string(),
vec![
"TODO".to_string()
]
)]
}
}
#[tracing::instrument(skip_all, fields( #[tracing::instrument(skip_all, fields(
channels = %self.channels.join(","), channels = %self.channels.join(","),
))] ))]

View file

@ -24,24 +24,17 @@ impl StartSystemdUnit {
#[async_trait::async_trait] #[async_trait::async_trait]
impl Actionable for StartSystemdUnit { impl Actionable for StartSystemdUnit {
type Error = StartSystemdUnitError; type Error = StartSystemdUnitError;
fn description(&self) -> Vec<ActionDescription> {
match self.action_state { fn describe_execute(&self) -> Vec<ActionDescription> {
ActionState::Uncompleted => vec![ if self.action_state == ActionState::Completed {
ActionDescription::new( vec![]
"Start the systemd Nix service and socket".to_string(), } else {
vec![ vec![ActionDescription::new(
"The `nix` command line tool communicates with a running Nix daemon managed by your init system".to_string() "Start 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::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()
]
),
],
} }
} }
@ -71,6 +64,19 @@ impl Actionable for StartSystemdUnit {
Ok(()) Ok(())
} }
fn describe_revert(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Uncompleted {
vec![]
} else {
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()
]
)]
}
}
#[tracing::instrument(skip_all, fields( #[tracing::instrument(skip_all, fields(
unit = %self.unit, unit = %self.unit,
))] ))]

View file

@ -66,7 +66,7 @@ impl ConfigureNix {
#[async_trait::async_trait] #[async_trait::async_trait]
impl Actionable for ConfigureNix { impl Actionable for ConfigureNix {
type Error = ConfigureNixError; type Error = ConfigureNixError;
fn description(&self) -> Vec<ActionDescription> { fn describe_execute(&self) -> Vec<ActionDescription> {
let Self { let Self {
setup_default_profile, setup_default_profile,
configure_nix_daemon_service, configure_nix_daemon_service,
@ -76,15 +76,18 @@ impl Actionable for ConfigureNix {
action_state: _, action_state: _,
} = &self; } = &self;
let mut buf = setup_default_profile.description(); if self.action_state == ActionState::Completed {
buf.append(&mut configure_nix_daemon_service.description()); vec![]
buf.append(&mut place_nix_configuration.description()); } else {
buf.append(&mut place_channel_configuration.description()); let mut buf = setup_default_profile.describe_execute();
if let Some(configure_shell_profile) = configure_shell_profile { buf.append(&mut configure_nix_daemon_service.describe_execute());
buf.append(&mut configure_shell_profile.description()); buf.append(&mut place_nix_configuration.describe_execute());
buf.append(&mut place_channel_configuration.describe_execute());
if let Some(configure_shell_profile) = configure_shell_profile {
buf.append(&mut configure_shell_profile.describe_execute());
}
buf
} }
buf
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
@ -159,6 +162,33 @@ impl Actionable for ConfigureNix {
Ok(()) Ok(())
} }
fn describe_revert(&self) -> Vec<ActionDescription> {
let Self {
setup_default_profile,
configure_nix_daemon_service,
place_nix_configuration,
place_channel_configuration,
configure_shell_profile,
action_state: _,
} = &self;
if self.action_state == ActionState::Uncompleted {
vec![]
} else {
let mut buf = Vec::default();
if let Some(configure_shell_profile) = configure_shell_profile {
buf.append(&mut configure_shell_profile.describe_revert());
}
buf.append(&mut place_channel_configuration.describe_revert());
buf.append(&mut place_nix_configuration.describe_revert());
buf.append(&mut configure_nix_daemon_service.describe_revert());
buf.append(&mut setup_default_profile.describe_revert());
buf
}
}
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn revert(&mut self) -> Result<(), Self::Error> { async fn revert(&mut self) -> Result<(), Self::Error> {
let Self { let Self {
@ -197,14 +227,14 @@ impl From<ConfigureNix> for Action {
#[derive(Debug, thiserror::Error, Serialize)] #[derive(Debug, thiserror::Error, Serialize)]
pub enum ConfigureNixError { pub enum ConfigureNixError {
#[error(transparent)] #[error("Setting up default profile")]
SetupDefaultProfile(#[from] SetupDefaultProfileError), SetupDefaultProfile(#[source] #[from] SetupDefaultProfileError),
#[error(transparent)] #[error("Placing Nix configuration")]
PlaceNixConfiguration(#[from] PlaceNixConfigurationError), PlaceNixConfiguration(#[source] #[from] PlaceNixConfigurationError),
#[error(transparent)] #[error("Placing channel configuration")]
PlaceChannelConfiguration(#[from] PlaceChannelConfigurationError), PlaceChannelConfiguration(#[source] #[from] PlaceChannelConfigurationError),
#[error(transparent)] #[error("Configuring Nix daemon")]
ConfigureNixDaemonService(#[from] ConfigureNixDaemonServiceError), ConfigureNixDaemonService(#[source] #[from] ConfigureNixDaemonServiceError),
#[error(transparent)] #[error("Configuring shell profile")]
ConfigureShellProfile(#[from] ConfigureShellProfileError), ConfigureShellProfile(#[source] #[from] ConfigureShellProfileError),
} }

View file

@ -57,11 +57,16 @@ impl ConfigureShellProfile {
#[async_trait::async_trait] #[async_trait::async_trait]
impl Actionable for ConfigureShellProfile { impl Actionable for ConfigureShellProfile {
type Error = ConfigureShellProfileError; type Error = ConfigureShellProfileError;
fn description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new( fn describe_execute(&self) -> Vec<ActionDescription> {
"Configure the shell profiles".to_string(), if self.action_state == ActionState::Completed {
vec!["Update shell profiles to import Nix".to_string()], vec![]
)] } else {
vec![ActionDescription::new(
"Configure the shell profiles".to_string(),
vec!["Update shell profiles to import Nix".to_string()],
)]
}
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
@ -112,6 +117,18 @@ impl Actionable for ConfigureShellProfile {
Ok(()) Ok(())
} }
fn describe_revert(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Uncompleted {
vec![]
} else {
vec![ActionDescription::new(
"Unconfigure the shell profiles".to_string(),
vec!["Update shell profiles to no longer import Nix".to_string()],
)]
}
}
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn revert(&mut self) -> Result<(), Self::Error> { async fn revert(&mut self) -> Result<(), Self::Error> {
let Self { let Self {
@ -169,12 +186,17 @@ impl From<ConfigureShellProfile> for Action {
#[derive(Debug, thiserror::Error, Serialize)] #[derive(Debug, thiserror::Error, Serialize)]
pub enum ConfigureShellProfileError { pub enum ConfigureShellProfileError {
#[error(transparent)] #[error("Creating or appending to file")]
CreateOrAppendFile(#[from] CreateOrAppendFileError), CreateOrAppendFile(
#[from]
#[source]
CreateOrAppendFileError
),
#[error("Multiple errors: {}", .0.iter().map(|v| format!("{v}")).collect::<Vec<_>>().join(" & "))] #[error("Multiple errors: {}", .0.iter().map(|v| format!("{v}")).collect::<Vec<_>>().join(" & "))]
MultipleCreateOrAppendFile(Vec<CreateOrAppendFileError>), MultipleCreateOrAppendFile(Vec<CreateOrAppendFileError>),
#[error(transparent)] #[error("Joining spawned async task")]
Join( Join(
#[source]
#[from] #[from]
#[serde(serialize_with = "crate::serialize_error_to_display")] #[serde(serialize_with = "crate::serialize_error_to_display")]
JoinError, JoinError,

View file

@ -47,23 +47,28 @@ impl CreateNixTree {
#[async_trait::async_trait] #[async_trait::async_trait]
impl Actionable for CreateNixTree { impl Actionable for CreateNixTree {
type Error = CreateNixTreeError; type Error = CreateNixTreeError;
fn description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new( fn describe_execute(&self) -> Vec<ActionDescription> {
format!("Create a directory tree in `/nix`"), if self.action_state == ActionState::Completed {
vec![ vec![]
format!( } else {
"Nix and the Nix daemon require a Nix Store, which will be stored at `/nix`" vec![ActionDescription::new(
), format!("Create a directory tree in `/nix`"),
format!( vec![
"Creates: {}", format!(
PATHS "Nix and the Nix daemon require a Nix Store, which will be stored at `/nix`"
.iter() ),
.map(|v| format!("`{v}`")) format!(
.collect::<Vec<_>>() "Creates: {}",
.join(", ") PATHS
), .iter()
], .map(|v| format!("`{v}`"))
)] .collect::<Vec<_>>()
.join(", ")
),
],
)]
}
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
@ -88,6 +93,30 @@ impl Actionable for CreateNixTree {
Ok(()) Ok(())
} }
fn describe_revert(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Uncompleted {
vec![]
} else {
vec![ActionDescription::new(
format!("Remove the directory tree in `/nix`"),
vec![
format!(
"Nix and the Nix daemon require a Nix Store, which will be stored at `/nix`"
),
format!(
"Removes: {}",
PATHS
.iter()
.rev()
.map(|v| format!("`{v}`"))
.collect::<Vec<_>>()
.join(", ")
),
],
)]
}
}
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn revert(&mut self) -> Result<(), Self::Error> { async fn revert(&mut self) -> Result<(), Self::Error> {
let Self { let Self {
@ -119,6 +148,6 @@ impl From<CreateNixTree> for Action {
#[derive(Debug, thiserror::Error, Serialize)] #[derive(Debug, thiserror::Error, Serialize)]
pub enum CreateNixTreeError { pub enum CreateNixTreeError {
#[error(transparent)] #[error("Creating directory")]
CreateDirectory(#[from] CreateDirectoryError), CreateDirectory(#[source] #[from] CreateDirectoryError),
} }

View file

@ -52,7 +52,8 @@ impl CreateUsersAndGroup {
#[async_trait::async_trait] #[async_trait::async_trait]
impl Actionable for CreateUsersAndGroup { impl Actionable for CreateUsersAndGroup {
type Error = CreateUsersAndGroupError; type Error = CreateUsersAndGroupError;
fn description(&self) -> Vec<ActionDescription> {
fn describe_execute(&self) -> Vec<ActionDescription> {
let Self { let Self {
daemon_user_count, daemon_user_count,
nix_build_group_name, nix_build_group_name,
@ -63,17 +64,20 @@ impl Actionable for CreateUsersAndGroup {
create_users: _, create_users: _,
action_state: _, action_state: _,
} = &self; } = &self;
if self.action_state == ActionState::Completed {
vec![ vec![]
ActionDescription::new( } else {
format!("Create build users and group"), vec![
vec![ ActionDescription::new(
format!("The nix daemon requires system users (and a group they share) which it can act as in order to build"), format!("Create build users and group"),
format!("Create group `{nix_build_group_name}` with uid `{nix_build_group_id}`"), vec![
format!("Create {daemon_user_count} users with prefix `{nix_build_user_prefix}` starting at uid `{nix_build_user_id_base}`"), format!("The nix daemon requires system users (and a group they share) which it can act as in order to build"),
], format!("Create group `{nix_build_group_name}` with uid `{nix_build_group_id}`"),
) format!("Create {daemon_user_count} users with prefix `{nix_build_user_prefix}` starting at uid `{nix_build_user_id_base}`"),
] ],
)
]
}
} }
#[tracing::instrument(skip_all, fields( #[tracing::instrument(skip_all, fields(
@ -138,6 +142,32 @@ impl Actionable for CreateUsersAndGroup {
Ok(()) Ok(())
} }
fn describe_revert(&self) -> Vec<ActionDescription> {
let Self {
daemon_user_count,
nix_build_group_name,
nix_build_group_id,
nix_build_user_prefix,
nix_build_user_id_base,
create_group: _,
create_users: _,
action_state: _,
} = &self;
if self.action_state == ActionState::Uncompleted {
vec![]
} else {
vec![
ActionDescription::new(
format!("Remove build users and group"),
vec![
format!("The nix daemon requires system users (and a group they share) which it can act as in order to build"),
format!("Create group `{nix_build_group_name}` with uid `{nix_build_group_id}`"),
format!("Create {daemon_user_count} users with prefix `{nix_build_user_prefix}` starting at uid `{nix_build_user_id_base}`"),
],
)
]
}
}
#[tracing::instrument(skip_all, fields( #[tracing::instrument(skip_all, fields(
daemon_user_count = self.daemon_user_count, daemon_user_count = self.daemon_user_count,
@ -208,14 +238,15 @@ impl From<CreateUsersAndGroup> for Action {
#[derive(Debug, thiserror::Error, Serialize)] #[derive(Debug, thiserror::Error, Serialize)]
pub enum CreateUsersAndGroupError { pub enum CreateUsersAndGroupError {
#[error(transparent)] #[error("Creating user")]
CreateUser(#[from] CreateUserError), CreateUser(#[source] #[from] CreateUserError),
#[error("Multiple errors: {}", .0.iter().map(|v| format!("{v}")).collect::<Vec<_>>().join(" & "))] #[error("Multiple errors: {}", .0.iter().map(|v| format!("{v}")).collect::<Vec<_>>().join(" & "))]
CreateUsers(Vec<CreateUserError>), CreateUsers(Vec<CreateUserError>),
#[error(transparent)] #[error("Creating group")]
CreateGroup(#[from] CreateGroupError), CreateGroup(#[source] #[from] CreateGroupError),
#[error(transparent)] #[error("Joining spawned async task")]
Join( Join(
#[source]
#[from] #[from]
#[serde(serialize_with = "crate::serialize_error_to_display")] #[serde(serialize_with = "crate::serialize_error_to_display")]
JoinError, JoinError,

View file

@ -45,16 +45,21 @@ impl PlaceChannelConfiguration {
#[async_trait::async_trait] #[async_trait::async_trait]
impl Actionable for PlaceChannelConfiguration { impl Actionable for PlaceChannelConfiguration {
type Error = PlaceChannelConfigurationError; type Error = PlaceChannelConfigurationError;
fn description(&self) -> Vec<ActionDescription> {
fn describe_execute(&self) -> Vec<ActionDescription> {
let Self { let Self {
channels: _, channels: _,
create_file: _, create_file: _,
action_state: _, action_state: _,
} = self; } = self;
vec![ActionDescription::new( if self.action_state == ActionState::Completed {
"Place a channel configuration".to_string(), vec![]
vec!["Place a configuration at `{NIX_CHANNELS_PATH}` setting the channels".to_string()], } else {
)] vec![ActionDescription::new(
"Place channel configuration at `{NIX_CHANNELS_PATH}`".to_string(),
vec!["Place channel configuration at `{NIX_CHANNELS_PATH}`".to_string()],
)]
}
} }
#[tracing::instrument(skip_all, fields( #[tracing::instrument(skip_all, fields(
@ -79,6 +84,22 @@ impl Actionable for PlaceChannelConfiguration {
Ok(()) Ok(())
} }
fn describe_revert(&self) -> Vec<ActionDescription> {
let Self {
channels: _,
create_file: _,
action_state: _,
} = self;
if self.action_state == ActionState::Uncompleted {
vec![]
} else {
vec![ActionDescription::new(
"Remove channel configuration at `{NIX_CHANNELS_PATH}`".to_string(),
vec!["Remove channel configuration at `{NIX_CHANNELS_PATH}`".to_string()],
)]
}
}
#[tracing::instrument(skip_all, fields( #[tracing::instrument(skip_all, fields(
channels = self.channels.iter().map(|(c, u)| format!("{c}={u}")).collect::<Vec<_>>().join(", "), channels = self.channels.iter().map(|(c, u)| format!("{c}={u}")).collect::<Vec<_>>().join(", "),
))] ))]
@ -110,6 +131,6 @@ impl From<PlaceChannelConfiguration> for Action {
#[derive(Debug, thiserror::Error, Serialize)] #[derive(Debug, thiserror::Error, Serialize)]
pub enum PlaceChannelConfigurationError { pub enum PlaceChannelConfigurationError {
#[error(transparent)] #[error("Creating file")]
CreateFile(#[from] CreateFileError), CreateFile(#[source] #[from] CreateFileError),
} }

View file

@ -49,14 +49,18 @@ impl PlaceNixConfiguration {
impl Actionable for PlaceNixConfiguration { impl Actionable for PlaceNixConfiguration {
type Error = PlaceNixConfigurationError; type Error = PlaceNixConfigurationError;
fn description(&self) -> Vec<ActionDescription> { fn describe_execute(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new( if self.action_state == ActionState::Completed {
format!("Place the nix configuration in `{NIX_CONF}`"), vec![]
vec![ } else {
"This file is read by the Nix daemon to set its configuration options at runtime." vec![ActionDescription::new(
.to_string(), format!("Place the nix configuration in `{NIX_CONF}`"),
], vec![
)] "This file is read by the Nix daemon to set its configuration options at runtime."
.to_string(),
],
)]
}
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
@ -80,6 +84,20 @@ impl Actionable for PlaceNixConfiguration {
Ok(()) Ok(())
} }
fn describe_revert(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Uncompleted {
vec![]
} else {
vec![ActionDescription::new(
format!("Remove the nix configuration in `{NIX_CONF}`"),
vec![
"This file is read by the Nix daemon to set its configuration options at runtime."
.to_string(),
],
)]
}
}
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn revert(&mut self) -> Result<(), Self::Error> { async fn revert(&mut self) -> Result<(), Self::Error> {
let Self { let Self {
@ -110,8 +128,8 @@ impl From<PlaceNixConfiguration> for Action {
#[derive(Debug, thiserror::Error, Serialize)] #[derive(Debug, thiserror::Error, Serialize)]
pub enum PlaceNixConfigurationError { pub enum PlaceNixConfigurationError {
#[error(transparent)] #[error("Creating file")]
CreateFile(#[from] CreateFileError), CreateFile(#[source] #[from] CreateFileError),
#[error(transparent)] #[error("Creating directory")]
CreateDirectory(#[from] CreateDirectoryError), CreateDirectory(#[source] #[from] CreateDirectoryError),
} }

View file

@ -44,7 +44,7 @@ impl ProvisionNix {
#[async_trait::async_trait] #[async_trait::async_trait]
impl Actionable for ProvisionNix { impl Actionable for ProvisionNix {
type Error = ProvisionNixError; type Error = ProvisionNixError;
fn description(&self) -> Vec<ActionDescription> { fn describe_execute(&self) -> Vec<ActionDescription> {
let Self { let Self {
fetch_nix, fetch_nix,
create_users_and_group, create_users_and_group,
@ -52,13 +52,16 @@ impl Actionable for ProvisionNix {
move_unpacked_nix, move_unpacked_nix,
action_state: _, action_state: _,
} = &self; } = &self;
if self.action_state == ActionState::Completed {
let mut buf = fetch_nix.description(); vec![]
buf.append(&mut create_users_and_group.description()); } else {
buf.append(&mut create_nix_tree.description()); let mut buf = fetch_nix.describe_execute();
buf.append(&mut move_unpacked_nix.description()); buf.append(&mut create_users_and_group.describe_execute());
buf.append(&mut create_nix_tree.describe_execute());
buf buf.append(&mut move_unpacked_nix.describe_execute());
buf
}
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
@ -97,6 +100,27 @@ impl Actionable for ProvisionNix {
Ok(()) Ok(())
} }
fn describe_revert(&self) -> Vec<ActionDescription> {
let Self {
fetch_nix,
create_users_and_group,
create_nix_tree,
move_unpacked_nix,
action_state: _,
} = &self;
if self.action_state == ActionState::Uncompleted {
vec![]
} else {
let mut buf = Vec::default();
buf.append(&mut move_unpacked_nix.describe_revert());
buf.append(&mut create_nix_tree.describe_revert());
buf.append(&mut create_users_and_group.describe_revert());
buf.append(&mut fetch_nix.describe_revert());
buf
}
}
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn revert(&mut self) -> Result<(), Self::Error> { async fn revert(&mut self) -> Result<(), Self::Error> {
let Self { let Self {
@ -148,18 +172,19 @@ pub enum ProvisionNixError {
#[serde(serialize_with = "crate::serialize_error_to_display")] #[serde(serialize_with = "crate::serialize_error_to_display")]
std::io::Error, std::io::Error,
), ),
#[error(transparent)] #[error("Fetching Nix")]
FetchNix(#[from] FetchNixError), FetchNix(#[source] #[from] FetchNixError),
#[error(transparent)] #[error("Joining spawned async task")]
Join( Join(
#[source]
#[from] #[from]
#[serde(serialize_with = "crate::serialize_error_to_display")] #[serde(serialize_with = "crate::serialize_error_to_display")]
JoinError, JoinError,
), ),
#[error(transparent)] #[error("Creating users and group")]
CreateUsersAndGroup(#[from] CreateUsersAndGroupError), CreateUsersAndGroup(#[source] #[from] CreateUsersAndGroupError),
#[error(transparent)] #[error("Creating nix tree")]
CreateNixTree(#[from] CreateNixTreeError), CreateNixTree(#[source] #[from] CreateNixTreeError),
#[error(transparent)] #[error("Moving unpacked nix")]
MoveUnpackedNix(#[from] MoveUnpackedNixError), MoveUnpackedNix(#[source] #[from] MoveUnpackedNixError),
} }

View file

@ -26,8 +26,12 @@ impl StartNixDaemon {
impl Actionable for StartNixDaemon { impl Actionable for StartNixDaemon {
type Error = StartNixDaemonError; type Error = StartNixDaemonError;
fn description(&self) -> Vec<ActionDescription> { fn describe_execute(&self) -> Vec<ActionDescription> {
self.start_systemd_socket.description() if self.action_state == ActionState::Completed {
vec![]
} else {
self.start_systemd_socket.describe_execute()
}
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
@ -49,6 +53,14 @@ impl Actionable for StartNixDaemon {
Ok(()) Ok(())
} }
fn describe_revert(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Uncompleted {
vec![]
} else {
self.start_systemd_socket.describe_revert()
}
}
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn revert(&mut self) -> Result<(), Self::Error> { async fn revert(&mut self) -> Result<(), Self::Error> {
let Self { let Self {
@ -78,6 +90,6 @@ impl From<StartNixDaemon> for Action {
#[derive(Debug, thiserror::Error, Serialize)] #[derive(Debug, thiserror::Error, Serialize)]
pub enum StartNixDaemonError { pub enum StartNixDaemonError {
#[error(transparent)] #[error("Starting systemd unit")]
StartSystemdUnit(#[from] StartSystemdUnitError), StartSystemdUnit(#[source] #[from] StartSystemdUnitError),
} }

View file

@ -23,7 +23,8 @@ use self::base::{StartSystemdUnit, StartSystemdUnitError};
pub trait Actionable: DeserializeOwned + Serialize + Into<Action> { pub trait Actionable: DeserializeOwned + Serialize + Into<Action> {
type Error: std::error::Error + std::fmt::Debug + Serialize + Into<ActionError>; type Error: std::error::Error + std::fmt::Debug + Serialize + Into<ActionError>;
fn description(&self) -> Vec<ActionDescription>; fn describe_execute(&self) -> Vec<ActionDescription>;
fn describe_revert(&self) -> Vec<ActionDescription>;
// They should also have an `async fn plan(args...) -> Result<ActionState<Self>, Self::Error>;` // They should also have an `async fn plan(args...) -> Result<ActionState<Self>, Self::Error>;`
async fn execute(&mut self) -> Result<(), Self::Error>; async fn execute(&mut self) -> Result<(), Self::Error>;
@ -123,26 +124,26 @@ pub enum ActionError {
#[async_trait::async_trait] #[async_trait::async_trait]
impl Actionable for Action { impl Actionable for Action {
type Error = ActionError; type Error = ActionError;
fn description(&self) -> Vec<ActionDescription> { fn describe_execute(&self) -> Vec<ActionDescription> {
match self { match self {
Action::ConfigureNixDaemonService(i) => i.description(), Action::ConfigureNixDaemonService(i) => i.describe_execute(),
Action::ConfigureNix(i) => i.description(), Action::ConfigureNix(i) => i.describe_execute(),
Action::ConfigureShellProfile(i) => i.description(), Action::ConfigureShellProfile(i) => i.describe_execute(),
Action::CreateDirectory(i) => i.description(), Action::CreateDirectory(i) => i.describe_execute(),
Action::CreateFile(i) => i.description(), Action::CreateFile(i) => i.describe_execute(),
Action::CreateGroup(i) => i.description(), Action::CreateGroup(i) => i.describe_execute(),
Action::CreateOrAppendFile(i) => i.description(), Action::CreateOrAppendFile(i) => i.describe_execute(),
Action::CreateNixTree(i) => i.description(), Action::CreateNixTree(i) => i.describe_execute(),
Action::CreateUser(i) => i.description(), Action::CreateUser(i) => i.describe_execute(),
Action::CreateUsersAndGroup(i) => i.description(), Action::CreateUsersAndGroup(i) => i.describe_execute(),
Action::FetchNix(i) => i.description(), Action::FetchNix(i) => i.describe_execute(),
Action::MoveUnpackedNix(i) => i.description(), Action::MoveUnpackedNix(i) => i.describe_execute(),
Action::PlaceChannelConfiguration(i) => i.description(), Action::PlaceChannelConfiguration(i) => i.describe_execute(),
Action::PlaceNixConfiguration(i) => i.description(), Action::PlaceNixConfiguration(i) => i.describe_execute(),
Action::SetupDefaultProfile(i) => i.description(), Action::SetupDefaultProfile(i) => i.describe_execute(),
Action::StartNixDaemon(i) => i.description(), Action::StartNixDaemon(i) => i.describe_execute(),
Action::StartSystemdUnit(i) => i.description(), Action::StartSystemdUnit(i) => i.describe_execute(),
Action::ProvisionNix(i) => i.description(), Action::ProvisionNix(i) => i.describe_execute(),
} }
} }
@ -170,6 +171,29 @@ impl Actionable for Action {
Ok(()) Ok(())
} }
fn describe_revert(&self) -> Vec<ActionDescription> {
match self {
Action::ConfigureNixDaemonService(i) => i.describe_revert(),
Action::ConfigureNix(i) => i.describe_revert(),
Action::ConfigureShellProfile(i) => i.describe_revert(),
Action::CreateDirectory(i) => i.describe_revert(),
Action::CreateFile(i) => i.describe_revert(),
Action::CreateGroup(i) => i.describe_revert(),
Action::CreateOrAppendFile(i) => i.describe_revert(),
Action::CreateNixTree(i) => i.describe_revert(),
Action::CreateUser(i) => i.describe_revert(),
Action::CreateUsersAndGroup(i) => i.describe_revert(),
Action::FetchNix(i) => i.describe_revert(),
Action::MoveUnpackedNix(i) => i.describe_revert(),
Action::PlaceChannelConfiguration(i) => i.describe_revert(),
Action::PlaceNixConfiguration(i) => i.describe_revert(),
Action::SetupDefaultProfile(i) => i.describe_revert(),
Action::StartNixDaemon(i) => i.describe_revert(),
Action::StartSystemdUnit(i) => i.describe_revert(),
Action::ProvisionNix(i) => i.describe_revert(),
}
}
async fn revert(&mut self) -> Result<(), Self::Error> { async fn revert(&mut self) -> Result<(), Self::Error> {
match self { match self {
Action::ConfigureNixDaemonService(i) => i.revert().await?, Action::ConfigureNixDaemonService(i) => i.revert().await?,

View file

@ -96,11 +96,17 @@ impl CommandExecute for HarmonicCli {
let mut plan = InstallPlan::new(settings).await?; let mut plan = InstallPlan::new(settings).await?;
// TODO(@Hoverbear): Make this smarter // TODO(@Hoverbear): Make this smarter
if !interaction::confirm(plan.description()).await? { if !interaction::confirm(plan.describe_execute()).await? {
interaction::clean_exit_with_message("Okay, didn't do anything! Bye!").await; interaction::clean_exit_with_message("Okay, didn't do anything! Bye!").await;
} }
let _receipt = plan.install().await?; if let Err(err) = plan.install().await {
tracing::error!("{err:#?}");
if !interaction::confirm(plan.describe_revert()).await? {
interaction::clean_exit_with_message("Okay, didn't do anything! Bye!").await;
}
plan.revert().await?
}
Ok(ExitCode::SUCCESS) Ok(ExitCode::SUCCESS)
}, },

View file

@ -32,12 +32,18 @@ impl CommandExecute for Execute {
let mut plan: InstallPlan = serde_json::from_str(&install_plan_string)?; let mut plan: InstallPlan = serde_json::from_str(&install_plan_string)?;
if !no_confirm { if !no_confirm {
if !interaction::confirm(plan.description()).await? { if !interaction::confirm(plan.describe_execute()).await? {
interaction::clean_exit_with_message("Okay, didn't do anything! Bye!").await; interaction::clean_exit_with_message("Okay, didn't do anything! Bye!").await;
} }
} }
plan.install().await?; if let Err(err) = plan.install().await {
tracing::error!("{err:#?}");
if !interaction::confirm(plan.describe_revert()).await? {
interaction::clean_exit_with_message("Okay, didn't do anything! Bye!").await;
}
plan.revert().await?
}
Ok(ExitCode::SUCCESS) Ok(ExitCode::SUCCESS)
} }

View file

@ -35,7 +35,7 @@ impl CommandExecute for Revert {
let mut plan: InstallPlan = serde_json::from_str(&install_receipt_string)?; let mut plan: InstallPlan = serde_json::from_str(&install_receipt_string)?;
if !no_confirm { if !no_confirm {
if !interaction::confirm(plan.description()).await? { if !interaction::confirm(plan.describe_execute()).await? {
interaction::clean_exit_with_message("Okay, didn't do anything! Bye!").await; interaction::clean_exit_with_message("Okay, didn't do anything! Bye!").await;
} }
} }

View file

@ -36,8 +36,24 @@ pub struct InstallPlan {
} }
impl InstallPlan { impl InstallPlan {
pub async fn new(settings: InstallSettings) -> Result<Self, HarmonicError> {
Ok(Self {
settings: settings.clone(),
provision_nix: ProvisionNix::plan(settings.clone())
.await
.map_err(|e| ActionError::from(e))?,
configure_nix: ConfigureNix::plan(settings)
.await
.map_err(|e| ActionError::from(e))?,
start_nix_daemon: StartNixDaemon::plan()
.await
.map_err(|e| ActionError::from(e))?,
})
}
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
pub fn description(&self) -> String { pub fn describe_execute(&self) -> String {
let Self { settings, provision_nix, configure_nix, start_nix_daemon } = self;
format!( format!(
"\ "\
This Nix install is for:\n\ This Nix install is for:\n\
@ -50,17 +66,16 @@ impl InstallPlan {
", ",
os_type = "Linux", os_type = "Linux",
init_type = "systemd", init_type = "systemd",
nix_channels = self nix_channels = settings
.settings
.channels .channels
.iter() .iter()
.map(|(name, url)| format!("{name}={url}")) .map(|(name, url)| format!("{name}={url}"))
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(","), .join(","),
actions = { actions = {
let mut buf = self.provision_nix.description(); let mut buf = provision_nix.describe_execute();
buf.append(&mut self.configure_nix.description()); buf.append(&mut configure_nix.describe_execute());
buf.append(&mut self.start_nix_daemon.description()); buf.append(&mut start_nix_daemon.describe_execute());
buf.iter() buf.iter()
.map(|desc| { .map(|desc| {
let ActionDescription { let ActionDescription {
@ -82,20 +97,7 @@ impl InstallPlan {
}, },
) )
} }
pub async fn new(settings: InstallSettings) -> Result<Self, HarmonicError> {
Ok(Self {
settings: settings.clone(),
provision_nix: ProvisionNix::plan(settings.clone())
.await
.map_err(|e| ActionError::from(e))?,
configure_nix: ConfigureNix::plan(settings)
.await
.map_err(|e| ActionError::from(e))?,
start_nix_daemon: StartNixDaemon::plan()
.await
.map_err(|e| ActionError::from(e))?,
})
}
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
pub async fn install(&mut self) -> Result<(), HarmonicError> { pub async fn install(&mut self) -> Result<(), HarmonicError> {
@ -125,6 +127,53 @@ impl InstallPlan {
Ok(()) Ok(())
} }
#[tracing::instrument(skip_all)]
pub fn describe_revert(&self) -> String {
let Self { settings, provision_nix, configure_nix, start_nix_daemon } = self;
format!(
"\
This Nix uninstall is for:\n\
Operating System: {os_type}\n\
Init system: {init_type}\n\
Nix channels: {nix_channels}\n\
\n\
The following actions will be taken:\n\
{actions}
",
os_type = "Linux",
init_type = "systemd",
nix_channels = settings
.channels
.iter()
.map(|(name, url)| format!("{name}={url}"))
.collect::<Vec<_>>()
.join(","),
actions = {
let mut buf = provision_nix.describe_revert();
buf.append(&mut configure_nix.describe_revert());
buf.append(&mut start_nix_daemon.describe_revert());
buf.iter()
.map(|desc| {
let ActionDescription {
description,
explanation,
} = desc;
let mut buf = String::default();
buf.push_str(&format!("* {description}\n"));
if self.settings.explain {
for line in explanation {
buf.push_str(&format!(" {line}\n"));
}
}
buf
})
.collect::<Vec<_>>()
.join("\n")
},
)
}
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
pub async fn revert(&mut self) -> Result<(), HarmonicError> { pub async fn revert(&mut self) -> Result<(), HarmonicError> {
// This is **deliberately sequential**. // This is **deliberately sequential**.