Files
mwitnessing/_deploy/webhook-receiver.js
2025-04-10 03:02:30 +03:00

137 lines
4.4 KiB
JavaScript

// Webhook receiver for Gitea push events
// This service listens for webhook events from Gitea and triggers a deployment
// when changes are pushed to the monitored branches
const express = require('express');
const http = require('http');
const crypto = require('crypto');
const { exec } = require('child_process');
const path = require('path');
const fs = require('fs');
// Configuration (can be moved to environment variables)
const config = {
port: process.env.WEBHOOK_PORT || 9000,
secret: process.env.WEBHOOK_SECRET || 'change-this-secret-in-production',
deployScript: process.env.DEPLOY_SCRIPT || path.join(__dirname, 'webhook-deploy.sh'),
logFile: process.env.LOG_FILE || path.join(__dirname, '../logs/webhook.log'),
allowedBranches: (process.env.ALLOWED_BRANCHES || 'main,master').split(','),
allowedRepositories: (process.env.ALLOWED_REPOSITORIES || 'mwhitnessing').split(',')
};
// Create logs directory if it doesn't exist
const logsDir = path.dirname(config.logFile);
if (!fs.existsSync(logsDir)) {
fs.mkdirSync(logsDir, { recursive: true });
}
// Create Express app
const app = express();
app.use(express.json());
// Helper function to log messages
function log(message) {
const timestamp = new Date().toISOString();
const logMessage = `[${timestamp}] ${message}\n`;
console.log(logMessage.trim());
fs.appendFileSync(config.logFile, logMessage);
}
// Verify Gitea webhook signature
function verifySignature(req) {
const signature = req.headers['x-gitea-signature'];
if (!signature) {
return false;
}
const hmac = crypto.createHmac('sha256', config.secret);
const computedSignature = hmac.update(JSON.stringify(req.body)).digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(computedSignature)
);
}
// Handle Gitea push webhook
app.post('/webhook', (req, res) => {
// Log receipt of webhook
log('Received webhook request');
try {
// Check if webhook signature is valid
if (!verifySignature(req)) {
log('Invalid webhook signature');
return res.status(403).send('Invalid signature');
}
// Extract relevant information from the webhook payload
const { ref, repository } = req.body;
// Check if this is a branch we care about
const branchName = ref.replace('refs/heads/', '');
if (!config.allowedBranches.includes(branchName)) {
log(`Ignoring push to branch: ${branchName}`);
return res.status(200).send('Ignored branch');
}
// Check if this is a repository we care about
const repoName = repository.name;
if (!config.allowedRepositories.includes(repoName)) {
log(`Ignoring push to repository: ${repoName}`);
return res.status(200).send('Ignored repository');
}
// Log the event
log(`Received push event for ${repoName}/${branchName}`);
// Respond to webhook immediately
res.status(200).send('Processing deployment');
// Execute the deployment script with the branch name
const command = `${config.deployScript} ${branchName}`;
log(`Executing: ${command}`);
exec(command, (error, stdout, stderr) => {
if (error) {
log(`Deployment error: ${error.message}`);
return;
}
if (stderr) {
log(`Deployment stderr: ${stderr}`);
}
log(`Deployment stdout: ${stdout}`);
log('Deployment completed successfully');
});
} catch (error) {
log(`Error processing webhook: ${error.message}`);
if (!res.headersSent) {
res.status(500).send('Error processing webhook');
}
}
});
// Start the server
const server = http.createServer(app);
server.listen(config.port, () => {
log(`Webhook receiver listening on port ${config.port}`);
log(`Monitoring branches: ${config.allowedBranches.join(', ')}`);
log(`Monitoring repositories: ${config.allowedRepositories.join(', ')}`);
});
// Handle server shutdown
process.on('SIGTERM', () => {
log('Shutting down webhook receiver...');
server.close(() => {
log('Webhook receiver stopped');
process.exit(0);
});
});
process.on('SIGINT', () => {
log('Shutting down webhook receiver...');
server.close(() => {
log('Webhook receiver stopped');
process.exit(0);
});
});