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