documentation
Everything Sitrep does, in one place.
Architecture, install, setup, every CLI command and API endpoint, the exact shape of every report, and the tradeoffs made along the way. For a faster overview, see the homepage.
Overview
Sitrep is a manager and worker agent for Linux fleets. A manager listens for status reports and exposes them over a local JSON API; workers enroll with the manager once and then report on an interval the manager controls. Every connection between a worker and its manager is mutual TLS, using a certificate authority the manager generates itself — no external PKI, no cloud service, no cluster to run.
Every report carries an epoch timestamp and covers whichever checks the manager has enabled: internet reachability, disk space, DRAM usage, CPU usage, and git auth status. The manager keeps only the latest report per worker, in memory — see design tradeoffs for why.
Architecture
Two listeners, two ports, one certificate authority. The manager generates its CA and its own server certificate on first startup. A worker enrolls exactly once — over a connection pinned to the manager's server certificate fingerprint — and receives a signed client certificate back. Every connection after that is full mutual TLS.
Why two separate listeners
Go's tls.Config.ClientAuth is a listener-wide policy, not a per-route one. Rather than branch on it inside a handler, Sitrep runs the enrollment endpoint and the report endpoint on entirely separate net.Listeners with different tls.Configs. A worker with no certificate yet can physically only reach the enrollment port; the report port rejects the TLS handshake itself — before any handler code runs — for any connection that doesn't present a certificate signed by the manager's CA.
Identity binding
Every report handler cross-checks the verified client certificate's common name against the worker_id claimed in the report body. A worker cannot report under another worker's identity, even with a certificate that's validly signed by the same CA.
Config propagation
There's no separate push channel for interval or enabled-check changes. Every response to a report — a ReportAck — carries the manager's current interval and enabled checks. The worker compares that to what it's currently using and, if either differs, updates its ticker and rewrites its local config to match. A manager-side change (via the local API) reaches every worker within one of that worker's own reporting intervals, and workers only ever dial out — nothing needs an inbound connection to a worker.
Requirements
- Linux with systemd — the setup wizards install and manage systemd units directly.
- Root privileges for first-run setup on both the manager and each worker (creates a dedicated
sitrepsystem user, writes to/etc/sitrepand/etc/systemd/system). pingon any worker with theinternetcheck enabled.gh(the GitHub CLI) on any worker with thegitauthcheck enabled.
Missing ping/gh aren't a blocker — the worker wizard detects them, shows the exact install command for your distro's package manager, and only runs it on explicit confirmation. Declining just means that check reports an error until it's installed.
Install
Sitrep is a single Go binary with no runtime dependencies of its own. Build it from source (Go 1.27+):
git clone https://github.com/justwaters/Sitrep.git
cd Sitrep
make linux
make linux cross-compiles bin/sitrep-linux-amd64 and bin/sitrep-linux-arm64 — the artifacts you actually deploy, regardless of what platform you're building on. Copy the one matching your target's architecture and put it on PATH:
scp bin/sitrep-linux-amd64 myhost:/tmp/sitrep
ssh myhost 'sudo install -m 0755 /tmp/sitrep /usr/local/bin/sitrep'
make build also exists, producing a host-platform binary — useful for running go test/go vet during development, but not what you'd deploy unless your dev machine is also Linux.
Manager setup
On the machine that will collect reports:
sudo sitrep manager start
With no existing config, this launches a one-time interactive wizard:
- What do you want to call this machine? — the manager's name, embedded in its CA and server certificate's common name and shown wherever the manager identifies itself (e.g.
token createoutput). - Report listen address — mTLS, default
0.0.0.0:8443. Where workers send reports. - Enrollment listen address — mTLS bootstrap, default
0.0.0.0:8444. Where workers enroll. - Advertised address — the hostname or IP workers should use to reach this manager. Embedded as a SAN in the manager's server certificate and printed by
token create. - Local query API address — default
127.0.0.1:8080. Must resolve to a loopback address; the manager refuses to start otherwise. - Enabled checks — all five by default, individually toggleable.
- Reporting interval — Go duration syntax, e.g.
30s,1m,5m.
The wizard then creates the sitrep system user, writes /etc/sitrep/manager/config.yaml, and installs and starts sitrep-manager.service. The manager generates its CA and server certificate on its own first real startup, running as the sitrep user — the wizard itself never touches key material.
Running sitrep manager start again after setup doesn't re-open the wizard — it just prints the current config path and systemd status.
Worker enrollment
On the manager, create a one-time token for the new worker:
sudo sitrep manager token create
This prints four values the worker operator needs — the manager's enrollment address, the token itself, its expiry, and the manager's server certificate fingerprint:
Enrollment token created for manager "prod-manager". Give the worker operator all four of these values:
Manager address: 10.0.0.5:8444
Token: sitrep_3f9a...
Expires: 2026-09-03T15:32:10Z
Cert fingerprint: 8f2c1e...
The token is single-use and expires 15 minutes after creation by default — override with --ttl 1h or any other duration. On the worker machine:
sudo sitrep worker start
The wizard asks what to call this machine — the name the manager will report it under; checks for ping/gh, offering to install whichever is missing; asks for the manager address, token, and certificate fingerprint; performs the enrollment handshake; writes /etc/sitrep/worker/config.yaml plus the issued client certificate and the manager's CA certificate; and installs and starts sitrep-worker.service. The worker begins reporting immediately, at the interval the manager pushed during enrollment.
Under the hood: the worker generates its own keypair and a certificate signing request locally — the private key never leaves the worker. It presents that CSR plus the token over a TLS connection pinned to the fingerprint above; the manager validates the token, signs the CSR, and returns the resulting client certificate along with its own CA certificate and current config.
CLI reference
sitrep manager start
Runs the setup wizard on first launch (no config present yet), or reports current status if already configured. With --foreground, skips the wizard and runs the manager directly in the foreground — this is what the systemd unit's ExecStart actually invokes.
| Flag | Default | Meaning |
|---|---|---|
--data-dir | /etc/sitrep/manager | Directory holding the manager's CA, server cert, and config. |
--foreground | false | Run without the interactive wizard, even on first launch. |
sitrep manager token create
Creates a one-time enrollment token via the manager's local API and prints the four values a worker needs.
| Flag | Default | Meaning |
|---|---|---|
--data-dir | /etc/sitrep/manager | Directory holding the manager's config (used to find its local API address). |
--ttl | 15m | Token validity window; it must be used before this elapses. |
sitrep manager status
Prints the sitrep-manager.service systemd state (systemctl is-active under the hood). No flags.
sitrep worker start
Same shape as manager start, for the worker agent.
| Flag | Default | Meaning |
|---|---|---|
--data-dir | /etc/sitrep/worker | Directory holding the worker's client cert and config. |
--foreground | false | Run without the interactive wizard, even on first launch. |
sitrep worker status
Prints the sitrep-worker.service systemd state. No flags.
sitrep version
Prints the build version, commit, and build date (set via -ldflags at build time — see the Makefile). No flags.
Configuration reference
Both agents store their config as YAML. Field names match the YAML keys on disk.
Manager — /etc/sitrep/manager/config.yaml
| Field | Type | Default | Meaning |
|---|---|---|---|
name | string | — | Operator-chosen at setup; embedded in the CA and server certificate common names. |
listen_addr | string | 0.0.0.0:8443 | mTLS report listener bind address. |
enroll_addr | string | 0.0.0.0:8444 | Bootstrap enrollment listener bind address. |
advertise_host | string | 127.0.0.1 | Hostname/IP workers use to reach this manager; embedded as a certificate SAN. |
api_listen_addr | string | 127.0.0.1:8080 | Local JSON API bind address. Must be loopback. |
interval_seconds | int | 60 | Reporting interval pushed to workers. |
enabled_checks | []string | all five | Which checks workers should run — see report data reference for names. |
data_dir | string | /etc/sitrep/manager | Where the CA, server cert, and this file itself live. |
Worker — /etc/sitrep/worker/config.yaml
| Field | Type | Meaning |
|---|---|---|
name | string | Operator-chosen at setup; sent as Report.name on every check-in. |
worker_id | string | Assigned by the worker itself at enrollment (e.g. wkr_4a1f9c22); also the client certificate's common name. |
manager_addr | string | The manager's report address, host:port, from the enrollment response. |
interval_seconds | int | Cached copy of the manager's last-pushed interval — rewritten whenever a report acknowledgement carries a different value. |
enabled_checks | []string | Cached copy of the manager's last-pushed enabled-checks list. |
data_dir | string | Where the client cert, the manager's CA cert, and this file live. |
On-disk layout
| Path | Owner | Contents |
|---|---|---|
/etc/sitrep/manager/ca.{crt,key}.pem | sitrep | The manager's self-signed root CA. |
/etc/sitrep/manager/server.{crt,key}.pem | sitrep | The manager's server certificate (auto-renewed within 30 days of expiry). |
/etc/sitrep/worker/client.{crt,key}.pem | sitrep | This worker's client certificate. |
/etc/sitrep/worker/ca.crt.pem | sitrep | The manager's CA certificate, for verifying the manager on report connections. |
All data directories are created 0700; private keys are 0600; certificates are 0644.
Report data reference
Every report is a JSON object with a worker ID, the operator-chosen name for that machine, an epoch-seconds timestamp, and a map of check name → result:
{
"worker_id": "wkr_4a1f9c22",
"name": "web-01",
"timestamp": 1767450300,
"stats": {
"internet": { "ok": true, "value": { "...": "..." } },
"disk": { "ok": true, "value": { "...": "..." } }
}
}
Each entry in stats has an ok boolean; when false, it carries an error string instead of value — a check that failed to run (missing binary, timeout) is reported, not silently dropped. When ok is true, value's shape depends on the check:
| Check | value shape |
|---|---|
internet | { reachable_ms: number, packet_loss: boolean } |
disk | { path: string, total_bytes: number, used_bytes: number, used_percent: number } |
dram | { total_bytes: number, used_bytes: number, used_percent: number } |
cpu | { used_percent: number } |
gitauth | { logged_in: boolean, account?: string } |
Checks run concurrently with an independent timeout each, so one slow or hung check (typically internet, if the network is unreachable) can't delay or block the others in the same report cycle.
Local API
Plain HTTP+JSON, bound strictly to loopback — no authentication or client certificate needed, since reaching it already requires running on the same machine as the manager.
GET /v1/workers
List every known worker.
curl http://127.0.0.1:8080/v1/workers
[
{ "id": "wkr_4a1f9c22", "name": "web-01", "enrolled_at": 1767450000, "last_seen": 1767450300 }
]
GET /v1/workers/{id}
A single worker plus its most recent report (see report data reference for the shape). Returns 404 if the ID is unknown.
GET /v1/config
The manager's current live configuration.
curl http://127.0.0.1:8080/v1/config
{
"name": "prod-manager",
"listen_addr": "0.0.0.0:8443",
"api_listen_addr": "127.0.0.1:8080",
"interval_seconds": 60,
"enabled_checks": ["internet", "disk", "dram", "cpu", "gitauth"]
}
PATCH /v1/config
Update the reporting interval and/or enabled checks. Omitted fields are left unchanged. Persisted to config.yaml immediately and rolled out to every enrolled worker within one reporting interval — no manager restart, no per-worker action.
curl -X PATCH http://127.0.0.1:8080/v1/config \
-d '{"interval_seconds": 30, "enabled_checks": ["disk", "cpu"]}'
Returns the updated config. interval_seconds must be positive — a zero or negative value returns 400.
POST /v1/tokens
Create a one-time worker enrollment token — this is what sitrep manager token create calls. Useful directly if you're scripting worker provisioning.
curl -X POST http://127.0.0.1:8080/v1/tokens -d '{"ttl_seconds": 3600}'
{
"token": "sitrep_3f9a...",
"expires_at": 1767453600,
"enroll_addr": "10.0.0.5:8444",
"server_cert_fingerprint": "8f2c1e...",
"manager_name": "prod-manager"
}
ttl_seconds is optional and defaults to 15 minutes.
Managing the services
systemctl status sitrep-manager # or sitrep-worker
systemctl restart sitrep-manager
journalctl -u sitrep-manager -f # follow logs
sitrep manager status / sitrep worker status are thin wrappers around systemctl is-active for convenience — same information, one fewer thing to remember the unit name for.
To re-run setup from scratch on a machine: stop the service, remove its data directory (/etc/sitrep/manager or /etc/sitrep/worker), and run sitrep manager start / sitrep worker start again.
Troubleshooting
Worker enrollment fails with "invalid or expired enrollment token"
The token was already used, has expired (15 minutes by default), or the manager restarted after it was created — tokens are held in memory only. Generate a new one with sitrep manager token create and retry.
Worker enrollment fails with a TLS/certificate error
Double check the certificate fingerprint was copied exactly from token create's output. The worker pins that specific fingerprint for the one bootstrap connection; a mismatch — including a stale fingerprint from a manager that's since regenerated its server certificate — is rejected outright rather than silently trusted.
PATCH /v1/config returns 400
interval_seconds must be a positive integer. Everything else about the patch is otherwise applied field-by-field; omitted fields are left as-is.
systemd shows the service as failed
Check journalctl -u sitrep-manager (or sitrep-worker) for the actual error. The most common cause on a manager is one of its three ports (report, enroll, local API) already in use by something else — the wizard doesn't check for port conflicts ahead of time.
A specific check always reports an error
For internet/gitauth, confirm the underlying binary (ping/gh) is actually on PATH for the user the systemd service runs as (sitrep, not necessarily your own login shell's PATH). The worker wizard's dependency check runs once, at setup time — installing a dependency afterward doesn't require re-enrollment, just restart the worker service.
Design tradeoffs
Deliberate, not oversights — worth knowing before you rely on any of these:
Enrollment tokens are in-memory only
A manager restart invalidates any outstanding, unused token. Tokens that have already been consumed are unaffected — enrolled workers keep working. The fix for a stale token is just generating a new one.
No automated certificate rotation
The CA is valid 10 years. The manager's server certificate is valid 2 years and silently auto-renewed on startup within 30 days of expiry. Worker client certificates are valid 1 year with no auto-renewal — an expired one simply fails its next mTLS handshake, and the fix is re-enrolling that worker with a fresh token, not a background renewal protocol.
No persistent report history
The manager holds only each worker's latest report, in memory. This is a live-status view, not a trend store — restarting the manager clears it. If you need history, poll GET /v1/workers/{id} from your own tooling and store it yourself.
Development
make build # bin/sitrep for the host platform (dev/test convenience)
make linux # cross-compiled bin/sitrep-linux-{amd64,arm64} — what you ship
make test # go test ./...
make vet # go vet ./...
make fmt # gofmt -l . (lists any unformatted files)
go test ./... covers PKI/certificate issuance, the enrollment token store, all five stat checkers, the mTLS handshake itself (client-cert required vs. not, fingerprint pinning), the worker registry under -race, and the local API's handlers. Actually installing systemd units — useradd, apt/dnf/pacman — is Linux-only and can only be exercised on a real Linux host, VM, or systemd-enabled container, not in go test.
Manual end-to-end check
sudo sitrep manager start→ wizard →systemctl status sitrep-managershould beactive.sudo sitrep manager token create→ copy the four printed values.- On the same or another machine,
sudo sitrep worker start→ wizard →systemctl status sitrep-workershould beactive. - After one interval:
curl http://127.0.0.1:8080/v1/workerson the manager should show the worker with a freshlast_report.