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
.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 {
execute_command(
Command::new("launchctl")
.process_group(0)
.arg("kickstart")
.arg("-k")
.arg("system/org.nixos.nix-daemon")
.arg(&format!("{domain}/{service}"))
.stdin(std::process::Stdio::null()),
)
.await

View file

@ -8,6 +8,8 @@ use crate::execute_command;
use crate::action::{Action, ActionDescription};
use super::service_is_disabled;
/**
Bootstrap and kickstart an APFS volume
*/
@ -16,6 +18,8 @@ pub struct BootstrapLaunchctlService {
domain: String,
service: String,
path: PathBuf,
is_present: bool,
is_disabled: bool,
}
impl BootstrapLaunchctlService {
@ -29,24 +33,38 @@ impl BootstrapLaunchctlService {
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| Self::error(ActionErrorKind::command(&command, e)))?;
if output.status.success() || output.status.code() == Some(37) {
let is_present = {
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 command_output = command
.output()
.await
.map_err(|e| Self::error(ActionErrorKind::command(&command, e)))?;
// 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 {
service,
domain,
path,
is_present,
is_disabled,
}));
}
@ -54,6 +72,8 @@ impl BootstrapLaunchctlService {
domain,
service,
path,
is_present,
is_disabled,
}))
}
}
@ -79,6 +99,8 @@ impl Action for BootstrapLaunchctlService {
"bootstrap_launchctl_service",
domain = self.domain,
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> {
let Self {
domain,
service: _,
service,
path,
is_present,
is_disabled,
} = self;
execute_command(
Command::new("launchctl")
.process_group(0)
.arg("bootstrap")
.arg(domain)
.arg(path)
.stdin(std::process::Stdio::null()),
)
.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 !*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(())
}

View file

@ -29,6 +29,8 @@ use tokio::process::Command;
pub use unmount_apfs_volume::UnmountApfsVolume;
use uuid::Uuid;
use crate::execute_command;
use super::ActionErrorKind;
async fn get_uuid_for_label(apfs_volume_label: &str) -> Result<Option<Uuid>, ActionErrorKind> {
@ -75,3 +77,23 @@ struct DiskUtilApfsInfoOutput {
#[serde(rename = "VolumeUUID")]
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": {
"domain": "system",
"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"
},