launchctl bootstrap fails with disabled in a dirty state (#555)

* Handle a MacOS service being disabled during bootstrap

* Handle service disabled in configure_init_service

* Fixup missed line

* Fix import

* Don't deref pointer

* Tweak detection and re-enablement
This commit is contained in:
Ana Hobden 2023-07-05 13:38:50 -07:00 committed by GitHub
parent f8b3e29751
commit ba841149e7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 106 additions and 26 deletions

View file

@ -189,13 +189,31 @@ impl Action for ConfigureInitService {
.await .await
.map_err(Self::error)?; .map_err(Self::error)?;
let domain = "system";
let service = "org.nixos.nix-daemon";
let is_disabled = crate::action::macos::service_is_disabled(&domain, &service)
.await
.map_err(Self::error)?;
if is_disabled {
execute_command(
Command::new("launchctl")
.process_group(0)
.arg("enable")
.arg(&format!("{domain}/{service}"))
.stdin(std::process::Stdio::null()),
)
.await
.map_err(Self::error)?;
}
if *start_daemon { if *start_daemon {
execute_command( execute_command(
Command::new("launchctl") Command::new("launchctl")
.process_group(0) .process_group(0)
.arg("kickstart") .arg("kickstart")
.arg("-k") .arg("-k")
.arg("system/org.nixos.nix-daemon") .arg(&format!("{domain}/{service}"))
.stdin(std::process::Stdio::null()), .stdin(std::process::Stdio::null()),
) )
.await .await

View file

@ -8,6 +8,8 @@ use crate::execute_command;
use crate::action::{Action, ActionDescription}; use crate::action::{Action, ActionDescription};
use super::service_is_disabled;
/** /**
Bootstrap and kickstart an APFS volume Bootstrap and kickstart an APFS volume
*/ */
@ -16,6 +18,8 @@ pub struct BootstrapLaunchctlService {
domain: String, domain: String,
service: String, service: String,
path: PathBuf, path: PathBuf,
is_present: bool,
is_disabled: bool,
} }
impl BootstrapLaunchctlService { impl BootstrapLaunchctlService {
@ -29,24 +33,38 @@ impl BootstrapLaunchctlService {
let service = service.as_ref().to_string(); let service = service.as_ref().to_string();
let path = path.as_ref().to_path_buf(); let path = path.as_ref().to_path_buf();
let mut command = Command::new("launchctl"); let is_present = {
command.process_group(0); let mut command = Command::new("launchctl");
command.arg("print"); command.process_group(0);
command.arg(format!("{domain}/{service}")); command.arg("print");
command.arg("-plist"); command.arg(format!("{domain}/{service}"));
command.stdin(std::process::Stdio::null()); command.arg("-plist");
command.stdout(std::process::Stdio::piped()); command.stdin(std::process::Stdio::null());
command.stderr(std::process::Stdio::piped()); command.stdout(std::process::Stdio::piped());
let output = command command.stderr(std::process::Stdio::piped());
.output() let command_output = command
.await .output()
.map_err(|e| Self::error(ActionErrorKind::command(&command, e)))?; .await
if output.status.success() || output.status.code() == Some(37) { .map_err(|e| Self::error(ActionErrorKind::command(&command, e)))?;
// We presume that success means it's found // We presume that success means it's found
if command_output.status.success() || command_output.status.code() == Some(37) {
true
} else {
false
}
};
let is_disabled = service_is_disabled(&domain, &service)
.await
.map_err(Self::error)?;
if is_present && !is_disabled {
return Ok(StatefulAction::completed(Self { return Ok(StatefulAction::completed(Self {
service, service,
domain, domain,
path, path,
is_present,
is_disabled,
})); }));
} }
@ -54,6 +72,8 @@ impl BootstrapLaunchctlService {
domain, domain,
service, service,
path, path,
is_present,
is_disabled,
})) }))
} }
} }
@ -79,6 +99,8 @@ impl Action for BootstrapLaunchctlService {
"bootstrap_launchctl_service", "bootstrap_launchctl_service",
domain = self.domain, domain = self.domain,
path = %self.path.display(), path = %self.path.display(),
is_disabled = self.is_disabled,
is_present = self.is_present,
) )
} }
@ -90,20 +112,36 @@ impl Action for BootstrapLaunchctlService {
async fn execute(&mut self) -> Result<(), ActionError> { async fn execute(&mut self) -> Result<(), ActionError> {
let Self { let Self {
domain, domain,
service: _, service,
path, path,
is_present,
is_disabled,
} = self; } = self;
execute_command( if *is_disabled {
Command::new("launchctl") execute_command(
.process_group(0) Command::new("launchctl")
.arg("bootstrap") .process_group(0)
.arg(domain) .arg("enable")
.arg(path) .arg(&format!("{domain}/{service}"))
.stdin(std::process::Stdio::null()), .stdin(std::process::Stdio::null()),
) )
.await .await
.map_err(Self::error)?; .map_err(Self::error)?;
}
if !*is_present {
execute_command(
Command::new("launchctl")
.process_group(0)
.arg("bootstrap")
.arg(&domain)
.arg(&path)
.stdin(std::process::Stdio::null()),
)
.await
.map_err(Self::error)?;
}
Ok(()) Ok(())
} }

View file

@ -29,6 +29,8 @@ use tokio::process::Command;
pub use unmount_apfs_volume::UnmountApfsVolume; pub use unmount_apfs_volume::UnmountApfsVolume;
use uuid::Uuid; use uuid::Uuid;
use crate::execute_command;
use super::ActionErrorKind; use super::ActionErrorKind;
async fn get_uuid_for_label(apfs_volume_label: &str) -> Result<Option<Uuid>, ActionErrorKind> { async fn get_uuid_for_label(apfs_volume_label: &str) -> Result<Option<Uuid>, ActionErrorKind> {
@ -75,3 +77,23 @@ struct DiskUtilApfsInfoOutput {
#[serde(rename = "VolumeUUID")] #[serde(rename = "VolumeUUID")]
volume_uuid: Option<Uuid>, volume_uuid: Option<Uuid>,
} }
#[tracing::instrument]
pub(crate) async fn service_is_disabled(
domain: &str,
service: &str,
) -> Result<bool, ActionErrorKind> {
let output = execute_command(
Command::new("launchctl")
.arg("print-disabled")
.arg(&domain)
.stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped()),
)
.await?;
let utf8_output = String::from_utf8_lossy(&output.stdout);
let is_disabled = utf8_output.contains(&format!("\"{service}\" => disabled"));
tracing::trace!(is_disabled, "Service disabled status");
Ok(is_disabled)
}

View file

@ -61,7 +61,9 @@
"action": { "action": {
"domain": "system", "domain": "system",
"service": "org.nixos.darwin-store", "service": "org.nixos.darwin-store",
"path": "/Library/LaunchDaemons/org.nixos.darwin-store.plist" "path": "/Library/LaunchDaemons/org.nixos.darwin-store.plist",
"is_present": false,
"is_disabled": false
}, },
"state": "Uncompleted" "state": "Uncompleted"
}, },