A bunch of fleshing out

Signed-off-by: Ana Hobden <operator@hoverbear.org>
This commit is contained in:
Ana Hobden 2022-09-21 13:26:45 -07:00
parent 4fdba83039
commit 3c1a8fdcc8
28 changed files with 881 additions and 283 deletions

View file

@ -1,13 +1,31 @@
use crate::HarmonicError;
use std::path::{Path, PathBuf};
use tokio::process::Command;
use crate::{HarmonicError, execute_command};
use crate::actions::{ActionDescription, ActionReceipt, 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 TMPFILES_DEST: &str = "/etc/tmpfiles.d/nix-daemon.conf";
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct ConfigureNixDaemonService {}
impl ConfigureNixDaemonService {
pub fn plan() -> Self {
Self {}
pub async fn plan() -> Result<Self, HarmonicError> {
if !Path::new("/run/systemd/system").exists() {
return Err(HarmonicError::InitNotSupported);
}
Ok(Self {})
}
}
@ -26,7 +44,29 @@ impl<'a> Actionable<'a> for ConfigureNixDaemonService {
}
async fn execute(self) -> Result<Self::Receipt, HarmonicError> {
todo!()
tracing::info!("Configuring nix daemon service");
tracing::trace!("Symlinking");
tokio::fs::symlink(TMPFILES_SRC, TMPFILES_DEST)
.await
.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?;
execute_command(
Command::new("systemctl").arg("link").arg(SERVICE_SRC),
false,
)
.await?;
execute_command(Command::new("systemctl").arg("daemon-reload"), false).await?;
Ok(Self::Receipt {})
}
}

View file

@ -1,13 +1,48 @@
use std::path::Path;
use tokio::task::JoinSet;
use crate::HarmonicError;
use crate::actions::{ActionDescription, ActionReceipt, Actionable, Revertable};
use super::{CreateOrAppendFile, CreateOrAppendFileReceipt};
const PROFILE_TARGETS: &[&str] = &[
"/etc/bashrc",
"/etc/profile.d/nix.sh",
"/etc/zshrc",
"/etc/bash.bashrc",
"/etc/zsh/zshrc",
// TODO(@hoverbear): FIsh
];
const PROFILE_NIX_FILE: &str = "/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh";
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct ConfigureShellProfile {}
pub struct ConfigureShellProfile {
create_or_append_files: Vec<CreateOrAppendFile>,
}
impl ConfigureShellProfile {
pub fn plan() -> Self {
Self {}
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);
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_files.push(CreateOrAppendFile::plan(path, "root".to_string(), "root".to_string(), 0o0644, buf).await?);
}
Ok(Self {
create_or_append_files
})
}
}
@ -26,24 +61,49 @@ impl<'a> Actionable<'a> for ConfigureShellProfile {
}
async fn execute(self) -> Result<Self::Receipt, HarmonicError> {
todo!()
let Self { create_or_append_files } = self;
tracing::info!("Configuring shell profile");
let mut set = JoinSet::new();
let mut successes = Vec::with_capacity(create_or_append_files.len());
let mut errors = Vec::default();
for create_or_append_file in create_or_append_files {
let _abort_handle = set.spawn(async move { create_or_append_file.execute().await });
}
while let Some(result) = set.join_next().await {
match result {
Ok(Ok(success)) => successes.push(success),
Ok(Err(e)) => errors.push(e),
Err(e) => errors.push(e.into()),
};
}
if !errors.is_empty() {
if errors.len() == 1 {
return Err(errors.into_iter().next().unwrap());
} else {
return Err(HarmonicError::Multiple(errors));
}
}
Ok(Self::Receipt {
create_or_append_files: successes,
})
}
}
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct ConfigureShellProfileReceipt {}
pub struct ConfigureShellProfileReceipt {
create_or_append_files: Vec<CreateOrAppendFileReceipt>,
}
#[async_trait::async_trait]
impl<'a> Revertable<'a> for ConfigureShellProfileReceipt {
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()
]
),
]
todo!()
}
async fn revert(self) -> Result<(), HarmonicError> {

View file

@ -3,6 +3,9 @@ use std::{
path::{Path, PathBuf},
};
use nix::unistd::{Group, User, Gid, Uid, chown};
use tokio::fs::create_dir;
use crate::HarmonicError;
use crate::actions::{ActionDescription, ActionReceipt, Actionable, Revertable};
@ -16,14 +19,26 @@ pub struct CreateDirectory {
}
impl CreateDirectory {
pub fn plan(path: impl AsRef<Path>, user: String, group: String, mode: u32) -> Self {
let path = path.as_ref().to_path_buf();
Self {
path,
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()))))
}
// 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,
group,
mode,
}
})
}
}
@ -31,10 +46,11 @@ impl CreateDirectory {
impl<'a> Actionable<'a> for CreateDirectory {
type Receipt = CreateDirectoryReceipt;
fn description(&self) -> Vec<ActionDescription> {
let Self { path, user, group, mode } = &self;
vec![ActionDescription::new(
format!("Create the directory `/nix`"),
format!("Create the directory `{}`", path.display()),
vec![format!(
"Nix and the Nix daemon require a Nix Store, which will be stored at `/nix`"
"Creating directory `{}` owned by `{user}:{group}` with mode `{mode:#o}`", path.display()
)],
)]
}
@ -46,7 +62,22 @@ impl<'a> Actionable<'a> for CreateDirectory {
group,
mode,
} = self;
todo!();
let gid = Group::from_name(group.as_str())
.map_err(|e| HarmonicError::GroupId(group.clone(), e))?
.ok_or(HarmonicError::NoGroup(group.clone()))?
.gid;
let uid = User::from_name(user.as_str())
.map_err(|e| HarmonicError::UserId(user.clone(), e))?
.ok_or(HarmonicError::NoUser(user.clone()))?
.uid;
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))?;
Ok(CreateDirectoryReceipt {
path,
user,

View file

@ -0,0 +1,100 @@
use std::{
fs::Permissions,
path::{Path, PathBuf, self}, io::SeekFrom,
};
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};
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct CreateFile {
path: PathBuf,
user: String,
group: String,
mode: u32,
buf: String,
}
impl CreateFile {
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 })
}
}
#[async_trait::async_trait]
impl<'a> Actionable<'a> for CreateFile {
type Receipt = CreateFileReceipt;
fn description(&self) -> Vec<ActionDescription> {
let Self { path, user, group, mode, buf } = &self;
vec![ActionDescription::new(
format!("Create or overwrite file `{}`", path.display()),
vec![format!(
"Create or overwrite `{}` owned by `{user}:{group}` with mode `{mode:#o}` with `{buf}`", path.display()
)],
)]
}
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 mut file = OpenOptions::new()
.create_new(true)
.write(true)
.read(true)
.open(&path)
.await
.map_err(|e| HarmonicError::OpenFile(path.to_owned(), e))?;
file.write_all(buf.as_bytes())
.await
.map_err(|e| HarmonicError::WriteFile(path.to_owned(), e))?;
let gid = Group::from_name(group.as_str())
.map_err(|e| HarmonicError::GroupId(group.clone(), e))?
.ok_or(HarmonicError::NoGroup(group.clone()))?
.gid;
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 })
}
}
#[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!()
}
async fn revert(self) -> Result<(), HarmonicError> {
todo!();
Ok(())
}
}

