fix(browser): platform-aware error messages + corrupted download detection
Root cause on macOS: 'camoufox-browser install' downloaded v150.0.2-alpha.26 instead of the stable browser binary. This alpha version is missing libmozglue.dylib, causing dyld to crash immediately. Changes: 1. detect_os() — returns Mac/Linux/Wsl/Windows enum for platform-aware logic 2. check_known_errors() — parses daemon stderr for known failures (missing libmozglue.dylib on macOS, missing libgtk/libnss on Linux, code-signing) 3. platform_fix_instructions() — returns OS-specific recovery steps: - macOS: rm -rf ~/Library/Caches/camoufox, reinstall, or Docker fallback - WSL/Linux: correct apt-get install commands (with t64 package variants) 4. Browser cache detection now checks Camoufox.app on macOS, camoufox on Linux 5. All error messages use detect_os() — no more Linux apt-get on macOS 6. Corrupted download detected automatically, user told to clear cache + retry Clean build: 0 errors, 0 warnings
This commit is contained in:
@@ -19,11 +19,6 @@
|
||||
//! The daemon is started via Node.js's `npx`. Resolution order:
|
||||
//! 1. `config.browser.node_path` — explicit path in config
|
||||
//! 2. `"npx"` on Linux/macOS, `"npx.cmd"` on Windows — PATH lookup
|
||||
//!
|
||||
//! WSL Detection
|
||||
//!
|
||||
//! Windows Subsystem for Linux is detected by reading `/proc/version`.
|
||||
//! WSL-specific error messages guide users to install Node.js inside WSL.
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
@@ -50,6 +45,37 @@ pub enum BrowserInitResult {
|
||||
Unavailable(String),
|
||||
}
|
||||
|
||||
/// Platform category for platform-specific error messages.
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum OsKind {
|
||||
Mac,
|
||||
Linux,
|
||||
Windows,
|
||||
Wsl,
|
||||
}
|
||||
|
||||
/// Detect what operating system we're running on.
|
||||
fn detect_os() -> OsKind {
|
||||
if cfg!(target_os = "windows") {
|
||||
return OsKind::Windows;
|
||||
}
|
||||
if cfg!(target_os = "macos") {
|
||||
return OsKind::Mac;
|
||||
}
|
||||
// Linux — check for WSL
|
||||
if std::fs::read_to_string("/proc/version")
|
||||
.map(|c| {
|
||||
let l = c.to_lowercase();
|
||||
l.contains("microsoft") || l.contains("wsl")
|
||||
})
|
||||
.unwrap_or(false)
|
||||
{
|
||||
OsKind::Wsl
|
||||
} else {
|
||||
OsKind::Linux
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolve the npx executable name, respecting config and platform.
|
||||
fn resolve_npx(node_path: Option<&str>) -> &str {
|
||||
if let Some(p) = node_path {
|
||||
@@ -64,36 +90,113 @@ fn resolve_npx(node_path: Option<&str>) -> &str {
|
||||
}
|
||||
}
|
||||
|
||||
/// Detect if we're running inside Windows Subsystem for Linux (WSL).
|
||||
fn is_wsl() -> bool {
|
||||
std::fs::read_to_string("/proc/version")
|
||||
.map(|content| {
|
||||
let lower = content.to_lowercase();
|
||||
lower.contains("microsoft") || lower.contains("wsl")
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Get a platform-aware example npx path for error messages.
|
||||
fn example_npx_path() -> &'static str {
|
||||
if cfg!(target_os = "macos") {
|
||||
"\"/Users/yourname/.nvm/versions/node/v22/bin/npx\""
|
||||
} else {
|
||||
"\"/home/user/.nvm/versions/node/v22/bin/npx\""
|
||||
match detect_os() {
|
||||
OsKind::Mac => "\"/Users/yourname/.nvm/versions/node/v22/bin/npx\"",
|
||||
_ => "\"/home/user/.nvm/versions/node/v22/bin/npx\"",
|
||||
}
|
||||
}
|
||||
|
||||
/// Path to the Camoufox browser binary cache directory.
|
||||
/// This is where `camoufox-browser install` downloads the ~662MB browser.
|
||||
/// macOS: ~/Library/Caches/camoufox/
|
||||
/// Linux/WSL: ~/.cache/camoufox/
|
||||
fn camoufox_browser_cache_dir() -> std::path::PathBuf {
|
||||
let home = dirs::home_dir().unwrap_or_else(|| std::path::PathBuf::from("/tmp"));
|
||||
home.join(".cache").join("camoufox")
|
||||
if cfg!(target_os = "macos") {
|
||||
home.join("Library").join("Caches").join("camoufox")
|
||||
} else {
|
||||
home.join(".cache").join("camoufox")
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if the Camoufox browser binary is already installed.
|
||||
fn is_browser_installed() -> bool {
|
||||
let cache_dir = camoufox_browser_cache_dir();
|
||||
cache_dir.exists() && cache_dir.join("camoufox").exists()
|
||||
let app_bundle = cache_dir.join("Camoufox.app");
|
||||
let binary = cache_dir.join("camoufox");
|
||||
|
||||
if cfg!(target_os = "macos") {
|
||||
// macOS: binary lives inside Camoufox.app/Contents/MacOS/camoufox
|
||||
app_bundle.exists() && app_bundle.join("Contents").join("MacOS").join("camoufox").exists()
|
||||
} else {
|
||||
// Linux/WSL: binary at ~/.cache/camoufox/camoufox
|
||||
binary.exists()
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if the browser binary appears corrupted (missing critical dylib).
|
||||
/// Looks for common indicators in the daemon log.
|
||||
fn check_known_errors(log: &str) -> Option<String> {
|
||||
let lower = log.to_lowercase();
|
||||
|
||||
// macOS: missing libmozglue.dylib — corrupted download
|
||||
if lower.contains("libmozglue.dylib") && lower.contains("no such file") {
|
||||
return Some(
|
||||
"The Camoufox browser download appears corrupted (missing libmozglue.dylib). \
|
||||
This can happen with unstable alpha versions."
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
// Linux: missing libgtk / libnss
|
||||
if lower.contains("error while loading shared libraries") {
|
||||
if lower.contains("libgtk") || lower.contains("libnss") || lower.contains("libasound") {
|
||||
return Some(
|
||||
"Missing Linux system libraries needed by the Camoufox browser."
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// macOS: code signing issue
|
||||
if lower.contains("killed") && lower.contains("code signature") {
|
||||
return Some(
|
||||
"macOS code signing issue. The downloaded browser binary may need manual approval."
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Get platform-specific dependency fix instructions.
|
||||
fn platform_fix_instructions() -> &'static str {
|
||||
match detect_os() {
|
||||
OsKind::Mac => "\
|
||||
\u{2192} The Camoufox browser download was corrupted. Fix:\n \
|
||||
1. Remove the corrupted cache:\n \
|
||||
rm -rf ~/Library/Caches/camoufox\n \
|
||||
2. Reinstall the browser binary:\n \
|
||||
npx camoufox-browser@latest install\n \
|
||||
3. The download is ~662MB. If it still fails, try:\n \
|
||||
npx camoufox-browser@latest install 2>&1 | tail -20\n \
|
||||
to see the real download error.\n \
|
||||
4. Alternative: use Docker instead:\n \
|
||||
docker run -d --name camoufox -p 9377:9377 \\\n \
|
||||
ghcr.io/nousresearch/camoufox-browser:latest",
|
||||
OsKind::Wsl => "\
|
||||
\u{2192} Missing Linux dependencies on WSL. Install them:\n \
|
||||
sudo apt-get update && sudo apt-get install -y \\\n \
|
||||
libgtk-3-0 libnss3 libnspr4 libatk1.0-0t64 \\\n \
|
||||
libatk-bridge2.0-0t64 libcups2t64 libdrm2 \\\n \
|
||||
libxkbcommon0 libxcomposite1 libxdamage1 \\\n \
|
||||
libxfixes3 libxrandr2 libgbm1 libpango-1.0-0 \\\n \
|
||||
libcairo2 libasound2t64 libxcb-shm0 \\\n \
|
||||
libx11-xcb1 libx11-6 libxcb1 libxext6 \\\n \
|
||||
libxcursor1 libxi6 libfreetype6 libfontconfig1",
|
||||
OsKind::Linux => "\
|
||||
\u{2192} Missing Linux dependencies. Install them:\n \
|
||||
sudo apt-get update && sudo apt-get install -y \\\n \
|
||||
libgtk-3-0 libnss3 libnspr4 libatk1.0-0t64 \\\n \
|
||||
libatk-bridge2.0-0t64 libcups2t64 libdrm2 \\\n \
|
||||
libxkbcommon0 libxcomposite1 libxdamage1 \\\n \
|
||||
libxfixes3 libxrandr2 libgbm1 libpango-1.0-0 \\\n \
|
||||
libcairo2 libasound2t64 libxcb-shm0",
|
||||
OsKind::Windows => "\
|
||||
\u{2192} Windows issue. Try reinstalling camoufox-browser:\n \
|
||||
npm install -g camoufox-browser",
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize the camoufox-browser daemon and return a ready client.
|
||||
@@ -120,18 +223,26 @@ pub async fn init_browser(node_path: Option<&str>) -> BrowserInitResult {
|
||||
Ok(msg) => info!("Camoufox browser install: {}", msg),
|
||||
Err(e) => {
|
||||
let npx_name = resolve_npx(node_path);
|
||||
let wsl_hint = if is_wsl() {
|
||||
"\n\n \u{26a0} WSL detected: You may need to install system deps first. Run:\n sudo apt-get update && sudo apt-get install -y libgtk-3-0 libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 libxfixes3 libxrandr2 libgbm1 libpango-1.0-0 libcairo2 libasound2"
|
||||
} else {
|
||||
""
|
||||
let os_type = detect_os();
|
||||
let platform_hint = match os_type {
|
||||
OsKind::Wsl => "\n\n \u{26a0} WSL detected: Make sure you're not behind a proxy/firewall that blocks \
|
||||
the GitHub release download. The browser binary is ~662MB.\n \
|
||||
Try: curl -L https://github.com/msly/camoufox-browser/releases/download/v0.1.0/camoufox-browser-linux-x64 -o /dev/null"
|
||||
.to_string(),
|
||||
OsKind::Mac => "\n\n \u{26a0} macOS: The download server may be slow or unstable. Try:\n \
|
||||
npx camoufox-browser@latest install --verbose\n \
|
||||
If it keeps failing, use Docker instead:\n \
|
||||
docker run -d --name camoufox -p 9377:9377 \\\n \
|
||||
ghcr.io/nousresearch/camoufox-browser:latest"
|
||||
.to_string(),
|
||||
_ => String::new(),
|
||||
};
|
||||
return BrowserInitResult::Unavailable(format!(
|
||||
"Failed to download Camoufox browser: {}\n\n\
|
||||
Run this manually to install:\n \
|
||||
{} camoufox-browser@latest install\n\n\
|
||||
You need ~662MB of free disk space for the browser binary.\n\
|
||||
After install completes, run 'gyandoot run' again.{}",
|
||||
e, npx_name, wsl_hint
|
||||
You need ~662MB of free disk space for the browser binary.{}",
|
||||
e, npx_name, platform_hint
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -151,21 +262,13 @@ pub async fn init_browser(node_path: Option<&str>) -> BrowserInitResult {
|
||||
}
|
||||
Ok(false) => {
|
||||
let msg = format!(
|
||||
"Daemon did not start within {}s \u{2014} socket not found at {}\n\
|
||||
This usually means:\n \
|
||||
1. Missing Linux system deps (WSL/Ubuntu):\n \
|
||||
sudo apt-get update && sudo apt-get install -y \\\n \
|
||||
libgtk-3-0 libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 \\\n \
|
||||
libcups2 libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 \\\n \
|
||||
libxfixes3 libxrandr2 libgbm1 libpango-1.0-0 libcairo2 \\\n \
|
||||
libasound2 libxcb-shm0 libx11-xcb1 libx11-6 libxcb1 \\\n \
|
||||
libxext6 libxcursor1 libxi6 libfreetype6 libfontconfig1\n \
|
||||
2. Try starting manually to see the error:\n \
|
||||
{} camoufox-browser@latest tab_list --session default\n\n\
|
||||
Check the daemon log at the path above for details.",
|
||||
"Camoufox daemon did not start within {}s \u{2014} socket not found at {}\n\n\
|
||||
The browser binary may be corrupted. Try fixing it:\n\
|
||||
{}\n\n\
|
||||
Then run 'gyandoot run' again.",
|
||||
DAEMON_STARTUP_TIMEOUT_SECS,
|
||||
socket_path.display(),
|
||||
resolve_npx(node_path),
|
||||
platform_fix_instructions(),
|
||||
);
|
||||
warn!("{}", msg);
|
||||
BrowserInitResult::Unavailable(msg)
|
||||
@@ -178,25 +281,38 @@ pub async fn init_browser(node_path: Option<&str>) -> BrowserInitResult {
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
let error_detail = e.to_string();
|
||||
let npx_name = resolve_npx(node_path);
|
||||
let msg = format!(
|
||||
"Could not start camoufox daemon: {}\n\n\
|
||||
The npm package 'camoufox-browser' is installed, and the browser\n\
|
||||
binary has been downloaded, but the daemon failed to start.\n\n\
|
||||
Try running this manually to see the real error:\n \
|
||||
{} camoufox-browser@latest tab_list --session default\n\n\
|
||||
Common fixes:\n \
|
||||
1. Install missing system dependencies (Linux/WSL):\n \
|
||||
sudo apt-get update && sudo apt-get install -y \\\n \
|
||||
libgtk-3-0 libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 \\\n \
|
||||
libcups2 libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 \\\n \
|
||||
libxfixes3 libxrandr2 libgbm1 libpango-1.0-0 libcairo2 \\\n \
|
||||
libasound2\n \
|
||||
2. If Node is via nvm/fnm/asdf, set browser.node_path in config:\n \
|
||||
[browser]\n \
|
||||
node_path = {}",
|
||||
e, npx_name, example_npx_path()
|
||||
|
||||
// Check for known errors in the daemon output
|
||||
let known_error = check_known_errors(&error_detail);
|
||||
|
||||
// Build the full message
|
||||
let mut msg = format!(
|
||||
"\u{26a0} Camoufox daemon failed to start.\n\n\
|
||||
Error:\n {}\n\n",
|
||||
error_detail,
|
||||
);
|
||||
|
||||
if let Some(hint) = known_error {
|
||||
msg.push_str(&format!("Detected: {}\n\n", hint));
|
||||
msg.push_str(platform_fix_instructions());
|
||||
} else {
|
||||
msg.push_str(&format!(
|
||||
"Try running this manually to see the full error:\n \
|
||||
{} camoufox-browser@latest tab_list --session default\n\n\
|
||||
Common fixes:\n\
|
||||
{}\n\n\
|
||||
3. If Node is via nvm/fnm/asdf, set browser.node_path in config:\n \
|
||||
[browser]\n \
|
||||
node_path = {}",
|
||||
npx_name,
|
||||
platform_fix_instructions(),
|
||||
example_npx_path()
|
||||
));
|
||||
}
|
||||
|
||||
msg.push_str("\n\nThen run 'gyandoot run' again.");
|
||||
warn!("{}", msg);
|
||||
BrowserInitResult::Unavailable(msg)
|
||||
}
|
||||
@@ -249,7 +365,6 @@ async fn trigger_daemon_start(session: &str, node_path: Option<&str>) -> Result<
|
||||
|
||||
let npx = resolve_npx(node_path);
|
||||
|
||||
// Run a command — the CLI auto-starts the daemon as a side effect
|
||||
let output = Command::new(npx)
|
||||
.args(&[
|
||||
"camoufox-browser@latest",
|
||||
@@ -283,8 +398,7 @@ async fn trigger_daemon_start(session: &str, node_path: Option<&str>) -> Result<
|
||||
));
|
||||
|
||||
return Err(format!(
|
||||
"camoufox-browser daemon start failed (exit: {}):\n {}\n \u{2192} Full log saved to {}",
|
||||
output.status.code().unwrap_or(-1),
|
||||
"{}\n \u{2192} Full log saved to {}",
|
||||
details,
|
||||
log_file.display(),
|
||||
));
|
||||
|
||||
Reference in New Issue
Block a user