Plan · waves & rollback

Michel Media — Migration Plan

Versie 1 · 2026-05-07 · gebaseerd op DISCOVERY.md

Strategische uitgangspunten

  1. Eigen alles, voor zover praktisch en betaalbaar.
  2. Bedrijfskritiek = eigen hardware of cloud waar je voor betaalt (Cloudflare, Mac mini). Niet werk-VPS.
  3. Cloudflare als platform (Pages, Workers, R2, D1) waar mogelijk, paid tier OK.
  4. Mac mini als 24/7 hub — productiever maken loont meer dan NAS behouden.
  5. NAS-exit gepland, niet vandaag uitgevoerd. Camera-vraag eerst beantwoord.
  6. Cashspot wordt afgesloten, Coolify wordt afgebouwd na de twee resterende deploys gemigreerd zijn.

Filosofie per wave

Volgorde

Wave 0   → Foundations (Syncthing + parity + restic backup)       DONE 2026-05-07
Wave 0.5 → R2 secondary repo (off-site backup)                    ~30 min
Wave 0.7 → Dry-run failover oefening                              ~1 uur
Wave 1   → SPOF mitigatie (Vaultwarden encrypted export)          ~30 min
Wave 2   → Mac mini server prep (Docker + tunnel + Caddy)         ~2-3 uur
Wave 3   → Vaultwarden migratie naar Mac mini                     ~2 uur
Wave 4   → Paperless migratie naar Mac mini                       ~3 uur
Wave 5   → soulwise-site → Cloudflare Pages                       ~1 uur
Wave 6   → dekei1 migratie (next-on-pages compat-check + Postgres) ~3-5 uur
Wave 7   → plex VPS schoonmaak (Coolify + Supabase + Happy)       ~2 uur
Wave 8   → NAS audit + Time Machine via Mac mini                  ~3 uur
Wave 9   → Camera transitie (geparkeerd tot beslissing)           tbd

Totaal geschat: 19-27 uur, verspreid over 6-9 weken in eigen tempo.

Motivatie (juridische ondergrond)

De migratie is niet alleen pragmatisch maar ook juridisch geboden:


Wave 0 — Foundations · DONE 2026-05-07

Status: uitgevoerd 7 mei 2026 ~23:30. Mac mini is hot standby. Claude Code config + werkmappen syncen via Syncthing. Onvervangbare data nightly naar Mac mini SFTP via restic. Dotfiles op GitHub. R2 secondary repo deferred naar Wave 0.5 (vereist 1-min UI-stap voor S3-keys).

Geïmplementeerd:

Component Status
brew install syncthing restic op MBP + macmini
Syncthing devices gepaird (S4IXMDC... ↔ JVIW4R2...)
5 Syncthing folders shared two-way met .stignore patterns
- michelmedia (~/Documents/michelmedia)
- dekei1, lurvink, og (~/Documents/...)
- claude-config (~/.claude met aggressive ignore: shell-snapshots, ide, projects, plugin-git)
Restic SFTP repo sftp:macmini:/Users/michelhelsdingen/restic-backups/main
Restic passphrase 40 chars in Vaultwarden item "restic backup passphrase" + ~/.cache/restic-passphrase.txt
restic-backup.sh script in infra/scripts/
LaunchAgent com.michelmedia.restic-backup.plist (03:30 nightly)
Eerste backup-run: 16,7 GB raw → 5,4 GB stored, 56k files in 57 sec
Telegram-notificatie hooks (Spotbot bot 8402924483 chat 467689725)
Dotfiles repo michelhelsdingen/dotfiles private op GitHub met bootstrap-script + Brewfile

claude-mem strategie (afwijking van origineel plan): - ~/.claude-mem zit NIET in Syncthing (zou SQLite single-writer corruptie veroorzaken) - ~/.claude-mem zit WEL in restic nightly snapshot naar Mac mini (en straks ook R2) - MacBook = primary, recovery via restic restore latest --include /Users/michelhelsdingen/.claude-mem

