diff --git a/.env.production.example b/.env.production.example new file mode 100644 index 0000000..18999b4 --- /dev/null +++ b/.env.production.example @@ -0,0 +1,47 @@ +# ============================================================================= +# 生产环境配置 — 复制为 .env.production 并填入真实值 +# ============================================================================= + +# Domain +DOMAIN=makefire.fun + +# Frontend host (用于后端生成邮件链接等) +FRONTEND_HOST=https://makefire.fun + +# Environment +ENVIRONMENT=production + +PROJECT_NAME="Full Stack FastAPI Project" +STACK_NAME=full-stack-fastapi-project + +# Backend +BACKEND_CORS_ORIGINS="https://makefire.fun,https://api.makefire.fun" +# ⚠️ 必须修改:运行 openssl rand -hex 32 生成 +SECRET_KEY=changethis +FIRST_SUPERUSER=admin@makefire.fun +# ⚠️ 必须修改:设置强密码 +FIRST_SUPERUSER_PASSWORD=changethis + +# Emails (可选,如不需要发送邮件可留空) +SMTP_HOST= +SMTP_USER= +SMTP_PASSWORD= +EMAILS_FROM_EMAIL=info@makefire.fun +SMTP_TLS=True +SMTP_SSL=False +SMTP_PORT=587 + +# Postgres — 连接 1Panel 已有的 PostgreSQL 容器 +# 在 Docker 网络 1panel-network 内,主机名为 postgresql +POSTGRES_SERVER=postgresql +POSTGRES_PORT=5432 +POSTGRES_DB=app +# ⚠️ 使用已有 PG 的凭据,或为本项目创建专用用户 +POSTGRES_USER=user_ZPKMQ6 +POSTGRES_PASSWORD=password_CYmsGt + +SENTRY_DSN= + +# Docker images (本地构建,不需要远程 registry) +DOCKER_IMAGE_BACKEND=backend +DOCKER_IMAGE_FRONTEND=frontend diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml new file mode 100644 index 0000000..7bfcce8 --- /dev/null +++ b/.gitea/workflows/deploy.yml @@ -0,0 +1,79 @@ +name: Deploy to Production + +on: + push: + branches: + - main + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Create .env.production from secrets + run: | + cat > .env.production << 'ENVEOF' + DOMAIN=${{ secrets.DOMAIN }} + FRONTEND_HOST=${{ secrets.FRONTEND_HOST }} + ENVIRONMENT=production + PROJECT_NAME=${{ secrets.PROJECT_NAME }} + STACK_NAME=${{ secrets.STACK_NAME }} + BACKEND_CORS_ORIGINS=${{ secrets.BACKEND_CORS_ORIGINS }} + SECRET_KEY=${{ secrets.SECRET_KEY }} + FIRST_SUPERUSER=${{ secrets.FIRST_SUPERUSER }} + FIRST_SUPERUSER_PASSWORD=${{ secrets.FIRST_SUPERUSER_PASSWORD }} + SMTP_HOST=${{ secrets.SMTP_HOST }} + SMTP_USER=${{ secrets.SMTP_USER }} + SMTP_PASSWORD=${{ secrets.SMTP_PASSWORD }} + EMAILS_FROM_EMAIL=${{ secrets.EMAILS_FROM_EMAIL }} + SMTP_TLS=${{ secrets.SMTP_TLS }} + SMTP_SSL=${{ secrets.SMTP_SSL }} + SMTP_PORT=${{ secrets.SMTP_PORT }} + POSTGRES_SERVER=${{ secrets.POSTGRES_SERVER }} + POSTGRES_PORT=${{ secrets.POSTGRES_PORT }} + POSTGRES_DB=${{ secrets.POSTGRES_DB }} + POSTGRES_USER=${{ secrets.POSTGRES_USER }} + POSTGRES_PASSWORD=${{ secrets.POSTGRES_PASSWORD }} + SENTRY_DSN=${{ secrets.SENTRY_DSN }} + DOCKER_IMAGE_BACKEND=${{ secrets.DOCKER_IMAGE_BACKEND }} + DOCKER_IMAGE_FRONTEND=${{ secrets.DOCKER_IMAGE_FRONTEND }} + ENVEOF + + - name: Build Docker images + run: docker compose -f compose.prod.yml build + + - name: Stop existing services + run: docker compose -f compose.prod.yml down --remove-orphans || true + + - name: Start services + run: docker compose -f compose.prod.yml up -d + + - name: Wait for backend health check + run: | + echo "Waiting for backend to be healthy..." + for i in $(seq 1 30); do + if curl -sf http://127.0.0.1:8000/api/v1/utils/health-check/ > /dev/null 2>&1; then + echo "✅ Backend is healthy!" + exit 0 + fi + echo "Attempt $i/30 - waiting 10s..." + sleep 10 + done + echo "❌ Backend health check failed after 300s" + docker compose -f compose.prod.yml logs backend + exit 1 + + - name: Verify frontend + run: | + if curl -sf http://127.0.0.1:3001 > /dev/null 2>&1; then + echo "✅ Frontend is accessible!" + else + echo "❌ Frontend is not accessible" + docker compose -f compose.prod.yml logs frontend + exit 1 + fi + + - name: Cleanup old Docker images + run: docker image prune -f || true diff --git a/.gitignore b/.gitignore index f903ab6..0200edb 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,6 @@ node_modules/ /playwright-report/ /blob-report/ /playwright/.cache/ +.DS_Store +.env.production +.venv/ diff --git a/backend/Dockerfile b/backend/Dockerfile index 9f31dcd..f84c12d 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -42,4 +42,4 @@ RUN --mount=type=cache,target=/root/.cache/uv \ WORKDIR /app/backend/ -CMD ["fastapi", "run", "--workers", "4", "app/main.py"] +CMD ["fastapi", "run", "--workers", "2", "app/main.py"] diff --git a/compose.prod.yml b/compose.prod.yml new file mode 100644 index 0000000..627b635 --- /dev/null +++ b/compose.prod.yml @@ -0,0 +1,89 @@ +services: + + prestart: + image: '${DOCKER_IMAGE_BACKEND?Variable not set}:${TAG-latest}' + build: + context: . + dockerfile: backend/Dockerfile + networks: + - 1panel-network + command: bash scripts/prestart.sh + env_file: + - .env.production + environment: + - DOMAIN=${DOMAIN} + - FRONTEND_HOST=${FRONTEND_HOST?Variable not set} + - ENVIRONMENT=${ENVIRONMENT} + - BACKEND_CORS_ORIGINS=${BACKEND_CORS_ORIGINS} + - SECRET_KEY=${SECRET_KEY?Variable not set} + - FIRST_SUPERUSER=${FIRST_SUPERUSER?Variable not set} + - FIRST_SUPERUSER_PASSWORD=${FIRST_SUPERUSER_PASSWORD?Variable not set} + - SMTP_HOST=${SMTP_HOST} + - SMTP_USER=${SMTP_USER} + - SMTP_PASSWORD=${SMTP_PASSWORD} + - EMAILS_FROM_EMAIL=${EMAILS_FROM_EMAIL} + - POSTGRES_SERVER=${POSTGRES_SERVER} + - POSTGRES_PORT=${POSTGRES_PORT} + - POSTGRES_DB=${POSTGRES_DB} + - POSTGRES_USER=${POSTGRES_USER?Variable not set} + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD?Variable not set} + - SENTRY_DSN=${SENTRY_DSN} + + backend: + image: '${DOCKER_IMAGE_BACKEND?Variable not set}:${TAG-latest}' + restart: always + networks: + - 1panel-network + depends_on: + prestart: + condition: service_completed_successfully + ports: + - "127.0.0.1:8000:8000" + env_file: + - .env.production + environment: + - DOMAIN=${DOMAIN} + - FRONTEND_HOST=${FRONTEND_HOST?Variable not set} + - ENVIRONMENT=${ENVIRONMENT} + - BACKEND_CORS_ORIGINS=${BACKEND_CORS_ORIGINS} + - SECRET_KEY=${SECRET_KEY?Variable not set} + - FIRST_SUPERUSER=${FIRST_SUPERUSER?Variable not set} + - FIRST_SUPERUSER_PASSWORD=${FIRST_SUPERUSER_PASSWORD?Variable not set} + - SMTP_HOST=${SMTP_HOST} + - SMTP_USER=${SMTP_USER} + - SMTP_PASSWORD=${SMTP_PASSWORD} + - EMAILS_FROM_EMAIL=${EMAILS_FROM_EMAIL} + - POSTGRES_SERVER=${POSTGRES_SERVER} + - POSTGRES_PORT=${POSTGRES_PORT} + - POSTGRES_DB=${POSTGRES_DB} + - POSTGRES_USER=${POSTGRES_USER?Variable not set} + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD?Variable not set} + - SENTRY_DSN=${SENTRY_DSN} + + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8000/api/v1/utils/health-check/"] + interval: 10s + timeout: 5s + retries: 5 + + build: + context: . + dockerfile: backend/Dockerfile + + frontend: + image: '${DOCKER_IMAGE_FRONTEND?Variable not set}:${TAG-latest}' + restart: always + networks: + - 1panel-network + ports: + - "127.0.0.1:3001:80" + build: + context: . + dockerfile: frontend/Dockerfile + args: + - VITE_API_URL=https://api.${DOMAIN?Variable not set} + - NODE_ENV=production + +networks: + 1panel-network: + external: true diff --git a/deploy-tencent.md b/deploy-tencent.md new file mode 100644 index 0000000..18d6455 --- /dev/null +++ b/deploy-tencent.md @@ -0,0 +1,452 @@ +# 部署指南:腾讯云 + 1Panel + OpenResty + Gitea CI/CD + +## 目录 + +1. [架构概览](#1-架构概览) +2. [服务器准备](#2-服务器准备) +3. [配置环境变量](#3-配置环境变量) +4. [创建数据库](#4-创建数据库) +5. [手动首次部署](#5-手动首次部署) +6. [在 1Panel 中配置 OpenResty](#6-在-1panel-中配置-openresty) +7. [DNS 解析配置](#7-dns-解析配置) +8. [配置 Gitea Actions CI/CD](#8-配置-gitea-actions-cicd) +9. [验证部署](#9-验证部署) +10. [日常运维](#10-日常运维) + +--- + +## 1. 架构概览 + +``` +用户浏览器 + │ + ▼ +OpenResty (1Panel 管理, SSL, 端口 80/443) + │ + ├── makefire.fun → 127.0.0.1:3001 (Frontend Nginx 容器) + └── api.makefire.fun → 127.0.0.1:8000 (Backend FastAPI 容器) + │ + ▼ + PostgreSQL (1Panel 已有容器, 1panel-network) +``` + +**关键设计决策:** +- 后端和前端容器只绑定 `127.0.0.1`,不对外暴露,由 OpenResty 统一反代 +- 所有容器加入 `1panel-network`,可直接通过 `postgresql` 主机名访问已有数据库 +- 不使用 Traefik(用 1Panel 自带的 OpenResty 替代) +- 不启动独立 PostgreSQL 容器(复用已有的) + +--- + +## 2. 服务器准备 + +### 2.1 创建部署目录 + +```bash +# SSH 登录服务器后执行 +sudo mkdir -p /opt/fastapi-app +sudo chown $USER:$USER /opt/fastapi-app +``` + +### 2.2 初始化 Git 仓库(在 Gitea 上) + +1. 登录 Gitea(`http://your-server-ip:3000`) +2. 创建新仓库,例如 `full-stack-fastapi` +3. 在本地开发机上添加 Gitea 远程仓库: + +```bash +cd /Users/weifeng/Workspace/full-stack-fastapi-template + +# 添加 Gitea 远程 +git remote add gitea http://your-server-ip:3000/your-username/full-stack-fastapi.git + +# 推送代码 +git push gitea main +``` + +--- + +## 3. 配置环境变量 + +### 3.1 在服务器上创建生产环境文件 + +```bash +cd /opt/fastapi-app + +# 复制示例文件(首次需要从代码仓库获取) +cp .env.production.example .env.production +``` + +### 3.2 修改关键配置 + +```bash +nano .env.production +``` + +**必须修改的项:** + +```bash +# 生成强随机密钥 +SECRET_KEY=$(openssl rand -hex 32) +echo "生成的 SECRET_KEY: $SECRET_KEY" + +# 设置强管理员密码 +FIRST_SUPERUSER_PASSWORD=你的强密码 +``` + +> ⚠️ `SECRET_KEY` 和 `FIRST_SUPERUSER_PASSWORD` 不能使用默认值 `changethis`,否则生产环境会报错。 + +--- + +## 4. 创建数据库 + +在已有的 PostgreSQL 中为本项目创建专用数据库: + +```bash +# 方法一:使用 docker exec +docker exec -it 1Panel-postgresql-bxrK psql -U user_ZPKMQ6 -c "CREATE DATABASE app;" + +# 验证数据库是否创建成功 +docker exec -it 1Panel-postgresql-bxrK psql -U user_ZPKMQ6 -c "\l" | grep app +``` + +如果你想创建专用用户(更安全,可选): + +```bash +docker exec -it 1Panel-postgresql-bxrK psql -U user_ZPKMQ6 -c " +CREATE USER fastapi_user WITH PASSWORD 'your_strong_password'; +GRANT ALL PRIVILEGES ON DATABASE app TO fastapi_user; +ALTER DATABASE app OWNER TO fastapi_user; +" +``` + +> 如果使用专用用户,记得更新 `.env.production` 中的 `POSTGRES_USER` 和 `POSTGRES_PASSWORD`。 + +--- + +## 5. 手动首次部署 + +### 5.1 克隆代码到服务器 + +```bash +cd /opt/fastapi-app +git clone http://localhost:3000/your-username/full-stack-fastapi.git . +# 或者如果已经有代码 +git pull origin main +``` + +### 5.2 复制环境变量文件 + +确保 `.env.production` 在 `/opt/fastapi-app/` 目录下。 + +### 5.3 构建和启动 + +```bash +cd /opt/fastapi-app + +# 构建镜像 +docker compose -f compose.prod.yml build + +# 启动服务 +docker compose -f compose.prod.yml up -d + +# 查看日志 +docker compose -f compose.prod.yml logs -f +``` + +### 5.4 验证容器状态 + +```bash +# 查看运行状态 +docker compose -f compose.prod.yml ps + +# 测试后端 +curl http://127.0.0.1:8000/api/v1/utils/health-check/ + +# 测试前端 +curl -I http://127.0.0.1:3001 +``` + +--- + +## 6. 在 1Panel 中配置 OpenResty + +### 6.1 创建前端网站 + +1. 打开 1Panel → **网站** → **创建网站** +2. 选择 **反向代理** +3. 配置: + - **主域名**: `makefire.fun` + - **代理地址**: `http://127.0.0.1:3001` +4. 点击创建 + +### 6.2 创建后端 API 网站 + +1. **网站** → **创建网站** → **反向代理** +2. 配置: + - **主域名**: `api.makefire.fun` + - **代理地址**: `http://127.0.0.1:8000` +3. 点击创建 + +### 6.3 配置 SSL 证书 + +对每个网站: + +1. 点击网站名称进入设置 +2. 选择 **HTTPS** 标签 +3. 选择 **申请证书** → **Let's Encrypt** +4. 勾选 **自动续签** +5. 勾选 **HTTP → HTTPS 强制跳转** + +### 6.4 修改后端网站配置(可选优化) + +进入 `api.makefire.fun` 网站设置 → **配置文件**,在 `location /` 块中添加: + +```nginx +# WebSocket 支持 +proxy_http_version 1.1; +proxy_set_header Upgrade $http_upgrade; +proxy_set_header Connection "upgrade"; + +# 请求体大小限制 +client_max_body_size 10m; + +# 超时 +proxy_connect_timeout 60s; +proxy_send_timeout 60s; +proxy_read_timeout 60s; +``` + +> 完整参考配置见项目根目录的 `openresty-example.conf`。 + +--- + +## 7. DNS 解析配置 + +在你的域名 DNS 管理处(腾讯云 DNS 或其他)添加: + +| 记录类型 | 主机记录 | 记录值 | TTL | +|---------|---------|--------|-----| +| A | @ | 你的服务器 IP | 600 | +| A | api | 你的服务器 IP | 600 | + +> 如果使用腾讯云域名,进入 **DNS 解析 DNSPod** 配置。 + +--- + +## 8. 配置 Gitea Actions CI/CD + +### 8.1 安装 Gitea Actions Runner + +```bash +# 1. 下载 Gitea Actions Runner +# 访问 https://gitea.com/gitea/act_runner/releases 获取最新版本 +wget https://gitea.com/gitea/act_runner/releases/download/v0.2.11/act_runner-0.2.11-linux-amd64 +chmod +x act_runner-0.2.11-linux-amd64 +sudo mv act_runner-0.2.11-linux-amd64 /usr/local/bin/act_runner + +# 2. 生成配置文件 +cd /opt +act_runner generate-config > act_runner_config.yaml +``` + +### 8.2 修改 Runner 配置 + +编辑 `/opt/act_runner_config.yaml`,关键修改: + +```yaml +runner: + # 标签,决定 workflow 中 runs-on 可以匹配的值 + labels: + - "ubuntu-latest:host" + # ↑ 使用 host 模式,直接在服务器上运行(不在 Docker 中套 Docker) +``` + +> **为什么用 `host` 模式?** 因为 workflow 需要执行 `docker compose` 命令来管理服务器上的容器。若在容器中运行,需要额外配置 Docker-in-Docker,对低内存服务器更加不友好。 + +### 8.3 注册 Runner + +```bash +# 1. 在 Gitea 中获取 Runner Token +# 进入仓库 → Settings → Actions → Runners → 点击 "Create new runner" +# 复制显示的 Token + +# 2. 注册 +act_runner register \ + --instance http://localhost:3000 \ + --token YOUR_RUNNER_TOKEN \ + --name my-runner \ + --labels "ubuntu-latest:host" \ + --config /opt/act_runner_config.yaml \ + --no-interactive + +# 3. 启动(测试) +act_runner daemon --config /opt/act_runner_config.yaml +``` + +### 8.4 设置为系统服务(推荐) + +```bash +sudo tee /etc/systemd/system/gitea-runner.service << 'EOF' +[Unit] +Description=Gitea Actions Runner +After=network.target docker.service + +[Service] +Type=simple +User=root +WorkingDirectory=/opt/fastapi-app +ExecStart=/usr/local/bin/act_runner daemon --config /opt/act_runner_config.yaml +Restart=always +RestartSec=5 + +[Install] +WantedBy=multi-user.target +EOF + +sudo systemctl daemon-reload +sudo systemctl enable gitea-runner +sudo systemctl start gitea-runner + +# 查看状态 +sudo systemctl status gitea-runner +``` + +### 8.5 配置 Gitea Secrets + +在 Gitea 仓库中配置环境变量密钥: + +1. 进入仓库 → **Settings** → **Actions** → **Secrets** +2. 添加以下 Secrets: + +| Secret 名称 | 值 | +|-------------|-----| +| `DOMAIN` | `makefire.fun` | +| `FRONTEND_HOST` | `https://makefire.fun` | +| `PROJECT_NAME` | `Full Stack FastAPI Project` | +| `STACK_NAME` | `full-stack-fastapi-project` | +| `BACKEND_CORS_ORIGINS` | `https://makefire.fun,https://api.makefire.fun` | +| `SECRET_KEY` | *(用 `openssl rand -hex 32` 生成)* | +| `FIRST_SUPERUSER` | `admin@makefire.fun` | +| `FIRST_SUPERUSER_PASSWORD` | *(你的强密码)* | +| `SMTP_HOST` | *(留空或填写)* | +| `SMTP_USER` | *(留空或填写)* | +| `SMTP_PASSWORD` | *(留空或填写)* | +| `EMAILS_FROM_EMAIL` | `info@makefire.fun` | +| `SMTP_TLS` | `True` | +| `SMTP_SSL` | `False` | +| `SMTP_PORT` | `587` | +| `POSTGRES_SERVER` | `postgresql` | +| `POSTGRES_PORT` | `5432` | +| `POSTGRES_DB` | `app` | +| `POSTGRES_USER` | `user_ZPKMQ6` | +| `POSTGRES_PASSWORD` | `password_CYmsGt` | +| `SENTRY_DSN` | *(留空)* | +| `DOCKER_IMAGE_BACKEND` | `backend` | +| `DOCKER_IMAGE_FRONTEND` | `frontend` | + +### 8.6 启用 Gitea Actions + +1. 进入仓库 → **Settings** → **Repository** +2. 确保 **Actions** 功能已启用 +3. 如果 Gitea 全局未启用 Actions,需要在 Gitea 配置文件中添加: + +```ini +; 在 /opt/1panel/apps/gitea/gitea/data/gitea/conf/app.ini 中 +[actions] +ENABLED = true +``` + +修改后重启 Gitea 容器: + +```bash +docker restart 1Panel-gitea-FSXv +``` + +--- + +## 9. 验证部署 + +### 9.1 本地访问测试 + +```bash +# 后端健康检查 +curl http://127.0.0.1:8000/api/v1/utils/health-check/ + +# 前端页面 +curl -I http://127.0.0.1:3001 +``` + +### 9.2 域名访问测试 + +```bash +# 前端 +curl -I https://makefire.fun + +# 后端 API 文档 +curl -I https://api.makefire.fun/docs + +# 后端健康检查 +curl https://api.makefire.fun/api/v1/utils/health-check/ +``` + +### 9.3 CI/CD 测试 + +```bash +# 在本地开发机上 +cd /Users/weifeng/Workspace/full-stack-fastapi-template +echo "# test" >> README.md +git add . +git commit -m "test: trigger CI/CD" +git push gitea main +``` + +然后在 Gitea 仓库的 **Actions** 标签中查看运行状态。 + +--- + +## 10. 日常运维 + +### 查看日志 + +```bash +cd /opt/fastapi-app +docker compose -f compose.prod.yml logs -f backend # 后端日志 +docker compose -f compose.prod.yml logs -f frontend # 前端日志 +docker compose -f compose.prod.yml logs -f # 全部日志 +``` + +### 重启服务 + +```bash +docker compose -f compose.prod.yml restart backend +docker compose -f compose.prod.yml restart frontend +``` + +### 手动重新部署 + +```bash +cd /opt/fastapi-app +git pull origin main +docker compose -f compose.prod.yml build +docker compose -f compose.prod.yml down +docker compose -f compose.prod.yml up -d +``` + +### 清理 Docker 资源 + +```bash +# 清理无用镜像(释放磁盘空间) +docker image prune -f +docker system prune -f +``` + +### 数据库备份 + +```bash +# 备份 +docker exec 1Panel-postgresql-bxrK pg_dump -U user_ZPKMQ6 app > backup_$(date +%Y%m%d).sql + +# 恢复 +docker exec -i 1Panel-postgresql-bxrK psql -U user_ZPKMQ6 app < backup_20260311.sql +``` diff --git a/openresty-example.conf b/openresty-example.conf new file mode 100644 index 0000000..e56a41b --- /dev/null +++ b/openresty-example.conf @@ -0,0 +1,111 @@ +# ============================================================================= +# OpenResty / Nginx 反向代理配置示例 +# 此文件仅供参考,实际配置在 1Panel 网站管理中完成 +# ============================================================================= +# +# 在 1Panel 中需要创建 2 个网站: +# 1. makefire.fun → 前端 +# 2. api.makefire.fun → 后端 API +# +# 两个网站都需要: +# - 开启 SSL (1Panel 可自动申请 Let's Encrypt 证书) +# - 开启 HTTP → HTTPS 强制跳转 +# +# 以下是每个网站的反向代理配置内容: + +# ============================================= +# 网站 1: makefire.fun (前端) +# ============================================= +# 在 1Panel 中: 网站 → 创建网站 → 反向代理 +# 代理地址: http://127.0.0.1:3001 + +server { + listen 80; + listen 443 ssl http2; + server_name makefire.fun; + + # SSL 证书 (由 1Panel 自动管理,以下路径仅为示例) + # ssl_certificate /path/to/cert.pem; + # ssl_certificate_key /path/to/key.pem; + + # HTTP → HTTPS 跳转 + if ($scheme = http) { + return 301 https://$host$request_uri; + } + + # 前端反向代理 + location / { + proxy_pass http://127.0.0.1:3001; + 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; + + # SPA 路由支持 — 当后端 nginx 返回 404 时,由前端处理 + proxy_intercept_errors on; + error_page 404 = /index.html; + } + + # 静态资源缓存 + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + proxy_pass http://127.0.0.1:3001; + proxy_set_header Host $host; + expires 30d; + add_header Cache-Control "public, immutable"; + } + + # Gzip 压缩 + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml; +} + +# ============================================= +# 网站 2: api.makefire.fun (后端 API) +# ============================================= +# 在 1Panel 中: 网站 → 创建网站 → 反向代理 +# 代理地址: http://127.0.0.1:8000 + +server { + listen 80; + listen 443 ssl http2; + server_name api.makefire.fun; + + # SSL 证书 (由 1Panel 自动管理) + # ssl_certificate /path/to/cert.pem; + # ssl_certificate_key /path/to/key.pem; + + # HTTP → HTTPS 跳转 + if ($scheme = http) { + return 301 https://$host$request_uri; + } + + # API 反向代理 + location / { + proxy_pass http://127.0.0.1:8000; + 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 支持 (如后续需要) + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + + # 超时设置 + proxy_connect_timeout 60s; + proxy_send_timeout 60s; + proxy_read_timeout 60s; + + # 请求体大小限制 (文件上传等) + client_max_body_size 10m; + } + + # Gzip 压缩 + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; +}