Saltar al contenido
Development

Publicar el pipeline de lanzamiento de Greenhouse en la Mac Mini de una cámara de seguridad

Por Victor Da Luz
tauricimacosinfrastructuredev-loggreenhouse

Greenhouse necesitaba un pipeline de lanzamiento: hacer push de un tag y obtener un .dmg firmado. La documentación de Tauri hace que esto parezca un trabajo de cinco minutos: agregar un workflow de GitHub Actions, apuntarlo a macos-latest, listo. Excepto que acá no uso runners alojados por GitHub. Todo en este homelab corre sobre hardware que me pertenece, incluido el CI.

Esa única restricción convirtió un trabajo de cinco minutos en un proyecto de infraestructura real, y de paso, en una historia de debugging genuina.

Encontrar una Mac que no estuviera haciendo nada

Compilar una app de macOS necesita una Mac de verdad. Ya tenía una del lado del servidor: una Mac Mini corriendo Frigate, el NVR de detección de objetos de mi cámara de seguridad. Antes de tocarla revisé su carga real, en vez de simplemente asumir que tenía margen, y no lo tenía. 75-80% de CPU sostenido en los 8 núcleos, de forma continua, por el trabajo de detección en tiempo real. Agregar un compilador de Rust encima de eso es una pelea real por recursos, no una hipotética.

No tenía una segunda Mac libre por ahí, así que hice una concesión deliberada en vez de fingir que la contención no existía: registré el build runner en la misma máquina, pero le puse un tope a su prioridad de scheduling (nice más un tipo de proceso en segundo plano) para que le ceda CPU al sistema de cámaras cuando esté bajo carga. Los lanzamientos son eventos raros, disparados por tags; que las compilaciones sean un poco más lentas de vez en cuando es un precio aceptable por no comprar hardware nuevo.

La compilación que tardó dieciséis minutos en fallar

Primera corrida de prueba real. Hice push de un tag, vi cómo tomaba el trabajo, vi a cargo compilar todo el árbol de dependencias de Tauri desde una caché fría (tauri, wry, rusqlite empaquetado desde fuente en C, todo). Diez minutos de compilación legítima. Después llegó a empaquetar el archivo .dmg y se quedó ahí, sin más. Seis minutos después: Finder got an error: AppleEvent timed out.

Resulta que el empaquetador de DMG de Tauri no solo comprime archivos en una imagen de disco, controla Finder mediante AppleScript para acomodar dónde va el ícono de la app y el acceso directo a la carpeta Applications dentro de la ventana del instalador. Eso requiere un permiso que se otorga una sola vez, el tipo de diálogo que macOS hace aparecer preguntando “Allow Terminal to control Finder?”. En una máquina frente a la que nadie está sentado, no hay quien haga clic en Allow. El diálogo o nunca se renderiza, o expira sin respuesta, y toda la compilación falla.

Reproduje el mismo error exacto primero en mi propia máquina interactiva, específicamente para descartar la falta de sesión gráfica como causa, y aun así falló, incluso con un Finder activo y una sesión real iniciada. Eso me indicó que esto no tenía nada que ver con la falta de interfaz gráfica. Era una puerta de consentimiento que se cruza una sola vez, no una limitación fundamental de capacidad.

El arreglo que no arregló nada

El remedio documentado es poner CI=true, que le indica al empaquetador de Tauri que agregue una bandera para saltarse el paso de AppleScript por completo. Lo puse. No funcionó. Mismo timeout, mismo fallo, a los dieciséis minutos.

En vez de volver a adivinar, fui y de verdad leí el código fuente, tanto el del empaquetador en Rust como el de la GitHub Action en JavaScript que lo envuelve, no publicaciones de blog que los describieran de segunda mano. La lógica del empaquetador era exactamente lo que esperaba: saltarse el paso de AppleScript cuando CI es true. Pero la Action que estaba usando para manejar toda la compilación tenía su propia opinión. Define una segunda variable, más específica, que sobrescribe en silencio la primera, precisamente para que las compilaciones en los runners de Mac alojados por el propio GitHub, que sí tienen permisos de Automation funcionando, conserven su acomodo de íconos más prolijo. Un valor por defecto razonable para el caso común. Exactamente el equivocado para el mío.

Verifiqué el arreglo real de la forma aburrida y confiable, antes de confiar en él dentro del CI: me conecté por SSH a la máquina de compilación, corrí a mano el mismo comando de build con las dos variables puestas correctamente, y vi salir del otro lado un .dmg real, válido y firmado. Solo después de eso hice push de un segundo tag de prueba a través del pipeline real. Dieciséis minutos después: una compilación en verde, un borrador de release en GitHub, y un instalador funcionando, producido por una Mac Mini que pasa el otro 99% de su tiempo vigilando mi patio trasero por si aparecen mapaches.

Lo que le diría a alguien que se tope con esto

Si una compilación de Tauri para macOS se queda esperando en el paso del .dmg con un error de AppleEvent, y ya se está compilando a través de tauri-apps/tauri-action, con poner CI=true a solas no alcanza. También hace falta desactivar explícitamente TAURI_BUNDLER_DMG_IGNORE_CI. Encontré una nota existente en mi propia base de conocimiento, de una versión anterior y más pequeña de este mismo problema, escrita meses atrás, antes de haber encontrado el arreglo real, que solo decía “build without a DMG, or do it on a real desktop.” No estaba exactamente mal. Solo se quedaba a un paso de la respuesta real. Volví y la corregí en vez de escribir una nota nueva al lado de la vieja e incompleta.

Lecturas relacionadas

Development

La corrección que me convencí de no hacer

Una recomendación de 'no construir esto' basada en dos suposiciones sin verificar, y la corrección de una sola línea en la política de activación de Tauri que estuvo ahí todo el tiempo.

Leer