8.9 KiB
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:
- Checkout del código
npm ci— instala dependenciasastro check— type checkingnpm run build— construye el sitio- Comenta en el PR si pasa o falla
deploy.yml — Se ejecuta al mergear a main:
- Build del sitio (
dist/) - rsync de
dist/al VPS vía SSH 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 <OPENWEBUI_API_KEY>
Ventaja del gateway centralizado:
- Cambiar de GPT-4o a Claude o Llama solo requiere cambiar
OPENWEBUI_MODELen.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:
- Escucha mensajes en su canal de Discord asignado
- Llama a OpenWebUI con su system prompt + el mensaje del usuario
- 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 archivosPull 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 |