bc/webhook.js

116 lines
6.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// webhook-receiver.js
const https = require('https'); // Используем https
const fs = require('fs'); // Для чтения файлов сертификатов
const express = require('express');
const crypto = require('crypto'); // Для проверки секрета Gitea (если будете использовать)
const { exec } = require('child_process'); // Для запуска скрипта деплоя
const path = require('path'); // Убедитесь, что path импортирован в начале файла
const app = express();
const port = 3800; // Порт, на котором слушает этот сервис
const ipAddress = "81.177.140.16"; // Ваш IP-адрес, на котором слушать
// --- Ваши переменные ---
const GITEA_SECRET = 'Hjp"f2mWF]3>Mc'; // Ваш секрет Gitea (используется для проверки подписи)
const DEPLOY_SCRIPT_PATH = path.join(__dirname, 'deploy-script.sh');
// --- Опции для HTTPS сервера ---
// Убедитесь, что пути к сертификатам верны и у Node.js есть права на их чтение
const sslOptions = {
key: fs.readFileSync('/etc/letsencrypt/live/pavel-chagovsky.com/privkey.pem'),
cert: fs.readFileSync('/etc/letsencrypt/live/pavel-chagovsky.com/fullchain.pem'),
};
// --- Middlewares ---
app.use(express.json()); // Для парсинга JSON тела запроса
// app.use(express.urlencoded({ extended: true })); // Если будете принимать form-urlencoded данные
// --- Маршруты ---
app.post('/', (req, res) => { // Слушаем POST-запросы на корневой путь "/"
console.log(`[${new Date().toISOString()}] --- HTTPS POST Request to / Received ---`);
console.log('Headers:', req.headers);
console.log('Body:', req.body);
// Опционально: Проверка секрета, если запрос от Gitea
// Эту часть можно закомментировать, если вы тестируете просто с cURL из PHP без секрета
const giteaSignature = req.headers['x-gitea-signature'];
if (GITEA_SECRET && giteaSignature) { // Проверяем только если секрет задан и подпись пришла
const hmac = crypto.createHmac('sha256', GITEA_SECRET);
// Важно: Gitea подписывает СЫРОЕ тело запроса.
// express.json() уже распарсил req.body. Для точной проверки подписи нужно использовать сырое тело.
// Для этого можно использовать middleware типа `raw-body` или настроить express.json с опцией `verify`.
// Пока для простоты пропустим точную проверку подписи, но в продакшене это ВАЖНО.
// Просто для примера, как это могло бы быть (требует доработки с raw body):
// const digest = 'sha256=' + hmac.update(JSON.stringify(req.body) /* НЕПРАВИЛЬНО для проверки подписи Gitea, нужно сырое тело */).digest('hex');
// if (!crypto.timingSafeEqual(Buffer.from(digest), Buffer.from(giteaSignature))) {
// console.warn(`[${new Date().toISOString()}] Webhook: Invalid signature`);
// return res.status(401).send('Invalid signature');
// }
console.log(`[${new Date().toISOString()}] Webhook: Gitea signature present (проверка пока упрощена).`);
} else if (GITEA_SECRET && !giteaSignature) {
console.warn(`[${new Date().toISOString()}] Webhook: Missing Gitea signature, but secret is configured.`);
// return res.status(401).send('Missing signature'); // Можно раскомментировать для строгости
}
// Запуск скрипта деплоя
console.log(`[${new Date().toISOString()}] Executing deploy script: ${DEPLOY_SCRIPT_PATH}`);
exec(DEPLOY_SCRIPT_PATH, (error, stdout, stderr) => {
if (error) {
console.error(`[${new Date().toISOString()}] Deploy Script Error: ${error.message}`);
console.error(`[${new Date().toISOString()}] Deploy Script Stderr: ${stderr}`);
// Не отправляем ошибку клиенту сразу, чтобы не раскрывать детали,
// но можно отправить общий код ошибки.
if (!res.headersSent) {
return res.status(500).send('Deployment script failed to execute.');
}
return;
}
if (stderr) {
console.warn(`[${new Date().toISOString()}] Deploy Script Stderr: ${stderr}`);
}
console.log(`[${new Date().toISOString()}] Deploy Script Stdout: ${stdout}`);
if (!res.headersSent) {
res.status(200).send('Webhook received and deployment script initiated.');
}
});
});
// GET-маршрут для простой проверки, что сервер жив
app.get('/', (req, res) => {
console.log(`[${new Date().toISOString()}] --- HTTPS GET Request to / Received ---`);
res.status(200).send('Express HTTPS server is running.');
});
// --- Запуск HTTPS сервера ---
const server = https.createServer(sslOptions, app);
server.listen(port, ipAddress, () => {
console.log(`[${new Date().toISOString()}] Express HTTPS server listening at https://${ipAddress}:${port}`);
});
server.on('error', (err) => {
console.error(`[${new Date().toISOString()}] Server critical error:`, err);
if (err.code === 'EADDRINUSE') {
console.error(`[${new Date().toISOString()}] Port ${port} on IP ${ipAddress} is already in use.`);
}
// process.exit(1); // Можно завершить процесс при критической ошибке сервера
});
// Обработка сигналов для корректного завершения (опционально, но хорошо для pm2/systemd)
function gracefulShutdown(signal) {
console.log(`[${new Date().toISOString()}] Received ${signal}. Shutting down server...`);
server.close(() => {
console.log(`[${new Date().toISOString()}] HTTP server closed.`);
// Здесь можно добавить закрытие других ресурсов, если они есть
process.exit(0);
});
// Если сервер не закрывается за таймаут, принудительно завершить
setTimeout(() => {
console.error(`[${new Date().toISOString()}] Could not close connections in time, forcefully shutting down.`);
process.exit(1);
}, 10000); // 10 секунд
}
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
process.on('SIGINT', () => gracefulShutdown('SIGINT')); // Ctrl+C