Rollback Wave 0: - Syncthing pauzeren via UI of brew services stop syncthing op een/beide machines - LaunchAgent uitzetten: launchctl unload ~/Library/LaunchAgents/com.michelmedia.restic-backup.plist - Restic repo op Mac mini blijft staan; geen impact op MBP data


Wave 0.5 — R2 secondary backup repo

Doel: off-site backup naast lokale Mac mini, beschermt tegen "huis brandt af".

Prerequisites (1-min UI-stap): 1. Login op https://dash.cloudflare.com → R2 → Manage R2 API tokensCreate API token 2. Permissions: Object Read & Write voor bucket noaber-backups 3. Kopieer Access Key ID + Secret Access Key + endpoint URL 4. Plak in Vaultwarden item "restic R2 backup credentials"

Stappen (door Claude na UI):

  1. Init R2 als tweede restic repo: restic -r s3:https://<account>.r2.cloudflarestorage.com/noaber-backups init
  2. Update restic-backup.sh: backup naar BEIDE repos (SFTP primary + R2 secondary)
  3. Test eerste R2-backup en verify met restic snapshots -r s3:...
  4. Update FAILOVER-PLAYBOOK met R2 restore-procedure

Verificatie: - ✓ Beide repos zichtbaar in nightly run-log - ✓ R2 dashboard toont bucket-content (~5-7 GB)


Wave 0.7 — Dry-run failover oefening

Doel: voor het eerst echt testen dat recovery werkt, vóór het urgent nodig is.

Stappen:

  1. Op Mac mini: open Claude Code-sessie. Verifieer dat skills/plugins werken (claude-mem write, custom skills callable).
  2. Simuleer "MBP stuk": stop Syncthing op MBP, sluit alle Claude Code sessies.
  3. Doe een task op Mac mini, valideer dat claude-mem-write werkt op Mac mini's lokale DB.
  4. Restart Syncthing op MBP, verifieer two-way conflict-resolution werkt netjes.
  5. Test restic restore: in /tmp directory, restore latest snapshot van een specifieke folder, verifieer file-integriteit.
  6. Documenteer alle verschillen tussen MBP en Mac mini-omgeving die opvallen (font-rendering, app-paden, etc.)

Verificatie: documenteren in infra/FAILOVER-DRYRUN-RESULTS.md. Pas dán Wave 1+ starten.


Wave 1 — Vaultwarden SPOF mitigatie

Doel: Het ergste single-point-of-failure dichten zonder dat we Vaultwarden verhuizen. Maximaal 30 minuten werk, voorzichtigheidsnet voordat Wave 2/3 plaatsvinden.

Stappen:

  1. CLI-export: bw login → bw unlock → bw export --format encrypted_json --password "<sterkte-passphrase>" → uploaden naar R2 als vaultwarden/encrypted-export-YYYYMMDD.json
  2. Encrypted file ook kopiëren naar offline USB stick. Stick fysiek opbergen op extern adres (kluis of vertrouwd familieadres).
  3. Encryptie-passphrase noteren op papier in dezelfde kluis (of in Vaultwarden zelf — paradox).
  4. Master password van Vaultwarden noteren op papier in kluis.
  5. Maandelijkse herhaling automatiseren via LaunchAgent + telegram-bericht "vault-export YYYY-MM uploaded".

Verificatie: - ✓ Test-restore op een tijdelijke Vaultwarden-instance (Docker lokaal) werkt - ✓ R2 snapshot zichtbaar via aws s3 ls (rclone of wrangler r2)

Rollback: geen — additief, niets te rollbacken.


Wave 2 — Mac mini server prep

Doel: Mac mini klaar maken om Docker-services te hosten op productie-niveau. Reverse proxy + Cloudflare Tunnel + persistent storage.