View file

@ -1,3 +1,5 @@
use tokio::process::Command;
use crate::HarmonicError;
use crate::actions::{ActionDescription, ActionReceipt, Actionable, Revertable};
@ -5,12 +7,14 @@ use crate::actions::{ActionDescription, ActionReceipt, Actionable, Revertable};
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct CreateGroup {
name: String,
uid: usize,
gid: usize,
}
impl CreateGroup {
pub fn plan(name: String, uid: usize) -> Self {
Self { name, uid }
pub fn plan(name: String, gid: usize) -> Self {
Self { name, gid }
}
}
@ -18,10 +22,9 @@ impl CreateGroup {
impl<'a> Actionable<'a> for CreateGroup {
type Receipt = CreateGroupReceipt;
fn description(&self) -> Vec<ActionDescription> {
let name = &self.name;
let uid = &self.uid;
let Self { name, gid } = &self;
vec![ActionDescription::new(
format!("Create group {name} with UID {uid}"),
format!("Create group {name} with GID {gid}"),
vec![format!(
"The nix daemon requires a system user group its system users can be part of"
)],
@ -29,15 +32,36 @@ impl<'a> Actionable<'a> for CreateGroup {
}
async fn execute(self) -> Result<Self::Receipt, HarmonicError> {
let Self { name, uid } = self;
Ok(CreateGroupReceipt { name, uid })
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)),
}
Ok(CreateGroupReceipt { name, gid })
}
}
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct CreateGroupReceipt {
name: String,
uid: usize,
gid: usize,
}
#[async_trait::async_trait]

View file

@ -0,0 +1,108 @@
use std::{
fs::Permissions,
path::{Path, PathBuf, self}, io::SeekFrom,
};
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};
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct CreateOrAppendFile {
path: PathBuf,
user: String,
group: String,
mode: u32,
buf: String,
}
impl CreateOrAppendFile {
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 })
}
}
#[async_trait::async_trait]
impl<'a> Actionable<'a> for CreateOrAppendFile {
type Receipt = CreateOrAppendFileReceipt;
fn description(&self) -> Vec<ActionDescription> {
let Self { path, user, group, mode, buf } = &self;
vec![ActionDescription::new(
format!("Create or append file `{}`", path.display()),
vec![format!(
"Create or append `{}` owned by `{user}:{group}` with mode `{mode:#o}` with `{buf}`", path.display()
)],
)]
}
async fn execute(self) -> Result<CreateOrAppendFileReceipt, 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 mut file = OpenOptions::new()
.create(true)
.write(true)
.read(true)
.open(&path)
.await
.map_err(|e| HarmonicError::OpenFile(path.to_owned(), e))?;
file.seek(SeekFrom::End(0))
.await
.map_err(|e| HarmonicError::SeekFile(path.to_owned(), e))?;
file.write_all(buf.as_bytes())
.await
.map_err(|e| HarmonicError::WriteFile(path.to_owned(), e))?;
let gid = Group::from_name(group.as_str())
.map_err(|e| HarmonicError::GroupId(group.clone(), e))?
.ok_or(HarmonicError::NoGroup(group.clone()))?
.gid;
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 })
}
}
#[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`"
)],
)]
}
async fn revert(self) -> Result<(), HarmonicError> {
todo!();
Ok(())
}
}

View file

