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

245 lines
8.2 KiB
Markdown

# 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`:
```php
$middleware->validateCsrfTokens(except: ['webhook/*']);
```
---
## Configuración inicial del server
### 1. SSH key para acceso de emergencia
```bash
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
```bash
# 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
```bash
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`:
```ini
[webhook]
ALLOWED_HOST_LIST = *
SKIP_TLS_VERIFY = false
```
Y reiniciar: `docker restart gitea`.
### 5. Crear el webhook en Gitea
```bash
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:
```bash
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)
```bash
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
```bash
# 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
```bash
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`).