--- name: comfyui_ensure_server kind: function lang: py domain: infra version: "1.0.0" purity: impure signature: "def comfyui_ensure_server(*, port: int = 8188, lowvram: bool | None = None, health_timeout: int = 60, comfyui_dir: str = '~/ComfyUI', unit_name: str = 'comfyui', runner=None) -> dict" description: "Garantiza que ComfyUI corre como servicio systemd-user resiliente y sano. Genera/instala el unit systemd-user comfyui.service (ExecStart con el venv de ComfyUI + main.py --port, anadiendo --lowvram si lowvram=True o autodetectando GPUs <= 8 GB; Restart=always — NO on-failure; WantedBy=default.target), hace daemon-reload + enable + start, y comprueba la salud via GET /system_stats (2xx) con timeout. Idempotente: si el servicio ya esta gestionado por systemd, activo y respondiendo, no toca nada. Migracion limpia: si ComfyUI ya corre a mano (puerto ocupado por un proceso main.py que systemd NO gestiona), lo para con SIGTERM (nunca SIGKILL) y lo levanta via systemd. Solo stdlib (subprocess, urllib, os, signal, time, re). No lanza excepciones: devuelve un dict de estado." tags: [comfyui, systemd, service, server, resilient, ml, healthcheck, infra] uses_functions: [] uses_types: [] returns: [] returns_optional: false error_type: "error_go_core" imports: ["os", "re", "signal", "subprocess", "time", "urllib.request"] params: - name: port desc: "puerto HTTP del backend ComfyUI; tambien el que escribe en el unit (--port) y el que sondea el health check (default 8188)" - name: lowvram desc: "True/False fuerza/omite el flag --lowvram en ExecStart; None autodetecta por VRAM (GPUs con <= 8200 MiB -> True). Recomendado True en GPUs de 8 GB para modelos grandes (Flux, video)" - name: health_timeout desc: "segundos maximos sondeando GET /system_stats tras arrancar el servicio antes de declararlo no-sano (default 60)" - name: comfyui_dir desc: "raiz de la instalacion de ComfyUI; debe contener .venv/bin/python y main.py (default ~/ComfyUI, se expande y normaliza a absoluto)" - name: unit_name desc: "nombre del unit systemd-user (sin .service); el archivo va a ~/.config/systemd/user/.service (default 'comfyui')" - name: runner desc: "callable(cmd: list) -> CompletedProcess inyectable para tests; default ejecuta subprocess.run capturando salida" output: "dict con ok (bool: servicio activo y sano), active (ActiveState del unit: active|inactive|failed), port, health (bool: /system_stats respondio 2xx), error (str|None), lowvram (bool aplicado), unit_path (ruta del .service escrito), migrated (bool: paro un ComfyUI a mano para migrar a systemd), reloaded (bool: hubo daemon-reload), idempotent (bool: ya estaba activo+sano y no se toco nada)" tested: true tests: - "_detect_lowvram aplica el umbral de 8 GB (8192/8200 -> True, 8201/24564/None -> False)" - "_render_unit incluye Restart=always, WantedBy=default.target y nunca on-failure; anade --lowvram solo cuando corresponde" - "error claro si falta el venv python en comfyui_dir" - "idempotente: si is-active=active y /system_stats sano, no llama a start" - "arranque fresco: escribe el unit, daemon-reload + enable + start y espera salud" - "lowvram=False omite el flag --lowvram en el unit escrito" test_file_path: "python/functions/infra/comfyui_ensure_server_test.py" file_path: "python/functions/infra/comfyui_ensure_server.py" --- ## Ejemplo ```python import sys, os sys.path.insert(0, os.path.join("python", "functions")) from infra.comfyui_ensure_server import comfyui_ensure_server # Deja ComfyUI corriendo como servicio systemd-user, sano y con --lowvram # autodetectado en GPUs de 8 GB. Idempotente: relanzarla no rompe nada. res = comfyui_ensure_server(port=8188, lowvram=True) print(res) # {'ok': True, 'active': 'active', 'port': 8188, 'health': True, 'error': None, # 'lowvram': True, 'unit_path': '/home/enmanuel/.config/systemd/user/comfyui.service', # 'migrated': True, 'reloaded': True, 'idempotent': False} ``` CLI directa (despacha por el venv del registry): ```bash python/.venv/bin/python3 python/functions/infra/comfyui_ensure_server.py --port=8188 --lowvram ``` El usuario lo gestiona despues con systemd-user normal: ```bash systemctl --user status comfyui # estado + ultimos logs systemctl --user restart comfyui # reiniciar (la salud vuelve verde sola) systemctl --user stop comfyui # parar systemctl --user disable --now comfyui # revertir: para y deshabilita el arranque automatico journalctl --user -u comfyui -n 50 # diagnosticar fallos de arranque ``` ## Cuando usarla Usala cuando necesites que ComfyUI este garantizado arriba y sano antes de encolar workflows (txt2img, video, 3D), o para convertir el ComfyUI que hoy se relanza a mano en un servicio que arranca solo al boot y se reinicia si cae (gap del roadmap 0064). Es el primer paso del grupo `comfyui`: dejar el backend disponible; despues vienen `comfyui_build_*_workflow` + `comfyui_submit_workflow`. ## Gotchas - **systemd-user requiere linger** para sobrevivir al cierre de sesion / arrancar al boot: `loginctl enable-linger $USER`. Sin linger el unit solo vive mientras hay sesion activa. Si `enable` falla por esto, el dict lo dice en `error`. - **Migracion limpia con SIGTERM, nunca SIGKILL**: si ComfyUI ya corre a mano ocupando el puerto, la funcion lo para con SIGTERM y espera a que libere el bind (hasta ~25 s) antes de arrancar el servicio. Si el puerto lo ocupa un proceso que NO es ComfyUI (cmdline sin `main.py`), NO lo toca y devuelve `error` — no arranca para no duplicar el bind. - **Cambiar los flags del unit (p.ej. lowvram) NO reinicia un servicio ya sano**: la funcion reescribe el `.service` y hace daemon-reload, pero si el servicio ya esta active+healthy no lo reinicia para no interrumpir. Para aplicar flags nuevos: `systemctl --user restart comfyui`. - **Carga la GPU al arrancar**: levantar ComfyUI reserva VRAM. En una GPU de 8 GB compartida, evita lanzarlo mientras otra tarea pesada usa la GPU. - **Restart=always (no on-failure)**: un `systemctl --user stop` limpio es exit success; con `on-failure` el servicio reviviria solo tras crash. Para pararlo de verdad usa `stop` (no `restart`) o `disable --now`. - El health check es `GET http://127.0.0.1:/system_stats` y espera 2xx; solo loopback.