Actualiza toolkit operativo y documentación
This commit is contained in:
@@ -0,0 +1,408 @@
|
||||
# Tool index — catálogo canónico de herramientas
|
||||
|
||||
**Este es el índice único de todo lo ejecutable del repo.** Si buscas "qué script
|
||||
uso para X", empieza aquí y no en los `TOOLS.md` de cada skill (esos son guías de
|
||||
uso; este es el catálogo).
|
||||
|
||||
Verificado contra el host real el **2026-08-07**. Las firmas de parámetros se
|
||||
extrajeron del AST de PowerShell, no a mano.
|
||||
|
||||
---
|
||||
|
||||
## 0. Cómo leer este índice
|
||||
|
||||
- **R/W** — `RO` = solo lectura, se puede ejecutar sin preguntar. `W` = muta
|
||||
estado, **exige confirmación explícita del usuario antes de ejecutar**.
|
||||
`RO/W` = depende de los parámetros (columna "notas" lo aclara).
|
||||
- **Env** — variables que deben estar cargadas (`. .\.env.local.ps1`). Si faltan,
|
||||
el script *lanza excepción*, no falla silenciosamente.
|
||||
- Todo se ejecuta desde la raíz del repo, en PowerShell.
|
||||
|
||||
---
|
||||
|
||||
## 1. Los 6 gotchas que producen resultados silenciosamente incorrectos
|
||||
|
||||
Léelos antes de invocar nada. No son teóricos: los cuatro primeros se
|
||||
verificaron el 2026-08-07 y el quinto el 2026-08-23. Cada uno rompe una tarea de
|
||||
forma que *parece* haber funcionado.
|
||||
|
||||
### 1.1 `-Raw` significa lo OPUESTO en dos familias de wrappers
|
||||
|
||||
Hay dos convenciones incompatibles. Elegir mal no da error: da un resultado vacío.
|
||||
|
||||
| Wrapper | Sin `-Raw` (default) | Con `-Raw` |
|
||||
|---|---|---|
|
||||
| `coolify_skill\scripts\Invoke-CoolifyApi.ps1` | **string JSON** | **objetos PowerShell** |
|
||||
| `scripts\Invoke-CloudflareApi.ps1` | **string JSON** | **objetos PowerShell** |
|
||||
| `deploy_skill\scripts\Invoke-GitHubApi.ps1` | **objetos PowerShell** | `{Status, Headers, Body}` |
|
||||
| `gitea_skill\scripts\Invoke-GiteaApi.ps1` | **objetos PowerShell** | `{Status, Headers, Body}` |
|
||||
|
||||
Consecuencia práctica: en Coolify y Cloudflare, **si vas a filtrar o proyectar el
|
||||
resultado necesitas `-Raw`**. Sin él recibes un `System.String` y
|
||||
|
||||
```powershell
|
||||
# MAL: devuelve una fila vacía, sin error. El resultado es un string.
|
||||
.\coolify_skill\scripts\Invoke-CoolifyApi.ps1 -Path "/resources" |
|
||||
Select-Object name, uuid
|
||||
|
||||
# BIEN
|
||||
.\coolify_skill\scripts\Invoke-CoolifyApi.ps1 -Path "/resources" -Raw |
|
||||
Select-Object name, uuid
|
||||
```
|
||||
|
||||
Usa el default (string JSON) solo cuando vas a mostrar la respuesta tal cual.
|
||||
|
||||
### 1.2 `Invoke-ProxmoxSsh.ps1` corrompe las comillas anidadas
|
||||
|
||||
`Invoke-ProxmoxSshCommand` pasa `$Command` como un único argumento a `ssh`, y
|
||||
PowerShell 5.1 destroza las comillas embebidas al invocar un ejecutable nativo.
|
||||
Cualquier comando con quoting anidado —típicamente `docker exec ... bash -lc '...
|
||||
psql -c "SELECT ..."'`— llega mutilado al host:
|
||||
|
||||
```
|
||||
bash: line 1: -c: command not found
|
||||
psql: option requires an argument -- 'F'
|
||||
```
|
||||
|
||||
**Solución verificada: codifica el comando remoto en base64.** Es el patrón a
|
||||
usar para cualquier cosa con más de un nivel de comillas:
|
||||
|
||||
```powershell
|
||||
$remote = @'
|
||||
pct exec 102 -- docker exec -i postgres-c11xzy2tx2cdapm32f5b89vy bash -lc 'PGPASSWORD="$POSTGRES_PASSWORD" psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -At -c "SELECT name FROM public.installation_configs"'
|
||||
'@
|
||||
$b64 = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($remote))
|
||||
.\scripts\Invoke-ProxmoxSsh.ps1 -Command "echo $b64 | base64 -d | bash 2>&1"
|
||||
```
|
||||
|
||||
El here-string `@'...'@` (comillas simples) es obligatorio: evita que PowerShell
|
||||
expanda `$POSTGRES_PASSWORD` del lado de Windows.
|
||||
|
||||
Para comandos de un solo nivel de comillas (`pct list`, `docker ps --format
|
||||
'{{.Names}}'`) el wrapper directo funciona bien.
|
||||
|
||||
### 1.3 Los nombres de contenedor NO se pueden adivinar
|
||||
|
||||
Coolify nombra cada contenedor `<servicio>-<uuid>` (y a veces le añade un sufijo
|
||||
numérico de build). No existe un contenedor llamado `chatwoot` ni `nextcloud`:
|
||||
|
||||
```
|
||||
chatwoot-c11xzy2tx2cdapm32f5b89vy
|
||||
nextcloud-db-hdcdpkm0jko3qqvn5683ercc
|
||||
web-instademo0portal0insta0demo1-060825532589
|
||||
```
|
||||
|
||||
**Siempre resuelve el nombre real antes de usarlo.** Dos caminos:
|
||||
|
||||
```powershell
|
||||
# a) desde Docker, por patrón
|
||||
.\scripts\Invoke-ProxmoxSsh.ps1 -Command "pct exec 102 -- docker ps --format '{{.Names}}' | grep -i chatwoot"
|
||||
|
||||
# b) desde Coolify, para obtener el uuid del recurso (y de ahí el sufijo)
|
||||
.\coolify_skill\scripts\Invoke-CoolifyApi.ps1 -Path "/resources" -Raw |
|
||||
Where-Object { $_.name -match 'chatwoot' } | Select-Object name, uuid, fqdn
|
||||
```
|
||||
|
||||
Corolario: **un redeploy puede recrear el contenedor con otro sufijo**, y cualquier
|
||||
script o cron que tenga el nombre hardcodeado empieza a fallar. Es exactamente lo
|
||||
que le pasó al guard de Chatwoot (ver [runbooks/chatwoot-update.md](runbooks/chatwoot-update.md)).
|
||||
|
||||
### 1.4 `/applications/*` da 404 — por Cloudflare, no por Coolify (corregido 2026-08-29)
|
||||
|
||||
**Re-verificado a fondo el 2026-08-29 sobre v4.3.14 y el diagnóstico anterior
|
||||
cambió:** el 404 NO lo produce Coolify. Es un bloqueo del edge de Cloudflare en
|
||||
el hostname público. Mismo token, misma ruta:
|
||||
|
||||
| Llamada | Resultado |
|
||||
|---|---|
|
||||
| `https://coolify.urieljareth.org/api/v1/applications` (vía Cloudflare) | **404** |
|
||||
| `http://192.168.0.117:8000/api/v1/applications` (origen, LXC 102) | **200** |
|
||||
| `/github-apps` | igual: 404 vía CF, 200 vía origen |
|
||||
| `/version`, `/resources`, `/services`, `/databases`, `/projects`, `/servers`, `/teams`, `/deployments`, `/security/keys` | OK por ambas vías |
|
||||
|
||||
La API del origen está **completa**: el `openapi.yaml` del contenedor
|
||||
(`/var/www/html/openapi.yaml`) declara todo el namespace de `/applications/*`,
|
||||
notificaciones, proveedores cloud y MCP, y responde. **Para esos endpoints,
|
||||
apunta la llamada al origen** (`$env:COOLIFY_API_URL_ORIGIN`) o corrige la regla
|
||||
de Cloudflare en el dashboard. Además, desde v4.2 los endpoints de estado
|
||||
exigen **POST** (`GET /deploy` → 405; ver notas §10.1).
|
||||
|
||||
Esto re-habilita (previa verificación en el próximo deploy) herramientas que
|
||||
estaban marcadas rotas — ver §4.
|
||||
|
||||
**Nota (2026-08-24):** `POST /services` **sí funciona** para stacks compose
|
||||
propios, con tres condiciones que la API no perdona: **no** enviar `type` junto a
|
||||
`docker_compose_raw`, mandar el compose en **base64**, y enviar el body como
|
||||
**bytes UTF-8**. Las tres estaban mal en el toolkit y ya están corregidas; el
|
||||
detalle está en
|
||||
[docs/casos/firecrawl-stack-minimo.md §4](casos/firecrawl-stack-minimo.md).
|
||||
`Test-PreDeployChecklist.ps1` sigue sin leer `docker-compose.coolify.yml`
|
||||
(solo mira `docker-compose.yml|yaml` y `compose.yml|yaml`), así que **puede dar
|
||||
todo PASS sobre un archivo que no abrió**.
|
||||
|
||||
### 1.5 "Verde en Coolify" NO significa "enrutado en Traefik"
|
||||
|
||||
Verificado el 2026-08-23. Es la causa de que un servicio recién creado desde la
|
||||
librería de Coolify devuelva **`503 no available server`** estando en verde.
|
||||
|
||||
Son dos señales distintas y la UI solo muestra una:
|
||||
|
||||
| Señal | Quién la usa | Qué significa |
|
||||
|---|---|---|
|
||||
| `State.Status = running` | **La UI de Coolify** (el punto verde) | El contenedor existe y no ha muerto |
|
||||
| `State.Health.Status = healthy` | **Traefik** | El contenedor entra al balanceador |
|
||||
|
||||
Traefik solo enruta contenedores que Docker reporta `healthy`. Un contenedor
|
||||
`running` + `unhealthy` **no tiene ruta**, la petición cae al catch-all de
|
||||
Coolify (`default_redirect_503.yaml`: `priority: -1000`, servicio `noop` con
|
||||
`servers: { }`) y de ahí sale el string `no available server`.
|
||||
|
||||
En este host el primer boot de un servicio tarda **minutos** (rootfs ext4 sobre
|
||||
loopback sobre HDD, ~39 ms por escritura), pero **12 de los 14 servicios no
|
||||
tienen `start_period`** en su healthcheck. Se les declara `unhealthy` mucho antes
|
||||
de que la app llegue a escuchar.
|
||||
|
||||
**Distingue el código HTTP antes de tocar nada:**
|
||||
|
||||
- **`502 Bad Gateway`** → Traefik *tiene* la ruta, el backend rechaza. Problema
|
||||
de la app o del puerto.
|
||||
- **`503 no available server`** → Traefik **no tiene** la ruta. Casi siempre es
|
||||
un contenedor que todavía está arrancando. **Espera, no redeployes**: un
|
||||
redeploy reinicia el entrypoint desde cero y reinicia el arranque lento.
|
||||
|
||||
```powershell
|
||||
# El diagnóstico correcto, en un comando (solo lectura):
|
||||
.\coolify_skill\scripts\Test-CoolifyServiceReady.ps1 -Uuid <uuid> -WaitSeconds 600
|
||||
```
|
||||
|
||||
Detalle completo, evidencia e hipótesis descartadas:
|
||||
[docs/casos/coolify-servicio-nuevo-503-no-available-server.md](casos/coolify-servicio-nuevo-503-no-available-server.md).
|
||||
|
||||
### 1.6 Si la imagen no expone el puerto donde sirve, el FQDN necesita `:puerto`
|
||||
|
||||
Verificado el 2026-08-24 diagnosticando `grimmory`, que devolvía **502**.
|
||||
|
||||
Coolify deriva el label `traefik.http.services.*.loadbalancer.server.port` del
|
||||
**puerto que lleve el FQDN guardado** en `service_applications.fqdn`. Si el FQDN
|
||||
no lleva puerto, Coolify **no emite el label**, y Traefik cae al único puerto que
|
||||
declare la imagen (`Config.ExposedPorts`).
|
||||
|
||||
Eso funciona por accidente cuando app e imagen coinciden, y falla en silencio
|
||||
cuando no:
|
||||
|
||||
| Servicio | FQDN guardado | Sirve en | Imagen expone | Resultado |
|
||||
|---|---|---|---|---|
|
||||
| `nextcloud` | sin puerto | 80 | 80 | OK por coincidencia |
|
||||
| `n8n` | `…:5678` | 5678 | 5678 | OK explícito |
|
||||
| `qdrant` | `…:6333` | 6333 | 6333 | OK explícito |
|
||||
| **`grimmory`** | **sin puerto** | **80** | **6060** | **502** |
|
||||
|
||||
`grimmory` corría `healthy` (su healthcheck prueba `http://127.0.0.1/health`, o
|
||||
sea el 80, y pasaba), Traefik lo enrutaba, y llegaba al 6060 donde no hay nada:
|
||||
**connection refused → 502**.
|
||||
|
||||
Ojo con la confusión: tener `SERVICE_URL_GRIMMORY_80` en el compose **no basta**.
|
||||
Lo que manda es el puerto en el FQDN almacenado.
|
||||
|
||||
**Cómo distinguirlo de otros fallos:**
|
||||
|
||||
- **`502`** → hay ruta, el backend rechaza. Compara el puerto donde escucha la app
|
||||
con el que busca Traefik. Casi siempre es esto.
|
||||
- **`503 no available server`** → no hay ruta (§1.5).
|
||||
|
||||
```powershell
|
||||
# Puertos donde escucha de verdad (en hex; 0050 = 80, 1F90 = 8080):
|
||||
.\scripts\Invoke-ProxmoxSsh.ps1 -Command "pct exec 102 -- docker exec <cont> sh -c 'cat /proc/net/tcp'"
|
||||
|
||||
# Puerto que busca Traefik (si no sale nada, cae al ExposedPorts de la imagen):
|
||||
.\scripts\Invoke-ProxmoxSsh.ps1 -Command "pct exec 102 -- docker inspect <cont> --format '{{json .Config.Labels}}'"
|
||||
```
|
||||
|
||||
**Arreglo:** poner el puerto en el FQDN (`https://x.urieljareth.org:80`) y
|
||||
**redeployar** el servicio — los labels solo se regeneran al recrear el
|
||||
contenedor.
|
||||
|
||||
---
|
||||
|
||||
## 2. Infraestructura: host Proxmox, red, apps del host
|
||||
|
||||
Todo llega al host por un solo camino:
|
||||
|
||||
```
|
||||
PowerShell → Invoke-ProxmoxSshCommand → ssh [email protected]
|
||||
→ pct exec 102 -- docker ... (para cualquier cosa de Docker)
|
||||
```
|
||||
|
||||
| Script | R/W | Env | Parámetros | Qué hace |
|
||||
|---|---|---|---|---|
|
||||
| [scripts/ProxmoxAgent.ps1](../scripts/ProxmoxAgent.ps1) | librería | — | — | **Dot-source obligatorio** (`. .\scripts\ProxmoxAgent.ps1`). Expone `Get-ProxmoxConfig`, `Assert-ProxmoxConfig`, `Invoke-ProxmoxSshCommand`, `Invoke-ProxmoxApi`. Todos los demás scripts lo consumen; no reimplementes la conexión. |
|
||||
| [scripts/Test-ProxmoxConnection.ps1](../scripts/Test-ProxmoxConnection.ps1) | RO | opcional `PROXMOX_API_TOKEN_*` | — | Smoke test: config + SSH + muestra de Docker + auth de API. Sale 1 si algo falla. **Ejecútalo antes de trabajo operativo.** |
|
||||
| [scripts/Get-ProxmoxInventory.ps1](../scripts/Get-ProxmoxInventory.ps1) | RO | — | — | Snapshot completo: host, LXC, QEMU, Docker en LXC 102. |
|
||||
| [scripts/Invoke-ProxmoxSsh.ps1](../scripts/Invoke-ProxmoxSsh.ps1) | **RO/W** | — | `-Command <string>` | Comando arbitrario por SSH. **El R/W lo determina el comando**: `pct list` es RO, `pct stop` es W. Ver gotcha §1.2 para comillas anidadas. |
|
||||
| [scripts/Invoke-CloudflareApi.ps1](../scripts/Invoke-CloudflareApi.ps1) | **RO/W** | `CLOUDFLARE_API_TOKEN` | `-Method` `-Path` `-BodyJson` `-Raw` | API de Cloudflare (túnel + DNS). `GET` es RO; el resto W. `-Raw` → objetos (§1.1). |
|
||||
| [scripts/Install-CoolifyAutostart.ps1](../scripts/Install-CoolifyAutostart.ps1) | **W** (RO con `-VerifyOnly`) | — | `-VerifyOnly` `-SkipOnboot` `-RunNow` `-Uninstall` | Instala el auto-arranque del stack tras corte de luz (LXC 102 + túnel). Usa `-VerifyOnly` para auditar sin tocar nada. |
|
||||
|
||||
**API REST de Proxmox** (distinta de la de Coolify): requiere
|
||||
`PROXMOX_API_TOKEN_ID` + `PROXMOX_API_TOKEN_SECRET`. `Invoke-ProxmoxApi` lanza
|
||||
excepción si faltan.
|
||||
|
||||
> ⚠️ **Estado actual (2026-08-07):** `.env.local.ps1` **no** define
|
||||
> `PROXMOX_API_TOKEN_ID`/`_SECRET`, `CLOUDFLARE_API_TOKEN` ni
|
||||
> `COOLIFY_EMAIL`/`COOLIFY_PASSWORD`. Las tres rutas que dependen de ellas
|
||||
> (API de Proxmox, API de Cloudflare, flujo UI de Coolify) **fallan hoy**. Lo
|
||||
> que sí está cargado: `PROXMOX_HOST/NODE/USER/SSH_KEY/COOLIFY_LXC`,
|
||||
> `COOLIFY_API_URL`, `COOLIFY_TOKEN`, `GITHUB_TOKEN`, `GITHUB_OWNER`,
|
||||
> `GITEA_URL/USER/TOKEN`.
|
||||
|
||||
### 2.1 Chatwoot — parche enterprise
|
||||
|
||||
Contexto completo en [runbooks/chatwoot-update.md](runbooks/chatwoot-update.md).
|
||||
|
||||
| Script | R/W | Parámetros | Qué hace |
|
||||
|---|---|---|---|
|
||||
| [scripts/Get-ChatwootLicenseStatus.ps1](../scripts/Get-ChatwootLicenseStatus.ps1) | RO | `-ServiceUuid` `-AppContainer` `-DbContainer` `-Deep` | Estado de la licencia. `-Deep` verifica además los feature flags por cuenta (`accounts.feature_flags`). **Usa esto para diagnosticar; nunca el parche.** |
|
||||
| [scripts/Apply-ChatwootEnterprisePatch.ps1](../scripts/Apply-ChatwootEnterprisePatch.ps1) | **W** | `-DryRun` `-ReenableAccountFeatures` `-ServiceUuid` `-Container` `-AppContainer` `-LxcId` `-ProxmoxHost` `-SshKey` | Reaplica el parche. **El SQL de 3 filas no basta**: pasa siempre `-ReenableAccountFeatures`. Usa `-DryRun` primero. |
|
||||
| [scripts/chatwoot-enterprise-guard.sh](../scripts/chatwoot-enterprise-guard.sh) | — | — | **Copia versionada** del guard que corre en el host. No se ejecuta desde Windows. Instalado en `/root/scripts/chatwoot-enterprise-guard.sh`, agendado por `/etc/cron.d/chatwoot-enterprise-guard` cada 5 min. Log: `/var/log/chatwoot-enterprise-guard.log` (solo escribe cuando actúa). |
|
||||
|
||||
### 2.2 Artefactos que viven en el host (no se invocan desde Windows)
|
||||
|
||||
| Archivo | Qué es |
|
||||
|---|---|
|
||||
| [scripts/host/coolify-autostart.sh](../scripts/host/coolify-autostart.sh) | Script de arranque; lo despliega `Install-CoolifyAutostart.ps1`. |
|
||||
| [scripts/host/coolify-autostart.service](../scripts/host/coolify-autostart.service) | Unit de systemd correspondiente. |
|
||||
| [scripts/fix-nextcloud-config.php](../scripts/fix-nextcloud-config.php) | Fragmento puntual del fix HTTPS de Nextcloud. Ver [runbooks/nextcloud.md](runbooks/nextcloud.md). |
|
||||
| [scripts/Set-ProxmoxEnv.example.ps1](../scripts/Set-ProxmoxEnv.example.ps1) | Plantilla de entorno. El template completo es [.env.example](../.env.example). |
|
||||
|
||||
### 2.3 Scripts de deploy específicos de una app
|
||||
|
||||
`scripts/apps/` guarda scripts one-off con uuid y dominio **hardcodeados**. Son
|
||||
**mutantes** y están atados a un recurso concreto: lee la cabecera antes de
|
||||
ejecutar uno, y verifica que el uuid siga siendo el correcto (§1.3).
|
||||
|
||||
| Script | R/W | Env | Qué hace |
|
||||
|---|---|---|---|
|
||||
| [scripts/apps/Deploy-SoloLeveling.ps1](../scripts/apps/Deploy-SoloLeveling.ps1) | **W** | `COOLIFY_*`, `PROXMOX_*`, `GITHUB_TOKEN` (scope `repo`) | Re-deploy de "El Sistema (Solo Leveling)" sin depender del pull de GHCR (el registry es privado y el token no tiene `read:packages`). Construye la imagen **en el servidor** (clone del repo privado + `docker build`), la taguea con el nombre que espera el compose generado por Coolify, y levanta el servicio. Params: `-ServiceUuid` `-Domain` `-Repo` `-Image` `-NoBuild`. Requiere que el service ya esté registrado en Coolify. |
|
||||
| [scripts/apps/Deploy-OhDaddy.ps1](../scripts/apps/Deploy-OhDaddy.ps1) | **W** | `COOLIFY_*`, `PROXMOX_*` | Redeploy de oh-daddy (stack app+db+Inngest self-hosted, servicio `rzittzudkunwx8gilonn7tqe`). Clone del repo público + `stacks/oh-daddy/Dockerfile` inyectado, build de `oh-daddy-app:local` en el server, `compose up -d`, schema idempotente y re-registro de funciones Inngest (`PUT /api/inngest`). Params: `-ServiceUuid` `-Fqdn` `-Repo` `-Image` `-NoBuild`. Detalle: [casos/oh-daddy-deploy.md](casos/oh-daddy-deploy.md). |
|
||||
|
||||
---
|
||||
|
||||
## 3. Coolify — operación
|
||||
|
||||
| Script | R/W | Env | Parámetros | Qué hace |
|
||||
|---|---|---|---|---|
|
||||
| [coolify_skill/scripts/Get-CoolifyDockerStatus.ps1](../coolify_skill/scripts/Get-CoolifyDockerStatus.ps1) | RO | — | `-Filter <regex>` `-All` | Estado de contenedores vía LXC 102. `-All` lista todo; `-Filter` acota por regex. Primera parada para "¿está corriendo X?". |
|
||||
| [coolify_skill/scripts/Test-CoolifyServiceReady.ps1](../coolify_skill/scripts/Test-CoolifyServiceReady.ps1) | RO | — | `-Uuid <uuid>` `-Fqdn <url>` `-WaitSeconds <n>` | Contrasta `running` vs `healthy` por contenedor (la discrepancia que la UI esconde), avisa de `start_period` insuficiente, detecta setup en curso (`chown`/`apt`) y prueba el dominio distinguiendo 503 de 502. **Primera parada para un `503 no available server`** — ver §1.5. |
|
||||
| [coolify_skill/scripts/Set-CoolifyHealthcheckGrace.ps1](../coolify_skill/scripts/Set-CoolifyHealthcheckGrace.ps1) | **RO/W** | — | `-Uuid <uuid>` `-StartPeriodSeconds 300` `-MinIntervalSeconds 10` `-ShowResult` `-Apply` | Inserta `start_period` en los healthcheck que no lo tienen y sube intervalos demasiado cortos, editando `services.docker_compose_raw`. **Dry-run sin `-Apply`.** Con `-Apply` guarda copia de rollback en `backups/` y verifica leyendo de vuelta. **No redeploya** — el healthcheck solo aplica al recrear el contenedor. Ver §1.5. |
|
||||
| [coolify_skill/scripts/Invoke-CoolifyApi.ps1](../coolify_skill/scripts/Invoke-CoolifyApi.ps1) | **RO/W** | `COOLIFY_TOKEN` | `-Method` `-Path` `-BodyJson` `-Raw` | API de Coolify. `GET` RO; `POST/PUT/PATCH/DELETE` W → confirmación. Ver §1.1 (`-Raw`) y §1.4 (endpoints que dan 404). |
|
||||
| `coolify_skill/scripts/coolify.sh` | RO/W | `COOLIFY_TOKEN` | — | Helper Bash legacy para sesiones Linux/WSL. En este repo se prefieren los wrappers PowerShell. |
|
||||
|
||||
**Referencia de la API:** no cargues el árbol completo. Busca y abre un solo
|
||||
archivo:
|
||||
|
||||
```powershell
|
||||
rg -n "deploy|database|environment" .\coolify_skill\references
|
||||
Get-Content .\coolify_skill\references\ops\deploy-by-tag-or-uuid.md
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Deploy de proyectos nuevos a Coolify
|
||||
|
||||
> ⚠️ **Lee esto antes de usar cualquier cosa de esta sección.** El pipeline
|
||||
> "una sola línea" fallaba porque `/applications/*` daba 404 (§1.4). Desde el
|
||||
> 2026-08-29 sabemos que ese 404 es de **Cloudflare, no de Coolify**: apuntando
|
||||
> `COOLIFY_API_URL` al origen (`http://192.168.0.117:8000/api/v1`) el namespace
|
||||
> completo responde. Scripts marcados ❌ abajo deben funcionar así — pendiente de
|
||||
> verificar en el próximo deploy; el flujo UI y la vía DB siguen como fallback.
|
||||
|
||||
| Script | R/W | Env | Estado en esta instancia |
|
||||
|---|---|---|---|
|
||||
| [Publish-ProjectToCoolify.ps1](../deploy_skill/scripts/Publish-ProjectToCoolify.ps1) | W | `GITHUB_TOKEN`, `COOLIFY_TOKEN` | ⚠️ Su paso 5 invoca `New-CoolifyApplication.ps1` → probar contra el origen. |
|
||||
| [New-CoolifyApplication.ps1](../deploy_skill/scripts/New-CoolifyApplication.ps1) | W | `COOLIFY_TOKEN` | ⚠️ Usa `POST /applications/public` y `PATCH /applications/{uuid}` → funcionan vía origen (404 solo vía Cloudflare). |
|
||||
| [Invoke-CoolifyRollback.ps1](../deploy_skill/scripts/Invoke-CoolifyRollback.ps1) | W | `COOLIFY_TOKEN` | ⚠️ Usa `PATCH /applications/{uuid}` → funciona vía origen. |
|
||||
| [New-CoolifyService.ps1](../deploy_skill/scripts/New-CoolifyService.ps1) | W | `COOLIFY_TOKEN` | ✅ **Funciona.** Usa `POST /services`. Es la vía válida para stacks multi-contenedor. |
|
||||
| [New-CoolifyAppViaDB.ps1](../deploy_skill/scripts/New-CoolifyAppViaDB.ps1) | **W (INSERT directo en la DB)** | — | ⚠️ **Último recurso.** Escribe la fila en `applications` de la DB de Coolify saltándose API y UI. Sin validación ni rollback. Requiere `-EnvironmentId` y `-GithubRepoId` reales. |
|
||||
| `coolify-ui/coolify-login.mjs` + `Configure-CoolifyComposeApp.mjs` | W (vía navegador) | `COOLIFY_EMAIL`, `COOLIFY_PASSWORD` | ✅ Fallback soportado para apps git build-from-source. Credenciales probables ya en `.env.local.ps1` (sin verificar). |
|
||||
|
||||
**Vías, en orden de preferencia:**
|
||||
|
||||
1. Stack multi-contenedor con `docker-compose.coolify.yml` → `New-CoolifyService.ps1` (API).
|
||||
2. App git build-from-source → API contra el origen; si falla, flujo UI con Playwright (`coolify-ui/`).
|
||||
3. Último recurso → `New-CoolifyAppViaDB.ps1`.
|
||||
|
||||
Detalle completo del flujo v4.1.2 y sus trampas (BOM UTF-8 rompe el parser YAML
|
||||
de Coolify; "Reload Compose File" es obligatorio; fijar dominios por servicio o
|
||||
sale 503; no encolar deploys concurrentes) en
|
||||
[deploy_skill/references/coolify-4.1.2-notes.md](../deploy_skill/references/coolify-4.1.2-notes.md).
|
||||
|
||||
### 4.1 Scripts de deploy que funcionan sin depender de `/applications`
|
||||
|
||||
| Script | R/W | Env | Parámetros | Qué hace |
|
||||
|---|---|---|---|---|
|
||||
| [Initialize-CoolifyProject.ps1](../deploy_skill/scripts/Initialize-CoolifyProject.ps1) | W (solo local) | — | `-Path` `-Stack {node\|python\|compose\|static}` `-AppPort` `-Force` | Genera Dockerfile/compose/.dockerignore compatibles. Idempotente: no sobreescribe sin `-Force`. |
|
||||
| [Test-PreDeployChecklist.ps1](../deploy_skill/scripts/Test-PreDeployChecklist.ps1) | RO | — | `-Path` `-ExpectedPort` `-Strict` | Valida el proyecto contra las reglas duras de [AGENTS-coolify-apps.md](AGENTS-coolify-apps.md). **Gate obligatorio antes de cualquier push.** |
|
||||
| [New-GitHubRepo.ps1](../deploy_skill/scripts/New-GitHubRepo.ps1) | W (remoto) | `GITHUB_TOKEN` | `-Name` `-Description` `-Private` `-Owner` | Crea el repo en GitHub. Idempotente: avisa si ya existe. |
|
||||
| [Invoke-GitHubApi.ps1](../deploy_skill/scripts/Invoke-GitHubApi.ps1) | RO/W | `GITHUB_TOKEN` | `-Method` `-Path` `-BodyJson` `-Raw` | API de GitHub. Default → objetos (§1.1). |
|
||||
| [Test-PostDeploy.ps1](../deploy_skill/scripts/Test-PostDeploy.ps1) | RO | `COOLIFY_TOKEN` (opcional) | `-Fqdn` `-ContainerName` `-SiblingService` `-ApplicationUuid` | Verifica el estado en Coolify: contenedores, logs del proxy, DNS entre hermanos. `-ApplicationUuid` consulta `/applications/*` → vía pública da 404 (Cloudflare), vía origen funciona. |
|
||||
| [Test-ServiceOnline.ps1](../deploy_skill/scripts/Test-ServiceOnline.ps1) | RO | — | `-Fqdn` `-Path` `-ExpectedStatusCode` `-ExpectTitle` `-Screenshot` `-SkipBrowser` `-TimeoutMs` `-Retries` | **La "definición de done".** Dos capas: HTTP 200 vía curl + render real en Chromium (Playwright). Detecta 502 de Traefik, TLS a medias y crashes de JS del cliente. Sin Node/Playwright la capa de navegador se omite con warning, no falla. |
|
||||
| `deploy_skill/scripts/verify-online.mjs` | RO | — | (invocado por el script de arriba) | Capa de navegador de `Test-ServiceOnline.ps1`: navegación real con Playwright, captura `pageerror` y requests fallidos del main frame. No lo invoques directo. Requiere `npm install` en la raíz del repo. |
|
||||
|
||||
`git push` usa **Windows Credential Manager (wincred)**, no el PAT. Verificado
|
||||
para la cuenta `urieljarethbusiness-cpu`.
|
||||
|
||||
**Plantillas:** [deploy_skill/references/templates/](../deploy_skill/references/templates/) —
|
||||
`Dockerfile.node`, `Dockerfile.python`, `docker-compose.app-db.yml`,
|
||||
`env.local.template.ps1`.
|
||||
|
||||
---
|
||||
|
||||
## 5. Gitea — hosting git self-hosted
|
||||
|
||||
Distinto de deploy_skill: esto administra la capa de hosting git, no Coolify.
|
||||
El propio repo Manager vive aquí.
|
||||
|
||||
| Script | R/W | Env | Parámetros | Qué hace |
|
||||
|---|---|---|---|---|
|
||||
| [Test-GiteaConnection.ps1](../gitea_skill/scripts/Test-GiteaConnection.ps1) | RO | `GITEA_URL`, `GITEA_TOKEN` | — | Smoke test: `/version`, `/user`, `/settings/api`, `/repos/search`. Sale 1 si algo falla. |
|
||||
| [Get-GiteaRepo.ps1](../gitea_skill/scripts/Get-GiteaRepo.ps1) | RO | `GITEA_URL`, `GITEA_TOKEN` | `-Owner` `-Name` `-Search` `-List` | Lee, lista o busca repos. `-Owner` default = usuario del token. |
|
||||
| [Invoke-GiteaApi.ps1](../gitea_skill/scripts/Invoke-GiteaApi.ps1) | RO/W | `GITEA_URL`, `GITEA_TOKEN` | `-Method` `-Path` `-BodyJson` `-Raw` | API de Gitea. Usa `curl.exe --data-binary` con UTF-8 sin BOM (el parser de Gitea se rompe con BOM). Default → objetos (§1.1). |
|
||||
| [New-GiteaRepo.ps1](../gitea_skill/scripts/New-GiteaRepo.ps1) | W | `GITEA_URL`, `GITEA_TOKEN` | `-Name` `-Description` `-Private` `-Owner` `-NoAutoInit` | Crea repo (idempotente). `-NoAutoInit` evita el README del lado servidor para poder empujar historia local sin conflicto. |
|
||||
| [Sync-GiteaRemote.ps1](../gitea_skill/scripts/Sync-GiteaRemote.ps1) | W | `GITEA_URL`, `GITEA_TOKEN` | `-AppPath` `-Name` `-Owner` `-RemoteName` `-Branch` `-CreateIfMissing` `-Private` `-Force` | Cablea el remoto y hace push headless. **El token nunca toca el disco**: va en un `http.extraHeader` de un solo uso, no en `.git/config`. `-RemoteName gitea` (default) convive con un `origin` de GitHub. |
|
||||
|
||||
---
|
||||
|
||||
## 6. Reglas de seguridad (no negociables)
|
||||
|
||||
1. **Read-only primero.** El default es diagnosticar: list, status, logs, inspect,
|
||||
health checks.
|
||||
2. **Confirmación explícita antes de cualquier cambio de estado.** Aplica a:
|
||||
`pct`/`qm` start/stop/reboot/destroy; `docker` restart/stop/rm/compose up-down;
|
||||
deploys de Coolify y cualquier `POST`/`PUT`/`PATCH`/`DELETE`; escritura de
|
||||
variables de entorno; y todo cambio de firewall, red, storage, volumen, clave
|
||||
o token. Antes de una acción riesgosa: captura el estado actual y enuncia el
|
||||
camino de rollback.
|
||||
3. **Nunca escribas secretos en el repo.** Ni tokens, ni passwords, ni claves
|
||||
privadas, ni cookies, ni 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 de secretos del SO. Al depurar bases de datos, verifica conectividad
|
||||
sin imprimir credenciales.
|
||||
4. **Prefiere los scripts del repo** antes que cadenas de comandos manuales
|
||||
largas, y no construyas comandos destructivos amplios a partir de strings
|
||||
generados.
|
||||
5. `-Force` existe en varios scripts para saltarse los prompts. **Úsalo solo
|
||||
después de que el usuario haya aprobado el plan concreto.**
|
||||
|
||||
---
|
||||
|
||||
## 7. Dónde está el resto del contexto
|
||||
|
||||
| Qué necesitas | Dónde |
|
||||
|---|---|
|
||||
| Arquitectura + router de intención | [CLAUDE.md](../CLAUDE.md) |
|
||||
| Topología verificada (fuente de verdad del estado) | [proxmox-inventory.md](proxmox-inventory.md) |
|
||||
| Procedimientos concretos | [runbooks/](runbooks/) |
|
||||
| Casos resueltos paso a paso | [casos/](casos/) |
|
||||
| Incidentes archivados | [incidentes/](incidentes/) |
|
||||
| Contrato para *construir* una app deployable | [AGENTS-coolify-apps.md](AGENTS-coolify-apps.md) |
|
||||
| Reglas operativas por dominio | `agent/SKILL.md`, `coolify_skill/SKILL.md`, `deploy_skill/SKILL.md`, `gitea_skill/SKILL.md` |
|
||||
| Guías de uso por dominio (ejemplos ejecutables) | los `TOOLS.md` de cada carpeta `*_skill/` |
|
||||
Reference in New Issue
Block a user