This commit is contained in:
Ana Hobden 2022-09-28 13:32:54 -07:00
parent 878a071183
commit 72a4356a8b
22 changed files with 159 additions and 94 deletions

View file

@ -98,7 +98,7 @@ impl Actionable for ConfigureNixDaemonService {
vec![ActionDescription::new(
"Unconfigure Nix daemon related settings with systemd".to_string(),
vec![
"Run `systemctl disable {SOCKET_SRC}`".to_string(),
"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(),

View file

@ -28,12 +28,10 @@ impl CreateDirectory {
let path = path.as_ref();
if path.exists() && !force {
return Err(CreateDirectoryError::Exists(
std::io::Error::new(
std::io::ErrorKind::AlreadyExists,
format!("Directory `{}` already exists", path.display()),
),
));
return Err(CreateDirectoryError::Exists(std::io::Error::new(
std::io::ErrorKind::AlreadyExists,
format!("Directory `{}` already exists", path.display()),
)));
}
Ok(Self {
@ -114,7 +112,6 @@ impl Actionable for CreateDirectory {
Ok(())
}
fn describe_revert(&self) -> Vec<ActionDescription> {
let Self {
path,
@ -173,10 +170,7 @@ impl From<CreateDirectory> for Action {
#[derive(Debug, thiserror::Error, Serialize)]
pub enum CreateDirectoryError {
#[error(transparent)]
Exists(
#[serde(serialize_with = "crate::serialize_error_to_display")]
std::io::Error,
),
Exists(#[serde(serialize_with = "crate::serialize_error_to_display")] std::io::Error),
#[error("Creating directory `{0}`")]
Creating(
std::path::PathBuf,

View file

@ -126,7 +126,6 @@ impl Actionable for CreateFile {
Ok(())
}
fn describe_revert(&self) -> Vec<ActionDescription> {
let Self {
path,
@ -142,9 +141,7 @@ impl Actionable for CreateFile {
} else {
vec![ActionDescription::new(
format!("Delete file `{}`", path.display()),
vec![format!(
"Delete file `{}`", path.display()
)],
vec![format!("Delete file `{}`", path.display())],
)]
}
}

View file

@ -70,7 +70,6 @@ impl Actionable for CreateGroup {
Ok(())
}
fn describe_revert(&self) -> Vec<ActionDescription> {
let Self {
name,

View file

@ -143,7 +143,8 @@ impl Actionable for CreateOrAppendFile {
vec![ActionDescription::new(
format!("Delete Nix related fragment from file `{}`", path.display()),
vec![format!(
"Delete Nix related fragment from file `{}`. Fragment: `{buf}`", path.display()
"Delete Nix related fragment from file `{}`. Fragment: `{buf}`",
path.display()
)],
)]
}
@ -192,7 +193,7 @@ impl Actionable for CreateOrAppendFile {
remove_file(&path)
.await
.map_err(|e| Self::Error::RemoveFile(path.to_owned(), e))?;
tracing::trace!("Removed file (since all content was removed)");
} else {
file.seek(SeekFrom::Start(0))

View file

@ -33,7 +33,12 @@ impl Actionable for CreateUser {
if self.action_state == ActionState::Completed {
vec![]
} else {
let Self { name, uid, gid, action_state: _ } = self;
let Self {
name,
uid,
gid,
action_state: _,
} = self;
vec![ActionDescription::new(
format!("Create user {name} with UID {uid} with group {gid}"),
@ -89,12 +94,16 @@ impl Actionable for CreateUser {
Ok(())
}
fn describe_revert(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Uncompleted {
vec![]
} else {
let Self { name, uid, gid, action_state: _ } = self;
let Self {
name,
uid,
gid,
action_state: _,
} = self;
vec![ActionDescription::new(
format!("Delete user {name} with UID {uid} with group {gid}"),

View file

@ -43,10 +43,7 @@ impl Actionable for FetchNix {
} else {
vec![ActionDescription::new(
format!("Fetch Nix from `{url}`"),
vec![format!(
"Unpack it to `{}` (moved later)",
dest.display()
)],
vec![format!("Unpack it to `{}` (moved later)", dest.display())],
)]
}
}
@ -77,7 +74,9 @@ impl Actionable for FetchNix {
let handle: Result<(), Self::Error> = spawn_blocking(move || {
let decoder = xz2::read::XzDecoder::new(bytes.reader());
let mut archive = tar::Archive::new(decoder);
archive.unpack(&dest_clone).map_err(Self::Error::Unarchive)?;
archive
.unpack(&dest_clone)
.map_err(Self::Error::Unarchive)?;
tracing::debug!(dest = %dest_clone.display(), "Downloaded & extracted Nix");
Ok(())
})

View file

@ -46,10 +46,7 @@ impl Actionable for MoveUnpackedNix {
dest = DEST,
))]
async fn execute(&mut self) -> Result<(), Self::Error> {
let Self {
src,
action_state,
} = self;
let Self { src, action_state } = self;
if *action_state == ActionState::Completed {
tracing::trace!("Already completed: Moving Nix");
return Ok(());
@ -84,7 +81,6 @@ impl Actionable for MoveUnpackedNix {
vec![/* Deliberately empty -- this is a noop */]
}
}
#[tracing::instrument(skip_all, fields(
src = %self.src.display(),

View file

@ -29,16 +29,13 @@ impl SetupDefaultProfile {
impl Actionable for SetupDefaultProfile {
type Error = SetupDefaultProfileError;
fn describe_execute(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Completed {
vec![]
} else {
vec![ActionDescription::new(
"Setup the default Nix profile".to_string(),
vec![
"TODO".to_string()
]
vec!["TODO".to_string()],
)]
}
}
@ -145,9 +142,7 @@ impl Actionable for SetupDefaultProfile {
} else {
vec![ActionDescription::new(
"Unset the default Nix profile".to_string(),
vec![
"TODO".to_string()
]
vec!["TODO".to_string()],
)]
}
}

View file

@ -2,13 +2,12 @@ use serde::Serialize;
use crate::actions::{
base::{
ConfigureNixDaemonService, ConfigureNixDaemonServiceError,
SetupDefaultProfile, SetupDefaultProfileError,
ConfigureNixDaemonService, ConfigureNixDaemonServiceError, SetupDefaultProfile,
SetupDefaultProfileError,
},
meta::{
ConfigureShellProfile, ConfigureShellProfileError,
PlaceChannelConfiguration, PlaceChannelConfigurationError,
PlaceNixConfiguration, PlaceNixConfigurationError,
ConfigureShellProfile, ConfigureShellProfileError, PlaceChannelConfiguration,
PlaceChannelConfigurationError, PlaceNixConfiguration, PlaceNixConfigurationError,
},
Action, ActionState,
};
@ -189,7 +188,6 @@ impl Actionable for ConfigureNix {
}
}
#[tracing::instrument(skip_all)]
async fn revert(&mut self) -> Result<(), Self::Error> {
let Self {
@ -230,13 +228,33 @@ impl From<ConfigureNix> for Action {
#[derive(Debug, thiserror::Error, Serialize)]
pub enum ConfigureNixError {
#[error("Setting up default profile")]
SetupDefaultProfile(#[source] #[from] SetupDefaultProfileError),
SetupDefaultProfile(
#[source]
#[from]
SetupDefaultProfileError,
),
#[error("Placing Nix configuration")]
PlaceNixConfiguration(#[source] #[from] PlaceNixConfigurationError),
PlaceNixConfiguration(
#[source]
#[from]
PlaceNixConfigurationError,
),
#[error("Placing channel configuration")]
PlaceChannelConfiguration(#[source] #[from] PlaceChannelConfigurationError),
PlaceChannelConfiguration(
#[source]
#[from]
PlaceChannelConfigurationError,
),
#[error("Configuring Nix daemon")]
ConfigureNixDaemonService(#[source] #[from] ConfigureNixDaemonServiceError),
ConfigureNixDaemonService(
#[source]
#[from]
ConfigureNixDaemonServiceError,
),
#[error("Configuring shell profile")]
ConfigureShellProfile(#[source] #[from] ConfigureShellProfileError),
ConfigureShellProfile(
#[source]
#[from]
ConfigureShellProfileError,
),
}

View file

@ -129,7 +129,6 @@ impl Actionable for ConfigureShellProfile {
}
}
#[tracing::instrument(skip_all)]
async fn revert(&mut self) -> Result<(), Self::Error> {
let Self {
@ -192,7 +191,7 @@ pub enum ConfigureShellProfileError {
CreateOrAppendFile(
#[from]
#[source]
CreateOrAppendFileError
CreateOrAppendFileError,
),
#[error("Multiple errors: {}", .0.iter().map(|v| format!("{v}")).collect::<Vec<_>>().join(" & "))]
MultipleCreateOrAppendFile(Vec<CreateOrAppendFileError>),

View file

@ -151,5 +151,9 @@ impl From<CreateNixTree> for Action {
#[derive(Debug, thiserror::Error, Serialize)]
pub enum CreateNixTreeError {
#[error("Creating directory")]
CreateDirectory(#[source] #[from] CreateDirectoryError),
CreateDirectory(
#[source]
#[from]
CreateDirectoryError,
),
}

View file

@ -241,11 +241,19 @@ impl From<CreateUsersAndGroup> for Action {
#[derive(Debug, thiserror::Error, Serialize)]
pub enum CreateUsersAndGroupError {
#[error("Creating user")]
CreateUser(#[source] #[from] CreateUserError),
CreateUser(
#[source]
#[from]
CreateUserError,
),
#[error("Multiple errors: {}", .0.iter().map(|v| format!("{v}")).collect::<Vec<_>>().join(" & "))]
CreateUsers(Vec<CreateUserError>),
#[error("Creating group")]
CreateGroup(#[source] #[from] CreateGroupError),
CreateGroup(
#[source]
#[from]
CreateGroupError,
),
#[error("Joining spawned async task")]
Join(
#[source]

View file

@ -57,7 +57,9 @@ impl Actionable for PlaceChannelConfiguration {
} else {
vec![ActionDescription::new(
format!("Place channel configuration at `{NIX_CHANNELS_PATH}`"),
vec![format!("Place channel configuration at `{NIX_CHANNELS_PATH}`")],
vec![format!(
"Place channel configuration at `{NIX_CHANNELS_PATH}`"
)],
)]
}
}
@ -96,7 +98,9 @@ impl Actionable for PlaceChannelConfiguration {
} else {
vec![ActionDescription::new(
format!("Remove channel configuration at `{NIX_CHANNELS_PATH}`"),
vec![format!("Remove channel configuration at `{NIX_CHANNELS_PATH}`")],
vec![format!(
"Remove channel configuration at `{NIX_CHANNELS_PATH}`"
)],
)]
}
}
@ -116,7 +120,7 @@ impl Actionable for PlaceChannelConfiguration {
}
*action_state = ActionState::Progress;
tracing::debug!("Removing channel configuration");
create_file.revert().await?;
tracing::debug!("Removed channel configuration");
@ -134,5 +138,9 @@ impl From<PlaceChannelConfiguration> for Action {
#[derive(Debug, thiserror::Error, Serialize)]
pub enum PlaceChannelConfigurationError {
#[error("Creating file")]
CreateFile(#[source] #[from] CreateFileError),
CreateFile(
#[source]
#[from]
CreateFileError,
),
}

View file

@ -131,7 +131,15 @@ impl From<PlaceNixConfiguration> for Action {
#[derive(Debug, thiserror::Error, Serialize)]
pub enum PlaceNixConfigurationError {
#[error("Creating file")]
CreateFile(#[source] #[from] CreateFileError),
CreateFile(
#[source]
#[from]
CreateFileError,
),
#[error("Creating directory")]
CreateDirectory(#[source] #[from] CreateDirectoryError),
CreateDirectory(
#[source]
#[from]
CreateDirectoryError,
),
}

View file

@ -59,7 +59,7 @@ impl Actionable for ProvisionNix {
buf.append(&mut create_users_and_group.describe_execute());
buf.append(&mut create_nix_tree.describe_execute());
buf.append(&mut move_unpacked_nix.describe_execute());
buf
}
}
@ -121,7 +121,6 @@ impl Actionable for ProvisionNix {
}
}
#[tracing::instrument(skip_all)]
async fn revert(&mut self) -> Result<(), Self::Error> {
let Self {
@ -175,7 +174,11 @@ pub enum ProvisionNixError {
std::io::Error,
),
#[error("Fetching Nix")]
FetchNix(#[source] #[from] FetchNixError),
FetchNix(
#[source]
#[from]
FetchNixError,
),
#[error("Joining spawned async task")]
Join(
#[source]
@ -184,9 +187,21 @@ pub enum ProvisionNixError {
JoinError,
),
#[error("Creating users and group")]
CreateUsersAndGroup(#[source] #[from] CreateUsersAndGroupError),
CreateUsersAndGroup(
#[source]
#[from]
CreateUsersAndGroupError,
),
#[error("Creating nix tree")]
CreateNixTree(#[source] #[from] CreateNixTreeError),
CreateNixTree(
#[source]
#[from]
CreateNixTreeError,
),
#[error("Moving unpacked nix")]
MoveUnpackedNix(#[source] #[from] MoveUnpackedNixError),
MoveUnpackedNix(
#[source]
#[from]
MoveUnpackedNixError,
),
}

View file

@ -93,5 +93,9 @@ impl From<StartNixDaemon> for Action {
#[derive(Debug, thiserror::Error, Serialize)]
pub enum StartNixDaemonError {
#[error("Starting systemd unit")]
StartSystemdUnit(#[source] #[from] StartSystemdUnitError),
StartSystemdUnit(
#[source]
#[from]
StartSystemdUnitError,
),
}

View file

@ -5,15 +5,15 @@ use base::{
ConfigureNixDaemonService, ConfigureNixDaemonServiceError, CreateDirectory,
CreateDirectoryError, CreateFile, CreateFileError, CreateGroup, CreateGroupError,
CreateOrAppendFile, CreateOrAppendFileError, CreateUser, CreateUserError, FetchNix,
FetchNixError, MoveUnpackedNix, MoveUnpackedNixError,
SetupDefaultProfile, SetupDefaultProfileError,
FetchNixError, MoveUnpackedNix, MoveUnpackedNixError, SetupDefaultProfile,
SetupDefaultProfileError,
};
use meta::{
ConfigureNix, ConfigureNixError, ConfigureShellProfile, ConfigureShellProfileError,
CreateNixTree, CreateNixTreeError, CreateUsersAndGroup, PlaceChannelConfiguration,
PlaceNixConfiguration, PlaceNixConfigurationError,
PlaceChannelConfigurationError, CreateUsersAndGroupError, ProvisionNix,
ProvisionNixError, StartNixDaemon, StartNixDaemonError,
CreateNixTree, CreateNixTreeError, CreateUsersAndGroup, CreateUsersAndGroupError,
PlaceChannelConfiguration, PlaceChannelConfigurationError, PlaceNixConfiguration,
PlaceNixConfigurationError, ProvisionNix, ProvisionNixError, StartNixDaemon,
StartNixDaemonError,
};
use serde::{de::DeserializeOwned, Deserialize, Serialize};

View file

@ -3,8 +3,8 @@ pub(crate) mod subcommand;
use crate::{cli::arg::ChannelValue, interaction};
use clap::{ArgAction, Parser};
use harmonic::{InstallPlan, InstallSettings};
use eyre::eyre;
use harmonic::{InstallPlan, InstallSettings};
use std::process::ExitCode;
use self::subcommand::HarmonicSubcommand;
@ -100,11 +100,12 @@ impl CommandExecute for HarmonicCli {
if !interaction::confirm(plan.describe_execute()).await? {
interaction::clean_exit_with_message("Okay, didn't do anything! Bye!").await;
}
if let Err(err) = plan.install().await {
tracing::error!("{:?}", eyre!(err));
if !interaction::confirm(plan.describe_revert()).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.revert().await?
}

View file

@ -1,7 +1,7 @@
use std::{path::PathBuf, process::ExitCode};
use clap::{ArgAction, Parser};
use eyre::{WrapErr, eyre};
use eyre::{eyre, WrapErr};
use harmonic::InstallPlan;
use crate::{cli::CommandExecute, interaction};

View file

@ -20,7 +20,7 @@ pub(crate) async fn confirm(question: impl AsRef<str>) -> eyre::Result<bool> {
stdout.write_all(with_confirm.as_bytes()).await?;
stdout.flush().await?;
crossterm::terminal::enable_raw_mode()?;
let mut reader = EventStream::new();
let retval = loop {
@ -30,7 +30,7 @@ pub(crate) async fn confirm(question: impl AsRef<str>) -> eyre::Result<bool> {
if let crossterm::event::Event::Key(key) = event {
match key.code {
KeyCode::Enter => continue, // Many users will hit enter accidently when they are agreeing/declining prompts.
// TODO(@hoverbear): Should maybe actually even wait for it?
// TODO(@hoverbear): Should maybe actually even wait for it?
KeyCode::Char('y') => break Ok(true),
_ => {
stdout

View file

@ -53,7 +53,12 @@ impl InstallPlan {
#[tracing::instrument(skip_all)]
pub fn describe_execute(&self) -> String {
let Self { settings, provision_nix, configure_nix, start_nix_daemon } = self;
let Self {
settings,
provision_nix,
configure_nix,
start_nix_daemon,
} = self;
format!(
"\
This Nix install is for:\n\
@ -98,36 +103,40 @@ impl InstallPlan {
)
}
#[tracing::instrument(skip_all)]
pub async fn install(&mut self) -> Result<(), HarmonicError> {
// This is **deliberately sequential**.
// Actions which are parallelizable are represented by "group actions" like CreateUsers
// The plan itself represents the concept of the sequence of stages.
if let Err(err) = self.provision_nix.execute().await {
write_receipt(self.clone()).await?;
return Err(ActionError::from(err).into())
return Err(ActionError::from(err).into());
}
if let Err(err) = self.configure_nix.execute().await {
write_receipt(self.clone()).await?;
return Err(ActionError::from(err).into())
return Err(ActionError::from(err).into());
}
if let Err(err) = self.start_nix_daemon.execute().await {
write_receipt(self.clone()).await?;
return Err(ActionError::from(err).into())
return Err(ActionError::from(err).into());
}
write_receipt(self.clone()).await?;
Ok(())
}
#[tracing::instrument(skip_all)]
pub fn describe_revert(&self) -> String {
let Self { settings, provision_nix, configure_nix, start_nix_daemon } = self;
let Self {
settings,
provision_nix,
configure_nix,
start_nix_daemon,
} = self;
format!(
"\
This Nix uninstall is for:\n\
@ -179,17 +188,17 @@ impl InstallPlan {
// The plan itself represents the concept of the sequence of stages.
if let Err(err) = self.start_nix_daemon.revert().await {
write_receipt(self.clone()).await?;
return Err(ActionError::from(err).into())
return Err(ActionError::from(err).into());
}
if let Err(err) = self.configure_nix.revert().await {
write_receipt(self.clone()).await?;
return Err(ActionError::from(err).into())
return Err(ActionError::from(err).into());
}
if let Err(err) = self.provision_nix.revert().await {
write_receipt(self.clone()).await?;
return Err(ActionError::from(err).into())
return Err(ActionError::from(err).into());
}
Ok(())
@ -197,7 +206,8 @@ impl InstallPlan {
}
async fn write_receipt(plan: InstallPlan) -> Result<(), HarmonicError> {
tokio::fs::create_dir_all("/nix").await
tokio::fs::create_dir_all("/nix")
.await
.map_err(|e| HarmonicError::RecordingReceipt(PathBuf::from("/nix"), e))?;
let install_receipt_path = PathBuf::from("/nix/receipt.json");
let self_json =