Stappen:

  1. Docker Desktop opstarten en in PATH brengen - Auto-start bij login configureren - Resources: 8 GB RAM toewijzen, 4 cores
  2. Tailscale running zetten (huidige error-state oplossen)
  3. Folder layout vaststellen: /Users/michelhelsdingen/Docker/{vaultwarden,paperless,...} voor service-volumes
  4. Cloudflare Tunnel container installeren met eigen tunnel-ID - Maak nieuwe tunnel "macmini-services" via CF dashboard of cloudflared tunnel create macmini-services - Token in macOS Keychain - Container start via Docker Compose, auto-restart unless-stopped
  5. Caddy als reverse proxy in Docker (eenvoudig automatisch HTTPS via tunnel) — of route alles via cloudflared tunnel ingress (geen Caddy nodig)
  6. Test met dummy-service: nginx-test container achter nginx-test.helsdingen.com route

Verificatie: - ✓ Cloudflare dashboard toont actieve tunnel - ✓ Test-route bereikt nginx-test container vanaf publiek internet via CF Access - ✓ Mac mini reboot test: alle services starten weer automatisch

Rollback: Docker stoppen + tunnel verwijderen. Geen impact op Plex VPS.


Wave 3 — Vaultwarden migratie

Doel: vault.helsdingen.com draait op Mac mini, plex Vaultwarden uitgezet maar containers nog aanwezig (1 week buffer).

Stappen:

  1. Snapshot maken op plex VPS: docker stop vaultwarden && tar czf /mnt/docker/backups/vaultwarden-pre-migration-YYYYMMDD.tgz /mnt/docker/vaultwarden/
  2. Sync naar Mac mini: rsync -avz plex:/mnt/docker/vaultwarden/ ~/Docker/vaultwarden/
  3. Mac mini Vaultwarden Docker compose: - Image vaultwarden/server:1.35.6 (zelfde versie als plex) - Volume mount op ~/Docker/vaultwarden - Environment variables overnemen (admin token, signups disabled, etc.) - Tunnel-ingress route: vault.helsdingen.comhttp://vaultwarden:80
  4. Start Mac mini container met aparte test-host eerst: vault-test.helsdingen.com
  5. Login-test, vault decrypt, browser sync, mobile sync (iOS Bitwarden app)
  6. Stop plex Vaultwarden container (niet rm)
  7. DNS swap: vault.helsdingen.com CNAME wijzigen van plex-tunnel naar mac mini-tunnel
  8. Test live na propagation
  9. Wachttijd 7 dagen voordat plex Vaultwarden container verwijderd wordt
  10. Encrypted export pipeline (Wave 1) updaten om Mac mini's volume te exporteren

Verificatie: - ✓ Login succes op vault.helsdingen.com via desktop browser, mobiele app, en bw CLI - ✓ Recent items zichtbaar (geen lege vault) - ✓ Sync van een nieuw item werkt op alle devices - ✓ Mac mini reboot test: Vaultwarden komt weer up

Rollback: - DNS-swap terug naar plex (TTL 5 min) → plex container start opnieuw - Volume op Mac mini blijft als referentie, geen data-verlies


Wave 4 — Paperless migratie

Urgentie: laag (SPOF is gemitigeerd 2026-05-11 via 3-laags backup, Wave 0.8). Verhuizen is nu een nice-to-have voor ownership/CF-consolidatie, niet voor risico-mitigatie.

Doel: docs.helsdingen.com draait op Mac mini incl. mail-fetcher en gotenberg/tika. NAS-replicatie + R2 encrypted blijven als backup-lagen.

