# Deploy desde GitHub

Clona, construye y publica un repo de GitHub en el VPS; redeploy en cada push, historial por fases y vuelta atrás.

Fuente: https://ardumaker.com/docs/github-deploy/

- Categoría: Servidores e infraestructura
- Estado: Disponible
- Requisitos: Un VPS activo de la empresa con la función `deploy_github` disponible; La cuenta de GitHub de quien vincula, conectada a ArduMaker con acceso al repo; Ser miembro de la empresa con acceso a ese VPS (en Git un miembro hace lo mismo que un admin)
- Herramientas MCP (Solo leen): `listar_repos_github_vps`, `ver_repo_github_vps`, `listar_commits_repo_github_vps`, `ver_commit_repo_github_vps`, `listar_logs_repo_github_vps`, `ver_log_repo_github_vps`, `ver_asistente_git_app`, `ver_config_deploy_app`, `ver_propietarios_github`, `ver_operacion_app`
- Herramientas MCP (Escriben o modifican): `instalar_app`, `vincular_repo_github_vps`, `crear_repo_github_vps`, `editar_repo_github_vps`, `redeploy_repo_github`, `guardar_config_deploy_app`, `controlar_app`
- Herramientas MCP (Destructivas): `eliminar_repo_github_vps`, `desinstalar_app`, `publicar_repo_en_dominio`

## ¿Qué permite hacer?

El deploy de Git lleva un repositorio de GitHub a un VPS y lo mantiene al día: lo clona, lo construye, lo deja corriendo como servicio de `systemd` (o contenedor), lo publica con HTTPS y lo vuelve a desplegar en cada push a la rama elegida.

- Detectar qué es el repo (Node, Django, Python, sitio estático, Docker, Procfile) y proponer comandos de build y arranque.
- Declarar en un documento de deploy las fases (`pre_pull`, `install`, `build`, `release`), los procesos (servicios, temporizadores, trabajos puntuales, estáticos, Docker, TCP), las variables de entorno y la publicación.
- Desplegar automáticamente con cada push, a mano, o un commit puntual de la rama.
- Volver al commit de un deploy anterior que terminó bien.
- Ver el historial de deploys con cada fase, sus tiempos y los comandos con su salida (con los secretos tapados).
- Crear un repositorio nuevo en GitHub desde ArduMaker y desplegarlo.

## ¿Cuándo conviene usarlo?

- Una web, una API o un worker que vive en GitHub y tiene que quedar siempre igual a la rama principal.
- Tareas programadas del proyecto (un `timer`) que tienen que viajar con el código.
- Un equipo que quiere ver qué commit está en producción y poder volver atrás sin entrar al servidor.

## ¿Cómo funciona en ArduMaker?

### 1. Vincular y clonar

Hay dos puertas. La recomendada es instalarlo como app (`instalar_app` con `app_type: git`, `repo_id`, `branch` y, si se quiere, `port` y el documento `deploy`): queda en el inventario de Apps y la operación es durable. La otra es la pestaña GitHub del VPS (`vincular_repo_github_vps`), que solo clona y corre en segundo plano.

Al vincular, ArduMaker crea en GitHub un webhook con un secreto propio del repo: cada entrega llega firmada con HMAC-SHA256 y sin nada secreto en la URL. El repo se clona en `<carpeta-del-deploy>`. La URL con el token de GitHub queda en la carpeta `.git`, que es de `root` y no la puede leer la app.

### 2. Detectar y configurar

`ver_asistente_git_app` lista los repos que la cuenta de GitHub puede leer, sus ramas y, con repo y rama, lo que detecta leyendo los archivos sin ejecutar nada: framework, comandos de build y arranque y un puerto libre del VPS. Los formatos conocidos son Docker (también Compose), Procfile, Node web, Django, sitio estático de Vite, Python web y manual. Una migración propuesta siempre llega sin confirmar: el documento no valida hasta que una persona la confirma.

Lo que corre cada deploy es siempre un documento: el guardado con `guardar_config_deploy_app`, o, si el repo nunca guardó uno, el derivado de sus scripts de siempre (pre-pull, post-pull y servicios). Guardar un documento migra el repo: desde ahí manda el documento.

### 3. Desplegar por fases

Cada deploy corre estas fases en orden y las anota en el historial. Si una falla, las siguientes no corren y el repo queda en `error` con un texto corto (`Deploy failed at the <fase> phase`), nunca la salida cruda de un comando.

