forked from lix-project/lix-installer
other revert attempt
Signed-off-by: Ana Hobden <operator@hoverbear.org>
This commit is contained in:
parent
a44c9eb45f
commit
aa069cedf7
|
@ -1,10 +1,11 @@
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use serde::Serialize;
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
|
|
||||||
use crate::{execute_command, HarmonicError};
|
use crate::{execute_command, HarmonicError};
|
||||||
|
|
||||||
use crate::actions::{ActionDescription, Actionable, Revertable};
|
use crate::actions::{ActionDescription, Actionable, ActionState, Action};
|
||||||
|
|
||||||
const SERVICE_SRC: &str = "/nix/var/nix/profiles/default/lib/systemd/system/nix-daemon.service";
|
const SERVICE_SRC: &str = "/nix/var/nix/profiles/default/lib/systemd/system/nix-daemon.service";
|
||||||
const SOCKET_SRC: &str = "/nix/var/nix/profiles/default/lib/systemd/system/nix-daemon.socket";
|
const SOCKET_SRC: &str = "/nix/var/nix/profiles/default/lib/systemd/system/nix-daemon.socket";
|
||||||
|
@ -25,8 +26,8 @@ impl ConfigureNixDaemonService {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<'a> Actionable<'a> for ConfigureNixDaemonService {
|
impl Actionable for ActionState<ConfigureNixDaemonService> {
|
||||||
type Receipt = ConfigureNixDaemonServiceReceipt;
|
type Error = ConfigureNixDaemonServiceError;
|
||||||
fn description(&self) -> Vec<ActionDescription> {
|
fn description(&self) -> Vec<ActionDescription> {
|
||||||
vec![ActionDescription::new(
|
vec![ActionDescription::new(
|
||||||
"Configure Nix daemon related settings with systemd".to_string(),
|
"Configure Nix daemon related settings with systemd".to_string(),
|
||||||
|
@ -40,7 +41,7 @@ impl<'a> Actionable<'a> for ConfigureNixDaemonService {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn execute(self) -> Result<Self::Receipt, HarmonicError> {
|
async fn execute(&mut self) -> Result<(), Self::Error> {
|
||||||
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");
|
||||||
|
@ -68,30 +69,31 @@ impl<'a> Actionable<'a> for ConfigureNixDaemonService {
|
||||||
|
|
||||||
execute_command(Command::new("systemctl").arg("daemon-reload"), false).await?;
|
execute_command(Command::new("systemctl").arg("daemon-reload"), false).await?;
|
||||||
|
|
||||||
Ok(Self::Receipt {})
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
|
||||||
pub struct ConfigureNixDaemonServiceReceipt {}
|
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
|
||||||
impl<'a> Revertable<'a> for ConfigureNixDaemonServiceReceipt {
|
|
||||||
fn description(&self) -> Vec<ActionDescription> {
|
|
||||||
vec![
|
|
||||||
ActionDescription::new(
|
|
||||||
"Stop the systemd Nix daemon".to_string(),
|
|
||||||
vec![
|
|
||||||
"The `nix` command line tool communicates with a running Nix daemon managed by your init system".to_string()
|
|
||||||
]
|
|
||||||
),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn revert(self) -> Result<(), HarmonicError> {
|
async fn revert(&mut self) -> Result<(), Self::Error> {
|
||||||
todo!();
|
todo!();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
impl From<ActionState<ConfigureNixDaemonService>> for ActionState<Action> {
|
||||||
|
fn from(v: ActionState<ConfigureNixDaemonService>) -> Self {
|
||||||
|
match 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)]
|
||||||
|
pub enum ConfigureNixDaemonServiceError {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use nix::unistd::{chown, Group, User};
|
use nix::unistd::{chown, Group, User};
|
||||||
|
use serde::Serialize;
|
||||||
use tokio::fs::create_dir;
|
use tokio::fs::create_dir;
|
||||||
|
|
||||||
use crate::HarmonicError;
|
use crate::HarmonicError;
|
||||||
|
|
||||||
use crate::actions::{ActionDescription, Actionable, Revertable};
|
use crate::actions::{ActionDescription, Actionable, ActionState, Action};
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
pub struct CreateDirectory {
|
pub struct CreateDirectory {
|
||||||
|
@ -46,8 +47,8 @@ impl CreateDirectory {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<'a> Actionable<'a> for CreateDirectory {
|
impl Actionable for ActionState<CreateDirectory> {
|
||||||
type Receipt = CreateDirectoryReceipt;
|
type Error = CreateDirectoryError;
|
||||||
fn description(&self) -> Vec<ActionDescription> {
|
fn description(&self) -> Vec<ActionDescription> {
|
||||||
let Self {
|
let Self {
|
||||||
path,
|
path,
|
||||||
|
@ -65,7 +66,7 @@ impl<'a> Actionable<'a> for CreateDirectory {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn execute(self) -> Result<CreateDirectoryReceipt, HarmonicError> {
|
async fn execute(&mut self) -> Result<(), Self::Error> {
|
||||||
let Self {
|
let Self {
|
||||||
path,
|
path,
|
||||||
user,
|
user,
|
||||||
|
@ -88,45 +89,31 @@ impl<'a> Actionable<'a> for CreateDirectory {
|
||||||
.map_err(|e| HarmonicError::CreateDirectory(path.clone(), e))?;
|
.map_err(|e| HarmonicError::CreateDirectory(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| HarmonicError::Chown(path.clone(), e))?;
|
||||||
|
|
||||||
Ok(CreateDirectoryReceipt {
|
Ok(())
|
||||||
path,
|
|
||||||
user,
|
|
||||||
group,
|
|
||||||
mode,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
|
||||||
pub struct CreateDirectoryReceipt {
|
|
||||||
path: PathBuf,
|
|
||||||
user: String,
|
|
||||||
group: String,
|
|
||||||
mode: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
|
||||||
impl<'a> Revertable<'a> for CreateDirectoryReceipt {
|
|
||||||
fn description(&self) -> Vec<ActionDescription> {
|
|
||||||
vec![ActionDescription::new(
|
|
||||||
format!("Create the directory `/nix`"),
|
|
||||||
vec![format!(
|
|
||||||
"Nix and the Nix daemon require a Nix Store, which will be stored at `/nix`"
|
|
||||||
)],
|
|
||||||
)]
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn revert(self) -> Result<(), HarmonicError> {
|
async fn revert(&mut self) -> Result<(), Self::Error> {
|
||||||
let Self {
|
|
||||||
path,
|
|
||||||
user,
|
|
||||||
group,
|
|
||||||
mode,
|
|
||||||
} = self;
|
|
||||||
|
|
||||||
todo!();
|
todo!();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl From<ActionState<CreateDirectory>> for ActionState<Action> {
|
||||||
|
fn from(v: ActionState<CreateDirectory>) -> Self {
|
||||||
|
match 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)]
|
||||||
|
pub enum CreateDirectoryError {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
use nix::unistd::{chown, Group, User};
|
use nix::unistd::{chown, Group, User};
|
||||||
|
use serde::Serialize;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use tokio::{
|
use tokio::{
|
||||||
fs::{create_dir_all, OpenOptions},
|
fs::{create_dir_all, OpenOptions},
|
||||||
io::AsyncWriteExt,
|
io::AsyncWriteExt,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::HarmonicError;
|
use crate::{HarmonicError, actions::{ActionState, Action}};
|
||||||
|
|
||||||
use crate::actions::{ActionDescription, Actionable, Revertable};
|
use crate::actions::{ActionDescription, Actionable};
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
pub struct CreateFile {
|
pub struct CreateFile {
|
||||||
|
@ -53,8 +54,8 @@ impl CreateFile {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<'a> Actionable<'a> for CreateFile {
|
impl Actionable for ActionState<CreateFile> {
|
||||||
type Receipt = CreateFileReceipt;
|
type Error = CreateFileError;
|
||||||
fn description(&self) -> Vec<ActionDescription> {
|
fn description(&self) -> Vec<ActionDescription> {
|
||||||
let Self {
|
let Self {
|
||||||
path,
|
path,
|
||||||
|
@ -73,7 +74,7 @@ impl<'a> Actionable<'a> for CreateFile {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn execute(self) -> Result<CreateFileReceipt, HarmonicError> {
|
async fn execute(&mut self) -> Result<(), Self::Error> {
|
||||||
let Self {
|
let Self {
|
||||||
path,
|
path,
|
||||||
user,
|
user,
|
||||||
|
@ -107,35 +108,29 @@ impl<'a> Actionable<'a> for CreateFile {
|
||||||
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| HarmonicError::Chown(path.clone(), e))?;
|
||||||
|
|
||||||
Ok(Self::Receipt {
|
Ok(())
|
||||||
path,
|
|
||||||
user,
|
|
||||||
group,
|
|
||||||
mode,
|
|
||||||
buf,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
|
||||||
pub struct CreateFileReceipt {
|
|
||||||
path: PathBuf,
|
|
||||||
user: String,
|
|
||||||
group: String,
|
|
||||||
mode: u32,
|
|
||||||
buf: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
|
||||||
impl<'a> Revertable<'a> for CreateFileReceipt {
|
|
||||||
fn description(&self) -> Vec<ActionDescription> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn revert(self) -> Result<(), HarmonicError> {
|
async fn revert(&mut self) -> Result<(), Self::Error> {
|
||||||
todo!();
|
todo!();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<ActionState<CreateFile>> for ActionState<Action> {
|
||||||
|
fn from(v: ActionState<CreateFile>) -> Self {
|
||||||
|
match 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)]
|
||||||
|
pub enum CreateFileError {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
|
use serde::Serialize;
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
|
|
||||||
use crate::{HarmonicError, execute_command};
|
use crate::{HarmonicError, execute_command};
|
||||||
|
|
||||||
use crate::actions::{ActionDescription, Actionable, Revertable};
|
use crate::actions::{ActionDescription, Actionable, ActionState, Action};
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
pub struct CreateGroup {
|
pub struct CreateGroup {
|
||||||
|
@ -18,8 +19,8 @@ impl CreateGroup {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<'a> Actionable<'a> for CreateGroup {
|
impl Actionable for ActionState<CreateGroup> {
|
||||||
type Receipt = CreateGroupReceipt;
|
type Error = CreateOrAppendFileError;
|
||||||
fn description(&self) -> Vec<ActionDescription> {
|
fn description(&self) -> Vec<ActionDescription> {
|
||||||
let Self { name, gid } = &self;
|
let Self { name, gid } = &self;
|
||||||
vec![ActionDescription::new(
|
vec![ActionDescription::new(
|
||||||
|
@ -31,7 +32,7 @@ impl<'a> Actionable<'a> for CreateGroup {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn execute(self) -> Result<Self::Receipt, HarmonicError> {
|
async fn execute(&mut self) -> Result<(), Self::Error> {
|
||||||
let Self { name, gid } = self;
|
let Self { name, gid } = self;
|
||||||
|
|
||||||
execute_command(
|
execute_command(
|
||||||
|
@ -39,26 +40,27 @@ impl<'a> Actionable<'a> for CreateGroup {
|
||||||
false,
|
false,
|
||||||
).await?;
|
).await?;
|
||||||
|
|
||||||
Ok(CreateGroupReceipt { name, gid })
|
Ok(())
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
|
||||||
pub struct CreateGroupReceipt {
|
|
||||||
name: String,
|
|
||||||
gid: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
|
||||||
impl<'a> Revertable<'a> for CreateGroupReceipt {
|
|
||||||
fn description(&self) -> Vec<ActionDescription> {
|
|
||||||
todo!()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn revert(self) -> Result<(), HarmonicError> {
|
async fn revert(&mut self) -> Result<(), Self::Error> {
|
||||||
todo!();
|
todo!();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<ActionState<CreateGroup>> for ActionState<Action> {
|
||||||
|
fn from(v: ActionState<CreateGroup>) -> Self {
|
||||||
|
match 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)]
|
||||||
|
pub enum CreateGroupError {
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
use nix::unistd::{chown, Group, User};
|
use nix::unistd::{chown, Group, User};
|
||||||
|
use serde::Serialize;
|
||||||
use std::{
|
use std::{
|
||||||
io::SeekFrom,
|
io::SeekFrom,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
|
@ -8,9 +9,9 @@ use tokio::{
|
||||||
io::{AsyncSeekExt, AsyncWriteExt},
|
io::{AsyncSeekExt, AsyncWriteExt},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::HarmonicError;
|
use crate::{HarmonicError, actions::{ActionState, Action}};
|
||||||
|
|
||||||
use crate::actions::{ActionDescription, Actionable, Revertable};
|
use crate::actions::{ActionDescription, Actionable};
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
pub struct CreateOrAppendFile {
|
pub struct CreateOrAppendFile {
|
||||||
|
@ -43,8 +44,8 @@ impl CreateOrAppendFile {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<'a> Actionable<'a> for CreateOrAppendFile {
|
impl Actionable for ActionState<CreateOrAppendFile> {
|
||||||
type Receipt = CreateOrAppendFileReceipt;
|
type Error = CreateOrAppendFileError;
|
||||||
fn description(&self) -> Vec<ActionDescription> {
|
fn description(&self) -> Vec<ActionDescription> {
|
||||||
let Self {
|
let Self {
|
||||||
path,
|
path,
|
||||||
|
@ -62,7 +63,7 @@ impl<'a> Actionable<'a> for CreateOrAppendFile {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn execute(self) -> Result<CreateOrAppendFileReceipt, HarmonicError> {
|
async fn execute(&mut self) -> Result<(), Self::Error> {
|
||||||
let Self {
|
let Self {
|
||||||
path,
|
path,
|
||||||
user,
|
user,
|
||||||
|
@ -99,40 +100,30 @@ impl<'a> Actionable<'a> for CreateOrAppendFile {
|
||||||
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| HarmonicError::Chown(path.clone(), e))?;
|
||||||
|
|
||||||
Ok(Self::Receipt {
|
Ok(())
|
||||||
path,
|
|
||||||
user,
|
|
||||||
group,
|
|
||||||
mode,
|
|
||||||
buf,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
|
||||||
pub struct CreateOrAppendFileReceipt {
|
|
||||||
path: PathBuf,
|
|
||||||
user: String,
|
|
||||||
group: String,
|
|
||||||
mode: u32,
|
|
||||||
buf: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
|
||||||
impl<'a> Revertable<'a> for CreateOrAppendFileReceipt {
|
|
||||||
fn description(&self) -> Vec<ActionDescription> {
|
|
||||||
vec![ActionDescription::new(
|
|
||||||
format!("Create the directory `/nix`"),
|
|
||||||
vec![format!(
|
|
||||||
"Nix and the Nix daemon require a Nix Store, which will be stored at `/nix`"
|
|
||||||
)],
|
|
||||||
)]
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn revert(self) -> Result<(), HarmonicError> {
|
async fn revert(&mut self) -> Result<(), Self::Error> {
|
||||||
todo!();
|
todo!();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl From<ActionState<CreateOrAppendFile>> for ActionState<Action> {
|
||||||
|
fn from(v: ActionState<CreateOrAppendFile>) -> Self {
|
||||||
|
match 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)]
|
||||||
|
pub enum CreateOrAppendFileError {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
|
use serde::Serialize;
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
|
|
||||||
use crate::{HarmonicError, execute_command};
|
use crate::{HarmonicError, execute_command};
|
||||||
|
|
||||||
use crate::actions::{ActionDescription, Actionable, Revertable};
|
use crate::actions::{ActionDescription, Actionable, ActionState, Action};
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
pub struct CreateUser {
|
pub struct CreateUser {
|
||||||
|
@ -19,8 +20,8 @@ impl CreateUser {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<'a> Actionable<'a> for CreateUser {
|
impl Actionable for ActionState<CreateUser> {
|
||||||
type Receipt = CreateUserReceipt;
|
type Error = CreateUserError;
|
||||||
fn description(&self) -> Vec<ActionDescription> {
|
fn description(&self) -> Vec<ActionDescription> {
|
||||||
let name = &self.name;
|
let name = &self.name;
|
||||||
let uid = &self.uid;
|
let uid = &self.uid;
|
||||||
|
@ -33,7 +34,7 @@ impl<'a> Actionable<'a> for CreateUser {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn execute(self) -> Result<Self::Receipt, HarmonicError> {
|
async fn execute(&mut self) -> Result<(), Self::Error> {
|
||||||
let Self { name, uid, gid } = self;
|
let Self { name, uid, gid } = self;
|
||||||
|
|
||||||
execute_command(Command::new("useradd").args([
|
execute_command(Command::new("useradd").args([
|
||||||
|
@ -56,27 +57,29 @@ impl<'a> Actionable<'a> for CreateUser {
|
||||||
&name.to_string(),
|
&name.to_string(),
|
||||||
]), false).await?;
|
]), false).await?;
|
||||||
|
|
||||||
Ok(CreateUserReceipt { name, uid, gid })
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
|
||||||
pub struct CreateUserReceipt {
|
|
||||||
name: String,
|
|
||||||
uid: usize,
|
|
||||||
gid: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
|
||||||
impl<'a> Revertable<'a> for CreateUserReceipt {
|
|
||||||
fn description(&self) -> Vec<ActionDescription> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn revert(self) -> Result<(), HarmonicError> {
|
async fn revert(&mut self) -> Result<(), Self::Error> {
|
||||||
todo!();
|
todo!();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<ActionState<CreateUser>> for ActionState<Action> {
|
||||||
|
fn from(v: ActionState<CreateUser>) -> Self {
|
||||||
|
match 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)]
|
||||||
|
pub enum CreateUserError {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -2,11 +2,12 @@ use std::path::{PathBuf};
|
||||||
|
|
||||||
use bytes::Buf;
|
use bytes::Buf;
|
||||||
use reqwest::Url;
|
use reqwest::Url;
|
||||||
|
use serde::Serialize;
|
||||||
use tokio::task::spawn_blocking;
|
use tokio::task::spawn_blocking;
|
||||||
|
|
||||||
use crate::HarmonicError;
|
use crate::HarmonicError;
|
||||||
|
|
||||||
use crate::actions::{ActionDescription, Actionable, Revertable};
|
use crate::actions::{ActionDescription, Actionable, ActionState, Action};
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
pub struct FetchNix {
|
pub struct FetchNix {
|
||||||
|
@ -16,7 +17,7 @@ pub struct FetchNix {
|
||||||
|
|
||||||
impl FetchNix {
|
impl FetchNix {
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub async fn plan(url: Url, destination: PathBuf) -> Result<Self, HarmonicError> {
|
pub async fn plan(url: Url, destination: PathBuf) -> Result<Self, FetchNixError> {
|
||||||
// TODO(@hoverbear): Check URL exists?
|
// TODO(@hoverbear): Check URL exists?
|
||||||
// TODO(@hoverbear): Check tempdir exists
|
// TODO(@hoverbear): Check tempdir exists
|
||||||
|
|
||||||
|
@ -25,8 +26,8 @@ impl FetchNix {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<'a> Actionable<'a> for FetchNix {
|
impl Actionable for ActionState<FetchNix> {
|
||||||
type Receipt = FetchNixReceipt;
|
type Error = FetchNixError;
|
||||||
fn description(&self) -> Vec<ActionDescription> {
|
fn description(&self) -> Vec<ActionDescription> {
|
||||||
let Self { url, destination } = &self;
|
let Self { url, destination } = &self;
|
||||||
vec![ActionDescription::new(
|
vec![ActionDescription::new(
|
||||||
|
@ -39,7 +40,7 @@ impl<'a> Actionable<'a> for FetchNix {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn execute(self) -> Result<Self::Receipt, HarmonicError> {
|
async fn execute(&mut self) -> Result<(), Self::Error> {
|
||||||
let Self { url, destination } = self;
|
let Self { url, destination } = self;
|
||||||
|
|
||||||
tracing::trace!(%url, "Fetching url");
|
tracing::trace!(%url, "Fetching url");
|
||||||
|
@ -61,26 +62,29 @@ impl<'a> Actionable<'a> for FetchNix {
|
||||||
|
|
||||||
handle?;
|
handle?;
|
||||||
|
|
||||||
Ok(FetchNixReceipt { url, destination })
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
|
||||||
pub struct FetchNixReceipt {
|
|
||||||
url: Url,
|
|
||||||
destination: PathBuf,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
|
||||||
impl<'a> Revertable<'a> for FetchNixReceipt {
|
|
||||||
fn description(&self) -> Vec<ActionDescription> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn revert(self) -> Result<(), HarmonicError> {
|
async fn revert(&mut self) -> Result<(), Self::Error> {
|
||||||
todo!();
|
todo!();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<ActionState<FetchNix>> for ActionState<Action> {
|
||||||
|
fn from(v: ActionState<FetchNix>) -> Self {
|
||||||
|
match 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)]
|
||||||
|
pub enum FetchNixError {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -14,18 +14,18 @@ mod setup_default_profile;
|
||||||
mod start_systemd_unit;
|
mod start_systemd_unit;
|
||||||
|
|
||||||
pub use configure_nix_daemon_service::{
|
pub use configure_nix_daemon_service::{
|
||||||
ConfigureNixDaemonService, ConfigureNixDaemonServiceReceipt,
|
ConfigureNixDaemonService, ConfigureNixDaemonServiceError,
|
||||||
};
|
};
|
||||||
pub use create_directory::{CreateDirectory, CreateDirectoryReceipt};
|
pub use create_directory::{CreateDirectory, CreateDirectoryError};
|
||||||
pub use create_file::{CreateFile, CreateFileReceipt};
|
pub use create_file::{CreateFile, CreateFileError};
|
||||||
pub use create_group::{CreateGroup, CreateGroupReceipt};
|
pub use create_group::{CreateGroup, CreateGroupError};
|
||||||
pub use create_or_append_file::{CreateOrAppendFile, CreateOrAppendFileReceipt};
|
pub use create_or_append_file::{CreateOrAppendFile, CreateOrAppendFileError};
|
||||||
pub use create_user::{CreateUser, CreateUserReceipt};
|
pub use create_user::{CreateUser, CreateUserError};
|
||||||
pub use fetch_nix::{FetchNix, FetchNixReceipt};
|
pub use fetch_nix::{FetchNix, FetchNixError};
|
||||||
pub use move_unpacked_nix::{MoveUnpackedNix, MoveUnpackedNixReceipt};
|
pub use move_unpacked_nix::{MoveUnpackedNix, MoveUnpackedNixError};
|
||||||
pub use place_channel_configuration::{
|
pub use place_channel_configuration::{
|
||||||
PlaceChannelConfiguration, PlaceChannelConfigurationReceipt,
|
PlaceChannelConfiguration, PlaceChannelConfigurationError,
|
||||||
};
|
};
|
||||||
pub use place_nix_configuration::{PlaceNixConfiguration, PlaceNixConfigurationReceipt};
|
pub use place_nix_configuration::{PlaceNixConfiguration, PlaceNixConfigurationError};
|
||||||
pub use setup_default_profile::{SetupDefaultProfile, SetupDefaultProfileReceipt};
|
pub use setup_default_profile::{SetupDefaultProfile, SetupDefaultProfileError};
|
||||||
pub use start_systemd_unit::{StartSystemdUnit, StartSystemdUnitReceipt};
|
pub use start_systemd_unit::{StartSystemdUnit, StartSystemdUnitError};
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::HarmonicError;
|
use crate::HarmonicError;
|
||||||
|
|
||||||
use crate::actions::{ActionDescription, Actionable, Revertable};
|
use crate::actions::{ActionDescription, Actionable, ActionState, Action};
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
pub struct MoveUnpackedNix {
|
pub struct MoveUnpackedNix {
|
||||||
|
@ -18,8 +20,8 @@ impl MoveUnpackedNix {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<'a> Actionable<'a> for MoveUnpackedNix {
|
impl Actionable for ActionState<MoveUnpackedNix> {
|
||||||
type Receipt = MoveUnpackedNixReceipt;
|
type Error = MoveUnpackedNixError;
|
||||||
fn description(&self) -> Vec<ActionDescription> {
|
fn description(&self) -> Vec<ActionDescription> {
|
||||||
let Self { source } = &self;
|
let Self { source } = &self;
|
||||||
vec![ActionDescription::new(
|
vec![ActionDescription::new(
|
||||||
|
@ -32,7 +34,7 @@ impl<'a> Actionable<'a> for MoveUnpackedNix {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn execute(self) -> Result<Self::Receipt, HarmonicError> {
|
async fn execute(&mut self) -> Result<(), Self::Error> {
|
||||||
let Self { source } = self;
|
let Self { source } = self;
|
||||||
|
|
||||||
// TODO(@Hoverbear): I would like to make this less awful
|
// TODO(@Hoverbear): I would like to make this less awful
|
||||||
|
@ -51,23 +53,29 @@ impl<'a> Actionable<'a> for MoveUnpackedNix {
|
||||||
.await
|
.await
|
||||||
.map_err(|e| HarmonicError::Rename(src, dest.to_owned(), e))?;
|
.map_err(|e| HarmonicError::Rename(src, dest.to_owned(), e))?;
|
||||||
|
|
||||||
Ok(MoveUnpackedNixReceipt {})
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
|
||||||
pub struct MoveUnpackedNixReceipt {}
|
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
|
||||||
impl<'a> Revertable<'a> for MoveUnpackedNixReceipt {
|
|
||||||
fn description(&self) -> Vec<ActionDescription> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn revert(self) -> Result<(), HarmonicError> {
|
async fn revert(&mut self) -> Result<(), Self::Error> {
|
||||||
todo!();
|
todo!();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<ActionState<MoveUnpackedNix>> for ActionState<Action> {
|
||||||
|
fn from(v: ActionState<MoveUnpackedNix>) -> Self {
|
||||||
|
match 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)]
|
||||||
|
pub enum MoveUnpackedNixError {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
use reqwest::Url;
|
use reqwest::Url;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::HarmonicError;
|
use crate::HarmonicError;
|
||||||
|
|
||||||
use crate::actions::{ActionDescription, Actionable, Revertable};
|
use crate::actions::{ActionDescription, Actionable, ActionState, Action};
|
||||||
|
|
||||||
use super::{CreateFile, CreateFileReceipt};
|
use super::{CreateFile, CreateFileError};
|
||||||
|
|
||||||
const NIX_CHANNELS_PATH: &str = "/root/.nix-channels";
|
const NIX_CHANNELS_PATH: &str = "/root/.nix-channels";
|
||||||
|
|
||||||
|
@ -32,8 +33,8 @@ impl PlaceChannelConfiguration {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<'a> Actionable<'a> for PlaceChannelConfiguration {
|
impl Actionable for ActionState<PlaceChannelConfiguration> {
|
||||||
type Receipt = PlaceChannelConfigurationReceipt;
|
type Error = PlaceChannelConfigurationError;
|
||||||
fn description(&self) -> Vec<ActionDescription> {
|
fn description(&self) -> Vec<ActionDescription> {
|
||||||
let Self {
|
let Self {
|
||||||
channels,
|
channels,
|
||||||
|
@ -46,35 +47,38 @@ impl<'a> Actionable<'a> for PlaceChannelConfiguration {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn execute(self) -> Result<Self::Receipt, HarmonicError> {
|
async fn execute(&mut self) -> Result<(), Self::Error> {
|
||||||
let Self {
|
let Self {
|
||||||
create_file,
|
create_file,
|
||||||
channels,
|
channels,
|
||||||
} = self;
|
} = self;
|
||||||
let create_file = create_file.execute().await?;
|
|
||||||
Ok(Self::Receipt {
|
|
||||||
create_file,
|
|
||||||
channels,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
create_file.execute().await?;
|
||||||
pub struct PlaceChannelConfigurationReceipt {
|
|
||||||
channels: Vec<(String, Url)>,
|
|
||||||
create_file: CreateFileReceipt,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
Ok(())
|
||||||
impl<'a> Revertable<'a> for PlaceChannelConfigurationReceipt {
|
|
||||||
fn description(&self) -> Vec<ActionDescription> {
|
|
||||||
todo!()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn revert(self) -> Result<(), HarmonicError> {
|
async fn revert(&mut self) -> Result<(), Self::Error> {
|
||||||
todo!();
|
todo!();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<ActionState<PlaceChannelConfiguration>> for ActionState<Action> {
|
||||||
|
fn from(v: ActionState<PlaceChannelConfiguration>) -> Self {
|
||||||
|
match 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)]
|
||||||
|
pub enum PlaceChannelConfigurationError {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::HarmonicError;
|
use crate::HarmonicError;
|
||||||
|
|
||||||
use crate::actions::{ActionDescription, Actionable, Revertable};
|
use crate::actions::{ActionDescription, Actionable, ActionState, Action};
|
||||||
|
|
||||||
use super::{CreateFile, CreateFileReceipt, CreateDirectory, CreateDirectoryReceipt};
|
use super::{CreateFile, CreateFileError, CreateDirectory, CreateDirectoryError};
|
||||||
|
|
||||||
const NIX_CONF_FOLDER: &str = "/etc/nix";
|
const NIX_CONF_FOLDER: &str = "/etc/nix";
|
||||||
const NIX_CONF: &str = "/etc/nix/nix.conf";
|
const NIX_CONF: &str = "/etc/nix/nix.conf";
|
||||||
|
@ -35,8 +37,9 @@ impl PlaceNixConfiguration {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<'a> Actionable<'a> for PlaceNixConfiguration {
|
impl Actionable for ActionState<PlaceNixConfiguration> {
|
||||||
type Receipt = PlaceNixConfigurationReceipt;
|
type Error = PlaceNixConfigurationError;
|
||||||
|
|
||||||
fn description(&self) -> Vec<ActionDescription> {
|
fn description(&self) -> Vec<ActionDescription> {
|
||||||
vec![ActionDescription::new(
|
vec![ActionDescription::new(
|
||||||
format!("Place the nix configuration in `{NIX_CONF}`"),
|
format!("Place the nix configuration in `{NIX_CONF}`"),
|
||||||
|
@ -45,30 +48,36 @@ impl<'a> Actionable<'a> for PlaceNixConfiguration {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn execute(self) -> Result<Self::Receipt, HarmonicError> {
|
async fn execute(&mut self) -> Result<(), Self::Error> {
|
||||||
let Self { create_file, create_directory } = self;
|
let Self { create_file, create_directory } = self;
|
||||||
let create_directory = create_directory.execute().await?;
|
|
||||||
let create_file = create_file.execute().await?;
|
|
||||||
Ok(Self::Receipt { create_file, create_directory })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
create_directory.execute().await?;
|
||||||
pub struct PlaceNixConfigurationReceipt {
|
create_file.execute().await?;
|
||||||
create_directory: CreateDirectoryReceipt,
|
|
||||||
create_file: CreateFileReceipt,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
Ok(())
|
||||||
impl<'a> Revertable<'a> for PlaceNixConfigurationReceipt {
|
|
||||||
fn description(&self) -> Vec<ActionDescription> {
|
|
||||||
todo!()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn revert(self) -> Result<(), HarmonicError> {
|
async fn revert(&mut self) -> Result<(), Self::Error> {
|
||||||
todo!();
|
todo!();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<ActionState<PlaceNixConfiguration>> for ActionState<Action> {
|
||||||
|
fn from(v: ActionState<PlaceNixConfiguration>) -> Self {
|
||||||
|
match 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)]
|
||||||
|
pub enum PlaceNixConfigurationError {
|
||||||
|
|
||||||
|
}
|
|
@ -1,9 +1,10 @@
|
||||||
use crate::{execute_command, HarmonicError};
|
use crate::{execute_command, HarmonicError, actions::{ActionState, Action}};
|
||||||
|
|
||||||
use glob::glob;
|
use glob::glob;
|
||||||
|
use serde::Serialize;
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
|
|
||||||
use crate::actions::{ActionDescription, Actionable, Revertable};
|
use crate::actions::{ActionDescription, Actionable};
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
pub struct SetupDefaultProfile {
|
pub struct SetupDefaultProfile {
|
||||||
|
@ -18,8 +19,8 @@ impl SetupDefaultProfile {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<'a> Actionable<'a> for SetupDefaultProfile {
|
impl Actionable for ActionState<SetupDefaultProfile> {
|
||||||
type Receipt = SetupDefaultProfileReceipt;
|
type Error = SetupDefaultProfileError;
|
||||||
fn description(&self) -> Vec<ActionDescription> {
|
fn description(&self) -> Vec<ActionDescription> {
|
||||||
vec![ActionDescription::new(
|
vec![ActionDescription::new(
|
||||||
"Setup the default Nix profile".to_string(),
|
"Setup the default Nix profile".to_string(),
|
||||||
|
@ -28,7 +29,7 @@ impl<'a> Actionable<'a> for SetupDefaultProfile {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn execute(self) -> Result<Self::Receipt, HarmonicError> {
|
async fn execute(&mut self) -> Result<(), Self::Error> {
|
||||||
let Self { channels } = self;
|
let Self { channels } = self;
|
||||||
tracing::info!("Setting up default profile");
|
tracing::info!("Setting up default profile");
|
||||||
|
|
||||||
|
@ -110,26 +111,30 @@ impl<'a> Actionable<'a> for SetupDefaultProfile {
|
||||||
false => return Err(HarmonicError::CommandFailedStatus(command_str)),
|
false => return Err(HarmonicError::CommandFailedStatus(command_str)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(Self::Receipt {})
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
|
||||||
pub struct SetupDefaultProfileReceipt {}
|
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
|
||||||
impl<'a> Revertable<'a> for SetupDefaultProfileReceipt {
|
|
||||||
fn description(&self) -> Vec<ActionDescription> {
|
|
||||||
vec![ActionDescription::new(
|
|
||||||
"Unset the default Nix profile".to_string(),
|
|
||||||
vec!["TODO".to_string()],
|
|
||||||
)]
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn revert(self) -> Result<(), HarmonicError> {
|
async fn revert(&mut self) -> Result<(), Self::Error> {
|
||||||
todo!();
|
todo!();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<ActionState<SetupDefaultProfile>> for ActionState<Action> {
|
||||||
|
fn from(v: ActionState<SetupDefaultProfile>) -> Self {
|
||||||
|
match 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)]
|
||||||
|
pub enum SetupDefaultProfileError {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
|
use serde::Serialize;
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
|
|
||||||
|
use crate::actions::meta::StartNixDaemon;
|
||||||
use crate::{execute_command, HarmonicError};
|
use crate::{execute_command, HarmonicError};
|
||||||
|
|
||||||
use crate::actions::{ActionDescription, Actionable, Revertable};
|
use crate::actions::{ActionDescription, Actionable, ActionState, Action, ActionError};
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
pub struct StartSystemdUnit {
|
pub struct StartSystemdUnit {
|
||||||
|
@ -11,56 +13,83 @@ pub struct StartSystemdUnit {
|
||||||
|
|
||||||
impl StartSystemdUnit {
|
impl StartSystemdUnit {
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub async fn plan(unit: String) -> Result<Self, HarmonicError> {
|
pub async fn plan(unit: String) -> Result<ActionState<Self>, StartSystemdUnitError> {
|
||||||
Ok(Self { unit })
|
Ok(ActionState::Planned(Self { unit }))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<'a> Actionable<'a> for StartSystemdUnit {
|
impl Actionable for ActionState<StartSystemdUnit> {
|
||||||
type Receipt = StartSystemdUnitReceipt;
|
type Error = StartSystemdUnitError;
|
||||||
fn description(&self) -> Vec<ActionDescription> {
|
fn description(&self) -> Vec<ActionDescription> {
|
||||||
vec![
|
match self {
|
||||||
|
ActionState::Planned(v) => 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![
|
||||||
"The `nix` command line tool communicates with a running Nix daemon managed by your init system".to_string()
|
"The `nix` command line tool communicates with a running Nix daemon managed by your init system".to_string()
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
ActionState::Completed(_) => vec![
|
||||||
|
ActionDescription::new(
|
||||||
|
"Stop the systemd Nix service and socket".to_string(),
|
||||||
|
vec![
|
||||||
|
"The `nix` command line tool communicates with a running Nix daemon managed by your init system".to_string()
|
||||||
]
|
]
|
||||||
|
),
|
||||||
|
],
|
||||||
|
ActionState::Reverted(_) => vec![
|
||||||
|
ActionDescription::new(
|
||||||
|
"Stopped the systemd Nix service and socket".to_string(),
|
||||||
|
vec![
|
||||||
|
"The `nix` command line tool communicates with a running Nix daemon managed by your init system".to_string()
|
||||||
|
]
|
||||||
|
),
|
||||||
|
],
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn execute(self) -> Result<Self::Receipt, HarmonicError> {
|
async fn execute(&mut self) -> Result<(), ActionError> {
|
||||||
let Self { unit } = self;
|
let StartSystemdUnit { unit } = match 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")
|
||||||
.arg("enable")
|
.arg("enable")
|
||||||
.arg("--now")
|
.arg("--now")
|
||||||
.arg(format!("{unit}")),
|
.arg(format!("{unit}")),
|
||||||
false,
|
|
||||||
)
|
)
|
||||||
.await?;
|
.await.map_err(StartSystemdUnitError::Command)?;
|
||||||
|
|
||||||
Ok(Self::Receipt {})
|
Ok(())
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
|
||||||
pub struct StartSystemdUnitReceipt {}
|
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
|
||||||
impl<'a> Revertable<'a> for StartSystemdUnitReceipt {
|
|
||||||
fn description(&self) -> Vec<ActionDescription> {
|
|
||||||
todo!()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn revert(self) -> Result<(), HarmonicError> {
|
async fn revert(&mut self) -> Result<(), ActionError> {
|
||||||
todo!();
|
todo!();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<ActionState<StartSystemdUnit>> for ActionState<Action> {
|
||||||
|
fn from(v: ActionState<StartSystemdUnit>) -> Self {
|
||||||
|
match 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)]
|
||||||
|
pub enum StartSystemdUnitError {
|
||||||
|
#[error("Failed to execute command")]
|
||||||
|
#[serde(serialize_with = "crate::serialize_std_io_error_to_display")]
|
||||||
|
Command(#[source] std::io::Error)
|
||||||
|
}
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::actions::{
|
use crate::actions::{
|
||||||
base::{
|
base::{
|
||||||
ConfigureNixDaemonService, ConfigureNixDaemonServiceReceipt, PlaceNixConfiguration,
|
ConfigureNixDaemonService, ConfigureNixDaemonServiceError, PlaceNixConfiguration,
|
||||||
PlaceNixConfigurationReceipt, SetupDefaultProfile, SetupDefaultProfileReceipt, PlaceChannelConfiguration, PlaceChannelConfigurationReceipt,
|
PlaceNixConfigurationError, SetupDefaultProfile, SetupDefaultProfileError, PlaceChannelConfiguration, PlaceChannelConfigurationError,
|
||||||
},
|
},
|
||||||
meta::{ConfigureShellProfile, ConfigureShellProfileReceipt},
|
meta::{ConfigureShellProfile, ConfigureShellProfileError}, ActionState, Action,
|
||||||
};
|
};
|
||||||
use crate::{HarmonicError, InstallSettings};
|
use crate::{HarmonicError, InstallSettings};
|
||||||
|
|
||||||
use crate::actions::{ActionDescription, Actionable, Revertable};
|
use crate::actions::{ActionDescription, Actionable};
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
pub struct ConfigureNix {
|
pub struct ConfigureNix {
|
||||||
|
@ -20,7 +22,7 @@ pub struct ConfigureNix {
|
||||||
|
|
||||||
impl ConfigureNix {
|
impl ConfigureNix {
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub async fn plan(settings: InstallSettings) -> Result<Self, HarmonicError> {
|
pub async fn plan(settings: InstallSettings) -> Result<ActionState<Self>, ConfigureNixError> {
|
||||||
let channels = settings
|
let channels = settings
|
||||||
.channels
|
.channels
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -51,8 +53,8 @@ impl ConfigureNix {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<'a> Actionable<'a> for ConfigureNix {
|
impl Actionable for ActionState<ConfigureNix> {
|
||||||
type Receipt = ConfigureNixReceipt;
|
type Error = ConfigureNixError;
|
||||||
fn description(&self) -> Vec<ActionDescription> {
|
fn description(&self) -> Vec<ActionDescription> {
|
||||||
let Self {
|
let Self {
|
||||||
setup_default_profile,
|
setup_default_profile,
|
||||||
|
@ -74,7 +76,7 @@ impl<'a> Actionable<'a> for ConfigureNix {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn execute(self) -> Result<Self::Receipt, HarmonicError> {
|
async fn execute(&mut self) -> Result<(), Self::Error> {
|
||||||
let Self {
|
let Self {
|
||||||
setup_default_profile,
|
setup_default_profile,
|
||||||
configure_nix_daemon_service,
|
configure_nix_daemon_service,
|
||||||
|
@ -114,34 +116,27 @@ impl<'a> Actionable<'a> for ConfigureNix {
|
||||||
configure_shell_profile,
|
configure_shell_profile,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
|
||||||
pub struct ConfigureNixReceipt {
|
|
||||||
setup_default_profile: SetupDefaultProfileReceipt,
|
|
||||||
configure_shell_profile: Option<ConfigureShellProfileReceipt>,
|
|
||||||
place_nix_configuration: PlaceNixConfigurationReceipt,
|
|
||||||
place_channel_configuration: PlaceChannelConfigurationReceipt,
|
|
||||||
configure_nix_daemon_service: ConfigureNixDaemonServiceReceipt,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
|
||||||
impl<'a> Revertable<'a> for ConfigureNixReceipt {
|
|
||||||
fn description(&self) -> Vec<ActionDescription> {
|
|
||||||
vec![
|
|
||||||
ActionDescription::new(
|
|
||||||
"Stop the systemd Nix daemon".to_string(),
|
|
||||||
vec![
|
|
||||||
"The `nix` command line tool communicates with a running Nix daemon managed by your init system".to_string()
|
|
||||||
]
|
|
||||||
),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn revert(self) -> Result<(), HarmonicError> {
|
async fn revert(&mut self) -> Result<(), Self::Error> {
|
||||||
todo!();
|
todo!();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<ActionState<ConfigureNix>> for ActionState<Action> {
|
||||||
|
fn from(v: ActionState<ConfigureNix>) -> Self {
|
||||||
|
match 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)]
|
||||||
|
pub enum ConfigureNixError {
|
||||||
|
|
||||||
|
}
|
|
@ -1,11 +1,12 @@
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
|
use serde::Serialize;
|
||||||
use tokio::task::JoinSet;
|
use tokio::task::JoinSet;
|
||||||
|
|
||||||
use crate::HarmonicError;
|
use crate::HarmonicError;
|
||||||
|
|
||||||
use crate::actions::base::{CreateOrAppendFile, CreateOrAppendFileReceipt};
|
use crate::actions::base::{CreateOrAppendFile, CreateOrAppendFileError};
|
||||||
use crate::actions::{ActionDescription, Actionable, Revertable};
|
use crate::actions::{ActionDescription, Actionable, ActionState, Action};
|
||||||
|
|
||||||
const PROFILE_TARGETS: &[&str] = &[
|
const PROFILE_TARGETS: &[&str] = &[
|
||||||
"/etc/bashrc",
|
"/etc/bashrc",
|
||||||
|
@ -24,7 +25,7 @@ pub struct ConfigureShellProfile {
|
||||||
|
|
||||||
impl ConfigureShellProfile {
|
impl ConfigureShellProfile {
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub async fn plan() -> Result<Self, HarmonicError> {
|
pub async fn plan() -> Result<ActionState<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);
|
||||||
|
@ -54,8 +55,8 @@ impl ConfigureShellProfile {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<'a> Actionable<'a> for ConfigureShellProfile {
|
impl Actionable for ActionState<ConfigureShellProfile> {
|
||||||
type Receipt = ConfigureShellProfileReceipt;
|
type Error = ConfigureShellProfileError;
|
||||||
fn description(&self) -> Vec<ActionDescription> {
|
fn description(&self) -> Vec<ActionDescription> {
|
||||||
vec![ActionDescription::new(
|
vec![ActionDescription::new(
|
||||||
"Configure the shell profiles".to_string(),
|
"Configure the shell profiles".to_string(),
|
||||||
|
@ -64,7 +65,7 @@ impl<'a> Actionable<'a> for ConfigureShellProfile {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn execute(self) -> Result<Self::Receipt, HarmonicError> {
|
async fn execute(&mut self) -> Result<(), Self::Error> {
|
||||||
let Self {
|
let Self {
|
||||||
create_or_append_files,
|
create_or_append_files,
|
||||||
} = self;
|
} = self;
|
||||||
|
@ -81,7 +82,7 @@ impl<'a> Actionable<'a> for ConfigureShellProfile {
|
||||||
|
|
||||||
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(())) => (),
|
||||||
Ok(Err(e)) => errors.push(e),
|
Ok(Err(e)) => errors.push(e),
|
||||||
Err(e) => errors.push(e.into()),
|
Err(e) => errors.push(e.into()),
|
||||||
};
|
};
|
||||||
|
@ -95,27 +96,29 @@ impl<'a> Actionable<'a> for ConfigureShellProfile {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Self::Receipt {
|
Ok(())
|
||||||
create_or_append_files: successes,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
|
||||||
pub struct ConfigureShellProfileReceipt {
|
|
||||||
create_or_append_files: Vec<CreateOrAppendFileReceipt>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
|
||||||
impl<'a> Revertable<'a> for ConfigureShellProfileReceipt {
|
|
||||||
fn description(&self) -> Vec<ActionDescription> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn revert(self) -> Result<(), HarmonicError> {
|
async fn revert(&mut self) -> Result<(), Self::Error> {
|
||||||
todo!();
|
todo!();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<ActionState<ConfigureShellProfile>> for ActionState<Action> {
|
||||||
|
fn from(v: ActionState<ConfigureShellProfile>) -> Self {
|
||||||
|
match 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)]
|
||||||
|
pub enum ConfigureShellProfileError {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::HarmonicError;
|
use crate::HarmonicError;
|
||||||
|
|
||||||
use crate::actions::base::{CreateDirectory, CreateDirectoryReceipt};
|
use crate::actions::base::{CreateDirectory, CreateDirectoryError};
|
||||||
use crate::actions::{ActionDescription, Actionable, Revertable};
|
use crate::actions::{ActionDescription, Actionable, ActionState, Action};
|
||||||
|
|
||||||
const PATHS: &[&str] = &[
|
const PATHS: &[&str] = &[
|
||||||
"/nix",
|
"/nix",
|
||||||
|
@ -41,8 +43,8 @@ impl CreateNixTree {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<'a> Actionable<'a> for CreateNixTree {
|
impl Actionable for ActionState<CreateNixTree> {
|
||||||
type Receipt = CreateNixTreeReceipt;
|
type Error = CreateNixTreeError;
|
||||||
fn description(&self) -> Vec<ActionDescription> {
|
fn description(&self) -> Vec<ActionDescription> {
|
||||||
vec![ActionDescription::new(
|
vec![ActionDescription::new(
|
||||||
format!("Create a directory tree in `/nix`"),
|
format!("Create a directory tree in `/nix`"),
|
||||||
|
@ -54,36 +56,38 @@ impl<'a> Actionable<'a> for CreateNixTree {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn execute(self) -> Result<Self::Receipt, HarmonicError> {
|
async fn execute(&mut self) -> Result<(), HarmonicError> {
|
||||||
let Self { create_directories } = self;
|
let Self { create_directories } = self;
|
||||||
|
|
||||||
let mut successes = Vec::with_capacity(create_directories.len());
|
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 {
|
||||||
successes.push(create_directory.execute().await?)
|
create_directory.execute().await?
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(CreateNixTreeReceipt {
|
Ok(())
|
||||||
create_directories: successes,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
|
||||||
pub struct CreateNixTreeReceipt {
|
|
||||||
create_directories: Vec<CreateDirectoryReceipt>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
|
||||||
impl<'a> Revertable<'a> for CreateNixTreeReceipt {
|
|
||||||
fn description(&self) -> Vec<ActionDescription> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn revert(self) -> Result<(), HarmonicError> {
|
async fn revert(&mut self) -> Result<(), Self::Error> {
|
||||||
todo!();
|
todo!();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<ActionState<CreateNixTree>> for ActionState<Action> {
|
||||||
|
fn from(v: ActionState<CreateNixTree>) -> Self {
|
||||||
|
match 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)]
|
||||||
|
pub enum CreateNixTreeError {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
|
use serde::Serialize;
|
||||||
use tokio::task::JoinSet;
|
use tokio::task::JoinSet;
|
||||||
|
|
||||||
use crate::{HarmonicError, InstallSettings};
|
use crate::{HarmonicError, InstallSettings};
|
||||||
|
|
||||||
use crate::actions::base::{CreateGroup, CreateGroupReceipt, CreateUserReceipt};
|
use crate::actions::base::{CreateGroup, CreateGroupError, CreateUserError};
|
||||||
use crate::actions::{ActionDescription, Actionable, CreateUser, Revertable};
|
use crate::actions::{ActionDescription, Actionable, CreateUser, ActionState, Action};
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
pub struct CreateUsersAndGroup {
|
pub struct CreateUsersAndGroup {
|
||||||
|
@ -47,8 +48,8 @@ impl CreateUsersAndGroup {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<'a> Actionable<'a> for CreateUsersAndGroup {
|
impl Actionable for ActionState<CreateUsersAndGroup> {
|
||||||
type Receipt = CreateUsersAndGroupReceipt;
|
type Error = CreateUsersAndGroupError;
|
||||||
fn description(&self) -> Vec<ActionDescription> {
|
fn description(&self) -> Vec<ActionDescription> {
|
||||||
let Self {
|
let Self {
|
||||||
daemon_user_count,
|
daemon_user_count,
|
||||||
|
@ -72,7 +73,7 @@ impl<'a> Actionable<'a> for CreateUsersAndGroup {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn execute(self) -> Result<Self::Receipt, HarmonicError> {
|
async fn execute(&mut self) -> Result<(), HarmonicError> {
|
||||||
let Self {
|
let Self {
|
||||||
create_users,
|
create_users,
|
||||||
create_group,
|
create_group,
|
||||||
|
@ -114,24 +115,29 @@ impl<'a> Actionable<'a> for CreateUsersAndGroup {
|
||||||
create_users: successes,
|
create_users: successes,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
|
||||||
pub struct CreateUsersAndGroupReceipt {
|
|
||||||
create_group: CreateGroupReceipt,
|
|
||||||
create_users: Vec<CreateUserReceipt>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
|
||||||
impl<'a> Revertable<'a> for CreateUsersAndGroupReceipt {
|
|
||||||
fn description(&self) -> Vec<ActionDescription> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn revert(self) -> Result<(), HarmonicError> {
|
async fn revert(&mut self) -> Result<(), Self::Error> {
|
||||||
todo!();
|
todo!();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl From<ActionState<CreateUsersAndGroup>> for ActionState<Action> {
|
||||||
|
fn from(v: ActionState<CreateUsersAndGroup>) -> Self {
|
||||||
|
match 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)]
|
||||||
|
pub enum CreateUsersAndGroupError {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,9 +7,9 @@ mod create_users_and_group;
|
||||||
mod provision_nix;
|
mod provision_nix;
|
||||||
mod start_nix_daemon;
|
mod start_nix_daemon;
|
||||||
|
|
||||||
pub use configure_nix::{ConfigureNix, ConfigureNixReceipt};
|
pub use configure_nix::{ConfigureNix, ConfigureNixError};
|
||||||
pub use configure_shell_profile::{ConfigureShellProfile, ConfigureShellProfileReceipt};
|
pub use configure_shell_profile::{ConfigureShellProfile, ConfigureShellProfileError};
|
||||||
pub use create_nix_tree::{CreateNixTree, CreateNixTreeReceipt};
|
pub use create_nix_tree::{CreateNixTree, CreateNixTreeError};
|
||||||
pub use create_users_and_group::{CreateUsersAndGroup, CreateUsersAndGroupReceipt};
|
pub use create_users_and_group::{CreateUsersAndGroup, CreateUsersAndGroupError};
|
||||||
pub use provision_nix::{ProvisionNix, ProvisionNixReceipt};
|
pub use provision_nix::{ProvisionNix, ProvisionNixError};
|
||||||
pub use start_nix_daemon::{StartNixDaemon, StartNixDaemonReceipt};
|
pub use start_nix_daemon::{StartNixDaemon, StartNixDaemonError};
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
|
use serde::Serialize;
|
||||||
use tempdir::TempDir;
|
use tempdir::TempDir;
|
||||||
|
|
||||||
use crate::actions::base::{FetchNix, FetchNixReceipt, MoveUnpackedNix, MoveUnpackedNixReceipt};
|
use crate::actions::base::{FetchNix, FetchNixError, MoveUnpackedNix, MoveUnpackedNixError};
|
||||||
use crate::{HarmonicError, InstallSettings};
|
use crate::{HarmonicError, InstallSettings};
|
||||||
|
|
||||||
use crate::actions::{ActionDescription, Actionable, Revertable};
|
use crate::actions::{ActionDescription, Actionable, ActionState, Action};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
CreateNixTree, CreateNixTreeReceipt,
|
CreateNixTree, CreateNixTreeError,
|
||||||
CreateUsersAndGroup, CreateUsersAndGroupReceipt,
|
CreateUsersAndGroup, CreateUsersAndGroupError,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
|
@ -20,8 +21,8 @@ pub struct ProvisionNix {
|
||||||
|
|
||||||
impl ProvisionNix {
|
impl ProvisionNix {
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub async fn plan(settings: InstallSettings) -> Result<Self, HarmonicError> {
|
pub async fn plan(settings: InstallSettings) -> Result<ActionState<Self>, ProvisionNixError> {
|
||||||
let tempdir = TempDir::new("nix").map_err(HarmonicError::TempDir)?;
|
let tempdir = TempDir::new("nix").map_err(ProvisionNixError::TempDir)?;
|
||||||
|
|
||||||
let fetch_nix = FetchNix::plan(
|
let fetch_nix = FetchNix::plan(
|
||||||
settings.nix_package_url.clone(),
|
settings.nix_package_url.clone(),
|
||||||
|
@ -31,19 +32,24 @@ 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(Self {
|
Ok(ActionState::Planned(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,
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<'a> Actionable<'a> for ProvisionNix {
|
impl Actionable for ActionState<ProvisionNix> {
|
||||||
type Receipt = ProvisionNixReceipt;
|
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,
|
||||||
|
@ -60,7 +66,7 @@ impl<'a> Actionable<'a> for ProvisionNix {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn execute(self) -> Result<Self::Receipt, HarmonicError> {
|
async fn execute(&mut self) -> Result<(), Self::Error> {
|
||||||
let Self {
|
let Self {
|
||||||
fetch_nix,
|
fetch_nix,
|
||||||
create_nix_tree,
|
create_nix_tree,
|
||||||
|
@ -71,39 +77,37 @@ impl<'a> Actionable<'a> for ProvisionNix {
|
||||||
// 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 fetch_nix_handle = tokio::spawn(async move { fetch_nix.execute().await });
|
||||||
|
|
||||||
let create_users_and_group = create_users_and_group.execute().await?;
|
create_users_and_group.execute().await?;
|
||||||
let create_nix_tree = create_nix_tree.execute().await?;
|
create_nix_tree.execute().await?;
|
||||||
|
|
||||||
let fetch_nix = fetch_nix_handle.await??;
|
fetch_nix_handle.await??;
|
||||||
let move_unpacked_nix = move_unpacked_nix.execute().await?;
|
move_unpacked_nix.execute().await?;
|
||||||
|
|
||||||
Ok(ProvisionNixReceipt {
|
Ok(())
|
||||||
fetch_nix,
|
|
||||||
create_users_and_group,
|
|
||||||
create_nix_tree,
|
|
||||||
move_unpacked_nix,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
|
||||||
pub struct ProvisionNixReceipt {
|
|
||||||
fetch_nix: FetchNixReceipt,
|
|
||||||
create_users_and_group: CreateUsersAndGroupReceipt,
|
|
||||||
create_nix_tree: CreateNixTreeReceipt,
|
|
||||||
move_unpacked_nix: MoveUnpackedNixReceipt,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
|
||||||
impl<'a> Revertable<'a> for ProvisionNixReceipt {
|
|
||||||
fn description(&self) -> Vec<ActionDescription> {
|
|
||||||
todo!()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn revert(self) -> Result<(), HarmonicError> {
|
async fn revert(&mut self) -> Result<(), Self::Error> {
|
||||||
todo!();
|
todo!();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<ActionState<ProvisionNix>> for ActionState<Action> {
|
||||||
|
fn from(v: ActionState<ProvisionNix>) -> Self {
|
||||||
|
match v {
|
||||||
|
ActionState::Completed(_) => ActionState::Completed(Action::ProvisionNix(v)),
|
||||||
|
ActionState::Planned(_) => ActionState::Planned(Action::ProvisionNix(v)),
|
||||||
|
ActionState::Reverted(_) => ActionState::Reverted(Action::ProvisionNix(v)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error, Serialize)]
|
||||||
|
pub enum ProvisionNixError {
|
||||||
|
#[error("Failed create tempdir")]
|
||||||
|
#[serde(serialize_with = "crate::serialize_std_io_error_to_display")]
|
||||||
|
TempDir(#[source] std::io::Error)
|
||||||
|
}
|
||||||
|
|
|
@ -1,62 +1,80 @@
|
||||||
use crate::actions::base::{StartSystemdUnit, StartSystemdUnitReceipt};
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::actions::base::{StartSystemdUnit, StartSystemdUnitError};
|
||||||
use crate::HarmonicError;
|
use crate::HarmonicError;
|
||||||
|
|
||||||
use crate::actions::{ActionDescription, Actionable, Revertable};
|
use crate::actions::{ActionDescription, Actionable, ActionState, Action, ActionError};
|
||||||
|
|
||||||
/// 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: StartSystemdUnit,
|
start_systemd_socket: ActionState<StartSystemdUnit>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StartNixDaemon {
|
impl StartNixDaemon {
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub async fn plan() -> Result<Self, HarmonicError> {
|
pub async fn plan() -> Result<ActionState<Self>, StartNixDaemonError> {
|
||||||
let start_systemd_socket = StartSystemdUnit::plan("nix-daemon.socket".into()).await?;
|
let start_systemd_socket = StartSystemdUnit::plan("nix-daemon.socket".into()).await?;
|
||||||
let start_systemd_service = StartSystemdUnit::plan("nix-daemon.service".into()).await?;
|
Ok(ActionState::Planned(Self {
|
||||||
Ok(Self {
|
|
||||||
start_systemd_socket,
|
start_systemd_socket,
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<'a> Actionable<'a> for StartNixDaemon {
|
impl Actionable for ActionState<StartNixDaemon> {
|
||||||
type Receipt = StartNixDaemonReceipt;
|
type Error = StartNixDaemonError;
|
||||||
|
|
||||||
fn description(&self) -> Vec<ActionDescription> {
|
fn description(&self) -> Vec<ActionDescription> {
|
||||||
let Self {
|
let StartNixDaemon { start_systemd_socket } = match self {
|
||||||
start_systemd_socket,
|
ActionState::Completed(v) | ActionState::Reverted(v) | ActionState::Planned(v) => v,
|
||||||
} = &self;
|
};
|
||||||
start_systemd_socket.description()
|
start_systemd_socket.description()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn execute(self) -> Result<Self::Receipt, HarmonicError> {
|
async fn execute(self) -> Result<Self, ActionError> {
|
||||||
let Self {
|
let StartNixDaemon { start_systemd_socket } = match self {
|
||||||
start_systemd_socket,
|
ActionState::Planned(v) => v,
|
||||||
} = self;
|
ActionState::Completed(_) => return Err(ActionError::AlreadyExecuted(self.clone().into())),
|
||||||
let start_systemd_socket = start_systemd_socket.execute().await?;
|
ActionState::Reverted(_) => return Err(ActionError::AlreadyReverted(self.clone().into())),
|
||||||
Ok(Self::Receipt {
|
};
|
||||||
start_systemd_socket,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
start_systemd_socket.execute().await?;
|
||||||
pub struct StartNixDaemonReceipt {
|
|
||||||
start_systemd_socket: StartSystemdUnitReceipt,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
Ok(Self::Completed(StartNixDaemon {
|
||||||
impl<'a> Revertable<'a> for StartNixDaemonReceipt {
|
start_systemd_socket,
|
||||||
fn description(&self) -> Vec<ActionDescription> {
|
}))
|
||||||
todo!()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn revert(self) -> Result<(), HarmonicError> {
|
async fn revert(self) -> Result<Self, ActionError> {
|
||||||
todo!();
|
let StartNixDaemon { start_systemd_socket } = match self {
|
||||||
|
ActionState::Planned(v) => return Err(ActionError::NotExecuted(self.clone().into())),
|
||||||
|
ActionState::Completed(v) => v,
|
||||||
|
ActionState::Reverted(_) => return Err(ActionError::AlreadyReverted(self.clone().into())),
|
||||||
|
};
|
||||||
|
|
||||||
Ok(())
|
start_systemd_socket.revert().await?;
|
||||||
|
|
||||||
|
Ok(Self::Reverted(StartNixDaemon {
|
||||||
|
start_systemd_socket,
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<ActionState<StartNixDaemon>> for ActionState<Action> {
|
||||||
|
fn from(v: ActionState<StartNixDaemon>) -> Self {
|
||||||
|
match v {
|
||||||
|
ActionState::Completed(_) => ActionState::Completed(Action::StartNixDaemon(v)),
|
||||||
|
ActionState::Planned(_) => ActionState::Planned(Action::StartNixDaemon(v)),
|
||||||
|
ActionState::Reverted(_) => ActionState::Reverted(Action::StartNixDaemon(v)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error, Serialize)]
|
||||||
|
pub enum StartNixDaemonError {
|
||||||
|
#[error(transparent)]
|
||||||
|
StartSystemdUnit(#[from] StartSystemdUnitError)
|
||||||
|
}
|
||||||
|
|
|
@ -1,33 +1,45 @@
|
||||||
pub mod base;
|
pub mod base;
|
||||||
pub mod meta;
|
pub mod meta;
|
||||||
|
|
||||||
|
use std::{error::Error, fmt::Display};
|
||||||
|
|
||||||
use base::{
|
use base::{
|
||||||
ConfigureNixDaemonService, ConfigureNixDaemonServiceReceipt, CreateDirectory,
|
ConfigureNixDaemonService, ConfigureNixDaemonServiceError, CreateDirectory,
|
||||||
CreateDirectoryReceipt, CreateFile, CreateFileReceipt, CreateGroup, CreateGroupReceipt,
|
CreateDirectoryError, CreateFile, CreateFileError, CreateGroup, CreateGroupError,
|
||||||
CreateOrAppendFile, CreateOrAppendFileReceipt, CreateUser, CreateUserReceipt, FetchNix,
|
CreateOrAppendFile, CreateOrAppendFileError, CreateUser, CreateUserError, FetchNix,
|
||||||
FetchNixReceipt, MoveUnpackedNix, MoveUnpackedNixReceipt, PlaceChannelConfiguration,
|
FetchNixError, MoveUnpackedNix, MoveUnpackedNixError, PlaceChannelConfiguration,
|
||||||
PlaceChannelConfigurationReceipt, PlaceNixConfiguration, PlaceNixConfigurationReceipt,
|
PlaceChannelConfigurationError, PlaceNixConfiguration, PlaceNixConfigurationError,
|
||||||
SetupDefaultProfile, SetupDefaultProfileReceipt,
|
SetupDefaultProfile, SetupDefaultProfileError,
|
||||||
};
|
};
|
||||||
use meta::{
|
use meta::{
|
||||||
ConfigureNix, ConfigureNixReceipt, ConfigureShellProfile, ConfigureShellProfileReceipt,
|
ConfigureNix, ConfigureNixError, ConfigureShellProfile, ConfigureShellProfileError,
|
||||||
CreateNixTree, CreateNixTreeReceipt, CreateUsersAndGroup, CreateUsersAndGroupReceipt,
|
CreateNixTree, CreateNixTreeError, CreateUsersAndGroup, CreateUsersAndGroupError,
|
||||||
ProvisionNix, ProvisionNixReceipt, StartNixDaemon, StartNixDaemonReceipt,
|
ProvisionNix, ProvisionNixError, StartNixDaemon, StartNixDaemonError,
|
||||||
};
|
};
|
||||||
|
use serde::{Deserialize, de::DeserializeOwned, Serialize};
|
||||||
|
|
||||||
use crate::HarmonicError;
|
|
||||||
|
use self::base::{StartSystemdUnit, StartSystemdUnitError};
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
pub trait Actionable<'a>: serde::de::Deserialize<'a> + serde::Serialize {
|
pub trait Actionable: DeserializeOwned + Serialize + Into<ActionState<Action>> {
|
||||||
type Receipt;
|
type Error: std::error::Error + std::fmt::Debug + Serialize;
|
||||||
|
|
||||||
fn description(&self) -> Vec<ActionDescription>;
|
fn description(&self) -> Vec<ActionDescription>;
|
||||||
async fn execute(self) -> Result<Self::Receipt, HarmonicError>;
|
|
||||||
|
// They should also have an `async fn plan(args...) -> Result<ActionState<Self>, Self::Error>;`
|
||||||
|
async fn execute(self) -> Result<Self, ActionError>;
|
||||||
|
async fn revert(self) -> Result<Self, ActionError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[derive(thiserror::Error, Debug, Serialize, Deserialize, Clone)]
|
||||||
pub trait Revertable<'a>: serde::de::Deserialize<'a> + serde::Serialize {
|
pub enum ActionState<P> where P: Serialize + DeserializeOwned + Clone {
|
||||||
fn description(&self) -> Vec<ActionDescription>;
|
#[serde(bound = "P: DeserializeOwned")]
|
||||||
async fn revert(self) -> Result<(), HarmonicError>;
|
Completed(P),
|
||||||
|
#[serde(bound = "P: DeserializeOwned")]
|
||||||
|
Planned(P),
|
||||||
|
#[serde(bound = "P: DeserializeOwned")]
|
||||||
|
Reverted(P),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
|
@ -48,51 +60,80 @@ impl ActionDescription {
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
pub enum Action {
|
pub enum Action {
|
||||||
ConfigureNixDaemonService(ConfigureNixDaemonService),
|
ConfigureNixDaemonService(ActionState<ConfigureNixDaemonService>),
|
||||||
ConfigureNix(ConfigureNix),
|
ConfigureNix(ActionState<ConfigureNix>),
|
||||||
ConfigureShellProfile(ConfigureShellProfile),
|
ConfigureShellProfile(ActionState<ConfigureShellProfile>),
|
||||||
CreateDirectory(CreateDirectory),
|
CreateDirectory(ActionState<CreateDirectory>),
|
||||||
CreateFile(CreateFile),
|
CreateFile(ActionState<CreateFile>),
|
||||||
CreateGroup(CreateGroup),
|
CreateGroup(ActionState<CreateGroup>),
|
||||||
CreateOrAppendFile(CreateOrAppendFile),
|
CreateOrAppendFile(ActionState<CreateOrAppendFile>),
|
||||||
CreateNixTree(CreateNixTree),
|
CreateNixTree(ActionState<CreateNixTree>),
|
||||||
CreateUser(CreateUser),
|
CreateUser(ActionState<CreateUser>),
|
||||||
CreateUsersAndGroup(CreateUsersAndGroup),
|
CreateUsersAndGroup(ActionState<CreateUsersAndGroup>),
|
||||||
FetchNix(FetchNix),
|
FetchNix(ActionState<FetchNix>),
|
||||||
MoveUnpackedNix(MoveUnpackedNix),
|
MoveUnpackedNix(ActionState<MoveUnpackedNix>),
|
||||||
PlaceChannelConfiguration(PlaceChannelConfiguration),
|
PlaceChannelConfiguration(ActionState<PlaceChannelConfiguration>),
|
||||||
PlaceNixConfiguration(PlaceNixConfiguration),
|
PlaceNixConfiguration(ActionState<PlaceNixConfiguration>),
|
||||||
SetupDefaultProfile(SetupDefaultProfile),
|
SetupDefaultProfile(ActionState<SetupDefaultProfile>),
|
||||||
StartNixDaemon(StartNixDaemon),
|
StartNixDaemon(ActionState<StartNixDaemon>),
|
||||||
ProvisionNix(ProvisionNix),
|
StartSystemdUnit(ActionState<StartSystemdUnit>),
|
||||||
|
ProvisionNix(ActionState<ProvisionNix>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, thiserror::Error, serde::Serialize)]
|
||||||
pub enum ActionReceipt {
|
pub enum ActionError {
|
||||||
ConfigureNixDaemonService(ConfigureNixDaemonServiceReceipt),
|
#[error("Attempted to revert an unexecuted action")]
|
||||||
ConfigureNix(ConfigureNixReceipt),
|
NotExecuted(ActionState<Action>),
|
||||||
ConfigureShellProfile(ConfigureShellProfileReceipt),
|
#[error("Attempted to execute an already executed action")]
|
||||||
CreateDirectory(CreateDirectoryReceipt),
|
AlreadyExecuted(ActionState<Action>),
|
||||||
CreateFile(CreateFileReceipt),
|
#[error("Attempted to revert an already reverted action")]
|
||||||
CreateGroup(CreateGroupReceipt),
|
AlreadyReverted(ActionState<Action>),
|
||||||
CreateOrAppendFile(CreateOrAppendFileReceipt),
|
#[error(transparent)]
|
||||||
CreateNixTree(CreateNixTreeReceipt),
|
ConfigureNixDaemonService(#[from] ConfigureNixDaemonServiceError),
|
||||||
CreateUser(CreateUserReceipt),
|
#[error(transparent)]
|
||||||
CreateUsersAndGroup(CreateUsersAndGroupReceipt),
|
ConfigureNix(#[from] ConfigureNixError),
|
||||||
FetchNix(FetchNixReceipt),
|
#[error(transparent)]
|
||||||
MoveUnpackedNix(MoveUnpackedNixReceipt),
|
ConfigureShellProfile(#[from] ConfigureShellProfileError),
|
||||||
PlaceChannelConfiguration(PlaceChannelConfigurationReceipt),
|
#[error(transparent)]
|
||||||
PlaceNixConfiguration(PlaceNixConfigurationReceipt),
|
CreateDirectory(#[from] CreateDirectoryError),
|
||||||
SetupDefaultProfile(SetupDefaultProfileReceipt),
|
#[error(transparent)]
|
||||||
StartNixDaemon(StartNixDaemonReceipt),
|
CreateFile(#[from] CreateFileError),
|
||||||
ProvisionNix(ProvisionNixReceipt),
|
#[error(transparent)]
|
||||||
|
CreateGroup(#[from] CreateGroupError),
|
||||||
|
#[error(transparent)]
|
||||||
|
CreateOrAppendFile(#[from] CreateOrAppendFileError),
|
||||||
|
#[error(transparent)]
|
||||||
|
CreateNixTree(#[from] CreateNixTreeError),
|
||||||
|
#[error(transparent)]
|
||||||
|
CreateUser(#[from] CreateUserError),
|
||||||
|
#[error(transparent)]
|
||||||
|
CreateUsersAndGroup(#[from] CreateUsersAndGroupError),
|
||||||
|
#[error(transparent)]
|
||||||
|
FetchNix(#[from] FetchNixError),
|
||||||
|
#[error(transparent)]
|
||||||
|
MoveUnpackedNix(#[from] MoveUnpackedNixError),
|
||||||
|
#[error(transparent)]
|
||||||
|
PlaceChannelConfiguration(#[from] PlaceChannelConfigurationError),
|
||||||
|
#[error(transparent)]
|
||||||
|
PlaceNixConfiguration(#[from] PlaceNixConfigurationError),
|
||||||
|
#[error(transparent)]
|
||||||
|
SetupDefaultProfile(#[from] SetupDefaultProfileError),
|
||||||
|
#[error(transparent)]
|
||||||
|
StartNixDaemon(#[from] StartNixDaemonError),
|
||||||
|
#[error(transparent)]
|
||||||
|
StartSystemdUnit(#[from] StartSystemdUnitError),
|
||||||
|
#[error(transparent)]
|
||||||
|
ProvisionNix(#[from] ProvisionNixError),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<'a> Actionable<'a> for Action {
|
impl Actionable for ActionState<Action> {
|
||||||
type Receipt = ActionReceipt;
|
type Error = ActionError;
|
||||||
fn description(&self) -> Vec<ActionDescription> {
|
fn description(&self) -> Vec<ActionDescription> {
|
||||||
match self {
|
let inner = 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(),
|
||||||
|
@ -109,92 +150,64 @@ impl<'a> Actionable<'a> for Action {
|
||||||
Action::PlaceNixConfiguration(i) => i.description(),
|
Action::PlaceNixConfiguration(i) => i.description(),
|
||||||
Action::SetupDefaultProfile(i) => i.description(),
|
Action::SetupDefaultProfile(i) => i.description(),
|
||||||
Action::StartNixDaemon(i) => i.description(),
|
Action::StartNixDaemon(i) => i.description(),
|
||||||
|
Action::StartSystemdUnit(i) => i.description(),
|
||||||
Action::ProvisionNix(i) => i.description(),
|
Action::ProvisionNix(i) => i.description(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn execute(self) -> Result<Self::Receipt, HarmonicError> {
|
async fn execute(&mut self) -> Result<(), Self::Error> {
|
||||||
match self {
|
let inner = match self {
|
||||||
Action::ConfigureNixDaemonService(i) => i
|
ActionState::Completed(p) => todo!(),
|
||||||
.execute()
|
ActionState::Planned(p) => p,
|
||||||
.await
|
ActionState::Reverted(p) => todo!(),
|
||||||
.map(ActionReceipt::ConfigureNixDaemonService),
|
};
|
||||||
Action::ConfigureNix(i) => i.execute().await.map(ActionReceipt::ConfigureNix),
|
match inner {
|
||||||
Action::ConfigureShellProfile(i) => {
|
Action::ConfigureNixDaemonService(i) => i.execute().await,
|
||||||
i.execute().await.map(ActionReceipt::ConfigureShellProfile)
|
Action::ConfigureNix(i) => i.execute().await,
|
||||||
},
|
Action::ConfigureShellProfile(i) => i.execute().await,
|
||||||
Action::CreateDirectory(i) => i.execute().await.map(ActionReceipt::CreateDirectory),
|
Action::CreateDirectory(i) => i.execute().await,
|
||||||
Action::CreateFile(i) => i.execute().await.map(ActionReceipt::CreateFile),
|
Action::CreateFile(i) => i.execute().await,
|
||||||
Action::CreateGroup(i) => i.execute().await.map(ActionReceipt::CreateGroup),
|
Action::CreateGroup(i) => i.execute().await,
|
||||||
Action::CreateOrAppendFile(i) => {
|
Action::CreateOrAppendFile(i) => i.execute().await,
|
||||||
i.execute().await.map(ActionReceipt::CreateOrAppendFile)
|
Action::CreateNixTree(i) => i.execute().await,
|
||||||
},
|
Action::CreateUser(i) => i.execute().await,
|
||||||
Action::CreateNixTree(i) => i.execute().await.map(ActionReceipt::CreateNixTree),
|
Action::CreateUsersAndGroup(i) => i.execute().await,
|
||||||
Action::CreateUser(i) => i.execute().await.map(ActionReceipt::CreateUser),
|
Action::FetchNix(i) => i.execute().await,
|
||||||
Action::CreateUsersAndGroup(i) => {
|
Action::MoveUnpackedNix(i) => i.execute().await,
|
||||||
i.execute().await.map(ActionReceipt::CreateUsersAndGroup)
|
Action::PlaceChannelConfiguration(i) => i.execute().await,
|
||||||
},
|
Action::PlaceNixConfiguration(i) => i.execute().await,
|
||||||
Action::FetchNix(i) => i.execute().await.map(ActionReceipt::FetchNix),
|
Action::SetupDefaultProfile(i) => i.execute().await,
|
||||||
Action::MoveUnpackedNix(i) => i.execute().await.map(ActionReceipt::MoveUnpackedNix),
|
Action::StartNixDaemon(i) => i.execute().await,
|
||||||
Action::PlaceChannelConfiguration(i) => i
|
Action::StartSystemdUnit(i) => i.execute().await,
|
||||||
.execute()
|
Action::ProvisionNix(i) => i.execute().await,
|
||||||
.await
|
|
||||||
.map(ActionReceipt::PlaceChannelConfiguration),
|
|
||||||
Action::PlaceNixConfiguration(i) => {
|
|
||||||
i.execute().await.map(ActionReceipt::PlaceNixConfiguration)
|
|
||||||
},
|
|
||||||
Action::SetupDefaultProfile(i) => {
|
|
||||||
i.execute().await.map(ActionReceipt::SetupDefaultProfile)
|
|
||||||
},
|
|
||||||
Action::StartNixDaemon(i) => i.execute().await.map(ActionReceipt::StartNixDaemon),
|
|
||||||
Action::ProvisionNix(i) => i.execute().await.map(ActionReceipt::ProvisionNix),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
|
||||||
impl<'a> Revertable<'a> for ActionReceipt {
|
|
||||||
fn description(&self) -> Vec<ActionDescription> {
|
|
||||||
match self {
|
|
||||||
ActionReceipt::ConfigureNixDaemonService(i) => i.description(),
|
|
||||||
ActionReceipt::ConfigureNix(i) => i.description(),
|
|
||||||
ActionReceipt::ConfigureShellProfile(i) => i.description(),
|
|
||||||
ActionReceipt::CreateDirectory(i) => i.description(),
|
|
||||||
ActionReceipt::CreateFile(i) => i.description(),
|
|
||||||
ActionReceipt::CreateGroup(i) => i.description(),
|
|
||||||
ActionReceipt::CreateOrAppendFile(i) => i.description(),
|
|
||||||
ActionReceipt::CreateNixTree(i) => i.description(),
|
|
||||||
ActionReceipt::CreateUser(i) => i.description(),
|
|
||||||
ActionReceipt::CreateUsersAndGroup(i) => i.description(),
|
|
||||||
ActionReceipt::FetchNix(i) => i.description(),
|
|
||||||
ActionReceipt::MoveUnpackedNix(i) => i.description(),
|
|
||||||
ActionReceipt::PlaceChannelConfiguration(i) => i.description(),
|
|
||||||
ActionReceipt::PlaceNixConfiguration(i) => i.description(),
|
|
||||||
ActionReceipt::SetupDefaultProfile(i) => i.description(),
|
|
||||||
ActionReceipt::StartNixDaemon(i) => i.description(),
|
|
||||||
ActionReceipt::ProvisionNix(i) => i.description(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn revert(self) -> Result<(), HarmonicError> {
|
async fn revert(&mut self) -> Result<(), Self::Error> {
|
||||||
match self {
|
let inner = match self {
|
||||||
ActionReceipt::ConfigureNixDaemonService(i) => i.revert().await,
|
ActionState::Planned(p) => todo!(),
|
||||||
ActionReceipt::ConfigureNix(i) => i.revert().await,
|
ActionState::Completed(p) => p,
|
||||||
ActionReceipt::ConfigureShellProfile(i) => i.revert().await,
|
ActionState::Reverted(p) => todo!(),
|
||||||
ActionReceipt::CreateDirectory(i) => i.revert().await,
|
};
|
||||||
ActionReceipt::CreateFile(i) => i.revert().await,
|
match inner {
|
||||||
ActionReceipt::CreateGroup(i) => i.revert().await,
|
Action::ConfigureNixDaemonService(i) => i.revert().await,
|
||||||
ActionReceipt::CreateOrAppendFile(i) => i.revert().await,
|
Action::ConfigureNix(i) => i.revert().await,
|
||||||
ActionReceipt::CreateNixTree(i) => i.revert().await,
|
Action::ConfigureShellProfile(i) => i.revert().await,
|
||||||
ActionReceipt::CreateUser(i) => i.revert().await,
|
Action::CreateDirectory(i) => i.revert().await,
|
||||||
ActionReceipt::CreateUsersAndGroup(i) => i.revert().await,
|
Action::CreateFile(i) => i.revert().await,
|
||||||
ActionReceipt::FetchNix(i) => i.revert().await,
|
Action::CreateGroup(i) => i.revert().await,
|
||||||
ActionReceipt::MoveUnpackedNix(i) => i.revert().await,
|
Action::CreateOrAppendFile(i) => i.revert().await,
|
||||||
ActionReceipt::PlaceChannelConfiguration(i) => i.revert().await,
|
Action::CreateNixTree(i) => i.revert().await,
|
||||||
ActionReceipt::PlaceNixConfiguration(i) => i.revert().await,
|
Action::CreateUser(i) => i.revert().await,
|
||||||
ActionReceipt::SetupDefaultProfile(i) => i.revert().await,
|
Action::CreateUsersAndGroup(i) => i.revert().await,
|
||||||
ActionReceipt::StartNixDaemon(i) => i.revert().await,
|
Action::FetchNix(i) => i.revert().await,
|
||||||
ActionReceipt::ProvisionNix(i) => i.revert().await,
|
Action::MoveUnpackedNix(i) => i.revert().await,
|
||||||
|
Action::PlaceChannelConfiguration(i) => i.revert().await,
|
||||||
|
Action::PlaceNixConfiguration(i) => i.revert().await,
|
||||||
|
Action::SetupDefaultProfile(i) => i.revert().await,
|
||||||
|
Action::StartNixDaemon(i) => i.revert().await,
|
||||||
|
Action::StartSystemdUnit(i) => i.revert().await,
|
||||||
|
Action::ProvisionNix(i) => i.revert().await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
419
src/lib.rs
419
src/lib.rs
|
@ -14,6 +14,7 @@ use std::{
|
||||||
|
|
||||||
pub use error::HarmonicError;
|
pub use error::HarmonicError;
|
||||||
pub use plan::InstallPlan;
|
pub use plan::InstallPlan;
|
||||||
|
use serde::Serializer;
|
||||||
pub use settings::InstallSettings;
|
pub use settings::InstallSettings;
|
||||||
|
|
||||||
use bytes::Buf;
|
use bytes::Buf;
|
||||||
|
@ -26,402 +27,6 @@ use tokio::{
|
||||||
task::spawn_blocking,
|
task::spawn_blocking,
|
||||||
};
|
};
|
||||||
|
|
||||||
// This uses a Rust builder pattern
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Harmonic {
|
|
||||||
dry_run: bool,
|
|
||||||
daemon_user_count: usize,
|
|
||||||
channels: Vec<(String, Url)>,
|
|
||||||
modify_profile: bool,
|
|
||||||
nix_build_group_name: String,
|
|
||||||
nix_build_group_id: usize,
|
|
||||||
nix_build_user_prefix: String,
|
|
||||||
nix_build_user_id_base: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Harmonic {
|
|
||||||
pub fn dry_run(&mut self, dry_run: bool) -> &mut Self {
|
|
||||||
self.dry_run = dry_run;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
pub fn daemon_user_count(&mut self, count: usize) -> &mut Self {
|
|
||||||
self.daemon_user_count = count;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn channels(&mut self, channels: impl IntoIterator<Item = (String, Url)>) -> &mut Self {
|
|
||||||
self.channels = channels.into_iter().collect();
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn modify_profile(&mut self, toggle: bool) -> &mut Self {
|
|
||||||
self.modify_profile = toggle;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Harmonic {
|
|
||||||
#[tracing::instrument(skip_all)]
|
|
||||||
pub async fn fetch_nix(&self) -> Result<(), HarmonicError> {
|
|
||||||
tracing::info!("Fetching nix");
|
|
||||||
// TODO(@hoverbear): architecture specific download
|
|
||||||
// TODO(@hoverbear): hash check
|
|
||||||
// TODO(@hoverbear): custom url
|
|
||||||
let tempdir = TempDir::new("nix").map_err(HarmonicError::TempDir)?;
|
|
||||||
fetch_url_and_unpack_xz(
|
|
||||||
"https://releases.nixos.org/nix/nix-2.11.0/nix-2.11.0-x86_64-linux.tar.xz",
|
|
||||||
tempdir.path(),
|
|
||||||
self.dry_run,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let found_nix_path = if !self.dry_run {
|
|
||||||
// TODO(@Hoverbear): I would like to make this less awful
|
|
||||||
let found_nix_paths = glob::glob(&format!("{}/nix-*", tempdir.path().display()))?
|
|
||||||
.collect::<Result<Vec<_>, _>>()?;
|
|
||||||
assert_eq!(
|
|
||||||
found_nix_paths.len(),
|
|
||||||
1,
|
|
||||||
"Did not expect to find multiple nix paths, please report this"
|
|
||||||
);
|
|
||||||
found_nix_paths.into_iter().next().unwrap()
|
|
||||||
} else {
|
|
||||||
PathBuf::from("/nix/nix-*")
|
|
||||||
};
|
|
||||||
rename(found_nix_path.join("store"), "/nix/store", self.dry_run).await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
|
||||||
pub async fn create_group(&self) -> Result<(), HarmonicError> {
|
|
||||||
tracing::info!("Creating group");
|
|
||||||
execute_command(
|
|
||||||
Command::new("groupadd")
|
|
||||||
.arg("-g")
|
|
||||||
.arg(self.nix_build_group_id.to_string())
|
|
||||||
.arg("--system")
|
|
||||||
.arg(&self.nix_build_group_name),
|
|
||||||
self.dry_run,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
|
||||||
pub async fn create_users(&self) -> Result<(), HarmonicError> {
|
|
||||||
tracing::info!("Creating users");
|
|
||||||
for index in 1..=self.daemon_user_count {
|
|
||||||
let user_name = format!("{}{index}", self.nix_build_user_prefix);
|
|
||||||
let user_id = self.nix_build_user_id_base + index;
|
|
||||||
execute_command(
|
|
||||||
Command::new("useradd").args([
|
|
||||||
"--home-dir",
|
|
||||||
"/var/empty",
|
|
||||||
"--comment",
|
|
||||||
&format!("\"Nix build user {user_id}\""),
|
|
||||||
"--gid",
|
|
||||||
&self.nix_build_group_name.to_string(),
|
|
||||||
"--groups",
|
|
||||||
&self.nix_build_group_name.to_string(),
|
|
||||||
"--no-user-group",
|
|
||||||
"--system",
|
|
||||||
"--shell",
|
|
||||||
"/sbin/nologin",
|
|
||||||
"--uid",
|
|
||||||
&user_id.to_string(),
|
|
||||||
"--password",
|
|
||||||
"\"!\"",
|
|
||||||
&user_name.to_string(),
|
|
||||||
]),
|
|
||||||
self.dry_run,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
|
||||||
pub async fn create_directories(&self) -> Result<(), HarmonicError> {
|
|
||||||
tracing::info!("Creating directories");
|
|
||||||
create_directory("/nix", self.dry_run).await?;
|
|
||||||
set_permissions(
|
|
||||||
"/nix",
|
|
||||||
None,
|
|
||||||
Some("root".to_string()),
|
|
||||||
Some(self.nix_build_group_name.clone()),
|
|
||||||
self.dry_run,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let permissions = Permissions::from_mode(0o0755);
|
|
||||||
let paths = [
|
|
||||||
"/nix/var",
|
|
||||||
"/nix/var/log",
|
|
||||||
"/nix/var/log/nix",
|
|
||||||
"/nix/var/log/nix/drvs",
|
|
||||||
"/nix/var/nix",
|
|
||||||
"/nix/var/nix/db",
|
|
||||||
"/nix/var/nix/gcroots",
|
|
||||||
"/nix/var/nix/gcroots/per-user",
|
|
||||||
"/nix/var/nix/profiles",
|
|
||||||
"/nix/var/nix/profiles/per-user",
|
|
||||||
"/nix/var/nix/temproots",
|
|
||||||
"/nix/var/nix/userpool",
|
|
||||||
"/nix/var/nix/daemon-socket",
|
|
||||||
];
|
|
||||||
|
|
||||||
for path in paths {
|
|
||||||
// We use `create_dir` over `create_dir_all` to ensure we always set permissions right
|
|
||||||
create_directory(path, self.dry_run).await?;
|
|
||||||
set_permissions(path, Some(permissions.clone()), None, None, self.dry_run).await?;
|
|
||||||
}
|
|
||||||
create_directory("/nix/store", self.dry_run).await?;
|
|
||||||
set_permissions(
|
|
||||||
"/nix/store",
|
|
||||||
Some(Permissions::from_mode(0o1775)),
|
|
||||||
None,
|
|
||||||
Some(self.nix_build_group_name.clone()),
|
|
||||||
self.dry_run,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
create_directory("/etc/nix", self.dry_run).await?;
|
|
||||||
set_permissions(
|
|
||||||
"/etc/nix",
|
|
||||||
Some(Permissions::from_mode(0o0555)),
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
self.dry_run,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
|
||||||
pub async fn place_channel_configuration(&self) -> Result<(), HarmonicError> {
|
|
||||||
tracing::info!("Placing channel configuration");
|
|
||||||
let buf = self
|
|
||||||
.channels
|
|
||||||
.iter()
|
|
||||||
.map(|(name, url)| format!("{} {}", url, name))
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join("\n");
|
|
||||||
|
|
||||||
create_file_if_not_exists("/root/.nix-channels", buf, self.dry_run).await?;
|
|
||||||
set_permissions(
|
|
||||||
"/root/.nix-channels",
|
|
||||||
Some(Permissions::from_mode(0o0664)),
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
self.dry_run,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
|
||||||
pub async fn configure_shell_profile(&self) -> Result<(), HarmonicError> {
|
|
||||||
tracing::info!("Configuring shell profile");
|
|
||||||
const PROFILE_TARGETS: &[&str] = &[
|
|
||||||
"/etc/bashrc",
|
|
||||||
"/etc/profile.d/nix.sh",
|
|
||||||
"/etc/zshrc",
|
|
||||||
"/etc/bash.bashrc",
|
|
||||||
"/etc/zsh/zshrc",
|
|
||||||
];
|
|
||||||
const PROFILE_NIX_FILE: &str = "/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh";
|
|
||||||
for profile_target in PROFILE_TARGETS {
|
|
||||||
let path = Path::new(profile_target);
|
|
||||||
let buf = format!(
|
|
||||||
"\n\
|
|
||||||
# Nix\n\
|
|
||||||
if [ -e '{PROFILE_NIX_FILE}' ]; then\n\
|
|
||||||
. '{PROFILE_NIX_FILE}'\n\
|
|
||||||
fi\n\
|
|
||||||
# End Nix\n
|
|
||||||
\n",
|
|
||||||
);
|
|
||||||
create_or_append_file(path, buf, self.dry_run).await?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
|
||||||
pub async fn setup_default_profile(&self) -> Result<(), HarmonicError> {
|
|
||||||
tracing::info!("Setting up default profile");
|
|
||||||
// Find an `nix` package
|
|
||||||
let nix_pkg_glob = "/nix/store/*-nix-*";
|
|
||||||
let found_nix_pkg = if !self.dry_run {
|
|
||||||
let mut found_pkg = None;
|
|
||||||
for entry in glob(nix_pkg_glob).map_err(HarmonicError::GlobPatternError)? {
|
|
||||||
match entry {
|
|
||||||
Ok(path) => {
|
|
||||||
// TODO(@Hoverbear): Should probably ensure is unique
|
|
||||||
found_pkg = Some(path);
|
|
||||||
break;
|
|
||||||
},
|
|
||||||
Err(_) => continue, /* Ignore it */
|
|
||||||
};
|
|
||||||
}
|
|
||||||
found_pkg
|
|
||||||
} else {
|
|
||||||
// This is a mock for dry running.
|
|
||||||
Some(PathBuf::from(nix_pkg_glob))
|
|
||||||
};
|
|
||||||
let nix_pkg = if let Some(nix_pkg) = found_nix_pkg {
|
|
||||||
nix_pkg
|
|
||||||
} else {
|
|
||||||
return Err(HarmonicError::NoNssCacert);
|
|
||||||
};
|
|
||||||
|
|
||||||
execute_command(
|
|
||||||
Command::new(nix_pkg.join("bin/nix-env"))
|
|
||||||
.arg("-i")
|
|
||||||
.arg(&nix_pkg),
|
|
||||||
self.dry_run,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// Find an `nss-cacert` package, add it too.
|
|
||||||
let nss_ca_cert_pkg_glob = "/nix/store/*-nss-cacert-*";
|
|
||||||
let found_nss_ca_cert_pkg = if !self.dry_run {
|
|
||||||
let mut found_pkg = None;
|
|
||||||
for entry in glob(nss_ca_cert_pkg_glob).map_err(HarmonicError::GlobPatternError)? {
|
|
||||||
match entry {
|
|
||||||
Ok(path) => {
|
|
||||||
// TODO(@Hoverbear): Should probably ensure is unique
|
|
||||||
found_pkg = Some(path);
|
|
||||||
break;
|
|
||||||
},
|
|
||||||
Err(_) => continue, /* Ignore it */
|
|
||||||
};
|
|
||||||
}
|
|
||||||
found_pkg
|
|
||||||
} else {
|
|
||||||
// This is a mock for dry running.
|
|
||||||
Some(PathBuf::from(nss_ca_cert_pkg_glob))
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(nss_ca_cert_pkg) = found_nss_ca_cert_pkg {
|
|
||||||
execute_command(
|
|
||||||
Command::new(nix_pkg.join("bin/nix-env"))
|
|
||||||
.arg("-i")
|
|
||||||
.arg(&nss_ca_cert_pkg),
|
|
||||||
self.dry_run,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
set_env(
|
|
||||||
"NIX_SSL_CERT_FILE",
|
|
||||||
"/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt",
|
|
||||||
self.dry_run,
|
|
||||||
);
|
|
||||||
nss_ca_cert_pkg
|
|
||||||
} else {
|
|
||||||
return Err(HarmonicError::NoNssCacert);
|
|
||||||
};
|
|
||||||
if !self.channels.is_empty() {
|
|
||||||
execute_command(
|
|
||||||
Command::new(nix_pkg.join("bin/nix-channel"))
|
|
||||||
.arg("--update")
|
|
||||||
.arg("nixpkgs")
|
|
||||||
.env(
|
|
||||||
"NIX_SSL_CERT_FILE",
|
|
||||||
"/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt",
|
|
||||||
),
|
|
||||||
self.dry_run,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
|
||||||
pub async fn place_nix_configuration(&self) -> Result<(), HarmonicError> {
|
|
||||||
tracing::info!("Placing nix configuration");
|
|
||||||
let buf = format!(
|
|
||||||
"\
|
|
||||||
{extra_conf}\n\
|
|
||||||
build-users-group = {build_group_name}\n\
|
|
||||||
",
|
|
||||||
extra_conf = "", // TODO(@Hoverbear): populate me
|
|
||||||
build_group_name = self.nix_build_group_name,
|
|
||||||
);
|
|
||||||
create_file_if_not_exists("/etc/nix/nix.conf", buf, self.dry_run).await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
|
||||||
pub async fn configure_nix_daemon_service(&self) -> Result<(), HarmonicError> {
|
|
||||||
tracing::info!("Configuring nix daemon service");
|
|
||||||
if Path::new("/run/systemd/system").exists() {
|
|
||||||
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 TMPFILES_SRC: &str =
|
|
||||||
"/nix/var/nix/profiles/default//lib/tmpfiles.d/nix-daemon.conf";
|
|
||||||
const TMPFILES_DEST: &str = "/etc/tmpfiles.d/nix-daemon.conf";
|
|
||||||
|
|
||||||
symlink(TMPFILES_SRC, TMPFILES_DEST, self.dry_run).await?;
|
|
||||||
execute_command(
|
|
||||||
Command::new("systemd-tmpfiles")
|
|
||||||
.arg("--create")
|
|
||||||
.arg("--prefix=/nix/var/nix"),
|
|
||||||
self.dry_run,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
execute_command(
|
|
||||||
Command::new("systemctl").arg("link").arg(SERVICE_SRC),
|
|
||||||
self.dry_run,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
execute_command(
|
|
||||||
Command::new("systemctl").arg("enable").arg(SOCKET_SRC),
|
|
||||||
self.dry_run,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
// TODO(@Hoverbear): Handle proxy vars
|
|
||||||
execute_command(Command::new("systemctl").arg("daemon-reload"), self.dry_run).await?;
|
|
||||||
execute_command(
|
|
||||||
Command::new("systemctl")
|
|
||||||
.arg("start")
|
|
||||||
.arg("nix-daemon.socket"),
|
|
||||||
self.dry_run,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
execute_command(
|
|
||||||
Command::new("systemctl")
|
|
||||||
.arg("restart")
|
|
||||||
.arg("nix-daemon.service"),
|
|
||||||
self.dry_run,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
} else {
|
|
||||||
return Err(HarmonicError::InitNotSupported);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Harmonic {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
dry_run: true,
|
|
||||||
channels: vec![(
|
|
||||||
"nixpkgs".to_string(),
|
|
||||||
"https://nixos.org/channels/nixpkgs-unstable"
|
|
||||||
.parse::<Url>()
|
|
||||||
.unwrap(),
|
|
||||||
)],
|
|
||||||
daemon_user_count: 32,
|
|
||||||
modify_profile: true,
|
|
||||||
nix_build_group_name: String::from("nixbld"),
|
|
||||||
nix_build_group_id: 30000,
|
|
||||||
nix_build_user_prefix: String::from("nixbld"),
|
|
||||||
nix_build_user_id_base: 30000,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(
|
#[tracing::instrument(skip_all, fields(
|
||||||
path = %path.as_ref().display(),
|
path = %path.as_ref().display(),
|
||||||
|
@ -497,27 +102,15 @@ async fn create_directory(path: impl AsRef<Path>, dry_run: bool) -> Result<(), H
|
||||||
#[tracing::instrument(skip_all, fields(command = %format!("{:?}", command.as_std())))]
|
#[tracing::instrument(skip_all, fields(command = %format!("{:?}", command.as_std())))]
|
||||||
async fn execute_command(
|
async fn execute_command(
|
||||||
command: &mut Command,
|
command: &mut Command,
|
||||||
dry_run: bool,
|
) -> Result<ExitStatus, std::io::Error> {
|
||||||
) -> Result<ExitStatus, HarmonicError> {
|
|
||||||
if !dry_run {
|
|
||||||
tracing::trace!("Executing");
|
tracing::trace!("Executing");
|
||||||
let command_str = format!("{:?}", command.as_std());
|
let command_str = format!("{:?}", command.as_std());
|
||||||
let status = command
|
let status = command
|
||||||
.status()
|
.status()
|
||||||
.await
|
.await?;
|
||||||
.map_err(|e| HarmonicError::CommandFailedExec(command_str.clone(), e))?;
|
|
||||||
match status.success() {
|
match status.success() {
|
||||||
true => Ok(status),
|
true => Ok(status),
|
||||||
false => Err(HarmonicError::CommandFailedStatus(command_str)),
|
false => Err(std::io::Error::new(std::io::ErrorKind::Other, "Failed status")),
|
||||||
}
|
|
||||||
} else {
|
|
||||||
tracing::info!("Dry run: Would execute");
|
|
||||||
// You cannot conjure "good" exit status in Rust without breaking the rules
|
|
||||||
// So... we conjure one from `true`
|
|
||||||
Command::new("true")
|
|
||||||
.status()
|
|
||||||
.await
|
|
||||||
.map_err(|e| HarmonicError::CommandFailedExec(String::from("true"), e))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -686,3 +279,7 @@ fn set_env(k: impl AsRef<OsStr>, v: impl AsRef<OsStr>, dry_run: bool) {
|
||||||
tracing::info!("Dry run: Would set env");
|
tracing::info!("Dry run: Would set env");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn serialize_std_io_error_to_display<S>(err: &std::io::Error, ser: S) -> Result<S::Ok, S::Error> where S: Serializer {
|
||||||
|
ser.serialize_str(&format!("{err:#}"))
|
||||||
|
}
|
64
src/plan.rs
64
src/plan.rs
|
@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};
|
||||||
use crate::{
|
use crate::{
|
||||||
actions::{
|
actions::{
|
||||||
meta::{ConfigureNix, ProvisionNix, StartNixDaemon},
|
meta::{ConfigureNix, ProvisionNix, StartNixDaemon},
|
||||||
Action, ActionDescription, ActionReceipt, Actionable, Revertable,
|
Action, ActionDescription, Actionable, ActionState, ActionError,
|
||||||
},
|
},
|
||||||
settings::InstallSettings,
|
settings::InstallSettings,
|
||||||
HarmonicError,
|
HarmonicError,
|
||||||
|
@ -30,7 +30,9 @@ pub struct InstallPlan {
|
||||||
* "Start Nix"
|
* "Start Nix"
|
||||||
* start_nix_daemon_service
|
* start_nix_daemon_service
|
||||||
*/
|
*/
|
||||||
actions: Vec<Action>,
|
provision_nix: ActionState<ProvisionNix>,
|
||||||
|
configure_nix: ActionState<ConfigureNix>,
|
||||||
|
start_nix_daemon: ActionState<StartNixDaemon>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InstallPlan {
|
impl InstallPlan {
|
||||||
|
@ -55,10 +57,11 @@ impl InstallPlan {
|
||||||
.map(|(name, url)| format!("{name}={url}"))
|
.map(|(name, url)| format!("{name}={url}"))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join(","),
|
.join(","),
|
||||||
actions = self
|
actions = {
|
||||||
.actions
|
let mut buf = self.provision_nix.description();
|
||||||
.iter()
|
buf.append(&mut self.configure_nix.description());
|
||||||
.flat_map(|action| action.description())
|
buf.append(&mut self.start_nix_daemon.description());
|
||||||
|
buf.iter()
|
||||||
.map(|desc| {
|
.map(|desc| {
|
||||||
let ActionDescription {
|
let ActionDescription {
|
||||||
description,
|
description,
|
||||||
|
@ -75,48 +78,27 @@ impl InstallPlan {
|
||||||
buf
|
buf
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join("\n"),
|
.join("\n")
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
pub async fn new(settings: InstallSettings) -> Result<Self, HarmonicError> {
|
pub async fn new(settings: InstallSettings) -> Result<Self, ActionError> {
|
||||||
let actions = vec![
|
Ok(Self {
|
||||||
Action::ProvisionNix(ProvisionNix::plan(settings.clone()).await?),
|
settings: settings.clone(),
|
||||||
Action::ConfigureNix(ConfigureNix::plan(settings.clone()).await?),
|
provision_nix: ProvisionNix::plan(settings.clone()).await?,
|
||||||
Action::StartNixDaemon(StartNixDaemon::plan().await?),
|
configure_nix: ConfigureNix::plan(settings).await?,
|
||||||
];
|
start_nix_daemon: StartNixDaemon::plan().await?,
|
||||||
Ok(Self { settings, actions })
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub async fn install(self) -> Result<Receipt, HarmonicError> {
|
pub async fn install(&mut self) -> Result<(), ActionError> {
|
||||||
let mut receipt = Receipt::default();
|
|
||||||
// This is **deliberately sequential**.
|
// This is **deliberately sequential**.
|
||||||
// Actions which are parallelizable are represented by "group actions" like CreateUsers
|
// Actions which are parallelizable are represented by "group actions" like CreateUsers
|
||||||
// The plan itself represents the concept of the sequence of stages.
|
// The plan itself represents the concept of the sequence of stages.
|
||||||
for action in self.actions {
|
self.provision_nix.execute().await?;
|
||||||
match action.execute().await {
|
self.configure_nix.execute().await?;
|
||||||
Ok(action_receipt) => receipt.actions.push(action_receipt),
|
self.start_nix_daemon.execute().await?;
|
||||||
Err(err) => {
|
Ok(())
|
||||||
return Err(err);
|
|
||||||
// TODO
|
|
||||||
// let mut revert_errs = Vec::default();
|
|
||||||
|
|
||||||
// for action_receipt in receipt.actions {
|
|
||||||
// if let Err(err) = action_receipt.revert().await {
|
|
||||||
// revert_errs.push(err);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// if !revert_errs.is_empty() {
|
|
||||||
// return Err(HarmonicError::FailedReverts(vec![err], revert_errs));
|
|
||||||
// }
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
Ok(receipt)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Serialize, Deserialize)]
|
|
||||||
pub struct Receipt {
|
|
||||||
actions: Vec<ActionReceipt>,
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue