Skip to content

Commit 49700fd

Browse files
author
Paul C
committed
fix: WolfUSB install handles all distros + KB auto-download + UI polish
usbip installer: correct package names for all platforms: Arch: usbip, Debian/Ubuntu/Proxmox: usbip, Fedora/RHEL: usbip-utils, openSUSE: usbip-utils. No set -e, all commands guarded with || true. Creates symlink for Ubuntu's non-standard binary path. Persists kernel modules to /etc/modules-load.d/wolfusb.conf. AI knowledge base: auto-downloads from GitHub on first run if not found locally. Fixes "Knowledge: 0KB" on prebuilt binary installs. WolfUSB UI: centered install page with icon and manual install commands, improved device table layout (Status + Assign columns), better empty state, install failure shows detailed output with platform-specific help. Also: merge_remote_assignments uses self_node_id() not hostname.
1 parent cbb634c commit 49700fd

3 files changed

Lines changed: 166 additions & 63 deletions

File tree

src/ai/mod.rs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -800,22 +800,46 @@ impl AiAgent {
800800
fn load_knowledge_base() -> String {
801801
let mut knowledge = String::new();
802802

803+
let kb_dest = "/etc/wolfstack/knowledge/wolfstack-kb.md";
804+
803805
// Load the expert knowledge base first (shipped with WolfStack)
804806
let kb_paths = [
805-
"/etc/wolfstack/knowledge/wolfstack-kb.md",
807+
kb_dest,
806808
"knowledge/wolfstack-kb.md",
807809
"../knowledge/wolfstack-kb.md",
808810
];
809811
for kb_path in &kb_paths {
810812
if let Ok(content) = std::fs::read_to_string(kb_path) {
811813
if !content.trim().is_empty() {
812814
knowledge.push_str(&content);
813-
tracing::info!("Loaded expert knowledge base from {}", kb_path);
815+
tracing::info!("Loaded expert knowledge base from {} ({} bytes)", kb_path, content.len());
814816
break;
815817
}
816818
}
817819
}
818820

821+
// If no knowledge base found, try downloading it from GitHub
822+
if knowledge.is_empty() {
823+
tracing::info!("AI knowledge base not found locally — downloading from GitHub...");
824+
let _ = std::fs::create_dir_all("/etc/wolfstack/knowledge");
825+
let url = "https://raw.githubusercontent.com/wolfsoftwaresystemsltd/WolfStack/master/knowledge/wolfstack-kb.md";
826+
match std::process::Command::new("curl")
827+
.args(["-fsSL", "--connect-timeout", "10", "--max-time", "30", "-o", kb_dest, url])
828+
.status()
829+
{
830+
Ok(s) if s.success() => {
831+
if let Ok(content) = std::fs::read_to_string(kb_dest) {
832+
if !content.trim().is_empty() {
833+
tracing::info!("Downloaded AI knowledge base ({} bytes)", content.len());
834+
knowledge.push_str(&content);
835+
}
836+
}
837+
}
838+
Ok(s) => tracing::warn!("Failed to download knowledge base (exit {})", s),
839+
Err(e) => tracing::warn!("Failed to download knowledge base: {}", e),
840+
}
841+
}
842+
819843
// Also load wolfscale web files for additional context
820844
let dirs = [KNOWLEDGE_DIR, KNOWLEDGE_DIR_DEV, "wolfscale/web", "../wolfscale/web"];
821845
let mut found_dir = None;

src/wolfusb/mod.rs

Lines changed: 85 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -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)
137137
pub 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
144155
pub 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

Comments
 (0)