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:
+119
-61
@@ -1,17 +1,50 @@
|
||||
# Novalon 静态站点 Nginx 配置
|
||||
# 用法:替换现有 nginx.conf,然后 nginx -t && nginx -s reload
|
||||
# Novalon 站点边缘配置(HTTP→HTTPS + HSTS + gzip + limit_req + 反代 standalone 应用)
|
||||
#
|
||||
# ⚠️ 这是一段 **http 上下文 include 片段**(放进 conf.d/ 或被主配置 include),
|
||||
# 不是完整的 nginx.conf:它没有 events{}/http{} 外壳。
|
||||
# 历史缺陷:旧版 Dockerfile 与 docker-compose.yml 把本文件 COPY/MOUNT 到
|
||||
# /etc/nginx/nginx.conf,而本文件只有裸 server 块 → 直接 nginx -t 即失败;
|
||||
# 需要完整生产网关(含 gitea / jenkins 虚拟主机)时用 nginx-static-production.conf。
|
||||
#
|
||||
# 头部分工(唯一来源,勿在此叠加):
|
||||
# - 文档级安全头 CSP / X-Frame-Options / X-Content-Type-Options / Referrer-Policy /
|
||||
# Permissions-Policy / X-XSS-Protection / X-DNS-Prefetch-Control 全部由
|
||||
# next.config.mjs 的 headers() 随应用一起发布;本文件不再 add_header 这些名字。
|
||||
# 原因:nginx add_header 不会替换上游同名头部,只做追加,两层各设一次就会让浏览器收到
|
||||
# `X-Frame-Options: DENY, SAMEORIGIN` 这类非法的重复/互斥值(验收 §5 确认的线上缺陷)。
|
||||
# - 本文件只保留确实属于边缘的东西:TLS 与 301、HSTS(Next 不发送,且只有终结 TLS 的
|
||||
# 一层知道协议与证书状态)、gzip、limit_req,以及应用未设置的 Content-Disposition。
|
||||
# - 页面不再由 nginx 直服静态 HTML:output 为 standalone 后,HTML 由 Next 运行时
|
||||
# (ISR/SSR/Draft)产出,静态资源与 /uploads/ 仍可由磁盘直供(见下,缺文件即 404)。
|
||||
|
||||
limit_req_zone $binary_remote_addr zone=novalon_page:10m rate=50r/s;
|
||||
|
||||
upstream novalon_app {
|
||||
# 应用与 nginx 同机(systemd / 端口发布)时用 127.0.0.1;
|
||||
# 与 docker-compose.yml 同网络时改为容器名:server novalon-website:3000;
|
||||
server 127.0.0.1:3000;
|
||||
keepalive 32;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name novalon.cn www.novalon.cn;
|
||||
return 301 https://www.novalon.cn$request_uri;
|
||||
|
||||
# ACME http-01 challenge 必须留在 80 且不能被跳转吞掉
|
||||
location /.well-known/acme-challenge/ {
|
||||
root /usr/share/nginx/html;
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
location / {
|
||||
return 301 https://www.novalon.cn$request_uri;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name novalon.cn www.novalon.cn;
|
||||
|
||||
# SSL 证书配置
|
||||
ssl_certificate /etc/nginx/ssl/fullchain.pem;
|
||||
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
@@ -19,11 +52,89 @@ server {
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
ssl_session_timeout 10m;
|
||||
|
||||
# 静态文件根目录
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
# 边缘自有头部:Next 不发送 HSTS(它无法得知终结点使用的协议),故只在这里设置
|
||||
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
|
||||
|
||||
# 注意:以下 location 均含 add_header,按 nginx 语义不再继承 server 级头部,
|
||||
# 故每块各自重述 HSTS;应用级头部一律不在此重述(由 next.config.mjs 负责)。
|
||||
|
||||
# 预构建产物可用磁盘直服时启用(root 需与部署侧同步目录一致);
|
||||
# 未配置磁盘副本时删除本块即可,全部流量交给 novalon_app。
|
||||
location /_next/static/ {
|
||||
alias /var/www/novalon/_next/static/;
|
||||
expires 1y;
|
||||
# Cache-Control 由 Next 官方声明为「immutable 资源不可覆盖」,此处仅补充磁盘直服
|
||||
# 路径(Next 未应答)所需的缓存语义,与应用头部不冲突、不同名重复。
|
||||
add_header Cache-Control "public, max-age=31536000, immutable" always;
|
||||
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
|
||||
try_files $uri @novalon_proxy;
|
||||
}
|
||||
|
||||
# 本地字体(约束:字体全部本地文件,禁外部 CDN)
|
||||
location /fonts/ {
|
||||
alias /var/www/novalon/fonts/;
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, max-age=31536000, immutable" always;
|
||||
add_header Access-Control-Allow-Origin "*";
|
||||
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
|
||||
try_files $uri @novalon_proxy;
|
||||
}
|
||||
|
||||
# 媒体库接口可签发短期签名 URL:需要直取文件时优先走下面 location / 的反代;
|
||||
# 本块仅在存在磁盘副本(如 CDN 回源目录)时生效。
|
||||
location ~* \.(jpg|jpeg|png|gif|webp|avif|ico)$ {
|
||||
root /var/www/novalon;
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, max-age=31536000, immutable" always;
|
||||
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
|
||||
try_files $uri @novalon_proxy;
|
||||
}
|
||||
|
||||
# 上传件:应用自身对 /uploads/ 只发 nosniff/CSP(同源落点风险),
|
||||
# 文档与压缩包在这里强制下载。Content-Disposition 应用未设置 ⇒ 不构成跨层重复。
|
||||
location /uploads/ {
|
||||
limit_req zone=novalon_page burst=20 nodelay;
|
||||
proxy_pass http://novalon_app;
|
||||
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;
|
||||
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
|
||||
|
||||
location ~* /uploads/.*\.(pdf|zip|docx?|xlsx?|pptx?)$ {
|
||||
add_header Content-Disposition "attachment" always;
|
||||
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
|
||||
}
|
||||
}
|
||||
|
||||
# 页面 / API / 管理后台 / 缺文件的静态资源:交给 Next standalone 运行时,
|
||||
# 应用头部(含 CSP/XFO)随响应原样透传。
|
||||
location / {
|
||||
limit_req zone=novalon_page burst=20 nodelay;
|
||||
proxy_pass http://novalon_app;
|
||||
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 X-Forwarded-Host $host;
|
||||
# 复用 upstream keepalive:Connection 置空。生产 standalone 无 HMR/WebSocket,
|
||||
# 若将来需要 Upgrade 透传,请像 nginx-static-production.conf 那样加 $connection_upgrade map。
|
||||
proxy_set_header Connection "";
|
||||
proxy_hide_header X-Powered-By;
|
||||
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
|
||||
}
|
||||
|
||||
# 静态资源磁盘副本未命中时的回源出口
|
||||
location @novalon_proxy {
|
||||
proxy_pass http://novalon_app;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
|
||||
}
|
||||
|
||||
# Gzip 压缩
|
||||
gzip on;
|
||||
gzip_vary on;
|
||||
gzip_proxied any;
|
||||
@@ -40,59 +151,6 @@ server {
|
||||
application/rss+xml
|
||||
image/svg+xml;
|
||||
|
||||
# 安全头
|
||||
add_header X-DNS-Prefetch-Control "on" always;
|
||||
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
|
||||
add_header X-XSS-Protection "1; mode=block" always;
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header Referrer-Policy "origin-when-cross-origin" always;
|
||||
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
|
||||
|
||||
# 静态资源长期缓存(带内容哈希的文件)
|
||||
location /_next/static/ {
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, max-age=31536000, immutable";
|
||||
# 安全头需要重新添加(location 块会覆盖 server 级别的 add_header)
|
||||
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
# 字体文件缓存
|
||||
location /fonts/ {
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, max-age=31536000, immutable";
|
||||
add_header Access-Control-Allow-Origin "*";
|
||||
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
# 图片文件缓存
|
||||
location ~* \.(svg|jpg|jpeg|png|gif|webp|avif|ico)$ {
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, max-age=31536000, immutable";
|
||||
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
# Let's Encrypt ACME challenge
|
||||
location /.well-known/acme-challenge/ {
|
||||
root /usr/share/nginx/html;
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
# Next.js 静态导出的页面路由
|
||||
# /about -> /about.html 或 /about/index.html
|
||||
location / {
|
||||
try_files $uri $uri.html $uri/ /404.html;
|
||||
}
|
||||
|
||||
# 自定义 404 页面
|
||||
error_page 404 /404.html;
|
||||
|
||||
# 优化文件传输
|
||||
sendfile on;
|
||||
tcp_nopush on;
|
||||
tcp_nodelay on;
|
||||
|
||||
Reference in New Issue
Block a user