From 2d66ea212d1562aa6ef3da8e8c9d04c3f47b1e8f Mon Sep 17 00:00:00 2001 From: Inferencebot Date: Sun, 25 Oct 2026 22:44:34 +0000 Subject: [PATCH] README: document the in-app key-sync scheduling, the creds file, and the fail-closed guards --- README.md | 53 +++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 47 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index caf9c8c..2c6ca3b 100644 --- a/README.md +++ b/README.md @@ -299,10 +299,49 @@ member's own portal-provisioned key automatically: `customUserVars` config so image generation bills to the member's own team. Encryption uses LibreChat's own scheme (`CREDS_KEY`/`CREDS_IV`, AES-256-CBC - via `@librechat/data-schemas` `encrypt()`), read from the app process env - (`/proc/1/environ`) because `cloudron exec` sessions don't inherit it. -4. Members who haven't logged in to the sandbox yet are skipped; the next - run seeds them. Run hourly or nightly: + via `@librechat/data-schemas` `encrypt()`). +4. Members who haven't logged in to the sandbox yet are skipped; the next run + seeds them. + +### Scheduling (in this app, since 2026-10-25) + +`CloudronManifest.json` declares the task in `addons.scheduler`: + +```json +"scheduler": { + "librechat_key_sync": { + "schedule": "*/10 * * * *", + "command": "cd /app/code/librechat/api && node /app/data/sync-keys.js" + } +} +``` + +Seeding therefore depends on nothing outside this app — it previously ran from a +steward cron on another machine, which made a member-facing function depend on +that machine's uptime and paths. + +Two consequences of how the scheduler addon works, both learned the hard way: + +- **Each task runs in its own container.** The app's process env is absent there, + and `/proc/1/environ` belongs to the task launcher, not to LibreChat — so + `CREDS_KEY`/`CREDS_IV` come from `/app/data/.librechat-creds` (root:cloudron + `0640`), written from the live app env: + + ```bash + cloudron --server my.inference.coop --token "$CLOUDRON_TOKEN" exec \ + --app 4023ccbf-d57a-4500-97df-14de9cf3516e \ + -- sh -c 'umask 077; tr "\0" "\n" < /proc/1/environ | grep -E "^CREDS_(KEY|IV)=" > /app/data/.librechat-creds; chown root:cloudron /app/data/.librechat-creds; chmod 640 /app/data/.librechat-creds' + ``` + + If the app's key is ever rotated, re-run that. A stale key does not corrupt + anything — the guard below refuses to write. +- **It fails closed.** `sync-keys.js` writes nothing unless `CREDS_KEY` first + decrypts an existing key row (proof it is the app's real key) and unless each + fresh ciphertext round-trips back to the plaintext. A credential problem exits + non-zero, logs `FATAL: ...`, and writes `/app/data/key-sync-diag.txt` (uid, cwd, + which env sources were readable) instead of overwriting working member keys. + +Manual run (unchanged, useful after an onboarding wave): ```bash cloudron --server my.inference.coop --token "$CLOUDRON_TOKEN" exec \ @@ -310,8 +349,10 @@ cloudron --server my.inference.coop --token "$CLOUDRON_TOKEN" exec \ -- bash -c "cd /app/code/librechat/api && node /app/data/sync-keys.js" ``` -Idempotent. Verify with the DB probe: `keys` and `pluginauths` counts should -equal the number of members who have logged in to the sandbox. +Idempotent. Verify: the app log shows the task's output +(`creds check: decrypt-ok …` / `portal feed: N active member key(s)` / +`done: N seeded, M skipped`), and `keys` / `pluginauths` counts should equal the +number of members who have logged in to the sandbox. > **Output-redaction gotcha:** terminal output renders values after > `apiKey:` as `***` even when the file contains `user_provided` or