--- 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: [] 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 en remote_dir → build remoto (build_cmd via SSH) → systemctl restart → health check ``` ### docker-compose ``` SSH check → git pull origin 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/ | Directorio en el host remoto donde vive la app | | `--binary` | | 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/) | | `--branch` | main | Branch de git para strategies remotas (git pull origin ) | | `--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//`, extrae `` 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="" gitea_create_webhook "" "" "http://:9090/webhook/push" "" ``` **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 --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 :/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= 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`.