4 Commits

Author SHA1 Message Date
René Mathieu
6808715b3d Add timestamped Pushover start/end messages 2026-03-11 08:31:21 +01:00
René Mathieu
681f7f0cfc Create Tomedo-only backup script 2026-03-11 08:28:29 +01:00
René Mathieu
6f654ffdb3 Harden Pushover error notifications on abort 2026-03-08 15:25:22 +01:00
René Mathieu
02dfb054b5 Handle Borg warning exit codes without aborting 2026-03-08 15:13:08 +01:00

View File

@@ -1,15 +1,11 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Robust: WireGuard hoch, CIFS mounten (inkl. Stale-Handle-Fix), rsync, # Tomedo-only Backup:
# nur erlaubte Dateitypen zusätzlich nach paperless-consume (flach, OHNE GIF), # WireGuard hoch, CIFS mounten (inkl. Stale-Handle-Fix),
# Paperless-Backup rsync als echtes Sync (mit --delete), # danach nur Tomedo Snapshot + Tomedo Files per rsync.
# Anmeldung ebenfalls sync + consume,
# zusätzlich: Borg lokal (inkrementell, versioniert) + Mirror per rsync auf CIFS (mit delete),
# zusätzlich: Tomedo-Backup per rsync,
# robust gegen CIFS-Aussetzer: ensure_cifs + rsync temp-dir lokal + Mount-Fallback.
# Zusätzlich: Pushover-Benachrichtigung bei Erfolg/Fehler. # Zusätzlich: Pushover-Benachrichtigung bei Erfolg/Fehler.
# Fix: Wenn wg0 schon aktiv ist, wird es nicht erneut gestartet und beim Cleanup # Wenn wg0 schon aktiv ist, wird es nicht erneut gestartet und beim Cleanup
# nur dann beendet, wenn dieses Skript es selbst hochgebracht hat. # nur dann beendet, wenn dieses Skript es selbst hochgebracht hat.
# Zusätzlich: Schutz gegen parallele Ausführung per flock. # Schutz gegen parallele Ausführung per flock.
set -Eeuo pipefail set -Eeuo pipefail
@@ -25,16 +21,6 @@ LOCK_FILE="/var/lock/agathe_backup.lock"
CIFS_SHARE="//10.202.101.10/nchsdhg" CIFS_SHARE="//10.202.101.10/nchsdhg"
CIFS_MOUNTPOINT="/mnt/nchsdhg_agathe" CIFS_MOUNTPOINT="/mnt/nchsdhg_agathe"
DEST_GROOT="${CIFS_MOUNTPOINT}/groot_mobile/"
DEST_ANMELDUNG="${CIFS_MOUNTPOINT}/Anmeldung/"
DEST_PAPERLESS_BACKUP="${CIFS_MOUNTPOINT}/paperless-backup/"
SRC_GROOT="/mnt/groot/"
SRC_ANMELDUNG="/mnt/Anmeldung/"
SRC_PAPERLESS_BACKUP="/mnt/paperless-backup/daily/"
PAPERLESS_CONSUME="/mnt/paperless-consume"
CIFS_USER="nchsdhg" CIFS_USER="nchsdhg"
CIFS_PASS="mugkeN-zexdab-9gyfky" CIFS_PASS="mugkeN-zexdab-9gyfky"
CIFS_CRED_FILE="" # z.B. "/etc/samba/cred_nchsdhg" CIFS_CRED_FILE="" # z.B. "/etc/samba/cred_nchsdhg"
@@ -46,40 +32,6 @@ PUSHOVER_USER_TOKEN="uFBKJ1LmL3eMUbHZs2ktLjSH8RyJ2Z"
PUSHOVER_API_TOKEN="a3xzevfk8vwpbj6wp6duzbwy43pcmx" PUSHOVER_API_TOKEN="a3xzevfk8vwpbj6wp6duzbwy43pcmx"
PUSHOVER_TITLE="nchsdhg" PUSHOVER_TITLE="nchsdhg"
# -----------------------------
# Borg (lokal + Mirror auf CIFS)
# -----------------------------
LOCAL_BORG_REPO="/var/backups/borg/agathe"
LOCAL_BORG_BASE="/var/backups/borg"
REMOTE_BORG_MIRROR="${CIFS_MOUNTPOINT}/borg-mirror/agathe"
BORG_PASSPHRASE_FILE="/root/.config/borg/passphrase"
BORG_LOCK_WAIT="120"
KEEP_DAILY=7
KEEP_WEEKLY=4
KEEP_MONTHLY=6
# Mirror: Ziel exakt wie Quelle (löscht auf Ziel, was lokal nicht mehr existiert)
MIRROR_DELETE="1"
# Optional: nach jedem Lauf borg check machen (kostet Zeit)
BORG_CHECK="0"
# rsync Optionen (CIFS-freundlich: kein owner/group/perms setzen)
# Zusätzlich: --temp-dir=/tmp -> rsync mkstemp NICHT auf CIFS (hilft bei CIFS-Reconnect/Timeouts)
RSYNC_OPTS=(-rltvh --info=progress2 --no-owner --no-group --no-perms --temp-dir=/tmp)
RSYNC_EXCLUDES=(--exclude="#recycle/" --exclude=".DS_Store" --exclude="._*" --exclude="Thumbs.db")
# Paperless Backup: echtes Sync (LÖSCHT auf Ziel, was in Quelle nicht mehr existiert)
RSYNC_DELETE_OPTS=(--delete --delete-delay)
# Optional: Testlauf für delete (auf "1" setzen um erstmal nur zu sehen, was gelöscht würde)
DRY_RUN_DELETE="0"
# Welche Dateien nach paperless-consume? (OHNE GIF)
CONSUME_EXT_REGEX='\.([Pp][Dd][Ff]|[Dd][Oo][Cc][Xx]?|[Xx][Ll][Ss][Xx]?|[Pp][Pp][Tt][Xx]?|[Oo][Dd][Tt]|[Oo][Dd][Ss]|[Oo][Dd][Pp]|[Rr][Tt][Ff]|[Tt][Xx][Tt]|[Cc][Ss][Vv]|[Mm][Dd]|[Hh][Tt][Mm][Ll]?|[Jj][Pp][Ee]?[Gg]|[Pp][Nn][Gg]|[Tt][Ii][Ff][Ff]?|[Ww][Ee][Bb][Pp]|[Hh][Ee][Ii][Cc])$'
# ----------------------------- # -----------------------------
# Tomedo Backup # Tomedo Backup
# ----------------------------- # -----------------------------
@@ -173,7 +125,7 @@ trap 'on_error $LINENO' ERR
trap on_exit EXIT trap on_exit EXIT
check_deps() { check_deps() {
local deps=(wg-quick wg mount rsync ping mountpoint umount awk cp date basename tee mktemp sleep id mkdir rm dirname borg df curl hostname flock cat) 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 for d in "${deps[@]}"; do
command -v "$d" >/dev/null 2>&1 || { echo "Fehlt: $d"; exit 1; } command -v "$d" >/dev/null 2>&1 || { echo "Fehlt: $d"; exit 1; }
done done
@@ -268,107 +220,6 @@ ensure_cifs() {
fi fi
} }
copy_to_paperless_flat_if_allowed_ext() {
local src="$1"
local rel="$2"
local base
base="$(basename "$src")"
[[ "$base" == ._* ]] && return 0
[[ ! "$base" =~ $CONSUME_EXT_REGEX ]] && return 0
mkdir -p "$PAPERLESS_CONSUME"
local dst="$PAPERLESS_CONSUME/$base"
if [[ -e "$dst" ]]; then
local ts name ext
ts="$(date +%Y%m%d-%H%M%S)"
name="${base%.*}"
ext="${base##*.}"
if [[ "$base" == *.* && "$ext" != "$base" ]]; then
dst="$PAPERLESS_CONSUME/${name}_${ts}.${ext}"
else
dst="$PAPERLESS_CONSUME/${base}_${ts}"
fi
fi
log "Paperless copy: $rel -> $(basename "$dst")"
cp --preserve=mode,timestamps -- "$src" "$dst" || {
log "WARNUNG: cp fehlgeschlagen: '$src' -> '$dst' (mache weiter)"
return 0
}
}
rsync_and_copy_to_consume_flat() {
local SRC="$1"
local DEST="$2"
local LABEL="$3"
ensure_cifs
log "== rsync ${LABEL}: $SRC -> $DEST =="
mkdir -p "$DEST"
local tmpfile rc
tmpfile="$(mktemp)"
set +e
rsync "${RSYNC_OPTS[@]}" "${RSYNC_EXCLUDES[@]}" --out-format='%i %n' "$SRC" "$DEST" | tee "$tmpfile"
rc=${PIPESTATUS[0]}
set -e
log "rsync(${LABEL}) Exit-Code: $rc"
if [[ "$rc" -ne 0 && "$rc" -ne 23 && "$rc" -ne 24 ]]; then
log "FEHLER: rsync (${LABEL}) Exit-Code $rc"
exit "$rc"
fi
if [[ "$rc" -ne 0 ]]; then
log "WARNUNG: rsync (${LABEL}) Exit-Code $rc (mache trotzdem weiter)"
fi
log "== Kopiere zusätzlich (${LABEL}) (NUR erlaubte Endungen, OHNE GIF) flach nach: $PAPERLESS_CONSUME =="
awk '
$1 !~ /^\./ && $1 ~ /^.[f]/ {
$1=""; sub(/^ /,""); print
}
' "$tmpfile" | while IFS= read -r rel; do
local local_src="${SRC}${rel}"
if [[ ! -f "$local_src" ]]; then
log "WARNUNG: Quelle nicht gefunden (überspringe): $local_src"
continue
fi
copy_to_paperless_flat_if_allowed_ext "$local_src" "$rel"
done
rm -f "$tmpfile"
}
rsync_paperless_backup() {
ensure_cifs
log "== rsync Paperless Backup (SYNC mit delete): $SRC_PAPERLESS_BACKUP -> $DEST_PAPERLESS_BACKUP =="
if [[ -z "$DEST_PAPERLESS_BACKUP" || "$DEST_PAPERLESS_BACKUP" == "/" ]]; then
log "FEHLER: DEST_PAPERLESS_BACKUP ist unsicher gesetzt."
exit 1
fi
mkdir -p "$DEST_PAPERLESS_BACKUP"
local extra=()
if [[ "$DRY_RUN_DELETE" == "1" ]]; then
extra+=(--dry-run)
log "HINWEIS: DRY RUN aktiv (es wird nichts wirklich gelöscht/kopiert)."
fi
rsync "${RSYNC_OPTS[@]}" "${RSYNC_EXCLUDES[@]}" "${RSYNC_DELETE_OPTS[@]}" "${extra[@]}" \
"$SRC_PAPERLESS_BACKUP" "$DEST_PAPERLESS_BACKUP"
}
rsync_tomedo_backup() { rsync_tomedo_backup() {
ensure_cifs ensure_cifs
@@ -430,107 +281,6 @@ rsync_tomedo_backup() {
log "== Tomedo Backup fertig: $last_backup ==" log "== Tomedo Backup fertig: $last_backup =="
} }
# -----------------------------
# Borg: lokal sichern + Mirror auf CIFS
# -----------------------------
run_borg_allow_warning() {
local label="$1"
shift
local rc
set +e
"$@"
rc=$?
set -e
if [[ "$rc" -ge 2 ]]; then
log "FEHLER: ${label} fehlgeschlagen (Exit-Code ${rc})."
return "$rc"
fi
if [[ "$rc" -eq 1 ]]; then
log "WARNUNG: ${label} mit Warnungen beendet (Exit-Code 1), mache weiter."
fi
}
borg_local_backup() {
log "== Borg lokal Backup -> $LOCAL_BORG_REPO =="
if [[ ! -d "$LOCAL_BORG_REPO" ]]; then
log "FEHLER: Lokales Borg Repo fehlt: $LOCAL_BORG_REPO (einmal borg init ausführen)"
exit 1
fi
if [[ ! -f "$BORG_PASSPHRASE_FILE" ]]; then
log "FEHLER: Passphrase-Datei fehlt: $BORG_PASSPHRASE_FILE"
exit 1
fi
export BORG_PASSPHRASE
BORG_PASSPHRASE="$(<"$BORG_PASSPHRASE_FILE")"
export BORG_LOCK_WAIT="$BORG_LOCK_WAIT"
local archive="agathe-$(hostname)-$(date +%F_%H%M%S)"
log "Platz lokal: $(df -h "$LOCAL_BORG_BASE" | tail -1)"
run_borg_allow_warning "borg create" borg create --stats --compression zstd,6 \
"$LOCAL_BORG_REPO::$archive" \
/ \
/boot \
/boot/efi \
--numeric-ids \
--one-file-system \
--exclude /proc \
--exclude /sys \
--exclude /dev \
--exclude /run \
--exclude /tmp \
--exclude /var/tmp \
--exclude /mnt \
--exclude /media \
--exclude /var/cache \
--exclude /var/lib/docker \
--exclude /var/lib/containerd \
--exclude /swapfile \
--exclude /mnt/paperless \
--exclude /mnt/paperless-consume \
--exclude /mnt/paperless-backup \
--exclude /mnt/nchsdhg_agathe \
--exclude /mnt/groot \
--exclude-caches
run_borg_allow_warning "borg prune" borg prune -v --list "$LOCAL_BORG_REPO" \
--keep-daily="$KEEP_DAILY" \
--keep-weekly="$KEEP_WEEKLY" \
--keep-monthly="$KEEP_MONTHLY" \
--prefix "agathe-$(hostname)-"
if [[ "$BORG_CHECK" == "1" ]]; then
run_borg_allow_warning "borg check" borg check -v --verify-data "$LOCAL_BORG_REPO"
fi
log "Lokales Borg Backup fertig: $archive"
}
rsync_borg_mirror_to_cifs() {
log "== Spiegel Borg Repo per rsync -> $REMOTE_BORG_MIRROR =="
ensure_cifs
mkdir -p "$REMOTE_BORG_MIRROR"
local extra=()
if [[ "$MIRROR_DELETE" == "1" ]]; then
extra+=(--delete --delete-delay)
log "Mirror: delete aktiv (Ziel wird an Quelle angepasst)."
else
log "Mirror: delete NICHT aktiv."
fi
rsync -rltvh --info=progress2 --no-owner --no-group --no-perms --temp-dir=/tmp \
"${extra[@]}" \
"$LOCAL_BORG_REPO/" \
"$REMOTE_BORG_MIRROR/"
}
# ----------------------------- # -----------------------------
# Main # Main
# ----------------------------- # -----------------------------
@@ -543,14 +293,7 @@ send_pushover "Backup START auf $(hostname) um $(ts_now)."
bringup_wg bringup_wg
mount_cifs mount_cifs
rsync_and_copy_to_consume_flat "$SRC_GROOT" "$DEST_GROOT" "GROOT"
rsync_and_copy_to_consume_flat "$SRC_ANMELDUNG" "$DEST_ANMELDUNG" "ANMELDUNG"
rsync_paperless_backup
rsync_tomedo_backup rsync_tomedo_backup
borg_local_backup
rsync_borg_mirror_to_cifs
log "== Fertig. ==" log "== Fertig. =="
send_pushover "Backup ENDE auf $(hostname) um $(ts_now)." send_pushover "Backup ENDE auf $(hostname) um $(ts_now)."