/api/auto_update.php and the platform updates itself on every push to // the branch it is running. // // Assumed location: /shared/platform-tools/api/auto_update.php // dirname(__DIR__, 3) === // The per-platform stub at /api/auto_update.php is a one-line require. // // Answers JSON always, and with a status code the webhook UI can be read from: // 202 accepted (work continues after the reply) / disabled // 200 skipped — push was for another branch // 401 bad signature · 405 wrong method · 503 no secret configured // // A refusal is deliberately a 4xx/5xx: Gitea shows the delivery in red and // keeps the response body, which is the only place a rejected delivery would // ever be noticed before someone opens the platform page. // // Everything that can be answered instantly IS answered instantly, and only the // slow part runs after the reply. Gitea allows a webhook delivery 5 seconds by // default; a git pull alone exceeds that and a pre-update backup exceeds it by // minutes, so a synchronous reply would mark every successful deploy as failed. // The outcome is recorded in auto_update_last_* — the platform page and the // dashboard strip are where you read it. $repo_root = dirname(__DIR__, 3); require_once $repo_root . '/config/db.php'; // opens $pdo, starts the session require_once $repo_root . '/config/functions.php'; // env(), helpers require_once __DIR__ . '/../functions_auto_update.php'; header('Content-Type: application/json; charset=utf-8'); $reply = function (int $code, array $data): never { http_response_code($code); echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); exit; }; if ($_SERVER['REQUEST_METHOD'] !== 'POST') { $reply(405, ['ok' => false, 'error' => 'Só POST.']); } // Read the body ONCE and verify against those exact bytes — re-reading // php://input after the fact can come back empty and would silently turn the // signature check into a no-op. $body = (string) file_get_contents('php://input'); $secret = auto_update_secret(); if ($secret === '') { $reply(503, ['ok' => false, 'error' => 'AUTO_UPDATE_SECRET não está definido no .env desta plataforma.']); } if (!auto_update_verify($body, $secret)) { $reply(401, ['ok' => false, 'error' => 'Assinatura ausente ou inválida.']); } // Signature first, switch second: an unauthenticated caller learns nothing // about this platform, not even whether the feature is on. if (!auto_update_enabled($pdo)) { $reply(202, ['ok' => true, 'status' => 'disabled', 'message' => 'Auto-update desligado nesta plataforma.']); } // A push to a branch this server is not running is not our business — the // webhook fires for every branch, and pulling one you are not on does nothing // useful at best. $ref = auto_update_payload_branch($body); if ($ref !== null && function_exists('git_run')) { $cur = git_run(['rev-parse', '--abbrev-ref', 'HEAD'], $repo_root); $cur = $cur['ok'] ? trim($cur['output']) : ''; if ($cur !== '' && $cur !== $ref) { $reply(200, ['ok' => true, 'status' => 'skipped', 'message' => "Push para {$ref}; este servidor está em {$cur}."]); } } // Server-to-server past this point: drop the session lock so the pull (which // can take a while) does not hold up the operator's own browsing. @session_write_close(); // Answer Gitea now; do the slow work with the connection already closed. platform_respond_and_continue(202, [ 'ok' => true, 'status' => 'accepted', 'message' => 'Atualização iniciada. O resultado fica registado na plataforma.', 'ref' => $ref, ]); auto_update_run($pdo, $repo_root, $ref);