feat: 添加生产环境部署配置 #5
20
.env.production.example
Normal file
20
.env.production.example
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# 生产环境配置模板
|
||||||
|
# 复制此文件为 .env.production 并填入实际值
|
||||||
|
|
||||||
|
# 数据库配置
|
||||||
|
DB_HOST=localhost
|
||||||
|
DB_PORT=3306
|
||||||
|
DB_USERNAME=your_db_username
|
||||||
|
DB_PASSWORD=your_db_password
|
||||||
|
DB_NAME=your_db_name
|
||||||
|
|
||||||
|
# 应用配置
|
||||||
|
NODE_ENV=production
|
||||||
|
PORT=3000
|
||||||
|
|
||||||
|
# JWT 配置(如果有的话)
|
||||||
|
JWT_SECRET=your_jwt_secret_key_here_at_least_32_characters
|
||||||
|
JWT_EXPIRES_IN=7d
|
||||||
|
|
||||||
|
# 其他配置
|
||||||
|
# 根据项目需要添加其他环境变量
|
||||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -11,6 +11,11 @@ build/
|
|||||||
.env
|
.env
|
||||||
.env.local
|
.env.local
|
||||||
.env.*.local
|
.env.*.local
|
||||||
|
.env.production
|
||||||
|
|
||||||
|
# 部署相关敏感文件
|
||||||
|
deploy.sh
|
||||||
|
webhook-handler.js
|
||||||
|
|
||||||
# 日志
|
# 日志
|
||||||
*.log
|
*.log
|
||||||
|
|||||||
217
DEPLOYMENT.md
Normal file
217
DEPLOYMENT.md
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
# 部署指南
|
||||||
|
|
||||||
|
本文档详细说明如何部署 Pixel Game Server 到生产环境。
|
||||||
|
|
||||||
|
## 前置要求
|
||||||
|
|
||||||
|
- Node.js 18+
|
||||||
|
- pnpm 包管理器
|
||||||
|
- MySQL 8.0+
|
||||||
|
- PM2 进程管理器(推荐)
|
||||||
|
- Nginx(可选,用于反向代理)
|
||||||
|
|
||||||
|
## 部署步骤
|
||||||
|
|
||||||
|
### 1. 服务器环境准备
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 安装 Node.js (使用 NodeSource 仓库)
|
||||||
|
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
|
||||||
|
sudo apt-get install -y nodejs
|
||||||
|
|
||||||
|
# 安装 pnpm
|
||||||
|
curl -fsSL https://get.pnpm.io/install.sh | sh
|
||||||
|
source ~/.bashrc
|
||||||
|
|
||||||
|
# 安装 PM2
|
||||||
|
npm install -g pm2
|
||||||
|
|
||||||
|
# 安装 MySQL
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install mysql-server
|
||||||
|
sudo mysql_secure_installation
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 克隆项目
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 创建项目目录
|
||||||
|
sudo mkdir -p /var/www
|
||||||
|
cd /var/www
|
||||||
|
|
||||||
|
# 克隆项目(替换为你的实际仓库地址)
|
||||||
|
sudo git clone https://your-gitea-server.com/username/pixel-game-server.git
|
||||||
|
sudo chown -R $USER:$USER pixel-game-server
|
||||||
|
cd pixel-game-server
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 配置环境
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 复制环境配置文件
|
||||||
|
cp .env.production.example .env.production
|
||||||
|
|
||||||
|
# 编辑环境配置(填入实际的数据库信息)
|
||||||
|
nano .env.production
|
||||||
|
|
||||||
|
# 复制部署脚本
|
||||||
|
cp deploy.sh.example deploy.sh
|
||||||
|
chmod +x deploy.sh
|
||||||
|
|
||||||
|
# 编辑部署脚本(修改路径配置)
|
||||||
|
nano deploy.sh
|
||||||
|
|
||||||
|
# 复制 webhook 处理器
|
||||||
|
cp webhook-handler.js.example webhook-handler.js
|
||||||
|
|
||||||
|
# 编辑 webhook 处理器(修改密钥和路径)
|
||||||
|
nano webhook-handler.js
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 数据库设置
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 登录 MySQL
|
||||||
|
sudo mysql -u root -p
|
||||||
|
|
||||||
|
# 创建数据库和用户
|
||||||
|
CREATE DATABASE pixel_game_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
CREATE USER 'pixel_game'@'localhost' IDENTIFIED BY 'your_secure_password';
|
||||||
|
GRANT ALL PRIVILEGES ON pixel_game_db.* TO 'pixel_game'@'localhost';
|
||||||
|
FLUSH PRIVILEGES;
|
||||||
|
EXIT;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. 安装依赖和构建
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 安装依赖
|
||||||
|
pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
# 构建项目
|
||||||
|
pnpm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. 启动服务
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 使用 PM2 启动应用
|
||||||
|
pm2 start ecosystem.config.js --env production
|
||||||
|
|
||||||
|
# 保存 PM2 配置
|
||||||
|
pm2 save
|
||||||
|
|
||||||
|
# 设置开机自启
|
||||||
|
pm2 startup
|
||||||
|
# 按照提示执行显示的命令
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7. 配置 Nginx(可选)
|
||||||
|
|
||||||
|
创建 Nginx 配置文件:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo nano /etc/nginx/sites-available/pixel-game-server
|
||||||
|
```
|
||||||
|
|
||||||
|
添加以下内容:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name your-domain.com;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://localhost: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_cache_bypass $http_upgrade;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /webhook {
|
||||||
|
proxy_pass http://localhost:9000;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
启用站点:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo ln -s /etc/nginx/sites-available/pixel-game-server /etc/nginx/sites-enabled/
|
||||||
|
sudo nginx -t
|
||||||
|
sudo systemctl reload nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
## Gitea Webhook 配置
|
||||||
|
|
||||||
|
1. 在 Gitea 仓库中进入 **Settings** → **Webhooks**
|
||||||
|
2. 点击 **Add Webhook** → **Gitea**
|
||||||
|
3. 配置:
|
||||||
|
- **Target URL**: `http://your-server.com:9000/webhook` 或 `http://your-domain.com/webhook`
|
||||||
|
- **HTTP Method**: `POST`
|
||||||
|
- **POST Content Type**: `application/json`
|
||||||
|
- **Secret**: 与 `webhook-handler.js` 中的 `SECRET` 一致
|
||||||
|
- **Trigger On**: 选择 `Push events`
|
||||||
|
- **Branch filter**: `main`
|
||||||
|
|
||||||
|
## 验证部署
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 检查服务状态
|
||||||
|
pm2 status
|
||||||
|
|
||||||
|
# 查看日志
|
||||||
|
pm2 logs pixel-game-server
|
||||||
|
pm2 logs webhook-handler
|
||||||
|
|
||||||
|
# 测试 API
|
||||||
|
curl http://localhost:3000/
|
||||||
|
curl http://localhost:3000/api-docs
|
||||||
|
```
|
||||||
|
|
||||||
|
## 常用命令
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 重启服务
|
||||||
|
pm2 restart pixel-game-server
|
||||||
|
|
||||||
|
# 查看日志
|
||||||
|
pm2 logs pixel-game-server --lines 100
|
||||||
|
|
||||||
|
# 手动部署
|
||||||
|
bash deploy.sh
|
||||||
|
|
||||||
|
# 更新代码(不重启)
|
||||||
|
git pull origin main
|
||||||
|
pnpm install
|
||||||
|
pnpm run build
|
||||||
|
pm2 reload pixel-game-server
|
||||||
|
```
|
||||||
|
|
||||||
|
## 故障排除
|
||||||
|
|
||||||
|
### 服务无法启动
|
||||||
|
- 检查环境变量配置
|
||||||
|
- 检查数据库连接
|
||||||
|
- 查看 PM2 日志
|
||||||
|
|
||||||
|
### Webhook 不工作
|
||||||
|
- 检查防火墙设置
|
||||||
|
- 验证 webhook URL 可访问性
|
||||||
|
- 检查 Gitea webhook 日志
|
||||||
|
- 验证签名密钥是否一致
|
||||||
|
|
||||||
|
### 数据库连接失败
|
||||||
|
- 检查 MySQL 服务状态
|
||||||
|
- 验证数据库用户权限
|
||||||
|
- 检查网络连接
|
||||||
26
Dockerfile
Normal file
26
Dockerfile
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# 使用官方 Node.js 镜像
|
||||||
|
FROM node:18-alpine
|
||||||
|
|
||||||
|
# 设置工作目录
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# 安装 pnpm
|
||||||
|
RUN npm install -g pnpm
|
||||||
|
|
||||||
|
# 复制 package.json 和 pnpm-lock.yaml
|
||||||
|
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
|
||||||
|
|
||||||
|
# 安装依赖
|
||||||
|
RUN pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
# 复制源代码
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# 构建应用
|
||||||
|
RUN pnpm run build
|
||||||
|
|
||||||
|
# 暴露端口
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
# 启动应用
|
||||||
|
CMD ["pnpm", "run", "start:prod"]
|
||||||
54
deploy.sh.example
Normal file
54
deploy.sh.example
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# 部署脚本模板 - 用于 Gitea Webhook 自动部署
|
||||||
|
# 复制此文件为 deploy.sh 并根据服务器环境修改配置
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "开始部署 Pixel Game Server..."
|
||||||
|
|
||||||
|
# 项目路径(根据你的服务器实际路径修改)
|
||||||
|
PROJECT_PATH="/var/www/pixel-game-server"
|
||||||
|
BACKUP_PATH="/var/backups/pixel-game-server"
|
||||||
|
|
||||||
|
# 创建备份
|
||||||
|
echo "创建备份..."
|
||||||
|
mkdir -p $BACKUP_PATH
|
||||||
|
cp -r $PROJECT_PATH $BACKUP_PATH/backup-$(date +%Y%m%d-%H%M%S)
|
||||||
|
|
||||||
|
# 进入项目目录
|
||||||
|
cd $PROJECT_PATH
|
||||||
|
|
||||||
|
# 拉取最新代码
|
||||||
|
echo "拉取最新代码..."
|
||||||
|
git pull origin main
|
||||||
|
|
||||||
|
# 安装/更新依赖
|
||||||
|
echo "安装依赖..."
|
||||||
|
pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
# 构建项目
|
||||||
|
echo "构建项目..."
|
||||||
|
pnpm run build
|
||||||
|
|
||||||
|
# 重启服务
|
||||||
|
echo "重启服务..."
|
||||||
|
if command -v pm2 &> /dev/null; then
|
||||||
|
# 使用 PM2
|
||||||
|
pm2 restart pixel-game-server || pm2 start dist/main.js --name pixel-game-server
|
||||||
|
elif command -v docker-compose &> /dev/null; then
|
||||||
|
# 使用 Docker Compose
|
||||||
|
docker-compose down
|
||||||
|
docker-compose up -d --build
|
||||||
|
else
|
||||||
|
# 使用 systemd
|
||||||
|
sudo systemctl restart pixel-game-server
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "部署完成!"
|
||||||
|
|
||||||
|
# 清理旧备份(保留最近5个)
|
||||||
|
find $BACKUP_PATH -maxdepth 1 -type d -name "backup-*" | sort -r | tail -n +6 | xargs rm -rf
|
||||||
|
|
||||||
|
echo "服务状态检查..."
|
||||||
|
sleep 5
|
||||||
|
curl -f http://localhost:3000/health || echo "警告:服务健康检查失败"
|
||||||
36
docker-compose.yml
Normal file
36
docker-compose.yml
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
build: .
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=production
|
||||||
|
- DB_HOST=mysql
|
||||||
|
- DB_PORT=3306
|
||||||
|
- DB_USERNAME=pixel_game
|
||||||
|
- DB_PASSWORD=your_password
|
||||||
|
- DB_NAME=pixel_game_db
|
||||||
|
depends_on:
|
||||||
|
- mysql
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- ./logs:/app/logs
|
||||||
|
|
||||||
|
mysql:
|
||||||
|
image: mysql:8.0
|
||||||
|
environment:
|
||||||
|
- MYSQL_ROOT_PASSWORD=root_password
|
||||||
|
- MYSQL_DATABASE=pixel_game_db
|
||||||
|
- MYSQL_USER=pixel_game
|
||||||
|
- MYSQL_PASSWORD=your_password
|
||||||
|
ports:
|
||||||
|
- "3306:3306"
|
||||||
|
volumes:
|
||||||
|
- mysql_data:/var/lib/mysql
|
||||||
|
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
mysql_data:
|
||||||
38
ecosystem.config.js
Normal file
38
ecosystem.config.js
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
module.exports = {
|
||||||
|
apps: [
|
||||||
|
{
|
||||||
|
name: 'pixel-game-server',
|
||||||
|
script: 'dist/main.js',
|
||||||
|
instances: 1,
|
||||||
|
exec_mode: 'cluster',
|
||||||
|
env: {
|
||||||
|
NODE_ENV: 'production',
|
||||||
|
PORT: 3000
|
||||||
|
},
|
||||||
|
env_production: {
|
||||||
|
NODE_ENV: 'production',
|
||||||
|
PORT: 3000
|
||||||
|
},
|
||||||
|
log_file: './logs/combined.log',
|
||||||
|
out_file: './logs/out.log',
|
||||||
|
error_file: './logs/error.log',
|
||||||
|
log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
|
||||||
|
merge_logs: true,
|
||||||
|
max_memory_restart: '1G',
|
||||||
|
restart_delay: 4000,
|
||||||
|
watch: false,
|
||||||
|
ignore_watch: ['node_modules', 'logs']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'webhook-handler',
|
||||||
|
script: 'webhook-handler.js',
|
||||||
|
instances: 1,
|
||||||
|
env: {
|
||||||
|
NODE_ENV: 'production',
|
||||||
|
PORT: 9000
|
||||||
|
},
|
||||||
|
restart_delay: 4000,
|
||||||
|
watch: false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
86
webhook-handler.js.example
Normal file
86
webhook-handler.js.example
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
const http = require('http');
|
||||||
|
const crypto = require('crypto');
|
||||||
|
const { exec } = require('child_process');
|
||||||
|
|
||||||
|
// 配置 - 复制此文件为 webhook-handler.js 并修改配置
|
||||||
|
const PORT = 9000;
|
||||||
|
const SECRET = 'your_webhook_secret_change_this'; // 与 Gitea 中配置的密钥一致
|
||||||
|
const DEPLOY_SCRIPT = '/var/www/pixel-game-server/deploy.sh'; // 修改为实际路径
|
||||||
|
|
||||||
|
// 验证 Gitea 签名
|
||||||
|
function verifySignature(payload, signature, secret) {
|
||||||
|
const hmac = crypto.createHmac('sha256', secret);
|
||||||
|
hmac.update(payload);
|
||||||
|
const calculatedSignature = hmac.digest('hex');
|
||||||
|
return crypto.timingSafeEqual(
|
||||||
|
Buffer.from(signature, 'hex'),
|
||||||
|
Buffer.from(calculatedSignature, 'hex')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建 HTTP 服务器
|
||||||
|
const server = http.createServer((req, res) => {
|
||||||
|
if (req.method !== 'POST') {
|
||||||
|
res.writeHead(405, { 'Content-Type': 'text/plain' });
|
||||||
|
res.end('Method Not Allowed');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let body = '';
|
||||||
|
req.on('data', chunk => {
|
||||||
|
body += chunk.toString();
|
||||||
|
});
|
||||||
|
|
||||||
|
req.on('end', () => {
|
||||||
|
try {
|
||||||
|
// 验证签名
|
||||||
|
const signature = req.headers['x-gitea-signature'];
|
||||||
|
if (!signature || !verifySignature(body, signature.replace('sha256=', ''), SECRET)) {
|
||||||
|
console.log('签名验证失败');
|
||||||
|
res.writeHead(401, { 'Content-Type': 'text/plain' });
|
||||||
|
res.end('Unauthorized');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = JSON.parse(body);
|
||||||
|
|
||||||
|
// 检查是否是推送到 main 分支
|
||||||
|
if (payload.ref === 'refs/heads/main') {
|
||||||
|
console.log('收到 main 分支推送,开始部署...');
|
||||||
|
|
||||||
|
// 执行部署脚本
|
||||||
|
exec(`bash ${DEPLOY_SCRIPT}`, (error, stdout, stderr) => {
|
||||||
|
if (error) {
|
||||||
|
console.error('部署失败:', error);
|
||||||
|
console.error('stderr:', stderr);
|
||||||
|
} else {
|
||||||
|
console.log('部署成功:', stdout);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||||
|
res.end('Deployment triggered');
|
||||||
|
} else {
|
||||||
|
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||||
|
res.end('Not main branch, ignored');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('处理 webhook 失败:', error);
|
||||||
|
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
||||||
|
res.end('Internal Server Error');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
server.listen(PORT, () => {
|
||||||
|
console.log(`Webhook 处理器运行在端口 ${PORT}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 优雅关闭
|
||||||
|
process.on('SIGTERM', () => {
|
||||||
|
console.log('收到 SIGTERM,正在关闭服务器...');
|
||||||
|
server.close(() => {
|
||||||
|
console.log('服务器已关闭');
|
||||||
|
process.exit(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user