diff --git a/launchd/com.renacode.cloudmachine.vm-start.plist.template b/launchd/com.renacode.cloudmachine.vm-start.plist.template index e990144..21969c8 100644 --- a/launchd/com.renacode.cloudmachine.vm-start.plist.template +++ b/launchd/com.renacode.cloudmachine.vm-start.plist.template @@ -5,20 +5,21 @@ Label com.renacode.cloudmachine.vm-start ProgramArguments - /usr/bin/env - limactl - start - --tty=false - cmsmb + __CM_AGENT_BIN__ + vm-hold RunAtLoad + ExitTimeOut + 120 EnvironmentVariables PATH diff --git a/mac-app/Sources/CloudMachineAgent/CloudMachineAgent.swift b/mac-app/Sources/CloudMachineAgent/CloudMachineAgent.swift index 65c5daa..b755bf0 100644 --- a/mac-app/Sources/CloudMachineAgent/CloudMachineAgent.swift +++ b/mac-app/Sources/CloudMachineAgent/CloudMachineAgent.swift @@ -21,6 +21,7 @@ struct CloudMachineAgent: AsyncParsableCommand { VMStatusCommand.self, VMUpdateCommand.self, VMGuardCommand.self, + VMHoldCommand.self, VMEncrypt.self, BackupHealthCommand.self, PrepareShutdown.self, diff --git a/mac-app/Sources/CloudMachineAgent/VMHoldCommand.swift b/mac-app/Sources/CloudMachineAgent/VMHoldCommand.swift new file mode 100644 index 0000000..6e87b37 --- /dev/null +++ b/mac-app/Sources/CloudMachineAgent/VMHoldCommand.swift @@ -0,0 +1,20 @@ +import ArgumentParser +import CloudMachineCore +import Foundation + +struct VMHoldCommand: AsyncParsableCommand { + static let configuration = CommandConfiguration( + commandName: "vm-hold", + // Not translated: only the launchd job runs this, nobody reads its help. + abstract: + "Starts the machine and stays running; when it is stopped (logout, shutdown) it stops the machine cleanly." + ) + + func run() async throws { + // The handlers must be in place before the (minutes long) start: a SIGTERM + // that arrives during it is remembered and cancels the start. + let signals = TerminationSignals() + let code = await VMHold.run(steps: VMHold.live, termination: signals.latch) + if code != 0 { throw ExitCode(code) } + } +} diff --git a/mac-app/Sources/CloudMachineCore/LaunchdInstaller.swift b/mac-app/Sources/CloudMachineCore/LaunchdInstaller.swift index 6f4f342..4367d7c 100644 --- a/mac-app/Sources/CloudMachineCore/LaunchdInstaller.swift +++ b/mac-app/Sources/CloudMachineCore/LaunchdInstaller.swift @@ -50,8 +50,11 @@ public enum LaunchdInstaller { // app bundle. CMTooling.linkCommandIntoPath() - // Reloading touches nothing that holds data: `vm-start` on a running - // machine only says so, the others are one-shot checks. + // Reloading is `unload` (SIGTERM) and `load`. The one-shot agents are + // checks and lose nothing. `vm-start` is `vm-hold`, which stops the + // machine on SIGTERM: a running one is NOT reloaded (see + // `installAgents(isRunning:)`), its new plist is picked up at the next + // login or boot. guard let templatesDir = CMPaths.launchdTemplatesDir else { return CMActionResult( succeeded: false, message: L10n.tr("Could not find the launchd/ directory with templates.")) @@ -131,6 +134,21 @@ public enum LaunchdInstaller { var failed: [Failure] = [] } + /// Agents whose process IS the machine's lifetime: never reloaded while running. + static let holdingLabels: Set = ["com.renacode.cloudmachine.vm-start"] + + /// `launchctl list