Repository navigation
fix(worker): the Valkey pi-dispatch starts gets a per-deployment password (#468) - #475
Merged
Merged
Conversation
edgehero
added a commit
that referenced
this pull request
Sep 28, 2026
…r the wizard's receiver, the CLI's VALKEY_URL from .env (#468) PR #475's review. The password could be printed. ioredis hangs the failed command on every reply error, and for a failed AUTH its args are the password; after a rotation, a Queue with no `error` listener let BullMQ `console.error` the whole object (measured against Redis with CONFIG SET requirepass and CLIENT KILL). connection.mjs now keeps only the command's name on every error any client of the process emits (one prototype hook, BullMQ's clients included, before any listener or fallback sees it), and every Queue and Worker the project builds carries a listener that writes the message alone. A test rotates a stand-in Valkey's password under a running Queue in a child process and checks neither stream carries either password; the gate's own reproduction against a real Redis now prints two message-only lines and no password. The wizard's compose answer, after the copy moved to <dir>/deploy/, ran as compose project "deploy" for every folder. It now passes `-p <folder name>`, normalised as compose normalises a directory name (measured with compose 2.31, including "My Deploy.v2_x" -> "mydeployv2_x"), which is the project, and volume, the old layout's copy ran as; the legacy warning names both. Its title and its skip hint are the argv that runs. And the wizard's receiver now uses the worker's Valkey: `up`, earlier in the wizard, runs pi-dispatch-valkey on 6379, where compose's own valkey could not bind (or would be a second queue the receiver fills). When up's container exists, a create-only deploy/docker-compose.valkey.yml gives compose's valkey up's volume, pi-dispatch-valkey-data, and the consented run stops and removes up's container before compose starts on the same data; later runs name the override. A containerised receiver cannot reach a Valkey bound to the host's 127.0.0.1 on Linux, which is why the queue moves to compose rather than the receiver to the host. The CLI verbs (run, pause, resume, status, cancel) and `service restart --drain` read VALKEY_URL from the shell alone while reading the password from the deployment .env, so from the folder of a Valkey on another port they dialled 6379. One resolver (valkeyUrlFor, cliValkeyUrl) now reads it as the password is read: this shell's, else the .env's, a disagreement named on stderr and never a refusal (the kill switch must not refuse). `up`'s offer to restart pi-dispatch-valkey with its password now says that a running job is interrupted and that the worker and the receiver get NOAUTH until restarted, and to pause first. `service install` already says so before it acts. Doctor's accepted-password line names where the password came from (the environment, the .env, or VALKEY_URL). Specs: REQ-DEPLOYMENT-BOOTSTRAP (clause 5), DES-PODMAN-STACK-AS-QUADLET-UNITS (the password bullet) and DES-FIRST-RUN-SETUP-WIZARD amended, their #468 rows extended. UNCHANGED, checked: INT-DEPLOYMENT-POINTER-CONTRACT, DES-WORKER-ON-HOST, CONST-RETRY-INFRA-ONLY. Signed-off-by: Rob Boerman <robboerman@live.nl>
edgehero
force-pushed
the
fix/468-valkey-password
branch
from
September 28, 2026 19:40
0dfe8ec to
b76c3e5
Compare
…word (#468) The Valkey pi-dispatch starts (the Quadlet unit, the compose file and up's docker run) had no password. Loopback is shared by every account on a host, so any local account could read another account's queue, enqueue jobs its worker would run with that account's provider key, or wipe it. - A per-deployment VALKEY_PASSWORD in .env, separate from VALKEY_URL (which doctor prints and the panel's pointer stores). init writes one and creates .env at 0600; service install and up add one when missing, never over a value, and narrow .env to its owner. None is generated for a shared Valkey or an operator's own VALKEY_URL. - Never on a command line: the image's PID 1 keeps its whole argv, so --requirepass shows in /proc to every account (measured on both VMs). The password reaches the container in its environment (a 0600 valkey.env for the Quadlet unit, --env-file .env for compose, the docker CLI's own environment for up), and one start script hands it to valkey-server on stdin and unsets it. The health check requires PONG with REDISCLI_AUTH. - Every client attaches it through parseConnection. A failed command's arguments are stripped from every error any client builds, at emit, silentEmit and each command's reject, and every Queue and Worker logs errors by message only, so a rotation never prints the password; the entry points print an unhandled rejection by its message. - A Valkey that refuses the credential makes every entry point refuse by name; a loopback Valkey with no password is a doctor warning naming the upgrade. service install --force and up restart the Valkey with the password on its volume, warning that a running job is interrupted. - The CLI reads VALKEY_URL like the password (shell, else .env). On a disagreement pause pauses both queues, status shows both, and resume and cancel refuse until --valkey-url names one; URLs print without userinfo. - Compose publishes Valkey on VALKEY_URL's port (PI_VALKEY_PORT). The wizard's compose copy keeps its old project name with -p, and when up's Valkey exists it hands that volume to compose (pull first, every exit code checked, the way back named), so worker and receiver share one queue; up then starts compose's Valkey in such a folder. - A Valkey container or volume counts as this deployment's only when it provably is: up labels what it creates with the deployment folder, an unlabelled container counts only when it publishes this deployment's port, an unlabelled volume is adopted only on an explicit yes (never --yes alone), and a pi-dispatch:owner marker inside the Valkey is checked on every start. Nothing stops, removes or starts beside another deployment's Valkey or on its volume. PI_VALKEY_PORT is written into .env for a non-default port; the admin panel's pause and resume follow the CLI's kill-switch rule. - On #471's rule (doctor hands no program anything from .env): doctor starts no Valkey any more; its --fix offer for an unreachable loopback Valkey is gone, since that docker run needed the password in the docker CLI's environment, and the fix line names pi-dispatch up. Doctor's own in-process AUTH check uses the resolved password; VALKEY_PASSWORD and PI_VALKEY_PORT are service keys, the password a secret shown by name only. The panel's Valkey clients and kill switch read the pointer's .env through #471's one reader. - Found on the way: a password in VALKEY_URL reached the client still percent-encoded; the .env writer's temp file was world-readable until its chmod. Specs: REQ-DEPLOYMENT-BOOTSTRAP, REQ-OPERATOR-JOB-CANCEL, DES-PODMAN-STACK-AS-QUADLET-UNITS, DES-WORKER-ON-HOST, DES-FIRST-RUN-SETUP-WIZARD and INT-DEPLOYMENT-POINTER-CONTRACT amended; the rest named UNCHANGED, checked in the revision rows. Signed-off-by: Rob Boerman <robboerman@live.nl>
edgehero
force-pushed
the
fix/468-valkey-password
branch
from
September 28, 2026 21:25
b76c3e5 to
24fd923
Compare
…cord the adoption (#468) PR #475's round-cap re-review. - The wizard's compose steps no longer carry a shell-exported VALKEY_PASSWORD: compose prefers the environment over --env-file .env, so an exported one became the Valkey's password (measured). The deployment's comes from .env, as up hands docker the deployment's. - Adopting an unlabelled pi-dispatch-valkey-data no longer publishes a Valkey on another deployment's data for a second after a wrong y: up first loads the volume in a Valkey with --network none, no published port and no password, reads pi-dispatch:owner through docker exec, stops and removes it, and only then starts the published one. Another folder's marker refuses with nothing published. - An adoption is recorded in the deployment folder, .pi-dispatch-valkey-volume.json (0600, create-only, never over a different record): the volume's name and CreatedAt from docker volume inspect. An unlabelled volume whose CreatedAt matches is this deployment's without asking, so --yes works after the first adoption; a volume made anew is asked about again. The wizard adopts nothing (it names up, which asks) and records the volume up's own container served when it hands it over. - Removed the wizard's unused readValkeyKeys import. Measured on pd-fedora (root's Podman socket): the owner check's container has no port bindings and network mode none, and reads the marker of an unlabelled volume; CreatedAt is present for such a volume. Specs: REQ-DEPLOYMENT-BOOTSTRAP clause (5) and DES-WORKER-ON-HOST, DES-FIRST-RUN-SETUP-WIZARD amended; the #468 rows extended. Signed-off-by: Rob Boerman <robboerman@live.nl>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #468
What was wrong
The Valkey pi-dispatch starts (the Quadlet unit, the compose file and
up'sdocker run) had no password. Loopback is shared by every account on a host, so any local account could read another account's queue (task text, repository names), enqueue jobs that account's worker would run with its provider key, or wipe it. #464 stops accidents; this stops a co-tenant who connects on purpose.Change
VALKEY_PASSWORD, in.env. A separate key, not insideVALKEY_URL, because doctor prints the URL, the panel's pointer stores it and refusals quote it.initwrites one and creates.envat 0600;service installandupadd one when missing, never over a value, and narrow.envto its owner. None is generated for a shared Valkey (PI_VALKEY_SHARED) or an operator's ownVALKEY_URL, which are left as they are.tini) keeps its whole argv, sovalkey-server --requirepass Xshows X in/proc/<pid>/cmdlineto every account for as long as the container runs, and the image'sVALKEY_EXTRA_FLAGSappends to that same argv. The password reaches the container in its environment (a 0600~/.config/pi-dispatch/valkey.envfor the Quadlet unit,--env-file .envfor compose, the docker CLI's own environment forup), and one start script hands it tovalkey-server -as config on stdin and unsets it. The health check requiresPONGwithREDISCLI_AUTH, since a barevalkey-cli pingexits 0 onNOAUTH(measured).parseConnectionalone attaches the credential (the URL's own, thenVALKEY_PASSWORD), for every client through a second account on one host: shared default jobs dir, podman after linger off, and a half-written service install #464's one connection function. A Valkey that refuses the credential makes the worker exit 2 and the CLI, receiver and doctor refuse, naming the key. The value is never logged;VALKEY_PASSWORDjoins the worker-only secrets, so it can never reach a job container.service install --forcerestarts the Quadlet Valkey with the password (the AOF volume is kept) and then the running worker and receiver;updoes the same for docker, warning that a running job is interrupted. doctor itself starts no Valkey: by the doctor judges many service keys from its shell while the service reads them from .env #471 rule it hands no program anything from.env, so its fix line namespi-dispatch up, and its password check is in-process.uplabels what it creates with the deployment folder; an unlabelled container counts only when it publishes this deployment's port; an unlabelled volume is adopted only on an explicit yes (never--yesalone), after itspi-dispatch:ownermarker is read in a container with no network, and the adoption is recorded in the folder. Nothing stops, removes or starts beside another deployment's Valkey or on its volume.VALKEY_URLlike the password. On a shell/.envdisagreementpausepauses both queues,statusshows both, andresumeandcancelrefuse until--valkey-urlnames one. URLs print without userinfo.VALKEY_URL's port (PI_VALKEY_PORT, written into.envfor a non-default port). The wizard keeps its old compose project name with-p, and whenup's Valkey exists it hands that volume to compose (pull first, every exit code checked, the way back named), so worker and receiver share one queue.VALKEY_URLreached the client still percent-encoded; the.envwriter's temp file was world-readable until its chmod;up's docker Valkey step ignoredVALKEY_URL's port; and the wizard's compose copy resolved its relative paths outside the deployment folder. All four are fixed, and the admin panel's pause and resume now follow the CLI's rule.Specs
REQ-DEPLOYMENT-BOOTSTRAP,DES-PODMAN-STACK-AS-QUADLET-UNITS,DES-WORKER-ON-HOST,DES-FIRST-RUN-SETUP-WIZARD,INT-DEPLOYMENT-POINTER-CONTRACT; the others UNCHANGED, checked in the rows.Verification
PING,KEYSandFLUSHALLgetNOAUTH; no command line on the host carried the password during install or at rest; an upgrade from a passwordless deployment kept its queue (marker key and 3 paused jobs survived and completed); a shared Valkey without the password is refused by name. Compose was checked on Fedora through root's Podman socket; Ubuntu has no compose.