一,从零搭建守护环境
1,在 root 目录下新建 all-n1-install.sh 文件,内容如下
#!/bin/bashset-eecho"==== N1盒子自愈全套【无jq版·重新生成守护环境】 ===="# 不再安装jq依赖echo"[1/7] 跳过jq依赖,使用pm2 id返回码判断状态"# 2.生成 /root/n1.shecho"[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_EOFchmod+x /root/n1.sh# 3.生成systemd开机恢复服务:宿主机pm2 resurrect + 容器pm2 resurrectecho"[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_EOFsystemctl daemon-reload systemctlenablen1-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_EOFcrontab/root/tmp_cron.txtrm-f/root/tmp_cron.txt# 5.生成日志清理脚本 ql‑scrub.shecho"[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_EOFchmod+x /root/ql-scrub.sh# 6.清理旧锁、创建目录echo"[6/7] 清理旧锁文件,创建目录"rm-f/root/.n1-deploy/n1.lockmkdir-p/root/ql/logsmkdir-p/root/.pm2/logs# 7.首次执行测试echo"[7/7] 执行首次巡检测试"bash/root/n1.shecho""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,脚本跑完校验
crontab-lsystemctl status n1-ql-restore.servicebash/root/n1.sh只会输出宿主机三条 ✅ xxx online,容器部分不会输出任何内容
使用场景:N1 彻底重装 Armbian 从零部署守护环境就运行这个脚本
二,备份守护环境
1,确认全部 5 个文件都存在
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上面命令不报错,列出全部文件,就可以打包
crontab-l>/root/cron_backup.txt pm2 ecosystem>/root/pm2_ecosystem_backup.jsontar-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.json2,查看包里文件清单确认
tar-ztvf/root/n1_backup.tar.gz3,能看到 5 个文件就全部正常
root/n1.sh root/ql-scrub.sh etc/systemd/system/n1-ql-restore.service root/cron_backup.txt root/pm2_ecosystem_backup.json4,看备份包大小
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 目录下,然后依次执行
tar-zxvf/root/n1_backup.tar.gz-C/chmod+x /root/n1.shchmod+x /root/ql-scrub.shcrontab/root/cron_backup.txt systemctl daemon-reload systemctlenablen1-ql-restore.service pm2 start /root/pm2_ecosystem_backup.json pm2 savemkdir-p/root/ql/logsbash/root/n1.sh执行完最后一条 bash /root/n1.sh,输出三条 ✅ xxx online,代表整套自愈恢复成功
4,恢复完成后校验
#1. 确认定时任务加载正确crontab-l#2. 确认开机自启服务已经启用systemctl status n1-ql-restore.service#3. 再手动跑一次自愈脚本确认状态bash/root/n1.sh✅预期输出
===N1 夜维 xxxx===✅ 腿[go-cqhttp]online ✅ 腿[sillyGirl]online ✅ 腿[xdd]online3/30/0 ✅ 磁盘 xx%===巡检结束===#4. 看实时日志是否正常写入tail-n20/root/ql/logs/selfheal.log5,可选,整机重启测试(最重要的验证)
reboot盒子重启,SSH 重新登录进来
#重启登录后检查pm2 listbash/root/n1.sh重启后 pm2 三个进程能自动拉起来、自愈脚本识别全部 online,代表完整恢复成功
后台自动工作:
①每 5 分钟自动运行 /root/n1.sh,进程挂掉自动重启
②凌晨 3:05 运行日志清理脚本,防止日志占磁盘
③开机 systemd 服务自动恢复 pm2 进程
6,日常排查命令
crontab-l# 核对定时任务systemctl status n1-ql-restore.service# 核对开机恢复服务已启用bash/root/n1.sh# 手动跑一遍自愈脚本,确认全部onlinetail-n20/root/ql/logs/selfheal.log# 查看最近20行日志,确认日志写入正常