fix(deploy): excluir CSRF en /webhook/* + script self-contained
Some checks are pending
Deploy DummyBot / Build & Deploy to ${{ env.DEPLOY_HOST }} (push) Waiting to run
Some checks are pending
Deploy DummyBot / Build & Deploy to ${{ env.DEPLOY_HOST }} (push) Waiting to run
- bootstrap/app.php: validateCsrfTokens except webhook/* - WebhookController: ejecuta deploy-runner.sh (vive en repo) - deploy-runner.sh: hace git pull + composer + npm + migrate + cache - docker-compose.yml: solo monta el repo (sin script externo)
This commit is contained in:
parent
65981b4bd6
commit
00fcbc9a7c
@ -5,15 +5,21 @@
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
class WebhookController extends Controller {
|
||||
/**
|
||||
* Webhook de Gitea para disparar el deploy.
|
||||
* Gitea envía POST con X-Hub-Signature-256 (HMAC-SHA256).
|
||||
* Si la firma coincide, lanza el script de deploy en background.
|
||||
* Webhook de Gitea → deploy self-contained.
|
||||
*
|
||||
* Gitea envía POST al hacer push a main, con X-Hub-Signature-256.
|
||||
* Si la firma coincide, lanza `git pull` + migrate + cache clear
|
||||
* en background usando el binario git del container.
|
||||
*
|
||||
* IMPORTANTE: este código debe existir ANTES de que el webhook
|
||||
* se active. El primer deploy se hace a mano vía SSH.
|
||||
*/
|
||||
public function deploy(Request $r): JsonResponse {
|
||||
$secret = config('app.deploy_webhook_secret', env('DEPLOY_WEBHOOK_SECRET'));
|
||||
$secret = env('DEPLOY_WEBHOOK_SECRET');
|
||||
$body = $r->getContent();
|
||||
$sig = $r->header('X-Hub-Signature-256', '');
|
||||
|
||||
@ -26,27 +32,37 @@ public function deploy(Request $r): JsonResponse {
|
||||
$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);
|
||||
$logFile = storage_path('logs/deploy-' . date('Ymd-His') . '.log');
|
||||
$this->dispatchDeployInBackground($branch, $logFile);
|
||||
|
||||
Log::info("[webhook] deploy branch=$branch lanzado log=$logFile");
|
||||
return response()->json(['ok' => true, 'status' => 'deploy started', 'log' => basename($logFile)], 202);
|
||||
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 dispatchDeployInBackground(string $branch, string $logFile): void {
|
||||
$script = base_path('deploy-runner.sh');
|
||||
if (! file_exists($script)) {
|
||||
// Modo fallback: registrar en log y salir
|
||||
Log::warning("[webhook] $script no existe, deploy no ejecutado");
|
||||
return;
|
||||
}
|
||||
$cmd = sprintf(
|
||||
'BRANCH=%s BRANCH=%s nohup bash %s > %s 2>&1 &',
|
||||
escapeshellarg($branch),
|
||||
escapeshellarg($branch),
|
||||
escapeshellarg($script),
|
||||
escapeshellarg($logFile)
|
||||
);
|
||||
exec($cmd);
|
||||
}
|
||||
|
||||
private function validSignature(string $body, string $sig, string $secret): bool {
|
||||
|
||||
@ -16,6 +16,11 @@
|
||||
$middleware->alias([
|
||||
'auth.device' => \App\Http\Middleware\AuthenticateDevice::class,
|
||||
]);
|
||||
|
||||
// Excluir CSRF para el webhook de Gitea (autenticado por HMAC)
|
||||
$middleware->validateCsrfTokens(except: [
|
||||
'webhook/*',
|
||||
]);
|
||||
})
|
||||
->withExceptions(function (Exceptions $exceptions): void {
|
||||
$exceptions->shouldRenderJsonWhen(
|
||||
|
||||
59
deploy-runner.sh
Executable file
59
deploy-runner.sh
Executable file
@ -0,0 +1,59 @@
|
||||
#!/bin/bash
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# DummyBot – deploy runner (vive en el repo)
|
||||
# Llamado por el webhook de Gitea. Hace git pull + composer +
|
||||
# npm + migrate + cache clear + restart.
|
||||
#
|
||||
# IMPORTANTE: este script se ejecuta DENTRO del container
|
||||
# dummybot_app (PHP-FPM). Por tanto:
|
||||
# - git, composer, npm están disponibles (Dockerfile los instala)
|
||||
# - artisan funciona con `php artisan`
|
||||
# - `docker compose` se puede invocar contra el socket del host
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
set -e
|
||||
BRANCH="${BRANCH:-main}"
|
||||
APP_DIR="${APP_DIR:-/var/www}"
|
||||
cd "$APP_DIR"
|
||||
|
||||
echo "═══════════════════════════════════════════"
|
||||
echo " DummyBot deploy — $(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||
echo " Branch: $BRANCH"
|
||||
echo " App dir: $APP_DIR"
|
||||
echo "═══════════════════════════════════════════"
|
||||
|
||||
echo "→ git fetch + reset"
|
||||
git fetch origin "$BRANCH" 2>&1 | tail -3
|
||||
git reset --hard "origin/$BRANCH" 2>&1 | tail -3
|
||||
NEW_COMMIT=$(git rev-parse --short HEAD)
|
||||
echo "HEAD: $NEW_COMMIT"
|
||||
|
||||
echo "→ composer install"
|
||||
composer install --no-dev --no-interaction --no-progress --optimize-autoloader 2>&1 | tail -3
|
||||
|
||||
echo "→ npm install + build"
|
||||
if [ -f package-lock.json ]; then
|
||||
npm ci --no-audit --no-fund 2>&1 | tail -3
|
||||
else
|
||||
npm install --no-audit --no-fund 2>&1 | tail -3
|
||||
fi
|
||||
npm run build 2>&1 | tail -3 || echo " (build skipped)"
|
||||
|
||||
echo "→ artisan migrate"
|
||||
php artisan migrate --force 2>&1 | tail -5
|
||||
|
||||
echo "→ artisan cache:clear"
|
||||
php artisan config:clear 2>&1 | tail -1
|
||||
php artisan route:clear 2>&1 | tail -1
|
||||
php artisan view:clear 2>&1 | tail -1
|
||||
php artisan event:clear 2>&1 | tail -1
|
||||
php artisan optimize 2>&1 | tail -3
|
||||
|
||||
echo "→ restart php-fpm (graceful)"
|
||||
# Recargar el FPM para que coja el nuevo código
|
||||
if command -v kill &> /dev/null; then
|
||||
kill -USR2 1 2>&1 || true
|
||||
fi
|
||||
|
||||
echo "═══════════════════════════════════════════"
|
||||
echo " Deploy OK: $NEW_COMMIT"
|
||||
echo "═══════════════════════════════════════════"
|
||||
@ -10,8 +10,6 @@ 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:-}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user