fix: complete backend deployment hardening
This commit is contained in:
113
DEPLOYMENT.md
Normal file
113
DEPLOYMENT.md
Normal file
@@ -0,0 +1,113 @@
|
||||
# WhaleTown End V2 部署
|
||||
|
||||
本文档覆盖 NestJS API、原生 WebSocket 服务和 React 管理端的单机部署。示例域名和目录与 `deploy/nginx` 中的模板一致,可按实际环境替换。
|
||||
|
||||
## 1. 环境要求
|
||||
|
||||
- Node.js 20 或更高版本
|
||||
- pnpm 9
|
||||
- MySQL 8 和 Redis 7
|
||||
- PM2
|
||||
- Nginx
|
||||
- Python 3(仅皮肤生成功能需要)
|
||||
|
||||
生产目录默认为 `/var/www/whale-town-end-v2`。所有命令均在该目录执行。
|
||||
|
||||
## 2. 安装与配置
|
||||
|
||||
```bash
|
||||
pnpm install --frozen-lockfile
|
||||
cp .env.production.example .env
|
||||
chmod 600 .env
|
||||
```
|
||||
|
||||
编辑 `.env` 并至少完成以下配置:
|
||||
|
||||
- 为 `JWT_SECRET` 和 `ADMIN_TOKEN_SECRET` 设置独立的随机值。
|
||||
- 完整设置 `DB_HOST`、`DB_PORT`、`DB_USERNAME`、`DB_PASSWORD` 和 `DB_NAME`,避免服务回退到内存存储。
|
||||
- 完整设置 Redis 连接信息。
|
||||
- 保持 REST API 使用 `PORT=3000`,聊天 WebSocket 使用 `WEBSOCKET_PORT=3001`。
|
||||
- 使用 Zulip 时设置机器人凭据和至少 32 字节的 `ZULIP_API_KEY_ENCRYPTION_KEY`,并将 `ZULIP_DEGRADED_MODE_ENABLED` 设为 `false`。
|
||||
- 不使用 Zulip 时可将 `ZULIP_DEGRADED_MODE_ENABLED` 设为 `true` 并留空 Zulip 凭据;此时 Zulip 集成和 API Key 加密存取功能不可用。
|
||||
|
||||
可分别生成随机密钥:
|
||||
|
||||
```bash
|
||||
openssl rand -hex 32
|
||||
```
|
||||
|
||||
不要把 `.env`、生成的密钥或数据库备份提交到 Git。
|
||||
|
||||
## 3. 构建
|
||||
|
||||
构建后端:
|
||||
|
||||
```bash
|
||||
pnpm run build
|
||||
```
|
||||
|
||||
配置并构建管理端:
|
||||
|
||||
```bash
|
||||
cp client/.env.example client/.env.local
|
||||
pnpm --filter whale-town-admin run build
|
||||
```
|
||||
|
||||
确认 `client/.env.local` 中的 `VITE_API_BASE_URL` 指向实际后端 HTTPS 地址。该值在构建时写入管理端产物,修改后需要重新构建。
|
||||
|
||||
## 4. 启动服务
|
||||
|
||||
```bash
|
||||
pm2 start ecosystem.config.js
|
||||
pm2 save
|
||||
```
|
||||
|
||||
服务使用仓库根目录作为工作目录,并从根目录的 `.env` 加载运行配置。查看状态和日志:
|
||||
|
||||
```bash
|
||||
pm2 status
|
||||
pm2 logs whale-town-end-v2
|
||||
```
|
||||
|
||||
## 5. 配置 Nginx
|
||||
|
||||
安装后端和管理端模板:
|
||||
|
||||
```bash
|
||||
sudo cp deploy/nginx/whaletownend-v2.conf.example /etc/nginx/conf.d/whaletownend-v2.conf
|
||||
sudo cp deploy/nginx/whaletown-admin-v2.conf.example /etc/nginx/conf.d/whaletown-admin-v2.conf
|
||||
sudo nginx -t
|
||||
sudo systemctl reload nginx
|
||||
```
|
||||
|
||||
后端模板将 REST API 转发到 `3000`,将 `/game` 转发到独立的聊天 WebSocket 端口 `3001`,并为 `/location-broadcast` 和 `/ws/notice` 保留 REST 端口上的 WebSocket Upgrade。上线前还需在 Nginx 或上游代理配置 TLS。
|
||||
|
||||
## 6. 验收
|
||||
|
||||
```bash
|
||||
curl --fail https://whaletownend.xinghangee.icu/
|
||||
curl --fail https://whaletownend.xinghangee.icu/health
|
||||
curl --fail https://whaletownend.xinghangee.icu/api-docs
|
||||
```
|
||||
|
||||
根接口应返回 `version: 2.0.0`,健康接口应返回 `status: ok`。还应分别验证以下 WebSocket 地址能够完成 `101 Switching Protocols`:
|
||||
|
||||
- `wss://whaletownend.xinghangee.icu/game`
|
||||
- `wss://whaletownend.xinghangee.icu/location-broadcast`
|
||||
- `wss://whaletownend.xinghangee.icu/ws/notice`
|
||||
|
||||
最后使用管理端和游戏客户端完成登录、刷新令牌、世界聊天、位置同步和通知的冒烟测试。
|
||||
|
||||
## 7. 更新与回滚
|
||||
|
||||
更新前备份 `.env` 和数据库,然后执行:
|
||||
|
||||
```bash
|
||||
git pull --ff-only
|
||||
pnpm install --frozen-lockfile
|
||||
pnpm run build
|
||||
pnpm --filter whale-town-admin run build
|
||||
pm2 reload whale-town-end-v2
|
||||
```
|
||||
|
||||
出现问题时切回上一已验证提交,重新安装锁定依赖并构建,然后执行 `pm2 reload whale-town-end-v2`。数据库结构变更必须使用对应版本的迁移或备份恢复方案,不能只回滚应用代码。
|
||||
@@ -27,7 +27,7 @@ pnpm run build
|
||||
pnpm run start:prod
|
||||
```
|
||||
|
||||
启动前至少需要在 `.env` 中设置随机的 `JWT_SECRET`、`ADMIN_TOKEN_SECRET` 和 `ZULIP_API_KEY_ENCRYPTION_KEY`。生产环境请从 `.env.production.example` 开始配置,不要直接使用示例值。
|
||||
启动前至少需要在 `.env` 中设置随机的 `JWT_SECRET` 和 `ADMIN_TOKEN_SECRET`。启用 Zulip 时还必须设置 `ZULIP_API_KEY_ENCRYPTION_KEY`;若 `ZULIP_DEGRADED_MODE_ENABLED=true`,可以不配置 Zulip 凭据和加密密钥,但 Zulip 集成及 API Key 加密存取功能将不可用。生产环境请从 `.env.production.example` 开始配置,不要直接使用示例值。
|
||||
|
||||
API 默认监听 `3000` 端口,Swagger 地址为 `/api-docs`。
|
||||
|
||||
@@ -39,6 +39,10 @@ pnpm --filter whale-town-admin run build
|
||||
|
||||
管理端的 API 地址通过 `client/.env.local` 中的 `VITE_API_BASE_URL` 配置。
|
||||
|
||||
## 部署
|
||||
|
||||
生产部署、Nginx、PM2、验收和回滚步骤见 [DEPLOYMENT.md](DEPLOYMENT.md)。
|
||||
|
||||
## 安全
|
||||
|
||||
仓库不包含 `.env`、访问令牌、SSH 私钥、数据库文件、Redis 数据、日志或生成资产。敏感配置必须通过部署环境注入。
|
||||
|
||||
@@ -17,6 +17,32 @@ server {
|
||||
proxy_send_timeout 3600s;
|
||||
}
|
||||
|
||||
location = /location-broadcast {
|
||||
proxy_pass http://127.0.0.1:3000;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
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_read_timeout 3600s;
|
||||
proxy_send_timeout 3600s;
|
||||
}
|
||||
|
||||
location = /ws/notice {
|
||||
proxy_pass http://127.0.0.1:3000;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
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_read_timeout 3600s;
|
||||
proxy_send_timeout 3600s;
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:3000;
|
||||
proxy_http_version 1.1;
|
||||
|
||||
@@ -360,7 +360,7 @@ export class ConfigManagerService implements OnModuleDestroy {
|
||||
zulipBotEmail: process.env.ZULIP_BOT_EMAIL || 'bot@example.com',
|
||||
zulipBotApiKey: process.env.ZULIP_BOT_API_KEY || '',
|
||||
|
||||
websocketPort: parseInt(process.env.WEBSOCKET_PORT || '3000', 10),
|
||||
websocketPort: parseInt(process.env.WEBSOCKET_PORT || '3001', 10),
|
||||
websocketNamespace: process.env.WEBSOCKET_NAMESPACE || '/game',
|
||||
|
||||
messageRateLimit: parseInt(process.env.MESSAGE_RATE_LIMIT || '10', 10),
|
||||
@@ -1189,7 +1189,7 @@ export class ConfigManagerService implements OnModuleDestroy {
|
||||
zulipServerUrl: 'https://your-zulip-server.com',
|
||||
zulipBotEmail: 'bot@example.com',
|
||||
zulipBotApiKey: '',
|
||||
websocketPort: 3000,
|
||||
websocketPort: 3001,
|
||||
websocketNamespace: '/game',
|
||||
messageRateLimit: 10,
|
||||
messageMaxLength: 1000,
|
||||
|
||||
@@ -169,7 +169,7 @@ export const DEFAULT_ZULIP_CONFIG: ZulipConfiguration = {
|
||||
botApiKey: '',
|
||||
},
|
||||
websocket: {
|
||||
port: 3000,
|
||||
port: 3001,
|
||||
namespace: '/game',
|
||||
pingInterval: 25000,
|
||||
pingTimeout: 5000,
|
||||
|
||||
Reference in New Issue
Block a user