Salus // Release

Salus v1.1.0 Release

Salus v1.1.0 is out: a Go CLI that checks disk, memory, CPU, Docker, Kubernetes, services, certificates, and misconfigurations, then reports PASS/WARN/FAIL.

By 7 min read

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, and cert-expiry
    • docker-status now flags containers that fail their HEALTHCHECK or keep restarting
    • kubernetes-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 authentication
    • Every misconfig finding starts with a stable rule ID, such as sshd-root-login: ..., so scripts can match on it
  • Per-target results
    • Repeat --disk-path, --service, or --cert to check several targets in one run; each target gets its own result
    • JSON results carry target, value, and unit fields where they apply
  • Monitoring and CI output
    • --format selects text, json, nagios, prometheus, or junit
    • 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 fail lets a run whose worst result is WARN exit 0
  • Run history
    • salus jobs diff shows which results changed between two runs; with --exit-code it exits 1 only when something changed
    • salus jobs stats counts PASS, WARN, and FAIL results per check and flags checks that keep flapping
    • salus jobs prune --older-than 30d, or salus 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-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 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) or SIGTERM stops a run cleanly with exit code 3
  • Container image
    • Multi-arch (linux/amd64 and linux/arm64) image at ghcr.io/jabbott-iii/salus, tagged 1.1.0, 1.1, and latest
    • Runs as an unprivileged user (UID 10001)

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 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.
  • Repeating --disk-path or --service now 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.txt and 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!