This directory holds the deployment inputs for a self-hosted install: the Quadlet unit and volume, the backup timer, the Cloudflare ingress snippet, and the release, deploy, import, backup, and restore scripts. Start with docs/deployment.md for the full procedure and docs/configuration.md for every environment variable.
The three dash-control routes let an external controller close the generation maintenance gate, watch admitted work drain, and open the gate again:
| Method and path | Meaning |
|---|---|
POST /internal/dash-control/quiesce |
Stop admitting new generation work. Idempotent. |
GET /internal/dash-control/quiesce |
Report locked, idle, active leases, and running regeneration jobs. |
POST /internal/dash-control/resume |
Open the gate again, or refuse with 409 while work is still active. |
Every request must carry the X-Dash-Control-Token header. The application compares it in constant
time against the file named by UNFLINCHER_DASH_CONTROL_TOKEN_FILE. If that file is missing,
unreadable, or empty, every request is denied. These paths are the only ones exempt from the
Cloudflare Access check, and that exemption is safe only because the token check is mandatory on
all of them.
The token value must never be committed, never be passed as an environment variable, and never be written into the unit file. Only its file path appears in quadlet/unflincher.container.
Generate the value once, give the same value to the controller's own secret store, then discard the shell variable:
TOKEN="$(openssl rand -base64 48)"
printf '%s' "$TOKEN" | podman secret create unflincher-dash-control-token -
# store the same value in the controller's secret store here
unset TOKENThe unit mounts it read-only at /run/secrets/dash-control-token. Install or reload the unit
afterwards:
systemctl --user daemon-reload
systemctl --user restart unflincher.servicePodman secrets are immutable, so rotation replaces the secret and restarts the container. The application reads the file on every request, but the mounted copy itself only changes when the container starts, so the restart is required.
TOKEN="$(openssl rand -base64 48)"
printf '%s' "$TOKEN" | podman secret create --replace unflincher-dash-control-token -
# update the controller's secret store with the same value here
unset TOKEN
systemctl --user restart unflincher.serviceOn a Podman version without --replace, run podman secret rm unflincher-dash-control-token
first. Update the controller before the restart if a short mismatch window is acceptable, or pause
controller polling during rotation. A mismatch only causes 403 responses on these three paths;
it never affects normal application traffic and never changes the maintenance gate.
Read the token from a shell variable through curl --config so it never appears in the process
arguments:
printf 'header = "X-Dash-Control-Token: %s"\n' "$TOKEN" \
| curl --config - -s -o /dev/null -w '%{http_code}\n' \
http://127.0.0.1:8096/internal/dash-control/quiesce200 means the token matches. 403 means the header is missing or wrong, or the secret is not
mounted. Check that the container sees the file with
podman exec unflincher test -r /run/secrets/dash-control-token.
ops/dashboard/workload.declaration.json publishes the logical contract this project owns: its roles, its logical HTTP port, its health check, and the three quiesce paths above. It deliberately contains no unit names, host ports, commands, file paths, or secret names. Those belong to the host side of the contract, which owns the binding.