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.
This commit is contained in:
chaos committed 2026-07-12 00:49:45 +08:00
1 parent a790a371a8
commit 767516745e
5 files changed
+289 -70

No files matched your search

+100 -12
View File
@@ -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: