116 lines
6.9 KiB
JavaScript
116 lines
6.9 KiB
JavaScript
// 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
|