监控脚本去重:fail2ban 封禁 IP 仅新增时告警;同步密钥轮换后的 SSH 记录
This commit is contained in:
parent
363caaedd7
commit
4a6477d5b6
3
.workbuddy/memory/2026-09-11.md
Normal file
3
.workbuddy/memory/2026-09-11.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# 2026-09-11 工作日志
|
||||||
|
|
||||||
|
- 22:40 | FIX | 服务器监控脚本告警去重改造:fail2ban 封禁 IP 改为「仅新增封禁推送一次」。**原因**=IP `157.230.37.93` 被 fail2ban 封禁后(bantime 1h),脚本每 10 分钟重复推送 Bark 告警(日志 21:40~22:30 连续 6 条 ALERT)。**根因**=脚本第 3 段逻辑为「Banned IP list 非空即 alert」,无状态记忆。**改法**=新增状态文件 `/var/lib/server-monitor/banned-ips.state`(记录当前已封禁 IP 集合),仅对不在集合中的 IP 告警;状态同步为当前封禁列表,已解封 IP 自动移出(日后再被封视为新事件、可再提醒)。**验证**=真实环境当前封禁「本轮告警=无→静默」;模拟 5 轮全部符合预期(新封→提醒 / 列表不变→静默 / 解封→剔除 / 再封→再提醒 / 全解封→清空);`bash -n` 语法通过。**留档**=脚本备份 `/usr/local/bin/server-monitor.sh.bak.20260911`;本地副本 `.workbuddy/tmp/server-monitor.sh`。**顺带记录**=cron 位置确认为 xxcool crontab(`*/10 * * * * sudo /usr/local/bin/server-monitor.sh`);bantime=1h/findtime=10m/maxretry=5(`/etc/fail2ban/jail.local`);日志 204K 暂无需轮转。
|
||||||
@ -56,11 +56,23 @@
|
|||||||
- DB:`xpcool-mysql` 容器(root 密码见该容器 env),库名 `service`,种子执行顺序 003→004→004b→007(服务器表结构旧,必须先 003 加列;导入用 `sudo sh -c 'docker exec -i xpcool-mysql mysql ... < file'` 宿主机重定向)。
|
- DB:`xpcool-mysql` 容器(root 密码见该容器 env),库名 `service`,种子执行顺序 003→004→004b→007(服务器表结构旧,必须先 003 加列;导入用 `sudo sh -c 'docker exec -i xpcool-mysql mysql ... < file'` 宿主机重定向)。
|
||||||
- 权限映射:admin_menu type=2 行的 path 必须匹配实际路由(现为 `POST /api/service/admin/...`,见 manifest/sql/007_menu_permissions_v3.sql)。
|
- 权限映射:admin_menu type=2 行的 path 必须匹配实际路由(现为 `POST /api/service/admin/...`,见 manifest/sql/007_menu_permissions_v3.sql)。
|
||||||
- 文件下载站(2026-09-03 上线,当晚迁移):`file.xpcool.com` = **FileBrowser Quantum** 容器 `fbq`(`gtstef/filebrowser:stable` v1.5.5,`127.0.0.1:10881→80`,restart unless-stopped,`/data/share:/srv:ro` **只读挂载**,数据卷 `/data/filebrowser-q`:config.yaml+database.db+tmp,属主=镜像 uid1000)+ nginx 反代。**要点**:初始凭据 **admin/admin**(首登后立即改密);配置走 `config.yaml`(sources/cacheDir),管理走网页 UI;API 与原版不兼容(需用户建 long-live token,/swagger 亦需);镜像默认非 root(uid1000);原版 filebrowser(容器 `filebrowser`/10880/db `/data/filebrowser/database`)已于 2026-09-01 归档、现已停用**保留回滚**;Quantum v2.0 beta 权限模型改 per-source(view/download/modify/create/delete)。改密/建只读账号/临时分享(过期+密码+匿名)均在网页 UI。
|
- 文件下载站(2026-09-03 上线,当晚迁移):`file.xpcool.com` = **FileBrowser Quantum** 容器 `fbq`(`gtstef/filebrowser:stable` v1.5.5,`127.0.0.1:10881→80`,restart unless-stopped,`/data/share:/srv:ro` **只读挂载**,数据卷 `/data/filebrowser-q`:config.yaml+database.db+tmp,属主=镜像 uid1000)+ nginx 反代。**要点**:初始凭据 **admin/admin**(首登后立即改密);配置走 `config.yaml`(sources/cacheDir),管理走网页 UI;API 与原版不兼容(需用户建 long-live token,/swagger 亦需);镜像默认非 root(uid1000);原版 filebrowser(容器 `filebrowser`/10880/db `/data/filebrowser/database`)已于 2026-09-01 归档、现已停用**保留回滚**;Quantum v2.0 beta 权限模型改 per-source(view/download/modify/create/delete)。改密/建只读账号/临时分享(过期+密码+匿名)均在网页 UI。
|
||||||
- SSH 凭据/密码不入库,需要时询问用户或查对话记忆;远程批量命令用 paramiko 脚本(`%TEMP%\wb_deploy\ssh_exec.py`,支持第 2 参数超时秒数),docker build 用服务器端 nohup+日志轮询防超时。**(2026-09-03 实测/更新)**:当前登录 = 项目目录 `CentOS.pem`(RSA) → `xxcool@193.112.118.168:22025`(root 被拒;密码登录已禁);安全组按出口 IP 限放 22025(本机出口 1.204.105.116 曾因未放行连不上,80/443 可达);本机旧记录 `~/.ssh/id_ed25519` 与 wb_deploy 脚本**在当前设备不存在**。
|
- SSH 凭据/密码不入库,需要时询问用户或查对话记忆;远程批量命令用 paramiko 脚本(`%TEMP%\wb_deploy\ssh_exec.py`,支持第 2 参数超时秒数),docker build 用服务器端 nohup+日志轮询防超时。**(2026-09-11 密钥轮换后更新)**:当前登录 = 本地 `~/.ssh/xpcool_ed25519`(ed25519,注释 xpcool-server-20260904)→ `xxcool@193.112.118.168:22025`(root 被拒;密码登录已禁);旧 `CentOS.pem`(RSA,对应服务器公钥 `skey-q6pq6jb3`)**已作废并从 authorized_keys 移除**(因私钥误提交 Gitea 事件而轮换),服务器 authorized_keys 另存 2 把钥(`mcp-server-manager@local`、`xxcoolwork@gmail.com`)为其他用途、保留不动;安全组按出口 IP 限放 22025(本机出口 1.204.105.116 曾因未放行连不上,80/443 可达);本机旧记录 `~/.ssh/id_ed25519` 与 wb_deploy 脚本**在当前设备不存在**。
|
||||||
- **服务器安全加固(2026-08-27 完成)**:firewalld 已启用(放行 22025/80/443/3000/2222;cockpit/rpcbind 已停);fail2ban 保护 sshd(port=22025, maxretry5/bantime1h);监控脚本 `/usr/local/bin/server-monitor.sh` + cron 每 10 分钟(日志 `/var/log/server-monitor.log`,Bark 推送配 `/etc/server-monitor.conf` 的 `BARK_URL`);dnf-automatic 每天 06:00 自动安全补丁;SSH 密钥登录已部署(本地 `~/.ssh/id_ed25519`)但用户要求保留密码登录。**坑**:① 腾讯云安全组未放行 3000/2222/8081,Gitea 走 nginx 反代 80/443;② 改 sshd 前必须先 `sshd -t` 预检+备份(曾因脏行导致 SSH 断开);③ EPEL 源需腾讯云镜像(`mirrors.cloud.tencent.com/epel`)且 baseurl 必须手动写全;④ CentOS9 的 fail2ban 在 EPEL。**⑤(2026-09-04 新坑)腾讯云改带宽若切计费模式会触发实例冷重启,重启后 firewalld 自定义端口规则曾丢失致 SSH 22025 失联(80/443 正常)——firewall-cmd 放行务必带 `--permanent`,且实例冷重启后要复查 22025 放行**。另:带宽 3M→5M 已升级(2026-09-04,实测 619KB/s 满速)。
|
- **服务器安全加固(2026-08-27 完成)**:firewalld 已启用(放行 22025/80/443/3000/2222;cockpit/rpcbind 已停);fail2ban 保护 sshd(port=22025, maxretry5/bantime1h);监控脚本 `/usr/local/bin/server-monitor.sh` + cron 每 10 分钟(调度在 **xxcool 的 crontab**:`*/10 * * * * sudo /usr/local/bin/server-monitor.sh`;日志 `/var/log/server-monitor.log`,Bark 推送配 `/etc/server-monitor.conf` 的 `BARK_URL`);**fail2ban 封禁告警已去重(2026-09-11)**:状态文件 `/var/lib/server-monitor/banned-ips.state` 存当前封禁 IP 集合,仅对**新增**封禁推送一次;已解封 IP 自动移出状态,日后再被封视为新事件可再提醒(脚本备份 `server-monitor.sh.bak.20260911`);dnf-automatic 每天 06:00 自动安全补丁;SSH 密钥登录已部署(本地 `~/.ssh/id_ed25519`)但用户要求保留密码登录。**坑**:① 腾讯云安全组未放行 3000/2222/8081,Gitea 走 nginx 反代 80/443;② 改 sshd 前必须先 `sshd -t` 预检+备份(曾因脏行导致 SSH 断开);③ EPEL 源需腾讯云镜像(`mirrors.cloud.tencent.com/epel`)且 baseurl 必须手动写全;④ CentOS9 的 fail2ban 在 EPEL。**⑤(2026-09-04 新坑)腾讯云改带宽若切计费模式会触发实例冷重启,重启后 firewalld 自定义端口规则曾丢失致 SSH 22025 失联(80/443 正常)——firewall-cmd 放行务必带 `--permanent`,且实例冷重启后要复查 22025 放行**。另:带宽 3M→5M 已升级(2026-09-04,实测 619KB/s 满速)。
|
||||||
- 本地 pnpm 修复(2026-08-27 升级为 wrapper 方案):根因=本环境 MSYS 路径转换故障,把 `/c/...` 参数转成 `E:\c\...`(多 `E:\c\` 前缀)致 corepack shim 与 node 都找不到 pnpm.js。**已建持久 wrapper**:`~/.workbuddy/binaries/node/bin/pnpm`(内容:`export NODE_OPTIONS=--experimental-sqlite; exec "<托管node.exe>" "C:/Users/ybtdevxxl/.workbuddy/binaries/node/workspace/node_modules/pnpm/bin/pnpm.cjs" "$@"`,关键=给 node 传 Windows 风格路径 `C:/...` 规避 MSYS 转换)。**提交/构建前需 `PATH="~/.workbuddy/binaries/node/bin:$PATH"` 前置**,pre-commit 钩子(lefthook oxlint/oxfmt/eslint/stylelint/checkType)即可正常运行;否则钩子崩 → 只能 --no-verify。已验证 pnpm 11.16.0 + `pnpm exec oxlint` 1.79.0。
|
- 本地 pnpm 修复(2026-08-27 升级为 wrapper 方案):根因=本环境 MSYS 路径转换故障,把 `/c/...` 参数转成 `E:\c\...`(多 `E:\c\` 前缀)致 corepack shim 与 node 都找不到 pnpm.js。**已建持久 wrapper**:`~/.workbuddy/binaries/node/bin/pnpm`(内容:`export NODE_OPTIONS=--experimental-sqlite; exec "<托管node.exe>" "C:/Users/ybtdevxxl/.workbuddy/binaries/node/workspace/node_modules/pnpm/bin/pnpm.cjs" "$@"`,关键=给 node 传 Windows 风格路径 `C:/...` 规避 MSYS 转换)。**提交/构建前需 `PATH="~/.workbuddy/binaries/node/bin:$PATH"` 前置**,pre-commit 钩子(lefthook oxlint/oxfmt/eslint/stylelint/checkType)即可正常运行;否则钩子崩 → 只能 --no-verify。已验证 pnpm 11.16.0 + `pnpm exec oxlint` 1.79.0。
|
||||||
|
|
||||||
## 日常同步习惯(建议)
|
## 日常同步习惯(建议)
|
||||||
- 每次工作结束:在各改动项目 `git add + commit`,本工作空间同样提交(规则/日志变化时)。**只 commit、不要 push**(2026-09-01 用户明确:推送由用户自行完成,AI 无需代为 push,避免凭据交互失败)。
|
- 每次工作结束:在各改动项目 `git add + commit`,本工作空间同样提交(规则/日志变化时)。**只 commit、不要 push**(2026-09-01 用户明确:推送由用户自行完成,AI 无需代为 push,避免凭据交互失败)。
|
||||||
- **验证分工(2026-09-01 用户明确)**:AI 能做的验证(类型检查、构建、单元/二进制测试、自动化冒烟)照做;**AI 不方便验证的部分(真实浏览器交互体验、视觉效果、下载弹窗等)直接交给用户手动验证**,不要反复折腾自动化工具。
|
- **验证分工(2026-09-01 用户明确)**:AI 能做的验证(类型检查、构建、单元/二进制测试、自动化冒烟)照做;**AI 不方便验证的部分(真实浏览器交互体验、视觉效果、下载弹窗等)直接交给用户手动验证**,不要反复折腾自动化工具。
|
||||||
- 换设备开工前:先 `git pull` 各仓库,保证规则与记录最新。
|
- 换设备开工前:先 `git pull` 各仓库,保证规则与记录最新。
|
||||||
|
|
||||||
|
## 本机 Windows/PowerShell 工具链坑位(2026-09-10 实测,写脚本必看)
|
||||||
|
- **PowerShell 工具不回显 stdout**:exit code 正常但看不到输出;必须把结果 `Out-File` 落盘,再用 Read 读取。Bash 里调 `powershell.exe` 会被安全策略拒绝。
|
||||||
|
- **`[byte] -shl 8` 会溢出归零**(结果仍是 byte 宽度)。字节拼 16 位整数必须先 `[int]` 转换,否则高位丢失(剪贴板/协议解析类代码的高频坑)。
|
||||||
|
- **给 .ps1 加 BOM 绝不能用 `Get-Content -Raw` + `Out-File`**:本机默认按 GBK 解码 UTF-8 文件,中文会被永久破坏且不可逆。正确做法是字节级前置 `EF BB BF`(`[System.IO.File]::WriteAllBytes`)。
|
||||||
|
- **PATH 被裁剪**:脚本里直接写 `ping` / `netsh` 会报"无法识别",须用 `$env:SystemRoot\System32\xxx.exe` 绝对路径;且不要写 `& $exe args 2>$null | Out-String`(会报"无法在管道中间运行文档"),改用 `$out = & $exe args` 再 `@($out) -join ' '`。
|
||||||
|
- **PowerShell 变量名不区分大小写**:`$R` 与循环变量 `$r` 是同一个变量,会导致集合被静默覆盖。
|
||||||
|
- **后台监听的进程可能杀不掉**(Stop-Process / taskkill 均无效),换端口重新起更省事。
|
||||||
|
- **防火墙规则按可执行文件路径匹配**:一批 node.exe 规则指向已不存在的路径(失效);目前 `C:\Users\xxl\AppData\Roaming\nvm\v23.0.0\node.exe` 有有效的 Public 档「TCP+UDP 任意端口」入站放行规则——需要做**免提权的入站可达性测试**时就用它监听。
|
||||||
|
- 测入站**不要用 ping**:Windows 默认拦 ICMPv6/ICMP 入站,必须用 TCP 端口测试。
|
||||||
|
- 国内测速:Cloudflare 端点不可用;用 **Ookla 官方 CLI**(`install.speedtest.net` 可直连下载)。
|
||||||
|
- 本机网络备注:家里的宽带是**运营商级 NAT(CGNAT,第 2 跳 100.64.0.1)**,无公网 IPv4;但有**公网 IPv6**(`240e:338:263:3600::/64`)。以太网被判定为 Public 网络档。
|
||||||
|
|||||||
99
.workbuddy/tmp/server-monitor.sh
Normal file
99
.workbuddy/tmp/server-monitor.sh
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# ============================================================
|
||||||
|
# 服务器安全与资源监控脚本(轻量版)
|
||||||
|
# 功能:
|
||||||
|
# 1. SSH 爆破检测(10分钟内失败认证超阈值)
|
||||||
|
# 2. 新增 SSH 登录记录
|
||||||
|
# 3. fail2ban 新增封禁 IP(同一 IP 仅提醒一次;解封后再被封会重新提醒)
|
||||||
|
# 4. 根分区磁盘、内存、系统负载告警
|
||||||
|
# 5. 未登记的公网监听端口变化告警(暴露面漂移)
|
||||||
|
# 告警方式:写日志 /var/log/server-monitor.log + 可选 Bark 推送
|
||||||
|
# Bark 配置:/etc/server-monitor.conf 中设置 BARK_URL 后生效
|
||||||
|
# 部署:/usr/local/bin/server-monitor.sh,配合 cron 每 10 分钟执行
|
||||||
|
# 去重状态:/var/lib/server-monitor/banned-ips.state(存放当前已封禁 IP 集合)
|
||||||
|
# ============================================================
|
||||||
|
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||||
|
|
||||||
|
# 读取可选配置(BARK_URL)
|
||||||
|
CONF=/etc/server-monitor.conf
|
||||||
|
[ -f "$CONF" ] && . "$CONF"
|
||||||
|
|
||||||
|
LOG=/var/log/server-monitor.log
|
||||||
|
NOW=$(date '+%F %T')
|
||||||
|
ALERT=""
|
||||||
|
BARK_URL=${BARK_URL:-}
|
||||||
|
|
||||||
|
alert() { ALERT="${ALERT}$1
|
||||||
|
"; }
|
||||||
|
|
||||||
|
# ---------- 1) SSH 爆破检测(近10分钟失败认证) ----------
|
||||||
|
FAILED=$(sudo journalctl _SYSTEMD_UNIT=sshd.service --since "10 minutes ago" 2>/dev/null | grep -ci "Failed password")
|
||||||
|
if [ "${FAILED:-0}" -ge 10 ]; then
|
||||||
|
alert "SSH爆破风险: 10分钟内 ${FAILED} 次密码失败"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---------- 2) 新增成功登录 ----------
|
||||||
|
NEWLOGIN=$(sudo journalctl _SYSTEMD_UNIT=sshd.service --since "10 minutes ago" 2>/dev/null | grep -iE "Accepted (password|publickey)" | tail -3)
|
||||||
|
[ -n "$NEWLOGIN" ] && alert "新增SSH登录:
|
||||||
|
$NEWLOGIN"
|
||||||
|
|
||||||
|
# ---------- 3) fail2ban 新增封禁 IP(同一 IP 只提醒一次) ----------
|
||||||
|
# 状态文件记录"当前已封禁的 IP 集合":
|
||||||
|
# - 当前封禁列表里出现状态文件中没有的 IP → 新封禁,告警一次
|
||||||
|
# - 已解封的 IP 会从状态文件移除,若日后再次被封,视为新事件可再提醒
|
||||||
|
STATE_DIR=/var/lib/server-monitor
|
||||||
|
STATE_FILE=$STATE_DIR/banned-ips.state
|
||||||
|
mkdir -p "$STATE_DIR" 2>/dev/null
|
||||||
|
touch "$STATE_FILE" 2>/dev/null
|
||||||
|
|
||||||
|
CUR_BANNED=$(sudo fail2ban-client status sshd 2>/dev/null | sed -n 's/.*Banned IP list:[[:space:]]*//p')
|
||||||
|
NEW_BANNED=""
|
||||||
|
for ip in $CUR_BANNED; do
|
||||||
|
grep -qxF "$ip" "$STATE_FILE" 2>/dev/null || NEW_BANNED="${NEW_BANNED} ${ip}"
|
||||||
|
done
|
||||||
|
# 状态文件同步为当前封禁集合(自动剔除已解封 IP)
|
||||||
|
if [ -n "$CUR_BANNED" ]; then
|
||||||
|
printf '%s\n' $CUR_BANNED > "$STATE_FILE"
|
||||||
|
else
|
||||||
|
: > "$STATE_FILE"
|
||||||
|
fi
|
||||||
|
[ -n "$NEW_BANNED" ] && alert "fail2ban 新封禁IP:${NEW_BANNED}(已自动封禁,无需处理)"
|
||||||
|
|
||||||
|
# ---------- 4) 根分区磁盘 ----------
|
||||||
|
DF=$(df -P / 2>/dev/null | tail -1 | awk '{print $5}' | tr -d '%')
|
||||||
|
[ "${DF:-0}" -ge 85 ] && alert "根分区使用率已达 ${DF}%"
|
||||||
|
|
||||||
|
# ---------- 5) 内存可用比 ----------
|
||||||
|
MEMFREE=$(free -m 2>/dev/null | awk '/Mem:/{print int($7/$2*100)}')
|
||||||
|
[ "${MEMFREE:-100}" -le 10 ] && alert "内存可用仅剩 ${MEMFREE}%"
|
||||||
|
|
||||||
|
# ---------- 6) 系统负载 ----------
|
||||||
|
LOAD=$(awk '{print int($1)}' /proc/loadavg 2>/dev/null)
|
||||||
|
[ "${LOAD:-0}" -ge 4 ] && alert "系统负载过高: $LOAD"
|
||||||
|
|
||||||
|
# ---------- 7) 公网监听端口漂移检测 ----------
|
||||||
|
# 基线:当前业务期望对外监听的端口
|
||||||
|
# SSH(22025) / Nginx(80,443) / Gitea(3000,2222) / Bark(8081, docker映射,防火墙已挡公网)
|
||||||
|
BASE_PORTS="22025 80 443 3000 2222 8081"
|
||||||
|
NOW_PORTS=$(ss -tlnH 2>/dev/null | awk '{print $4}' | grep -vE '^127\.' | grep -oE ':[0-9]+$' | tr -d ':' | sort -u)
|
||||||
|
EXTRA=""
|
||||||
|
for p in $NOW_PORTS; do
|
||||||
|
case " $BASE_PORTS " in
|
||||||
|
*" $p "*) ;;
|
||||||
|
*) EXTRA="${EXTRA} ${p}" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
[ -n "$EXTRA" ] && alert "发现未登记的公网监听端口:${EXTRA}(请核查是否为新增服务)"
|
||||||
|
|
||||||
|
# ---------- 输出:日志 + Bark 推送 ----------
|
||||||
|
if [ -n "$ALERT" ]; then
|
||||||
|
echo -e "$NOW [ALERT] $HOSTNAME
|
||||||
|
$ALERT" >> "$LOG"
|
||||||
|
if [ -n "$BARK_URL" ]; then
|
||||||
|
# Bark 自建服务端(路径式 key):POST JSON 到 /<device_key>
|
||||||
|
BODY=$(printf '%s' "$ALERT" | tr '\n' ' ' | tr '"' "'")
|
||||||
|
curl -s -m 8 -X POST "$BARK_URL" -H "Content-Type: application/json" -d "{\"title\":\"服务器告警\",\"body\":\"$BODY\"}" >/dev/null 2>&1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "$NOW [OK] 登录正常 负载${LOAD:-?} 根分区${DF:-?}% 内存可用${MEMFREE:-?}%" >> "$LOG"
|
||||||
|
fi
|
||||||
Loading…
Reference in New Issue
Block a user