---
name: dockerize_app
kind: pipeline
lang: bash
domain: pipelines
version: "1.0.0"
purity: impure
signature: "dockerize_app(app_name: string, [--domain DOMAIN], [--port PORT], [--ssh-host HOST], [--remote-dir DIR], [--basic-auth USER:PASS], [--no-auth], [--no-gzip], [--env KEY=VAL]..., [--volume NAME], [--build-cmd CMD], [--standalone], [--dry-run]) -> json"
description: "Empaqueta una app Go del registry para deploy a VPS organic-machine via Docker + Traefik + Coolify. Genera Dockerfile multi-stage, docker-compose.yml, traefik-dynamic.yml con basicAuth opcional y gzip, sube via rsync al VPS y arranca el stack remoto. Replica el patron de apps/registry_api/."
tags: ["docker", "traefik", "coolify", "deploy", "pipeline", "launcher"]
uses_functions:
  - generate_dockerfile_go_infra
  - bcrypt_htpasswd_go_infra
  - generate_compose_traefik_go_infra
  - generate_traefik_dynamic_go_infra
  - rsync_deploy_bash_infra
  - docker_compose_remote_deploy_bash_infra
  - health_check_http_go_infra
  - gitea_create_repo_bash_infra
  - gitea_push_directory_bash_infra
uses_types:
  - ComposeTraefikConfig_go_infra
  - TraefikDynamicConfig_go_infra
returns: []
returns_optional: false
error_type: "error_go_core"
imports: []
params:
  - name: app_name
    desc: "Nombre o ID parcial de la app en registry.db (ej: kanban, deploy_server). Se busca con LIKE '<app_name>%' OR name='<app_name>'."
  - name: domain
    desc: "Dominio publico completo para el router Traefik (ej: kanban.organic-machine.com). Obligatorio."
  - name: port
    desc: "Puerto interno del contenedor Docker (default: 8080). Debe coincidir con el puerto en que la app escucha."
  - name: ssh-host
    desc: "Alias o IP del host SSH destino (default: organic-machine.com). Debe estar en ~/.ssh/config o ser accesible con key auth."
  - name: remote-dir
    desc: "Ruta absoluta en el VPS donde se desplegara la app (default: /home/ubuntu/coolify-apps/<app_name>). En modo rsync apunta al subdir de la app dentro del build root."
  - name: basic-auth
    desc: "Credenciales para basicAuth de Traefik en formato USER:PASS. Obligatorio si auth esta ON (defecto). Se hashea con bcrypt via htpasswd o python3+bcrypt."
  - name: no-auth
    desc: "Flag para deshabilitar basicAuth. Por defecto auth esta habilitado; se requiere --basic-auth USER:PASS si no se pasa --no-auth."
  - name: no-gzip
    desc: "Flag para deshabilitar el middleware gzip de Traefik. Por defecto gzip esta habilitado."
  - name: env
    desc: "Variable de entorno KEY=VAL a incluir en el .env y en la seccion environment del docker-compose.yml. Repetible para multiples vars."
  - name: volume
    desc: "Nombre de un Docker volume que se monta en /data dentro del contenedor. Se declara en la seccion volumes del compose."
  - name: build-cmd
    desc: "Comando de build personalizado (documentado para uso futuro; Phase 1 usa el Dockerfile multi-stage generado)."
  - name: standalone
    desc: "Modo standalone: crea repo Gitea dataforge/<app> y usa git clone/pull en el VPS en vez de rsync. Requiere GITEA_URL y credenciales Gitea configuradas."
  - name: dry-run
    desc: "Imprime los artefactos generados (Dockerfile, docker-compose.yml, traefik-dynamic.yml, .env) a stderr y retorna JSON con status=dry-run sin ejecutar ningun comando remoto ni escribir ficheros en la app."
output: "JSON a stdout: {status, app, domain, remote_dir, container_id, duration_seconds, auth_enabled, gzip_enabled, http_code, url}. status='ok' si el health check responde HTTP 200/401, 'failed' si hay timeout, 'dry-run' en modo dry-run."
tested: false
tests: []
test_file_path: ""
file_path: "bash/functions/pipelines/dockerize_app.sh"
---

## Ejemplo

