Proxmox Cluster mit USBIP-Server und USBIP-Client mit PVE Hookscript
Einleitung
Proxmox als Virtualisierungsplattform bietet viele Vorteile um FHEM auf virtuellen Maschinen zu betreiben. Insbesondere ist der Einsatz eines Proxmox Clusters aus zwei oder mehr Maschinen hilfreich, um eine Ausfallsicherheit (HA-Lösung) zu erzielen wenn es notwendig oder gewünscht ist.
Problematisch in diesem Umfeld ist die Einbindung von USB-Geräten, wie die diversen Sticks (IT, ZWave, ConBee etc.). Wird eine virtuelle FHEM-Maschine (VM) bei Ausfall eines Cluster-Knotens (Rechner) auf den zweiten Knoten geswitcht, kann die VM vom Cluster-Manager nicht gestartet werden da die USB Ressourcen auf dem zweiten Knoten nicht verfügbar sind. Sie sind üblicherweise direkt an dem Rechner, auf dem FHEM standardmäßig läuft, angeschlossen.
Man benötigt also einen zentralen USB-Server, der die USB-Geräte der FHEM Instanz im Cluster auf dem jeweiligen Knoten zur Verfügung stellt. Ein solcher Server kann mit dem im Debian Linux enthaltene usbip-Paket realisiert werden. USB/IP (USB over IP) ist ein System, mit dem man USB-Geräte über ein Netzwerk freigeben kann. Dabei werden die USB-Anfragen (Requests) in IP-Pakete eingekapselt, sodass ein Gerät, das physisch an einem Rechner angeschlossen ist (Server), auf einem anderen Rechner (Client) so genutzt werden kann, als wäre es lokal angeschlossen.
Nachfolgend wird eine Variante beschrieben, bei der ein Proxmox Clusterknoten als zentraler USBIP-Server agiert und zwei andere Cluster-Knoten die VM's hosten die im HA-Fall auf jeweils dem anderen Knoten betrieben werden. In dem (real im Einsatz befindlichen) Beispiel wird eingesetzt:
- Virtual Environment 8.4.1 mit drei Cluster-Knoten PVE1, PVE2 und PVE3
- PVE1: TRIGKEY Mini PC 5800H AMD Ryzen 7
- PVE2: Intel NUC BOXNUC6I5SYH Intel Core i5
- PVE3: Lenovo ThinkCentre M910q Tiny Intel Core i5-7.Gen
- PV1 und PV2 hosten die HA-Gruppen, d.h. PVE1 und PV2 übernehmen im HA-Fall die VM's und LXC-Container
- PVE3 arbeitet als zentraler USBIP-Server (es könnte natürlich auch ein externer, nicht im Cluster intergierter Rechner sein)
- ein auf allen Cluster-Knoten vorhandenes Data-Storage (hier eine Synolgy-NFS Freigabe) mit der Proxmox Inhaltsangabe "Schnipsel". Das Data-Storage ist unter
/mnt/pve/Austausch/snippets von allen Knoten erreichbar
Es wird die bash Version 4 oder höher vorausgesetzt. Die vorhandene Version kann mit:
bash --version
kontrolliert werden.
USBIP installieren
Auf allen drei Knoten des Clusters wird usbip installiert.
root@host:~# apt-get install usbip
Benötigte Kernelmodule laden
Auf den Knoten des Clusters werden die entsprechend ihrer Funktion benötigten Kernelmodul geladen und in /etc/modules eingetragen.
Auf dem USBIP-Serverknoten PV3:
modprobe usbip_host echo 'usbip_host' >> /etc/modules
Auf den USBIP-Clientknoten PVE1 und PVE2:
modprobe vhci-hcd echo vhci-hcd >> /etc/modules echo usbip_core >> /etc/modules
Erstellung einer zentralen Konfigurationsdatei
Ermittlung der relevanten USB-Geräte Bus-ID's
In der anzulegenden Konfigurationsdatei sind die Bus-ID's der im Netzwerk freizugebenden USB-Geräte anzugeben. Sind die USB-Geräte an dem USBIP-Serverknoten (PVE3) angeschlossen, können diese ID's mit folgendem Befehl ermittelt werden:
usbip list -l
Es sollten alle angeschlossenen Geräte erscheinen. Hier Beispiel sind es:
- busid 1-6.1.1 (0658:0200) Sigma Designs, Inc. : Aeotec Z-Stick Gen5 (ZW090) - UZB (0658:0200) - busid 1-6.1.3 (2013:0258) PCTV Systems : DVB-S2 Stick 461e (2013:0258) - busid 1-6.1.4 (1b1f:c020) eQ-3 Entwicklung GmbH : HmIP-RFUSB (1b1f:c020) - busid 1-6.2 (03eb:204b) Atmel Corp. : LUFA USB to Serial Adapter Project (03eb:204b) - busid 1-6.3 (03eb:204b) Atmel Corp. : LUFA USB to Serial Adapter Project (03eb:204b) - busid 2-1 (1058:259f) Western Digital Technologies, Inc. : My Passport Ultra (WD10JMVW) (1058:259f)
Freigegeben sollen im Beispiel nur die Atmel-Sticks (FHEM CUL 433/868 MHz), HmIP-RFUSB und der Aeotec Z-Stick (ZWave). Diese Bus-ID's werden in der nachfolgenden Konfigurationsdatei als BUS_IDS bzw. BUS_IDS_ALL hinterlegt.
Konfigurationdatei anlegen
Sowohl auf dem USBIP-Serverknoten als auch auf den USBIP-Clientknoten werden Dienste und Scripte ausgeführt, die bestimmte Konfigurationsangaben benötigen. Es ist von Vorteil die Konfiguration zentral pflegen zu können. Zu diesem Zweck wird eine Konfigurationdatei usbip.conf mit
nano /mnt/pve/Austausch/snippets/usbip.conf
auf dem zentralen Data-Storage angelegt. Die Datei enthält folgende Konfigurationsangaben:
# Konfiguration für USBIP
#!/bin/bash
# Definition aller vom USBIP-Server zu exportierenden USB-Geräte (Bus-ID's)
BUS_IDS_ALL=("1-6.2" "1-6.3" "1-6.1.1" "1-6.1.4")
# Definition aller an eine bestimmte VM anzuschließenden USB-Geräte (Bus-ID's)
# Wir definieren ein assoziatives Array, das den Proxmox-VMID's spezifische Bus-ID's zuordnet.
# Zum Beispiel: VM 113 bekommt "1-6.2", "1-6.3" und "1-6.1.1", VM 125 bekommt die "1-6.1.4"
declare -A VM_BUS_IDS
VM_BUS_IDS=(
["113"]="1-6.2 1-6.3 1-6.1.1"
["125"]="1-6.1.4"
# Hier können weitere Zuordnungen stehen.
)
# Der Remote-Host, von dem die USBIP-Geräte bezogen werden
REMOTE_HOST="pve3.myds.me"
# Verzeichnis, in dem die Skripte und Log-Dateien liegen
SNIPPETS_DIR="/mnt/pve/Austausch/snippets"
LOG_DIR="/mnt/pve/Austausch/snippets"
# Logfiles für Hook- und Monitor-Scripte
LOGFILEHOOK="${LOG_DIR}/usbip-hook.log"
LOGFILEMON="${LOG_DIR}/usbip-monitor.log"
LOGFILESERVER="${LOG_DIR}/usbip-server.log"
# Das Intervall, in dem der Monitor den Bindestatus der USBIP-Geräte überprüft (in Sekunden)
SLEEP_INTERVAL=10
Achte darauf, dass diese Datei vom Benutzer root lesbar ist.
Die USBIP-Server Einrichtung
USBIP-Daemon einrichten
Auf dem Cluster-Knote PVE3 wird eine Systemd-Dienstdatei für den USBIP-Daemon erstellt:
nano /lib/systemd/system/usbipd.service
Der Datei wird der folgende Inhalt hinzugefügt:
[Unit] Description=USBIP Host Daemon After=network.target [Service] Type=forking ExecStart=/usr/sbin/usbipd -D [Install] WantedBy=multi-user.target
USBIP-Dienst aktivieren und starten
Nach Erstellung des benötigten Scriptes wird der Dienst folgenden Befehlen gestartet und aktiviert:
systemctl daemon-reload systemctl enable usbipd.service systemctl start usbipd.service
Zum Überprüfen des Status des Dienstes wird ausgeführt:
systemctl status usbipd.service
Im Erfolgsfall wird der aktive Status des Dienstes ausgeschrieben:
root@pve3:~# systemctl status usbipd.service ● usbipd.service - USBIP Host Daemon Loaded: loaded (/lib/systemd/system/usbipd.service; enabled; preset: enabled) Active: active (running) since Tue 2025-05-27 20:55:30 CEST; 8s ago Process: 15404 ExecStart=/usr/sbin/usbipd -D (code=exited, status=0/SUCCESS) Main PID: 15405 (usbipd) Tasks: 1 (limit: 9323) Memory: 492.0K CPU: 4ms CGroup: /system.slice/usbipd.service └─15405 /usr/sbin/usbipd -D May 27 20:55:30 pve3 systemd[1]: Starting usbipd.service - USBIP Host Daemon... May 27 20:55:30 pve3 usbipd[15405]: usbipd: info: starting usbipd (usbip-utils 2.0) May 27 20:55:30 pve3 usbipd[15405]: usbipd: info: listening on 0.0.0.0:3240 May 27 20:55:30 pve3 systemd[1]: Started usbipd.service - USBIP Host Daemon. May 27 20:55:30 pve3 usbipd[15405]: usbipd: info: listening on :::3240
Die relevanten USB-Geräte im Netzwerk freigeben
Die hier verwendete Lösung hat den Vorteil, dass USB Geräte automatisch neu im Netzwerk freigegeben (gebunden) werden wenn sie abgesteckt/angesteckt werden. Die relevanten Bus-ID's sind im zentralen Konfigurations-Script hinterlegt.
Es wird ein Script erstellt, das für jedes Gerät in der Liste überprüft, ob es bereits exportiert (gebunden) ist – wenn nicht, wird der Bind-Befehl ausgeführt.
nano /mnt/pve/Austausch/snippets/usbip-server-bind.sh
Dieses Script hat folgenden Inhalt:
#!/bin/bash
# Dieses Skript überprüft für alle in der Liste angegebenen USB-Geräte,
# ob sie an den usbip-Treiber gebunden sind. Falls nicht, wird usbip bind ausgeführt.
# Konfiguration: Hier wird die Konfiguration aus /mnt/pve/Austausch/snippets/usbip.conf (BUS_ID's, LOGFILESERVER) eingebunden.
if [ -f /mnt/pve/Austausch/snippets/usbip.conf ]; then
source /mnt/pve/Austausch/snippets/usbip.conf
else
echo "Konfigurationsdatei /mnt/pve/Austausch/snippets/usbip.conf nicht gefunden!" >&2
exit 1
fi
# Funktionen
##############
log() {
echo "$(date '+%Y-%m-%d %H:%M:%S') – $1" >> "$LOGFILESERVER"
}
for BUS_ID in "${BUS_IDS_ALL[@]}"; do
DRIVER_LINK="/sys/bus/usb/devices/${BUS_ID}/driver"
if [ -L "$DRIVER_LINK" ]; then
# Löst den symbolischen Link auf, um den tatsächlichen Treiberpfad zu ermitteln
DRIVER_TARGET=$(readlink -f "$DRIVER_LINK")
if [[ "$DRIVER_TARGET" == *"usbip"* ]]; then
log "Gerät $BUS_ID ist bereits an usbip gebunden."
continue
fi
fi
log "Gerät $BUS_ID ist noch nicht an usbip gebunden – führe Bind aus."
usbip bind -b "$BUS_ID"
done
Die Datei ausführbar machen:
chmod +x /mnt/pve/Austausch/snippets/usbip-server-bind.sh
Erklärung zur Funktion des Scripts
- Direkte Abfrage des Treiberstatus
- Für jedes Gerät wird geprüft, ob im Verzeichnis /sys/bus/usb/devices/<busid>/driver ein symbolischer Link existiert. Existiert dieser Link, so wird dessen Zielpfad per readlink -f ermittelt. Enthält der Zielpfad den String „usbip“, ist das Gerät bereits exportiert.
- Fallunterscheidung
- Ist der Link nicht vorhanden oder verweist er nicht auf den usbip‑Treiber, wird angenommen, dass das Gerät noch nicht exportiert wurde. In diesem Fall wird der Befehl usbip bind -b <busid> ausgeführt, um das Gerät zu exportieren.
- Logging
- Jeder Schritt wird mit Datum und Uhrzeit protokolliert, was die Überwachung und Fehlersuche erleichtert.
Dienst zum regelmäßigen Aufruf des Scripts usbip-server-bind.sh erstellen und aktivieren
Es wird eine Dienstdatei zur Einbindung der USB-Geräte auf dem Knoten PVE3 erstellt und auch auf diesem Rechner gestartet:
nano /lib/systemd/system/usbip-bind.service
Folgender Inhalt wird eingefügt:
[Unit] Description=USBIP Bind Service – ruft usbip-server-bind.sh alle 5 Sekunden auf After=network-online.target usbipd.service Wants=network-online.target Requires=usbipd.service [Service] Type=simple # Die Endlosschleife ruft das Script usbip-server-bind.sh alle 5 Sekunden auf ExecStart=/bin/bash -c 'while true; do /mnt/pve/Austausch/snippets/usbip-server-bind.sh; sleep 5; done' Restart=on-failure [Install] WantedBy=multi-user.target
Mit diesem Ansatz wird ein Dienst gestartet, der dauerhaft läuft und in regelmäßigen 5‑Sekunden-Intervallen das Skript /mnt/pve/Austausch/snippets/usbip-server-bind.sh ausführt.
Danach wird der Daemon neu geladen und der Dienst aktiviert:
systemctl daemon-reload systemctl enable usbip-bind systemctl start usbip-bind
Mit nachfolgenden Kommandos kann man die Protokolle des Dienstes bzw. die Log-Ausschriften des Scriptes verfolgen:
systemctl status usbip-bind journalctl -u usbip-bind journalctl -u usbip-bind -f tail -f /mnt/pve/Austausch/snippets/usbip-server.log
Checks und manuelle Administration
Für ein manuelles "unbind" aller freigegebenen USB-Geräte wird zunächst noch das Script nano /mnt/pve/Austausch/snippets/usbip-server-unbind.sh erstellt:
#!/bin/bash
# Dieses Script muß auf dem USBIP-Server ausgeführt werden und 'unbind' die konfigurierten USBIP Bus-Id's
# Konfiguration: Hier wird die Konfiguration aus /mnt/pve/Austausch/snippets/usbip.conf (BUS_ID's, LOGFILESERVER) eingebunden.
if [ -f /mnt/pve/Austausch/snippets/usbip.conf ]; then
source /mnt/pve/Austausch/snippets/usbip.conf
else
echo "Konfigurationsdatei /mnt/pve/Austausch/snippets/usbip.conf nicht gefunden!" >&2
exit 1
fi
for bus_id in "${BUS_IDS_ALL[@]}"; do
echo "USBIP unbind ${bus_id}"
usbip unbind -b ${bus_id}
done
Die angelegte Datei wird wieder ausführbar gemacht:
chmod +x /mnt/pve/Austausch/snippets/usbip-server-unbind.sh
Zur manuellen Administration können nun die nachfolgenden Befehle und Scripte verwendet werden.
systemctl start usbip-bind startet den USB-Server Bind-Dienst systemctl stop usbip-bind stoppt den USB-Server Bind-Dienst systemctl restart usbip-bind stoppt und startet den USB-Server Bind-Dienst systemctl status usbip-bind ermittelt den Status des USB-Server Bind-Dienstes /mnt/pve/Austausch/snippets/usbip-server-bind.sh Bindet alle in der Konfigurations-Datei angegebenen USBIP Bus-Id's einmalig manuell /mnt/pve/Austausch/snippets/usbip-server-unbind.sh löst alle in der Konfigurations-Datei angegebenen USBIP bus-id's vom USBIP
Um alle freigegebenen USB-Geräte des USBIP-Servers anzuzeigen, kann der Befehl
usbip list -r pve3.myds.me
verwendet werden. Dabei ist pve3.myds.me der DNS-Name bzw. IP-Adresse des USBIP-Servers und ist durch den realen Wert zu ersetzen. Dieser Befehl zeigt nur die Geräte an, die noch nicht durch einen Client atteched wurden. Sind alle freigegeben USBIP-Beräte attached, erscheint die Ausgabe:
usbip: info: no exportable devices found on pve3.myds.me
Die USBIP-Client Einrichtung
Das Hook-Script
Hier folgt ein komplettes Proxmox‑Hook‑Skript, das:
- Beim post‑start der Gast‑VM auf dem aktiven Knoten
- - die USBIP‑Geräte (definiert durch ihre Bus‑IDs) vom definierten USBIP‑Server anbindet
- - und einen Hintergrund‑Monitor startet, der – solange die VM auf diesem Knoten läuft – in regelmäßigen Abständen prüft, ob alle Geräte noch korrekt gebunden sind und bei Bedarf erneut attachet.
- Beim pre‑stop (bzw. post‑stop) der Gast‑VM
- - den Hintergrund‑Monitor beendet
- - und alle aktuell gebundenen USBIP‑Geräte detachet
Dieses Skript wird auf dem Proxmox‑Host (also dem PVE‑Server) ausgeführt und nicht in der Gast‑VM. Außerdem wird vorausgesetzt, dass folgende Umgebungsvariablen verfügbar sind (standardmäßig von Proxmox übergeben):
- HOOKCMD – z. B. post‑start, pre‑stop, post‑stop
- VMID – die ID der betreffenden VM
Hinweis: Damit der USB‑Passthrough in der VM funktioniert, müssen die vom Host gebundenen USBIP‑Ports in der VM über die Konfiguration durchgereicht werden.
Das Script ist auf einem für alle Proxmox-Knoten erreichbaren Daten-Storage mit dem Daten-Typ "Snippets" abzulegen. In dem Beispiel ist dieser Storage gemountet und das Verzeichnis unter /mnt/pve/Austausch/snippets erreichbar. Das folgende Skript mit
nano /mnt/pve/Austausch/snippets/usbip-hook.sh
editieren und speichern:
#!/bin/bash
#
# Proxmox Hook-Skript: USBIP-Geräte attach/detach & Start/Stop des Monitorings
#
# Dieses Skript wird bei VM-Events (post-start und pre-/post-stop) auf dem PVE-Host aufgerufen.
# Es bindet beim Post‑Start die USBIP-Geräte und startet den separaten Monitoring-Daemon.
# Beim Stop wird der Monitoring-Daemon beendet und alle Geräte detached.
#
# Hinweis: Falls die Parameter vertauscht übergeben werden, wird geprüft,
# ob $1 numerisch ist (VMID) und $2 der HOOKCMD.
if [[ "$1" =~ ^[0-9]+$ ]]; then
VMID="$1"
HOOKCMD="$2"
else
HOOKCMD="$1"
VMID="$2"
fi
# Konfiguration
#################
# Hier wird die Konfiguration aus /mnt/pve/Austausch/snippets/usbip.conf eingebunden.
if [ -f /mnt/pve/Austausch/snippets/usbip.conf ]; then
source /mnt/pve/Austausch/snippets/usbip.conf
else
echo "Konfigurationsdatei /mnt/pve/Austausch/snippets/usbip.conf nicht gefunden!" >&2
exit 1
fi
# Funktionen
##############
log() {
echo "$(date '+%Y-%m-%d %H:%M:%S') – $1" >> "$LOGFILEHOOK"
}
# Hier bestimmen wir, welche BUS_IDS für die aktuelle VM gelten.
# Wir prüfen, ob für diese VMID eine Zuordnung im assoziativen Array existiert.
if [ -n "${VM_BUS_IDS[$VMID]}" ]; then
# Die zugehörigen BUS_IDS werden in das Array BUS_IDS übernommen.
IFS=' ' read -r -a BUS_IDS <<< "${VM_BUS_IDS[$VMID]}"
else
log "Fehler: Keine spezifische BUS_IDS-Zuordnung für VMID $VMID gefunden. Skript wird beendet."
exit 1
fi
MON_PID_FILE="/var/run/usbip-monitor-${VMID}.pid"
# Funktion zum Attachen der Geräte (wie zuvor definiert)
attach_devices() {
for BUS_ID in "${BUS_IDS[@]}"; do
if ! /usr/sbin/usbip port | grep -q "$BUS_ID"; then
log "Gerät mit Bus-ID $BUS_ID nicht gefunden – versuche zu verbinden..."
if ! /usr/sbin/usbip attach -r "$REMOTE_HOST" -b "$BUS_ID"; then
log "Verbindung von Bus-ID $BUS_ID schlug fehl. Versuche es erneut..."
/usr/sbin/usbip unbind -b "$BUS_ID"
sleep 5
if /usr/sbin/usbip attach -r "$REMOTE_HOST" -b "$BUS_ID"; then
log "Gerät mit Bus-ID $BUS_ID erfolgreich verbunden."
else
log "Erneuter Verbindungs-Versuch von Bus-ID $BUS_ID schlug fehl."
fi
else
log "Gerät mit Bus-ID $BUS_ID erfolgreich verbunden."
fi
else
log "Gerät mit Bus-ID $BUS_ID ist bereits verbunden."
fi
done
}
# Hilfsfunktion, um zu prüfen, ob ein Element im Array enthalten ist
contains() {
local search="$1"
shift
for element in "$@"; do
if [[ "$element" == "$search" ]]; then
return 0
fi
done
return 1
}
# selektives detachen von USBIP-Devices
detach_devices() {
# Die Ausgabe von usbip port hat mehrere Zeilen. Zum Beispiel:
#
# Port 00: <Port in Use> at Full Speed(12Mbps)
# Atmel Corp. : LUFA USB to Serial Adapter Project (03eb:204b)
# 5-1 -> usbip://pve3.myds.me:3240/1-6.2
# -> remote bus/dev 001/028
#
# Wir lesen hier zunächst die Portnummer aus der Zeile, die mit "Port" beginnt,
# und speichern sie in der Variable "port". In der nächsten relevanten Zeile, die "usbip://"
# enthält, extrahieren wir die Bus-ID, indem wir alle Zeichen bis zum letzten "/" entfernen.
/usr/sbin/usbip port | awk '
/^Port/ {
port = $2;
gsub(":", "", port);
next;
}
/usbip:\/\// {
n = split($0, a, "/");
busid = a[n];
print port, busid;
}
' | while read -r current_port busid; do
if contains "$busid" "${BUS_IDS[@]}"; then
log "Trennung von USBIP Port $current_port mit BUS-ID $busid..."
if /usr/sbin/usbip detach -p "$current_port"; then
log "Port $current_port (BUS-ID $busid) erfolgreich getrennt."
else
log "Fehler bei Trennung von Port $current_port (BUS-ID $busid)."
fi
else
log "Port $current_port (BUS-ID $busid) gehört nicht zu den definierten BUS_IDS und wird nicht getrennt."
fi
done
}
# Hauptroutine
################
log "==== Start Hook-Skript -> VMID=${VMID}, HOOKCMD=${HOOKCMD} ===="
case "$HOOKCMD" in
pre-start|post-start)
log "HOOK $HOOKCMD: Starte Bindungs-Vorgang."
attach_devices
# Prüfen, ob der Monitoring-Daemon bereits läuft:
if [ -f "$MON_PID_FILE" ]; then
OLD_PID=$(cat "$MON_PID_FILE")
if kill -0 "$OLD_PID" 2>/dev/null; then
log "Monitoring-Daemon läuft bereits mit PID $OLD_PID...kein Neustart."
exit 0
else
log "Prozess $OLD_PID läuft nicht mehr. Entferne alte PID-Datei."
rm -f "$MON_PID_FILE"
fi
fi
log "Starte Monitoring-Daemon für VM $VMID."
# Starte den separaten Monitoring-Daemon
nohup "${SNIPPETS_DIR}/usbip-monitor.sh" "$VMID" >/dev/null 2>&1 & MON_PID=$!
echo "$MON_PID" > "$MON_PID_FILE"
log "Monitoring-Daemon gestartet mit PID $MON_PID."
;;
pre-stop|post-stop)
log "HOOK $HOOKCMD: Beende Monitoring-Daemon für VM $VMID und trenne USB-Geräte."
if [ -f "$MON_PID_FILE" ]; then
MON_PID=$(cat "$MON_PID_FILE")
log "Beende Monitoring-Daemon mit PID $MON_PID."
if kill "$MON_PID"; then
log "Prozess $MON_PID erfolgreich beendet."
else
log "Fehler: Prozess $MON_PID konnte nicht beendet werden."
fi
rm -f "$MON_PID_FILE"
else
log "Monitoring für VM ${VMID} ist nicht mehr aktiv."
fi
detach_devices
;;
*)
log "HOOK $HOOKCMD nicht erkannt – keine Aktion erfolgt."
;;
esac
exit 0
Das Script ausführbar machen
chmod +x /mnt/pve/Austausch/snippets/usbip-hook.sh
Das Hook-Script in der VM-Konfiguration hinterlegen
Damit das Hook Script funktioniert, muß es in der entsprechenden VM Konfiguration eintragen werden. Die VM Konfigurationen liegen im Verzeichnis /etc/pve/qemu-server auf dem Proxmox Server der die VM aktuell hostet. Mit dem folgendem Befehl wird das Hook-Script in die Konfiguration eingetragen. Dabei ist "113" die VM-ID, bei deren Start/Stop-Vorgängen die USB-Geräte eingebunden oder freigegeben werden sollen und ist durch die entsprechende VM-ID zu ersetzen:
qm set 113 --hookscript Austausch:snippets/usbip-hook.sh
Der Befehl wird auf dem Proxmox-Knoten ausgeführt, der die VM aktuell hostet.
Nun wird das Script automatisch bei den entsprechenden VM-Ereignissen gestartet.
Die Funktionsweise im Detail
- Auf VM-Start (HOOKCMD=pre-start oder post‑start):
- Das Script versucht, für jede definierte Bus-ID die USBIP‑Geräte vom REMOTE_HOST anzubinden.
- Anschließend wird eine Hintergrundschleife (Monitoring) gestartet, die alle 10 Sekunden prüft, ob die Geräte noch vorhanden sind – falls nicht, wird erneut attached.
- Die Monitor-PID wird in einer Datei gespeichert (z. B. /var/run/usbip-monitor-<VMID>.pid).
- Auf VM-Beendigung (HOOKCMD=pre‑stop oder post‑stop):
- Das Skript liest die gespeicherte PID und beendet den Monitor.
- Danach werden alle aktuellen USBIP‑Geräte detached, die in der Liste Bus-Id's der Konfigurationsdatei enthalten sind.
- Nur auf dem aktiven Knoten:
- Im Monitor-Loop wird zusätzlich mit qm status $VMID geprüft, ob die VM tatsächlich auf diesem Host läuft. Falls nicht, beendet sich der Monitor selbstständig. So übernimmt nach einer Migration der neue Host die Aufgabe, während der alte Knoten keine Bind/Unbind-Vorgänge mehr vornimmt.
Der Monitoring-Daemon
Dieses separate Skript führt eine Endlosschleife aus, in der es in regelmäßigen Abständen überprüft, ob alle gewünschten USBIP-Geräte (über Bus-IDs) noch attached sind – und falls nicht, wird versucht, sie neu anzubinden. Außerdem prüft es, ob die VM auf diesem Knoten aktiv läuft (mithilfe von qm status).
Dieses Skript wird mit
nano /mnt/pve/Austausch/snippets/usbip-monitor.sh
angelegt und mit diesen Inhalt befüllt:
#!/bin/bash
#
# USBIP Monitoring Daemon
#
# Dieses Skript überwacht in regelmäßigen Abständen die definierten USBIP-Geräte
# und versucht neu, fehlende Geräte anzubinden – und zwar nur, solange die VM
# mit der ID $1 auf diesem Knoten als "running" gemeldet ist.
#
# Aufruf: /<Pfad>/usbip-monitor.sh <VMID>
if [ -z "$1" ]; then
echo "Usage: $0 <VMID>"
exit 1
fi
VMID="$1"
# Konfiguration
#################
# Hier wird die Konfiguration aus /mnt/pve/Austausch/snippets/usbip.conf eingebunden.
if [ -f /mnt/pve/Austausch/snippets/usbip.conf ]; then
source /mnt/pve/Austausch/snippets/usbip.conf
else
echo "Konfigurationsdatei /mnt/pve/Austausch/snippets/usbip.conf nicht gefunden!" >&2
exit 1
fi
# Funktionen
##############
log() {
echo "$(date '+%Y-%m-%d %H:%M:%S') Monitor für VMID $VMID (PID: $$) – $1" >> "$LOGFILEMON"
}
# Hier bestimmen wir, welche BUS_IDS für die aktuelle VM gelten.
# Wir prüfen, ob für diese VMID eine Zuordnung im assoziativen Array existiert.
if [ -n "${VM_BUS_IDS[$VMID]}" ]; then
# Die zugehörigen BUS_IDS werden in das Array BUS_IDS übernommen.
IFS=' ' read -r -a BUS_IDS <<< "${VM_BUS_IDS[$VMID]}"
else
log "Fehler: Keine BUS_IDS-Zuordnung für VMID $VMID gefunden. Skript wird beendet."
exit 1
fi
attach_devices() {
for BUS_ID in "${BUS_IDS[@]}"; do
if ! /usr/sbin/usbip port | grep -q "$BUS_ID"; then
log "Gerät mit Bus-ID $BUS_ID fehlt – versuche zu verbinden..."
if ! /usr/sbin/usbip attach -r "$REMOTE_HOST" -b "$BUS_ID"; then
log "Verbindung von Bus-ID $BUS_ID schlug fehl – versuche neue Verbindung..."
/usr/sbin/usbip unbind -b "$BUS_ID"
sleep 5
if /usr/sbin/usbip attach -r "$REMOTE_HOST" -b "$BUS_ID"; then
log "Gerät mit Bus-ID $BUS_ID erfolgreich verbunden."
else
log "Verbindung für Bus-ID $BUS_ID ist fehlgeschlagen."
fi
else
log "Gerät mit Bus-ID $BUS_ID erfolgreich verbunden."
fi
else
log "Gerät mit Bus-ID $BUS_ID ist aktiv."
fi
done
}
# Hauptschleife
log "Monitoring-Daemon gestartet für VMID $VMID (PID $$)."
while true; do
# Prüfe, ob die VM auf diesem Knoten läuft; wenn nicht, beenden wir den Daemon.
if ! qm status "$VMID" 2>/dev/null | grep -q "status: running"; then
log "VM $VMID läuft nicht mehr auf diesem Knoten – Monitoring-Daemon beendet sich."
exit 0
fi
attach_devices
sleep "$SLEEP_INTERVAL"
done
Das Script ausführbar machen
chmod +x /mnt/pve/Austausch/snippets/usbip-monitor.sh
Funktionsweise & Erklärung
- Detachment des Monitors
- Im Hook-Skript wird der Monitoring-Daemon mit nohup "${SNIPPETS_DIR}/usbip-monitor.sh" "$VMID" … & gestartet. Dadurch wird er vom Elternprozess (dem Hook) komplett abgetrennt. Das Hook-Skript kann somit sofort erfolgreich beenden und den Post‑Start-Vorgang abschließen. Das ist wichtig damit der VM-Start in Proxmox erfolgreich beendet werden kann.
- Schnelles Beenden des Hooks
- Da das Hook-Skript sofort nach dem Start des Monitor-Daemons (und dem Speichern der PID) beendet wird, hält die VM nicht im Status „startend“.
- Beendigung beim Stop
- Beim pre‑stop/post‑stop wird die gespeicherte PID mittels kill beendet und im Anschluss werden alle USBIP-Geräte detached.
- Prüfung des VM-Status
- Der Monitoring-Daemon läuft nur, solange qm status <VMID> meldet, dass die VM auf diesem Knoten als running gilt. Falls die VM migriert wird, beendet sich der Daemon selbst.
Den Monitor manuell starten
Bei einer Neuzuordnung eines aktuell noch nicht einer bereits existierenden/laufenden VM kann es erforderlich sein, das USB-Gerät zu attachen, ohne die VM stoppen und restarten zu wollen oder zu können. Zu dem Zweck kann der Monitor unter Angabe der VMID, für die das USB-Gerät atteched werden soll, manuell zu starten. Das Script ist auf dem Proxmox-Knoten auszuführen der die VM aktuell hostet:
/mnt/pve/Austausch/snippets/usbip-monitor.sh 125
Der Monitor erkennt das Fehlen des konfigurierten USB-Gerätes und bindet es an den Proxmox-Knoten.
Der Erfolg kann sowohl im Logfile:
tail -f /mnt/pve/Austausch/snippets/usbip-monitor.log Monitor für VMID 125 (PID: 251502) – Gerät mit Bus-ID 1-6.1.4 ist aktiv
als auch mit dem Befehl:
usbip port
auf dem hostenden Proxmox-Knoten geprüft werden:
root@pve2:/etc/pve/qemu-server# usbip port Imported USB devices ==================== Port 00: <Port in Use> at Full Speed(12Mbps) eQ-3 Entwicklung GmbH : HmIP-RFUSB (1b1f:c020) 3-1 -> usbip://pve3.myds.me:3240/1-6.1.4 -> remote bus/dev 001/012
Nun kann das USB-Gerät in Proxmox gemappt (Port-Mapping) und an die laufende VM durchgereicht werden. Danach das Monitor-Script wieder beenden.
Beim nächsten regulären Stopp und Start der VM wird das in der VM-Konfiguration hinterlegten Hook-Script automatisch ausgeführt und verwaltet den Monitor-Prozess.
Logrotate für die automatische Verwaltung der Logfiles einrichten
Die geschriebenen usbip*-Logs sollen automatisch gelöscht werden. Mit Logrotate kann diese Aufgabe einfach eingerichtet werden.
Dazu wird eine Konfigurationsdatei angelegt:
nano /etc/logrotate.d/usbip
Der Inhalt der Datei sieht wird wie folgt erstellt:
/mnt/pve/Austausch/snippets/usbip-*.log { daily rotate 2 missingok notifempty nocompress copytruncate create 644 root root }
Erklärung der Optionen:
- /mnt/pve/Austausch/snippets/usbip-.log* Legt fest, dass alle Logfiles, deren Namen mit „usbip-“ beginnen und in diesem Verzeichnis liegen, durch diese Konfiguration verarbeitet werden.
- daily Weist Logrotate an, die Logfiles täglich zu prüfen und zu rotieren.
- rotate 2 Sorgt dafür, dass nur zwei alte Versionen der Logfiles aufbewahrt werden – danach werden die älteren gelöscht.
- missingok Falls ein Logfile nicht vorhanden ist, wird kein Fehler gemeldet.
- notifempty Rotiert die Logdatei nur, wenn sie nicht leer ist.
- nocompress Das verhindert, dass Logrotate die alten Logfiles komprimiert, was nicht nötig ist weil sie nicht so groß sind
- copytruncate kopiert den Inhalt in die Rotationsdatei und leert die Originaldatei, sodass der Prozess weiterhin in dieselbe, nun leere, Datei schreibt.
- create 644 root root Nachdem ein Logfile rotiert wurde, wird eine neue Logdatei mit den angegebenen Rechten und Eigentümerinformationen erstellt.
Logrotate läuft im Normalfall täglich über einen Cronjob (üblicherweise in /etc/cron.daily/logrotate), sodass ab dem nächsten Durchlauf die Logfiles entsprechend behandelt werden. Um die Funktion von logrotate zu prüfen, kann ein manueller Aufruf mit dem -f (force) bzw. -v (verbose) Flag erfolgen:
logrotate -vf /etc/logrotate.d/usbip
Logfile Zugriff
Alle Logfiles der USBIP-Server und USBIP-Client Scripte liegen an zentraler Stelle auf dem Data-Storage. Sie können z.B. mit folgenden Befehlen verfolgt werden:
tail -f /mnt/pve/Austausch/snippets/usbip-server.log tail -f /mnt/pve/Austausch/snippets/usbip-hook.log tail -f /mnt/pve/Austausch/snippets/usbip-monitor.log
Wie werden neue USBIP-Geräte nach der Ersteinrichtung integriert
Wurde die Ersteinrichtung wie in vorherigen Kapiteln beschrieben durchgeführt, ist die Integration neuer USBIP-Geräte mit wenigen Schritten möglich:
- das neue USB-Gerät am USBIP-Serverknoten verbinden
- die BUS-ID des neuen Gerätes identifizieren mit
usbip list -l
- in der zentralen Konfigurationsdatei usbip.conf die ermittelte BUS-ID eintragen:
- in BUS_IDS_ALL und im - assoziatives Array VM_BUS_IDS in Verbindung mit der Proxmox VM-Id die das USB-Geräte benutzen soll
- das Hook-Script in der VM-Konfiguration hinterlegen:
qm set <VM-Id> --hookscript Austausch:snippets/usbip-hook.sh
- ist das neue Gerät mit dem Proxmox-Knoten verbunden (Kontrolle mit "usbip port"), kann es in der Proxmox GUI gemappt und an die VM durchgereicht werden.