rpi: бэкап OneDrive на HDD + SMART/бэкап-метрики через pushgateway на fr1
- servers/rpi.md: новая домашняя Raspberry Pi 4 (Trixie, cloud-init), HDD 4 ТБ, грабли с питанием двух HDD от USB - stacks/rpi-backup: rclone sync с вечным архивом версий, еженедельная сверка хэшей, smartd, пуш SMART и статуса бэкапов - fr1: pushgateway (127.0.0.1:9091), приём по SSH через forced command ~/bin/pushgw-ingest, алерты группы rpi Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
3604e843c8
commit
d1269162e7
Executable
+16
@@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
# backup-metrics <job> — из ExecStopPost юнита бэкапа: пушит итог прогона (job=backup_<name>).
|
||||
# last_success хранится локально: PUT в pushgateway заменяет группу целиком,
|
||||
# и после неудачи время последнего успеха иначе бы потерялось.
|
||||
set -uo pipefail
|
||||
job="$1"; state=/var/lib/backup-metrics; mkdir -p "$state"
|
||||
now=$(date +%s)
|
||||
code=${EXIT_STATUS:-1}; [ "${SERVICE_RESULT:-}" = success ] && code=0
|
||||
[ "$code" = 0 ] && echo "$now" > "$state/$job.success"
|
||||
last_ok=$(cat "$state/$job.success" 2>/dev/null || echo 0)
|
||||
metrics-push "$job" <<M
|
||||
# TYPE backup_last_exit_code gauge
|
||||
backup_last_exit_code $code
|
||||
backup_last_run_timestamp $now
|
||||
backup_last_success_timestamp $last_ok
|
||||
M
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
#!/bin/sh
|
||||
# metrics-push <job> < metrics.prom — отправка в pushgateway на fr1.
|
||||
# На fr1 ключ привязан к forced command ~/bin/pushgw-ingest rpi: можно только запушить, больше ничего.
|
||||
exec ssh -i /etc/metrics-push/id_ed25519 \
|
||||
-o UserKnownHostsFile=/etc/metrics-push/known_hosts -o StrictHostKeyChecking=yes \
|
||||
-o BatchMode=yes -o ConnectTimeout=20 \
|
||||
rus@161.97.93.252 "$1"
|
||||
Executable
+30
@@ -0,0 +1,30 @@
|
||||
#!/bin/bash
|
||||
# OneDrive -> /mnt/backup. Ничего не удаляется навсегда:
|
||||
# current/ — точное зеркало OneDrive
|
||||
# archive/<время>/ — всё, что в OneDrive удалили или перезаписали (хранится вечно)
|
||||
# manifests/ — quickxor-хэши архивных файлов (для onedrive-verify)
|
||||
# logs/ — журнал каждого прогона: какие файлы скачаны/изменены/убраны в архив
|
||||
set -euo pipefail
|
||||
ROOT=/mnt/backup/onedrive
|
||||
TS=$(date +%F_%H%M)
|
||||
|
||||
mountpoint -q /mnt/backup || { echo "/mnt/backup не смонтирован — пропуск"; exit 1; }
|
||||
mkdir -p "$ROOT"/{current,archive,manifests,logs}
|
||||
LOG="$ROOT/logs/sync-$TS.log"
|
||||
|
||||
# rclone сам сверяет хэш каждого скачанного файла с хэшем OneDrive
|
||||
rclone sync onedrive: "$ROOT/current" \
|
||||
--backup-dir "$ROOT/archive/$TS" \
|
||||
--exclude "/Personal Vault/**" \
|
||||
--transfers 4 --checkers 8 \
|
||||
--retries 5 --low-level-retries 20 \
|
||||
--log-file "$LOG" --log-level INFO \
|
||||
--stats 10m --stats-one-line
|
||||
|
||||
if [ -d "$ROOT/archive/$TS" ]; then
|
||||
rclone hashsum quickxor "$ROOT/archive/$TS" > "$ROOT/manifests/archive-$TS.qxh"
|
||||
echo "в архив ушло: $(wc -l < "$ROOT/manifests/archive-$TS.qxh") файлов"
|
||||
fi
|
||||
|
||||
echo "OK: current=$(du -sh "$ROOT/current" | cut -f1), диск занят на $(df --output=pcent /mnt/backup | tail -1 | tr -d " ")"
|
||||
[ "$(df --output=pcent /mnt/backup | tail -1 | tr -dc 0-9)" -lt 90 ] || { echo "ВНИМАНИЕ: диск заполнен >90%"; exit 2; }
|
||||
Executable
+24
@@ -0,0 +1,24 @@
|
||||
#!/bin/bash
|
||||
# Еженедельная проверка целостности: перечитывает весь HDD и сверяет хэши
|
||||
# current/ — с хэшами OneDrive (ловит битые/недокачанные/лишние файлы)
|
||||
# archive/ — с сохранёнными манифестами (ловит порчу данных на диске)
|
||||
set -uo pipefail
|
||||
ROOT=/mnt/backup/onedrive
|
||||
TS=$(date +%F_%H%M)
|
||||
LOG="$ROOT/logs/verify-$TS.log"
|
||||
mountpoint -q /mnt/backup || { echo "/mnt/backup не смонтирован — пропуск"; exit 1; }
|
||||
rc=0
|
||||
|
||||
rclone check onedrive: "$ROOT/current" --exclude "/Personal Vault/**" \
|
||||
--checkers 8 --combined "$ROOT/logs/verify-$TS.current.txt" \
|
||||
--log-file "$LOG" --log-level NOTICE || rc=1
|
||||
|
||||
for m in "$ROOT"/manifests/archive-*.qxh; do
|
||||
[ -e "$m" ] || continue
|
||||
d=$(basename "$m" .qxh); d=${d#archive-}
|
||||
rclone checksum quickxor "$m" "$ROOT/archive/$d" --log-file "$LOG" --log-level NOTICE || { echo "архив $d: несовпадение"; rc=1; }
|
||||
done
|
||||
|
||||
grep -vc "^=" "$ROOT/logs/verify-$TS.current.txt" | xargs echo "расхождений в current:"
|
||||
[ $rc -eq 0 ] && echo "OK: целостность подтверждена" || echo "ЕСТЬ РАСХОЖДЕНИЯ — см. $LOG и verify-$TS.current.txt"
|
||||
exit $rc
|
||||
Executable
+36
@@ -0,0 +1,36 @@
|
||||
#!/bin/bash
|
||||
# SMART всех USB/SATA-дисков → pushgateway (job=smart).
|
||||
# -n standby: спящий диск НЕ будим (иначе опрос раз в 15 мин не дал бы ему спать) —
|
||||
# для него отдаём последние снятые значения из кэша и smart_disk_standby=1.
|
||||
set -uo pipefail
|
||||
CACHE=/var/lib/smart-metrics; mkdir -p "$CACHE"
|
||||
out=$(mktemp); trap 'rm -f "$out"' EXIT
|
||||
echo "# TYPE smart_disk_standby gauge" >> "$out"
|
||||
for dev in /dev/sd?; do
|
||||
[ -b "$dev" ] || continue
|
||||
json=$(smartctl -d sat -n standby,7 -j -H -i -A -l selftest "$dev"); rc=$?
|
||||
serial=$(udevadm info -q property -n "$dev" | sed -n 's/^ID_SERIAL_SHORT=//p')
|
||||
[ -n "$serial" ] || continue
|
||||
f="$CACHE/$serial.prom"
|
||||
if [ $rc -eq 7 ]; then
|
||||
standby=1
|
||||
else
|
||||
standby=0
|
||||
echo "$json" | jq -r --arg dev "${dev#/dev/}" '
|
||||
(.model_name // "unknown") as $m | (.serial_number // "unknown") as $s |
|
||||
"{model=\"\($m)\",serial=\"\($s)\",device=\"\($dev)\"" as $l |
|
||||
"smart_health_passed\($l)} \(if .smart_status.passed then 1 else 0 end)",
|
||||
"smart_temperature_celsius\($l)} \(.temperature.current // "NaN")",
|
||||
"smart_power_on_hours\($l)} \(.power_on_time.hours // "NaN")",
|
||||
(.ata_smart_self_test_log.standard.table[0]? // empty |
|
||||
"smart_selftest_last_passed\($l)} \(if .status.passed then 1 else 0 end)"),
|
||||
(.ata_smart_attributes.table[]? |
|
||||
"smart_attribute_raw\($l),id=\"\(.id)\",name=\"\(.name)\"} \(.raw.value)",
|
||||
"smart_attribute_value\($l),id=\"\(.id)\",name=\"\(.name)\"} \(.value)"),
|
||||
"smart_last_read_timestamp\($l)} \(now|floor)"
|
||||
' > "$f.tmp" && [ -s "$f.tmp" ] && mv "$f.tmp" "$f"
|
||||
fi
|
||||
[ -f "$f" ] && cat "$f" >> "$out"
|
||||
echo "smart_disk_standby{serial=\"$serial\",device=\"${dev#/dev/}\"} $standby" >> "$out"
|
||||
done
|
||||
metrics-push smart < "$out"
|
||||
@@ -0,0 +1 @@
|
||||
UUID=6eea3421-da3f-4a0f-b66d-26000b9a1b01 /mnt/backup ext4 defaults,noatime,nofail,x-systemd.device-timeout=30s 0 2
|
||||
@@ -0,0 +1,7 @@
|
||||
# smartd: следит за всеми дисками (DEVICESCAN подхватит и второй диск после хаба).
|
||||
# -d removable — тип моста определяется сам; диска может не быть (не падать)
|
||||
# -n standby,q — не будить спящий диск ради опроса
|
||||
# -s — короткий самотест по субботам 03:00, длинный (~10ч на 4ТБ) 1-го числа в 05:00
|
||||
# -W 4,50,55 — фиксировать скачки температуры ≥4°, порог предупреждения 50, критичный 55
|
||||
# Почту не шлём (-m нет): тревоги идут через Prometheus на fr1 (smart-metrics → pushgateway).
|
||||
DEVICESCAN -d removable -a -o on -S on -n standby,q -s (S/../../6/03|L/../01/./05) -W 4,50,55
|
||||
@@ -0,0 +1,14 @@
|
||||
[Unit]
|
||||
Description=OneDrive -> HDD backup (rclone)
|
||||
Wants=network-online.target
|
||||
After=network-online.target
|
||||
RequiresMountsFor=/mnt/backup
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
User=rus
|
||||
ExecStart=/usr/bin/flock /run/lock/onedrive-backup.lock /usr/local/bin/onedrive-backup
|
||||
ExecStopPost=+/usr/local/bin/backup-metrics backup_onedrive
|
||||
Nice=10
|
||||
IOSchedulingClass=idle
|
||||
TimeoutStartSec=infinity
|
||||
@@ -0,0 +1,10 @@
|
||||
[Unit]
|
||||
Description=OneDrive backup every 4h
|
||||
|
||||
[Timer]
|
||||
OnCalendar=*-*-* 00/4:30
|
||||
RandomizedDelaySec=5m
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
@@ -0,0 +1,14 @@
|
||||
[Unit]
|
||||
Description=OneDrive backup integrity check
|
||||
Wants=network-online.target
|
||||
After=network-online.target onedrive-backup.service
|
||||
RequiresMountsFor=/mnt/backup
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
User=rus
|
||||
ExecStart=/usr/bin/flock /run/lock/onedrive-backup.lock /usr/local/bin/onedrive-verify
|
||||
ExecStopPost=+/usr/local/bin/backup-metrics backup_onedrive_verify
|
||||
Nice=15
|
||||
IOSchedulingClass=idle
|
||||
TimeoutStartSec=infinity
|
||||
@@ -0,0 +1,9 @@
|
||||
[Unit]
|
||||
Description=Weekly OneDrive backup integrity check
|
||||
|
||||
[Timer]
|
||||
OnCalendar=Sun *-*-* 05:00
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
@@ -0,0 +1,8 @@
|
||||
[Unit]
|
||||
Description=Push SMART metrics to pushgateway on fr1
|
||||
Wants=network-online.target
|
||||
After=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/bin/smart-metrics
|
||||
@@ -0,0 +1,9 @@
|
||||
[Unit]
|
||||
Description=SMART metrics push every 15 min
|
||||
|
||||
[Timer]
|
||||
OnBootSec=2min
|
||||
OnUnitActiveSec=15min
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
Reference in New Issue
Block a user