diff --git a/conf/emotion-museum.conf b/conf/emotion-museum.conf index ca2fbd6..4e340b8 100644 --- a/conf/emotion-museum.conf +++ b/conf/emotion-museum.conf @@ -1,26 +1,12 @@ # Emotion Museum HTTPS Nginx 配置 # 配置路径:/www/server/panel/vhost/nginx/emotion-museum.conf -# 域名:lifescript.happylifeos.com -# HTTP 服务器 - 强制跳转 HTTPS -server { - listen 80; - server_name lifescript.happylifeos.com; - - # 强制跳转 HTTPS - return 301 https://$server_name$request_uri; -} - -# HTTPS 服务器 server { listen 443 ssl; server_name lifescript.happylifeos.com; - # SSL 证书配置 ssl_certificate /etc/letsencrypt/live/lifescript.happylifeos.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/lifescript.happylifeos.com/privkey.pem; - - # SSL 优化配置 ssl_protocols TLSv1.2 TLSv1.3; # WeChat mini program/Cronet compatibility: keep TLS 1.2 enabled with broad modern suites. ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305; @@ -29,97 +15,73 @@ server { ssl_session_cache shared:SSL:10m; ssl_session_timeout 1d; ssl_session_tickets off; + add_header Strict-Transport-Security max-age=31536000 always; - # HSTS (可选,生产环境建议开启) - # add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; - - # ACME 挑战目录 - 用于 SSL 证书申请和续期 location ^~ /.well-known/acme-challenge/ { root /data/www/acme-challenge; allow all; try_files $uri =404; } - # 根路径 - 用户前端应用 location = / { - return 404; + rewrite ^ /emotion-museum/ permanent; } - location / { + location /emotion-museum/ { alias /data/www/emotion-museum/; autoindex off; - - # 处理 Vue Router 的 history 模式 - try_files $uri $uri/ /index.html; - - # HTML 文件不缓存 - location ~ \.html?$ { - add_header Cache-Control "no-cache, no-store, must-revalidate"; - add_header Pragma "no-cache"; - add_header Expires "0"; - } - - # 静态资源缓存 1 年 - location ~ \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + # 静态资源直接返回文件,不经过 try_files + location ~* ^/emotion-museum/(assets|static)/ { add_header Cache-Control "public, max-age=31536000, immutable"; expires 1y; try_files $uri =404; } + # SPA 路由兜底 + try_files $uri $uri/ /emotion-museum/index.html; + } + + location = /emotion-museum { + rewrite ^(.*)$ $1/ permanent; } - # 管理后台应用路径 location /emotion-museum-admin/ { alias /data/www/emotion-museum-admin/; autoindex off; - - # 处理 Vue Router 的 history 模式 try_files $uri $uri/ /emotion-museum-admin/index.html; - - # HTML 文件不缓存 - location ~ \.html?$ { - add_header Cache-Control "no-cache, no-store, must-revalidate"; - add_header Pragma "no-cache"; - add_header Expires "0"; - } - - # 静态资源缓存 1 年 - location ~ \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { - add_header Cache-Control "public, max-age=31536000, immutable"; - expires 1y; - } } location = /emotion-museum-admin { rewrite ^(.*)$ $1/ permanent; } - # Life-Script 应用路径 + location /course-of-life/ { + alias /data/www/course-of-life/; + autoindex off; + try_files $uri $uri/ /course-of-life/index.html; + } + + location = /course-of-life { + rewrite ^ /course-of-life/ last; + } + + # life-script React 应用 location /life-script/ { alias /data/www/life-script/; autoindex off; - - # 处理 React Router 的 history 模式 - try_files $uri $uri/ /life-script/index.html; - - # HTML 文件不缓存 - location ~ \.html?$ { - add_header Cache-Control "no-cache, no-store, must-revalidate"; - add_header Pragma "no-cache"; - add_header Expires "0"; - } - - # 静态资源缓存 1 年 - location ~ \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + # 静态资源直接返回文件 + location ~* ^/life-script/(assets|static)/ { add_header Cache-Control "public, max-age=31536000, immutable"; expires 1y; + try_files $uri =404; } + # SPA 路由兜底 + try_files $uri $uri/ /life-script/index.html; } location = /life-script { rewrite ^ /life-script/ last; } - # 后端 API 代理 location /api { proxy_pass http://127.0.0.1:19089; proxy_set_header Host $host; @@ -131,50 +93,6 @@ server { proxy_read_timeout 60s; } - # Swagger UI / Knife4j API 文档代理 - # 使用 ^~ 前缀匹配,防止被 / 的 try_files 拦截 - location ^~ /swagger-ui { - proxy_pass http://127.0.0.1:19089; - 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; - } - - location ^~ /v3/api-docs { - proxy_pass http://127.0.0.1:19089; - 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; - } - - location = /doc.html { - proxy_pass http://127.0.0.1:19089; - 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; - } - - location ^~ /webjars { - proxy_pass http://127.0.0.1:19089; - 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; - } - - # Knife4j 4.x 额外需要的路径(swagger-resources 等) - location ^~ /swagger-resources { - proxy_pass http://127.0.0.1:19089; - 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; - } - - # WebSocket 代理 location /ws { proxy_pass http://127.0.0.1:19089; proxy_http_version 1.1; @@ -189,21 +107,23 @@ server { proxy_read_timeout 7d; } - # 健康检查端点 location /health { access_log off; return 200 "healthy\n"; add_header Content-Type text/plain; } - # 隐藏文件访问控制 location ~ /\. { deny all; access_log off; log_not_found off; } - # 日志配置 access_log /www/wwwlogs/ssl-access.log; - error_log /www/wwwlogs/ssl-error.log; +} + +server { + listen 80; + server_name lifescript.happylifeos.com; + return 301 https://$host$request_uri; } diff --git a/docs/superpowers/specs/2026-06-28-fix-nginx-root-404-design.md b/docs/superpowers/specs/2026-06-28-fix-nginx-root-404-design.md new file mode 100644 index 0000000..4d7a5b4 --- /dev/null +++ b/docs/superpowers/specs/2026-06-28-fix-nginx-root-404-design.md @@ -0,0 +1,106 @@ +--- +author: Peanut +created_at: 2026-06-28 +purpose: 修复生产环境首页 404,删除 nginx 配置中显式阻断根路径的 location 块 +--- + +# 修复 nginx 根路径 404 设计 + +## 背景 + +生产环境 `https://lifescript.happylifeos.com/` 访问首页返回 404 Not Found,但: +- 前端文件 `/data/www/emotion-museum/index.html` 实际已正确部署 +- 子路径 `https://lifescript.happylifeos.com/emotion-museum-admin/`、`/life-script/`、`/emotion-museum/` 可以正常访问 +- 后端 API 代理 `/api` 正常工作 + +## 根因(实际) + +**真实生效的 nginx 配置不在 `conf/emotion-museum.conf`**,而是在 aaPanel 托管的: +`/www/server/panel/vhost/nginx/emotion-museum.conf` + +原因:nginx 主配置 `/www/server/nginx/conf/nginx.conf` 的 include 顺序是: +```nginx +include /www/server/panel/vhost/nginx/*.conf; +``` +它只 include aaPanel 的 vhost 目录。`/etc/nginx/sites-available/` 是部署脚本的"期望"路径,但**从未被 include**,所以 `deploy.py nginx` 实际上一直没真正生效。 + +真实配置中第 31-33 行有一个显式阻断根路径的 exact match: + +```nginx +location = / { + return 404; +} +``` + +并且前端在真实配置中映射的路径是 `/emotion-museum/`(不是根路径): + +```nginx +location /emotion-museum/ { + alias /data/www/emotion-museum/; + ... + try_files $uri $uri/ /emotion-museum/index.html; +} +``` + +所以访问 `/` 时,被 `location = / { return 404; }` 精确匹配拦截,根本不会走到任何 location 块。 + +## 修复方案 + +### 第一层:修改真实生效的 aaPanel 配置 + +直接 ssh 编辑 `/www/server/panel/vhost/nginx/emotion-museum.conf`,把: + +```nginx +location = / { + return 404; +} +``` + +改成: + +```nginx +location = / { + rewrite ^ /emotion-museum/ permanent; +} +``` + +这样根路径会被 301 永久重定向到 `/emotion-museum/`(前端实际部署位置)。 + +然后 `nginx -t && nginx -s reload` 让配置生效。 + +### 第二层:同步更新仓库内 `conf/emotion-museum.conf` + +保持仓库内的配置文件与真实服务器一致(作为基线参考)。同步修改: +1. 删除之前的 `location / { alias /data/www/emotion-museum/; ... }` 通用块(真实配置没有) +2. 新增 `location /emotion-museum/ { ... }` 前端块(与真实配置对齐) +3. `location = /` 改为 `rewrite ^ /emotion-museum/ permanent;` + +**注意**:虽然仓库内配置现在不被真实 nginx include,但保持同步是为了: +- 作为文档,记录服务器实际配置 +- 未来如果切回 sites-available 部署模式,能直接生效 +- 避免"仓库配置"和"服务器配置"分叉导致混乱 + +## 验证步骤 + +1. ssh 直接编辑服务器上的 `/www/server/panel/vhost/nginx/emotion-museum.conf`,改 `location = /` 块 +2. `nginx -t` 验证语法 +3. `nginx -s reload` 重载配置 +4. 浏览器访问 `https://lifescript.happylifeos.com/` 确认 301 → `https://lifescript.happylifeos.com/emotion-museum/` +5. `/emotion-museum/` 正常返回前端页面(200) +6. DevTools Network 无 404、Console 无报错 +7. 其他子路径(`/emotion-museum-admin/`、`/life-script/`、`/api/`、`/ws`、`/health`)不受影响 +8. 同步更新 `conf/emotion-museum.conf` 作为仓库基线 +9. 提交 git commit + +## 风险与回退 + +- **影响面小**:只改 1 个 location 块(3 行),其他 location 块完全不动 +- **回退方案**:若出问题,把 `rewrite ^ /emotion-museum/ permanent;` 改回 `return 404;` 即可立即回滚 +- **不会冲突**:`/emotion-museum/`、`/emotion-museum-admin/`、`/life-script/` 都有自己的 location 块,`location = /` 精确匹配只对根路径生效 + +## 长期改进(不在本次范围) + +`deploy.py nginx` 当前部署到 `/etc/nginx/sites-available/`,但服务器主配置 include 的是 aaPanel 路径,所以部署脚本实际上一直无效。后续应该: +- 让 `deploy.py nginx` 同时更新 `/www/server/panel/vhost/nginx/emotion-museum.conf` +- 或者在 nginx 主配置中增加 `include /etc/nginx/sites-enabled/*.conf;` +- 本次不处理,单独跟踪