Mi propio pipeline local para publicar .NET MAUI iOS en TestFlight

En este post explico cómo monté un pequeño pipeline local para publicar una app .NET MAUI iOS en TestFlight sin depender de un servicio externo de CI.

La idea final fue:

Visual Studio en Windows
  -> ejecuta una External Tool
  -> conecta por SSH al Mac mini
  -> el Mac compila la app iOS
  -> genera la IPA
  -> sube la IPA a TestFlight

1. Separar el repositorio de los secretos

El primer aprendizaje importante fue no mezclar el repositorio Git con los secretos ni con scripts locales que necesitan permisos de ejecución.

Por ejemplo, podemos tener el repositorio en:

/Users/usuario/Builds/MiApp.Mobile

Y el pipeline local en una carpeta independiente:

/Users/usuario/LocalMagic

Creamos la estructura:

mkdir -p /Users/usuario/LocalMagic/secrets
mkdir -p /Users/usuario/LocalMagic/logs

2. Preparar los archivos necesarios

En la carpeta de secretos coloqué los siguientes archivos:

ios_distribution.p12
AuthKey_XXXXXXXXXX.p8
AppStore_Profile.mobileprovision
local-secrets.env

El archivo .p12 se exporta desde Acceso a Llaveros en el Mac, seleccionando el certificado de distribución de Apple junto con su clave privada.

El provisioning profile puede localizarse con este comando:

for f in ~/Library/MobileDevice/Provisioning\ Profiles/*.mobileprovision; do
  echo "----"
  echo "$f"
  security cms -D -i "$f" | plutil -extract Name raw -
done

Después se copia el profile correcto:

cp "/Users/usuario/Library/MobileDevice/Provisioning Profiles/PROFILE_UUID.mobileprovision" \
   /Users/usuario/LocalMagic/secrets/AppStore_Profile.mobileprovision

3. Crear el archivo de configuración local

Creamos el archivo:

nano /Users/usuario/LocalMagic/secrets/local-secrets.env

Con este contenido:

P12_PASSWORD="PASSWORD_DEL_P12"
APP_STORE_CONNECT_API_KEY_ID="XXXXXXXXXX"
APP_STORE_CONNECT_API_ISSUER_ID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

Este archivo contiene secretos y no debe subirse nunca al repositorio.

4. Crear el script local del Mac

Después creamos el script principal:

nano /Users/usuario/LocalMagic/build-ios-local.sh

Este script realiza el pipeline completo:

crear un keychain temporal
importar el certificado .p12
instalar el provisioning profile
hacer git pull
leer el build number desde Info.plist
compilar la IPA
subir la IPA a TestFlight
eliminar el keychain temporal

Después le damos permisos:

chmod +x /Users/usuario/LocalMagic/build-ios-local.sh

Y lo probamos directamente en el Mac:

/Users/usuario/LocalMagic/build-ios-local.sh

Si todo va bien, al final veremos un mensaje parecido a:

UPLOAD SUCCEEDED with no errors

5. Crear el script PowerShell en Windows

En el repositorio añadí un script PowerShell:

build/Publish-iOS-TestFlight.ps1

Su función es lanzar el script remoto del Mac por SSH:

$MacUser = "usuario"
$MacHost = "192.168.1.100"
$RemoteScript = "/Users/usuario/LocalMagic/build-ios-local.sh"

ssh -tt "$MacUser@$MacHost" "$RemoteScript"

También añadí guardado de logs y una pausa final para que la ventana no se cierre automáticamente.

6. Añadirlo como External Tool en Visual Studio

En Visual Studio abrimos:

Tools > External Tools...

Y añadimos una nueva herramienta con esta configuración:

Title:
Publish iOS TestFlight

Command:
powershell.exe

Arguments:
-ExecutionPolicy Bypass -File "$(SolutionDir)build\Publish-iOS-TestFlight.ps1"

Initial directory:
$(SolutionDir)

Con esto queda disponible desde el menú Tools de Visual Studio.

7. Ejecutar la publicación

El flujo final queda así:

Visual Studio
  -> Tools
  -> Publish iOS TestFlight
  -> pide password SSH
  -> ejecuta el script en el Mac
  -> compila
  -> sube a TestFlight

En mi caso mantuve la password SSH como una confirmación manual para evitar publicaciones accidentales.

Conclusión

El resultado es un pequeño pipeline local para publicar una app iOS desde Visual Studio usando un Mac mini.

Las principales ventajas son:

  • No depende de colas externas.
  • Usa un Mac propio.
  • Los secretos quedan fuera del repositorio.
  • Se lanza desde Visual Studio.
  • Muestra logs en directo.
  • Sube automáticamente a TestFlight.

El punto clave fue separar claramente las responsabilidades:

Repositorio Git: código fuente
Carpeta local del Mac: scripts y secretos
Visual Studio: botón de lanzamiento

Comentarios

Entradas populares de este blog

Automatización y workflows de emails con Mailchimp

Qué necesito para ser desarrollador de aplicaciones móviles (2ª parte)

Azure Insights Viewer: una forma más simple de explorar tus logs de Application Insights