Guia

Como adicionar um Mac como agente Jenkins para builds de iOS

Para adicionar um Mac ao Jenkins, crie um nó permanente que se conecta ao controller. No Mac, instale o Java 21 e rode o agent.jar com a opção -webSocket. Coloque esse comando em um LaunchAgent e ative o login automático, para que o agente se reconecte depois de reinicializar. Depois dê ao nó o rótulo macos e mande para ele as suas etapas de iOS.

O que você precisa antes

  • Um controller Jenkins que o Mac consiga acessar por HTTPS.
  • Um Mac com Apple silicon, acesso de administrador, Homebrew e Xcode.
  • O Java certo. A política de suporte a Java do Jenkins, lida em outubro de 2026, diz que o LTS 2.555.1 e os mais novos precisam de Java 21 ou 25. Essa regra vale também para os agentes, não só para o controller.
  • Uma sessão na área de trabalho do Mac para os últimos passos, por compartilhamento de tela.

1. Instale o Java no Mac

brew install openjdk@21
/opt/homebrew/opt/openjdk@21/bin/java -version

O Homebrew instala esse JDK como keg-only, então ele não fica no seu PATH. Use o caminho completo acima em todo lugar. Isso também mantém o agente no Java 21 quando um JDK mais novo chegar.

2. Crie o nó no controller

  • Abra Manage Jenkins, depois Nodes, depois New Node. Escolha Permanent Agent.
  • Number of executors: 1. A própria documentação do Jenkins diz que um executor por nó é a configuração mais segura. Builds do Xcode já usam todos os núcleos.
  • Remote root directory: /Users/YOUR-USER/jenkins.
  • Labels: macos xcode.
  • Usage: only build jobs with label expressions matching this node. Isso mantém os jobs de Linux fora do seu Mac.
  • Launch method: Launch agent by connecting it to the controller.

Salve. A página do nó agora mostra o comando de execução, o nome do agente e um segredo hexadecimal longo. O segredo está ligado ao nome do agente. Se ele vazar, o Jenkins diz para não reutilizar esse nome.

3. Baixe o agent.jar e teste manualmente

O Jenkins fornece o agent.jar certo para o seu controller em /jnlpJars/agent.jar. Guarde o segredo em um arquivo e passe com @, para que ele nunca apareça em uma lista de processos.

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

A página do nó deve mudar para conectado. Aperte Control C para parar. Com -webSocket, o agente abre uma única conexão HTTPS. Sem essa opção, o agente também precisa da porta TCP inbound separada do controller.

4. Rode o agente com um LaunchAgent

O launchd inicia o agente no login e reinicia o agente se ele sair. Rode isto no Terminal na área de trabalho do Mac. O heredoc preenche o caminho da sua pasta pessoal.

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

Conferimos este plist com plutil -lint. Por que um LaunchAgent e não um LaunchDaemon? Um daemon roda fora de qualquer sessão de login. Os simuladores e o keychain de login ficam dentro de uma. O GitLab e o Buildkite dão o mesmo motivo para os seus agentes Mac.

Deixe o plist em ~/Library/LaunchAgents. No macOS 27 vimos um plist em /Library/LaunchAgents quebrar o login automático em toda inicialização. Os detalhes estão em login automático em um Mac sem monitor.

5. Faça ele sobreviver a uma reinicialização

Ative o login automático para a conta do agente em System Settings, em Users and Groups. Impeça o Mac de entrar em repouso. Depois reinicialize e acompanhe a página do nó.

sudo pmset -a sleep 0
sudo shutdown -r now
# then, after it is back:
tail -n 20 ~/jenkins/agent.log

6. Um Jenkinsfile com uma etapa de iOS

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
        }
    }
}

Um result bundle é uma pasta, então compactamos com ditto antes de arquivar. Abra o zip em qualquer Mac e dê dois cliques no bundle para ver no Xcode.

Duas versões do Xcode em um agente? Escolha uma por pipeline com DEVELOPER_DIR. A man page do xcode-select da Apple diz que essa variável substitui a escolha do sistema sem alterá-la. Defina em um bloco environment, para não afetar outros jobs no Mac:

environment {
    DEVELOPER_DIR = '/Applications/Xcode.app/Contents/Developer'
}

Coloque esse bloco dentro de pipeline, ao lado de agent. Mudar a configuração global com sudo xcode-select -s também funciona, mas muda todos os jobs da máquina de uma vez.

Erros comuns e soluções

  • O agente conecta e some depois de reinicializar. Ninguém entrou, então o LaunchAgent nunca iniciou. Confira o login automático.
  • O launchctl bootstrap falha com erro de domínio por SSH. Rode no Terminal na área de trabalho. O GitLab e o Buildkite documentam o mesmo erro para os seus LaunchAgents.
  • O agente não inicia e fala de Java. O Jenkins confere a versão do Java ao iniciar. Use a versão da política de suporte.
  • xcodebuild: error: Existing file at -resultBundlePath. O workspace continua entre builds. Apague o bundle antigo antes, como no Jenkinsfile.

Por que um Mac dedicado ajuda

Um agente permanente mantém o workspace e o DerivedData do Xcode entre builds. A recompensa são os builds incrementais. No nosso benchmark usamos o app iOS da Wikipedia no Xcode 26.6, com a mediana de 3 execuções. Uma pequena mudança foi recompilada em 27 segundos em um M6 aquecido. Um runner macos-26 novo hospedado pelo GitHub levou 269 segundos para o mesmo job.

Quando você não precisa disso

Se você ainda não usa Jenkins, não comece por causa de um app iOS. Um serviço hospedado com runners Mac dá menos manutenção. Com a tarifa do GitHub de $0.062 por minuto de macOS (conferida em setembro de 2026), um Mac com preço fixo de $139 se paga depois de cerca de 2,242 minutos por mês. Abaixo disso, pagar por minuto sai mais barato. A MacRun também não serve se o seu controller só aceita agentes de um IP fixo. Não temos IP fixo.

Nossa página de outros sistemas de CI resume o Jenkins em um Mac da MacRun. O guia de pipeline de CI/CD para iOS mostra o que colocar nas etapas.

Perguntas frequentes

Um agente Mac do Jenkins deve usar SSH ou conexão inbound?

+

A conexão inbound funciona quando o Mac acessa o controller, mas o contrário não. Com -webSocket ela só precisa de HTTPS até o controller.

Qual versão do Java um agente Jenkins no macOS precisa?

+

A mesma família que o controller precisa. A política de suporte do Jenkins, lida em outubro de 2026, exige Java 21 ou 25 a partir do LTS 2.555.1.

Quantos executors um agente Mac deve ter?

+

Comece com um. O Jenkins diz que um executor por nó é a configuração mais segura, e um build do Xcode já usa todos os núcleos.

Por que meu agente Mac do Jenkins não se reconecta depois de reiniciar?

+

Um LaunchAgent só roda depois que o usuário entra. Ative o login automático para essa conta e deixe o plist em ~/Library/LaunchAgents.

Guias relacionados