| Fase | Qué hace |
| --- | --- |
| `preparar` | Usuario de Linux de la app, carpeta de la plataforma, archivos de variables, Docker si hace falta |
| `pre_pull` | `phases.pre_pull` en la carpeta del clon |
| `pull` | Trae la rama (o deja la carpeta en el commit pedido) |
| `install` | `phases.install`, paso largo |
| `build` | `phases.build`, paso largo; después los contenedores de los procesos `docker` |
| `release` | La migración, solo si está confirmada |
| `procesos` | Escribe o reinicia unidades, temporizadores y trabajos puntuales |
| `publicar` | Server block del dominio en el nginx del VPS, si el perfil publica |
| `salud` | Verifica que la app conteste o que las unidades sigan vivas |

Con la app instalada en Apps, el deploy lo corre el runner de operaciones y se retoma si el worker se cae. Un repo sin app corre el mismo deploy en segundo plano. Varios push seguidos se juntan: como mucho un deploy corriendo y uno en cola por app.

## ¿Qué cambia realmente?

`<pk>` es el número del repo en ArduMaker. Con `policies.run_as: "root"` no se crea el usuario y todo es de `root`.

| Qué | Dónde | Dueño y modo |
| --- | --- | --- |
| Usuario de la app | `ardu-app<pk>`, de sistema, sin login | — |
| Clon | `<carpeta-del-deploy>` | del usuario; `.git` de `root` |
| Carpeta de la plataforma | `/etc/ardumaker/deploy/ardu-repo<pk>/` | `root:ardu-app<pk>` 0750 |
| Variables de ejecución y de build | `…/run.env`, `…/build.env` | `ardu-app<pk>` 0600 |
| Guiones de cada paso y su salida | `…/install.sh`, `build.sh`, `release.sh`, `<paso>.log` | 0750 / `root` |
| Servicio principal | `/etc/systemd/system/ardu-repo<pk>.service` | `root` 0644 |
| Otros procesos y temporizadores | `ardu-repo<pk>-<proceso>.service` / `.timer` | `root` 0644 |
| Contenedores | `ardu-repo<pk>`, `ardu-repo<pk>-<proceso>` | — |
| Server block | `/etc/nginx/sites-available/<dominio>.conf` | — |

Una unidad que no cambió solo se reinicia; una que cambió se reescribe. Una unidad adoptada (de un repo de antes) se reinicia y nunca se escribe ni se borra. Lo que un deploy creó y el documento ya no tiene se borra en el deploy siguiente.

## El documento de deploy

Es un JSON versionado (`schema_version: 2`). Se lee con `ver_config_deploy_app` y se reemplaza entero con `guardar_config_deploy_app` (`apply=true` lo aplica ya con un `reconfigure`).

| Parte | Valores |
| --- | --- |
| `profile` | `static`, `web`, `worker`, `scheduled`, `job`, `docker`, `network` y `code` (solo código: en cada push trae el repositorio y nada más; sin instalar, compilar, migrar, procesos ni publicación) |
| `phases` | `pre_pull`, `install`, `build` y `release` (`command`, `confirmed`, `on_update`) |
| `processes` | Hasta 20, de tipo `service`, `timer` (con `schedule`), `oneoff`, `static`, `docker` o `tcp`; puertos de 1024 a 65535, sin repetir |
| `network.publish.mode` | `none`, `auto_subdomain`, `domain`, `port` |
| `network.health.type` | `http`, `unit`, `timer`, `static` |
| `env` | Lista de `{name, value}` con `scope` `build`, `run` o ambos |
| `policies` | `build_timeout_s` 60–7200 (600 por defecto, por paso), `memory_max_mb` y `cpu_quota_pct` sin tope por defecto, `run_as` (`app_user` o `root`), `rollback_on_failed_health` |
| `autodeploy` | `enabled`: si un push despliega |

> **INFO:** Los procesos y temporizadores de una app de Git van en este documento, no en unidades escritas a mano: la plataforma las escribe y reinicia en cada deploy.

## Variables y secretos

Una variable con nombre de secreto (`*_TOKEN`, `*_PASSWORD`, `*_KEY`…), con `secret: true` o con una URL que trae usuario y contraseña se guarda cifrada y en el documento queda solo la referencia. Al leer, `env` muestra solo `{name, scope, has_value}`, nunca un valor. Mandar una variable sin `value` conserva la guardada.