Stappen:

  1. Snapshot op plex: docker compose -f /mnt/docker/paperless/docker-compose.yml stop && tar czf paperless-pre-migration.tgz /mnt/docker/paperless/
  2. Rsync volume naar Mac mini: rsync -avz plex:/mnt/docker/paperless/ ~/Docker/paperless/
  3. Mac mini Docker Compose met identieke services (paperless, redis, gotenberg, tika, mail-fetcher)
  4. Mail-fetcher: .env-mailfetcher overzetten, mail-fetcher.py ook (incl. recente subject-skip update)
  5. Tunnel route: docs.helsdingen.comhttp://paperless:8000
  6. Test-host eerst: docs-test.helsdingen.com
  7. Verifieer nieuwe scan@ mail wordt verwerkt door Mac mini-mail-fetcher
  8. NAS-replicatie inrichten: - rsync van ~/Docker/paperless/ naar terror:/volume1/docker/paperless-mirror/ om 04:00 nightly - Plus encrypted snapshot naar R2 (restic)
  9. DNS swap docs.helsdingen.com
  10. Buffer 7 dagen, dan plex containers stoppen

Verificatie: - ✓ Web-UI laadt op docs.helsdingen.com - ✓ Bestaande documenten zichtbaar - ✓ Nieuwe mail naar scan@ wordt geclassificeerd binnen 5 minuten - ✓ Cron-backup dump naar terror succesvol (eerste run handmatig triggeren) - ✓ R2 snapshot zichtbaar

Rollback: - DNS terug naar plex - Mail-fetcher op plex weer starten


Wave 5 — soulwise-site naar Cloudflare Pages

Doel: Coolify-deploy soulwise-site (nginx static op soulwiseapp.com) verhuizen naar CF Pages. Eerste warm-up voor dekei1.

Stappen:

  1. Clone michelhelsdingen/soulwise repo lokaal (als nog niet aanwezig)
  2. Bouw lokaal als nginx serveerbare output (dist/ of public/)
  3. CF Pages project aanmaken: soulwise-site
  4. Custom domain soulwiseapp.com + www.soulwiseapp.com
  5. DNS records swappen
  6. Verifieer site online
  7. Coolify deployment uitzetten en verwijderen
  8. GitHub Actions of CF Git-integratie inrichten voor auto-deploy bij push naar main

Verificatie: - ✓ soulwiseapp.com responds 200 OK - ✓ Visuele check: site identiek aan voor migratie - ✓ Auto-deploy test: push naar main triggert nieuwe deploy

Rollback: - DNS terug naar plex (Coolify deployment niet verwijderen tot stabiel)


Wave 6 — dekei1 migratie

Doel: dekei1.ltcdekei.nl van Coolify Next.js + self-hosted Supabase af.

Eerst beslissing nemen voor de Postgres-laag. Drie opties:

Optie Hosting Migratie-werk Maandelijkse kosten Geschikt voor
A. CF Pages + Supabase hosted (Frankfurt) CF + Supabase medium (DB-export/import) gratis tier mogelijk meest pragmatisch, snelst
B. CF Pages + D1 volledig CF hoog (schema-rewrite, geen pgvector etc.) gratis ruim als app simpel genoeg is
C. Mac mini Docker (Next + Postgres beide lokaal) eigen laag, lift-and-shift stroom als je self-host wilt blijven

Aanbeveling: A mits app niet veel Postgres-extensies gebruikt. Eerst auditeren.

Stappen (uitgaande van A):

  1. Repo audit: michelhelsdingen/dekei1. Lijst Postgres extensies, RLS policies, Storage gebruik
  2. Supabase Frankfurt hosted project: bestaande LTC project gebruiken of nieuw aanmaken
  3. Schema dump van self-hosted: pg_dump van Supabase-db container, restore in hosted project via Studio of psql
  4. Storage migratie (indien gebruikt): rsync MinIO bucket → Supabase Storage of R2
  5. App rebuild met nieuwe SUPABASE_URL en keys, deploy naar CF Pages (Next.js SSR via @cloudflare/next-on-pages)
  6. Custom domain dekei1.ltcdekei.nl
  7. Acceptatietest met klant Theo
  8. Coolify deployment uit, self-hosted Supabase containers nog niet aanraken (Wave 7)

