Files
datensicherung_rsync/README.md
T

9.3 KiB
Raw Blame History

README Snapshot-Backup mit rsync

Dieses Skript erstellt Snapshots des Home-Verzeichnisses mit rsync und --link-dest.[cite:43][cite:34] Jeder Snapshot sieht wie ein vollständiges Backup aus, aber unveränderte Dateien werden per Hardlink wiederverwendet, sodass Speicherplatz gespart wird.[cite:34][cite:92]

Zweck

Das Skript sichert den Inhalt von HOME auf ein externes Zielverzeichnis und legt dabei zeitgestempelte Snapshot-Ordner an.[cite:43] Über den Symlink latest wird immer auf den zuletzt erfolgreichen Sicherungsstand verwiesen.[cite:34][cite:92]

Typische Vorteile dieses Ansatzes:

  • Einfach browsebare Backups, weil jeder Snapshot als normales Verzeichnis vorliegt.[cite:43][cite:91]
  • Platzersparnis durch Hardlinks mit --link-dest.[cite:34][cite:92]
  • Aufbewahrung mehrerer Sicherungsstände durch Rotation älterer Snapshots.[cite:43]

Voraussetzungen

Für --link-dest sollte das Ziel auf einem Linux-Dateisystem liegen, das Hardlinks sauber unterstützt, etwa ext4, XFS oder btrfs.[cite:34][cite:91] Dateisysteme wie exFAT sind für diese Snapshot-Technik ungeeignet.[cite:34]

Benötigte Werkzeuge:

  • bash
  • rsync
  • readlink
  • tee
  • Standard-Tools wie mkdir, ln, ls, tail, rm

Verzeichnisaufbau

Das Skript erzeugt pro Lauf einen neuen Snapshot mit Zeitstempel.[cite:43][cite:92]

Beispiel:

/run/media/hans/usbsicherung/jacboy_sicherung/
└── <hostname>/
    ├── 2026-04-28_10-30-00/
    ├── 2026-04-29_10-30-00/
    ├── latest -> /run/media/hans/usbsicherung/jacboy_sicherung/<hostname>/2026-04-29_10-30-00
    └── logs/

Funktionsweise

Das Skript prüft zunächst, ob das Zielverzeichnis existiert und beschreibbar ist, und legt anschließend Snapshot- und Log-Verzeichnisse an.[cite:43][cite:97] Danach wird rsync mit Archivoptionen sowie Ausschlüssen für nicht benötigte Verzeichnisse ausgeführt.[cite:43][cite:91]

Wenn bereits ein gültiger vorheriger Snapshot existiert, wird dieser über --link-dest als Referenz verwendet.[cite:34][cite:92] Unveränderte Dateien werden dann nicht erneut kopiert, sondern als Hardlinks im neuen Snapshot eingebunden.[cite:34]

Nach erfolgreichem Lauf aktualisiert das Skript den Symlink latest und entfernt ältere Snapshots oberhalb des konfigurierten Aufbewahrungswerts KEEP.[cite:43]

Verwendete rsync-Optionen

Option Bedeutung
-a Archivmodus: rekursiv kopieren und Metadaten erhalten.[cite:91][cite:43]
-H Hardlinks in der Quelle erhalten.[cite:91]
-A ACLs mitnehmen, sofern vorhanden.[cite:91]
-X Erweiterte Attribute mitnehmen.[cite:91]
-x Auf demselben Dateisystem bleiben; zusätzliche Mounts unterhalb von HOME werden nicht traversiert.[cite:91][cite:43]
--delete Entfernt Dateien im neuen Snapshot, die in der Quelle nicht mehr vorhanden sind; ältere Snapshots bleiben dabei unberührt.[cite:91][cite:34]
--delete-excluded Entfernt auch explizit ausgeschlossene Inhalte aus dem Ziel-Snapshot.[cite:91]
--link-dest Verweist auf den letzten Snapshot, damit unveränderte Dateien per Hardlink wiederverwendet werden.[cite:34][cite:92]

Anpassbare Variablen

Die wichtigsten Variablen stehen am Anfang des Skripts:

