236 lines
8.9 KiB
Markdown
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 |
|