194 lines
12 KiB
Markdown
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.
|