SRC="${HOME}/"
DEST_BASE="/run/media/hans/usbsicherung/jacboy_sicherung"
KEEP=30
  • SRC: Quellverzeichnis, standardmäßig das eigene Home-Verzeichnis.
  • DEST_BASE: Basisverzeichnis auf dem Backup-Medium.
  • KEEP: Anzahl der Snapshots, die aufbewahrt werden sollen.

Auch die Ausschlussliste kann direkt im Array EXCLUDES angepasst werden, zum Beispiel für Cache-, Download- oder temporäre Verzeichnisse.[cite:43]

Verwendung

Syntax prüfen:

bash -n sicherung.sh

Skript ausführen:

bash sicherung.sh

Für einen sicheren Test kann vorübergehend --dry-run in die RSYNC_OPTS aufgenommen werden; dann zeigt rsync nur an, was passieren würde, ohne tatsächlich Daten zu schreiben.[cite:91][cite:43]

Wiederherstellungstest

Ein Backup gilt erst dann als verlässlich, wenn sich Daten daraus testweise wiederherstellen und inhaltlich prüfen lassen.[cite:105][cite:109] Der empfohlene Weg ist, einen Snapshot nicht direkt ins echte Home-Verzeichnis zurückzuschreiben, sondern zunächst in ein separates Testverzeichnis zu restaurieren.[cite:105][cite:107]

Testverzeichnis anlegen

mkdir -p /tmp/restore-test-home

Snapshot auswählen und zurückspielen

Beispiel für einen Restore eines bestimmten Snapshots:

SNAP="/run/media/hans/usbsicherung/jacboy_sicherung/$(hostname -s)/2026-04-28_10-30-00"

rsync -aHAX \
  "${SNAP}/" \
  /tmp/restore-test-home/

Damit werden die Daten aus dem Snapshot mit Rechten, ACLs und erweiterten Attributen in das Testziel kopiert.[cite:91][cite:43] Alternativ kann auch nur ein Teilbereich wie Documents/, ein Projektordner oder .config/ restauriert werden, um einzelne Anwendungsszenarien gezielt zu prüfen.[cite:105][cite:107]

Integrität prüfen

Nach dem Restore sollten mehrere Prüfungen erfolgen:[cite:105][cite:110]

  • Wichtige Dateien öffnen, etwa Dokumente, PDFs, Bilder oder Quellcode.[cite:105]
  • Anwendungen testweise mit restaurierten Daten starten, zum Beispiel ein Projekt im Editor oder eine Konfiguration in einem Testprofil.[cite:107]
  • Für wichtige Dateien Checksummen vergleichen, um die inhaltliche Identität sicherzustellen.[cite:110]

Beispiel mit sha256sum:

cd "${HOME}"
sha256sum wichtige_datei.odt

cd /tmp/restore-test-home
sha256sum wichtige_datei.odt

Die Prüfsummen sollten identisch sein.[cite:110]

Worauf beim Restore zu achten ist

Beim Restore-Test sollten insbesondere Dateirechte, Symlinks, ACLs, xattrs und eventuelle Fehlermeldungen von rsync geprüft werden.[cite:91][cite:105] Gerade bei einem Restore auf ein anderes Dateisystem kann es Einschränkungen bei ACLs oder erweiterten Attributen geben.[cite:91]

Empfehlung für die Praxis

Sinnvoll ist ein regelmäßiger Wiederherstellungstest, zum Beispiel quartalsweise, mit kurzer Dokumentation: verwendeter Snapshot, Testziel, geprüfte Dateien, Ergebnis und Auffälligkeiten.[cite:105][cite:109] Erst ein erfolgreich getesteter Restore macht aus einem Backup eine belastbare Datensicherung.[cite:105]

Beispielskript

#!/usr/bin/env bash
set -Eeuo pipefail

SRC="${HOME}/"
DEST_BASE="/run/media/hans/usbsicherung/jacboy_sicherung"
HOST="$(hostname -s)"
STAMP="$(date +%F_%H-%M-%S)"
SNAPSHOT_DIR="${DEST_BASE}/${HOST}/${STAMP}"
LAST_LINK="${DEST_BASE}/${HOST}/latest"
LOG_DIR="${DEST_BASE}/${HOST}/logs"
LOG_FILE="${LOG_DIR}/backup-${STAMP}.log"
KEEP=30