@ -1,3 +1,5 @@
use tokio::process::Command;
use crate::HarmonicError;
use crate::actions::{ActionDescription, ActionReceipt, Actionable, Revertable};
@ -6,11 +8,12 @@ use crate::actions::{ActionDescription, ActionReceipt, Actionable, Revertable};
pub struct CreateUser {
name: String,
uid: usize,
gid: usize,
}
impl CreateUser {
pub fn plan(name: String, uid: usize) -> Self {
Self { name, uid }
pub fn plan(name: String, uid: usize, gid: usize) -> Self {
Self { name, uid, gid }
}
}
@ -29,8 +32,41 @@ impl<'a> Actionable<'a> for CreateUser {
}
async fn execute(self) -> Result<Self::Receipt, HarmonicError> {
let Self { name, uid } = self;
Ok(CreateUserReceipt { name, uid })
let Self { name, uid, gid } = self;
let mut command = Command::new("useradd");
command.args([
"--home-dir",
"/var/empty",
"--comment",
&format!("\"Nix build user\""),
"--gid",
&gid.to_string(),
"--groups",
&gid.to_string(),
"--no-user-group",
"--system",
"--shell",
"/sbin/nologin",
"--uid",
&uid.to_string(),
"--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)),
}
Ok(CreateUserReceipt { name, uid, gid })
}
}
@ -38,6 +74,7 @@ impl<'a> Actionable<'a> for CreateUser {
pub struct CreateUserReceipt {
name: String,
uid: usize,
gid: usize,
}
#[async_trait::async_trait]

View file

@ -1,6 +1,8 @@
use std::path::{Path, PathBuf};
use bytes::Buf;
use reqwest::Url;
use tokio::task::spawn_blocking;
use crate::HarmonicError;
@ -13,8 +15,11 @@ pub struct FetchNix {
}
impl FetchNix {
pub fn plan(url: Url, destination: PathBuf) -> Self {
Self { url, destination }
pub async fn plan(url: Url, destination: PathBuf) -> Result<Self, HarmonicError> {
// TODO(@hoverbear): Check URL exists?
// TODO(@hoverbear): Check tempdir exists
Ok(Self { url, destination })
}
}
@ -35,6 +40,24 @@ impl<'a> Actionable<'a> for FetchNix {
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)?;
let bytes = res.bytes().await.map_err(HarmonicError::Reqwest)?;
// TODO(@Hoverbear): Pick directory
tracing::trace!("Unpacking tar.xz");
let destination_clone = destination.clone();
let handle: Result<(), HarmonicError> = spawn_blocking(move || {
let decoder = xz2::read::XzDecoder::new(bytes.reader());
let mut archive = tar::Archive::new(decoder);
archive.unpack(&destination_clone).map_err(HarmonicError::Unarchive)?;
tracing::debug!(destination = %destination_clone.display(), "Downloaded & extracted Nix");
Ok(())
})
.await?;
handle?;
Ok(FetchNixReceipt { url, destination })
}
}

View file

@ -3,21 +3,25 @@
mod configure_nix_daemon_service;
mod configure_shell_profile;
mod create_directory;
mod create_file;
mod create_group;
mod create_or_append_file;
mod create_user;
mod fetch_nix;
mod move_unpacked_nix;
mod place_channel_configuration;
mod place_nix_configuration;
mod setup_default_profile;
mod start_systemd_service;
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};
pub use create_or_append_file::{CreateOrAppendFile, CreateOrAppendFileReceipt};
pub use create_user::{CreateUser, CreateUserReceipt};
pub use fetch_nix::{FetchNix, FetchNixReceipt};
pub use move_unpacked_nix::{MoveUnpackedNix, MoveUnpackedNixReceipt};
@ -26,4 +30,4 @@ pub use place_channel_configuration::{
};
pub use place_nix_configuration::{PlaceNixConfiguration, PlaceNixConfigurationReceipt};
pub use setup_default_profile::{SetupDefaultProfile, SetupDefaultProfileReceipt};
pub use start_systemd_service::{StartSystemdService, StartSystemdServiceReceipt};
pub use start_systemd_unit::{StartSystemdUnit, StartSystemdUnitReceipt};

View file

@ -1,4 +1,4 @@
use std::path::PathBuf;
use std::path::{PathBuf, Path};
use crate::HarmonicError;
@ -10,8 +10,9 @@ pub struct MoveUnpackedNix {
}
impl MoveUnpackedNix {
pub fn plan(source: PathBuf) -> Self {
Self { source, }
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, })
}
}
@ -30,6 +31,23 @@ impl<'a> Actionable<'a> for MoveUnpackedNix {
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<_>, _>>()?;
assert_eq!(
found_nix_paths.len(),
1,
"Did not expect to find multiple nix paths, please report this"
);
let found_nix_path = found_nix_paths.into_iter().next().unwrap();
tracing::trace!("Renaming");
let src = found_nix_path.join("store");
let dest = Path::new("/nix/store");
tokio::fs::rename(src.clone(), dest)
.await
.map_err(|e| HarmonicError::Rename(src, dest.to_owned(), e))?;
Ok(MoveUnpackedNixReceipt { })
}
}

View file

