Files
chaos c87b01967f Refactor registration flow, update config, replace Turnstile patch
- Major refactor of grok_register_ttk.py: improved flow, error handling, logging
- Update config.example.json and config.json with new fields
- Replace turnstilePatch/content.js with new script.js approach
- Update start.sh for better Chromium/Turnstile handling
- Remove deprecated turnstilePatch/content.js
2026-07-12 14:44:02 +08:00

791 lines
28 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# ============================================================================
# Grok 注册机 - 一键启动脚本 (Linux / macOS)
# 自动检测并安装缺失环境,然后启动 register_cli.py
#
# 用法:
# ./start.sh # 默认不限数量(持续注册)
# ./start.sh --count 10 # 跑 10 个
# ./start.sh --count 10 --threads 3 # 3 线程跑 10 个
# ./start.sh --extra 5 # 在已有基础上再注册 5 个
#
# 首次运行会自动:
# 1. 安装 uv(如缺失)
# 2. 安装 Python 3.13(如缺失,经 uv 管理)
# 3. 同步 Python 依赖
# 4. 优先安装/选用 Chromium(可加载 turnstilePatch;仅 Chrome 过 CF 易失败)
# 5. 从模板复制 .env / config.json(如缺失)
# 6. 无 DISPLAY 时自动装/启 Xvfb(虚拟显示,过 CF 比纯 headless 稳)
#
# 环境变量:
# SKIP_XVFB=1 # 强制不用 Xvfb(走 Python 侧自动 headless)
# FORCE_XVFB=1 # 即使已有 DISPLAY 也用 xvfb-run 包一层
# XVFB_DISPLAY=:99 # 常驻 Xvfb 使用的 display(默认 :99)
# BROWSER_PREFER=chromium|chrome|auto # 覆盖浏览器偏好(默认有 Chromium 时强制 chromium)
# BROWSER_PATH=/path/to/browser # 强制浏览器可执行文件
# SKIP_BROWSER_INSTALL=1 # 跳过自动安装 Chromium
# ============================================================================
set -euo pipefail
# ── 颜色 ──
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
BOLD='\033[1m'
NC='\033[0m'
PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)"
cd "$PROJECT_DIR"
# uv 常见安装路径,确保后续命令能找到
export PATH="$HOME/.local/bin:$HOME/.cargo/bin:$PATH"
# 启动包装:默认直接 exec;无桌面时改为 xvfb-run / 自启 Xvfb
RUN_PREFIX=()
USE_XVFB=0
XVFB_MODE="" # xvfb-run | persistent | none
# 浏览器探测结果(step4 写入;main 启动前 export 给 Python)
SELECTED_BROWSER=""
SELECTED_BROWSER_KIND="" # chromium | chrome | none
echo -e "${CYAN}${BOLD}============================================${NC}"
echo -e "${CYAN}${BOLD} Grok 注册机 - 一键启动 (Linux/macOS)${NC}"
echo -e "${CYAN}${BOLD}============================================${NC}"
echo ""
have_cmd() {
command -v "$1" &>/dev/null
}
# 是否已有可用图形显示(含用户已 export 的 DISPLAY / Wayland)
has_display() {
[ -n "${DISPLAY:-}" ] || [ -n "${WAYLAND_DISPLAY:-}" ]
}
# 尝试用包管理器安装 xvfb(仅免密 sudo 时自动装)
try_install_xvfb() {
if have_cmd xvfb-run || have_cmd Xvfb; then
return 0
fi
echo -e " ${YELLOW}⚠${NC} 未检测到 Xvfb,尝试自动安装..."
if have_cmd apt-get && have_cmd sudo && sudo -n true 2>/dev/null; then
echo " → apt 安装 xvfb..."
if sudo DEBIAN_FRONTEND=noninteractive apt-get update -qq \
&& sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq xvfb 2>/dev/null; then
echo -e " ${GREEN}✓${NC} xvfb 安装成功"
return 0
fi
elif have_cmd dnf && have_cmd sudo && sudo -n true 2>/dev/null; then
echo " → dnf 安装 xorg-x11-server-Xvfb..."
if sudo dnf install -y xorg-x11-server-Xvfb 2>/dev/null; then
echo -e " ${GREEN}✓${NC} Xvfb 安装成功"
return 0
fi
elif have_cmd pacman && have_cmd sudo && sudo -n true 2>/dev/null; then
echo " → pacman 安装 xorg-server-xvfb..."
if sudo pacman -Sy --noconfirm xorg-server-xvfb 2>/dev/null; then
echo -e " ${GREEN}✓${NC} Xvfb 安装成功"
return 0
fi
elif have_cmd yum && have_cmd sudo && sudo -n true 2>/dev/null; then
echo " → yum 安装 xorg-x11-server-Xvfb..."
if sudo yum install -y xorg-x11-server-Xvfb 2>/dev/null; then
echo -e " ${GREEN}✓${NC} Xvfb 安装成功"
return 0
fi
else
echo -e " ${YELLOW}⚠${NC} 无法自动安装(需要免密 sudo 或手动装)"
echo " Ubuntu/Debian: sudo apt install -y xvfb"
echo " Fedora: sudo dnf install -y xorg-x11-server-Xvfb"
echo " Arch: sudo pacman -S xorg-server-xvfb"
fi
if have_cmd xvfb-run || have_cmd Xvfb; then
return 0
fi
return 1
}
# 启动常驻 Xvfb 并 export DISPLAY
start_persistent_xvfb() {
local disp="${XVFB_DISPLAY:-:99}"
local sock="/tmp/.X11-unix/X${disp#:}"
# 已有该 display 的 X socket 则直接复用
if [ -S "$sock" ] 2>/dev/null; then
export DISPLAY="$disp"
echo -e " ${GREEN}✓${NC} 复用已有 Xvfb DISPLAY=$DISPLAY"
return 0
fi
if ! have_cmd Xvfb; then
return 1
fi
echo " → 启动常驻 Xvfb $disp ..."
# 后台起;失败不阻断(上层会回退 headless)
Xvfb "$disp" -screen 0 1920x1080x24 -ac +extension GLX +render -noreset \
>/tmp/grok-xvfb.log 2>&1 &
local xvfb_pid=$!
sleep 0.5
if [ -S "$sock" ] 2>/dev/null || kill -0 "$xvfb_pid" 2>/dev/null; then
export DISPLAY="$disp"
echo -e " ${GREEN}✓${NC} Xvfb 已启动 DISPLAY=$DISPLAY pid=$xvfb_pid (log: /tmp/grok-xvfb.log)"
return 0
fi
echo -e " ${YELLOW}⚠${NC} 常驻 Xvfb 启动失败,见 /tmp/grok-xvfb.log"
return 1
}
# 无桌面时准备虚拟显示;成功则 RUN_PREFIX / DISPLAY 已设好
step6_ensure_display() {
echo -e "${CYAN}[6/6] 检测图形显示 / 自动 Xvfb...${NC}"
# macOS 一般有 Aqua,不强制 Xvfb
if [ "$(uname -s 2>/dev/null || echo unknown)" = "Darwin" ]; then
if has_display; then
echo -e " ${GREEN}✓${NC} macOS 已有显示 DISPLAY=${DISPLAY:-} WAYLAND=${WAYLAND_DISPLAY:-}"
else
echo -e " ${GREEN}✓${NC} macOS:不强制 Xvfb(Python 侧可 headless)"
fi
USE_XVFB=0
XVFB_MODE="none"
return 0
fi
if [ "${SKIP_XVFB:-0}" = "1" ]; then
echo -e " ${YELLOW}⚠${NC} SKIP_XVFB=1:跳过虚拟显示,走 Python 自动 headless"
USE_XVFB=0
XVFB_MODE="none"
return 0
fi
local need_xvfb=0
if [ "${FORCE_XVFB:-0}" = "1" ]; then
need_xvfb=1
echo -e " ${CYAN}→${NC} FORCE_XVFB=1,将使用虚拟显示"
elif ! has_display; then
need_xvfb=1
echo -e " ${YELLOW}⚠${NC} 无 DISPLAY/WAYLAND(无头服务器),准备自动启用 Xvfb"
else
echo -e " ${GREEN}✓${NC} 已有显示 DISPLAY=${DISPLAY:-} WAYLAND=${WAYLAND_DISPLAY:-}"
USE_XVFB=0
XVFB_MODE="none"
return 0
fi
if [ "$need_xvfb" -ne 1 ]; then
return 0
fi
try_install_xvfb || true
# 优先 xvfb-run(自动分配 display,最省心)
if have_cmd xvfb-run; then
RUN_PREFIX=(xvfb-run -a -s "-screen 0 1920x1080x24")
USE_XVFB=1
XVFB_MODE="xvfb-run"
echo -e " ${GREEN}✓${NC} 将用 xvfb-run 启动(虚拟有头,利于过 CF/Turnstile)"
echo " 若需纯 headless: SKIP_XVFB=1 ./start.sh ..."
return 0
fi
# 其次常驻 Xvfb + export DISPLAY
if start_persistent_xvfb; then
RUN_PREFIX=()
USE_XVFB=1
XVFB_MODE="persistent"
echo -e " ${GREEN}✓${NC} 使用常驻 Xvfb DISPLAY=$DISPLAY"
return 0
fi
# 都失败 → Python 侧 create_browser_options 会自动 headless
echo -e " ${YELLOW}⚠${NC} 未能启用 Xvfb,将依赖 Python 自动 headless(可能更易被 CF 拦)"
echo " 手动: sudo apt install -y xvfb && xvfb-run -a ./start.sh"
USE_XVFB=0
XVFB_MODE="none"
return 0
}
# ── 0. 基础工具 ──
step0_basics() {
echo -e "${CYAN}[0/6] 检查基础工具...${NC}"
if ! have_cmd curl && ! have_cmd wget; then
echo -e " ${RED}✗${NC} 需要 curl 或 wget 才能自动安装环境"
echo " 请先安装: sudo apt install -y curl 或 sudo yum install -y curl"
return 1
fi
if have_cmd curl; then
echo -e " ${GREEN}✓${NC} curl 可用"
else
echo -e " ${GREEN}✓${NC} wget 可用"
fi
}
# ── 1. 检测/安装 uv ──
step1_install_uv() {
echo -e "${CYAN}[1/6] 检测/安装 uv 包管理器...${NC}"
if have_cmd uv; then
echo -e " ${GREEN}✓${NC} uv: $(uv --version 2>&1)"
return 0
fi
echo -e " ${YELLOW}⚠${NC} 未检测到 uv,正在自动安装..."
if have_cmd curl; then
curl -LsSf https://astral.sh/uv/install.sh | sh
else
wget -qO- https://astral.sh/uv/install.sh | sh
fi
export PATH="$HOME/.local/bin:$HOME/.cargo/bin:$PATH"
# 部分环境写到 ~/.profile,再补一次常见路径
if [ -f "$HOME/.local/bin/env" ]; then
# shellcheck disable=SC1091
source "$HOME/.local/bin/env" 2>/dev/null || true
fi
if have_cmd uv; then
echo -e " ${GREEN}✓${NC} uv 安装成功: $(uv --version 2>&1)"
return 0
fi
echo -e " ${RED}✗${NC} uv 安装失败,请手动: curl -LsSf https://astral.sh/uv/install.sh | sh"
return 1
}
# ── 2. 检测/安装 Python 3.13 ──
# 策略: 优先系统/mise 已有 3.13;否则用 uv 下载管理 Python
step2_install_python() {
echo -e "${CYAN}[2/6] 检测/安装 Python 3.13...${NC}"
PYTHON_BIN=""
# mise 托管的 python
if have_cmd mise && [ -f "$PROJECT_DIR/mise.toml" ]; then
# 尝试按项目配置安装工具链
if mise install 2>/dev/null; then
local py
py=$(mise which python 2>/dev/null || true)
if [ -n "$py" ] && "$py" -c "import sys; sys.exit(0 if sys.version_info >= (3,13) else 1)" 2>/dev/null; then
PYTHON_BIN="$py"
echo -e " ${GREEN}✓${NC} Python (mise): $($py --version 2>&1) at $py"
return 0
fi
fi
fi
# 系统 / PATH 上的 3.13+
local candidate
for candidate in python3.13 python3.14 python3 python; do
if have_cmd "$candidate"; then
if "$candidate" -c "import sys; sys.exit(0 if sys.version_info >= (3,13) else 1)" 2>/dev/null; then
PYTHON_BIN="$candidate"
echo -e " ${GREEN}✓${NC} Python: $($candidate --version 2>&1)"
return 0
fi
fi
done
# 项目 .venv
if [ -x "$PROJECT_DIR/.venv/bin/python" ]; then
if "$PROJECT_DIR/.venv/bin/python" -c "import sys; sys.exit(0 if sys.version_info >= (3,13) else 1)" 2>/dev/null; then
PYTHON_BIN="$PROJECT_DIR/.venv/bin/python"
echo -e " ${GREEN}✓${NC} Python (.venv): $($PYTHON_BIN --version 2>&1)"
return 0
fi
fi
# 用 uv 安装托管的 Python 3.13
echo -e " ${YELLOW}⚠${NC} 未找到 Python 3.13+,正在通过 uv 安装..."
if ! uv python install 3.13; then
echo -e " ${RED}✗${NC} uv python install 3.13 失败"
echo " 也可手动: uv python install 3.13"
return 1
fi
# 解析 uv 管理的 python 路径
local uv_py
uv_py=$(uv python find 3.13 2>/dev/null || true)
if [ -n "$uv_py" ] && [ -x "$uv_py" ]; then
PYTHON_BIN="$uv_py"
echo -e " ${GREEN}✓${NC} Python (uv): $($uv_py --version 2>&1) at $uv_py"
return 0
fi
# uv python find 失败时,后续 uv run 仍可自动解析
if uv run --python 3.13 python -c "import sys; sys.exit(0 if sys.version_info >= (3,13) else 1)" 2>/dev/null; then
PYTHON_BIN="uv-managed"
echo -e " ${GREEN}✓${NC} Python 3.13 已由 uv 管理(将通过 uv run 调用)"
return 0
fi
echo -e " ${RED}✗${NC} 无法安装或定位 Python 3.13"
return 1
}
# ── 3. 同步依赖 ──
step3_sync_deps() {
echo -e "${CYAN}[3/6] 同步 Python 依赖...${NC}"
# 固定使用 3.13,避免系统默认 python 过旧
if ! uv sync --python 3.13; then
echo -e " ${YELLOW}⚠${NC} uv sync --python 3.13 失败,尝试默认 uv sync..."
if ! uv sync; then
echo -e " ${RED}✗${NC} 依赖同步失败"
return 1
fi
fi
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
p="$cand"
elif have_cmd "$cand"; then
p="$(command -v "$cand" 2>/dev/null || true)"
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
fi
fi
if _is_valid_browser_bin "$p"; then
echo "$p"
return 0
fi
return 1
}
find_playwright_chromium() {
# Playwright Chromium 无 snap AppArmor 限制,ReinerBRO/grok-register 同款优先路径
local root cand
for root in \
"${HOME}/.cache/ms-playwright" \
"${LOCALAPPDATA:-}/ms-playwright"; do
[ -d "$root" ] || continue
# 取最新 chromium-* 目录
cand=$(find "$root" -maxdepth 3 -type f \( -path '*/chrome-linux64/chrome' -o -path '*/chrome-linux/chrome' -o -path '*/chrome-win64/chrome.exe' \) 2>/dev/null | sort -r | head -1 || true)
if [ -n "$cand" ] && [ -x "$cand" ] || [ -f "$cand" ]; then
echo "$cand"
return 0
fi
done
return 1
}
find_chromium_bin() {
local candidate p
# 1) Playwright Chromium(推荐,服务器无 AppArmor/snap 坑)
if p="$(find_playwright_chromium 2>/dev/null)"; then
echo "$p"
return 0
fi
# 2) deb/系统包路径,snap 放后面(且不 resolve 成 /usr/bin/snap)
for candidate in \
/usr/bin/chromium \
/usr/bin/chromium-browser \
/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 p="$(_resolve_bin "$candidate")"; then
case "$p" in
*google-chrome*) continue ;;
*/snap) continue ;;
esac
# 必须像 chromium
case "$p" in
*chromium*|*Chromium*|*chrome-linux*)
echo "$p"
return 0
;;
esac
fi
done
return 1
}
find_chrome_bin() {
local candidate p
for candidate in \
/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
done
return 1
}
# 选中浏览器并 export,供 register_cli / create_browser_options 使用
select_and_export_browser() {
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 优先,但必须通过校验(拒绝 /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
fi
# 有 Chromium 时强制偏好 chromium,避免 Python 仍选到 Chrome
if [ "$SELECTED_BROWSER_KIND" = "chromium" ]; then
export BROWSER_PREFER="${BROWSER_PREFER:-chromium}"
export BROWSER_PATH="$SELECTED_BROWSER"
export CHROMIUM_PATH="$SELECTED_BROWSER"
else
export BROWSER_PREFER="${BROWSER_PREFER:-chrome}"
export BROWSER_PATH="$SELECTED_BROWSER"
fi
return 0
}
try_install_chromium() {
if [ "${SKIP_BROWSER_INSTALL:-0}" = "1" ]; then
echo -e " ${YELLOW}⚠${NC} SKIP_BROWSER_INSTALL=1,跳过自动安装 Chromium"
return 1
fi
local ok=false
# Debian/Ubuntu
if have_cmd apt-get && have_cmd sudo; then
if sudo -n true 2>/dev/null; then
echo " → apt 安装 chromium(免密 sudo)..."
sudo apt-get update -qq 2>/dev/null || true
# 不同发行版包名不一,逐个试
if sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq chromium 2>/dev/null \
|| sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq chromium-browser 2>/dev/null; then
ok=true
fi
# Ubuntu 常见 snap 包
if ! $ok && have_cmd snap && sudo -n true 2>/dev/null; then
echo " → snap 安装 chromium..."
if sudo snap install chromium 2>/dev/null; then
ok=true
fi
fi
else
echo -e " ${YELLOW}⚠${NC} 需要交互 sudo,无法自动装 Chromium"
echo " 请手动: sudo apt install -y chromium 或 sudo snap install chromium"
fi
# Arch
elif have_cmd pacman && have_cmd sudo && sudo -n true 2>/dev/null; then
echo " → pacman 安装 chromium..."
if sudo pacman -Sy --noconfirm chromium 2>/dev/null; then
ok=true
fi
# Fedora/RHEL
elif have_cmd dnf && have_cmd sudo && sudo -n true 2>/dev/null; then
echo " → dnf 安装 chromium..."
if sudo dnf install -y chromium 2>/dev/null; then
ok=true
fi
elif have_cmd yum && have_cmd sudo && sudo -n true 2>/dev/null; then
echo " → yum 安装 chromium..."
if sudo yum install -y chromium 2>/dev/null; then
ok=true
fi
# macOS
elif have_cmd brew; then
echo " → brew 安装 chromium..."
if brew install --cask chromium 2>/dev/null; then
ok=true
fi
fi
if $ok; then
echo -e " ${GREEN}✓${NC} Chromium 安装命令已成功"
return 0
fi
return 1
}
# ── 4. 检测/尝试安装浏览器(优先 Chromium) ──
step4_check_browser() {
echo -e "${CYAN}[4/6] 检测/安装浏览器(优先 Chromium + turnstilePatch)...${NC}"
# 已有 Chromium → 直接选用
if select_and_export_browser && [ "$SELECTED_BROWSER_KIND" = "chromium" ]; then
echo -e " ${GREEN}✓${NC} Chromium: $SELECTED_BROWSER"
echo -e " ${CYAN}·${NC} export BROWSER_PREFER=$BROWSER_PREFER BROWSER_PATH=$BROWSER_PATH"
local chrome_extra
chrome_extra="$(find_chrome_bin 2>/dev/null || true)"
if [ -n "$chrome_extra" ]; then
echo -e " ${CYAN}·${NC} 另检测到 Chrome: $chrome_extra(已忽略,注册强制 Chromium)"
fi
return 0
fi
if [ "$SELECTED_BROWSER_KIND" = "chrome" ]; then
echo -e " ${YELLOW}⚠${NC} 仅有 Google Chrome: $SELECTED_BROWSER"
echo " Chrome 禁止 --load-extension,无法加载 turnstilePatch,Turnstile 常 token=0。"
echo " 将尝试安装 Chromium..."
else
echo -e " ${YELLOW}⚠${NC} 未检测到浏览器,尝试自动安装 Chromium..."
fi
try_install_chromium || true
# 刷新 PATH(snap 等)
export PATH="/snap/bin:$PATH"
if select_and_export_browser && [ "$SELECTED_BROWSER_KIND" = "chromium" ]; then
echo -e " ${GREEN}✓${NC} Chromium 已就绪: $SELECTED_BROWSER"
echo -e " ${CYAN}·${NC} export BROWSER_PREFER=$BROWSER_PREFER BROWSER_PATH=$BROWSER_PATH"
return 0
fi
if [ "$SELECTED_BROWSER_KIND" = "chrome" ]; then
echo -e " ${YELLOW}⚠${NC} 仍只有 Google Chrome: $SELECTED_BROWSER"
echo " 注册将继续,但 CF/Turnstile 成功率低。"
echo " 请尽快: sudo apt install -y chromium 或 sudo snap install chromium"
echo -e " ${CYAN}·${NC} 当前 BROWSER_PATH=$BROWSER_PATH"
return 0
fi
echo -e " ${YELLOW}⚠${NC} 未能找到任何浏览器"
echo " 注册需要 Chromium(推荐)才能稳定过 Turnstile。"
echo " 手动安装:"
echo " Ubuntu/Debian: sudo apt install -y chromium"
echo " 或 sudo snap install chromium"
echo " Arch: sudo pacman -S chromium"
echo " Fedora: sudo dnf install -y chromium"
echo " macOS: brew install --cask chromium"
echo " ${YELLOW}脚本将继续,注册很可能失败。${NC}"
}
# ── 5. 配置文件模板 ──
step5_config_templates() {
echo -e "${CYAN}[5/6] 检查配置文件...${NC}"
if [ ! -f "$PROJECT_DIR/.env" ] && [ -f "$PROJECT_DIR/.env.example" ]; then
cp "$PROJECT_DIR/.env.example" "$PROJECT_DIR/.env"
echo -e " ${YELLOW}⚠${NC} 已从 .env.example 创建 .env,请编辑填入真实配置"
elif [ -f "$PROJECT_DIR/.env" ]; then
echo -e " ${GREEN}✓${NC} .env 已存在"
else
echo -e " ${YELLOW}⚠${NC} 无 .env / .env.example(可直接写 config.json)"
fi
if [ ! -f "$PROJECT_DIR/config.json" ] && [ -f "$PROJECT_DIR/config.example.json" ]; then
cp "$PROJECT_DIR/config.example.json" "$PROJECT_DIR/config.json"
echo -e " ${YELLOW}⚠${NC} 已从 config.example.json 创建 config.json,请编辑填入真实配置"
elif [ -f "$PROJECT_DIR/config.json" ]; then
echo -e " ${GREEN}✓${NC} config.json 已存在"
else
echo -e " ${RED}✗${NC} 缺少 config.json 与 config.example.json"
return 1
fi
}
# ── 主流程 ──
main() {
step0_basics || exit 1
echo ""
step1_install_uv || exit 1
echo ""
step2_install_python || exit 1
echo ""
step3_sync_deps || exit 1
echo ""
step4_check_browser
echo ""
step5_config_templates || exit 1
echo ""
step6_ensure_display
echo ""
# 启动前再确认一次浏览器 env(step4 可能装完后 PATH 变化)
select_and_export_browser || true
echo -e "${CYAN}${BOLD}============================================${NC}"
echo -e "${GREEN}${BOLD} 环境就绪,启动 register_cli.py${NC}"
echo -e "${CYAN}${BOLD}============================================${NC}"
if [ "$USE_XVFB" = "1" ]; then
echo -e " 显示模式: ${GREEN}Xvfb (${XVFB_MODE})${NC} DISPLAY=${DISPLAY:-(xvfb-run 自动分配)}"
elif has_display; then
echo -e " 显示模式: ${GREEN}headed${NC} DISPLAY=${DISPLAY:-} WAYLAND=${WAYLAND_DISPLAY:-}"
else
echo -e " 显示模式: ${YELLOW}headless (Python 自动)${NC}"
fi
if [ -n "${SELECTED_BROWSER:-}" ]; then
if [ "${SELECTED_BROWSER_KIND:-}" = "chromium" ]; then
echo -e " 浏览器: ${GREEN}Chromium${NC} $SELECTED_BROWSER"
echo -e " 扩展: ${GREEN}turnstilePatch 可加载${NC}"
else
echo -e " 浏览器: ${YELLOW}Chrome(无扩展,Turnstile 易失败)${NC} $SELECTED_BROWSER"
echo -e " 建议: ${YELLOW}sudo apt install -y chromium${NC}"
fi
echo -e " env: BROWSER_PREFER=${BROWSER_PREFER:-} BROWSER_PATH=${BROWSER_PATH:-}"
else
echo -e " 浏览器: ${RED}未检测到${NC}"
fi
echo ""
# 默认不限数量(--count 0),除非用户显式传参
local -a args
if [ $# -eq 0 ]; then
echo -e "${YELLOW}[*] 默认模式:不限数量,持续注册(Ctrl+C 停止)${NC}"
echo ""
args=(--count 0)
else
args=("$@")
fi
# 统一走 uv run,确保用项目 venv + 正确 Python
# mise 仅作可选加速;有 mise.toml 时先确保工具已装,仍用 uv 启动更稳
if have_cmd mise && [ -f "$PROJECT_DIR/mise.toml" ]; then
mise install >/dev/null 2>&1 || true
fi
# 显式把浏览器相关 env 传给子进程,避免 Python 仍选到 Chrome
export BROWSER_PREFER="${BROWSER_PREFER:-}"
export BROWSER_PATH="${BROWSER_PATH:-}"
if [ -n "${CHROMIUM_PATH:-}" ]; then
export CHROMIUM_PATH
fi
# 组装 env:绝不把空 DISPLAY= 传给子进程。
# 否则会覆盖 xvfb-run 自动分配的 DISPLAY,Python 侧 has_display=False → 强制 headless,
# Turnstile 会卡在 token长度=0(参考 grokRegister-cpa 的成功路径是纯 headed + extension)。
local -a env_args=(
"BROWSER_PREFER=${BROWSER_PREFER:-}"
"BROWSER_PATH=${BROWSER_PATH:-}"
"CHROMIUM_PATH=${CHROMIUM_PATH:-}"
)
if [ -n "${DISPLAY:-}" ]; then
env_args+=("DISPLAY=${DISPLAY}")
fi
if [ -n "${XAUTHORITY:-}" ]; then
env_args+=("XAUTHORITY=${XAUTHORITY}")
fi
if [ -n "${WAYLAND_DISPLAY:-}" ]; then
env_args+=("WAYLAND_DISPLAY=${WAYLAND_DISPLAY}")
fi
if [ ${#RUN_PREFIX[@]} -gt 0 ]; then
echo -e "${CYAN}[*] 启动命令: ${RUN_PREFIX[*]} uv run --python 3.13 python -u register_cli.py ${args[*]}${NC}"
echo -e "${CYAN}[*] 注意: 不向 xvfb-run 子进程注入空 DISPLAY(保留虚拟显示)${NC}"
echo ""
exec "${RUN_PREFIX[@]}" env \
"${env_args[@]}" \
uv run --python 3.13 python -u register_cli.py "${args[@]}"
else
exec env \
"${env_args[@]}" \
uv run --python 3.13 python -u register_cli.py "${args[@]}"
fi
}
main "$@"