Three drifts between this repo and reality, found while moving the per-member key
seed into the app:
- The 'verify after every image deploy' block asserted the CREAM palette
(rgb(250,248,245)). The deployed palette has been the green wash
(rgb(234,243,234) with white text areas) for some time, so a correct deploy
would have read as a failure — and invited someone to 'fix' the CSS back.
- The block still verified against librechat-test.inference.coop; the swap to
chat.inference.coop happened 2026-10-21 and that alias is not provisioned.
- Nothing checked that the scheduler task's script exists in the running image,
which is exactly the failure mode a manifest repoint can introduce.
release.sh encodes the documented release flow (dirty-tree guard, always
--no-cache, pin-before-deploy) and its verify subcommand runs checks 1-5 plus the
seed task's log lines. sync-keys.js now prints its own md5 prefix on every run,
so a stale volume copy can never hide again — the repo copy's md5 is the
authority.
Verified: ./release.sh verify passes all six checks against the live app;
./release.sh volume sync-keys.js confirms md5 parity.
The first scheduled run failed closed (good) — Cloudron's scheduler addon runs
each task in its OWN container, so the app's process env is absent there and
/proc/1/environ belongs to the task launcher, not to LibreChat. CREDS_KEY/CREDS_IV
now come from /app/data/.librechat-creds (root:cloudron 0640), written from the
live app env; a stale key makes the decrypt-against-existing-rows guard refuse to
write rather than corrupt member keys. Credential failure now also writes
/app/data/key-sync-diag.txt so a future failure explains itself.
Verified: the 22:40 scheduled fire logged 'creds check: decrypt-ok (25 existing
row(s))' / 'done: 50 seeded, 15 skipped'.
The per-member key seed ran from a steward cron on the co-op agent VM, shelling
into this app over the Cloudron API. That made a member-facing function depend on
a separate machine's uptime and paths (it went silently stale once, exiting 127
on schedule while the job looked healthy).
- CloudronManifest.json: addons.scheduler gains librechat_key_sync
(cd /app/code/librechat/api && node /app/data/sync-keys.js, */10 * * * *).
Official addon set (postgresql, mongodb, localstorage, oidc, sendmail) and the
dockerImage pin are untouched.
- sync-keys.js: refuse to write unless CREDS_KEY can first decrypt an existing
key row, and unless each fresh ciphertext round-trips back to the plaintext.
Scheduled runs get a different environment than an interactive exec, so a wrong
or absent CREDS_KEY must fail loudly instead of overwriting working member keys.