From 432bc2f271f99af8eb47cf3e0b62f3598a7c684e Mon Sep 17 00:00:00 2001 From: Shubham Malik Date: Fri, 22 May 2026 06:48:49 +0530 Subject: [PATCH 1/2] chore(mdm): use launchctl bootstrap/bootout instead of load/unload --- internal/launchd/launchd.go | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/internal/launchd/launchd.go b/internal/launchd/launchd.go index 5f8bdcd..f8fea3d 100644 --- a/internal/launchd/launchd.go +++ b/internal/launchd/launchd.go @@ -130,11 +130,19 @@ func Install(exec executor.Executor, log *progress.Logger) error { log.Debug("launchd install: plist=%q log_dir=%q interval=%ds user_home=%q is_root=%v", plistPath, logDir, intervalSeconds, userHome, exec.IsRoot()) - // Load plist - _, _, exitCode, err := exec.Run(ctx, "launchctl", "load", plistPath) - log.Debug("launchctl load %q: exit_code=%d err=%v", plistPath, exitCode, err) + // Bootstrap plist into its launchd domain. Apple actively recommends + // `bootstrap`/`bootout` over the older `load`/`unload` verbs, which + // are on the path to deprecation. Root daemons live in the `system` + // domain; user LaunchAgents in `gui/`. Available since macOS + // 10.11, so every machine we target supports it. + domain := "system" + if !exec.IsRoot() { + domain = fmt.Sprintf("gui/%d", os.Getuid()) + } + _, _, exitCode, err := exec.Run(ctx, "launchctl", "bootstrap", domain, plistPath) + log.Debug("launchctl bootstrap %q %q: exit_code=%d err=%v", domain, plistPath, exitCode, err) if err != nil || exitCode != 0 { - return fmt.Errorf("failed to load launchd configuration") + return fmt.Errorf("failed to bootstrap launchd configuration") } log.Progress("launchd configuration completed successfully") @@ -164,11 +172,18 @@ func doUninstall(ctx context.Context, exec executor.Executor, log *progress.Logg plistPath = agentPlistPath() } - // Unload + // Bootout. `bootout` removes a service from its domain regardless of + // how it was originally added, so it works on plists previously + // `launchctl load`-ed by older agent versions during upgrade. stdout, _, _, _ := exec.Run(ctx, "launchctl", "list") if strings.Contains(stdout, label) { - _, _, exitCode, err := exec.Run(ctx, "launchctl", "unload", plistPath) - log.Debug("launchctl unload %q: exit_code=%d err=%v", plistPath, exitCode, err) + domain := "system" + if !exec.IsRoot() { + domain = fmt.Sprintf("gui/%d", os.Getuid()) + } + target := domain + "/" + label + _, _, exitCode, err := exec.Run(ctx, "launchctl", "bootout", target) + log.Debug("launchctl bootout %q: exit_code=%d err=%v", target, exitCode, err) log.Progress("Unloaded launchd agent") } From 58b023015d11035848df9ced4c6e3d6c2df2a089 Mon Sep 17 00:00:00 2001 From: Shubham Malik Date: Fri, 22 May 2026 09:11:44 +0530 Subject: [PATCH 2/2] fix(launchd): surface bootstrap/bootout failures instead of swallowing them --- internal/launchd/launchd.go | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/internal/launchd/launchd.go b/internal/launchd/launchd.go index f8fea3d..a1ddd37 100644 --- a/internal/launchd/launchd.go +++ b/internal/launchd/launchd.go @@ -139,10 +139,13 @@ func Install(exec executor.Executor, log *progress.Logger) error { if !exec.IsRoot() { domain = fmt.Sprintf("gui/%d", os.Getuid()) } - _, _, exitCode, err := exec.Run(ctx, "launchctl", "bootstrap", domain, plistPath) - log.Debug("launchctl bootstrap %q %q: exit_code=%d err=%v", domain, plistPath, exitCode, err) - if err != nil || exitCode != 0 { - return fmt.Errorf("failed to bootstrap launchd configuration") + _, stderr, exitCode, err := exec.Run(ctx, "launchctl", "bootstrap", domain, plistPath) + log.Debug("launchctl bootstrap %q %q: exit_code=%d err=%v stderr=%q", domain, plistPath, exitCode, err, stderr) + if err != nil { + return fmt.Errorf("launchctl bootstrap failed: %w", err) + } + if exitCode != 0 { + return fmt.Errorf("launchctl bootstrap failed (exit code %d): %s", exitCode, strings.TrimSpace(stderr)) } log.Progress("launchd configuration completed successfully") @@ -182,9 +185,16 @@ func doUninstall(ctx context.Context, exec executor.Executor, log *progress.Logg domain = fmt.Sprintf("gui/%d", os.Getuid()) } target := domain + "/" + label - _, _, exitCode, err := exec.Run(ctx, "launchctl", "bootout", target) - log.Debug("launchctl bootout %q: exit_code=%d err=%v", target, exitCode, err) - log.Progress("Unloaded launchd agent") + _, stderr, exitCode, err := exec.Run(ctx, "launchctl", "bootout", target) + log.Debug("launchctl bootout %q: exit_code=%d err=%v stderr=%q", target, exitCode, err, stderr) + switch { + case err != nil: + log.Warn("launchctl bootout failed: %v — the running service may persist until reboot; plist will still be removed", err) + case exitCode != 0: + log.Warn("launchctl bootout failed (exit code %d): %s — the running service may persist until reboot; plist will still be removed", exitCode, strings.TrimSpace(stderr)) + default: + log.Progress("Unloaded launchd agent") + } } // Remove plist