Actualiza toolkit operativo y documentación
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
# OpenSEO — stack self-host en Coolify
|
||||
|
||||
> Resuelto el **2026-08-27** contra el host real.
|
||||
> Target: **LXC 102** (`coolify`), servicio compose, FQDN
|
||||
> **`https://openseo.urieljareth.org`**.
|
||||
> Imagen: **`ghcr.io/every-app/open-seo:sha-c469a48`** (mismo SHA que el deploy
|
||||
> fallido anterior — esta vez la build de Vite sí corre, en el entrypoint).
|
||||
|
||||
---
|
||||
|
||||
## Por qué existe este stack
|
||||
|
||||
El deploy previo (`uuid kj0kccsb4d46tm0d6qe6docy`, `name=open-seo:main-...`,
|
||||
deployment `fkojyfkqzp69hcba6miy8oer`) terminó con Coolify marcando verde y
|
||||
**Caddy respondiendo 404 a todo**. Diagnóstico:
|
||||
|
||||
- `build_pack=railpack` clonó `every-app/open-seo@main`, construyó imagen local
|
||||
con el mismo SHA `c469a48ae90ab58413b198fe3d1ac1aa90a9b070` y la cacheó.
|
||||
- En el redeploy: `No build configuration changed & image found (...) Build
|
||||
step skipped` → la imagen cacheada **no tenía `/app/dist`** (los artefactos
|
||||
del build de Vite) y Coolify la reusó.
|
||||
- Caddy (`/Caddyfile` con `root * /app/dist` + SPA fallback a `/index.html`)
|
||||
no encontró nada y devolvió 404 a `/`, `/robots.txt`, `/health`.
|
||||
- El README upstream lo dice textual: *"We recommend self-hosting with
|
||||
Cloudflare as opposed to Railway, Coolify or Dokploy. We plan to make it
|
||||
simpler to host on those platforms in the next few months."*
|
||||
|
||||
**Solución:** dejar de seguir upstream y consumir la **imagen prebuilt** que el
|
||||
propio equipo publica en GHCR. Esa imagen tiene la cadena correcta:
|
||||
`docker-entrypoint.sh` corre preflight → migrations → `pnpm run build` (que sí
|
||||
genera `/app/dist`) → `vite preview` en el puerto `3001`. Y usa un fingerprint
|
||||
para no reconstruir cuando los env vars relevantes no cambiaron.
|
||||
|
||||
Stack: **un solo servicio** (OpenSEO es self-contained: SQLite vía workerd en
|
||||
`/app/.wrangler`, volumen `openseo-data`). Sin DB externa.
|
||||
|
||||
---
|
||||
|
||||
## Archivos
|
||||
|
||||
| Archivo | Para qué |
|
||||
|---|---|
|
||||
| `docker-compose.coolify.yml` | Compose que consume Coolify vía `POST /services` |
|
||||
| `.env.example` | Template de env vars (sin secretos) |
|
||||
| `.env.coolify` | **No committed.** Lo crea el operador con `cp .env.example .env.coolify` y rellena |
|
||||
|
||||
---
|
||||
|
||||
## Variables de entorno
|
||||
|
||||
Hardcoded en el compose (porque son decisión de arquitectura, no secretos):
|
||||
|
||||
| Var | Valor | Por qué |
|
||||
|---|---|---|
|
||||
| `PORT` | `3001` | Es donde escucha `vite preview` (per `Dockerfile.selfhost`) |
|
||||
| `AUTH_MODE` | `local_noauth` | Single admin, sin pantalla de login. Aquí no tenemos `TEAM_DOMAIN`/`POLICY_AUD` de Cloudflare Access |
|
||||
| `ALLOWED_HOST` | `openseo.urieljareth.org` | Sin esto, Vite bloquea toda petición externa con "Blocked request" |
|
||||
| `CLOUDFLARE_INCLUDE_PROCESS_ENV` | `true` | Lo exige el runtime workerd para que process.env llegue a los bindings |
|
||||
|
||||
Suministradas vía `.env.coolify` (env vars del servicio en Coolify):
|
||||
|
||||
| Var | Default | Efecto |
|
||||
|---|---|---|
|
||||
| `DATAFORSEO_API_KEY` | vacío | **WARN** del preflight (no FAIL). Vacío = la app arranca, los workflows SEO devuelven "no data" |
|
||||
| `OPENSEO_TELEMETRY_DISABLED` | `1` | Apaga el heartbeat anónimo |
|
||||
| `DO_NOT_TRACK` | `1` | Alias del anterior |
|
||||
| `OPENROUTER_API_KEY` | vacío | Habilita a SAM (el agente SEO integrado) si se setea |
|
||||
| `OPENROUTER_MODEL` | vacío | Modelo a usar con SAM |
|
||||
|
||||
---
|
||||
|
||||
## Deploy
|
||||
|
||||
### 1. (Manual, una sola vez) Ingress del túnel de Cloudflare
|
||||
|
||||
El token de Cloudflare **no está** en `.env.local.ps1`, así que esto se hace en
|
||||
el dashboard:
|
||||
|
||||
1. Cloudflare Zero Trust → Networks → Tunnels → tunnel `urieljareth` →
|
||||
Configure → Public hostname.
|
||||
2. Add a public hostname:
|
||||
- Subdomain: `openseo`
|
||||
- Domain: `urieljareth.org`
|
||||
- Service: **HTTP** (no HTTPS, lo gestiona Coolify/Traefik)
|
||||
- URL: `coolify.urieljareth.org` (o la IP interna del proxy de Coolify —
|
||||
misma que usan los demás subdominios)
|
||||
|
||||
### 2. Crear el servicio en Coolify
|
||||
|
||||
```powershell
|
||||
. .\.env.local.ps1
|
||||
|
||||
# Crear el archivo de env real (gitignored)
|
||||
Copy-Item .\stacks\open-seo\.env.example .\stacks\open-seo\.env.coolify
|
||||
# Editar .\stacks\open-seo\.env.coolify si quieres setear DATAFORSEO_API_KEY
|
||||
|
||||
.\deploy_skill\scripts\New-CoolifyService.ps1 `
|
||||
-AppPath .\stacks\open-seo `
|
||||
-AppName open-seo `
|
||||
-Fqdn https://openseo.urieljareth.org `
|
||||
-PrimaryService app `
|
||||
-ProjectName "AI AGENCY" -EnvironmentName production `
|
||||
-EnvFile .\stacks\open-seo\.env.coolify `
|
||||
-InstantDeploy
|
||||
```
|
||||
|
||||
### 3. Esperar al primer arranque
|
||||
|
||||
El primer `up` tarda **1-2 min**: preflight + migrations + vite build + arranque
|
||||
de `vite preview`. Traefik no enruta hasta que el contenedor esté `healthy`.
|
||||
|
||||
```powershell
|
||||
.\coolify_skill\scripts\Test-CoolifyServiceReady.ps1 -Uuid <uuid> -WaitSeconds 600
|
||||
```
|
||||
|
||||
### 4. Verificar
|
||||
|
||||
```powershell
|
||||
# 1. Endpoint público responde (TLS emitido, Traefik enrutando)
|
||||
curl.exe -k -sSI https://openseo.urieljareth.org/
|
||||
|
||||
# 2. Status del contenedor
|
||||
.\coolify_skill\scripts\Get-CoolifyDockerStatus.ps1 -Filter openseo
|
||||
|
||||
# 3. Logs del entrypoint (debería verse "Preflight passed")
|
||||
.\scripts\Invoke-ProxmoxSsh.ps1 -Command "pct exec 102 -- docker logs <cont> --tail 60"
|
||||
|
||||
# 4. Preflight reporta lo que falta
|
||||
.\scripts\Invoke-ProxmoxSsh.ps1 -Command "pct exec 102 -- docker exec <cont> wget -qO- http://127.0.0.1:3001/api/health"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Rollback / limpieza
|
||||
|
||||
| Acción | Comando |
|
||||
|---|---|
|
||||
| Parar la app rota original | `docker stop kj0kccsb4d46tm0d6qe6docy-055629993145` (vía `pct exec 102 --`) |
|
||||
| Borrar la app rota de Coolify | UI → Service `kj0kccsb4d46tm0d6qe6docy` → Delete |
|
||||
| Re-deployar | UI → Service `open-seo` → Redeploy |
|
||||
|
||||
---
|
||||
|
||||
## Cosas que caducan
|
||||
|
||||
- **El tag `:sha-c469a48` se queda viejo.** Cuando el upstream publique un SHA
|
||||
más reciente, actualizar el `image:` en `docker-compose.coolify.yml` y
|
||||
redeployar. `v0.1.6` también existe (publicado 8 días antes).
|
||||
- **El entrypoint vuelve a buildear `dist`** cada vez que algún env var del
|
||||
prefijo `VITE_*` / `AUTH_MODE` / `POSTHOG_*` / `TURNSTILE_SITE_KEY` /
|
||||
`BYPASS_EMAIL_VERIFICATION` cambie. Es intencional — el fingerprint está
|
||||
ahí para no rehacer cuando nada relevante cambió.
|
||||
- **Coolify normaliza el compose al guardarlo y borra los comentarios.** La
|
||||
versión con explicaciones es la del repo, no la que se ve en la UI.
|
||||
Reference in New Issue
Block a user