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

+183 -7
View File
@@ -15,6 +15,12 @@
# 3. 同步 Python 依赖
# 4. 尝试安装 Chrome/Chromium(如缺失且有权限)
# 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)
# ============================================================================
set -euo pipefail
@@ -32,6 +38,11 @@ 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
echo -e "${CYAN}${BOLD}============================================${NC}"
echo -e "${CYAN}${BOLD} Grok 注册机 - 一键启动 (Linux/macOS)${NC}"
echo -e "${CYAN}${BOLD}============================================${NC}"
@@ -41,9 +52,159 @@ 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/5] 检查基础工具...${NC}"
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"
@@ -58,7 +219,7 @@ step0_basics() {
# ── 1. 检测/安装 uv ──
step1_install_uv() {
echo -e "${CYAN}[1/5] 检测/安装 uv 包管理器...${NC}"
echo -e "${CYAN}[1/6] 检测/安装 uv 包管理器...${NC}"
if have_cmd uv; then
echo -e " ${GREEN}✓${NC} uv: $(uv --version 2>&1)"
@@ -92,7 +253,7 @@ step1_install_uv() {
# ── 2. 检测/安装 Python 3.13 ──
# 策略: 优先系统/mise 已有 3.13;否则用 uv 下载管理 Python
step2_install_python() {
echo -e "${CYAN}[2/5] 检测/安装 Python 3.13...${NC}"
echo -e "${CYAN}[2/6] 检测/安装 Python 3.13...${NC}"
PYTHON_BIN=""
# mise 托管的 python
@@ -160,7 +321,7 @@ step2_install_python() {
# ── 3. 同步依赖 ──
step3_sync_deps() {
echo -e "${CYAN}[3/5] 同步 Python 依赖...${NC}"
echo -e "${CYAN}[3/6] 同步 Python 依赖...${NC}"
# 固定使用 3.13,避免系统默认 python 过旧
if ! uv sync --python 3.13; then
@@ -176,7 +337,7 @@ step3_sync_deps() {
# ── 4. 检测/尝试安装浏览器 ──
step4_check_browser() {
echo -e "${CYAN}[4/5] 检测/安装浏览器...${NC}"
echo -e "${CYAN}[4/6] 检测/安装浏览器...${NC}"
local candidate
for candidate in \
@@ -262,7 +423,7 @@ step4_check_browser() {
# ── 5. 配置文件模板 ──
step5_config_templates() {
echo -e "${CYAN}[5/5] 检查配置文件...${NC}"
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"
@@ -298,10 +459,19 @@ main() {
echo ""
step5_config_templates || exit 1
echo ""
step6_ensure_display
echo ""
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
echo ""
# 默认不限数量(--count 0),除非用户显式传参
@@ -320,7 +490,13 @@ main() {
mise install >/dev/null 2>&1 || true
fi
exec uv run --python 3.13 python -u register_cli.py "${args[@]}"
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[@]}" uv run --python 3.13 python -u register_cli.py "${args[@]}"
else
exec uv run --python 3.13 python -u register_cli.py "${args[@]}"
fi
}
main "$@"