diff --git a/src/actions/meta/darwin/create_apfs_volume.rs b/src/actions/meta/darwin/create_apfs_volume.rs new file mode 100644 index 0000000..12118b1 --- /dev/null +++ b/src/actions/meta/darwin/create_apfs_volume.rs @@ -0,0 +1,234 @@ +use serde::Serialize; +use std::path::{Path, PathBuf}; + +use crate::actions::base::{ + darwin::{ + BootstrapVolume, BootstrapVolumeError, CreateSyntheticObjects, CreateSyntheticObjectsError, + CreateVolume, CreateVolumeError, EnableOwnership, EnableOwnershipError, EncryptVolume, + EncryptVolumeError, UnmountVolume, UnmountVolumeError, + }, + CreateDirectory, CreateDirectoryError, CreateFile, CreateFileError, CreateOrAppendFile, + CreateOrAppendFileError, +}; +use crate::actions::{Action, ActionDescription, ActionState, Actionable}; + +const NIX_VOLUME_MOUNTD_DEST: &str = "/Library/LaunchDaemons/org.nixos.darwin-store.plist"; + +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +pub struct CreateApfsVolume { + disk: PathBuf, + name: String, + case_sensitive: bool, + encrypt: Option, + create_or_append_synthetic_conf: CreateOrAppendFile, + create_synthetic_objects: CreateSyntheticObjects, + unmount_volume: UnmountVolume, + create_volume: CreateVolume, + create_or_append_fstab: CreateOrAppendFile, + encrypt_volume: Option, + bootstrap_volume: BootstrapVolume, + enable_ownership: EnableOwnership, + action_state: ActionState, +} + +impl CreateApfsVolume { + #[tracing::instrument(skip_all)] + pub async fn plan( + disk: impl AsRef, + name: String, + case_sensitive: bool, + encrypt: Option, + ) -> Result { + 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_synthetic_objects = CreateSyntheticObjects::plan().await?; + + let unmount_volume = + UnmountVolume::plan(disk, name.clone(), case_sensitive, encrypt).await?; + + let create_volume = CreateVolume::plan(disk, name.clone(), case_sensitive, encrypt).await?; + + let create_or_append_fstab = CreateOrAppendFile::plan( + "/etc/fstab", + "root".into(), + "root".into(), + 0o0655, + "NAME={name} /nix apfs rw,noauto,nobrowse,suid,owners".into(), + ) + .await?; + + let encrypt_volume = if let Some(password) = encrypt { + Some(EncryptVolume::plan(disk, password).await) + } else { + None + }; + + let bootstrap_volume = BootstrapVolume::plan(NIX_VOLUME_MOUNTD_DEST).await?; + let enable_ownership = EnableOwnership::plan("/nix").await?; + + Ok(Self { + disk: disk.to_path_buf(), + name, + case_sensitive, + encrypt, + create_or_append_synthetic_conf, + create_synthetic_objects, + unmount_volume, + create_volume, + create_or_append_fstab, + encrypt_volume, + bootstrap_volume, + enable_ownership, + action_state: ActionState::Uncompleted, + }) + } +} + +#[async_trait::async_trait] +impl Actionable for CreateApfsVolume { + type Error = CreateApfsVolumeError; + + fn describe_execute(&self) -> Vec { + let Self { + disk, + name, + case_sensitive, + encrypt, + create_or_append_synthetic_conf, + create_synthetic_objects, + unmount_volume, + create_volume, + create_or_append_fstab, + encrypt_volume, + bootstrap_volume, + enable_ownership, + action_state: _, + } = &self; + if self.action_state == ActionState::Completed { + vec![] + } else { + vec![ActionDescription::new( + format!("Create an APFS volume", destination.display()), + vec![format!( + "Create a writable, persistent systemd system extension.", + )], + )] + } + } + + #[tracing::instrument(skip_all, fields(destination,))] + async fn execute(&mut self) -> Result<(), Self::Error> { + let Self { + disk, + name, + case_sensitive, + encrypt, + create_or_append_synthetic_conf, + create_synthetic_objects, + unmount_volume, + create_volume, + create_or_append_fstab, + encrypt_volume, + bootstrap_volume, + enable_ownership, + action_state, + } = self; + if *action_state == ActionState::Completed { + tracing::trace!("Already completed: Creating sysext"); + return Ok(()); + } + tracing::debug!("Creating sysext"); + + for create_directory in create_directories { + create_directory.execute().await?; + } + create_extension_release.execute().await?; + create_bind_mount_unit.execute().await?; + + tracing::trace!("Created sysext"); + *action_state = ActionState::Completed; + Ok(()) + } + + fn describe_revert(&self) -> Vec { + let Self { + disk, + name, + case_sensitive, + encrypt, + create_or_append_synthetic_conf, + create_synthetic_objects, + unmount_volume, + create_volume, + create_or_append_fstab, + encrypt_volume, + bootstrap_volume, + enable_ownership, + action_state: _, + } = &self; + if self.action_state == ActionState::Uncompleted { + vec![] + } else { + vec![ActionDescription::new( + format!("Remove the sysext located at `{}`", destination.display()), + vec![], + )] + } + } + + #[tracing::instrument(skip_all, fields(destination,))] + async fn revert(&mut self) -> Result<(), Self::Error> { + let Self { + disk, + name, + case_sensitive, + encrypt, + create_or_append_synthetic_conf, + create_synthetic_objects, + unmount_volume, + create_volume, + create_or_append_fstab, + encrypt_volume, + bootstrap_volume, + enable_ownership, + action_state, + } = self; + if *action_state == ActionState::Uncompleted { + tracing::trace!("Already reverted: Removing sysext"); + return Ok(()); + } + tracing::debug!("Removing sysext"); + + create_bind_mount_unit.revert().await?; + + create_extension_release.revert().await?; + + for create_directory in create_directories.iter_mut().rev() { + create_directory.revert().await?; + } + + tracing::trace!("Removed sysext"); + *action_state = ActionState::Uncompleted; + Ok(()) + } +} + +impl From for Action { + fn from(v: CreateApfsVolume) -> Self { + Action::CreateApfsVolume(v) + } +} + +#[derive(Debug, thiserror::Error, Serialize)] +pub enum CreateApfsVolumeError { + #[error(transparent)] + CreateOrAppendFile(#[from] CreateOrAppendFileError), +} diff --git a/src/actions/meta/darwin/mod.rs b/src/actions/meta/darwin/mod.rs new file mode 100644 index 0000000..4b210b0 --- /dev/null +++ b/src/actions/meta/darwin/mod.rs @@ -0,0 +1,2 @@ +mod create_apfs_volume; +pub use create_apfs_volume::{CreateApfsVolume, CreateApfsVolumeError}; diff --git a/src/actions/meta/mod.rs b/src/actions/meta/mod.rs index 7a0f544..cfda484 100644 --- a/src/actions/meta/mod.rs +++ b/src/actions/meta/mod.rs @@ -5,6 +5,7 @@ mod configure_shell_profile; mod create_nix_tree; mod create_systemd_sysext; mod create_users_and_group; +pub mod darwin; mod place_channel_configuration; mod place_nix_configuration; mod provision_nix; diff --git a/src/actions/mod.rs b/src/actions/mod.rs index d0d6bbd..935f2ac 100644 --- a/src/actions/mod.rs +++ b/src/actions/mod.rs @@ -10,6 +10,7 @@ use base::{ SystemdSysextMergeError, }; use meta::{ + darwin::{CreateApfsVolume, CreateApfsVolumeError}, ConfigureNix, ConfigureNixError, ConfigureShellProfile, ConfigureShellProfileError, CreateNixTree, CreateNixTreeError, CreateSystemdSysext, CreateSystemdSysextError, CreateUsersAndGroup, CreateUsersAndGroupError, PlaceChannelConfiguration, @@ -56,26 +57,27 @@ impl ActionDescription { #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] pub enum Action { - ConfigureNixDaemonService(ConfigureNixDaemonService), ConfigureNix(ConfigureNix), + ConfigureNixDaemonService(ConfigureNixDaemonService), ConfigureShellProfile(ConfigureShellProfile), + CreateApfsVolume(CreateApfsVolume), CreateDirectory(CreateDirectory), - CreateSystemdSysext(CreateSystemdSysext), CreateFile(CreateFile), CreateGroup(CreateGroup), - CreateOrAppendFile(CreateOrAppendFile), CreateNixTree(CreateNixTree), + CreateOrAppendFile(CreateOrAppendFile), + CreateSystemdSysext(CreateSystemdSysext), CreateUser(CreateUser), CreateUsersAndGroup(CreateUsersAndGroup), FetchNix(FetchNix), MoveUnpackedNix(MoveUnpackedNix), PlaceChannelConfiguration(PlaceChannelConfiguration), PlaceNixConfiguration(PlaceNixConfiguration), + ProvisionNix(ProvisionNix), SetupDefaultProfile(SetupDefaultProfile), StartNixDaemon(StartNixDaemon), StartSystemdUnit(StartSystemdUnit), SystemdSysextMerge(SystemdSysextMerge), - ProvisionNix(ProvisionNix), } #[derive(Debug, thiserror::Error, serde::Serialize)] @@ -87,6 +89,8 @@ pub enum ActionError { #[error("Attempted to revert an already reverted action")] AlreadyReverted(Action), #[error(transparent)] + CreateApfsVolume(#[from] CreateApfsVolumeError), + #[error(transparent)] ConfigureNixDaemonService(#[from] ConfigureNixDaemonServiceError), #[error(transparent)] ConfigureNix(#[from] ConfigureNixError), @@ -133,102 +137,106 @@ impl Actionable for Action { type Error = ActionError; fn describe_execute(&self) -> Vec { match self { - Action::ConfigureNixDaemonService(i) => i.describe_execute(), Action::ConfigureNix(i) => i.describe_execute(), + Action::ConfigureNixDaemonService(i) => i.describe_execute(), Action::ConfigureShellProfile(i) => i.describe_execute(), + Action::CreateApfsVolume(i) => i.describe_execute(), Action::CreateDirectory(i) => i.describe_execute(), - Action::CreateSystemdSysext(i) => i.describe_execute(), Action::CreateFile(i) => i.describe_execute(), Action::CreateGroup(i) => i.describe_execute(), - Action::CreateOrAppendFile(i) => i.describe_execute(), Action::CreateNixTree(i) => i.describe_execute(), + Action::CreateOrAppendFile(i) => i.describe_execute(), + Action::CreateSystemdSysext(i) => i.describe_execute(), Action::CreateUser(i) => i.describe_execute(), Action::CreateUsersAndGroup(i) => i.describe_execute(), Action::FetchNix(i) => i.describe_execute(), Action::MoveUnpackedNix(i) => i.describe_execute(), Action::PlaceChannelConfiguration(i) => i.describe_execute(), 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::StartSystemdUnit(i) => i.describe_execute(), Action::SystemdSysextMerge(i) => i.describe_execute(), - Action::ProvisionNix(i) => i.describe_execute(), } } async fn execute(&mut self) -> Result<(), Self::Error> { match self { - Action::ConfigureNixDaemonService(i) => i.execute().await?, Action::ConfigureNix(i) => i.execute().await?, + Action::ConfigureNixDaemonService(i) => i.execute().await?, Action::ConfigureShellProfile(i) => i.execute().await?, + Action::CreateApfsVolume(i) => i.execute().await?, Action::CreateDirectory(i) => i.execute().await?, - Action::CreateSystemdSysext(i) => i.execute().await?, Action::CreateFile(i) => i.execute().await?, Action::CreateGroup(i) => i.execute().await?, - Action::CreateOrAppendFile(i) => i.execute().await?, Action::CreateNixTree(i) => i.execute().await?, + Action::CreateOrAppendFile(i) => i.execute().await?, + Action::CreateSystemdSysext(i) => i.execute().await?, Action::CreateUser(i) => i.execute().await?, Action::CreateUsersAndGroup(i) => i.execute().await?, Action::FetchNix(i) => i.execute().await?, Action::MoveUnpackedNix(i) => i.execute().await?, Action::PlaceChannelConfiguration(i) => i.execute().await?, Action::PlaceNixConfiguration(i) => i.execute().await?, + Action::ProvisionNix(i) => i.execute().await?, Action::SetupDefaultProfile(i) => i.execute().await?, Action::StartNixDaemon(i) => i.execute().await?, Action::StartSystemdUnit(i) => i.execute().await?, Action::SystemdSysextMerge(i) => i.execute().await?, - Action::ProvisionNix(i) => i.execute().await?, }; Ok(()) } fn describe_revert(&self) -> Vec { match self { - Action::ConfigureNixDaemonService(i) => i.describe_revert(), Action::ConfigureNix(i) => i.describe_revert(), + Action::ConfigureNixDaemonService(i) => i.describe_revert(), Action::ConfigureShellProfile(i) => i.describe_revert(), + Action::CreateApfsVolume(i) => i.describe_revert(), Action::CreateDirectory(i) => i.describe_revert(), - Action::CreateSystemdSysext(i) => i.describe_revert(), Action::CreateFile(i) => i.describe_revert(), Action::CreateGroup(i) => i.describe_revert(), - Action::CreateOrAppendFile(i) => i.describe_revert(), Action::CreateNixTree(i) => i.describe_revert(), + Action::CreateOrAppendFile(i) => i.describe_revert(), + Action::CreateSystemdSysext(i) => i.describe_revert(), Action::CreateUser(i) => i.describe_revert(), Action::CreateUsersAndGroup(i) => i.describe_revert(), Action::FetchNix(i) => i.describe_revert(), Action::MoveUnpackedNix(i) => i.describe_revert(), Action::PlaceChannelConfiguration(i) => i.describe_revert(), 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::StartSystemdUnit(i) => i.describe_revert(), Action::SystemdSysextMerge(i) => i.describe_revert(), - Action::ProvisionNix(i) => i.describe_revert(), } } async fn revert(&mut self) -> Result<(), Self::Error> { match self { - Action::ConfigureNixDaemonService(i) => i.revert().await?, Action::ConfigureNix(i) => i.revert().await?, + Action::ConfigureNixDaemonService(i) => i.revert().await?, Action::ConfigureShellProfile(i) => i.revert().await?, + Action::CreateApfsVolume(i) => i.revert().await?, Action::CreateDirectory(i) => i.revert().await?, - Action::CreateSystemdSysext(i) => i.revert().await?, Action::CreateFile(i) => i.revert().await?, Action::CreateGroup(i) => i.revert().await?, - Action::CreateOrAppendFile(i) => i.revert().await?, Action::CreateNixTree(i) => i.revert().await?, + Action::CreateOrAppendFile(i) => i.revert().await?, + Action::CreateSystemdSysext(i) => i.revert().await?, Action::CreateUser(i) => i.revert().await?, Action::CreateUsersAndGroup(i) => i.revert().await?, Action::FetchNix(i) => i.revert().await?, Action::MoveUnpackedNix(i) => i.revert().await?, Action::PlaceChannelConfiguration(i) => i.revert().await?, 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::StartSystemdUnit(i) => i.revert().await?, Action::SystemdSysextMerge(i) => i.revert().await?, - Action::ProvisionNix(i) => i.revert().await?, } Ok(()) } diff --git a/src/planner/darwin/multi_user.rs b/src/planner/darwin/multi_user.rs index f31f02b..7b64632 100644 --- a/src/planner/darwin/multi_user.rs +++ b/src/planner/darwin/multi_user.rs @@ -1,4 +1,11 @@ -use crate::{planner::Plannable, Planner}; +use crate::{ + actions::{ + meta::{darwin::CreateApfsVolume, ConfigureNix, ProvisionNix, StartNixDaemon}, + Action, ActionError, + }, + planner::Plannable, + InstallPlan, Planner, +}; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)] pub struct DarwinMultiUser; @@ -11,7 +18,32 @@ impl Plannable for DarwinMultiUser { async fn plan( settings: crate::InstallSettings, ) -> Result { - todo!() + Ok(InstallPlan { + planner: Self.into(), + settings: settings.clone(), + actions: vec![ + // Create Volume step: + // + // setup_Synthetic -> create_synthetic_objects + // Unmount -> create_volume -> Setup_fstab -> maybe encrypt_volume -> launchctl bootstrap -> launchctl kickstart -> await_volume -> maybe enableOwnership + CreateApfsVolume::plan(settings.clone()) + .await + .map(Action::from) + .map_err(ActionError::from)?, + ProvisionNix::plan(settings.clone()) + .await + .map(Action::from) + .map_err(ActionError::from)?, + ConfigureNix::plan(settings) + .await + .map(Action::from) + .map_err(ActionError::from)?, + StartNixDaemon::plan() + .await + .map(Action::from) + .map_err(ActionError::from)?, + ], + }) } }