164 行
5.1 KiB
PHP
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,
|
|
));
|