Files
carlospalanca.es/docs/architecture.md

236 lines
8.9 KiB
Markdown

# 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 <OPENWEBUI_API_KEY>
```
**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 |