# Deep dive #11 — Github CI fix (deploy.yml)

**Status**: ✅ DONE (2026-05-02)
**Frente master**: #11 NOW
**Tiempo invertido**: ~30 min
**Doc relacionado**: [00-master-roadmap.md](./00-master-roadmap.md)
**Doc operativo del flujo CI completo (2 repos + 3 workflows)**: [../infrastructure/06-frontend-pr-workflow.md](../infrastructure/06-frontend-pr-workflow.md)

---

## Situación inicial (antes del fix)

- **Workflow afectado**: `.github/workflows/deploy.yml` ("Deploy → VPS1 (bewpro22)")
- **Síntoma**: TODOS los runs del workflow fallan en 15-27 segundos consistentemente desde hace ≥1 día
- **Workflow `build-runtime-image.yml`**: ✅ pasa OK en 4-5 min (no afectado)
- **Impacto**: cada `git push` a `cd-system` no se autodeploya en bewpro22 → manual `git pull` requerido en VPS1

## Causa raíz

El workflow espera 2 secrets que **NO estaban seteados en el repo**:

```
##[error] VPS1_SSH_HOST VPS1_SSH_KEY
SSH_HOST: (vacío)
SSH_KEY: (vacío)
```

`gh secret list` confirmó: 0 secrets configurados.

## Fix aplicado

### 1. Generar keypair dedicada (NO reusar keys personales)

```bash
ssh-keygen -t ed25519 -N "" -C "github-actions-deploy@bewpro $(date +%Y-%m-%d)" \
    -f /tmp/bewpro_gh_deploy_key
```

**Razón de keypair dedicada**: separación de blast radius. Si el repo se compromete, solo se pierde acceso desde CI, no las keys personales del founder/devs.

### 2. Subir public key a `/root/.ssh/authorized_keys` de VPS1

```bash
PUB=$(cat /tmp/bewpro_gh_deploy_key.pub)
ssh vps1-claude "echo '$PUB' >> /root/.ssh/authorized_keys"
```

⚠️ **Edge case encontrado**: el archivo `authorized_keys` no terminaba en `\n`, así que el `>>` concatenó la nueva key con la última línea sin separador. Resultado: 2 keys quedaron en una sola línea inválida (`...juan@vps1ssh-ed25519 AAAA...`).

**Lesson learned**: siempre garantizar newline antes de append:

```bash
ssh vps1-claude "
[ -s /root/.ssh/authorized_keys ] && echo '' >> /root/.ssh/authorized_keys
echo '$PUB' >> /root/.ssh/authorized_keys
"
```

### 3. Setear GH secrets

```bash
echo "72.61.45.136" | gh secret set VPS1_SSH_HOST
gh secret set VPS1_SSH_KEY < /tmp/bewpro_gh_deploy_key
```

Verificación:

```
$ gh secret list
VPS1_SSH_HOST   2026-05-02T17:44:15Z
VPS1_SSH_KEY    2026-05-02T17:44:15Z
```

### 4. Cleanup local (security)

```bash
rm -f /tmp/bewpro_gh_deploy_key /tmp/bewpro_gh_deploy_key.pub
```

La private key NO debe quedar en disco del laptop del founder. Vive solo en:
- GH Secrets (encrypted at rest)
- Memoria efímera del runner durante deploy

### 5. Validación

```bash
gh workflow run deploy.yml --ref cd-system
gh run watch <run-id>
```

**Resultado**: `completed:success` en **1m11s**, log final dice:
```
::notice::Deploy OK — HEAD ahora 878936be
```

## Anatomía del workflow fixed

```
trigger: push a cd-system | workflow_dispatch
   ↓
1. Checkout (actions/checkout@v4)
2. Verify secrets present  ← FIX en este step
3. Setup SSH key (chmod 600 + ssh-keyscan)
4. Deploy via SSH:
   - git fetch + reset --hard origin/cd-system
   - composer install --no-dev --optimize-autoloader
   - artisan migrate --force
   - artisan {config,view,route}:clear
   - artisan queue:restart
   - smoke test HTTP bewpro.com
   - sync-scripts.sh --check
5. Cleanup deploy_key del runner
```

## Mantenimiento futuro

### Rotación de la SSH key (recomendado cada 6-12 meses)

```bash
# 1. Generar nueva keypair
ssh-keygen -t ed25519 -N "" -C "github-actions-deploy@bewpro $(date +%Y-%m-%d)" \
    -f /tmp/bewpro_gh_deploy_key_v2
# 2. Agregar nueva pub a VPS1
ssh vps1-claude "echo '' >> /root/.ssh/authorized_keys && cat >> /root/.ssh/authorized_keys" < /tmp/bewpro_gh_deploy_key_v2.pub
# 3. Update GH secret
gh secret set VPS1_SSH_KEY < /tmp/bewpro_gh_deploy_key_v2
# 4. Trigger run para validar
gh workflow run deploy.yml --ref cd-system
# 5. Después de 1 deploy exitoso: borrar pub vieja del authorized_keys
ssh vps1-claude "grep -v 'github-actions-deploy@bewpro 2026-05-02' /root/.ssh/authorized_keys > /tmp/ak && mv /tmp/ak /root/.ssh/authorized_keys"
# 6. Cleanup local
rm -f /tmp/bewpro_gh_deploy_key_v2*
```

### Warning leve (no urgente)

```
Node.js 20 actions are deprecated. Forced to Node.js 24 from June 2nd, 2026.
```

`actions/checkout@v4` actualmente corre en Node 20. **Acción requerida antes de junio 2026**: bumpear a una versión que soporte Node 24 cuando esté disponible. Sin urgencia hoy.

### Observabilidad

Si futuros runs fallan, ver log:

```bash
gh run list --workflow=deploy.yml --status=failure --limit=3
gh run view <id> --log-failed
```

Errores comunes a esperar:
| Error | Causa probable | Fix |
|-------|----------------|-----|
| `Permission denied (publickey)` | Pub key removida de VPS1 o key rotada | Re-agregar a authorized_keys |
| `Host key verification failed` | VPS1 reinstalado/cambió host key | `ssh-keyscan` actualizada en step 3 |
| `composer install` falla | Conflictos de deps en composer.lock | `composer update` local + commit |
| `migrate --force` falla | Migration con sintaxis error | Revisar última migration |
| Smoke test HTTP `!= 2xx/3xx` | bewpro.com caído / config rota | SSH manual + `tail storage/logs/laravel.log` |

## Resultados / KPIs

| Métrica | Antes | Después |
|---------|-------|---------|
| Run success rate (deploy.yml) | 0% (10+ runs falló en cadena) | 100% (1/1 desde fix) |
| Tiempo de deploy | manual (∞) | **1m11s automatizado** |
| Manual git pull en VPS1 post push | requerido | no requerido |

## Decisiones registradas

- **Keypair dedicada vs reuse de personal key**: dedicada (separación blast radius)
- **Donde vive la pub key**: `/root/.ssh/authorized_keys` (deploy necesita root para `sudo -u bewpro22`)
- **Donde vive la private key**: solo en GH Secrets (encrypted) + memoria efímera del runner

---

*Cerrado: 2026-05-02*
