macOSFastlaneCI/CDDistribuciónNotarización

CI/CD para macOS con Fastlane (parte 1): panorama de distribución fuera de la App Store

Distribuir una app macOS fuera del Mac App Store parece un tema resuelto —hasta que te sientas a hacerlo. La firma no basta, la notarización tiene su propio flujo, el ticket hay que adjuntarlo al binario, y todo eso tiene que orquestarse cada vez que sacas una versión nueva. Sin un pipeline lo resuelves a mano cada vez que haya un release.

Esta serie de cinco partes arma ese pipeline con Fastlane, corriéndolo en local en mi Mac y subiendo el binario final a GitHub Releases. La app de referencia es pokedex-macos, una Pokédex mínima en SwiftUI: deliberadamente pequeña, para que el foco sean los pasos de distribución y no la lógica.

Lo que vamos a construir

Un flujo completo que, con un solo comando, hace todo esto:

bundle exec fastlane release version:1.0.1
  1. Sube el número de versión y el build.
  2. Compila la app en configuración Release.
  3. La firma con un certificado Developer ID Application.
  4. La envía a notarizar a Apple.
  5. Adjunta el ticket de notarización al .app.
  6. Crea un DMG.
  7. Hace tag, push y publica una GitHub Release con el DMG como asset.

Al final del comando tienes una URL pública que cualquiera puede descargar, doble clic, y usar sin ver advertencias.

Por qué distribuir fuera del Mac App Store

El Mac App Store resuelve muchas cosas —a cambio de otras. Apple exige sandbox estricto, revisa cada versión, corta ciertas APIs de sistema y se queda con una parte de los ingresos. Para bastantes apps eso está bien. Pero hay casos donde no encaja:

  • Herramientas de desarrollador que necesitan acceso a otros procesos o a directorios arbitrarios.
  • Betas privadas o distribuciones internas.
  • Utilidades pequeñas que no justifican el ciclo de review.
  • Apps que dependen de APIs bloqueadas dentro del sandbox.

En cualquiera de esos casos la ruta es Developer ID + notarización. Tú alojas el binario donde quieras (tu web, S3, GitHub Releases), y Apple valida a distancia que la app es tuya y no es maliciosa.

Gatekeeper — el guardia de la puerta

macOS trae un componente llamado Gatekeeper que decide si una app descargada de internet puede arrancar. Cuando bajas un .dmg con el navegador, macOS le añade un atributo extendido llamado com.apple.quarantine. La primera vez que el usuario abre la app, Gatekeeper mira ese atributo y hace tres preguntas:

  1. ¿Está firmada con un certificado Developer ID válido?
  2. ¿Tiene un ticket de notarización de Apple?
  3. ¿El ticket coincide con lo que hay en el binario?

Si las tres respuestas son sí, la app abre sin fricción. Si falta alguna, el usuario ve un diálogo pidiendo permiso y —cada vez es peor— la única salida es ir a Ajustes del Sistema → Privacidad y Seguridad → “Abrir de todas formas”. La mayoría de la gente cierra el diálogo y no vuelve.

Los tres pasos que pide Apple

El pipeline vive alrededor de estos tres actos:

Firma. Firmar significa aplicar tu identidad criptográfica al binario con un certificado Developer ID Application (más sobre este certificado, y cómo se distingue del de Mac App Distribution, en la parte 2). La herramienta es codesign, aunque casi nunca la llamamos a mano —lo hace Xcode y por encima Fastlane.

Notarización. La app firmada se envía a Apple con notarytool. Del otro lado corre un análisis automático —malware, patrones sospechosos, uso indebido de APIs privadas— y en un par de minutos devuelven un veredicto. Si aprueban, generan un “ticket” que queda asociado a esa build específica.

Stapling. El ticket vive en los servidores de Apple, pero Gatekeeper también funciona offline. Para eso stapler adjunta el ticket dentro del .app. Así aunque el usuario abra la app sin red, macOS puede verificar todo localmente.

Los tres son obligatorios en macOS 10.15 en adelante. Saltarte uno funciona en tu Mac (donde el certificado ya está en el Keychain), pero le arruina la experiencia a quien te descargue.

Dónde entra Fastlane

Nada de esto lo inventó Fastlane. Todas las herramientas —codesign, xcodebuild, notarytool, stapler, hdiutil— vienen con macOS y con Xcode. Se puede armar el mismo pipeline con un script bash de 200 líneas y funcionar.

Lo que aporta Fastlane es declarar el pipeline como código. En vez de una tira de comandos con ramas de error, tienes un Fastfile con dos lanes:

lane :qa do
  # build interno, firmado pero sin notarizar
end

lane :release do |options|
  # build + firma + notarización + adjuntar ticket + DMG + GitHub Release
end

Cada acción de Fastlane (build_mac_app, notarize, set_github_release) es un wrapper delgado sobre las herramientas oficiales. Fastlane no oculta lo que pasa —lo ordena. Cuando algo falla, el output te dice qué comando exacto se cayó y con qué argumentos, así puedes reproducirlo a mano si hace falta.

Y para pipelines locales como este, tiene una ventaja concreta: las credenciales viven en un .env que Fastlane carga solo, así que el Fastfile se ve limpio y no hay tokens hardcodeados en ningún lado.

Cómo sigue la serie

Cinco partes en total:

  1. Panorama — esta. Qué exige Apple y por qué vale la pena orquestarlo.
  2. Setup — el certificado Developer ID Application (y su distinción crítica con Mac App Distribution), la API Key de App Store Connect, el PAT de GitHub, cómo queda el .env.
  3. Notarización a fondo — qué firma exactamente codesign, el Hardened Runtime, entitlements para Developer ID, notarytool con API Key, y por qué adjuntar el ticket al binario importa.
  4. Auto-versionado y lanes — cómo Fastlane maneja MARKETING_VERSION y CURRENT_PROJECT_VERSION, la diferencia entre qa y release, y el modo skip_publish para ensayos.
  5. Distribución por GitHub Releases — el paso final del pipeline: DMG, tag, push y publicación con set_github_release.

Al terminar tengo un bundle exec fastlane release version:x.y.z que va del código a una descarga pública sin tocar nada más. En la parte 2 arrancamos con el setup.