README: document the in-app key-sync scheduling, the creds file, and the fail-closed guards

This commit is contained in:
inference-admin committed 2026-10-25 22:44:34 +00:00
1 parent e544dc4a11
commit 2d66ea212d
1 file changed
+47 -6
+47 -6
View File
@@ -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