@@ -133,37 +133,92 @@ pub fn ensure_usbip_modules() -> Result<(), String> {
133133 Ok ( ( ) )
134134}
135135
136- /// Check if usbip tools are available
136+ /// Check if usbip is available (binary in PATH or kernel modules loaded)
137137pub fn is_usbip_available ( ) -> bool {
138- Command :: new ( "which" ) . arg ( "usbip" ) . output ( )
139- . map ( |o| o. status . success ( ) )
140- . unwrap_or ( false )
138+ // Check for binary via shell (more reliable than `which` across distros)
139+ if Command :: new ( "sh" ) . args ( [ "-c" , "command -v usbip" ] ) . output ( )
140+ . map ( |o| o. status . success ( ) ) . unwrap_or ( false )
141+ {
142+ return true ;
143+ }
144+ // Check common paths directly
145+ if std:: path:: Path :: new ( "/usr/local/bin/usbip" ) . exists ( )
146+ || std:: path:: Path :: new ( "/usr/sbin/usbip" ) . exists ( )
147+ {
148+ return true ;
149+ }
150+ // Check if kernel module is loaded
151+ std:: path:: Path :: new ( "/sys/module/usbip_host" ) . exists ( )
141152}
142153
143- /// Install usbip tools (part of linux-tools package)
154+ /// Install usbip tools — handles Arch, Debian, Ubuntu, Proxmox, Fedora, RHEL, openSUSE
144155pub async fn install_usbip ( ) -> Result < String , String > {
145156 info ! ( "WolfUSB: installing usbip tools" ) ;
146- // Detect package manager and install
147157 let script = r#"
148- if command -v apt-get >/dev/null 2>&1; then
149- KERNEL=$(uname -r)
150- apt-get update -y && apt-get install -y linux-tools-generic linux-tools-$KERNEL 2>/dev/null || apt-get install -y usbip 2>/dev/null || true
151- elif command -v dnf >/dev/null 2>&1; then
152- dnf install -y usbip
153- elif command -v pacman >/dev/null 2>&1; then
154- # usbip is included in the linux package on Arch
155- true
156- elif command -v zypper >/dev/null 2>&1; then
157- zypper install -y usbip
158- fi
159- # Verify
160- if command -v usbip >/dev/null 2>&1; then
161- echo "usbip installed successfully"
162- usbip version 2>/dev/null || true
163- else
164- echo "ERROR: usbip not found after install" >&2
165- exit 1
158+ KERNEL="$(uname -r)"
159+ echo "Kernel: $KERNEL"
160+
161+ # ─── Arch / CachyOS / Manjaro ───
162+ if command -v pacman >/dev/null 2>&1; then
163+ echo "Detected: Arch-based (pacman)"
164+ pacman -S --noconfirm usbip 2>/dev/null || true
165+
166+ # ─── Debian / Ubuntu / Proxmox VE ───
167+ elif command -v apt-get >/dev/null 2>&1; then
168+ echo "Detected: Debian/Ubuntu-based (apt)"
169+ apt-get update -y || true
170+ # Debian, Proxmox: package is simply "usbip"
171+ # Ubuntu: "usbip" or "linux-tools-generic" + "linux-tools-$KERNEL"
172+ apt-get install -y usbip 2>/dev/null \
173+ || apt-get install -y linux-tools-generic "linux-tools-$KERNEL" 2>/dev/null \
174+ || apt-get install -y linux-tools-common 2>/dev/null \
175+ || true
176+
177+ # ─── Fedora / RHEL / Rocky / AlmaLinux ───
178+ elif command -v dnf >/dev/null 2>&1; then
179+ echo "Detected: Fedora/RHEL-based (dnf)"
180+ dnf install -y usbip-utils 2>/dev/null || true
181+
182+ # ─── openSUSE ───
183+ elif command -v zypper >/dev/null 2>&1; then
184+ echo "Detected: openSUSE (zypper)"
185+ zypper install -y usbip-utils 2>/dev/null || zypper install -y usbip 2>/dev/null || true
186+
187+ else
188+ echo "ERROR: No supported package manager found"
189+ fi
190+
191+ # ─── Load kernel modules ───
192+ modprobe usbip-core 2>/dev/null || true
193+ modprobe usbip-host 2>/dev/null || true
194+ modprobe vhci-hcd 2>/dev/null || true
195+
196+ # ─── Persist modules across reboots ───
197+ mkdir -p /etc/modules-load.d
198+ printf 'usbip-core\nusbip-host\nvhci-hcd\n' > /etc/modules-load.d/wolfusb.conf 2>/dev/null || true
199+
200+ # ─── Find binary if not in PATH (Ubuntu puts it under /usr/lib/linux-tools/) ───
201+ if ! command -v usbip >/dev/null 2>&1; then
202+ for p in /usr/lib/linux-tools/"$KERNEL"/usbip /usr/lib/linux-tools/*/usbip /usr/sbin/usbip; do
203+ if [ -x "$p" ]; then
204+ echo "Found usbip at $p — creating symlink to /usr/local/bin/usbip"
205+ ln -sf "$p" /usr/local/bin/usbip
206+ break
166207 fi
208+ done
209+ fi
210+
211+ # ─── Verify ───
212+ if command -v usbip >/dev/null 2>&1; then
213+ echo "OK: usbip installed at $(command -v usbip)"
214+ usbip version 2>/dev/null || true
215+ elif [ -d /sys/module/usbip_host ]; then
216+ echo "OK: usbip kernel modules loaded but binary not found in PATH"
217+ else
218+ echo "WARNING: usbip installation may be incomplete"
219+ echo "Modules in /lib/modules/$KERNEL/kernel/drivers/usb/usbip/:"
220+ ls /lib/modules/"$KERNEL"/kernel/drivers/usb/usbip/ 2>/dev/null || echo " (none)"
221+ fi
167222 "# ;
168223
169224 let output = tokio:: process:: Command :: new ( "bash" )
@@ -179,11 +234,14 @@ pub async fn install_usbip() -> Result<String, String> {
179234 String :: from_utf8_lossy( & output. stderr)
180235 ) ;
181236
237+ // Try loading modules after install
238+ let _ = ensure_usbip_modules ( ) ;
239+
182240 if is_usbip_available ( ) {
183- let _ = ensure_usbip_modules ( ) ;
184241 Ok ( combined)
185242 } else {
186- Err ( format ! ( "Installation failed:\n {}" , combined) )
243+ // Even if the check fails, the script output tells the user what happened
244+ Err ( format ! ( "Installation may have partially succeeded. Check output:\n {}" , combined) )
187245 }
188246}
189247
@@ -707,7 +765,7 @@ pub fn merge_remote_assignments(remote_assignments: &[UsbAssignment]) {
707765
708766 // Remove assignments that no longer exist on any remote node
709767 // (Only remove if the source node is NOT us — we're authoritative for our own devices)
710- let self_id = hostname :: get ( ) . map ( |h| h . to_string_lossy ( ) . to_string ( ) ) . unwrap_or_default ( ) ;
768+ let self_id = crate :: agent :: self_node_id ( ) ;
711769 let remote_busids: Vec < ( & str , & str ) > = remote_assignments. iter ( )
712770 . map ( |a| ( a. busid . as_str ( ) , a. source_node_id . as_str ( ) ) )
713771 . collect ( ) ;
0 commit comments