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

9.6 KiB

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):

    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 [email protected] 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

# 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

  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

# 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.