Skip to content

Commit e0682dd

Browse files
committed
feat(fleet): install-service — user LaunchAgent for start-on-login on macOS
New `install-service` / `uninstall-service` subcommands render a LaunchAgent plist against the invoking binary path and the agent's data-dir log path, write it to `~/Library/LaunchAgents/`, and drive `launchctl bootstrap` / `bootout`. No sudo, no static plist file to ship, no manual `launchctl` steps for the operator. The plist runs `arcbox-fleet-agent serve` at login (RunAtLoad), respawns only on non-zero exit (KeepAlive.SuccessfulExit=false — a `launchctl bootout` SIGTERM lets the agent drain runners and exit cleanly without getting resurrected), and tees launchd's own stdout/stderr into the same log dir the agent's logger uses so early-startup output before `arcbox_logging::init` still lands next to the real logs. Uninstall is idempotent — a `bootout` with the job not loaded and a missing plist file both count as success, since the desired end state is "not installed." macOS-only for this cut. Linux systemd (user unit) is the natural follow-up; the CLI shape stays uniform (subcommand exists everywhere, bails with a clear message on non-macOS) so docs and completions don't diverge.
1 parent 3c61b1c commit e0682dd

4 files changed

Lines changed: 313 additions & 16 deletions

File tree

Cargo.lock

Lines changed: 14 additions & 16 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

fleet/arcbox-fleet-agent/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ arcbox-protocol.workspace = true
4343
arcbox-constants.workspace = true
4444
russh = { version = "0.62.1", default-features = false, features = ["ring", "flate2"] }
4545
thiserror.workspace = true
46+
plist = "1.10.0"
47+
libc.workspace = true
4648

4749
[lints]
4850
workspace = true

fleet/arcbox-fleet-agent/src/main.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ mod enroll;
3434
mod fsutil;
3535
mod host;
3636
mod runner;
37+
#[cfg(target_os = "macos")]
38+
mod service;
3739
mod settings;
3840
mod state;
3941
mod vm;
@@ -125,6 +127,16 @@ enum Command {
125127
/// supports.
126128
kinds: Vec<String>,
127129
},
130+
/// Install a per-user LaunchAgent so `serve` starts on login (macOS).
131+
///
132+
/// Renders the plist against the invoking binary (`env::current_exe()`)
133+
/// and the agent's data-dir log path, writes it to
134+
/// `~/Library/LaunchAgents/`, and `launchctl bootstrap`s it into the
135+
/// current GUI session. Re-run after moving the binary.
136+
InstallService,
137+
/// Remove the LaunchAgent installed by `install-service` (macOS).
138+
/// Idempotent — safe to run when nothing is installed.
139+
UninstallService,
128140
}
129141

130142
#[derive(Debug, Subcommand)]
@@ -444,9 +456,41 @@ async fn run(command: Command, config: AgentConfig) -> Result<()> {
444456
}
445457
Ok(())
446458
}
459+
Command::InstallService => install_service(&config),
460+
Command::UninstallService => uninstall_service(),
447461
}
448462
}
449463

464+
/// macOS: install the LaunchAgent via [`service::install`]. Other Unix
465+
/// targets (Linux) bail with a pointer to the planned systemd support;
466+
/// keeping the subcommand in the CLI shape lets docs and tab-completion
467+
/// stay uniform across hosts.
468+
#[cfg(target_os = "macos")]
469+
fn install_service(config: &AgentConfig) -> Result<()> {
470+
service::install(config)
471+
}
472+
473+
#[cfg(not(target_os = "macos"))]
474+
fn install_service(_config: &AgentConfig) -> Result<()> {
475+
anyhow::bail!(
476+
"install-service is only implemented for macOS; Linux systemd (user unit) support \
477+
is planned as a follow-up"
478+
)
479+
}
480+
481+
#[cfg(target_os = "macos")]
482+
fn uninstall_service() -> Result<()> {
483+
service::uninstall()
484+
}
485+
486+
#[cfg(not(target_os = "macos"))]
487+
fn uninstall_service() -> Result<()> {
488+
anyhow::bail!(
489+
"uninstall-service is only implemented for macOS; Linux systemd (user unit) support \
490+
is planned as a follow-up"
491+
)
492+
}
493+
450494
/// Build a `CancellationToken` cancelled on the first termination signal
451495
/// (see [`shutdown_signal`]), logging `message` when it fires. Shared by
452496
/// `run` and `serve`, whose shutdown trigger is otherwise identical.

0 commit comments

Comments
 (0)