文件
Henglianshutong/public/webhook.php
2026-09-16 23:21:23 +08:00

164 行
5.1 KiB
PHP

<?php
/**
* Gitea 推送自动部署接收端
*
* GitCode 配置:
* - URL: https://henglian.gynhc.com/webhook.php
* - 内容类型: application/json
* - Secret: 与项目根目录 .webhook_secret 一致
* - 触发事件: Push Events
* - 分支过滤: main
*/
$projectDir = dirname(__DIR__);
$allowBranches = array('main', 'master');
$secretFile = $projectDir . '/.webhook_secret';
$logFile = $projectDir . '/runtime/log/webhook.log';
$triggerFile = $projectDir . '/runtime/deploy.trigger';
$deployScript = $projectDir . '/scripts/deploy.sh';
function webhook_log($logFile, $msg)
{
$dir = dirname($logFile);
if (!is_dir($dir)) {
@mkdir($dir, 0775, true);
}
$line = '[' . date('Y-m-d H:i:s') . '] [webhook] ' . $msg . PHP_EOL;
@file_put_contents($logFile, $line, FILE_APPEND | LOCK_EX);
}
function json_exit($code, $message, $httpCode = 200, $extra = array())
{
http_response_code($httpCode);
header('Content-Type: application/json; charset=utf-8');
echo json_encode(array_merge(array(
'code' => $code,
'message' => $message,
'time' => date('Y-m-d H:i:s'),
), $extra), JSON_UNESCAPED_UNICODE);
exit;
}
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
json_exit(405, 'Method Not Allowed', 405);
}
$secret = '';
if (is_readable($secretFile)) {
$secret = trim((string)file_get_contents($secretFile));
}
if ($secret === '') {
webhook_log($logFile, '未配置 .webhook_secret');
json_exit(500, 'Webhook secret not configured', 500);
}
$payload = file_get_contents('php://input');
if ($payload === false) {
$payload = '';
}
$remoteIp = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : 'unknown';
$event = '';
if (!empty($_SERVER['HTTP_X_GITEA_EVENT'])) {
$event = $_SERVER['HTTP_X_GITEA_EVENT'];
} elseif (!empty($_SERVER['HTTP_X_GITHUB_EVENT'])) {
$event = $_SERVER['HTTP_X_GITHUB_EVENT'];
}
$signature = '';
if (!empty($_SERVER['HTTP_X_GITEA_SIGNATURE'])) {
$signature = $_SERVER['HTTP_X_GITEA_SIGNATURE'];
} elseif (!empty($_SERVER['HTTP_X_GOGS_SIGNATURE'])) {
$signature = $_SERVER['HTTP_X_GOGS_SIGNATURE'];
} elseif (!empty($_SERVER['HTTP_X_HUB_SIGNATURE_256'])) {
$signature = preg_replace('/^sha256=/', '', $_SERVER['HTTP_X_HUB_SIGNATURE_256']);
}
$signatureMatched = false;
if ($signature !== '') {
$expected = hash_hmac('sha256', $payload, $secret);
if (hash_equals($expected, $signature)) {
$signatureMatched = true;
}
}
$token = '';
if (!empty($_GET['token'])) {
$token = $_GET['token'];
} elseif (!empty($_SERVER['HTTP_X_GITEA_TOKEN'])) {
$token = $_SERVER['HTTP_X_GITEA_TOKEN'];
} elseif (!empty($_SERVER['HTTP_X_TOKEN'])) {
$token = $_SERVER['HTTP_X_TOKEN'];
} elseif (!empty($_SERVER['HTTP_AUTHORIZATION'])) {
if (preg_match('/Bearer\s+(.+)/i', $_SERVER['HTTP_AUTHORIZATION'], $m)) {
$token = $m[1];
}
}
$tokenMatched = ($token !== '' && hash_equals($secret, $token));
if (!$signatureMatched && !$tokenMatched) {
webhook_log($logFile, '密钥验证失败 IP=' . $remoteIp);
json_exit(403, 'Forbidden', 403);
}
$data = json_decode($payload, true);
if (!is_array($data)) {
$data = array();
}
$ref = isset($data['ref']) ? $data['ref'] : '';
$branch = $ref !== '' ? preg_replace('#^refs/heads/#', '', $ref) : 'main';
if ($ref !== '' && strpos($ref, 'refs/heads/') !== 0) {
webhook_log($logFile, '忽略非分支推送: ' . $ref);
json_exit(0, 'Ref ignored', 200, array('ref' => $ref));
}
if ($ref !== '' && !in_array($branch, $allowBranches, true)) {
webhook_log($logFile, '分支不在白名单: ' . $branch);
json_exit(0, 'Branch ignored', 200, array('branch' => $branch));
}
$pusher = 'unknown';
if (!empty($data['pusher']['login'])) {
$pusher = $data['pusher']['login'];
} elseif (!empty($data['pusher']['username'])) {
$pusher = $data['pusher']['username'];
} elseif (!empty($data['pusher']['name'])) {
$pusher = $data['pusher']['name'];
} elseif (!empty($data['sender']['login'])) {
$pusher = $data['sender']['login'];
}
$after = isset($data['after']) ? $data['after'] : '';
$trigger = json_encode(array(
'time' => date('Y-m-d H:i:s'),
'event' => $event !== '' ? $event : 'push',
'branch' => $branch,
'pusher' => $pusher,
'after' => $after,
'ip' => $remoteIp,
), JSON_UNESCAPED_UNICODE);
if (@file_put_contents($triggerFile, $trigger) === false) {
webhook_log($logFile, '写入触发文件失败: ' . $triggerFile);
json_exit(500, 'Failed to write trigger', 500);
}
@chmod($triggerFile, 0664);
$started = false;
if (is_file($deployScript) && function_exists('exec') && !in_array('exec', array_map('trim', explode(',', (string)ini_get('disable_functions'))), true)) {
$cmd = '/bin/bash ' . escapeshellarg($deployScript) . ' --if-triggered > /dev/null 2>&1 &';
@exec($cmd);
$started = true;
}
webhook_log($logFile, '部署已触发 branch=' . $branch . ' pusher=' . $pusher . ' after=' . substr($after, 0, 12) . ' exec=' . ($started ? '1' : '0'));
json_exit(0, $started ? '部署已触发' : '部署任务已写入,等待 cron 执行', 200, array(
'branch' => $branch,
'pusher' => $pusher,
'started' => $started,
));