1. Pourquoi un Mac dédié pour Jenkins ?
Jenkins est la plateforme CI/CD la plus utilisée, exécutant plus de 50 % des pipelines de build en entreprise. Cependant, compiler des applications iOS avec Jenkins nécessite macOS, et macOS ne peut légalement fonctionner que sur du matériel Apple. Un Mac Mini M4 dédié vous offre :
Le même matériel, le même OS, les mêmes outils à chaque build. Plus de builds instables dus à la dérive d'environnement.
Installez n'importe quel outil, configurez les paramètres système, gérez les trousseaux pour la signature de code.
La puce M4 offre des builds Xcode 2 à 3 fois plus rapides par rapport aux Mac Mini Intel.
DerivedData, CocoaPods et les caches SPM survivent entre les builds.
2. Prérequis
- Un Mac Mini M4 chez My Remote Mac (à partir de 75 $/mois)
- Un contrôleur Jenkins (peut être sur Linux, Docker ou n'importe quelle VM cloud)
- Un accès SSH à votre Mac Mini
- Xcode installé sur le Mac Mini (voir notre guide GitHub Actions pour les étapes d'installation de Xcode)
3. Étape 1 : Installer le JDK Java sur macOS
Les agents Jenkins nécessitent un environnement d'exécution Java. Nous recommandons le JDK 17 (LTS) ou le JDK 21 (LTS) installé via Homebrew pour une gestion facile.
# Install Homebrew if not already present
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile
eval "$(/opt/homebrew/bin/brew shellenv)"
# Install Java JDK 17 (recommended for Jenkins)
brew install openjdk@17
# Create the symlink for system Java wrappers
sudo ln -sfn /opt/homebrew/opt/openjdk@17/libexec/openjdk.jdk \
/Library/Java/JavaVirtualMachines/openjdk-17.jdk
# Add to PATH
echo 'export PATH="/opt/homebrew/opt/openjdk@17/bin:$PATH"' >> ~/.zprofile
source ~/.zprofile
# Verify Java installation
java -version
# openjdk version "17.0.13" 2024-10-15
# OpenJDK Runtime Environment Homebrew (build 17.0.13+0)
# OpenJDK 64-Bit Server VM Homebrew (build 17.0.13+0, mixed mode, sharing)
4. Étape 2 : Configurer l'agent Jenkins
Il existe deux méthodes principales pour connecter un agent Jenkins : JNLP (connexion entrante de l'agent vers le contrôleur) et SSH (connexion sortante du contrôleur vers l'agent). Nous couvrons les deux.
Méthode A : Agent SSH (recommandé)
C'est l'approche la plus fiable. Jenkins se connecte au Mac via SSH et gère le processus de l'agent à distance.
# On the Mac Mini: Create a dedicated Jenkins user
sudo dscl . -create /Users/jenkins
sudo dscl . -create /Users/jenkins UserShell /bin/zsh
sudo dscl . -create /Users/jenkins RealName "Jenkins Agent"
sudo dscl . -create /Users/jenkins UniqueID 550
sudo dscl . -create /Users/jenkins PrimaryGroupID 20
sudo dscl . -create /Users/jenkins NFSHomeDirectory /Users/jenkins
sudo mkdir -p /Users/jenkins
sudo chown jenkins:staff /Users/jenkins
# Set a password for the jenkins user
sudo dscl . -passwd /Users/jenkins "STRONG_PASSWORD_HERE"
# Create SSH directory and add the Jenkins controller's public key
sudo mkdir -p /Users/jenkins/.ssh
sudo sh -c 'echo "ssh-rsa YOUR_JENKINS_CONTROLLER_PUBLIC_KEY" > /Users/jenkins/.ssh/authorized_keys'
sudo chmod 700 /Users/jenkins/.ssh
sudo chmod 600 /Users/jenkins/.ssh/authorized_keys
sudo chown -R jenkins:staff /Users/jenkins/.ssh
# Create a workspace directory
sudo mkdir -p /Users/jenkins/workspace
sudo chown jenkins:staff /Users/jenkins/workspace
Configurez maintenant l'agent dans Jenkins :
- Allez dans Manage Jenkins > Nodes > New Node
- Définissez le nom sur
mac-mini-m4 - Définissez le répertoire racine distant sur
/Users/jenkins/workspace - Labels :
mac macos apple-silicon m4 ios - Méthode de lancement : Launch agents via SSH
- Hôte : l'adresse IP de votre Mac Mini
- Identifiants : ajoutez la clé privée SSH correspondant aux authorized_keys du Mac
Méthode B : Agent JNLP (entrant)
Utilisez cette méthode lorsque le contrôleur Jenkins ne peut pas atteindre directement le Mac (par ex., le Mac est derrière un pare-feu). L'agent initie la connexion.
# First, create the node in Jenkins UI:
# Manage Jenkins > Nodes > New Node
# Launch method: "Launch agent by connecting it to the controller"
# Note the secret token from the node configuration page
# On the Mac Mini, download the agent JAR:
mkdir -p ~/jenkins-agent && cd ~/jenkins-agent
curl -sO https://your-jenkins-url/jnlpJars/agent.jar
# Test the agent connection
java -jar agent.jar \
-url https://your-jenkins-url \
-secret YOUR_AGENT_SECRET \
-name "mac-mini-m4" \
-workDir "/Users/jenkins/workspace"
Exécuter l'agent JNLP en tant que service launchd
# Create the launchd plist file
cat > ~/Library/LaunchAgents/com.jenkins.agent.plist <<'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.jenkins.agent</string>
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/opt/openjdk@17/bin/java</string>
<string>-jar</string>
<string>/Users/jenkins/jenkins-agent/agent.jar</string>
<string>-url</string>
<string>https://your-jenkins-url</string>
<string>-secret</string>
<string>YOUR_AGENT_SECRET</string>
<string>-name</string>
<string>mac-mini-m4</string>
<string>-workDir</string>
<string>/Users/jenkins/workspace</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/Users/jenkins/jenkins-agent/stdout.log</string>
<key>StandardErrorPath</key>
<string>/Users/jenkins/jenkins-agent/stderr.log</string>
</dict>
</plist>
EOF
# Load the service
launchctl load ~/Library/LaunchAgents/com.jenkins.agent.plist
# Verify it's running
launchctl list | grep jenkins
5. Étape 3 : Créer un pipeline de build iOS
Créez un Jenkinsfile à la racine de votre dépôt. Ce pipeline déclaratif compile, teste et archive optionnellement votre application iOS.
pipeline {
agent { label 'mac && m4' }
environment {
SCHEME = 'MyApp'
WORKSPACE = 'MyApp.xcworkspace'
DESTINATION = 'platform=iOS Simulator,name=iPhone 16 Pro,OS=18.2'
DERIVED_DATA = "${WORKSPACE_DIR}/DerivedData"
}
options {
timeout(time: 30, unit: 'MINUTES')
buildDiscarder(logRotator(numToKeepStr: '20'))
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Install Dependencies') {
steps {
sh '''
# Select Xcode version
sudo xcode-select -s /Applications/Xcode-16.2.app/Contents/Developer
xcodebuild -version
# Install CocoaPods dependencies
if [ -f "Podfile" ]; then
pod install --repo-update
fi
'''
}
}
stage('Build') {
steps {
sh '''
xcodebuild build \
-workspace "${WORKSPACE}" \
-scheme "${SCHEME}" \
-destination "${DESTINATION}" \
-derivedDataPath "${DERIVED_DATA}" \
CODE_SIGNING_ALLOWED=NO \
| xcbeautify
'''
}
}
stage('Test') {
steps {
sh '''
xcodebuild test \
-workspace "${WORKSPACE}" \
-scheme "${SCHEME}" \
-destination "${DESTINATION}" \
-derivedDataPath "${DERIVED_DATA}" \
-resultBundlePath "TestResults.xcresult" \
-parallel-testing-enabled YES \
| xcbeautify
'''
}
post {
always {
archiveArtifacts artifacts: 'TestResults.xcresult/**', allowEmptyArchive: true
}
}
}
stage('Archive') {
when {
branch 'main'
}
steps {
sh '''
xcodebuild archive \
-workspace "${WORKSPACE}" \
-scheme "${SCHEME}" \
-archivePath "${DERIVED_DATA}/MyApp.xcarchive" \
-destination "generic/platform=iOS" \
| xcbeautify
'''
}
}
}
post {
success {
echo 'Build and tests passed!'
}
failure {
echo 'Build or tests failed.'
}
cleanup {
sh 'xcrun simctl shutdown all 2>/dev/null || true'
}
}
}
6. Étape 4 : Intégrer Fastlane pour le déploiement
Fastlane simplifie la signature de code et le déploiement TestFlight. Ajoutez une étape de déploiement à votre Jenkinsfile :
# Install Fastlane on the Mac Mini (one-time setup)
brew install fastlane
# Or via Ruby:
gem install fastlane -NV
Ajoutez une étape de déploiement à votre Jenkinsfile :
stage('Deploy to TestFlight') {
when {
branch 'main'
}
environment {
APP_STORE_CONNECT_API_KEY_ID = credentials('app-store-key-id')
APP_STORE_CONNECT_API_ISSUER_ID = credentials('app-store-issuer-id')
APP_STORE_CONNECT_API_KEY_CONTENT = credentials('app-store-key-content')
MATCH_PASSWORD = credentials('match-password')
}
steps {
sh '''
fastlane beta
'''
}
}
Et créez un fastlane/Fastfile correspondant :
default_platform(:ios)
platform :ios do
desc "Push a new beta build to TestFlight"
lane :beta do
setup_ci
# Fetch code signing certificates via match
match(type: "appstore", readonly: true)
# Increment build number
increment_build_number(
build_number: ENV["BUILD_NUMBER"]
)
# Build the app
build_app(
workspace: "MyApp.xcworkspace",
scheme: "MyApp",
export_method: "app-store",
derived_data_path: "DerivedData"
)
# Upload to TestFlight
upload_to_testflight(
skip_waiting_for_build_processing: true,
api_key: app_store_connect_api_key(
key_id: ENV["APP_STORE_CONNECT_API_KEY_ID"],
issuer_id: ENV["APP_STORE_CONNECT_API_ISSUER_ID"],
key_content: ENV["APP_STORE_CONNECT_API_KEY_CONTENT"]
)
)
end
end
7. Bonnes pratiques de sécurité
N'exécutez jamais l'agent Jenkins en tant que root. Créez un utilisateur dédié avec des permissions minimales.
Ne codez jamais en dur les certificats, clés API ou mots de passe dans les Jenkinsfiles. Utilisez le plugin Jenkins Credentials.
Désactivez l'authentification par mot de passe pour SSH. Utilisez des clés Ed25519 ou RSA 4096 bits.
Restreignez les connexions entrantes à l'IP du contrôleur Jenkins uniquement. My Remote Mac fournit un pare-feu géré avec accès API.
Appliquez les correctifs de sécurité rapidement. Utilisez softwareupdate -l pour vérifier les mises à jour.
8. FAQ
Puis-je exécuter le contrôleur Jenkins sur le même Mac ?
Oui, mais nous recommandons de les séparer. Exécutez le contrôleur sur une VM Linux ou un conteneur Docker, et utilisez le Mac exclusivement comme agent de build. Cela évite la contention des ressources pendant les builds lourds.
Combien de builds simultanés un Mac Mini M4 peut-il gérer ?
Un Mac Mini M4 avec 16 Go de RAM peut gérer confortablement 2 builds Xcode simultanés. Avec 24 Go, vous pouvez aller jusqu'à 3. Le M4 Pro avec 48 Go gère facilement 4+ builds simultanés. Configurez le "Number of executors" dans les paramètres de votre nœud Jenkins en conséquence.
Ai-je besoin d'une session graphique pour les simulateurs iOS ?
Non. Les simulateurs iOS fonctionnent en mode headless via SSH. Cependant, si vous avez besoin d'un affichage graphique (par ex., pour les captures d'écran de tests UI), utilisez VNC pour vous connecter au Mac et assurez-vous qu'une session GUI est active.
Comment gérer la signature de code sur un serveur sans écran ?
Utilisez Fastlane Match pour synchroniser les certificats depuis un dépôt Git ou un stockage cloud. Alternativement, importez les certificats dans le trousseau macOS et déverrouillez-le dans votre pipeline en utilisant security unlock-keychain.
Guides connexes
Runner GitHub Actions auto-hébergé
Configurez un runner GitHub Actions sur votre Mac Mini M4.
Fastlane + serveur Mac dédié
Automatisez les builds iOS et les déploiements TestFlight avec Fastlane.
Runner GitLab CI Mac
Enregistrez un runner GitLab sur un Mac Mini dédié pour les pipelines CI iOS/macOS.