diff --git a/script/start-linux.sh b/script/start-linux.sh index 09418125..bc27da0f 100644 --- a/script/start-linux.sh +++ b/script/start-linux.sh @@ -2,12 +2,100 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" export PATH=$PATH:/usr/bin:/usr/local/bin + RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m' +CLEANING=0 +PMHQ_PID="" +USE_XVFB=0 +DISTRO="" + log() { echo -e "${GREEN}>>> $1${NC}"; } warn() { echo -e "${YELLOW}>>> $1${NC}"; } error() { echo -e "${RED}错误: $1${NC}"; exit 1; } +check_sudo() { + log "验证 Sudo 权限..." + if ! sudo -v; then + error "Sudo 验证失败或被取消,脚本终止。" + fi +} + +cleanup() { + # 防止用户对 Ctrl+C 突然产生某种异样的迷恋然后狂按 Ctrl+C + [ "$CLEANING" -eq 1 ] && return + CLEANING=1 + + # 防止日志挡交互 + if [ -n "$PMHQ_PID" ] && kill -0 "$PMHQ_PID" 2>/dev/null; then + kill -STOP -"$PMHQ_PID" 2>/dev/null + fi + + echo "" + warn "收到退出信号 (进程已挂起) <<<" + + local kill_qq=1 + local choice="" + + if [ -n "$PMHQ_PID" ]; then + if read -t 5 -n 1 -r -p "是否关闭 QQ 及相关进程? [Y/n] (5秒后默认关闭): " choice < /dev/tty; then + echo "" + else + echo "" + log "等待超时,执行默认关闭操作。" + fi + + if [[ "$choice" == "n" || "$choice" == "N" ]]; then + kill_qq=0 + fi + fi + + if [ $kill_qq -eq 1 ]; then + warn "正在停止服务..." + + kill -CONT -"$PMHQ_PID" 2>/dev/null + + kill -TERM -"$PMHQ_PID" 2>/dev/null + pkill -15 -f "$PMHQ_BIN" 2>/dev/null + pkill -15 -f "/opt/QQ/qq" 2>/dev/null + + local wait_count=0 + + while kill -0 "$PMHQ_PID" 2>/dev/null || \ + pgrep -f "$PMHQ_BIN" > /dev/null || \ + pgrep -f "/opt/QQ/qq" > /dev/null; do + sleep 0.5 + ((wait_count++)) + if [ $wait_count -ge 6 ]; then + warn "检测到残留进程,执行强制清理..." + # 气死我了,总有点关不掉进程的毛病,累了,跟我 pkill -9 说去吧 + # 期待大手子修复 + kill -KILL -"$PMHQ_PID" 2>/dev/null + pkill -9 -f "$PMHQ_BIN" 2>/dev/null + pkill -9 -f "/opt/QQ/qq" 2>/dev/null + break + fi + done + + # 环境清理 + if [ "$DISTRO" != "arch" ] && [ "$USE_XVFB" -eq 0 ]; then + if command -v xhost &> /dev/null; then + xhost -local:$(whoami) > /dev/null 2>&1 + fi + fi + log "所有进程已清理完毕。" + else + # 选择不关闭时必须恢复进程运行 + kill -CONT -"$PMHQ_PID" 2>/dev/null + log "已恢复后台进程运行 (PGID: $PMHQ_PID)" + warn "注意:它们已脱离脚本控制,后续请手动管理" + fi + + exit 0 +} + +trap cleanup SIGINT SIGTERM + confirm() { read -n 1 -s -r -p "$1 (Y/n) " key echo "" @@ -15,13 +103,13 @@ confirm() { } find_port() { - # 让系统自动分配可用端口 + # 优先尝试让系统自动分配 local port=$(python3 -c 'import socket; s=socket.socket(); s.bind(("",0)); print(s.getsockname()[1]); s.close()' 2>/dev/null) if [ -n "$port" ]; then echo $port return 0 fi - # 回退方案:从指定端口开始查找 + # 回退方案:扫描可用端口 local port=$1 while [ $port -lt 65535 ]; do if ! ss -tuln 2>/dev/null | grep -q ":$port " && ! netstat -tuln 2>/dev/null | grep -q ":$port "; then @@ -38,19 +126,20 @@ if command -v pacman &> /dev/null; then elif command -v apt &> /dev/null; then DISTRO="debian" else - error "只支持 apt 或 pacman 包管理器" + error "当前只支持 apt 或 pacman 包管理器" fi log "检测到系统: $DISTRO" install_arch() { + check_sudo log "检查 Arch 依赖..." - sudo pacman -S --needed --noconfirm base-devel git ffmpeg xorg-server-xvfb libvips imagemagick dbus xorg-xhost fcitx5-im wget + sudo pacman -S --needed --noconfirm base-devel git ffmpeg xorg-server-xvfb libvips imagemagick dbus xorg-xhost fcitx5-im wget || error "基础依赖安装失败" if [ ! -f "/opt/QQ/qq" ] && confirm "未检测到 QQ,是否通过 AUR 安装?"; then if ! command -v yay &> /dev/null; then warn "未检测到 yay,尝试安装..." if ! sudo pacman -S --needed --noconfirm yay 2>/dev/null; then - warn "pacman 安装失败,切换源码编译..." + warn "pacman 安装 yay 失败,切换源码编译..." rm -rf /tmp/yay_install && git clone https://aur.archlinux.org/yay.git /tmp/yay_install (cd /tmp/yay_install && makepkg -si --noconfirm) || error "yay 编译失败" rm -rf /tmp/yay_install @@ -61,6 +150,7 @@ install_arch() { } install_debian() { + check_sudo local MACHINE=$(uname -m) local ARCH="" case "$MACHINE" in @@ -71,66 +161,76 @@ install_debian() { if [ ! -f "/opt/QQ/qq" ] && confirm "未检测到 QQ,是否安装?"; then log "下载并安装 QQ ($ARCH)..." - sudo apt-get update && sudo apt-get install -y wget + sudo apt-get update && sudo apt-get install -y wget || error "apt update 或 wget 安装失败" + local DEB="/tmp/qq.deb" - wget -O "$DEB" "https://dldir1v6.qq.com/qqfile/qq/QQNT/ab90fdfa/linuxqq_3.2.20-40768_$ARCH.deb" || error "下载失败" + wget -O "$DEB" "https://dldir1v6.qq.com/qqfile/qq/QQNT/ab90fdfa/linuxqq_3.2.20-40768_$ARCH.deb" || error "QQ 安装包下载失败" - # 依赖判断 (新版 Ubuntu 24.04+ 用 libasound2t64,旧版用 libasound2) local LIB_SND="libasound2" if apt-cache show libasound2t64 &>/dev/null; then LIB_SND="libasound2t64" fi + log "使用 ALSA 库包名: $LIB_SND" - echo "使用 ALSA 库包: $LIB_SND" - - sudo apt install -y "$DEB" x11-utils libgtk-3-0 libxcb-xinerama0 libgl1-mesa-dri libnotify4 libnss3 xdg-utils libsecret-1-0 libappindicator3-1 libgbm1 $LIB_SND fonts-noto-cjk libxss1 + sudo apt install -y "$DEB" x11-utils libgtk-3-0 libxcb-xinerama0 libgl1-mesa-dri libnotify4 libnss3 xdg-utils libsecret-1-0 libappindicator3-1 libgbm1 $LIB_SND fonts-noto-cjk libxss1 || error "QQ 依赖安装失败" rm -f "$DEB" fi + # 检查其他工具 + local missing_pkgs="" for pkg in ffmpeg xvfb; do - command -v $pkg &> /dev/null || sudo apt-get install -y $pkg + if ! command -v $pkg &> /dev/null; then + missing_pkgs="$missing_pkgs $pkg" + fi done + + if [ -n "$missing_pkgs" ]; then + log "安装缺失工具: $missing_pkgs" + sudo apt-get install -y $missing_pkgs || error "工具安装失败" + fi } -[ "$DISTRO" == "arch" ] && install_arch || install_debian +# 执行安装 +if [ "$DISTRO" == "arch" ]; then + install_arch +else + install_debian +fi chmod +x "$SCRIPT_DIR/llbot/node" "$SCRIPT_DIR/llbot/pmhq" 2>/dev/null -[ "$DISTRO" == "arch" ] && sudo chown -R $(whoami):$(whoami) "$SCRIPT_DIR/llbot" + +sudo chown -R $(whoami):$(whoami) "$SCRIPT_DIR/llbot" 2>/dev/null PORT=$(find_port 13000) [ -z "$PORT" ] && error "无法找到可用端口" -log "使用端口: $PORT" - -HAS_DISPLAY=0 -[[ -n "$DISPLAY" || -n "$WAYLAND_DISPLAY" ]] && HAS_DISPLAY=1 +log "分配端口: $PORT" echo "------------------------------------------------" -echo "1) GUI 模式" -echo "2) Shell 模式 (默认)" +echo "1) GUI 模式 (有界面)" +echo "2) Shell 模式 (无界面/Headless,默认)" echo "------------------------------------------------" MODE_CHOICE="" TIMEOUT=5 while [ $TIMEOUT -gt 0 ]; do - printf "\r请选择 [1/2] (${TIMEOUT}秒后默认选择 Shell): " + printf "\r请选择 [1/2] (${TIMEOUT}秒后自动选择 Shell): " if read -t 1 -n 1 MODE_CHOICE; then echo "" break fi ((TIMEOUT--)) done -[ $TIMEOUT -eq 0 ] && echo "" && log "超时,使用默认 Shell 模式" +[ $TIMEOUT -eq 0 ] && echo "" && log "超时,自动选择 Shell 模式" + MODE_CHOICE=${MODE_CHOICE:-2} USE_XVFB=$([ "$MODE_CHOICE" == "2" ] && echo 1 || echo 0) -# 授权 X11 if [ $USE_XVFB -eq 0 ]; then if command -v xauth &> /dev/null; then export XAUTHORITY=${XAUTHORITY:-$HOME/.Xauthority} else warn "未检测到 xauth,使用临时 xhost 授权" xhost +local:$(whoami) > /dev/null 2>&1 - trap "xhost -local:$(whoami) > /dev/null 2>&1" EXIT fi fi @@ -138,11 +238,11 @@ IM_ENV="" EXTRA_FLAGS="" if [[ "$XDG_SESSION_TYPE" == "wayland" || -n "$WAYLAND_DISPLAY" ]]; then - log "环境: Wayland" + log "检测到 Wayland 环境" IM_ENV="XMODIFIERS=@im=fcitx" EXTRA_FLAGS="--enable-features=UseOzonePlatform --ozone-platform=wayland --enable-wayland-ime" else - log "环境: X11" + log "检测到 X11 环境" IM_ENV="GTK_IM_MODULE=fcitx QT_IM_MODULE=fcitx XMODIFIERS=@im=fcitx SDL_IM_MODULE=fcitx GLFW_IM_MODULE=ibus" fi @@ -151,6 +251,8 @@ LLBOT_JS="$SCRIPT_DIR/llbot/llbot.js" PMHQ_BIN="$SCRIPT_DIR/llbot/pmhq" run_llbot() { + set -m + if [ "$DISTRO" == "arch" ]; then export LD_PRELOAD="/usr/lib/libstdc++.so.6:/usr/lib/libgcc_s.so.1" export DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$(id -u)/bus" @@ -158,14 +260,22 @@ run_llbot() { local sub_cmd="$NODE_BIN --enable-source-maps $LLBOT_JS -- --pmhq-port=$PORT --no-sandbox $EXTRA_FLAGS" - log "启动中... (模式: $([ $USE_XVFB -eq 1 ] && echo "Headless" || echo "GUI"))" + log "正在启动 LLBot... (模式: $([ $USE_XVFB -eq 1 ] && echo "Headless" || echo "GUI"))" + log "按 Ctrl+C 可停止运行" if [ $USE_XVFB -eq 1 ]; then - env $IM_ENV xvfb-run -a "$PMHQ_BIN" --port="$PORT" --sub-cmd="$sub_cmd" + env $IM_ENV xvfb-run -a "$PMHQ_BIN" --port="$PORT" --sub-cmd="$sub_cmd" & else - [ "$DISTRO" != "arch" ] && xhost +local:$(whoami) > /dev/null 2>&1 - env $IM_ENV "$PMHQ_BIN" --port="$PORT" --sub-cmd="$sub_cmd" + if [ "$DISTRO" != "arch" ]; then + xhost +local:$(whoami) > /dev/null 2>&1 + fi + env $IM_ENV "$PMHQ_BIN" --port="$PORT" --sub-cmd="$sub_cmd" & fi + + PMHQ_PID=$! + + # 阻塞等待进程结束 + wait "$PMHQ_PID" || true } run_llbot