@ -1,13 +1,30 @@
use std::path::Path;
use reqwest::Url;
use crate::HarmonicError;
use crate::actions::{ActionDescription, ActionReceipt, Actionable, Revertable};
use super::{CreateOrAppendFile, CreateFile, CreateFileReceipt};
const NIX_CHANNELS_PATH: &str = "/root/.nix-channels";
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct PlaceChannelConfiguration {}
pub struct PlaceChannelConfiguration {
channels: Vec<(String, Url)>,
create_file: CreateFile,
}
impl PlaceChannelConfiguration {
pub fn plan() -> Self {
Self {}
pub async fn plan(channels: Vec<(String, Url)>) -> 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 })
}
}
@ -15,35 +32,34 @@ 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(
"Start the systemd Nix daemon".to_string(),
"Place a channel configuration".to_string(),
vec![
"The `nix` command line tool communicates with a running Nix daemon managed by your init system".to_string()
"Place a configuration at `{NIX_CHANNELS_PATH}` setting the channels".to_string()
]
),
]
}
async fn execute(self) -> Result<Self::Receipt, HarmonicError> {
todo!()
let Self { create_file, channels } = self;
let create_file = create_file.execute().await?;
Ok(Self::Receipt { create_file, channels })
}
}
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct PlaceChannelConfigurationReceipt {}
pub struct PlaceChannelConfigurationReceipt {
channels: Vec<(String, Url)>,
create_file: CreateFileReceipt,
}
#[async_trait::async_trait]
impl<'a> Revertable<'a> for PlaceChannelConfigurationReceipt {
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()
]
),
]
todo!()
}
async fn revert(self) -> Result<(), HarmonicError> {

View file

@ -2,12 +2,26 @@ use crate::HarmonicError;
use crate::actions::{ActionDescription, ActionReceipt, Actionable, Revertable};
use super::{CreateFile, CreateFileReceipt};
const NIX_CONF: &str = "/etc/nix/nix.conf";
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct PlaceNixConfiguration {}
pub struct PlaceNixConfiguration {
create_file: CreateFile,
}
impl PlaceNixConfiguration {
pub fn plan() -> Self {
Self {}
pub async fn plan(nix_build_group_name: String, extra_conf: Option<String>) -> Result<Self, HarmonicError> {
let buf = format!(
"\
{extra_conf}\n\
build-users-group = {nix_build_group_name}\n\
",
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 })
}
}
@ -17,33 +31,30 @@ impl<'a> Actionable<'a> for PlaceNixConfiguration {
fn description(&self) -> Vec<ActionDescription> {
vec![
ActionDescription::new(
"Start the systemd Nix daemon".to_string(),
"Place the nix configuration".to_string(),
vec![
"The `nix` command line tool communicates with a running Nix daemon managed by your init system".to_string()
"Boop".to_string()
]
),
]
}
async fn execute(self) -> Result<Self::Receipt, HarmonicError> {
todo!()
let Self { create_file } = self;
let create_file = create_file.execute().await?;
Ok(Self::Receipt { create_file })
}
}
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct PlaceNixConfigurationReceipt {}
pub struct PlaceNixConfigurationReceipt {
create_file: CreateFileReceipt,
}
#[async_trait::async_trait]
impl<'a> Revertable<'a> for PlaceNixConfigurationReceipt {
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()
]
),
]
todo!()
}
async fn revert(self) -> Result<(), HarmonicError> {

View file

@ -1,13 +1,18 @@
use crate::HarmonicError;
use crate::{HarmonicError, execute_command};
use glob::glob;
use tokio::process::Command;
use crate::actions::{ActionDescription, ActionReceipt, Actionable, Revertable};
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct SetupDefaultProfile {}
pub struct SetupDefaultProfile {
channels: Vec<String>,
}
impl SetupDefaultProfile {
pub fn plan() -> Self {
Self {}
pub async fn plan(channels: Vec<String>) -> Result<Self, HarmonicError> {
Ok(Self { channels })
}
}
@ -26,7 +31,85 @@ impl<'a> Actionable<'a> for SetupDefaultProfile {
}
async fn execute(self) -> Result<Self::Receipt, HarmonicError> {
todo!()
let Self { channels } = self;
tracing::info!("Setting up default profile");
// Find an `nix` package
let nix_pkg_glob = "/nix/store/*-nix-*";
let mut found_nix_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_nix_pkg = Some(path);
break;
},
Err(_) => continue, /* Ignore it */
};
}
let nix_pkg = if let Some(nix_pkg) = found_nix_pkg {
nix_pkg
} else {
return Err(HarmonicError::NoNssCacert); // TODO(@hoverbear): Fix this error
};
// Install `nix` itself into the store
execute_command(
Command::new(nix_pkg.join("bin/nix-env"))
.arg("-i")
.arg(&nix_pkg),
false,
)
.await?;
// Find an `nss-cacert` package, add it too.
let nss_ca_cert_pkg_glob = "/nix/store/*-nss-cacert-*";
let mut found_nss_ca_cert_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_nss_ca_cert_pkg = Some(path);
break;
},
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"))
.arg("-i")
.arg(&nss_ca_cert_pkg),
false,
)
.await?;
if !channels.is_empty() {
let mut command = Command::new(nix_pkg.join("bin/nix-channel"));
command.arg("--update");
for channel in channels {
command.arg(channel);
}
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)),
}
}
Ok(Self::Receipt {})
}
}

View file

@ -1,54 +0,0 @@
use crate::HarmonicError;
use crate::actions::{ActionDescription, ActionReceipt, Actionable, Revertable};
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct StartSystemdService {}
impl StartSystemdService {
pub fn plan() -> Self {
Self {}
}
}
#[async_trait::async_trait]
impl<'a> Actionable<'a> for StartSystemdService {
type Receipt = StartSystemdServiceReceipt;
fn description(&self) -> Vec<ActionDescription> {
vec![
ActionDescription::new(
"Start 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()
]
),
]
}
async fn execute(self) -> Result<Self::Receipt, HarmonicError> {
todo!()
}
}
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct StartSystemdServiceReceipt {}
#[async_trait::async_trait]
impl<'a> Revertable<'a> for StartSystemdServiceReceipt {
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()
]
),
]
}
async fn revert(self) -> Result<(), HarmonicError> {
todo!();
Ok(())
}
}

View file

