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:
|
//! The daemon is started via Node.js's `npx`. Resolution order:
|
||||||
//! 1. `config.browser.node_path` — explicit path in config
|
//! 1. `config.browser.node_path` — explicit path in config
|
||||||
//! 2. `"npx"` on Linux/macOS, `"npx.cmd"` on Windows — PATH lookup
|
//! 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;
|
use std::time::Duration;
|
||||||
|
|
||||||
@@ -50,6 +45,37 @@ pub enum BrowserInitResult {
|
|||||||
Unavailable(String),
|
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.
|
/// Resolve the npx executable name, respecting config and platform.
|
||||||
fn resolve_npx(node_path: Option<&str>) -> &str {
|
fn resolve_npx(node_path: Option<&str>) -> &str {
|
||||||
if let Some(p) = node_path {
|
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.
|
/// Get a platform-aware example npx path for error messages.
|
||||||
fn example_npx_path() -> &'static str {
|
fn example_npx_path() -> &'static str {
|
||||||
if cfg!(target_os = "macos") {
|
match detect_os() {
|
||||||
"\"/Users/yourname/.nvm/versions/node/v22/bin/npx\""
|
OsKind::Mac => "\"/Users/yourname/.nvm/versions/node/v22/bin/npx\"",
|
||||||
} else {
|
_ => "\"/home/user/.nvm/versions/node/v22/bin/npx\"",
|
||||||
"\"/home/user/.nvm/versions/node/v22/bin/npx\""
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Path to the Camoufox browser binary cache directory.
|
/// 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 {
|
fn camoufox_browser_cache_dir() -> std::path::PathBuf {
|
||||||
let home = dirs::home_dir().unwrap_or_else(|| std::path::PathBuf::from("/tmp"));
|
let home = dirs::home_dir().unwrap_or_else(|| std::path::PathBuf::from("/tmp"));
|
||||||
|
if cfg!(target_os = "macos") {
|
||||||
|
home.join("Library").join("Caches").join("camoufox")
|
||||||
|
} else {
|
||||||
home.join(".cache").join("camoufox")
|
home.join(".cache").join("camoufox")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if the Camoufox browser binary is already installed.
|
/// Check if the Camoufox browser binary is already installed.
|
||||||
fn is_browser_installed() -> bool {
|
fn is_browser_installed() -> bool {
|
||||||
let cache_dir = camoufox_browser_cache_dir();
|
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.
|
/// 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),
|
Ok(msg) => info!("Camoufox browser install: {}", msg),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let npx_name = resolve_npx(node_path);
|
let npx_name = resolve_npx(node_path);
|
||||||
let wsl_hint = if is_wsl() {
|
let os_type = detect_os();
|
||||||
"\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"
|
let platform_hint = match os_type {
|
||||||
} else {
|
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!(
|
return BrowserInitResult::Unavailable(format!(
|
||||||
"Failed to download Camoufox browser: {}\n\n\
|
"Failed to download Camoufox browser: {}\n\n\
|
||||||
Run this manually to install:\n \
|
Run this manually to install:\n \
|
||||||
{} camoufox-browser@latest install\n\n\
|
{} camoufox-browser@latest install\n\n\
|
||||||
You need ~662MB of free disk space for the browser binary.\n\
|
You need ~662MB of free disk space for the browser binary.{}",
|
||||||
After install completes, run 'gyandoot run' again.{}",
|
e, npx_name, platform_hint
|
||||||
e, npx_name, wsl_hint
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -151,21 +262,13 @@ pub async fn init_browser(node_path: Option<&str>) -> BrowserInitResult {
|
|||||||
}
|
}
|
||||||
Ok(false) => {
|
Ok(false) => {
|
||||||
let msg = format!(
|
let msg = format!(
|
||||||
"Daemon did not start within {}s \u{2014} socket not found at {}\n\
|
"Camoufox daemon did not start within {}s \u{2014} socket not found at {}\n\n\
|
||||||
This usually means:\n \
|
The browser binary may be corrupted. Try fixing it:\n\
|
||||||
1. Missing Linux system deps (WSL/Ubuntu):\n \
|
{}\n\n\
|
||||||
sudo apt-get update && sudo apt-get install -y \\\n \
|
Then run 'gyandoot run' again.",
|
||||||
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.",
|
|
||||||
DAEMON_STARTUP_TIMEOUT_SECS,
|
DAEMON_STARTUP_TIMEOUT_SECS,
|
||||||
socket_path.display(),
|
socket_path.display(),
|
||||||
resolve_npx(node_path),
|
platform_fix_instructions(),
|
||||||
);
|
);
|
||||||
warn!("{}", msg);
|
warn!("{}", msg);
|
||||||
BrowserInitResult::Unavailable(msg)
|
BrowserInitResult::Unavailable(msg)
|
||||||
@@ -178,25 +281,38 @@ pub async fn init_browser(node_path: Option<&str>) -> BrowserInitResult {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
let error_detail = e.to_string();
|
||||||
let npx_name = resolve_npx(node_path);
|
let npx_name = resolve_npx(node_path);
|
||||||
let msg = format!(
|
|
||||||
"Could not start camoufox daemon: {}\n\n\
|
// Check for known errors in the daemon output
|
||||||
The npm package 'camoufox-browser' is installed, and the browser\n\
|
let known_error = check_known_errors(&error_detail);
|
||||||
binary has been downloaded, but the daemon failed to start.\n\n\
|
|
||||||
Try running this manually to see the real error:\n \
|
// 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\
|
{} camoufox-browser@latest tab_list --session default\n\n\
|
||||||
Common fixes:\n \
|
Common fixes:\n\
|
||||||
1. Install missing system dependencies (Linux/WSL):\n \
|
{}\n\n\
|
||||||
sudo apt-get update && sudo apt-get install -y \\\n \
|
3. If Node is via nvm/fnm/asdf, set browser.node_path in config:\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 \
|
[browser]\n \
|
||||||
node_path = {}",
|
node_path = {}",
|
||||||
e, npx_name, example_npx_path()
|
npx_name,
|
||||||
);
|
platform_fix_instructions(),
|
||||||
|
example_npx_path()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.push_str("\n\nThen run 'gyandoot run' again.");
|
||||||
warn!("{}", msg);
|
warn!("{}", msg);
|
||||||
BrowserInitResult::Unavailable(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);
|
let npx = resolve_npx(node_path);
|
||||||
|
|
||||||
// Run a command — the CLI auto-starts the daemon as a side effect
|
|
||||||
let output = Command::new(npx)
|
let output = Command::new(npx)
|
||||||
.args(&[
|
.args(&[
|
||||||
"camoufox-browser@latest",
|
"camoufox-browser@latest",
|
||||||
@@ -283,8 +398,7 @@ async fn trigger_daemon_start(session: &str, node_path: Option<&str>) -> Result<
|
|||||||
));
|
));
|
||||||
|
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
"camoufox-browser daemon start failed (exit: {}):\n {}\n \u{2192} Full log saved to {}",
|
"{}\n \u{2192} Full log saved to {}",
|
||||||
output.status.code().unwrap_or(-1),
|
|
||||||
details,
|
details,
|
||||||
log_file.display(),
|
log_file.display(),
|
||||||
));
|
));
|
||||||
|
|||||||
Reference in New Issue
Block a user