```bash
# Deploy completo con basicAuth
cd $HOME/fn_registry
bash bash/functions/pipelines/dockerize_app.sh kanban \
  --domain kanban.organic-machine.com \
  --port 8421 \
  --basic-auth lucas:supersecret \
  --env KANBAN_DB=/data/kanban.db \
  --volume kanban_data

# Salida esperada:
# {"status":"ok","app":"kanban","domain":"kanban.organic-machine.com","remote_dir":"...","container_id":"abc123","duration_seconds":45,"auth_enabled":true,"gzip_enabled":true,"http_code":"401","url":"https://kanban.organic-machine.com"}

# Deploy sin auth (app publica)
bash bash/functions/pipelines/dockerize_app.sh registry_api \
  --domain registry.organic-machine.com \
  --port 8080 \
  --no-auth \
  --env REGISTRY_API_TOKEN=mytoken

# Dry-run: ver YAMLs sin tocar nada
bash bash/functions/pipelines/dockerize_app.sh kanban \
  --dry-run \
  --domain kanban.organic-machine.com \
  --port 8421 \
  --basic-auth lucas:test123

# Standalone: repo Gitea + git clone en VPS
bash bash/functions/pipelines/dockerize_app.sh deploy_server \
  --domain deploy.organic-machine.com \
  --port 9090 \
  --basic-auth lucas:secret \
  --standalone
```

## Pasos internos

| Paso | Descripcion |
|------|-------------|
| 1 | Valida la app en registry.db (SQL sobre tabla apps) |
| 2 | Valida conectividad SSH (BatchMode, ConnectTimeout=5) |
| 3 | Genera hash bcrypt via htpasswd o python3+bcrypt |
| 4 | Genera Dockerfile multi-stage Go (heredoc, patron generate_dockerfile_go_infra) |
| 5 | Genera docker-compose.yml con Traefik labels y red coolify |
| 6 | Genera traefik-dynamic.yml con routers HTTP/HTTPS, basicAuth, gzip, certResolver letsencrypt |
| 7 | Genera/actualiza .env con merge no destructivo |
| 8 | Rsync del repo completo al VPS (o git clone en standalone) |
| 9 | Crea directorio remoto de deploy |
| 10 | Sube traefik-dynamic.yml a /data/coolify/proxy/dynamic/<name>.yml via SSH+sudo tee |
| 11 | Sube docker-compose.yml, Dockerfile y .env al remote_dir via scp |
| 12 | Crea red Docker coolify si no existe; `docker compose up -d --build` remoto |
| 13 | Health check: 10 intentos, 3s intervalo, acepta HTTP 200 y 401 |

## Decision de implementacion

Los YAMLs se generan con heredocs bash inline, replicando la logica de
`generate_dockerfile_go_infra`, `generate_compose_traefik_go_infra` y
`generate_traefik_dynamic_go_infra`. Esta decision evita crear un nuevo
binario `cmd/dockerize_helpers/` y mantiene el pipeline completamente
self-contained, siguiendo el patron de `setup_registry_api_bash_infra`.
Las funciones Go quedan referenciadas en `uses_functions` como fuente de
verdad documental del patron que replica.

## Requisitos en el host local

- `ssh` y `rsync` instalados
- `htpasswd` (apache2-utils) o `python3` + modulo `bcrypt` para generar hash
- Acceso SSH sin password al host destino (key en ~/.ssh/config)
- `sqlite3` CLI para leer registry.db

## Requisitos en el VPS

- Docker + docker compose (v2)
- Coolify con Traefik corriendo y `/data/coolify/proxy/dynamic/` accesible via sudo
- Red Docker `coolify` (se crea automaticamente si no existe)
- Usuario SSH con sudo sin password para: `mkdir`, `tee` en `/data/coolify/proxy/dynamic/`

## Codigos de salida

| Codigo | Significado |
|--------|-------------|
| 0 | Exito: stack activo y health check OK |
| 1 | Error: app no encontrada, SSH inavalcable, fallo de build, health check timeout |

## Notas

- Phase 1 soporta exclusivamente apps `lang: go`. Soporte para Python y Bash en fases futuras.
- El Dockerfile se genera solo si no existe en el directorio de la app. Si ya existe (con tweaks manuales), se preserva y se avisa por stderr.
- El build context del Dockerfile es `../../` relativo a `apps/<app>/` para que el multi-stage copie el `go.mod` del root del registry y compile correctamente.
- El nombre del router Traefik reemplaza `_` por `-` (ej: `registry_api` → `registry-api`).
- En modo `--standalone`, se requieren las variables `GITEA_URL`, `GITEA_TOKEN` (o `GITEA_USER`/`GITEA_PASS`) configuradas para `gitea_create_repo` y `gitea_push_directory`.
