From 767516745e972f32379d9dd5fc71447377ddcb87 Mon Sep 17 00:00:00 2001 From: Chaos Date: Sun, 12 Jul 2026 00:49:45 +0800 Subject: [PATCH] Fix snap chromium path collapsing to /usr/bin/snap readlink/realpath of /snap/bin/chromium becomes /usr/bin/snap, which is not a browser and breaks DrissionPage launch. Keep the snap wrapper path, reject snap host basenames, and prefer real deb chromium binaries. --- auto_register.py | 36 +++++++- cpa_xai/browser_confirm.py | 22 ++++- grok_register_ttk.py | 112 ++++++++++++++++++++++--- register_cli.py | 24 +++++- start.sh | 165 ++++++++++++++++++++++++++----------- 5 files changed, 289 insertions(+), 70 deletions(-) diff --git a/auto_register.py b/auto_register.py index 7567299..4e5b0bc 100755 --- a/auto_register.py +++ b/auto_register.py @@ -182,18 +182,48 @@ def save_account(email: str, password: str, sso: str = ""): f.write(f"{email}----{password}----{sso}\n") print(f"[+] Saved: {email}") +def _normalize_browser_path(path: str | None) -> str | None: + """Keep /snap/bin/chromium; never collapse to /usr/bin/snap.""" + if not path: + return None + path = str(path).strip() + if not path: + return None + if not (os.path.isfile(path) or os.path.islink(path) or os.path.exists(path)): + return None + base = os.path.basename(path) + if base in ("snap", "snap-confine", "snapd", "env", "bash", "sh"): + return None + low = path.replace("\\", "/").lower() + if "chromium" not in low and "chrome" not in low: + return None + if path.startswith("/snap/bin/"): + return path + try: + real = os.path.realpath(path) + if os.path.basename(real) in ("snap", "snap-confine", "snapd"): + return path if ("chromium" in low or "chrome" in low) else None + if "chromium" in real.lower() or "chrome" in real.lower(): + return real + except Exception: + pass + return path + + def resolve_browser_path() -> str | None: # Prefer Chromium: Google Chrome blocks --load-extension (turnstilePatch). - # Chrome-first order often leaves Turnstile token=0 on Xvfb servers. + # Never use realpath of /snap/bin/chromium → /usr/bin/snap (breaks launch). for cand in ( "/usr/bin/chromium", "/usr/bin/chromium-browser", + "/usr/lib/chromium/chromium", "/snap/bin/chromium", "/usr/bin/google-chrome-stable", "/usr/bin/google-chrome", ): - if os.path.isfile(cand) or os.path.islink(cand): - return cand + norm = _normalize_browser_path(cand) + if norm: + return norm return None diff --git a/cpa_xai/browser_confirm.py b/cpa_xai/browser_confirm.py index b5f5714..f4c6d79 100644 --- a/cpa_xai/browser_confirm.py +++ b/cpa_xai/browser_confirm.py @@ -232,20 +232,34 @@ def create_standalone_page( log(f"headed browser DISPLAY={display!r}") # Prefer Chromium: Google Chrome blocks --load-extension (turnstilePatch). + # Never use realpath(/snap/bin/chromium)=/usr/bin/snap — that is not a browser. for cand in ( "/usr/bin/chromium", "/usr/bin/chromium-browser", + "/usr/lib/chromium/chromium", "/snap/bin/chromium", "/usr/bin/google-chrome-stable", "/usr/bin/google-chrome", ): - if os.path.isfile(cand) or os.path.islink(cand): + if not (os.path.isfile(cand) or os.path.islink(cand)): + continue + if os.path.basename(cand) in ("snap", "snap-confine", "snapd"): + continue + use = cand + if not cand.startswith("/snap/bin/"): try: - opts.set_browser_path(cand) - log(f"browser path={cand}") + real = os.path.realpath(cand) + if os.path.basename(real) not in ("snap", "snap-confine", "snapd"): + if "chromium" in real.lower() or "chrome" in real.lower(): + use = real except Exception: pass - break + try: + opts.set_browser_path(use) + log(f"browser path={use}") + except Exception: + pass + break from .proxyutil import proxy_for_chromium, proxy_log_label, resolve_proxy diff --git a/grok_register_ttk.py b/grok_register_ttk.py index a5cce3b..0dbe582 100644 --- a/grok_register_ttk.py +++ b/grok_register_ttk.py @@ -781,7 +781,9 @@ def apply_headless_to_options(options, headless: bool = True) -> None: CHROMIUM_CANDIDATES = ( "/usr/bin/chromium", "/usr/bin/chromium-browser", - "/snap/bin/chromium", + "/usr/lib/chromium/chromium", + "/usr/lib/chromium-browser/chromium-browser", + "/snap/bin/chromium", # keep as /snap/bin/chromium — do NOT resolve to /usr/bin/snap ) CHROME_CANDIDATES = ( "/usr/bin/google-chrome-stable", @@ -816,17 +818,94 @@ STEALTH_INIT_JS = r""" })(); """ +# Paths that look like snap host / shell wrappers, not a browser binary. +_INVALID_BROWSER_BASENAMES = frozenset( + { + "snap", + "snap-confine", + "snapd", + "env", + "bash", + "sh", + "python", + "python3", + "uv", + } +) + + def _is_google_chrome_path(path: str | None) -> bool: if not path: return False base = os.path.basename(path) - return "chrome" in base and "chromium" not in path + # /usr/bin/snap is NOT chrome + if base in _INVALID_BROWSER_BASENAMES: + return False + return "chrome" in base and "chromium" not in path.lower() + + +def _looks_like_browser_path(path: str | None) -> bool: + """Reject snap host binary etc. Accept chromium/chrome paths only.""" + if not path: + return False + if not (os.path.isfile(path) or os.path.islink(path)): + return False + base = os.path.basename(path) + if base in _INVALID_BROWSER_BASENAMES: + return False + low = path.replace("\\", "/").lower() + # Must look like a browser, not generic "snap" + if "chromium" in low or "chrome" in low: + return True + return False + + +def _normalize_browser_path(path: str | None) -> str | None: + """Return a usable browser executable path. + + Critical: /snap/bin/chromium often realpath()'s to /usr/bin/snap. + Using /usr/bin/snap as browser_path makes DrissionPage fail to connect. + Keep the original /snap/bin/chromium wrapper path instead. + """ + if not path: + return None + path = str(path).strip() + if not path: + return None + if not (os.path.isfile(path) or os.path.islink(path) or os.path.exists(path)): + return None + + # Prefer original path if already valid (esp. /snap/bin/chromium) + if _looks_like_browser_path(path): + # If under /snap/bin/, never realpath — that collapses to /usr/bin/snap + if path.startswith("/snap/bin/"): + return path + try: + real = os.path.realpath(path) + except Exception: + real = path + if _looks_like_browser_path(real): + return real + # realpath bad (e.g. snap) but original ok + return path + + # realpath might still help for normal symlinks + try: + real = os.path.realpath(path) + except Exception: + real = "" + if real and _looks_like_browser_path(real): + return real + return None def _first_existing_browser(candidates) -> str | None: for cand in candidates: - if cand and (os.path.isfile(cand) or os.path.islink(cand)): - return cand + if not cand: + continue + norm = _normalize_browser_path(cand) + if norm: + return norm return None @@ -835,19 +914,29 @@ def resolve_browser_path(): Priority: 1. config browser_path (absolute) - 2. env BROWSER_PATH / CHROME_PATH + 2. env BROWSER_PATH / CHROME_PATH / CHROMIUM_PATH 3. browser_prefer=chromium|chrome|auto auto → Chromium first when turnstilePatch exists (needed for CF) 4. any remaining candidate + + Never returns /usr/bin/snap (common snap symlink collapse bug). """ # Optional override from config.json "browser_path" override = str((config.get("browser_path") if isinstance(config, dict) else "") or "").strip() - if override and (os.path.isfile(override) or os.path.islink(override)): - return override - for env_key in ("BROWSER_PATH", "CHROME_PATH", "CHROMIUM_PATH"): + if override: + norm = _normalize_browser_path(override) + if norm: + return norm + print(f" [browser] ignore invalid browser_path={override!r}", flush=True) + + for env_key in ("BROWSER_PATH", "CHROMIUM_PATH", "CHROME_PATH"): env_path = (os.environ.get(env_key) or "").strip() - if env_path and (os.path.isfile(env_path) or os.path.islink(env_path)): - return env_path + if not env_path: + continue + norm = _normalize_browser_path(env_path) + if norm: + return norm + print(f" [browser] ignore invalid {env_key}={env_path!r}", flush=True) prefer = str((config.get("browser_prefer") if isinstance(config, dict) else "") or "auto").strip().lower() if prefer in ("", "default", "detect"): @@ -869,8 +958,7 @@ def resolve_browser_path(): else: order = list(CHROME_CANDIDATES) + list(CHROMIUM_CANDIDATES) - path = _first_existing_browser(order) - return path + return _first_existing_browser(order) def apply_stealth_patches(page=None, log_callback=None) -> bool: diff --git a/register_cli.py b/register_cli.py index 778f9e7..b06feb3 100644 --- a/register_cli.py +++ b/register_cli.py @@ -69,16 +69,36 @@ def _patched_create_browser_options(): except Exception: browser_path = None if not browser_path: + normalize = getattr(reg, "_normalize_browser_path", None) for cand in ( "/usr/bin/chromium", "/usr/bin/chromium-browser", - "/snap/bin/chromium", + "/usr/lib/chromium/chromium", + "/snap/bin/chromium", # keep wrapper path; never /usr/bin/snap "/usr/bin/google-chrome-stable", "/usr/bin/google-chrome", ): - if os.path.isfile(cand) or os.path.islink(cand): + if callable(normalize): + norm = normalize(cand) + if norm: + browser_path = norm + break + elif os.path.isfile(cand) or os.path.islink(cand): + # refuse snap host binary + if os.path.basename(cand) == "snap": + continue browser_path = cand break + # Final sanitize: never pass /usr/bin/snap to DrissionPage + if browser_path and os.path.basename(str(browser_path)) in ("snap", "snap-confine", "snapd"): + print(f"[browser] reject invalid path={browser_path!r}, re-resolve", flush=True) + browser_path = None + resolve = getattr(reg, "resolve_browser_path", None) + if callable(resolve): + try: + browser_path = resolve() + except Exception: + browser_path = None if browser_path: try: opts.set_browser_path(browser_path) diff --git a/start.sh b/start.sh index d041965..6a3d125 100755 --- a/start.sh +++ b/start.sh @@ -342,65 +342,108 @@ step3_sync_deps() { echo -e " ${GREEN}✓${NC} 依赖同步完成" } +# 是否是合法浏览器可执行路径(拒绝 /usr/bin/snap 这类 snap 软链目标) +_is_valid_browser_bin() { + local p="$1" + local base + [ -n "$p" ] || return 1 + [ -x "$p" ] 2>/dev/null || return 1 + base="$(basename "$p")" + case "$base" in + snap|snap-confine|snapd|env|bash|sh|python*|uv) + return 1 + ;; + esac + # 路径名应像浏览器 + case "$p" in + *chromium*|*chrome*|*Chrome*|*Chromium*) + return 0 + ;; + esac + return 1 +} + # 解析路径:command -v 或可执行绝对路径 +# 注意:/snap/bin/chromium 经 readlink -f 会变成 /usr/bin/snap —— 绝不能用那个结果启动浏览器 _resolve_bin() { local cand="$1" + local p="" real="" if [ -z "$cand" ]; then return 1 fi + if [ -x "$cand" ] 2>/dev/null; then - # resolve symlinks when possible - if have_cmd readlink; then - readlink -f "$cand" 2>/dev/null || echo "$cand" - else - echo "$cand" - fi - return 0 - fi - if have_cmd "$cand"; then - local p + p="$cand" + elif have_cmd "$cand"; then p="$(command -v "$cand" 2>/dev/null || true)" - if [ -n "$p" ]; then - if have_cmd readlink; then - readlink -f "$p" 2>/dev/null || echo "$p" - else + else + return 1 + fi + [ -n "$p" ] || return 1 + + # snap 包装器:保留 /snap/bin/chromium,不要 resolve 成 /usr/bin/snap + case "$p" in + /snap/bin/*) + if _is_valid_browser_bin "$p"; then echo "$p" + return 0 + fi + return 1 + ;; + esac + + if have_cmd readlink; then + real="$(readlink -f "$p" 2>/dev/null || true)" + # readlink 落到 snap 宿主时,回退用原路径(若原路径仍有效) + if [ -n "$real" ]; then + case "$(basename "$real")" in + snap|snap-confine|snapd) + if _is_valid_browser_bin "$p"; then + echo "$p" + return 0 + fi + return 1 + ;; + esac + if _is_valid_browser_bin "$real"; then + echo "$real" + return 0 fi - return 0 fi fi + + if _is_valid_browser_bin "$p"; then + echo "$p" + return 0 + fi return 1 } find_chromium_bin() { local candidate p + # 优先 deb/系统包路径,snap 放后面(且不 resolve 成 /usr/bin/snap) for candidate in \ - "${BROWSER_PATH:-}" \ - chromium \ - chromium-browser \ /usr/bin/chromium \ /usr/bin/chromium-browser \ - /snap/bin/chromium \ /usr/lib/chromium/chromium \ /usr/lib/chromium-browser/chromium-browser \ + chromium \ + chromium-browser \ + /snap/bin/chromium \ "/Applications/Chromium.app/Contents/MacOS/Chromium"; do [ -z "$candidate" ] && continue - # if BROWSER_PATH points to chrome, skip in chromium finder - case "$candidate" in - *google-chrome*|*chrome*) - # only accept if path name contains chromium - case "$candidate" in - *chromium*) ;; - *) continue ;; - esac - ;; - esac if p="$(_resolve_bin "$candidate")"; then case "$p" in *google-chrome*) continue ;; + */snap) continue ;; + esac + # 必须像 chromium + case "$p" in + *chromium*|*Chromium*) + echo "$p" + return 0 + ;; esac - echo "$p" - return 0 fi done return 1 @@ -409,12 +452,15 @@ find_chromium_bin() { find_chrome_bin() { local candidate p for candidate in \ - google-chrome-stable \ - google-chrome \ /usr/bin/google-chrome-stable \ /usr/bin/google-chrome \ + google-chrome-stable \ + google-chrome \ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"; do if p="$(_resolve_bin "$candidate")"; then + case "$(basename "$p")" in + snap) continue ;; + esac echo "$p" return 0 fi @@ -424,24 +470,46 @@ find_chrome_bin() { # 选中浏览器并 export,供 register_cli / create_browser_options 使用 select_and_export_browser() { - local chromium_bin="" chrome_bin="" + local chromium_bin="" chrome_bin="" user_path="" chromium_bin="$(find_chromium_bin 2>/dev/null || true)" chrome_bin="$(find_chrome_bin 2>/dev/null || true)" - # 用户显式 BROWSER_PATH 优先 - if [ -n "${BROWSER_PATH:-}" ] && [ -x "${BROWSER_PATH}" ]; then - SELECTED_BROWSER="$BROWSER_PATH" - case "$SELECTED_BROWSER" in - *chromium*) SELECTED_BROWSER_KIND="chromium" ;; - *) SELECTED_BROWSER_KIND="chrome" ;; - esac - elif [ -n "$chromium_bin" ]; then - SELECTED_BROWSER="$chromium_bin" - SELECTED_BROWSER_KIND="chromium" - elif [ -n "$chrome_bin" ]; then - SELECTED_BROWSER="$chrome_bin" - SELECTED_BROWSER_KIND="chrome" - else + # 用户显式 BROWSER_PATH 优先,但必须通过校验(拒绝 /usr/bin/snap) + if [ -n "${BROWSER_PATH:-}" ]; then + user_path="$(_resolve_bin "$BROWSER_PATH" 2>/dev/null || true)" + if [ -z "$user_path" ] && _is_valid_browser_bin "$BROWSER_PATH"; then + user_path="$BROWSER_PATH" + fi + if [ -n "$user_path" ]; then + SELECTED_BROWSER="$user_path" + case "$SELECTED_BROWSER" in + *chromium*|*Chromium*) SELECTED_BROWSER_KIND="chromium" ;; + *) SELECTED_BROWSER_KIND="chrome" ;; + esac + else + echo -e " ${YELLOW}⚠${NC} 忽略无效 BROWSER_PATH=${BROWSER_PATH}(常见于 snap 解析成 /usr/bin/snap)" + unset BROWSER_PATH + user_path="" + fi + fi + + if [ -z "${SELECTED_BROWSER:-}" ] || [ -z "${user_path:-}" ]; then + if [ -n "$chromium_bin" ]; then + SELECTED_BROWSER="$chromium_bin" + SELECTED_BROWSER_KIND="chromium" + elif [ -n "$chrome_bin" ]; then + SELECTED_BROWSER="$chrome_bin" + SELECTED_BROWSER_KIND="chrome" + else + SELECTED_BROWSER="" + SELECTED_BROWSER_KIND="none" + return 1 + fi + fi + + # 二次校验:绝不能导出 snap 宿主 + if ! _is_valid_browser_bin "$SELECTED_BROWSER"; then + echo -e " ${RED}✗${NC} 浏览器路径无效: $SELECTED_BROWSER" SELECTED_BROWSER="" SELECTED_BROWSER_KIND="none" return 1 @@ -453,7 +521,6 @@ select_and_export_browser() { export BROWSER_PATH="$SELECTED_BROWSER" export CHROMIUM_PATH="$SELECTED_BROWSER" else - # 仅 Chrome:不写 BROWSER_PATH 强制,但打印警告;Python 侧也会提示 export BROWSER_PREFER="${BROWSER_PREFER:-chrome}" export BROWSER_PATH="$SELECTED_BROWSER" fi