diff --git a/.workbuddy/memory/2026-09-14.md b/.workbuddy/memory/2026-09-14.md index 29d04c9..ca436e9 100644 --- a/.workbuddy/memory/2026-09-14.md +++ b/.workbuddy/memory/2026-09-14.md @@ -14,3 +14,8 @@ 2026-09-14 | DOC | 沉淀经验到 skill `uptime-kuma-sqlite-ops`:新增「告警不生效排查:先分三段」章节(① 有无事件触发 → ② 推送是否发出 → ③ 手机是否收到),新增两条 SSH 踩坑(回传 base64 的那条 ssh 不能夹带 `ls`/`echo`,否则 `base64 -d` 报 invalid input;`scp ... 2>&1 | grep -v 噪音 && ssh ...` 因 grep 无匹配返回 1 而短路),并新增可复用自检脚本 `scripts/kuma-bark-check.sh`(变量化容器名与通知 id,自带备份与自清理) 2026-09-14 | DEP | **Bark 通知增强补丁正式部署并端到端验证通过**(9-13 备好的补丁因沙箱拦截 SSH 一直未落地)。流程:逐个 scp 7 个文件(icon/down/up.png、bark.js、status.xpcool.com.conf、deploy-bark-custom.sh、kuma-bark-verify.sh)→ 远端执行部署脚本(备份 `docker inspect` 与 nginx conf → 素材落 `/data/www/bark-assets/` → 补丁落 `/data/uptime-kuma-patch/bark.js` 并做 `node --check` 语法门禁 → nginx reload → 重建容器带 `:ro` 挂载 → 校验 md5)。结果:补丁 md5 `98c586f988084b89e1b5452257442b08` 宿主机与容器内一致、挂载在(`RW=false`)、9 处定制标记;端到端验证(临时监控 id=12 触发真实 DOWN)时 Bark 日志收到完整定制参数 `icon=…/bark-assets/icon.png & level=critical & volume=5 & badge=auto & isArchive=1 & image=…/bark-assets/down.png`,对比部署前仅 `icon`(GitHub)+`group`+`sound` 三项;清理后监控恢复 9 个、关联 9 条、面板 302 正常。**部署包归档 `E:\CentOS\deploy-bark-custom\`(含 README)** 2026-09-14 | FIX | 修 `deploy-bark-custom.sh` 两处隐患:① 补丁语法检查失败原先只打印错误、仍继续重建容器 → 改为 `exit 1` 中止(语法错会让 Kuma 整个通知模块挂掉);② 取图验证原先只看 HTTP 状态码,会被 Kuma SPA 的 `200 + index.html` 骗过(且 `nginx reload` 后旧 worker 仍在退出,请求会落到 `location /`)→ 改为 `sleep 3` + 校验 `content_type=image/png`。另 `sudo docker cp` 产生的 `/tmp/_bark_check.js` 属 root,`rm` 需加 `sudo` +2026-09-14 | FIX | **排查并优化 read.xpcool.com 访问慢 —— 根因不在服务器,在前端资源传输**。read 是 **Flutter Web** 应用,首屏需下载 `main.dart.js` 12.75MB + `canvaskit.wasm` 6.76MB 等合计 **20.6MB**,而应用侧(Solon)既未压缩也未设有效缓存(上游仅给 `Cache-Control: max-age=600`)。仅改宿主机 `/data/nginx/conf.d/read.xpcool.com.conf`(**未重启容器、零中断**):① 开启 gzip(`gzip_types` 含 `application/wasm`、`comp_level 5`、**`gzip_proxied any` 必写**,否则 proxy_pass 的上游响应不压缩);② 分级缓存(`/canvaskit/*` 30d、构建产物 7d、`/` 不缓存以保换版及时);③ 静态资源 `proxy_hide_header Set-Cookie`(上游给每张 JS/WASM/图片都塞 `SOLONID`,静态资源不需要;**首页与 API 的 cookie 保持不变并已实测未误伤**)。公网实测首屏 **20.6MB → 6.04MB(省 71%)**,服务端 TTFB 15ms。**排除项(均非瓶颈)**:应用内部响应仅 0.9ms、数据库 3.2MB/16 表、出口网络正常、负载 0.06 +2026-09-14 | DOC | **拦下一次误改**:`conf.yml` 的 `server.http.coreThreads: x5` / `maxThreads: x10` 初判为「无效数值」,查证 Solon 官方文档后确认 **`xN` 是合法语法 = CPU 内核倍数**,jstack 实测正好 20 个 `smarthttp-` 线程(4 核×5)证明确实生效,遂未动手。教训:**框架配置里的可疑值先查文档,别凭直觉改** +2026-09-14 | CFG | 服务器补 **2GB swap**(原 swap=0,3.6G 内存跑 12 个容器是 OOM 隐患):`/swapfile` 已 swapon 并写入 `/etc/fstab`;`vm.swappiness` 60→10 并写入 `/etc/sysctl.conf` +2026-09-14 | DOC | read 提速部署包归档 `E:\CentOS\deploy-read\`(nginx 配置 + README,含完整实测表、三个坑、回滚命令与可选优化清单) +2026-09-14 | CFG | **用户给出正式备案号:`黔ICP备2026003933号`**(此前站点挂的 `闽ICP备19024351号` 为旧值)。xpcool.com 改 `src/config.ts` 的 `SITE.icp`(合规信息集中一处)→ 重建 → 重新部署上线,线上 JS 核对新号在、旧号零残留,页脚桌面/移动端均无溢出;admin.xpcool.com 同域名需同步,改 `packages/preferences/src/index.ts` 的 `appCopyrightPreferences`(真正生效处)+ `@core` 框架默认值 + snapshot 三处,push 后由 Gitea Actions 自动部署。**遗留**:home.xpcool.com 与 tool.xpcool.com 两个子站目前**完全没有展示备案号**,已告知用户待其决定是否补 diff --git a/.workbuddy/memory/DEPLOY.md b/.workbuddy/memory/DEPLOY.md index 45a2414..d55e5d0 100644 --- a/.workbuddy/memory/DEPLOY.md +++ b/.workbuddy/memory/DEPLOY.md @@ -61,7 +61,7 @@ | 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 自托管 | +| read.xpcool.com | `qread` | 127.0.0.1:8082 | 阅读 legado 自托管。**2026-09-14 性能优化:nginx 加 gzip + 分级缓存,首屏 20.6MB → 6.04MB(省 71%)**,详见下方专节 | | 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 | @@ -73,7 +73,7 @@ ### 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 波及同机其他容器。 +- 启动参数:`--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,必须限内存,避免 OOM 波及同机其他容器。(**2026-09-14 起已从 swap=0 补到 2G swap**,见「服务器 swap」节。) - **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` 比对,确认证书与私钥配套。 @@ -117,3 +117,32 @@ - **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 的通知设置里开启重要警告权限,否则仍会被系统静音。 + +### read.xpcool.com 访问提速(2026-09-14) + +**根因不在服务器,在前端资源传输。** read 是 **Flutter Web** 应用(非 Vue/React SPA),产物极大而应用侧(Solon)既没压缩也没缓存: + +| 资源 | 原始 | gzip 后 | 压缩率 | +|---|---|---|---| +| main.dart.js | 13,369,421 B | 3,420,588 B | 74.4% | +| canvaskit.wasm | 7,083,768 B | 2,879,449 B | 59.4% | +| skwasm.wasm | 3,551,820 B | 1,529,141 B | 57.0% | +| **首屏合计** | **约 20.6 MB** | **约 6.04 MB** | **71%** | + +**只改一个文件**:`/data/nginx/conf.d/read.xpcool.com.conf`(未重启容器、零中断): +1. **`gzip on`,`gzip_types` 必须含 `application/wasm`**;`gzip_comp_level 5`(5 与 9 压缩率差 <2%,CPU 更省);**`gzip_proxied any` 必写**(走 proxy_pass,不写则上游响应不压缩)。 +2. **分级缓存**:`/canvaskit/*` → 30 天;`main.dart.js` 等构建产物 → 7 天;**`/` 不缓存**(index.html 带 `serviceWorkerVersion`,每次必须拿最新才能换版)。 +3. **仅静态资源 `proxy_hide_header Set-Cookie`**:上游给每张 JS/WASM/图片都塞 `SOLONID`,静态资源并不需要;隐去后响应变可缓存,为接 CDN 铺路。**首页与 API 的 cookie 保持原样**(已验证)。 + +**三个坑(均实际踩过)**: +- **`conf.yml` 的 `coreThreads: x5` / `maxThreads: x10` 不是笔误**。查证 Solon 官方文档:**`xN` 是合法语法 = CPU 内核倍数**;jstack 实测 20 个 `smarthttp-` 线程(4 核×5),配置真实生效。**别凭直觉改**,要验证就数线程 `docker exec qread jcmd Thread.print`。 +- **不能只看状态码判断压缩**:容器内直连 8082 带 `Accept-Encoding: gzip` 仍返回 `Content-Length: 13369421` —— 压缩是 nginx 做的、不是应用做的,必须经 443 测才有 `content-encoding: gzip`。 +- **别重复加 `Cache-Control`**:`expires 7d` 已自动生成 `Expires` + `Cache-Control`,再 `add_header` 会出重复头。 + +**回滚**:`sudo cp /data/nginx/conf.d/read.xpcool.com.conf.bak.20260914-010244 /data/nginx/conf.d/read.xpcool.com.conf && sudo nginx -s reload`。部署包归档 `E:\CentOS\deploy-read\`(含 README 与完整实测数据)。 +**未做的可选优化**:接 CDN(收益最大,需配 DNS)、编译 nginx brotli 模块(再省 15~20%,风险高)、开 Solon 虚拟线程(需版本 ≥2.7.3,要重启容器)、静态资源提取到磁盘 + `gzip_static`(CPU 本非瓶颈)。 +**另注**:`main.dart.js` 达 12.75MB,正常 Flutter Web release 构建通常 1.5~3MB,怀疑镜像构建未充分优化 —— 属上游镜像范畴,未擅改。 + +### 服务器 swap(2026-09-14 新增) + +内存仅 **3.6G** 却跑 12 个容器(MySQL 435M / Gitea 479M / qread 370M / Kuma 115M…),原先 **swap=0**,内存峰值时无缓冲会直接 OOM 杀进程。已补 **2GB swapfile**(`/swapfile`:`fallocate`→`chmod 600`→`mkswap`→`swapon`)并写入 `/etc/fstab`;`vm.swappiness` 由 60 调为 **10**(避免过早换出反而拖慢响应),已入 `/etc/sysctl.conf`。 diff --git a/.workbuddy/memory/MEMORY.md b/.workbuddy/memory/MEMORY.md index 56e3a35..34cdaad 100644 --- a/.workbuddy/memory/MEMORY.md +++ b/.workbuddy/memory/MEMORY.md @@ -29,6 +29,8 @@ | home.xpcool.com | 前端服务台(公开服务入口) | `../home.xpcool.com/.workbuddy/memory/CHANGELOG.md` | 本地 git init/commit,远程待建 | | xpcool.com | 主域名:随机壁纸站(浏览器实时生成,可导出 4K;中英日韩 + 日夜主题) | `../xpcool.com/.workbuddy/memory/CHANGELOG.md` | 2026-09-13 上线;本地 git init/commit,远程待建 | +> **站点备案号(同域名子站通用):`黔ICP备2026003933号`** —— 集中写在 xpcool.com `src/config.ts` 的 `SITE.icp`,跳转核验 `https://beian.miit.gov.cn/`;admin.xpcool.com 在 `packages/preferences/src/index.ts` 的 `appCopyrightPreferences`。改号时两处一起改。**待办**:home / tool 两个子站尚未展示备案号。 + ## 命中流程 判断涉及哪个项目 → 读该项目 CHANGELOG 头部 10–15 条 → 处理后于顶部追加本次记录 → 新增项目时同步更新上方索引表。 diff --git a/.workbuddy/tmp/read.xpcool.com.conf b/.workbuddy/tmp/read.xpcool.com.conf new file mode 100644 index 0000000..3fff90c --- /dev/null +++ b/.workbuddy/tmp/read.xpcool.com.conf @@ -0,0 +1,106 @@ +# read.xpcool.com — 轻阅读 Web 端 +# 后端:Docker qread 容器,宿主端口 8082 +# +# 2026-09-14 性能优化: +# 前端为 Flutter Web 产物,main.dart.js 达 12.75MB、canvaskit.wasm 达 6.76MB, +# 且应用侧未做任何压缩与缓存(实测响应头无 Content-Encoding / Cache-Control)。 +# 本次开启 gzip(实测可省约 70% 流量)并分级加缓存头。 + +server { + listen 80; + server_name read.xpcool.com; + return 301 https://$host$request_uri; +} + +server { + listen 443 ssl http2; + server_name read.xpcool.com; + + ssl_certificate /data/nginx/ssl/read.xpcool.com/read.xpcool.com_bundle.crt; + ssl_certificate_key /data/nginx/ssl/read.xpcool.com/read.xpcool.com.key; + + client_max_body_size 256m; + proxy_read_timeout 300s; + proxy_send_timeout 300s; + + # ---------- gzip 压缩 ---------- + # 应用侧(Solon)不压缩静态资源,全部由 nginx 承担。 + # 实测 gzip -6:main.dart.js 13.37MB→3.16MB,canvaskit.wasm 6.76MB→2.73MB。 + gzip on; + gzip_comp_level 5; # 5 已接近最优压缩率,比 9 明显省 CPU + gzip_min_length 1024; + gzip_vary on; # 按 Accept-Encoding 分别缓存,避免代理串味 + gzip_proxied any; # 上游(proxy_pass)响应同样压缩 + gzip_buffers 32 8k; # 大文件压缩时加大缓冲,减少写盘/发送次数 + gzip_http_version 1.1; + gzip_types + text/plain + text/css + text/xml + text/javascript + application/javascript + application/x-javascript + application/json + application/xml + application/wasm # Flutter CanvasKit / SkWasm 引擎 + image/svg+xml + font/woff + font/woff2; + # text/html 为 nginx 默认包含,无需重复声明 + + # ---------- Flutter 引擎资源 ---------- + # 体积最大、内容随引擎版本固定 → 长缓存,命中后完全不走网络。 + location ^~ /canvaskit/ { + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + # 上游不压缩,显式清空避免双重压缩 + proxy_set_header Accept-Encoding ""; + proxy_pass http://127.0.0.1:8082; + + # 静态资源不需要会话,隐去无用 cookie: + # 既省 header 体积,也让响应可被正常缓存(带 Set-Cookie 的响应在多数代理/CDN 下不可缓存) + proxy_hide_header Set-Cookie; + + expires 30d; + access_log off; + } + + # ---------- Flutter 构建产物 ---------- + # 文件名固定(无内容 hash),故用中等缓存,配合 Flutter 自带 service worker 换版。 + location ~* ^/(main\.dart\.js|flutter_bootstrap\.js|flutter\.js|flutter_service_worker\.js|manifest\.json|favicon\.png|assets/|icons/) { + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Accept-Encoding ""; + proxy_pass http://127.0.0.1:8082; + + proxy_hide_header Set-Cookie; + + expires 7d; + access_log off; + } + + # ---------- 应用接口与页面 ---------- + # 注意:/ 返回的 index.html 带 serviceWorkerVersion,不能缓存,保持原样。 + location / { + # WebSocket 支持(书源调试、实时推送依赖) + proxy_http_version 1.1; + set $conn_upgrade "close"; + if ($http_upgrade != "") { + set $conn_upgrade "upgrade"; + } + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $conn_upgrade; + + proxy_pass http://127.0.0.1:8082; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +}