Backup Raspberry Pi na Hetzner Storage Box z restic
Kompletna konfiguracja zaszyfrowanego backupu Raspberry Pi na Hetzner Storage Box — od kluczy SSH po systemd timer.
Problem
Raspberry Pi służy mi jako mały serwer domowy — LAMP, kilka skryptów, konfigi. Karta SD lubi się sypać, więc backup to nie opcja, tylko obowiązek. Cel: codzienny przyrostowy backup szyfrowany po stronie klienta, trzymany off-site na Hetzner Storage Box, z rotacją snapshotów i bez ręcznego klikania.
Stack: restic (deduplikacja + szyfrowanie), SFTP (wbudowany w restic, nie potrzeba żadnego agenta), systemd timer (lepszy niż cron — Persistent, randomizacja, łatwe logi).
Instalacja restic
$sudo apt update$sudo apt install restic$sudo restic self-update
self-update warto odpalić — wersja w apt bywa starsza.
Klucz SSH dla Storage Boxa
Wszystko robimy jako root — backup systemowy musi czytać /etc, /var, pliki różnych userów.
$sudo -i$ssh-keygen -t ed25519 -f /root/.ssh/hetzner_storagebox -N ""
-N "" = bez passphrase. Klucz musi działać bez interakcji, bo systemd nie ma jak go odblokować.
Wgranie klucza na Storage Box
Jeśli w nowym UI nie widać opcji dodania klucza, najszybciej przez SFTP — np. WinSCP albo z terminala:
$ssh -p23 u123456@u123456.your-storagebox.de mkdir -p .ssh$cat /root/.ssh/hetzner_storagebox.pub | \ssh -p23 u123456@u123456.your-storagebox.de install-ssh-key
install-ssh-key to wbudowany skrypt Hetznera — dopisuje klucz do authorized_keys z poprawnymi uprawnieniami.
Ręczne wgranie przez WinSCP też działa — pamiętaj o uprawnieniach po stronie Storage Boxa: .ssh → 700, authorized_keys → 600. Bez tego SSH zignoruje plik i nadal będzie pytać o hasło.
Konfiguracja SSH dla restica
/root/.ssh/config:
Host *.your-storagebox.de
User u123456
Port 23
IdentityFile /root/.ssh/hetzner_storagebox
ServerAliveInterval 60
$chmod 600 /root/.ssh/config
Test — pierwsze połączenie zaakceptuje fingerprint i wpisze go do known_hosts:
$ssh -p23 -i /root/.ssh/hetzner_storagebox u123456@u123456.your-storagebox.de
Powinno wpuścić bez pytania o hasło. Storage Box ma ograniczony shell — to OK, restic używa SFTP w tle.
Repo i hasło
Hasło repozytorium (zaszyfruje wszystko po stronie klienta):
$openssl rand -base64 32 > /root/.restic-password$chmod 600 /root/.restic-password
Zmienne środowiskowe — /root/.restic-env:
export RESTIC_REPOSITORY="sftp:u123456@u123456.your-storagebox.de:/home/backups/raspi"
export RESTIC_PASSWORD_FILE="/root/.restic-password"
$chmod 600 /root/.restic-env
Inicjalizacja repo — robi się tylko raz:
$source /root/.restic-env$restic init
Skrypt backupu
/usr/local/bin/restic-backup.sh:
#!/bin/bash
set -euo pipefail
source /root/.restic-env
BACKUP_PATHS=(
/etc
/home
/var/www
/root
/opt
)
EXCLUDES=(
--exclude-caches
--exclude='/home/*/.cache'
--exclude='*.tmp'
--exclude='/var/www/*/cache'
--exclude='/var/www/*/var/cache'
--exclude='node_modules'
)
restic backup \
"${BACKUP_PATHS[@]}" \
"${EXCLUDES[@]}" \
--tag auto \
--host raspi
restic forget \
--keep-daily 7 \
--keep-weekly 4 \
--keep-monthly 6 \
--prune
if [ "$(date +%u)" -eq 7 ]; then
restic check --read-data-subset=10%
fi
$chmod +x /usr/local/bin/restic-backup.sh
--read-data-subset=10% w niedzielę realnie ściąga i weryfikuje 10% danych — wykryje uszkodzenie po stronie Storage Boxa, którego sam forget nie zauważy.
Systemd timer
/etc/systemd/system/restic-backup.service:
[Unit]
Description=Restic backup to Hetzner Storage Box
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/restic-backup.sh
Nice=10
IOSchedulingClass=best-effort
IOSchedulingPriority=7
/etc/systemd/system/restic-backup.timer:
[Unit]
Description=Daily restic backup
[Timer]
OnCalendar=*-*-* 03:30:00
RandomizedDelaySec=30min
Persistent=true
[Install]
WantedBy=timers.target
Persistent=true = jeśli Pi było wyłączone o 3:30, backup ruszy zaraz po starcie. Cron tego nie potrafi.
$systemctl daemon-reload$systemctl enable --now restic-backup.timer$systemctl list-timers restic-backup.timer
Test ręczny
Timer to tylko harmonogram — żeby ręcznie odpalić backup, uruchamiasz service:
#Terminal 1 — log na żywo$sudo journalctl -u restic-backup.service -f#Terminal 2 — wystrzał$sudo systemctl start restic-backup.service
Pierwszy backup pójdzie wolno (Storage Boxy nie są demonem prędkości), kolejne lecą tylko delty.
Quality of life
Żeby restic snapshots działał z marszu po sudo -i, dopisz do /root/.bashrc:
[ -f /root/.restic-env ] && source /root/.restic-env
Co się dzieje pod spodem
Restic dzieli pliki na bloki ~1 MB, oblicza SHA-256 każdego, szyfruje AES-256 i wysyła tylko bloki, których jeszcze nie ma w repo. Snapshot to lekki manifest wskazujący na bloki — stąd kosztuje grosze nawet przy codziennej rotacji. SFTP po stronie restica to natywny klient w Go, bez wywoływania zewnętrznego sftp — używa tylko OpenSSH-owej konfiguracji do nawiązania połączenia.