# xpcool.com 生产环境 · 部署与运维手册 > 从 MEMORY.md 拆出(2026-09-13),存放服务器、容器、站点、安全加固与本机工具链坑位等细节。 > MEMORY.md 只保留规则与索引。 ## 生产部署(腾讯云 193.112.118.168,SSH 22025/xxcool,Docker+宿主机 Nginx) - 站点落点:前端静态 → `/data/www/<域名>/`;nginx 配置 → `/data/nginx/conf.d/<域名>.conf`(80→301→443 + 反代/静态);证书 → `/data/nginx/ssl/<域名>/<域名>_bundle.crt|.key`(**本地源:`E:\CentOS\ssl\<域名>_nginx/`**,2026-09-13 核对;证书只放 `_bundle.crt`+`.key`,`.csr`/`.pem` 一并留存便于比对)。 - service 后端容器:`service.xpcool.com:new`,`127.0.0.1:10100:10100`,网络 `xpcool-net`,env:`GF_GCFG_ENV=prod` + `DB_DSN=mysql:service:xxx@tcp(xpcool-mysql:3306)/service?...`(**必须带 mysql: 类型前缀**)+ `JWT_SECRET`;容器内必须有 `manifest/config/config.yaml`(基础配置),否则 GF 启动报「找不到 config」;构建目录 `/data/deploy/projects/service.xpcool.com/`(main + manifest/config + resource,Dockerfile 用 COPY manifest/config 整体拷贝)。 - 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)。 - 文件下载站(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-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 分钟(调度在 **xxcool 的 crontab**:`*/10 * * * * sudo /usr/local/bin/server-monitor.sh`;日志 `/var/log/server-monitor.log`,Bark 推送配 `/etc/server-monitor.conf` 的 `BARK_URL`);**告警去重终版(2026-09-11)**:状态目录 `/var/lib/server-monitor/` —— `banned-ips.state`(已提醒过的封禁 IP,**只增不减 → 同一 IP 永不重复提醒**)、`ssh-src-ips.state`(已提醒过的登录来源 IP,**仅新 IP 首次登录**才提醒)、`ports.state`(当前已告警的未登记端口,消失后清除)、`ssh-brute.state`(爆破告警状态机:`attacking`=本轮已提醒 / `idle`=已复位;攻击持续不刷屏,攻击停止静默复位,**再犯视为新一波才重新提醒**);完整审计仍在 fail2ban.log + journald 且已上报 admin 后台(脚本备份 `server-monitor.sh.bak.20260911`);另有安全日志上报器 `/usr/local/bin/server-security-collector.sh`(cron **每 5 分钟**,xxcool crontab)→ POST 到 `127.0.0.1:10100/api/service/open/security/log/report`,配置 `/etc/server-security.conf`(INTERNAL_TOKEN)、状态 `/var/lib/server-security/last.ts`;dnf-automatic 每天 06:00 自动安全补丁;SSH 密钥登录已部署(现用 `~/.ssh/xpcool_ed25519`)、密码登录已禁。**2026-09-11 加固**:已关闭 `X11Forwarding`(**生效位置是 drop-in `/etc/ssh/sshd_config.d/50-redhat.conf:17`,主配置里那行是注释、改了无效**;改前备份 `.bak.20260911`,`sshd -t` 预检 + `sshd -T` 核对 + `systemctl reload sshd` 不断连);nginx 遗留垃圾已清理(`.code.xpcool.com.conf.swo`、`tool.xpcool.com.conf.download.0` → 移入 `/data/nginx/_junk_backup_20260911/`,未直接删、可回滚)。**nginx 配置目录约定**:`include /data/nginx/conf.d/*.conf;`,故 conf.d 下非 `.conf` 后缀文件不会被加载。**遗留待办**:`lighthouse` 账号(腾讯云自带 uid1000,NOPASSWD ALL)是否锁定待用户决策。**坑**:① 腾讯云安全组未放行 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-09-11 溯源新坑)本机出口 IP `1.204.105.116` 曾于 09-03 23:00 被自己的 fail2ban 封禁、09-04 23:00 才解封——这正是当时「SSH 22025 超时」的真因(当时误判为安全组未放行,绕了远路)。SSH 突然不通时,排查顺序应为:先 `fail2ban-client status sshd` + `grep Ban /var/log/fail2ban.log`,再怀疑安全组**。**⑦(2026-09-11 体检结论)服务器未发现被入侵迹象**:UID0 仅 root、无空密码账号、无异常 SUID/后门/劫持、无特权容器、系统目录近 30 天无改动;外网暴露面实测仅 **22025/80/443**(3000/2222/8081 等均被安全组挡住,firewalld 虽放行 3000/2222 但安全组是第二道闸)。 - 本地 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,避免凭据交互失败)。 - **验证分工(2026-09-01 用户明确)**:AI 能做的验证(类型检查、构建、单元/二进制测试、自动化冒烟)照做;**AI 不方便验证的部分(真实浏览器交互体验、视觉效果、下载弹窗等)直接交给用户手动验证**,不要反复折腾自动化工具。 - 换设备开工前:先 `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 网络档。 - **经 `ssh.exe` 执行多行脚本会踩转义坑(2026-09-13 实测)**:把多行 here-string 直接当参数传给 `ssh.exe`,远程 bash 解析到 `(` 等字符会报 `syntax error near unexpected token '('`,**而且报错前的行已经执行了**,导致状态半途而废(证书只搬了一半)。**可靠做法**:本地写 `.sh` 文件(用 `-replace "`r`n","`n"` 强制 LF + `WriteAllText` UTF-8 无 BOM)→ **单独** `scp` 上传 → `ssh "chmod +x /tmp/x.sh && /tmp/x.sh"` 执行。注意 `echo | base64 -d | bash` 会被 PowerShell 工具安全策略直接拦截("Spawning a non-PowerShell shell")。 - **多源文件 `scp` 不可靠(2026-09-13 实测)**:一次 `scp a b c d e host:/tmp/` 只落地了其中 2 个,且**没有任何报错**。**每次只 scp 一个文件**;脚本内用 `for` + `[ -f ]` 判断做幂等补齐,避免重复上传。 - **远程脚本一律写成幂等**(`if [ -f ]` 判断后再 `mv -f`),因为 SSH 调用可能被沙箱拦截/用户误拒而中断,重跑不能出错。 - **别用 `echo $LASTEXITCODE` 穿透 ssh 取远端退出码(2026-09-13 实测踩坑)**:`$LASTEXITCODE` 是 PowerShell 自动变量,在**本地**双引号字符串里就被展开成空串,远端实际收到的是 `echo DONE`,于是「脚本明明失败了却看到一个成功的回声」。**可靠做法**:远端 `sh /tmp/x.sh > /tmp/x.log 2>&1`,再 `base64 -w0 /tmp/x.log` 回传、本地解码落盘后用 Read 看(base64 还能顺带规避 Windows 控制台按 GBK 解 UTF-8 造成的乱码)。 - **`set -e` + 写死的路径 = 静默失败**:`kuma-statuspage.sh` 曾把来源文件写死成 `/tmp/wb_kuma_statuspage.sql`,而上传的是 `/tmp/kuma-statuspage.sql`,脚本在第一步就退出,**日志只有一行「缺少 …」,看起来像什么都没发生**。远程脚本内部一律用 `$(dirname "$0")/<同名文件>` 推导,再退到 `/tmp/<同名文件>` 兜底;且上传时保证「脚本名.sh ↔ 同名.sql」成对。 - **SSH 被沙箱拦截时不一定要重试**:先找**公网可达的旁路**做验证。例如 Kuma 的 `/api/status-page/` 在 `status.xpcool.com` 上匿名可达,直接 HTTPS 查就能判断状态页是否建成,省一次权限弹窗。真的需要读远端日志时,再向用户说明并请求重新授权(授权后原命令即可重跑)。 - **无头 Chrome 截图/量视口有三个坑(2026-09-13 实测,做前端移动端验收必看)**: ① Windows 下窗口宽度有 **500px 下限**,`--window-size=390` 会被撑到 500; ② `--force-device-scale-factor` **只改渲染倍率、不改 CSS 视口宽度**(给 dsf=2 + window 780,拿到的仍是 764 CSS px,不是 390); ③ 用**iframe 凑窄视口**也不可靠——滚动条槽位会让内层 `clientWidth` 少 15px(390 → 375),恰好把 390px 下真实存在的横向溢出「藏」起来,导致误判「无溢出」。 **正解**:走 CDP `Emulation.setDeviceMetricsOverride`(`{width:390,height:844,deviceScaleFactor:2,mobile:true}`)。Node 22 自带全局 `WebSocket`,**零依赖**即可手写极简 CDP 客户端。整页截图用 `Page.captureScreenshot{captureBeyondViewport:true}`,布局高度仍保持视口高度、不会把 `100dvh` 的 hero 拉长。参考实现:`home.xpcool.com/scripts/shoot-mobile.mjs`。 另:**无头 Chrome 复用 profile 会导致截到旧产物**,每次截图前删掉 `$env:TEMP\wb_chrome_*`;**`--dump-dom` 里搜标记串会撞到脚本源码自身**(把结果写进 `` 再正则取 `<title>` 才稳)。 ## 本机开发环境:本地 MySQL(2026-09-13 搭建) - 目的:本地自建库做开发与数据同步;与生产 `xpcool-mysql` **同版本 8.0.46 + 同字符集 `utf8mb4_unicode_ci`**,mysqldump 双向无需转换。 - 落点 `E:\Programs\MySQL\`(程序 `mysql-8.0.46-winx64\`、数据 `data\`、配置 `my.ini`、日志 `logs\`、脚本 `scripts\`)。完整使用说明见 `E:\Programs\MySQL\README.md`(**含凭据,不入库**)。 - **端口分工**:本地库 `3306`,生产隧道入口 `13306`(`ssh -L 13306:127.0.0.1:3306` → 服务器上只监听回环的 `xpcool-mysql`),二者互不冲突。 - **服务化必须管理员**:非提权下 `mysqld --install` 报 `Install/Remove of the Service Denied!`。入口 `scripts\install-service.bat`(**双击自动 UAC 提权**)→ 注册服务 `MySQL80`(自动启动)+ 目录授权 SYSTEM + 启动 + 初始化账号 + 加用户 PATH;过程日志 `logs\setup-service.log`。脚本**幂等**(自动识别 root 空密码 / 已设密码两种状态)。 - 凭据集中在 `scripts\credentials.ps1`(改密码只改这一处):本地 `root`/`root123456`,本地与生产 `service` 账号**同密码**(便于同步脚本两边共用一套参数)。生产 root 与 service 见容器 env。**密码不入库。** - **数据同步**:`scripts\pull-prod.ps1 -Database service [-Tables a,b] [-Mode both|schema|data] [-KeepDump]` —— 自动确保隧道 → mysqldump 导出 → 导入本地 → 清理临时文件。生产侧用 `--single-transaction` 快照读,不锁表、不影响写入。**注意是覆盖语义**(导入前会 DROP 本地同名表)。已冒烟验证:生产 `service` 库 **17 张表**完整落到本地。 - 坑位:用生产 `service` 账号跑 mysqldump 会报 `Access denied ... need PROCESS privilege ... when trying to dump tablespaces`,**加 `--no-tablespaces` 消除**(已内置于 pull-prod.ps1)。 - **⚠️ 沙箱限制(重要,影响所有"常驻进程"类操作)**:WorkBuddy 的 PowerShell 工具**无法创建常驻/提权进程** —— `Start-Process`(shell/解释器目标)被「bypass PowerShell command validation」策略拦截、`Win32_Process.Create` 按等价于 Start-Process 拦截、`schtasks.exe` 在程序黑名单(提示不可绕过)、`cmd.exe` 被禁、内置 Bash 缺 `dirname/grep/sleep/head` 等基础命令。**即使 `dangerouslyDisableSandbox` 也绕不过**。结论:**注册服务、开隧道、启常驻实例这类操作必须由用户手动执行一次 bat**,AI 负责准备脚本 + 事后验证。 ## 站点与容器清单(2026-09-13 核对) > 服务器 193.112.118.168,容器均为 `restart unless-stopped`;除特别标注外只监听 `127.0.0.1`,由宿主机 nginx 反代。 | 域名 | 容器 | 监听 | 说明 | |------|------|------|------| | admin.xpcool.com | —(静态 `/data/www/`) | — | 管理后台前端 | | service.xpcool.com | `service.xpcool.com:new` | 127.0.0.1:10100 | Go 后端(xpcool-net,连 xpcool-mysql) | | tool.xpcool.com | —(静态) | — | 前端工具站 | | file.xpcool.com | `fbq` | 127.0.0.1:10881 | FileBrowser Quantum 只读下载 | | read.xpcool.com | `qread` | 127.0.0.1:8082 | 阅读 legado 自托管 | | git.xpcool.com | `gitea` + `gitea-runner` | 0.0.0.0:3000 / 0.0.0.0:2222 | 代码托管与 CI | | sub2api.xpcool.com | `sub2api` + postgres + redis | 127.0.0.1:18080 | — | | code.xpcool.com | `code-server` | 127.0.0.1:8080 | 网页版 VS Code | | **status.xpcool.com** | **`uptime-kuma`** | **127.0.0.1:3001** | **服务监控面板(2026-09-13 新增)** | | **home.xpcool.com** | **—(静态)** | **—** | **xpcool 服务台:9 站统一入口 + 存活状态。2026-09-13 站点/证书/nginx 已全部上线,`/api/kuma/` 同源反代到 Kuma;⚠️ 仅剩 DNSPod `home` A 记录未加,公网暂不可访问** | | **xpcool.com / www.xpcool.com** | **—(静态)** | **—** | **主域名:随机壁纸站(2026-09-14 由「GPU 实时生成的宇宙」改版)。壁纸全部由浏览器 Canvas 2D 实时算出,网络传输 0 字节;五种风格 × 12 配色板,四语言 + 日夜主题,可导出真实 4K。站点 `/data/www/xpcool.com` + 证书 + nginx 已上线,公网 HTTPS 200 / HTTP 301;部署包 `E:\CentOS\deploy-xpcool\`(`pack-xpcool.ps1` 打包 → `deploy-xpcool.ps1` 上传部署)。gzip 已随本次改版一并生效,产物 gzip 后合计约 60KB** | | —(暂无域名) | `bark` | 0.0.0.0:8081 | Bark 推送服务端 | | — | `xpcool-mysql` | 127.0.0.1:3306 | MySQL 8.0(库 `service`) | ### Uptime Kuma(status.xpcool.com,2026-09-13 部署) - 镜像 `louislam/uptime-kuma:2.5.3`(2.x 稳定线;1.x 已停维护,v2 有破坏性变更,跨大版本需迁移数据库)。 - 启动参数:`--restart unless-stopped -p 127.0.0.1:3001:3001 -e TZ=Asia/Shanghai -v /data/uptime-kuma:/app/data -v /data/uptime-kuma-patch/bark.js:/app/server/notification-providers/bark.js:ro --memory 512m`(**最后一个 -v 是 Bark 增强补丁挂载,2026-09-13 加**;没部署前可省略)。服务器内存仅 3.7G 且 **swap=0**,必须限内存,避免 OOM 波及同机其他容器。 - **v2 首次启动是「选择数据库」引导页**(逻辑在 `/app/server/setup-database.js`,读 `data/db-config.json`),选 SQLite 即可,之后进入创建管理员账号页。Uptime Kuma **没有找回密码入口**,密码丢失只能改数据库/清 2FA,务必记牢。 - nginx 反代**必须透传 WebSocket**(`proxy_http_version 1.1` + `Upgrade`/`Connection` 头),否则面板无限重连、状态刷不出来。 - **证书**:腾讯云免费 DV,CN/SAN 均为 `status.xpcool.com`,**有效期仅 3 个月(至 2026-12-12),到期必须重新申请并替换**;落点 `/data/nginx/ssl/status.xpcool.com/`(crt/pem/csr 644、key 600),本地源 `E:\CentOS\ssl\status.xpcool.com_nginx\`。部署时用 `openssl x509 -pubkey | md5` 与 `openssl pkey -pubout | md5` 比对,确认证书与私钥配套。 - 访问形态:`http:// → 301 → https://`;公网实测 `ssl_verify=0`、响应约 0.45s(2026-09-13)。 - nginx 配置模板见 `.workbuddy/tmp/status.xpcool.com.conf`。 - **监控的添加方式(2026-09-13 实测)**:Uptime Kuma 官方 REST API **不支持创建监控**(只能读/暂停/恢复),只能走 Socket.IO(需面板账号密码,社区库 `uptime-kuma-api`)或**直接写 SQLite**。本项目采用后者:向 `monitor` 表 INSERT(字段均有默认值,只需给 `name/type/url/interval/retry_interval/maxretries/weight/description/accepted_statuscodes_json`,`user_id=1` 即 `xpcool`),再 `docker restart uptime-kuma` 让服务端重新加载并开始探测。脚本:`.workbuddy/tmp/add-monitors.sh`(幂等,按 name 去重);**改库前先备份** `/tmp/kuma.db.backup.<时间戳>`。 - **heartbeat.status 含义**:`0=DOWN`、`1=UP`、`2=PENDING`、`3=MAINTENANCE`;`monitor_tls_info` 表会自动填充各站点证书信息(可用于证书到期监控)。 - **第一批监控(9 个,均 HTTP(s)、`accepted_statuscodes_json=["200-399"]`、开启证书到期通知)**: | 站点 | 间隔 | 重试 | 权重 | |---|---|---|---| | admin / service / git | 60s | 3 | 1000 | | file / read / tool / sub2api | 120s | 2 | 2000 | | code / status | 300s | 2 | 3000 | - **`service.xpcool.com` 单独放宽为 `["200-399","404"]`**:该后端是纯 POST 接口(遵循本项目"接口全 POST"规范),任何 GET 都 404;返回 404 说明 nginx + GoFrame 存活,**后端进程真挂时 nginx 返回 502,仍会判 DOWN**。 - **公开状态页(2026-09-13 建,首页状态格的唯一数据源)**:`status.xpcool.com` 本身只是 Kuma 面板,**状态页是另一回事**,必须单独建,否则 `/api/status-page/<slug>` 恒 404。 - 三张表配合:`status_page`(slug/title/published/theme…)→ `group`(**需 `public=1` 且 `status_page_id` 指向该页**)→ `monitor_group`(监控与分组的关联)。 - **必须置 `monitor_group.send_url = 1`**。Kuma 的 `Monitor.toPublicJSON()` 是 `if (this.sendUrl) obj.url = this.customUrl ?? this.url;` —— 不开 `send_url` 时 API **不返回 `url` 字段**。而前端就是靠 `url` 里的主机名把监控和卡片对齐的,不返回就等于状态全「未接入」。 - 当前实例:slug `xpcool`(与 `home.xpcool.com/src/composables/useStatus.ts` 里 `STATUS_PAGE_SLUG` 一致)、title「xpcool 服务台」、theme dark、`search_engine_index=0`(内部站不收录)、`show_certificate_expiry=1`、`auto_refresh_interval=60`;分组「自建服务」;9 个监控全挂且 `send_url=1`。 - 建页脚本(幂等,含 DB 备份):`E:\CentOS\deploy-home\kuma-statuspage.{sql,sh}`。**改库后必须 `docker restart uptime-kuma`** 才生效。 - 验证端点:`/api/status-page/<slug>`(分组+监控列表)与 `/api/status-page/heartbeat/<slug>`(`heartbeatList`/`uptimeList`,键形如 `"3_24"`,前缀是监控 id)。**这两个端点在 status.xpcool.com 上公网匿名可达**,SSH 被拦时可直接用 HTTPS 验证状态页是否建成。 - 回滚:`sudo cp /tmp/kuma.db.backup.<时间戳> /data/uptime-kuma/kuma.db && sudo docker restart uptime-kuma`。 - **通知渠道 = Bark(2026-09-13 已配置并验证)**:Kuma 2.5.3 原生支持 Bark(`/app/server/notification-providers/bark.js`)。`notification` 表:`id/name/active/user_id/is_default/config`,**`config` 是整条通知对象的 JSON 字符串(含 `type`),Kuma 用 `providerList[notification.type]` 派发,靠 `item.name` 索引**。 - **最大的坑:`type` 必须写 `Bark`(首字母大写)**,不是 `bark`。前端 `NotificationDialog.vue:257` 就是 `Bark: "Bark"`;写成小写则查不到 provider,**静默不发通知**。 - 当前配置:`id=1 / name='Bark 手机推送' / is_default=1`,`config` = `{"type":"Bark","barkEndpoint":"http://172.17.0.1:8081/sm5WnyANWx89GgLtFnRXhk","barkGroup":"Uptime-Kuma","apiVersion":"v1"}`。 - **端点必须用容器网关 `172.17.0.1:8081`**:kuma 在默认 `bridge` 网络(网关 172.17.0.1),容器内的 `127.0.0.1` 不是宿主机;Bark 容器端口发布在宿主机 `0.0.0.0:8081`,故走网关可达(实测 200)。 - `apiVersion:"v1"` → 走 GET `{endpoint}/{title}/{body}?icon=…&group=…&sound=…`(bark-server 支持);不填也默认 v1。Bark key 就是 `/etc/server-monitor.conf` 里 `BARK_URL` 那个(`sm5WnyANWx89GgLtFnRXhk`),手机端就是 `https://service.xpcool.com/bark/<key>/`。 - 挂到监控:`monitor_notification(monitor_id, notification_id)`;**等价于 UI 的 `applyExisting`**。已为 9 个监控全部关联(共 9 行)。 - 脚本:`.workbuddy/tmp/kuma-bark-setup.sh`(幂等写入)、`.workbuddy/tmp/kuma-bark-verify.sh`(端到端验证 + 自清理);改库前必备份 `/tmp/kuma.db.backup.<时间戳>`。 - **端到端验证方法(推荐复用)**:临时插一个指向 `http://127.0.0.1:9/` 的监控(`interval=20/retry_interval=20/maxretries=0`)→ 重启容器 → 十几秒即 DOWN 并推送 → 查 `docker logs bark` 确认真的收到 GET 推送(200)→ 删掉临时监控与其 heartbeat 再重启。全程不清真站点,可靠性最高。 - 全部 9 个监控 `expiry_notification=1`(证书到期通知已开),当前均 UP。 - **Bark 通知增强(2026-09-13 · 自定义图标/分组/重要警告/图片/角标)**:✅ **2026-09-14 已部署并端到端验证通过**(部署包归档 `E:\CentOS\deploy-bark-custom\`)。官方 provider 只支持 `barkEndpoint/barkGroup/barkSound`,且图标写死 GitHub 上的 Kuma logo。扩展办法 = **用只读挂载覆盖 provider 单文件**: - 宿主机 `/data/uptime-kuma-patch/bark.js` ←→ 容器 `/app/server/notification-providers/bark.js`(`:ro`)。补丁源码在 `.workbuddy/tmp/bark.js`,改动点集中在 `statusParams()` 与 `additionalParameters()`,均以中文注释标了「xpcool 定制」。 - 行为:DOWN → `level=critical`(重要警告,穿透静音/专注)+ `volume=5` + `badge=auto`(角标自增)+ `isArchive=1` + 异常配图;UP → `badge=0`(清角标)+ 恢复配图。`call=1`(持续响铃 30s)**默认关闭**,要开就在 config 里加 `"barkCall": true`。 - 所有项都可用通知 `config` 里的同名字段覆盖:`barkIcon / barkGroup / barkSound / barkLevel / barkVolume / barkCall / barkBadge / barkImageDown / barkImageUp / barkBadgeReset`。**但 UI 里保存通知会重写 config、丢掉这些额外字段** —— 保持默认行为即可(补丁有兜底默认值),要改就走 SQL。 - 素材自托管在 `https://status.xpcool.com/bark-assets/{icon,down,up}.png`(落点 `/data/www/bark-assets/`,nginx 新增 `location /bark-assets/`,`alias + default_type image/png + expires 7d`)。**必须公网 HTTPS 可匿名访问**:图是手机端收到通知后自己去拉的。 - 素材由 `.workbuddy/tmp/make-bark-assets.py`(Pillow 手绘)生成,纯几何 + 微软雅黑文字,体积 22~78 KB。 - **⚠️ 升级 uptime-kuma 镜像后**:`bark.js` 会被挂载覆盖成旧版实现,必须重新对照新版官方 `bark.js` 更新补丁(或临时去掉挂载)。 - **⚠️ 重建容器必须带回挂载**,否则补丁失效(表现为:通知还发,但图标/重要警告/配图/角标全没了)。完整启动参数见上。 - **部署包已归档到 `E:\CentOS\deploy-bark-custom\`**(补丁 + 素材 + 部署/验证脚本 + nginx 配置 + README);`.workbuddy/tmp/` 下的同名文件为工作副本。 - 部署脚本:`deploy-bark-custom.sh`(远端执行,幂等)+ `deploy-bark-custom.ps1`(本机一键:上传 → 部署 → 端到端验证,日志写 `.workbuddy/tmp/logs/`)。 - **2026-09-14 部署实测**:补丁 md5 `98c586f988084b89e1b5452257442b08` 宿主机与容器内一致;`docker inspect` 确认挂载在(`/data/uptime-kuma-patch/bark.js -> /app/server/notification-providers/bark.js`,`RW=false`);临时监控触发真实 DOWN 后,Bark 服务端日志收到带完整定制参数的推送(`icon=…/bark-assets/icon.png&group=Uptime-Kuma&sound=telegraph&level=critical&volume=5&badge=auto&isArchive=1&image=…/bark-assets/down.png`),对比部署前只有 `icon`(GitHub) + `group` + `sound` 三项。 - **⚠️ 验证静态资源别只看 HTTP 状态码**:Kuma 的 SPA 对任何未知路径都返回 `200 + index.html`,只检查 code 会把「location 未生效」误判成成功;且 `nginx reload` 是优雅重启,旧 worker 退出期间请求仍可能落到 `location /`。故取图验证要先 `sleep 3` 再校验 `content_type=image/png`。 - **⚠️ 手机上需允许「重要警告」**:`level=critical` 要真正穿透静音/专注,需在 Bark App 的通知设置里开启重要警告权限,否则仍会被系统静音。