Umbra
← All docs

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:

OSBackendArtifact
Linuxsystemd/etc/systemd/system/umbra-agent.service
macOSlaunchd/Library/LaunchDaemons/io.umbra.agent.plist
WindowsService Control Managerservice 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

CommandEffect
sudo umbra-agent --installEnroll + drop service artifact + start. One-time per host.
sudo umbra-agent --statusPrint agent_id, version, control plane, daemon state. No network calls. Fast diagnostic over SSH.
sudo umbra-agent --logStream 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 --disableStop + remove from autostart. Credential preserved; agent shows as offline in the dashboard, NOT revoked.
sudo umbra-agent --enableRe-enable autostart and start. Inverse of --disable.
sudo umbra-agent --uninstallRevoke 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:

OSSourceEquivalent native command
Linuxsystemd journalsudo 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
WindowsApplication Event Log, source UmbraAgentwevtutil 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

  1. Binary copied to the OS install path (/usr/local/bin/umbra-agent on Linux+macOS, C:\Program Files\Umbra\umbra-agent.exe on Windows). Atomic-write-via-rename so a crash mid-copy never leaves a corrupt binary.
  2. 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
  3. The OS service registration (unit / plist / SCM entry).
  4. 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.