Skip to content

Commit

Permalink
Cure APFS/Fstabs on Mac (#246)
Browse files Browse the repository at this point in the history
* wip

* Do main editing portion

* Some more curing on fstab entries

* Overwrite fstab instead of append

* Add newline

* Improve --explain output for CreateNixVolume

* Tweak some permissions

* Fixup a few more permissions spots

* Improve encrypted volume handling

* Handle APFS volumes existing already to some degree

* Correct speeling

* More tweaking preparing for bootstrap/kickstart work

* Most of volume curing works

* Make kickstart use domain/service too

* Fixup nits

* Fix a missing format!
  • Loading branch information
Hoverbear committed Mar 8, 2023
1 parent 4a3deef commit 07a48fe
Show file tree
Hide file tree
Showing 14 changed files with 675 additions and 246 deletions.
2 changes: 1 addition & 1 deletion src/action/common/configure_init_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ impl Action for ConfigureInitService {
InitSystem::Launchd => {
vec![ActionDescription::new(
"Unconfigure Nix daemon related settings with launchctl".to_string(),
vec!["Run `launchctl unload {DARWIN_NIX_DAEMON_DEST}`".to_string()],
vec![format!("Run `launchctl unload {DARWIN_NIX_DAEMON_DEST}`")],
)]
},
#[cfg(not(target_os = "macos"))]
Expand Down
103 changes: 0 additions & 103 deletions src/action/macos/bootstrap_apfs_volume.rs

This file was deleted.

141 changes: 141 additions & 0 deletions src/action/macos/bootstrap_launchctl_service.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
use std::path::{Path, PathBuf};

use tokio::process::Command;
use tracing::{span, Span};

use crate::action::{ActionError, ActionTag, StatefulAction};
use crate::execute_command;

use crate::action::{Action, ActionDescription};

/**
Bootstrap and kickstart an APFS volume
*/
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct BootstrapLaunchctlService {
domain: String,
service: String,
path: PathBuf,
}

impl BootstrapLaunchctlService {
#[tracing::instrument(level = "debug", skip_all)]
pub async fn plan(
domain: impl AsRef<str>,
service: impl AsRef<str>,
path: impl AsRef<Path>,
) -> Result<StatefulAction<Self>, ActionError> {
let domain = domain.as_ref().to_string();
let service = service.as_ref().to_string();
let path = path.as_ref().to_path_buf();

let mut command = Command::new("launchctl");
command.process_group(0);
command.arg("print");
command.arg(format!("{domain}/{service}"));
command.arg("-plist");
command.stdin(std::process::Stdio::null());
command.stdout(std::process::Stdio::piped());
command.stderr(std::process::Stdio::piped());
let output = command
.output()
.await
.map_err(|e| ActionError::command(&command, e))?;
if output.status.success() || output.status.code() == Some(37) {
// We presume that success means it's found
return Ok(StatefulAction::completed(Self {
service,
domain,
path,
}));
}

Ok(StatefulAction::uncompleted(Self {
domain,
service,
path,
}))
}
}

#[async_trait::async_trait]
#[typetag::serde(name = "bootstrap_launchctl_service")]
impl Action for BootstrapLaunchctlService {
fn action_tag() -> ActionTag {
ActionTag("bootstrap_launchctl_service")
}
fn tracing_synopsis(&self) -> String {
format!(
"Bootstrap the `{}` service via `launchctl bootstrap {} {}`",
self.service,
self.domain,
self.path.display()
)
}

fn tracing_span(&self) -> Span {
span!(
tracing::Level::DEBUG,
"bootstrap_launchctl_service",
domain = self.domain,
path = %self.path.display(),
)
}

fn execute_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(self.tracing_synopsis(), vec![])]
}

#[tracing::instrument(level = "debug", skip_all)]
async fn execute(&mut self) -> Result<(), ActionError> {
let Self {
domain,
service: _,
path,
} = self;

execute_command(
Command::new("launchctl")
.process_group(0)
.arg("bootstrap")
.arg(domain)
.arg(path)
.stdin(std::process::Stdio::null()),
)
.await?;

Ok(())
}

fn revert_description(&self) -> Vec<ActionDescription> {
vec![ActionDescription::new(
format!(
"Run `launchctl bootout {} {}`",
self.domain,
self.path.display()
),
vec![],
)]
}

#[tracing::instrument(level = "debug", skip_all)]
async fn revert(&mut self) -> Result<(), ActionError> {
let Self {
path,
service: _,
domain,
} = self;

execute_command(
Command::new("launchctl")
.process_group(0)
.arg("bootout")
.arg(domain)
.arg(path)
.stdin(std::process::Stdio::null()),
)
.await?;

Ok(())
}
}
40 changes: 8 additions & 32 deletions src/action/macos/create_apfs_volume.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ use tracing::{span, Span};

use crate::action::{ActionError, ActionTag, StatefulAction};
use crate::execute_command;
use serde::Deserialize;

use crate::action::{Action, ActionDescription};
use crate::os::darwin::DiskUtilApfsListOutput;

#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct CreateApfsVolume {
Expand All @@ -31,19 +31,20 @@ impl CreateApfsVolume {
for container in parsed.containers {
for volume in container.volumes {
if volume.name == name {
return Err(ActionError::Custom(Box::new(
CreateApfsVolumeError::ExistingVolume(name),
)));
return Ok(StatefulAction::completed(Self {
disk: disk.as_ref().to_path_buf(),
name,
case_sensitive,
}));
}
}
}

Ok(Self {
Ok(StatefulAction::uncompleted(Self {
disk: disk.as_ref().to_path_buf(),
name,
case_sensitive,
}
.into())
}))
}
}

Expand Down Expand Up @@ -135,28 +136,3 @@ impl Action for CreateApfsVolume {
Ok(())
}
}

#[non_exhaustive]
#[derive(Debug, thiserror::Error)]
pub enum CreateApfsVolumeError {
#[error("Existing volume called `{0}` found in `diskutil apfs list`, delete it with `diskutil apfs deleteVolume \"{0}\"`")]
ExistingVolume(String),
}

#[derive(Deserialize, Clone, Debug)]
#[serde(rename_all = "PascalCase")]
struct DiskUtilApfsListOutput {
containers: Vec<DiskUtilApfsContainer>,
}

#[derive(Deserialize, Clone, Debug)]
#[serde(rename_all = "PascalCase")]
struct DiskUtilApfsContainer {
volumes: Vec<DiskUtilApfsListVolume>,
}

#[derive(Deserialize, Clone, Debug)]
#[serde(rename_all = "PascalCase")]
struct DiskUtilApfsListVolume {
name: String,
}
Loading

0 comments on commit 07a48fe

Please sign in to comment.