dummybot/docs/DEPLOY.md
javiservices 3a80d5e76b
Some checks failed
Deploy DummyBot / Build & Deploy to ${{ env.DEPLOY_HOST }} (push) Has been cancelled
docs: documentar deploy automático + firmware ESP32
2026-06-18 12:32:35 +02:00

8.2 KiB

Despliegue automático

DummyBot se despliega automáticamente a producción cuando se hace push a la rama main del repositorio.

push a main
  → Gitea dispara webhook "deploy"
  → POST http://dummybot_expose:80/webhook/deploy (red Docker interna)
  → Laravel valida HMAC-SHA256 (X-Hub-Signature-256)
  → exec('nohup deploy-runner.sh &')
  → git pull + composer install + npm ci + npm run build
  → php artisan migrate --force
  → php artisan optimize
  → Healthcheck GET /up
  → Log en storage/logs/deploy-YYYYMMDD-HHMMSS.log

Tiempo total: ~25-35 segundos desde push hasta server actualizado.


Arquitectura

┌─────────────────────┐
│ git.anabolictrack   │  Gitea 1.22.6
│ (rootless, 3000)    │  - Repos: dummybot, dummybot-firmware
└──────────┬──────────┘
           │ webhook push event
           │ POST http://dummybot_expose:80/webhook/deploy
           ▼
┌─────────────────────┐
│ 157.180.77.232:80   │  nginx-proxy-manager (172.20.0.2)
└──────────┬──────────┘
           │ red "proxy" (172.20.0.0/16)
           ▼
┌─────────────────────┐    ┌──────────────────────┐
│ dummybot_expose     │───▶│ dummybot_web (nginx) │
│ socat :80→web:80    │    │ /var/www/public      │
│ (172.20.0.8)        │    └──────────┬───────────┘
└─────────────────────┘               │
                                      ▼
                          ┌──────────────────────┐
                          │ dummybot_app (php-fpm)│
                          │ php artisan          │
                          │ /var/www             │
                          └──────────┬───────────┘
                                     │
                          ┌──────────▼───────────┐
                          │ dummybot_db (mysql)  │
                          └──────────────────────┘

Red Docker proxy (172.20.0.0/16): comparten gitea, dummybot_web, dummybot_expose, nginx-proxy-manager. Gitea hace POST al webhook usando http://dummybot_expose:80/webhook/deploy — resolución DNS por nombre de container, no atraviesa UFW ni la interfaz pública.

Volúmenes clave:

  • /root/dummybot:/var/www (en el container) — el repo clonado, mismo path en host y container.
  • /root/dummybot.env — backup del .env real (no se commitea).
  • /root/dummybot/storage/logs/ — logs de deploy.

Webhook de Gitea

Endpoint: POST /webhook/deploy Auth: HMAC-SHA256 con secreto compartido en header X-Hub-Signature-256. Trigger: push a rama main. Branch filter: main (configurado en Gitea).

Validación en Laravel (app/Http/Controllers/WebhookController.php):

  1. Lee DEPLOY_WEBHOOK_SECRET del .env.
  2. Calcula hash_hmac('sha256', body, secret) y compara con header.
  3. Decodifica payload, comprueba ref == "refs/heads/main".
  4. Lanza nohup bash deploy-runner.sh > log 2>&1 &.
  5. Responde 202 con path al log.

Excluido de CSRF en bootstrap/app.php:

$middleware->validateCsrfTokens(except: ['webhook/*']);

Configuración inicial del server

1. SSH key para acceso de emergencia

ssh-keygen -t ed25519 -C "deploy-key" -f ~/.ssh/dummybot_deploy -N ""
ssh-copy-id -i ~/.ssh/dummybot_deploy.pub root@157.180.77.232

2. Respaldo del .env

# En el server, una vez esté configurado
cp /root/dummybot/.env /root/dummybot.env

El deploy restaura .env desde /root/dummybot.env en cada push.

3. Generar secreto del webhook

openssl rand -hex 32  # → "75f02729eecd38c9..."

Guardarlo en dos sitios:

  • /root/dummybot/.env: DEPLOY_WEBHOOK_SECRET=<secret>
  • Configuración del webhook en Gitea (al crearlo).

4. Configurar Gitea para permitir webhooks a hosts internos

