forked from lix-project/lix-installer
It buildsgit add src/git add src/
Signed-off-by: Ana Hobden <operator@hoverbear.org>
This commit is contained in:
parent
aa069cedf7
commit
199ca7f911
|
@ -5,7 +5,7 @@ use tokio::process::Command;
|
||||||
|
|
||||||
use crate::{execute_command, HarmonicError};
|
use crate::{execute_command, HarmonicError};
|
||||||
|
|
||||||
use crate::actions::{ActionDescription, Actionable, ActionState, Action};
|
use crate::actions::{ActionDescription, Actionable, ActionState, Action, ActionError};
|
||||||
|
|
||||||
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";
|
||||||
|
@ -13,20 +13,24 @@ const TMPFILES_SRC: &str = "/nix/var/nix/profiles/default//lib/tmpfiles.d/nix-da
|
||||||
const TMPFILES_DEST: &str = "/etc/tmpfiles.d/nix-daemon.conf";
|
const TMPFILES_DEST: &str = "/etc/tmpfiles.d/nix-daemon.conf";
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
pub struct ConfigureNixDaemonService {}
|
pub struct ConfigureNixDaemonService {
|
||||||
|
action_state: ActionState,
|
||||||
|
}
|
||||||
|
|
||||||
impl ConfigureNixDaemonService {
|
impl ConfigureNixDaemonService {
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub async fn plan() -> Result<Self, HarmonicError> {
|
pub async fn plan() -> Result<Self, ConfigureNixDaemonServiceError> {
|
||||||
if !Path::new("/run/systemd/system").exists() {
|
if !Path::new("/run/systemd/system").exists() {
|
||||||
return Err(HarmonicError::InitNotSupported);
|
return Err(ConfigureNixDaemonServiceError::InitNotSupported);
|
||||||
}
|
}
|
||||||
Ok(Self {})
|
Ok(Self {
|
||||||
|
action_state: ActionState::Planned,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl Actionable for ActionState<ConfigureNixDaemonService> {
|
impl Actionable for ConfigureNixDaemonService {
|
||||||
type Error = ConfigureNixDaemonServiceError;
|
type Error = ConfigureNixDaemonServiceError;
|
||||||
fn description(&self) -> Vec<ActionDescription> {
|
fn description(&self) -> Vec<ActionDescription> {
|
||||||
vec![ActionDescription::new(
|
vec![ActionDescription::new(
|
||||||
|
@ -42,33 +46,33 @@ impl Actionable for ActionState<ConfigureNixDaemonService> {
|
||||||
|
|
||||||
#[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 { action_state } = self;
|
||||||
tracing::info!("Configuring nix daemon service");
|
tracing::info!("Configuring nix daemon service");
|
||||||
|
|
||||||
tracing::trace!(src = TMPFILES_SRC, dest = TMPFILES_DEST, "Symlinking");
|
tracing::trace!(src = TMPFILES_SRC, dest = TMPFILES_DEST, "Symlinking");
|
||||||
tokio::fs::symlink(TMPFILES_SRC, TMPFILES_DEST)
|
tokio::fs::symlink(TMPFILES_SRC, TMPFILES_DEST)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
HarmonicError::Symlink(PathBuf::from(TMPFILES_SRC), PathBuf::from(TMPFILES_DEST), e)
|
Self::Error::Symlink(PathBuf::from(TMPFILES_SRC), PathBuf::from(TMPFILES_DEST), e)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
execute_command(
|
execute_command(
|
||||||
Command::new("systemd-tmpfiles")
|
Command::new("systemd-tmpfiles")
|
||||||
.arg("--create")
|
.arg("--create")
|
||||||
.arg("--prefix=/nix/var/nix"),
|
.arg("--prefix=/nix/var/nix"),
|
||||||
false,
|
|
||||||
)
|
)
|
||||||
.await?;
|
.await.map_err(Self::Error::CommandFailed)?;
|
||||||
|
|
||||||
execute_command(
|
execute_command(
|
||||||
Command::new("systemctl").arg("link").arg(SERVICE_SRC),
|
Command::new("systemctl").arg("link").arg(SERVICE_SRC),
|
||||||
false,
|
|
||||||
)
|
)
|
||||||
.await?;
|
.await.map_err(Self::Error::CommandFailed)?;
|
||||||
|
|
||||||
execute_command(Command::new("systemctl").arg("link").arg(SOCKET_SRC), false).await?;
|
execute_command(Command::new("systemctl").arg("link").arg(SOCKET_SRC)).await.map_err(Self::Error::CommandFailed)?;
|
||||||
|
|
||||||
execute_command(Command::new("systemctl").arg("daemon-reload"), false).await?;
|
execute_command(Command::new("systemctl").arg("daemon-reload")).await.map_err(Self::Error::CommandFailed)?;
|
||||||
|
|
||||||
|
*action_state = ActionState::Completed;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,17 +87,28 @@ impl Actionable for ActionState<ConfigureNixDaemonService> {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
impl From<ActionState<ConfigureNixDaemonService>> for ActionState<Action> {
|
impl From<ConfigureNixDaemonService> for Action {
|
||||||
fn from(v: ActionState<ConfigureNixDaemonService>) -> Self {
|
fn from(v: ConfigureNixDaemonService) -> Self {
|
||||||
match v {
|
Action::ConfigureNixDaemonService(v)
|
||||||
ActionState::Completed(_) => ActionState::Completed(Action::ConfigureNixDaemonService(v)),
|
|
||||||
ActionState::Planned(_) => ActionState::Planned(Action::ConfigureNixDaemonService(v)),
|
|
||||||
ActionState::Reverted(_) => ActionState::Reverted(Action::ConfigureNixDaemonService(v)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error, Serialize)]
|
#[derive(Debug, thiserror::Error, Serialize)]
|
||||||
pub enum ConfigureNixDaemonServiceError {
|
pub enum ConfigureNixDaemonServiceError {
|
||||||
|
#[error("Symlinking from `{0}` to `{1}`")]
|
||||||
|
Symlink(
|
||||||
|
std::path::PathBuf,
|
||||||
|
std::path::PathBuf,
|
||||||
|
#[source]
|
||||||
|
#[serde(serialize_with = "crate::serialize_error_to_display")]
|
||||||
|
std::io::Error
|
||||||
|
),
|
||||||
|
#[error("Command `{0}` failed to execute")]
|
||||||
|
CommandFailed(
|
||||||
|
#[source]
|
||||||
|
#[serde(serialize_with = "crate::serialize_error_to_display")]
|
||||||
|
std::io::Error
|
||||||
|
),
|
||||||
|
#[error("No supported init system found")]
|
||||||
|
InitNotSupported,
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ use tokio::fs::create_dir;
|
||||||
|
|
||||||
use crate::HarmonicError;
|
use crate::HarmonicError;
|
||||||
|
|
||||||
use crate::actions::{ActionDescription, Actionable, ActionState, Action};
|
use crate::actions::{ActionDescription, Actionable, ActionState, Action, ActionError};
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
pub struct CreateDirectory {
|
pub struct CreateDirectory {
|
||||||
|
@ -14,6 +14,7 @@ pub struct CreateDirectory {
|
||||||
user: String,
|
user: String,
|
||||||
group: String,
|
group: String,
|
||||||
mode: u32,
|
mode: u32,
|
||||||
|
action_state: ActionState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CreateDirectory {
|
impl CreateDirectory {
|
||||||
|
@ -24,11 +25,11 @@ impl CreateDirectory {
|
||||||
group: String,
|
group: String,
|
||||||
mode: u32,
|
mode: u32,
|
||||||
force: bool,
|
force: bool,
|
||||||
) -> Result<Self, HarmonicError> {
|
) -> Result<Self, CreateDirectoryError> {
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
|
|
||||||
if path.exists() && !force {
|
if path.exists() && !force {
|
||||||
return Err(HarmonicError::CreateDirectory(
|
return Err(CreateDirectoryError::Exists(
|
||||||
path.to_path_buf(),
|
path.to_path_buf(),
|
||||||
std::io::Error::new(
|
std::io::Error::new(
|
||||||
std::io::ErrorKind::AlreadyExists,
|
std::io::ErrorKind::AlreadyExists,
|
||||||
|
@ -42,12 +43,13 @@ impl CreateDirectory {
|
||||||
user,
|
user,
|
||||||
group,
|
group,
|
||||||
mode,
|
mode,
|
||||||
|
action_state: ActionState::Planned,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl Actionable for ActionState<CreateDirectory> {
|
impl Actionable for CreateDirectory {
|
||||||
type Error = CreateDirectoryError;
|
type Error = CreateDirectoryError;
|
||||||
fn description(&self) -> Vec<ActionDescription> {
|
fn description(&self) -> Vec<ActionDescription> {
|
||||||
let Self {
|
let Self {
|
||||||
|
@ -55,6 +57,7 @@ impl Actionable for ActionState<CreateDirectory> {
|
||||||
user,
|
user,
|
||||||
group,
|
group,
|
||||||
mode,
|
mode,
|
||||||
|
action_state,
|
||||||
} = &self;
|
} = &self;
|
||||||
vec![ActionDescription::new(
|
vec![ActionDescription::new(
|
||||||
format!("Create the directory `{}`", path.display()),
|
format!("Create the directory `{}`", path.display()),
|
||||||
|
@ -72,23 +75,25 @@ impl Actionable for ActionState<CreateDirectory> {
|
||||||
user,
|
user,
|
||||||
group,
|
group,
|
||||||
mode,
|
mode,
|
||||||
|
action_state,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
let gid = Group::from_name(group.as_str())
|
let gid = Group::from_name(group.as_str())
|
||||||
.map_err(|e| HarmonicError::GroupId(group.clone(), e))?
|
.map_err(|e| Self::Error::GroupId(group.clone(), e))?
|
||||||
.ok_or(HarmonicError::NoGroup(group.clone()))?
|
.ok_or(Self::Error::NoGroup(group.clone()))?
|
||||||
.gid;
|
.gid;
|
||||||
let uid = User::from_name(user.as_str())
|
let uid = User::from_name(user.as_str())
|
||||||
.map_err(|e| HarmonicError::UserId(user.clone(), e))?
|
.map_err(|e| Self::Error::UserId(user.clone(), e))?
|
||||||
.ok_or(HarmonicError::NoUser(user.clone()))?
|
.ok_or(Self::Error::NoUser(user.clone()))?
|
||||||
.uid;
|
.uid;
|
||||||
|
|
||||||
tracing::trace!(path = %path.display(), "Creating directory");
|
tracing::trace!(path = %path.display(), "Creating directory");
|
||||||
create_dir(path.clone())
|
create_dir(path.clone())
|
||||||
.await
|
.await
|
||||||
.map_err(|e| HarmonicError::CreateDirectory(path.clone(), e))?;
|
.map_err(|e| Self::Error::Creating(path.clone(), e))?;
|
||||||
chown(&path, Some(uid), Some(gid)).map_err(|e| HarmonicError::Chown(path.clone(), e))?;
|
chown(path, Some(uid), Some(gid)).map_err(|e| Self::Error::Chown(path.clone(), e))?;
|
||||||
|
|
||||||
|
*action_state = ActionState::Completed;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,18 +107,27 @@ impl Actionable for ActionState<CreateDirectory> {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl From<ActionState<CreateDirectory>> for ActionState<Action> {
|
impl From<CreateDirectory> for Action {
|
||||||
fn from(v: ActionState<CreateDirectory>) -> Self {
|
fn from(v: CreateDirectory) -> Self {
|
||||||
match v {
|
Action::CreateDirectory(v)
|
||||||
ActionState::Completed(_) => ActionState::Completed(Action::CreateDirectory(v)),
|
|
||||||
ActionState::Planned(_) => ActionState::Planned(Action::CreateDirectory(v)),
|
|
||||||
ActionState::Reverted(_) => ActionState::Reverted(Action::CreateDirectory(v)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error, Serialize)]
|
#[derive(Debug, thiserror::Error, Serialize)]
|
||||||
pub enum CreateDirectoryError {
|
pub enum CreateDirectoryError {
|
||||||
|
#[error("Directory exists `{0}`")]
|
||||||
|
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("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}`")]
|
||||||
|
UserId(String, #[source] #[serde(serialize_with = "crate::serialize_error_to_display")] nix::errno::Errno),
|
||||||
|
#[error("Getting user `{0}`")]
|
||||||
|
NoUser(String),
|
||||||
|
#[error("Getting gid for group `{0}`")]
|
||||||
|
GroupId(String, #[source] #[serde(serialize_with = "crate::serialize_error_to_display")] nix::errno::Errno),
|
||||||
|
#[error("Getting group `{0}`")]
|
||||||
|
NoGroup(String),
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ use tokio::{
|
||||||
io::AsyncWriteExt,
|
io::AsyncWriteExt,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{HarmonicError, actions::{ActionState, Action}};
|
use crate::{HarmonicError, actions::{ActionState, Action, ActionError}};
|
||||||
|
|
||||||
use crate::actions::{ActionDescription, Actionable};
|
use crate::actions::{ActionDescription, Actionable};
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ pub struct CreateFile {
|
||||||
mode: u32,
|
mode: u32,
|
||||||
buf: String,
|
buf: String,
|
||||||
force: bool,
|
force: bool,
|
||||||
|
action_state: ActionState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CreateFile {
|
impl CreateFile {
|
||||||
|
@ -29,17 +30,11 @@ impl CreateFile {
|
||||||
mode: u32,
|
mode: u32,
|
||||||
buf: String,
|
buf: String,
|
||||||
force: bool,
|
force: bool,
|
||||||
) -> Result<Self, HarmonicError> {
|
) -> Result<Self, CreateFileError> {
|
||||||
let path = path.as_ref().to_path_buf();
|
let path = path.as_ref().to_path_buf();
|
||||||
|
|
||||||
if path.exists() && !force {
|
if path.exists() && !force {
|
||||||
return Err(HarmonicError::CreateFile(
|
return Err(CreateFileError::Exists(path.to_path_buf()));
|
||||||
path.to_path_buf(),
|
|
||||||
std::io::Error::new(
|
|
||||||
std::io::ErrorKind::AlreadyExists,
|
|
||||||
format!("Directory `{}` already exists", path.display()),
|
|
||||||
),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
|
@ -49,12 +44,13 @@ impl CreateFile {
|
||||||
mode,
|
mode,
|
||||||
buf,
|
buf,
|
||||||
force,
|
force,
|
||||||
|
action_state: ActionState::Planned,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl Actionable for ActionState<CreateFile> {
|
impl Actionable for CreateFile {
|
||||||
type Error = CreateFileError;
|
type Error = CreateFileError;
|
||||||
fn description(&self) -> Vec<ActionDescription> {
|
fn description(&self) -> Vec<ActionDescription> {
|
||||||
let Self {
|
let Self {
|
||||||
|
@ -64,6 +60,7 @@ impl Actionable for ActionState<CreateFile> {
|
||||||
mode,
|
mode,
|
||||||
buf,
|
buf,
|
||||||
force,
|
force,
|
||||||
|
action_state: _,
|
||||||
} = &self;
|
} = &self;
|
||||||
vec![ActionDescription::new(
|
vec![ActionDescription::new(
|
||||||
format!("Create or overwrite file `{}`", path.display()),
|
format!("Create or overwrite file `{}`", path.display()),
|
||||||
|
@ -82,6 +79,7 @@ impl Actionable for ActionState<CreateFile> {
|
||||||
mode,
|
mode,
|
||||||
buf,
|
buf,
|
||||||
force: _,
|
force: _,
|
||||||
|
action_state,
|
||||||
} = self;
|
} = self;
|
||||||
tracing::trace!(path = %path.display(), "Creating file");
|
tracing::trace!(path = %path.display(), "Creating file");
|
||||||
let mut file = OpenOptions::new()
|
let mut file = OpenOptions::new()
|
||||||
|
@ -90,24 +88,25 @@ impl Actionable for ActionState<CreateFile> {
|
||||||
.read(true)
|
.read(true)
|
||||||
.open(&path)
|
.open(&path)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| HarmonicError::OpenFile(path.to_owned(), e))?;
|
.map_err(|e| Self::Error::OpenFile(path.to_owned(), e))?;
|
||||||
|
|
||||||
file.write_all(buf.as_bytes())
|
file.write_all(buf.as_bytes())
|
||||||
.await
|
.await
|
||||||
.map_err(|e| HarmonicError::WriteFile(path.to_owned(), e))?;
|
.map_err(|e| Self::Error::WriteFile(path.to_owned(), e))?;
|
||||||
|
|
||||||
let gid = Group::from_name(group.as_str())
|
let gid = Group::from_name(group.as_str())
|
||||||
.map_err(|e| HarmonicError::GroupId(group.clone(), e))?
|
.map_err(|e| Self::Error::GroupId(group.clone(), e))?
|
||||||
.ok_or(HarmonicError::NoGroup(group.clone()))?
|
.ok_or(Self::Error::NoGroup(group.clone()))?
|
||||||
.gid;
|
.gid;
|
||||||
let uid = User::from_name(user.as_str())
|
let uid = User::from_name(user.as_str())
|
||||||
.map_err(|e| HarmonicError::UserId(user.clone(), e))?
|
.map_err(|e| Self::Error::UserId(user.clone(), e))?
|
||||||
.ok_or(HarmonicError::NoUser(user.clone()))?
|
.ok_or(Self::Error::NoUser(user.clone()))?
|
||||||
.uid;
|
.uid;
|
||||||
|
|
||||||
tracing::trace!(path = %path.display(), "Chowning file");
|
tracing::trace!(path = %path.display(), "Chowning file");
|
||||||
chown(&path, Some(uid), Some(gid)).map_err(|e| HarmonicError::Chown(path.clone(), e))?;
|
chown(path, Some(uid), Some(gid)).map_err(|e| Self::Error::Chown(path.clone(), e))?;
|
||||||
|
|
||||||
|
*action_state = ActionState::Completed;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,17 +119,28 @@ impl Actionable for ActionState<CreateFile> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ActionState<CreateFile>> for ActionState<Action> {
|
impl From<CreateFile> for Action {
|
||||||
fn from(v: ActionState<CreateFile>) -> Self {
|
fn from(v: CreateFile) -> Self {
|
||||||
match v {
|
Action::CreateFile(v)
|
||||||
ActionState::Completed(_) => ActionState::Completed(Action::CreateFile(v)),
|
|
||||||
ActionState::Planned(_) => ActionState::Planned(Action::CreateFile(v)),
|
|
||||||
ActionState::Reverted(_) => ActionState::Reverted(Action::CreateFile(v)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error, Serialize)]
|
#[derive(Debug, thiserror::Error, Serialize)]
|
||||||
pub enum CreateFileError {
|
pub enum CreateFileError {
|
||||||
|
#[error("File exists `{0}`")]
|
||||||
|
Exists(std::path::PathBuf),
|
||||||
|
#[error("Open file `{0}`")]
|
||||||
|
OpenFile(std::path::PathBuf, #[source] #[serde(serialize_with = "crate::serialize_error_to_display")] std::io::Error),
|
||||||
|
#[error("Write file `{0}`")]
|
||||||
|
WriteFile(std::path::PathBuf, #[source] #[serde(serialize_with = "crate::serialize_error_to_display")] std::io::Error),
|
||||||
|
#[error("Getting uid for user `{0}`")]
|
||||||
|
UserId(String, #[source] #[serde(serialize_with = "crate::serialize_error_to_display")] nix::errno::Errno),
|
||||||
|
#[error("Getting user `{0}`")]
|
||||||
|
NoUser(String),
|
||||||
|
#[error("Getting gid for group `{0}`")]
|
||||||
|
GroupId(String, #[source] #[serde(serialize_with = "crate::serialize_error_to_display")] nix::errno::Errno),
|
||||||
|
#[error("Getting group `{0}`")]
|
||||||
|
NoGroup(String),
|
||||||
|
#[error("Chowning directory `{0}`")]
|
||||||
|
Chown(std::path::PathBuf, #[source] #[serde(serialize_with = "crate::serialize_error_to_display")] nix::errno::Errno),
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,26 +3,27 @@ use tokio::process::Command;
|
||||||
|
|
||||||
use crate::{HarmonicError, execute_command};
|
use crate::{HarmonicError, execute_command};
|
||||||
|
|
||||||
use crate::actions::{ActionDescription, Actionable, ActionState, Action};
|
use crate::actions::{ActionDescription, Actionable, ActionState, Action, ActionError};
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
pub struct CreateGroup {
|
pub struct CreateGroup {
|
||||||
name: String,
|
name: String,
|
||||||
gid: usize,
|
gid: usize,
|
||||||
|
action_state: ActionState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CreateGroup {
|
impl CreateGroup {
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub fn plan(name: String, gid: usize) -> Self {
|
pub fn plan(name: String, gid: usize) -> Self {
|
||||||
Self { name, gid }
|
Self { name, gid, action_state: ActionState::Planned }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl Actionable for ActionState<CreateGroup> {
|
impl Actionable for CreateGroup {
|
||||||
type Error = CreateOrAppendFileError;
|
type Error = CreateGroupError;
|
||||||
fn description(&self) -> Vec<ActionDescription> {
|
fn description(&self) -> Vec<ActionDescription> {
|
||||||
let Self { name, gid } = &self;
|
let Self { name, gid, action_state: _ } = &self;
|
||||||
vec![ActionDescription::new(
|
vec![ActionDescription::new(
|
||||||
format!("Create group {name} with GID {gid}"),
|
format!("Create group {name} with GID {gid}"),
|
||||||
vec![format!(
|
vec![format!(
|
||||||
|
@ -33,13 +34,14 @@ impl Actionable for ActionState<CreateGroup> {
|
||||||
|
|
||||||
#[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 { name, gid } = self;
|
let Self { name, gid, action_state } = self;
|
||||||
|
|
||||||
execute_command(
|
execute_command(
|
||||||
Command::new("groupadd").args(["-g", &gid.to_string(), "--system", &name]),
|
Command::new("groupadd").args(["-g", &gid.to_string(), "--system", &name]),
|
||||||
false,
|
).await.map_err(CreateGroupError::Command)?;
|
||||||
).await?;
|
|
||||||
|
|
||||||
|
|
||||||
|
*action_state = ActionState::Completed;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,16 +53,14 @@ impl Actionable for ActionState<CreateGroup> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ActionState<CreateGroup>> for ActionState<Action> {
|
impl From<CreateGroup> for Action {
|
||||||
fn from(v: ActionState<CreateGroup>) -> Self {
|
fn from(v: CreateGroup) -> Self {
|
||||||
match v {
|
Action::CreateGroup(v)
|
||||||
ActionState::Completed(_) => ActionState::Completed(Action::CreateGroup(v)),
|
|
||||||
ActionState::Planned(_) => ActionState::Planned(Action::CreateGroup(v)),
|
|
||||||
ActionState::Reverted(_) => ActionState::Reverted(Action::CreateGroup(v)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error, Serialize)]
|
#[derive(Debug, thiserror::Error, Serialize)]
|
||||||
pub enum CreateGroupError {
|
pub enum CreateGroupError {
|
||||||
|
#[error("Failed to execute command")]
|
||||||
|
Command(#[source] #[serde(serialize_with = "crate::serialize_error_to_display")] std::io::Error)
|
||||||
}
|
}
|
|
@ -9,7 +9,7 @@ use tokio::{
|
||||||
io::{AsyncSeekExt, AsyncWriteExt},
|
io::{AsyncSeekExt, AsyncWriteExt},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{HarmonicError, actions::{ActionState, Action}};
|
use crate::{HarmonicError, actions::{ActionState, Action, ActionError}};
|
||||||
|
|
||||||
use crate::actions::{ActionDescription, Actionable};
|
use crate::actions::{ActionDescription, Actionable};
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ pub struct CreateOrAppendFile {
|
||||||
group: String,
|
group: String,
|
||||||
mode: u32,
|
mode: u32,
|
||||||
buf: String,
|
buf: String,
|
||||||
|
action_state: ActionState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CreateOrAppendFile {
|
impl CreateOrAppendFile {
|
||||||
|
@ -30,7 +31,7 @@ impl CreateOrAppendFile {
|
||||||
group: String,
|
group: String,
|
||||||
mode: u32,
|
mode: u32,
|
||||||
buf: String,
|
buf: String,
|
||||||
) -> Result<Self, HarmonicError> {
|
) -> Result<Self, CreateOrAppendFileError> {
|
||||||
let path = path.as_ref().to_path_buf();
|
let path = path.as_ref().to_path_buf();
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
|
@ -39,12 +40,13 @@ impl CreateOrAppendFile {
|
||||||
group,
|
group,
|
||||||
mode,
|
mode,
|
||||||
buf,
|
buf,
|
||||||
|
action_state: ActionState::Planned,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl Actionable for ActionState<CreateOrAppendFile> {
|
impl Actionable for CreateOrAppendFile {
|
||||||
type Error = CreateOrAppendFileError;
|
type Error = CreateOrAppendFileError;
|
||||||
fn description(&self) -> Vec<ActionDescription> {
|
fn description(&self) -> Vec<ActionDescription> {
|
||||||
let Self {
|
let Self {
|
||||||
|
@ -53,6 +55,7 @@ impl Actionable for ActionState<CreateOrAppendFile> {
|
||||||
group,
|
group,
|
||||||
mode,
|
mode,
|
||||||
buf,
|
buf,
|
||||||
|
action_state: _,
|
||||||
} = &self;
|
} = &self;
|
||||||
vec![ActionDescription::new(
|
vec![ActionDescription::new(
|
||||||
format!("Create or append file `{}`", path.display()),
|
format!("Create or append file `{}`", path.display()),
|
||||||
|
@ -70,6 +73,7 @@ impl Actionable for ActionState<CreateOrAppendFile> {
|
||||||
group,
|
group,
|
||||||
mode,
|
mode,
|
||||||
buf,
|
buf,
|
||||||
|
action_state,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
tracing::trace!(path = %path.display(), "Creating or appending");
|
tracing::trace!(path = %path.display(), "Creating or appending");
|
||||||
|
@ -79,27 +83,28 @@ impl Actionable for ActionState<CreateOrAppendFile> {
|
||||||
.read(true)
|
.read(true)
|
||||||
.open(&path)
|
.open(&path)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| HarmonicError::OpenFile(path.to_owned(), e))?;
|
.map_err(|e| Self::Error::OpenFile(path.to_owned(), e))?;
|
||||||
|
|
||||||
file.seek(SeekFrom::End(0))
|
file.seek(SeekFrom::End(0))
|
||||||
.await
|
.await
|
||||||
.map_err(|e| HarmonicError::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| HarmonicError::WriteFile(path.to_owned(), e))?;
|
.map_err(|e| Self::Error::WriteFile(path.to_owned(), e))?;
|
||||||
|
|
||||||
let gid = Group::from_name(group.as_str())
|
let gid = Group::from_name(group.as_str())
|
||||||
.map_err(|e| HarmonicError::GroupId(group.clone(), e))?
|
.map_err(|e| Self::Error::GroupId(group.clone(), e))?
|
||||||
.ok_or(HarmonicError::NoGroup(group.clone()))?
|
.ok_or(Self::Error::NoGroup(group.clone()))?
|
||||||
.gid;
|
.gid;
|
||||||
let uid = User::from_name(user.as_str())
|
let uid = User::from_name(user.as_str())
|
||||||
.map_err(|e| HarmonicError::UserId(user.clone(), e))?
|
.map_err(|e| Self::Error::UserId(user.clone(), e))?
|
||||||
.ok_or(HarmonicError::NoUser(user.clone()))?
|
.ok_or(Self::Error::NoUser(user.clone()))?
|
||||||
.uid;
|
.uid;
|
||||||
|
|
||||||
tracing::trace!(path = %path.display(), "Chowning");
|
tracing::trace!(path = %path.display(), "Chowning");
|
||||||
chown(&path, Some(uid), Some(gid)).map_err(|e| HarmonicError::Chown(path.clone(), e))?;
|
chown(path, Some(uid), Some(gid)).map_err(|e| Self::Error::Chown(path.clone(), e))?;
|
||||||
|
|
||||||
|
*action_state = ActionState::Completed;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,17 +118,28 @@ impl Actionable for ActionState<CreateOrAppendFile> {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl From<ActionState<CreateOrAppendFile>> for ActionState<Action> {
|
impl From<CreateOrAppendFile> for Action {
|
||||||
fn from(v: ActionState<CreateOrAppendFile>) -> Self {
|
fn from(v: CreateOrAppendFile) -> Self {
|
||||||
match v {
|
Action::CreateOrAppendFile(v)
|
||||||
ActionState::Completed(_) => ActionState::Completed(Action::CreateOrAppendFile(v)),
|
|
||||||
ActionState::Planned(_) => ActionState::Planned(Action::CreateOrAppendFile(v)),
|
|
||||||
ActionState::Reverted(_) => ActionState::Reverted(Action::CreateOrAppendFile(v)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error, Serialize)]
|
#[derive(Debug, thiserror::Error, Serialize)]
|
||||||
pub enum CreateOrAppendFileError {
|
pub enum CreateOrAppendFileError {
|
||||||
|
#[error("Open file `{0}`")]
|
||||||
|
OpenFile(std::path::PathBuf, #[source] #[serde(serialize_with = "crate::serialize_error_to_display")] std::io::Error),
|
||||||
|
#[error("Write file `{0}`")]
|
||||||
|
WriteFile(std::path::PathBuf, #[source] #[serde(serialize_with = "crate::serialize_error_to_display")] std::io::Error),
|
||||||
|
#[error("Seek file `{0}`")]
|
||||||
|
SeekFile(std::path::PathBuf, #[source] #[serde(serialize_with = "crate::serialize_error_to_display")] std::io::Error),
|
||||||
|
#[error("Getting uid for user `{0}`")]
|
||||||
|
UserId(String, #[source] #[serde(serialize_with = "crate::serialize_error_to_display")] nix::errno::Errno),
|
||||||
|
#[error("Getting user `{0}`")]
|
||||||
|
NoUser(String),
|
||||||
|
#[error("Getting gid for group `{0}`")]
|
||||||
|
GroupId(String, #[source] #[serde(serialize_with = "crate::serialize_error_to_display")] nix::errno::Errno),
|
||||||
|
#[error("Getting group `{0}`")]
|
||||||
|
NoGroup(String),
|
||||||
|
#[error("Chowning directory `{0}`")]
|
||||||
|
Chown(std::path::PathBuf, #[source] #[serde(serialize_with = "crate::serialize_error_to_display")] nix::errno::Errno),
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,24 +3,25 @@ use tokio::process::Command;
|
||||||
|
|
||||||
use crate::{HarmonicError, execute_command};
|
use crate::{HarmonicError, execute_command};
|
||||||
|
|
||||||
use crate::actions::{ActionDescription, Actionable, ActionState, Action};
|
use crate::actions::{ActionDescription, Actionable, ActionState, Action, ActionError};
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
pub struct CreateUser {
|
pub struct CreateUser {
|
||||||
name: String,
|
name: String,
|
||||||
uid: usize,
|
uid: usize,
|
||||||
gid: usize,
|
gid: usize,
|
||||||
|
action_state: ActionState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CreateUser {
|
impl CreateUser {
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub fn plan(name: String, uid: usize, gid: usize) -> Self {
|
pub fn plan(name: String, uid: usize, gid: usize) -> Self {
|
||||||
Self { name, uid, gid }
|
Self { name, uid, gid, action_state: ActionState::Planned }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl Actionable for ActionState<CreateUser> {
|
impl Actionable for CreateUser {
|
||||||
type Error = CreateUserError;
|
type Error = CreateUserError;
|
||||||
fn description(&self) -> Vec<ActionDescription> {
|
fn description(&self) -> Vec<ActionDescription> {
|
||||||
let name = &self.name;
|
let name = &self.name;
|
||||||
|
@ -35,7 +36,7 @@ impl Actionable for ActionState<CreateUser> {
|
||||||
|
|
||||||
#[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 { name, uid, gid } = self;
|
let Self { name, uid, gid, action_state } = self;
|
||||||
|
|
||||||
execute_command(Command::new("useradd").args([
|
execute_command(Command::new("useradd").args([
|
||||||
"--home-dir",
|
"--home-dir",
|
||||||
|
@ -55,8 +56,9 @@ impl Actionable for ActionState<CreateUser> {
|
||||||
"--password",
|
"--password",
|
||||||
"\"!\"",
|
"\"!\"",
|
||||||
&name.to_string(),
|
&name.to_string(),
|
||||||
]), false).await?;
|
])).await.map_err(Self::Error::Command)?;
|
||||||
|
|
||||||
|
*action_state = ActionState::Completed;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,17 +71,14 @@ impl Actionable for ActionState<CreateUser> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ActionState<CreateUser>> for ActionState<Action> {
|
impl From<CreateUser> for Action {
|
||||||
fn from(v: ActionState<CreateUser>) -> Self {
|
fn from(v: CreateUser) -> Self {
|
||||||
match v {
|
Action::CreateUser(v)
|
||||||
ActionState::Completed(_) => ActionState::Completed(Action::CreateUser(v)),
|
|
||||||
ActionState::Planned(_) => ActionState::Planned(Action::CreateUser(v)),
|
|
||||||
ActionState::Reverted(_) => ActionState::Reverted(Action::CreateUser(v)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error, Serialize)]
|
#[derive(Debug, thiserror::Error, Serialize)]
|
||||||
pub enum CreateUserError {
|
pub enum CreateUserError {
|
||||||
|
#[error("Failed to execute command")]
|
||||||
|
Command(#[source] #[serde(serialize_with = "crate::serialize_error_to_display")] std::io::Error)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,16 +3,17 @@ use std::path::{PathBuf};
|
||||||
use bytes::Buf;
|
use bytes::Buf;
|
||||||
use reqwest::Url;
|
use reqwest::Url;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use tokio::task::spawn_blocking;
|
use tokio::task::{spawn_blocking, JoinError};
|
||||||
|
|
||||||
use crate::HarmonicError;
|
use crate::HarmonicError;
|
||||||
|
|
||||||
use crate::actions::{ActionDescription, Actionable, ActionState, Action};
|
use crate::actions::{ActionDescription, Actionable, ActionState, Action, ActionError};
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
pub struct FetchNix {
|
pub struct FetchNix {
|
||||||
url: Url,
|
url: Url,
|
||||||
destination: PathBuf,
|
destination: PathBuf,
|
||||||
|
action_state: ActionState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FetchNix {
|
impl FetchNix {
|
||||||
|
@ -21,15 +22,15 @@ impl FetchNix {
|
||||||
// TODO(@hoverbear): Check URL exists?
|
// TODO(@hoverbear): Check URL exists?
|
||||||
// TODO(@hoverbear): Check tempdir exists
|
// TODO(@hoverbear): Check tempdir exists
|
||||||
|
|
||||||
Ok(Self { url, destination })
|
Ok(Self { url, destination, action_state: ActionState::Planned })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl Actionable for ActionState<FetchNix> {
|
impl Actionable for FetchNix {
|
||||||
type Error = FetchNixError;
|
type Error = FetchNixError;
|
||||||
fn description(&self) -> Vec<ActionDescription> {
|
fn description(&self) -> Vec<ActionDescription> {
|
||||||
let Self { url, destination } = &self;
|
let Self { url, destination, action_state: _ } = &self;
|
||||||
vec![ActionDescription::new(
|
vec![ActionDescription::new(
|
||||||
format!("Fetch Nix from `{url}`"),
|
format!("Fetch Nix from `{url}`"),
|
||||||
vec![format!(
|
vec![format!(
|
||||||
|
@ -41,20 +42,20 @@ impl Actionable for ActionState<FetchNix> {
|
||||||
|
|
||||||
#[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 { url, destination } = self;
|
let Self { url, destination, action_state } = self;
|
||||||
|
|
||||||
tracing::trace!(%url, "Fetching url");
|
tracing::trace!(%url, "Fetching url");
|
||||||
let res = reqwest::get(url.clone())
|
let res = reqwest::get(url.clone())
|
||||||
.await
|
.await
|
||||||
.map_err(HarmonicError::Reqwest)?;
|
.map_err(Self::Error::Reqwest)?;
|
||||||
let bytes = res.bytes().await.map_err(HarmonicError::Reqwest)?;
|
let bytes = res.bytes().await.map_err(Self::Error::Reqwest)?;
|
||||||
// TODO(@Hoverbear): Pick directory
|
// TODO(@Hoverbear): Pick directory
|
||||||
tracing::trace!("Unpacking tar.xz");
|
tracing::trace!("Unpacking tar.xz");
|
||||||
let destination_clone = destination.clone();
|
let destination_clone = destination.clone();
|
||||||
let handle: Result<(), HarmonicError> = spawn_blocking(move || {
|
let handle: Result<(), Self::Error> = spawn_blocking(move || {
|
||||||
let decoder = xz2::read::XzDecoder::new(bytes.reader());
|
let decoder = xz2::read::XzDecoder::new(bytes.reader());
|
||||||
let mut archive = tar::Archive::new(decoder);
|
let mut archive = tar::Archive::new(decoder);
|
||||||
archive.unpack(&destination_clone).map_err(HarmonicError::Unarchive)?;
|
archive.unpack(&destination_clone).map_err(Self::Error::Unarchive)?;
|
||||||
tracing::debug!(destination = %destination_clone.display(), "Downloaded & extracted Nix");
|
tracing::debug!(destination = %destination_clone.display(), "Downloaded & extracted Nix");
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
|
@ -62,6 +63,7 @@ impl Actionable for ActionState<FetchNix> {
|
||||||
|
|
||||||
handle?;
|
handle?;
|
||||||
|
|
||||||
|
*action_state = ActionState::Completed;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,17 +76,22 @@ impl Actionable for ActionState<FetchNix> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ActionState<FetchNix>> for ActionState<Action> {
|
impl From<FetchNix> for Action {
|
||||||
fn from(v: ActionState<FetchNix>) -> Self {
|
fn from(v: FetchNix) -> Self {
|
||||||
match v {
|
Action::FetchNix(v)
|
||||||
ActionState::Completed(_) => ActionState::Completed(Action::FetchNix(v)),
|
|
||||||
ActionState::Planned(_) => ActionState::Planned(Action::FetchNix(v)),
|
|
||||||
ActionState::Reverted(_) => ActionState::Reverted(Action::FetchNix(v)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error, Serialize)]
|
#[derive(Debug, thiserror::Error, Serialize)]
|
||||||
pub enum FetchNixError {
|
pub enum FetchNixError {
|
||||||
|
#[error(transparent)]
|
||||||
|
Join(
|
||||||
|
#[from]
|
||||||
|
#[serde(serialize_with = "crate::serialize_error_to_display")]
|
||||||
|
JoinError
|
||||||
|
),
|
||||||
|
#[error("Request error")]
|
||||||
|
Reqwest(#[from] #[source] #[serde(serialize_with = "crate::serialize_error_to_display")] reqwest::Error),
|
||||||
|
#[error("Unarchiving error")]
|
||||||
|
Unarchive(#[source] #[serde(serialize_with = "crate::serialize_error_to_display")] std::io::Error),
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,26 +4,27 @@ use serde::Serialize;
|
||||||
|
|
||||||
use crate::HarmonicError;
|
use crate::HarmonicError;
|
||||||
|
|
||||||
use crate::actions::{ActionDescription, Actionable, ActionState, Action};
|
use crate::actions::{ActionDescription, Actionable, ActionState, Action, ActionError};
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
pub struct MoveUnpackedNix {
|
pub struct MoveUnpackedNix {
|
||||||
source: PathBuf,
|
source: PathBuf,
|
||||||
|
action_state: ActionState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MoveUnpackedNix {
|
impl MoveUnpackedNix {
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub async fn plan(source: PathBuf) -> Result<Self, HarmonicError> {
|
pub async fn plan(source: PathBuf) -> Result<Self, MoveUnpackedNixError> {
|
||||||
// Note: Do NOT try to check for the source/dest since the installer creates those
|
// Note: Do NOT try to check for the source/dest since the installer creates those
|
||||||
Ok(Self { source })
|
Ok(Self { source, action_state: ActionState::Planned })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl Actionable for ActionState<MoveUnpackedNix> {
|
impl Actionable for MoveUnpackedNix {
|
||||||
type Error = MoveUnpackedNixError;
|
type Error = MoveUnpackedNixError;
|
||||||
fn description(&self) -> Vec<ActionDescription> {
|
fn description(&self) -> Vec<ActionDescription> {
|
||||||
let Self { source } = &self;
|
let Self { source, action_state: _ } = &self;
|
||||||
vec![ActionDescription::new(
|
vec![ActionDescription::new(
|
||||||
format!("Move the downloaded Nix into `/nix`"),
|
format!("Move the downloaded Nix into `/nix`"),
|
||||||
vec![format!(
|
vec![format!(
|
||||||
|
@ -35,7 +36,7 @@ impl Actionable for ActionState<MoveUnpackedNix> {
|
||||||
|
|
||||||
#[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 { source } = self;
|
let Self { source, action_state } = self;
|
||||||
|
|
||||||
// TODO(@Hoverbear): I would like to make this less awful
|
// TODO(@Hoverbear): I would like to make this less awful
|
||||||
let found_nix_paths =
|
let found_nix_paths =
|
||||||
|
@ -51,8 +52,9 @@ impl Actionable for ActionState<MoveUnpackedNix> {
|
||||||
let dest = Path::new("/nix/store");
|
let dest = Path::new("/nix/store");
|
||||||
tokio::fs::rename(src.clone(), dest)
|
tokio::fs::rename(src.clone(), dest)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| HarmonicError::Rename(src, dest.to_owned(), e))?;
|
.map_err(|e| MoveUnpackedNixError::Rename(src, dest.to_owned(), e))?;
|
||||||
|
|
||||||
|
*action_state = ActionState::Completed;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,17 +67,18 @@ impl Actionable for ActionState<MoveUnpackedNix> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ActionState<MoveUnpackedNix>> for ActionState<Action> {
|
impl From<MoveUnpackedNix> for Action {
|
||||||
fn from(v: ActionState<MoveUnpackedNix>) -> Self {
|
fn from(v: MoveUnpackedNix) -> Self {
|
||||||
match v {
|
Action::MoveUnpackedNix(v)
|
||||||
ActionState::Completed(_) => ActionState::Completed(Action::MoveUnpackedNix(v)),
|
|
||||||
ActionState::Planned(_) => ActionState::Planned(Action::MoveUnpackedNix(v)),
|
|
||||||
ActionState::Reverted(_) => ActionState::Reverted(Action::MoveUnpackedNix(v)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error, Serialize)]
|
#[derive(Debug, thiserror::Error, Serialize)]
|
||||||
pub enum MoveUnpackedNixError {
|
pub enum MoveUnpackedNixError {
|
||||||
|
#[error("Glob pattern error")]
|
||||||
|
GlobPatternError(#[from] #[source] #[serde(serialize_with = "crate::serialize_error_to_display")] glob::PatternError),
|
||||||
|
#[error("Glob globbing error")]
|
||||||
|
GlobGlobError(#[from] #[source] #[serde(serialize_with = "crate::serialize_error_to_display")] glob::GlobError),
|
||||||
|
#[error("Rename `{0}` to `{1}`")]
|
||||||
|
Rename(std::path::PathBuf, std::path::PathBuf, #[source] #[serde(serialize_with = "crate::serialize_error_to_display")] std::io::Error),
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ use serde::Serialize;
|
||||||
|
|
||||||
use crate::HarmonicError;
|
use crate::HarmonicError;
|
||||||
|
|
||||||
use crate::actions::{ActionDescription, Actionable, ActionState, Action};
|
use crate::actions::{ActionDescription, Actionable, ActionState, Action, ActionError};
|
||||||
|
|
||||||
use super::{CreateFile, CreateFileError};
|
use super::{CreateFile, CreateFileError};
|
||||||
|
|
||||||
|
@ -13,11 +13,12 @@ const NIX_CHANNELS_PATH: &str = "/root/.nix-channels";
|
||||||
pub struct PlaceChannelConfiguration {
|
pub struct PlaceChannelConfiguration {
|
||||||
channels: Vec<(String, Url)>,
|
channels: Vec<(String, Url)>,
|
||||||
create_file: CreateFile,
|
create_file: CreateFile,
|
||||||
|
action_state: ActionState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlaceChannelConfiguration {
|
impl PlaceChannelConfiguration {
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub async fn plan(channels: Vec<(String, Url)>, force: bool) -> Result<Self, HarmonicError> {
|
pub async fn plan(channels: Vec<(String, Url)>, force: bool) -> Result<Self, PlaceChannelConfigurationError> {
|
||||||
let buf = channels
|
let buf = channels
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(name, url)| format!("{} {}", url, name))
|
.map(|(name, url)| format!("{} {}", url, name))
|
||||||
|
@ -28,17 +29,19 @@ impl PlaceChannelConfiguration {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
create_file,
|
create_file,
|
||||||
channels,
|
channels,
|
||||||
|
action_state: ActionState::Planned,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl Actionable for ActionState<PlaceChannelConfiguration> {
|
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: _,
|
||||||
} = self;
|
} = self;
|
||||||
vec![ActionDescription::new(
|
vec![ActionDescription::new(
|
||||||
"Place a channel configuration".to_string(),
|
"Place a channel configuration".to_string(),
|
||||||
|
@ -51,10 +54,12 @@ impl Actionable for ActionState<PlaceChannelConfiguration> {
|
||||||
let Self {
|
let Self {
|
||||||
create_file,
|
create_file,
|
||||||
channels,
|
channels,
|
||||||
|
action_state,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
create_file.execute().await?;
|
create_file.execute().await?;
|
||||||
|
|
||||||
|
*action_state = ActionState::Completed;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,18 +72,15 @@ impl Actionable for ActionState<PlaceChannelConfiguration> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ActionState<PlaceChannelConfiguration>> for ActionState<Action> {
|
impl From<PlaceChannelConfiguration> for Action {
|
||||||
fn from(v: ActionState<PlaceChannelConfiguration>) -> Self {
|
fn from(v: PlaceChannelConfiguration) -> Self {
|
||||||
match v {
|
Action::PlaceChannelConfiguration(v)
|
||||||
ActionState::Completed(_) => ActionState::Completed(Action::PlaceChannelConfiguration(v)),
|
|
||||||
ActionState::Planned(_) => ActionState::Planned(Action::PlaceChannelConfiguration(v)),
|
|
||||||
ActionState::Reverted(_) => ActionState::Reverted(Action::PlaceChannelConfiguration(v)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error, Serialize)]
|
#[derive(Debug, thiserror::Error, Serialize)]
|
||||||
pub enum PlaceChannelConfigurationError {
|
pub enum PlaceChannelConfigurationError {
|
||||||
|
#[error(transparent)]
|
||||||
|
CreateFile(#[from] CreateFileError),
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ use serde::Serialize;
|
||||||
|
|
||||||
use crate::HarmonicError;
|
use crate::HarmonicError;
|
||||||
|
|
||||||
use crate::actions::{ActionDescription, Actionable, ActionState, Action};
|
use crate::actions::{ActionDescription, Actionable, ActionState, Action, ActionError};
|
||||||
|
|
||||||
use super::{CreateFile, CreateFileError, CreateDirectory, CreateDirectoryError};
|
use super::{CreateFile, CreateFileError, CreateDirectory, CreateDirectoryError};
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ const NIX_CONF: &str = "/etc/nix/nix.conf";
|
||||||
pub struct PlaceNixConfiguration {
|
pub struct PlaceNixConfiguration {
|
||||||
create_directory: CreateDirectory,
|
create_directory: CreateDirectory,
|
||||||
create_file: CreateFile,
|
create_file: CreateFile,
|
||||||
|
action_state: ActionState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlaceNixConfiguration {
|
impl PlaceNixConfiguration {
|
||||||
|
@ -21,7 +22,7 @@ impl PlaceNixConfiguration {
|
||||||
nix_build_group_name: String,
|
nix_build_group_name: String,
|
||||||
extra_conf: Option<String>,
|
extra_conf: Option<String>,
|
||||||
force: bool,
|
force: bool,
|
||||||
) -> Result<Self, HarmonicError> {
|
) -> Result<Self, PlaceNixConfigurationError> {
|
||||||
let buf = format!(
|
let buf = format!(
|
||||||
"\
|
"\
|
||||||
{extra_conf}\n\
|
{extra_conf}\n\
|
||||||
|
@ -32,12 +33,12 @@ impl PlaceNixConfiguration {
|
||||||
let create_directory = CreateDirectory::plan(NIX_CONF_FOLDER, "root".into(), "root".into(), 0o0755, force).await?;
|
let create_directory = CreateDirectory::plan(NIX_CONF_FOLDER, "root".into(), "root".into(), 0o0755, force).await?;
|
||||||
let create_file =
|
let create_file =
|
||||||
CreateFile::plan(NIX_CONF, "root".into(), "root".into(), 0o0664, buf, force).await?;
|
CreateFile::plan(NIX_CONF, "root".into(), "root".into(), 0o0664, buf, force).await?;
|
||||||
Ok(Self { create_directory, create_file })
|
Ok(Self { create_directory, create_file, action_state: ActionState::Planned })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl Actionable for ActionState<PlaceNixConfiguration> {
|
impl Actionable for PlaceNixConfiguration {
|
||||||
type Error = PlaceNixConfigurationError;
|
type Error = PlaceNixConfigurationError;
|
||||||
|
|
||||||
fn description(&self) -> Vec<ActionDescription> {
|
fn description(&self) -> Vec<ActionDescription> {
|
||||||
|
@ -49,11 +50,12 @@ impl Actionable for ActionState<PlaceNixConfiguration> {
|
||||||
|
|
||||||
#[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 { create_file, create_directory } = self;
|
let Self { create_file, create_directory, action_state } = self;
|
||||||
|
|
||||||
create_directory.execute().await?;
|
create_directory.execute().await?;
|
||||||
create_file.execute().await?;
|
create_file.execute().await?;
|
||||||
|
|
||||||
|
*action_state = ActionState::Completed;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,18 +68,17 @@ impl Actionable for ActionState<PlaceNixConfiguration> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ActionState<PlaceNixConfiguration>> for ActionState<Action> {
|
impl From<PlaceNixConfiguration> for Action {
|
||||||
fn from(v: ActionState<PlaceNixConfiguration>) -> Self {
|
fn from(v: PlaceNixConfiguration) -> Self {
|
||||||
match v {
|
Action::PlaceNixConfiguration(v)
|
||||||
ActionState::Completed(_) => ActionState::Completed(Action::PlaceNixConfiguration(v)),
|
|
||||||
ActionState::Planned(_) => ActionState::Planned(Action::PlaceNixConfiguration(v)),
|
|
||||||
ActionState::Reverted(_) => ActionState::Reverted(Action::PlaceNixConfiguration(v)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error, Serialize)]
|
#[derive(Debug, thiserror::Error, Serialize)]
|
||||||
pub enum PlaceNixConfigurationError {
|
pub enum PlaceNixConfigurationError {
|
||||||
|
#[error(transparent)]
|
||||||
|
CreateFile(#[from] CreateFileError),
|
||||||
|
#[error(transparent)]
|
||||||
|
CreateDirectory(#[from] CreateDirectoryError),
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{execute_command, HarmonicError, actions::{ActionState, Action}};
|
use crate::{execute_command, actions::{ActionState, Action, ActionError}};
|
||||||
|
|
||||||
use glob::glob;
|
use glob::glob;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
@ -13,13 +13,13 @@ pub struct SetupDefaultProfile {
|
||||||
|
|
||||||
impl SetupDefaultProfile {
|
impl SetupDefaultProfile {
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub async fn plan(channels: Vec<String>) -> Result<Self, HarmonicError> {
|
pub async fn plan(channels: Vec<String>) -> Result<Self, SetupDefaultProfileError> {
|
||||||
Ok(Self { channels })
|
Ok(Self { channels })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl Actionable for ActionState<SetupDefaultProfile> {
|
impl Actionable for SetupDefaultProfile {
|
||||||
type Error = SetupDefaultProfileError;
|
type Error = SetupDefaultProfileError;
|
||||||
fn description(&self) -> Vec<ActionDescription> {
|
fn description(&self) -> Vec<ActionDescription> {
|
||||||
vec![ActionDescription::new(
|
vec![ActionDescription::new(
|
||||||
|
@ -36,7 +36,7 @@ impl Actionable for ActionState<SetupDefaultProfile> {
|
||||||
// Find an `nix` package
|
// Find an `nix` package
|
||||||
let nix_pkg_glob = "/nix/store/*-nix-*";
|
let nix_pkg_glob = "/nix/store/*-nix-*";
|
||||||
let mut found_nix_pkg = None;
|
let mut found_nix_pkg = None;
|
||||||
for entry in glob(nix_pkg_glob).map_err(HarmonicError::GlobPatternError)? {
|
for entry in glob(nix_pkg_glob).map_err(Self::Error::GlobPatternError)? {
|
||||||
match entry {
|
match entry {
|
||||||
Ok(path) => {
|
Ok(path) => {
|
||||||
// TODO(@Hoverbear): Should probably ensure is unique
|
// TODO(@Hoverbear): Should probably ensure is unique
|
||||||
|
@ -49,7 +49,7 @@ impl Actionable for ActionState<SetupDefaultProfile> {
|
||||||
let nix_pkg = if let Some(nix_pkg) = found_nix_pkg {
|
let nix_pkg = if let Some(nix_pkg) = found_nix_pkg {
|
||||||
nix_pkg
|
nix_pkg
|
||||||
} else {
|
} else {
|
||||||
return Err(HarmonicError::NoNssCacert); // TODO(@hoverbear): Fix this error
|
return Err(Self::Error::NoNssCacert); // TODO(@hoverbear): Fix this error
|
||||||
};
|
};
|
||||||
|
|
||||||
// Install `nix` itself into the store
|
// Install `nix` itself into the store
|
||||||
|
@ -57,14 +57,13 @@ impl Actionable for ActionState<SetupDefaultProfile> {
|
||||||
Command::new(nix_pkg.join("bin/nix-env"))
|
Command::new(nix_pkg.join("bin/nix-env"))
|
||||||
.arg("-i")
|
.arg("-i")
|
||||||
.arg(&nix_pkg),
|
.arg(&nix_pkg),
|
||||||
false,
|
|
||||||
)
|
)
|
||||||
.await?;
|
.await.map_err(SetupDefaultProfileError::Command)?;
|
||||||
|
|
||||||
// Find an `nss-cacert` package, add it too.
|
// Find an `nss-cacert` package, add it too.
|
||||||
let nss_ca_cert_pkg_glob = "/nix/store/*-nss-cacert-*";
|
let nss_ca_cert_pkg_glob = "/nix/store/*-nss-cacert-*";
|
||||||
let mut found_nss_ca_cert_pkg = None;
|
let mut found_nss_ca_cert_pkg = None;
|
||||||
for entry in glob(nss_ca_cert_pkg_glob).map_err(HarmonicError::GlobPatternError)? {
|
for entry in glob(nss_ca_cert_pkg_glob).map_err(Self::Error::GlobPatternError)? {
|
||||||
match entry {
|
match entry {
|
||||||
Ok(path) => {
|
Ok(path) => {
|
||||||
// TODO(@Hoverbear): Should probably ensure is unique
|
// TODO(@Hoverbear): Should probably ensure is unique
|
||||||
|
@ -77,17 +76,15 @@ impl Actionable for ActionState<SetupDefaultProfile> {
|
||||||
let nss_ca_cert_pkg = if let Some(nss_ca_cert_pkg) = found_nss_ca_cert_pkg {
|
let nss_ca_cert_pkg = if let Some(nss_ca_cert_pkg) = found_nss_ca_cert_pkg {
|
||||||
nss_ca_cert_pkg
|
nss_ca_cert_pkg
|
||||||
} else {
|
} else {
|
||||||
return Err(HarmonicError::NoNssCacert);
|
return Err(Self::Error::NoNssCacert);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Install `nss-cacert` into the store
|
// Install `nss-cacert` into the store
|
||||||
execute_command(
|
execute_command(
|
||||||
Command::new(nix_pkg.join("bin/nix-env"))
|
Command::new(nix_pkg.join("bin/nix-env"))
|
||||||
.arg("-i")
|
.arg("-i")
|
||||||
.arg(&nss_ca_cert_pkg),
|
|
||||||
false,
|
|
||||||
)
|
)
|
||||||
.await?;
|
.await.map_err(SetupDefaultProfileError::Command)?;
|
||||||
|
|
||||||
if !channels.is_empty() {
|
if !channels.is_empty() {
|
||||||
let mut command = Command::new(nix_pkg.join("bin/nix-channel"));
|
let mut command = Command::new(nix_pkg.join("bin/nix-channel"));
|
||||||
|
@ -100,16 +97,7 @@ impl Actionable for ActionState<SetupDefaultProfile> {
|
||||||
"/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt",
|
"/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt",
|
||||||
);
|
);
|
||||||
|
|
||||||
let command_str = format!("{:?}", command.as_std());
|
execute_command(&mut command).await.map_err(SetupDefaultProfileError::Command)?;
|
||||||
let status = command
|
|
||||||
.status()
|
|
||||||
.await
|
|
||||||
.map_err(|e| HarmonicError::CommandFailedExec(command_str.clone(), e))?;
|
|
||||||
|
|
||||||
match status.success() {
|
|
||||||
true => (),
|
|
||||||
false => return Err(HarmonicError::CommandFailedStatus(command_str)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -123,18 +111,21 @@ impl Actionable for ActionState<SetupDefaultProfile> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ActionState<SetupDefaultProfile>> for ActionState<Action> {
|
impl From<SetupDefaultProfile> for Action {
|
||||||
fn from(v: ActionState<SetupDefaultProfile>) -> Self {
|
fn from(v: SetupDefaultProfile) -> Self {
|
||||||
match v {
|
Action::SetupDefaultProfile(v)
|
||||||
ActionState::Completed(_) => ActionState::Completed(Action::SetupDefaultProfile(v)),
|
|
||||||
ActionState::Planned(_) => ActionState::Planned(Action::SetupDefaultProfile(v)),
|
|
||||||
ActionState::Reverted(_) => ActionState::Reverted(Action::SetupDefaultProfile(v)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error, Serialize)]
|
#[derive(Debug, thiserror::Error, Serialize)]
|
||||||
pub enum SetupDefaultProfileError {
|
pub enum SetupDefaultProfileError {
|
||||||
|
#[error("Glob pattern error")]
|
||||||
|
GlobPatternError(#[from] #[source] #[serde(serialize_with = "crate::serialize_error_to_display")] glob::PatternError),
|
||||||
|
#[error("Glob globbing error")]
|
||||||
|
GlobGlobError(#[from] #[source] #[serde(serialize_with = "crate::serialize_error_to_display")] glob::GlobError),
|
||||||
|
#[error("Unarchived Nix store did not appear to include a `nss-cacert` location")]
|
||||||
|
NoNssCacert,
|
||||||
|
#[error("Failed to execute command")]
|
||||||
|
Command(#[source] #[serde(serialize_with = "crate::serialize_error_to_display")] std::io::Error)
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,21 +9,22 @@ use crate::actions::{ActionDescription, Actionable, ActionState, Action, ActionE
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
pub struct StartSystemdUnit {
|
pub struct StartSystemdUnit {
|
||||||
unit: String,
|
unit: String,
|
||||||
|
action_state: ActionState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StartSystemdUnit {
|
impl StartSystemdUnit {
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub async fn plan(unit: String) -> Result<ActionState<Self>, StartSystemdUnitError> {
|
pub async fn plan(unit: String) -> Result<Self, StartSystemdUnitError> {
|
||||||
Ok(ActionState::Planned(Self { unit }))
|
Ok(Self { unit, action_state: ActionState::Planned })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl Actionable for ActionState<StartSystemdUnit> {
|
impl Actionable for StartSystemdUnit {
|
||||||
type Error = StartSystemdUnitError;
|
type Error = StartSystemdUnitError;
|
||||||
fn description(&self) -> Vec<ActionDescription> {
|
fn description(&self) -> Vec<ActionDescription> {
|
||||||
match self {
|
match self.action_state {
|
||||||
ActionState::Planned(v) => vec![
|
ActionState::Planned => vec![
|
||||||
ActionDescription::new(
|
ActionDescription::new(
|
||||||
"Start the systemd Nix service and socket".to_string(),
|
"Start the systemd Nix service and socket".to_string(),
|
||||||
vec![
|
vec![
|
||||||
|
@ -31,7 +32,7 @@ impl Actionable for ActionState<StartSystemdUnit> {
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
ActionState::Completed(_) => vec![
|
ActionState::Completed => vec![
|
||||||
ActionDescription::new(
|
ActionDescription::new(
|
||||||
"Stop the systemd Nix service and socket".to_string(),
|
"Stop the systemd Nix service and socket".to_string(),
|
||||||
vec![
|
vec![
|
||||||
|
@ -39,7 +40,7 @@ impl Actionable for ActionState<StartSystemdUnit> {
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
ActionState::Reverted(_) => vec![
|
ActionState::Reverted => vec![
|
||||||
ActionDescription::new(
|
ActionDescription::new(
|
||||||
"Stopped the systemd Nix service and socket".to_string(),
|
"Stopped the systemd Nix service and socket".to_string(),
|
||||||
vec![
|
vec![
|
||||||
|
@ -51,12 +52,9 @@ impl Actionable for ActionState<StartSystemdUnit> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn execute(&mut self) -> Result<(), ActionError> {
|
async fn execute(&mut self) -> Result<(), Self::Error> {
|
||||||
let StartSystemdUnit { unit } = match self {
|
let Self { unit, action_state } = self;
|
||||||
ActionState::Completed(_) => return Err(ActionError::AlreadyExecuted(self.clone().into())),
|
|
||||||
ActionState::Reverted(_) => return Err(ActionError::AlreadyReverted(self.clone().into())),
|
|
||||||
ActionState::Planned(v) => v,
|
|
||||||
};
|
|
||||||
// TODO(@Hoverbear): Handle proxy vars
|
// TODO(@Hoverbear): Handle proxy vars
|
||||||
execute_command(
|
execute_command(
|
||||||
Command::new("systemctl")
|
Command::new("systemctl")
|
||||||
|
@ -66,30 +64,26 @@ impl Actionable for ActionState<StartSystemdUnit> {
|
||||||
)
|
)
|
||||||
.await.map_err(StartSystemdUnitError::Command)?;
|
.await.map_err(StartSystemdUnitError::Command)?;
|
||||||
|
|
||||||
|
*action_state = ActionState::Completed;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn revert(&mut self) -> Result<(), ActionError> {
|
async fn revert(&mut self) -> Result<(), Self::Error> {
|
||||||
todo!();
|
todo!();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ActionState<StartSystemdUnit>> for ActionState<Action> {
|
impl From<StartSystemdUnit> for Action {
|
||||||
fn from(v: ActionState<StartSystemdUnit>) -> Self {
|
fn from(v: StartSystemdUnit) -> Self {
|
||||||
match v {
|
Action::StartSystemdUnit(v)
|
||||||
ActionState::Completed(_) => ActionState::Completed(Action::StartSystemdUnit(v)),
|
|
||||||
ActionState::Planned(_) => ActionState::Planned(Action::StartSystemdUnit(v)),
|
|
||||||
ActionState::Reverted(_) => ActionState::Reverted(Action::StartSystemdUnit(v)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error, Serialize)]
|
#[derive(Debug, thiserror::Error, Serialize)]
|
||||||
pub enum StartSystemdUnitError {
|
pub enum StartSystemdUnitError {
|
||||||
#[error("Failed to execute command")]
|
#[error("Failed to execute command")]
|
||||||
#[serde(serialize_with = "crate::serialize_std_io_error_to_display")]
|
Command(#[source] #[serde(serialize_with = "crate::serialize_error_to_display")] std::io::Error)
|
||||||
Command(#[source] std::io::Error)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ use crate::actions::{
|
||||||
ConfigureNixDaemonService, ConfigureNixDaemonServiceError, PlaceNixConfiguration,
|
ConfigureNixDaemonService, ConfigureNixDaemonServiceError, PlaceNixConfiguration,
|
||||||
PlaceNixConfigurationError, SetupDefaultProfile, SetupDefaultProfileError, PlaceChannelConfiguration, PlaceChannelConfigurationError,
|
PlaceNixConfigurationError, SetupDefaultProfile, SetupDefaultProfileError, PlaceChannelConfiguration, PlaceChannelConfigurationError,
|
||||||
},
|
},
|
||||||
meta::{ConfigureShellProfile, ConfigureShellProfileError}, ActionState, Action,
|
meta::{ConfigureShellProfile, ConfigureShellProfileError}, ActionState, Action, ActionError,
|
||||||
};
|
};
|
||||||
use crate::{HarmonicError, InstallSettings};
|
use crate::{HarmonicError, InstallSettings};
|
||||||
|
|
||||||
|
@ -18,11 +18,12 @@ pub struct ConfigureNix {
|
||||||
place_channel_configuration: PlaceChannelConfiguration,
|
place_channel_configuration: PlaceChannelConfiguration,
|
||||||
place_nix_configuration: PlaceNixConfiguration,
|
place_nix_configuration: PlaceNixConfiguration,
|
||||||
configure_nix_daemon_service: ConfigureNixDaemonService,
|
configure_nix_daemon_service: ConfigureNixDaemonService,
|
||||||
|
action_state: ActionState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConfigureNix {
|
impl ConfigureNix {
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub async fn plan(settings: InstallSettings) -> Result<ActionState<Self>, ConfigureNixError> {
|
pub async fn plan(settings: InstallSettings) -> Result<Self, ConfigureNixError> {
|
||||||
let channels = settings
|
let channels = settings
|
||||||
.channels
|
.channels
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -48,12 +49,13 @@ impl ConfigureNix {
|
||||||
setup_default_profile,
|
setup_default_profile,
|
||||||
configure_nix_daemon_service,
|
configure_nix_daemon_service,
|
||||||
configure_shell_profile,
|
configure_shell_profile,
|
||||||
|
action_state: ActionState::Planned,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl Actionable for ActionState<ConfigureNix> {
|
impl Actionable for ConfigureNix {
|
||||||
type Error = ConfigureNixError;
|
type Error = ConfigureNixError;
|
||||||
fn description(&self) -> Vec<ActionDescription> {
|
fn description(&self) -> Vec<ActionDescription> {
|
||||||
let Self {
|
let Self {
|
||||||
|
@ -62,6 +64,7 @@ impl Actionable for ActionState<ConfigureNix> {
|
||||||
place_nix_configuration,
|
place_nix_configuration,
|
||||||
place_channel_configuration,
|
place_channel_configuration,
|
||||||
configure_shell_profile,
|
configure_shell_profile,
|
||||||
|
action_state: _,
|
||||||
} = &self;
|
} = &self;
|
||||||
|
|
||||||
let mut buf = setup_default_profile.description();
|
let mut buf = setup_default_profile.description();
|
||||||
|
@ -83,6 +86,7 @@ impl Actionable for ActionState<ConfigureNix> {
|
||||||
place_nix_configuration,
|
place_nix_configuration,
|
||||||
place_channel_configuration,
|
place_channel_configuration,
|
||||||
configure_shell_profile,
|
configure_shell_profile,
|
||||||
|
action_state,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
let (
|
let (
|
||||||
|
@ -92,29 +96,24 @@ impl Actionable for ActionState<ConfigureNix> {
|
||||||
configure_shell_profile,
|
configure_shell_profile,
|
||||||
) = if let Some(configure_shell_profile) = configure_shell_profile {
|
) = if let Some(configure_shell_profile) = configure_shell_profile {
|
||||||
let (a, b, c, d) = tokio::try_join!(
|
let (a, b, c, d) = tokio::try_join!(
|
||||||
setup_default_profile.execute(),
|
async move { setup_default_profile.execute().await.map_err(|e| ConfigureNixError::from(e)) },
|
||||||
place_nix_configuration.execute(),
|
async move { place_nix_configuration.execute().await.map_err(|e| ConfigureNixError::from(e)) },
|
||||||
place_channel_configuration.execute(),
|
async move { place_channel_configuration.execute().await.map_err(|e| ConfigureNixError::from(e)) },
|
||||||
configure_shell_profile.execute(),
|
async move { configure_shell_profile.execute().await.map_err(|e| ConfigureNixError::from(e)) },
|
||||||
)?;
|
)?;
|
||||||
(a, b, c, Some(d))
|
(a, b, c, Some(d))
|
||||||
} else {
|
} else {
|
||||||
let (a, b, c) = tokio::try_join!(
|
let (a, b, c) = tokio::try_join!(
|
||||||
setup_default_profile.execute(),
|
async move { setup_default_profile.execute().await.map_err(|e| ConfigureNixError::from(e)) },
|
||||||
place_nix_configuration.execute(),
|
async move { place_nix_configuration.execute().await.map_err(|e| ConfigureNixError::from(e)) },
|
||||||
place_channel_configuration.execute(),
|
async move { place_channel_configuration.execute().await.map_err(|e| ConfigureNixError::from(e)) },
|
||||||
)?;
|
)?;
|
||||||
(a, b, c, None)
|
(a, b, c, None)
|
||||||
};
|
};
|
||||||
let configure_nix_daemon_service = configure_nix_daemon_service.execute().await?;
|
let configure_nix_daemon_service = configure_nix_daemon_service.execute().await?;
|
||||||
|
|
||||||
Ok(Self::Receipt {
|
*action_state = ActionState::Completed;
|
||||||
setup_default_profile,
|
Ok(())
|
||||||
configure_nix_daemon_service,
|
|
||||||
place_nix_configuration,
|
|
||||||
place_channel_configuration,
|
|
||||||
configure_shell_profile,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -126,17 +125,22 @@ impl Actionable for ActionState<ConfigureNix> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ActionState<ConfigureNix>> for ActionState<Action> {
|
impl From<ConfigureNix> for Action {
|
||||||
fn from(v: ActionState<ConfigureNix>) -> Self {
|
fn from(v: ConfigureNix) -> Self {
|
||||||
match v {
|
Action::ConfigureNix(v)
|
||||||
ActionState::Completed(_) => ActionState::Completed(Action::ConfigureNix(v)),
|
|
||||||
ActionState::Planned(_) => ActionState::Planned(Action::ConfigureNix(v)),
|
|
||||||
ActionState::Reverted(_) => ActionState::Reverted(Action::ConfigureNix(v)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error, Serialize)]
|
#[derive(Debug, thiserror::Error, Serialize)]
|
||||||
pub enum ConfigureNixError {
|
pub enum ConfigureNixError {
|
||||||
|
#[error(transparent)]
|
||||||
|
SetupDefaultProfile(#[from] SetupDefaultProfileError),
|
||||||
|
#[error(transparent)]
|
||||||
|
PlaceNixConfiguration(#[from] PlaceNixConfigurationError),
|
||||||
|
#[error(transparent)]
|
||||||
|
PlaceChannelConfiguration(#[from] PlaceChannelConfigurationError),
|
||||||
|
#[error(transparent)]
|
||||||
|
ConfigureNixDaemonService(#[from] ConfigureNixDaemonServiceError),
|
||||||
|
#[error(transparent)]
|
||||||
|
ConfigureShellProfile(#[from] ConfigureShellProfileError),
|
||||||
}
|
}
|
|
@ -1,12 +1,12 @@
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use tokio::task::JoinSet;
|
use tokio::task::{JoinSet, JoinError};
|
||||||
|
|
||||||
use crate::HarmonicError;
|
use crate::HarmonicError;
|
||||||
|
|
||||||
use crate::actions::base::{CreateOrAppendFile, CreateOrAppendFileError};
|
use crate::actions::base::{CreateOrAppendFile, CreateOrAppendFileError};
|
||||||
use crate::actions::{ActionDescription, Actionable, ActionState, Action};
|
use crate::actions::{ActionDescription, Actionable, ActionState, Action, ActionError};
|
||||||
|
|
||||||
const PROFILE_TARGETS: &[&str] = &[
|
const PROFILE_TARGETS: &[&str] = &[
|
||||||
"/etc/bashrc",
|
"/etc/bashrc",
|
||||||
|
@ -21,11 +21,12 @@ const PROFILE_NIX_FILE: &str = "/nix/var/nix/profiles/default/etc/profile.d/nix-
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
pub struct ConfigureShellProfile {
|
pub struct ConfigureShellProfile {
|
||||||
create_or_append_files: Vec<CreateOrAppendFile>,
|
create_or_append_files: Vec<CreateOrAppendFile>,
|
||||||
|
action_state: ActionState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConfigureShellProfile {
|
impl ConfigureShellProfile {
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub async fn plan() -> Result<ActionState<Self>, ConfigureShellProfileError> {
|
pub async fn plan() -> Result<Self, ConfigureShellProfileError> {
|
||||||
let mut create_or_append_files = Vec::default();
|
let mut create_or_append_files = Vec::default();
|
||||||
for profile_target in PROFILE_TARGETS {
|
for profile_target in PROFILE_TARGETS {
|
||||||
let path = Path::new(profile_target);
|
let path = Path::new(profile_target);
|
||||||
|
@ -50,12 +51,13 @@ impl ConfigureShellProfile {
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
create_or_append_files,
|
create_or_append_files,
|
||||||
|
action_state: ActionState::Planned,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl Actionable for ActionState<ConfigureShellProfile> {
|
impl Actionable for ConfigureShellProfile {
|
||||||
type Error = ConfigureShellProfileError;
|
type Error = ConfigureShellProfileError;
|
||||||
fn description(&self) -> Vec<ActionDescription> {
|
fn description(&self) -> Vec<ActionDescription> {
|
||||||
vec![ActionDescription::new(
|
vec![ActionDescription::new(
|
||||||
|
@ -68,34 +70,35 @@ impl Actionable for ActionState<ConfigureShellProfile> {
|
||||||
async fn execute(&mut self) -> Result<(), Self::Error> {
|
async fn execute(&mut self) -> Result<(), Self::Error> {
|
||||||
let Self {
|
let Self {
|
||||||
create_or_append_files,
|
create_or_append_files,
|
||||||
|
action_state,
|
||||||
} = self;
|
} = self;
|
||||||
tracing::info!("Configuring shell profile");
|
tracing::info!("Configuring shell profile");
|
||||||
|
|
||||||
let mut set = JoinSet::new();
|
let mut set = JoinSet::new();
|
||||||
|
|
||||||
let mut successes = Vec::with_capacity(create_or_append_files.len());
|
|
||||||
let mut errors = Vec::default();
|
let mut errors = Vec::default();
|
||||||
|
|
||||||
for create_or_append_file in create_or_append_files {
|
for (idx, create_or_append_file) in create_or_append_files.iter().enumerate() {
|
||||||
let _abort_handle = set.spawn(async move { create_or_append_file.execute().await });
|
let mut create_or_append_file_clone = create_or_append_file.clone();
|
||||||
|
let _abort_handle = set.spawn(async move { create_or_append_file_clone.execute().await?; Result::<_, CreateOrAppendFileError>::Ok((idx, create_or_append_file_clone)) });
|
||||||
}
|
}
|
||||||
|
|
||||||
while let Some(result) = set.join_next().await {
|
while let Some(result) = set.join_next().await {
|
||||||
match result {
|
match result {
|
||||||
Ok(Ok(())) => (),
|
Ok(Ok((idx, create_or_append_file))) => create_or_append_files[idx] = create_or_append_file,
|
||||||
Ok(Err(e)) => errors.push(e),
|
Ok(Err(e)) => errors.push(e),
|
||||||
Err(e) => errors.push(e.into()),
|
Err(e) => return Err(e.into()),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if !errors.is_empty() {
|
if !errors.is_empty() {
|
||||||
if errors.len() == 1 {
|
if errors.len() == 1 {
|
||||||
return Err(errors.into_iter().next().unwrap());
|
return Err(errors.into_iter().next().unwrap().into());
|
||||||
} else {
|
} else {
|
||||||
return Err(HarmonicError::Multiple(errors));
|
return Err(ConfigureShellProfileError::MultipleCreateOrAppendFile(errors));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*action_state = ActionState::Completed;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,17 +111,18 @@ impl Actionable for ActionState<ConfigureShellProfile> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ActionState<ConfigureShellProfile>> for ActionState<Action> {
|
impl From<ConfigureShellProfile> for Action {
|
||||||
fn from(v: ActionState<ConfigureShellProfile>) -> Self {
|
fn from(v: ConfigureShellProfile) -> Self {
|
||||||
match v {
|
Action::ConfigureShellProfile(v)
|
||||||
ActionState::Completed(_) => ActionState::Completed(Action::ConfigureShellProfile(v)),
|
|
||||||
ActionState::Planned(_) => ActionState::Planned(Action::ConfigureShellProfile(v)),
|
|
||||||
ActionState::Reverted(_) => ActionState::Reverted(Action::ConfigureShellProfile(v)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error, Serialize)]
|
#[derive(Debug, thiserror::Error, Serialize)]
|
||||||
pub enum ConfigureShellProfileError {
|
pub enum ConfigureShellProfileError {
|
||||||
|
#[error(transparent)]
|
||||||
|
CreateOrAppendFile(#[from] CreateOrAppendFileError),
|
||||||
|
#[error("Multiple errors: {}", .0.iter().map(|v| format!("{v}")).collect::<Vec<_>>().join(" & "))]
|
||||||
|
MultipleCreateOrAppendFile(Vec<CreateOrAppendFileError>),
|
||||||
|
#[error(transparent)]
|
||||||
|
Join(#[from] #[serde(serialize_with = "crate::serialize_error_to_display")] JoinError),
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ use serde::Serialize;
|
||||||
use crate::HarmonicError;
|
use crate::HarmonicError;
|
||||||
|
|
||||||
use crate::actions::base::{CreateDirectory, CreateDirectoryError};
|
use crate::actions::base::{CreateDirectory, CreateDirectoryError};
|
||||||
use crate::actions::{ActionDescription, Actionable, ActionState, Action};
|
use crate::actions::{ActionDescription, Actionable, ActionState, Action, ActionError};
|
||||||
|
|
||||||
const PATHS: &[&str] = &[
|
const PATHS: &[&str] = &[
|
||||||
"/nix",
|
"/nix",
|
||||||
|
@ -25,11 +25,12 @@ const PATHS: &[&str] = &[
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
pub struct CreateNixTree {
|
pub struct CreateNixTree {
|
||||||
create_directories: Vec<CreateDirectory>,
|
create_directories: Vec<CreateDirectory>,
|
||||||
|
action_state: ActionState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CreateNixTree {
|
impl CreateNixTree {
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub async fn plan(force: bool) -> Result<Self, HarmonicError> {
|
pub async fn plan(force: bool) -> Result<Self, CreateNixTreeError> {
|
||||||
let mut create_directories = Vec::default();
|
let mut create_directories = Vec::default();
|
||||||
for path in PATHS {
|
for path in PATHS {
|
||||||
// We use `create_dir` over `create_dir_all` to ensure we always set permissions right
|
// We use `create_dir` over `create_dir_all` to ensure we always set permissions right
|
||||||
|
@ -38,12 +39,12 @@ impl CreateNixTree {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Self { create_directories })
|
Ok(Self { create_directories, action_state: ActionState::Planned })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl Actionable for ActionState<CreateNixTree> {
|
impl Actionable for CreateNixTree {
|
||||||
type Error = CreateNixTreeError;
|
type Error = CreateNixTreeError;
|
||||||
fn description(&self) -> Vec<ActionDescription> {
|
fn description(&self) -> Vec<ActionDescription> {
|
||||||
vec![ActionDescription::new(
|
vec![ActionDescription::new(
|
||||||
|
@ -56,15 +57,15 @@ impl Actionable for ActionState<CreateNixTree> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn execute(&mut self) -> Result<(), HarmonicError> {
|
async fn execute(&mut self) -> Result<(), Self::Error> {
|
||||||
let Self { create_directories } = self;
|
let Self { create_directories, action_state } = self;
|
||||||
|
|
||||||
let mut successes = Vec::with_capacity(create_directories.len());
|
|
||||||
// Just do sequential since parallizing this will have little benefit
|
// Just do sequential since parallizing this will have little benefit
|
||||||
for create_directory in create_directories {
|
for create_directory in create_directories {
|
||||||
create_directory.execute().await?
|
create_directory.execute().await?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*action_state = ActionState::Completed;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,17 +78,14 @@ impl Actionable for ActionState<CreateNixTree> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ActionState<CreateNixTree>> for ActionState<Action> {
|
impl From<CreateNixTree> for Action {
|
||||||
fn from(v: ActionState<CreateNixTree>) -> Self {
|
fn from(v: CreateNixTree) -> Self {
|
||||||
match v {
|
Action::CreateNixTree(v)
|
||||||
ActionState::Completed(_) => ActionState::Completed(Action::CreateNixTree(v)),
|
|
||||||
ActionState::Planned(_) => ActionState::Planned(Action::CreateNixTree(v)),
|
|
||||||
ActionState::Reverted(_) => ActionState::Reverted(Action::CreateNixTree(v)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error, Serialize)]
|
#[derive(Debug, thiserror::Error, Serialize)]
|
||||||
pub enum CreateNixTreeError {
|
pub enum CreateNixTreeError {
|
||||||
|
#[error(transparent)]
|
||||||
|
CreateDirectory(#[from] CreateDirectoryError),
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use tokio::task::JoinSet;
|
use tokio::task::{JoinSet, JoinError};
|
||||||
|
|
||||||
use crate::{HarmonicError, InstallSettings};
|
use crate::{HarmonicError, InstallSettings};
|
||||||
|
|
||||||
|
@ -15,11 +15,12 @@ pub struct CreateUsersAndGroup {
|
||||||
nix_build_user_id_base: usize,
|
nix_build_user_id_base: usize,
|
||||||
create_group: CreateGroup,
|
create_group: CreateGroup,
|
||||||
create_users: Vec<CreateUser>,
|
create_users: Vec<CreateUser>,
|
||||||
|
action_state: ActionState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CreateUsersAndGroup {
|
impl CreateUsersAndGroup {
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub async fn plan(settings: InstallSettings) -> Result<Self, HarmonicError> {
|
pub async fn plan(settings: InstallSettings) -> Result<Self, CreateUsersAndGroupError> {
|
||||||
// TODO(@hoverbear): CHeck if it exist, error if so
|
// TODO(@hoverbear): CHeck if it exist, error if so
|
||||||
let create_group = CreateGroup::plan(
|
let create_group = CreateGroup::plan(
|
||||||
settings.nix_build_group_name.clone(),
|
settings.nix_build_group_name.clone(),
|
||||||
|
@ -43,12 +44,13 @@ impl CreateUsersAndGroup {
|
||||||
nix_build_user_id_base: settings.nix_build_user_id_base,
|
nix_build_user_id_base: settings.nix_build_user_id_base,
|
||||||
create_group,
|
create_group,
|
||||||
create_users,
|
create_users,
|
||||||
|
action_state: ActionState::Planned,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl Actionable for ActionState<CreateUsersAndGroup> {
|
impl Actionable for CreateUsersAndGroup {
|
||||||
type Error = CreateUsersAndGroupError;
|
type Error = CreateUsersAndGroupError;
|
||||||
fn description(&self) -> Vec<ActionDescription> {
|
fn description(&self) -> Vec<ActionDescription> {
|
||||||
let Self {
|
let Self {
|
||||||
|
@ -57,7 +59,9 @@ impl Actionable for ActionState<CreateUsersAndGroup> {
|
||||||
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,
|
||||||
..
|
create_group: _,
|
||||||
|
create_users: _,
|
||||||
|
action_state: _,
|
||||||
} = &self;
|
} = &self;
|
||||||
|
|
||||||
vec![
|
vec![
|
||||||
|
@ -73,13 +77,19 @@ impl Actionable for ActionState<CreateUsersAndGroup> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn execute(&mut self) -> Result<(), HarmonicError> {
|
async fn execute(&mut self) -> Result<(), Self::Error> {
|
||||||
let Self {
|
let Self {
|
||||||
create_users,
|
create_users,
|
||||||
create_group,
|
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;
|
} = self;
|
||||||
|
|
||||||
|
|
||||||
// Create group
|
// Create group
|
||||||
let create_group = create_group.execute().await?;
|
let create_group = create_group.execute().await?;
|
||||||
|
|
||||||
|
@ -87,33 +97,32 @@ impl Actionable for ActionState<CreateUsersAndGroup> {
|
||||||
// TODO(@hoverbear): Abstract this, it will be common
|
// TODO(@hoverbear): Abstract this, it will be common
|
||||||
let mut set = JoinSet::new();
|
let mut set = JoinSet::new();
|
||||||
|
|
||||||
let mut successes = Vec::with_capacity(create_users.len());
|
|
||||||
let mut errors = Vec::default();
|
let mut errors = Vec::default();
|
||||||
|
|
||||||
for create_user in create_users {
|
for (idx, create_user) in create_users.iter().enumerate() {
|
||||||
let _abort_handle = set.spawn(async move { create_user.execute().await });
|
let mut create_user_clone = create_user.clone();
|
||||||
|
let _abort_handle = set.spawn(async move { create_user_clone.execute().await?; Result::<_, CreateUserError>::Ok((idx, create_user_clone)) });
|
||||||
}
|
}
|
||||||
|
|
||||||
while let Some(result) = set.join_next().await {
|
while let Some(result) = set.join_next().await {
|
||||||
match result {
|
match result {
|
||||||
Ok(Ok(success)) => successes.push(success),
|
Ok(Ok((idx, success))) => create_users[idx] = success,
|
||||||
Ok(Err(e)) => errors.push(e),
|
Ok(Err(e)) => errors.push(e),
|
||||||
Err(e) => errors.push(e.into()),
|
Err(e) => return Err(e)?,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if !errors.is_empty() {
|
if !errors.is_empty() {
|
||||||
if errors.len() == 1 {
|
if errors.len() == 1 {
|
||||||
return Err(errors.into_iter().next().unwrap());
|
return Err(errors.into_iter().next().unwrap().into());
|
||||||
} else {
|
} else {
|
||||||
return Err(HarmonicError::Multiple(errors));
|
return Err(CreateUsersAndGroupError::CreateUsers(errors));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Self::Receipt {
|
|
||||||
create_group,
|
*action_state = ActionState::Completed;
|
||||||
create_users: successes,
|
Ok(())
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
|
@ -125,19 +134,26 @@ impl Actionable for ActionState<CreateUsersAndGroup> {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl From<ActionState<CreateUsersAndGroup>> for ActionState<Action> {
|
impl From<CreateUsersAndGroup> for Action {
|
||||||
fn from(v: ActionState<CreateUsersAndGroup>) -> Self {
|
fn from(v: CreateUsersAndGroup) -> Self {
|
||||||
match v {
|
Action::CreateUsersAndGroup(v)
|
||||||
ActionState::Completed(_) => ActionState::Completed(Action::CreateUsersAndGroup(v)),
|
|
||||||
ActionState::Planned(_) => ActionState::Planned(Action::CreateUsersAndGroup(v)),
|
|
||||||
ActionState::Reverted(_) => ActionState::Reverted(Action::CreateUsersAndGroup(v)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error, Serialize)]
|
#[derive(Debug, thiserror::Error, Serialize)]
|
||||||
pub enum CreateUsersAndGroupError {
|
pub enum CreateUsersAndGroupError {
|
||||||
|
#[error(transparent)]
|
||||||
|
CreateUser(#[from] CreateUserError),
|
||||||
|
#[error("Multiple errors: {}", .0.iter().map(|v| format!("{v}")).collect::<Vec<_>>().join(" & "))]
|
||||||
|
CreateUsers(Vec<CreateUserError>),
|
||||||
|
#[error(transparent)]
|
||||||
|
CreateGroup(#[from] CreateGroupError),
|
||||||
|
#[error(transparent)]
|
||||||
|
Join(
|
||||||
|
#[from]
|
||||||
|
#[serde(serialize_with = "crate::serialize_error_to_display")]
|
||||||
|
JoinError
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use tempdir::TempDir;
|
use tempdir::TempDir;
|
||||||
|
use tokio::task::JoinError;
|
||||||
|
|
||||||
use crate::actions::base::{FetchNix, FetchNixError, MoveUnpackedNix, MoveUnpackedNixError};
|
use crate::actions::base::{FetchNix, FetchNixError, MoveUnpackedNix, MoveUnpackedNixError};
|
||||||
use crate::{HarmonicError, InstallSettings};
|
use crate::{HarmonicError, InstallSettings};
|
||||||
|
|
||||||
use crate::actions::{ActionDescription, Actionable, ActionState, Action};
|
use crate::actions::{ActionDescription, Actionable, ActionState, Action, ActionError};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
CreateNixTree, CreateNixTreeError,
|
CreateNixTree, CreateNixTreeError,
|
||||||
|
@ -17,11 +18,12 @@ pub struct ProvisionNix {
|
||||||
create_users_and_group: CreateUsersAndGroup,
|
create_users_and_group: CreateUsersAndGroup,
|
||||||
create_nix_tree: CreateNixTree,
|
create_nix_tree: CreateNixTree,
|
||||||
move_unpacked_nix: MoveUnpackedNix,
|
move_unpacked_nix: MoveUnpackedNix,
|
||||||
|
action_state: ActionState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProvisionNix {
|
impl ProvisionNix {
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub async fn plan(settings: InstallSettings) -> Result<ActionState<Self>, ProvisionNixError> {
|
pub async fn plan(settings: InstallSettings) -> Result<Self, ProvisionNixError> {
|
||||||
let tempdir = TempDir::new("nix").map_err(ProvisionNixError::TempDir)?;
|
let tempdir = TempDir::new("nix").map_err(ProvisionNixError::TempDir)?;
|
||||||
|
|
||||||
let fetch_nix = FetchNix::plan(
|
let fetch_nix = FetchNix::plan(
|
||||||
|
@ -32,29 +34,26 @@ impl ProvisionNix {
|
||||||
let create_users_and_group = CreateUsersAndGroup::plan(settings.clone()).await?;
|
let create_users_and_group = CreateUsersAndGroup::plan(settings.clone()).await?;
|
||||||
let create_nix_tree = CreateNixTree::plan(settings.force).await?;
|
let create_nix_tree = CreateNixTree::plan(settings.force).await?;
|
||||||
let move_unpacked_nix = MoveUnpackedNix::plan(tempdir.path().to_path_buf()).await?;
|
let move_unpacked_nix = MoveUnpackedNix::plan(tempdir.path().to_path_buf()).await?;
|
||||||
Ok(ActionState::Planned(Self {
|
Ok(Self {
|
||||||
fetch_nix,
|
fetch_nix,
|
||||||
create_users_and_group,
|
create_users_and_group,
|
||||||
create_nix_tree,
|
create_nix_tree,
|
||||||
move_unpacked_nix,
|
move_unpacked_nix,
|
||||||
}))
|
action_state: ActionState::Planned,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl Actionable for ActionState<ProvisionNix> {
|
impl Actionable for ProvisionNix {
|
||||||
type Error = ProvisionNixError;
|
type Error = ProvisionNixError;
|
||||||
fn description(&self) -> Vec<ActionDescription> {
|
fn description(&self) -> Vec<ActionDescription> {
|
||||||
match self {
|
|
||||||
ActionState::Completed(action) => action.start_systemd_socket.description(),
|
|
||||||
ActionState::Planned(action) => action.start_systemd_socket.description(),
|
|
||||||
ActionState::Reverted(_) => todo!(),
|
|
||||||
}
|
|
||||||
let Self {
|
let Self {
|
||||||
fetch_nix,
|
fetch_nix,
|
||||||
create_users_and_group,
|
create_users_and_group,
|
||||||
create_nix_tree,
|
create_nix_tree,
|
||||||
move_unpacked_nix,
|
move_unpacked_nix,
|
||||||
|
action_state: _,
|
||||||
} = &self;
|
} = &self;
|
||||||
|
|
||||||
let mut buf = fetch_nix.description();
|
let mut buf = fetch_nix.description();
|
||||||
|
@ -72,17 +71,20 @@ impl Actionable for ActionState<ProvisionNix> {
|
||||||
create_nix_tree,
|
create_nix_tree,
|
||||||
create_users_and_group,
|
create_users_and_group,
|
||||||
move_unpacked_nix,
|
move_unpacked_nix,
|
||||||
|
action_state,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
// We fetch nix while doing the rest, then move it over.
|
// We fetch nix while doing the rest, then move it over.
|
||||||
let fetch_nix_handle = tokio::spawn(async move { fetch_nix.execute().await });
|
let mut fetch_nix_clone = fetch_nix.clone();
|
||||||
|
let fetch_nix_handle = tokio::task::spawn_local(async { fetch_nix_clone.execute().await?; Result::<_, Self::Error>::Ok(fetch_nix_clone) });
|
||||||
|
|
||||||
create_users_and_group.execute().await?;
|
create_users_and_group.execute().await?;
|
||||||
create_nix_tree.execute().await?;
|
create_nix_tree.execute().await.map_err(ProvisionNixError::from)?;
|
||||||
|
|
||||||
fetch_nix_handle.await??;
|
*fetch_nix = fetch_nix_handle.await.map_err(ProvisionNixError::from)??;
|
||||||
move_unpacked_nix.execute().await?;
|
move_unpacked_nix.execute().await?;
|
||||||
|
|
||||||
|
*action_state = ActionState::Completed;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,13 +96,9 @@ impl Actionable for ActionState<ProvisionNix> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ActionState<ProvisionNix>> for ActionState<Action> {
|
impl From<ProvisionNix> for Action {
|
||||||
fn from(v: ActionState<ProvisionNix>) -> Self {
|
fn from(v: ProvisionNix) -> Self {
|
||||||
match v {
|
Action::ProvisionNix(v)
|
||||||
ActionState::Completed(_) => ActionState::Completed(Action::ProvisionNix(v)),
|
|
||||||
ActionState::Planned(_) => ActionState::Planned(Action::ProvisionNix(v)),
|
|
||||||
ActionState::Reverted(_) => ActionState::Reverted(Action::ProvisionNix(v)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,6 +106,23 @@ impl From<ActionState<ProvisionNix>> for ActionState<Action> {
|
||||||
#[derive(Debug, thiserror::Error, Serialize)]
|
#[derive(Debug, thiserror::Error, Serialize)]
|
||||||
pub enum ProvisionNixError {
|
pub enum ProvisionNixError {
|
||||||
#[error("Failed create tempdir")]
|
#[error("Failed create tempdir")]
|
||||||
#[serde(serialize_with = "crate::serialize_std_io_error_to_display")]
|
TempDir(
|
||||||
TempDir(#[source] std::io::Error)
|
#[source]
|
||||||
|
#[serde(serialize_with = "crate::serialize_error_to_display")]
|
||||||
|
std::io::Error
|
||||||
|
),
|
||||||
|
#[error(transparent)]
|
||||||
|
FetchNix(#[from] FetchNixError),
|
||||||
|
#[error(transparent)]
|
||||||
|
Join(
|
||||||
|
#[from]
|
||||||
|
#[serde(serialize_with = "crate::serialize_error_to_display")]
|
||||||
|
JoinError
|
||||||
|
),
|
||||||
|
#[error(transparent)]
|
||||||
|
CreateUsersAndGroup(#[from] CreateUsersAndGroupError),
|
||||||
|
#[error(transparent)]
|
||||||
|
CreateNixTree(#[from] CreateNixTreeError),
|
||||||
|
#[error(transparent)]
|
||||||
|
MoveUnpackedNix(#[from] MoveUnpackedNixError),
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,68 +8,52 @@ use crate::actions::{ActionDescription, Actionable, ActionState, Action, ActionE
|
||||||
/// This is mostly indirection for supporting non-systemd
|
/// This is mostly indirection for supporting non-systemd
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
pub struct StartNixDaemon {
|
pub struct StartNixDaemon {
|
||||||
start_systemd_socket: ActionState<StartSystemdUnit>,
|
start_systemd_socket: StartSystemdUnit,
|
||||||
|
action_state: ActionState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StartNixDaemon {
|
impl StartNixDaemon {
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub async fn plan() -> Result<ActionState<Self>, StartNixDaemonError> {
|
pub async fn plan() -> Result<Self, StartNixDaemonError> {
|
||||||
let start_systemd_socket = StartSystemdUnit::plan("nix-daemon.socket".into()).await?;
|
let start_systemd_socket = StartSystemdUnit::plan("nix-daemon.socket".into()).await?;
|
||||||
Ok(ActionState::Planned(Self {
|
Ok(Self {
|
||||||
start_systemd_socket,
|
start_systemd_socket,
|
||||||
}))
|
action_state: ActionState::Planned,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl Actionable for ActionState<StartNixDaemon> {
|
impl Actionable for StartNixDaemon {
|
||||||
type Error = StartNixDaemonError;
|
type Error = StartNixDaemonError;
|
||||||
|
|
||||||
fn description(&self) -> Vec<ActionDescription> {
|
fn description(&self) -> Vec<ActionDescription> {
|
||||||
let StartNixDaemon { start_systemd_socket } = match self {
|
self.start_systemd_socket.description()
|
||||||
ActionState::Completed(v) | ActionState::Reverted(v) | ActionState::Planned(v) => v,
|
|
||||||
};
|
|
||||||
start_systemd_socket.description()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn execute(self) -> Result<Self, ActionError> {
|
async fn execute(&mut self) -> Result<(), Self::Error> {
|
||||||
let StartNixDaemon { start_systemd_socket } = match self {
|
let Self { start_systemd_socket, action_state } = self;
|
||||||
ActionState::Planned(v) => v,
|
|
||||||
ActionState::Completed(_) => return Err(ActionError::AlreadyExecuted(self.clone().into())),
|
|
||||||
ActionState::Reverted(_) => return Err(ActionError::AlreadyReverted(self.clone().into())),
|
|
||||||
};
|
|
||||||
|
|
||||||
start_systemd_socket.execute().await?;
|
start_systemd_socket.execute().await?;
|
||||||
|
|
||||||
Ok(Self::Completed(StartNixDaemon {
|
Ok(())
|
||||||
start_systemd_socket,
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn revert(self) -> Result<Self, ActionError> {
|
async fn revert(&mut self) -> Result<(), Self::Error> {
|
||||||
let StartNixDaemon { start_systemd_socket } = match self {
|
let Self { start_systemd_socket, action_state, .. } = self;
|
||||||
ActionState::Planned(v) => return Err(ActionError::NotExecuted(self.clone().into())),
|
|
||||||
ActionState::Completed(v) => v,
|
|
||||||
ActionState::Reverted(_) => return Err(ActionError::AlreadyReverted(self.clone().into())),
|
|
||||||
};
|
|
||||||
|
|
||||||
start_systemd_socket.revert().await?;
|
start_systemd_socket.revert().await?;
|
||||||
|
|
||||||
Ok(Self::Reverted(StartNixDaemon {
|
*action_state = ActionState::Completed;
|
||||||
start_systemd_socket,
|
Ok(())
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ActionState<StartNixDaemon>> for ActionState<Action> {
|
impl From<StartNixDaemon> for Action {
|
||||||
fn from(v: ActionState<StartNixDaemon>) -> Self {
|
fn from(v: StartNixDaemon) -> Self {
|
||||||
match v {
|
Action::StartNixDaemon(v)
|
||||||
ActionState::Completed(_) => ActionState::Completed(Action::StartNixDaemon(v)),
|
|
||||||
ActionState::Planned(_) => ActionState::Planned(Action::StartNixDaemon(v)),
|
|
||||||
ActionState::Reverted(_) => ActionState::Reverted(Action::StartNixDaemon(v)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,24 +22,21 @@ use serde::{Deserialize, de::DeserializeOwned, Serialize};
|
||||||
use self::base::{StartSystemdUnit, StartSystemdUnitError};
|
use self::base::{StartSystemdUnit, StartSystemdUnitError};
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
pub trait Actionable: DeserializeOwned + Serialize + Into<ActionState<Action>> {
|
pub trait Actionable: DeserializeOwned + Serialize + Into<Action> {
|
||||||
type Error: std::error::Error + std::fmt::Debug + Serialize;
|
type Error: std::error::Error + std::fmt::Debug + Serialize + Into<ActionError>;
|
||||||
|
|
||||||
fn description(&self) -> Vec<ActionDescription>;
|
fn description(&self) -> Vec<ActionDescription>;
|
||||||
|
|
||||||
// They should also have an `async fn plan(args...) -> Result<ActionState<Self>, Self::Error>;`
|
// They should also have an `async fn plan(args...) -> Result<ActionState<Self>, Self::Error>;`
|
||||||
async fn execute(self) -> Result<Self, ActionError>;
|
async fn execute(&mut self) -> Result<(), Self::Error>;
|
||||||
async fn revert(self) -> Result<Self, ActionError>;
|
async fn revert(&mut self) -> Result<(), Self::Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub enum ActionState<P> where P: Serialize + DeserializeOwned + Clone {
|
pub enum ActionState {
|
||||||
#[serde(bound = "P: DeserializeOwned")]
|
Completed,
|
||||||
Completed(P),
|
Planned,
|
||||||
#[serde(bound = "P: DeserializeOwned")]
|
Reverted,
|
||||||
Planned(P),
|
|
||||||
#[serde(bound = "P: DeserializeOwned")]
|
|
||||||
Reverted(P),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
|
@ -60,34 +57,34 @@ impl ActionDescription {
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
pub enum Action {
|
pub enum Action {
|
||||||
ConfigureNixDaemonService(ActionState<ConfigureNixDaemonService>),
|
ConfigureNixDaemonService(ConfigureNixDaemonService),
|
||||||
ConfigureNix(ActionState<ConfigureNix>),
|
ConfigureNix(ConfigureNix),
|
||||||
ConfigureShellProfile(ActionState<ConfigureShellProfile>),
|
ConfigureShellProfile(ConfigureShellProfile),
|
||||||
CreateDirectory(ActionState<CreateDirectory>),
|
CreateDirectory(CreateDirectory),
|
||||||
CreateFile(ActionState<CreateFile>),
|
CreateFile(CreateFile),
|
||||||
CreateGroup(ActionState<CreateGroup>),
|
CreateGroup(CreateGroup),
|
||||||
CreateOrAppendFile(ActionState<CreateOrAppendFile>),
|
CreateOrAppendFile(CreateOrAppendFile),
|
||||||
CreateNixTree(ActionState<CreateNixTree>),
|
CreateNixTree(CreateNixTree),
|
||||||
CreateUser(ActionState<CreateUser>),
|
CreateUser(CreateUser),
|
||||||
CreateUsersAndGroup(ActionState<CreateUsersAndGroup>),
|
CreateUsersAndGroup(CreateUsersAndGroup),
|
||||||
FetchNix(ActionState<FetchNix>),
|
FetchNix(FetchNix),
|
||||||
MoveUnpackedNix(ActionState<MoveUnpackedNix>),
|
MoveUnpackedNix(MoveUnpackedNix),
|
||||||
PlaceChannelConfiguration(ActionState<PlaceChannelConfiguration>),
|
PlaceChannelConfiguration(PlaceChannelConfiguration),
|
||||||
PlaceNixConfiguration(ActionState<PlaceNixConfiguration>),
|
PlaceNixConfiguration(PlaceNixConfiguration),
|
||||||
SetupDefaultProfile(ActionState<SetupDefaultProfile>),
|
SetupDefaultProfile(SetupDefaultProfile),
|
||||||
StartNixDaemon(ActionState<StartNixDaemon>),
|
StartNixDaemon(StartNixDaemon),
|
||||||
StartSystemdUnit(ActionState<StartSystemdUnit>),
|
StartSystemdUnit(StartSystemdUnit),
|
||||||
ProvisionNix(ActionState<ProvisionNix>),
|
ProvisionNix(ProvisionNix),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error, serde::Serialize)]
|
#[derive(Debug, thiserror::Error, serde::Serialize)]
|
||||||
pub enum ActionError {
|
pub enum ActionError {
|
||||||
#[error("Attempted to revert an unexecuted action")]
|
#[error("Attempted to revert an unexecuted action")]
|
||||||
NotExecuted(ActionState<Action>),
|
NotExecuted(Action),
|
||||||
#[error("Attempted to execute an already executed action")]
|
#[error("Attempted to execute an already executed action")]
|
||||||
AlreadyExecuted(ActionState<Action>),
|
AlreadyExecuted(Action),
|
||||||
#[error("Attempted to revert an already reverted action")]
|
#[error("Attempted to revert an already reverted action")]
|
||||||
AlreadyReverted(ActionState<Action>),
|
AlreadyReverted(Action),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
ConfigureNixDaemonService(#[from] ConfigureNixDaemonServiceError),
|
ConfigureNixDaemonService(#[from] ConfigureNixDaemonServiceError),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
|
@ -127,13 +124,10 @@ pub enum ActionError {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl Actionable for ActionState<Action> {
|
impl Actionable for Action {
|
||||||
type Error = ActionError;
|
type Error = ActionError;
|
||||||
fn description(&self) -> Vec<ActionDescription> {
|
fn description(&self) -> Vec<ActionDescription> {
|
||||||
let inner = match self {
|
match self {
|
||||||
ActionState::Planned(p) | ActionState::Completed(p) | ActionState::Reverted(p) => p,
|
|
||||||
};
|
|
||||||
match inner {
|
|
||||||
Action::ConfigureNixDaemonService(i) => i.description(),
|
Action::ConfigureNixDaemonService(i) => i.description(),
|
||||||
Action::ConfigureNix(i) => i.description(),
|
Action::ConfigureNix(i) => i.description(),
|
||||||
Action::ConfigureShellProfile(i) => i.description(),
|
Action::ConfigureShellProfile(i) => i.description(),
|
||||||
|
@ -156,58 +150,50 @@ impl Actionable for ActionState<Action> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn execute(&mut self) -> Result<(), Self::Error> {
|
async fn execute(&mut self) -> Result<(), Self::Error> {
|
||||||
let inner = match self {
|
match self {
|
||||||
ActionState::Completed(p) => todo!(),
|
Action::ConfigureNixDaemonService(i) => i.execute().await?,
|
||||||
ActionState::Planned(p) => p,
|
Action::ConfigureNix(i) => i.execute().await?,
|
||||||
ActionState::Reverted(p) => todo!(),
|
Action::ConfigureShellProfile(i) => i.execute().await?,
|
||||||
|
Action::CreateDirectory(i) => i.execute().await?,
|
||||||
|
Action::CreateFile(i) => i.execute().await?,
|
||||||
|
Action::CreateGroup(i) => i.execute().await?,
|
||||||
|
Action::CreateOrAppendFile(i) => i.execute().await?,
|
||||||
|
Action::CreateNixTree(i) => i.execute().await?,
|
||||||
|
Action::CreateUser(i) => i.execute().await?,
|
||||||
|
Action::CreateUsersAndGroup(i) => i.execute().await?,
|
||||||
|
Action::FetchNix(i) => i.execute().await?,
|
||||||
|
Action::MoveUnpackedNix(i) => i.execute().await?,
|
||||||
|
Action::PlaceChannelConfiguration(i) => i.execute().await?,
|
||||||
|
Action::PlaceNixConfiguration(i) => i.execute().await?,
|
||||||
|
Action::SetupDefaultProfile(i) => i.execute().await?,
|
||||||
|
Action::StartNixDaemon(i) => i.execute().await?,
|
||||||
|
Action::StartSystemdUnit(i) => i.execute().await?,
|
||||||
|
Action::ProvisionNix(i) => i.execute().await?,
|
||||||
};
|
};
|
||||||
match inner {
|
Ok(())
|
||||||
Action::ConfigureNixDaemonService(i) => i.execute().await,
|
|
||||||
Action::ConfigureNix(i) => i.execute().await,
|
|
||||||
Action::ConfigureShellProfile(i) => i.execute().await,
|
|
||||||
Action::CreateDirectory(i) => i.execute().await,
|
|
||||||
Action::CreateFile(i) => i.execute().await,
|
|
||||||
Action::CreateGroup(i) => i.execute().await,
|
|
||||||
Action::CreateOrAppendFile(i) => i.execute().await,
|
|
||||||
Action::CreateNixTree(i) => i.execute().await,
|
|
||||||
Action::CreateUser(i) => i.execute().await,
|
|
||||||
Action::CreateUsersAndGroup(i) => i.execute().await,
|
|
||||||
Action::FetchNix(i) => i.execute().await,
|
|
||||||
Action::MoveUnpackedNix(i) => i.execute().await,
|
|
||||||
Action::PlaceChannelConfiguration(i) => i.execute().await,
|
|
||||||
Action::PlaceNixConfiguration(i) => i.execute().await,
|
|
||||||
Action::SetupDefaultProfile(i) => i.execute().await,
|
|
||||||
Action::StartNixDaemon(i) => i.execute().await,
|
|
||||||
Action::StartSystemdUnit(i) => i.execute().await,
|
|
||||||
Action::ProvisionNix(i) => i.execute().await,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn revert(&mut self) -> Result<(), Self::Error> {
|
async fn revert(&mut self) -> Result<(), Self::Error> {
|
||||||
let inner = match self {
|
match self {
|
||||||
ActionState::Planned(p) => todo!(),
|
Action::ConfigureNixDaemonService(i) => i.revert().await?,
|
||||||
ActionState::Completed(p) => p,
|
Action::ConfigureNix(i) => i.revert().await?,
|
||||||
ActionState::Reverted(p) => todo!(),
|
Action::ConfigureShellProfile(i) => i.revert().await?,
|
||||||
};
|
Action::CreateDirectory(i) => i.revert().await?,
|
||||||
match inner {
|
Action::CreateFile(i) => i.revert().await?,
|
||||||
Action::ConfigureNixDaemonService(i) => i.revert().await,
|
Action::CreateGroup(i) => i.revert().await?,
|
||||||
Action::ConfigureNix(i) => i.revert().await,
|
Action::CreateOrAppendFile(i) => i.revert().await?,
|
||||||
Action::ConfigureShellProfile(i) => i.revert().await,
|
Action::CreateNixTree(i) => i.revert().await?,
|
||||||
Action::CreateDirectory(i) => i.revert().await,
|
Action::CreateUser(i) => i.revert().await?,
|
||||||
Action::CreateFile(i) => i.revert().await,
|
Action::CreateUsersAndGroup(i) => i.revert().await?,
|
||||||
Action::CreateGroup(i) => i.revert().await,
|
Action::FetchNix(i) => i.revert().await?,
|
||||||
Action::CreateOrAppendFile(i) => i.revert().await,
|
Action::MoveUnpackedNix(i) => i.revert().await?,
|
||||||
Action::CreateNixTree(i) => i.revert().await,
|
Action::PlaceChannelConfiguration(i) => i.revert().await?,
|
||||||
Action::CreateUser(i) => i.revert().await,
|
Action::PlaceNixConfiguration(i) => i.revert().await?,
|
||||||
Action::CreateUsersAndGroup(i) => i.revert().await,
|
Action::SetupDefaultProfile(i) => i.revert().await?,
|
||||||
Action::FetchNix(i) => i.revert().await,
|
Action::StartNixDaemon(i) => i.revert().await?,
|
||||||
Action::MoveUnpackedNix(i) => i.revert().await,
|
Action::StartSystemdUnit(i) => i.revert().await?,
|
||||||
Action::PlaceChannelConfiguration(i) => i.revert().await,
|
Action::ProvisionNix(i) => i.revert().await?,
|
||||||
Action::PlaceNixConfiguration(i) => i.revert().await,
|
}
|
||||||
Action::SetupDefaultProfile(i) => i.revert().await,
|
Ok(())
|
||||||
Action::StartNixDaemon(i) => i.revert().await,
|
|
||||||
Action::StartSystemdUnit(i) => i.revert().await,
|
|
||||||
Action::ProvisionNix(i) => i.revert().await,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ pub(crate) mod subcommand;
|
||||||
|
|
||||||
use crate::{cli::arg::ChannelValue, interaction};
|
use crate::{cli::arg::ChannelValue, interaction};
|
||||||
use clap::{ArgAction, Parser};
|
use clap::{ArgAction, Parser};
|
||||||
use harmonic::{Harmonic, InstallPlan, InstallSettings};
|
use harmonic::{InstallPlan, InstallSettings};
|
||||||
use std::process::ExitCode;
|
use std::process::ExitCode;
|
||||||
|
|
||||||
use self::subcommand::HarmonicSubcommand;
|
use self::subcommand::HarmonicSubcommand;
|
||||||
|
@ -92,7 +92,7 @@ impl CommandExecute for HarmonicCli {
|
||||||
);
|
);
|
||||||
settings.modify_profile(!no_modify_profile);
|
settings.modify_profile(!no_modify_profile);
|
||||||
|
|
||||||
let plan = InstallPlan::new(settings).await?;
|
let mut plan = InstallPlan::new(settings).await?;
|
||||||
|
|
||||||
// TODO(@Hoverbear): Make this smarter
|
// TODO(@Hoverbear): Make this smarter
|
||||||
if !interaction::confirm(plan.description()).await? {
|
if !interaction::confirm(plan.description()).await? {
|
||||||
|
|
|
@ -9,7 +9,7 @@ use std::{
|
||||||
io::SeekFrom,
|
io::SeekFrom,
|
||||||
os::unix::fs::PermissionsExt,
|
os::unix::fs::PermissionsExt,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
process::ExitStatus,
|
process::ExitStatus, fmt::Display,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use error::HarmonicError;
|
pub use error::HarmonicError;
|
||||||
|
@ -280,6 +280,6 @@ fn set_env(k: impl AsRef<OsStr>, v: impl AsRef<OsStr>, dry_run: bool) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serialize_std_io_error_to_display<S>(err: &std::io::Error, ser: S) -> Result<S::Ok, S::Error> where S: Serializer {
|
fn serialize_error_to_display<E, S>(err: &E, ser: S) -> Result<S::Ok, S::Error> where E: Display, S: Serializer {
|
||||||
ser.serialize_str(&format!("{err:#}"))
|
ser.serialize_str(&format!("{err:#}"))
|
||||||
}
|
}
|
|
@ -30,9 +30,9 @@ pub struct InstallPlan {
|
||||||
* "Start Nix"
|
* "Start Nix"
|
||||||
* start_nix_daemon_service
|
* start_nix_daemon_service
|
||||||
*/
|
*/
|
||||||
provision_nix: ActionState<ProvisionNix>,
|
provision_nix: ProvisionNix,
|
||||||
configure_nix: ActionState<ConfigureNix>,
|
configure_nix: ConfigureNix,
|
||||||
start_nix_daemon: ActionState<StartNixDaemon>,
|
start_nix_daemon: StartNixDaemon,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InstallPlan {
|
impl InstallPlan {
|
||||||
|
|
Loading…
Reference in a new issue