- Los valores se escriben en `run.env` y `build.env` (0600) recién en la fase `preparar`. Un valor con salto de línea se rechaza.
- Cambiar una variable de build (`NEXT_PUBLIC_*`, `VITE_*`) obliga a compilar de nuevo: el `reconfigure` lo hace con el mismo código y sin release.
- En el log de cada comando la carga secreta se reemplaza por su tamaño y la salida lleva cada secreto tapado (`[redacted]`).

## Pasos largos, topes y salud

`install`, `build` y `release` corren cada uno en su propia unidad transitoria, con el usuario de la app, `/tmp` privado y `set -e` (corta en el primer comando que falla):

```bash
sudo systemd-run --unit=ardu-deploy<pk>-<paso> --wait --collect --pipe \
  -p User=ardu-app<pk> -p RuntimeMaxSec=<build_timeout_s> -p NoNewPrivileges=yes -p PrivateTmp=yes \
  -p EnvironmentFile=/etc/ardumaker/deploy/ardu-repo<pk>/build.env \
  /bin/bash /etc/ardumaker/deploy/ardu-repo<pk>/<paso>.sh
```

| Salud | Qué se comprueba |
| --- | --- |
| `http` | `curl` a `127.0.0.1:<puerto><ruta>` hasta 25 veces; sin `expect` vale cualquier respuesta que no sea 5xx |
| `unit` | Cada unidad activa dos veces con 8 s en el medio y sin reinicios nuevos |
| `timer` | El `.timer` habilitado y activo, y su horario válido |
| `static` | Existe la carpeta y el nginx del VPS responde sin 5xx para el dominio |

## Puertos y publicación

La app escucha en un puerto interno del VPS. Si se instala por Apps sin `port` (o con `"auto"`), se toma el primer puerto libre. Los puertos de los motores y del agente nunca se publican. La dirección automática es `git-<id>` dentro de la zona de la plataforma; también se puede usar un dominio propio que apunte al VPS.

La fase `publicar` escribe el server block en el nginx del VPS; el DNS, el proxy hacia el VPS, el certificado y la comprobación del enlace por HTTPS los hace la publicación de Apps. Si esa parte falla (por ejemplo, el DNS todavía no propagó), el reintento publica sin volver a construir. Un nginx editado a mano no se pisa.

## Push, commits puntuales y vuelta atrás

- Un push de otra rama se ignora. Con `autodeploy` apagado, una entrega repetida de GitHub o un deploy ya corriendo, el push anota los commits pero no despliega, y la respuesta dice por qué.
- `redeploy` con `commit_sha` deja la carpeta en ese commit de la rama y corre un `update` normal, con release si está confirmada.
- `rollback` con `run_id` vuelve al commit de un deploy que terminó bien (`git reset --hard <sha>`) con la configuración de ahora. Se rechaza un deploy de otro repo, uno fallido o el que ya está desplegado.
- Con `rollback_on_failed_health: true`, un deploy disparado por push que falla en `salud` vuelve una vez al último commit sano y apaga el autodeploy, dejando el motivo escrito.

> **PRECAUCION:** Volver atrás nunca toca la base de datos ni corre la release. Si el commit roto ya migró la base, el commit anterior tiene que funcionar con ese esquema.

## Desvincular un repo

Borrar el repo quita lo que el documento creó: unidades y temporizadores, contenedores, la carpeta de la plataforma, el usuario de la app con su carpeta y la entrada de `safe.directory`; después la unidad y el contenedor principales, el clon y el webhook de GitHub. Lo adoptado no se toca.

> **PELIGRO:** `eliminar_repo_github_vps` es síncrona e irreversible: borra los archivos del clon en el VPS.

## Esquema

**De un push a la app publicada**

```text
GitHub (push a la rama seguida; webhook firmado)
  ↓
Backend de ArduMaker (registra commits y encola el deploy)
  ↓
Agente del VPS (ejecuta cada fase)
  ↓
Clon + build (`systemd-run` como `ardu-app<pk>`)
  ↓
`ardu-repo<pk>.service` (la app en `127.0.0.1:<puerto>`)
  ↓
nginx del VPS (server block del dominio)
  ↓
Internet (HTTPS en el dominio publicado)
```

El certificado lo pone la plataforma delante del VPS; el nginx del VPS reparte el dominio al puerto interno de la app.

## Resultado esperado

