Files
Proxmox-Coolify-Manager/CLAUDE.md
T

12 KiB

CLAUDE.md

Guía para Claude Code (claude.ai/code) al trabajar en este repositorio.

Antes de invocar cualquier herramienta

  1. Carga los secretos: . .\.env.local.ps1 (gitignored). Sin esto, todas las llamadas a API lanzan excepción.
  2. Lee docs/TOOL-INDEX.md — es el catálogo canónico de todo lo ejecutable: firma real de cada script, variables de entorno que requiere, y si es solo-lectura o mutante. Su §1 lista seis gotchas que producen resultados silenciosamente incorrectos. No los adivines.
  3. Para el estado actual del sistema (qué existe, qué versión corre): docs/proxmox-inventory.md.

Los seis gotchas, en una línea cada uno (detalle en el índice):

  • -Raw está invertido entre wrappers. En Coolify y Cloudflare, sin -Raw recibes un string, no objetos: filtrar da vacío sin error.
  • Invoke-ProxmoxSsh.ps1 corrompe comillas anidadas. Para comandos con más de un nivel de comillas, codifica en base64.
  • Los nombres de contenedor llevan sufijo uuid y cambian en cada redeploy. Resuélvelos siempre; nunca los escribas a mano.
  • /applications/* de la API de Coolify da 404 por el hostname público (Cloudflare), no por Coolify. Contra el origen (http://192.168.0.117:8000/api/v1, $env:COOLIFY_API_URL_ORIGIN) la API completa responde; y desde v4.2 los endpoints de estado exigen POST.
  • "Verde en Coolify" no es "enrutado en Traefik". La UI mira running; Traefik exige healthy. Un servicio nuevo aún arrancando da 503 no available server sin que nada esté mal configurado.
  • Un 502 casi siempre es el puerto. Coolify saca el puerto de Traefik del :puerto del FQDN guardado; sin él cae al EXPOSE de la imagen. Si no coinciden, connection refused.

Qué es este repo

No es el código de una aplicación. Es un toolkit de operaciones: wrappers de PowerShell más documentación de contexto y runbooks que permiten a un agente diagnosticar y administrar un homelab — un host Proxmox local (192.168.0.200, nodo thinkcentre) y el stack Coolify que corre dentro de su LXC 102. No hay build, lint ni tests: los "comandos" son los scripts operativos.

Convención de idioma: la documentación (docs/, README.md, este archivo) está en español. Los scripts y los SKILL.md/TOOLS.md de cada skill están en inglés.

Router de intención → herramienta

Lo que pide el usuario, y con qué se resuelve. Para la firma completa de cada script, ve al índice.

El usuario pide… Usa
"¿está bien el servidor?", "revisa el Proxmox" .\scripts\Test-ProxmoxConnection.ps1 y luego .\scripts\Get-ProxmoxInventory.ps1
"inventario de LXC/VMs", "qué hay corriendo" .\scripts\Get-ProxmoxInventory.ps1
"¿está corriendo X?", "estado de los contenedores" .\coolify_skill\scripts\Get-CoolifyDockerStatus.ps1 -Filter <regex>
"logs de X" .\scripts\Invoke-ProxmoxSsh.ps1 -Command "pct exec 102 -- docker logs <nombre-resuelto> --tail 100"
"qué apps hay en Coolify", "dame el uuid de X" .\coolify_skill\scripts\Invoke-CoolifyApi.ps1 -Path "/resources" -Raw
cualquier cosa de la API de Coolify .\coolify_skill\scripts\Invoke-CoolifyApi.ps1 — pero revisa primero qué endpoints viven (§1.4 del índice)
"¿está online el sitio X?" .\deploy_skill\scripts\Test-ServiceOnline.ps1 -Fqdn https://x.urieljareth.org
"el servicio nuevo está en verde pero da 503 / no available server" .\coolify_skill\scripts\Test-CoolifyServiceReady.ps1 -Uuid <uuid> -WaitSeconds 600 — espera, no redeployes; ver §1.5 del índice
"arregla el túnel de Cloudflare", rutas/DNS .\scripts\Invoke-CloudflareApi.ps1 + docs/runbooks/cloudflare-tunnel.md
"Chatwoot perdió el enterprise" diagnostica con .\scripts\Get-ChatwootLicenseStatus.ps1 -Deep; repara con Apply-ChatwootEnterprisePatch.ps1 -ReenableAccountFeatures
"que arranque solo tras un apagón" .\scripts\Install-CoolifyAutostart.ps1 -VerifyOnly primero
"publica este proyecto en Coolify" lee §4 del índice antes. Stack compose → New-CoolifyService.ps1 (arreglado y verificado el 2026-08-24); app git → flujo UI con Playwright
"una app de Coolify sigue el main de upstream y se rompió" docs/casos/firecrawl-stack-minimo.md — cambiar a imágenes precompiladas y compose propio
"valida que este proyecto se puede deployar" .\deploy_skill\scripts\Test-PreDeployChecklist.ps1 -Path <ruta> -Strict
"estoy construyendo una app para este Coolify" docs/AGENTS-coolify-apps.md (contrato de red, puertos, dominios, volúmenes)
"crea/lista un repo en Gitea", "sube esto a Gitea" .\gitea_skill\scripts\ — ver §5 del índice
"diagnosticar/iniciar Hermes (LXC 100)", "MiniMax-M3" .\scripts\Invoke-ProxmoxSsh.ps1 -Command "pct status 100" + docs/casos/hermes-minimax-m3-setup.md
"un contenedor no conecta a su base de datos" ver "Gotcha de red Docker" abajo

Arquitectura

Todo llega al host por un único camino SSH. No hay acceso directo a Docker ni a la red interna:

script PowerShell → Invoke-ProxmoxSshCommand (scripts/ProxmoxAgent.ps1)
  → ssh [email protected]
    → pct exec 102 -- docker ...        (cualquier trabajo de Docker/Coolify)
  • scripts/ProxmoxAgent.ps1 es la librería compartida. Hazle dot-source (. .\scripts\ProxmoxAgent.ps1) para obtener Get-ProxmoxConfig, Invoke-ProxmoxSshCommand e Invoke-ProxmoxApi. Todos los demás scripts la consumen en lugar de reimplementar la conexión.
  • Resolución de configuración: Get-ProxmoxConfig lee variables de entorno (PROXMOX_HOST, PROXMOX_NODE, PROXMOX_SSH_KEY, PROXMOX_COOLIFY_LXC…) y cae a los defaults locales hardcodeados. SSH funciona solo con los defaults; las llamadas a la API exigen PROXMOX_API_TOKEN_ID + PROXMOX_API_TOKEN_SECRET (si faltan, Invoke-ProxmoxApi lanza). El ID del LXC de Coolify (102) viene de la config: usa $config.CoolifyLxc, no lo hardcodees en scripts nuevos.
  • Cuatro APIs independientes, con scripts y variables distintas: Proxmox REST (curl.exe -k, header de token PVE), Coolify (Invoke-RestMethod, Bearer, base https://coolify.urieljareth.org/api/v1), Cloudflare y Gitea.
  • Docker no se administra en el host Proxmox: vive dentro del LXC 102. Todo comando de contenedor va envuelto en pct exec 102 -- docker ....

Gotcha de red Docker

Cuando una app de Coolify y su base de datos son contenedores hermanos en la misma red Docker, la app debe alcanzar la DB por su nombre de servicio Docker, no por localhost (Nextcloud, por ejemplo, usa el host nextcloud-db). El DNS entre servicios es la causa raíz habitual de los fallos de "conexión a la base de datos" aquí. Compruébalo con:

.\scripts\Invoke-ProxmoxSsh.ps1 -Command "pct exec 102 -- docker exec <app> getent hosts <nombre-servicio>"

Reglas de operación (las imponen las skills — cúmplelas)

  • Solo-lectura primero. El default es diagnosticar: list, status, logs, inspect, health checks.
  • Confirma antes de cualquier cambio de estado. Pregunta explícitamente antes de: pct/qm start/stop/reboot/destroy; docker restart/stop/rm/compose up-down; deploys de Coolify o cualquier POST/PUT/PATCH/DELETE; escritura de variables de entorno; y todo cambio de firewall, red, storage, volumen, clave o token. Para acciones riesgosas: captura el estado actual y enuncia primero el camino de rollback.
  • Nunca escribas secretos en el repo. Ni tokens, passwords, claves privadas, cookies o secretos de token PVE — no en Markdown, no en scripts, no en logs. Viven solo en .env.local.ps1 (gitignored) o en el almacén del SO. .gitignore bloquea además ACCESS.md, *.key, *.pem, *.crt. Al depurar bases de datos, verifica conectividad sin imprimir credenciales.
  • Prefiere los scripts del repo antes que cadenas de comandos manuales largas, y no ejecutes comandos destructivos amplios construidos desde strings generados.

Chatwoot: el parche enterprise se degrada solo — pero un guard lo auto-repara

El parche no se pierde al actualizar. Internal::CheckNewVersionsJob hace ping diario a hub.2.chatwoot.com (a los MD5(INSTALLATION_IDENTIFIER).hex % 1440 minutos pasada la medianoche UTC = 16:16 UTC en esta instalación) y reescribe INSTALLATION_PRICING_PLAN con la respuesta del hub; después ReconcilePlanConfigService apaga los 9 feature flags premium en todas las cuentas. De ahí tres consecuencias:

  • El SQL de 3 filas no basta. Los flags por cuenta viven en accounts.feature_flags (bitmask). Usa .\scripts\Apply-ChatwootEnterprisePatch.ps1 -ReenableAccountFeatures; comprueba el estado con .\scripts\Get-ChatwootLicenseStatus.ps1 -Deep.
  • Los UPDATE por SQL no invalidan la caché Redis de GlobalConfig (TTL de 1 día, V1:GLOBAL_CONFIG:*) porque se saltan el after_commit :clear_cache de InstallationConfig. Cualquier parche por SQL debe llamar además a GlobalConfig.clear_cache.
  • No bloquees hub.2.chatwoot.com. Ese mismo host relaya el push móvil (ChatwootHub.send_push), activo aquí porque FIREBASE_* está vacío. En su lugar, scripts/chatwoot-enterprise-guard.sh corre en el host Proxmox desde /root/scripts/, agendado por /etc/cron.d/chatwoot-enterprise-guard cada 5 minutos, y repara un revert detectado en ~13s (log: /var/log/chatwoot-enterprise-guard.log, solo escribe cuando actúa).

Nunca pulses Refresh en /super_admin/settings.

Estado al 2026-08-07: corre chatwoot/chatwoot:v4.16.2 (el pin documentado antes era v4.16.1, así que el pin no sostuvo la versión). El plan está en enterprise y una ejecución manual del guard pasa correctamente, pero el log registra ERROR: no se pudo leer INSTALLATION_PRICING_PLAN durante la ventana del update. El guard tiene el nombre del contenedor hardcodeado (postgres-c11xzy2tx2cdapm32f5b89vy), así que un redeploy que cambie el sufijo lo deja ciego. Análisis completo, registro de ejecución y rollback: docs/runbooks/chatwoot-update.md.

Túnel de Cloudflare

El túnel es gestionado desde el dashboard (el ingress baja del edge — se ve como INF Updated to new configuration version=N en los logs de cloudflared). Corrige rutas en el dashboard o vía scripts/Invoke-CloudflareApi.ps1, nunca editando archivos en el host. Los puertos 6001/6002 deben usar http://. Ver docs/runbooks/cloudflare-tunnel.md.

Dónde vive el contexto

Qué Dónde
Catálogo de herramientas + gotchas docs/TOOL-INDEX.md
Estado verificado del sistema docs/proxmox-inventory.md
Procedimientos vigentes docs/runbooks/ — conexion.md, diagnostico.md, coolify-docker.md, seguridad.md, cloudflare-tunnel.md, autostart-coolify.md, nextcloud.md, baserow.md, chatwoot-update.md
Casos resueltos paso a paso docs/casos/ — chatwoot-enterprise-patch.md, hermes-minimax-m3-setup.md, coolify-servicio-nuevo-503-no-available-server.md, firecrawl-stack-minimo.md
Incidentes archivados (no fuente de verdad) docs/incidentes/
Contrato para construir una app deployable docs/AGENTS-coolify-apps.md
Reglas operativas por dominio agent/SKILL.md, coolify_skill/SKILL.md, deploy_skill/SKILL.md, gitea_skill/SKILL.md
Ejemplos ejecutables por dominio el TOOLS.md de cada carpeta *_skill/
Referencia de la API de Coolify coolify_skill/references/ops/*.md — busca con rg, no cargues el árbol

Para la referencia de la API de Coolify: no leas el árbol completo. Busca en coolify_skill/references/ con rg y abre el único references/ops/*.md que coincida.