feat(backup): add rsync --link-dest off-host transport, restore docs

backup.sh now supports BACKUP_REMOTE_RSYNC alongside BACKUP_REMOTE.
The rsync path writes <root>/YYYY-MM-DD/<archive> on the target with
--link-dest pointing at the previous day's directory, so unchanged
archives become hard links and daily snapshots cost almost zero
extra bytes. BACKUP_REMOTE_SSH_KEY routes the rsync ssh leg to a
dedicated identity (e.g. rsync.net restricted accounts).

Timer moved to 03:00 Europe/Berlin (was 03:17 UTC) per #41.

docs/operations.md: full restore procedure (parallel stack first,
then atomic swap) plus the rsync vs rclone trade-off. Closes most
of #41 — the only remaining task is the operator's choice of
off-host target.
This commit is contained in:
2026-04-29 14:47:42 +02:00
parent aa6b5b7e36
commit 9eefece320
3 changed files with 114 additions and 11 deletions
+28 -1
View File
@@ -13,10 +13,18 @@
# Optional env:
# BACKUP_DIR where to write archives (default /var/backups/librenotes)
# BACKUP_REMOTE rclone target for off-site copy (e.g. s3:bucket/path)
# BACKUP_REMOTE_RSYNC rsync destination for hard-link-deduplicated
# off-host copy, e.g. backup@rsync.net:librenotes/
# Layout written at the target:
# <root>/YYYY-MM-DD/librenotes-<ts>.tar.gz
# Each run passes --link-dest=../<previous>/ so
# unchanged archives cost zero extra bytes.
# BACKUP_REMOTE_SSH_KEY path to a private SSH key used for the rsync
# leg (passed via -e "ssh -i <key>").
# BACKUP_VERSION version string written into info.txt
#
# The script is intentionally a single self-contained file so it
# can run on a minimal host with only sqlite3, tar, gzip, and
# can run on a minimal host with only sqlite3, tar, gzip, rsync, and
# (optionally) rclone.
set -euo pipefail
@@ -58,3 +66,22 @@ if [ -n "${BACKUP_REMOTE:-}" ]; then
rclone copy "$archive" "$BACKUP_REMOTE" --quiet
echo "uploaded to $BACKUP_REMOTE"
fi
if [ -n "${BACKUP_REMOTE_RSYNC:-}" ]; then
# Compute today / previous-day directory names. Layout at the
# remote target is <root>/YYYY-MM-DD/. We pass --link-dest pointing
# at yesterday's dir so unchanged archives become hard links —
# ~free for daily snapshots that are mostly identical.
today="$(date -u +%Y-%m-%d)"
yesterday="$(date -u -d 'yesterday' +%Y-%m-%d 2>/dev/null \
|| date -u -v-1d +%Y-%m-%d)"
ssh_opts=()
if [ -n "${BACKUP_REMOTE_SSH_KEY:-}" ]; then
ssh_opts=(-e "ssh -i $BACKUP_REMOTE_SSH_KEY -o StrictHostKeyChecking=accept-new")
fi
# Strip any trailing slash so we control the join.
remote="${BACKUP_REMOTE_RSYNC%/}"
rsync -a --link-dest="../$yesterday/" "${ssh_opts[@]}" \
"$archive" "$remote/$today/"
echo "rsync'd $archive -> $remote/$today/"
fi