iOS बिल्ड के लिए Mac को Jenkins एजेंट के रूप में कैसे जोड़ें
Jenkins में Mac जोड़ने के लिए एक permanent नोड बनाएँ, जो controller से जुड़ता है। Mac पर Java 21 इंस्टॉल करें और agent.jar को -webSocket विकल्प के साथ चलाएँ। उस कमांड को एक LaunchAgent में रखें और ऑटोमैटिक लॉग-इन चालू करें, ताकि रीबूट के बाद एजेंट फिर जुड़ जाए। फिर नोड पर macos लेबल लगाएँ और अपने iOS stages उस पर भेजें।
पहले क्या चाहिए
- एक Jenkins controller, जिस तक Mac HTTPS से पहुँच सके।
- एडमिन एक्सेस, Homebrew और Xcode वाला Apple silicon Mac।
- सही Java। अक्टूबर 2026 में पढ़ी गई Jenkins की Java सपोर्ट पॉलिसी कहती है कि LTS 2.555.1 और नए वर्ज़न को Java 21 या 25 चाहिए। यह नियम एजेंट पर भी लागू है, सिर्फ़ controller पर नहीं।
- आख़िरी स्टेप के लिए Mac पर एक डेस्कटॉप सेशन, स्क्रीन शेयरिंग से।
1. Mac पर Java इंस्टॉल करें
brew install openjdk@21 /opt/homebrew/opt/openjdk@21/bin/java -version
Homebrew यह JDK keg-only इंस्टॉल करता है, इसलिए यह आपके PATH पर नहीं होता। हर जगह ऊपर वाला पूरा पाथ इस्तेमाल करें। इससे बाद में नया JDK आने पर भी एजेंट Java 21 पर ही रहता है।
2. controller पर नोड बनाएँ
- Manage Jenkins खोलें, फिर Nodes, फिर New Node। Permanent Agent चुनें।
- Number of executors: 1। Jenkins के अपने डॉक्स हर नोड पर एक executor को सबसे सुरक्षित सेटिंग कहते हैं। Xcode बिल्ड पहले ही हर कोर इस्तेमाल करते हैं।
- Remote root directory:
/Users/YOUR-USER/jenkins। - Labels:
macos xcode। - Usage: only build jobs with label expressions matching this node। इससे Linux जॉब आपके Mac से दूर रहते हैं।
- Launch method: Launch agent by connecting it to the controller।
Save करें। अब नोड के पेज पर run कमांड, एजेंट का नाम और एक लंबा hex secret दिखता है। secret एजेंट के नाम से बँधा है। अगर यह लीक हो जाए, तो Jenkins कहता है कि वह नाम दोबारा इस्तेमाल न करें।
3. agent.jar डाउनलोड करें और हाथ से टेस्ट करें
Jenkins आपके controller के लिए सही agent.jar /jnlpJars/agent.jar पर देता है। secret को एक फ़ाइल में रखें और @ के साथ दें, ताकि यह कभी process list में न दिखे।
mkdir -p ~/jenkins && cd ~/jenkins curl -sO https://jenkins.example.com/jnlpJars/agent.jar echo 'PASTE-THE-SECRET' > secret-file chmod 600 secret-file /opt/homebrew/opt/openjdk@21/bin/java -jar agent.jar \ -url https://jenkins.example.com/ \ -name mac-mini-1 \ -secret @secret-file \ -workDir "$HOME/jenkins" \ -webSocket
नोड पेज connected पर आ जाना चाहिए। रोकने के लिए Control C दबाएँ। -webSocket के साथ एजेंट एक HTTPS कनेक्शन बनाता है। इसके बिना एजेंट को controller का अलग inbound TCP पोर्ट भी चाहिए।
4. एजेंट को LaunchAgent से चलाएँ
launchd लॉग-इन पर एजेंट शुरू करता है और बंद होने पर उसे फिर चालू करता है। इसे Mac के डेस्कटॉप पर Terminal से चलाएँ। heredoc आपके होम फ़ोल्डर का पाथ भर देता है।
mkdir -p ~/Library/LaunchAgents
cat > ~/Library/LaunchAgents/local.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>local.jenkins-agent</string>
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/opt/openjdk@21/bin/java</string>
<string>-jar</string><string>$HOME/jenkins/agent.jar</string>
<string>-url</string><string>https://jenkins.example.com/</string>
<string>-name</string><string>mac-mini-1</string>
<string>-secret</string><string>@$HOME/jenkins/secret-file</string>
<string>-workDir</string><string>$HOME/jenkins</string>
<string>-webSocket</string>
</array>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key><string>/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
<key>LANG</key><string>en_US.UTF-8</string>
</dict>
<key>RunAtLoad</key><true/>
<key>KeepAlive</key><true/>
<key>ThrottleInterval</key><integer>30</integer>
<key>StandardOutPath</key><string>$HOME/jenkins/agent.log</string>
<key>StandardErrorPath</key><string>$HOME/jenkins/agent.log</string>
</dict>
</plist>
EOF
plutil -lint ~/Library/LaunchAgents/local.jenkins-agent.plist
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/local.jenkins-agent.plist
launchctl print gui/$(id -u)/local.jenkins-agent | head -20हमने यह plist plutil -lint से जाँची। LaunchDaemon की जगह LaunchAgent क्यों? daemon किसी लॉग-इन सेशन के बाहर चलता है। सिम्युलेटर और लॉग-इन keychain सेशन के अंदर रहते हैं। GitLab और Buildkite भी अपने Mac एजेंट के लिए यही वजह देते हैं।
plist को ~/Library/LaunchAgents में रखें। macOS 27 पर हमने देखा कि /Library/LaunchAgents में रखी plist हर बूट पर ऑटो-लॉग-इन तोड़ देती है। ब्योरा headless Mac पर ऑटो-लॉग-इन में है।
5. रीबूट के बाद भी चालू रखें
System Settings में, Users and Groups के नीचे, एजेंट के अकाउंट के लिए ऑटोमैटिक लॉग-इन चालू करें। Mac को स्लीप होने से रोकें। फिर रीबूट करें और नोड पेज देखें।
sudo pmset -a sleep 0 sudo shutdown -r now # then, after it is back: tail -n 20 ~/jenkins/agent.log
6. iOS stage वाली Jenkinsfile
pipeline {
agent { label 'macos' }
options { timeout(time: 30, unit: 'MINUTES') }
stages {
stage('Test') {
steps {
sh 'xcodebuild -version'
sh 'rm -rf build/TestResults.xcresult'
sh "xcodebuild test -project MyApp.xcodeproj -scheme MyApp -destination 'platform=iOS Simulator,name=iPhone 17,OS=26.5' -resultBundlePath build/TestResults.xcresult"
}
}
}
post {
always {
sh 'ditto -c -k --keepParent build/TestResults.xcresult build/TestResults.xcresult.zip || true'
archiveArtifacts artifacts: 'build/TestResults.xcresult.zip', fingerprint: true
}
}
}result bundle एक फ़ोल्डर है, इसलिए आर्काइव करने से पहले हम उसे ditto से zip करते हैं। zip को किसी भी Mac पर खोलें और bundle पर डबल क्लिक करके उसे Xcode में देखें।
एक एजेंट पर दो Xcode वर्ज़न? हर पाइपलाइन के लिए एक को DEVELOPER_DIR से चुनें। Apple का xcode-select man पेज कहता है कि यह सिस्टम-भर की पसंद को बदले बिना उसे ओवरराइड करता है। इसे environment ब्लॉक में सेट करें, ताकि Mac पर दूसरे जॉब पर असर न पड़े:
environment {
DEVELOPER_DIR = '/Applications/Xcode.app/Contents/Developer'
}यह ब्लॉक pipeline के अंदर, agent के बगल में रखें। sudo xcode-select -s से ग्लोबल सेटिंग बदलना भी काम करता है, पर इससे मशीन का हर जॉब एक साथ बदल जाता है।
आम एरर और उनके हल
- एजेंट जुड़ता है, फिर रीबूट के बाद गायब हो जाता है। किसी ने लॉग-इन नहीं किया, इसलिए LaunchAgent शुरू ही नहीं हुआ। ऑटोमैटिक लॉग-इन जाँचें।
- SSH पर
launchctl bootstrapdomain एरर के साथ फ़ेल होता है। इसे डेस्कटॉप पर Terminal से चलाएँ। GitLab और Buildkite भी अपने LaunchAgent के लिए यही एरर बताते हैं। - एजेंट शुरू होने से मना करता है और Java का ज़िक्र करता है। Jenkins लॉन्च पर Java वर्ज़न जाँचता है। सपोर्ट पॉलिसी वाला वर्ज़न इस्तेमाल करें।
xcodebuild: error: Existing file at -resultBundlePath। workspace बिल्ड के बीच बना रहता है। Jenkinsfile की तरह पहले पुराना bundle मिटाएँ।
डेडिकेटेड Mac क्यों मदद करता है
permanent एजेंट बिल्ड के बीच अपना workspace और Xcode का DerivedData रखता है। इसका फ़ायदा इन्क्रीमेंटल बिल्ड में मिलता है। हमारे बेंचमार्क में हमने Xcode 26.6 पर Wikipedia iOS ऐप इस्तेमाल किया, 3 रन का मीडियन। छोटा बदलाव वॉर्म M6 पर 27 सेकंड में फिर बना। नए GitHub-होस्टेड macos-26 रनर को उसी जॉब में 269 सेकंड लगे।
कब इसकी ज़रूरत नहीं
अगर आप पहले से Jenkins नहीं चलाते, तो एक iOS ऐप के लिए शुरू न करें। Mac रनर वाली होस्टेड सेवा को संभालना कम काम है। GitHub की $0.062 प्रति macOS मिनट की दर पर (सितंबर 2026 में जाँची), फ़्लैट $139 वाला Mac महीने में लगभग 2,242 मिनट के बाद फ़ायदे में आता है। उससे नीचे मीटर वाला सस्ता है। अगर आपका controller सिर्फ़ तय IP से एजेंट स्वीकार करता है, तो भी MacRun सही नहीं है। हमारे पास स्टैटिक IP नहीं है।
हमारा दूसरे CI सिस्टम वाला पेज MacRun Mac पर Jenkins को छोटे में बताता है। iOS CI/CD पाइपलाइन गाइड बताती है कि stages में क्या रखें।
अक्सर पूछे जाने वाले सवाल
Jenkins Mac एजेंट को SSH इस्तेमाल करना चाहिए या inbound launch?
+
inbound तब काम करता है, जब Mac controller तक पहुँच सके पर controller, Mac तक नहीं। -webSocket के साथ इसे controller तक सिर्फ़ HTTPS चाहिए।
macOS पर Jenkins एजेंट को कौन सा Java वर्ज़न चाहिए?
+
वही फ़ैमिली जो controller को चाहिए। अक्टूबर 2026 में पढ़ी गई Jenkins की सपोर्ट पॉलिसी LTS 2.555.1 से आगे Java 21 या 25 माँगती है।
Mac एजेंट पर कितने executor होने चाहिए?
+
एक से शुरू करें। Jenkins हर नोड पर एक executor को सबसे सुरक्षित सेटिंग कहता है, और Xcode बिल्ड पहले ही हर कोर इस्तेमाल करता है।
रीस्टार्ट के बाद मेरा Jenkins Mac एजेंट फिर क्यों नहीं जुड़ता?
+
LaunchAgent तभी चलता है, जब उसका यूज़र लॉग-इन करे। उस अकाउंट के लिए ऑटोमैटिक लॉग-इन चालू करें और plist को ~/Library/LaunchAgents में रखें।