holdon
Wait for anything. Know why if it doesn't.
A next-gen "wait for service ready" CLI in Rust. One static binary, parallel by default, protocol-aware, with diagnostic failures that actually tell you what broke.
$ holdon postgres://db:5432 redis://cache:6379 https://api/health
✓ ready postgres://db:5432 · 27ms
✓ ready redis://cache:6379 · 14ms
✗ failed https://api/health · 5.0s · ▁▂▄▆█ · 510ms
├ dns ✓ 2ms
├ tcp ✓ 3ms
└ http ✗ status 503
hint: service may still be initializing
→ 2/3 ready · 5.1s
Install
| Method | Command |
|---|---|
| Cargo | cargo install holdon |
| Prebuilt binaries | GitHub Releases |
| Docker | docker pull ghcr.io/imjustprism/holdon |
| Install script | curl -fsSL https://raw.githubusercontent.com/imjustprism/holdon/main/install.sh | sh |
Minimum supported Rust version: 1.85.
Shell completions and man page
|
Prebuilt copies of every shell's completion script plus the man page are attached to each GitHub Release as holdon-completions-and-manpage.tar.gz.
Config file
Pass --config holdon.toml, or drop holdon.toml / .holdon.toml next to where you run holdon and it's auto-detected.
= "200ms"
= "60s"
= 2
= [
"tcp://db:5432",
"https://api.local/health",
]
Explicit CLI flags always win over the config file. See examples/holdon.toml.
Quickstart
Protocols
| Scheme | What it checks |
|---|---|
tcp://, :port, host:port |
DNS resolve, TCP connect |
http://, https:// |
TCP, TLS, HTTP request (-H, --method, --expect-body, --expect-body-regex, --expect-json, --no-follow-redirects, --ca-cert, --tls-min) |
dns:// |
Hostname resolves |
file:///path |
Path exists (?mode=absent inverse) |
postgres://, postgresql:// |
Connect + SELECT 1 (TLS by default) |
mysql://, mariadb:// |
Connect + SELECT 1 (TLS by default) |
redis://, rediss:// |
Connect + PING (rediss:// for TLS) |
grpc://, grpcs:// |
grpc.health.v1.Health/Check unary (optional /Service path) |
log:///path?match=... |
Wait for a substring or regex to appear in a local log file (last 1 MiB) |
exec://program?arg=... |
External command, ready iff exit 0 |
Feature flags
Defaults (http + json-output) cover most CI use cases. Database probes
are opt-in to keep the default binary small.
| Feature | Adds |
|---|---|
http |
HTTP / HTTPS probes (rustls) |
postgres |
Postgres probe via tokio-postgres + rustls |
mysql |
MySQL / MariaDB probe via mysql_async + rustls |
grpc |
gRPC Health/Check probe via tonic + rustls |
redis |
Redis probe via redis crate + rustls |
json-output |
--output json line-delimited events |
all-databases |
postgres + mysql + redis |
full |
Everything above |
Exit codes
| Code | Meaning |
|---|---|
0 |
All targets ready |
2 |
CLI misuse or parse error |
124 |
Overall timeout elapsed (GNU timeout convention) |
126 |
Exec'd child not executable |
127 |
Exec'd child binary not found |
130 |
Interrupted by SIGINT (Ctrl-C) |
143 |
Interrupted by SIGTERM |
Output modes
- Plain (default). Live spinner, colored status, sparklines on stderr. Auto-disabled in non-TTY environments.
- JSON (
--output json). Line-delimited events on stdout, stable schema documented indocs/json-schema.md. - Quiet (
-q). Only the exit code.
Library
use Duration;
use ;
use RunnerConfig;
async
See the examples directory and the API docs.
Security notes
- TLS is rustls only. No OpenSSL anywhere in the tree.
- Postgres and Redis use rustls with webpki roots out of the box.
Postgres opportunistically upgrades to TLS unless the URL passes
?sslmode=disable. - HTTP redirects are followed up to 5 hops and refuse
https → httpdowngrades. --insecure(HTTP only) disables TLS verification for HTTP probes and prints a stderr warning. Do not use in production.exec://runs whatever command you point it at. Treat target strings as code.- Symlinks are not followed by
file://probes. - URL passwords are redacted in
Display,Debug, and error chains.
See SECURITY.md for the full threat model and disclosure instructions.
License
Dual MIT or Apache-2.0, at your option.