Pi: minimal self-contained compose project (D-125)
The Kvitt Pi runs Kvitt from /home/jacobhagstedt/kvitt: compose.yaml, .env, secrets/ and relative bind mounts ./data, ./backups, ./import. Both containers run as the owner's uid/gid (KVITT_UID, KVITT_GID). No install script, no /opt, no Docker daemon or system changes; operations are plain docker compose commands (README "Pi: what the owner does", RESTORE.md). Removed install.sh, deploy.sh, bootstrap.sh, backup-now.sh, install-apk.sh, lib.sh. .env holds only the keys compose.yaml reads; RCLONE_REMOTE is fixed to gdrive: in compose.yaml. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
This commit is contained in:
+28
-26
@@ -1,14 +1,13 @@
|
||||
# Restore Kvitt from a Drive backup (SPEC §10.9)
|
||||
|
||||
Run on the Kvitt Pi, in `/opt/kvitt`, as the owner's user. Every command uses the running `backup` container,
|
||||
which has `sqlite3`, `rclone`, the Drive credentials and the `data` and `backups` volumes, and runs as uid 10001,
|
||||
the owner of the server's files. Nothing is deleted: the current database and pictures are moved aside.
|
||||
Do not run it around 03:30, when the nightly backup runs.
|
||||
Run on the Kvitt Pi, in `~/kvitt` (`/home/jacobhagstedt/kvitt`), as `jacobhagstedt`. The files in `data/` and
|
||||
`backups/` are yours (both containers run as your uid, D-125), so they are moved with plain `mv` and `cp`; only
|
||||
`rclone` and `sqlite3` come from the running `backup` container. Nothing is deleted: the current database and
|
||||
pictures are moved aside. Do not run it around 03:30, when the nightly backup runs.
|
||||
|
||||
```bash
|
||||
cd /opt/kvitt
|
||||
B="docker compose exec -T backup sh -c"
|
||||
FOLDER=$(sed -n 's/^RCLONE_FOLDER_ID=//p' .env); REMOTE=$(sed -n 's/^RCLONE_REMOTE=//p' .env)
|
||||
cd ~/kvitt
|
||||
FOLDER=$(sed -n 's/^RCLONE_FOLDER_ID=//p' .env)
|
||||
```
|
||||
|
||||
## 1. Stop the server
|
||||
@@ -22,26 +21,29 @@ docker compose stop server
|
||||
List what is on Drive (newest last) and pick a stamp:
|
||||
|
||||
```bash
|
||||
$B 'c=$(mktemp); cp /config/rclone/rclone.conf "$c"; RCLONE_CONFIG=$c rclone lsf "$1" --drive-root-folder-id "$2" --files-only; rm -f "$c"' sh "$REMOTE" "$FOLDER" | sort
|
||||
docker compose exec -T backup sh -c 'c=$(mktemp); cp /config/rclone/rclone.conf "$c"; RCLONE_CONFIG=$c rclone lsf gdrive: --drive-root-folder-id "$1" --files-only; rm -f "$c"' sh "$FOLDER" | sort
|
||||
STAMP=20261102T013000Z # the stamp of the pair you chose
|
||||
```
|
||||
|
||||
Download both files into the `backups` volume:
|
||||
Download both files into `backups/restore/`:
|
||||
|
||||
```bash
|
||||
$B 'set -e; mkdir -p /backups/restore; c=$(mktemp); cp /config/rclone/rclone.conf "$c"
|
||||
for f in "kvitt-$3.sqlite.gz" "pictures-$3.tar.gz"; do
|
||||
RCLONE_CONFIG=$c rclone copy "$1$f" /backups/restore/ --drive-root-folder-id "$2"
|
||||
done; rm -f "$c"; ls -l /backups/restore' sh "$REMOTE" "$FOLDER" "$STAMP"
|
||||
mkdir -p backups/restore
|
||||
docker compose exec -T backup sh -c 'set -e; c=$(mktemp); cp /config/rclone/rclone.conf "$c"
|
||||
for f in "kvitt-$2.sqlite.gz" "pictures-$2.tar.gz"; do
|
||||
RCLONE_CONFIG=$c rclone copy "gdrive:$f" /backups/restore/ --drive-root-folder-id "$1"
|
||||
done; rm -f "$c"' sh "$FOLDER" "$STAMP"
|
||||
ls -l backups/restore
|
||||
```
|
||||
|
||||
(A file downloaded in the browser instead: copy it to the Pi with `scp`, then
|
||||
`docker compose cp <file> backup:/backups/restore/`.)
|
||||
(A file downloaded in the browser instead: copy it from the Mac with
|
||||
`scp <file> jacobhagstedt@<kvitt-pi-address>:kvitt/backups/restore/`.)
|
||||
|
||||
## 3. Unpack and check
|
||||
|
||||
```bash
|
||||
$B 'set -e; cd /backups/restore; gunzip -f "kvitt-$1.sqlite.gz"; sqlite3 "kvitt-$1.sqlite" "PRAGMA integrity_check"' sh "$STAMP"
|
||||
gunzip -f "backups/restore/kvitt-$STAMP.sqlite.gz"
|
||||
docker compose exec -T backup sqlite3 "/backups/restore/kvitt-$STAMP.sqlite" "PRAGMA integrity_check"
|
||||
```
|
||||
|
||||
It must print `ok`. Anything else: stop here and pick another backup.
|
||||
@@ -49,13 +51,13 @@ It must print `ok`. Anything else: stop here and pick another backup.
|
||||
## 4. Move the current data aside and put the backup in place
|
||||
|
||||
```bash
|
||||
$B 'set -e; aside="/data/pre-restore-$(date -u +%Y%m%dT%H%M%SZ)"; mkdir "$aside"
|
||||
for f in /data/kvitt.sqlite /data/kvitt.sqlite-wal /data/kvitt.sqlite-shm /data/pictures; do
|
||||
[ -e "$f" ] && mv "$f" "$aside/"
|
||||
done
|
||||
cp "/backups/restore/kvitt-$1.sqlite" /data/kvitt.sqlite
|
||||
tar -xzf "/backups/restore/pictures-$1.tar.gz" -C /data
|
||||
echo "previous data kept in $aside"; ls -l /data' sh "$STAMP"
|
||||
aside="data/pre-restore-$(date -u +%Y%m%dT%H%M%SZ)"; mkdir "$aside"
|
||||
for f in data/kvitt.sqlite data/kvitt.sqlite-wal data/kvitt.sqlite-shm data/pictures; do
|
||||
[ -e "$f" ] && mv "$f" "$aside/"
|
||||
done
|
||||
cp "backups/restore/kvitt-$STAMP.sqlite" data/kvitt.sqlite
|
||||
tar -xzf "backups/restore/pictures-$STAMP.tar.gz" -C data
|
||||
echo "previous data kept in $aside"; ls -l data
|
||||
```
|
||||
|
||||
## 5. New sync epoch
|
||||
@@ -84,6 +86,6 @@ epoch and re-pushes the phone's data (if nothing seems to happen, close the app
|
||||
|
||||
## Afterwards
|
||||
|
||||
When the phones agree, the downloaded files can go:
|
||||
`$B 'rm -r /backups/restore'`. The `/data/pre-restore-<stamp>/` folder stays until you decide otherwise
|
||||
(`$B 'ls /data'` shows it). Run `/opt/kvitt/backup-now.sh` once to put the restored state on Drive.
|
||||
When the phones agree, the downloaded files can go: `rm -r backups/restore`. The `data/pre-restore-<stamp>/`
|
||||
folder stays until you decide otherwise. Run `docker compose exec backup /backup.sh` once to put the restored state
|
||||
on Drive.
|
||||
|
||||
Reference in New Issue
Block a user