Some checks failed
Deploy DummyBot / Build & Deploy to ${{ env.DEPLOY_HOST }} (push) Has been cancelled
245 lines
8.2 KiB
Markdown
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`).
|