Compare commits
2 Commits
178130bb27
...
74ca9f90df
| Author | SHA1 | Date | |
|---|---|---|---|
| 74ca9f90df | |||
|
|
a907e64f40 |
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.local
|
||||
.env.*.local
|
||||
.env.production
|
||||
|
||||
# 部署相关敏感文件
|
||||
deploy.sh
|
||||
webhook-handler.js
|
||||
|
||||
# 日志
|
||||
*.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