#!/usr/bin/env bash set -Eeuo pipefail ######################################## # Konfiguration ######################################## 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" # Anzahl der aufzubewahrenden Snapshots KEEP=30 ######################################## # Hilfsfunktionen ######################################## log() { # Schreibt gleichzeitig auf stdout und ins Log (wenn LOG_FILE existiert) 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 } ######################################## # Vorprüfungen ######################################## # 1. Zielbasis muss existieren und beschreibbar sein 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 # 2. Snapshot- und Log-Verzeichnisse anlegen mkdir -p "${SNAPSHOT_DIR}" "${LOG_DIR}" log "Starte Backup: ${SRC} -> ${SNAPSHOT_DIR}" ######################################## # rsync-Optionen ######################################## 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/" ) # Wenn es bereits einen gültigen "latest"-Snapshot gibt, diesen als link-dest verwenden 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 ######################################## # rsync-Lauf ######################################## # Optional: Dry-Run zum Testen (auskommentieren, wenn du real sichern willst) # RSYNC_OPTS+=(--dry-run) log "Starte rsync..." 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 ######################################## # latest-Symlink aktualisieren ######################################## ln -sfn "${SNAPSHOT_DIR}" "${LAST_LINK}" log "latest-Link zeigt jetzt auf ${SNAPSHOT_DIR}" ######################################## # Snapshot-Rotation ######################################## cd "${DEST_BASE}/${HOST}" # Nur Verzeichnisse mit Datum/Zeit-Präfix (Beginn mit '20') betrachten SNAPS=(20*/) if (( ${#SNAPS[@]} > KEEP )); then log "Rotationslauf: Es existieren ${#SNAPS[@]} Snapshots, KEEP=${KEEP}." # Sortiert nach Datum (neuestes zuerst), alle älteren als KEEP löschen 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."