建立推送机制 webhook
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
/**
|
||||
* CodeUp / GitLab 兼容的部署 Webhook 接收器
|
||||
* 监听 127.0.0.1:8095,由 Nginx 反代 /hooks/deploy
|
||||
*/
|
||||
import http from 'http';
|
||||
import { spawn } from 'child_process';
|
||||
import { readFileSync, existsSync } from 'fs';
|
||||
import { dirname, join } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
function loadEnv() {
|
||||
const envPath = join(__dirname, 'auto-release.env');
|
||||
if (!existsSync(envPath)) return;
|
||||
for (const line of readFileSync(envPath, 'utf8').split('\n')) {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed || trimmed.startsWith('#')) continue;
|
||||
const eq = trimmed.indexOf('=');
|
||||
if (eq === -1) continue;
|
||||
const key = trimmed.slice(0, eq).trim();
|
||||
let val = trimmed.slice(eq + 1).trim();
|
||||
if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
|
||||
val = val.slice(1, -1);
|
||||
}
|
||||
if (!(key in process.env)) process.env[key] = val;
|
||||
}
|
||||
}
|
||||
|
||||
loadEnv();
|
||||
|
||||
const PORT = Number(process.env.DEPLOY_WEBHOOK_PORT || 8095);
|
||||
const HOST = process.env.DEPLOY_WEBHOOK_HOST || '127.0.0.1';
|
||||
const SECRET = process.env.DEPLOY_WEBHOOK_SECRET || '';
|
||||
const ALLOWED_REF = process.env.DEPLOY_GIT_REF || 'refs/heads/dev';
|
||||
const APP_ROOT = process.env.APP_ROOT || '/opt/dukang-haoke';
|
||||
const RELEASE_SCRIPT = join(APP_ROOT, 'deploy', 'auto-release.sh');
|
||||
|
||||
function readBody(req) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const chunks = [];
|
||||
req.on('data', (c) => chunks.push(c));
|
||||
req.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
|
||||
req.on('error', reject);
|
||||
});
|
||||
}
|
||||
|
||||
function getToken(req) {
|
||||
return (
|
||||
req.headers['x-gitlab-token'] ||
|
||||
req.headers['x-codeup-token'] ||
|
||||
req.headers['x-deploy-token'] ||
|
||||
''
|
||||
);
|
||||
}
|
||||
|
||||
function json(res, status, data) {
|
||||
const body = JSON.stringify(data);
|
||||
res.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8' });
|
||||
res.end(body);
|
||||
}
|
||||
|
||||
function triggerRelease(trigger) {
|
||||
const child = spawn('bash', [RELEASE_SCRIPT], {
|
||||
detached: true,
|
||||
stdio: 'ignore',
|
||||
env: { ...process.env, DEPLOY_TRIGGER: trigger },
|
||||
});
|
||||
child.unref();
|
||||
}
|
||||
|
||||
const server = http.createServer(async (req, res) => {
|
||||
const url = new URL(req.url || '/', `http://${req.headers.host || 'localhost'}`);
|
||||
|
||||
if (req.method === 'GET' && url.pathname === '/health') {
|
||||
return json(res, 200, { ok: true, service: 'dukang-deploy-webhook' });
|
||||
}
|
||||
|
||||
if (req.method !== 'POST' || url.pathname !== '/deploy') {
|
||||
return json(res, 404, { ok: false, message: 'not found' });
|
||||
}
|
||||
|
||||
if (!SECRET) {
|
||||
return json(res, 503, { ok: false, message: 'webhook secret not configured' });
|
||||
}
|
||||
|
||||
const token = getToken(req);
|
||||
if (token !== SECRET) {
|
||||
return json(res, 403, { ok: false, message: 'invalid token' });
|
||||
}
|
||||
|
||||
let payload = {};
|
||||
try {
|
||||
const raw = await readBody(req);
|
||||
if (raw) payload = JSON.parse(raw);
|
||||
} catch {
|
||||
return json(res, 400, { ok: false, message: 'invalid json body' });
|
||||
}
|
||||
|
||||
const ref = payload.ref || payload.object_attributes?.ref || '';
|
||||
if (ref && ref !== ALLOWED_REF) {
|
||||
return json(res, 200, {
|
||||
ok: true,
|
||||
skipped: true,
|
||||
message: `ignored ref: ${ref} (allowed: ${ALLOWED_REF})`,
|
||||
});
|
||||
}
|
||||
|
||||
triggerRelease(ref || 'manual');
|
||||
return json(res, 202, {
|
||||
ok: true,
|
||||
accepted: true,
|
||||
message: 'deploy started',
|
||||
ref: ref || ALLOWED_REF,
|
||||
});
|
||||
});
|
||||
|
||||
server.listen(PORT, HOST, () => {
|
||||
console.log(`dukang deploy webhook listening on http://${HOST}:${PORT}`);
|
||||
});
|
||||
Reference in New Issue
Block a user