22
33SCRIPT_DIR=" $( cd " $( dirname " $0 " ) " && pwd) "
44export PATH=$PATH :/usr/bin:/usr/local/bin
5+
56RED=' \033[0;31m' ; GREEN=' \033[0;32m' ; YELLOW=' \033[1;33m' ; NC=' \033[0m'
67
8+ CLEANING=0
9+ PMHQ_PID=" "
10+ USE_XVFB=0
11+ DISTRO=" "
12+
713log () { echo -e " ${GREEN} >>> $1 ${NC} " ; }
814warn () { echo -e " ${YELLOW} >>> $1 ${NC} " ; }
915error () { echo -e " ${RED} 错误: $1 ${NC} " ; exit 1; }
1016
17+ check_sudo () {
18+ log " 验证 Sudo 权限..."
19+ if ! sudo -v; then
20+ error " Sudo 验证失败或被取消,脚本终止。"
21+ fi
22+ }
23+
24+ cleanup () {
25+ # 防止用户对 Ctrl+C 突然产生某种异样的迷恋然后狂按 Ctrl+C
26+ [ " $CLEANING " -eq 1 ] && return
27+ CLEANING=1
28+
29+ # 防止日志挡交互
30+ if [ -n " $PMHQ_PID " ] && kill -0 " $PMHQ_PID " 2> /dev/null; then
31+ kill -STOP -" $PMHQ_PID " 2> /dev/null
32+ fi
33+
34+ echo " "
35+ warn " 收到退出信号 (进程已挂起) <<<"
36+
37+ local kill_qq=1
38+ local choice=" "
39+
40+ if [ -n " $PMHQ_PID " ]; then
41+ if read -t 5 -n 1 -r -p " 是否关闭 QQ 及相关进程? [Y/n] (5秒后默认关闭): " choice < /dev/tty; then
42+ echo " "
43+ else
44+ echo " "
45+ log " 等待超时,执行默认关闭操作。"
46+ fi
47+
48+ if [[ " $choice " == " n" || " $choice " == " N" ]]; then
49+ kill_qq=0
50+ fi
51+ fi
52+
53+ if [ $kill_qq -eq 1 ]; then
54+ warn " 正在停止服务..."
55+
56+ kill -CONT -" $PMHQ_PID " 2> /dev/null
57+
58+ kill -TERM -" $PMHQ_PID " 2> /dev/null
59+ pkill -15 -f " $PMHQ_BIN " 2> /dev/null
60+ pkill -15 -f " /opt/QQ/qq" 2> /dev/null
61+
62+ local wait_count=0
63+
64+ while kill -0 " $PMHQ_PID " 2> /dev/null || \
65+ pgrep -f " $PMHQ_BIN " > /dev/null || \
66+ pgrep -f " /opt/QQ/qq" > /dev/null; do
67+ sleep 0.5
68+ (( wait_count++ ))
69+ if [ $wait_count -ge 6 ]; then
70+ warn " 检测到残留进程,执行强制清理..."
71+ # 气死我了,总有点关不掉进程的毛病,累了,跟我 pkill -9 说去吧
72+ # 期待大手子修复
73+ kill -KILL -" $PMHQ_PID " 2> /dev/null
74+ pkill -9 -f " $PMHQ_BIN " 2> /dev/null
75+ pkill -9 -f " /opt/QQ/qq" 2> /dev/null
76+ break
77+ fi
78+ done
79+
80+ # 环境清理
81+ if [ " $DISTRO " != " arch" ] && [ " $USE_XVFB " -eq 0 ]; then
82+ if command -v xhost & > /dev/null; then
83+ xhost -local:$( whoami) > /dev/null 2>&1
84+ fi
85+ fi
86+ log " 所有进程已清理完毕。"
87+ else
88+ # 选择不关闭时必须恢复进程运行
89+ kill -CONT -" $PMHQ_PID " 2> /dev/null
90+ log " 已恢复后台进程运行 (PGID: $PMHQ_PID )"
91+ warn " 注意:它们已脱离脚本控制,后续请手动管理"
92+ fi
93+
94+ exit 0
95+ }
96+
97+ trap cleanup SIGINT SIGTERM
98+
1199confirm () {
12100 read -n 1 -s -r -p " $1 (Y/n) " key
13101 echo " "
14102 [[ " $key " == " Y" || " $key " == " y" || " $key " == " " ]]
15103}
16104
17105find_port () {
18- # 让系统自动分配可用端口
106+ # 优先尝试让系统自动分配
19107 local port=$( python3 -c ' import socket; s=socket.socket(); s.bind(("",0)); print(s.getsockname()[1]); s.close()' 2> /dev/null)
20108 if [ -n " $port " ]; then
21109 echo $port
22110 return 0
23111 fi
24- # 回退方案:从指定端口开始查找
112+ # 回退方案:扫描可用端口
25113 local port=$1
26114 while [ $port -lt 65535 ]; do
27115 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
38126elif command -v apt & > /dev/null; then
39127 DISTRO=" debian"
40128else
41- error " 只支持 apt 或 pacman 包管理器"
129+ error " 当前只支持 apt 或 pacman 包管理器"
42130fi
43131log " 检测到系统: $DISTRO "
44132
45133install_arch () {
134+ check_sudo
46135 log " 检查 Arch 依赖..."
47- sudo pacman -S --needed --noconfirm base-devel git ffmpeg xorg-server-xvfb libvips imagemagick dbus xorg-xhost fcitx5-im wget
136+ sudo pacman -S --needed --noconfirm base-devel git ffmpeg xorg-server-xvfb libvips imagemagick dbus xorg-xhost fcitx5-im wget || error " 基础依赖安装失败 "
48137
49138 if [ ! -f " /opt/QQ/qq" ] && confirm " 未检测到 QQ,是否通过 AUR 安装?" ; then
50139 if ! command -v yay & > /dev/null; then
51140 warn " 未检测到 yay,尝试安装..."
52141 if ! sudo pacman -S --needed --noconfirm yay 2> /dev/null; then
53- warn " pacman 安装失败 ,切换源码编译..."
142+ warn " pacman 安装 yay 失败 ,切换源码编译..."
54143 rm -rf /tmp/yay_install && git clone https://aur.archlinux.org/yay.git /tmp/yay_install
55144 (cd /tmp/yay_install && makepkg -si --noconfirm) || error " yay 编译失败"
56145 rm -rf /tmp/yay_install
@@ -61,6 +150,7 @@ install_arch() {
61150}
62151
63152install_debian () {
153+ check_sudo
64154 local MACHINE=$( uname -m)
65155 local ARCH=" "
66156 case " $MACHINE " in
@@ -71,78 +161,88 @@ install_debian() {
71161
72162 if [ ! -f " /opt/QQ/qq" ] && confirm " 未检测到 QQ,是否安装?" ; then
73163 log " 下载并安装 QQ ($ARCH )..."
74- sudo apt-get update && sudo apt-get install -y wget
164+ sudo apt-get update && sudo apt-get install -y wget || error " apt update 或 wget 安装失败"
165+
75166 local DEB=" /tmp/qq.deb"
76- wget -O " $DEB " " https://dldir1v6.qq.com/qqfile/qq/QQNT/ab90fdfa/linuxqq_3.2.20-40768_$ARCH .deb" || error " 下载失败 "
167+ wget -O " $DEB " " https://dldir1v6.qq.com/qqfile/qq/QQNT/ab90fdfa/linuxqq_3.2.20-40768_$ARCH .deb" || error " QQ 安装包下载失败 "
77168
78- # 依赖判断 (新版 Ubuntu 24.04+ 用 libasound2t64,旧版用 libasound2)
79169 local LIB_SND=" libasound2"
80170 if apt-cache show libasound2t64 & > /dev/null; then
81171 LIB_SND=" libasound2t64"
82172 fi
173+ log " 使用 ALSA 库包名: $LIB_SND "
83174
84- echo " 使用 ALSA 库包: $LIB_SND "
85-
86- 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
175+ 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 依赖安装失败"
87176 rm -f " $DEB "
88177 fi
89178
179+ # 检查其他工具
180+ local missing_pkgs=" "
90181 for pkg in ffmpeg xvfb; do
91- command -v $pkg & > /dev/null || sudo apt-get install -y $pkg
182+ if ! command -v $pkg & > /dev/null; then
183+ missing_pkgs=" $missing_pkgs $pkg "
184+ fi
92185 done
186+
187+ if [ -n " $missing_pkgs " ]; then
188+ log " 安装缺失工具: $missing_pkgs "
189+ sudo apt-get install -y $missing_pkgs || error " 工具安装失败"
190+ fi
93191}
94192
95- [ " $DISTRO " == " arch" ] && install_arch || install_debian
193+ # 执行安装
194+ if [ " $DISTRO " == " arch" ]; then
195+ install_arch
196+ else
197+ install_debian
198+ fi
96199
97200chmod +x " $SCRIPT_DIR /llbot/node" " $SCRIPT_DIR /llbot/pmhq" 2> /dev/null
98- [ " $DISTRO " == " arch" ] && sudo chown -R $( whoami) :$( whoami) " $SCRIPT_DIR /llbot"
201+
202+ sudo chown -R $( whoami) :$( whoami) " $SCRIPT_DIR /llbot" 2> /dev/null
99203
100204PORT=$( find_port 13000)
101205[ -z " $PORT " ] && error " 无法找到可用端口"
102- log " 使用端口: $PORT "
103-
104- HAS_DISPLAY=0
105- [[ -n " $DISPLAY " || -n " $WAYLAND_DISPLAY " ]] && HAS_DISPLAY=1
206+ log " 分配端口: $PORT "
106207
107208echo " ------------------------------------------------"
108- echo " 1) GUI 模式"
109- echo " 2) Shell 模式 (默认)"
209+ echo " 1) GUI 模式 (有界面) "
210+ echo " 2) Shell 模式 (无界面/Headless, 默认)"
110211echo " ------------------------------------------------"
111212
112213MODE_CHOICE=" "
113214TIMEOUT=5
114215while [ $TIMEOUT -gt 0 ]; do
115- printf " \r请选择 [1/2] (${TIMEOUT} 秒后默认选择 Shell): "
216+ printf " \r请选择 [1/2] (${TIMEOUT} 秒后自动选择 Shell): "
116217 if read -t 1 -n 1 MODE_CHOICE; then
117218 echo " "
118219 break
119220 fi
120221 (( TIMEOUT-- ))
121222done
122- [ $TIMEOUT -eq 0 ] && echo " " && log " 超时,使用默认 Shell 模式"
223+ [ $TIMEOUT -eq 0 ] && echo " " && log " 超时,自动选择 Shell 模式"
224+
123225MODE_CHOICE=${MODE_CHOICE:- 2}
124226USE_XVFB=$( [ " $MODE_CHOICE " == " 2" ] && echo 1 || echo 0)
125227
126- # 授权 X11
127228if [ $USE_XVFB -eq 0 ]; then
128229 if command -v xauth & > /dev/null; then
129230 export XAUTHORITY=${XAUTHORITY:- $HOME / .Xauthority}
130231 else
131232 warn " 未检测到 xauth,使用临时 xhost 授权"
132233 xhost +local:$( whoami) > /dev/null 2>&1
133- trap " xhost -local:$( whoami) > /dev/null 2>&1" EXIT
134234 fi
135235fi
136236
137237IM_ENV=" "
138238EXTRA_FLAGS=" "
139239
140240if [[ " $XDG_SESSION_TYPE " == " wayland" || -n " $WAYLAND_DISPLAY " ]]; then
141- log " 环境: Wayland"
241+ log " 检测到 Wayland 环境 "
142242 IM_ENV=" XMODIFIERS=@im=fcitx"
143243 EXTRA_FLAGS=" --enable-features=UseOzonePlatform --ozone-platform=wayland --enable-wayland-ime"
144244else
145- log " 环境: X11"
245+ log " 检测到 X11 环境 "
146246 IM_ENV=" GTK_IM_MODULE=fcitx QT_IM_MODULE=fcitx XMODIFIERS=@im=fcitx SDL_IM_MODULE=fcitx GLFW_IM_MODULE=ibus"
147247fi
148248
@@ -151,21 +251,31 @@ LLBOT_JS="$SCRIPT_DIR/llbot/llbot.js"
151251PMHQ_BIN=" $SCRIPT_DIR /llbot/pmhq"
152252
153253run_llbot () {
254+ set -m
255+
154256 if [ " $DISTRO " == " arch" ]; then
155257 export LD_PRELOAD=" /usr/lib/libstdc++.so.6:/usr/lib/libgcc_s.so.1"
156258 export DBUS_SESSION_BUS_ADDRESS=" unix:path=/run/user/$( id -u) /bus"
157259 fi
158260
159261 local sub_cmd=" $NODE_BIN --enable-source-maps $LLBOT_JS -- --pmhq-port=$PORT --no-sandbox $EXTRA_FLAGS "
160262
161- log " 启动中... (模式: $( [ $USE_XVFB -eq 1 ] && echo " Headless" || echo " GUI" ) )"
263+ log " 正在启动 LLBot... (模式: $( [ $USE_XVFB -eq 1 ] && echo " Headless" || echo " GUI" ) )"
264+ log " 按 Ctrl+C 可停止运行"
162265
163266 if [ $USE_XVFB -eq 1 ]; then
164- env $IM_ENV xvfb-run -a " $PMHQ_BIN " --port=" $PORT " --sub-cmd=" $sub_cmd "
267+ env $IM_ENV xvfb-run -a " $PMHQ_BIN " --port=" $PORT " --sub-cmd=" $sub_cmd " &
165268 else
166- [ " $DISTRO " != " arch" ] && xhost +local:$( whoami) > /dev/null 2>&1
167- env $IM_ENV " $PMHQ_BIN " --port=" $PORT " --sub-cmd=" $sub_cmd "
269+ if [ " $DISTRO " != " arch" ]; then
270+ xhost +local:$( whoami) > /dev/null 2>&1
271+ fi
272+ env $IM_ENV " $PMHQ_BIN " --port=" $PORT " --sub-cmd=" $sub_cmd " &
168273 fi
274+
275+ PMHQ_PID=$!
276+
277+ # 阻塞等待进程结束
278+ wait " $PMHQ_PID " || true
169279}
170280
171281run_llbot
0 commit comments