一,从零搭建守护环境

1,在 root 目录下新建 all-n1-install.sh 文件,内容如下

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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
#!/bin/bash
set -e
echo "==== N1盒子自愈全套【无jq版·重新生成守护环境】 ===="

# 不再安装jq依赖
echo "[1/7] 跳过jq依赖,使用pm2 id返回码判断状态"

# 2.生成 /root/n1.sh
echo "[2/7] 生成 /root/n1.sh"
cat > /root/n1.sh <<'INNER_EOF'
#!/bin/bash
# N1盒子服务自愈脚本【无jq最终版】
# 宿主机服务: go-cqhttp sillyGirl xdd
# 青龙容器内服务:默认关闭巡检 QL_APPS=()
QL_CONTAINER="qinglong"
LOCK_DIR="/root/.n1-deploy"
LOG_FILE="/root/ql/logs/selfheal.log"
CRON_LOG="/root/ql/logs/cron-n1.log"
PM2_LOG_DIR="/root/.pm2/logs"

mkdir -p "${LOCK_DIR}"
mkdir -p "/root/ql/logs"
mkdir -p "${PM2_LOG_DIR}"

# ====================== 配置参数 ======================
log_max_size=$((20*1024*1024)) # 单文件上限 20MB
log_keep_days=3 # 碎片日志保留天数
docker_exec_timeout=8 # docker exec超时秒数
# =====================================================

## ① 主日志超过阈值清空
for logfile in "${LOG_FILE}" "${CRON_LOG}";do
if [ -f "${logfile}" ];then
filesize=$(stat -c%s "${logfile}")
if [ "${filesize}" -gt "${log_max_size}" ];then
> "${logfile}"
fi
fi
done

## ② 碎片日志过期删除
find /root/ql/logs -type f -name "*.log" ! -name "selfheal.log" ! -name "cron-n1.log" -mtime +${log_keep_days} -delete

## ③ 宿主机pm2日志处理
for logfile in ${PM2_LOG_DIR}/*.log;do
if [ -f "${logfile}" ];then
filesize=$(stat -c%s "${logfile}")
if [ "${filesize}" -gt "${log_max_size}" ];then
> "${logfile}"
fi
fi
done
find "${PM2_LOG_DIR}" -type f -name "*.log" -mtime +${log_keep_days} -delete

## ④ docker容器日志截断
docker_log_path=$(docker inspect --format='{{.LogPath}}' "${QL_CONTAINER}" 2>/dev/null || true)
if [ -n "${docker_log_path}" ] && [ -f "${docker_log_path}" ];then
docker_log_size=$(stat -c%s "${docker_log_path}")
if [ "${docker_log_size}" -gt "${log_max_size}" ];then
truncate -s 0 "${docker_log_path}"
fi
fi

# 互斥锁,防止脚本并发运行
exec 9>"${LOCK_DIR}/n1.lock"
if ! flock -n 9 ; then
echo "$(date '+%Y-%m-%d %H:%M:%S') n1.sh 已在运行,放弃本次执行" >> ${LOG_FILE}
exit 0
fi

log(){
echo "$(date '+%Y-%m-%d %H:%M:%S') $*" >> ${LOG_FILE}
echo "$*"
}

# ---------------- pm2 id方式检测,无jq ----------------
host_is_online(){
local app="$1"
pm2 id "${app}" >/dev/null 2>&1
return $?
}

ql_is_online(){
local app="$1"
docker exec --timeout ${docker_exec_timeout} "${QL_CONTAINER}" pm2 id "${app}" >/dev/null 2>&1
return $?
}

HOST_APPS=("go-cqhttp" "sillyGirl" "xdd")
QL_APPS=()

log "=== N1 夜维 $(date '+%Y-%m-%d_%H:%M') ==="

# 宿主机服务巡检
host_ok=0
host_total=${#HOST_APPS[@]}
for app in "${HOST_APPS[@]}";do
if host_is_online "${app}";then
log "✅ 腿[${app}] online"
host_ok=$((host_ok+1))
else
log "⚠️ 腿[${app}] 异常,执行重启"
log "🔧 重启宿主机 ${app}"
pm2 restart "${app}" >>${LOG_FILE} 2>&1
fi
done
log "${host_ok}/${host_total}"

# 容器服务巡检,QL_APPS为空数组时不会循环执行
ql_ok=0
ql_total=${#QL_APPS[@]}
for app in "${QL_APPS[@]}";do
if ql_is_online "${app}";then
log "✅ ql[${app}] online"
ql_ok=$((ql_ok+1))
else
log "⚠️ ql[${app}] 异常,执行重启"
log "🔧 重启容器 ${app}"
docker exec --timeout ${docker_exec_timeout} "${QL_CONTAINER}" pm2 restart "${app}" >>${LOG_FILE} 2>&1
fi
done
log "${ql_ok}/${ql_total}"

disk_used=$(df -P / | awk 'NR==2{print $5}' | tr -d '%')
log "✅ 磁盘 ${disk_used}%"
log "=== 巡检结束 ==="
exit 0
INNER_EOF
chmod +x /root/n1.sh

# 3.生成systemd开机恢复服务:宿主机pm2 resurrect + 容器pm2 resurrect
echo "[3/7] 生成 systemd 开机延时恢复服务"
cat > /etc/systemd/system/n1-ql-restore.service <<'UNIT_EOF'
[Unit]
Description=N1开机延时恢复宿主机&青龙容器pm2
After=docker.service
Requires=docker.service

[Service]
Type=oneshot
ExecStart=/bin/sh -c 'sleep 40; pm2 resurrect >/dev/null 2>&1; pm2 save >/dev/null 2>&1; docker exec qinglong pm2 resurrect >/dev/null 2>&1; docker exec qinglong pm2 save >/dev/null 2>&1;'

[Install]
WantedBy=multi-user.target
UNIT_EOF
systemctl daemon-reload
systemctl enable n1-ql-restore.service

# 4.配置crontab:覆盖式写入干净定时,避免重复任务
echo "[4/7] 设置crontab定时任务,覆盖旧定时"
cat > /root/tmp_cron.txt <<'CRON_EOF'
05 3 * * * /root/ql-scrub.sh >> /root/ql/logs/night.log 2>&1
15 3 * * * /root/n1.sh
*/5 * * * * /bin/bash /root/n1.sh
CRON_EOF
crontab /root/tmp_cron.txt
rm -f /root/tmp_cron.txt

