83 lines
3.6 KiB
Bash
83 lines
3.6 KiB
Bash
#!/bin/bash
|
|
# Бэкап серверов на rpi (вариант «малина забирает»): ssh к серверу → forced command отдаёт
|
|
# tar|zstd|age, зашифрованный ОТКРЫТЫМ ключом. Закрытого ключа на малине нет — расшифровать
|
|
# здесь ничего нельзя (ключ у владельца: Mac ~/.config/age/servers-backup.key + резервная копия).
|
|
#
|
|
# servers-backup all — ежедневный прогон (таймер): fr1 daily, ru1 daily, по воскресеньям fr1 weekly
|
|
# servers-backup verify — сверка sha256 всех хранимых архивов (порча на диске)
|
|
#
|
|
# Хранение: <host>/daily — 14 последних, <host>/weekly — 8, <host>/monthly — 12 (хардлинк
|
|
# первого daily за месяц, места не занимает).
|
|
set -uo pipefail
|
|
ROOT=/mnt/backup/servers
|
|
KEY=/etc/server-backup/id_ed25519
|
|
KH=/etc/server-backup/known_hosts
|
|
declare -A TARGET=([fr1]=rus@161.97.93.252 [ru1]=mik@85.198.109.86)
|
|
declare -A KEEP=([daily]=14 [weekly]=8 [monthly]=12)
|
|
|
|
log() { echo "$*"; }
|
|
|
|
pull() { # pull <host> <profile>
|
|
local host=$1 profile=$2 dir="$ROOT/$1/$2" ts f rc want got
|
|
mkdir -p "$dir"
|
|
ts=$(date +%F_%H%M); f="$dir/$host-$profile-$ts.tar.zst.age"
|
|
ssh -i "$KEY" -o UserKnownHostsFile="$KH" -o StrictHostKeyChecking=yes -o BatchMode=yes \
|
|
-o ConnectTimeout=30 -o ServerAliveInterval=30 -o ServerAliveCountMax=6 \
|
|
"${TARGET[$host]}" "$profile" > "$f.part" 2> "$f.stderr"
|
|
rc=$?
|
|
want=$(grep '^SHA256 ' "$f.stderr" | tail -1 | cut -d' ' -f2)
|
|
got=$(sha256sum "$f.part" | cut -d' ' -f1)
|
|
if [ $rc -ne 0 ] || [ -z "$want" ] || [ "$want" != "$got" ] || [ ! -s "$f.part" ]; then
|
|
log "FAIL $host/$profile: ssh rc=$rc, sha сервера=${want:-нет}, sha принятого=$got"
|
|
sed 's/^/ stderr: /' "$f.stderr" | tail -20
|
|
rm -f "$f.part" "$f.stderr"
|
|
return 1
|
|
fi
|
|
mv "$f.part" "$f"
|
|
echo "$got $(basename "$f")" > "$f.sha256"
|
|
grep -v '^SHA256 ' "$f.stderr" | tail -50 > "$f.log"; [ -s "$f.log" ] || rm -f "$f.log"
|
|
rm -f "$f.stderr"
|
|
log "OK $host/$profile: $(du -h "$f" | cut -f1) $(basename "$f")"
|
|
# первый daily за месяц → monthly (хардлинк)
|
|
if [ "$profile" = daily ] && ! ls "$ROOT/$host/monthly/$host-daily-$(date +%Y-%m)"-* >/dev/null 2>&1; then
|
|
mkdir -p "$ROOT/$host/monthly"; ln "$f" "$f.sha256" "$ROOT/$host/monthly/"
|
|
fi
|
|
}
|
|
|
|
prune() {
|
|
local d keep
|
|
for d in "$ROOT"/*/{daily,weekly,monthly}; do
|
|
[ -d "$d" ] || continue
|
|
keep=${KEEP[$(basename "$d")]}
|
|
ls -1t "$d"/*.age 2>/dev/null | tail -n +$((keep + 1)) | while read -r old; do
|
|
rm -f "$old" "$old.sha256" "$old.log"; log "prune $(basename "$old")"
|
|
done
|
|
done
|
|
}
|
|
|
|
verify() {
|
|
local bad=0 n=0 s
|
|
while read -r s; do
|
|
n=$((n + 1))
|
|
(cd "$(dirname "$s")" && sha256sum --quiet -c "$(basename "$s")") || { log "BAD $s"; bad=1; }
|
|
done < <(find "$ROOT" -name '*.sha256')
|
|
log "verify: проверено $n архивов, $([ $bad = 0 ] && echo 'всё цело' || echo 'ЕСТЬ ПОВРЕЖДЁННЫЕ')"
|
|
return $bad
|
|
}
|
|
|
|
mountpoint -q /mnt/backup || { echo "/mnt/backup не смонтирован — пропуск"; exit 1; }
|
|
install -d -m 700 "$ROOT"
|
|
case "${1:-all}" in
|
|
all)
|
|
rc=0
|
|
pull fr1 daily || rc=1
|
|
pull ru1 daily || rc=1
|
|
[ "$(date +%u)" = 7 ] && { pull fr1 weekly || rc=1; }
|
|
prune
|
|
[ "$(date +%u)" = 7 ] && { verify || rc=1; }
|
|
exit $rc ;;
|
|
verify) verify ;;
|
|
pull) pull "$2" "$3" ;;
|
|
*) echo "usage: servers-backup all|verify|pull <host> <profile>" >&2; exit 2 ;;
|
|
esac
|