Files
Proxmox-Coolify-Manager/CLAUDE.md
T

194 lines
12 KiB
Markdown

# 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](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](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](docs/TOOL-INDEX.md).
| 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](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](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](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](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](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:
```powershell
.\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](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](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](scripts/Invoke-CloudflareApi.ps1), **nunca**
editando archivos en el host. Los puertos 6001/6002 deben usar `http://`. Ver
[docs/runbooks/cloudflare-tunnel.md](docs/runbooks/cloudflare-tunnel.md).
## Dónde vive el contexto
| Qué | Dónde |
|---|---|
| **Catálogo de herramientas + gotchas** | **[docs/TOOL-INDEX.md](docs/TOOL-INDEX.md)** |
| Estado verificado del sistema | [docs/proxmox-inventory.md](docs/proxmox-inventory.md) |
| Procedimientos vigentes | [docs/runbooks/](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/](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/](docs/incidentes/) |
| Contrato para *construir* una app deployable | [docs/AGENTS-coolify-apps.md](docs/AGENTS-coolify-apps.md) |
| Reglas operativas por dominio | [agent/SKILL.md](agent/SKILL.md), [coolify_skill/SKILL.md](coolify_skill/SKILL.md), [deploy_skill/SKILL.md](deploy_skill/SKILL.md), [gitea_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.