Uninstall works
This commit is contained in:
parent
63a08acdea
commit
48646c7cad
|
@ -1,11 +1,12 @@
|
|||
use std::path::{Path, PathBuf};
|
||||
|
||||
use serde::Serialize;
|
||||
use tokio::fs::remove_file;
|
||||
use tokio::process::Command;
|
||||
|
||||
use crate::{execute_command, HarmonicError};
|
||||
use crate::{execute_command};
|
||||
|
||||
use crate::actions::{ActionDescription, Actionable, ActionState, Action, ActionError};
|
||||
use crate::actions::{ActionDescription, Actionable, ActionState, Action};
|
||||
|
||||
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";
|
||||
|
@ -68,9 +69,11 @@ impl Actionable for ConfigureNixDaemonService {
|
|||
)
|
||||
.await.map_err(Self::Error::CommandFailed)?;
|
||||
|
||||
execute_command(Command::new("systemctl").arg("link").arg(SOCKET_SRC)).await.map_err(Self::Error::CommandFailed)?;
|
||||
execute_command(Command::new("systemctl").arg("link").arg(SOCKET_SRC)).await
|
||||
.map_err(Self::Error::CommandFailed)?;
|
||||
|
||||
execute_command(Command::new("systemctl").arg("daemon-reload")).await.map_err(Self::Error::CommandFailed)?;
|
||||
execute_command(Command::new("systemctl").arg("daemon-reload")).await
|
||||
.map_err(Self::Error::CommandFailed)?;
|
||||
|
||||
*action_state = ActionState::Completed;
|
||||
Ok(())
|
||||
|
@ -79,8 +82,32 @@ impl Actionable for ConfigureNixDaemonService {
|
|||
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn revert(&mut self) -> Result<(), Self::Error> {
|
||||
todo!();
|
||||
let Self { action_state } = self;
|
||||
tracing::info!("Unconfiguring nix daemon service");
|
||||
|
||||
// We don't need to do this! Systemd does it for us! (In fact, it's an error if we try to do this...)
|
||||
execute_command(Command::new("systemctl").args(["disable", SOCKET_SRC])).await
|
||||
.map_err(Self::Error::CommandFailed)?;
|
||||
|
||||
execute_command(
|
||||
Command::new("systemctl").args(["disable", SERVICE_SRC]),
|
||||
)
|
||||
.await.map_err(Self::Error::CommandFailed)?;
|
||||
|
||||
execute_command(
|
||||
Command::new("systemd-tmpfiles")
|
||||
.arg("--remove")
|
||||
.arg("--prefix=/nix/var/nix"),
|
||||
)
|
||||
.await.map_err(Self::Error::CommandFailed)?;
|
||||
|
||||
remove_file(TMPFILES_DEST).await
|
||||
.map_err(|e| Self::Error::RemoveFile(PathBuf::from(TMPFILES_DEST), e))?;
|
||||
|
||||
execute_command(Command::new("systemctl").arg("daemon-reload")).await
|
||||
.map_err(Self::Error::CommandFailed)?;
|
||||
|
||||
*action_state = ActionState::Reverted;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -103,12 +130,14 @@ pub enum ConfigureNixDaemonServiceError {
|
|||
#[serde(serialize_with = "crate::serialize_error_to_display")]
|
||||
std::io::Error
|
||||
),
|
||||
#[error("Command `{0}` failed to execute")]
|
||||
#[error("Command failed to execute")]
|
||||
CommandFailed(
|
||||
#[source]
|
||||
#[serde(serialize_with = "crate::serialize_error_to_display")]
|
||||
std::io::Error
|
||||
),
|
||||
#[error("Remove file `{0}`")]
|
||||
RemoveFile(std::path::PathBuf, #[source] #[serde(serialize_with = "crate::serialize_error_to_display")] std::io::Error),
|
||||
#[error("No supported init system found")]
|
||||
InitNotSupported,
|
||||
}
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
|
||||
use std::os::unix::prelude::PermissionsExt;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use nix::unistd::{chown, Group, User};
|
||||
use serde::Serialize;
|
||||
use tokio::fs::create_dir;
|
||||
use tokio::fs::{create_dir, remove_dir_all};
|
||||
|
||||
use crate::HarmonicError;
|
||||
|
||||
use crate::actions::{ActionDescription, Actionable, ActionState, Action, ActionError};
|
||||
|
||||
use crate::actions::{ActionDescription, Actionable, ActionState, Action};
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||
pub struct CreateDirectory {
|
||||
|
@ -57,7 +59,7 @@ impl Actionable for CreateDirectory {
|
|||
user,
|
||||
group,
|
||||
mode,
|
||||
action_state,
|
||||
action_state: _,
|
||||
} = &self;
|
||||
vec![ActionDescription::new(
|
||||
format!("Create the directory `{}`", path.display()),
|
||||
|
@ -93,6 +95,12 @@ impl Actionable for CreateDirectory {
|
|||
.map_err(|e| Self::Error::Creating(path.clone(), e))?;
|
||||
chown(path, Some(uid), Some(gid)).map_err(|e| Self::Error::Chown(path.clone(), e))?;
|
||||
|
||||
tracing::trace!(path = %path.display(), "Changing permissions on directory");
|
||||
tokio::fs::set_permissions(&path, PermissionsExt::from_mode(*mode))
|
||||
.await
|
||||
.map_err(|e| Self::Error::SetPermissions(*mode, path.to_owned(), e))?;
|
||||
|
||||
|
||||
*action_state = ActionState::Completed;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -100,8 +108,20 @@ impl Actionable for CreateDirectory {
|
|||
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn revert(&mut self) -> Result<(), Self::Error> {
|
||||
todo!();
|
||||
let Self {
|
||||
path,
|
||||
user: _,
|
||||
group: _,
|
||||
mode: _,
|
||||
action_state,
|
||||
} = self;
|
||||
|
||||
tracing::trace!(path = %path.display(), "Removing directory");
|
||||
remove_dir_all(path.clone())
|
||||
.await
|
||||
.map_err(|e| Self::Error::Removing(path.clone(), e))?;
|
||||
|
||||
*action_state = ActionState::Reverted;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -120,6 +140,10 @@ pub enum CreateDirectoryError {
|
|||
Exists(std::path::PathBuf, #[source] #[serde(serialize_with = "crate::serialize_error_to_display")] std::io::Error),
|
||||
#[error("Creating directory `{0}`")]
|
||||
Creating(std::path::PathBuf, #[source] #[serde(serialize_with = "crate::serialize_error_to_display")] std::io::Error),
|
||||
#[error("Removing directory `{0}`")]
|
||||
Removing(std::path::PathBuf, #[source] #[serde(serialize_with = "crate::serialize_error_to_display")] std::io::Error),
|
||||
#[error("Set mode `{0}` on `{1}`")]
|
||||
SetPermissions(u32, std::path::PathBuf, #[source] #[serde(serialize_with = "crate::serialize_error_to_display")] std::io::Error),
|
||||
#[error("Chowning directory `{0}`")]
|
||||
Chown(std::path::PathBuf, #[source] #[serde(serialize_with = "crate::serialize_error_to_display")] nix::errno::Errno),
|
||||
#[error("Getting uid for user `{0}`")]
|
||||
|
|
|
@ -2,11 +2,11 @@ use nix::unistd::{chown, Group, User};
|
|||
use serde::Serialize;
|
||||
use std::path::{Path, PathBuf};
|
||||
use tokio::{
|
||||
fs::{create_dir_all, OpenOptions},
|
||||
fs::{OpenOptions, remove_file},
|
||||
io::AsyncWriteExt,
|
||||
};
|
||||
|
||||
use crate::{HarmonicError, actions::{ActionState, Action, ActionError}};
|
||||
use crate::{actions::{ActionState, Action, ActionError}};
|
||||
|
||||
use crate::actions::{ActionDescription, Actionable};
|
||||
|
||||
|
@ -59,7 +59,7 @@ impl Actionable for CreateFile {
|
|||
group,
|
||||
mode,
|
||||
buf,
|
||||
force,
|
||||
force: _,
|
||||
action_state: _,
|
||||
} = &self;
|
||||
vec![ActionDescription::new(
|
||||
|
@ -84,6 +84,7 @@ impl Actionable for CreateFile {
|
|||
tracing::trace!(path = %path.display(), "Creating file");
|
||||
let mut file = OpenOptions::new()
|
||||
.create_new(true)
|
||||
.mode(*mode)
|
||||
.write(true)
|
||||
.read(true)
|
||||
.open(&path)
|
||||
|
@ -113,8 +114,22 @@ impl Actionable for CreateFile {
|
|||
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn revert(&mut self) -> Result<(), Self::Error> {
|
||||
todo!();
|
||||
let Self {
|
||||
path,
|
||||
user: _,
|
||||
group: _,
|
||||
mode: _,
|
||||
buf: _,
|
||||
force: _,
|
||||
action_state,
|
||||
} = self;
|
||||
|
||||
tracing::trace!(path = %path.display(), "Deleting file");
|
||||
|
||||
remove_file(&path).await
|
||||
.map_err(|e| Self::Error::RemoveFile(path.to_owned(), e))?;
|
||||
|
||||
*action_state = ActionState::Reverted;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -129,6 +144,8 @@ impl From<CreateFile> for Action {
|
|||
pub enum CreateFileError {
|
||||
#[error("File exists `{0}`")]
|
||||
Exists(std::path::PathBuf),
|
||||
#[error("Remove file `{0}`")]
|
||||
RemoveFile(std::path::PathBuf, #[source] #[serde(serialize_with = "crate::serialize_error_to_display")] std::io::Error),
|
||||
#[error("Open file `{0}`")]
|
||||
OpenFile(std::path::PathBuf, #[source] #[serde(serialize_with = "crate::serialize_error_to_display")] std::io::Error),
|
||||
#[error("Write file `{0}`")]
|
||||
|
|
|
@ -47,8 +47,13 @@ impl Actionable for CreateGroup {
|
|||
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn revert(&mut self) -> Result<(), Self::Error> {
|
||||
todo!();
|
||||
let Self { name, gid: _, action_state } = self;
|
||||
|
||||
execute_command(
|
||||
Command::new("groupdel").arg(&name),
|
||||
).await.map_err(CreateGroupError::Command)?;
|
||||
|
||||
*action_state = ActionState::Reverted;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,11 +2,11 @@ use nix::unistd::{chown, Group, User};
|
|||
use serde::Serialize;
|
||||
use std::{
|
||||
io::SeekFrom,
|
||||
path::{Path, PathBuf},
|
||||
path::{Path, PathBuf}, os::unix::prelude::PermissionsExt, f32::consts::E,
|
||||
};
|
||||
use tokio::{
|
||||
fs::{create_dir_all, OpenOptions},
|
||||
io::{AsyncSeekExt, AsyncWriteExt},
|
||||
fs::{create_dir_all, OpenOptions, remove_file},
|
||||
io::{AsyncSeekExt, AsyncWriteExt, AsyncReadExt},
|
||||
};
|
||||
|
||||
use crate::{HarmonicError, actions::{ActionState, Action, ActionError}};
|
||||
|
@ -88,6 +88,7 @@ impl Actionable for CreateOrAppendFile {
|
|||
file.seek(SeekFrom::End(0))
|
||||
.await
|
||||
.map_err(|e| Self::Error::SeekFile(path.to_owned(), e))?;
|
||||
|
||||
file.write_all(buf.as_bytes())
|
||||
.await
|
||||
.map_err(|e| Self::Error::WriteFile(path.to_owned(), e))?;
|
||||
|
@ -101,8 +102,14 @@ impl Actionable for CreateOrAppendFile {
|
|||
.ok_or(Self::Error::NoUser(user.clone()))?
|
||||
.uid;
|
||||
|
||||
tracing::trace!(path = %path.display(), "Chowning");
|
||||
tracing::trace!(path = %path.display(), "Changing permissions on file");
|
||||
tokio::fs::set_permissions(&path, PermissionsExt::from_mode(*mode))
|
||||
.await
|
||||
.map_err(|e| Self::Error::SetPermissions(*mode, path.to_owned(), e))?;
|
||||
|
||||
tracing::trace!(path = %path.display(), "Chowning");
|
||||
chown(path, Some(uid), Some(gid)).map_err(|e| Self::Error::Chown(path.clone(), e))?;
|
||||
|
||||
|
||||
*action_state = ActionState::Completed;
|
||||
Ok(())
|
||||
|
@ -111,8 +118,45 @@ impl Actionable for CreateOrAppendFile {
|
|||
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn revert(&mut self) -> Result<(), Self::Error> {
|
||||
todo!();
|
||||
let Self {
|
||||
path,
|
||||
user: _,
|
||||
group: _,
|
||||
mode: _,
|
||||
buf,
|
||||
action_state,
|
||||
} = self;
|
||||
tracing::trace!(path = %path.display(), "Deleting or trimming content from file");
|
||||
|
||||
let mut file = OpenOptions::new()
|
||||
.create(false)
|
||||
.write(true)
|
||||
.read(true)
|
||||
.open(&path)
|
||||
.await
|
||||
.map_err(|e| Self::Error::ReadFile(path.to_owned(), e))?;
|
||||
|
||||
let mut file_contents = String::default();
|
||||
file.read_to_string(&mut file_contents).await
|
||||
.map_err(|e| Self::Error::SeekFile(path.to_owned(), e))?;
|
||||
|
||||
if let Some(start) = file_contents.rfind(buf.as_str()) {
|
||||
let end = start + buf.len();
|
||||
file_contents.replace_range(start..end, "")
|
||||
}
|
||||
|
||||
if buf.is_empty() {
|
||||
remove_file(&path).await.map_err(|e| Self::Error::RemoveFile(path.to_owned(), e))?;
|
||||
} else {
|
||||
file.seek(SeekFrom::Start(0))
|
||||
.await
|
||||
.map_err(|e| Self::Error::SeekFile(path.to_owned(), e))?;
|
||||
file.write_all(file_contents.as_bytes())
|
||||
.await
|
||||
.map_err(|e| Self::Error::WriteFile(path.to_owned(), e))?;
|
||||
}
|
||||
|
||||
*action_state = ActionState::Reverted;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -126,6 +170,10 @@ impl From<CreateOrAppendFile> for Action {
|
|||
|
||||
#[derive(Debug, thiserror::Error, Serialize)]
|
||||
pub enum CreateOrAppendFileError {
|
||||
#[error("Remove file `{0}`")]
|
||||
RemoveFile(std::path::PathBuf, #[source] #[serde(serialize_with = "crate::serialize_error_to_display")] std::io::Error),
|
||||
#[error("Remove file `{0}`")]
|
||||
ReadFile(std::path::PathBuf, #[source] #[serde(serialize_with = "crate::serialize_error_to_display")] std::io::Error),
|
||||
#[error("Open file `{0}`")]
|
||||
OpenFile(std::path::PathBuf, #[source] #[serde(serialize_with = "crate::serialize_error_to_display")] std::io::Error),
|
||||
#[error("Write file `{0}`")]
|
||||
|
@ -140,6 +188,8 @@ pub enum CreateOrAppendFileError {
|
|||
GroupId(String, #[source] #[serde(serialize_with = "crate::serialize_error_to_display")] nix::errno::Errno),
|
||||
#[error("Getting group `{0}`")]
|
||||
NoGroup(String),
|
||||
#[error("Set mode `{0}` on `{1}`")]
|
||||
SetPermissions(u32, std::path::PathBuf, #[source] #[serde(serialize_with = "crate::serialize_error_to_display")] std::io::Error),
|
||||
#[error("Chowning directory `{0}`")]
|
||||
Chown(std::path::PathBuf, #[source] #[serde(serialize_with = "crate::serialize_error_to_display")] nix::errno::Errno),
|
||||
}
|
||||
|
|
|
@ -65,8 +65,13 @@ impl Actionable for CreateUser {
|
|||
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn revert(&mut self) -> Result<(), Self::Error> {
|
||||
todo!();
|
||||
let Self { name, uid: _, gid: _, action_state } = self;
|
||||
|
||||
execute_command(Command::new("userdel").args([
|
||||
&name.to_string(),
|
||||
])).await.map_err(Self::Error::Command)?;
|
||||
|
||||
*action_state = ActionState::Completed;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -70,8 +70,11 @@ impl Actionable for FetchNix {
|
|||
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn revert(&mut self) -> Result<(), Self::Error> {
|
||||
todo!();
|
||||
let Self { url: _, destination: _, action_state } = self;
|
||||
|
||||
tracing::trace!("Nothing to do for `FetchNix` revert");
|
||||
|
||||
*action_state = ActionState::Reverted;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,8 +61,11 @@ impl Actionable for MoveUnpackedNix {
|
|||
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn revert(&mut self) -> Result<(), Self::Error> {
|
||||
todo!();
|
||||
let Self { source: _, action_state } = self;
|
||||
|
||||
tracing::trace!("Nothing to do for `MoveUnpackedNix` revert");
|
||||
|
||||
*action_state = ActionState::Reverted;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,9 +39,9 @@ impl Actionable for PlaceChannelConfiguration {
|
|||
type Error = PlaceChannelConfigurationError;
|
||||
fn description(&self) -> Vec<ActionDescription> {
|
||||
let Self {
|
||||
channels,
|
||||
create_file,
|
||||
action_state: _,
|
||||
channels: _,
|
||||
create_file: _,
|
||||
action_state: _,
|
||||
} = self;
|
||||
vec![ActionDescription::new(
|
||||
"Place a channel configuration".to_string(),
|
||||
|
@ -53,7 +53,7 @@ impl Actionable for PlaceChannelConfiguration {
|
|||
async fn execute(&mut self) -> Result<(), Self::Error> {
|
||||
let Self {
|
||||
create_file,
|
||||
channels,
|
||||
channels: _,
|
||||
action_state,
|
||||
} = self;
|
||||
|
||||
|
@ -66,8 +66,15 @@ impl Actionable for PlaceChannelConfiguration {
|
|||
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn revert(&mut self) -> Result<(), Self::Error> {
|
||||
todo!();
|
||||
|
||||
let Self {
|
||||
create_file,
|
||||
channels: _,
|
||||
action_state,
|
||||
} = self;
|
||||
|
||||
create_file.revert().await?;
|
||||
|
||||
*action_state = ActionState::Reverted;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,8 +66,12 @@ impl Actionable for PlaceNixConfiguration {
|
|||
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn revert(&mut self) -> Result<(), Self::Error> {
|
||||
todo!();
|
||||
let Self { create_file, create_directory, action_state } = self;
|
||||
|
||||
create_file.revert().await?;
|
||||
create_directory.revert().await?;
|
||||
|
||||
*action_state = ActionState::Reverted;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,12 +9,13 @@ use crate::actions::{ActionDescription, Actionable};
|
|||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||
pub struct SetupDefaultProfile {
|
||||
channels: Vec<String>,
|
||||
action_state: ActionState,
|
||||
}
|
||||
|
||||
impl SetupDefaultProfile {
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub async fn plan(channels: Vec<String>) -> Result<Self, SetupDefaultProfileError> {
|
||||
Ok(Self { channels })
|
||||
Ok(Self { channels, action_state: ActionState::Planned })
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,7 +31,7 @@ impl Actionable for SetupDefaultProfile {
|
|||
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn execute(&mut self) -> Result<(), Self::Error> {
|
||||
let Self { channels } = self;
|
||||
let Self { channels, action_state } = self;
|
||||
tracing::info!("Setting up default profile");
|
||||
|
||||
// Find an `nix` package
|
||||
|
@ -105,14 +106,18 @@ impl Actionable for SetupDefaultProfile {
|
|||
|
||||
execute_command(&mut command).await.map_err(SetupDefaultProfileError::Command)?;
|
||||
}
|
||||
*action_state = ActionState::Completed;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn revert(&mut self) -> Result<(), Self::Error> {
|
||||
todo!();
|
||||
let Self { channels: _, action_state } = self;
|
||||
|
||||
std::env::remove_var("NIX_SSL_CERT_FILE");
|
||||
|
||||
*action_state = ActionState::Reverted;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -70,8 +70,17 @@ impl Actionable for StartSystemdUnit {
|
|||
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn revert(&mut self) -> Result<(), Self::Error> {
|
||||
todo!();
|
||||
let Self { unit, action_state } = self;
|
||||
|
||||
// TODO(@Hoverbear): Handle proxy vars
|
||||
execute_command(
|
||||
Command::new("systemctl")
|
||||
.arg("stop")
|
||||
.arg(format!("{unit}")),
|
||||
)
|
||||
.await.map_err(StartSystemdUnitError::Command)?;
|
||||
|
||||
*action_state = ActionState::Reverted;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -89,28 +89,21 @@ impl Actionable for ConfigureNix {
|
|||
action_state,
|
||||
} = self;
|
||||
|
||||
let (
|
||||
setup_default_profile,
|
||||
place_nix_configuration,
|
||||
place_channel_configuration,
|
||||
configure_shell_profile,
|
||||
) = if let Some(configure_shell_profile) = configure_shell_profile {
|
||||
let (a, b, c, d) = tokio::try_join!(
|
||||
if let Some(configure_shell_profile) = configure_shell_profile {
|
||||
tokio::try_join!(
|
||||
async move { setup_default_profile.execute().await.map_err(|e| ConfigureNixError::from(e)) },
|
||||
async move { place_nix_configuration.execute().await.map_err(|e| ConfigureNixError::from(e)) },
|
||||
async move { place_channel_configuration.execute().await.map_err(|e| ConfigureNixError::from(e)) },
|
||||
async move { configure_shell_profile.execute().await.map_err(|e| ConfigureNixError::from(e)) },
|
||||
)?;
|
||||
(a, b, c, Some(d))
|
||||
} else {
|
||||
let (a, b, c) = tokio::try_join!(
|
||||
tokio::try_join!(
|
||||
async move { setup_default_profile.execute().await.map_err(|e| ConfigureNixError::from(e)) },
|
||||
async move { place_nix_configuration.execute().await.map_err(|e| ConfigureNixError::from(e)) },
|
||||
async move { place_channel_configuration.execute().await.map_err(|e| ConfigureNixError::from(e)) },
|
||||
)?;
|
||||
(a, b, c, None)
|
||||
};
|
||||
let configure_nix_daemon_service = configure_nix_daemon_service.execute().await?;
|
||||
configure_nix_daemon_service.execute().await?;
|
||||
|
||||
*action_state = ActionState::Completed;
|
||||
Ok(())
|
||||
|
@ -119,8 +112,24 @@ impl Actionable for ConfigureNix {
|
|||
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn revert(&mut self) -> Result<(), Self::Error> {
|
||||
todo!();
|
||||
let Self {
|
||||
setup_default_profile,
|
||||
configure_nix_daemon_service,
|
||||
place_nix_configuration,
|
||||
place_channel_configuration,
|
||||
configure_shell_profile,
|
||||
action_state,
|
||||
} = self;
|
||||
|
||||
configure_nix_daemon_service.revert().await?;
|
||||
if let Some(configure_shell_profile) = configure_shell_profile {
|
||||
configure_shell_profile.revert().await?;
|
||||
}
|
||||
place_channel_configuration.revert().await?;
|
||||
place_nix_configuration.revert().await?;
|
||||
setup_default_profile.revert().await?;
|
||||
|
||||
*action_state = ActionState::Reverted;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -105,8 +105,37 @@ impl Actionable for ConfigureShellProfile {
|
|||
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn revert(&mut self) -> Result<(), Self::Error> {
|
||||
todo!();
|
||||
let Self {
|
||||
create_or_append_files,
|
||||
action_state,
|
||||
} = self;
|
||||
tracing::info!("Configuring shell profile");
|
||||
|
||||
let mut set = JoinSet::new();
|
||||
let mut errors = Vec::default();
|
||||
|
||||
for (idx, create_or_append_file) in create_or_append_files.iter().enumerate() {
|
||||
let mut create_or_append_file_clone = create_or_append_file.clone();
|
||||
let _abort_handle = set.spawn(async move { create_or_append_file_clone.revert().await?; Result::<_, CreateOrAppendFileError>::Ok((idx, create_or_append_file_clone)) });
|
||||
}
|
||||
|
||||
while let Some(result) = set.join_next().await {
|
||||
match result {
|
||||
Ok(Ok((idx, create_or_append_file))) => create_or_append_files[idx] = create_or_append_file,
|
||||
Ok(Err(e)) => errors.push(e),
|
||||
Err(e) => return Err(e.into()),
|
||||
};
|
||||
}
|
||||
|
||||
if !errors.is_empty() {
|
||||
if errors.len() == 1 {
|
||||
return Err(errors.into_iter().next().unwrap().into());
|
||||
} else {
|
||||
return Err(ConfigureShellProfileError::MultipleCreateOrAppendFile(errors));
|
||||
}
|
||||
}
|
||||
|
||||
*action_state = ActionState::Reverted;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -72,8 +72,14 @@ impl Actionable for CreateNixTree {
|
|||
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn revert(&mut self) -> Result<(), Self::Error> {
|
||||
todo!();
|
||||
let Self { create_directories, action_state } = self;
|
||||
|
||||
// Just do sequential since parallizing this will have little benefit
|
||||
for create_directory in create_directories.iter_mut().rev() {
|
||||
create_directory.revert().await?
|
||||
}
|
||||
|
||||
*action_state = ActionState::Reverted;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -81,17 +81,17 @@ impl Actionable for CreateUsersAndGroup {
|
|||
let Self {
|
||||
create_users,
|
||||
create_group,
|
||||
daemon_user_count,
|
||||
nix_build_group_name,
|
||||
nix_build_group_id,
|
||||
nix_build_user_prefix,
|
||||
nix_build_user_id_base,
|
||||
action_state
|
||||
daemon_user_count: _,
|
||||
nix_build_group_name: _,
|
||||
nix_build_group_id: _,
|
||||
nix_build_user_prefix: _,
|
||||
nix_build_user_id_base: _,
|
||||
action_state,
|
||||
} = self;
|
||||
|
||||
|
||||
// Create group
|
||||
let create_group = create_group.execute().await?;
|
||||
create_group.execute().await?;
|
||||
|
||||
// Create users
|
||||
// TODO(@hoverbear): Abstract this, it will be common
|
||||
|
@ -127,8 +127,48 @@ impl Actionable for CreateUsersAndGroup {
|
|||
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn revert(&mut self) -> Result<(), Self::Error> {
|
||||
todo!();
|
||||
let Self {
|
||||
create_users,
|
||||
create_group,
|
||||
daemon_user_count: _,
|
||||
nix_build_group_name: _,
|
||||
nix_build_group_id: _,
|
||||
nix_build_user_prefix: _,
|
||||
nix_build_user_id_base: _,
|
||||
action_state,
|
||||
} = self;
|
||||
|
||||
// Create users
|
||||
// TODO(@hoverbear): Abstract this, it will be common
|
||||
let mut set = JoinSet::new();
|
||||
|
||||
let mut errors = Vec::default();
|
||||
|
||||
for (idx, create_user) in create_users.iter().enumerate() {
|
||||
let mut create_user_clone = create_user.clone();
|
||||
let _abort_handle = set.spawn(async move { create_user_clone.revert().await?; Result::<_, CreateUserError>::Ok((idx, create_user_clone)) });
|
||||
}
|
||||
|
||||
while let Some(result) = set.join_next().await {
|
||||
match result {
|
||||
Ok(Ok((idx, success))) => create_users[idx] = success,
|
||||
Ok(Err(e)) => errors.push(e),
|
||||
Err(e) => return Err(e)?,
|
||||
};
|
||||
}
|
||||
|
||||
if !errors.is_empty() {
|
||||
if errors.len() == 1 {
|
||||
return Err(errors.into_iter().next().unwrap().into());
|
||||
} else {
|
||||
return Err(CreateUsersAndGroupError::CreateUsers(errors));
|
||||
}
|
||||
}
|
||||
|
||||
// Create group
|
||||
create_group.revert().await?;
|
||||
|
||||
*action_state = ActionState::Reverted;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -90,8 +90,25 @@ impl Actionable for ProvisionNix {
|
|||
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn revert(&mut self) -> Result<(), Self::Error> {
|
||||
todo!();
|
||||
let Self {
|
||||
fetch_nix,
|
||||
create_nix_tree,
|
||||
create_users_and_group,
|
||||
move_unpacked_nix,
|
||||
action_state,
|
||||
} = self;
|
||||
|
||||
// We fetch nix while doing the rest, then move it over.
|
||||
let mut fetch_nix_clone = fetch_nix.clone();
|
||||
let fetch_nix_handle = tokio::task::spawn(async { fetch_nix_clone.revert().await?; Result::<_, Self::Error>::Ok(fetch_nix_clone) });
|
||||
|
||||
create_users_and_group.revert().await?;
|
||||
create_nix_tree.revert().await.map_err(ProvisionNixError::from)?;
|
||||
|
||||
*fetch_nix = fetch_nix_handle.await.map_err(ProvisionNixError::from)??;
|
||||
move_unpacked_nix.revert().await?;
|
||||
|
||||
*action_state = ActionState::Completed;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,6 +37,7 @@ impl Actionable for StartNixDaemon {
|
|||
|
||||
start_systemd_socket.execute().await?;
|
||||
|
||||
*action_state = ActionState::Completed;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -46,7 +47,7 @@ impl Actionable for StartNixDaemon {
|
|||
|
||||
start_systemd_socket.revert().await?;
|
||||
|
||||
*action_state = ActionState::Completed;
|
||||
*action_state = ActionState::Reverted;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -79,6 +79,7 @@ impl CommandExecute for HarmonicCli {
|
|||
match subcommand {
|
||||
Some(HarmonicSubcommand::Plan(plan)) => plan.execute().await,
|
||||
Some(HarmonicSubcommand::Execute(execute)) => execute.execute().await,
|
||||
Some(HarmonicSubcommand::Revert(revert)) => revert.execute().await,
|
||||
None => {
|
||||
let mut settings = InstallSettings::default();
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use std::process::ExitCode;
|
||||
use std::{process::ExitCode, path::PathBuf};
|
||||
|
||||
use clap::{ArgAction, Parser};
|
||||
use harmonic::InstallPlan;
|
||||
use tokio::io::AsyncReadExt;
|
||||
use eyre::WrapErr;
|
||||
|
||||
use crate::{
|
||||
cli::CommandExecute,
|
||||
|
@ -19,18 +19,18 @@ pub(crate) struct Execute {
|
|||
global = true
|
||||
)]
|
||||
no_confirm: bool,
|
||||
#[clap(default_value = "/dev/stdin")]
|
||||
plan: PathBuf,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl CommandExecute for Execute {
|
||||
#[tracing::instrument(skip_all, fields())]
|
||||
async fn execute(self) -> eyre::Result<ExitCode> {
|
||||
let Self { no_confirm } = self;
|
||||
let Self { no_confirm, plan } = self;
|
||||
|
||||
let mut stdin = tokio::io::stdin();
|
||||
let mut json = String::default();
|
||||
stdin.read_to_string(&mut json).await?;
|
||||
let plan: InstallPlan = serde_json::from_str(&json)?;
|
||||
let install_plan_string = tokio::fs::read_to_string(plan).await.wrap_err("Reading plan")?;
|
||||
let mut plan: InstallPlan = serde_json::from_str(&install_plan_string)?;
|
||||
|
||||
if !no_confirm {
|
||||
if !interaction::confirm(plan.description()).await? {
|
||||
|
@ -38,6 +38,8 @@ impl CommandExecute for Execute {
|
|||
}
|
||||
}
|
||||
|
||||
plan.install().await?;
|
||||
|
||||
Ok(ExitCode::SUCCESS)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,9 +2,12 @@ mod plan;
|
|||
use plan::Plan;
|
||||
mod execute;
|
||||
use execute::Execute;
|
||||
mod revert;
|
||||
use revert::Revert;
|
||||
|
||||
#[derive(Debug, clap::Subcommand)]
|
||||
pub(crate) enum HarmonicSubcommand {
|
||||
Plan(Plan),
|
||||
Execute(Execute),
|
||||
Revert(Revert),
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
use std::process::ExitCode;
|
||||
use std::{process::ExitCode, path::PathBuf};
|
||||
|
||||
use clap::{ArgAction, Parser};
|
||||
use harmonic::{InstallPlan, InstallSettings};
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use eyre::WrapErr;
|
||||
|
||||
use crate::cli::{arg::ChannelValue, CommandExecute};
|
||||
|
||||
|
@ -43,6 +44,8 @@ pub(crate) struct Plan {
|
|||
global = true
|
||||
)]
|
||||
pub(crate) force: bool,
|
||||
#[clap(default_value = "/dev/stdout")]
|
||||
plan: PathBuf,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
|
@ -59,6 +62,7 @@ impl CommandExecute for Plan {
|
|||
daemon_user_count,
|
||||
explain,
|
||||
force,
|
||||
plan,
|
||||
} = self;
|
||||
|
||||
let mut settings = InstallSettings::default();
|
||||
|
@ -73,11 +77,11 @@ impl CommandExecute for Plan {
|
|||
);
|
||||
settings.modify_profile(!no_modify_profile);
|
||||
|
||||
let plan = InstallPlan::new(settings).await?;
|
||||
let install_plan = InstallPlan::new(settings).await?;
|
||||
|
||||
let json = serde_json::to_string_pretty(&install_plan)?;
|
||||
tokio::fs::write(plan, json).await.wrap_err("Writing plan")?;
|
||||
|
||||
let json = serde_json::to_string_pretty(&plan)?;
|
||||
let mut stdout = tokio::io::stdout();
|
||||
stdout.write_all(json.as_bytes()).await?;
|
||||
Ok(ExitCode::SUCCESS)
|
||||
}
|
||||
}
|
||||
|
|
45
src/cli/subcommand/revert.rs
Normal file
45
src/cli/subcommand/revert.rs
Normal file
|
@ -0,0 +1,45 @@
|
|||
use std::{process::ExitCode, path::PathBuf};
|
||||
|
||||
use clap::{ArgAction, Parser};
|
||||
use harmonic::InstallPlan;
|
||||
use eyre::WrapErr;
|
||||
|
||||
use crate::{
|
||||
cli::CommandExecute,
|
||||
interaction,
|
||||
};
|
||||
|
||||
/// An opinionated, experimental Nix installer
|
||||
#[derive(Debug, Parser)]
|
||||
pub(crate) struct Revert {
|
||||
#[clap(
|
||||
long,
|
||||
action(ArgAction::SetTrue),
|
||||
default_value = "false",
|
||||
global = true
|
||||
)]
|
||||
no_confirm: bool,
|
||||
#[clap(default_value = "/nix/receipt.json")]
|
||||
receipt: PathBuf,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl CommandExecute for Revert {
|
||||
#[tracing::instrument(skip_all, fields())]
|
||||
async fn execute(self) -> eyre::Result<ExitCode> {
|
||||
let Self { no_confirm, receipt } = self;
|
||||
|
||||
let install_receipt_string = tokio::fs::read_to_string(receipt).await.wrap_err("Reading receipt")?;
|
||||
let mut plan: InstallPlan = serde_json::from_str(&install_receipt_string)?;
|
||||
|
||||
if !no_confirm {
|
||||
if !interaction::confirm(plan.description()).await? {
|
||||
interaction::clean_exit_with_message("Okay, didn't do anything! Bye!").await;
|
||||
}
|
||||
}
|
||||
|
||||
plan.revert().await?;
|
||||
|
||||
Ok(ExitCode::SUCCESS)
|
||||
}
|
||||
}
|
63
src/error.rs
63
src/error.rs
|
@ -1,58 +1,13 @@
|
|||
use serde::de::value::Error;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::actions::ActionError;
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum HarmonicError {
|
||||
#[error("Request error")]
|
||||
Reqwest(#[from] reqwest::Error),
|
||||
#[error("Unarchiving error")]
|
||||
Unarchive(std::io::Error),
|
||||
#[error("Getting temporary directory")]
|
||||
TempDir(std::io::Error),
|
||||
#[error("Glob pattern error")]
|
||||
GlobPatternError(#[from] glob::PatternError),
|
||||
#[error("Glob globbing error")]
|
||||
GlobGlobError(#[from] glob::GlobError),
|
||||
#[error("Symlinking from `{0}` to `{1}`")]
|
||||
Symlink(std::path::PathBuf, std::path::PathBuf, std::io::Error),
|
||||
#[error("Renaming from `{0}` to `{1}`")]
|
||||
Rename(std::path::PathBuf, std::path::PathBuf, std::io::Error),
|
||||
#[error("Unarchived Nix store did not appear to include a `nss-cacert` location")]
|
||||
NoNssCacert,
|
||||
#[error("No supported init system found")]
|
||||
InitNotSupported,
|
||||
#[error("Creating file `{0}`: {1}")]
|
||||
CreateFile(std::path::PathBuf, std::io::Error),
|
||||
#[error("Creating directory `{0}`: {1}")]
|
||||
CreateDirectory(std::path::PathBuf, std::io::Error),
|
||||
#[error("Walking directory `{0}`")]
|
||||
WalkDirectory(std::path::PathBuf, walkdir::Error),
|
||||
#[error("Setting permissions `{0}`")]
|
||||
SetPermissions(std::path::PathBuf, std::io::Error),
|
||||
#[error("Command `{0}` failed to execute")]
|
||||
CommandFailedExec(String, std::io::Error),
|
||||
// TODO(@Hoverbear): This should capture the stdout.
|
||||
#[error("Command `{0}` did not to return a success status")]
|
||||
CommandFailedStatus(String),
|
||||
#[error("Join error")]
|
||||
JoinError(#[from] tokio::task::JoinError),
|
||||
#[error("Opening file `{0}` for writing")]
|
||||
OpenFile(std::path::PathBuf, std::io::Error),
|
||||
#[error("Opening file `{0}` for writing")]
|
||||
WriteFile(std::path::PathBuf, std::io::Error),
|
||||
#[error("Seeking file `{0}` for writing")]
|
||||
SeekFile(std::path::PathBuf, std::io::Error),
|
||||
#[error("Changing ownership of `{0}`")]
|
||||
Chown(std::path::PathBuf, nix::errno::Errno),
|
||||
#[error("Getting uid for user `{0}`")]
|
||||
UserId(String, nix::errno::Errno),
|
||||
#[error("Getting user `{0}`")]
|
||||
NoUser(String),
|
||||
#[error("Getting gid for group `{0}`")]
|
||||
GroupId(String, nix::errno::Errno),
|
||||
#[error("Getting group `{0}`")]
|
||||
NoGroup(String),
|
||||
#[error("Errors with additional failures during reverts: {}\nDuring Revert:{}", .0.iter().map(|v| format!("{v}")).collect::<Vec<_>>().join(" & "), .1.iter().map(|v| format!("{v}")).collect::<Vec<_>>().join(" & "))]
|
||||
FailedReverts(Vec<HarmonicError>, Vec<HarmonicError>),
|
||||
#[error("Multiple errors: {}", .0.iter().map(|v| format!("{v}")).collect::<Vec<_>>().join(" & "))]
|
||||
Multiple(Vec<HarmonicError>),
|
||||
#[error("Error executing action")]
|
||||
ActionError(#[source] #[from] ActionError),
|
||||
#[error("Recording install receipt")]
|
||||
RecordingReceipt(PathBuf, #[source] std::io::Error),
|
||||
#[error(transparent)]
|
||||
SerializingReceipt(serde_json::Error),
|
||||
}
|
||||
|
|
226
src/lib.rs
226
src/lib.rs
|
@ -28,77 +28,6 @@ use tokio::{
|
|||
};
|
||||
|
||||
|
||||
#[tracing::instrument(skip_all, fields(
|
||||
path = %path.as_ref().display(),
|
||||
permissions = tracing::field::valuable(&permissions.clone().map(|v| format!("{:#o}", v.mode()))),
|
||||
owner = tracing::field::valuable(&owner),
|
||||
group = tracing::field::valuable(&group),
|
||||
))]
|
||||
async fn set_permissions(
|
||||
path: impl AsRef<Path>,
|
||||
permissions: Option<Permissions>,
|
||||
owner: Option<String>,
|
||||
group: Option<String>,
|
||||
dry_run: bool,
|
||||
) -> Result<(), HarmonicError> {
|
||||
use nix::unistd::{chown, Group, User};
|
||||
use walkdir::WalkDir;
|
||||
if !dry_run {
|
||||
tracing::trace!("Setting permissions");
|
||||
let path = path.as_ref();
|
||||
let uid = if let Some(owner) = owner {
|
||||
let uid = User::from_name(owner.as_str())
|
||||
.map_err(|e| HarmonicError::UserId(owner.clone(), e))?
|
||||
.ok_or(HarmonicError::NoUser(owner))?
|
||||
.uid;
|
||||
Some(uid)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let gid = if let Some(group) = group {
|
||||
let gid = Group::from_name(group.as_str())
|
||||
.map_err(|e| HarmonicError::GroupId(group.clone(), e))?
|
||||
.ok_or(HarmonicError::NoGroup(group))?
|
||||
.gid;
|
||||
Some(gid)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
for child in WalkDir::new(path) {
|
||||
let entry = child.map_err(|e| HarmonicError::WalkDirectory(path.to_owned(), e))?;
|
||||
if let Some(ref perms) = permissions {
|
||||
tokio::fs::set_permissions(path, perms.clone())
|
||||
.await
|
||||
.map_err(|e| HarmonicError::SetPermissions(path.to_owned(), e))?;
|
||||
}
|
||||
chown(entry.path(), uid, gid)
|
||||
.map_err(|e| HarmonicError::Chown(entry.path().to_owned(), e))?;
|
||||
}
|
||||
} else {
|
||||
tracing::info!("Dry run: Would recursively set permissions/ownership");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all, fields(
|
||||
path = %path.as_ref().display(),
|
||||
))]
|
||||
async fn create_directory(path: impl AsRef<Path>, dry_run: bool) -> Result<(), HarmonicError> {
|
||||
use tokio::fs::create_dir;
|
||||
if !dry_run {
|
||||
tracing::trace!("Creating directory");
|
||||
let path = path.as_ref();
|
||||
create_dir(path)
|
||||
.await
|
||||
.map_err(|e| HarmonicError::CreateDirectory(path.to_owned(), e))?;
|
||||
} else {
|
||||
tracing::info!("Dry run: Would create directory");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all, fields(command = %format!("{:?}", command.as_std())))]
|
||||
async fn execute_command(
|
||||
command: &mut Command,
|
||||
|
@ -110,163 +39,10 @@ async fn execute_command(
|
|||
.await?;
|
||||
match status.success() {
|
||||
true => Ok(status),
|
||||
false => Err(std::io::Error::new(std::io::ErrorKind::Other, "Failed status")),
|
||||
false => Err(std::io::Error::new(std::io::ErrorKind::Other, format!("Command `{command_str}` failed status"))),
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all, fields(
|
||||
path = %path.as_ref().display(),
|
||||
buf = %format!("```{}```", buf.as_ref()),
|
||||
))]
|
||||
async fn create_or_append_file(
|
||||
path: impl AsRef<Path>,
|
||||
buf: impl AsRef<str>,
|
||||
dry_run: bool,
|
||||
) -> Result<(), HarmonicError> {
|
||||
use tokio::fs::{create_dir_all, OpenOptions};
|
||||
let path = path.as_ref();
|
||||
let buf = buf.as_ref();
|
||||
if !dry_run {
|
||||
tracing::trace!("Creating or appending");
|
||||
if let Some(parent) = path.parent() {
|
||||
create_dir_all(parent)
|
||||
.await
|
||||
.map_err(|e| HarmonicError::CreateDirectory(parent.to_owned(), e))?;
|
||||
}
|
||||
let mut file = OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.read(true)
|
||||
.open(&path)
|
||||
.await
|
||||
.map_err(|e| HarmonicError::OpenFile(path.to_owned(), e))?;
|
||||
|
||||
file.seek(SeekFrom::End(0))
|
||||
.await
|
||||
.map_err(|e| HarmonicError::SeekFile(path.to_owned(), e))?;
|
||||
file.write_all(buf.as_bytes())
|
||||
.await
|
||||
.map_err(|e| HarmonicError::WriteFile(path.to_owned(), e))?;
|
||||
} else {
|
||||
tracing::info!("Dry run: Would create or append");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all, fields(
|
||||
path = %path.as_ref().display(),
|
||||
buf = %format!("```{}```", buf.as_ref()),
|
||||
))]
|
||||
async fn create_file_if_not_exists(
|
||||
path: impl AsRef<Path>,
|
||||
buf: impl AsRef<str>,
|
||||
dry_run: bool,
|
||||
) -> Result<(), HarmonicError> {
|
||||
use tokio::fs::{create_dir_all, OpenOptions};
|
||||
let path = path.as_ref();
|
||||
let buf = buf.as_ref();
|
||||
if !dry_run {
|
||||
tracing::trace!("Creating if not exists");
|
||||
if let Some(parent) = path.parent() {
|
||||
create_dir_all(parent)
|
||||
.await
|
||||
.map_err(|e| HarmonicError::CreateDirectory(parent.to_owned(), e))?;
|
||||
}
|
||||
let mut file = OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.read(true)
|
||||
.open(&path)
|
||||
.await
|
||||
.map_err(|e| HarmonicError::OpenFile(path.to_owned(), e))?;
|
||||
|
||||
file.write_all(buf.as_bytes())
|
||||
.await
|
||||
.map_err(|e| HarmonicError::WriteFile(path.to_owned(), e))?;
|
||||
} else {
|
||||
tracing::info!("Dry run: Would create (or error if exists)");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all, fields(
|
||||
src = %src.as_ref().display(),
|
||||
dest = %dest.as_ref().display(),
|
||||
))]
|
||||
async fn symlink(
|
||||
src: impl AsRef<Path>,
|
||||
dest: impl AsRef<Path>,
|
||||
dry_run: bool,
|
||||
) -> Result<(), HarmonicError> {
|
||||
let src = src.as_ref();
|
||||
let dest = dest.as_ref();
|
||||
if !dry_run {
|
||||
tracing::trace!("Symlinking");
|
||||
tokio::fs::symlink(src, dest)
|
||||
.await
|
||||
.map_err(|e| HarmonicError::Symlink(src.to_owned(), dest.to_owned(), e))?;
|
||||
} else {
|
||||
tracing::info!("Dry run: Would symlink",);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all, fields(
|
||||
src = %src.as_ref().display(),
|
||||
dest = %dest.as_ref().display(),
|
||||
))]
|
||||
async fn rename(
|
||||
src: impl AsRef<Path>,
|
||||
dest: impl AsRef<Path>,
|
||||
dry_run: bool,
|
||||
) -> Result<(), HarmonicError> {
|
||||
let src = src.as_ref();
|
||||
let dest = dest.as_ref();
|
||||
if !dry_run {
|
||||
tracing::trace!("Renaming");
|
||||
tokio::fs::rename(src, dest)
|
||||
.await
|
||||
.map_err(|e| HarmonicError::Rename(src.to_owned(), dest.to_owned(), e))?;
|
||||
} else {
|
||||
tracing::info!("Dry run: Would rename",);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all, fields(
|
||||
url = %url.as_ref(),
|
||||
dest = %dest.as_ref().display(),
|
||||
))]
|
||||
async fn fetch_url_and_unpack_xz(
|
||||
url: impl AsRef<str>,
|
||||
dest: impl AsRef<Path>,
|
||||
dry_run: bool,
|
||||
) -> Result<(), HarmonicError> {
|
||||
let url = url.as_ref();
|
||||
let dest = dest.as_ref().to_owned();
|
||||
if !dry_run {
|
||||
tracing::trace!("Fetching url");
|
||||
let res = reqwest::get(url).await.map_err(HarmonicError::Reqwest)?;
|
||||
let bytes = res.bytes().await.map_err(HarmonicError::Reqwest)?;
|
||||
// TODO(@Hoverbear): Pick directory
|
||||
tracing::trace!("Unpacking tar.xz");
|
||||
let handle: Result<(), HarmonicError> = spawn_blocking(move || {
|
||||
let decoder = xz2::read::XzDecoder::new(bytes.reader());
|
||||
let mut archive = tar::Archive::new(decoder);
|
||||
archive.unpack(&dest).map_err(HarmonicError::Unarchive)?;
|
||||
tracing::debug!(dest = %dest.display(), "Downloaded & extracted Nix");
|
||||
Ok(())
|
||||
})
|
||||
.await?;
|
||||
|
||||
handle?;
|
||||
} else {
|
||||
tracing::info!("Dry run: Would fetch and unpack xz tarball");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all, fields(
|
||||
k = %k.as_ref().to_string_lossy(),
|
||||
v = %v.as_ref().to_string_lossy(),
|
||||
|
|
47
src/plan.rs
47
src/plan.rs
|
@ -1,4 +1,7 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::fs::File;
|
||||
|
||||
use crate::{
|
||||
actions::{
|
||||
|
@ -82,23 +85,51 @@ impl InstallPlan {
|
|||
},
|
||||
)
|
||||
}
|
||||
pub async fn new(settings: InstallSettings) -> Result<Self, ActionError> {
|
||||
pub async fn new(settings: InstallSettings) -> Result<Self, HarmonicError> {
|
||||
Ok(Self {
|
||||
settings: settings.clone(),
|
||||
provision_nix: ProvisionNix::plan(settings.clone()).await?,
|
||||
configure_nix: ConfigureNix::plan(settings).await?,
|
||||
start_nix_daemon: StartNixDaemon::plan().await?,
|
||||
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)]
|
||||
pub async fn install(&mut self) -> Result<(), ActionError> {
|
||||
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.
|
||||
self.provision_nix.execute().await?;
|
||||
self.configure_nix.execute().await?;
|
||||
self.start_nix_daemon.execute().await?;
|
||||
self.provision_nix.execute().await
|
||||
.map_err(|e| ActionError::from(e))?;
|
||||
self.configure_nix.execute().await
|
||||
.map_err(|e| ActionError::from(e))?;
|
||||
self.start_nix_daemon.execute().await
|
||||
.map_err(|e| ActionError::from(e))?;
|
||||
|
||||
let install_receipt_path = PathBuf::from("/nix/receipt.json");
|
||||
let self_json = serde_json::to_string_pretty(&self)
|
||||
.map_err(HarmonicError::SerializingReceipt)?;
|
||||
tokio::fs::write(&install_receipt_path, self_json).await
|
||||
.map_err(|e| HarmonicError::RecordingReceipt(install_receipt_path, e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub async fn revert(&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.
|
||||
self.start_nix_daemon.revert().await
|
||||
.map_err(|e| ActionError::from(e))?;
|
||||
self.configure_nix.revert().await
|
||||
.map_err(|e| ActionError::from(e))?;
|
||||
self.provision_nix.revert().await
|
||||
.map_err(|e| ActionError::from(e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue