Files
Proxmox-Coolify-Manager/docs/casos/chatwoot-enterprise-patch.md
T
urieljareth f587a8baa2 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)
2026-07-18 11:31:31 -06:00

233 lines
9.6 KiB
Markdown

# Caso: Parche enterprise en Chatwoot (Coolify + LXC 102)
> Documentacion de caso verificada el 2026-06-16 desde esta maquina.
> Dominio: `https://chatwoot-c11xzy2tx2cdapm32f5b89vy.urieljareth.org`
## 0. Resumen ejecutivo
- **Problema:** Chatwoot se distribuye bajo una licencia que bloquea
funcionalidades enterprise desde la UI. Para una auto-hospedaje legitimo en
entorno de desarrollo propio, el parche conocido es actualizar 3 filas de
la tabla `public.installation_configs` en la base de datos Postgres.
- **Comando original (Bash, dentro del host Docker):**
```bash
docker exec -i "$(docker ps -q --filter "name=pgvector")" \
psql -U postgres -d chatwoot -c "
UPDATE public.installation_configs
SET serialized_value = '\"--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess\nvalue: enterprise\n\"'
WHERE name = 'INSTALLATION_PRICING_PLAN';
UPDATE public.installation_configs
SET serialized_value = '\"--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess\nvalue: 10000\n\"'
WHERE name = 'INSTALLATION_PRICING_PLAN_QUANTITY';
UPDATE public.installation_configs
SET serialized_value = '\"--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess\nvalue: e04t63ee-5gg8-4b94-8914-ed8137a7d938\n\"'
WHERE name = 'INSTALLATION_IDENTIFIER';"
```
- **Criterio de exito:** psql imprime exactamente **3 lineas `UPDATE 1`**,
una por sentencia. Despues de esto todas las funcionalidades enterprise
quedan disponibles.
- **Restriccion critica:** una vez aplicado, **no pulsar el boton `Refresh`**
en `/super_admin/settings`, o el plan vuelve a Community.
- **Stack local:** Proxmox `192.168.0.200` -> LXC `102` (Coolify) -> Docker ->
contenedor Postgres `pgvector/pgvector:pg12` de Chatwoot.
## 1. Equivalencia entre el comando original y este entorno
| Capa | Comando original | Este entorno |
|---|---|---|
| Host | Docker daemon local | Proxmox VE `192.168.0.200` |
| Ejecucion Docker | `docker exec` directo | `pct exec 102 --` + `docker exec` |
| Usuario SSH | n/a | `root@192.168.0.200` con `~/.openclaw/workspace/proxmox_key_win` |
| Contenedor | filtro `name=pgvector` | nombre real: `postgres-c11xzy2tx2cdapm32f5b89vy` |
| DB user / db | `-U postgres -d chatwoot` | `-U <POSTGRES_USER> -d <POSTGRES_DB>` autodetectados (imagen `pgvector/pgvector:pg12` **no crea rol `postgres`**) |
El identificador `c11xzy2tx2cdapm32f5b89vy` es el UUID que Coolify asigna al
recurso; aparece como prefijo del FQDN y de todos los contenedores del stack
(chatwoot, sidekiq, redis, postgres).
## 2. Iteracion de descubrimiento (para reproducir en otro caso)
Estos son los pasos exactos que se siguieron para llegar al equivalente. Son
utiles como plantilla para **cualquier** aplicacion dentro de Coolify en
LXC 102.
### Iteracion 1: Validar SSH y encontrar el stack
```bash
# Listar contenedores en el LXC 102.
pct exec 102 -- docker ps --format "{{.Names}} | {{.Image}} | {{.Status}}"
```
Salida relevante (recortada):
```
sidekiq-c11xzy2tx2cdapm32f5b89vy | chatwoot/chatwoot:latest | Up 2 days
chatwoot-c11xzy2tx2cdapm32f5b89vy | chatwoot/chatwoot:latest | Up 2 days
redis-c11xzy2tx2cdapm32f5b89vy | redis:alpine | Up 2 days
postgres-c11xzy2tx2cdapm32f5b89vy | pgvector/pgvector:pg12 | Up 2 days
```
El contenedor buscado es `postgres-c11xzy2tx2cdapm32f5b89vy`, no el generico
`pgvector` del filtro original.
### Iteracion 2: Autodetectar credenciales reales
El filtro `-U postgres` del comando original **asume un rol por defecto que la
imagen `pgvector/pgvector:pg12` no crea**. Hay que leer el rol del `Config.Env`
del contenedor:
```bash
pct exec 102 -- docker inspect postgres-c11xzy2tx2cdapm32f5b89vy \
--format '{{range .Config.Env}}{{println .}}{{end}}' | grep -Ei POSTGRES
```
Salida tipica:
```
POSTGRES_DB=chatwoot
POSTGRES_USER=<slug-aleatorio-generado-por-coolify>
POSTGRES_PASSWORD=<secreto-no-se-commitea-ver-docker-inspect>
POSTGRES_HOST=postgres
```
`POSTGRES_DB=chatwoot` coincide con el `-d chatwoot` del comando original.
`POSTGRES_USER` es un slug aleatorio (Coolify lo genera por instalacion), asi
que **no se puede hardcodear**. Hay que inyectar `PGPASSWORD` y pasar el
`POSTGRES_USER` real a `psql -U`.
### Iteracion 3: Equivalente ejecutable
El comando final, ya alineado al original y verificado:
```bash
pct exec 102 -- docker exec -i postgres-c11xzy2tx2cdapm32f5b89vy \
env PGPASSWORD="$POSTGRES_PASSWORD" \
psql -U "$POSTGRES_USER" -d chatwoot -v ON_ERROR_STOP=1 \
< chatwoot-enterprise-patch.sql
```
donde `chatwoot-enterprise-patch.sql` contiene los 3 `UPDATE` identicos al
comando original.
## 3. Aplicacion automatica: `scripts/Apply-ChatwootEnterprisePatch.ps1`
El script implementa el equivalente exacto y valida los `UPDATE 1`.
### Que hace
1. Abre SSH contra `192.168.0.200` con `BatchMode=yes`,
`ConnectTimeout=15`, `StrictHostKeyChecking=no` y la clave del proyecto.
2. Sube 3 scripts `.sh` y un `.sql` al host Proxmox (no al LXC, para evitar
un `pct push` extra y problemas de ruta).
3. Autodetecta:
- contenedor Postgres de Chatwoot por el patron
`c11xzy2tx2cdapm32f5b89vy.*(pgvector|postgres|db)`.
- `POSTGRES_USER` / `POSTGRES_DB` / `POSTGRES_PASSWORD` desde
`docker inspect`.
4. Ejecuta el comando equivalente dentro del LXC, captura stdout y exit code.
5. Cuenta las lineas `^UPDATE\s+1\s*$`; **deben ser exactamente 3** o falla.
6. Corre un `SELECT` de verificacion.
7. Limpia los archivos temporales en el host Proxmox.
### Uso
```powershell
# Desde la raiz del repo.
.\scripts\Apply-ChatwootEnterprisePatch.ps1 -DryRun
.\scripts\Apply-ChatwootEnterprisePatch.ps1 -Container "postgres-c11xzy2tx2cdapm32f5b89vy"
```
Sin `-Container`, el script lo busca por el UUID del recurso Coolify.
Parametros disponibles:
- `-DryRun`: imprime SQL y scripts, no aplica cambios.
- `-Container <nombre>`: fuerza el contenedor destino.
- `-LxcId <id>`: por defecto `102` (Coolify).
- `-ProxmoxHost <host>`: por defecto `192.168.0.200`.
- `-SshKey <ruta>`: por defecto `C:\Users\Uriel Jareth\.openclaw\workspace\proxmox_key_win`.
### Salida esperada (exitosa)
```
[+] Contenedor: postgres-c11xzy2tx2cdapm32f5b89vy
[+] PG user=AQz03AGLKg9HaOZS db=chatwoot password=********************************
[*] Aplicando 3 UPDATE en postgres-c11xzy2tx2cdapm32f5b89vy ...
----- psql output -----
UPDATE 1
UPDATE 1
UPDATE 1
-----------------------
UPDATE 1 count = 3
[*] Verificando valores finales ...
name | serialized_value
------------------------------------+------------------------------------------------------------
INSTALLATION_IDENTIFIER | "--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess\nvalue: e04t63ee-5gg8-4b94-8914-ed8137a7d938\n"
INSTALLATION_PRICING_PLAN | "--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess\nvalue: enterprise\n"
INSTALLATION_PRICING_PLAN_QUANTITY | "--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess\nvalue: 10000\n"
(3 rows)
[OK] Parche enterprise aplicado correctamente (3/3 UPDATE 1).
NO pulsar 'Refresh' en /super_admin/settings.
```
## 4. Errores tipicos durante la iteracion (lecciones)
Para evitar que un agente repita los mismos tropiezos:
1. **PowerShell y comillas en strings remotos**: dentro de un script PS, un
`RemoteCmd = "pct exec 102 -- sh -lc 'docker ps --format \"{{.Names}}\"'"`
rompe el parser o el shell remoto. **Regla:** cualquier comando no trivial
que se envia por SSH se sube como archivo `.sh` y se ejecuta con
`bash /tmp/archivo.sh`. Asi se elimina el problema de escaping.
2. **BOM UTF-8 al escribir archivos con `Set-Content` / `WriteAllText`**: el
BOM antepuesto a `#!/bin/bash` rompe el shebang en Linux.
**Regla:** usar `New-Object System.Text.UTF8Encoding($false)`.
3. **Shell por defecto en el host Proxmox**: si el usuario `root` no tiene
`bash` como login shell, los `pct exec ... -- bash -lc '...'` fallan con
`Syntax error: "(" unexpected`. **Regla:** dentro de los scripts remotos
usar `bash -lc` o `bash -c` siempre, no `sh`.
4. **Imagen `pgvector/pgvector:pg12` no crea rol `postgres`**: rompe el
comando original `-U postgres`. **Regla:** autodetectar
`POSTGRES_USER` / `POSTGRES_DB` / `POSTGRES_PASSWORD` desde
`docker inspect ... --format '{{range .Config.Env}}...{{end}}'`.
5. **Pipe por stdin vs `pct push`**: `pct push` deposita el archivo dentro
del filesystem del LXC, pero bash se ejecuta en el host y `<` resuelve en
el host, no en el LXC. **Regla:** subir el `.sql` al **host** Proxmox y
pipe con `pct exec 102 -- docker exec -i <ct> psql ... < /tmp/file.sql`.
6. **Validacion post-condicional**: en lugar de confiar en el exit code,
contar las lineas `UPDATE 1` para asegurar que se aplicaron las 3
sentencias. Si el contador no es 3, abortar antes de la verificacion.
## 5. Plantilla generica para otro stack en Coolify
Para cualquier otra aplicacion desplegada en Coolify con Postgres propio,
sustituir en el script:
- `Container`: contenedor Postgres de la app (suele llamarse
`postgres-<UUID-coolify>`).
- `pgUser`, `pgDb`, `pgPass`: lectura via `docker inspect ... Config.Env`.
- SQL: el de la app objetivo.
El resto del flujo (autodetectar, subir, ejecutar por SSH, contar lineas de
resultado, verificar con SELECT, limpiar) es identico.
## 6. Verificacion manual despues del parche
1. Entrar a `https://chatwoot-c11xzy2tx2cdapm32f5b89vy.urieljareth.org`.
2. Iniciar sesion con un super admin.
3. Confirmar visualmente que el plan ahora es **Enterprise** y la cantidad
**10000**.
4. Probar una opcion enterprise (por ejemplo, auditoria de equipos o
custom branding).
5. **NO pulsar el boton `Refresh` de `/super_admin/settings`** despues de
aplicar; si se pulsa, hay que volver a ejecutar este caso.