#!/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_chromium_bin() { local candidate p # 优先 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*) 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 if [ ${#RUN_PREFIX[@]} -gt 0 ]; then echo -e "${CYAN}[*] 启动命令: ${RUN_PREFIX[*]} uv run --python 3.13 python -u register_cli.py ${args[*]}${NC}" echo "" exec "${RUN_PREFIX[@]}" env \ BROWSER_PREFER="${BROWSER_PREFER:-}" \ BROWSER_PATH="${BROWSER_PATH:-}" \ CHROMIUM_PATH="${CHROMIUM_PATH:-}" \ DISPLAY="${DISPLAY:-}" \ uv run --python 3.13 python -u register_cli.py "${args[@]}" else exec env \ BROWSER_PREFER="${BROWSER_PREFER:-}" \ BROWSER_PATH="${BROWSER_PATH:-}" \ CHROMIUM_PATH="${CHROMIUM_PATH:-}" \ uv run --python 3.13 python -u register_cli.py "${args[@]}" fi } main "$@"