2026-04-28 11:22:40 +02:00
|
|
|
|
#!/usr/bin/env bash
|
|
|
|
|
|
set -Eeuo pipefail
|
|
|
|
|
|
|
2026-04-29 08:29:30 +02:00
|
|
|
|
DEST_BASE="/run/media/hans/usbsicherung/jacboy_sicherung/system_sicherung"
|
2026-04-30 08:54:54 +02:00
|
|
|
|
#DEST_BASE="/run/media/hans/usbsicherung/sicherungen"
|
|
|
|
|
|
|
2026-04-28 11:22:40 +02:00
|
|
|
|
HOST="$(hostname -s)"
|
|
|
|
|
|
STAMP="$(date +%F_%H-%M-%S)"
|
|
|
|
|
|
TARGET_DIR="${DEST_BASE}/${HOST}/${STAMP}"
|
|
|
|
|
|
LATEST_LINK="${DEST_BASE}/${HOST}/latest"
|
|
|
|
|
|
LOG_DIR="${DEST_BASE}/${HOST}/logs"
|
|
|
|
|
|
LOG_FILE="${LOG_DIR}/netzwerk-backup-${STAMP}.log"
|
|
|
|
|
|
KEEP=20
|
|
|
|
|
|
|
2026-04-29 08:29:30 +02:00
|
|
|
|
# Quellen
|
2026-04-28 11:22:40 +02:00
|
|
|
|
WG_SRC="/etc/wireguard"
|
|
|
|
|
|
NM_SRC="/etc/NetworkManager/system-connections"
|
2026-04-29 08:29:30 +02:00
|
|
|
|
APT_SOURCES_LIST="/etc/apt/sources.list"
|
|
|
|
|
|
APT_SOURCES_D="/etc/apt/sources.list.d"
|
|
|
|
|
|
APT_TRUSTED_D="/etc/apt/trusted.gpg.d"
|
|
|
|
|
|
APT_KEYRINGS="/etc/apt/keyrings"
|
2026-04-30 08:54:54 +02:00
|
|
|
|
USR_KEYRINGS="/usr/share/keyrings"
|
2026-04-28 11:22:40 +02:00
|
|
|
|
|
|
|
|
|
|
log() {
|
2026-04-29 08:29:30 +02:00
|
|
|
|
local msg="[$(date +%F\\ %T)] $*"
|
2026-04-28 11:22:40 +02:00
|
|
|
|
echo "${msg}"
|
|
|
|
|
|
mkdir -p "${LOG_DIR}"
|
|
|
|
|
|
echo "${msg}" >> "${LOG_FILE}"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fail() {
|
|
|
|
|
|
log "FEHLER: $*"
|
|
|
|
|
|
exit 1
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-29 08:29:30 +02:00
|
|
|
|
# Root nötig, weil /etc/* und ggf. restriktive Rechte
|
2026-04-28 11:22:40 +02:00
|
|
|
|
if [[ ${EUID} -ne 0 ]]; then
|
2026-04-29 08:29:30 +02:00
|
|
|
|
fail "Dieses Skript muss mit sudo oder als root ausgeführt werden."
|
2026-04-28 11:22:40 +02:00
|
|
|
|
fi
|
|
|
|
|
|
|
2026-04-29 08:29:30 +02:00
|
|
|
|
# Ziel prüfen
|
2026-04-28 11:22:40 +02:00
|
|
|
|
if [[ ! -d "${DEST_BASE}" ]]; then
|
|
|
|
|
|
fail "Zielbasis ${DEST_BASE} existiert nicht oder ist nicht gemountet."
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
if [[ ! -w "${DEST_BASE}" ]]; then
|
|
|
|
|
|
fail "Zielbasis ${DEST_BASE} ist nicht beschreibbar."
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
mkdir -p "${TARGET_DIR}" "${LOG_DIR}"
|
|
|
|
|
|
|
|
|
|
|
|
RSYNC_OPTS=(
|
|
|
|
|
|
-aHAX
|
|
|
|
|
|
--numeric-ids
|
|
|
|
|
|
--delete
|
|
|
|
|
|
--info=stats2,progress2
|
|
|
|
|
|
--human-readable
|
|
|
|
|
|
--partial
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if [[ -L "${LATEST_LINK}" ]] && [[ -d "$(readlink -f "${LATEST_LINK}")" ]]; then
|
|
|
|
|
|
RSYNC_OPTS+=(--link-dest="$(readlink -f "${LATEST_LINK}")")
|
|
|
|
|
|
log "Verwende link-dest: $(readlink -f "${LATEST_LINK}")"
|
|
|
|
|
|
else
|
|
|
|
|
|
log "Kein vorheriger Snapshot gefunden – es wird ein vollständiger Lauf erstellt."
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
|
|
backup_tree() {
|
|
|
|
|
|
local src="$1"
|
|
|
|
|
|
local name="$2"
|
|
|
|
|
|
|
|
|
|
|
|
if [[ -d "${src}" ]]; then
|
2026-04-29 08:29:30 +02:00
|
|
|
|
log "Sichere Verzeichnis ${src} -> ${TARGET_DIR}/${name}/"
|
2026-04-28 11:22:40 +02:00
|
|
|
|
rsync "${RSYNC_OPTS[@]}" "${src}/" "${TARGET_DIR}/${name}/" | tee -a "${LOG_FILE}"
|
|
|
|
|
|
else
|
2026-04-29 08:29:30 +02:00
|
|
|
|
log "Quelle (Verzeichnis) ${src} existiert nicht – wird übersprungen."
|
2026-04-28 11:22:40 +02:00
|
|
|
|
fi
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-29 08:29:30 +02:00
|
|
|
|
backup_file() {
|
|
|
|
|
|
local src="$1"
|
|
|
|
|
|
local name="$2"
|
|
|
|
|
|
|
|
|
|
|
|
if [[ -f "${src}" ]]; then
|
|
|
|
|
|
log "Sichere Datei ${src} -> ${TARGET_DIR}/${name}"
|
|
|
|
|
|
mkdir -p "$(dirname "${TARGET_DIR}/${name}")"
|
|
|
|
|
|
rsync "${RSYNC_OPTS[@]}" "${src}" "${TARGET_DIR}/${name}" | tee -a "${LOG_FILE}"
|
|
|
|
|
|
else
|
|
|
|
|
|
log "Quelle (Datei) ${src} existiert nicht – wird übersprungen."
|
|
|
|
|
|
fi
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# WireGuard & NetworkManager
|
2026-04-28 11:22:40 +02:00
|
|
|
|
backup_tree "${WG_SRC}" "wireguard"
|
|
|
|
|
|
backup_tree "${NM_SRC}" "networkmanager-system-connections"
|
|
|
|
|
|
|
2026-04-29 08:29:30 +02:00
|
|
|
|
# APT: sources und Keys
|
|
|
|
|
|
backup_file "${APT_SOURCES_LIST}" "apt/sources.list"
|
|
|
|
|
|
backup_tree "${APT_SOURCES_D}" "apt/sources.list.d"
|
|
|
|
|
|
backup_tree "${APT_TRUSTED_D}" "apt/trusted.gpg.d"
|
|
|
|
|
|
backup_tree "${APT_KEYRINGS}" "apt/keyrings"
|
2026-04-30 08:54:54 +02:00
|
|
|
|
backup_tree "${USR_KEYRINGS}" "apt/usr_keyrings"
|
2026-04-29 08:29:30 +02:00
|
|
|
|
|
|
|
|
|
|
# latest-Link aktualisieren
|
2026-04-28 11:22:40 +02:00
|
|
|
|
ln -sfn "${TARGET_DIR}" "${LATEST_LINK}"
|
|
|
|
|
|
log "latest-Link zeigt jetzt auf ${TARGET_DIR}"
|
|
|
|
|
|
|
2026-04-29 08:29:30 +02:00
|
|
|
|
# Rotation
|
2026-04-28 11:22:40 +02:00
|
|
|
|
cd "${DEST_BASE}/${HOST}"
|
|
|
|
|
|
SNAPS=(20*/)
|
|
|
|
|
|
|
|
|
|
|
|
if (( ${#SNAPS[@]} > KEEP )); then
|
|
|
|
|
|
log "Rotationslauf: ${#SNAPS[@]} Snapshots vorhanden, 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 "Keine Rotation nötig. Snapshots vorhanden: ${#SNAPS[@]}"
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
2026-04-29 08:29:30 +02:00
|
|
|
|
log "Netzwerk- und APT-Konfigurationsbackup abgeschlossen."
|