From 65981b4bd60f0a5b1c24739ed8f566f00bd01670 Mon Sep 17 00:00:00 2001 From: javiservices Date: Thu, 18 Jun 2026 12:12:11 +0200 Subject: [PATCH] =?UTF-8?q?feat(deploy):=20webhook=20de=20Gitea=20?= =?UTF-8?q?=E2=86=92=20/webhook/deploy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gitea 1.22.6 no soporta Actions completo. Solución: - WebhookController valida HMAC-SHA256, dispara script en background - /usr/local/bin/dummybot-deploy.sh hace git pull + composer + npm + migrate + up - Webhook en Gitea → POST http://157.180.77.232:18080/webhook/deploy - Secreto DEPLOY_WEBHOOK_SECRET en .env, validado por X-Hub-Signature-256 - Mount del script y log dir al container dummybot_app --- app/Http/Controllers/WebhookController.php | 57 ++++++++++++++++++++++ docker-compose.yml | 3 ++ routes/web.php | 4 ++ 3 files changed, 64 insertions(+) create mode 100644 app/Http/Controllers/WebhookController.php diff --git a/app/Http/Controllers/WebhookController.php b/app/Http/Controllers/WebhookController.php new file mode 100644 index 0000000..88bf68a --- /dev/null +++ b/app/Http/Controllers/WebhookController.php @@ -0,0 +1,57 @@ +getContent(); + $sig = $r->header('X-Hub-Signature-256', ''); + + if ($secret && ! $this->validSignature($body, $sig, $secret)) { + Log::warning('[webhook] firma inválida', ['sig' => substr((string) $sig, 0, 20)]); + return response()->json(['ok' => false, 'error' => 'invalid signature'], 401); + } + + $payload = json_decode($body, true) ?: []; + $ref = $payload['ref'] ?? ''; + $branch = str_starts_with($ref, 'refs/heads/') ? substr($ref, 10) : 'main'; + + // Solo main (o el branch que se quiera) + if (! in_array($branch, ['main', 'master'], true)) { + Log::info("[webhook] push a '$branch' ignorado"); + return response()->json(['ok' => true, 'skipped' => 'branch != main'], 200); + } + + // Lanza el deploy en background, desacoplado del request + $script = '/usr/local/bin/dummybot-deploy.sh'; + if (is_executable($script)) { + $logDir = '/var/log/dummybot-deploy'; + @mkdir($logDir, 0755, true); + $logFile = $logDir . '/deploy-' . date('Ymd-His') . '.log'; + $cmd = sprintf('BRANCH=%s nohup %s >> %s 2>&1 &', escapeshellarg($branch), $script, escapeshellarg($logFile)); + exec($cmd); + Log::info("[webhook] deploy branch=$branch lanzado log=$logFile"); + return response()->json(['ok' => true, 'status' => 'deploy started', 'log' => basename($logFile)], 202); + } + + // Si el script no está (entorno local / sin permisos) — solo log + Log::info("[webhook] push branch=$branch (script no disponible)"); + return response()->json(['ok' => true, 'status' => 'logged only'], 200); + } + + private function validSignature(string $body, string $sig, string $secret): bool { + if (! str_starts_with($sig, 'sha256=')) return false; + $expected = 'sha256=' . hash_hmac('sha256', $body, $secret); + return hash_equals($expected, $sig); + } +} diff --git a/docker-compose.yml b/docker-compose.yml index 079325e..26ebc5b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,8 +10,11 @@ services: working_dir: /var/www volumes: - .:/var/www + - /usr/local/bin/dummybot-deploy.sh:/usr/local/bin/dummybot-deploy.sh:ro + - /var/log/dummybot-deploy:/var/log/dummybot-deploy environment: - AUTO_MIGRATE=${AUTO_MIGRATE:-0} + - DEPLOY_WEBHOOK_SECRET=${DEPLOY_WEBHOOK_SECRET:-} networks: - laravel diff --git a/routes/web.php b/routes/web.php index 1a6a23a..d31086f 100644 --- a/routes/web.php +++ b/routes/web.php @@ -2,6 +2,7 @@ use App\Http\Controllers\PageController; use App\Http\Controllers\TaskController; +use App\Http\Controllers\WebhookController; use App\Http\Controllers\Auth\LoginController; use App\Http\Controllers\Api\DeviceController; use App\Http\Controllers\Api\ProjectController; @@ -16,6 +17,9 @@ Route::post('/register',[LoginController::class, 'register']); Route::post('/logout', [LoginController::class, 'logout'])->middleware('auth:sanctum')->name('logout'); +// ─── Webhook de Gitea (dispara deploy) ─── +Route::post('/webhook/deploy', [WebhookController::class, 'deploy']); + // ─── Páginas protegidas ─── Route::middleware('auth:sanctum')->group(function () { Route::get('/app', [PageController::class, 'app'])->name('app');