12 KiB
Caso: Parche enterprise en Chatwoot (Coolify + LXC 102)
Documentacion de caso verificada el 2026-06-16 desde esta maquina. Dominio:
https://chat.urieljareth.org(corregido el 2026-07-24; el FQDNchatwoot-c11xzy2tx2cdapm32f5b89vy.urieljareth.orgque decia antes ya no aplica).
Leer antes de usar este caso (revision 2026-07-24):
- El parche caduca solo en <= 24 h. No hace falta actualizar para perderlo:
Internal::CheckNewVersionsJobhace ping diario ahub.2.chatwoot.comy reescribe el plan con lo que responda el hub. Con el identifier actual la ventana es todos los dias a las 16:16 UTC.- Los 3
UPDATEde este caso no alcanzan si el plan ya paso porcommunity:Internal::ReconcilePlanConfigServiceapago los 9 feature flags premium enaccounts.feature_flagsde cada cuenta. Hay que correrApply-ChatwootEnterprisePatch.ps1 -ReenableAccountFeatures.Causa raiz completa, plan de actualizacion y fix durable: docs/runbooks/chatwoot-update.md.
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_configsen la base de datos Postgres. -
Comando original (Bash, dentro del host Docker):
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
Refreshen/super_admin/settings, o el plan vuelve a Community. -
Stack local: Proxmox
192.168.0.200-> LXC102(Coolify) -> Docker -> contenedor Postgrespgvector/pgvector:pg12de 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 | [email protected] con keys/proxmox_ed25519 |
| 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
# 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:
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:
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
- Abre SSH contra
192.168.0.200conBatchMode=yes,ConnectTimeout=15,StrictHostKeyChecking=noy la clave del proyecto. - Sube 3 scripts
.shy un.sqlal host Proxmox (no al LXC, para evitar unpct pushextra y problemas de ruta). - Autodetecta:
- contenedor Postgres de Chatwoot: filtra
docker pspor el uuid del servicio y luego por(pgvector|postgres|db). Son dos greps encadenados a proposito — Coolify nombra los contenedores<servicio>-<uuid>(postgres-c11xzy...), asi que el patron unico<uuid>.*postgresque tenia antes no casaba nunca y la autodeteccion fallaba siempre (corregido el 2026-07-24). POSTGRES_USER/POSTGRES_DB/POSTGRES_PASSWORDdesdedocker inspect.
- contenedor Postgres de Chatwoot: filtra
- Ejecuta el comando equivalente dentro del LXC, captura stdout y exit code.
- Cuenta las lineas
^UPDATE\s+1\s*$; deben ser exactamente 3 o falla. - Corre un
SELECTde verificacion. - Con
-ReenableAccountFeatures: reactiva los 9 feature flags premium en todas las cuentas viarails runnery falla si queda alguno pendiente. - Limpia los archivos temporales en el host Proxmox.
En el -DryRun la PGPASSWORD sale enmascarada (antes se imprimia en claro).
Uso
# Desde la raiz del repo.
.\scripts\Apply-ChatwootEnterprisePatch.ps1 -DryRun -ReenableAccountFeatures
.\scripts\Apply-ChatwootEnterprisePatch.ps1 -ReenableAccountFeatures
.\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.-ReenableAccountFeatures: ademas de los 3UPDATE, reactiva viarails runnerlos 9 feature flags premium en todas las cuentas y verifica que no quede ninguno pendiente. Necesario siempre que el plan venga decommunity(ver el aviso al inicio de este documento).-Container <nombre>: fuerza el contenedor Postgres destino.-AppContainer <nombre>: contenedor de la app Rails, por defectochatwoot-<ServiceUuid>(solo lo usa-ReenableAccountFeatures).-ServiceUuid <uuid>: uuid del servicio en Coolify, por defectoc11xzy2tx2cdapm32f5b89vy. De aqui se derivan los nombres de contenedor.-LxcId <id>: por defecto102(Coolify).-ProxmoxHost <host>: por defecto192.168.0.200.-SshKey <ruta>: por defectokeys\proxmox_ed25519.
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:
-
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.shy se ejecuta conbash /tmp/archivo.sh. Asi se elimina el problema de escaping. -
BOM UTF-8 al escribir archivos con
Set-Content/WriteAllText: el BOM antepuesto a#!/bin/bashrompe el shebang en Linux. Regla: usarNew-Object System.Text.UTF8Encoding($false). -
Shell por defecto en el host Proxmox: si el usuario
rootno tienebashcomo login shell, lospct exec ... -- bash -lc '...'fallan conSyntax error: "(" unexpected. Regla: dentro de los scripts remotos usarbash -lcobash -csiempre, nosh. -
Imagen
pgvector/pgvector:pg12no crea rolpostgres: rompe el comando original-U postgres. Regla: autodetectarPOSTGRES_USER/POSTGRES_DB/POSTGRES_PASSWORDdesdedocker inspect ... --format '{{range .Config.Env}}...{{end}}'. -
Pipe por stdin vs
pct push:pct pushdeposita 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.sqlal host Proxmox y pipe conpct exec 102 -- docker exec -i <ct> psql ... < /tmp/file.sql. -
Validacion post-condicional: en lugar de confiar en el exit code, contar las lineas
UPDATE 1para 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 llamarsepostgres-<UUID-coolify>).pgUser,pgDb,pgPass: lectura viadocker 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
Automatica primero:
.\scripts\Get-ChatwootLicenseStatus.ps1 -Deep
Luego a mano:
- Entrar a
https://chat.urieljareth.org. - Iniciar sesion con un super admin.
- Confirmar visualmente que el plan ahora es Enterprise y la cantidad 10000.
- Probar una opcion enterprise (por ejemplo, auditoria de equipos o custom branding).
- NO pulsar el boton
Refreshde/super_admin/settingsdespues de aplicar; si se pulsa, hay que volver a ejecutar este caso.