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.
755 lines
26 KiB
Bash
Executable File
755 lines
26 KiB
Bash
Executable File
#!/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 "$@"
|