forked from lix-project/lix-installer
Uninstall works
This commit is contained in:
parent
63a08acdea
commit
48646c7cad
|
@ -1,11 +1,12 @@
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
use tokio::fs::remove_file;
|
||||||
use tokio::process::Command;
|
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 SERVICE_SRC: &str = "/nix/var/nix/profiles/default/lib/systemd/system/nix-daemon.service";
|
||||||
const SOCKET_SRC: &str = "/nix/var/nix/profiles/default/lib/systemd/system/nix-daemon.socket";
|
const SOCKET_SRC: &str = "/nix/var/nix/profiles/default/lib/systemd/system/nix-daemon.socket";
|
||||||
|
@ -68,9 +69,11 @@ impl Actionable for ConfigureNixDaemonService {
|
||||||
)
|
)
|
||||||
.await.map_err(Self::Error::CommandFailed)?;
|
.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;
|
*action_state = ActionState::Completed;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -79,8 +82,32 @@ impl Actionable for ConfigureNixDaemonService {
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn revert(&mut self) -> Result<(), Self::Error> {
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -103,12 +130,14 @@ pub enum ConfigureNixDaemonServiceError {
|
||||||
#[serde(serialize_with = "crate::serialize_error_to_display")]
|
#[serde(serialize_with = "crate::serialize_error_to_display")]
|
||||||
std::io::Error
|
std::io::Error
|
||||||
),
|
),
|
||||||
#[error("Command `{0}` failed to execute")]
|
#[error("Command failed to execute")]
|
||||||
CommandFailed(
|
CommandFailed(
|
||||||
#[source]
|
#[source]
|
||||||
#[serde(serialize_with = "crate::serialize_error_to_display")]
|
#[serde(serialize_with = "crate::serialize_error_to_display")]
|
||||||
std::io::Error
|
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")]
|
#[error("No supported init system found")]
|
||||||
InitNotSupported,
|
InitNotSupported,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
|
|
||||||
|
use std::os::unix::prelude::PermissionsExt;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use nix::unistd::{chown, Group, User};
|
use nix::unistd::{chown, Group, User};
|
||||||
use serde::Serialize;
|
use 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)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
pub struct CreateDirectory {
|
pub struct CreateDirectory {
|
||||||
|
@ -57,7 +59,7 @@ impl Actionable for CreateDirectory {
|
||||||
user,
|
user,
|
||||||
group,
|
group,
|
||||||
mode,
|
mode,
|
||||||
action_state,
|
action_state: _,
|
||||||
} = &self;
|
} = &self;
|
||||||
vec![ActionDescription::new(
|
vec![ActionDescription::new(
|
||||||
format!("Create the directory `{}`", path.display()),
|
format!("Create the directory `{}`", path.display()),
|
||||||
|
@ -93,6 +95,12 @@ impl Actionable for CreateDirectory {
|
||||||
.map_err(|e| Self::Error::Creating(path.clone(), e))?;
|
.map_err(|e| Self::Error::Creating(path.clone(), e))?;
|
||||||
chown(path, Some(uid), Some(gid)).map_err(|e| Self::Error::Chown(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;
|
*action_state = ActionState::Completed;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -100,8 +108,20 @@ impl Actionable for CreateDirectory {
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn revert(&mut self) -> Result<(), Self::Error> {
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -120,6 +140,10 @@ pub enum CreateDirectoryError {
|
||||||
Exists(std::path::PathBuf, #[source] #[serde(serialize_with = "crate::serialize_error_to_display")] std::io::Error),
|
Exists(std::path::PathBuf, #[source] #[serde(serialize_with = "crate::serialize_error_to_display")] std::io::Error),
|
||||||
#[error("Creating directory `{0}`")]
|
#[error("Creating directory `{0}`")]
|
||||||
Creating(std::path::PathBuf, #[source] #[serde(serialize_with = "crate::serialize_error_to_display")] std::io::Error),
|
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}`")]
|
#[error("Chowning directory `{0}`")]
|
||||||
Chown(std::path::PathBuf, #[source] #[serde(serialize_with = "crate::serialize_error_to_display")] nix::errno::Errno),
|
Chown(std::path::PathBuf, #[source] #[serde(serialize_with = "crate::serialize_error_to_display")] nix::errno::Errno),
|
||||||
#[error("Getting uid for user `{0}`")]
|
#[error("Getting uid for user `{0}`")]
|
||||||
|
|
|
@ -2,11 +2,11 @@ use nix::unistd::{chown, Group, User};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use tokio::{
|
use tokio::{
|
||||||
fs::{create_dir_all, OpenOptions},
|
fs::{OpenOptions, remove_file},
|
||||||
io::AsyncWriteExt,
|
io::AsyncWriteExt,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{HarmonicError, actions::{ActionState, Action, ActionError}};
|
use crate::{actions::{ActionState, Action, ActionError}};
|
||||||
|
|
||||||
use crate::actions::{ActionDescription, Actionable};
|
use crate::actions::{ActionDescription, Actionable};
|
||||||
|
|
||||||
|
@ -59,7 +59,7 @@ impl Actionable for CreateFile {
|
||||||
group,
|
group,
|
||||||
mode,
|
mode,
|
||||||
buf,
|
buf,
|
||||||
force,
|
force: _,
|
||||||
action_state: _,
|
action_state: _,
|
||||||
} = &self;
|
} = &self;
|
||||||
vec![ActionDescription::new(
|
vec![ActionDescription::new(
|
||||||
|
@ -84,6 +84,7 @@ impl Actionable for CreateFile {
|
||||||
tracing::trace!(path = %path.display(), "Creating file");
|
tracing::trace!(path = %path.display(), "Creating file");
|
||||||
let mut file = OpenOptions::new()
|
let mut file = OpenOptions::new()
|
||||||
.create_new(true)
|
.create_new(true)
|
||||||
|
.mode(*mode)
|
||||||
.write(true)
|
.write(true)
|
||||||
.read(true)
|
.read(true)
|
||||||
.open(&path)
|
.open(&path)
|
||||||
|
@ -113,8 +114,22 @@ impl Actionable for CreateFile {
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn revert(&mut self) -> Result<(), Self::Error> {
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -129,6 +144,8 @@ impl From<CreateFile> for Action {
|
||||||
pub enum CreateFileError {
|
pub enum CreateFileError {
|
||||||
#[error("File exists `{0}`")]
|
#[error("File exists `{0}`")]
|
||||||
Exists(std::path::PathBuf),
|
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}`")]
|
#[error("Open file `{0}`")]
|
||||||
OpenFile(std::path::PathBuf, #[source] #[serde(serialize_with = "crate::serialize_error_to_display")] std::io::Error),
|
OpenFile(std::path::PathBuf, #[source] #[serde(serialize_with = "crate::serialize_error_to_display")] std::io::Error),
|
||||||
#[error("Write file `{0}`")]
|
#[error("Write file `{0}`")]
|
||||||
|
|
|
@ -47,8 +47,13 @@ impl Actionable for CreateGroup {
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn revert(&mut self) -> Result<(), Self::Error> {
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,11 +2,11 @@ use nix::unistd::{chown, Group, User};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::{
|
use std::{
|
||||||
io::SeekFrom,
|
io::SeekFrom,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf}, os::unix::prelude::PermissionsExt, f32::consts::E,
|
||||||
};
|
};
|
||||||
use tokio::{
|
use tokio::{
|
||||||
fs::{create_dir_all, OpenOptions},
|
fs::{create_dir_all, OpenOptions, remove_file},
|
||||||
io::{AsyncSeekExt, AsyncWriteExt},
|
io::{AsyncSeekExt, AsyncWriteExt, AsyncReadExt},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{HarmonicError, actions::{ActionState, Action, ActionError}};
|
use crate::{HarmonicError, actions::{ActionState, Action, ActionError}};
|
||||||
|
@ -88,6 +88,7 @@ impl Actionable for CreateOrAppendFile {
|
||||||
file.seek(SeekFrom::End(0))
|
file.seek(SeekFrom::End(0))
|
||||||
.await
|
.await
|
||||||
.map_err(|e| Self::Error::SeekFile(path.to_owned(), e))?;
|
.map_err(|e| Self::Error::SeekFile(path.to_owned(), e))?;
|
||||||
|
|
||||||
file.write_all(buf.as_bytes())
|
file.write_all(buf.as_bytes())
|
||||||
.await
|
.await
|
||||||
.map_err(|e| Self::Error::WriteFile(path.to_owned(), e))?;
|
.map_err(|e| Self::Error::WriteFile(path.to_owned(), e))?;
|
||||||
|
@ -101,9 +102,15 @@ impl Actionable for CreateOrAppendFile {
|
||||||
.ok_or(Self::Error::NoUser(user.clone()))?
|
.ok_or(Self::Error::NoUser(user.clone()))?
|
||||||
.uid;
|
.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))?;
|
chown(path, Some(uid), Some(gid)).map_err(|e| Self::Error::Chown(path.clone(), e))?;
|
||||||
|
|
||||||
|
|
||||||
*action_state = ActionState::Completed;
|
*action_state = ActionState::Completed;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -111,8 +118,45 @@ impl Actionable for CreateOrAppendFile {
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn revert(&mut self) -> Result<(), Self::Error> {
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -126,6 +170,10 @@ impl From<CreateOrAppendFile> for Action {
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error, Serialize)]
|
#[derive(Debug, thiserror::Error, Serialize)]
|
||||||
pub enum CreateOrAppendFileError {
|
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}`")]
|
#[error("Open file `{0}`")]
|
||||||
OpenFile(std::path::PathBuf, #[source] #[serde(serialize_with = "crate::serialize_error_to_display")] std::io::Error),
|
OpenFile(std::path::PathBuf, #[source] #[serde(serialize_with = "crate::serialize_error_to_display")] std::io::Error),
|
||||||
#[error("Write file `{0}`")]
|
#[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),
|
GroupId(String, #[source] #[serde(serialize_with = "crate::serialize_error_to_display")] nix::errno::Errno),
|
||||||
#[error("Getting group `{0}`")]
|
#[error("Getting group `{0}`")]
|
||||||
NoGroup(String),
|
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}`")]
|
#[error("Chowning directory `{0}`")]
|
||||||
Chown(std::path::PathBuf, #[source] #[serde(serialize_with = "crate::serialize_error_to_display")] nix::errno::Errno),
|
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)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn revert(&mut self) -> Result<(), Self::Error> {
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,8 +70,11 @@ impl Actionable for FetchNix {
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn revert(&mut self) -> Result<(), Self::Error> {
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,8 +61,11 @@ impl Actionable for MoveUnpackedNix {
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn revert(&mut self) -> Result<(), Self::Error> {
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,9 +39,9 @@ impl Actionable for PlaceChannelConfiguration {
|
||||||
type Error = PlaceChannelConfigurationError;
|
type Error = PlaceChannelConfigurationError;
|
||||||
fn description(&self) -> Vec<ActionDescription> {
|
fn description(&self) -> Vec<ActionDescription> {
|
||||||
let Self {
|
let Self {
|
||||||
channels,
|
channels: _,
|
||||||
create_file,
|
create_file: _,
|
||||||
action_state: _,
|
action_state: _,
|
||||||
} = self;
|
} = self;
|
||||||
vec![ActionDescription::new(
|
vec![ActionDescription::new(
|
||||||
"Place a channel configuration".to_string(),
|
"Place a channel configuration".to_string(),
|
||||||
|
@ -53,7 +53,7 @@ impl Actionable for PlaceChannelConfiguration {
|
||||||
async fn execute(&mut self) -> Result<(), Self::Error> {
|
async fn execute(&mut self) -> Result<(), Self::Error> {
|
||||||
let Self {
|
let Self {
|
||||||
create_file,
|
create_file,
|
||||||
channels,
|
channels: _,
|
||||||
action_state,
|
action_state,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
|
@ -66,8 +66,15 @@ impl Actionable for PlaceChannelConfiguration {
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn revert(&mut self) -> Result<(), Self::Error> {
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,8 +66,12 @@ impl Actionable for PlaceNixConfiguration {
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn revert(&mut self) -> Result<(), Self::Error> {
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,12 +9,13 @@ use crate::actions::{ActionDescription, Actionable};
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
pub struct SetupDefaultProfile {
|
pub struct SetupDefaultProfile {
|
||||||
channels: Vec<String>,
|
channels: Vec<String>,
|
||||||
|
action_state: ActionState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SetupDefaultProfile {
|
impl SetupDefaultProfile {
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub async fn plan(channels: Vec<String>) -> Result<Self, SetupDefaultProfileError> {
|
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)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn execute(&mut self) -> Result<(), Self::Error> {
|
async fn execute(&mut self) -> Result<(), Self::Error> {
|
||||||
let Self { channels } = self;
|
let Self { channels, action_state } = self;
|
||||||
tracing::info!("Setting up default profile");
|
tracing::info!("Setting up default profile");
|
||||||
|
|
||||||
// Find an `nix` package
|
// Find an `nix` package
|
||||||
|
@ -105,14 +106,18 @@ impl Actionable for SetupDefaultProfile {
|
||||||
|
|
||||||
execute_command(&mut command).await.map_err(SetupDefaultProfileError::Command)?;
|
execute_command(&mut command).await.map_err(SetupDefaultProfileError::Command)?;
|
||||||
}
|
}
|
||||||
|
*action_state = ActionState::Completed;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn revert(&mut self) -> Result<(), Self::Error> {
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,8 +70,17 @@ impl Actionable for StartSystemdUnit {
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn revert(&mut self) -> Result<(), Self::Error> {
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,28 +89,21 @@ impl Actionable for ConfigureNix {
|
||||||
action_state,
|
action_state,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
let (
|
if let Some(configure_shell_profile) = configure_shell_profile {
|
||||||
setup_default_profile,
|
tokio::try_join!(
|
||||||
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!(
|
|
||||||
async move { setup_default_profile.execute().await.map_err(|e| ConfigureNixError::from(e)) },
|
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_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 { place_channel_configuration.execute().await.map_err(|e| ConfigureNixError::from(e)) },
|
||||||
async move { configure_shell_profile.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 {
|
} 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 { 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_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 { 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;
|
*action_state = ActionState::Completed;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -119,8 +112,24 @@ impl Actionable for ConfigureNix {
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn revert(&mut self) -> Result<(), Self::Error> {
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,8 +105,37 @@ impl Actionable for ConfigureShellProfile {
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn revert(&mut self) -> Result<(), Self::Error> {
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,8 +72,14 @@ impl Actionable for CreateNixTree {
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn revert(&mut self) -> Result<(), Self::Error> {
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,17 +81,17 @@ impl Actionable for CreateUsersAndGroup {
|
||||||
let Self {
|
let Self {
|
||||||
create_users,
|
create_users,
|
||||||
create_group,
|
create_group,
|
||||||
daemon_user_count,
|
daemon_user_count: _,
|
||||||
nix_build_group_name,
|
nix_build_group_name: _,
|
||||||
nix_build_group_id,
|
nix_build_group_id: _,
|
||||||
nix_build_user_prefix,
|
nix_build_user_prefix: _,
|
||||||
nix_build_user_id_base,
|
nix_build_user_id_base: _,
|
||||||
action_state
|
action_state,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
|
|
||||||
// Create group
|
// Create group
|
||||||
let create_group = create_group.execute().await?;
|
create_group.execute().await?;
|
||||||
|
|
||||||
// Create users
|
// Create users
|
||||||
// TODO(@hoverbear): Abstract this, it will be common
|
// TODO(@hoverbear): Abstract this, it will be common
|
||||||
|
@ -127,8 +127,48 @@ impl Actionable for CreateUsersAndGroup {
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn revert(&mut self) -> Result<(), Self::Error> {
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,8 +90,25 @@ impl Actionable for ProvisionNix {
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn revert(&mut self) -> Result<(), Self::Error> {
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,7 @@ impl Actionable for StartNixDaemon {
|
||||||
|
|
||||||
start_systemd_socket.execute().await?;
|
start_systemd_socket.execute().await?;
|
||||||
|
|
||||||
|
*action_state = ActionState::Completed;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,7 +47,7 @@ impl Actionable for StartNixDaemon {
|
||||||
|
|
||||||
start_systemd_socket.revert().await?;
|
start_systemd_socket.revert().await?;
|
||||||
|
|
||||||
*action_state = ActionState::Completed;
|
*action_state = ActionState::Reverted;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,6 +79,7 @@ impl CommandExecute for HarmonicCli {
|
||||||
match subcommand {
|
match subcommand {
|
||||||
Some(HarmonicSubcommand::Plan(plan)) => plan.execute().await,
|
Some(HarmonicSubcommand::Plan(plan)) => plan.execute().await,
|
||||||
Some(HarmonicSubcommand::Execute(execute)) => execute.execute().await,
|
Some(HarmonicSubcommand::Execute(execute)) => execute.execute().await,
|
||||||
|
Some(HarmonicSubcommand::Revert(revert)) => revert.execute().await,
|
||||||
None => {
|
None => {
|
||||||
let mut settings = InstallSettings::default();
|
let mut settings = InstallSettings::default();
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use std::process::ExitCode;
|
use std::{process::ExitCode, path::PathBuf};
|
||||||
|
|
||||||
use clap::{ArgAction, Parser};
|
use clap::{ArgAction, Parser};
|
||||||
use harmonic::InstallPlan;
|
use harmonic::InstallPlan;
|
||||||
use tokio::io::AsyncReadExt;
|
use eyre::WrapErr;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
cli::CommandExecute,
|
cli::CommandExecute,
|
||||||
|
@ -19,18 +19,18 @@ pub(crate) struct Execute {
|
||||||
global = true
|
global = true
|
||||||
)]
|
)]
|
||||||
no_confirm: bool,
|
no_confirm: bool,
|
||||||
|
#[clap(default_value = "/dev/stdin")]
|
||||||
|
plan: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl CommandExecute for Execute {
|
impl CommandExecute for Execute {
|
||||||
#[tracing::instrument(skip_all, fields())]
|
#[tracing::instrument(skip_all, fields())]
|
||||||
async fn execute(self) -> eyre::Result<ExitCode> {
|
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 install_plan_string = tokio::fs::read_to_string(plan).await.wrap_err("Reading plan")?;
|
||||||
let mut json = String::default();
|
let mut plan: InstallPlan = serde_json::from_str(&install_plan_string)?;
|
||||||
stdin.read_to_string(&mut json).await?;
|
|
||||||
let plan: InstallPlan = serde_json::from_str(&json)?;
|
|
||||||
|
|
||||||
if !no_confirm {
|
if !no_confirm {
|
||||||
if !interaction::confirm(plan.description()).await? {
|
if !interaction::confirm(plan.description()).await? {
|
||||||
|
@ -38,6 +38,8 @@ impl CommandExecute for Execute {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
plan.install().await?;
|
||||||
|
|
||||||
Ok(ExitCode::SUCCESS)
|
Ok(ExitCode::SUCCESS)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,12 @@ mod plan;
|
||||||
use plan::Plan;
|
use plan::Plan;
|
||||||
mod execute;
|
mod execute;
|
||||||
use execute::Execute;
|
use execute::Execute;
|
||||||
|
mod revert;
|
||||||
|
use revert::Revert;
|
||||||
|
|
||||||
#[derive(Debug, clap::Subcommand)]
|
#[derive(Debug, clap::Subcommand)]
|
||||||
pub(crate) enum HarmonicSubcommand {
|
pub(crate) enum HarmonicSubcommand {
|
||||||
Plan(Plan),
|
Plan(Plan),
|
||||||
Execute(Execute),
|
Execute(Execute),
|
||||||
|
Revert(Revert),
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
use std::process::ExitCode;
|
use std::{process::ExitCode, path::PathBuf};
|
||||||
|
|
||||||
use clap::{ArgAction, Parser};
|
use clap::{ArgAction, Parser};
|
||||||
use harmonic::{InstallPlan, InstallSettings};
|
use harmonic::{InstallPlan, InstallSettings};
|
||||||
use tokio::io::AsyncWriteExt;
|
use tokio::io::AsyncWriteExt;
|
||||||
|
use eyre::WrapErr;
|
||||||
|
|
||||||
use crate::cli::{arg::ChannelValue, CommandExecute};
|
use crate::cli::{arg::ChannelValue, CommandExecute};
|
||||||
|
|
||||||
|
@ -43,6 +44,8 @@ pub(crate) struct Plan {
|
||||||
global = true
|
global = true
|
||||||
)]
|
)]
|
||||||
pub(crate) force: bool,
|
pub(crate) force: bool,
|
||||||
|
#[clap(default_value = "/dev/stdout")]
|
||||||
|
plan: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
|
@ -59,6 +62,7 @@ impl CommandExecute for Plan {
|
||||||
daemon_user_count,
|
daemon_user_count,
|
||||||
explain,
|
explain,
|
||||||
force,
|
force,
|
||||||
|
plan,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
let mut settings = InstallSettings::default();
|
let mut settings = InstallSettings::default();
|
||||||
|
@ -73,11 +77,11 @@ impl CommandExecute for Plan {
|
||||||
);
|
);
|
||||||
settings.modify_profile(!no_modify_profile);
|
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)
|
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)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
pub enum HarmonicError {
|
pub enum HarmonicError {
|
||||||
#[error("Request error")]
|
#[error("Error executing action")]
|
||||||
Reqwest(#[from] reqwest::Error),
|
ActionError(#[source] #[from] ActionError),
|
||||||
#[error("Unarchiving error")]
|
#[error("Recording install receipt")]
|
||||||
Unarchive(std::io::Error),
|
RecordingReceipt(PathBuf, #[source] std::io::Error),
|
||||||
#[error("Getting temporary directory")]
|
#[error(transparent)]
|
||||||
TempDir(std::io::Error),
|
SerializingReceipt(serde_json::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>),
|
|
||||||
}
|
}
|
||||||
|
|
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())))]
|
#[tracing::instrument(skip_all, fields(command = %format!("{:?}", command.as_std())))]
|
||||||
async fn execute_command(
|
async fn execute_command(
|
||||||
command: &mut Command,
|
command: &mut Command,
|
||||||
|
@ -110,163 +39,10 @@ async fn execute_command(
|
||||||
.await?;
|
.await?;
|
||||||
match status.success() {
|
match status.success() {
|
||||||
true => Ok(status),
|
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(
|
#[tracing::instrument(skip_all, fields(
|
||||||
k = %k.as_ref().to_string_lossy(),
|
k = %k.as_ref().to_string_lossy(),
|
||||||
v = %v.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 serde::{Deserialize, Serialize};
|
||||||
|
use tokio::fs::File;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
actions::{
|
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 {
|
Ok(Self {
|
||||||
settings: settings.clone(),
|
settings: settings.clone(),
|
||||||
provision_nix: ProvisionNix::plan(settings.clone()).await?,
|
provision_nix: ProvisionNix::plan(settings.clone()).await
|
||||||
configure_nix: ConfigureNix::plan(settings).await?,
|
.map_err(|e| ActionError::from(e))?,
|
||||||
start_nix_daemon: StartNixDaemon::plan().await?,
|
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<(), ActionError> {
|
pub async fn install(&mut self) -> Result<(), HarmonicError> {
|
||||||
// This is **deliberately sequential**.
|
// This is **deliberately sequential**.
|
||||||
// Actions which are parallelizable are represented by "group actions" like CreateUsers
|
// Actions which are parallelizable are represented by "group actions" like CreateUsers
|
||||||
// The plan itself represents the concept of the sequence of stages.
|
// The plan itself represents the concept of the sequence of stages.
|
||||||
self.provision_nix.execute().await?;
|
self.provision_nix.execute().await
|
||||||
self.configure_nix.execute().await?;
|
.map_err(|e| ActionError::from(e))?;
|
||||||
self.start_nix_daemon.execute().await?;
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue