Dateien nach "/" hochladen

This commit is contained in:
2026-07-14 08:07:49 +00:00
parent e73405ea51
commit aad3cc29f1
5 changed files with 317 additions and 3 deletions

View File

@@ -86,3 +86,48 @@ NetworkManager oder systemd-networkd neu aufzubauen.
prüfen.
- Nach dem Restore zuerst nur eine VM starten und insbesondere UEFI, virtuelles
TPM, Secure Boot, BitLocker und Netzwerk testen.
## PAW-Netzwerk aus `50-paw-install.yml` (Version 1.2)
Das Paket sichert jetzt zusätzlich die vom Playbook verwendete Konfiguration:
- `/etc/NetworkManager/system-connections/`
- `/etc/NetworkManager/dispatcher.d/`
- `/etc/libvirt/qemu/networks/`
- relevante sysctl-, UFW-, Routing- und `/hilden`-Dateien
- aktuelle MAC von `br-lan`
- permanente MACs und Hardwarepfade aller physischen Interfaces
- Zuordnung der Bridge-Ports zu `br-lan`
Das alte PAW-Profil bindet die Ports fest an `eth0` und `enp2s0f0`. Beim Restore
werden diese Namen anhand der gesicherten permanenten MAC-Adressen auf die unter
Debian tatsächlich vorhandenen Gerätenamen umgesetzt. Zusätzlich wird die
bisher dynamische MAC von `br-lan` als `bridge.mac-address` im
NetworkManager-Keyfile festgeschrieben.
Empfohlene Reihenfolge auf Debian:
```bash
# 1. 50-paw-install.yml ausführen
# 2. VM- und Netzwerk-Restore vorbereiten, NetworkManager noch nicht neu starten
sudo RESTORE_PAW_NETWORK=1 ./libvirt-restore.sh /mnt/backup/libvirt-host
# 3. Profile kontrollieren
sudo grep -R -E '^(interface-name|mac-address|master)=' \
/etc/NetworkManager/system-connections/
# 4. Nur lokal an der Konsole aktivieren
sudo RESTORE_PAW_NETWORK=1 APPLY_PAW_NETWORK=1 \
./libvirt-restore.sh /mnt/backup/libvirt-host
```
Alternativ kann nur der Netzwerkteil ausgeführt werden:
```bash
sudo ./paw-network-backup.sh /mnt/backup/paw-network
sudo ./paw-network-restore.sh /mnt/backup/paw-network
sudo APPLY_NETWORK=1 ./paw-network-restore.sh /mnt/backup/paw-network
```
Der Netzwerk-Restore startet NetworkManager standardmäßig **nicht** neu. Das
verhindert einen unbeabsichtigten Verbindungsabbruch bei einem Restore per SSH.

View File

@@ -5,11 +5,12 @@ set -Eeuo pipefail
IFS=$'\n\t'
umask 077
VERSION="1.0"
VERSION="1.2"
URI="${LIBVIRT_URI:-qemu:///system}"
BACKUP_ROOT="${1:-}"
RSYNC_OPTS=(-aHAX --numeric-ids --sparse --info=progress2)
ALLOW_RUNNING=0
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
usage() {
cat <<'EOF'
@@ -28,6 +29,7 @@ Das Skript sichert:
- UEFI-NVRAM-Dateien
- dateibasierte TPM-Backend-Zustände
- qemu-img-Informationen und Prüfsummen
- PAW-Netzwerk aus 50-paw-install.yml inklusive br-lan- und Port-MACs
Blockgeräte wie LVM-LVs werden nur dokumentiert und NICHT kopiert.
EOF
@@ -41,7 +43,7 @@ log() { printf '\n==> %s\n' "$*"; }
[[ $EUID -eq 0 ]] || die "Bitte als root ausführen."
[[ -n "$BACKUP_ROOT" ]] || { usage; exit 2; }
for cmd in virsh rsync sha256sum python3 qemu-img find sort awk sed; do
for cmd in virsh rsync sha256sum python3 qemu-img find sort awk sed findmnt; do
command -v "$cmd" >/dev/null || die "Benötigtes Programm fehlt: $cmd"
done
@@ -51,6 +53,20 @@ mkdir -p "$BACKUP_ROOT"
[[ -z "$(find "$BACKUP_ROOT" -mindepth 1 -maxdepth 1 -print -quit)" ]] \
|| die "Backup-Ziel ist nicht leer: $BACKUP_ROOT"
# Früh erkennen, ob das Ziel beschreibbar ist und der Mount read-only ist.
mount_target="$(findmnt -no TARGET -T "$BACKUP_ROOT" 2>/dev/null || true)"
mount_opts="$(findmnt -no OPTIONS -T "$BACKUP_ROOT" 2>/dev/null || true)"
[[ ",$mount_opts," != *,ro,* ]] \
|| die "Backup-Dateisystem ist nur lesbar eingehängt: ${mount_target:-unbekannt}"
probe="$BACKUP_ROOT/.libvirt-backup-write-test.$$"
printf 'libvirt backup write test\n' > "$probe" \
|| die "Backup-Ziel ist nicht beschreibbar: $BACKUP_ROOT"
sync "$probe" 2>/dev/null || sync
grep -qx 'libvirt backup write test' "$probe" \
|| die "Schreib-/Lesetest auf dem Backup-Ziel fehlgeschlagen."
rm -f "$probe"
mkdir -p "$BACKUP_ROOT"/{domains,networks,pools,interfaces,files,metadata,logs}
exec > >(tee -a "$BACKUP_ROOT/logs/backup.log") 2>&1
@@ -118,7 +134,10 @@ export_objects() {
warn "$kind konnte nicht exportiert werden: $name"
rm -f "$BACKUP_ROOT/$outdir/$safe.xml"
fi
done < <(virsh -c "$URI" "$list_cmd" --all --name 2>/dev/null | sed '/^[[:space:]]*$/d')
done < <(
virsh -c "$URI" "$list_cmd" --all --name 2>/dev/null |
sed 's/^[[:space:]]*//; s/[[:space:]]*$//; /^[[:space:]]*$/d'
)
}
log "Exportiere Netzwerke, Storage-Pools und Host-Interfaces"
@@ -243,6 +262,26 @@ if [[ -s "$BACKUP_ROOT/metadata/block-sources.txt" ]]; then
warn "Diese LVM-/Block-Datenträger separat sichern, z. B. per LVM-Snapshot, dd oder Storage-Replikation."
fi
log "Sichere PAW-Netzwerk und br-lan-MAC"
if [[ -x "$SCRIPT_DIR/paw-network-backup.sh" ]]; then
"$SCRIPT_DIR/paw-network-backup.sh" "$BACKUP_ROOT/network"
else
warn "paw-network-backup.sh fehlt neben dem Backup-Skript; Netzwerk wurde nicht gesichert."
fi
log "Prüfe Backup-Datenträger vor der Prüfsummenphase"
mount_opts="$(findmnt -no OPTIONS -T "$BACKUP_ROOT" 2>/dev/null || true)"
[[ ",$mount_opts," != *,ro,* ]] \
|| die "Backup-Dateisystem wurde während des Backups nur-lesbar. Backup ist unvollständig."
probe="$BACKUP_ROOT/.libvirt-backup-final-test.$$"
printf 'final write test\n' > "$probe" \
|| die "Abschließender Schreibtest fehlgeschlagen. Backup ist unvollständig."
sync "$probe" 2>/dev/null || sync
grep -qx 'final write test' "$probe" \
|| die "Abschließender Lesetest fehlgeschlagen. Backup ist unvollständig."
rm -f "$probe"
log "Erzeuge Dateiliste und SHA-256-Prüfsummen"
(
cd "$BACKUP_ROOT"

View File

@@ -13,6 +13,9 @@ RESTORE_INTERFACES="${RESTORE_INTERFACES:-0}"
START_NETWORKS="${START_NETWORKS:-1}"
START_POOLS="${START_POOLS:-1}"
AUTOSTART_OBJECTS="${AUTOSTART_OBJECTS:-0}"
RESTORE_PAW_NETWORK="${RESTORE_PAW_NETWORK:-0}"
APPLY_PAW_NETWORK="${APPLY_PAW_NETWORK:-0}"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
usage() {
cat <<'EOF'
@@ -27,6 +30,8 @@ Umgebungsvariablen:
START_NETWORKS=0 importierte Netzwerke nicht starten
START_POOLS=0 importierte Pools nicht starten
AUTOSTART_OBJECTS=1 importierte Domains/Netze/Pools auf Autostart setzen
RESTORE_PAW_NETWORK=1 NetworkManager/PAW-Konfiguration und MAC-Zuordnung restaurieren
APPLY_PAW_NETWORK=1 NetworkManager danach neu starten (nur lokal/mit Konsole)
Standardmäßig werden vorhandene Dateien und Definitionen NICHT überschrieben.
Die Dateien werden an ihre ursprünglichen absoluten Pfade zurückkopiert.
@@ -90,6 +95,17 @@ while IFS=$'\t' read -r kind original relative; do
fi
done < "$BACKUP_ROOT/metadata/file-map.tsv"
if [[ "$RESTORE_PAW_NETWORK" == "1" ]]; then
log "Stelle PAW-Netzwerk und br-lan-MAC wieder her"
if [[ -x "$SCRIPT_DIR/paw-network-restore.sh" && -d "$BACKUP_ROOT/network" ]]; then
APPLY_NETWORK="$APPLY_PAW_NETWORK" "$SCRIPT_DIR/paw-network-restore.sh" "$BACKUP_ROOT/network"
else
warn "Netzwerk-Backup oder paw-network-restore.sh fehlt."
fi
else
log "PAW-Netzwerk-Restore übersprungen; RESTORE_PAW_NETWORK=1 würde ihn ausführen"
fi
define_network() {
local xml="$1" name
name="$(python3 - "$xml" <<'PY'

95
paw-network-backup.sh Normal file
View File

@@ -0,0 +1,95 @@
#!/usr/bin/env bash
# Sichert die durch 50-paw-install.yml relevante PAW-Netzwerkkonfiguration.
set -Eeuo pipefail
IFS=$'\n\t'
umask 077
DEST="${1:?Aufruf: sudo $0 /pfad/zum/backup/network}"
[[ $EUID -eq 0 ]] || { echo "FEHLER: Bitte als root ausführen." >&2; exit 1; }
mkdir -p "$DEST"/{rootfs,metadata,state}
copy_path() {
local src="$1" rel
[[ -e "$src" ]] || return 0
rel="${src#/}"
mkdir -p "$DEST/rootfs/$(dirname "$rel")"
rsync -aHAX --numeric-ids "$src" "$DEST/rootfs/$rel"
printf '%s\t%s\n' "$src" "rootfs/$rel" >> "$DEST/metadata/files.tsv"
}
: > "$DEST/metadata/files.tsv"
# Genau die Pfade, die 50-paw-install.yml installiert bzw. verwendet.
copy_path /etc/NetworkManager/system-connections
copy_path /etc/NetworkManager/dispatcher.d
copy_path /etc/libvirt/qemu/networks
copy_path /etc/sysctl.d
copy_path /usr/local/bin/ufw-paw.sh
copy_path /etc/default/ufw
copy_path /etc/ufw
copy_path /etc/iproute2/rt_tables
copy_path /hilden
# Laufzeit-Zustand dokumentieren.
ip -details link show > "$DEST/state/ip-link.txt"
ip -details address show > "$DEST/state/ip-address.txt"
ip route show table all > "$DEST/state/ip-routes-all.txt"
ip rule show > "$DEST/state/ip-rules.txt"
bridge link show > "$DEST/state/bridge-links.txt" 2>/dev/null || true
bridge vlan show > "$DEST/state/bridge-vlans.txt" 2>/dev/null || true
nmcli -f all connection show > "$DEST/state/nm-connections.txt" 2>/dev/null || true
nmcli -f all device status > "$DEST/state/nm-devices.txt" 2>/dev/null || true
nmcli --show-secrets connection show > "$DEST/state/nm-connections-with-secrets.txt" 2>/dev/null || true
nft list ruleset > "$DEST/state/nftables.txt" 2>/dev/null || true
ufw status verbose > "$DEST/state/ufw-status.txt" 2>/dev/null || true
# Bridge-MAC sichern. Diese ist für DHCP-Leases/Netzidentität relevant.
if [[ -e /sys/class/net/br-lan/address ]]; then
cat /sys/class/net/br-lan/address > "$DEST/metadata/br-lan.mac"
fi
# Permanente MAC, aktuelle MAC und Hardwarepfad aller Interfaces erfassen.
printf 'interface\tcurrent_mac\tpermanent_mac\tifindex\tdriver\tdevice_path\tid_path\tid_net_name_path\tid_net_name_mac\n' \
> "$DEST/metadata/interfaces.tsv"
for sysdev in /sys/class/net/*; do
iface="${sysdev##*/}"
current="$(cat "$sysdev/address" 2>/dev/null || true)"
permanent="$(ethtool -P "$iface" 2>/dev/null | awk '{print $3}' || true)"
[[ "$permanent" =~ ^([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$ ]] || permanent="$current"
ifindex="$(cat "$sysdev/ifindex" 2>/dev/null || true)"
driver="$(basename "$(readlink -f "$sysdev/device/driver" 2>/dev/null)" 2>/dev/null || true)"
device_path="$(readlink -f "$sysdev/device" 2>/dev/null || true)"
props="$(udevadm info -q property -p "$sysdev" 2>/dev/null || true)"
id_path="$(awk -F= '$1=="ID_PATH"{print substr($0,index($0,"=")+1)}' <<<"$props")"
id_name_path="$(awk -F= '$1=="ID_NET_NAME_PATH"{print substr($0,index($0,"=")+1)}' <<<"$props")"
id_name_mac="$(awk -F= '$1=="ID_NET_NAME_MAC"{print substr($0,index($0,"=")+1)}' <<<"$props")"
printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \
"$iface" "$current" "$permanent" "$ifindex" "$driver" "$device_path" \
"$id_path" "$id_name_path" "$id_name_mac" >> "$DEST/metadata/interfaces.tsv"
done
# Die in den PAW-Keyfiles verwendeten Ports inklusive ihrer permanenten MAC sichern.
printf 'profile\tconnection_id\told_interface\tpermanent_mac\tmaster\n' \
> "$DEST/metadata/paw-ports.tsv"
shopt -s nullglob
for profile in /etc/NetworkManager/system-connections/*.nmconnection; do
type="$(awk -F= '$1=="type"{print $2; exit}' "$profile")"
[[ "$type" == "ethernet" ]] || continue
master="$(awk -F= '$1=="master"{print $2; exit}' "$profile")"
[[ "$master" == "br-lan" ]] || continue
con_id="$(awk -F= '$1=="id"{print $2; exit}' "$profile")"
old_if="$(awk -F= '$1=="interface-name"{print $2; exit}' "$profile")"
pmac=""
if [[ -n "$old_if" && -e "/sys/class/net/$old_if" ]]; then
pmac="$(ethtool -P "$old_if" 2>/dev/null | awk '{print $3}' || true)"
[[ "$pmac" =~ ^([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$ ]] || pmac="$(cat "/sys/class/net/$old_if/address")"
fi
printf '%s\t%s\t%s\t%s\t%s\n' "$(basename "$profile")" "$con_id" "$old_if" "$pmac" "$master" \
>> "$DEST/metadata/paw-ports.tsv"
done
chmod -R go-rwx "$DEST"
echo "PAW-Netzwerk gesichert: $DEST"
[[ -f "$DEST/metadata/br-lan.mac" ]] && echo "br-lan MAC: $(cat "$DEST/metadata/br-lan.mac")"

119
paw-network-restore.sh Normal file
View File

@@ -0,0 +1,119 @@
#!/usr/bin/env bash
# Stellt die für 50-paw-install.yml relevante NetworkManager-Konfiguration wieder her.
set -Eeuo pipefail
IFS=$'\n\t'
umask 077
SRC="${1:?Aufruf: sudo $0 /pfad/zum/backup/network}"
APPLY_NETWORK="${APPLY_NETWORK:-0}"
OVERWRITE_NETWORK="${OVERWRITE_NETWORK:-1}"
[[ $EUID -eq 0 ]] || { echo "FEHLER: Bitte als root ausführen." >&2; exit 1; }
[[ -d "$SRC/rootfs" ]] || { echo "FEHLER: Ungültiges Netzwerk-Backup: $SRC" >&2; exit 1; }
log(){ printf '\n==> %s\n' "$*"; }
warn(){ echo "WARNUNG: $*" >&2; }
# Aktuelle Geräte anhand permanenter MAC inventarisieren.
declare -A MAC_TO_IF
for sysdev in /sys/class/net/*; do
iface="${sysdev##*/}"
[[ "$iface" == "lo" ]] && continue
pmac="$(ethtool -P "$iface" 2>/dev/null | awk '{print tolower($3)}' || true)"
[[ "$pmac" =~ ^([0-9a-f]{2}:){5}[0-9a-f]{2}$ ]] || pmac="$(tr '[:upper:]' '[:lower:]' < "$sysdev/address")"
MAC_TO_IF["$pmac"]="$iface"
done
log "Kopiere gesicherte PAW-Konfiguration"
while IFS=$'\t' read -r original relative; do
[[ -n "${original:-}" && -n "${relative:-}" ]] || continue
source_path="$SRC/$relative"
[[ -e "$source_path" ]] || { warn "Quelle fehlt: $source_path"; continue; }
if [[ -e "$original" && "$OVERWRITE_NETWORK" != "1" ]]; then
warn "Vorhanden, übersprungen: $original"
continue
fi
mkdir -p "$(dirname "$original")"
if [[ -d "$source_path" ]]; then
mkdir -p "$original"
rsync -aHAX --numeric-ids "$source_path/" "$original/"
else
rsync -aHAX --numeric-ids "$source_path" "$original"
fi
done < "$SRC/metadata/files.tsv"
log "Ordne br-lan-Ports anhand permanenter MAC neu zu"
if [[ -f "$SRC/metadata/paw-ports.tsv" ]]; then
tail -n +2 "$SRC/metadata/paw-ports.tsv" | while IFS=$'\t' read -r profile con_id old_if pmac master; do
[[ -n "$profile" ]] || continue
target="/etc/NetworkManager/system-connections/$profile"
[[ -f "$target" ]] || { warn "Profil fehlt nach Restore: $target"; continue; }
pmac="$(tr '[:upper:]' '[:lower:]' <<<"$pmac")"
new_if="${MAC_TO_IF[$pmac]:-}"
if [[ -z "$pmac" || -z "$new_if" ]]; then
warn "Keine aktuelle Schnittstelle für $con_id (alte Schnittstelle $old_if, MAC ${pmac:-unbekannt}) gefunden."
continue
fi
sed -i -E "s/^interface-name=.*/interface-name=$new_if/" "$target"
# Hardwarebindung ergänzen/aktualisieren, damit wechselnde Gerätenamen unschädlich sind.
python3 - "$target" "$pmac" <<'PY'
import configparser, sys
p, mac = sys.argv[1:]
cfg = configparser.RawConfigParser(strict=False, interpolation=None)
cfg.optionxform = str
cfg.read(p)
if not cfg.has_section('802-3-ethernet'):
cfg.add_section('802-3-ethernet')
cfg.set('802-3-ethernet', 'mac-address', mac)
with open(p, 'w') as f:
cfg.write(f, space_around_delimiters=False)
PY
echo "$con_id: $old_if -> $new_if ($pmac)"
done
fi
# Bridge-MAC festschreiben. 50-paw-install.yml tut dies bislang nicht.
if [[ -s "$SRC/metadata/br-lan.mac" ]]; then
bridge_mac="$(tr '[:upper:]' '[:lower:]' < "$SRC/metadata/br-lan.mac")"
bridge_profile="/etc/NetworkManager/system-connections/br-lan.nmconnection"
if [[ -f "$bridge_profile" ]]; then
python3 - "$bridge_profile" "$bridge_mac" <<'PY'
import configparser, sys
p, mac = sys.argv[1:]
cfg = configparser.RawConfigParser(strict=False, interpolation=None)
cfg.optionxform = str
cfg.read(p)
if not cfg.has_section('bridge'):
cfg.add_section('bridge')
cfg.set('bridge', 'mac-address', mac)
with open(p, 'w') as f:
cfg.write(f, space_around_delimiters=False)
PY
echo "br-lan MAC festgelegt: $bridge_mac"
else
warn "br-lan.nmconnection fehlt; Bridge-MAC konnte nicht eingetragen werden."
fi
fi
chown root:root /etc/NetworkManager/system-connections/*.nmconnection 2>/dev/null || true
chmod 600 /etc/NetworkManager/system-connections/*.nmconnection 2>/dev/null || true
chown root:root /etc/NetworkManager/dispatcher.d/* 2>/dev/null || true
chmod 0755 /etc/NetworkManager/dispatcher.d/* 2>/dev/null || true
log "Validiere NetworkManager-Keyfiles"
for f in /etc/NetworkManager/system-connections/*.nmconnection; do
[[ -f "$f" ]] || continue
grep -q '^\[connection\]' "$f" || warn "Ungültiges Profil: $f"
done
if [[ "$APPLY_NETWORK" == "1" ]]; then
log "Aktiviere NetworkManager-Konfiguration"
nmcli connection reload
systemctl restart NetworkManager
sleep 3
nmcli connection up br-lan || warn "br-lan konnte nicht automatisch aktiviert werden."
ip -br link show br-lan || true
else
log "Konfiguration vorbereitet, aber nicht aktiviert"
echo "Aktivierung lokal an der Konsole:"
echo " sudo APPLY_NETWORK=1 $0 '$SRC'"
fi