Notes on software, projects, and education.
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
| 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.
disk-inodes, kubernetes-pods, systemd-failed, time-sync, and cert-expirydocker-status now flags containers that fail their HEALTHCHECK or keep restartingkubernetes-status now checks node readiness and node pressure, for the current or a chosen kubeconfig context (--kube-context)misconfig now also checks kubeconfig and Docker socket permissions, world-writable PATH directories, Docker TCP endpoints without TLS verification, and SSH servers that permit root login or password authenticationmisconfig finding starts with a stable rule ID, such as sshd-root-login: ..., so scripts can match on it--disk-path, --service, or --cert to check several targets in one run; each target gets its own resulttarget, value, and unit fields where they apply--format selects text, json, nagios, prometheus, or junit--output <file> replaces the report file atomically, so readers never see a partial report--fail-on fail lets a run whose worst result is WARN exit 0salus jobs diff shows which results changed between two runs; with --exit-code it exits 1 only when something changedsalus jobs stats counts PASS, WARN, and FAIL results per check and flags checks that keep flappingsalus jobs prune --older-than 30d, or salus check run --retain 30d, keeps the database small--check-timeout and --run-timeout stop a hung check, such as disk-space on an unresponsive NFS mount, and report it as FAIL instead of blocking the runSIGINT (Ctrl-C) or SIGTERM stops a run cleanly with exit code 3linux/amd64 and linux/arm64) image at ghcr.io/jabbott-iii/salus, tagged 1.1.0, 1.1, and latest10001)# 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
| 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.
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.
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 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
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
Some of these changes can raise the exit code on hosts that passed before:
salus check run now also runs disk-inodes, kubernetes-pods, systemd-failed, and time-sync. Without systemd (containers, macOS, Windows) or kubectl, they report WARN; choose checks with --only, or pass --fail-on fail to let WARN pass.docker-status and kubernetes-status are stricter: unhealthy or restarting containers, NotReady nodes, and node pressure now report WARN, and a cluster with no Ready node reports FAIL.misconfig messages now start with a rule ID, so scripts that match them must allow for the prefix. Hosts with a stock OpenSSH configuration usually warn about sshd-password-auth, because OpenSSH accepts passwords unless PasswordAuthentication no is set.--disk-path or --service now checks every value; v1.0.2 used only the last one.The full list is in the README under Upgrading from 1.0.2.
go vet, golangci-lint, and the full test suite on Linux, macOS, and Windows, and smoke-tests the built binarychecksums.txt and build provenance attestationsFound a bug or have an idea for a check? Please open an issue. Contributions are welcome, see CONTRIBUTING.md.
Thank you for following along!