一,从零搭建守护环境
1,在 root 目录下新建 all-n1-install.sh 文件,内容如下
bash
#!/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,脚本跑完校验
bash
crontab -l
systemctl status n1-ql-restore.service
bash /root/n1.sh
只会输出宿主机三条 ✅ xxx online,容器部分不会输出任何内容
使用场景:N1 彻底重装 Armbian 从零部署守护环境就运行这个脚本
二,备份守护环境
1,确认全部 5 个文件都存在
bash
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
上面命令不报错,列出全部文件,就可以打包
bash
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,查看包里文件清单确认
bash
tar -ztvf /root/n1_backup.tar.gz
3,能看到 5 个文件就全部正常
bash
root/n1.sh
root/ql-scrub.sh
etc/systemd/system/n1-ql-restore.service
root/cron_backup.txt
root/pm2_ecosystem_backup.json
4,看备份包大小
bash
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 目录下,然后依次执行
bash
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,恢复完成后校验
bash
#1. 确认定时任务加载正确
crontab -l
#2. 确认开机自启服务已经启用
systemctl status n1-ql-restore.service
#3. 再手动跑一次自愈脚本确认状态
bash /root/n1.sh
✅预期输出
bash
=== N1 夜维 xxxx ===
✅ 腿[go-cqhttp] online
✅ 腿[sillyGirl] online
✅ 腿[xdd] online
3/3
0/0
✅ 磁盘 xx%
=== 巡检结束 ===
bash
#4. 看实时日志是否正常写入
tail -n 20 /root/ql/logs/selfheal.log
5,可选,整机重启测试(最重要的验证)
bash
reboot
盒子重启,SSH 重新登录进来
bash
#重启登录后检查
pm2 list
bash /root/n1.sh
重启后 pm2 三个进程能自动拉起来、自愈脚本识别全部 online,代表完整恢复成功
后台自动工作:
①每 5 分钟自动运行 /root/n1.sh,进程挂掉自动重启
②凌晨 3:05 运行日志清理脚本,防止日志占磁盘
③开机 systemd 服务自动恢复 pm2 进程
6,日常排查命令
bash
crontab -l # 核对定时任务
systemctl status n1-ql-restore.service # 核对开机恢复服务已启用
bash /root/n1.sh # 手动跑一遍自愈脚本,确认全部online
tail -n 20 /root/ql/logs/selfheal.log # 查看最近20行日志,确认日志写入正常