Salus v1.1.0 — Environment Health Checks from the Command Line
I am excited to announce Salus v1.1.0, the first feature release of my environment health checker!
Salus is a Go CLI that checks a host’s disk space and inodes, memory, CPU load, Docker and Kubernetes status, service uptime, failed systemd units, time synchronization, certificate expiry, and common misconfigurations, then prints a concise PASS / WARN / FAIL summary. It is built for operators, cron jobs, CI pipelines, and monitoring systems: the exit code reflects the worst result, and every run is recorded in a local SQLite database so you can review and compare runs later.
v1.0.0 shipped on September 27. v1.1.0 adds five new checks, monitoring-friendly output formats, run history tools, macOS and Windows support for the resource checks, and a multi-arch container image on GitHub Container Registry.
$ salus check run --only disk-space,memory,cpu-load,misconfig
Environment Health Check
[PASS] disk-space /: 46.1% used (53.9% free)
[PASS] memory memory 39.1% used, swap 8.9% used
[PASS] cpu-load load average 1.80 across 16 CPU(s) (11% per-core)
[PASS] misconfig no common misconfigurations detected
What Salus checks
| Check | What it verifies |
|---|---|
disk-space |
Free disk space on one or more mount points |
disk-inodes (new) |
Free inodes on one or more mount points |
memory |
Memory and swap usage |
cpu-load |
Load average relative to available CPUs (busy percentage on Windows) |
docker-status |
The Docker daemon is reachable and no container is unhealthy or restarting |
kubernetes-status |
The cluster is reachable and nodes are Ready without Memory, Disk, or PID pressure |
kubernetes-pods (new) |
Pods in a namespace are Ready and not crash-looping or failed |
service-uptime |
One or more systemd services are active (or host uptime when no service is given) |
systemd-failed (new) |
No systemd unit is in the failed state |
time-sync (new) |
The system clock is synchronized |
cert-expiry (new) |
PEM or DER certificate files are valid and not close to expiry |
misconfig |
Common environment misconfigurations |
WARN and FAIL thresholds are set per run with --disk-warn/--disk-fail, --inode-warn/--inode-fail, --mem-warn/--mem-fail, and --load-warn/--load-fail. There is no configuration file to manage.
What’s new in v1.1.0
- New and stronger checks
- Five new checks:
disk-inodes,kubernetes-pods,systemd-failed,time-sync, andcert-expiry docker-statusnow flags containers that fail theirHEALTHCHECKor keep restartingkubernetes-statusnow checks node readiness and node pressure, for the current or a chosen kubeconfig context (--kube-context)misconfignow also checks kubeconfig and Docker socket permissions, world-writablePATHdirectories, Docker TCP endpoints without TLS verification, and SSH servers that permit root login or password authentication- Every
misconfigfinding starts with a stable rule ID, such assshd-root-login: ..., so scripts can match on it
- Five new checks:
- Per-target results
- Repeat
--disk-path,--service, or--certto check several targets in one run; each target gets its own result - JSON results carry
target,value, andunitfields where they apply
- Repeat
- Monitoring and CI output
--formatselectstext,json,nagios,prometheus, orjunit- Nagios output also works with Icinga and Zabbix
- Prometheus output is made for node_exporter’s textfile collector, including a last-run timestamp for stale-result alerts
- JUnit XML publishes each result as a CI test case
--output <file>replaces the report file atomically, so readers never see a partial report--fail-on faillets a run whose worst result is WARN exit0
- Run history
salus jobs diffshows which results changed between two runs; with--exit-codeit exits1only when something changedsalus jobs statscounts PASS, WARN, and FAIL results per check and flags checks that keep flappingsalus jobs prune --older-than 30d, orsalus check run --retain 30d, keeps the database small
- Cross-platform resource checks
- Disk space, inodes, memory, CPU load, and host uptime now measure the host on macOS and Windows, not just Linux
- Built for unattended runs
--check-timeoutand--run-timeoutstop a hung check, such asdisk-spaceon an unresponsive NFS mount, and report it as FAIL instead of blocking the run- When an external command times out, Salus also stops the processes it started (on Linux and macOS)
- Several Salus processes, such as two cron entries that start in the same minute, can share one database; writers wait for each other instead of failing
- The report is written before the run is saved, so a database error never loses the report
SIGINT(Ctrl-C) orSIGTERMstops a run cleanly with exit code3
- Container image
- Multi-arch (
linux/amd64andlinux/arm64) image atghcr.io/jabbott-iii/salus, tagged1.1.0,1.1, andlatest - Runs as an unprivileged user (UID
10001)
- Multi-arch (
Example uses
# Scheduled host check from cron: alert on a non-zero exit code
salus check run --quiet
# CI or deployment gate: exit 2 (FAIL) is fatal, exit 1 (WARN) is a warning
salus check run --only disk-space,memory --disk-fail 95 --json
# Confirm a service is up after a deployment
salus check run --only service-uptime --service nginx
# Feed Prometheus through node_exporter's textfile collector, keeping 30 days of history
salus check run --format prometheus --output /var/lib/node_exporter/textfile/salus.prom --retain 30d
# Use Salus as a Nagios, Icinga, or Zabbix plugin
salus check run --format nagios --no-save
# Publish results as JUnit test cases in CI
salus check run --format junit --output salus-junit.xml --fail-on fail
# Warn two weeks before a certificate expires
salus check run --only cert-expiry --cert /etc/ssl/certs/site.pem --cert-warn-days 14
# After each scheduled run, exit 1 only if a result changed
salus jobs diff --exit-code
Exit codes
| Code | Meaning |
|---|---|
0 |
Every check passed, or the command succeeded |
1 |
At least one check reported WARN, and none reported FAIL |
2 |
At least one check reported FAIL |
3 |
Salus could not complete the command |
These match the Nagios plugin convention, so Salus fits into existing monitoring without a wrapper script. Errors go to stderr, so --json output on stdout stays clean.
Installation
Download the archive for your platform and checksums.txt from the v1.1.0 release:
| Platform | Archive |
|---|---|
| Linux (x86-64) | salus_linux_amd64.tar.gz |
| Linux (ARM64) | salus_linux_arm64.tar.gz |
| macOS (Apple Silicon) | salus_darwin_arm64.tar.gz |
| macOS (Intel) | salus_darwin_amd64.tar.gz |
| Windows (x86-64) | salus_windows_amd64.zip |
| Windows (ARM64) | salus_windows_arm64.zip |
On Linux (use linux_arm64 in place of linux_amd64 on ARM64):
grep salus_linux_amd64.tar.gz checksums.txt | sha256sum --check
tar -xzf salus_linux_amd64.tar.gz
sudo mv salus_linux_amd64 /usr/local/bin/salus
salus --version
The Linux binaries are statically linked, so they do not depend on the system’s C library. The macOS and Windows steps are in the README.
Container image
docker pull ghcr.io/jabbott-iii/salus:1.1.0
docker run -it --rm \
-v salus-data:/app/data \
-e SALUS_DB_PATH=/app/data/salus.db \
ghcr.io/jabbott-iii/salus:1.1.0 check run
The image does not include docker, kubectl, systemctl, or timedatectl, so the Docker, Kubernetes, and systemd checks report WARN inside the container. Run the binary on the host for those checks, and do not mount the Docker socket into the container: it gives the container root-equivalent control of the host.
Building from source
Building needs Go 1.26.8 or newer and a C toolchain, because the SQLite driver uses CGO:
git clone https://github.com/jabbott-iii/Salus.git
cd Salus
make build
./salus --version
Verifying build provenance
Every release archive and the container image carry a signed SLSA build provenance attestation from the release workflow. Verify them with GitHub CLI 2.97 or newer (after gh auth login):
gh attestation verify salus_linux_amd64.tar.gz --repo jabbott-iii/Salus \
--signer-workflow jabbott-iii/Salus/.github/workflows/cd.yml \
--source-ref refs/tags/v1.1.0
gh attestation verify oci://ghcr.io/jabbott-iii/salus:1.1.0 --repo jabbott-iii/Salus \
--signer-workflow jabbott-iii/Salus/.github/workflows/cd.yml \
--source-ref refs/tags/v1.1.0
Upgrading from v1.0.2
Some of these changes can raise the exit code on hosts that passed before:
- A plain
salus check runnow also runsdisk-inodes,kubernetes-pods,systemd-failed, andtime-sync. Without systemd (containers, macOS, Windows) orkubectl, they report WARN; choose checks with--only, or pass--fail-on failto let WARN pass. docker-statusandkubernetes-statusare stricter: unhealthy or restarting containers, NotReady nodes, and node pressure now report WARN, and a cluster with no Ready node reports FAIL.misconfigmessages now start with a rule ID, so scripts that match them must allow for the prefix. Hosts with a stock OpenSSH configuration usually warn aboutsshd-password-auth, because OpenSSH accepts passwords unlessPasswordAuthentication nois set.- Repeating
--disk-pathor--servicenow checks every value; v1.0.2 used only the last one. - The first command after the upgrade updates the database schema, so it needs write access to the database file once.
The full list is in the README under Upgrading from 1.0.2.
Security and quality
- CI runs
go vet, golangci-lint, and the full test suite on Linux, macOS, and Windows, and smoke-tests the built binary - CodeQL, gosec, and govulncheck run on every push and pull request, and weekly
- Third-party GitHub Actions are pinned by commit SHA and container base images by digest, and Dependabot keeps them current
- Every release ships with
checksums.txtand build provenance attestations - Vulnerabilities can be reported privately through GitHub’s private vulnerability reporting, as described in SECURITY.md; security fixes ship in the latest release
Get involved
Found a bug or have an idea for a check? Please open an issue. Contributions are welcome, see CONTRIBUTING.md.
Thank you for following along!