Litestream replication to the NAS and nightly media backup
Add a litestream service (0.5.17) to the compose stack that streams /data/mealquest.db over SFTP to the Synology NAS with a dedicated key, daily snapshots kept for a week. Add a systemd timer that rsyncs the media directory to the NAS nightly at 03:30. Task: d2f2e87e-5114-4ef1-93ab-360ffb5c009e Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -46,6 +46,42 @@ services:
|
||||
cap_drop:
|
||||
- ALL
|
||||
|
||||
# Litestream streams the database to the Synology NAS over SFTP
|
||||
# (config: deploy/litestream.yml). The SFTP private key
|
||||
# deploy/secrets/litestream_key is a dedicated keypair, owned by
|
||||
# 10001:10001 with mode 0600 like the other secrets.
|
||||
#
|
||||
# The data volume is mounted read-write on purpose: Litestream writes its
|
||||
# tracking tables into the database and checkpoints the WAL. It runs as
|
||||
# the app's own user (owner of the volume) with a read-only root
|
||||
# filesystem, no privilege escalation and no Linux capabilities.
|
||||
#
|
||||
# Restore (app stopped; --no-deps so the app does not start and create a
|
||||
# fresh database first):
|
||||
# docker compose -f deploy/compose.yaml run --rm --no-deps litestream \
|
||||
# restore -config /etc/litestream.yml -o /data/mealquest.db /data/mealquest.db
|
||||
litestream:
|
||||
image: litestream/litestream:0.5.17
|
||||
user: "10001:10001"
|
||||
restart: unless-stopped
|
||||
command: ["replicate", "-config", "/etc/litestream.yml"]
|
||||
depends_on:
|
||||
mealquest:
|
||||
condition: service_healthy
|
||||
volumes:
|
||||
- mealquest-data:/data
|
||||
- ./litestream.yml:/etc/litestream.yml:ro
|
||||
- ./secrets/litestream_key:/etc/litestream/sftp_key:ro
|
||||
networks:
|
||||
- mealquest
|
||||
read_only: true
|
||||
tmpfs:
|
||||
- /tmp
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
cap_drop:
|
||||
- ALL
|
||||
|
||||
secrets:
|
||||
secret_key:
|
||||
file: ./secrets/secret_key
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
# Litestream continuous backup of the MealQuest SQLite database (Feature
|
||||
# 07). The ``litestream`` compose service mounts the same named volume as
|
||||
# the app and streams every committed change to the Synology NAS over
|
||||
# SFTP, so the backup lives outside the Docker host.
|
||||
#
|
||||
# Nothing here is secret. The SFTP private key is a dedicated, git-ignored
|
||||
# keypair (deploy/secrets/litestream_key) mounted read-only by compose at
|
||||
# /etc/litestream/sftp_key; its public half is in
|
||||
# joshua@10.60.1.50:~/.ssh/authorized_keys. ``host-key`` pins the NAS's
|
||||
# ECDSA host key, the algorithm Litestream's SSH client negotiates with
|
||||
# this NAS.
|
||||
#
|
||||
# The Synology SFTP server is chrooted to the shared folders, so the
|
||||
# directory seen over SSH as /volume1/homes/Joshua/mealquest-backups is
|
||||
# /homes/Joshua/mealquest-backups over SFTP.
|
||||
|
||||
# Take a full snapshot daily and keep a week of snapshots plus the
|
||||
# incremental changes between them (point-in-time restore window).
|
||||
snapshot:
|
||||
interval: 24h
|
||||
retention: 168h
|
||||
|
||||
dbs:
|
||||
- path: /data/mealquest.db
|
||||
replicas:
|
||||
- type: sftp
|
||||
host: 10.60.1.50
|
||||
user: joshua
|
||||
path: /homes/Joshua/mealquest-backups
|
||||
key-path: /etc/litestream/sftp_key
|
||||
host-key: ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNujay9nYEiXuRQf4wDRDU1gp+CDvpeMdhuhC2XypIihzKGEtB654kwrUGfA2kDWAoSPGlmojeS3OIRtHPkj6GY=
|
||||
@@ -0,0 +1,13 @@
|
||||
# Runs the nightly MealQuest media backup (/usr/local/sbin/mealquest-media-backup).
|
||||
# Triggered by mealquest-media-backup.timer; run by hand with
|
||||
# systemctl start mealquest-media-backup
|
||||
# and read the result with
|
||||
# journalctl -u mealquest-media-backup
|
||||
[Unit]
|
||||
Description=Back up MealQuest media to the Synology NAS
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/sbin/mealquest-media-backup
|
||||
Executable
+40
@@ -0,0 +1,40 @@
|
||||
#!/bin/sh
|
||||
# Nightly copy of the MealQuest media directory (recipe and dinner photos)
|
||||
# to the Synology NAS. The database itself is replicated continuously by
|
||||
# Litestream; this covers the files beside it.
|
||||
#
|
||||
# rsync mirrors the media directory onto .../mealquest-backups/media/ only:
|
||||
# both paths end in a slash, so --delete removes files from that target
|
||||
# directory and nothing outside it. A missing source directory (no media
|
||||
# uploaded yet) is reported and skipped, never synced, so an empty source
|
||||
# can never wipe the remote copy. Re-runs transfer only what changed.
|
||||
#
|
||||
# The volume is writable by the app container, so a media path that is a
|
||||
# symlink is refused: root's rsync would otherwise follow it and copy
|
||||
# whatever it points at. Symlinks inside the directory are copied as links
|
||||
# (-a), never followed.
|
||||
#
|
||||
# Connects with the dedicated MealQuest key; the NAS host key must already
|
||||
# be in root's known_hosts (StrictHostKeyChecking=yes fails otherwise).
|
||||
# Installed as /usr/local/sbin/mealquest-media-backup; output goes to the
|
||||
# journal via mealquest-media-backup.service.
|
||||
set -eu
|
||||
|
||||
src=/var/lib/docker/volumes/mealquest-data/_data/media/
|
||||
dest=joshua@10.60.1.50:/volume1/homes/Joshua/mealquest-backups/media/
|
||||
key=/home/overseer/mealquest/deploy/secrets/litestream_key
|
||||
|
||||
if [ -L "${src%/}" ]; then
|
||||
echo "mealquest-media-backup: ${src%/} is a symlink; refusing to back it up" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -d "$src" ]; then
|
||||
echo "mealquest-media-backup: no media directory at $src yet; nothing to back up"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
rsync -a --delete --stats \
|
||||
-e "ssh -i $key -o IdentitiesOnly=yes -o BatchMode=yes -o StrictHostKeyChecking=yes" \
|
||||
"$src" "$dest"
|
||||
echo "mealquest-media-backup: synced $src to $dest"
|
||||
@@ -0,0 +1,11 @@
|
||||
# Nightly trigger for mealquest-media-backup.service. Persistent= runs a
|
||||
# missed backup at the next boot.
|
||||
[Unit]
|
||||
Description=Nightly MealQuest media backup
|
||||
|
||||
[Timer]
|
||||
OnCalendar=*-*-* 03:30:00
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
Reference in New Issue
Block a user