-
Click "Use this template" on GitHub (or fork / clone) this repository
-
Find-and-replace globally (case-sensitive):
From To appcli-template,appcliyour app name (lowercase) appclidyour daemon binary name appclictlyour control binary name APPCLIyour app name (UPPERCASE) ventaquil/appcli-templateyour 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 | env vars > CLI > 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 | Won't start if daemon is already running |
| 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 /var/run/appclid.pid) # reload config
kill -TERM $(cat /var/run/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: /var/run/appclid.pid
socket-file: /var/run/appclid.sockAll fields are optional. Every value can be overridden via env vars or CLI flags.
See docs/CONFIG.md.
| Document | Description |
|---|---|
| docs/ARCHITECTURE.md | Module layout, data flow, extension points |
| docs/CONFIG.md | YAML config fields, accepted values, defaults, live reload |
| docs/PROTOCOL.md | IPC wire-format spec, JSON examples, Rust type definitions |
| docs/DAEMON.md | appclid flags, startup sequence, signal handling |
| docs/CONTROL.md | appclictl commands, flags, and examples |
| docs/DOCKER.md | Image variants, build and run instructions |
| docs/MAKEFILE.md | All make targets for build, lint, test, and Docker |
| docs/ACTIONS.md | CI/CD workflows, jobs, triggers, and permissions |
| config/config.yaml | Annotated example configuration file |
- 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 — env vars beat CLI, CLI beats 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