log() {
  local msg="[$(date +%F\ %T)] $*"
  echo "${msg}"
  if [[ -n "${LOG_FILE:-}" ]]; then
    mkdir -p "$(dirname "${LOG_FILE}")"
    echo "${msg}" >> "${LOG_FILE}"
  fi
}

fail() {
  log "FEHLER: $*"
  exit 1
}

if [[ ! -d "${DEST_BASE}" ]]; then
  fail "Zielbasis ${DEST_BASE} existiert nicht oder ist nicht gemountet. Bitte USB-Laufwerk prüfen."
fi

if [[ ! -w "${DEST_BASE}" ]]; then
  fail "Zielbasis ${DEST_BASE} ist nicht beschreibbar. Rechte/Mount prüfen."
fi

mkdir -p "${SNAPSHOT_DIR}" "${LOG_DIR}"

log "Starte Backup: ${SRC} -> ${SNAPSHOT_DIR}"

RSYNC_OPTS=(
  -aHAXx
  --numeric-ids
  --delete
  --delete-excluded
  --info=stats2,progress2
  --human-readable
  --partial
)

EXCLUDES=(
  --exclude=".cache/"
  --exclude="Downloads/"
  --exclude=".local/share/Trash/"
  --exclude=".gvfs/"
  --exclude=".dotnet/"
  --exclude=".codegpt/"
  --exclude=".copilot/"
  --exclude=".var/"
  --exclude=".vscode/"
  --exclude="temp/"
)

if [[ -L "${LAST_LINK}" ]] && [[ -d "$(readlink -f "${LAST_LINK}")" ]]; then
  RSYNC_OPTS+=(--link-dest="$(readlink -f "${LAST_LINK}")")
  log "Verwende link-dest: $(readlink -f "${LAST_LINK}")"
else
  log "Kein gültiger letzter Snapshot gefunden  es wird ein vollständiger Lauf erstellt."
fi

if rsync "${RSYNC_OPTS[@]}" \
    "${EXCLUDES[@]}" \
    "${SRC}" "${SNAPSHOT_DIR}/" | tee -a "${LOG_FILE}"
then
  log "rsync erfolgreich abgeschlossen."
else
  fail "rsync ist mit einem Fehler beendet worden."
fi

ln -sfn "${SNAPSHOT_DIR}" "${LAST_LINK}"
log "latest-Link zeigt jetzt auf ${SNAPSHOT_DIR}"

cd "${DEST_BASE}/${HOST}"
SNAPS=(20*/)

if (( ${#SNAPS[@]} > KEEP )); then
  log "Rotationslauf: Es existieren ${#SNAPS[@]} Snapshots, KEEP=${KEEP}."
  ls -1dt 20*/ | tail -n +$((KEEP + 1)) | while read -r old; do
    log "Lösche alten Snapshot: ${old}"
    rm -rf -- "${old}"
  done
else
  log "Rotationslauf: Es müssen keine alten Snapshots gelöscht werden (Anzahl: ${#SNAPS[@]}, KEEP=${KEEP})."
fi

log "Backup abgeschlossen."

Sicherheitshinweise

Ein erster Testlauf sollte als Dry-Run erfolgen, um Ausschlüsse, Zielpfad und Verhalten zu prüfen, bevor echte Daten geschrieben oder gelöscht werden.[cite:91][cite:43] Das Zielverzeichnis sollte nicht innerhalb des zu sichernden Home-Verzeichnisses liegen, um Rekursionen und ungewollte Mitsicherungen zu vermeiden.[cite:43]

Bei Verwendung von --delete gilt: Gelöschte Dateien verschwinden aus dem neu erzeugten Snapshot, bleiben aber in älteren Snapshots weiterhin vorhanden.[cite:34][cite:92] Genau dadurch entsteht die gewünschte Snapshot-Historie.[cite:92]

Mögliche Erweiterungen

Sinnvolle nächste Ausbaustufen wären ein optionaler --dry-run-Schalter, eine Prüfung des tatsächlichen Mountpoints per mountpoint, sowie eine Automatisierung über systemd-Timer statt manueller Ausführung.[cite:97][cite:43]