- 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)
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_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 ~/.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
- 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 por el patron
c11xzy2tx2cdapm32f5b89vy.*(pgvector|postgres|db). POSTGRES_USER/POSTGRES_DB/POSTGRES_PASSWORDdesdedocker inspect.
- contenedor Postgres de Chatwoot por el patron
- 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. - 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 defecto102(Coolify).-ProxmoxHost <host>: por defecto192.168.0.200.-SshKey <ruta>: por defectoC:\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:
-
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
- Entrar a
https://chatwoot-c11xzy2tx2cdapm32f5b89vy.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.