Skip to content

Latest commit

 

History

History
88 lines (67 loc) · 3.84 KB

File metadata and controls

88 lines (67 loc) · 3.84 KB

Deployment assets

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.

Dashboard quiesce control token

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.

Create the secret

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 TOKEN

The 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.service

Rotate the secret

Podman 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.service

On 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.

Verify without leaking the token

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/quiesce

200 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.

Workload declaration

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.