feat: 添加生产环境部署配置

- 添加 Dockerfile 和 docker-compose.yml 支持容器化部署
- 添加 PM2 配置文件 ecosystem.config.js
- 添加部署脚本模板 deploy.sh.example
- 添加 Gitea webhook 处理器模板 webhook-handler.js.example
- 添加生产环境配置模板 .env.production.example
- 添加详细的部署指南 DEPLOYMENT.md
- 更新 .gitignore 排除敏感配置文件
This commit is contained in:
moyin
2025-12-17 15:37:40 +08:00
parent 178130bb27
commit a907e64f40
8 changed files with 482 additions and 0 deletions

View 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);
});
});