Mein Framework 13 läuft seit einiger Zeit mit Kubuntu, aber zu meiner Schande hatte ich noch keine richtige Backup-Strategie eingerichtet. Seit dem Umzug auf das neue Ugreen-NAS ist dort genug Platz, also war es Zeit, das nachzuholen.
Meine Wünsche waren:
- Das Home-Verzeichnis und die Programm-Konfigurationen täglich sichern.
- Zusätzlich einmal pro Woche das ganze System, damit ich den Laptop im Notfall komplett wiederherstellen kann.
- Nur sichern, wenn der Laptop am Netzteil hängt.
- Von unterwegs soll es auch funktionieren.
- Aufbewahrung: 4 tägliche, 2 wöchentliche und 4 monatliche Sicherungen.
In diesem Artikel zeige ich, wie ich das mit restic, einem rest-server im Docker auf dem NAS und zwei systemd-Timern umgesetzt habe. Am Ende steht ein längerer Abschnitt zur Fehlersuche, denn der erste Lauf war zunächst quälend langsam.
Die Idee
Am Laptop läuft restic als root, gestartet von systemd-Timern. Es schickt die verschlüsselten Daten an einen rest-server, der als Container auf dem NAS läuft und alles in einer eigenen Freigabe ablegt. Zuhause geht das direkt über das Heimnetz, unterwegs über Tailscale. Nachts spiegelt das NAS die Freigabe zusätzlich auf eine USB-Platte.
Warum gerade so:
- restic: Habe ich schon bei anderen Setups in der Vergangenheit verwendet. Mir gefällt dran, dass man damit einfach überprüfen kann, ob das Backup im Ernstfall auch wirklich funktioniert. Denn das ist ja oft das Problem: Man hat ein super Backup und wenn man es einspielen will, wird einem dann erst klar, ob die Backupdatei sauber angelegt wurde oder ob sie kaputt ist. Es dabei aber zu beachten, dass bei restic der Speicherplatz am NAS bzw. externer Festplatte überwacht werden muss. Geht der Speicherplatz aus, ist das restic Backup nicht vollständig.
- rest-server statt SFTP: Er läuft als Container wie alles andere auf dem NAS. Ich brauche keinen SSH-Benutzer und muss nicht mit den Rechten von UGOS kämpfen.
- Append-only: Der rest-server läuft im Modus
--append-only. Der Laptop darf also nur neue Sicherungen anlegen, aber nichts löschen. Selbst ein Trojaner mit root-Rechten auf dem Laptop kann die Backups damit nicht zerstören. Der Preis dafür ist, dass das Aufräumen alter Sicherungen auf dem NAS laufen muss. - Ein Repository für beides: restic dedupliziert. Die wöchentliche System-Sicherung kostet nach dem ersten Lauf kaum noch Platz.
- Nur am Netzteil: Der Timer versucht es tagsüber alle drei Stunden. Das Skript sichert nur, wenn der Laptop am Strom hängt und die letzte Sicherung alt genug ist. So klappt es auch, wenn der Laptop nur selten am Netzteil hängt.
Es gibt zwei Sicherungen, unterschieden über restic-Tags:
| Tag | Was | Wann |
|---|---|---|
home |
/home/benutzer, /etc, /root, /usr/local, Paketlisten |
einmal pro Tag |
system |
das ganze / inklusive /boot und /boot/efi, ohne /home |
einmal pro Woche |
Die täglichen Konfigurationen in /etc sind damit doppelt drin, aber das kostet dank Deduplizierung nichts.
Voraussetzungen
- Ein NAS oder Server mit Docker und Docker Compose. Bei mir ist das ein Ugreen DXP4800 Plus mit UGOS.
- Tailscale auf NAS und Laptop. Auf dem NAS läuft Tailscale bei mir als eigener Container.
- restic ab Version 0.17 auf dem Laptop, weil erst ab dieser Version die Zugangsdaten für den rest-server per Umgebungsvariable gehen. Kubuntu 26.04 bringt 0.18.1 mit.
- Platz auf dem NAS. Mein Home hat knapp 400 GiB.
Teil 1: Das NAS
Freigabe anlegen
In UGOS unter „Systemsteuerung“ → „Freigegebener Ordner“ lege ich eine neue Freigabe fw13-backup an. Wichtig sind dabei zwei Einstellungen:
- Kein Zugriff für den normalen Benutzer. Der Laptop ist per SMB als mein normaler Benutzer angemeldet. Hätte dieser Schreibrechte auf die Freigabe, könnte ein Trojaner die Backups einfach über SMB löschen, und append-only wäre ausgehebelt. Der Container läuft als root und braucht keine Freigaberechte.
- Papierkorb aus. Gelöschte Daten braucht hier niemand, sie belegen nur Platz.
Bei der Gelegenheit ist mir aufgefallen, dass mein normaler Benutzer auch auf die Freigabe docker Lesen und Schreiben hatte. Dort liegen Zugangsdaten und Konfigurationen meiner Container. Das habe ich gleich mit abgestellt.
rest-server im Docker
Die Compose-Datei liegt bei mir unter /volume1/docker/rest-server/docker-compose.yaml:
services:
rest-server:
image: restic/rest-server:0.14.0
container_name: rest-server
restart: unless-stopped
ports:
- "8000:8000" # LAN direkt (zuhause schnell), unterwegs über Tailscale
volumes:
- /volume1/fw13-backup:/data
environment:
OPTIONS: "--append-only --private-repos"
--private-repos sorgt dafür, dass jeder Benutzer nur in sein eigenes Unterverzeichnis schreiben darf. Der Benutzer fw13 kommt also nur an /fw13/ heran. Kommen später weitere Notebooks dazu, bekommt jedes einen eigenen Benutzer.
Ursprünglich hatte ich den Port auf 127.0.0.1:8000 beschränkt, damit der rest-server nur über Tailscale erreichbar ist. Warum er jetzt doch im LAN offen ist, steht weiter unten bei der Fehlersuche.
Starten und einen Benutzer anlegen:
cd /volume1/docker/rest-server docker compose up -d docker exec -it rest-server create_user fw13
Das Passwort für den rest-server ist nicht das restic-Passwort. Du kannst es mit openssl rand -hex 16 oder einem anderen Zufallsgenerator erzeugen und speichere es in einen Passwortmanager. Am besten eines ohne Sonderzeichen, dann gibt es später keinen Ärger mit Anführungszeichen.
Die Warnung „No user exists“ im Log beim ersten Start ist normal, sie stammt aus der Zeit vor create_user.
Für Tailscale freigeben
Für unterwegs leite ich einen eigenen HTTPS-Port im Tailnet auf den rest-server weiter. Bei mir ist Port 8443 schon für etwas anderes belegt, deshalb 8444:
docker exec tailscale tailscale serve --bg --https=8444 http://127.0.0.1:8000 docker exec tailscale tailscale serve status
Das ist nur im Tailnet sichtbar, nicht im Internet (dafür wäre tailscale funnel zuständig). Die Einstellung bleibt auch nach einem Neustart erhalten.
Ein schneller Test vom Laptop aus. Hier und im Folgenden steht nas-name.<tailnet>.ts.net für die Tailscale-Adresse des NAS. Die Heimnetz-Adresse 192.168.1.10, der Benutzer benutzer und die Ablageorte der Passwort- und Zugangsdateien (repo-kennwort und zugang.env am Laptop, kennwort-fw13 am NAS) sind Beispiele, die du an dein System anpassen musst:
curl -u fw13 --max-time 15 https://nas-name.<tailnet>.ts.net:8444/fw13/config
Die Antwort Not Found ist hier richtig, denn das Repository gibt es noch nicht. 401 heißt falsches Passwort, und „Connection refused“ heißt, dass die Weiterleitung fehlt.
Teil 2: Der Laptop
Alles läuft als root, damit restic auch /etc und die Systemdateien lesen kann.
restic und Passwörter
sudo apt install restic curl restic version
Dann das Passwort für das Repository. Ohne dieses Passwort ist das Backup wertlos, es gehört also sofort in den Passwortmanager:
sudo install -d -m 700 /etc/restic openssl rand -base64 32 | sudo tee /etc/restic/repo-kennwort sudo chmod 600 /etc/restic/repo-kennwort
Die Zugangsdatei
Alle Einstellungen stehen in /etc/restic/zugang.env (danach sudo chmod 600 /etc/restic/zugang.env):
RESTIC_REPOSITORY=rest:https://nas-name.<tailnet>.ts.net:8444/fw13/ RESTIC_REST_USERNAME=fw13 RESTIC_REST_PASSWORD=<Passwort für den rest-server> RESTIC_PASSWORD_FILE=/etc/restic/repo-kennwort RESTIC_CACHE_DIR=/var/cache/restic RESTIC_PROGRESS_FPS=0.0166 # Fortschritt etwa 1x pro Minute im Journal # zuhause direkt per LAN RESTIC_REPOSITORY_LAN=rest:http://192.168.1.10:8000/fw13/ NAS_MAC=xx:xx:xx:xx:xx:xx
Zu den letzten Zeilen:
RESTIC_CACHE_DIRist unter systemd nötig. Dort gibt es kein$HOME, und ohne Cache muss restic bei jedem Lauf alle Metadaten neu vom NAS holen.RESTIC_PROGRESS_FPSlässt restic auch ohne Terminal etwa einmal pro Minute eine Fortschrittszeile mit Prozent und Restzeit ins Journal schreiben.NAS_MACist die MAC-Adresse des NAS, also der Wert nachlladdrin der Ausgabe vonip neigh show 192.168.1.10. Damit prüft das Skript, ob es wirklich im eigenen Heimnetz ist. Sonst würde es in einem fremden Netz mit zufällig gleicher IP das Passwort unverschlüsselt an ein fremdes Gerät schicken.
Damit ich nicht jedes Mal alle Variablen setzen muss, gibt es einen kleinen Kurzbefehl /usr/local/sbin/restic-fw13:
#!/bin/sh set -a; . /etc/restic/zugang.env; set +a exec restic "$@"
sudo chmod 755 /usr/local/sbin/restic-fw13 sudo restic-fw13 init
Ab jetzt gehen sudo restic-fw13 snapshots, restore und so weiter ohne Tipparbeit.
Was nicht ins Backup soll
/etc/restic/excludes-home.txt:
/home/benutzer/.cache /home/benutzer/.local/share/Trash /home/benutzer/.local/share/baloo /home/benutzer/.thumbnails /home/benutzer/.var/app/*/cache /home/benutzer/snap/*/*/.cache /home/benutzer/.local/share/Steam /home/benutzer/KI/model /root/.cache
Steam-Spiele und KI-Modelle kann man jederzeit neu herunterladen, die müssen nicht ins Backup. Meine Fotos und Videos bleiben drin, die liegen nur auf dem Laptop.
/etc/restic/excludes-system.txt:
/home /tmp /var/tmp /var/cache /var/lib/snapd/cache /var/log/journal /swap.img /swapfile
/proc, /sys, /dev und /run fallen durch --one-file-system automatisch weg. Weil /boot und /boot/efi bei mir eigene Partitionen sind (das legt der Kubuntu-Installer bei verschlüsselter Installation so an), stehen sie im Skript ausdrücklich als Quellen. Ob es sie gibt, zeigt findmnt -M /boot bzw. findmnt -M /boot/efi.
Das Backup-Skript
Das Herzstück ist /usr/local/sbin/restic-backup.sh (danach sudo chmod 755). Es bekommt als Parameter home oder system:
#!/bin/bash
# Aufruf: restic-backup.sh home|system
set -uo pipefail
MODE="${1:-}"
set -a; . /etc/restic/zugang.env; set +a
STATE=/var/lib/restic-backup
mkdir -p "$STATE"
case "$MODE" in
home) MAXAGE=$((20*3600)) ;; # höchstens 1x pro Tag
system) MAXAGE=$((6*24*3600)) ;; # höchstens 1x pro Woche
,*) echo "Aufruf: $0 home|system"; exit 2 ;;
esac
# Letzte erfolgreiche Sicherung noch frisch? Dann nichts tun.
if [ -f "$STATE/$MODE.last" ] && \
[ $(( $(date +%s) - $(stat -c %Y "$STATE/$MODE.last") )) -lt $MAXAGE ]; then
echo "$MODE: letzte Sicherung ist noch aktuell"; exit 0
fi
# Nur am Netzteil
if ! systemd-ac-power; then echo "Akkubetrieb, übersprungen"; exit 0; fi
# Zuhause? Dann direkt übers LAN, der Tailscale-Proxy ist beim Hochladen langsam
if [ -n "${RESTIC_REPOSITORY_LAN:-}" ] && ping -c1 -W1 192.168.1.10 >/dev/null 2>&1 \
&& ip neigh show 192.168.1.10 | grep -qi "${NAS_MAC:-none}"; then
export RESTIC_REPOSITORY="$RESTIC_REPOSITORY_LAN"; echo "Weg: LAN"
else
echo "Weg: Tailscale"
fi
# NAS erreichbar?
if ! restic cat config >/dev/null; then
echo "NAS nicht erreichbar, nächster Versuch beim nächsten Timer"; exit 0
fi
if [ "$MODE" = home ]; then
# Paketlisten für die Neuinstallation
P=/var/backups/pakete
mkdir -p "$P"
dpkg --get-selections > "$P/dpkg-selections.txt"
apt-mark showmanual > "$P/apt-manuell.txt"
command -v snap >/dev/null && snap list > "$P/snap.txt"
command -v flatpak >/dev/null && flatpak list --app --columns=application > "$P/flatpak.txt"
lsblk -f > "$P/lsblk.txt"; cp /etc/fstab "$P/fstab"
restic backup -o rest.connections=20 --tag home --exclude-caches \
--exclude-file=/etc/restic/excludes-home.txt \
/home/benutzer /etc /root /usr/local /var/backups/pakete
RC=$?
else
restic backup -o rest.connections=20 --tag system --one-file-system \
--exclude-file=/etc/restic/excludes-system.txt \
/ /boot /boot/efi
RC=$?
fi
# 0 = ok, 3 = ok, aber einzelne Dateien nicht lesbar
if [ $RC -eq 0 ] || [ $RC -eq 3 ]; then
touch "$STATE/$MODE.last"
echo "$MODE: fertig (Code $RC)"
exit 0
fi
echo "$MODE: FEHLER (Code $RC)"; exit $RC
Ein paar Gedanken dazu:
- Ob eine Sicherung fällig ist, merkt sich das Skript über einen
Zeitstempel in
/var/lib/restic-backup. Der systemd-Timer darf es daher ruhig oft aufrufen. - Fehlt das Netzteil oder das NAS, beendet sich das Skript mit Code 0. Das ist kein Fehler, sondern einfach „heute nicht“. Der nächste Timer versucht es wieder.
- Bei der Home-Sicherung landen nebenbei die Paketlisten (
apt-mark showmanual, Snap, Flatpak),lsblkundfstabim Backup. Damit lässt sich ein neu installiertes System schnell wieder mit denselben Programmen bestücken. -o rest.connections=20hat bei mir messbar nichts gebracht (siehe Fehlersuche), schadet aber auch nicht.
Teil 3: Zeitplan mit systemd
Ein Dienst für beide Sicherungen, /etc/systemd/system/restic-backup@.service:
[Unit] Description=restic-Sicherung (%i) aufs NAS Wants=network-online.target After=network-online.target tailscaled.service ConditionACPower=true [Service] Type=oneshot ExecStart=/usr/local/sbin/restic-backup.sh %i Nice=10 IOSchedulingClass=idle
Dazu zwei Timer. /etc/systemd/system/restic-backup-home.timer:
[Unit] Description=Home-Sicherung, Versuch alle 3 Stunden [Timer] OnCalendar=*-*-* 09,12,15,18,21:00 Persistent=true RandomizedDelaySec=10min Unit=restic-backup@home.service [Install] WantedBy=timers.target
Und /etc/systemd/system/restic-backup-system.timer mit denselben Zeiten, eine halbe Stunde versetzt. Dass das System nur einmal pro Woche gesichert wird, regelt das Skript:
[Unit] Description=System-Sicherung, wöchentlich [Timer] OnCalendar=*-*-* 09,12,15,18,21:30 Persistent=true RandomizedDelaySec=10min Unit=restic-backup@system.service [Install] WantedBy=timers.target
Persistent=true holt Termine nach, die der Laptop im Ruhezustand verpasst hat. Der erste Lauf von Hand, und danach die Timer einschalten:
sudo systemctl daemon-reload sudo systemctl start --no-block restic-backup@home.service journalctl -fu restic-backup@home.service sudo systemctl enable --now restic-backup-home.timer restic-backup-system.timer systemctl list-timers 'restic*'
Ohne --no-block wartet systemctl start bis zum Ende der Sicherung, und das kann beim ersten Mal Stunden dauern.
Läuft gerade eine Sicherung, schreibt restic auf Zuruf sofort eine Fortschrittszeile ins Journal:
sudo pkill -USR1 -x restic journalctl -u restic-backup@home.service -n 3 -o cat
Im Terminal selbst erscheint dabei nichts, die Zeile steht im Journal.
Der Bildschirm darf sich dabei übrigens sperren und der Laptop zugeklappt sein. Die Sicherung läuft als Systemdienst unabhängig von der Sitzung weiter, solange der Laptop nicht in den Ruhezustand geht.
Teil 4: Aufräumen und zweite Kopie auf dem NAS
Wegen append-only kann der Laptop keine alten Sicherungen löschen. Das übernimmt das NAS mit einem eigenen restic. Dafür liegt das Repository-Passwort des Laptops auf dem NAS in /volume1/docker/restic/kennwort-fw13 (chmod 600).
Das Skript /volume1/docker/restic/prune-notebooks.sh:
#!/bin/sh
R=/volume1/docker/restic/bin/restic
LOG=/volume1/docker/restic/prune-notebooks.log
for nb in fw13; do
echo "== $(date '+%F %T') $nb" >> "$LOG"
$R -r /volume1/fw13-backup/$nb \
--password-file /volume1/docker/restic/kennwort-$nb --retry-lock 30m \
forget --group-by host,tags \
--keep-daily 4 --keep-weekly 2 --keep-monthly 4 --prune >> "$LOG" 2>&1
# erste Woche im Monat: 5 % der Daten prüfen
if [ "$(date +%d)" -le 07 ]; then
$R -r /volume1/fw13-backup/$nb \
--password-file /volume1/docker/restic/kennwort-$nb \
check --read-data-subset=5% >> "$LOG" 2>&1
fi
done
--group-by host,tags ist wichtig: Damit gilt die Regel 4/2/4 getrennt für home und system. Bei system gibt es nur eine Sicherung pro Woche, dort bleiben also die letzten vier Wochen und vier Monatsstände.
Per Cron als root läuft es jeden Sonntag um 14:00, weit weg vom nächtlichen NAS-Backup:
0 14 * * 0 /volume1/docker/restic/prune-notebooks.sh
Läuft gerade eine Sicherung vom Laptop, wartet das Aufräumen dank --retry-lock bis zu 30 Minuten.
Mein NAS sichert sich nachts selbst mit restic auf eine USB-Platte. Das Notebook-Repository habe ich dort aber bewusst nicht als weitere Quelle eingetragen. Eine geänderte Quellenliste hätte ein Neueinlesen aller 3 TB ausgelöst, und das Repository ist ohnehin schon verschlüsselt. Stattdessen spiegelt eine zusätzliche Zeile im nächtlichen Skript die Freigabe 1:1 auf die Platte:
rsync -a --delete /volume1/fw13-backup/ "$MNT/fw13-backup/" && echo "fw13-backup gespiegelt"
$MNT ist dabei der Einhängepunkt der USB-Platte, den das Skript vorher ermittelt.
Teil 5: Prüfen und Test-Wiederherstellung
Ein Backup zählt erst, wenn eine Wiederherstellung einmal geklappt hat. Nach dem ersten Lauf und danach etwa alle drei Monate:
| Was | Befehl am Laptop |
|---|---|
| Sicherungen anzeigen | sudo restic-fw13 snapshots |
| Letzte Läufe und Fehler | journalctl -u 'restic-backup@*' --since -3d |
| Nächste Termine | systemctl list-timers 'restic*' |
| Einzelne Datei zurückholen | sudo restic-fw13 restore latest --tag home --include /home/benutzer/.zshrc --target /tmp/rtest |
| Durchsuchen wie ein Laufwerk | sudo restic-fw13 mount /mnt/restic
(Ende mit Strg+C) |
| Platzverbrauch | sudo restic-fw13 stats --mode raw-data |
Meine Test-Datei war die .zshrc. Ein diff mit dem Original war leer, also alles gut.
Auch append-only sollte man einmal testen: sudo restic-fw13 forget <ID> mit einer ID aus snapshots muss am Laptop mit einem Fehler abbrechen.
Im Notfall
Dann brauche ich nur zwei Einträge aus dem Passwortmanager: das restic-Passwort und das Passwort für den rest-server. Auf dem neuen System oder einem Live-USB installiere ich restic (und für unterwegs Tailscale) und lege /etc/restic/zugang.env, /etc/restic/repo-kennwort und restic-fw13 wie oben an.
Der einfachste Weg ist eine frische Installation plus Daten:
sudo restic-fw13 restore latest --tag home --target /var/tmp/r xargs -a /var/tmp/r/var/backups/pakete/apt-manuell.txt sudo apt install -y xargs -a /var/tmp/r/var/backups/pakete/flatpak.txt flatpak install -y sudo rsync -aH /var/tmp/r/home/benutzer/ /home/benutzer/
Aus /var/tmp/r/etc übernehme ich dann gezielt, was ich selbst geändert hatte. fstab, crypttab und /etc/default/grub kopiere ich nicht blind, die gehören zur neuen Installation.
Alternativ geht auch das komplette System 1:1 aus der wöchentlichen Sicherung: vom Live-USB Partitionen anlegen (Vorlage ist die gesicherte lsblk.txt), erst system und dann home nach /mnt wiederherstellen, die UUIDs in fstab und crypttab anpassen und im chroot Initramfs und GRUB neu bauen. Das lohnt sich aber nur, wenn viel am System selbst eingerichtet war.
Fehlersuche: Warum war der erste Lauf so langsam?
Eingerichtet war alles in einer guten Stunde. Dann kam der erste Home-Lauf mit knapp 400 GiB, und restic meldete:
[41:01] 1.37% 32650 files 5.680 GiB, total 276787 files 414.167 GiB, 0 errors ETA 84:11:57
84 Stunden, also rund 2 MB/s. Bis zur Lösung hat es mehrere Runden gebraucht. Weil die einzelnen Schritte für andere vielleicht nützlich sind, schreibe ich sie hier auf.
Runde 1: Ist es der Laptop?
Die naheliegende Vermutung war meine Einstellung IOSchedulingClass=idle im Dienst. Ein ionice und renice auf den laufenden Prozess hat aber kaum etwas gebracht. top zeigte den Laptop zu 99 % im Leerlauf, kein Warten auf die SSD. Und ein dd auf eine große Datei las mit mehreren GB/s. Der Laptop wartet also auf das NAS.
Runde 2: Eine Netzwerkdose mit 100 Mbit
Auf dem NAS lieferte der rest-server lokal 233 MB/s:
curl -s -u fw13 -o /dev/null -w '%{speed_download}\n' http://127.0.0.1:8000/fw13/data/<Datei-ID>
Vom Laptop aus, egal ob über Tailscale oder direkt im LAN, kamen nur etwa 11 MB/s an. Das ist ziemlich genau die Grenze einer 100-Mbit-Verbindung. Dabei zeigten beide Netzwerkkarten 1000 Mbit (cat /sys/class/net/<if>/speed). Der Engpass lag also dazwischen, in meinem Fall in der Hausverkabelung. Mit einer anderen Netzwerkdose kamen über Tailscale plötzlich 75 MB/s an.
Problem gelöst, dachte ich.
Runde 3: Hochladen ist nicht Herunterladen
Nach einigen Minuten lag restic aber wieder bei nur 3 MB/s. Mein Test hatte nur die Richtung NAS → Laptop gemessen, das Backup schreibt aber in die andere Richtung. Also alles einzeln durchgemessen:
| Test | Ergebnis |
|---|---|
Schreiben auf die NAS-Platten (dd, 2
GB) |
128 MB/s |
Netz Laptop → NAS (iperf3) |
ca. 500 Mbit/s (≈ 60 MB/s) |
fsync für ein 16-MB-Paket am NAS |
ca. 0,3 s |
| restic mit 20 statt 5 Verbindungen | kein Unterschied |
Alles einzeln schnell genug, und trotzdem kamen über Tailscale nur 3 MB/s an. Auffällig war, dass der Tailscale-Container auf dem NAS dabei 25 bis 30 % CPU brauchte.
Zwei Dinge sind mir in den Ausgaben von iperf3 aufgefallen. Erstens war die Absenderadresse meines Laptops eine Tailscale-Adresse (100.x.x.x), obwohl ich die LAN-Adresse des NAS angesprochen hatte. Der Grund: Mein Home Assistant bietet im Tailnet eine Subnetz-Route ins Heimnetz an, und der Laptop hat sie übernommen. Selbst der „direkte“ Verkehr im LAN lief also verschlüsselt durch Tailscale. Abhilfe am Laptop:
sudo tailscale set --accept-routes=false ip route get 192.168.1.10 # sollte jetzt die LAN-Schnittstelle zeigen
Zweitens fehlte noch genau ein Test: das Hochladen durch tailscale serve. Also den rest-server-Port testweise im LAN geöffnet ("8000:8000" in der Compose-Datei), die Repository-Adresse am Laptop auf
rest:http://192.168.1.10:8000/fw13/
umgestellt und neu gestartet:
[4:00] 7.09% 37763 files 28.291 GiB, total 276774 files 398.835 GiB, 0 errors ETA 1:48:29 [5:01] 8.05% 39008 files 32.113 GiB, total 276774 files 398.835 GiB, 0 errors ETA 1:45:58
Rund 3,7 GiB pro Minute, also etwa 60 MB/s, statt vorher 0,19. Der Upload durch den Proxy von tailscale serve auf dem NAS war die Bremse. Beim Herunterladen ist er schnell, beim Hochladen der vielen 16-MB-Pakete von restic aber nicht.
Die Lösung: zwei Wege
Daher der zweite Weg im Skript: Zuhause geht restic direkt per HTTP an den rest-server, unterwegs weiter über Tailscale. Unterwegs sind die täglichen Änderungen klein, da reichen die 3 MB/s. Damit das Skript nicht in einem fremden Netz mit zufällig gleicher Adresse das Passwort verschickt, prüft es vorher die MAC-Adresse des NAS.
Ein Kompromiss bleibt: Im Heimnetz geht das Passwort für den rest-server unverschlüsselt über HTTP. Die Backup-Daten selbst verschlüsselt restic immer, und löschen kann man wegen append-only ohnehin nichts. Für ein Heimnetz halte ich das für vertretbar.
Mit accept-routes=false erreiche ich Geräte im Heimnetz unterwegs nur noch über ihre Tailscale-Namen, nicht mehr über 192.168.1.x. Für mich passt das.
Der erste Home-Lauf mit knapp 399 GiB dauerte danach etwa zwei Stunden, der erste System-Lauf mit 32 GiB nur sechseinhalb Minuten. Gespeichert wurden davon dank Kompression 16,8 GiB.
Fazit
Das Grundgerüst aus restic, rest-server und zwei systemd-Timern war schnell aufgebaut und läuft ab jetzt ohne mein Zutun. Den meisten Aufwand hat die Ursachenforschung für die langsame Übertragungsgeschwindigkeit gemacht. Dabei habe ich gelernt, Up- und Download getrennt zu messen und jedes Stück der Kette einzeln zu prüfen, statt beim ersten guten Messwert aufzuhören.
Und das wichtigste: Ich habe jetzt endlich ein Backup vom Laptop, das ich auch schon einmal zurückgespielt habe.