// 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