chore(qa): 验收台账与证据入库 + 构建/部署配置同步

- docs/acceptance/qa-tracker.md:跨周期缺陷单一真源台账(§7=第五轮)。
- 周期 1/2 + iPhone SE/axe 验收证据目录、ACCEPTANCE_REVIEW 快照入库。
- 同步 README/CONTEXT/CLAUDE/DESIGN/testing/deployment/lessons-learned 口径;
  next.config/Dockerfile/nginx/Jenkinsfile/docker-compose/sentry/prisma 对齐
  standalone 产物装配与部署形态。
This commit is contained in:
2026-09-28 10:48:09 +08:00
parent 6bb7c557ee
commit a0328a623f
128 changed files with 22455 additions and 504 deletions
+33 -29
View File
@@ -5,75 +5,76 @@
**关键内容**:
- **全局配置**: `worker_processes auto`,使用 epoll,连接数 1024,Docker DNS 解析器 `127.0.0.11`
- **安全头**: X-Frame-Options、X-XSS-Protection、X-Content-Type-Options、Referrer-Policy
- **安全头分工**: 文档级安全头(CSP / X-Frame-Options: DENY / X-Content-Type-Options / Referrer-Policy / Permissions-Policy / X-XSS-Protection / X-DNS-Prefetch-Control)**只**由 `next.config.mjs` 的 `headers()` 发出(`next.config.mjs:21-37`);本文件不再 `add_header` 同名头部,只保留边缘职责:TLS/301、HSTS(Next 不发送)、gzip、limit_req,以及 `/uploads/` 磁盘直服的 nosniff + sandbox CSP(该 location 不经 Next 应答,故需自行声明)。原因:nginx `add_header` 只追加不替换,两层各设一次会让浏览器收到 `X-Frame-Options: DENY, SAMEORIGIN` 的重复互斥值(见本文件 `nginx-static-production.conf:8-20` 的分工注释)
- **限流**: `limit_req_zone` 全局限速 100r/s
- **Gzip 压缩**: 级别 6,覆盖 text/css/json/javascript/xml 等类型
- **Upstream 定义**: `gitea_app` (gitea:3000)、`jenkins_app` (jenkins:8080)
- **虚拟主机**:
- `novalon.cn / www.novalon.cn` -- 主站静态文件服务,根目录 `/var/www/novalon`,SSL/TLS 1.2+1.3,HSTS 2年,`_next/static/` 和字体/图片缓存 1 年,`try_files $uri $uri.html $uri/ /404.html`
- `novalon.cn / www.novalon.cn` -- 主站边缘:页面与 `/api/*`、`/admin/*` 全部反代到 standalone 运行时(`@nextjs` named location),**不再**从磁盘直服 HTML(原 `try_files $uri $uri.html $uri/ /404.html` 的 export 时代快路径已移除,理由见 `nginx-static-production.conf:22-25`);`/var/www/novalon` 只作静态资源/`/uploads/` 的磁盘直服根目录,SSL/TLS 1.2+1.3,HSTS 2 年,`_next/static/` 与字体/图片缓存 1 年(未命中回源 `@nextjs`)
- `git.f.novalon.cn` -- 反向代理到 Gitea(端口 3000)
- `ci.f.novalon.cn` -- 反向代理到 Jenkins(端口 8080),超时 60s
- **SSL**: Let's Encrypt ACME challenge 支持,独立子域名证书路径
#### 2. [nginx-static.conf](computer:///sessions/69e889f08f8c93c2514713f1/workspace/nginx-static.conf)
**用途**: 简化版 Nginx 配置,仅包含主站静态服务,用于 `Dockerfile` 构建镜像。
**用途**: 主站边缘配置片段(HTTP→HTTPS + HSTS + gzip + limit_req + 反代 standalone 应用),**是 http 上下文的 include 片段而非完整 `nginx.conf`**(无 `events{}`/`http{}` 外壳,见 `nginx-static.conf:3-8`)。
**关键内容**:
- 仅配置 `novalon.cn / www.novalon.cn`,无 Gitea/Jenkins 反向代理
- 静态根目录 `/usr/share/nginx/html`
- 静态根目录:ACME/落地场景用 `/usr/share/nginx/html`(`:35`),主站静态资源在 `/var/www/novalon`(`:86`),页面一律 `proxy_pass http://novalon_app`(`:97,115,131`)
- SSL 配置较简化(`ssl_ciphers HIGH:!aNULL:!MD5`)
- 包含与 production 版相同的安全头、Gzip、静态资源缓存策略
- 额外包含 `Permissions-Policy` 头(禁用摄像头/麦克风/地理位置)
- 安全头分工与 production 版一致:文档级头部由 `next.config.mjs` 单点发出,本片段只带 HSTS / Cache-Control / CORS / Content-Disposition(`nginx-static.conf:9-20`)
- 不再自设 `Permissions-Policy`:该头现在也在应用层(`next.config.mjs:33`)
#### 3. [nginx-internal.conf](computer:///sessions/69e889f08f8c93c2514713f1/workspace/nginx-internal.conf)
**用途**: 内部/开发环境配置,监听端口 3000,无 SSL。
**用途**: 内部/开发环境的纯静态资源片段,监听端口 3000,无 SSL。
**关键内容**:
- 监听 `localhost:3000`,根目录 `/var/www/novalon`
- 无 SSL、无 HSTS、无 ACME challenge
- 包含基本的 Gzip、安全头、静态资源缓存
- 用于 `Dockerfile.static` 构建的内部容器
- 只保留 Gzip 与静态资源缓存;文档级安全头不在本片段(唯一来源 `next.config.mjs`,见 `nginx-internal.conf:7-11`)
- 唯一使用者 `Dockerfile.static` 已删除,本片段现处于「保留但无人引用」状态,删除前需同步本文件(`nginx-internal.conf:3-6`)
---
### 二、Docker 相关文件(5 个)
#### 4. [Dockerfile](computer:///sessions/69e889f08f8c93c2514713f1/workspace/Dockerfile)
**用途**: 多阶段构建 -- Node.js 编译 + Nginx 静态服务。
#### 4. [Dockerfile](../Dockerfile)
**用途**: 单机应用镜像 -- Node.js 构建 + Node.js 运行时承载 Next.js 16 standalone(**不是** nginx 静态站)。
**关键内容**:
- **构建阶段**: `node:20-alpine`,`npm ci` 安装依赖,`npm run build` 构建
- **运行阶段**: `nginx:alpine`,将 `dist/` 复制到 `/usr/share/nginx/html`,挂载 `nginx-static.conf`
- 暴露端口 80
- **构建阶段**: `node:20-alpine`,`npm ci` 安装依赖,`npm run build` 构建(`Dockerfile:21-28`)
- **运行阶段**: `node:20-alpine`,拷入 `dist/standalone`、`dist/static`、`public`(`Dockerfile:53-57`),非 root 用户 `nextjs`,`EXPOSE 3000`,`CMD ["node", "server.js"]`(`Dockerfile:61-67`)
- 镜像内不再有 nginx 层:文档级安全头由 `next.config.mjs` 的 `headers()` 发出,容器直连时头部依然完整(`Dockerfile:65-66`)
#### 5. [Dockerfile.static](computer:///sessions/69e889f08f8c93c2514713f1/workspace/Dockerfile.static)
**用途**: 纯静态文件 Nginx 容器(无构建阶段),用于内部部署。
#### 5. [Dockerfile.prod](../Dockerfile.prod)
**用途**: 服务器侧镜像(`docker-compose.server.yml` 使用),**不在镜像内构建**:直接 COPY 本地已产出的 standalone 产物。
**关键内容**:
- 基础镜像 `nginx:alpine`
- 将 `html/` 目录复制到 `/var/www/novalon`
- 挂载 `nginx-internal.conf` 到 `/etc/nginx/conf.d/default.conf`
- 暴露端口 3000
- `sharp-deps` 阶段拉 `@img/sharp-linuxmusl-*` 覆盖 standalone 里的 darwin 二进制(`Dockerfile.prod:4-10`、`:30`)
- `runner` 阶段 COPY `dist/standalone`、`dist/static`、`public`,`ENV PORT=3000 HOSTNAME=0.0.0.0`,`EXPOSE 3000`,`CMD ["node", "server.js"]`(`Dockerfile.prod:12-38`)
- **已删除**:原 `Dockerfile.static`(纯静态 nginx 容器,COPY 一个 standalone 模式下根本不会产出的 `html/` 目录)已从仓库移除,其配套片段 `nginx-internal.conf` 现无人引用
#### 6. [docker-compose.yml](computer:///sessions/69e889f08f8c93c2514713f1/workspace/docker-compose.yml)
**用途**: 基础 Docker Compose 配置,使用 `Dockerfile` 构建。
#### 6. [docker-compose.yml](../docker-compose.yml)
**用途**: 单机应用容器,使用 `Dockerfile` 构建(跑 standalone 运行时,非静态 HTML)。
**关键内容**:
- 服务名 `novalon-website`,镜像标签 `novalon-website:1.0.0`
- 容器名 `novalon-website`
- 端口映射 `80:80` 和 `443:443`
- 挂载 `nginx-static.conf` 和 `ssl/` 目录(只读)
- 端口映射 `3000:3000`(容器内 `server.js` 监听 `0.0.0.0:3000`;TLS/跳转/HSTS 属边缘,见 nginx-static.conf)
- `env_file: .env.production` + `DATABASE_URL=file:/app/data/prod.db`,卷 `./data:/app/data`、`./uploads:/app/public/uploads`;历史缺陷(曾用本文件把 `dist/` 当静态根交给 nginx 并映射 80/443)已在文件头注明
#### 7. [docker-compose.server.yml](computer:///sessions/69e889f08f8c93c2514713f1/workspace/docker-compose.server.yml)
**用途**: 服务器端 Docker Compose 配置,使用 `Dockerfile.static` 构建。
#### 7. [docker-compose.server.yml](../docker-compose.server.yml)
**用途**: 服务器端 Compose 配置,使用 **`Dockerfile.prod`** 构建(`docker-compose.server.yml:7`)。
**关键内容**:
- 服务名 `novalon-website`,镜像标签 `novalon-website:latest`
- 容器名 `novalon-website`
- 端口映射 `3000:3000`
- 使用外部网络 `novalon-network`(bridge 驱动)
- 上传目录挂载为 `./uploads:/app/public/uploads`(与 `src/lib/media/storage.ts` 的落盘根 `<cwd>/public/uploads` 对齐;`docker-compose.yml:38` 同)
- 注:`docker-compose.yml:36-37` 的注释仍称本文件挂 `/app/uploads`,与实际挂载点不符,改该文件时一并修正
#### 8. [docker-compose-nginx.yml](computer:///sessions/69e889f08f8c93c2514713f1/workspace/docker-compose-nginx.yml)
#### 8. [docker-compose-nginx.yml](../docker-compose-nginx.yml)
**用途**: 独立 Nginx 反向代理容器配置(生产环境网关)。
**关键内容**:
@@ -82,11 +83,12 @@
- 端口映射 `80:80` 和 `443:443`
- 挂载:
- `nginx-static-production.conf` -> `/etc/nginx/nginx.conf`(只读)
- `conf.d/` -> `/etc/nginx/conf.d`(只读,子域名虚拟主机片段)
- `ssl/` -> `/etc/nginx/ssl`(只读)
- `logs/` -> `/var/log/nginx`
- `../certbot` -> `/var/www/certbot`(ACME 验证)
- `../novalon-website/dist` -> `/var/www/novalon`(只读,Novalon 主站静态文件)
- `../novalon-static` -> `/var/www/novavis-landing`(只读,Novavis 落地页)
- `../novalon-static` -> `/var/www/novalon`(只读,静态资源磁盘直服根)
- `./sites` -> `/var/www/sites`(只读,产品子站根)
- 使用外部网络 `novalon-network`
---
@@ -249,6 +251,8 @@ include /etc/nginx/conf.d/*.conf;
每个产品一个独立配置文件,例如 `conf.d/product-a.novalon.cn.conf`:
> **适用范围注(2026-09-23 核对)**:本模板给 **nginx 磁盘直服的独立静态子站 / 内部工具反代**用(现状即 `conf.d/ci.f.novalon.cn.conf:31-34`、`conf.d/git.f.novalon.cn.conf:27-31`,其中 Gitea/Jenkins 需要 `SAMEORIGIN` 以便同源内嵌)。主站不在此列:`novalon.cn` 的文档级安全头由 `next.config.mjs` 的 `headers()` 单点发出且取 `X-Frame-Options: DENY`(`next.config.mjs:21-37`),把本模板的 `add_header X-Frame-Options` 复制进主站 location 会与应用层同名头叠加成非法的 `DENY, SAMEORIGIN`(缺陷成因见 `nginx-static-production.conf:16-18`)。
```nginx
# 产品A官网 - product-a.novalon.cn
server {