# 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: ```text /run/media/hans/usbsicherung/jacboy_sicherung/ └── / ├── 2026-04-28_10-30-00/ ├── 2026-04-29_10-30-00/ ├── latest -> /run/media/hans/usbsicherung/jacboy_sicherung//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: ```bash 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 bash -n sicherung.sh ``` Skript ausführen: ```bash 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] ## Beispielskript ```bash #!/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]