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:
parent
f8b3e29751
commit
ba841149e7
|
@ -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
|
||||||
|
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
4
tests/fixtures/macos/macos.json
vendored
4
tests/fixtures/macos/macos.json
vendored
|
@ -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"
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue