---
name: refresh_local_hub
kind: pipeline
lang: py
domain: pipelines
purity: impure
version: "1.0.0"
signature: "def refresh_local_hub(manifest_path: str | None = None, reload: bool = True) -> dict"
description: "Orquesta el refresco del sistema local_hub: descubre los servicios locales, regenera el fragmento de Caddyfile y la config de Glance, recarga Caddy (admin API) y reinicia la user-unit glance. Compone discover_local_services + render_caddyfile + render_glance_config. Lo corre el dag_engine a diario y tambien el usuario a mano."
tags: [local-hub, pipelines, pipeline, caddy, glance, infra, dashboard]
uses_functions: [discover_local_services_py_infra, render_caddyfile_py_infra, render_glance_config_py_infra]
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: [pyyaml]
tested: true
tests:
  - "compone las tres funciones del registry y escribe ambas configs"
  - "la config completa de Glance generada es YAML parseable"
  - "con reload=False no se llama a subprocess.run"
  - "con reload=True se invoca caddy reload y systemctl --user restart glance"
  - "el dict de retorno tiene todas las claves del contrato"
test_file_path: "python/functions/pipelines/refresh_local_hub_test.py"
file_path: "python/functions/pipelines/refresh_local_hub.py"
params:
  - name: manifest_path
    desc: "Ruta al manifiesto YAML del local_hub. Si es None se usa <RAIZ>/apps/local_hub/local_services.yaml (RAIZ derivada de FN_REGISTRY_ROOT o del path del modulo)."
  - name: reload
    desc: "Si True recarga Caddy (admin API localhost:2019, sin sudo) y reinicia la user-unit glance (sin sudo). Si False solo regenera las configs y no toca servicios."
output: "dict {total, up, down, caddy_path, glance_path, reloaded, caddy_reload_rc, glance_restart_rc, services:[{name,subdomain,port,up}, ...]}"
---

## Ejemplo

```bash
./fn run refresh_local_hub
```

Sin recargar servicios (solo regenera las configs):

```bash
$HOME/fn_registry/python/.venv/bin/python3 \
  python/functions/pipelines/refresh_local_hub.py --no-reload
```

Desde Python:

```python
from pipelines.refresh_local_hub import refresh_local_hub

r = refresh_local_hub(reload=True)
print(r)
# {"total": 8, "up": 6, "down": 2, "caddy_path": "/etc/caddy/conf.d/local_hub.caddy",
#  "glance_path": ".../apps/local_hub/glance/glance.yml", "reloaded": True,
#  "caddy_reload_rc": 0, "glance_restart_rc": 0, "services": [...]}
```

## Cuando usarla

Cuando cambien los servicios locales expuestos como subdominios `*.localhost` (alta/baja de un servicio en el manifiesto, o un service nuevo del registry con bloque `service:`) y quieras que Caddy y el dashboard Glance reflejen el estado actual. Es el paso `function:` que el dag_engine corre a diario para mantener el `local_hub` sincronizado, y el comando que lanzas a mano tras editar `apps/local_hub/local_services.yaml`.

## Gotchas

- **Impura: escribe en `/etc/caddy/conf.d/local_hub.caddy` via ACL**, no via sudo. El usuario debe tener permiso de escritura ahi (ACL ya configurada en este PC). Sin la ACL, el `open(..., "w")` lanza `PermissionError`.
- **Recarga Caddy por su admin API** (`caddy reload` habla con `localhost:2019`), no reinicia el servicio: requiere que Caddy este corriendo con la admin API activa. Si Caddy no esta levantado, `caddy_reload_rc` queda en un valor != 0 (o -1 si el binario falla) pero el pipeline NO lanza.
- **Reinicia la user-unit `glance`** (`systemctl --user restart glance`), no la system-unit: requiere que la user-unit `glance` este instalada y el bus de usuario disponible. Si falta, `glance_restart_rc` refleja el fallo sin abortar.
- **Valida el YAML de Glance antes de escribir**: si `render_glance_config` produjera YAML invalido, el pipeline lanza `RuntimeError` con mensaje claro y no escribe el archivo (fail-fast).
- **Raiz dinamica**: la raiz del registry se deriva de `FN_REGISTRY_ROOT` o del path del modulo; nunca se hardcodea ningun `/home/<user>`.
- **`reload=False` no toca ningun servicio**: util para previsualizar las configs generadas sin recargar Caddy ni reiniciar Glance (lo que hace el test).

## Capability growth log

(sin entradas — v1.0.0 inicial)
