From b3131051481f7351ffe9664eab95c03cea35ea3d Mon Sep 17 00:00:00 2001 From: Inferencebot Date: Fri, 30 Oct 2026 20:04:07 +0000 Subject: [PATCH] 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. --- cloudron-server-migration.md | 24 +++- migration-before-state.json | 245 +++++++++++++++++++++++++++++++++++ 2 files changed, 265 insertions(+), 4 deletions(-) create mode 100644 migration-before-state.json diff --git a/cloudron-server-migration.md b/cloudron-server-migration.md index a6cce5e..a1a9c67 100644 --- a/cloudron-server-migration.md +++ b/cloudron-server-migration.md @@ -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 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 - `~/.hermes/security/backup_sites_backup.json` (0600) — check whether it carries the - key; if not, retrieve it from the Infomaniak console. +- [ ] **Retrieve the S3 secretAccessKey — the off-box copy does NOT have it.** Checked + 2026-10-30: `~/.hermes/security/backup_sites_backup.json` carries `accessKeyId` and + 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 ~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 @@ -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. - [ ] 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 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. - Whether the Vault export (§3 blocker) has actually been done. - 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 the subscription permits a server move. diff --git a/migration-before-state.json b/migration-before-state.json new file mode 100644 index 0000000..eeba9a2 --- /dev/null +++ b/migration-before-state.json @@ -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 + } +} \ No newline at end of file