System service install (systemd / launchd / Windows SCM)
One-line --install registers the agent as a system service that persists across reboots, survives SSH disconnects, and exposes a clean --disable / --enable / --status / --uninstall lifecycle. Linux + macOS + Windows.
Last updated May 28, 2026
What it is
A --install flag on the agent binary that registers it as a proper
system service:
| OS | Backend | Artifact |
|---|---|---|
| Linux | systemd | /etc/systemd/system/umbra-agent.service |
| macOS | launchd | /Library/LaunchDaemons/io.umbra.agent.plist |
| Windows | Service Control Manager | service UmbraAgent |
The dashboard’s Add-agent install snippet uses --install by default
so the new agent is persistent from the first paste. The operator
closes their SSH session, the agent keeps running. Host reboots,
the agent comes back. No supervisor wrapper to write, no nohup, no
screen / tmux.
Lifecycle commands
| Command | Effect |
|---|---|
sudo umbra-agent --install | Enroll + drop service artifact + start. One-time per host. |
sudo umbra-agent --status | Print agent_id, version, control plane, daemon state. No network calls. Fast diagnostic over SSH. |
sudo umbra-agent --log | Stream the daemon’s recent log entries from the OS-native journal. Add --lines N to control how many (default 100), --follow to tail. |
sudo umbra-agent --disable | Stop + remove from autostart. Credential preserved; agent shows as offline in the dashboard, NOT revoked. |
sudo umbra-agent --enable | Re-enable autostart and start. Inverse of --disable. |
sudo umbra-agent --uninstall | Revoke the credential with the control plane + remove the service artifact + wipe state.json. Full teardown. |
Where --log actually reads from
The agent’s daemon mode pipes stdout/stderr into the OS-native log
journal so the operator can use familiar tools if they prefer. The
--log flag is just a thin wrapper:
| OS | Source | Equivalent native command |
|---|---|---|
| Linux | systemd journal | sudo journalctl -u umbra-agent -n 100 [-f] |
| macOS | /var/log/umbra-agent.log (set in the launchd plist) | sudo tail -n 100 [-f] /var/log/umbra-agent.log |
| Windows | Application Event Log, source UmbraAgent | wevtutil qe Application /q:"*[System[Provider[@Name='UmbraAgent']]]" /c:100 /f:text |
Operators familiar with their platform can keep using the native
tools. --log exists so anyone unfamiliar gets the right output
without having to remember the platform-specific incantation.
What --install produces
- Binary copied to the OS install path (
/usr/local/bin/umbra-agenton Linux+macOS,C:\Program Files\Umbra\umbra-agent.exeon Windows). Atomic-write-via-rename so a crash mid-copy never leaves a corrupt binary. - Credential enrolled and persisted at the system state dir:
- Linux / macOS:
/etc/umbra-agent/state.json(root:root, 0600) - Windows:
%ProgramData%\Umbra\umbra-agent\state.json
- Linux / macOS:
- The OS service registration (unit / plist / SCM entry).
- The service started and enabled for autostart on boot.
Linux systemd hardening
The unit ships with the security primitives a SOC operator expects
to see on any service running root: NoNewPrivileges,
ProtectSystem=strict with read-write only for /etc/umbra-agent,
ProtectHome, PrivateTmp, ProtectKernelTunables / Modules /
ControlGroups, RestrictNamespaces, LockPersonality,
MemoryDenyWriteExecute, RestrictRealtime, RestrictSUIDSGID.
The unit is human-readable; the customer’s SOC can review it
in-place. Restart=on-failure with RestartSec=5s keeps a
network blip from cratering the agent without thrashing on a
genuinely-bad config.
macOS launchd
KeepAlive + RunAtLoad give us “start on boot, restart on
crash.” ThrottleInterval=5 matches systemd’s RestartSec.
Standard out/err captured at /var/log/umbra-agent.log for the
operator’s tail -f. Plist is dropped root:wheel mode 0644 so
launchd accepts it (it refuses looser perms on principle).
Windows SCM
Service runs as LocalSystem with StartType=Automatic. The
agent binary detects when it was launched by SCM
(svc.IsWindowsService()) and registers a dispatcher within
~30 seconds so SCM doesn’t kill the process as “did not start in
a timely fashion.” Stop / Shutdown control codes translate into
a context-cancel that the heartbeat loop honours within a few
seconds. The service stops cleanly, no SCM force-kill.
Why we don’t create a dedicated umbra-agent user
For MVP, the daemon runs as root (Linux/macOS) / LocalSystem
(Windows). The agent’s only privileged operation is net.Dial.
Dropping to a nobody-equivalent user would gain no meaningful
security, and creating a dedicated user requires distro-specific
useradd invocations that complicate the install path. The
systemd hardening above + launchd’s sandbox + Windows SCM
isolation give comparable blast-radius reduction without the
install friction.
Future work could add User=umbra-agent with DynamicUser=true
on systemd ≥235 hosts (which is most modern distros), gated on
detection. Additive: wouldn’t change the lifecycle commands.
Foreground mode still works
./umbra-agent (without --install) runs in the foreground
exactly as before. Useful for:
- Testing a new control plane URL before committing to a service install
- Local dev where you want stdout logs in your terminal
- Customer playbooks where systemd / launchd / SCM is unavailable (an immutable distro, a sandboxed environment, etc.)
In foreground mode the credential lives in ~/.umbra-agent/state.json,
distinct from the system state dir. Foreground and daemon installs
on the same host don’t collide.
Why this matters
Without it, the dashboard install snippet ran ./umbra-agent in
the operator’s SSH session. The session closed → SIGHUP → agent
died → customer thought the agent “expired.” The credential
itself never expired (and still doesn’t: credentials are valid
until revoked), but the process did. Customers couldn’t run a
serious monitoring deployment without writing their own systemd
unit, which is exactly the kind of friction that makes a tool
not get adopted.
The --install path closes the gap: paste one line, get a
production service. Same single static Go binary, same outbound-
HTTPS-only egress posture, same audit trail. The only difference
is who manages the process lifecycle.