migration: add the before-state snapshot; S3 secret is NOT off-box

Snapshot records apps/images/env-names/backup config and data fingerprints for a
post-restore diff. Also resolves an open unknown: the off-box JSON carries only
accessKeyId, so the S3 secretAccessKey must be retrieved before the window. The
restic passphrase is fine - it is on the agent host, which is not migrating.
This commit is contained in:
inference-bot committed 2026-10-30 20:04:07 +00:00
1 parent 144e7e77ca
commit b313105148
2 files changed
+265 -4

No files matched your search

+20 -4
View File
@@ -50,9 +50,16 @@ needed to restore are inside the thing being restored.
- [ ] Export the Vault contents and store them off-server (personal password manager or - [ ] Export the Vault contents and store them off-server (personal password manager or
offline media). Confirm the **Cloudron backup passphrase** is in your own hands. offline media). Confirm the **Cloudron backup passphrase** is in your own hands.
- [ ] Confirm the backup target's S3 access key is available off-box. The agent host has - [ ] **Retrieve the S3 secretAccessKey — the off-box copy does NOT have it.** Checked
`~/.hermes/security/backup_sites_backup.json` (0600) — check whether it carries the 2026-10-30: `~/.hermes/security/backup_sites_backup.json` carries `accessKeyId` and
key; if not, retrieve it from the Infomaniak console. nothing else. A restore begins by pointing the new Cloudron at the backup target, so
the *secret* must be in hand before the window — get it from the Infomaniak console
and store it with the passphrase, off-box.
- [x] The **restic** passphrase is safe: `~/.hermes/scripts/backup.sh` documents that
`RESTIC_PASSWORD` lives in the agent host's `.env` **and** the co-op Vault. The agent
host is not being migrated, so the agent-side backup stays recoverable. (The Vault,
however, is an app on the box being migrated — so the Vault copy alone is not
off-box until it is exported.)
- [x] New host capacity: **4 CPU, 12 GB RAM, 250 GB disk** (given 2026-10-30) — ample for - [x] New host capacity: **4 CPU, 12 GB RAM, 250 GB disk** (given 2026-10-30) — ample for
~6 GB of app data plus images. Old box used ~17 GB of 58 GB. ~6 GB of app data plus images. Old box used ~17 GB of 58 GB.
- [x] OS: **Ubuntu 26.04 LTS is supported** — Cloudron's install docs specify a fresh - [x] OS: **Ubuntu 26.04 LTS is supported** — Cloudron's install docs specify a fresh
@@ -151,6 +158,16 @@ needed to restore are inside the thing being restored.
advance, so a strict-host-key refusal doesn't stop you mid-migration. advance, so a strict-host-key refusal doesn't stop you mid-migration.
- [ ] Announce the window in `#updates`. - [ ] Announce the window in `#updates`.
## 3a. The "before" snapshot
`migration-before-state.json` in this repo is a machine-diffable record of the current
server: 16 apps with their manifest ids, image tags, state, health and **env var names**
(never values); the backup sites, schedule and retention; the newest run's id, state,
integrity signature and size; Cloudron's version; and data fingerprints (member count,
API-key count, ledger rows, `metrics_daily` rows, Gitea repo count). Capture nothing
further; after the restore, diff it. A silently-empty app is exactly what a diff catches
and a spot-check misses.
## 4. Cutover, in order ## 4. Cutover, in order
1. **Freeze writes.** Pause the portal's `membership_sweep` and `rag_refresh`, hold 1. **Freeze writes.** Pause the portal's `membership_sweep` and `rag_refresh`, hold
@@ -222,7 +239,6 @@ needed to restore are inside the thing being restored.
open question in this runbook. open question in this runbook.
- Whether the Vault export (§3 blocker) has actually been done. - Whether the Vault export (§3 blocker) has actually been done.
- The Cloudron host's OS version, disk and RAM (not visible over the API). - The Cloudron host's OS version, disk and RAM (not visible over the API).
- Whether the S3 access key is in the off-box JSON or only inside Cloudron.
- Whether any provider API key is bound to our current IP. - Whether any provider API key is bound to our current IP.
- Whether the subscription permits a server move. - Whether the subscription permits a server move.
+245
View File
@@ -0,0 +1,245 @@
{
"app_count": 16,
"apps": {
"4023ccbf-d57a-4500-97df-14de9cf3516e": {
"env_names": [
"ALLOW_EMAIL_LOGIN",
"APP_TITLE",
"IMAGE_GEN_OAI_BASEURL",
"IMAGE_GEN_OAI_MODEL",
"SEARXNG_INSTANCE_URL",
"STT_API_KEY",
"SYSTEM_IMAGE_KEY",
"TTS_API_KEY"
],
"health": "healthy",
"image": "20261027-d099d31",
"location": "chat.inference.coop",
"manifest": "ai.librechat.cloudronapp@1.4.5",
"state": null
},
"50ae6c97-e851-4d17-b013-51ca7a4e5be0": {
"env_names": [
"DEFAULT_PROMPT_SUGGESTIONS",
"ENABLE_ADMIN_CHAT_ACCESS",
"ENABLE_ADMIN_EXPORT",
"PYTHONPATH",
"RAG_EMBEDDING_ENGINE",
"RAG_EMBEDDING_MODEL",
"RAG_OPENAI_API_BASE_URL",
"RAG_OPENAI_API_KEY"
],
"health": "healthy",
"image": "202610270420400000",
"location": "chat-legacy.inference.coop",
"manifest": "com.openwebui.cloudronapp@3.7.8",
"state": null
},
"55f5f904-3233-44de-ad7b-5dc7c8533b75": {
"env_names": [],
"health": "healthy",
"image": "202610221509540000",
"location": "inference.coop",
"manifest": "io.cloudron.surfer@7.4.0",
"state": null
},
"8b1a972c-08af-4e92-852c-d573c37b01ad": {
"env_names": [
"B3_API_KEY",
"LOOMIO_DISABLE_DEMO_GROUPS"
],
"health": "healthy",
"image": "202610270319320000",
"location": "forum.inference.coop",
"manifest": "org.loomio.cloudronapp@1.29.1",
"state": null
},
"aa6f4597-6206-4509-b32e-c89750b2c1d8": {
"env_names": [],
"health": "healthy",
"image": "202610290719420000",
"location": "matrix.inference.coop",
"manifest": "org.matrix.synapse@1.147.1",
"state": null
},
"adababd4-2427-4bdd-9bf7-6bdad0302610": {
"env_names": [
"GREENPT_API_KEY",
"LITELLM_CORS_ORIGINS",
"LITELLM_MIGRATION_DIR",
"OLLAMA_API_KEY",
"TINFOIL_API_KEY",
"TINFOIL_USER_CACHE_SECRET"
],
"health": "healthy",
"image": "1.0.0-1793073788677",
"location": "gateway.inference.coop",
"manifest": "ai.coop.litellm@1.0.0",
"state": null
},
"ae187071-7a43-4a6e-8a02-986609a66a1a": {
"env_names": [
"ADMIN_TOKEN",
"ADMIN_USERNAMES",
"PORTAL_BASE"
],
"health": "healthy",
"image": "20261029-4687efb",
"location": "panel.inference.coop",
"manifest": "coop.inference.admin-panel@0.1.0",
"state": null
},
"c257ba3c-6737-4375-ad51-ea19586bf3f0": {
"env_names": [],
"health": "healthy",
"image": "202610221511140000",
"location": "registry.inference.coop",
"manifest": "io.cloudron.container-registry@1.4.0",
"state": null
},
"c91c13aa-30ed-4af9-9599-7a87ab4f221d": {
"env_names": [],
"health": "healthy",
"image": "202610211624040000",
"location": "git.inference.coop",
"manifest": "io.gitea.cloudronapp@1.43.1",
"state": null
},
"cb838bcf-f2e2-4670-b117-dca04b0ee957": {
"env_names": [],
"health": "healthy",
"image": "202610211943370000",
"location": "vault.inference.coop",
"manifest": "com.github.bitwardenrs@1.26.5",
"state": null
},
"d4f05e4b-75c6-492e-9faf-7afb0712fc94": {
"env_names": [
"ADMIN_TOKEN",
"ADMIN_TOKEN_LEGACY",
"BROKER_SECRET",
"CLOUDRON_TOKEN",
"DOCS_KNOWLEDGE_ID",
"INACTIVE_GROUP_ID",
"LIBRECHAT_SYNC_TOKEN",
"LISTMONK_API_TOKEN",
"LISTMONK_API_USER",
"LISTMONK_LIST_ID",
"LITELLM_BASE",
"LITELLM_DB_HOST",
"LITELLM_DB_NAME",
"LITELLM_DB_PASSWORD",
"LITELLM_DB_PORT",
"LITELLM_DB_USER",
"LITELLM_MASTER_KEY",
"LOOMIO_API_KEY",
"LOOMIO_B3_KEY",
"LOOMIO_GROUP_ID",
"MANUAL_MEMBERS",
"MATRIX_BOT_TOKEN",
"MATRIX_EXTRA_ROOMS",
"MATRIX_GENERAL_ROOM",
"MATRIX_HOMESERVER",
"MATRIX_SPACE_ID",
"MEMBERS_GROUP_ID",
"MEMBER_BUDGET",
"OC_OAUTH_CLIENT_ID",
"OC_OAUTH_CLIENT_SECRET",
"OC_PERSONAL_TOKEN",
"OWUI_API_KEY",
"OWUI_BASE",
"OWUI_JWT_SECRET",
"OWUI_MEMBERS_GROUP_ID",
"PORTAL_KEK",
"PORTAL_SECRET",
"WEBHOOK_TOKEN"
],
"health": "healthy",
"image": "20261030-6475a51",
"location": "portal.inference.coop",
"manifest": "coop.inference.member-portal@0.1.0",
"state": null
},
"d7c73fff-ee42-4db0-98c0-4ce7a8ab35cc": {
"env_names": [],
"health": "healthy",
"image": "202610252319260000",
"location": "search.inference.coop",
"manifest": "io.github.ascimoo.searx@2.119.0",
"state": null
},
"daf8750e-2683-4104-83ea-f77d1b4601e8": {
"env_names": [
"BROKER_SECRET",
"PORTAL_BASE"
],
"health": "healthy",
"image": "20261029-700339a",
"location": "dashboard.inference.coop",
"manifest": "coop.inference.member-dashboard@0.1.0",
"state": null
},
"eed2217c-b165-430e-bc29-7ccfef9b11e4": {
"env_names": [],
"health": "healthy",
"image": "202610220221130000",
"location": "newsletter.inference.coop",
"manifest": "app.listmonk.cloudronapp@1.17.1",
"state": null
},
"fbb256b9-ead3-41d8-bee0-d5ea87149436": {
"env_names": [],
"health": "healthy",
"image": "202610261602550000",
"location": "email.inference.coop",
"manifest": "io.cloudron.mail@1.6.1",
"state": null
},
"fd261d67-b790-424f-9e82-f74883ee8585": {
"env_names": [],
"health": "healthy",
"image": "202610211606270000",
"location": "cloud.inference.coop",
"manifest": "com.nextcloud.cloudronapp@5.9.0",
"state": null
}
},
"backup_runs_retained": 14,
"backup_sites": [
{
"bucket": "default",
"endpoint": "https://s3.swiss-backup02.infomaniak.com/",
"name": "Infomaniak Encrypted S3",
"provider": "s3-v4-compat",
"retention_days": 7,
"schedule": "00 00 23 * * *"
},
{
"bucket": null,
"endpoint": "filesystem",
"name": "Default",
"provider": "filesystem",
"retention_days": 2,
"schedule": "never"
}
],
"captured_at": "2026-10-30T20:03:49Z",
"cloudron_version": "10.1.5",
"gitea_code_repos": 5,
"newest_backup": {
"bytes": 4983397240,
"created": "2026-10-30T14:57:05.000Z",
"has_integrity_signature": true,
"id": "box_v10.1.5_482bf510",
"state": "normal"
},
"portal_tables": {
"credit_ledger": 18,
"held_credits": 1,
"member_api_keys": 64,
"members": 82,
"metrics_daily": 57,
"pending_members": 3,
"sqlite_sequence": 2
}
}