#!/usr/bin/env bash # Tomedo-only Backup: # WireGuard hoch, CIFS mounten (inkl. Stale-Handle-Fix), # danach nur Tomedo Snapshot + Tomedo Files per rsync. # Zusätzlich: Pushover-Benachrichtigung bei Erfolg/Fehler. # Wenn wg0 schon aktiv ist, wird es nicht erneut gestartet und beim Cleanup # nur dann beendet, wenn dieses Skript es selbst hochgebracht hat. # Schutz gegen parallele Ausführung per flock. set -Eeuo pipefail # ----------------------------- # Konfiguration # ----------------------------- WG_IF="wg0" VPN_TEST_IP="10.202.101.10" WG_WAS_STARTED_BY_SCRIPT=0 LOCK_FILE="/var/lock/agathe_backup.lock" CIFS_SHARE="//10.202.101.10/nchsdhg" CIFS_MOUNTPOINT="/mnt/nchsdhg_agathe" CIFS_USER="nchsdhg" CIFS_PASS="mugkeN-zexdab-9gyfky" CIFS_CRED_FILE="" # z.B. "/etc/samba/cred_nchsdhg" # ----------------------------- # Pushover # ----------------------------- PUSHOVER_USER_TOKEN="uFBKJ1LmL3eMUbHZs2ktLjSH8RyJ2Z" PUSHOVER_API_TOKEN="a3xzevfk8vwpbj6wp6duzbwy43pcmx" PUSHOVER_TITLE="nchsdhg" # ----------------------------- # Tomedo Backup # ----------------------------- TOMEDO_SRC_ROOT="/mnt/TomedoBackup" TOMEDO_LAST_FILE="${TOMEDO_SRC_ROOT}/lastFilesBackup" TOMEDO_DEST_ROOT="${CIFS_MOUNTPOINT}/TomedoBackup" TOMEDO_MACOS_EXCLUDES="${TOMEDO_DEST_ROOT}/macos.excludes" TOMEDO_FILES_EXCLUDES="${TOMEDO_DEST_ROOT}/files.excludes" # ----------------------------- # Hilfsfunktionen # ----------------------------- log() { echo "[$(date +'%F %T')] $*"; } ts_now() { date +'%F %T'; } send_pushover() { local message="$1" local priority="${2:-0}" if command -v curl >/dev/null 2>&1; then curl -s \ --form-string "token=${PUSHOVER_API_TOKEN}" \ --form-string "user=${PUSHOVER_USER_TOKEN}" \ --form-string "title=${PUSHOVER_TITLE}" \ --form-string "message=${message}" \ --form-string "priority=${priority}" \ https://api.pushover.net/1/messages.json >/dev/null || true else log "WARNUNG: curl fehlt, Pushover konnte nicht gesendet werden." fi } need_root() { if [[ "${EUID}" -ne 0 ]]; then echo "Bitte als root ausführen (z.B. sudo $0)." exit 1 fi } acquire_lock() { exec 200>"$LOCK_FILE" if ! flock -n 200; then log "Eine andere Instanz läuft bereits. Abbruch." send_pushover "Backup wurde nicht gestartet, weil bereits eine Instanz läuft." exit 0 fi echo "$$" 1>&200 || true log "Lock gesetzt: $LOCK_FILE" } cleanup() { set +e log "== Cleanup ==" if mountpoint -q "$CIFS_MOUNTPOINT"; then log "Unmount: $CIFS_MOUNTPOINT" umount "$CIFS_MOUNTPOINT" 2>/dev/null || umount -l "$CIFS_MOUNTPOINT" || true else log "Mountpoint nicht gemountet: $CIFS_MOUNTPOINT" fi if [[ "$WG_WAS_STARTED_BY_SCRIPT" -eq 1 ]]; then if wg show "$WG_IF" &>/dev/null; then log "WireGuard down: $WG_IF" wg-quick down "$WG_IF" >/dev/null 2>&1 || true else log "WireGuard war bereits nicht mehr aktiv: $WG_IF" fi else log "WireGuard bleibt aktiv (nicht von diesem Skript gestartet)." fi } on_error() { local exit_code=$? local line_no="${1:-unknown}" log "FEHLER in Zeile ${line_no}, Exit-Code ${exit_code}" send_pushover "Backup FEHLER auf $(hostname): Zeile ${line_no}, Exit-Code ${exit_code}" 1 trap - EXIT cleanup exit "$exit_code" } on_exit() { cleanup } trap 'on_error $LINENO' ERR trap on_exit EXIT check_deps() { local deps=(wg-quick wg mount rsync ping mountpoint umount date sleep id mkdir rm dirname curl hostname flock cat) for d in "${deps[@]}"; do command -v "$d" >/dev/null 2>&1 || { echo "Fehlt: $d"; exit 1; } done } bringup_wg() { log "== WireGuard prüfen: $WG_IF ==" if wg show "$WG_IF" &>/dev/null; then log "WireGuard ist bereits aktiv: $WG_IF" else log "== WireGuard up: $WG_IF ==" wg-quick up "$WG_IF" WG_WAS_STARTED_BY_SCRIPT=1 fi log "== Prüfe VPN/Konnektivität zu $VPN_TEST_IP ==" for i in {1..10}; do if ping -c1 -W1 "$VPN_TEST_IP" >/dev/null 2>&1; then log "OK: $VPN_TEST_IP erreichbar." return 0 fi sleep 1 done log "FEHLER: $VPN_TEST_IP nicht erreichbar." exit 1 } _mount_cifs_with_opts() { local opts="$1" if [[ -n "$CIFS_CRED_FILE" ]]; then log "Mount mit credentials file: $CIFS_CRED_FILE" mount -t cifs "$CIFS_SHARE" "$CIFS_MOUNTPOINT" -o "credentials=${CIFS_CRED_FILE},${opts}" else log "Mount mit username/pass (klartext) + opts: ${opts}" mount -t cifs "$CIFS_SHARE" "$CIFS_MOUNTPOINT" -o "username=${CIFS_USER},password=${CIFS_PASS},${opts}" fi } mount_cifs() { log "== CIFS mount: $CIFS_SHARE -> $CIFS_MOUNTPOINT ==" if mountpoint -q "$CIFS_MOUNTPOINT"; then log "Mountpoint ist gemountet -> versuche umount" umount "$CIFS_MOUNTPOINT" 2>/dev/null || umount -l "$CIFS_MOUNTPOINT" || true fi if ! mkdir -p "$CIFS_MOUNTPOINT" 2>/dev/null; then log "WARNUNG: Mountpoint kaputt (stale handle). Versuche umount -l und neu anlegen." umount -l "$CIFS_MOUNTPOINT" 2>/dev/null || true mkdir -p "$(dirname "$CIFS_MOUNTPOINT")" rm -rf "$CIFS_MOUNTPOINT" 2>/dev/null || true mkdir -p "$CIFS_MOUNTPOINT" fi local opts_base="uid=$(id -u),gid=$(id -g),iocharset=utf8,vers=3.0" local opts_robust="${opts_base},cache=strict,actimeo=30,echo_interval=30" set +e _mount_cifs_with_opts "$opts_robust" local rc=$? set -e if [[ $rc -eq 0 ]]; then log "OK gemountet (robust)." return 0 fi log "WARNUNG: CIFS mount (robust) fehlgeschlagen (rc=$rc). Fallback auf Basis-Optionen." set +e _mount_cifs_with_opts "$opts_base" rc=$? set -e if [[ $rc -eq 0 ]]; then log "OK gemountet (base)." return 0 fi log "FEHLER: CIFS mount fehlgeschlagen (rc=$rc)." exit 1 } ensure_cifs() { if ! mountpoint -q "$CIFS_MOUNTPOINT"; then log "WARNUNG: CIFS nicht gemountet -> remount" mount_cifs fi if ! mountpoint -q "$CIFS_MOUNTPOINT"; then log "FEHLER: CIFS Remount fehlgeschlagen." exit 1 fi } rsync_tomedo_backup() { ensure_cifs log "== Tomedo Backup starten ==" if [[ ! -f "$TOMEDO_LAST_FILE" ]]; then log "FEHLER: Datei fehlt: $TOMEDO_LAST_FILE" exit 1 fi local last_backup last_backup="$(cat "$TOMEDO_LAST_FILE")" if [[ -z "$last_backup" ]]; then log "FEHLER: $TOMEDO_LAST_FILE ist leer" exit 1 fi local src_snapshot="${TOMEDO_SRC_ROOT}/${last_backup}/" local dst_snapshot="${TOMEDO_DEST_ROOT}/${last_backup}" local src_files="${TOMEDO_SRC_ROOT}/files/" local dst_files="${TOMEDO_DEST_ROOT}/files" if [[ ! -d "$src_snapshot" ]]; then log "FEHLER: Tomedo Snapshot-Ordner fehlt: $src_snapshot" exit 1 fi if [[ ! -d "$src_files" ]]; then log "FEHLER: Tomedo files-Ordner fehlt: $src_files" exit 1 fi if [[ ! -f "$TOMEDO_MACOS_EXCLUDES" ]]; then log "FEHLER: Exclude-Datei fehlt: $TOMEDO_MACOS_EXCLUDES" exit 1 fi if [[ ! -f "$TOMEDO_FILES_EXCLUDES" ]]; then log "FEHLER: Exclude-Datei fehlt: $TOMEDO_FILES_EXCLUDES" exit 1 fi mkdir -p "$dst_snapshot" "$dst_files" log "== rsync Tomedo Snapshot: $src_snapshot -> $dst_snapshot ==" rsync -r -l -t -O --info=progress2 \ --exclude-from "$TOMEDO_MACOS_EXCLUDES" \ "$src_snapshot" \ "$dst_snapshot" log "== rsync Tomedo Files: $src_files -> $dst_files ==" rsync -r -l -t -O --info=progress2 \ --exclude-from "$TOMEDO_MACOS_EXCLUDES" \ --exclude-from "$TOMEDO_FILES_EXCLUDES" \ "$src_files" \ "$dst_files" log "== Tomedo Backup fertig: $last_backup ==" } # ----------------------------- # Main # ----------------------------- need_root check_deps acquire_lock send_pushover "Backup START auf $(hostname) um $(ts_now)." bringup_wg mount_cifs rsync_tomedo_backup log "== Fertig. ==" send_pushover "Backup ENDE auf $(hostname) um $(ts_now)."