Files
deploy_server/app.md
T
egutierrez d231f04a26 refactor: usar sqlite_apply_migrations_go_infra del registry
Sustituye applyMigrations + splitSQLStatements + isIdempotentError locales
por infra.ApplyMigrations del registry. ~55 LOC eliminadas.

- store.go: import fn-registry/functions/infra, llama infra.ApplyMigrations
- go.mod: declara dep fn-registry con replace local
- app.md: declara sqlite_apply_migrations_go_infra en uses_functions

Sin cambio funcional (mismo parser naive de `;`, mismas reglas idempotentes).
Bug fixes futuros se aplicaran automaticamente al consumir registry.
2026-05-09 12:40:10 +02:00

278 lines
11 KiB
Markdown

---
name: deploy_server
lang: go
domain: infra
description: "Servidor de deploy continuo para apps del registry. Recibe webhooks de Gitea, gestiona targets de deploy en operations.db y orquesta deploys a VPS remotos via SSH. Soporta tres estrategias: systemd, systemd-remote y docker-compose."
tags: [service, deploy, ci, cd, webhook, gitea, ssh, vps, docker-compose, systemd]
uses_functions:
- sqlite_apply_migrations_go_infra
uses_types: []
framework: "net/http"
entry_point: "main.go"
dir_path: "apps/deploy_server"
---
## Estrategias de deploy
Tres estrategias disponibles segun el tipo de app:
| Estrategia | Flujo | Para que |
|---|---|---|
| `systemd` (default) | Build local + rsync + systemctl restart | Apps Go compiladas localmente y subidas al VPS |
| `systemd-remote` | SSH: git pull + build remoto + systemctl restart | Apps Go cuyo source vive en el VPS (build in-situ) |
| `docker-compose` | SSH: git pull + docker compose pull + up -d | Stacks Docker Compose en el VPS |
### systemd (build local + rsync)
```
SSH check → build local (build_cmd en source_dir) → rsync al VPS
→ chmod +x binario → systemctl restart → health check
```
### systemd-remote (build remoto)
```
SSH check → git pull origin <branch> en remote_dir
→ build remoto (build_cmd via SSH) → systemctl restart → health check
```
### docker-compose
```
SSH check → git pull origin <branch> en remote_dir
→ docker compose [-f extra...] pull → docker compose up -d → health check
```
## Uso
```bash
# Servidor (escucha webhooks y expone API)
./deploy_server serve --port 9090
# CLI: gestionar targets de deploy
./deploy_server target add --app my_app --host produccion --port 8080 --health /api/health \
--build "CGO_ENABLED=0 GOOS=linux go build -o my_app ." --strategy systemd
./deploy_server target list
./deploy_server target remove my_app
# Target con strategy systemd-remote (build en el VPS)
./deploy_server target add --app agents_and_robots --host localhost \
--remote-dir /home/ubuntu/CodeProyects/agents_and_robots \
--binary launcher --build "bash build.sh" \
--strategy systemd-remote --branch master
# Target con strategy docker-compose
./deploy_server target add --app element_matrix_chat --host localhost \
--remote-dir /home/ubuntu/CodeProyects/element_matrix_chat \
--strategy docker-compose --branch master \
--compose-files "docker-compose.livekit.yml"
# CLI: deploy manual
./deploy_server deploy my_app # deploy a todos los hosts del target
./deploy_server deploy my_app --host produccion # deploy a un host específico
# CLI: setup inicial de una app en un VPS
./deploy_server setup my_app --host produccion
# CLI: estado de servicios remotos
./deploy_server status my_app # systemd: systemctl status / docker-compose: docker compose ps
./deploy_server status --all
```
### Flags de target add
| Flag | Default | Descripcion |
|---|---|---|
| `--app` | (requerido) | Nombre de la app (debe coincidir con el nombre del repo en Gitea para webhooks) |
| `--host` | (requerido) | Alias SSH de ~/.ssh/config (o `localhost` si deploy_server corre en el mismo host) |
| `--remote-dir` | /opt/apps/<app> | Directorio en el host remoto donde vive la app |
| `--binary` | <app> | Nombre del binario (solo para strategies systemd/systemd-remote) |
| `--build` | "" | Comando de build (local para systemd, remoto para systemd-remote) |
| `--user` | "" | Usuario systemd del servicio |
| `--port` | 0 | Puerto del servicio (para health checks) |
| `--health` | "" | Path del health check (ej: /api/health) |
| `--env` | {} | Variables de entorno como JSON |
| `--strategy` | systemd | Estrategia: `systemd`, `systemd-remote`, `docker-compose` |
| `--source-dir` | "" | Directorio local del source relativo al registry root (override de apps/<app>) |
| `--branch` | main | Branch de git para strategies remotas (git pull origin <branch>) |
| `--compose-files` | "" | Archivos compose adicionales separados por coma (ej: "docker-compose.livekit.yml") |
## API
```
POST /webhook/push — recibe push de Gitea, detecta app afectada, despliega
GET /api/targets — lista todos los targets de deploy
GET /api/targets/:app — detalle de un target
POST /api/deploy/:app — trigger manual de deploy
GET /api/status/:app — estado del servicio remoto (systemctl status o docker compose ps)
GET /api/health — health check del propio deploy_server
GET /api/logs — ultimos 20 deploy logs (todas las apps)
GET /api/logs/:app — ultimos 20 deploy logs de una app
```
## Webhook de Gitea
El endpoint `POST /webhook/push` recibe payloads de Gitea en formato JSON. El matching funciona en dos pasos:
1. **Por paths**: analiza los archivos modificados en los commits. Si algun archivo empieza con `apps/<nombre>/`, extrae `<nombre>` como app afectada.
2. **Fallback por nombre de repo**: si no hay match por paths, compara `repository.name` del payload contra los targets registrados.
El fallback es el mecanismo principal para repos externos (como agents_and_robots o element_matrix_chat) que no viven dentro de un monorepo con estructura `apps/`.
### Seguridad del webhook
- Si `DEPLOY_WEBHOOK_SECRET` esta seteada, se valida el header `X-Gitea-Signature` (HMAC-SHA256).
- Si no esta seteada, se acepta cualquier request (solo para desarrollo).
- El secret debe coincidir exactamente entre la env var y la configuracion del webhook en Gitea.
### Configuracion en Gitea
Para crear un webhook en Gitea que apunte al deploy_server:
```bash
source bash/functions/infra/gitea_create_webhook.sh
export GITEA_URL="https://tu-gitea.ejemplo.com"
export GITEA_TOKEN="<token-api>"
gitea_create_webhook "<owner>" "<repo>" "http://<ip>:9090/webhook/push" "<webhook_secret>"
```
**Gitea en Docker**: si Gitea corre en un container Docker, `127.0.0.1` apunta al container, no al host. Usar la gateway de la red Docker del container de Gitea:
```bash
# Encontrar la gateway
docker inspect <gitea_container> --format '{{range .NetworkSettings.Networks}}{{.Gateway}}{{end}}'
# Resultado: 10.0.17.1 (ejemplo)
# URL del webhook: http://10.0.17.1:9090/webhook/push
```
**Gitea ALLOWED_HOST_LIST**: Gitea restringe por defecto las URLs de webhooks. Configurar en `app.ini`:
```ini
[webhook]
ALLOWED_HOST_LIST = *
```
O listar las IPs/subredes permitidas. Reiniciar Gitea tras el cambio.
**Firewall (UFW)**: si el host tiene UFW activo, permitir tráfico desde redes Docker al puerto del deploy_server:
```bash
sudo ufw allow from 10.0.0.0/8 to any port 9090 comment 'deploy_server from Docker'
```
## Despliegue del propio deploy_server
deploy_server usa `github.com/mattn/go-sqlite3` (CGO). Para compilar:
```bash
# Local (Linux nativo o WSL)
CGO_ENABLED=1 go build -o deploy_server .
# Cross-compile para Linux desde otra plataforma
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o deploy_server_linux .
```
### Instalacion en un VPS
```bash
# 1. Subir binario
scp deploy_server_linux <host>:/home/ubuntu/CodeProyects/deploy_server/deploy_server
# 2. Crear systemd unit
sudo tee /etc/systemd/system/deploy_server.service << 'EOF'
[Unit]
Description=deploy_server CI/CD
After=network.target
[Service]
Type=simple
ExecStart=/home/ubuntu/CodeProyects/deploy_server/deploy_server serve --port 9090
WorkingDirectory=/home/ubuntu/CodeProyects/deploy_server
User=ubuntu
Group=ubuntu
Environment=DEPLOY_WEBHOOK_SECRET=<secret>
Environment=PATH=/usr/local/go/bin:/usr/bin:/bin
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
# 3. Activar
sudo systemctl daemon-reload && sudo systemctl enable --now deploy_server
# 4. Verificar
curl -s http://127.0.0.1:9090/api/health
```
### SSH a localhost
Cuando deploy_server y las apps estan en el mismo VPS, el deploy usa SSH a `localhost`. Requisitos:
```bash
# Generar key si no existe
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519 -N ""
cat ~/.ssh/id_ed25519.pub >> ~/.ssh/authorized_keys
# Aceptar host key
ssh-keyscan localhost >> ~/.ssh/known_hosts
# Verificar
ssh -o BatchMode=yes localhost true
```
## Despliegue actual: organic-machine.com
deploy_server corre en el VPS organic-machine.com como servicio systemd (`deploy_server.service`), puerto 9090.
### Targets registrados
| App | Estrategia | Host | Remote dir | Branch | Build |
|---|---|---|---|---|---|
| agents_and_robots | systemd-remote | localhost | /home/ubuntu/CodeProyects/agents_and_robots | master | `bash build.sh` |
| element_matrix_chat | docker-compose | localhost | /home/ubuntu/CodeProyects/element_matrix_chat | master | — |
| registry_api | docker-compose | localhost | /opt/fn-registry-build/apps/registry_api | master | docker compose build+up |
### Webhooks activos
Ambos repos en Gitea (`egutierrez/agents_and_robots`, `egutierrez/element_matrix_chat`) tienen webhooks push apuntando a `http://10.0.17.1:9090/webhook/push` (gateway de la red Docker de Gitea).
### Tiempos de deploy medidos
| App | Trigger | Duracion |
|---|---|---|
| agents_and_robots | webhook | ~8.5s (git pull + tests + compile 4 binarios + restart) |
| element_matrix_chat | webhook | ~2.7s (git pull + docker compose pull + up -d) |
### Servicios en el VPS
| Servicio | Tipo | Estado |
|---|---|---|
| deploy_server | systemd (deploy_server.service) | enabled, active, :9090 |
| agents_and_robots | systemd (agents_and_robots.service) | enabled, active (launcher) |
| registry_api | Docker (registry-api container) | running, :8420, HTTPS via Traefik en registry.organic-machine.com |
### Infraestructura relevante
- **Gitea**: corre en Docker (container `gitea-dgg044oo04woo4ggcsws4gk0`), red `10.0.17.0/24`, gateway `10.0.17.1`
- **Coolify**: proxy principal en puertos 80/443/8080, gestiona servicios Docker
- **UFW**: policy DROP, regla `allow from 10.0.0.0/8 to port 9090` para que Docker alcance deploy_server
- **Webhook secret**: guardado en `pass agentes/deploy-webhook-secret`
- **Gitea token**: guardado en `pass agentes/egutierrez-token`
- **Registry API basicAuth**: guardado en `pass registry/basicauth-user` y `pass registry/basicauth-pass`
- **Registry API token**: guardado en `pass registry/api-token`
## Registro en operations.db
Dos tablas:
- **deploy_targets** (PK: app + host): configuracion de cada target con strategy, branch, compose_files, etc.
- **deploy_logs**: un registro por cada deploy con app, host, status (success/failure), trigger (manual/api/webhook), error, duration_ms, started_at.
## Notas
- Los hosts SSH se resuelven via `~/.ssh/config` — el campo `host` en el target es el alias SSH.
- Para strategy `docker-compose`: usa `docker compose` (v2, sin guion). Verificar con `docker compose version`.
- Para strategy `systemd-remote`: el `build_cmd` se ejecuta via SSH en el remote_dir, no localmente.
- El webhook matching por `repository.name` permite que repos externos (no del monorepo) disparen deploys si el nombre del repo coincide con el nombre del target.
- deploy_server no se auto-despliega. Actualizaciones: cross-compile local + scp + `sudo systemctl restart deploy_server`.