It's working meme

Signed-off-by: Ana Hobden <operator@hoverbear.org>
This commit is contained in:
Ana Hobden 2022-09-21 15:20:31 -07:00
parent 3c1a8fdcc8
commit a44c9eb45f
25 changed files with 547 additions and 354 deletions

View file

@ -2,25 +2,20 @@ use std::path::{Path, PathBuf};
use tokio::process::Command;
use crate::{HarmonicError, execute_command};
use crate::{execute_command, HarmonicError};
use crate::actions::{ActionDescription, ActionReceipt, Actionable, Revertable};
use crate::actions::{ActionDescription, Actionable, Revertable};
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 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";
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct ConfigureNixDaemonService {}
impl ConfigureNixDaemonService {
#[tracing::instrument(skip_all)]
pub async fn plan() -> Result<Self, HarmonicError> {
if !Path::new("/run/systemd/system").exists() {
return Err(HarmonicError::InitNotSupported);
@ -33,30 +28,35 @@ impl ConfigureNixDaemonService {
impl<'a> Actionable<'a> for ConfigureNixDaemonService {
type Receipt = ConfigureNixDaemonServiceReceipt;
fn description(&self) -> Vec<ActionDescription> {
vec![
ActionDescription::new(
"Configure the Nix Daemon Service".to_string(),
vec![
"Sets init system specific options".to_string()
]
),
]
vec![ActionDescription::new(
"Configure Nix daemon related settings with systemd".to_string(),
vec![
"Run `systemd-tempfiles --create --prefix=/nix/var/nix`".to_string(),
"Run `systemctl link {SERVICE_SRC}`".to_string(),
"Run `systemctl link {SOCKET_SRC}`".to_string(),
"Run `systemctl daemon-reload`".to_string(),
],
)]
}
#[tracing::instrument(skip_all)]
async fn execute(self) -> Result<Self::Receipt, HarmonicError> {
tracing::info!("Configuring nix daemon service");
tracing::trace!("Symlinking");
tracing::trace!(src = TMPFILES_SRC, dest = TMPFILES_DEST, "Symlinking");
tokio::fs::symlink(TMPFILES_SRC, TMPFILES_DEST)
.await
.map_err(|e| HarmonicError::Symlink(PathBuf::from(TMPFILES_SRC), PathBuf::from(TMPFILES_DEST), e))?;
.map_err(|e| {
HarmonicError::Symlink(PathBuf::from(TMPFILES_SRC), PathBuf::from(TMPFILES_DEST), e)
})?;
execute_command(
Command::new("systemd-tmpfiles")
.arg("--create")
.arg("--prefix=/nix/var/nix"),
false,
).await?;
)
.await?;
execute_command(
Command::new("systemctl").arg("link").arg(SERVICE_SRC),
@ -64,6 +64,8 @@ impl<'a> Actionable<'a> for ConfigureNixDaemonService {
)
.await?;
execute_command(Command::new("systemctl").arg("link").arg(SOCKET_SRC), false).await?;
execute_command(Command::new("systemctl").arg("daemon-reload"), false).await?;
Ok(Self::Receipt {})
@ -86,6 +88,7 @@ impl<'a> Revertable<'a> for ConfigureNixDaemonServiceReceipt {
]
}
#[tracing::instrument(skip_all)]
async fn revert(self) -> Result<(), HarmonicError> {
todo!();

View file

@ -1,14 +1,11 @@
use std::{
fs::Permissions,
path::{Path, PathBuf},
};
use std::path::{Path, PathBuf};
use nix::unistd::{Group, User, Gid, Uid, chown};
use nix::unistd::{chown, Group, User};
use tokio::fs::create_dir;
use crate::HarmonicError;
use crate::actions::{ActionDescription, ActionReceipt, Actionable, Revertable};
use crate::actions::{ActionDescription, Actionable, Revertable};
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct CreateDirectory {
@ -19,20 +16,26 @@ pub struct CreateDirectory {
}
impl CreateDirectory {
pub async fn plan(path: impl AsRef<Path>, user: String, group: String, mode: u32, force: bool) -> Result<Self, HarmonicError> {
#[tracing::instrument(skip_all)]
pub async fn plan(
path: impl AsRef<Path>,
user: String,
group: String,
mode: u32,
force: bool,
) -> Result<Self, HarmonicError> {
let path = path.as_ref();
if path.exists() && !force {
return Err(HarmonicError::CreateDirectory(path.to_path_buf(), std::io::Error::new(std::io::ErrorKind::AlreadyExists, format!("Directory `{}` already exists", path.display()))))
return Err(HarmonicError::CreateDirectory(
path.to_path_buf(),
std::io::Error::new(
std::io::ErrorKind::AlreadyExists,
format!("Directory `{}` already exists", path.display()),
),
));
}
// Ensure the group/user exist, we don't store them since we really need to serialize them
let _has_gid = Group::from_name(group.as_str())
.map_err(|e| HarmonicError::GroupId(group.clone(), e))?
.ok_or(HarmonicError::NoGroup(group.clone()))?;
let _has_uid = User::from_name(user.as_str())
.map_err(|e| HarmonicError::UserId(user.clone(), e))?
.ok_or(HarmonicError::NoUser(user.clone()))?;
Ok(Self {
path: path.to_path_buf(),
user,
@ -46,15 +49,22 @@ impl CreateDirectory {
impl<'a> Actionable<'a> for CreateDirectory {
type Receipt = CreateDirectoryReceipt;
fn description(&self) -> Vec<ActionDescription> {
let Self { path, user, group, mode } = &self;
let Self {
path,
user,
group,
mode,
} = &self;
vec![ActionDescription::new(
format!("Create the directory `{}`", path.display()),
vec![format!(
"Creating directory `{}` owned by `{user}:{group}` with mode `{mode:#o}`", path.display()
"Creating directory `{}` owned by `{user}:{group}` with mode `{mode:#o}`",
path.display()
)],
)]
}
#[tracing::instrument(skip_all)]
async fn execute(self) -> Result<CreateDirectoryReceipt, HarmonicError> {
let Self {
path,
@ -62,7 +72,7 @@ impl<'a> Actionable<'a> for CreateDirectory {
group,
mode,
} = self;
let gid = Group::from_name(group.as_str())
.map_err(|e| HarmonicError::GroupId(group.clone(), e))?
.ok_or(HarmonicError::NoGroup(group.clone()))?
@ -72,12 +82,12 @@ impl<'a> Actionable<'a> for CreateDirectory {
.ok_or(HarmonicError::NoUser(user.clone()))?
.uid;
tracing::trace!(path = %path.display(), "Creating directory");
create_dir(path.clone())
.await
.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 {
path,
user,
@ -106,6 +116,7 @@ impl<'a> Revertable<'a> for CreateDirectoryReceipt {
)]
}
#[tracing::instrument(skip_all)]
async fn revert(self) -> Result<(), HarmonicError> {
let Self {
path,

View file

@ -1,13 +1,13 @@
use std::{
fs::Permissions,
path::{Path, PathBuf, self}, io::SeekFrom,
use nix::unistd::{chown, Group, User};
use std::path::{Path, PathBuf};
use tokio::{
fs::{create_dir_all, OpenOptions},
io::AsyncWriteExt,
};
use nix::unistd::{Group, User, Gid, Uid, chown};
use tokio::{fs::{create_dir, create_dir_all, OpenOptions}, io::{AsyncWriteExt, AsyncSeekExt}};
use crate::HarmonicError;
use crate::actions::{ActionDescription, ActionReceipt, Actionable, Revertable};
use crate::actions::{ActionDescription, Actionable, Revertable};
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct CreateFile {
@ -16,13 +16,39 @@ pub struct CreateFile {
group: String,
mode: u32,
buf: String,
force: bool,
}
impl CreateFile {
pub async fn plan(path: impl AsRef<Path>, user: String, group: String, mode: u32, buf: String) -> Result<Self, HarmonicError> {
#[tracing::instrument(skip_all)]
pub async fn plan(
path: impl AsRef<Path>,
user: String,
group: String,
mode: u32,
buf: String,
force: bool,
) -> Result<Self, HarmonicError> {
let path = path.as_ref().to_path_buf();
Ok(Self { path, user, group, mode, buf })
if path.exists() && !force {
return Err(HarmonicError::CreateFile(
path.to_path_buf(),
std::io::Error::new(
std::io::ErrorKind::AlreadyExists,
format!("Directory `{}` already exists", path.display()),
),
));
}
Ok(Self {
path,
user,
group,
mode,
buf,
force,
})
}
}
@ -30,7 +56,14 @@ impl CreateFile {
impl<'a> Actionable<'a> for CreateFile {
type Receipt = CreateFileReceipt;
fn description(&self) -> Vec<ActionDescription> {
let Self { path, user, group, mode, buf } = &self;
let Self {
path,
user,
group,
mode,
buf,
force,
} = &self;
vec![ActionDescription::new(
format!("Create or overwrite file `{}`", path.display()),
vec![format!(
@ -39,15 +72,17 @@ impl<'a> Actionable<'a> for CreateFile {
)]
}
#[tracing::instrument(skip_all)]
async fn execute(self) -> Result<CreateFileReceipt, HarmonicError> {
let Self { path, user, group, mode, buf } = self;
tracing::trace!("Creating or appending");
if let Some(parent) = path.parent() {
create_dir_all(parent)
.await
.map_err(|e| HarmonicError::CreateDirectory(parent.to_owned(), e))?;
}
let Self {
path,
user,
group,
mode,
buf,
force: _,
} = self;
tracing::trace!(path = %path.display(), "Creating file");
let mut file = OpenOptions::new()
.create_new(true)
.write(true)
@ -67,11 +102,18 @@ impl<'a> Actionable<'a> for CreateFile {
let uid = User::from_name(user.as_str())
.map_err(|e| HarmonicError::UserId(user.clone(), e))?
.ok_or(HarmonicError::NoUser(user.clone()))?
.uid;
chown(&path, Some(uid), Some(gid))
.map_err(|e| HarmonicError::Chown(path.clone(), e))?;
.uid;
Ok(Self::Receipt { path, user, group, mode, buf })
tracing::trace!(path = %path.display(), "Chowning file");
chown(&path, Some(uid), Some(gid)).map_err(|e| HarmonicError::Chown(path.clone(), e))?;
Ok(Self::Receipt {
path,
user,
group,
mode,
buf,
})
}
}
@ -90,9 +132,8 @@ impl<'a> Revertable<'a> for CreateFileReceipt {
todo!()
}
#[tracing::instrument(skip_all)]
async fn revert(self) -> Result<(), HarmonicError> {
todo!();
Ok(())

View file

@ -1,8 +1,8 @@
use tokio::process::Command;
use crate::HarmonicError;
use crate::{HarmonicError, execute_command};
use crate::actions::{ActionDescription, ActionReceipt, Actionable, Revertable};
use crate::actions::{ActionDescription, Actionable, Revertable};
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct CreateGroup {
@ -11,9 +11,8 @@ pub struct CreateGroup {
}
impl CreateGroup {
#[tracing::instrument(skip_all)]
pub fn plan(name: String, gid: usize) -> Self {
Self { name, gid }
}
}
@ -31,28 +30,14 @@ impl<'a> Actionable<'a> for CreateGroup {
)]
}
#[tracing::instrument(skip_all)]
async fn execute(self) -> Result<Self::Receipt, HarmonicError> {
let Self { name, gid } = self;
let mut command = Command::new("groupadd");
command.args([
"-g",
&gid.to_string(),
"--system",
&name
]);
let command_str = format!("{:?}", command.as_std());
let status = command
.status()
.await
.map_err(|e| HarmonicError::CommandFailedExec(command_str.clone(), e))?;
match status.success() {
true => (),
false => return Err(HarmonicError::CommandFailedStatus(command_str)),
}
execute_command(
Command::new("groupadd").args(["-g", &gid.to_string(), "--system", &name]),
false,
).await?;
Ok(CreateGroupReceipt { name, gid })
}
@ -70,6 +55,7 @@ impl<'a> Revertable<'a> for CreateGroupReceipt {
todo!()
}
#[tracing::instrument(skip_all)]
async fn revert(self) -> Result<(), HarmonicError> {
todo!();

View file

@ -1,13 +1,16 @@
use nix::unistd::{chown, Group, User};
use std::{
fs::Permissions,
path::{Path, PathBuf, self}, io::SeekFrom,
io::SeekFrom,
path::{Path, PathBuf},
};
use tokio::{
fs::{create_dir_all, OpenOptions},
io::{AsyncSeekExt, AsyncWriteExt},
};
use nix::unistd::{Group, User, Gid, Uid, chown};
use tokio::{fs::{create_dir, create_dir_all, OpenOptions}, io::{AsyncWriteExt, AsyncSeekExt}};
use crate::HarmonicError;
use crate::actions::{ActionDescription, ActionReceipt, Actionable, Revertable};
use crate::actions::{ActionDescription, Actionable, Revertable};
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct CreateOrAppendFile {
@ -19,10 +22,23 @@ pub struct CreateOrAppendFile {
}
impl CreateOrAppendFile {
pub async fn plan(path: impl AsRef<Path>, user: String, group: String, mode: u32, buf: String) -> Result<Self, HarmonicError> {
#[tracing::instrument(skip_all)]
pub async fn plan(
path: impl AsRef<Path>,
user: String,
group: String,
mode: u32,
buf: String,
) -> Result<Self, HarmonicError> {
let path = path.as_ref().to_path_buf();
Ok(Self { path, user, group, mode, buf })
Ok(Self {
path,
user,
group,
mode,
buf,
})
}
}
@ -30,7 +46,13 @@ impl CreateOrAppendFile {
impl<'a> Actionable<'a> for CreateOrAppendFile {
type Receipt = CreateOrAppendFileReceipt;
fn description(&self) -> Vec<ActionDescription> {
let Self { path, user, group, mode, buf } = &self;
let Self {
path,
user,
group,
mode,
buf,
} = &self;
vec![ActionDescription::new(
format!("Create or append file `{}`", path.display()),
vec![format!(
@ -39,15 +61,17 @@ impl<'a> Actionable<'a> for CreateOrAppendFile {
)]
}
#[tracing::instrument(skip_all)]
async fn execute(self) -> Result<CreateOrAppendFileReceipt, HarmonicError> {
let Self { path, user, group, mode, buf } = self;
let Self {
path,
user,
group,
mode,
buf,
} = self;
tracing::trace!("Creating or appending");
if let Some(parent) = path.parent() {
create_dir_all(parent)
.await
.map_err(|e| HarmonicError::CreateDirectory(parent.to_owned(), e))?;
}
tracing::trace!(path = %path.display(), "Creating or appending");
let mut file = OpenOptions::new()
.create(true)
.write(true)
@ -70,11 +94,18 @@ impl<'a> Actionable<'a> for CreateOrAppendFile {
let uid = User::from_name(user.as_str())
.map_err(|e| HarmonicError::UserId(user.clone(), e))?
.ok_or(HarmonicError::NoUser(user.clone()))?
.uid;
chown(&path, Some(uid), Some(gid))
.map_err(|e| HarmonicError::Chown(path.clone(), e))?;
Ok(Self::Receipt { path, user, group, mode, buf })
.uid;
tracing::trace!(path = %path.display(), "Chowning");
chown(&path, Some(uid), Some(gid)).map_err(|e| HarmonicError::Chown(path.clone(), e))?;
Ok(Self::Receipt {
path,
user,
group,
mode,
buf,
})
}
}
@ -98,9 +129,8 @@ impl<'a> Revertable<'a> for CreateOrAppendFileReceipt {
)]
}
#[tracing::instrument(skip_all)]
async fn revert(self) -> Result<(), HarmonicError> {
todo!();
Ok(())

View file

@ -1,8 +1,8 @@
use tokio::process::Command;
use crate::HarmonicError;
use crate::{HarmonicError, execute_command};
use crate::actions::{ActionDescription, ActionReceipt, Actionable, Revertable};
use crate::actions::{ActionDescription, Actionable, Revertable};
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct CreateUser {
@ -12,6 +12,7 @@ pub struct CreateUser {
}
impl CreateUser {
#[tracing::instrument(skip_all)]
pub fn plan(name: String, uid: usize, gid: usize) -> Self {
Self { name, uid, gid }
}
@ -31,11 +32,11 @@ impl<'a> Actionable<'a> for CreateUser {
)]
}
#[tracing::instrument(skip_all)]
async fn execute(self) -> Result<Self::Receipt, HarmonicError> {
let Self { name, uid, gid } = self;
let mut command = Command::new("useradd");
command.args([
execute_command(Command::new("useradd").args([
"--home-dir",
"/var/empty",
"--comment",
@ -53,18 +54,7 @@ impl<'a> Actionable<'a> for CreateUser {
"--password",
"\"!\"",
&name.to_string(),
]);
let command_str = format!("{:?}", command.as_std());
let status = command
.status()
.await
.map_err(|e| HarmonicError::CommandFailedExec(command_str.clone(), e))?;
match status.success() {
true => (),
false => return Err(HarmonicError::CommandFailedStatus(command_str)),
}
]), false).await?;
Ok(CreateUserReceipt { name, uid, gid })
}
@ -83,6 +73,7 @@ impl<'a> Revertable<'a> for CreateUserReceipt {
todo!()
}
#[tracing::instrument(skip_all)]
async fn revert(self) -> Result<(), HarmonicError> {
todo!();

View file

@ -1,4 +1,4 @@
use std::path::{Path, PathBuf};
use std::path::{PathBuf};
use bytes::Buf;
use reqwest::Url;
@ -6,7 +6,7 @@ use tokio::task::spawn_blocking;
use crate::HarmonicError;
use crate::actions::{ActionDescription, ActionReceipt, Actionable, Revertable};
use crate::actions::{ActionDescription, Actionable, Revertable};
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct FetchNix {
@ -15,6 +15,7 @@ pub struct FetchNix {
}
impl FetchNix {
#[tracing::instrument(skip_all)]
pub async fn plan(url: Url, destination: PathBuf) -> Result<Self, HarmonicError> {
// TODO(@hoverbear): Check URL exists?
// TODO(@hoverbear): Check tempdir exists
@ -27,22 +28,24 @@ impl FetchNix {
impl<'a> Actionable<'a> for FetchNix {
type Receipt = FetchNixReceipt;
fn description(&self) -> Vec<ActionDescription> {
let Self {
url, destination
} = &self;
let Self { url, destination } = &self;
vec![ActionDescription::new(
format!("Fetch Nix from `{url}`"),
vec![format!(
"Unpack it to `{}`, so it can later be moved into the Nix store at /nix", destination.display()
"Unpack it to `{}` (moved later)",
destination.display()
)],
)]
}
#[tracing::instrument(skip_all)]
async fn execute(self) -> Result<Self::Receipt, HarmonicError> {
let Self { url, destination } = self;
tracing::trace!("Fetching url");
let res = reqwest::get(url.clone()).await.map_err(HarmonicError::Reqwest)?;
tracing::trace!(%url, "Fetching url");
let res = reqwest::get(url.clone())
.await
.map_err(HarmonicError::Reqwest)?;
let bytes = res.bytes().await.map_err(HarmonicError::Reqwest)?;
// TODO(@Hoverbear): Pick directory
tracing::trace!("Unpacking tar.xz");
@ -74,6 +77,7 @@ impl<'a> Revertable<'a> for FetchNixReceipt {
todo!()
}
#[tracing::instrument(skip_all)]
async fn revert(self) -> Result<(), HarmonicError> {
todo!();

View file

@ -1,7 +1,6 @@
/*! Actions which do not only call other base plugins. */
mod configure_nix_daemon_service;
mod configure_shell_profile;
mod create_directory;
mod create_file;
mod create_group;
@ -17,7 +16,6 @@ mod start_systemd_unit;
pub use configure_nix_daemon_service::{
ConfigureNixDaemonService, ConfigureNixDaemonServiceReceipt,
};
pub use configure_shell_profile::{ConfigureShellProfile, ConfigureShellProfileReceipt};
pub use create_directory::{CreateDirectory, CreateDirectoryReceipt};
pub use create_file::{CreateFile, CreateFileReceipt};
pub use create_group::{CreateGroup, CreateGroupReceipt};
@ -30,4 +28,4 @@ pub use place_channel_configuration::{
};
pub use place_nix_configuration::{PlaceNixConfiguration, PlaceNixConfigurationReceipt};
pub use setup_default_profile::{SetupDefaultProfile, SetupDefaultProfileReceipt};
pub use start_systemd_unit::{StartSystemdUnit, StartSystemdUnitReceipt};
pub use start_systemd_unit::{StartSystemdUnit, StartSystemdUnitReceipt};

View file

@ -1,8 +1,8 @@
use std::path::{PathBuf, Path};
use std::path::{Path, PathBuf};
use crate::HarmonicError;
use crate::actions::{ActionDescription, ActionReceipt, Actionable, Revertable};
use crate::actions::{ActionDescription, Actionable, Revertable};
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct MoveUnpackedNix {
@ -10,9 +10,10 @@ pub struct MoveUnpackedNix {
}
impl MoveUnpackedNix {
#[tracing::instrument(skip_all)]
pub async fn plan(source: PathBuf) -> Result<Self, HarmonicError> {
// Note: Do NOT try to check for the source/dest since the installer creates those
Ok(Self { source, })
Ok(Self { source })
}
}
@ -24,17 +25,19 @@ impl<'a> Actionable<'a> for MoveUnpackedNix {
vec![ActionDescription::new(
format!("Move the downloaded Nix into `/nix`"),
vec![format!(
"Nix is downloaded to `{}` and should be in `nix`", source.display(),
"Nix is being downloaded to `{}` and should be in `nix`",
source.display(),
)],
)]
}
#[tracing::instrument(skip_all)]
async fn execute(self) -> Result<Self::Receipt, HarmonicError> {
let Self { source } = self;
// TODO(@Hoverbear): I would like to make this less awful
let found_nix_paths = glob::glob(&format!("{}/nix-*", source.display()))?
.collect::<Result<Vec<_>, _>>()?;
let found_nix_paths =
glob::glob(&format!("{}/nix-*", source.display()))?.collect::<Result<Vec<_>, _>>()?;
assert_eq!(
found_nix_paths.len(),
1,
@ -47,15 +50,13 @@ impl<'a> Actionable<'a> for MoveUnpackedNix {
tokio::fs::rename(src.clone(), dest)
.await
.map_err(|e| HarmonicError::Rename(src, dest.to_owned(), e))?;
Ok(MoveUnpackedNixReceipt { })
Ok(MoveUnpackedNixReceipt {})
}
}
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct MoveUnpackedNixReceipt {
}
pub struct MoveUnpackedNixReceipt {}
#[async_trait::async_trait]
impl<'a> Revertable<'a> for MoveUnpackedNixReceipt {
@ -63,6 +64,7 @@ impl<'a> Revertable<'a> for MoveUnpackedNixReceipt {
todo!()
}
#[tracing::instrument(skip_all)]
async fn revert(self) -> Result<(), HarmonicError> {
todo!();

View file

@ -1,12 +1,10 @@
use std::path::Path;
use reqwest::Url;
use crate::HarmonicError;
use crate::actions::{ActionDescription, ActionReceipt, Actionable, Revertable};
use crate::actions::{ActionDescription, Actionable, Revertable};
use super::{CreateOrAppendFile, CreateFile, CreateFileReceipt};
use super::{CreateFile, CreateFileReceipt};
const NIX_CHANNELS_PATH: &str = "/root/.nix-channels";
@ -17,14 +15,19 @@ pub struct PlaceChannelConfiguration {
}
impl PlaceChannelConfiguration {
pub async fn plan(channels: Vec<(String, Url)>) -> Result<Self, HarmonicError> {
#[tracing::instrument(skip_all)]
pub async fn plan(channels: Vec<(String, Url)>, force: bool) -> Result<Self, HarmonicError> {
let buf = channels
.iter()
.map(|(name, url)| format!("{} {}", url, name))
.collect::<Vec<_>>()
.join("\n");
let create_file = CreateFile::plan(NIX_CHANNELS_PATH, "root".into(), "root".into(), 0o0664, buf).await?;
Ok(Self { create_file, channels })
let create_file =
CreateFile::plan(NIX_CHANNELS_PATH, "root".into(), "root".into(), 0o0664, buf, force).await?;
Ok(Self {
create_file,
channels,
})
}
}
@ -32,21 +35,27 @@ impl PlaceChannelConfiguration {
impl<'a> Actionable<'a> for PlaceChannelConfiguration {
type Receipt = PlaceChannelConfigurationReceipt;
fn description(&self) -> Vec<ActionDescription> {
let Self { channels, create_file } = self;
vec![
ActionDescription::new(
"Place a channel configuration".to_string(),
vec![
"Place a configuration at `{NIX_CHANNELS_PATH}` setting the channels".to_string()
]
),
]
let Self {
channels,
create_file,
} = self;
vec![ActionDescription::new(
"Place a channel configuration".to_string(),
vec!["Place a configuration at `{NIX_CHANNELS_PATH}` setting the channels".to_string()],
)]
}
#[tracing::instrument(skip_all)]
async fn execute(self) -> Result<Self::Receipt, HarmonicError> {
let Self { create_file, channels } = self;
let Self {
create_file,
channels,
} = self;
let create_file = create_file.execute().await?;
Ok(Self::Receipt { create_file, channels })
Ok(Self::Receipt {
create_file,
channels,
})
}
}
@ -62,6 +71,7 @@ impl<'a> Revertable<'a> for PlaceChannelConfigurationReceipt {
todo!()
}
#[tracing::instrument(skip_all)]
async fn revert(self) -> Result<(), HarmonicError> {
todo!();

View file

@ -1,18 +1,25 @@
use crate::HarmonicError;
use crate::actions::{ActionDescription, ActionReceipt, Actionable, Revertable};
use crate::actions::{ActionDescription, Actionable, Revertable};
use super::{CreateFile, CreateFileReceipt};
use super::{CreateFile, CreateFileReceipt, CreateDirectory, CreateDirectoryReceipt};
const NIX_CONF_FOLDER: &str = "/etc/nix";
const NIX_CONF: &str = "/etc/nix/nix.conf";
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct PlaceNixConfiguration {
create_directory: CreateDirectory,
create_file: CreateFile,
}
impl PlaceNixConfiguration {
pub async fn plan(nix_build_group_name: String, extra_conf: Option<String>) -> Result<Self, HarmonicError> {
#[tracing::instrument(skip_all)]
pub async fn plan(
nix_build_group_name: String,
extra_conf: Option<String>,
force: bool,
) -> Result<Self, HarmonicError> {
let buf = format!(
"\
{extra_conf}\n\
@ -20,8 +27,10 @@ impl PlaceNixConfiguration {
",
extra_conf = extra_conf.unwrap_or_else(|| "".into()),
);
let create_file = CreateFile::plan(NIX_CONF, "root".into(), "root".into(), 0o0664, buf).await?;
Ok(Self { create_file })
let create_directory = CreateDirectory::plan(NIX_CONF_FOLDER, "root".into(), "root".into(), 0o0755, force).await?;
let create_file =
CreateFile::plan(NIX_CONF, "root".into(), "root".into(), 0o0664, buf, force).await?;
Ok(Self { create_directory, create_file })
}
}
@ -29,25 +38,24 @@ impl PlaceNixConfiguration {
impl<'a> Actionable<'a> for PlaceNixConfiguration {
type Receipt = PlaceNixConfigurationReceipt;
fn description(&self) -> Vec<ActionDescription> {
vec![
ActionDescription::new(
"Place the nix configuration".to_string(),
vec![
"Boop".to_string()
]
),
]
vec![ActionDescription::new(
format!("Place the nix configuration in `{NIX_CONF}`"),
vec!["This file is read by the Nix daemon to set its configuration options at runtime.".to_string()],
)]
}
#[tracing::instrument(skip_all)]
async fn execute(self) -> Result<Self::Receipt, HarmonicError> {
let Self { create_file } = 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 })
Ok(Self::Receipt { create_file, create_directory })
}
}
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct PlaceNixConfigurationReceipt {
create_directory: CreateDirectoryReceipt,
create_file: CreateFileReceipt,
}
@ -57,6 +65,7 @@ impl<'a> Revertable<'a> for PlaceNixConfigurationReceipt {
todo!()
}
#[tracing::instrument(skip_all)]
async fn revert(self) -> Result<(), HarmonicError> {
todo!();

View file

@ -1,9 +1,9 @@
use crate::{HarmonicError, execute_command};
use crate::{execute_command, HarmonicError};
use glob::glob;
use tokio::process::Command;
use crate::actions::{ActionDescription, ActionReceipt, Actionable, Revertable};
use crate::actions::{ActionDescription, Actionable, Revertable};
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct SetupDefaultProfile {
@ -11,6 +11,7 @@ pub struct SetupDefaultProfile {
}
impl SetupDefaultProfile {
#[tracing::instrument(skip_all)]
pub async fn plan(channels: Vec<String>) -> Result<Self, HarmonicError> {
Ok(Self { channels })
}
@ -20,16 +21,13 @@ impl SetupDefaultProfile {
impl<'a> Actionable<'a> for SetupDefaultProfile {
type Receipt = SetupDefaultProfileReceipt;
fn description(&self) -> Vec<ActionDescription> {
vec![
ActionDescription::new(
"Setup the default Nix profile".to_string(),
vec![
"TODO".to_string()
]
),
]
vec![ActionDescription::new(
"Setup the default Nix profile".to_string(),
vec!["TODO".to_string()],
)]
}
#[tracing::instrument(skip_all)]
async fn execute(self) -> Result<Self::Receipt, HarmonicError> {
let Self { channels } = self;
tracing::info!("Setting up default profile");
@ -74,13 +72,13 @@ impl<'a> Actionable<'a> for SetupDefaultProfile {
},
Err(_) => continue, /* Ignore it */
};
};
}
let nss_ca_cert_pkg = if let Some(nss_ca_cert_pkg) = found_nss_ca_cert_pkg {
nss_ca_cert_pkg
} else {
return Err(HarmonicError::NoNssCacert);
};
// Install `nss-cacert` into the store
execute_command(
Command::new(nix_pkg.join("bin/nix-env"))
@ -96,14 +94,17 @@ impl<'a> Actionable<'a> for SetupDefaultProfile {
for channel in channels {
command.arg(channel);
}
command.env("NIX_SSL_CERT_FILE", "/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt");
command.env(
"NIX_SSL_CERT_FILE",
"/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt",
);
let command_str = format!("{:?}", command.as_std());
let status = command
.status()
.await
.map_err(|e| HarmonicError::CommandFailedExec(command_str.clone(), e))?;
match status.success() {
true => (),
false => return Err(HarmonicError::CommandFailedStatus(command_str)),
@ -119,16 +120,13 @@ 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()
]
),
]
vec![ActionDescription::new(
"Unset the default Nix profile".to_string(),
vec!["TODO".to_string()],
)]
}
#[tracing::instrument(skip_all)]
async fn revert(self) -> Result<(), HarmonicError> {
todo!();

View file

@ -1,8 +1,8 @@
use tokio::process::Command;
use crate::{HarmonicError, execute_command};
use crate::{execute_command, HarmonicError};
use crate::actions::{ActionDescription, ActionReceipt, Actionable, Revertable};
use crate::actions::{ActionDescription, Actionable, Revertable};
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct StartSystemdUnit {
@ -10,6 +10,7 @@ pub struct StartSystemdUnit {
}
impl StartSystemdUnit {
#[tracing::instrument(skip_all)]
pub async fn plan(unit: String) -> Result<Self, HarmonicError> {
Ok(Self { unit })
}
@ -29,22 +30,15 @@ impl<'a> Actionable<'a> for StartSystemdUnit {
]
}
#[tracing::instrument(skip_all)]
async fn execute(self) -> Result<Self::Receipt, HarmonicError> {
let Self { unit } = self;
// TODO(@Hoverbear): Handle proxy vars
execute_command(
Command::new("systemctl")
.arg("enable")
.arg(format!("{unit}")),
false,
)
.await?;
execute_command(
Command::new("systemctl")
.arg("restart")
.arg("--now")
.arg(format!("{unit}")),
false,
)
@ -63,6 +57,7 @@ impl<'a> Revertable<'a> for StartSystemdUnitReceipt {
todo!()
}
#[tracing::instrument(skip_all)]
async fn revert(self) -> Result<(), HarmonicError> {
todo!();

View file

@ -1,22 +1,32 @@
use tokio::task::JoinSet;
use crate::actions::{
base::{
ConfigureNixDaemonService, ConfigureNixDaemonServiceReceipt, PlaceNixConfiguration,
PlaceNixConfigurationReceipt, SetupDefaultProfile, SetupDefaultProfileReceipt, PlaceChannelConfiguration, PlaceChannelConfigurationReceipt,
},
meta::{ConfigureShellProfile, ConfigureShellProfileReceipt},
};
use crate::{HarmonicError, InstallSettings};
use crate::actions::base::{SetupDefaultProfile, ConfigureNixDaemonService, ConfigureShellProfile, SetupDefaultProfileReceipt, ConfigureNixDaemonServiceReceipt, ConfigureShellProfileReceipt, PlaceNixConfigurationReceipt, PlaceNixConfiguration};
use crate::{HarmonicError, InstallSettings, Harmonic};
use crate::actions::{ActionDescription, ActionReceipt, Actionable, Revertable};
use crate::actions::{ActionDescription, Actionable, Revertable};
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct ConfigureNix {
setup_default_profile: SetupDefaultProfile,
configure_shell_profile: Option<ConfigureShellProfile>,
place_channel_configuration: PlaceChannelConfiguration,
place_nix_configuration: PlaceNixConfiguration,
configure_nix_daemon_service: ConfigureNixDaemonService,
}
impl ConfigureNix {
#[tracing::instrument(skip_all)]
pub async fn plan(settings: InstallSettings) -> Result<Self, HarmonicError> {
let channels = settings.channels.iter().map(|(channel, _)| channel.to_string()).collect();
let channels = settings
.channels
.iter()
.map(|(channel, _)| channel.to_string())
.collect();
let setup_default_profile = SetupDefaultProfile::plan(channels).await?;
let configure_shell_profile = if settings.modify_profile {
@ -24,11 +34,19 @@ impl ConfigureNix {
} else {
None
};
let place_nix_configuration = PlaceNixConfiguration::plan(settings.nix_build_group_name, settings.extra_conf).await?;
let place_channel_configuration =
PlaceChannelConfiguration::plan(settings.channels, settings.force).await?;
let place_nix_configuration =
PlaceNixConfiguration::plan(settings.nix_build_group_name, settings.extra_conf, settings.force).await?;
let configure_nix_daemon_service = ConfigureNixDaemonService::plan().await?;
Ok(Self { place_nix_configuration, setup_default_profile, configure_nix_daemon_service, configure_shell_profile })
Ok(Self {
place_channel_configuration,
place_nix_configuration,
setup_default_profile,
configure_nix_daemon_service,
configure_shell_profile,
})
}
}
@ -36,45 +54,65 @@ impl ConfigureNix {
impl<'a> Actionable<'a> for ConfigureNix {
type Receipt = ConfigureNixReceipt;
fn description(&self) -> Vec<ActionDescription> {
let Self { setup_default_profile, configure_nix_daemon_service, place_nix_configuration, configure_shell_profile } = &self;
let Self {
setup_default_profile,
configure_nix_daemon_service,
place_nix_configuration,
place_channel_configuration,
configure_shell_profile,
} = &self;
let mut buf = setup_default_profile.description();
buf.append(&mut configure_nix_daemon_service.description());
buf.append(&mut place_nix_configuration.description());
buf.append(&mut place_channel_configuration.description());
if let Some(configure_shell_profile) = configure_shell_profile {
buf.append(&mut configure_shell_profile.description());
}
buf
}
#[tracing::instrument(skip_all)]
async fn execute(self) -> Result<Self::Receipt, HarmonicError> {
let Self { setup_default_profile, configure_nix_daemon_service, place_nix_configuration, configure_shell_profile } = self;
let Self {
setup_default_profile,
configure_nix_daemon_service,
place_nix_configuration,
place_channel_configuration,
configure_shell_profile,
} = self;
let (setup_default_profile, configure_nix_daemon_service, place_nix_configuration, configure_shell_profile) = if let Some(configure_shell_profile) = configure_shell_profile {
let (
setup_default_profile,
place_nix_configuration,
place_channel_configuration,
configure_shell_profile,
) = if let Some(configure_shell_profile) = configure_shell_profile {
let (a, b, c, d) = tokio::try_join!(
setup_default_profile.execute(),
configure_nix_daemon_service.execute(),
place_nix_configuration.execute(),
place_channel_configuration.execute(),
configure_shell_profile.execute(),
)?;
(a, b, c, Some(d))
} else {
let (a, b, c) = tokio::try_join!(
setup_default_profile.execute(),
configure_nix_daemon_service.execute(),
place_nix_configuration.execute(),
place_channel_configuration.execute(),
)?;
(a, b, c, None)
};
let configure_nix_daemon_service = configure_nix_daemon_service.execute().await?;
Ok(Self::Receipt {
setup_default_profile,
configure_nix_daemon_service,
place_nix_configuration,
place_channel_configuration,
configure_shell_profile,
})
}
}
@ -83,6 +121,7 @@ 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,
}
@ -99,6 +138,7 @@ impl<'a> Revertable<'a> for ConfigureNixReceipt {
]
}
#[tracing::instrument(skip_all)]
async fn revert(self) -> Result<(), HarmonicError> {
todo!();

View file

@ -4,9 +4,8 @@ use tokio::task::JoinSet;
use crate::HarmonicError;
use crate::actions::{ActionDescription, ActionReceipt, Actionable, Revertable};
use super::{CreateOrAppendFile, CreateOrAppendFileReceipt};
use crate::actions::base::{CreateOrAppendFile, CreateOrAppendFileReceipt};
use crate::actions::{ActionDescription, Actionable, Revertable};
const PROFILE_TARGETS: &[&str] = &[
"/etc/bashrc",
@ -24,10 +23,15 @@ pub struct ConfigureShellProfile {
}
impl ConfigureShellProfile {
#[tracing::instrument(skip_all)]
pub async fn plan() -> Result<Self, HarmonicError> {
let mut create_or_append_files = Vec::default();
for profile_target in PROFILE_TARGETS {
let path = Path::new(profile_target);
if !path.exists() {
tracing::trace!("Did not plan to edit `{profile_target}` as it does not exist.");
continue;
}
let buf = format!(
"\n\
# Nix\n\
@ -37,11 +41,14 @@ impl ConfigureShellProfile {
# End Nix\n
\n",
);
create_or_append_files.push(CreateOrAppendFile::plan(path, "root".to_string(), "root".to_string(), 0o0644, buf).await?);
create_or_append_files.push(
CreateOrAppendFile::plan(path, "root".to_string(), "root".to_string(), 0o0644, buf)
.await?,
);
}
Ok(Self {
create_or_append_files
create_or_append_files,
})
}
}
@ -50,18 +57,17 @@ impl ConfigureShellProfile {
impl<'a> Actionable<'a> for ConfigureShellProfile {
type Receipt = ConfigureShellProfileReceipt;
fn description(&self) -> Vec<ActionDescription> {
vec![
ActionDescription::new(
"Configure the shell profiles".to_string(),
vec![
"Update shell profiles to import Nix".to_string()
]
),
]
vec![ActionDescription::new(
"Configure the shell profiles".to_string(),
vec!["Update shell profiles to import Nix".to_string()],
)]
}
#[tracing::instrument(skip_all)]
async fn execute(self) -> Result<Self::Receipt, HarmonicError> {
let Self { create_or_append_files } = self;
let Self {
create_or_append_files,
} = self;
tracing::info!("Configuring shell profile");
let mut set = JoinSet::new();
@ -106,6 +112,7 @@ impl<'a> Revertable<'a> for ConfigureShellProfileReceipt {
todo!()
}
#[tracing::instrument(skip_all)]
async fn revert(self) -> Result<(), HarmonicError> {
todo!();

View file

@ -1,7 +1,24 @@
use crate::HarmonicError;
use crate::actions::base::{CreateDirectory, CreateDirectoryReceipt};
use crate::actions::{ActionDescription, ActionReceipt, Actionable, Revertable};
use crate::actions::{ActionDescription, Actionable, Revertable};
const PATHS: &[&str] = &[
"/nix",
"/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",
];
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct CreateNixTree {
@ -9,26 +26,14 @@ pub struct CreateNixTree {
}
impl CreateNixTree {
#[tracing::instrument(skip_all)]
pub async fn plan(force: bool) -> Result<Self, HarmonicError> {
let mut create_directories = Vec::default();
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 {
for path in PATHS {
// We use `create_dir` over `create_dir_all` to ensure we always set permissions right
create_directories.push(CreateDirectory::plan(path, "root".into(), "root".into(), 0o0755, force).await?)
create_directories.push(
CreateDirectory::plan(path, "root".into(), "root".into(), 0o0755, force).await?,
)
}
Ok(Self { create_directories })
@ -41,22 +46,26 @@ impl<'a> Actionable<'a> for CreateNixTree {
fn description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(
format!("Create a directory tree in `/nix`"),
vec![format!(
"Nix and the Nix daemon require a Nix Store, which will be stored at `/nix`"
)],
vec![
format!("Nix and the Nix daemon require a Nix Store, which will be stored at `/nix`"),
format!("Creates: {}", PATHS.iter().map(|v| format!("`{v}`")).collect::<Vec<_>>().join(", ")),
],
)]
}
#[tracing::instrument(skip_all)]
async fn execute(self) -> Result<Self::Receipt, HarmonicError> {
let Self { create_directories } = self;
let mut successes = Vec::with_capacity(create_directories.len());
// Just do sequential since parallizing this will have little benefit
for create_directory in create_directories {
successes.push(create_directory.execute().await?)
}
Ok(CreateNixTreeReceipt { create_directories: successes })
Ok(CreateNixTreeReceipt {
create_directories: successes,
})
}
}
@ -71,6 +80,7 @@ impl<'a> Revertable<'a> for CreateNixTreeReceipt {
todo!()
}
#[tracing::instrument(skip_all)]
async fn revert(self) -> Result<(), HarmonicError> {
todo!();

View file

@ -2,8 +2,8 @@ use tokio::task::JoinSet;
use crate::{HarmonicError, InstallSettings};
use crate::actions::base::{CreateGroup, CreateUserReceipt, CreateGroupReceipt};
use crate::actions::{ActionDescription, ActionReceipt, Actionable, CreateUser, Revertable};
use crate::actions::base::{CreateGroup, CreateGroupReceipt, CreateUserReceipt};
use crate::actions::{ActionDescription, Actionable, CreateUser, Revertable};
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct CreateUsersAndGroup {
@ -17,11 +17,13 @@ pub struct CreateUsersAndGroup {
}
impl CreateUsersAndGroup {
pub async fn plan(
settings: InstallSettings
) -> Result<Self, HarmonicError> {
#[tracing::instrument(skip_all)]
pub async fn plan(settings: InstallSettings) -> Result<Self, HarmonicError> {
// TODO(@hoverbear): CHeck if it exist, error if so
let create_group = CreateGroup::plan(settings.nix_build_group_name.clone(), settings.nix_build_group_id);
let create_group = CreateGroup::plan(
settings.nix_build_group_name.clone(),
settings.nix_build_group_id,
);
// TODO(@hoverbear): CHeck if they exist, error if so
let create_users = (0..settings.daemon_user_count)
.map(|count| {
@ -69,8 +71,13 @@ impl<'a> Actionable<'a> for CreateUsersAndGroup {
]
}
#[tracing::instrument(skip_all)]
async fn execute(self) -> Result<Self::Receipt, HarmonicError> {
let Self { create_users, create_group, .. } = self;
let Self {
create_users,
create_group,
..
} = self;
// Create group
let create_group = create_group.execute().await?;
@ -81,7 +88,7 @@ impl<'a> Actionable<'a> for CreateUsersAndGroup {
let mut successes = Vec::with_capacity(create_users.len());
let mut errors = Vec::default();
for create_user in create_users {
let _abort_handle = set.spawn(async move { create_user.execute().await });
}
@ -121,6 +128,7 @@ impl<'a> Revertable<'a> for CreateUsersAndGroupReceipt {
todo!()
}
#[tracing::instrument(skip_all)]
async fn revert(self) -> Result<(), HarmonicError> {
todo!();

View file

@ -1,13 +1,15 @@
/*! Actions which only call other base plugins. */
mod configure_nix;
mod configure_shell_profile;
mod create_nix_tree;
mod create_users_and_group;
mod configure_nix;
mod start_nix_daemon;
mod provision_nix;
mod start_nix_daemon;
pub use configure_nix::{ConfigureNix, ConfigureNixReceipt};
pub use configure_shell_profile::{ConfigureShellProfile, ConfigureShellProfileReceipt};
pub use create_nix_tree::{CreateNixTree, CreateNixTreeReceipt};
pub use create_users_and_group::{CreateUsersAndGroup, CreateUsersAndGroupReceipt};
pub use configure_nix::{ConfigureNix, ConfigureNixReceipt};
pub use provision_nix::{ProvisionNix, ProvisionNixReceipt};
pub use start_nix_daemon::{StartNixDaemon, StartNixDaemonReceipt};
pub use provision_nix::{ProvisionNix, ProvisionNixReceipt};

View file

@ -1,11 +1,14 @@
use tempdir::TempDir;
use crate::actions::base::{FetchNix, MoveUnpackedNix, FetchNixReceipt, MoveUnpackedNixReceipt};
use crate::actions::base::{FetchNix, FetchNixReceipt, MoveUnpackedNix, MoveUnpackedNixReceipt};
use crate::{HarmonicError, InstallSettings};
use crate::actions::{ActionDescription, ActionReceipt, Actionable, Revertable};
use crate::actions::{ActionDescription, Actionable, Revertable};
use super::{CreateUsersAndGroup, CreateNixTree, create_users_and_group, create_nix_tree, CreateUsersAndGroupReceipt, CreateNixTreeReceipt};
use super::{
CreateNixTree, CreateNixTreeReceipt,
CreateUsersAndGroup, CreateUsersAndGroupReceipt,
};
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct ProvisionNix {
@ -16,14 +19,24 @@ pub struct ProvisionNix {
}
impl ProvisionNix {
#[tracing::instrument(skip_all)]
pub async fn plan(settings: InstallSettings) -> Result<Self, HarmonicError> {
let tempdir = TempDir::new("nix").map_err(HarmonicError::TempDir)?;
let fetch_nix = FetchNix::plan(settings.nix_package_url.clone(), tempdir.path().to_path_buf()).await?;
let fetch_nix = FetchNix::plan(
settings.nix_package_url.clone(),
tempdir.path().to_path_buf(),
)
.await?;
let create_users_and_group = CreateUsersAndGroup::plan(settings.clone()).await?;
let create_nix_tree = CreateNixTree::plan(settings.force).await?;
let move_unpacked_nix = MoveUnpackedNix::plan(tempdir.path().to_path_buf()).await?;
Ok(Self { fetch_nix, create_users_and_group, create_nix_tree, move_unpacked_nix })
Ok(Self {
fetch_nix,
create_users_and_group,
create_nix_tree,
move_unpacked_nix,
})
}
}
@ -31,29 +44,45 @@ impl ProvisionNix {
impl<'a> Actionable<'a> for ProvisionNix {
type Receipt = ProvisionNixReceipt;
fn description(&self) -> Vec<ActionDescription> {
let Self { fetch_nix, create_users_and_group, create_nix_tree, move_unpacked_nix } = &self;
let Self {
fetch_nix,
create_users_and_group,
create_nix_tree,
move_unpacked_nix,
} = &self;
let mut buf = fetch_nix.description();
buf.append(&mut create_users_and_group.description());
buf.append(&mut create_nix_tree.description());
buf.append(&mut move_unpacked_nix.description());
buf
}
#[tracing::instrument(skip_all)]
async fn execute(self) -> Result<Self::Receipt, HarmonicError> {
let Self { fetch_nix, create_nix_tree, create_users_and_group, move_unpacked_nix } = self;
let Self {
fetch_nix,
create_nix_tree,
create_users_and_group,
move_unpacked_nix,
} = self;
// We fetch nix while doing the rest, then move it over.
// We fetch nix while doing the rest, then move it over.
let fetch_nix_handle = tokio::spawn(async move { fetch_nix.execute().await });
let create_users_and_group = create_users_and_group.execute().await?;
let create_nix_tree = create_nix_tree.execute().await?;
let fetch_nix = fetch_nix_handle.await??;
let move_unpacked_nix = move_unpacked_nix.execute().await?;
Ok(ProvisionNixReceipt { fetch_nix, create_users_and_group, create_nix_tree, move_unpacked_nix })
Ok(ProvisionNixReceipt {
fetch_nix,
create_users_and_group,
create_nix_tree,
move_unpacked_nix,
})
}
}
@ -71,6 +100,7 @@ impl<'a> Revertable<'a> for ProvisionNixReceipt {
todo!()
}
#[tracing::instrument(skip_all)]
async fn revert(self) -> Result<(), HarmonicError> {
todo!();

View file

@ -1,20 +1,22 @@
use crate::actions::base::{ConfigureNixDaemonServiceReceipt, ConfigureNixDaemonService, StartSystemdUnit, StartSystemdUnitReceipt};
use crate::{HarmonicError, InstallSettings};
use crate::actions::base::{StartSystemdUnit, StartSystemdUnitReceipt};
use crate::HarmonicError;
use crate::actions::{ActionDescription, ActionReceipt, Actionable, Revertable};
use crate::actions::{ActionDescription, Actionable, Revertable};
/// This is mostly indirection for supporting non-systemd
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct StartNixDaemon {
start_systemd_socket: StartSystemdUnit,
start_systemd_service: StartSystemdUnit,
start_systemd_socket: StartSystemdUnit,
}
impl StartNixDaemon {
pub async fn plan(settings: InstallSettings) -> Result<Self, HarmonicError> {
#[tracing::instrument(skip_all)]
pub async fn plan() -> Result<Self, HarmonicError> {
let start_systemd_socket = StartSystemdUnit::plan("nix-daemon.socket".into()).await?;
let start_systemd_service = StartSystemdUnit::plan("nix-daemon.service".into()).await?;
Ok(Self { start_systemd_socket, start_systemd_service })
Ok(Self {
start_systemd_socket,
})
}
}
@ -22,22 +24,27 @@ impl StartNixDaemon {
impl<'a> Actionable<'a> for StartNixDaemon {
type Receipt = StartNixDaemonReceipt;
fn description(&self) -> Vec<ActionDescription> {
let Self { start_systemd_socket, start_systemd_service } = &self;
start_systemd_service.description()
let Self {
start_systemd_socket,
} = &self;
start_systemd_socket.description()
}
#[tracing::instrument(skip_all)]
async fn execute(self) -> Result<Self::Receipt, HarmonicError> {
let Self { start_systemd_socket, start_systemd_service } = self;
let start_systemd_service = start_systemd_service.execute().await?;
let Self {
start_systemd_socket,
} = self;
let start_systemd_socket = start_systemd_socket.execute().await?;
Ok(Self::Receipt { start_systemd_socket, start_systemd_service })
Ok(Self::Receipt {
start_systemd_socket,
})
}
}
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct StartNixDaemonReceipt {
start_systemd_socket: StartSystemdUnitReceipt,
start_systemd_service: StartSystemdUnitReceipt,
}
#[async_trait::async_trait]
@ -46,6 +53,7 @@ impl<'a> Revertable<'a> for StartNixDaemonReceipt {
todo!()
}
#[tracing::instrument(skip_all)]
async fn revert(self) -> Result<(), HarmonicError> {
todo!();

View file

@ -2,31 +2,21 @@ pub mod base;
pub mod meta;
use base::{
ConfigureNixDaemonService, ConfigureNixDaemonServiceReceipt,
ConfigureShellProfile, ConfigureShellProfileReceipt,
CreateDirectory, CreateDirectoryReceipt,
CreateFile, CreateFileReceipt,
CreateGroup, CreateGroupReceipt,
CreateOrAppendFile, CreateOrAppendFileReceipt,
CreateUser, CreateUserReceipt,
FetchNix, FetchNixReceipt,
MoveUnpackedNix, MoveUnpackedNixReceipt,
PlaceChannelConfiguration, PlaceChannelConfigurationReceipt,
PlaceNixConfiguration, PlaceNixConfigurationReceipt,
ConfigureNixDaemonService, ConfigureNixDaemonServiceReceipt, CreateDirectory,
CreateDirectoryReceipt, CreateFile, CreateFileReceipt, CreateGroup, CreateGroupReceipt,
CreateOrAppendFile, CreateOrAppendFileReceipt, CreateUser, CreateUserReceipt, FetchNix,
FetchNixReceipt, MoveUnpackedNix, MoveUnpackedNixReceipt, PlaceChannelConfiguration,
PlaceChannelConfigurationReceipt, PlaceNixConfiguration, PlaceNixConfigurationReceipt,
SetupDefaultProfile, SetupDefaultProfileReceipt,
};
use meta::{
ConfigureNix, ConfigureNixReceipt,
CreateNixTree, CreateNixTreeReceipt,
CreateUsersAndGroup, CreateUsersAndGroupReceipt,
StartNixDaemon, StartNixDaemonReceipt,
ConfigureNix, ConfigureNixReceipt, ConfigureShellProfile, ConfigureShellProfileReceipt,
CreateNixTree, CreateNixTreeReceipt, CreateUsersAndGroup, CreateUsersAndGroupReceipt,
ProvisionNix, ProvisionNixReceipt, StartNixDaemon, StartNixDaemonReceipt,
};
use crate::HarmonicError;
use self::meta::{ProvisionNix, ProvisionNixReceipt};
#[async_trait::async_trait]
pub trait Actionable<'a>: serde::de::Deserialize<'a> + serde::Serialize {
type Receipt;
@ -125,21 +115,37 @@ impl<'a> Actionable<'a> for Action {
async fn execute(self) -> Result<Self::Receipt, HarmonicError> {
match self {
Action::ConfigureNixDaemonService(i) => i.execute().await.map(ActionReceipt::ConfigureNixDaemonService),
Action::ConfigureNixDaemonService(i) => i
.execute()
.await
.map(ActionReceipt::ConfigureNixDaemonService),
Action::ConfigureNix(i) => i.execute().await.map(ActionReceipt::ConfigureNix),
Action::ConfigureShellProfile(i) => i.execute().await.map(ActionReceipt::ConfigureShellProfile),
Action::ConfigureShellProfile(i) => {
i.execute().await.map(ActionReceipt::ConfigureShellProfile)
},
Action::CreateDirectory(i) => i.execute().await.map(ActionReceipt::CreateDirectory),
Action::CreateFile(i) => i.execute().await.map(ActionReceipt::CreateFile),
Action::CreateGroup(i) => i.execute().await.map(ActionReceipt::CreateGroup),
Action::CreateOrAppendFile(i) => i.execute().await.map(ActionReceipt::CreateOrAppendFile),
Action::CreateOrAppendFile(i) => {
i.execute().await.map(ActionReceipt::CreateOrAppendFile)
},
Action::CreateNixTree(i) => i.execute().await.map(ActionReceipt::CreateNixTree),
Action::CreateUser(i) => i.execute().await.map(ActionReceipt::CreateUser),
Action::CreateUsersAndGroup(i) => i.execute().await.map(ActionReceipt::CreateUsersAndGroup),
Action::CreateUsersAndGroup(i) => {
i.execute().await.map(ActionReceipt::CreateUsersAndGroup)
},
Action::FetchNix(i) => i.execute().await.map(ActionReceipt::FetchNix),
Action::MoveUnpackedNix(i) => i.execute().await.map(ActionReceipt::MoveUnpackedNix),
Action::PlaceChannelConfiguration(i) => i.execute().await.map(ActionReceipt::PlaceChannelConfiguration),
Action::PlaceNixConfiguration(i) => i.execute().await.map(ActionReceipt::PlaceNixConfiguration),
Action::SetupDefaultProfile(i) => i.execute().await.map(ActionReceipt::SetupDefaultProfile),
Action::PlaceChannelConfiguration(i) => i
.execute()
.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),
}

View file

@ -1,11 +1,11 @@
use std::process::ExitCode;
use clap::{ArgAction, Parser};
use harmonic::{InstallPlan, InstallSettings};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use harmonic::InstallPlan;
use tokio::io::AsyncReadExt;
use crate::{
cli::{arg::ChannelValue, CommandExecute},
cli::CommandExecute,
interaction,
};

View file

@ -20,6 +20,8 @@ pub enum HarmonicError {
NoNssCacert,
#[error("No supported init system found")]
InitNotSupported,
#[error("Creating file `{0}`: {1}")]
CreateFile(std::path::PathBuf, std::io::Error),
#[error("Creating directory `{0}`: {1}")]
CreateDirectory(std::path::PathBuf, std::io::Error),
#[error("Walking directory `{0}`")]

View file

@ -2,12 +2,8 @@ use serde::{Deserialize, Serialize};
use crate::{
actions::{
meta::{ConfigureNix, ProvisionNix, StartNixDaemon},
Action, ActionDescription, ActionReceipt, Actionable, Revertable,
meta::{
ConfigureNix,
ProvisionNix,
StartNixDaemon,
},
},
settings::InstallSettings,
HarmonicError,
@ -38,6 +34,7 @@ pub struct InstallPlan {
}
impl InstallPlan {
#[tracing::instrument(skip_all)]
pub fn description(&self) -> String {
format!(
"\
@ -85,10 +82,12 @@ impl InstallPlan {
let actions = vec![
Action::ProvisionNix(ProvisionNix::plan(settings.clone()).await?),
Action::ConfigureNix(ConfigureNix::plan(settings.clone()).await?),
Action::StartNixDaemon(StartNixDaemon::plan(settings.clone()).await?),
Action::StartNixDaemon(StartNixDaemon::plan().await?),
];
Ok(Self { settings, actions })
}
#[tracing::instrument(skip_all)]
pub async fn install(self) -> Result<Receipt, HarmonicError> {
let mut receipt = Receipt::default();
// This is **deliberately sequential**.
@ -98,18 +97,18 @@ impl InstallPlan {
match action.execute().await {
Ok(action_receipt) => receipt.actions.push(action_receipt),
Err(err) => {
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));
}
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));
// }
},
};
}

View file

@ -26,7 +26,10 @@ impl Default for InstallSettings {
nix_build_group_id: 3000,
nix_build_user_prefix: String::from("nixbld"),
nix_build_user_id_base: 3001,
nix_package_url: "https://releases.nixos.org/nix/nix-2.11.0/nix-2.11.0-x86_64-linux.tar.xz".parse().expect("Could not parse default Nix archive url, please report this issue"),
nix_package_url:
"https://releases.nixos.org/nix/nix-2.11.0/nix-2.11.0-x86_64-linux.tar.xz"
.parse()
.expect("Could not parse default Nix archive url, please report this issue"),
extra_conf: Default::default(),
force: false,
}