feat(agent): SPEC-018 Phase 1 managed-agent SYSTEM service host
Run the managed/persistent GuruConnect agent as a LocalSystem Windows service so it is reachable at the login screen and across reboots, and so the SPEC-016 per-machine cak_ store (ACL-restricted to SYSTEM + Administrators) is finally readable in-context. Phase 1 scope (host + lifecycle only): - New agent/src/service/mod.rs: registers "GuruConnectAgent" with the SCM via the windows-service dispatcher, reports a correct lifecycle (StartPending -> Running -> StopPending -> Stopped), handles Stop/Shutdown via an AtomicBool the agent loop polls (graceful WS close), and provides install/uninstall/start (LocalSystem, AutoStart, sc-failure crash recovery). Idempotent install/uninstall. - main.rs: hidden `service-run` subcommand routes the SCM-launched process into the dispatcher; new run_managed_agent_service() runs the existing RunMode::PermanentAgent logic (resolve/enroll cak_, hold the relay) as SYSTEM. run_agent() now takes an optional SCM shutdown flag, skips the HKCU Run autostart and the tray when run as the service, and interrupts the reconnect backoff promptly on stop. An interactive launch of a managed binary now installs+starts the service and exits instead of double-running. - install.rs: a managed install (embedded config present) installs the LocalSystem service as the single autostart and removes the legacy HKCU Run entry; uninstall stops+deletes the service (idempotent). Attended/viewer installs are untouched. - Kept the SPEC-016 Phase B fail-fast guard as a harmless safety net for any non-SYSTEM invocation; updated its comment to name this service as the managed run context. Phase 2 NOT built (seams documented): session broker, per-session capture/input worker, CreateProcessAsUserW token handoff, service/worker IPC, and SERVICE_CONTROL_SESSIONCHANGE. Phase 1 enrolls/connects as SYSTEM but does not capture a desktop (a Session-0 process cannot). No service is installed/started on the dev host; that is a VM/admin integration step. fmt + clippy -D warnings + release build + 55 tests all pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -290,6 +290,18 @@ pub fn install(force_user_install: bool) -> Result<()> {
|
||||
// Register protocol handler
|
||||
register_protocol_handler(elevated)?;
|
||||
|
||||
// SPEC-018: a MANAGED install (embedded config => persistent agent) installs
|
||||
// the LocalSystem service as its single autostart and removes the per-user
|
||||
// HKCU\…\Run entry. Attended (support-code) and viewer installs are untouched:
|
||||
// they have no embedded config and continue to use the HKCU Run / protocol
|
||||
// handler paths exactly as before.
|
||||
#[cfg(windows)]
|
||||
{
|
||||
if crate::config::Config::has_embedded_config() {
|
||||
install_managed_service(&exe_path)?;
|
||||
}
|
||||
}
|
||||
|
||||
info!("Installation complete!");
|
||||
if elevated {
|
||||
info!("Installed system-wide to: {}", install_path.display());
|
||||
@@ -300,6 +312,64 @@ pub fn install(force_user_install: bool) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// SPEC-018: install the managed agent as a LocalSystem service and swap out the
|
||||
/// legacy per-user `HKCU\…\Run` autostart so the service is the single managed
|
||||
/// autostart (no double-run).
|
||||
///
|
||||
/// Installing a LocalSystem service requires Administrator. If the SCM rejects the
|
||||
/// create (not elevated), we surface the error rather than silently leaving the
|
||||
/// machine with no managed autostart — a managed deployment is expected to run the
|
||||
/// install elevated. The HKCU Run entry is removed best-effort regardless.
|
||||
#[cfg(windows)]
|
||||
pub fn install_managed_service(exe_path: &std::path::Path) -> Result<()> {
|
||||
info!("Managed install: registering LocalSystem service (SPEC-018)");
|
||||
|
||||
crate::service::install_service(exe_path)
|
||||
.map_err(|e| anyhow!("failed to install the managed agent service: {e:#}"))?;
|
||||
|
||||
// Start the service now so the agent comes up immediately on first install
|
||||
// rather than only on the next boot. Best-effort: the service is auto-start, so
|
||||
// a transient start failure still self-heals on reboot.
|
||||
if let Err(e) = crate::service::start_service() {
|
||||
warn!(
|
||||
"managed service installed but did not start now ({e:#}); \
|
||||
it is auto-start and will run on next boot"
|
||||
);
|
||||
}
|
||||
|
||||
// Remove the legacy per-user autostart so the agent does not also launch in the
|
||||
// user's session (which would double-run alongside the service).
|
||||
if let Err(e) = crate::startup::remove_from_startup() {
|
||||
warn!(
|
||||
"managed service installed, but failed to remove the legacy HKCU Run \
|
||||
autostart (harmless if it was never present): {}",
|
||||
e
|
||||
);
|
||||
} else {
|
||||
info!("removed legacy HKCU Run autostart (service is now the managed autostart)");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// SPEC-018: remove the managed agent service and any legacy HKCU Run autostart.
|
||||
/// Idempotent — succeeds if neither is present.
|
||||
#[cfg(windows)]
|
||||
pub fn uninstall_managed_service() -> Result<()> {
|
||||
info!("Managed uninstall: removing LocalSystem service (SPEC-018)");
|
||||
|
||||
// Best-effort removal of the legacy autostart first (cheap, no SCM).
|
||||
if let Err(e) = crate::startup::remove_from_startup() {
|
||||
warn!(
|
||||
"failed to remove legacy HKCU Run autostart during uninstall: {}",
|
||||
e
|
||||
);
|
||||
}
|
||||
|
||||
crate::service::uninstall_service()
|
||||
.map_err(|e| anyhow!("failed to uninstall the managed agent service: {e:#}"))
|
||||
}
|
||||
|
||||
/// Check if the guruconnect:// protocol handler is registered
|
||||
#[cfg(windows)]
|
||||
pub fn is_protocol_handler_registered() -> bool {
|
||||
|
||||
Reference in New Issue
Block a user