rpi: ежемесячная выборочная перечитка файлов OneDrive с диска роутера (sha256) + алерты
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
629b11f03f
commit
d780c91bd5
@@ -43,6 +43,7 @@ OneDrive на внешний HDD. Мониторится с fr1 через pushg
|
|||||||
- `router-backup` (`stacks/rpi-backup/`, таймер 06:00, `flock`): в `router:rpi-backup/` — `onedrive/{archive,manifests,logs}` только `copy` (там ничего не удаляется), затем зеркало `onedrive/current` с `--max-delete 1000`, затем зеркало зашифрованных `servers/` с `--max-delete 50`. Порядок важен: удалённое из current к этому моменту уже скопировано в archive. Логи — `/mnt/backup/router-logs/`.
|
- `router-backup` (`stacks/rpi-backup/`, таймер 06:00, `flock`): в `router:rpi-backup/` — `onedrive/{archive,manifests,logs}` только `copy` (там ничего не удаляется), затем зеркало `onedrive/current` с `--max-delete 1000`, затем зеркало зашифрованных `servers/` с `--max-delete 50`. Порядок важен: удалённое из current к этому моменту уже скопировано в archive. Логи — `/mnt/backup/router-logs/`.
|
||||||
- ⚠️ `--max-delete` — предохранитель: если локально «пропадёт» много файлов (сбой диска/монтирования), копия на роутере не зачистится, а прогон упадёт с ошибкой → алерт `BackupFailed`.
|
- ⚠️ `--max-delete` — предохранитель: если локально «пропадёт» много файлов (сбой диска/монтирования), копия на роутере не зачистится, а прогон упадёт с ошибкой → алерт `BackupFailed`.
|
||||||
- По воскресеньям `verify`: сверка размеров деревьев + перечитать с роутера последний daily каждого сервера и сравнить sha256.
|
- По воскресеньям `verify`: сверка размеров деревьев + перечитать с роутера последний daily каждого сервера и сравнить sha256.
|
||||||
|
- **Ежемесячно (1-го, 08:00) `router-sample`**: случайные файлы OneDrive из `current/` и `archive/` старше суток, ~2 ГБ / до 300 шт., перечитываются с роутера и сверяются sha256 с HDD — ловит тихую порчу, которую сверка размеров не видит. Отсутствующий на роутере файл = ошибка. Итог — `backup_*{job="backup_router_sample"}` → `BackupFailed`, `BackupStale` (>35 дней). Проверено 2026-10-11: 20 из 20 скопированных файлов совпали.
|
||||||
- Мониторинг: `router_mount_up` и заполнение `/mnt/router` (в `smart-metrics`, `df` под `timeout 20`), итог прогона — `backup_*{job="backup_router"}`; алерты `RouterDiskMountDown` (30 мин), `BackupDiskFull` (>90 %, любой диск бэкапа), `BackupStale` (>30 ч); ряд «Вторая копия» на дашборде rpi-disk.
|
- Мониторинг: `router_mount_up` и заполнение `/mnt/router` (в `smart-metrics`, `df` под `timeout 20`), итог прогона — `backup_*{job="backup_router"}`; алерты `RouterDiskMountDown` (30 мин), `BackupDiskFull` (>90 %, любой диск бэкапа), `BackupStale` (>30 ч); ряд «Вторая копия» на дашборде rpi-disk.
|
||||||
- ⚠️ 1 ТБ меньше основного диска: сейчас влезает (~370 ГБ OneDrive + ~100 ГБ серверов), но архив версий растёт вечно — следить за `BackupDiskFull`.
|
- ⚠️ 1 ТБ меньше основного диска: сейчас влезает (~370 ГБ OneDrive + ~100 ГБ серверов), но архив версий растёт вечно — следить за `BackupDiskFull`.
|
||||||
|
|
||||||
|
|||||||
@@ -97,6 +97,7 @@ groups:
|
|||||||
or (time() - backup_last_success_timestamp{job="backup_onedrive_verify"} > 8 * 86400)
|
or (time() - backup_last_success_timestamp{job="backup_onedrive_verify"} > 8 * 86400)
|
||||||
or (time() - backup_last_success_timestamp{job="backup_servers"} > 26 * 3600)
|
or (time() - backup_last_success_timestamp{job="backup_servers"} > 26 * 3600)
|
||||||
or (time() - backup_last_success_timestamp{job="backup_router"} > 30 * 3600)
|
or (time() - backup_last_success_timestamp{job="backup_router"} > 30 * 3600)
|
||||||
|
or (time() - backup_last_success_timestamp{job="backup_router_sample"} > 35 * 86400)
|
||||||
for: 10m
|
for: 10m
|
||||||
labels: {severity: critical}
|
labels: {severity: critical}
|
||||||
annotations:
|
annotations:
|
||||||
@@ -150,3 +151,9 @@ groups:
|
|||||||
labels: {severity: warning, instance: rpi, job: smart}
|
labels: {severity: warning, instance: rpi, job: smart}
|
||||||
annotations:
|
annotations:
|
||||||
summary: "rpi: метрики SMART в pushgateway отсутствуют 1+ ч — малина/пуш лежат или pushgateway потерял данные"
|
summary: "rpi: метрики SMART в pushgateway отсутствуют 1+ ч — малина/пуш лежат или pushgateway потерял данные"
|
||||||
|
- alert: BackupNeverReported
|
||||||
|
expr: absent(backup_last_success_timestamp{instance="rpi",job="backup_router_sample"})
|
||||||
|
for: 35d
|
||||||
|
labels: {severity: warning, instance: rpi, job: backup_router_sample}
|
||||||
|
annotations:
|
||||||
|
summary: "rpi: ежемесячная перечитка файлов с диска роутера не отчитывалась 35+ дней — проверь router-sample"
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#
|
#
|
||||||
# router-backup run — ежедневно (таймер 06:00)
|
# router-backup run — ежедневно (таймер 06:00)
|
||||||
# router-backup verify — по воскресеньям после run: размеры + перечитать свежие архивы серверов по sha256
|
# router-backup verify — по воскресеньям после run: размеры + перечитать свежие архивы серверов по sha256
|
||||||
|
# router-backup sample — ежемесячно: случайные файлы OneDrive (~2 ГБ) перечитать с роутера и сверить sha256
|
||||||
#
|
#
|
||||||
# Пишем прямо в remote, а не через FUSE /mnt/router: так надёжнее и есть коды ошибок.
|
# Пишем прямо в remote, а не через FUSE /mnt/router: так надёжнее и есть коды ошибок.
|
||||||
# Роутер не умеет хэши (shell_type=none) → сравнение по размеру+mtime, целостность — перечиткой.
|
# Роутер не умеет хэши (shell_type=none) → сравнение по размеру+mtime, целостность — перечиткой.
|
||||||
@@ -51,8 +52,30 @@ verify() {
|
|||||||
return $r
|
return $r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sample() {
|
||||||
|
# Роутер хэшей не считает, verify сверяет OneDrive только по размеру — тихую порчу внутри
|
||||||
|
# файла того же размера ловит только перечитка. Берём файлы старше суток (точно должны
|
||||||
|
# быть скопированы), до SAMPLE_BYTES суммарно и не больше SAMPLE_MAX штук.
|
||||||
|
local budget=${SAMPLE_BYTES:-2147483648} max=${SAMPLE_MAX:-300} total=0 n=0 bad=0 miss=0 size rel want got
|
||||||
|
while IFS=' ' read -r size rel; do
|
||||||
|
[ $n -ge $max ] && break
|
||||||
|
[ $total -gt 0 ] && [ $((total + size)) -gt $budget ] && continue
|
||||||
|
want=$(sha256sum "$SRC/onedrive/$rel" | cut -d' ' -f1)
|
||||||
|
if ! got=$(rclone --config "$C" cat "$R/onedrive/$rel" 2>>"$LOG" | sha256sum | cut -d' ' -f1) \
|
||||||
|
|| [ "$got" = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" -a "$size" != 0 ]; then
|
||||||
|
echo "MISSING $rel"; miss=$((miss + 1))
|
||||||
|
elif [ "$want" != "$got" ]; then
|
||||||
|
echo "BAD $rel"; bad=$((bad + 1))
|
||||||
|
fi
|
||||||
|
n=$((n + 1)); total=$((total + size))
|
||||||
|
done < <(cd "$SRC/onedrive" && find current archive -type f ! -name '*.partial' -mmin +1440 -printf '%s %p\n' | shuf)
|
||||||
|
echo "sample: проверено $n файлов, $((total / 1048576)) МиБ; повреждено $bad, нет на роутере $miss"
|
||||||
|
[ $bad = 0 ] && { [ $miss = 0 ] || [ -n "${SAMPLE_ALLOW_MISSING:-}" ]; }
|
||||||
|
}
|
||||||
|
|
||||||
case "${1:-run}" in
|
case "${1:-run}" in
|
||||||
run) run; r=$?; [ "$(date +%u)" = 7 ] && { verify || r=1; }; exit $r ;;
|
run) run; r=$?; [ "$(date +%u)" = 7 ] && { verify || r=1; }; exit $r ;;
|
||||||
verify) verify ;;
|
verify) verify ;;
|
||||||
*) echo "usage: router-backup run|verify" >&2; exit 2 ;;
|
sample) sample ;;
|
||||||
|
*) echo "usage: router-backup run|verify|sample" >&2; exit 2 ;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Monthly random read-back of OneDrive files from router disk (sha256)
|
||||||
|
Wants=network-online.target
|
||||||
|
After=network-online.target
|
||||||
|
RequiresMountsFor=/mnt/backup
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
# ждёт, если идёт router-backup (общий lock), а не падает
|
||||||
|
ExecStart=/usr/bin/flock /run/lock/router-backup.lock /usr/local/bin/router-backup sample
|
||||||
|
ExecStopPost=/usr/local/bin/backup-metrics backup_router_sample
|
||||||
|
Nice=10
|
||||||
|
IOSchedulingClass=idle
|
||||||
|
TimeoutStartSec=6h
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Monthly router disk read-back sample
|
||||||
|
|
||||||
|
[Timer]
|
||||||
|
OnCalendar=*-*-01 08:00
|
||||||
|
Persistent=true
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=timers.target
|
||||||
Reference in New Issue
Block a user