@ -0,0 +1,71 @@
use tokio::process::Command;
use crate::{HarmonicError, execute_command};
use crate::actions::{ActionDescription, ActionReceipt, Actionable, Revertable};
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct StartSystemdUnit {
unit: String,
}
impl StartSystemdUnit {
pub async fn plan(unit: String) -> Result<Self, HarmonicError> {
Ok(Self { unit })
}
}
#[async_trait::async_trait]
impl<'a> Actionable<'a> for StartSystemdUnit {
type Receipt = StartSystemdUnitReceipt;
fn description(&self) -> Vec<ActionDescription> {
vec![
ActionDescription::new(
"Start 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()
]
),
]
}
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(format!("{unit}")),
false,
)
.await?;
Ok(Self::Receipt {})
}
}
#[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!()
}
async fn revert(self) -> Result<(), HarmonicError> {
todo!();
Ok(())
}
}

View file

@ -1,30 +1,34 @@
use tokio::task::JoinSet;
use crate::actions::base::{SetupDefaultProfile, ConfigureNixDaemonService, ConfigureShellProfile, SetupDefaultProfileReceipt, ConfigureNixDaemonServiceReceipt, 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};
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct ConfigureNix {
setup_default_profile: SetupDefaultProfile,
configure_nix_daemon_service: ConfigureNixDaemonService,
configure_shell_profile: Option<ConfigureShellProfile>,
place_nix_configuration: PlaceNixConfiguration,
configure_nix_daemon_service: ConfigureNixDaemonService,
}
impl ConfigureNix {
pub fn plan(settings: InstallSettings) -> Self {
let setup_default_profile = SetupDefaultProfile::plan();
let configure_nix_daemon_service = ConfigureNixDaemonService::plan();
pub async fn plan(settings: InstallSettings) -> Result<Self, HarmonicError> {
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 {
Some(ConfigureShellProfile::plan())
Some(ConfigureShellProfile::plan().await?)
} else {
None
};
let place_nix_configuration = PlaceNixConfiguration::plan(settings.nix_build_group_name, settings.extra_conf).await?;
let configure_nix_daemon_service = ConfigureNixDaemonService::plan().await?;
Self { setup_default_profile, configure_nix_daemon_service, configure_shell_profile }
Ok(Self { place_nix_configuration, setup_default_profile, configure_nix_daemon_service, configure_shell_profile })
}
}
@ -32,10 +36,11 @@ 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, configure_shell_profile } = &self;
let Self { setup_default_profile, configure_nix_daemon_service, place_nix_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());
if let Some(configure_shell_profile) = configure_shell_profile {
buf.append(&mut configure_shell_profile.description());
}
@ -44,26 +49,29 @@ impl<'a> Actionable<'a> for ConfigureNix {
}
async fn execute(self) -> Result<Self::Receipt, HarmonicError> {
let Self { setup_default_profile, configure_nix_daemon_service, configure_shell_profile } = self;
let Self { setup_default_profile, configure_nix_daemon_service, place_nix_configuration, configure_shell_profile } = self;
let (setup_default_profile, configure_nix_daemon_service, configure_shell_profile) = if let Some(configure_shell_profile) = configure_shell_profile {
let (setup_default_profile, configure_nix_daemon_service, place_nix_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(),
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(),
configure_shell_profile.execute(),
place_nix_configuration.execute(),
)?;
(a, b, Some(c))
} else {
let (a, b) = tokio::try_join!(
setup_default_profile.execute(),
configure_nix_daemon_service.execute(),
)?;
(a, b, None)
(a, b, c, None)
};
Ok(Self::Receipt {
setup_default_profile,
configure_nix_daemon_service,
place_nix_configuration,
configure_shell_profile,
})
@ -73,8 +81,9 @@ impl<'a> Actionable<'a> for ConfigureNix {
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct ConfigureNixReceipt {
setup_default_profile: SetupDefaultProfileReceipt,
configure_nix_daemon_service: ConfigureNixDaemonServiceReceipt,
configure_shell_profile: Option<ConfigureShellProfileReceipt>,
place_nix_configuration: PlaceNixConfigurationReceipt,
configure_nix_daemon_service: ConfigureNixDaemonServiceReceipt,
}
#[async_trait::async_trait]

View file

@ -1,15 +1,37 @@
use crate::{HarmonicError, InstallSettings};
use crate::HarmonicError;
use crate::actions::base::{CreateDirectory, CreateDirectoryReceipt};
use crate::actions::{ActionDescription, ActionReceipt, Actionable, Revertable};
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct CreateNixTree {
settings: InstallSettings,
create_directories: Vec<CreateDirectory>,
}
impl CreateNixTree {
pub fn plan(settings: InstallSettings) -> Self {
Self { settings }
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 {
// 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?)
}
Ok(Self { create_directories })
}
}
@ -26,13 +48,22 @@ impl<'a> Actionable<'a> for CreateNixTree {
}
async fn execute(self) -> Result<Self::Receipt, HarmonicError> {
let Self { settings: _ } = self;
Ok(CreateNixTreeReceipt {})
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 })
}
}
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct CreateNixTreeReceipt {}
pub struct CreateNixTreeReceipt {
create_directories: Vec<CreateDirectoryReceipt>,
}
#[async_trait::async_trait]
impl<'a> Revertable<'a> for CreateNixTreeReceipt {

View file

@ -1,46 +0,0 @@
use crate::HarmonicError;
use crate::actions::{ActionDescription, ActionReceipt, Actionable, Revertable};
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct CreateNixTreeDirs {}
impl CreateNixTreeDirs {
pub fn plan(name: String, uid: usize) -> Self {
Self {}
}
}
#[async_trait::async_trait]
impl<'a> Actionable<'a> for CreateNixTreeDirs {
type Receipt = CreateNixTreeDirsReceipt;
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`"
)],
)]
}
async fn execute(self) -> Result<Self::Receipt, HarmonicError> {
let Self {} = self;
Ok(CreateNixTreeDirsReceipt {})
}
}
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct CreateNixTreeDirsReceipt {}
#[async_trait::async_trait]
impl<'a> Revertable<'a> for CreateNixTreeDirsReceipt {
fn description(&self) -> Vec<ActionDescription> {
todo!()
}
async fn revert(self) -> Result<(), HarmonicError> {
todo!();
Ok(())
}
}

View file

@ -7,29 +7,40 @@ use crate::actions::{ActionDescription, ActionReceipt, Actionable, CreateUser, R
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct CreateUsersAndGroup {
settings: InstallSettings,
daemon_user_count: usize,
nix_build_group_name: String,
nix_build_group_id: usize,
nix_build_user_prefix: String,
nix_build_user_id_base: usize,
create_group: CreateGroup,
create_users: Vec<CreateUser>,
}
impl CreateUsersAndGroup {
pub fn plan(
pub async fn plan(
settings: InstallSettings
) -> Self {
) -> 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);
// TODO(@hoverbear): CHeck if they exist, error if so
let create_users = (0..settings.daemon_user_count)
.map(|count| {
CreateUser::plan(
format!("{}{count}", settings.nix_build_user_prefix),
settings.nix_build_user_id_base + count,
settings.nix_build_group_id,
)
})
.collect();
Self {
settings,
Ok(Self {
daemon_user_count: settings.daemon_user_count,
nix_build_group_name: settings.nix_build_group_name,
nix_build_group_id: settings.nix_build_group_id,
nix_build_user_prefix: settings.nix_build_user_prefix,
nix_build_user_id_base: settings.nix_build_user_id_base,
create_group,
create_users,
}
})
}
}
@ -38,14 +49,11 @@ impl<'a> Actionable<'a> for CreateUsersAndGroup {
type Receipt = CreateUsersAndGroupReceipt;
fn description(&self) -> Vec<ActionDescription> {
let Self {
settings: InstallSettings {
daemon_user_count,
nix_build_group_name,
nix_build_group_id,
nix_build_user_prefix,
nix_build_user_id_base,
..
},
daemon_user_count,
nix_build_group_name,
nix_build_group_id,
nix_build_user_prefix,
nix_build_user_id_base,
..
} = &self;
@ -62,7 +70,7 @@ impl<'a> Actionable<'a> for CreateUsersAndGroup {
}
async fn execute(self) -> Result<Self::Receipt, HarmonicError> {
let Self { create_users, create_group, settings: _ } = self;
let Self { create_users, create_group, .. } = self;
// Create group
let create_group = create_group.execute().await?;
@ -87,19 +95,6 @@ impl<'a> Actionable<'a> for CreateUsersAndGroup {
}
if !errors.is_empty() {
// If we got an error in a child, we need to revert the successful ones:
let mut failed_reverts = Vec::default();
for success in successes {
match success.revert().await {
Ok(()) => (),
Err(e) => failed_reverts.push(e),
}
}
if !failed_reverts.is_empty() {
return Err(HarmonicError::FailedReverts(errors, failed_reverts));
}
if errors.len() == 1 {
return Err(errors.into_iter().next().unwrap());
} else {
@ -107,7 +102,7 @@ impl<'a> Actionable<'a> for CreateUsersAndGroup {
}
}
Ok(CreateUsersAndGroupReceipt {
Ok(Self::Receipt {
create_group,
create_users: successes,
})

View file

@ -1,14 +1,12 @@
/*! Actions which only call other base plugins. */
mod create_nix_tree;
mod create_nix_tree_dirs;
mod create_users_and_group;
mod configure_nix;
mod start_nix_daemon;
mod provision_nix;
pub use create_nix_tree::{CreateNixTree, CreateNixTreeReceipt};
pub use create_nix_tree_dirs::{CreateNixTreeDirs, CreateNixTreeDirsReceipt};
pub use create_users_and_group::{CreateUsersAndGroup, CreateUsersAndGroupReceipt};
pub use configure_nix::{ConfigureNix, ConfigureNixReceipt};
pub use start_nix_daemon::{StartNixDaemon, StartNixDaemonReceipt};

View file

@ -16,13 +16,13 @@ pub struct ProvisionNix {
}
impl ProvisionNix {
pub fn plan(settings: InstallSettings) -> Result<Self, HarmonicError> {
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());
let create_users_and_group = CreateUsersAndGroup::plan(settings.clone());
let create_nix_tree = CreateNixTree::plan(settings.clone());
let move_unpacked_nix = MoveUnpackedNix::plan(tempdir.path().to_path_buf());
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 })
}
}

View file

@ -1,13 +1,20 @@
use crate::actions::base::{ConfigureNixDaemonServiceReceipt, ConfigureNixDaemonService, StartSystemdUnit, StartSystemdUnitReceipt};
use crate::{HarmonicError, InstallSettings};
use crate::actions::{ActionDescription, ActionReceipt, Actionable, Revertable};
/// This is mostly indirection for supporting non-systemd
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct StartNixDaemon {}
pub struct StartNixDaemon {
start_systemd_socket: StartSystemdUnit,
start_systemd_service: StartSystemdUnit,
}
impl StartNixDaemon {
pub fn plan(settings: InstallSettings) -> Self {
Self {}
pub async fn plan(settings: InstallSettings) -> 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 })
}
}
@ -15,35 +22,28 @@ impl StartNixDaemon {
impl<'a> Actionable<'a> for StartNixDaemon {
type Receipt = StartNixDaemonReceipt;
fn description(&self) -> Vec<ActionDescription> {
vec![
ActionDescription::new(
"Start 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()
]
),
]
let Self { start_systemd_socket, start_systemd_service } = &self;
start_systemd_service.description()
}
async fn execute(self) -> Result<Self::Receipt, HarmonicError> {
todo!()
let Self { start_systemd_socket, start_systemd_service } = self;
let start_systemd_service = start_systemd_service.execute().await?;
let start_systemd_socket = start_systemd_socket.execute().await?;
Ok(Self::Receipt { start_systemd_socket, start_systemd_service })
}
}
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct StartNixDaemonReceipt {}
pub struct StartNixDaemonReceipt {
start_systemd_socket: StartSystemdUnitReceipt,
start_systemd_service: StartSystemdUnitReceipt,
}
#[async_trait::async_trait]
impl<'a> Revertable<'a> for StartNixDaemonReceipt {
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()
]
),
]
todo!()
}
async fn revert(self) -> Result<(), HarmonicError> {

View file

@ -5,19 +5,19 @@ use base::{
ConfigureNixDaemonService, ConfigureNixDaemonServiceReceipt,
ConfigureShellProfile, ConfigureShellProfileReceipt,
CreateDirectory, CreateDirectoryReceipt,
CreateFile, CreateFileReceipt,
CreateGroup, CreateGroupReceipt,
CreateOrAppendFile, CreateOrAppendFileReceipt,
CreateUser, CreateUserReceipt,
FetchNix, FetchNixReceipt,
MoveUnpackedNix, MoveUnpackedNixReceipt,
PlaceChannelConfiguration, PlaceChannelConfigurationReceipt,
PlaceNixConfiguration, PlaceNixConfigurationReceipt,
SetupDefaultProfile, SetupDefaultProfileReceipt,
StartSystemdService, StartSystemdServiceReceipt,
};
use meta::{
ConfigureNix, ConfigureNixReceipt,
CreateNixTree, CreateNixTreeReceipt,
CreateNixTreeDirs, CreateNixTreeDirsReceipt,
CreateUsersAndGroup, CreateUsersAndGroupReceipt,
StartNixDaemon, StartNixDaemonReceipt,
};
@ -62,8 +62,9 @@ pub enum Action {
ConfigureNix(ConfigureNix),
ConfigureShellProfile(ConfigureShellProfile),
CreateDirectory(CreateDirectory),
CreateFile(CreateFile),
CreateGroup(CreateGroup),
CreateNixTreeDirs(CreateNixTreeDirs),
CreateOrAppendFile(CreateOrAppendFile),
CreateNixTree(CreateNixTree),
CreateUser(CreateUser),
CreateUsersAndGroup(CreateUsersAndGroup),
@ -73,7 +74,6 @@ pub enum Action {
PlaceNixConfiguration(PlaceNixConfiguration),
SetupDefaultProfile(SetupDefaultProfile),
StartNixDaemon(StartNixDaemon),
StartSystemdService(StartSystemdService),
ProvisionNix(ProvisionNix),
}
@ -83,8 +83,9 @@ pub enum ActionReceipt {
ConfigureNix(ConfigureNixReceipt),
ConfigureShellProfile(ConfigureShellProfileReceipt),
CreateDirectory(CreateDirectoryReceipt),
CreateFile(CreateFileReceipt),
CreateGroup(CreateGroupReceipt),
CreateNixTreeDirs(CreateNixTreeDirsReceipt),
CreateOrAppendFile(CreateOrAppendFileReceipt),
CreateNixTree(CreateNixTreeReceipt),
CreateUser(CreateUserReceipt),
CreateUsersAndGroup(CreateUsersAndGroupReceipt),
@ -94,7 +95,6 @@ pub enum ActionReceipt {
PlaceNixConfiguration(PlaceNixConfigurationReceipt),
SetupDefaultProfile(SetupDefaultProfileReceipt),
StartNixDaemon(StartNixDaemonReceipt),
StartSystemdService(StartSystemdServiceReceipt),
ProvisionNix(ProvisionNixReceipt),
}
@ -107,8 +107,9 @@ impl<'a> Actionable<'a> for Action {
Action::ConfigureNix(i) => i.description(),
Action::ConfigureShellProfile(i) => i.description(),
Action::CreateDirectory(i) => i.description(),
Action::CreateFile(i) => i.description(),
Action::CreateGroup(i) => i.description(),
Action::CreateNixTreeDirs(i) => i.description(),
Action::CreateOrAppendFile(i) => i.description(),
Action::CreateNixTree(i) => i.description(),
Action::CreateUser(i) => i.description(),
Action::CreateUsersAndGroup(i) => i.description(),
@ -118,7 +119,6 @@ impl<'a> Actionable<'a> for Action {
Action::PlaceNixConfiguration(i) => i.description(),
Action::SetupDefaultProfile(i) => i.description(),
Action::StartNixDaemon(i) => i.description(),
Action::StartSystemdService(i) => i.description(),
Action::ProvisionNix(i) => i.description(),
}
}
@ -129,8 +129,9 @@ impl<'a> Actionable<'a> for Action {
Action::ConfigureNix(i) => i.execute().await.map(ActionReceipt::ConfigureNix),
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::CreateNixTreeDirs(i) => i.execute().await.map(ActionReceipt::CreateNixTreeDirs),
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),
@ -140,7 +141,6 @@ impl<'a> Actionable<'a> for Action {
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::StartSystemdService(i) => i.execute().await.map(ActionReceipt::StartSystemdService),
Action::ProvisionNix(i) => i.execute().await.map(ActionReceipt::ProvisionNix),
}
}
@ -154,8 +154,9 @@ impl<'a> Revertable<'a> for ActionReceipt {
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::CreateNixTreeDirs(i) => i.description(),
ActionReceipt::CreateOrAppendFile(i) => i.description(),
ActionReceipt::CreateNixTree(i) => i.description(),
ActionReceipt::CreateUser(i) => i.description(),
ActionReceipt::CreateUsersAndGroup(i) => i.description(),
@ -165,7 +166,6 @@ impl<'a> Revertable<'a> for ActionReceipt {
ActionReceipt::PlaceNixConfiguration(i) => i.description(),
ActionReceipt::SetupDefaultProfile(i) => i.description(),
ActionReceipt::StartNixDaemon(i) => i.description(),
ActionReceipt::StartSystemdService(i) => i.description(),
ActionReceipt::ProvisionNix(i) => i.description(),
}
}
@ -176,8 +176,9 @@ impl<'a> Revertable<'a> for ActionReceipt {
ActionReceipt::ConfigureNix(i) => i.revert().await,
ActionReceipt::ConfigureShellProfile(i) => i.revert().await,
ActionReceipt::CreateDirectory(i) => i.revert().await,
ActionReceipt::CreateFile(i) => i.revert().await,
ActionReceipt::CreateGroup(i) => i.revert().await,
ActionReceipt::CreateNixTreeDirs(i) => i.revert().await,
ActionReceipt::CreateOrAppendFile(i) => i.revert().await,
ActionReceipt::CreateNixTree(i) => i.revert().await,
ActionReceipt::CreateUser(i) => i.revert().await,
ActionReceipt::CreateUsersAndGroup(i) => i.revert().await,
@ -187,7 +188,6 @@ impl<'a> Revertable<'a> for ActionReceipt {
ActionReceipt::PlaceNixConfiguration(i) => i.revert().await,
ActionReceipt::SetupDefaultProfile(i) => i.revert().await,
ActionReceipt::StartNixDaemon(i) => i.revert().await,
ActionReceipt::StartSystemdService(i) => i.revert().await,
ActionReceipt::ProvisionNix(i) => i.revert().await,
}
}

View file

@ -46,6 +46,13 @@ pub(crate) struct HarmonicCli {
global = true
)]
pub(crate) explain: bool,
#[clap(
long,
action(ArgAction::SetTrue),
default_value = "false",
global = true
)]
pub(crate) force: bool,
#[clap(subcommand)]
subcommand: Option<HarmonicSubcommand>,
}
@ -66,6 +73,7 @@ impl CommandExecute for HarmonicCli {
no_modify_profile,
explain,
subcommand,
force,
} = self;
match subcommand {
@ -74,6 +82,7 @@ impl CommandExecute for HarmonicCli {
None => {
let mut settings = InstallSettings::default();
settings.force(force);
settings.explain(explain);
settings.daemon_user_count(daemon_user_count);
settings.channels(

View file

@ -29,6 +29,20 @@ pub(crate) struct Plan {
/// Number of build users to create
#[clap(long, default_value = "32", env = "HARMONIC_NIX_DAEMON_USER_COUNT")]
pub(crate) daemon_user_count: usize,
#[clap(
long,
action(ArgAction::SetTrue),
default_value = "false",
global = true
)]
pub(crate) explain: bool,
#[clap(
long,
action(ArgAction::SetTrue),
default_value = "false",
global = true
)]
pub(crate) force: bool,
}
#[async_trait::async_trait]
@ -43,10 +57,14 @@ impl CommandExecute for Plan {
channel,
no_modify_profile,
daemon_user_count,
explain,
force,
} = self;
let mut settings = InstallSettings::default();
settings.force(force);
settings.explain(explain);
settings.daemon_user_count(daemon_user_count);
settings.channels(
channel

View file

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

View file

@ -83,9 +83,9 @@ impl InstallPlan {
}
pub async fn new(settings: InstallSettings) -> Result<Self, HarmonicError> {
let actions = vec![
Action::ProvisionNix(ProvisionNix::plan(settings.clone())?),
Action::ConfigureNix(ConfigureNix::plan(settings.clone())),
Action::StartNixDaemon(StartNixDaemon::plan(settings.clone())),
Action::ProvisionNix(ProvisionNix::plan(settings.clone()).await?),
Action::ConfigureNix(ConfigureNix::plan(settings.clone()).await?),
Action::StartNixDaemon(StartNixDaemon::plan(settings.clone()).await?),
];
Ok(Self { settings, actions })
}

View file

@ -11,6 +11,8 @@ pub struct InstallSettings {
pub(crate) nix_build_user_prefix: String,
pub(crate) nix_build_user_id_base: usize,
pub(crate) nix_package_url: Url,
pub(crate) extra_conf: Option<String>,
pub(crate) force: bool,
}
impl Default for InstallSettings {
@ -24,7 +26,9 @@ 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,
}
}
}
@ -73,4 +77,12 @@ impl InstallSettings {
self.nix_package_url = url;
self
}
pub fn extra_conf(&mut self, extra_conf: Option<String>) -> &mut Self {
self.extra_conf = extra_conf;
self
}
pub fn force(&mut self, force: bool) -> &mut Self {
self.force = force;
self
}
}