141 lines
5.4 KiB
Bash
141 lines
5.4 KiB
Bash
#!/bin/bash
|
|
# Guard: mantiene la edicion enterprise de Chatwoot en LXC 102.
|
|
#
|
|
# Por que existe: Internal::CheckNewVersionsJob hace ping diario a
|
|
# hub.2.chatwoot.com (a las 16:16 UTC para este installation_identifier) y
|
|
# reescribe INSTALLATION_PRICING_PLAN con lo que responda el hub ('community'),
|
|
# y ademas Internal::ReconcilePlanConfigService apaga los 9 feature flags
|
|
# premium en TODAS las cuentas.
|
|
#
|
|
# No se bloquea el hub a proposito: ese mismo host relaya las notificaciones
|
|
# push del movil (ChatwootHub.send_push) porque FIREBASE_* esta vacio. Bloquearlo
|
|
# romperia el push. En vez de eso, este guard detecta el revert y lo deshace.
|
|
#
|
|
# Cheap por diseno: en el caso normal hace 1 SELECT y sale. Solo cuando detecta
|
|
# plan != enterprise levanta Rails para limpiar cache y reactivar flags.
|
|
#
|
|
# Instalado por el runbook docs/runbooks/chatwoot-update.md
|
|
# Cron: */15 * * * *
|
|
|
|
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
|
|
|
LXC=102
|
|
UUID=c11xzy2tx2cdapm32f5b89vy
|
|
DB_CT="postgres-$UUID"
|
|
APP_CT="chatwoot-$UUID"
|
|
LOG=/var/log/chatwoot-enterprise-guard.log
|
|
LOCK=/var/lock/chatwoot-enterprise-guard.lock
|
|
MAX_LOG=2097152
|
|
|
|
log() { echo "[$(date -u '+%Y-%m-%dT%H:%M:%SZ')] $*" >> "$LOG"; }
|
|
|
|
# Rotacion simple para que el log no crezca sin control.
|
|
if [ -f "$LOG" ] && [ "$(stat -c %s "$LOG" 2>/dev/null || echo 0)" -gt "$MAX_LOG" ]; then
|
|
mv -f "$LOG" "$LOG.1"
|
|
fi
|
|
|
|
# Una sola instancia a la vez (el camino de reparacion tarda ~1 min).
|
|
exec 9>"$LOCK" || exit 0
|
|
flock -n 9 || exit 0
|
|
|
|
# --- 1) Chequeo barato: en que plan estamos ---------------------------------
|
|
# Se traen todas las filas y se filtra con grep a proposito: un WHERE con
|
|
# literales necesitaria comillas simples anidadas dentro de bash -lc '...' y eso
|
|
# es exactamente la clase de escaping que rompe estos scripts.
|
|
PLAN=$(pct exec "$LXC" -- docker exec -i "$DB_CT" bash -lc \
|
|
'PGPASSWORD="$POSTGRES_PASSWORD" psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -At -F "|" -c "SELECT name, serialized_value::text FROM public.installation_configs"' \
|
|
2>/dev/null | grep '^INSTALLATION_PRICING_PLAN|')
|
|
|
|
if [ -z "$PLAN" ]; then
|
|
log "ERROR: no se pudo leer INSTALLATION_PRICING_PLAN (stack caido o contenedor renombrado?)"
|
|
exit 1
|
|
fi
|
|
|
|
case "$PLAN" in
|
|
*enterprise*)
|
|
# Caso normal: nada que hacer, y no ensuciamos el log.
|
|
exit 0
|
|
;;
|
|
esac
|
|
|
|
log "DETECTADO revert -> plan actual: $PLAN . Reparando..."
|
|
|
|
# --- 2) Reponer las 3 filas del parche --------------------------------------
|
|
cat > /tmp/cw-guard.sql <<'SQLEOF'
|
|
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';
|
|
SQLEOF
|
|
|
|
SQL_OUT=$(pct exec "$LXC" -- docker exec -i "$DB_CT" bash -lc \
|
|
'PGPASSWORD="$POSTGRES_PASSWORD" psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -v ON_ERROR_STOP=1' \
|
|
< /tmp/cw-guard.sql 2>&1)
|
|
SQL_RC=$?
|
|
rm -f /tmp/cw-guard.sql
|
|
|
|
UPDATES=$(printf '%s\n' "$SQL_OUT" | grep -c '^UPDATE 1$')
|
|
if [ "$SQL_RC" -ne 0 ] || [ "$UPDATES" -ne 3 ]; then
|
|
log "ERROR: los UPDATE fallaron (rc=$SQL_RC, 'UPDATE 1'=$UPDATES). Salida: $SQL_OUT"
|
|
exit 1
|
|
fi
|
|
log "OK: 3/3 UPDATE aplicados"
|
|
|
|
# --- 3) Limpiar cache de GlobalConfig y reactivar flags premium -------------
|
|
# El UPDATE por SQL no dispara el after_commit :clear_cache de InstallationConfig,
|
|
# y GlobalConfig cachea en Redis con TTL de 1 dia: sin este paso la app puede
|
|
# seguir sirviendo 'community'. Ademas hay que reactivar los flags por cuenta,
|
|
# que viven en accounts.feature_flags (bitmask) y el SQL de arriba no toca.
|
|
cat > /tmp/cw-guard.rb <<'RBEOF'
|
|
PREMIUM = %w[
|
|
disable_branding audit_logs sla custom_roles
|
|
captain_integration captain_integration_v2 captain_document_auto_sync
|
|
csat_review_notes conversation_required_attributes
|
|
]
|
|
GlobalConfig.clear_cache
|
|
Account.find_each do |account|
|
|
account.enable_features!(*PREMIUM)
|
|
account.reload
|
|
pend = PREMIUM.reject { |f| account.feature_enabled?(f) }
|
|
puts "account=#{account.id} pendientes=#{pend.empty? ? 'ninguno' : pend.join(',')}"
|
|
end
|
|
puts "self_hosted_enterprise=#{ChatwootApp.self_hosted_enterprise?}"
|
|
RBEOF
|
|
|
|
pct push "$LXC" /tmp/cw-guard.rb /tmp/cw-guard.rb
|
|
pct exec "$LXC" -- docker cp /tmp/cw-guard.rb "$APP_CT":/tmp/cw-guard.rb
|
|
RB_OUT=$(pct exec "$LXC" -- docker exec -i "$APP_CT" bundle exec rails runner /tmp/cw-guard.rb 2>&1)
|
|
RB_RC=$?
|
|
pct exec "$LXC" -- docker exec -i "$APP_CT" rm -f /tmp/cw-guard.rb
|
|
pct exec "$LXC" -- rm -f /tmp/cw-guard.rb
|
|
rm -f /tmp/cw-guard.rb
|
|
|
|
RESULT=$(printf '%s\n' "$RB_OUT" | grep -E '^(account=|self_hosted_enterprise=)' | tr '\n' ' ')
|
|
if [ "$RB_RC" -ne 0 ]; then
|
|
log "ERROR: rails runner fallo (rc=$RB_RC). Salida: $RB_OUT"
|
|
exit 1
|
|
fi
|
|
|
|
case "$RESULT" in
|
|
*"self_hosted_enterprise=true"*)
|
|
case "$RESULT" in
|
|
*"pendientes=ninguno"*)
|
|
log "REPARADO: $RESULT"
|
|
exit 0
|
|
;;
|
|
esac
|
|
log "PARCIAL: enterprise activo pero quedaron flags pendientes -> $RESULT"
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
log "ERROR: tras reparar, self_hosted_enterprise no dio true -> $RESULT"
|
|
exit 1
|