Auto headless + Xvfb for headless servers

Enable Chromium on machines without DISPLAY: Python auto headless
with Xvfb hints, and start.sh installs/starts Xvfb (xvfb-run or
persistent :99) so registration can run headed under a virtual display.
This commit is contained in:
chaos committed 2026-07-12 00:34:15 +08:00
1 parent 4feac70c91
commit cd4a2351ae
8 files changed
+536 -21

No files matched your search

+78 -1
View File
@@ -33,9 +33,11 @@ _orig_create_browser_options = reg.create_browser_options
def _patched_create_browser_options():
# Prefer original factory (proxy + CHROMIUM_SLIM_FLAGS + extension)
# Prefer original factory (proxy + CHROMIUM_SLIM_FLAGS + extension + auto-headless)
used_orig = False
try:
opts = _orig_create_browser_options()
used_orig = True
except Exception:
from DrissionPage import ChromiumOptions
@@ -107,6 +109,30 @@ def _patched_create_browser_options():
opts.add_extension(ext_path)
except Exception:
pass
# Fallback path only: original factory already applied headless when used_orig.
if not used_orig:
try:
apply = getattr(reg, "apply_headless_to_options", None)
should = getattr(reg, "should_use_headless", None)
if callable(apply) and callable(should):
use_hl = should(None, log_hint=True)
apply(opts, use_hl)
print(f"[browser] headless={use_hl} (fallback options)", flush=True)
else:
# Minimal fallback without helpers
has_disp = sys.platform == "win32" or bool(
(os.environ.get("DISPLAY") or "").strip()
or (os.environ.get("WAYLAND_DISPLAY") or "").strip()
)
if not has_disp:
try:
opts.headless(True)
except Exception:
opts.set_argument("--headless=new")
print("[browser] headless=True (no DISPLAY, minimal fallback)", flush=True)
except Exception as exc:
print(f"[browser] headless apply failed: {exc}", flush=True)
return opts
@@ -573,6 +599,16 @@ def main() -> int:
parser.add_argument("--browser-recycle-every", type=int, default=25, help="复用 N 次后完整回收")
parser.add_argument("--cookie-snapshot", action="store_true", help="注册成功写 cookie 快照(默认关,fast)")
parser.add_argument("--inline-mint", action="store_true", help="强制注册线程内联 mint(调试用)")
parser.add_argument(
"--headless",
action="store_true",
help="强制无头浏览器(无 DISPLAY 时默认也会自动 headless)",
)
parser.add_argument(
"--headed",
action="store_true",
help="强制有头浏览器(需 DISPLAY;无显示时仍会回退 headless 并提示 Xvfb)",
)
args = parser.parse_args()
reg.load_config()
@@ -580,6 +616,19 @@ def main() -> int:
threads = max(1, min(args.threads, 10))
fast = bool(args.fast) and not bool(args.no_fast)
# Headless preference: CLI > config/env/auto (no DISPLAY → headless)
if args.headless and args.headed:
print("[!] --headless 与 --headed 互斥,忽略 --headed", flush=True)
args.headed = False
if args.headless:
cfg0["browser_headless"] = True
if isinstance(getattr(reg, "config", None), dict):
reg.config["browser_headless"] = True
elif args.headed:
cfg0["browser_headless"] = False
if isinstance(getattr(reg, "config", None), dict):
reg.config["browser_headless"] = False
mint_workers = resolve_mint_workers(
cli_value=args.mint_workers,
threads=threads,
@@ -639,6 +688,28 @@ def main() -> int:
print("[*] 所有账号已完成,无需继续(可用 --extra N 再注册)", flush=True)
return 0
# Surface display/headless decision early (before TabPool cold-start)
try:
will_hl = reg.should_use_headless(None, log_hint=True)
has_disp = reg.has_display()
print(
f"[*] browser mode: {'headless' if will_hl else 'headed'} "
f"(DISPLAY={os.environ.get('DISPLAY', '')!r} has_display={has_disp})",
flush=True,
)
# Keep cpa browser fallback aligned when config left cpa_headless unset/false
# and we are on a headless host — only auto-enable if user did not force headed.
if will_hl and not args.headed:
if "cpa_headless" not in cfg0 or cfg0.get("cpa_headless") is False:
# Auto-headless host: enable cpa headless unless explicitly headed CLI
if not has_disp:
cfg0["cpa_headless"] = True
if isinstance(getattr(reg, "config", None), dict):
reg.config["cpa_headless"] = True
print("[*] cpa_headless 自动=true(无 DISPLAY;协议 mint 仍优先纯 HTTP)", flush=True)
except Exception as exc:
print(f"[!] headless probe failed: {exc}", flush=True)
log_thread = threading.Thread(target=_log_writer, daemon=True)
log_thread.start()
@@ -646,6 +717,12 @@ def main() -> int:
reg.TabPool.init(reg.create_browser_options, log_callback=lambda m: log(0, m))
except Exception as exc:
print(f"[!] 浏览器初始化失败: {exc}", flush=True)
if not reg.has_display():
print(
"[!] 无 DISPLAY 时若仍失败,请检查 Chrome/Chromium 是否安装,并尝试:\n"
" xvfb-run -a uv run python -u register_cli.py --extra 1 --threads 1",
flush=True,
)
return 1
task_queue: queue.Queue = queue.Queue()