All releases

v0.4.4

Signedpublished August 18, 2026

What changed

Added

  • The admin username and password can be chosen at install time. A fresh install at a terminal asks for both, offering to generate a password (the default) or to take one you type twice, unechoed. Nothing changes for unattended runs — --yes, no terminal, cloud-init — which still create admin with a generated password, printed in the closing banner. DEVGRAIL_ADMIN_USERNAME and DEVGRAIL_ADMIN_PASSWORD set either up front; both are now honoured from the environment, which the password previously was not.

    A generated password is still printed at the end, because nothing else ever shows it. One you typed is not echoed back, and neither is the value an upgrade reads out of deploy/.env — an upgrade asks nothing at all, since the server applies these only when it creates the account.

    A typed password is restricted to letters, digits and ! % * + , - . : ; = ? @ ^ _ ~, 12–72 characters. That is a file-format constraint, not a security opinion: deploy/.env is read verbatim by docker compose, by the installer itself and by the test harnesses, none of which unquote. Passwords set later through the app carry no such restriction.

  • Change your password in the dashboard. Settings → Password takes the current password and a new one (POST /auth/password). It re-authenticates first, so a hijacked session cannot lock the owner out, then revokes every session for that user — including the caller's, which is handed a replacement token so the page it was done from keeps working. That makes it a way to evict a session you no longer control, not just a way to rotate a secret.

  • sudo devgrail passwd resets a password from the host when nobody can log in. It prompts twice and pipes the result into the server binary's new -set-password mode on stdin — never argv or the environment, both readable via ps and docker inspect — then revokes that user's sessions. It runs against the live stack: SQLite is in WAL mode and sessions are re-checked per request, so no restart is needed. DEVGRAIL_FORCE_ADMIN_PASSWORD=1 still works as the fallback for a host that cannot run the one-shot container.

Changed

  • The installer now offers to fix the two Docker daemon settings it used to only complain about. install.sh printed a wall of text about userns-remap and default-address-pools, told the operator to edit /etc/docker/daemon.json by hand, and carried on — advice arriving at the one moment someone is already root at a terminal, and the one moment both changes are still cheap. It now explains each in plain terms, says what applying it costs, and offers to do it: the key is merged into daemon.json with jq (existing keys preserved, the prior file kept as .devgrail-bak-<timestamp>), Docker is restarted, and if the daemon does not come back the change is rolled back and the daemon restarted again — a host left without Docker is worse than either setting is worth. A daemon.json that does not parse is never touched.

    Nothing is applied without an explicit yes, and --yes is deliberately not that yes: restarting dockerd stops every container on the host. Unattended runs opt in on purpose with --userns-remap=yes|no / --address-pools=yes|no or DEVGRAIL_USERNS_REMAP / DEVGRAIL_ADDRESS_POOLS; no also stops the question being re-asked on later upgrades. --dry-run reports what it would offer and changes nothing.

    Two guards decide when the offer is even made. userns-remap is offered only on a host with no Docker images, containers or volumes: it moves the daemon to a per-UID data root, so anything created beforehand — the DevGrail data volume included — would become invisible, and on a host with state the installer explains the backed-up manual path instead of prompting for something it cannot make safe. The address-pool range is checked against this host's own routes before being offered (Docker's own docker0/br-* routes excluded, since they already sit in 172.16/12), falling back to 10.201.0.0/16 and then to declining outright — handing Docker a pool that shadows an operator's VPN or LAN route is worse than the ceiling it would lift. Pools an operator configured deliberately are reported, never rewritten.

Container images

registry build 12
amd64747 MB
sha256:518af9cad5021ccb53de72680336a3aa383746c38300de45f704a4b3de3a7449
Download
arm64616 MB
sha256:4fcf2de552d6422255e10054f1a7516673de330f8afb0ed68616d964ccde2718
Download
registry build 12
amd6445 MB
sha256:9cf26e5c5b61c176a40ded13ebe5929e49bb9b6df18756edc28abb5019886efe
Download
arm6444 MB
sha256:bfaeb8bcffc48c4488ac6c7ef48b08d6965889fa6d19243179b0f8cba4ef2c59
Download

Installer files

docker-compose.yml11 KBsha256:61ecf9e19a3c6be92a1161c645fa31e0cb16795419b1b2ff84c3fafeeb21f57a
install.sh118 KBsha256:d95f439701a773f6694477fe3d9463ab81484b55aacb391d7094ade7a2f8dca7
traefik-dynamic-http.yml5 KBsha256:e344c8efd13c7d84996e2ebfbc75dcee2d995d8538c8a245c89c0e44fad0089b
traefik-dynamic.yml6 KBsha256:d18fa561b8eb7d53b015284e0246b2d05953bf281b65269c1fae3d6883530db6
uninstall.sh10 KBsha256:d8f09fe906e13f57bc308f6f2340972a62b010c367174ec79597c86a84c706be

Signature

ecdsa-p256-sha256key f1f1eaf642454d65

One signature covers every artifact above. It is made off this host, and neither this site nor the registry behind it can check it — that is the point, and it is why verifying it catches things a checksum from the same server cannot. How to verify it.

Verify devgrail-container (amd64)
curl -fsSL https://web-dev.trixibot.com/api/registry/releases/v0.4.4 -o release.json

# -j, not -r: -r appends a newline the signer never saw, and the check fails.
jq -j '.signature.payload'   release.json > payload
jq -r '.signature.signature' release.json | base64 -d > payload.sig

openssl dgst -sha256 -verify devgrail-release.pem -signature payload.sig payload
# -> Verified OK

# The payload must name this release, so an older signed one cannot be replayed.
head -2 payload

# ...and it must name this exact file. -qxF: whole line, literal.
grep -qxF 'image devgrail-container 12 amd64 518af9cad5021ccb53de72680336a3aa383746c38300de45f704a4b3de3a7449' payload && echo "digest is covered by the signature"
The signed payload — shown for reading, not for verifying

Copy this from the manifest with jq -j, never from this page: the signature covers exact bytes, and rendering here drops the trailing newline the signer included.

devgrail-release-signature-v1
release v0.4.4
asset docker-compose.yml 11 61ecf9e19a3c6be92a1161c645fa31e0cb16795419b1b2ff84c3fafeeb21f57a
asset install.sh 11 d95f439701a773f6694477fe3d9463ab81484b55aacb391d7094ade7a2f8dca7
asset traefik-dynamic-http.yml 10 e344c8efd13c7d84996e2ebfbc75dcee2d995d8538c8a245c89c0e44fad0089b
asset traefik-dynamic.yml 11 d18fa561b8eb7d53b015284e0246b2d05953bf281b65269c1fae3d6883530db6
asset uninstall.sh 11 d8f09fe906e13f57bc308f6f2340972a62b010c367174ec79597c86a84c706be
image devgrail-container 12 amd64 518af9cad5021ccb53de72680336a3aa383746c38300de45f704a4b3de3a7449
image devgrail-container 12 arm64 4fcf2de552d6422255e10054f1a7516673de330f8afb0ed68616d964ccde2718
image devgrail-server 12 amd64 9cf26e5c5b61c176a40ded13ebe5929e49bb9b6df18756edc28abb5019886efe
image devgrail-server 12 arm64 bfaeb8bcffc48c4488ac6c7ef48b08d6965889fa6d19243179b0f8cba4ef2c59