Actualiza toolkit operativo y documentación

This commit is contained in:
urieljareth
2026-09-10 20:53:50 -06:00
parent 3b7209dcc1
commit 714057bfc8
69 changed files with 6023 additions and 384 deletions
+134 -5
View File
@@ -8,18 +8,28 @@
# Criterio de exito: psql imprime 3 lineas "UPDATE 1" (una por sentencia).
# Tras aplicar, NO pulsar "Refresh" en /super_admin/settings.
#
# IMPORTANTE: los 3 UPDATE por si solos NO alcanzan cuando el plan ya se habia
# revertido a 'community'. Al revertirse, Internal::ReconcilePlanConfigService
# apaga los 9 feature flags premium en CADA cuenta, y eso vive en la tabla
# accounts (bitmask), no en installation_configs. Usa -ReenableAccountFeatures
# para reactivarlos. Ver docs/runbooks/chatwoot-update.md.
#
# Uso:
# .\scripts\Apply-ChatwootEnterprisePatch.ps1
# .\scripts\Apply-ChatwootEnterprisePatch.ps1 -DryRun
# .\scripts\Apply-ChatwootEnterprisePatch.ps1 -ReenableAccountFeatures
# .\scripts\Apply-ChatwootEnterprisePatch.ps1 -Container "postgres-c11xzy2tx2cdapm32f5b89vy"
[CmdletBinding()]
param(
[switch]$DryRun,
[switch]$ReenableAccountFeatures,
[string]$ServiceUuid = "c11xzy2tx2cdapm32f5b89vy",
[string]$Container = "",
[string]$AppContainer = "",
[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" })
[string]$SshKey = $(if ($env:PROXMOX_SSH_KEY) { $env:PROXMOX_SSH_KEY } else { Join-Path $PSScriptRoot "..\keys\proxmox_ed25519" })
)
# Encoding UTF-8 sin BOM (BOM rompe el shebang #!/bin/bash en Linux).
@@ -44,13 +54,18 @@ function Invoke-Remote {
return & ssh @args
}
if (-not $AppContainer) { $AppContainer = "chatwoot-$ServiceUuid" }
# --- 1) Resolver contenedor Postgres de Chatwoot --------------------------
if (-not $Container) {
# Dos greps encadenados en vez de un solo patron: Coolify nombra los
# contenedores <servicio>-<uuid> (postgres-c11xzy...), asi que un patron
# "<uuid>.*postgres" nunca casa. El orden no importa con greps separados.
$detect = @'
#!/bin/bash
pct exec __LXC__ -- bash -lc 'docker ps --format "{{.Names}}" | grep -Ei "c11xzy2tx2cdapm32f5b89vy.*(pgvector|postgres|db)" | head -n1'
pct exec __LXC__ -- bash -lc 'docker ps --format "{{.Names}}" | grep -F "__UUID__" | grep -Ei "(pgvector|postgres|db)" | head -n1'
'@
$detect = $detect.Replace('__LXC__', $LxcId)
$detect = $detect.Replace('__LXC__', $LxcId).Replace('__UUID__', $ServiceUuid)
$tmpDetect = [IO.Path]::GetTempFileName() + ".sh"
[IO.File]::WriteAllText($tmpDetect, $detect, $utf8NoBom)
@@ -72,8 +87,9 @@ pct exec __LXC__ -- bash -lc 'docker ps --format "{{.Names}}" | grep -Ei "c11xzy
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)."
throw "No se encontro contenedor Postgres para el servicio $ServiceUuid en el LXC $LxcId. Pasa -Container explicito (ej: postgres-$ServiceUuid)."
}
$Container = $Container.Trim()
} finally {
Remove-Item -LiteralPath $tmpDetect -ErrorAction SilentlyContinue
}
@@ -174,8 +190,23 @@ if ($DryRun) {
Write-Host "------"
Write-Host "[dry-run] apply.sh:"
Write-Host "------"
Write-Host $applyScript
# PGPASSWORD se enmascara: la regla del repo es que ningun secreto salga por
# stdout ni quede en un log. (String.Replace revienta con un patron vacio,
# de ahi el guard.)
$safeApply = if ([string]::IsNullOrEmpty($pgPass)) { $applyScript } else { $applyScript.Replace($pgPass, "********") }
Write-Host $safeApply
Write-Host "------"
if ($ReenableAccountFeatures) {
Write-Host "[dry-run] Ademas reactivaria estos feature flags premium en TODAS las cuentas,"
Write-Host " via 'rails runner' en $AppContainer :"
Write-Host " disable_branding audit_logs sla custom_roles captain_integration"
Write-Host " captain_integration_v2 captain_document_auto_sync csat_review_notes"
Write-Host " conversation_required_attributes"
}
else {
Write-Host "[dry-run] Los feature flags premium por cuenta NO se tocarian."
Write-Host " Agrega -ReenableAccountFeatures si el plan venia de 'community'."
}
return
}
@@ -266,3 +297,101 @@ 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."
# --- 6) Reactivar feature flags premium por cuenta -------------------------
# Cuando el plan se revierte a 'community', Internal::ReconcilePlanConfigService
# corre account.disable_features!(*premium_features) sobre TODAS las cuentas.
# Esos flags viven en accounts.feature_flags (bitmask) y los 3 UPDATE de arriba
# no los tocan: hay que reactivarlos explicitamente o la UI sigue sin enterprise.
if (-not $ReenableAccountFeatures) {
Write-Host ""
Write-Host "[!] Los feature flags premium por cuenta NO se tocaron."
Write-Host " Si el plan venia de 'community', vuelve a correr con -ReenableAccountFeatures."
return
}
Write-Host ""
Write-Host "[*] Reactivando feature flags premium por cuenta (arranca Rails, ~40 s) ..."
$ruby = @'
PREMIUM = %w[
disable_branding audit_logs sla custom_roles
captain_integration captain_integration_v2 captain_document_auto_sync
csat_review_notes conversation_required_attributes
]
# Los 3 UPDATE se hacen por SQL puro, asi que NO disparan el
# `after_commit :clear_cache` de InstallationConfig. GlobalConfig cachea en Redis
# con TTL de 1 dia (V1:GLOBAL_CONFIG:*), asi que sin esta limpieza la app puede
# seguir sirviendo el plan viejo hasta 24 h.
GlobalConfig.clear_cache
puts "global_config_cache=limpiado"
Account.find_each do |account|
before = PREMIUM.reject { |f| account.feature_enabled?(f) }
account.enable_features!(*PREMIUM)
account.reload
after = PREMIUM.reject { |f| account.feature_enabled?(f) }
puts "account=#{account.id}|#{account.name}|reactivados=#{before.empty? ? 'ninguno' : before.join(',')}|pendientes=#{after.empty? ? 'ninguno' : after.join(',')}"
end
puts "self_hosted_enterprise=#{ChatwootApp.self_hosted_enterprise?}"
'@
$tmpRb = [IO.Path]::GetTempFileName()
$remoteRb = "/tmp/chatwoot-reenable-features.rb"
$tmpRunner = [IO.Path]::GetTempFileName()
$remoteRunner = "/tmp/chatwoot-reenable-features.sh"
$runner = @'
set -e
pct push __LXC__ __RB__ __RB__
pct exec __LXC__ -- docker cp __RB__ __APP__:__RB__
pct exec __LXC__ -- docker exec -i __APP__ bundle exec rails runner __RB__
pct exec __LXC__ -- docker exec -i __APP__ rm -f __RB__
pct exec __LXC__ -- rm -f __RB__
'@
$runner = $runner.Replace('__LXC__', $LxcId).Replace('__APP__', $AppContainer).Replace('__RB__', $remoteRb)
try {
[IO.File]::WriteAllText($tmpRb, $ruby, $utf8NoBom)
[IO.File]::WriteAllText($tmpRunner, $runner, $utf8NoBom)
foreach ($pair in @(@($tmpRb, $remoteRb), @($tmpRunner, $remoteRunner))) {
$scpArgs = @(
"-o", "BatchMode=yes"
"-o", "ConnectTimeout=15"
"-o", "StrictHostKeyChecking=no"
"-i", $SshKey
$pair[0]
"root@${ProxmoxHost}:$($pair[1])"
)
& scp @scpArgs | Out-Null
if ($LASTEXITCODE -ne 0) { throw "scp de $($pair[1]) fallo (exit $LASTEXITCODE)." }
}
$featOut = Invoke-Remote "bash $remoteRunner"
$featExit = $LASTEXITCODE
$featOut | ForEach-Object { Write-Host $_ }
}
finally {
Remove-Item -LiteralPath $tmpRb, $tmpRunner -ErrorAction SilentlyContinue
Invoke-Remote "rm -f $remoteRb $remoteRunner" | Out-Null
}
if ($featExit -ne 0) {
throw "La reactivacion de feature flags fallo (exit $featExit)."
}
$pending = @($featOut | Where-Object { $_ -match 'pendientes=(?!ninguno)' })
if ($pending.Count -gt 0) {
throw "Quedaron feature flags premium sin activar en $($pending.Count) cuenta(s). Revisa la salida de arriba."
}
$selfHosted = @($featOut | Where-Object { $_ -like "self_hosted_enterprise=*" })[0]
Write-Host ""
if ($selfHosted -eq "self_hosted_enterprise=true") {
Write-Host "[OK] Enterprise activo: plan=enterprise y feature flags premium reactivados en todas las cuentas."
}
else {
Write-Host "[WARN] Feature flags reactivados, pero ChatwootApp.self_hosted_enterprise? no dio true ($selfHosted)."
Write-Host " Reinicia el stack para limpiar el cache de GlobalConfig y vuelve a verificar con:"
Write-Host " .\scripts\Get-ChatwootLicenseStatus.ps1 -Deep"
}