Gitea 1.22 tiene whitelist de hosts (webhook.ALLOWED_HOST_LIST). Por defecto está vacía y rechaza cualquier host. Editar /etc/gitea/app.ini:

[webhook]
ALLOWED_HOST_LIST = *
SKIP_TLS_VERIFY = false

Y reiniciar: docker restart gitea.

5. Crear el webhook en Gitea

SECRET=$(cat /root/dummybot-webhook-secret)

curl -X POST "https://git.anabolictrack.com/api/v1/repos/javiservices/dummybot/hooks" \
  -u "javiservices:<token>" \
  -H "Content-Type: application/json" \
  -d "{
    \"type\": \"gitea\",
    \"config\": {
      \"url\": \"http://dummybot_expose:80/webhook/deploy\",
      \"content_type\": \"json\",
      \"secret\": \"$SECRET\"
    },
    \"events\": [\"push\"],
    \"active\": true,
    \"branch_filter\": \"main\"
  }"

Importante: la URL debe usar el nombre del container (dummybot_expose) no la IP pública. Si el container se renombra, hay que actualizar el webhook.

6. Primer deploy manual

Como el endpoint /webhook/deploy no existe hasta que se despliega por primera vez, hay que hacerlo manualmente:

ssh root@157.180.77.232
cd /root/dummybot
git fetch origin main
git reset --hard origin/main
cp /root/dummybot.env .env
docker compose exec -T app php artisan migrate --force
docker compose exec -T app php artisan config:clear route:clear view:clear optimize
docker compose up -d --remove-orphans
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:18080/up  # debe ser 200

Tras este primer deploy, los siguientes son automáticos.


Verificación

Probar manualmente (sin push)

SECRET=$(cat /root/dummybot-webhook-secret)
BODY='{"ref":"refs/heads/main","pusher":{"name":"manual"}}'
SIG="sha256=$(echo -n "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | awk '{print $2}')"

curl -X POST "http://157.180.77.232:18080/webhook/deploy" \
  -H "Content-Type: application/json" \
  -H "X-Hub-Signature-256: $SIG" \
  -d "$BODY"

Respuesta esperada: {"ok":true,"status":"deploy started","log":"deploy-..."} con HTTP 202.

Ver logs

# Logs de deploy
ls -lat /root/dummybot/storage/logs/deploy-*.log | head

# Log de un deploy concreto
tail -50 /root/dummybot/storage/logs/deploy-20260618-102042.log

# Laravel general
tail -f /root/dummybot/storage/logs/laravel.log

Probar webhook desde Gitea

curl -X POST "https://git.anabolictrack.com/api/v1/repos/javiservices/dummybot/hooks/1/tests" \
  -u "javiservices:<token>"

HTTP 204 = OK. Si hay error de entrega, ver logs del container gitea: docker logs gitea --tail 50.


Troubleshooting

Síntoma Causa Solución
Push no dispara deploy webhook.ALLOWED_HOST_LIST no configurado Editar /etc/gitea/app.ini, reiniciar gitea
HTTP 419 al webhook CSRF no excluido Revisar bootstrap/app.php
HTTP 401 al webhook Firma inválida Verificar DEPLOY_WEBHOOK_SECRET en server y Gitea
Webhook devuelve "skipped: branch != main" Bug substr($ref, 10) debe ser 11 Actualizar WebhookController.php
Deploy log dice "Deploy OK" pero server tiene código viejo php artisan optimize no recarga Reiniciar container: docker compose restart app
docker compose up falla con "container name already in use" Container huérfano de un deploy anterior docker rm -f dummybot_expose y reintentar

Por qué webhook y no Actions

Gitea 1.22.6 tiene Actions parcial:

  • ✓ Registro de runners funciona
  • ✓ API de secrets/variables funciona
  • ✗ API de dispatch devuelve 404
  • ✗ Ejecución automática de workflows no se dispara

Gitea Actions se estabilizó en 1.23+. El webhook de Gitea es la solución estándar pre-Actions y funciona en cualquier versión.

Si en el futuro se actualiza Gitea, el workflow .gitea/workflows/deploy.yml ya está listo y se puede activar configurando el secreto DEPLOY_SSH_KEY en Gitea (la SSH key pública ya está en el server en /root/.ssh/authorized_keys).