Бэкап серверов fr1/ru1 на rpi: сервер шифрует age открытым ключом, малина только хранит
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
e645377916
commit
7d0a891793
@@ -0,0 +1,48 @@
|
||||
#!/bin/bash
|
||||
# Выгрузка бэкапа fr1 для rpi. Forced command в ~rus/.ssh/authorized_keys:
|
||||
# restrict,command="/home/rus/bin/backup-export" <ключ server-backup@rpi>
|
||||
# Клиент передаёт профиль: daily | weekly. В stdout — tar | zstd | age (шифрует ОТКРЫТЫМ
|
||||
# ключом, закрытый только у владельца вне малины — rpi хранит то, что сама прочитать не может).
|
||||
# В stderr последней строкой — "SHA256 <hex>" шифротекста: rpi сверяет, что дошло целиком.
|
||||
#
|
||||
# Читает root-файлы через docker (rus в группе docker = root-эквивалент), а не через sudo:
|
||||
# sudo на fr1 по паролю. Монтирование / только read-only.
|
||||
set -euo pipefail
|
||||
PUB=age1qfsgzyglv0vmq4kskg888hzgq032mry5kxz9kptkvzsrz0fz0v7q6kxwk2
|
||||
AGE=/home/rus/bin/age
|
||||
IMG=debian:stable-slim # GNU tar: --exclude/--warning, в alpine — busybox
|
||||
profile="${SSH_ORIGINAL_COMMAND:-daily}"
|
||||
tmp=$(mktemp -d /tmp/backup-export.XXXXXX)
|
||||
cleanup() { rm -rf "$tmp"; docker exec gitea rm -f /tmp/gitea-snap.db >/dev/null 2>&1 || true; }
|
||||
trap cleanup EXIT
|
||||
|
||||
case "$profile" in
|
||||
daily)
|
||||
# Gitea на SQLite: копировать живой gitea.db нельзя (рваная копия) — .backup даёт согласованный снимок
|
||||
docker exec -u 1000 gitea sqlite3 /data/gitea.db ".backup '/tmp/gitea-snap.db'"
|
||||
docker cp gitea:/tmp/gitea-snap.db "$tmp/gitea.db" >/dev/null
|
||||
paths=(host/etc host/srv/gitea host/usr/local host/home/rus host/var/spool/cron host/root snap)
|
||||
excl=(--exclude=host/srv/gitea/data/gitea.db --exclude='host/srv/gitea/data/gitea.db-*'
|
||||
--exclude=host/home/rus/backups --exclude=host/home/rus/.openclaw
|
||||
--exclude=host/home/rus/openclaw --exclude=host/home/rus/openclaw-work
|
||||
--exclude=host/home/rus/.cache --exclude=host/home/rus/.claude --exclude='*.bak-*' --exclude='*.tar.gz')
|
||||
;;
|
||||
weekly)
|
||||
paths=(host/var/lib/docker/volumes/claude-channels_claude-home
|
||||
host/var/lib/docker/volumes/monitoring_grafana_data
|
||||
host/var/lib/docker/volumes/monitoring_alertmanager_data
|
||||
host/var/lib/docker/volumes/monitoring_pushgateway_data
|
||||
host/home/tgclaude)
|
||||
excl=(--exclude=host/home/tgclaude/backups --exclude=host/home/tgclaude/openclaw-deploy
|
||||
--exclude=host/home/tgclaude/.local --exclude=host/home/tgclaude/.cache --exclude=host/home/tgclaude/chrome-libs)
|
||||
;;
|
||||
*) echo "профиль: daily|weekly" >&2; exit 2 ;;
|
||||
esac
|
||||
|
||||
# tar exit 1 = «файл изменился при чтении» (живые логи/репо) — не ошибка для бэкапа
|
||||
docker run --rm --network none -v /:/host:ro -v "$tmp":/snap:ro "$IMG" \
|
||||
sh -c 'tar --numeric-owner --ignore-failed-read --warning=no-file-changed -C / -cf - "$@"; rc=$?; [ $rc -le 1 ] || exit $rc' tar "${excl[@]}" "${paths[@]}" \
|
||||
| zstd -3 -T0 -q | "$AGE" -r "$PUB" | tee >(sha256sum > "$tmp/sum")
|
||||
# процесс-подстановка живёт отдельно от конвейера — ждём, пока sha256sum допишет итог
|
||||
for _ in $(seq 300); do [ -s "$tmp/sum" ] && break; sleep 0.1; done
|
||||
echo "SHA256 $(cut -d' ' -f1 "$tmp/sum")" >&2
|
||||
Reference in New Issue
Block a user