Servicio pkg_updater — Descarga, verificación y actualización de paquetes del Agente
Descripción
Módulo: core/pkg_updater.py
Introducido: v1.45.14 (Agent 2.14.0, 2026-07-07)
Contexto: Lanzador Estable (F1)
Servicio de descarga, verificación de integridad y instalación atómica de paquetes de código del Agente (app-X.Y.Z.zip). Viaja dentro de cada paquete y se actualiza a sí mismo.
Cero carrera con el antivirus: nunca crea un binario nuevo. Solo descarga zip, verifica, y swapea un puntero atómico en el filesystem.
Interfaz pública
Funciones principales
async def download_and_verify_package(version: str, manifest_payload: dict) -> bool:
"""Descarga app-<version>.zip + meta.json del SaaS.
Parámetros:
- version: ej. "1.45.14"
- manifest_payload: dict con package_* fields del heartbeat
(package_sha256, package_sig, package_url)
Retorna:
- True si descarga, verifica y prepara OK
- False si falla cualquier paso (SaaS inaccesible, firma inválida, etc.)
Comportamiento:
- Descarga a ~/.crearack/packages/pending/<version>/
- Verifica SHA-256
- Verifica firma Ed25519 contra clave pública embebida en bootstrap
- Ejecuta --selftest-package en destino
- Marca como ready (atomic marker file)
"""
def swap_package_pointer(new_version: str, old_version: str | None) -> bool:
"""Swapea el puntero atómico de versión ejecutable.
Parámetros:
- new_version: versión a activar (debe estar ready)
- old_version: guardar como fallback (prev)
Comportamiento:
- Lee puntero current → guarda en prev
- Escribe new_version en current
- Operación en ocio (jamás durante ejecución)
- Retorna False si falla (filesystem lleno, permisos, etc.)
"""
def rollback_package_pointer() -> bool:
"""Revierte al paquete anterior (prev).
Usado por pkg-watchdog si el paquete current falla. Retorna True si éxito.
"""
def verify_package_with_bootstrap_functions(
zip_path: str,
sha256_expected: str,
sig_ed25519: str,
pubkey_hex: str
) -> bool:
"""Verifica integridad usando LAS MISMAS funciones que el bootstrap.
Parámetros:
- zip_path: ruta a app-X.Y.Z.zip
- sha256_expected: de app-X.Y.Z.meta.json
- sig_ed25519: de app-X.Y.Z.meta.json
- pubkey_hex: PACKAGE_PUBKEYS_HEX embebida en el bootstrap
Comportamiento:
- Importa modulo agent_bootstrap (registrado en sys.path por el propio bootstrap)
- Llama agent_bootstrap.verify_ed25519_signature(...)
- **Cero duplicación de código** → validación idéntica en descarga y en carga
Retorna: True si firma OK
"""
Componentes internos
| Componente | Responsabilidad |
|---|---|
| Downloader | GET desde package_url, manejo de timeouts, reintentos, resume |
| Verifier | SHA-256, Ed25519 (delega a agent_bootstrap), integridad ZIP |
| Selftest | Ejecuta --selftest-package en el zip desempaquetado (sanity check) |
| Atomic Pointer | Lee/escribe ~/.crearack/agent/version.current y version.prev race-free |
| Watchdog | Monitor async: detecta si current falla en <N ejecuciones, auto-rollback |
| Pruner | Limpia versiones viejas (mantiene current + prev + embebido) |
Ciclo de vida
Descarga y verificación
1. pkg_updater recibe manifest con package_*
2. Valida que package_sha256, package_sig, package_min_bootstrap_api son coherentes
3. Descarga app-X.Y.Z.zip a ~/.crearack/packages/pending/<version>/
4. Verifica SHA-256
5. Verifica firma Ed25519 (llamando agent_bootstrap.verify_ed25519_signature)
6. Extrae + selftest: verifica que imports funcionan, no hay archivos corrompidos
7. Marca como ready (toca file ~/.crearack/packages/<version>/.ready)
Swap atómico
8. En próximo ocio (idle, sin ejecutar app_main):
- Lee ~/.crearack/agent/version.current → guarda en version.prev
- Escribe <new_version> en version.current
- Próximo restart del bootstrap: carga new_version
Rollback por watchdog
9. Si app_main falla en <3 ejecuciones:
- pkg-watchdog detecta
- Lee version.prev
- Escribe version.prev en version.current
- Próximo restart: carga prev (fallback garantizado)
Cleanup
10. Prune: elimina paquetes >2 versiones atrás (mantiene current + prev + embebido)
Gestión de errores
Sin Internet / SaaS inaccesible
if not download_and_verify_package(...):
log.warning("Descarga/verificación falló, mantener current")
# Próximo intento en 6 horas
return
El Agente sigue funcionando con paquete anterior.
Firma inválida / alteración del ZIP
if not verify_ed25519_signature(...):
log.critical("FIRMA ED25519 FALLO — INTENTO DE INYECCIÓN DETECTADO")
# No swapea puntero
# Próximo intento en 6 horas (por si fue glitch)
return False
Jamás ejecuta código alterado. La clave privada está fuera del SaaS → un SaaS comprometido no puede falsificar firma.
Selftest falla
if not selftest_package(...):
log.warning("Selftest del paquete falló (corrupt ZIP?)")
# Descarta versión fallida
# Próximo intento con versión anterior del manifest
return False
Swap falló (filesystem lleno, permisos)
if not swap_package_pointer(...):
log.error("No se pudo swapear puntero (FS lleno?)")
# Paquete ready pero no activado
# Próximo restart puede intentar de nuevo
return False
Watchdog NO activa porque no hay error de ejecución (el swap falló antes de reiniciar).
Integración con bootstrap
Importación en el paquete
# En app_main.py, o en un handler de actualización:
from core.pkg_updater import download_and_verify_package
from core.agent_bootstrap import verify_ed25519_signature # módulo registrado por bootstrap
# Usa las mismas funciones → validación idéntica
Punto de entrada asíncrono
async def update_agent_from_heartbeat(manifest: dict):
"""Invocado por el handler de heartbeat si hay package_*."""
if manifest.get("package_version"):
await download_and_verify_package(
manifest["package_version"],
{
"package_sha256": manifest["package_sha256"],
"package_sig": manifest["package_sig"],
"package_url": manifest["package_url"],
}
)
Véase también
- [[feature—terminal—lanzador-estable-f1]]
- [[entity—terminal—service—app-main]]
- [[entity—core—endpoint—download-agent-package]]
- [[runbook—terminal—firma-y-distribucion-paquetes]]