Even more mac progress, can plan and start runs

This commit is contained in:
Ana Hobden 2022-10-20 11:36:44 -07:00
parent c75a4ed511
commit 86a518f2fe
24 changed files with 424 additions and 281 deletions

43
Cargo.lock generated
View file

@ -498,6 +498,26 @@ dependencies = [
"syn",
]
[[package]]
name = "dirs"
version = "4.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059"
dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs-sys"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
dependencies = [
"libc",
"redox_users",
"winapi",
]
[[package]]
name = "encoding_rs"
version = "0.8.31"
@ -684,6 +704,17 @@ dependencies = [
"slab",
]
[[package]]
name = "getrandom"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "ghost"
version = "0.1.6"
@ -749,6 +780,7 @@ dependencies = [
"clap",
"color-eyre",
"crossterm",
"dirs",
"eyre",
"futures 0.3.24",
"glob",
@ -1380,6 +1412,17 @@ dependencies = [
"bitflags",
]
[[package]]
name = "redox_users"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
dependencies = [
"getrandom",
"redox_syscall",
"thiserror",
]
[[package]]
name = "regex"
version = "1.6.0"

View file

@ -39,3 +39,4 @@ sxd-xpath = "0.4.2"
xz2 = { version = "0.1.7", features = ["static", "tokio"] }
sxd-document = "0.3.2"
plist = "1.3.1"
dirs = "4.0.0"

View file

@ -1,6 +1,7 @@
use std::path::{Path, PathBuf};
use serde::Serialize;
use target_lexicon::OperatingSystem;
use tokio::fs::remove_file;
use tokio::process::Command;
@ -21,9 +22,20 @@ pub struct ConfigureNixDaemonService {
impl ConfigureNixDaemonService {
#[tracing::instrument(skip_all)]
pub async fn plan() -> Result<Self, ConfigureNixDaemonServiceError> {
if !Path::new("/run/systemd/system").exists() {
return Err(ConfigureNixDaemonServiceError::InitNotSupported);
}
match OperatingSystem::host() {
OperatingSystem::MacOSX {
major: _,
minor: _,
patch: _,
}
| OperatingSystem::Darwin => (),
_ => {
if !Path::new("/run/systemd/system").exists() {
return Err(ConfigureNixDaemonServiceError::InitNotSupported);
}
},
};
Ok(Self {
action_state: ActionState::Uncompleted,
})
@ -59,32 +71,68 @@ impl Actionable for ConfigureNixDaemonService {
}
tracing::debug!("Configuring nix daemon service");
tracing::trace!(src = TMPFILES_SRC, dest = TMPFILES_DEST, "Symlinking");
tokio::fs::symlink(TMPFILES_SRC, TMPFILES_DEST)
.await
.map_err(|e| {
Self::Error::Symlink(PathBuf::from(TMPFILES_SRC), PathBuf::from(TMPFILES_DEST), e)
})?;
match OperatingSystem::host() {
OperatingSystem::MacOSX {
major: _,
minor: _,
patch: _,
}
| OperatingSystem::Darwin => {
const DARWIN_NIX_DAEMON_DEST: &str =
"/Library/LaunchDaemons/org.nixos.nix-daemon.plist";
execute_command(
Command::new("systemd-tmpfiles")
.arg("--create")
.arg("--prefix=/nix/var/nix"),
)
.await
.map_err(Self::Error::CommandFailed)?;
let src = Path::new("/nix/var/nix/profiles/default").join(DARWIN_NIX_DAEMON_DEST);
tokio::fs::copy(src.clone(), DARWIN_NIX_DAEMON_DEST)
.await
.map_err(|e| {
Self::Error::Copy(
src.to_path_buf(),
PathBuf::from(DARWIN_NIX_DAEMON_DEST),
e,
)
})?;
execute_command(Command::new("systemctl").arg("link").arg(SERVICE_SRC))
.await
.map_err(Self::Error::CommandFailed)?;
execute_command(
Command::new("launchctl")
.arg("load")
.arg(DARWIN_NIX_DAEMON_DEST),
)
.await
.map_err(Self::Error::CommandFailed)?;
},
_ => {
tracing::trace!(src = TMPFILES_SRC, dest = TMPFILES_DEST, "Symlinking");
tokio::fs::symlink(TMPFILES_SRC, TMPFILES_DEST)
.await
.map_err(|e| {
Self::Error::Symlink(
PathBuf::from(TMPFILES_SRC),
PathBuf::from(TMPFILES_DEST),
e,
)
})?;
execute_command(Command::new("systemctl").arg("link").arg(SOCKET_SRC))
.await
.map_err(Self::Error::CommandFailed)?;
execute_command(
Command::new("systemd-tmpfiles")
.arg("--create")
.arg("--prefix=/nix/var/nix"),
)
.await
.map_err(Self::Error::CommandFailed)?;
execute_command(Command::new("systemctl").arg("daemon-reload"))
.await
.map_err(Self::Error::CommandFailed)?;
execute_command(Command::new("systemctl").arg("link").arg(SERVICE_SRC))
.await
.map_err(Self::Error::CommandFailed)?;
execute_command(Command::new("systemctl").arg("link").arg(SOCKET_SRC))
.await
.map_err(Self::Error::CommandFailed)?;
execute_command(Command::new("systemctl").arg("daemon-reload"))
.await
.map_err(Self::Error::CommandFailed)?;
},
};
tracing::trace!("Configured nix daemon service");
*action_state = ActionState::Completed;
@ -176,6 +224,14 @@ pub enum ConfigureNixDaemonServiceError {
#[serde(serialize_with = "crate::serialize_error_to_display")]
std::io::Error,
),
#[error("Copying file `{0}` to `{1}`")]
Copy(
std::path::PathBuf,
std::path::PathBuf,
#[source]
#[serde(serialize_with = "crate::serialize_error_to_display")]
std::io::Error,
),
#[error("No supported init system found")]
InitNotSupported,
}

View file

@ -10,9 +10,9 @@ use crate::actions::{Action, ActionDescription, ActionState, Actionable};
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct CreateDirectory {
path: PathBuf,
user: String,
group: String,
mode: u32,
user: Option<String>,
group: Option<String>,
mode: Option<u32>,
action_state: ActionState,
force_prune_on_revert: bool,
}
@ -21,12 +21,15 @@ impl CreateDirectory {
#[tracing::instrument(skip_all)]
pub async fn plan(
path: impl AsRef<Path>,
user: String,
group: String,
mode: u32,
user: impl Into<Option<String>>,
group: impl Into<Option<String>>,
mode: impl Into<Option<u32>>,
force_prune_on_revert: bool,
) -> Result<Self, CreateDirectoryError> {
let path = path.as_ref();
let user = user.into();
let group = group.into();
let mode = mode.into();
let action_state = if path.exists() {
let metadata = tokio::fs::metadata(path)
@ -77,10 +80,7 @@ impl Actionable for CreateDirectory {
} else {
vec![ActionDescription::new(
format!("Create the directory `{}`", path.display()),
vec![format!(
"Creating directory `{}` owned by `{user}:{group}` with mode `{mode:#o}`",
path.display()
)],
vec![],
)]
}
}
@ -89,7 +89,7 @@ impl Actionable for CreateDirectory {
path = %self.path.display(),
user = self.user,
group = self.group,
mode = format!("{:#o}", self.mode),
mode = self.mode.map(|v| tracing::field::display(format!("{:#o}", v))),
))]
async fn execute(&mut self) -> Result<(), Self::Error> {
let Self {
@ -106,23 +106,37 @@ impl Actionable for CreateDirectory {
}
tracing::debug!("Creating directory");
let gid = Group::from_name(group.as_str())
.map_err(|e| Self::Error::GroupId(group.clone(), e))?
.ok_or(Self::Error::NoGroup(group.clone()))?
.gid;
let uid = User::from_name(user.as_str())
.map_err(|e| Self::Error::UserId(user.clone(), e))?
.ok_or(Self::Error::NoUser(user.clone()))?
.uid;
let gid = if let Some(group) = group {
Some(
Group::from_name(group.as_str())
.map_err(|e| Self::Error::GroupId(group.clone(), e))?
.ok_or(Self::Error::NoGroup(group.clone()))?
.gid,
)
} else {
None
};
let uid = if let Some(user) = user {
Some(
User::from_name(user.as_str())
.map_err(|e| Self::Error::UserId(user.clone(), e))?
.ok_or(Self::Error::NoUser(user.clone()))?
.uid,
)
} else {
None
};
create_dir(path.clone())
.await
.map_err(|e| Self::Error::Creating(path.clone(), e))?;
chown(path, Some(uid), Some(gid)).map_err(|e| Self::Error::Chown(path.clone(), e))?;
chown(path, uid, gid).map_err(|e| Self::Error::Chown(path.clone(), e))?;
tokio::fs::set_permissions(&path, PermissionsExt::from_mode(*mode))
.await
.map_err(|e| Self::Error::SetPermissions(*mode, path.to_owned(), e))?;
if let Some(mode) = mode {
tokio::fs::set_permissions(&path, PermissionsExt::from_mode(*mode))
.await
.map_err(|e| Self::Error::SetPermissions(*mode, path.to_owned(), e))?;
}
tracing::trace!("Created directory");
*action_state = ActionState::Completed;
@ -160,7 +174,7 @@ impl Actionable for CreateDirectory {
path = %self.path.display(),
user = self.user,
group = self.group,
mode = format!("{:#o}", self.mode),
mode = self.mode.map(|v| tracing::field::display(format!("{:#o}", v))),
))]
async fn revert(&mut self) -> Result<(), Self::Error> {
let Self {

View file

@ -12,10 +12,10 @@ use crate::actions::{ActionDescription, Actionable};
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct CreateFile {
path: PathBuf,
user: String,
group: String,
mode: u32,
pub(crate) path: PathBuf,
user: Option<String>,
group: Option<String>,
mode: Option<u32>,
buf: String,
force: bool,
action_state: ActionState,
@ -25,9 +25,9 @@ impl CreateFile {
#[tracing::instrument(skip_all)]
pub async fn plan(
path: impl AsRef<Path>,
user: String,
group: String,
mode: u32,
user: impl Into<Option<String>>,
group: impl Into<Option<String>>,
mode: impl Into<Option<u32>>,
buf: String,
force: bool,
) -> Result<Self, CreateFileError> {
@ -39,9 +39,9 @@ impl CreateFile {
Ok(Self {
path,
user,
group,
mode,
user: user.into(),
group: group.into(),
mode: mode.into(),
buf,
force,
action_state: ActionState::Uncompleted,
@ -68,9 +68,7 @@ impl Actionable for CreateFile {
} else {
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()
)],
vec![],
)]
}
}
@ -79,7 +77,7 @@ impl Actionable for CreateFile {
path = %self.path.display(),
user = self.user,
group = self.group,
mode = format!("{:#o}", self.mode),
mode = self.mode.map(|v| tracing::field::display(format!("{:#o}", v))),
))]
async fn execute(&mut self) -> Result<(), Self::Error> {
let Self {
@ -97,11 +95,14 @@ impl Actionable for CreateFile {
}
tracing::debug!("Creating file");
let mut file = OpenOptions::new()
.create_new(true)
.mode(*mode)
.write(true)
.read(true)
let mut options = OpenOptions::new();
options.create_new(true).write(true).read(true);
if let Some(mode) = mode {
options.mode(*mode);
}
let mut file = options
.open(&path)
.await
.map_err(|e| Self::Error::OpenFile(path.to_owned(), e))?;
@ -110,16 +111,27 @@ impl Actionable for CreateFile {
.await
.map_err(|e| Self::Error::WriteFile(path.to_owned(), e))?;
let gid = Group::from_name(group.as_str())
.map_err(|e| Self::Error::GroupId(group.clone(), e))?
.ok_or(Self::Error::NoGroup(group.clone()))?
.gid;
let uid = User::from_name(user.as_str())
.map_err(|e| Self::Error::UserId(user.clone(), e))?
.ok_or(Self::Error::NoUser(user.clone()))?
.uid;
chown(path, Some(uid), Some(gid)).map_err(|e| Self::Error::Chown(path.clone(), e))?;
let gid = if let Some(group) = group {
Some(
Group::from_name(group.as_str())
.map_err(|e| Self::Error::GroupId(group.clone(), e))?
.ok_or(Self::Error::NoGroup(group.clone()))?
.gid,
)
} else {
None
};
let uid = if let Some(user) = user {
Some(
User::from_name(user.as_str())
.map_err(|e| Self::Error::UserId(user.clone(), e))?
.ok_or(Self::Error::NoUser(user.clone()))?
.uid,
)
} else {
None
};
chown(path, uid, gid).map_err(|e| Self::Error::Chown(path.clone(), e))?;
tracing::trace!("Created file");
*action_state = ActionState::Completed;
@ -150,7 +162,7 @@ impl Actionable for CreateFile {
path = %self.path.display(),
user = self.user,
group = self.group,
mode = format!("{:#o}", self.mode),
mode = self.mode.map(|v| tracing::field::display(format!("{:#o}", v))),
))]
async fn revert(&mut self) -> Result<(), Self::Error> {
let Self {

View file

@ -17,9 +17,9 @@ use crate::actions::{ActionDescription, Actionable};
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct CreateOrAppendFile {
path: PathBuf,
user: String,
group: String,
mode: u32,
user: Option<String>,
group: Option<String>,
mode: Option<u32>,
buf: String,
action_state: ActionState,
}
@ -28,18 +28,18 @@ impl CreateOrAppendFile {
#[tracing::instrument(skip_all)]
pub async fn plan(
path: impl AsRef<Path>,
user: String,
group: String,
mode: u32,
user: impl Into<Option<String>>,
group: impl Into<Option<String>>,
mode: impl Into<Option<u32>>,
buf: String,
) -> Result<Self, CreateOrAppendFileError> {
let path = path.as_ref().to_path_buf();
Ok(Self {
path,
user,
group,
mode,
user: user.into(),
group: group.into(),
mode: mode.into(),
buf,
action_state: ActionState::Uncompleted,
})
@ -64,9 +64,7 @@ impl Actionable for CreateOrAppendFile {
} else {
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()
)],
vec![],
)]
}
}
@ -75,7 +73,7 @@ impl Actionable for CreateOrAppendFile {
path = %self.path.display(),
user = self.user,
group = self.group,
mode = format!("{:#o}", self.mode),
mode = self.mode.map(|v| tracing::field::display(format!("{:#o}", v))),
))]
async fn execute(&mut self) -> Result<(), Self::Error> {
let Self {
@ -108,20 +106,34 @@ impl Actionable for CreateOrAppendFile {
.await
.map_err(|e| Self::Error::WriteFile(path.to_owned(), e))?;
let gid = Group::from_name(group.as_str())
.map_err(|e| Self::Error::GroupId(group.clone(), e))?
.ok_or(Self::Error::NoGroup(group.clone()))?
.gid;
let uid = User::from_name(user.as_str())
.map_err(|e| Self::Error::UserId(user.clone(), e))?
.ok_or(Self::Error::NoUser(user.clone()))?
.uid;
let gid = if let Some(group) = group {
Some(
Group::from_name(group.as_str())
.map_err(|e| Self::Error::GroupId(group.clone(), e))?
.ok_or(Self::Error::NoGroup(group.clone()))?
.gid,
)
} else {
None
};
let uid = if let Some(user) = user {
Some(
User::from_name(user.as_str())
.map_err(|e| Self::Error::UserId(user.clone(), e))?
.ok_or(Self::Error::NoUser(user.clone()))?
.uid,
)
} else {
None
};
tokio::fs::set_permissions(&path, PermissionsExt::from_mode(*mode))
.await
.map_err(|e| Self::Error::SetPermissions(*mode, path.to_owned(), e))?;
if let Some(mode) = mode {
tokio::fs::set_permissions(&path, PermissionsExt::from_mode(*mode))
.await
.map_err(|e| Self::Error::SetPermissions(*mode, path.to_owned(), e))?;
}
chown(path, Some(uid), Some(gid)).map_err(|e| Self::Error::Chown(path.clone(), e))?;
chown(path, uid, gid).map_err(|e| Self::Error::Chown(path.clone(), e))?;
tracing::trace!("Created or appended fragment to file");
*action_state = ActionState::Completed;
@ -154,7 +166,7 @@ impl Actionable for CreateOrAppendFile {
path = %self.path.display(),
user = self.user,
group = self.group,
mode = format!("{:#o}", self.mode),
mode = self.mode.map(|v| tracing::field::display(format!("{:#o}", v))),
))]
async fn revert(&mut self) -> Result<(), Self::Error> {
let Self {

View file

@ -68,7 +68,7 @@ impl Actionable for CreateUser {
tracing::debug!("Creating user");
use target_lexicon::OperatingSystem;
match target_lexicon::OperatingSystem::host() {
match OperatingSystem::host() {
OperatingSystem::MacOSX {
major: _,
minor: _,

View file

@ -0,0 +1,115 @@
use serde::Serialize;
use tokio::process::Command;
use crate::execute_command;
use crate::actions::{Action, ActionDescription, ActionState, Actionable};
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct KickstartLaunchctlService {
unit: String,
action_state: ActionState,
}
impl KickstartLaunchctlService {
#[tracing::instrument(skip_all)]
pub async fn plan(unit: String) -> Result<Self, KickstartLaunchctlServiceError> {
Ok(Self {
unit,
action_state: ActionState::Uncompleted,
})
}
}
#[async_trait::async_trait]
impl Actionable for KickstartLaunchctlService {
type Error = KickstartLaunchctlServiceError;
fn describe_execute(&self) -> Vec<ActionDescription> {
let Self { unit, action_state } = self;
if *action_state == ActionState::Completed {
vec![]
} else {
vec![ActionDescription::new(
format!("Kickstart the launchctl unit `{unit}`"),
vec![
"The `nix` command line tool communicates with a running Nix daemon managed by your init system".to_string()
]
)]
}
}
#[tracing::instrument(skip_all, fields(
unit = %self.unit,
))]
async fn execute(&mut self) -> Result<(), Self::Error> {
let Self { unit, action_state } = self;
if *action_state == ActionState::Completed {
tracing::trace!("Already completed: Kickstarting launchctl unit");
return Ok(());
}
tracing::debug!("Kickstarting launchctl unit");
execute_command(
Command::new("launchctl")
.arg("kickstart")
.arg("-k")
.arg(unit),
)
.await
.map_err(KickstartLaunchctlServiceError::Command)?;
tracing::trace!("Kickstarted launchctl unit");
*action_state = ActionState::Completed;
Ok(())
}
fn describe_revert(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Uncompleted {
vec![]
} else {
vec![ActionDescription::new(
"Kick".to_string(),
vec![
"The `nix` command line tool communicates with a running Nix daemon managed by your init system".to_string()
]
)]
}
}
#[tracing::instrument(skip_all, fields(
unit = %self.unit,
))]
async fn revert(&mut self) -> Result<(), Self::Error> {
let Self { unit, action_state } = self;
if *action_state == ActionState::Uncompleted {
tracing::trace!("Already reverted: Stopping launchctl unit");
return Ok(());
}
tracing::debug!("Stopping launchctl unit");
execute_command(Command::new("launchctl").arg("stop").arg(unit))
.await
.map_err(KickstartLaunchctlServiceError::Command)?;
tracing::trace!("Stopped launchctl unit");
*action_state = ActionState::Completed;
Ok(())
}
}
impl From<KickstartLaunchctlService> for Action {
fn from(v: KickstartLaunchctlService) -> Self {
Action::DarwinKickStartLaunchctlService(v)
}
}
#[derive(Debug, thiserror::Error, Serialize)]
pub enum KickstartLaunchctlServiceError {
#[error("Failed to execute command")]
Command(
#[source]
#[serde(serialize_with = "crate::serialize_error_to_display")]
std::io::Error,
),
}

View file

@ -3,6 +3,7 @@ mod create_synthetic_objects;
mod create_volume;
mod enable_ownership;
mod encrypt_volume;
mod kickstart_launchctl_service;
mod unmount_volume;
pub use bootstrap_volume::{BootstrapVolume, BootstrapVolumeError};
@ -10,4 +11,5 @@ pub use create_synthetic_objects::{CreateSyntheticObjects, CreateSyntheticObject
pub use create_volume::{CreateVolume, CreateVolumeError};
pub use enable_ownership::{EnableOwnership, EnableOwnershipError};
pub use encrypt_volume::{EncryptVolume, EncryptVolumeError};
pub use kickstart_launchctl_service::{KickstartLaunchctlService, KickstartLaunchctlServiceError};
pub use unmount_volume::{UnmountVolume, UnmountVolumeError};

View file

@ -60,7 +60,7 @@ impl Actionable for UnmountVolume {
tracing::debug!("Unmounting volume");
execute_command(
Command::new(" /usr/sbin/diskutil")
Command::new("/usr/sbin/diskutil")
.args(["unmount", "force"])
.arg(name),
)

View file

@ -41,10 +41,8 @@ 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, None, None, 0o0644, buf).await?);
}
Ok(Self {

View file

@ -31,9 +31,7 @@ impl CreateNixTree {
let mut create_directories = Vec::default();
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, false).await?,
)
create_directories.push(CreateDirectory::plan(path, None, None, 0o0755, false).await?)
}
Ok(Self {

View file

@ -26,19 +26,11 @@ impl CreateSystemdSysext {
pub async fn plan(destination: impl AsRef<Path>) -> Result<Self, CreateSystemdSysextError> {
let destination = destination.as_ref();
let mut create_directories = vec![
CreateDirectory::plan(destination, "root".into(), "root".into(), 0o0755, true).await?,
];
let mut create_directories =
vec![CreateDirectory::plan(destination, None, None, 0o0755, true).await?];
for path in PATHS {
create_directories.push(
CreateDirectory::plan(
destination.join(path),
"root".into(),
"root".into(),
0o0755,
false,
)
.await?,
CreateDirectory::plan(destination.join(path), None, None, 0o0755, false).await?,
)
}
@ -57,8 +49,8 @@ impl CreateSystemdSysext {
format!("SYSEXT_LEVEL=1.0\nID=steamos\nVERSION_ID={version_id}");
let create_extension_release = CreateFile::plan(
destination.join("usr/lib/extension-release.d/extension-release.nix"),
"root".into(),
"root".into(),
None,
None,
0o0755,
extension_release_buf,
false,
@ -77,8 +69,8 @@ impl CreateSystemdSysext {
);
let create_bind_mount_unit = CreateFile::plan(
destination.join("usr/lib/systemd/system/nix.mount"),
"root".into(),
"root".into(),
None,
None,
0o0755,
create_bind_mount_buf,
false,

View file

@ -39,14 +39,9 @@ impl CreateApfsVolume {
encrypt: Option<String>,
) -> Result<Self, CreateApfsVolumeError> {
let disk = disk.as_ref();
let create_or_append_synthetic_conf = CreateOrAppendFile::plan(
"/etc/synthetic.conf",
"root".into(),
"wheel".into(),
0o0655,
"nix".into(),
)
.await?;
let create_or_append_synthetic_conf =
CreateOrAppendFile::plan("/etc/synthetic.conf", None, None, 0o0655, "nix".into())
.await?;
let create_synthetic_objects = CreateSyntheticObjects::plan().await?;
@ -56,10 +51,10 @@ impl CreateApfsVolume {
let create_or_append_fstab = CreateOrAppendFile::plan(
"/etc/fstab",
"root".into(),
"root".into(),
None,
None,
0o0655,
"NAME={name} /nix apfs rw,noauto,nobrowse,suid,owners".into(),
format!("NAME=\"{name}\" /nix apfs rw,noauto,nobrowse,suid,owners"),
)
.await?;
@ -138,7 +133,7 @@ impl Actionable for CreateApfsVolume {
create_or_append_synthetic_conf.execute().await?;
create_synthetic_objects.execute().await?;
unmount_volume.execute().await?;
unmount_volume.execute().await.ok(); // We actually expect this may fail.
create_volume.execute().await?;
create_or_append_fstab.execute().await?;
if let Some(encrypt_volume) = encrypt_volume {

View file

@ -9,7 +9,6 @@ pub mod darwin;
mod place_channel_configuration;
mod place_nix_configuration;
mod provision_nix;
mod start_nix_daemon;
pub use configure_nix::{ConfigureNix, ConfigureNixError};
pub use configure_shell_profile::{ConfigureShellProfile, ConfigureShellProfileError};
@ -19,4 +18,3 @@ pub use create_users_and_group::{CreateUsersAndGroup, CreateUsersAndGroupError};
pub use place_channel_configuration::{PlaceChannelConfiguration, PlaceChannelConfigurationError};
pub use place_nix_configuration::{PlaceNixConfiguration, PlaceNixConfigurationError};
pub use provision_nix::{ProvisionNix, ProvisionNixError};
pub use start_nix_daemon::{StartNixDaemon, StartNixDaemonError};

View file

@ -5,8 +5,6 @@ use crate::actions::{Action, ActionDescription, ActionState, Actionable};
use crate::actions::base::{CreateFile, CreateFileError};
const NIX_CHANNELS_PATH: &str = "/root/.nix-channels";
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct PlaceChannelConfiguration {
channels: Vec<(String, Url)>,
@ -26,9 +24,11 @@ impl PlaceChannelConfiguration {
.collect::<Vec<_>>()
.join("\n");
let create_file = CreateFile::plan(
NIX_CHANNELS_PATH,
"root".into(),
"root".into(),
dirs::home_dir()
.ok_or(PlaceChannelConfigurationError::NoRootHome)?
.join("/.nix-channels"),
None,
None,
0o0664,
buf,
force,
@ -49,17 +49,18 @@ impl Actionable for PlaceChannelConfiguration {
fn describe_execute(&self) -> Vec<ActionDescription> {
let Self {
channels: _,
create_file: _,
create_file,
action_state: _,
} = self;
if self.action_state == ActionState::Completed {
vec![]
} else {
vec![ActionDescription::new(
format!("Place channel configuration at `{NIX_CHANNELS_PATH}`"),
vec![format!(
"Place channel configuration at `{NIX_CHANNELS_PATH}`"
)],
format!(
"Place channel configuration at `{}`",
create_file.path.display()
),
vec![],
)]
}
}
@ -90,17 +91,18 @@ impl Actionable for PlaceChannelConfiguration {
fn describe_revert(&self) -> Vec<ActionDescription> {
let Self {
channels: _,
create_file: _,
create_file,
action_state: _,
} = self;
if self.action_state == ActionState::Uncompleted {
vec![]
} else {
vec![ActionDescription::new(
format!("Remove channel configuration at `{NIX_CHANNELS_PATH}`"),
vec![format!(
"Remove channel configuration at `{NIX_CHANNELS_PATH}`"
)],
format!(
"Remove channel configuration at `{}`",
create_file.path.display()
),
vec![],
)]
}
}
@ -143,4 +145,6 @@ pub enum PlaceChannelConfigurationError {
#[from]
CreateFileError,
),
#[error("No root home found to place channel configuration in")]
NoRootHome,
}

View file

@ -33,10 +33,8 @@ impl PlaceNixConfiguration {
extra_conf = extra_conf.unwrap_or_else(|| "".into()),
);
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?;
CreateDirectory::plan(NIX_CONF_FOLDER, None, None, 0o0755, force).await?;
let create_file = CreateFile::plan(NIX_CONF, None, None, 0o0664, buf, force).await?;
Ok(Self {
create_directory,
create_file,

View file

@ -26,8 +26,7 @@ pub struct ProvisionNix {
impl ProvisionNix {
#[tracing::instrument(skip_all)]
pub async fn plan(settings: InstallSettings) -> Result<Self, ProvisionNixError> {
let create_nix_dir =
CreateDirectory::plan("/nix", "root".into(), "root".into(), 0o0755, true).await?;
let create_nix_dir = CreateDirectory::plan("/nix", None, None, 0o0755, true).await?;
let fetch_nix = FetchNix::plan(
settings.nix_package_url.clone(),

View file

@ -1,101 +0,0 @@
use serde::Serialize;
use crate::actions::base::{StartSystemdUnit, StartSystemdUnitError};
use crate::actions::{Action, ActionDescription, ActionState, Actionable};
/// This is mostly indirection for supporting non-systemd
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct StartNixDaemon {
start_systemd_socket: StartSystemdUnit,
action_state: ActionState,
}
impl StartNixDaemon {
#[tracing::instrument(skip_all)]
pub async fn plan() -> Result<Self, StartNixDaemonError> {
let start_systemd_socket = StartSystemdUnit::plan("nix-daemon.socket".into()).await?;
Ok(Self {
start_systemd_socket,
action_state: ActionState::Uncompleted,
})
}
}
#[async_trait::async_trait]
impl Actionable for StartNixDaemon {
type Error = StartNixDaemonError;
fn describe_execute(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Completed {
vec![]
} else {
self.start_systemd_socket.describe_execute()
}
}
#[tracing::instrument(skip_all)]
async fn execute(&mut self) -> Result<(), Self::Error> {
let Self {
start_systemd_socket,
action_state,
} = self;
if *action_state == ActionState::Completed {
tracing::trace!("Already completed: Starting the nix daemon");
return Ok(());
}
*action_state = ActionState::Progress;
tracing::debug!("Starting the nix daemon");
start_systemd_socket.execute().await?;
tracing::trace!("Started the nix daemon");
*action_state = ActionState::Completed;
Ok(())
}
fn describe_revert(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Uncompleted {
vec![]
} else {
self.start_systemd_socket.describe_revert()
}
}
#[tracing::instrument(skip_all)]
async fn revert(&mut self) -> Result<(), Self::Error> {
let Self {
start_systemd_socket,
action_state,
..
} = self;
if *action_state == ActionState::Uncompleted {
tracing::trace!("Already reverted: Stop the nix daemon");
return Ok(());
}
*action_state = ActionState::Progress;
tracing::debug!("Stop the nix daemon");
start_systemd_socket.revert().await?;
tracing::trace!("Stopped the nix daemon");
*action_state = ActionState::Uncompleted;
Ok(())
}
}
impl From<StartNixDaemon> for Action {
fn from(v: StartNixDaemon) -> Self {
Action::StartNixDaemon(v)
}
}
#[derive(Debug, thiserror::Error, Serialize)]
pub enum StartNixDaemonError {
#[error("Starting systemd unit")]
StartSystemdUnit(
#[source]
#[from]
StartSystemdUnitError,
),
}

View file

@ -15,7 +15,7 @@ use meta::{
CreateNixTree, CreateNixTreeError, CreateSystemdSysext, CreateSystemdSysextError,
CreateUsersAndGroup, CreateUsersAndGroupError, PlaceChannelConfiguration,
PlaceChannelConfigurationError, PlaceNixConfiguration, PlaceNixConfigurationError,
ProvisionNix, ProvisionNixError, StartNixDaemon, StartNixDaemonError,
ProvisionNix, ProvisionNixError,
};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
@ -62,6 +62,7 @@ pub enum Action {
DarwinCreateVolume(base::darwin::CreateVolume),
DarwinEnableOwnership(base::darwin::EnableOwnership),
DarwinEncryptVolume(base::darwin::EncryptVolume),
DarwinKickStartLaunchctlService(base::darwin::KickstartLaunchctlService),
DarwinUnmountVolume(base::darwin::UnmountVolume),
ConfigureNix(ConfigureNix),
ConfigureNixDaemonService(ConfigureNixDaemonService),
@ -81,7 +82,6 @@ pub enum Action {
PlaceNixConfiguration(PlaceNixConfiguration),
ProvisionNix(ProvisionNix),
SetupDefaultProfile(SetupDefaultProfile),
StartNixDaemon(StartNixDaemon),
StartSystemdUnit(StartSystemdUnit),
SystemdSysextMerge(SystemdSysextMerge),
}
@ -99,6 +99,8 @@ pub enum ActionError {
#[error(transparent)]
DarwinEncryptVolume(#[from] base::darwin::EncryptVolumeError),
#[error(transparent)]
DarwinKickStartLaunchctlService(#[from] base::darwin::KickstartLaunchctlServiceError),
#[error(transparent)]
DarwinUnmountVolume(#[from] base::darwin::UnmountVolumeError),
#[error("Attempted to revert an unexecuted action")]
NotExecuted(Action),
@ -141,8 +143,6 @@ pub enum ActionError {
#[error(transparent)]
SetupDefaultProfile(#[from] SetupDefaultProfileError),
#[error(transparent)]
StartNixDaemon(#[from] StartNixDaemonError),
#[error(transparent)]
StartSystemdUnit(#[from] StartSystemdUnitError),
#[error(transparent)]
SystemdSysExtMerge(#[from] SystemdSysextMergeError),
@ -179,7 +179,7 @@ impl Actionable for Action {
Action::PlaceNixConfiguration(i) => i.describe_execute(),
Action::ProvisionNix(i) => i.describe_execute(),
Action::SetupDefaultProfile(i) => i.describe_execute(),
Action::StartNixDaemon(i) => i.describe_execute(),
Action::DarwinKickStartLaunchctlService(i) => i.describe_execute(),
Action::StartSystemdUnit(i) => i.describe_execute(),
Action::SystemdSysextMerge(i) => i.describe_execute(),
}
@ -211,7 +211,7 @@ impl Actionable for Action {
Action::PlaceNixConfiguration(i) => i.execute().await?,
Action::ProvisionNix(i) => i.execute().await?,
Action::SetupDefaultProfile(i) => i.execute().await?,
Action::StartNixDaemon(i) => i.execute().await?,
Action::DarwinKickStartLaunchctlService(i) => i.execute().await?,
Action::StartSystemdUnit(i) => i.execute().await?,
Action::SystemdSysextMerge(i) => i.execute().await?,
};
@ -244,7 +244,7 @@ impl Actionable for Action {
Action::PlaceNixConfiguration(i) => i.describe_revert(),
Action::ProvisionNix(i) => i.describe_revert(),
Action::SetupDefaultProfile(i) => i.describe_revert(),
Action::StartNixDaemon(i) => i.describe_revert(),
Action::DarwinKickStartLaunchctlService(i) => i.describe_revert(),
Action::StartSystemdUnit(i) => i.describe_revert(),
Action::SystemdSysextMerge(i) => i.describe_revert(),
}
@ -276,7 +276,7 @@ impl Actionable for Action {
Action::PlaceNixConfiguration(i) => i.revert().await?,
Action::ProvisionNix(i) => i.revert().await?,
Action::SetupDefaultProfile(i) => i.revert().await?,
Action::StartNixDaemon(i) => i.revert().await?,
Action::DarwinKickStartLaunchctlService(i) => i.revert().await?,
Action::StartSystemdUnit(i) => i.revert().await?,
Action::SystemdSysextMerge(i) => i.revert().await?,
}

View file

@ -85,7 +85,9 @@ impl InstallPlan {
// The plan itself represents the concept of the sequence of stages.
for action in actions {
if let Err(err) = action.execute().await {
write_receipt(self.clone()).await?;
if let Err(err) = write_receipt(self.clone()).await {
tracing::error!("Error saving receipt: {:?}", err);
}
return Err(ActionError::from(err).into());
}
}
@ -157,7 +159,9 @@ impl InstallPlan {
// The plan itself represents the concept of the sequence of stages.
for action in actions {
if let Err(err) = action.revert().await {
write_receipt(self.clone()).await?;
if let Err(err) = write_receipt(self.clone()).await {
tracing::error!("Error saving receipt: {:?}", err);
}
return Err(ActionError::from(err).into());
}
}

View file

@ -4,7 +4,8 @@ use tokio::process::Command;
use crate::{
actions::{
meta::{darwin::CreateApfsVolume, ConfigureNix, ProvisionNix, StartNixDaemon},
base::darwin::KickstartLaunchctlService,
meta::{darwin::CreateApfsVolume, ConfigureNix, ProvisionNix},
Action, ActionError,
},
execute_command,
@ -57,7 +58,7 @@ impl Plannable for DarwinMultiUser {
.await
.map(Action::from)
.map_err(ActionError::from)?,
StartNixDaemon::plan()
KickstartLaunchctlService::plan("system/org.nixos.nix-daemon".into())
.await
.map(Action::from)
.map_err(ActionError::from)?,

View file

@ -1,6 +1,7 @@
use crate::{
actions::{
meta::{ConfigureNix, ProvisionNix, StartNixDaemon},
base::StartSystemdUnit,
meta::{ConfigureNix, ProvisionNix},
Action, ActionError,
},
planner::{Plannable, PlannerError},
@ -28,7 +29,7 @@ impl Plannable for LinuxMultiUser {
.await
.map(Action::from)
.map_err(ActionError::from)?,
StartNixDaemon::plan()
StartSystemdUnit::plan("nix-daemon.socket".into())
.await
.map(Action::from)
.map_err(ActionError::from)?,

View file

@ -1,6 +1,7 @@
use crate::{
actions::{
meta::{CreateSystemdSysext, ProvisionNix, StartNixDaemon},
base::StartSystemdUnit,
meta::{CreateSystemdSysext, ProvisionNix},
Action, ActionError,
},
planner::Plannable,
@ -30,7 +31,7 @@ impl Plannable for SteamDeck {
.await
.map(Action::from)
.map_err(ActionError::from)?,
StartNixDaemon::plan()
StartSystemdUnit::plan("nix-daemon.socket".into())
.await
.map(Action::from)
.map_err(ActionError::from)?,