Verificatie: - ✓ Site live op CF Pages - ✓ DB queries werken (login, lijst tonen, schrijven) - ✓ Klant heeft minimaal 7 dagen kunnen testen - ✓ Geen errors in Sentry/CF logs

Rollback: - DNS terug naar plex - Self-hosted Supabase blijft in originele staat tot Wave 7


Wave 7 — plex VPS schoonmaak

Doel: Coolify uitzetten, self-hosted Supabase weg, Happy stack uit, irrelevante containers opruimen. plex VPS draait alleen wat daar logisch hoort.

Eindstaat plex VPS containers: - Immich (server, ML, postgres, redis) - Jellyfin (zit niet in huidige container-list — checken of het via Coolify draaide) - Uptime Kuma - Watchtower - Glances - Portainer (nog overwegen of nodig) - Cloudflared tunnel (alleen voor Immich + Jellyfin) - Optioneel: Homepage, change-detection, AI-news-bot, MEXC bot

Stappen:

  1. Coolify uitzetten: docker compose -f /coolify/source/docker-compose.yml down
  2. Self-hosted Supabase 12 containers down + volumes archiveren naar tar in /mnt/docker/backups/
  3. Happy server stack down (4 containers)
  4. n8n: blijft draaien op plex of migreren naar Mac mini? Onderdeel van beslissing in Wave 6 (gerelateerd aan dekei1?)
  5. Cloudflared config updaten — alleen routes overhouden voor diensten die nog op plex draaien
  6. Check disk usage: zou ~50 GB moeten vrijkomen door Supabase + Coolify cleanup
  7. Watchtower configuratie verifiëren

Verificatie: - ✓ Immich + Jellyfin werken op respectievelijke routes - ✓ Geen 404 of broken routes via cloudflared - ✓ disk usage /mnt/docker ligt onder 100 GB

Rollback: containers weer starten (volumes blijven), of restore tar.


Wave 8 — NAS audit + Time Machine via Mac mini

Doel: Beslissen of NAS blijft of weggaat. Time Machine van externe SSD af + naar Mac mini SMB share als secondaire bestemming.

Stappen:

  1. Audit /volume1/homes (2.7 TB): - Inhoud per top-level folder bepalen - Klasificeer: weggooien / R2 archive / Mac mini lokaal / NAS behouden - Dit kan dagen aan triage zijn — niet één sessie
  2. Audit Plex media op NAS (5.8 GB): verhuizen naar plex VPS Jellyfin of weggooien
  3. Surveillance migratie afhankelijk van Wave 9 (camera). Tot dan blijft NAS leven.
  4. Time Machine secondary destination: - Op Mac mini: enable File Sharing + maak TimeMachine share aan (SMB) - Configureer als TM-bestemming in Mac mini's /etc/backup-config - Op MacBook: Systeeminstellingen → Time Machine → Voeg Mac mini toe als 2e doel naast externe SSD
  5. NAS-beslissing: - Als surveillance migreert (Wave 9): NAS leeg en verkopen - Als surveillance blijft: NAS behouden alleen voor camera, andere shares uit

Verificatie: - ✓ Time Machine schrijft naar zowel SSD als Mac mini bij wekelijkse backup - ✓ Geen andere services dependen op NAS shares meer (audit)

Rollback: NAS niet wegdoen voordat 30 dagen volledig draait zonder NAS-toegang.


Wave 9 — Camera transitie (geparkeerd)

Status: Wacht op beslissing. Vrouw gebruikt DS Cam (Synology mobile app). Beelden moeten ergens.

Opties bij beslissing:

Bij C heeft NUC voldoende capaciteit (cams-lxc gebruikt al 1 GB RAM), maar disk-IO voor opnames is een vraag.

Geen actie tot Michel beslist.


Cross-cutting verwijzing