Files
mercury.kennethreitz.org/docs/photos-to-objectstorage-migration.md
2026-06-06 13:00:34 -04:00

108 lines
6.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Migration: photos.kennethreitz.org media → Hetzner Object Storage
**Goal:** move the `exiftree-media` photo library (≈166 GiB, ~100k objects) off the
expensive 1 TB Hetzner Cloud **volume** (MinIO) and onto cheap **Hetzner Object
Storage** (`kr-photos` bucket), then reclaim ~168 GB of volume space. Serving option
chosen: **(a) serve straight from the Hetzner bucket URL** (path-style, public-read).
*Status started 2026-06-05. This doc is the resume-from-interruption runbook.*
## Why
The volume costs €76.70/mo (1000 GB @ €0.0767/GB) — more than the server. The photo
media doesn't need fast block storage; it's HTTP-served static objects (it lived on
Tigris originally). Object storage is ~€6/mo. ⚠️ The old Tigris fallback is GONE
(bucket deleted) — so during this migration, the MinIO copy on the volume is the
**only complete copy** of the photo library until the Storage Box archive finishes.
**Do not delete the MinIO copy until the Hetzner copy is verified complete.**
## Endpoints & creds (also in repo `.env` / `/tmp/exiftree_creds.txt` on the Mac)
- **Source — MinIO media** (`minio-q3xgqx-minio-1:9000`, internal): bucket
`exiftree-media`, key `HU3BA5QMX2AZ67LYSKOQ`, secret in Dokploy/creds file.
Public today via `https://img.kennethreitz.org` (Traefik → MinIO on the volume).
- **Target — Hetzner Object Storage**: endpoint `https://hel1.your-objectstorage.com`,
region `hel1` (MUST set region or S3 ops fail), bucket `kr-photos`,
key `78BZ6Q8VF2I5IJF3PJEU`, secret `gQq8i4ZxpiGyaw7S4KvsF3utN79E421LUvDmSdWg`.
Bucket is set **public-read (download)**. Public URL pattern (verified 200):
`https://hel1.your-objectstorage.com/kr-photos/<key>`.
## Steps (checklist)
- [ ] **1. Copy** MinIO `exiftree-media` → Hetzner `kr-photos` (IN PROGRESS).
Runs as detached container `photos-to-hz` on mercury (rclone copy, idempotent —
safe to restart; re-skips done objects). ~27 GiB/hr (volume disk-throughput
capped; parallelism does not help), ~6h total.
- Immich heavy jobs are **PAUSED** during the copy (they read the same MinIO
objects and contend on volume throughput). Resume after — see step 6.
- Restart command if it dies: see "Copy command" below.
- [ ] **2. Verify** copy complete: object count + bytes match between
`media:exiftree-media` and `hz:kr-photos` (`rclone size` both). Must be equal
before proceeding.
- [ ] **3. Cutover photos app** — set these env vars on the photos compose
(`composeId WICLbVwy5JEbHz2SPb4tR`) and redeploy:
- `AWS_S3_ENDPOINT_URL=https://hel1.your-objectstorage.com`
- `AWS_STORAGE_BUCKET_NAME=kr-photos`
- `AWS_S3_REGION_NAME=hel1`
- `AWS_ACCESS_KEY_ID=78BZ6Q8VF2I5IJF3PJEU`
- `AWS_SECRET_ACCESS_KEY=gQq8i4ZxpiGyaw7S4KvsF3utN79E421LUvDmSdWg`
- `AWS_S3_CUSTOM_DOMAIN=hel1.your-objectstorage.com/kr-photos`
- `AWS_S3_ADDRESSING_STYLE=path`
- (keep DEBUG/SECRET_KEY/DATABASE_URL/CELERY_* unchanged)
- [ ] **4. Verify** photos.kennethreitz.org loads images (homepage img URLs should be
`https://hel1.your-objectstorage.com/kr-photos/...` and return 200).
- [ ] **5. Repoint Immich** photo-library mount: the host systemd unit
`minio-photos-mount.service` mounts `m:exiftree-media/originals` (MinIO). Re-point
it to `hz:kr-photos/originals` (swap the rclone env in the unit to the Hetzner
remote), `systemctl daemon-reload && systemctl restart minio-photos-mount`.
- [ ] **6. Resume Immich** jobs (paused in step 1): via API
`PUT /api/jobs/{thumbnailGeneration,smartSearch,faceDetection,metadataExtraction,library}`
with `{"command":"resume"}` (token = login me@kennethreitz.org / house pw).
- [ ] **7. Decommission MinIO media** ONLY after 34 verified: stop/remove the
`minio` (media) compose `UK8pWczw8d9GSmyLjZJiP`, then
`rm -rf /mnt/objects/minio` on the host → reclaims ~168 GB on the volume.
Update the media-minio inventory entry. (Note: `img.kennethreitz.org` and the
`media-minio.kennethreitz.org` console domains can be removed too.)
- [ ] **8. Optional next:** with 168 GB freed, the volume is over-sized; a later
shrink (migrate to a smaller volume) saves more — see the volume-migration notes.
Also drop the 291 GB iCloud Drive mirror off the volume once its Storage Box
archive completes.
## Copy command (to restart `photos-to-hz` if needed)
```sh
ssh root@mercury.kennethreitz.org "docker rm -f photos-to-hz 2>/dev/null
docker run -d --name photos-to-hz --network dokploy-network --entrypoint sh rclone/rclone:latest -c '
export RCLONE_CONFIG_MEDIA_TYPE=s3 RCLONE_CONFIG_MEDIA_PROVIDER=Minio RCLONE_CONFIG_MEDIA_ENDPOINT=http://minio-q3xgqx-minio-1:9000 RCLONE_CONFIG_MEDIA_ACCESS_KEY_ID=HU3BA5QMX2AZ67LYSKOQ RCLONE_CONFIG_MEDIA_SECRET_ACCESS_KEY=<media-secret>
export RCLONE_CONFIG_HZ_TYPE=s3 RCLONE_CONFIG_HZ_PROVIDER=Other RCLONE_CONFIG_HZ_REGION=hel1 RCLONE_CONFIG_HZ_LOCATION_CONSTRAINT=hel1 RCLONE_CONFIG_HZ_ENDPOINT=https://hel1.your-objectstorage.com RCLONE_CONFIG_HZ_ACCESS_KEY_ID=78BZ6Q8VF2I5IJF3PJEU RCLONE_CONFIG_HZ_SECRET_ACCESS_KEY=<hz-secret> RCLONE_CONFIG_HZ_NO_CHECK_BUCKET=true
rclone copy media:exiftree-media hz:kr-photos --transfers 32 --checkers 32 --s3-no-check-bucket -v --stats 1m --stats-one-line 2>&1
echo MIRROR_DONE_EXIT:\$?'"
```
## Rollback (if cutover breaks the site)
The MinIO copy is untouched until step 7. To roll back: revert the step-3 env vars to
the MinIO values (`AWS_S3_ENDPOINT_URL=http://minio-q3xgqx-minio-1:9000`,
`AWS_STORAGE_BUCKET_NAME=exiftree-media`, `AWS_S3_CUSTOM_DOMAIN=img.kennethreitz.org/exiftree-media`,
region `us-east-1`, MinIO key/secret) and redeploy. Photos serve from MinIO again.
## COMPLETED 2026-06-06
Steps 1-7 done: copy verified (100,632 objects identical), photos app cut over to
`hel1.your-objectstorage.com/kr-photos` (verified serving), Immich photo-library
mount repointed to `hz:kr-photos/originals`, storagebox-archive source repointed to
`hz:kr-photos`, MinIO media decommissioned, **167 GB reclaimed** (volume 473G→306G used).
Mid-migration the photos-backup vfs-cache filled root and broke Swarm — see the
incident note in server.md; recovered, and photos-backup cache now lives on the volume.
### Still pending (the actual €76.70 volume cost cut — step 8)
The volume is still 1 TB. To shrink it (option A: small volume for Immich), remaining:
- iCloud Drive mirror (291 G) must move off — needs its Storage Box archive COMPLETE
first (storagebox-archive reaches icloud-drive after kr-photos in its loop), then
repoint Filebrowser (drive.kennethreitz.org) to a Storage Box mount.
- photos-backup needs **Apple re-auth** (cookie corrupted in the incident):
`ssh -t root@mercury "docker run -it --rm -v /var/lib/icloudpd:/data icloudpd/icloudpd icloudpd --username me@kennethreitz.org --auth-only --cookie-directory /data/cookies"`
- Volume delete/recreate hits the 1 TB account quota — needs a Hetzner quota increase
for a SAFE side-by-side migrate (vs risky delete-first). Recommended given the incident.