Files
Proxmox-Coolify-Manager/scripts/Apply-ChatwootEnterprisePatch.ps1
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

269 lines
10 KiB
PowerShell

# Aplica el parche enterprise de Chatwoot alineado al comando original:
#
# docker exec -i "$(docker ps -q --filter 'name=pgvector')" psql \
# -U postgres -d chatwoot -c "UPDATE ... WHERE name = '...';"
#
# Convencion del repo: Proxmox 192.168.0.200 -> LXC 102 (coolify) -> docker.
#
# Criterio de exito: psql imprime 3 lineas "UPDATE 1" (una por sentencia).
# Tras aplicar, NO pulsar "Refresh" en /super_admin/settings.
#
# Uso:
# .\scripts\Apply-ChatwootEnterprisePatch.ps1
# .\scripts\Apply-ChatwootEnterprisePatch.ps1 -DryRun
# .\scripts\Apply-ChatwootEnterprisePatch.ps1 -Container "postgres-c11xzy2tx2cdapm32f5b89vy"
[CmdletBinding()]
param(
[switch]$DryRun,
[string]$Container = "",
[string]$LxcId = "102",
[string]$ProxmoxHost = $(if ($env:PROXMOX_HOST) { $env:PROXMOX_HOST } else { "192.168.0.200" }),
[string]$SshKey = $(if ($env:PROXMOX_SSH_KEY) { $env:PROXMOX_SSH_KEY } else { "C:\Users\Uriel Jareth\.openclaw\workspace\proxmox_key_win" })
)
# Encoding UTF-8 sin BOM (BOM rompe el shebang #!/bin/bash en Linux).
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)
$ErrorActionPreference = "Stop"
if (-not (Test-Path -LiteralPath $SshKey)) {
throw "No se encontro la clave SSH: $SshKey"
}
function Invoke-Remote {
param([string]$RemoteCmd)
$args = @(
"-o", "BatchMode=yes"
"-o", "ConnectTimeout=15"
"-o", "StrictHostKeyChecking=no"
"-i", $SshKey
"root@$ProxmoxHost"
$RemoteCmd
)
return & ssh @args
}
# --- 1) Resolver contenedor Postgres de Chatwoot --------------------------
if (-not $Container) {
$detect = @'
#!/bin/bash
pct exec __LXC__ -- bash -lc 'docker ps --format "{{.Names}}" | grep -Ei "c11xzy2tx2cdapm32f5b89vy.*(pgvector|postgres|db)" | head -n1'
'@
$detect = $detect.Replace('__LXC__', $LxcId)
$tmpDetect = [IO.Path]::GetTempFileName() + ".sh"
[IO.File]::WriteAllText($tmpDetect, $detect, $utf8NoBom)
try {
$localName = Split-Path -Leaf $tmpDetect
$scpArgs = @(
"-o", "BatchMode=yes"
"-o", "ConnectTimeout=15"
"-o", "StrictHostKeyChecking=no"
"-i", $SshKey
$tmpDetect
"root@${ProxmoxHost}:/tmp/$localName"
)
& scp @scpArgs | Out-Null
if ($LASTEXITCODE -ne 0) { throw "scp detect fallo (exit $LASTEXITCODE)." }
Write-Host "[*] Buscando contenedor Postgres de Chatwoot en LXC $LxcId ..."
$cand = Invoke-Remote "bash /tmp/$localName"
Invoke-Remote "rm -f /tmp/$localName" | Out-Null
if ($LASTEXITCODE -ne 0) { throw "Listado remoto fallo (exit $LASTEXITCODE)." }
$Container = @($cand | Where-Object { $_ -match '\S' })[0]
if (-not $Container) {
throw "No se encontro contenedor. Pasa -Container explicito (ej: postgres-c11xzy2tx2cdapm32f5b89vy)."
}
} finally {
Remove-Item -LiteralPath $tmpDetect -ErrorAction SilentlyContinue
}
}
Write-Host "[+] Contenedor: $Container"
# --- 1b) Detectar credenciales reales desde docker inspect ----------------
$inspectScript = @'
#!/bin/bash
pct exec __LXC__ -- docker inspect __CT__ --format '{{range .Config.Env}}{{println .}}{{end}}' | awk -F= '
/^POSTGRES_USER=/ { u=$2 }
/^POSTGRES_DB=/ { d=$2 }
/^POSTGRES_PASSWORD=/ { p=$2 }
/^POSTGRES_HOST=/ { h=$2 }
/^SERVICE_NAME_POSTGRES_5432_TCP=/ { skip=1; next }
/^SERVICE_NAME_POSTGRES=/ { skip=1; next }
/^SERVICE_USER_POSTGRES=/ { skip=1; next }
/^SERVICE_PASSWORD_POSTGRES=/ { skip=1; next }
/^COOLIFY_CONTAINER_NAME=/ { skip=1; next }
/^SERVICE_NAME_POSTGRES_5432_TCP_PORT=/ { skip=1; next }
/^SERVICE_NAME_POSTGRES_PORT=/ { skip=1; next }
/^SERVICE_NAME_POSTGRES_PORT_5432_TCP=/ { skip=1; next }
/^SERVICE_NAME_POSTGRES_PORT_5432_TCP_PROTO=/ { skip=1; next }
/^SERVICE_NAME_POSTGRES_PORT_5432_TCP_ADDR=/ { skip=1; next }
/^SERVICE_NAME_POSTGRES_PORT_5432_TCP_PORT=/ { skip=1; next }
/^SERVICE_NAME_POSTGRES_HOST=/ { skip=1; next }
/^SERVICE_NAME_POSTGRES_/ { skip=1; next }
/^POSTGRES_HOST=/ { skip=1; next }
/^POSTGRES_PORT=/ { skip=1; next }
/^POSTGRES_USERNAME=/ { skip=1; next }
/^POSTGRES_PASSWORD=/ { skip=1; next }
END { print "USER=" u; print "DB=" d; print "PASSWORD=" p }
'
'@
$inspectScript = $inspectScript.Replace('__LXC__', $LxcId).Replace('__CT__', $Container)
$tmpInspect = [IO.Path]::GetTempFileName() + ".sh"
[IO.File]::WriteAllText($tmpInspect, $inspectScript, $utf8NoBom)
try {
$localName = Split-Path -Leaf $tmpInspect
$scpArgs = @(
"-o", "BatchMode=yes"
"-o", "ConnectTimeout=15"
"-o", "StrictHostKeyChecking=no"
"-i", $SshKey
$tmpInspect
"root@${ProxmoxHost}:/tmp/$localName"
)
& scp @scpArgs | Out-Null
if ($LASTEXITCODE -ne 0) { throw "scp inspect fallo (exit $LASTEXITCODE)." }
$envOut = Invoke-Remote "bash /tmp/$localName"
Invoke-Remote "rm -f /tmp/$localName" | Out-Null
} finally {
Remove-Item -LiteralPath $tmpInspect -ErrorAction SilentlyContinue
}
$pgUser = ($envOut | Where-Object { $_ -match '^USER=' } | ForEach-Object { $_ -split '=', 2 } | Select-Object -Last 1) | Select-Object -First 1
$pgDb = ($envOut | Where-Object { $_ -match '^DB=' } | ForEach-Object { $_ -split '=', 2 } | Select-Object -Last 1) | Select-Object -First 1
$pgPass = ($envOut | Where-Object { $_ -match '^PASSWORD=' } | ForEach-Object { $_ -split '=', 2 } | Select-Object -Last 1) | Select-Object -First 1
if (-not $pgUser) { $pgUser = "postgres" }
if (-not $pgDb) { $pgDb = "chatwoot" }
Write-Host ("[+] PG user={0} db={1} password={2}" -f $pgUser, $pgDb, ($pgPass -replace '.', '*'))
# --- 2) SQL (3 sentencias identicas al comando original) -------------------
$sql = @'
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';
'@
# Script bash que envuelve el original: docker exec -i CT psql -U USER -d DB < sql.
$applyScript = @'
#!/bin/bash
pct exec __LXC__ -- docker exec -i __CT__ env PGPASSWORD=__PASS__ psql -U __USER__ -d __DB__ -v ON_ERROR_STOP=1 < "$1"
'@
$applyScript = $applyScript.Replace('__LXC__', $LxcId).Replace('__CT__', $Container).Replace('__USER__', $pgUser).Replace('__DB__', $pgDb).Replace('__PASS__', $pgPass)
$verifyScript = @'
#!/bin/bash
pct exec __LXC__ -- docker exec -i __CT__ env PGPASSWORD=__PASS__ psql -U __USER__ -d __DB__ -c "SELECT name, serialized_value FROM public.installation_configs WHERE name IN ('INSTALLATION_PRICING_PLAN','INSTALLATION_PRICING_PLAN_QUANTITY','INSTALLATION_IDENTIFIER') ORDER BY name;"
'@
$verifyScript = $verifyScript.Replace('__LXC__', $LxcId).Replace('__CT__', $Container).Replace('__USER__', $pgUser).Replace('__DB__', $pgDb).Replace('__PASS__', $pgPass)
if ($DryRun) {
Write-Host "[dry-run] SQL que se aplicaria:"
Write-Host "------"
Write-Host $sql
Write-Host "------"
Write-Host "[dry-run] apply.sh:"
Write-Host "------"
Write-Host $applyScript
Write-Host "------"
return
}
# --- 3) Subir SQL + scripts y ejecutar ------------------------------------
$tmpSql = [IO.Path]::GetTempFileName() + ".sql"
$tmpApply = [IO.Path]::GetTempFileName() + ".sh"
$tmpVerify = [IO.Path]::GetTempFileName() + ".sh"
$remoteSql = "/tmp/chatwoot-enterprise-patch.sql"
$remoteApply = "/tmp/chatwoot-apply.sh"
$remoteVerify = "/tmp/chatwoot-verify.sh"
try {
[IO.File]::WriteAllText($tmpSql, $sql, $utf8NoBom)
[IO.File]::WriteAllText($tmpApply, $applyScript, $utf8NoBom)
[IO.File]::WriteAllText($tmpVerify, $verifyScript, $utf8NoBom)
# Subir SQL al HOST Proxmox (no al LXC; lo lee bash local y redirige a pct exec).
$sqlLocal = Split-Path -Leaf $tmpSql
$scpArgs = @(
"-o", "BatchMode=yes"
"-o", "ConnectTimeout=15"
"-o", "StrictHostKeyChecking=no"
"-i", $SshKey
$tmpSql
"root@${ProxmoxHost}:$remoteSql"
)
& scp @scpArgs | Out-Null
if ($LASTEXITCODE -ne 0) { throw "scp SQL fallo (exit $LASTEXITCODE)." }
# Subir apply.sh y verify.sh al host.
$applyLocal = Split-Path -Leaf $tmpApply
$scpArgs = @(
"-o", "BatchMode=yes"
"-o", "ConnectTimeout=15"
"-o", "StrictHostKeyChecking=no"
"-i", $SshKey
$tmpApply
"root@${ProxmoxHost}:$remoteApply"
)
& scp @scpArgs | Out-Null
if ($LASTEXITCODE -ne 0) { throw "scp apply fallo (exit $LASTEXITCODE)." }
$verifyLocal = Split-Path -Leaf $tmpVerify
$scpArgs = @(
"-o", "BatchMode=yes"
"-o", "ConnectTimeout=15"
"-o", "StrictHostKeyChecking=no"
"-i", $SshKey
$tmpVerify
"root@${ProxmoxHost}:$remoteVerify"
)
& scp @scpArgs | Out-Null
if ($LASTEXITCODE -ne 0) { throw "scp verify fallo (exit $LASTEXITCODE)." }
Write-Host "[*] Aplicando 3 UPDATE en $Container ..."
$output = Invoke-Remote "bash $remoteApply $remoteSql"
$exitCode = $LASTEXITCODE
Write-Host "----- psql output -----"
$output | ForEach-Object { Write-Host $_ }
Write-Host "-----------------------"
}
finally {
Remove-Item -LiteralPath $tmpSql, $tmpApply, $tmpVerify -ErrorAction SilentlyContinue
}
# --- 4) Validacion --------------------------------------------------------
$updateLines = @($output | Where-Object { $_ -match '^UPDATE\s+1\s*$' })
Write-Host ("UPDATE 1 count = {0}" -f $updateLines.Count)
if ($exitCode -ne 0) {
Invoke-Remote "rm -f $remoteSql $remoteApply $remoteVerify" | Out-Null
throw "psql finalizo con exit code $exitCode."
}
if ($updateLines.Count -ne 3) {
Invoke-Remote "rm -f $remoteSql $remoteApply $remoteVerify" | Out-Null
throw "Se esperaban 3 lineas 'UPDATE 1', se obtuvieron $($updateLines.Count)."
}
# --- 5) Verificacion SELECT -----------------------------------------------
Write-Host "[*] Verificando valores finales ..."
$verify = Invoke-Remote "bash $remoteVerify"
$verify | ForEach-Object { Write-Host $_ }
# Limpieza final.
Invoke-Remote "rm -f $remoteSql $remoteApply $remoteVerify" | Out-Null
Write-Host ""
Write-Host "[OK] Parche enterprise aplicado correctamente (3/3 UPDATE 1)."
Write-Host " NO pulsar 'Refresh' en /super_admin/settings."