keepalive.sh
6.85 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
#!/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:-180000}"
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:-180000} \
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
if ! is_auto_reply_running; then
log "auto-reply.js not running, restarting..."
start_auto_reply
fi
done