Agrega scripts y runbooks: Cloudflare API, autostart Coolify, parche Chatwoot, deploy Solo Leveling + docs de casos

- scripts/Invoke-CloudflareApi.ps1: control de tunnel/DNS via API
- scripts/Install-CoolifyAutostart.ps1 + scripts/host/: autostart de LXC 102 + tunnel tras corte
- scripts/Apply-ChatwootEnterprisePatch.ps1: parche enterprise (autodetecta creds)
- Deploy-SoloLeveling.ps1: deploy build-on-server verificado
- docs/runbooks/cloudflare-tunnel.md y autostart-coolify.md
- docs/casos/chatwoot-enterprise-patch.md (credenciales redactadas)
- docs/AGENTS-coolify-apps.md + issues y reportes
- deploy_skill/references/coolify-4.1.2-notes.md: hallazgos verificados (seccion 7)
- package.json para verify-online.mjs del coolify-deploy skill
- .gitignore: excluye .claude/ y .opencode/ (config local de herramientas)
This commit is contained in:
urieljareth
2026-07-18 11:31:31 -06:00
parent e7d1c33cd5
commit f587a8baa2
23 changed files with 2312 additions and 2 deletions
+197
View File
@@ -0,0 +1,197 @@
# Runbook: Cloudflare Tunnel (coolify-tunnel)
Procedimiento para diagnosticar y corregir el túnel de Cloudflare que expone
Coolify (`coolify.urieljareth.org`) y las apps (`*.urieljareth.org`).
> **Dato clave:** el túnel está **gestionado desde el dashboard de Cloudflare**
> (remotely-managed), no por el `config.yml` local. El archivo
> `/etc/cloudflared/config.yml` dentro del contenedor solo contiene el UUID del
> túnel, las credenciales y el comodín `*.urieljareth.org`. **Todas las rutas de
> `coolify.urieljareth.org` (incluidas 6001/6002) viven en el dashboard / la API
> de Cloudflare.** Por eso un cambio de rutas NO se arregla editando archivos en
> el servidor — se arregla en el dashboard (rápido) o por la API (control total).
> La firma de esto en los logs es la línea `INF Updated to new configuration version=N`.
Síntomas típicos que llevan aquí:
- En la terminal de Coolify: `Terminal websocket connection lost. Reconnecting...`
- UI de Coolify inestable / reconexiones constantes / página en blanco al abrir una app
- En logs de `cloudflared`: `tls: first record does not look like a TLS handshake`
- Dominio de app con `502 Bad Gateway` o `530`
Documentos relacionados:
[ISSUE_cloudflare-tunnel_routing_websocket-tls-handshake.md](../ISSUE_cloudflare-tunnel_routing_websocket-tls-handshake.md) ·
[cloudflare-tunnel-coolify-agent_1.md](../cloudflare-tunnel-coolify-agent_1.md) ·
[issue-coolify-static-app-deploy.md](../issue-coolify-static-app-deploy.md)
---
## 1. Cargar entorno y verificar conexión
```powershell
# Secretos privados (gitignored) — necesario solo para llamadas a API
. .\.env.local.ps1
# Verifica config + SSH + Docker + API (sale 1 si algo falla)
.\scripts\Test-ProxmoxConnection.ps1
```
El SSH funciona con los defaults aunque no cargues `.env.local.ps1`.
---
## 2. Diagnóstico rápido (solo lectura)
### 2.1 Logs recientes del túnel
```powershell
.\scripts\Invoke-ProxmoxSsh.ps1 -Command "pct exec 102 -- docker logs cloudflared --tail 40"
```
Buscar:
- `Registered tunnel connection ... protocol=http2` (x4) → túnel arriba y sano
- `tls: first record does not look like a TLS handshake` → ruta 6001/6002 en `https://`
- `Lost connection with the edge` / `failed to dial to edge with quic` → ver nota de `--protocol http2`
### 2.2 Ver la configuración ACTIVA del túnel (lo que realmente aplica)
```powershell
.\scripts\Invoke-ProxmoxSsh.ps1 -Command "pct exec 102 -- docker logs cloudflared --tail 60" |
Select-String "Updated to new configuration"
```
Toma la línea con el `version=N` **más alto** (es la config vigente) y revisa el
JSON `ingress`. **Los puertos 6001 y 6002 deben decir `http://`, nunca `https://`.**
Cada vez que se guarda un cambio en el dashboard/API, `version` se incrementa.
### 2.3 Ver el `config.yml` local (opcional)
La imagen `cloudflared` es **distroless** (no tiene `cat`/`sh` propios para leer el
archivo directo), así que se copia con `docker cp` y se lee desde el LXC:
```powershell
.\scripts\Invoke-ProxmoxSsh.ps1 -Command "pct exec 102 -- sh -c 'docker cp cloudflared:/etc/cloudflared/config.yml /tmp/cf-config.yml && cat /tmp/cf-config.yml; rm -f /tmp/cf-config.yml'"
```
Esperado: solo `tunnel:`, `credentials-file:` y el comodín. Si aquí NO aparecen
las rutas de `coolify.urieljareth.org`, confirma que el túnel es dashboard-managed.
> Nunca leas `credentials.json` ni lo imprimas — contiene el secreto del túnel.
---
## 3. Reglas de oro de las rutas (ingress)
Cloudflare evalúa las rutas **de arriba hacia abajo**; las específicas van ANTES
del comodín. Esta es la configuración correcta (orden exacto):
| # | Hostname | Path | Servicio | Protocolo | Notas |
|---|----------|------|----------|-----------|-------|
| 1 | coolify.urieljareth.org | `/build/*` | `192.168.0.117:8000` | **http** | Build logs |
| 2 | coolify.urieljareth.org | `/project/*` | `192.168.0.117:8000` | **http** | **Crítico: UI de apps** |
| 3 | coolify.urieljareth.org | `/app/*` | `192.168.0.117:6001` | **http** ⚠️ | Websocket realtime |
| 4 | coolify.urieljareth.org | `/terminal/ws*` | `192.168.0.117:6002` | **http** ⚠️ | Terminal websocket |
| 5 | coolify.urieljareth.org | `*` | `192.168.0.117:8000` | **http** | Catch-all Coolify UI |
| 6 | `*.urieljareth.org` | `*` | `192.168.0.117:443` | **https** | Apps via Traefik (`noTLSVerify: true`) |
Reglas no negociables:
- **6001 y 6002 → `http://` siempre.** Esos puertos no hablan TLS; con `https://`
aparece `tls: first record does not look like a TLS handshake` y la terminal/realtime no conecta.
- **`/project/*` va ANTES de `/app/*`.** Si no, las URL con `/application/...` las
captura `/app/*` (puerto 6001) y la UI carga en blanco.
- **`/terminal/ws*` sin barra final** (no `/terminal/ws/` ni `/terminal/ws/*`).
- **`*.urieljareth.org` siempre al final**, con `noTLSVerify: true` (Traefik usa cert autofirmado interno).
---
## 4. Solución vía Dashboard (rápida, sin token)
1. Entrar a **Cloudflare Zero Trust → Networks → Tunnels → `coolify-tunnel` → Public Hostnames**.
2. Corregir cada ruta que esté mal según la tabla del paso 3. Lo más común:
- `/app/*` (6001): Service `HTTPS`**`HTTP`**.
- `/terminal/ws*` (6002): Service `HTTPS`**`HTTP`**; path sin barra final.
3. Guardar. **Cloudflared aplica el cambio solo, sin reinicio** (verás un nuevo `version=N`).
---
## 5. Solución vía API de Cloudflare (control total, requiere token)
Para gestionar el túnel sin tocar el dashboard (automatizable). Requiere un token
y los IDs cargados en `.env.local.ps1` (ver [Set-ProxmoxEnv.example.ps1](../../scripts/Set-ProxmoxEnv.example.ps1)):
```powershell
$env:CLOUDFLARE_API_TOKEN = "<token con Account:Cloudflare Tunnel:Edit + Zone:DNS:Edit>"
$env:CLOUDFLARE_ACCOUNT_ID = "<account id>"
$env:CLOUDFLARE_ZONE_ID = "<zone id de urieljareth.org>"
$env:CLOUDFLARE_TUNNEL_ID = "5f0e5c5b-a180-46f2-a090-44d151006a62"
```
Wrapper PowerShell: [scripts/Invoke-CloudflareApi.ps1](../../scripts/Invoke-CloudflareApi.ps1)
(mismo patrón que `Invoke-CoolifyApi.ps1`: Bearer token, `Invoke-RestMethod`).
```powershell
. .\.env.local.ps1
# Verificar que el token es válido y sus permisos
.\scripts\Invoke-CloudflareApi.ps1 -Path "/user/tokens/verify"
# Leer la configuración (ingress) actual del túnel
.\scripts\Invoke-CloudflareApi.ps1 `
-Path "/accounts/$($env:CLOUDFLARE_ACCOUNT_ID)/cfd_tunnel/$($env:CLOUDFLARE_TUNNEL_ID)/configurations"
# Aplicar la configuración corregida (PUT) — CONFIRMAR antes de ejecutar
$body = @{
config = @{
ingress = @(
@{ hostname = "coolify.urieljareth.org"; path = "/build/*"; service = "http://192.168.0.117:8000" }
@{ hostname = "coolify.urieljareth.org"; path = "/project/*"; service = "http://192.168.0.117:8000" }
@{ hostname = "coolify.urieljareth.org"; path = "/app/*"; service = "http://192.168.0.117:6001" }
@{ hostname = "coolify.urieljareth.org"; path = "/terminal/ws*"; service = "http://192.168.0.117:6002" }
@{ hostname = "coolify.urieljareth.org"; service = "http://192.168.0.117:8000" }
@{ hostname = "*.urieljareth.org"; service = "https://192.168.0.117:443"; originRequest = @{ noTLSVerify = $true } }
@{ service = "http_status:404" }
)
}
} | ConvertTo-Json -Depth 10
.\scripts\Invoke-CloudflareApi.ps1 -Method PUT `
-Path "/accounts/$($env:CLOUDFLARE_ACCOUNT_ID)/cfd_tunnel/$($env:CLOUDFLARE_TUNNEL_ID)/configurations" `
-BodyJson $body
```
> ⚠️ El último elemento `http_status:404` (sin hostname) es obligatorio o la API
> rechaza la configuración. La referencia completa de endpoints (DNS, crear túnel,
> obtener token) está en [cloudflare-tunnel-coolify-agent_1.md](../cloudflare-tunnel-coolify-agent_1.md).
**Cualquier `PUT`/`POST` a Cloudflare es un cambio de estado → confirmar con el usuario y
capturar el estado actual (paso 2.2) antes de aplicar, para tener rollback.**
---
## 6. Verificación
```powershell
# Debe aparecer un version=N mayor con 6001/6002 ya en http://
.\scripts\Invoke-ProxmoxSsh.ps1 -Command "pct exec 102 -- docker logs cloudflared --tail 10"
```
Luego en el navegador: recargar la UI de Coolify con **Ctrl+Shift+R** y abrir una
terminal en cualquier contenedor. Debe conectar sin `Terminal websocket connection lost`.
---
## Notas
- Los errores `tls: first record does not look like a TLS handshake` previos al
cambio son **residuales** de conexiones ya abiertas; desaparecen solos.
- Si el túnel se cae cada ~5 min con `failed to dial to edge with quic: timeout`,
el contenedor debe correr con `--protocol http2` (UDP/7844 suele estar bloqueado
en redes domésticas). Ver PASO 6 de [cloudflare-tunnel-coolify-agent_1.md](../cloudflare-tunnel-coolify-agent_1.md).
- `cloudflared` es distroless: para leer archivos internos usa `docker cp`, no `cat` directo.
---
**Verificado:** 2026-06-03 — corregidas rutas `/app/*` (6001) y `/terminal/ws*` (6002)
de `https://` a `http://` desde el dashboard; terminal de Coolify operativa
(config `version=16`).