- Click "Use this template" on GitHub (or fork / clone) this repository
- Find-and-replace globally (case-sensitive):
| From | To |
|---|---|
appcli-template, appcli |
your app name (lowercase) |
appclid |
your daemon binary name |
appclictl |
your control binary name |
APPCLI |
your app name (UPPERCASE) |
ventaquil/appcli-template |
your repository URL |
The repository slug (
username/reponame) is also used as the Docker image name — the CD workflow derives it automatically fromgithub.repository.
- Update
Cargo.toml(version,description,repository,authors, etc.) - Add your domain-specific commands to src/protocol/command.rs
- Add your daemon logic to
dispatch_commandin src/daemon/mod.rs
Everything else — Unix socket IPC, config layering, signal handling, graceful shutdown, daemonization — is already done and battle-tested.
| Feature | Implementation |
|---|---|
| Unix socket IPC | Newline-delimited JSON over a Unix socket |
| Config layering | CLI > env vars > YAML > hardcoded defaults |
| Runtime log-level | Change log level without restarting the daemon |
| SIGINT / SIGTERM | Graceful shutdown, removes socket + PID file |
| SIGHUP | Live config reload from disk |
| Daemonize | -b / --background flag to detach from terminal |
| PID file | Written on start, removed on clean exit |
| Pre-flight check | Best-effort: skips start if a daemon already responds |
| Typed errors | Structured error handling throughout |
docker pull ghcr.io/ventaquil/appcli-template:latestRun the daemon:
docker run --rm -it --name appcli ghcr.io/ventaquil/appcli-template
# or with custom flags
docker run --rm -it --name appcli ghcr.io/ventaquil/appcli-template appclid --log-format prettyControl it from another terminal:
docker exec -it appcli appclictl ping
docker exec -it appcli appclictl config show
docker exec -it appcli appclictl shutdownSee docs/DOCKER.md for available image variants, build and run instructions.
makeDaemon:
bin/appclid --config /etc/appcli/config.yaml # foreground
bin/appclid -b --config /etc/appcli/config.yaml # backgroundControl:
bin/appclictl ping # is it alive?
bin/appclictl config show # dump active config
bin/appclictl config reload # hot-reload from config file
bin/appclictl config reload /new/path.yaml # reload from different file
bin/appclictl shutdown # graceful shutdownSignals:
kill -HUP $(cat /run/appclid/appclid.pid) # reload config
kill -TERM $(cat /run/appclid/appclid.pid) # graceful shutdownSee docs/DAEMON.md and docs/CONTROL.md.
An annotated example config lives at config/config.yaml:
# /etc/appcli/config.yaml
log:
format: compact
level: info
pid-file: /run/appclid/appclid.pid
socket-file: /run/appclid/appclid.sockAll fields are optional. Every value can be overridden via env vars or CLI flags.
See docs/CONFIG.md.
Full documentation lives in docs/ — see docs/README.md for a one-line description of every document. An annotated example configuration is at config/config.yaml.
- No panics — every error is handled, logged, and exits with a meaningful exit code
- Single source of defaults — defaults live in exactly one place; CLI flags and env vars are always explicit overrides
- Type-safe config — fields are optional during parsing and fully resolved at runtime
- Correct priority chain — CLI beats env vars, env vars beat YAML, YAML beats defaults; no layer can accidentally win over a higher one
- Safe IPC — Unix socket with newline-delimited JSON; no raw bytes, no length prefixes
- Reactive signals — SIGINT and SIGTERM shut down cleanly; SIGHUP hot-reloads config without a restart (
pid-fileandsocket-fileare locked at startup and require a restart to change) - Clean shutdown — socket and PID file are always removed on exit, even after a signal
- Structured logging — format and level are runtime-configurable without restarting
MIT