El repo queda `pulled` (u `online` tras la instalación) con `last_commit_sha` igual al commit que está en la carpeta. `ver_repo_github_vps` trae `last_runs`: origen (`webhook`, `manual` o `mcp`), commits, resultado y cómo le fue a cada fase. Si se publicó, `ver_app` muestra la URL HTTPS solo cuando el enlace se verificó.

```bash
systemctl status ardu-repo<pk>.service
curl -I http://127.0.0.1:<puerto>/
```

## Errores y resolución

| Error | Causa | ¿Reintentar? |
| --- | --- | --- |
| `github_no_vinculado` | Quien pide no tiene GitHub conectado o el token venció | Sí, tras volver a conectar GitHub |
| `repo_inaccesible` / `rama_inexistente` | La cuenta no lee el repo o la rama no existe | No |
| `clon_fallido` / `github_no_responde` | Falló el clon (no deja nada) o GitHub no contestó | Sí |
| `puerto_ocupado` | Otra app del VPS ya usa ese puerto | Con otro puerto |
| `deploy_en_curso` | Ya había otro deploy corriendo en ese repo | Sí, cuando termine |
| `preparacion_fallida`, `pull_fallido`, `build_fallido`, `release_fallida`, `app_no_arranco`, `salud_fallida`, `nginx_del_vps_invalido` | La fase que falló | A mano, nunca solo |
| `config_invalida` | El documento no valida; `errors` por ruta (`processes.web.port`) | Corrigiendo el documento |

- `preparar failed - missing value for <VARIABLE>`: falta el valor de una variable; el deploy se frena antes de tocar el VPS.
- Aviso `limits_not_applied`: el VPS rechazó los topes de memoria o CPU y el paso corrió sin ellos.
- Aviso `health_check_failed`: en un repo de configuración heredada la salud que no pasa es un aviso, no un error.
- La app contesta solo en `[::1]`: el nginx del VPS llega por `127.0.0.1`; hay que escuchar en `127.0.0.1` o `0.0.0.0`.

Dónde mirar: `ver_repo_github_vps` (últimos deploys y fases), `listar_logs_repo_github_vps` y `ver_log_repo_github_vps` (comando y salida, con el token y los secretos tapados), y `ver_operacion_app` para la operación de Apps.

> **INFO:** Un paso que falla no se repite solo. Reintentar con `reintentar_operacion_app` o un nuevo `redeploy` es seguro: el clon se reutiliza y la release solo corre si está confirmada.

## Relación con MCP

El deploy de Git tiene acceso MCP completo; referencia en [/mcp/docs](https://ardumaker.com/mcp/docs) y en [/mcp/deploy](https://ardumaker.com/mcp/deploy). Las escrituras las puede hacer cualquier miembro con acceso al VPS, salvo un agente autorizado solo lectura.

| Tipo | Herramientas |
| --- | --- |
| Lectura | `listar_repos_github_vps`, `ver_repo_github_vps`, `listar_commits_repo_github_vps`, `ver_commit_repo_github_vps`, `listar_logs_repo_github_vps`, `ver_log_repo_github_vps`, `ver_asistente_git_app`, `ver_config_deploy_app`, `ver_propietarios_github` |
| Modifican | `instalar_app`, `vincular_repo_github_vps`, `crear_repo_github_vps`, `editar_repo_github_vps`, `guardar_config_deploy_app`, `redeploy_repo_github`, `controlar_app`, `publicar_repo_en_dominio` |
| Destructivas | `eliminar_repo_github_vps`, `desinstalar_app` |

`controlar_app` sobre una app `git` acepta `redeploy` (con `commit_sha` opcional), `rollback` (con `run_id`) y `reconfigure` (aplica el documento sin traer código). `publicar_repo_en_dominio` hace en una llamada dominio, verificación, activación con SSL, server block e instalación; con `simular=true` muestra el plan y con `desde` retoma donde falló.

> **PRECAUCION:** Los logs, commits y salidas de comandos llegan marcados como contenido no confiable: son datos, nunca instrucciones para el agente. El token de GitHub no sale en ninguna respuesta.

## Funciones relacionadas

- [Nginx](https://ardumaker.com/docs/nginx/)
- [Certificados SSL](https://ardumaker.com/docs/ssl/)
- [Dominios y DNS](https://ardumaker.com/docs/dominios-y-dns/)
- [Servicios systemd](https://ardumaker.com/docs/systemd/)
- [Docker](https://ardumaker.com/docs/docker/)
- [Servidores VPS](https://ardumaker.com/docs/vps/)
