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.

worker
enroll once, over a pinned connection
:8444 no client cert required
report on interval, mutual TLS
:8443 RequireAndVerifyClientCert
query results
127.0.0.1:8080 local json api, loopback only

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 sitrep system user, writes to /etc/sitrep and /etc/systemd/system).
  • ping on any worker with the internet check enabled.
  • gh (the GitHub CLI) on any worker with the gitauth check 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+):

build
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:

deploy
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:

manager
sudo sitrep manager start

With no existing config, this launches a one-time interactive wizard:

  1. 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 create output).
  2. Report listen address — mTLS, default 0.0.0.0:8443. Where workers send reports.
  3. Enrollment listen address — mTLS bootstrap, default 0.0.0.0:8444. Where workers enroll.
  4. 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.
  5. Local query API address — default 127.0.0.1:8080. Must resolve to a loopback address; the manager refuses to start otherwise.
  6. Enabled checks — all five by default, individually toggleable.
  7. 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:

manager
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:

worker
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.

FlagDefaultMeaning
--data-dir/etc/sitrep/managerDirectory holding the manager's CA, server cert, and config.
--foregroundfalseRun 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.

FlagDefaultMeaning
--data-dir/etc/sitrep/managerDirectory holding the manager's config (used to find its local API address).
--ttl15mToken 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.

FlagDefaultMeaning
--data-dir/etc/sitrep/workerDirectory holding the worker's client cert and config.
--foregroundfalseRun 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

FieldTypeDefaultMeaning
namestring—Operator-chosen at setup; embedded in the CA and server certificate common names.
listen_addrstring0.0.0.0:8443mTLS report listener bind address.
enroll_addrstring0.0.0.0:8444Bootstrap enrollment listener bind address.
advertise_hoststring127.0.0.1Hostname/IP workers use to reach this manager; embedded as a certificate SAN.
api_listen_addrstring127.0.0.1:8080Local JSON API bind address. Must be loopback.
interval_secondsint60Reporting interval pushed to workers.
enabled_checks[]stringall fiveWhich checks workers should run — see report data reference for names.
data_dirstring/etc/sitrep/managerWhere the CA, server cert, and this file itself live.

Worker — /etc/sitrep/worker/config.yaml

FieldTypeMeaning
namestringOperator-chosen at setup; sent as Report.name on every check-in.
worker_idstringAssigned by the worker itself at enrollment (e.g. wkr_4a1f9c22); also the client certificate's common name.
manager_addrstringThe manager's report address, host:port, from the enrollment response.
interval_secondsintCached copy of the manager's last-pushed interval — rewritten whenever a report acknowledgement carries a different value.
enabled_checks[]stringCached copy of the manager's last-pushed enabled-checks list.
data_dirstringWhere the client cert, the manager's CA cert, and this file live.

On-disk layout

PathOwnerContents
/etc/sitrep/manager/ca.{crt,key}.pemsitrepThe manager's self-signed root CA.
/etc/sitrep/manager/server.{crt,key}.pemsitrepThe manager's server certificate (auto-renewed within 30 days of expiry).
/etc/sitrep/worker/client.{crt,key}.pemsitrepThis worker's client certificate.
/etc/sitrep/worker/ca.crt.pemsitrepThe 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:

Checkvalue 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

systemd
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
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

  1. sudo sitrep manager start → wizard → systemctl status sitrep-manager should be active.
  2. sudo sitrep manager token create → copy the four printed values.
  3. On the same or another machine, sudo sitrep worker start → wizard → systemctl status sitrep-worker should be active.
  4. After one interval: curl http://127.0.0.1:8080/v1/workers on the manager should show the worker with a fresh last_report.