#!/data/data/com.termux/files/usr/bin/bash # keepalive.sh — Pixel 微信自动回复保活脚本 # # 守护以下进程,挂了就重启: # 1. termux wake-lock — 防 Doze 冻结 Termux:API(通知监听依赖它) # 2. HermesAccessibilityService — A11y 易被系统/更新关掉,发现 enabled_accessibility_services=null 就重启 # 3. hermes-agent gateway — :8642,LLM 网关 # 4. auto-reply.js — 微信回复主程序 # # 用法(在 Termux 用户 shell 里运行;不要在 root shell 起,wake-lock 拿不住 Termux 进程): # nohup bash ~/hermes-wechat/keepalive.sh > ~/hermes-wechat/keepalive.log 2>&1 & # # 前置条件(一次性设置): # 1. Android 设置 → 应用 → Termux / Termux:API / 微信 → 电池优化 → 不优化 # 2. Termux:API 已安装,并已授予通知访问权限 # 3. lsposed-wechat-sendmsg APK 已安装(含 HermesAccessibilityService) # 4. ~/hermes-wechat/start-ar.sh 存在并已设置 MY_WXID / MY_NICKNAME export PATH="/data/data/com.termux/files/usr/bin:$PATH" export HOME="/data/data/com.termux/files/home" HERMES_DIR="${HERMES_DIR:-$HOME/hermes-wechat}" HERMES_PORT="${HERMES_PORT:-8642}" GATEWAY_START="${GATEWAY_START:-$HOME/.hermes/scripts/start-gateway.sh}" A11Y_SVC="com.hermes.wechat.xposed/com.hermes.wechat.xposed.accessibility.HermesAccessibilityService" CHECK_INTERVAL_SEC="${CHECK_INTERVAL_SEC:-15}" # auto-reply 启动用的 env(避开 start-ar.sh 的 shell hook 污染) MY_WXID="${MY_WXID:-wxid_51ap3m6p9xs722}" MY_NICKNAME="${MY_NICKNAME:-小火子}" HERMES_TIMEOUT_MS="${HERMES_TIMEOUT_MS:-600000}" HISTORY_CONTEXT_SIZE="${HISTORY_CONTEXT_SIZE:-8}" log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"; } # ── 1. Termux wake-lock ────────────────────────────────────────── ensure_wake_lock() { # 用命令行 termux-wake-lock(依赖 Termux:API app);am startservice 是备用 if command -v termux-wake-lock >/dev/null 2>&1; then termux-wake-lock 2>/dev/null else # 后备:用 su 调系统 ServiceManager su -c "am startservice --user 0 -a com.termux.service_wake_lock com.termux/.app.TermuxService" 2>/dev/null || true fi } is_wake_lock_held() { su -c "dumpsys power 2>/dev/null | grep -q 'termux:service-wakelock'" } # ── 2. A11y service ────────────────────────────────────────────── is_a11y_enabled() { out=$(su -c "settings get secure enabled_accessibility_services" 2>/dev/null) case "$out" in *com.hermes.wechat.xposed*HermesAccessibilityService*) return 0 ;; *) return 1 ;; esac } enable_a11y() { log "Enabling HermesAccessibilityService..." su -c "settings put secure enabled_accessibility_services $A11Y_SVC; settings put secure accessibility_enabled 1" 2>/dev/null } # ── 3. hermes gateway ──────────────────────────────────────────── is_gateway_running() { # 用 /sys/class/net 风格的端口探测:通过 nc 或 curl,二选一 if command -v curl >/dev/null 2>&1; then # gateway 在 termux 用户下需要 Clash 代理(与本机回环无关); # 但 127.0.0.1:8642 走本机,不用代理 env -u HTTPS_PROXY -u HTTP_PROXY \ curl -s --max-time 3 "http://127.0.0.1:${HERMES_PORT}/v1/models" >/dev/null 2>&1 else # nc fallback echo > /dev/tcp/127.0.0.1/$HERMES_PORT 2>/dev/null fi } start_gateway() { log "Starting hermes gateway..." if [ -x "$GATEWAY_START" ]; then bash "$GATEWAY_START" >> "$HOME/.hermes/logs/gateway-start.log" 2>&1 else log "[ERROR] $GATEWAY_START not found" fi } # ── 4. auto-reply.js ───────────────────────────────────────────── AR_PID_FILE="$HERMES_DIR/.auto-reply.pid" is_auto_reply_running() { # 不能用 pgrep -f node.*auto-reply.js — 它会自匹配 su shell 自己的 cmdline。 # 用 ps -ef + grep 精确匹配 'node auto-reply.js'(含空格,排除 sh wrapper)。 # su 提权因为 auto-reply 跑在 root 下,u0_a327 看不到 root 进程的 ps。 # 用 . 占位(任何单字符)避开嵌套引号问题 su -c "ps -ef 2>/dev/null | grep -E node.auto-reply.js | grep -v sh.-c | grep -v grep" \ 2>/dev/null | grep -q . } start_auto_reply() { log "Starting auto-reply..." # 不走 start-ar.sh wrapper(其 shebang 走 dash 后会触发 phone 上某个 # claude-run/hermes 会话的 shell snapshot hook,污染 hermes-wechat/*.js)。 # 直接以 root 起 node + 内联 env,跨过那个 shell session。 # 起完后用 pgrep + grep 严格匹配 cmdline 确切包含 'node auto-reply.js' # (不能用 pgrep -f 因为正则 .* 会自匹配 su shell 本身),写入 PID 文件 su -c "cd $HERMES_DIR && \ MY_WXID=${MY_WXID:-wxid_51ap3m6p9xs722} \ MY_NICKNAME=\"${MY_NICKNAME:-小火子}\" \ HERMES_HOST=127.0.0.1 \ HERMES_PORT=$HERMES_PORT \ HERMES_TIMEOUT=${HERMES_TIMEOUT_MS:-600000} \ NOTIFICATION_ENABLED=true \ HISTORY_CONTEXT_SIZE=${HISTORY_CONTEXT_SIZE:-8} \ nohup /data/data/com.termux/files/usr/bin/node auto-reply.js \ > $HERMES_DIR/auto-reply.log 2>&1 < /dev/null & echo \$! > $AR_PID_FILE" sleep 2 if [ -f "$AR_PID_FILE" ]; then log "Auto-reply started PID=$(cat $AR_PID_FILE 2>/dev/null)" fi } # ── 5. 主循环 ──────────────────────────────────────────────────── log "=== Keepalive started ===" log "HERMES_DIR=$HERMES_DIR GATEWAY_START=$GATEWAY_START" log "MY_WXID=$MY_WXID MY_NICKNAME=$MY_NICKNAME HERMES_TIMEOUT=${HERMES_TIMEOUT_MS}ms" log "Check interval: ${CHECK_INTERVAL_SEC}s" # 首次进入:把所有依赖拉到位 ensure_wake_lock is_a11y_enabled || enable_a11y is_gateway_running || start_gateway sleep 5 is_auto_reply_running || start_auto_reply while true; do sleep "$CHECK_INTERVAL_SEC" # wake-lock 偶尔会因 Termux 重启 / 系统重启而丢,保险起见周期性 re-acquire if ! is_wake_lock_held; then log "Wake lock lost, re-acquiring..." ensure_wake_lock fi # A11y 易被系统升级 / 用户在设置中关掉 if ! is_a11y_enabled; then log "A11y service disabled, re-enabling..." enable_a11y fi if ! is_gateway_running; then log "Gateway not responding on :$HERMES_PORT, restarting..." start_gateway sleep 6 fi # ── restart-flag 桥:让 claude-run(proot 里够不到 Magisk root)也能触发重启 ── # claude-run 改完 *.js 后 `touch ~/hermes-wechat/.restart-request`,keepalive(已是 root # 能力)看到就杀掉 auto-reply,下面的 is_auto_reply_running 检测会随即用新代码重启。 # 只暴露「请求重启」这一个动作,不是任意命令执行。 if [ -f "$HERMES_DIR/.restart-request" ]; then log "Restart requested via .restart-request flag, killing auto-reply..." rm -f "$HERMES_DIR/.restart-request" su -c "pkill -f auto-reply.js" 2>/dev/null sleep 1 fi if ! is_auto_reply_running; then log "auto-reply.js not running, restarting..." start_auto_reply fi done