diff --git a/CloudronManifest.json b/CloudronManifest.json index 5c7ba63..9aacd29 100644 --- a/CloudronManifest.json +++ b/CloudronManifest.json @@ -26,7 +26,7 @@ "scheduler": { "librechat_key_sync": { "schedule": "*/10 * * * *", - "command": "cd /app/code/librechat/api && node /app/data/sync-keys.js" + "command": "cd /app/code/librechat/api && node /app/code/sync-keys.js" } } }, diff --git a/Dockerfile b/Dockerfile index 33c9479..7d315a4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,7 +15,9 @@ # built from this repo on every deploy (the base FROM stays the appstore # image we validated). A stale dockerImage pin silently redeploys an old # build — that is what broke the sandbox on 2026-10-18/19. -# 3. This image changes ONLY static brand assets. All behavior lives on the +# 3. This image changes ONLY static brand assets plus operational scripts that +# must not depend on the persistent volume (currently sync-keys.js, run by +# the manifest's scheduler task). All other behavior lives on the # persistent volume (/app/data/librechat.yaml, /app/data/env, # /app/data/mcp/) and survives image updates unchanged. FROM cloudron/ai.librechat.cloudronapp:202610150224320000 @@ -41,6 +43,12 @@ COPY assets/custom.css /app/code/librechat/client/dist/assets/custom.css # rebuild trigger COPY assets/custom2.js /app/code/librechat/client/dist/assets/custom2.js +# Per-member key seed (manifest scheduler task `librechat_key_sync`). Shipping it +# in the image rather than on the volume means it cannot vanish with a volume +# restore and cannot drift from this repo; the script prints its own md5 prefix +# on every run so the running revision is auditable. +COPY sync-keys.js /app/code/sync-keys.js + # Co-op UI strings: rename the "MCP Servers" button to "Tools" and replace # the LibreChat footer tagline with the co-op footer. Non-fatal at build. COPY patch_ui_strings.py /app/code/patch_ui_strings.py diff --git a/README.md b/README.md index 68405ba..67b3e13 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ md5sum librechat.yaml mcp/server.py mcp/image_server.py | Env vars (env-file wins!) | `/app/data/env` (volume, sourced LAST by start.sh) | yes | documented below | | Env vars (Cloudron-managed) | `cloudron env` (process env) | yes | documented below | | MCP servers | `/app/data/mcp/server.py` + `mcp/image_server.py` (volume) | yes | `mcp/` | -| Per-member key seed | `/app/data/sync-keys.js` (volume; run by the manifest's `scheduler` task) | yes | `sync-keys.js` | +| Per-member key seed | `sync-keys.js` baked into the image at `/app/code/sync-keys.js` (run by the manifest's `scheduler` task) | no — rebuilt per image | `sync-keys.js` | | Brand assets + branding CSS/JS | baked into image overlay | no — rebuilt per image | `assets/` | | `CloudronManifest.json` | repo only (controls what `cloudron update` deploys) | n/a | official appstore manifest **except `dockerImage`** | @@ -335,11 +335,21 @@ member's own portal-provisioned key automatically: "scheduler": { "librechat_key_sync": { "schedule": "*/10 * * * *", - "command": "cd /app/code/librechat/api && node /app/data/sync-keys.js" + "command": "cd /app/code/librechat/api && node /app/code/sync-keys.js" } } ``` +The script is **baked into the image** (`COPY sync-keys.js /app/code/…`), so it +cannot be lost with a volume restore and cannot drift from this repo. + +**Ordering matters on the first deploy of this change:** build the image → deploy +(pin the new tag in the manifest first — the manifest's repointed command ships +in the same `cloudron update`) → verify (`./release.sh verify` checks that the +command path exists in the running image) → only then delete the volume copy +`/app/data/sync-keys.js`, because rolling back to the previous image tag needs it +back. After that, a change to this script requires a rebuild, not a volume push. + 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.