---
name: check_service_health_via_ssh
kind: function
lang: bash
domain: infra
version: "1.0.0"
purity: impure
signature: "check_service_health_via_ssh(ssh_host: string, local_url: string, [--token-from-env <remote_env_path> <ENV_VAR>], [--token <literal>], [--expect-status <code>], [--connect-timeout <s>], [--curl-timeout <s>]) -> json"
description: "Comprueba la salud de un service HTTP que solo escucha en loopback (127.0.0.1) de un host remoto, entrando por SSH y haciendo curl con bearer token opcional. El token se resuelve dentro del host remoto (leyendo una variable de un .env remoto via grep, o pasado literal) y NUNCA se imprime ni se hardcodea. Emite JSON con http_code y healthy. Reemplaza el patron inline 'ssh host -> grep token .env -> curl -H Authorization: Bearer' repetido en monitorizacion."
tags: [ssh, systemd, health, curl, remote, service, bearer, loopback, monitoring, infra]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: []
params:
  - name: ssh_host
    desc: "alias SSH del host remoto definido en ~/.ssh/config (ej: om, organic-machine). Resuelve user/puerto/identityfile del config."
  - name: local_url
    desc: "URL del endpoint que el service expone en loopback del host remoto (ej: http://127.0.0.1:8487/agent). No es accesible desde fuera del host."
  - name: --token-from-env
    desc: "dos valores: <remote_env_path> <ENV_VAR>. Lee el bearer del .env remoto con grep '^ENV_VAR=' (ej: /home/ubuntu/app/.env AGENTS_API_KEY). El token se resuelve dentro del host, no viaja en argv local."
  - name: --token
    desc: "bearer literal (alternativa a --token-from-env). Util para tokens ya en variables de entorno locales; preferir --token-from-env para secretos en disco remoto."
  - name: --expect-status
    desc: "codigo HTTP exacto que marca healthy (ej: 200). Si se omite, cualquier 2xx cuenta como healthy."
  - name: --connect-timeout
    desc: "timeout de conexion SSH en segundos (default 5)."
  - name: --curl-timeout
    desc: "timeout maximo del curl remoto en segundos (default 10)."
output: "JSON a stdout: {\"status\":\"ok|error\",\"host\":\"...\",\"url\":\"...\",\"http_code\":NNN,\"healthy\":true|false}. status=error si el SSH fallo sin obtener codigo. healthy=true si http_code coincide con expect-status (o es 2xx por defecto). Exit 0 si healthy, 1 si no, 2 en error de uso."
tested: true
tests: ["service healthy con token desde env remoto", "service no healthy con http_code 503", "salida JSON nunca filtra el token", "sin token 2xx por defecto es healthy", "falta argumento obligatorio devuelve error de uso", "falta argumento sale con codigo distinto de 0"]
test_file_path: "bash/functions/infra/check_service_health_via_ssh_test.sh"
file_path: "bash/functions/infra/check_service_health_via_ssh.sh"
---

## Ejemplo

```bash
source bash/functions/infra/check_service_health_via_ssh.sh

# 1) Service en loopback del host 'om' con bearer leido de un .env remoto.
#    Reemplaza el patron inline de monitorizacion del agents_and_robots.
result=$(check_service_health_via_ssh om "http://127.0.0.1:8487/agent" \
    --token-from-env /home/ubuntu/CodeProyects/agents_and_robots/.env AGENTS_API_KEY \
    --expect-status 200)
echo "$result"
# {"status":"ok","host":"om","url":"http://127.0.0.1:8487/agent","http_code":200,"healthy":true}

# 2) Sin token (endpoint publico del host pero solo accesible por loopback).
check_service_health_via_ssh organic-machine "http://127.0.0.1:8080/healthz"
# {"status":"ok","host":"organic-machine","url":"http://127.0.0.1:8080/healthz","http_code":200,"healthy":true}

# 3) Uso como gate en un script de monitorizacion (exit code).
if check_service_health_via_ssh om "http://127.0.0.1:8487/agent" \
    --token-from-env /home/ubuntu/CodeProyects/agents_and_robots/.env AGENTS_API_KEY >/dev/null; then
    echo "service vivo"
else
    echo "service caido — alertar"
fi
```

## Cuando usarla

Usala cuando necesites comprobar si un service HTTP de un host remoto esta sano y ese
service **solo escucha en loopback** (127.0.0.1) del host, por lo que no puedes
curl-earlo directamente desde tu maquina. Tipico de APIs internas detras de un reverse
proxy, daemons con bearer auth, o services systemd que exponen un `/health` privado.
Antes de reiniciar un service, en un cron de monitorizacion, o como `e2e_check` de un
deploy.

## Gotchas

- Requiere **SSH por key auth** al host (usa `-o BatchMode=yes`): si el host pide
  password, falla en vez de colgarse. El alias debe estar en `~/.ssh/config`.
- El service objetivo **debe escuchar en loopback del host remoto** — la URL se
  resuelve *dentro* del host. `http://127.0.0.1:PORT` apunta al host remoto, no a tu PC.
- **No requiere sudo**: solo lee un `.env` (grep) y hace curl como el usuario SSH.
  El usuario SSH debe tener permiso de lectura sobre el `.env` remoto.
- El **token nunca se imprime ni se hardcodea**: con `--token-from-env` se resuelve
  dentro del host y solo se usa en el header `Authorization`. Con `--token <literal>`
  el secreto queda en el argv del comando ssh local — preferir `--token-from-env`
  para secretos persistidos en disco.
- `grep` del `.env` toma la **primera** linea que matchea `^<ENV_VAR>=` y recorta
  comillas/espacios. Si la var aparece varias veces o usa interpolacion, revisa el match.
- `curl -sf` no sigue redirects: un 3xx cuenta como no-2xx (healthy=false salvo
  `--expect-status` explicito).
- Requiere `curl` instalado en el **host remoto** (no en el local).
- El JSON de salida se emite siempre (incluso en fallo); el caller decide por el
  `exit code` (0 healthy, 1 no healthy, 2 error de uso) o por el campo `healthy`.

## Notas

- Testeable sin red: el runner SSH es inyectable via `CHECK_HEALTH_SSH_BIN` (un stub
  que emite el `http_code` deseado), por eso los tests no abren conexiones reales.
- El snippet remoto normaliza la salida de curl a un unico `http_code` aunque
  `curl -sf` devuelva error (emite `<curl_rc>:<http_code>` y la funcion extrae el codigo).