# 5.生成日志清理脚本 ql‑scrub.sh
echo "[5/7] 生成日志裁剪脚本 ql‑scrub.sh"
cat > /root/ql-scrub.sh <<'SCRUB_EOF'
#!/bin/bash
LOGDIR="/root/ql/logs"
MAX_SIZE=$((1024*1024*20)) #20MB
mkdir -p "${LOGDIR}"
for logfile in "${LOGDIR}"/*.log;do
if [ -f "${logfile}" ];then
filesize=$(stat -c%s "${logfile}")
if [ "${filesize}" -gt "${MAX_SIZE}" ];then
mv -f "${logfile}" "${logfile}.old"
touch "${logfile}"
fi
fi
done
SCRUB_EOF
chmod +x /root/ql-scrub.sh

# 6.清理旧锁、创建目录
echo "[6/7] 清理旧锁文件,创建目录"
rm -f /root/.n1-deploy/n1.lock
mkdir -p /root/ql/logs
mkdir -p /root/.pm2/logs

# 7.首次执行测试
echo "[7/7] 执行首次巡检测试"
bash /root/n1.sh

echo ""
echo "==================== 部署完成 ===================="
echo "✅ 完全无jq依赖,pm2 id返回码检测进程"
echo "✅ QL_APPS=() 默认关闭容器内pm2巡检,无需手动修改"
echo "✅ systemd:开机40秒后恢复宿主机+青龙容器pm2"
echo "✅ crontab:5分钟巡检;凌晨3:05日志清理;3:15额外巡检"
echo "✅ 日志策略:单文件>20MB轮转;碎片日志保留3天"
echo "✅ 巡检对象:宿主机go‑cqhttp/sillyGirl/xdd"
echo "✅ flock防并发、docker exec超时8秒保护"
echo ""
echo "手动巡检命令:bash /root/n1.sh"
echo "查看自愈日志:tail -f /root/ql/logs/selfheal.log"
echo "查看开机恢复服务状态:systemctl status n1-ql-restore.service"
echo "查看定时任务:crontab -l"
echo ""
echo "👉 青龙容器内部建议执行:pm2 resurrect && pm2 save"
echo "👉 整机重启测试:reboot"

2,脚本跑完校验

1
2
3
crontab -l
systemctl status n1-ql-restore.service
bash /root/n1.sh

只会输出宿主机三条 ✅ xxx online,容器部分不会输出任何内容

使用场景:N1 彻底重装 Armbian 从零部署守护环境就运行这个脚本

二,备份守护环境

1,确认全部 5 个文件都存在

1
2
3
4
5
6
ls -l \
/root/n1.sh \
/root/ql-scrub.sh \
/etc/systemd/system/n1-ql-restore.service \
/root/cron_backup.txt \
/root/pm2_ecosystem_backup.json

上面命令不报错,列出全部文件,就可以打包

1
2
3
4
5
6
7
8
crontab -l > /root/cron_backup.txt
pm2 ecosystem > /root/pm2_ecosystem_backup.json
tar -zcvf /root/n1_backup.tar.gz \
/root/n1.sh \
/root/ql-scrub.sh \
/etc/systemd/system/n1-ql-restore.service \
/root/cron_backup.txt \
/root/pm2_ecosystem_backup.json

2,查看包里文件清单确认

1
tar -ztvf /root/n1_backup.tar.gz

3,能看到 5 个文件就全部正常

1
2
3
4
5
root/n1.sh
root/ql-scrub.sh
etc/systemd/system/n1-ql-restore.service
root/cron_backup.txt
root/pm2_ecosystem_backup.json

4,看备份包大小

1
ls -lh /root/n1_backup.tar.gz

📌现在这个备份包,是基于无‑jq 一键脚本生成出来的环境打包出来的

  • n1.sh:QL_APPS=() 默认关闭容器巡检、pm2 id 判断、无 jq
  • ql‑scrub.sh:日志轮转脚本
  • systemd 服务:开机 40s 同时恢复宿主机 pm2 + 青龙容器 pm2
  • cron_backup.txt:保存当前干净的 3 条定时
  • pm2_ecosystem_backup.json:pm2 进程清单备份

三,N1 重装 Armbian 之后完整恢复整套守护环境

这个备份只保存自愈守护相关:巡检脚本、日志清理、systemd 开机服务、定时任务、pm2 进程清单

✅ n1.sh 自愈脚本:使用pm2 id判断进程,彻底避开 pm2 jq 解析 bug,go‑cqhttp/sillyGirl/xdd 识别 online,不会误重启
✅ ql‑scrub.sh 日志裁剪脚本重建完成,防止日志打爆磁盘
✅ systemd n1‑ql‑restore.service:开机延时恢复 pm2
✅ crontab 定时已精简,无重复任务,无宝塔旧垃圾定时
✅ flock 锁防止脚本并发重复运行
✅ 备份包包含全部守护逻辑脚本、systemd 服务、定时导出、pm2 进程清单

1,N1 重装 Armbian ,点我

2,安装好宝塔、docker、pm2、青龙容器、go‑cqhttp、sillyGirl、xdd

3,把备份的 n1_backup.tar.gz 上传到宝塔 root 目录下,然后依次执行

1
2
3
4
5
6
7
8
9
10
tar -zxvf /root/n1_backup.tar.gz -C /
chmod +x /root/n1.sh
chmod +x /root/ql-scrub.sh
crontab /root/cron_backup.txt
systemctl daemon-reload
systemctl enable n1-ql-restore.service
pm2 start /root/pm2_ecosystem_backup.json
pm2 save
mkdir -p /root/ql/logs
bash /root/n1.sh

执行完最后一条 bash /root/n1.sh,输出三条 ✅ xxx online,代表整套自愈恢复成功

4,恢复完成后校验

1
2
3
4
5
6
#1. 确认定时任务加载正确
crontab -l
#2. 确认开机自启服务已经启用
systemctl status n1-ql-restore.service
#3. 再手动跑一次自愈脚本确认状态
bash /root/n1.sh

✅预期输出

1
2
3
4
5
6
7
8
=== N1 夜维 xxxx ===
✅ 腿[go-cqhttp] online
✅ 腿[sillyGirl] online
✅ 腿[xdd] online
3/3
0/0
✅ 磁盘 xx%
=== 巡检结束 ===
1
2
#4. 看实时日志是否正常写入
tail -n 20 /root/ql/logs/selfheal.log

5,可选,整机重启测试(最重要的验证)

1
reboot

盒子重启,SSH 重新登录进来

1
2
3
#重启登录后检查
pm2 list
bash /root/n1.sh

重启后 pm2 三个进程能自动拉起来、自愈脚本识别全部 online,代表完整恢复成功

后台自动工作:

①每 5 分钟自动运行 /root/n1.sh,进程挂掉自动重启
②凌晨 3:05 运行日志清理脚本,防止日志占磁盘
③开机 systemd 服务自动恢复 pm2 进程

6,日常排查命令

1
2
3
4
crontab -l                              # 核对定时任务
systemctl status n1-ql-restore.service # 核对开机恢复服务已启用
bash /root/n1.sh # 手动跑一遍自愈脚本,确认全部online
tail -n 20 /root/ql/logs/selfheal.log # 查看最近20行日志,确认日志写入正常