Jenkins के लिए एक समर्पित Mac क्यों?
Jenkins सबसे व्यापक रूप से उपयोग किया जाने वाला CI/CD प्लेटफ़ॉर्म है, जो 50% से अधिक एंटरप्राइज़ बिल्ड पाइपलाइनों को चलाता है। हालांकि, Jenkins के साथ iOS ऐप्स बनाने के लिए macOS की आवश्यकता होती है, और macOS केवल Apple हार्डवेयर पर ही कानूनी रूप से चल सकता है। एक समर्पित Mac Mini M4 आपको देता है:
-
सुसंगत बिल्ड एनवायरनमेंट
हर बिल्ड में समान हार्डवेयर, समान OS, समान टूल्स। एनवायरनमेंट ड्रिफ्ट के कारण कोई अस्थिर बिल्ड नहीं।
-
पूर्ण Root एक्सेस
कोई भी टूल्स इंस्टॉल करें, सिस्टम सेटिंग्स कॉन्फ़िगर करें, कोड साइनिंग के लिए कीचेन प्रबंधित करें।
-
Apple Silicon प्रदर्शन
M4 चिप, Intel Mac Minis की तुलना में 2-3x तेज़ Xcode बिल्ड प्रदान करती है।
-
स्थायी कैश
DerivedData, CocoaPods और SPM कैश बिल्ड के बीच बने रहते हैं।
पूर्वापेक्षाएं
- My Remote Mac से एक समर्पित Mac mini M4 बिल्ड सर्वर ($85/माह से, USD, कर अतिरिक्त)
- एक Jenkins controller (Linux, Docker या किसी भी क्लाउड VM पर हो सकता है)
- आपके Mac Mini तक SSH एक्सेस
- Mac Mini पर Xcode इंस्टॉल किया गया (Xcode इंस्टॉलेशन चरणों के लिए हमारी GitHub Actions गाइड देखें)
चरण 1: macOS पर Java JDK इंस्टॉल करें
Jenkins agents को एक Java Runtime Environment की आवश्यकता होती है। Jenkins की मौजूदा रिलीज़ Java 21 और Java 25 को सपोर्ट करती हैं (Java 17 अब सपोर्टेड नहीं है), इसलिए आसान प्रबंधन के लिए हम Homebrew के माध्यम से JDK 21 (LTS) इंस्टॉल करते हैं।
# 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 21 (LTS): current Jenkins releases support Java 21 and 25
brew install openjdk@21
# Create the symlink for system Java wrappers
sudo ln -sfn /opt/homebrew/opt/openjdk@21/libexec/openjdk.jdk \
/Library/Java/JavaVirtualMachines/openjdk-21.jdk
# Add to PATH
echo 'export PATH="/opt/homebrew/opt/openjdk@21/bin:$PATH"' >> ~/.zprofile
source ~/.zprofile
# Verify Java installation (should report version 21)
java -version
# Tools used by the pipeline in Step 3 (CocoaPods only if you use a Podfile)
brew install xcbeautify cocoapods
चरण 2: Jenkins Agent कॉन्फ़िगर करें
एक Jenkins agent को कनेक्ट करने के दो प्राथमिक तरीके हैं: inbound agent (agent, controller से कनेक्ट करता है; पहले इसे JNLP कहा जाता था) और SSH (controller, agent से कनेक्ट करता है)। हम दोनों को कवर करते हैं।
विधि A: SSH Agent (अनुशंसित)
यह सबसे विश्वसनीय तरीका है। Jenkins, SSH के माध्यम से Mac से कनेक्ट होता है और agent प्रक्रिया को दूरस्थ रूप से प्रबंधित करता है।
# On the Mac mini: create a dedicated, non-admin Jenkins user
sudo sysadminctl -addUser jenkins -fullName "Jenkins Agent" -password "STRONG_PASSWORD_HERE"
# Create its home folder from the macOS template (includes ~/Library)
sudo createhomedir -c -u jenkins
# If Remote Login is limited to some users (System Settings > General >
# Sharing > Remote Login), allow the jenkins user to connect over SSH
sudo dseditgroup -o edit -a jenkins -t user com.apple.access_ssh
# 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
अब Jenkins में agent कॉन्फ़िगर करें:
- Manage Jenkins > Nodes > New Node पर जाएं
- नाम
mac-mini-m4पर सेट करें - Remote root directory को
/Users/jenkins/workspaceपर सेट करें - Labels:
mac macos apple-silicon m4 ios - Launch method: Launch agents via SSH
- Host: आपके Mac Mini का IP पता
- Credentials: Mac पर authorized_keys से मेल खाने वाली SSH private key जोड़ें
- Host Key Verification Strategy: Known hosts file Verification Strategy। पहली बार लॉन्च करने से पहले, controller से उस user के रूप में एक बार कनेक्ट करें जो Jenkins चलाता है (
ssh jenkins@YOUR_MAC_IP), ताकि Mac उसकी~/.ssh/known_hostsमें जुड़ जाए
विधि B: Inbound Agent (WebSocket)
इस विधि का उपयोग तब करें जब Jenkins controller, Mac तक सीधे नहीं पहुंच सकता (उदाहरण के लिए, Mac एक फ़ायरवॉल के पीछे है)। agent कनेक्शन शुरू करता है। -webSocket विकल्प के साथ यह केवल controller के वेब पोर्ट (HTTP या HTTPS) का उपयोग करता है, किसी अलग TCP agent पोर्ट का नहीं।
# 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 as the jenkins user
# (download it again after each Jenkins controller upgrade)
sudo -u jenkins mkdir -p /Users/jenkins/jenkins-agent
sudo -u jenkins curl -fsSL -o /Users/jenkins/jenkins-agent/agent.jar \
https://your-jenkins-url/jnlpJars/agent.jar
# Test the agent connection. -webSocket goes through the controller's
# HTTP(S) port: no separate TCP agent port to open
sudo -u jenkins /opt/homebrew/opt/openjdk@21/bin/java \
-jar /Users/jenkins/jenkins-agent/agent.jar \
-url https://your-jenkins-url \
-secret YOUR_AGENT_SECRET \
-name "mac-mini-m4" \
-webSocket \
-workDir "/Users/jenkins/workspace"
Inbound Agent को एक launchd सर्विस के रूप में चलाएं
agent, jenkins user के LaunchAgent के रूप में चलता है, इसलिए यह बूट पर नहीं, बल्कि उस user के लॉगिन पर शुरू होता है। हेडलेस Mac पर, jenkins user के लिए Automatic login सक्षम करें (System Settings > Users & Groups; FileVault चालू रहते उपलब्ध नहीं), ताकि रीबूट के बाद agent फिर से ऑनलाइन आ जाए। यही लॉग-इन सत्र Simulators, UI tests और login keychain को वह user environment भी देता है जिसकी उन्हें ज़रूरत होती है।
# Create the LaunchAgent in the jenkins user's own Library
sudo -u jenkins mkdir -p /Users/jenkins/Library/LaunchAgents
sudo -u jenkins tee /Users/jenkins/Library/LaunchAgents/io.jenkins.agent.plist > /dev/null <<'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>io.jenkins.agent</string>
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/opt/openjdk@21/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>-webSocket</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
# The agent loads at every login of the jenkins user. On a headless Mac,
# connect over VNC as an admin, open System Settings > Users & Groups and set
# "Automatically log in as" to jenkins (unavailable while FileVault is on),
# then restart:
sudo shutdown -r now
# After the restart, verify it's running
sudo launchctl print gui/$(id -u jenkins)/io.jenkins.agent | grep state
चरण 3: iOS Build Pipeline बनाएं
अपने रिपॉजिटरी रूट में एक Jenkinsfile बनाएं। यह डिक्लेरेटिव पाइपलाइन आपके iOS ऐप को बनाती, टेस्ट करती और वैकल्पिक रूप से आर्काइव करती है।
pipeline {
agent { label 'mac && m4' }
environment {
// Pick the Xcode for every stage without sudo (path from: xcodes installed)
DEVELOPER_DIR = '/Applications/Xcode-27.0.0.app/Contents/Developer'
SCHEME = 'MyApp'
// Not WORKSPACE: Jenkins already uses it for the job's workspace path
XC_WORKSPACE = 'MyApp.xcworkspace'
// Any device listed by: xcrun simctl list devices available
DESTINATION = 'platform=iOS Simulator,name=iPhone 17,OS=latest'
// Relative to the job workspace, kept between builds
DERIVED_DATA = 'DerivedData'
}
options {
timeout(time: 30, unit: 'MINUTES')
buildDiscarder(logRotator(numToKeepStr: '20'))
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Install Dependencies') {
steps {
sh '''
# Homebrew tools are not on the PATH of a non-interactive SSH session
export PATH="/opt/homebrew/bin:$PATH"
# Xcode comes from DEVELOPER_DIR: no sudo xcode-select in a pipeline
xcodebuild -version
# Install CocoaPods dependencies
if [ -f "Podfile" ]; then
pod install --repo-update
fi
'''
}
}
stage('Build') {
steps {
sh '''
# Jenkins runs sh -xe: without pipefail, xcbeautify's exit status would hide a failed build
set -o pipefail
export PATH="/opt/homebrew/bin:$PATH"
xcodebuild build \
-workspace "${XC_WORKSPACE}" \
-scheme "${SCHEME}" \
-destination "${DESTINATION}" \
-derivedDataPath "${DERIVED_DATA}" \
CODE_SIGNING_ALLOWED=NO \
| xcbeautify
'''
}
}
stage('Test') {
steps {
sh '''
set -o pipefail
export PATH="/opt/homebrew/bin:$PATH"
rm -rf TestResults.xcresult
xcodebuild test \
-workspace "${XC_WORKSPACE}" \
-scheme "${SCHEME}" \
-destination "${DESTINATION}" \
-derivedDataPath "${DERIVED_DATA}" \
-resultBundlePath "TestResults.xcresult" \
-parallel-testing-enabled YES \
| xcbeautify --report junit --report-path build/reports
'''
}
post {
always {
junit allowEmptyResults: true, testResults: 'build/reports/junit.xml'
archiveArtifacts artifacts: 'TestResults.xcresult/**', allowEmptyArchive: true
}
}
}
stage('Archive') {
when {
branch 'main'
}
steps {
sh '''
set -o pipefail
export PATH="/opt/homebrew/bin:$PATH"
# Unsigned archive: checks that the Release build compiles.
# Fastlane signs and uploads the real build in Step 4.
xcodebuild archive \
-workspace "${XC_WORKSPACE}" \
-scheme "${SCHEME}" \
-archivePath "${DERIVED_DATA}/MyApp.xcarchive" \
-destination "generic/platform=iOS" \
CODE_SIGNING_ALLOWED=NO \
| xcbeautify
'''
}
}
}
post {
success {
echo 'Build and tests passed!'
}
failure {
echo 'Build or tests failed.'
}
cleanup {
sh 'xcrun simctl shutdown all 2>/dev/null || true'
}
}
}
चरण 4: डिप्लॉयमेंट के लिए Fastlane एकीकृत करें
Fastlane, कोड साइनिंग और TestFlight डिप्लॉयमेंट को सरल बनाता है। इसे Mac पर एक बार इंस्टॉल करें:
# Install Fastlane on the Mac mini (one-time setup)
brew install fastlane
अपनी Jenkinsfile में एक deploy stage जोड़ें:
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 '''
export PATH="/opt/homebrew/bin:$PATH"
fastlane beta
'''
}
}
और एक मेल खाती fastlane/Fastfile बनाएं:
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
सुरक्षा सर्वोत्तम अभ्यास
-
एक समर्पित Jenkins उपयोगकर्ता का उपयोग करें
Jenkins agent को कभी भी root के रूप में न चलाएं। न्यूनतम परमिशन के साथ एक समर्पित उपयोगकर्ता बनाएं।
-
सीक्रेट्स को Jenkins Credentials में स्टोर करें
Jenkinsfiles में कभी भी सर्टिफिकेट, API keys या पासवर्ड हार्डकोड न करें। Jenkins Credentials plugin का उपयोग करें।
-
केवल SSH key-आधारित प्रमाणीकरण सक्षम करें
SSH के लिए पासवर्ड प्रमाणीकरण अक्षम करें। Ed25519 या RSA 4096-bit keys का उपयोग करें।
-
macOS फ़ायरवॉल का उपयोग करें
इनकमिंग कनेक्शन को केवल Jenkins controller IP तक सीमित करें। My Remote Mac, API एक्सेस के साथ एक मैनेज्ड फ़ायरवॉल प्रदान करता है।
-
macOS और Xcode को अपडेट रखें
सुरक्षा पैच तुरंत लागू करें। अपडेट की जांच के लिए
softwareupdate -lका उपयोग करें।
सामान्य प्रश्न
क्या मैं Jenkins controller को उसी Mac पर चला सकता हूं?
एक Mac Mini M4 कितने समवर्ती बिल्ड संभाल सकता है?
क्या मुझे iOS सिम्युलेटर के लिए एक GUI सत्र की आवश्यकता है?
मैं एक हेडलेस सर्वर पर कोड साइनिंग कैसे संभालूं?
security unlock-keychain का उपयोग करके इसे अनलॉक करें।