# Arquitectura del Sistema ## Visión General `carlospalanca.es` es un sistema en tres capas que conecta un canal de YouTube tech con una web personal, un sistema de agentes de IA y un pipeline de CI/CD automatizado. ``` ┌─────────────────────────────────────────────────────────────┐ │ Carlos (humano) │ │ │ │ Escribe en Discord ──► Revisa PRs en GitHub │ └────────────┬────────────────────────┬───────────────────────┘ │ │ ▼ ▼ ┌────────────────────┐ ┌───────────────────────┐ │ Discord Server │ │ GitHub Repository │ │ │ │ carlospalanca.es │ │ #el-trono-de- │ │ │ │ hierro (Tyrion) │ │ main ──► CI/CD ──► │ │ #la-ciudadela │ │ PRs de agentes │ │ #el-pajarillo │ │ Branch protection │ │ #el-muro │ └──────────┬────────────┘ │ ... (9 canales) │ │ └────────┬───────────┘ │ merge │ ▼ │ bot events ┌──────────────────────┐ ▼ │ GitHub Actions │ ┌────────────────────┐ │ │ │ VPS (Hetzner) │ │ ci.yml: build check │ │ │ │ deploy.yml: rsync │ │ ┌──────────────┐ │ └──────────┬───────────┘ │ │ 9 Discord │ │ │ │ │ Bot Agents │◄─┤ ◄────────────┘ │ │ (Docker) │ │ │ rsync dist/ │ └──────┬───────┘ │ ▼ │ │ │ ┌──────────────────────┐ │ │ HTTP API │ │ /var/www/ │ │ ▼ │ │ carlospalanca.es/ │ │ ┌──────────────┐ │ │ (archivos estáticos) │ │ │ OpenWebUI │ │ └──────────┬────────────┘ │ │ :3000 │ │ │ │ └──────────────┘ │ │ nginx sirve │ │ │ ▼ │ ▼ │ ┌──────────────────────┐ │ ┌──────────────┐ │ │ https:// │ │ │ Nginx │ │ │ carlospalanca.es │ │ │ (proxy/TLS) │ │ │ (web pública) │ │ └──────────────┘ │ └──────────────────────┘ └────────────────────┘ ``` --- ## Componentes ### 1. Web (Astro) **Tecnología:** Astro 5, MDX, sitemap, RSS **Tipo:** Sitio estático generado (SSG) **Contenido gestionado por:** - Carlos directamente - Agentes vía Pull Requests (nunca commits directos) **Colecciones de contenido:** | Colección | Ruta | Creador | |-----------|------|---------| | `blog` | `src/content/blog/` | Samwell / Carlos | | `guiones` | `src/content/guiones/` | Samwell | --- ### 2. CI/CD (GitHub Actions) **`ci.yml`** — Se ejecuta en cada Pull Request: 1. Checkout del código 2. `npm ci` — instala dependencias 3. `astro check` — type checking 4. `npm run build` — construye el sitio 5. Comenta en el PR si pasa o falla **`deploy.yml`** — Se ejecuta al mergear a `main`: 1. Build del sitio (`dist/`) 2. rsync de `dist/` al VPS vía SSH 3. `nginx -t && systemctl reload nginx` **GitHub Secrets necesarios:** | Secret | Valor | |--------|-------| | `VPS_SSH_PRIVATE_KEY` | Clave privada SSH del usuario `deploy` | | `VPS_HOST` | IP o hostname del VPS | | `VPS_USER` | `deploy` | --- ### 3. VPS **OS:** Ubuntu 24.04 LTS **Proveedor recomendado:** Hetzner CX22 (~5€/mes, 2 vCPU, 4 GB RAM) **Servicios corriendo:** | Servicio | Puerto | Acceso | |----------|--------|--------| | nginx | 80, 443 | Público | | OpenWebUI | 3000 | Solo localhost (proxificado por nginx) | | 9 Discord bots | — | Outbound only | **Estructura de directorios en el VPS:** ``` /var/www/carlospalanca.es/ ← Web estática (desplegada por GitHub Actions) /opt/openwebui/ ← Docker Compose de OpenWebUI /opt/agents/ ← Docker Compose de los 9 agentes /etc/nginx/sites-available/ ← Config de nginx /etc/letsencrypt/ ← Certificados SSL (Let's Encrypt) ``` --- ### 4. OpenWebUI **Imagen:** `ghcr.io/open-webui/open-webui:main` **Función:** Gateway de LLM. Los agentes hacen llamadas HTTP a su API compatible con OpenAI. **Endpoint que usan los agentes:** ``` POST https://ai.carlospalanca.es/api/chat/completions Authorization: Bearer ``` **Ventaja del gateway centralizado:** - Cambiar de GPT-4o a Claude o Llama solo requiere cambiar `OPENWEBUI_MODEL` en `.env` - Dashboard web para monitorizar el uso de tokens - No hay que redeployar los agentes para cambiar de modelo --- ### 5. Sistema de Agentes **Patrón:** Cada agente es un Discord bot en Python (discord.py) que: 1. Escucha mensajes en su canal de Discord asignado 2. Llama a OpenWebUI con su system prompt + el mensaje del usuario 3. Ejecuta la acción correspondiente (crear PR, responder texto, etc.) **Comunicación entre agentes:** ``` Carlos ──► #el-trono-de-hierro │ ▼ Tyrion analiza y enruta │ ├──► #la-ciudadela (Samwell) ├──► #el-pajarillo (Varys) ├──► #el-muro (Bran) └──► ... (resto de agentes) ``` Los agentes NO se llaman entre sí por API. Toda la comunicación es vía Discord. --- ### 6. GitHub Integration **Token:** Fine-Grained PAT con scope mínimo: - `Contents: Write` — crear ramas y archivos - `Pull requests: Write` — abrir y comentar PRs - Scoped **solo** al repo `carlospalanca.es` **Flujo de creación de contenido:** ``` Agente genera contenido │ ▼ agents/shared/github_client.py 1. Crea rama: tipo/agente-YYYYMMDD-slug 2. Commit del archivo en esa rama 3. Abre PR con etiquetas agent-created, needs-review │ ▼ GitHub Actions ejecuta ci.yml → Build check → Comenta en el PR │ ▼ Carlos revisa y mergea │ ▼ GitHub Actions ejecuta deploy.yml → rsync a VPS → Web actualizada ``` --- ## Flujo Completo de Ejemplo **Solicitud:** "Tyrion, necesito un guión para un vídeo sobre Kubernetes Ingress" ``` 1. Carlos escribe en #el-trono-de-hierro 2. Tyrion (bot) recibe el mensaje → Llama a OpenWebUI con su prompt de orquestador → Responde: "Entendido. He enviado la misión a Samwell en la Ciudadela." → Publica en #la-ciudadela: "[Delegado por Tyrion] Crear guión sobre Kubernetes Ingress..." 3. Samwell (bot) recibe la tarea en #la-ciudadela → Llama a OpenWebUI con su prompt de escritor + la tarea → Genera el guión completo en formato MDX con frontmatter → Llama a github_client.create_content_pr() - Crea rama: guiones/samwell-20240321-kubernetes-ingress - Hace commit de src/content/guiones/kubernetes-ingress.md - Abre PR: "[Samwell] Guión: Kubernetes Ingress" → Responde en #la-ciudadela: "PR creado: https://github.com/..." 4. GitHub Actions (ci.yml) se activa → npm run build → OK → Comenta en el PR: "✅ Build exitoso" 5. Carlos revisa el PR, edita si necesita, hace merge 6. GitHub Actions (deploy.yml) se activa → Build → rsync → nginx reload → Web actualizada con el nuevo guión ``` --- ## Seguridad | Medida | Implementación | |--------|---------------| | GitHub token scope mínimo | Fine-grained PAT, solo este repo, solo contents+PRs | | OpenWebUI solo en localhost | Bind a `127.0.0.1:3000`, no expuesto directamente | | Nginx como única entrada | TLS termination en nginx, HTTPS forzado | | Branch protection en main | Requiere PR + CI verde antes de mergear | | Agentes sin acceso a main | Solo pueden crear ramas y abrir PRs | | Secretos en `.env` | `.env` en `.gitignore`, nunca en el repo |