Files
Proxmox-Coolify-Manager/docs/TOOL-INDEX.md
T

409 lines
26 KiB
Markdown

# 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/` |