# Capability: ssh

Operar hosts remotos via SSH. Cubre: CRUD de `~/.ssh/config` (`ssh_config_add_entry`, `ssh_config_find`, `ssh_config_remove`), conexiones reutilizables (`SSHConn`), ejecucion remota (`ssh_run`, `ssh_check`), transferencia (`scp_put`, `scp_get`), port-forwarding, y orquestacion de deploys (setup_vps_app, deploy_app_remote).

## Funciones

| ID | Firma | Que hace |
|---|---|---|
| `audit_ssh_config_bash_cybersecurity` | `audit_ssh_config(config_path: string) -> void` | Audita la configuración de sshd_config evaluando parámetros de seguridad críticos (PermitRootLogin, PasswordAuthentication, Port, MaxAuthTries, X11Forwarding, AllowUsers). También revisa intentos de login fallidos en los logs y lista las claves autorizadas del usuario actual. |
| `check_service_health_via_ssh_bash_infra` | `check_service_health_via_ssh <ssh_host> <local_url> [--token-from-env <remote_env> <VAR>] [--token <literal>] [--expect-status 200]` | Comprueba la salud de un service HTTP que solo escucha en loopback de un host remoto: entra por SSH, lee opcionalmente un bearer token de un `.env` remoto, y hace `curl` al endpoint local con `Authorization: Bearer`. Emite JSON (`{status, host, url, http_code, healthy}`), exit 0 si sano. El token nunca se imprime; prefiere `--token-from-env` sobre `--token` (este deja el secreto en argv local). |
| `docker_compose_remote_deploy_bash_infra` | `docker_compose_remote_deploy(host: string, remote_dir: string, branch: string, compose_files: string) -> json` | Despliega un stack Docker Compose en un host remoto via SSH. Verifica conectividad, hace git pull del branch indicado, actualiza imagenes con docker-compose pull y levanta/recrea los servicios modificados con docker-compose up -d. Soporta compose files adicionales. Retorna JSON con status, containers corriendo y duracion. |
| `rsync_deploy_bash_infra` | `rsync_deploy(local_dir: string, ssh_alias: string, remote_dir: string) -> json` | Sincroniza un directorio local a un host remoto via rsync+SSH. Excluye archivos de desarrollo y bases de datos locales. Crea el directorio remoto si no existe. |
| `setup_registry_api_bash_infra` | `setup_registry_api(ssh_host: string, api_token: string, basic_auth_user: string, basic_auth_pass: string) -> json` | Deploy completo de registry_api en VPS con Docker + Traefik (Coolify proxy). Sincroniza el repo via rsync, genera el hash bcrypt para basicAuth, sube el traefik-dynamic.yml, crea el .env con el token, hace docker compose build+up y verifica el health check. |
| `setup_vps_app_go_infra` | `func SetupVPSApp(conn SSHConn, cfg DeployConfig) error` | Orquesta el setup inicial de una app en un VPS remoto: verifica SSH, crea dirs y usuario, sube binario, instala systemd unit y hace health check. |
| `ssh_check_go_infra` | `func SSHCheck(conn SSHConn) error` | Verifica conectividad SSH ejecutando un comando noop en el host remoto. Timeout de 5 segundos. |
| `ssh_config_add_entry_go_infra` | `func SSHConfigAddEntry(entries []SSHConfigEntry, entry SSHConfigEntry) ([]SSHConfigEntry, error)` | Añade un nuevo SSHConfigEntry a la lista. Error si el alias ya existe. |
| `ssh_config_find_go_infra` | `func SSHConfigFind(entries []SSHConfigEntry, alias string) (SSHConfigEntry, bool)` | Busca un entry por alias en la lista de SSHConfigEntry. |
| `ssh_config_parse_go_infra` | `func SSHConfigParse(content string) []SSHConfigEntry` | Parsea el contenido de un archivo ~/.ssh/config y retorna una lista de SSHConfigEntry. |
| `ssh_config_read_go_infra` | `func SSHConfigRead() ([]SSHConfigEntry, error)` | Lee y parsea ~/.ssh/config. Retorna lista vacia si el archivo no existe. |
| `ssh_config_remove_entry_go_infra` | `func SSHConfigRemoveEntry(entries []SSHConfigEntry, alias string) ([]SSHConfigEntry, error)` | Elimina un entry por alias de la lista. Error si el alias no existe. |
| `ssh_config_render_go_infra` | `func SSHConfigRender(entries []SSHConfigEntry) string` | Convierte una lista de SSHConfigEntry al formato texto de ~/.ssh/config. |
| `ssh_config_write_go_infra` | `func SSHConfigWrite(entries []SSHConfigEntry) error` | Escribe entries a ~/.ssh/config con backup automatico del archivo previo. |
| `ssh_download_go_infra` | `func SSHDownload(conn SSHConn, remotePath, localPath string) error` | Descarga un archivo del host remoto al filesystem local via scp. |
| `ssh_exec_go_infra` | `func SSHExec(conn SSHConn, command string) (string, string, int, error)` | Ejecuta un comando en el host remoto via SSH. Retorna stdout, stderr y exit code separados. |
| `ssh_tunnel_close_go_infra` | `func SSHTunnelClose(pid int) error` | Cierra un tunel SSH enviando SIGTERM al proceso por PID. |
| `ssh_tunnel_open_go_infra` | `func SSHTunnelOpen(conn SSHConn, localPort int, remoteHost string, remotePort int) (int, error)` | Abre un tunel SSH (local port forwarding) en background. Retorna el PID del proceso para cerrarlo despues. |
| `ssh_upload_go_infra` | `func SSHUpload(conn SSHConn, localPath, remotePath string) error` | Sube un archivo local al host remoto via scp. |
| `validate_git_ssh_uri_py_core` | `def validate_git_ssh_uri(url: str) -> None` | Valida el formato de una URI SSH de git (git@host:path). Lanza ValueError si la URI es invalida. |


## Ejemplo canonico

### Anadir host nuevo + comprobar + ejecutar comando

```bash
./fn run ssh_config_add_entry \
  --alias organic-machine \
  --hostname 1.2.3.4 \
  --user deploy \
  --identity-file ~/.ssh/organic_ed25519

./fn run ssh_check --host organic-machine
./fn run ssh_run --host organic-machine --cmd "systemctl status nginx"
```

### Transferencia + deploy

```bash
./fn run scp_put --host organic-machine --src ./build/myapp --dst /opt/apps/myapp/myapp
./fn run systemd_restart --host organic-machine --unit myapp.service
./fn run wait_for_http https://myapp.example.com/health 30
```

### Health-check de un service que solo escucha en loopback del host remoto

```bash
./fn run check_service_health_via_ssh om "http://127.0.0.1:8487/agents" \
  --token-from-env /home/ubuntu/CodeProyects/agents_and_robots/.env AGENTS_API_KEY \
  --expect-status 200
# {"status":"ok","host":"om","url":"http://127.0.0.1:8487/agents","http_code":200,"healthy":true}
```

## Fronteras

- **NO genera ni rota llaves SSH automaticamente**. Asume llave ya generada con `ssh-keygen`.
- **NO valida fingerprints de host**. Confianza inicial es responsabilidad del operador (acepta el prompt manualmente o usa `StrictHostKeyChecking=no` con conciencia del riesgo).
- **NO orquesta multi-hop / jump hosts** mas alla del ProxyJump del config. Para bastion compleja, edita `~/.ssh/config` a mano.
- **NO maneja credenciales con password**. Solo auth por llave. Tunneling password-based via sshpass queda fuera (ver `cybersecurity/audit_ssh_config` para auditar).
