C0: docs — add mealie borg restore how-to

Captures the procedure used to restore mealie's SQLite DB from a borgmatic
archive after the post-DR wipe: extract from borg, snapshot the wiped DB,
swap via a helper pod on the ReadWriteOnce PVC, fix UID 911 ownership.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erich Blume 2026-04-24 19:04:28 -07:00
commit 72b27b7fd2
2 changed files with 159 additions and 0 deletions

View file

@ -0,0 +1,157 @@
---
title: Restore Mealie from Borg
modified: 2026-04-24
last-reviewed: 2026-04-24
tags:
- how-to
- mealie
- backup
---
# Restore Mealie from Borg
How to restore [[mealie]]'s SQLite database from a [[borgmatic]] archive when data has been lost (e.g. PVC wiped, accidental deletion, post-DR rebuild).
## Prerequisites
- SSH access to [[indri]] (where borgmatic runs and stores k8s SQLite dumps)
- Mealie deployment present in the cluster (the PVC `mealie-data` exists in namespace `mealie`)
- Know which borg archive predates the data loss
## Procedure
### 1. Identify a Pre-Loss Archive
List archives and pick one before the incident:
```bash
ssh indri 'BORG_PASSCOMMAND="cat /Users/erichblume/.borg/config.yaml" \
/opt/homebrew/bin/borg list /Volumes/backups/borg | tail -30'
```
Compare dump sizes across archives if you're unsure when the loss happened — the daily borgmatic run captures `/Users/erichblume/.local/share/borgmatic/k8s-dumps/mealie.db`. A sudden drop in size signals the wipe:
```bash
ssh indri 'bash -c "BORG_PASSCOMMAND=\"cat /Users/erichblume/.borg/config.yaml\" \
/opt/homebrew/bin/borg list /Volumes/backups/borg::<archive-name> \
--pattern=+Users/erichblume/.local/share/borgmatic/k8s-dumps/mealie.db"'
```
### 2. Extract the Pre-Loss Dump
```bash
ssh indri 'mkdir -p ~/tmp/mealie-restore && cd ~/tmp/mealie-restore && \
BORG_PASSCOMMAND="cat /Users/erichblume/.borg/config.yaml" \
/opt/homebrew/bin/borg extract /Volumes/backups/borg::<archive-name> \
Users/erichblume/.local/share/borgmatic/k8s-dumps/mealie.db'
```
The file lands at `~/tmp/mealie-restore/Users/erichblume/.local/share/borgmatic/k8s-dumps/mealie.db` (borg preserves the full path).
### 3. Verify the Extracted DB
```bash
ssh indri 'sqlite3 ~/tmp/mealie-restore/Users/erichblume/.local/share/borgmatic/k8s-dumps/mealie.db \
"PRAGMA integrity_check; SELECT COUNT(*) FROM recipes; SELECT COUNT(*) FROM users;"'
```
Expect `ok` and non-zero recipe/user counts.
### 4. Snapshot the Current (Wiped) DB
Belt and suspenders — keep a copy of the live DB before overwriting, in case the restore goes wrong:
```bash
ssh indri 'bash -c "kubectl --context=minikube -n mealie exec deploy/mealie -- \
python3 -c \"import sqlite3; sqlite3.connect(\\\"/app/data/mealie.db\\\").backup(sqlite3.connect(\\\"/tmp/wiped-mealie.db\\\"))\" && \
POD=\$(kubectl --context=minikube -n mealie get pod -l app=mealie -o jsonpath=\"{.items[0].metadata.name}\") && \
kubectl --context=minikube cp mealie/\$POD:/tmp/wiped-mealie.db /Users/erichblume/tmp/mealie-restore/wiped-mealie.db"'
```
### 5. Scale Mealie Down
The PVC is `ReadWriteOnce`, so the helper pod can't mount it while mealie is running:
```bash
ssh indri 'kubectl --context=minikube -n mealie scale deploy/mealie --replicas=0 && \
kubectl --context=minikube -n mealie wait --for=delete pod -l app=mealie --timeout=60s'
```
### 6. Start a Helper Pod on the PVC
```bash
ssh indri 'bash -c "cat > /tmp/mealie-helper.yaml <<EOF
apiVersion: v1
kind: Pod
metadata:
name: mealie-restore
namespace: mealie
spec:
restartPolicy: Never
containers:
- name: helper
image: alpine:3.20
command: [\"sh\", \"-c\", \"sleep 3600\"]
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: mealie-data
EOF
kubectl --context=minikube apply -f /tmp/mealie-helper.yaml && \
kubectl --context=minikube -n mealie wait --for=condition=Ready pod/mealie-restore --timeout=60s"'
```
### 7. Copy and Swap the DB
```bash
ssh indri 'bash -c "kubectl --context=minikube cp \
/Users/erichblume/tmp/mealie-restore/Users/erichblume/.local/share/borgmatic/k8s-dumps/mealie.db \
mealie/mealie-restore:/data/mealie.db.restored && \
kubectl --context=minikube -n mealie exec mealie-restore -- sh -c \
\"mv /data/mealie.db /data/mealie.db.wiped && mv /data/mealie.db.restored /data/mealie.db && chown 911:911 /data/mealie.db\""'
```
Mealie's container runs as UID 911. `kubectl cp` preserves the host UID (501 on indri), so the `chown` is required — without it, mealie may fail to write to the DB.
### 8. Tear Down the Helper and Scale Back Up
```bash
ssh indri 'kubectl --context=minikube delete pod -n mealie mealie-restore --wait=true && \
kubectl --context=minikube -n mealie scale deploy/mealie --replicas=1 && \
kubectl --context=minikube -n mealie wait --for=condition=Available deploy/mealie --timeout=120s'
```
### 9. Verify the Restore is Live
```bash
ssh indri 'bash -c "kubectl --context=minikube -n mealie exec deploy/mealie -- \
python3 -c \"import sqlite3; c=sqlite3.connect(\\\"/app/data/mealie.db\\\"); \
print(\\\"recipes:\\\", c.execute(\\\"select count(*) from recipes\\\").fetchone()[0]); \
print(\\\"users:\\\", c.execute(\\\"select count(*) from users\\\").fetchone()[0])\""'
```
The counts should match what you saw in step 3.
### 10. Clean Up
Once mealie is working and you've confirmed the data, remove the in-PVC safety copy:
```bash
ssh indri 'kubectl --context=minikube -n mealie exec deploy/mealie -- rm -f /app/data/mealie.db.wiped'
```
Leave the host-side copy at `~/tmp/mealie-restore/wiped-mealie.db` until the next borgmatic run captures the restored state, in case you need to roll back.
## Notes
- The active kubectl context for mealie is `minikube` on [[indri]] until the [[ringtail]] migration completes. Update the `--context` flag if mealie has moved.
- OIDC sign-ins after the wipe may have created new user rows; the restored DB will replace them. Affected users will sign in fresh and Authentik will re-link them on next login.
## Related
- [[mealie]] — Service reference
- [[borgmatic]] — Backup tooling
- [[restore-1password-backup]] — Similar restore pattern for 1Password

View file

@ -46,6 +46,8 @@ OIDC via [[authentik]] using a confidential client. Client secret stored in 1Pas
SQLite database backed up via [[borgmatic]]'s `before_backup` hook. Borgmatic runs `kubectl exec` to create a safe `.backup` copy (via Python's `sqlite3` module), then `kubectl cp` to the host. The dump lands in `~/.local/share/borgmatic/k8s-dumps/mealie.db` and is included in both local (sifaka) and offsite (BorgBase) backups.
To restore from a borg archive, see [[restore-from-borg]].
## Networking
| Endpoint | Reachable from |