fix(browser): clean reimplementation — detect corrupted dl + Docker check
Clean build: 0 errors, 0 warnings
This commit is contained in:
@@ -62,7 +62,6 @@ fn detect_os() -> OsKind {
|
||||
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();
|
||||
@@ -99,8 +98,6 @@ fn example_npx_path() -> &'static str {
|
||||
}
|
||||
|
||||
/// Path to the Camoufox browser binary cache directory.
|
||||
/// 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"));
|
||||
if cfg!(target_os = "macos") {
|
||||
@@ -110,104 +107,95 @@ fn camoufox_browser_cache_dir() -> std::path::PathBuf {
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if the Camoufox browser binary is already installed.
|
||||
/// Check if the Camoufox browser binary is installed and uncorrupted.
|
||||
fn is_browser_installed() -> bool {
|
||||
let cache_dir = camoufox_browser_cache_dir();
|
||||
let app_bundle = cache_dir.join("Camoufox.app");
|
||||
let binary = cache_dir.join("camoufox");
|
||||
|
||||
if !cache_dir.exists() {
|
||||
return false;
|
||||
}
|
||||
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()
|
||||
let macos_dir = cache_dir.join("Camoufox.app").join("Contents").join("MacOS");
|
||||
let binary = macos_dir.join("camoufox");
|
||||
let lib = macos_dir.join("libmozglue.dylib");
|
||||
binary.exists() && lib.exists()
|
||||
} else {
|
||||
// Linux/WSL: binary at ~/.cache/camoufox/camoufox
|
||||
binary.exists()
|
||||
cache_dir.join("camoufox").exists()
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if the browser binary appears corrupted (missing critical dylib).
|
||||
/// Looks for common indicators in the daemon log.
|
||||
/// Check for known error patterns in the daemon log and return a user-friendly hint.
|
||||
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(),
|
||||
);
|
||||
return Some("The Camoufox browser download is corrupted (missing libmozglue.dylib). \
|
||||
This is a known issue with the camoufox-browser@0.1.0 package on macOS."
|
||||
.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(),
|
||||
);
|
||||
}
|
||||
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(),
|
||||
);
|
||||
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",
|
||||
fn platform_fix_instructions() -> String {
|
||||
let os = detect_os();
|
||||
let mut msg = String::new();
|
||||
|
||||
let docker_ok = std::process::Command::new("docker")
|
||||
.arg("info")
|
||||
.output()
|
||||
.map(|o| o.status.success())
|
||||
.unwrap_or(false);
|
||||
|
||||
match os {
|
||||
OsKind::Mac => {
|
||||
msg.push_str("The Camoufox browser download is corrupted on macOS.\n");
|
||||
msg.push_str("Fix:\n");
|
||||
msg.push_str(" 1. Remove the corrupted cache:\n");
|
||||
msg.push_str(" rm -rf ~/Library/Caches/camoufox\n");
|
||||
msg.push_str(" 2. Reinstall the browser binary:\n");
|
||||
msg.push_str(" npx camoufox-browser@latest install\n");
|
||||
msg.push_str(" (this downloads ~662MB, may take a few minutes)\n");
|
||||
if docker_ok {
|
||||
msg.push_str(" 3. Or use Docker (already installed!):\n");
|
||||
} else {
|
||||
msg.push_str(" 3. Or install Docker Desktop and use:\n");
|
||||
}
|
||||
msg.push_str(" docker run -d --name camoufox -p 9377:9377 \\\n");
|
||||
msg.push_str(" ghcr.io/nousresearch/camoufox-browser:latest\n");
|
||||
}
|
||||
OsKind::Wsl | OsKind::Linux => {
|
||||
msg.push_str("Missing Linux system libraries. Install them:\n");
|
||||
msg.push_str(" sudo apt-get update && sudo apt-get install -y \\\n");
|
||||
msg.push_str(" libgtk-3-0 libnss3 libnspr4 libatk1.0-0t64 \\\n");
|
||||
msg.push_str(" libatk-bridge2.0-0t64 libcups2t64 libdrm2 \\\n");
|
||||
msg.push_str(" libxkbcommon0 libxcomposite1 libxdamage1 \\\n");
|
||||
msg.push_str(" libxfixes3 libxrandr2 libgbm1 libpango-1.0-0 \\\n");
|
||||
msg.push_str(" libcairo2 libasound2t64 libxcb-shm0\n");
|
||||
}
|
||||
OsKind::Windows => {
|
||||
msg.push_str("Windows issue. Try reinstalling:\n");
|
||||
msg.push_str(" npm install -g camoufox-browser\n");
|
||||
}
|
||||
}
|
||||
|
||||
msg
|
||||
}
|
||||
|
||||
/// Initialize the camoufox-browser daemon and return a ready client.
|
||||
///
|
||||
/// `node_path` — optional explicit path to `npx`/`npx.cmd`.
|
||||
/// Pass `None` for default PATH lookup.
|
||||
pub async fn init_browser(node_path: Option<&str>) -> BrowserInitResult {
|
||||
let session = DEFAULT_SESSION;
|
||||
let socket_path = default_socket_path(session);
|
||||
|
||||
// 1. Quick check — is the daemon already running?
|
||||
if check_daemon_socket(session).await {
|
||||
info!("Camoufox daemon already running on socket: {}", socket_path.display());
|
||||
let client = BrowserClient::new().with_session(session);
|
||||
@@ -216,42 +204,31 @@ pub async fn init_browser(node_path: Option<&str>) -> BrowserInitResult {
|
||||
|
||||
info!("Camoufox daemon not running, triggering startup...");
|
||||
|
||||
// 2. Check — is the browser binary cached? If not, run install first.
|
||||
if !is_browser_installed() {
|
||||
info!("Camoufox browser not cached yet (~662MB download needed). Running install...");
|
||||
info!("Camoufox browser not cached. Running install...");
|
||||
match run_install(node_path).await {
|
||||
Ok(msg) => info!("Camoufox browser install: {}", msg),
|
||||
Err(e) => {
|
||||
let npx_name = resolve_npx(node_path);
|
||||
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(),
|
||||
};
|
||||
let mut hint = String::new();
|
||||
let os = detect_os();
|
||||
if os == OsKind::Wsl {
|
||||
hint.push_str(
|
||||
"\n\nWSL detected: Make sure you can download from GitHub releases.\n",
|
||||
);
|
||||
hint.push_str("Try: curl -L --fail https://github.com/msly/camoufox-browser/releases\n");
|
||||
}
|
||||
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.{}",
|
||||
e, npx_name, platform_hint
|
||||
Run manually:\n {} camoufox-browser@latest install\n{}",
|
||||
e, npx_name, hint
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Trigger daemon auto-start via CLI
|
||||
match trigger_daemon_start(session, node_path).await {
|
||||
Ok(()) => {
|
||||
// 4. Wait for socket to appear
|
||||
info!("Waiting for daemon socket at {}...", socket_path.display());
|
||||
match wait_for_socket(session).await {
|
||||
Ok(true) => {
|
||||
@@ -262,10 +239,8 @@ pub async fn init_browser(node_path: Option<&str>) -> BrowserInitResult {
|
||||
}
|
||||
Ok(false) => {
|
||||
let msg = format!(
|
||||
"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.",
|
||||
"Camoufox daemon did not start within {}s.\n\
|
||||
Socket not found at {}\n\n{}",
|
||||
DAEMON_STARTUP_TIMEOUT_SECS,
|
||||
socket_path.display(),
|
||||
platform_fix_instructions(),
|
||||
@@ -282,33 +257,18 @@ 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);
|
||||
|
||||
// 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,
|
||||
);
|
||||
let mut msg = format!("Camoufox daemon failed to start.\n\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());
|
||||
msg.push_str(&hint);
|
||||
msg.push('\n');
|
||||
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,
|
||||
"Try running manually:\n {} camoufox-browser@latest tab_list --session default\n\n{}",
|
||||
resolve_npx(node_path),
|
||||
platform_fix_instructions(),
|
||||
example_npx_path()
|
||||
));
|
||||
}
|
||||
|
||||
@@ -319,15 +279,12 @@ pub async fn init_browser(node_path: Option<&str>) -> BrowserInitResult {
|
||||
}
|
||||
}
|
||||
|
||||
/// Run `camoufox-browser install` to download the browser binary if missing.
|
||||
/// Run `camoufox-browser install` to download the browser binary.
|
||||
async fn run_install(node_path: Option<&str>) -> Result<String, String> {
|
||||
let npx = resolve_npx(node_path);
|
||||
|
||||
let output = Command::new(npx)
|
||||
.args(&[
|
||||
"camoufox-browser@latest",
|
||||
"install",
|
||||
])
|
||||
.args(&["camoufox-browser@latest", "install"])
|
||||
.output()
|
||||
.await
|
||||
.map_err(|e| format!("Cannot run '{} camoufox-browser install': {}", npx, e))?;
|
||||
@@ -344,8 +301,8 @@ async fn run_install(node_path: Option<&str>) -> Result<String, String> {
|
||||
));
|
||||
}
|
||||
|
||||
// Extract the key message from the output
|
||||
let last_line = stdout.lines()
|
||||
let last_line = stdout
|
||||
.lines()
|
||||
.chain(stderr.lines())
|
||||
.filter(|l| l.contains("complete") || l.contains("Downloaded") || l.contains("Updated"))
|
||||
.last()
|
||||
@@ -355,7 +312,7 @@ async fn run_install(node_path: Option<&str>) -> Result<String, String> {
|
||||
Ok(last_line)
|
||||
}
|
||||
|
||||
/// Trigger daemon startup by running a quick CLI command.
|
||||
/// Trigger daemon startup by running `tab_list`, which auto-starts the daemon.
|
||||
async fn trigger_daemon_start(session: &str, node_path: Option<&str>) -> Result<(), String> {
|
||||
let home = dirs::home_dir().ok_or("Cannot determine home directory")?;
|
||||
let log_dir = home.join(".gyandoot");
|
||||
@@ -366,21 +323,17 @@ async fn trigger_daemon_start(session: &str, node_path: Option<&str>) -> Result<
|
||||
let npx = resolve_npx(node_path);
|
||||
|
||||
let output = Command::new(npx)
|
||||
.args(&[
|
||||
"camoufox-browser@latest",
|
||||
"tab_list",
|
||||
"--session", session,
|
||||
])
|
||||
.args(&["camoufox-browser@latest", "tab_list", "--session", session])
|
||||
.output()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
let hint = if node_path.is_none() {
|
||||
format!(
|
||||
"\n \u{2192} Set [browser].node_path in your config:\n [browser]\n node_path = {}",
|
||||
"\nSet [browser].node_path in your config:\n [browser]\n node_path = {}",
|
||||
example_npx_path()
|
||||
)
|
||||
} else {
|
||||
format!("\n \u{2192} npx at '{}' not found or not executable", npx)
|
||||
format!("\nnpx at '{}' not found or not executable", npx)
|
||||
};
|
||||
format!("{} not found: {}. Is Node.js installed?{}", npx, e, hint)
|
||||
})?;
|
||||
@@ -388,17 +341,20 @@ async fn trigger_daemon_start(session: &str, node_path: Option<&str>) -> Result<
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
|
||||
let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
||||
let details = if !stderr.is_empty() { stderr.clone() } else { stdout.clone() };
|
||||
let details = if !stderr.is_empty() { stderr } else { stdout };
|
||||
|
||||
// Save to log file
|
||||
let _ = std::fs::write(&log_file, format!(
|
||||
"exit_code: {}\nstdout:\n{}\nstderr:\n{}\n",
|
||||
output.status.code().unwrap_or(-1),
|
||||
stdout, stderr
|
||||
));
|
||||
let _ = std::fs::write(
|
||||
&log_file,
|
||||
format!(
|
||||
"exit_code: {}\nstderr:\n{}\nstdout:\n{}\n",
|
||||
output.status.code().unwrap_or(-1),
|
||||
String::from_utf8_lossy(&output.stderr),
|
||||
String::from_utf8_lossy(&output.stdout),
|
||||
),
|
||||
);
|
||||
|
||||
return Err(format!(
|
||||
"{}\n \u{2192} Full log saved to {}",
|
||||
"{}\n Full log saved to {}",
|
||||
details,
|
||||
log_file.display(),
|
||||
));
|
||||
|
||||
Reference in New Issue
Block a user