Actualiza toolkit operativo y documentación
This commit is contained in:
@@ -0,0 +1,180 @@
|
||||
# Caso: evolution-go (API WhatsApp en Go) desplegado junto a Chatwoot
|
||||
|
||||
> Resuelto el **2026-09-02** contra el host real.
|
||||
> Target: **LXC 102** (`coolify`), proyecto `AI AGENCY` / `production`.
|
||||
> Resultado: `https://evo.urieljareth.org/server/ok` → **HTTP 200**
|
||||
> (`{"status":"ok"}`), Manager UI operativo, contenedores `healthy`.
|
||||
> **Licencia ACTIVA desde el 2026-09-02** (vía OAuth Google del portal; ver §3.0).
|
||||
|
||||
---
|
||||
|
||||
## 0. Resumen ejecutivo
|
||||
|
||||
El usuario pidió clonar
|
||||
[evolution-foundation/evolution-go](https://github.com/evolution-foundation/evolution-go)
|
||||
(rewritten en Go de Evolution API, motor WhatsApp sobre whatsmeow) e
|
||||
"implementar la funcionalidad" en el servicio Chatwoot
|
||||
(`/project/cho4d488omzjm4noyz98mwq7/environment/xhcy6urwmtk0onnxoeec24hq/service/c11xzy2tx2cdapm32f5b89vy`).
|
||||
|
||||
**Decisión:** el stack se desplegó como **servicio hermano** (`evolution-go`,
|
||||
uuid `j0jkacsfcgypm2jmpillls01`) en el **mismo proyecto y entorno** que Chatwoot,
|
||||
no dentro del compose de Chatwoot. Motivos:
|
||||
|
||||
- Editar el compose del stack Chatwoot fuerza un redeploy completo de Chatwoot
|
||||
(riesgo sobre un stack parcheado a mano — ver
|
||||
[chatwoot-enterprise-patch.md](chatwoot-enterprise-patch.md)).
|
||||
- Evolution-go necesita su propio PostgreSQL; meterle una DB ajena al stack de
|
||||
Chatwoot complica el rollback.
|
||||
- La integración WhatsApp→Chatwoot se hace por webhook/API hacia el FQDN público
|
||||
de Chatwoot — no requiere red compartida.
|
||||
|
||||
El clon local vive en `projects/evolution-go` (tag 0.7.2). El compose de
|
||||
despliegue vive versionado en [`stacks/evolution-go/docker-compose.coolify.yml`](../../stacks/evolution-go/docker-compose.coolify.yml).
|
||||
|
||||
| Pieza | Valor |
|
||||
|---|---|
|
||||
| Servicio Coolify | `evolution-go` (`j0jkacsfcgypm2jmpillls01`) |
|
||||
| Proyecto / entorno | `AI AGENCY` / `production` (mismo que Chatwoot) |
|
||||
| Imagen app | `evoapicloud/evolution-go:0.7.2` (pinada, Docker Hub) |
|
||||
| Imagen DB | `postgres:16-alpine` (hermana, `evolution-postgres`) |
|
||||
| FQDN | `https://evo.urieljareth.org` (wildcard del túnel, sin cambios CF) |
|
||||
| Healthcheck | `wget http://127.0.0.1:8080/server/ok` (200 sin licencia) |
|
||||
| Secretos | `GLOBAL_API_KEY` (40 car.) y `POSTGRES_PASSWORD` (24 car.) como envs del servicio, generados en memoria |
|
||||
|
||||
---
|
||||
|
||||
## 1. Bugs y trampas encontrados (lo que costó tiempo)
|
||||
|
||||
### 1.1 `POSTGRES_AUTH_DB` vacía = panic (bug upstream 0.7.2)
|
||||
|
||||
El primer arranque crash-loopeaba (exit 2). Stack trace:
|
||||
`NewPollService → autoMigrate` sobre un `*sql.DB` **nil**.
|
||||
|
||||
Cadena exacta en el código:
|
||||
|
||||
- `cmd/evolution-go/main.go:300` — `initPostgresAuthDB()` devuelve `(nil, nil)`
|
||||
cuando `POSTGRES_AUTH_DB == ""`: **sin error**.
|
||||
- `main.go:408` pasa ese nil a `setupRouter`.
|
||||
- `pkg/poll/service/poll_service.go:39` — `autoMigrate` dereferencia el nil → panic.
|
||||
|
||||
Es decir: la variable **parece opcional** (README no la marca obligatoria) pero
|
||||
sin ella el binario muere en loop. El fix fue setearla como URI completa:
|
||||
`postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@evolution-postgres:5432/evogo_auth?sslmode=disable`
|
||||
(interpolada por Coolify al deploy, el secreto no vive en el compose).
|
||||
|
||||
### 1.2 Los nombres de env del README están desactualizados
|
||||
|
||||
`docker/examples/docker-compose.yml` y el README usan `WADEBUG`/`LOGTYPE`, pero
|
||||
el código 0.7.2 (`pkg/config/env/env.go`) lee **`DEBUG_ENABLED`** y
|
||||
**`LOG_TYPE`**. Además `DATABASE_SAVE_MESSAGES` es obligatoria no-vacía
|
||||
(`panicIfEmpty`) aunque parezca opcional. La fuente de verdad es `env.go`.
|
||||
|
||||
### 1.3 `PATCH /services/{uuid}` rechaza campos de creación
|
||||
|
||||
`New-CoolifyService.ps1` en su flujo de actualización (`-ServiceUuid`) enviaba
|
||||
`project_uuid`/`environment_name`/`server_uuid` y la API responde
|
||||
**422 "This field is not allowed"** para los tres. Solo acepta `name`,
|
||||
`docker_compose_raw` y `urls`. Corregido en el script el 2026-09-02 (el caso
|
||||
firecrawl solo ejercitó la creación, no la actualización).
|
||||
|
||||
### 1.4 La licencia bloquea la API — pero no al Manager
|
||||
|
||||
`GateMiddleware` (`pkg/core/c0.go:638`) devuelve **503 `LICENSE_REQUIRED`** en
|
||||
todo hasta activar licencia, con excepciones: `/server/ok`, `/health`,
|
||||
`/manager*`, `/assets*`, `/license/*`, `/swagger*`, `/ws`. Esto es crítico para
|
||||
el healthcheck: como Traefik solo enruta contenedores `healthy` (ver
|
||||
[el caso del 503](coolify-servicio-nuevo-503-no-available-server.md)), un
|
||||
healthcheck contra cualquier endpoint bloqueado habría dejado al Manager
|
||||
**inalcanzable** — deadlock imposible de activar. `/server/ok` responde 200
|
||||
siempre.
|
||||
|
||||
### 1.5 No se puede montar `init-db.sql` por ruta
|
||||
|
||||
El compose de upstream monta `./init-db.sql` en el postgres. En Coolify el
|
||||
compose se guarda como `docker_compose_raw` en la DB — no hay árbol de archivos.
|
||||
No hace falta: `ensureDBExists()` (`pkg/config/config.go:79`) crea las DBs del
|
||||
DSN al arrancar (evogo_auth, evogo_users).
|
||||
|
||||
---
|
||||
|
||||
## 2. Cómo se reproduce
|
||||
|
||||
```powershell
|
||||
. .\.env.local.ps1
|
||||
|
||||
# 1. Crear (sin arrancar)
|
||||
.\deploy_skill\scripts\New-CoolifyService.ps1 `
|
||||
-AppPath .\stacks\evolution-go `
|
||||
-AppName evolution-go `
|
||||
-Fqdn https://evo.urieljareth.org `
|
||||
-PrimaryService evolution-go `
|
||||
-ProjectName "AI AGENCY" -EnvironmentName production -NoDeploy -Force
|
||||
|
||||
# 2. Secretos (POST /envs da 409 con vars ya sembradas: usar bulk PATCH;
|
||||
# generados en memoria, nunca en disco)
|
||||
# PATCH /services/j0jkacsfcgypm2jmpillls01/envs/bulk
|
||||
# data: [{POSTGRES_PASSWORD}, {GLOBAL_API_KEY}] con is_literal
|
||||
|
||||
# 3. Arrancar y esperar (primer boot: minutos)
|
||||
Invoke-CoolifyApi.ps1 -Method POST -Path "/services/j0jkacsfcgypm2jmpillls01/start"
|
||||
.\coolify_skill\scripts\Test-CoolifyServiceReady.ps1 -Uuid j0jkacsfcgypm2jmpillls01 -WaitSeconds 600
|
||||
|
||||
# 4. Verificación funcional
|
||||
curl.exe -sS https://evo.urieljareth.org/server/ok # {"status":"ok"}
|
||||
curl.exe -sSI https://evo.urieljareth.org/manager/login # 200
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Qué queda pendiente del lado del usuario (manual)
|
||||
|
||||
0. **Si el magic link del portal dice "ya usado o expiró"**: `GET /license/register`
|
||||
**cachea en memoria** la primera sesión de registro (`rc._v8` en
|
||||
`pkg/core/c0.go:710`) y devuelve siempre la misma `register_url` aunque el
|
||||
portal ya la haya invalidado (link consumido por preview del cliente de
|
||||
correo, doble click, o expirado). Fix verificado: `docker restart` del
|
||||
contenedor app → siguiente `/license/register` pide sesión nueva al portal.
|
||||
Después: abrir el magic link **una sola vez y directamente** (los previews
|
||||
de Outlook/Gmail consumen links single-use sin que los abras). Mejor aún:
|
||||
la página del portal ofrece **OAuth con Google/GitHub**, que evita el magic
|
||||
link por completo (verificado 2026-09-02: la sesión sobrevive aunque el
|
||||
magic link muera; tras el OAuth queda un `code` que se canjea con
|
||||
`GET /license/activate?code=...`).
|
||||
|
||||
**Resultado final:** el magic link falló 3/3 (el cliente de correo del
|
||||
usuario consumía el link single-use antes que el navegador — el portal
|
||||
respondía `authorization code expired or already used` al canjear). La
|
||||
activación se completó así: `docker restart` (limpia la sesión cacheada) →
|
||||
iniciar el registro **desde el Manager** (para que mande su `redirect_uri`
|
||||
y la vuelta sea automática) → en el portal, botón **Entrar com Google** →
|
||||
redirección de vuelta al Manager → `/license/status` = `active`.
|
||||
|
||||
1. **Activar la licencia** (requiere cuenta en Evolution Foundation):
|
||||
abrir `https://evo.urieljareth.org/manager/login`, entrar con la API URL
|
||||
(`https://evo.urieljareth.org`) y la `GLOBAL_API_KEY` — visible en Coolify:
|
||||
proyecto AI AGENCY → servicio evolution-go → pestaña Environment. Hasta
|
||||
entonces toda la API responde 503 `LICENSE_REQUIRED` (el Manager sí funciona).
|
||||
Alternativa por API: `GET /license/register` devuelve la URL de registro.
|
||||
2. **Conectar WhatsApp**: desde el Manager crear una instancia → escanear el QR
|
||||
(`POST /instance/create`, `GET /instance/qr` con header `apikey`). Las
|
||||
sesiones persisten en el volumen `evolution-data` (`/app/dbdata`).
|
||||
3. **Enlazar con Chatwoot**: evolution-go **no trae** integración Chatwoot
|
||||
nativa (cero menciones en el código, a diferencia de evolution-api Node). El
|
||||
puente sería por webhook (`WEBHOOK_URL`) hacia un inbox tipo API de Chatwoot,
|
||||
o usar el servicio evolution-api Node viejo que sí la tiene.
|
||||
|
||||
## 4. Notas de estado
|
||||
|
||||
- El servicio viejo `evolution-api` (`q6tnsvkvrjw4g0ab532l3r1s`, imagen Node
|
||||
`evoapicloud/evolution-api:v2.3.7`, mismo entorno) **está en producción en
|
||||
https://evoapi.urieljareth.org** (api/postgres/redis, 13+ días healthy; el
|
||||
contenedor app se llama `api-q6tn...`, no `evolution-api-...` — cuidado con
|
||||
los greps). Tiene 4 instancias WhatsApp (Personal, JM, INSTA, Asesoria
|
||||
Personal) sin integración Chatwoot configurada. NOTA 2026-09-02: este repo
|
||||
documentó erróneamente "app detenida" por un filtro truncado de
|
||||
`Get-CoolifyDockerStatus`; corregido tras verificación directa.
|
||||
- El compose normalizado por Coolify borra los comentarios; la versión
|
||||
documentada es la del repo (`stacks/evolution-go/`).
|
||||
- Imagen pinada a `0.7.2` (tag del repo clonado). Para subir de versión:
|
||||
cambiar el tag, repasar `pkg/config/env/env.go` del nuevo tag (los nombres de
|
||||
variables cambian entre versiones) y PATCH + start de nuevo.
|
||||
Reference in New Issue
Block a user