49 lines
3.3 KiB
Bash
49 lines
3.3 KiB
Bash
#!/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
|