Desenvolvimento iOS com Servidores Mac Remotos de Build

Compilar um app iOS exige um macOS em algum ponto do fluxo, mas isso não significa que seu editor precisa rodar em um Mac. Com o suporte a Remote-SSH do Cursor, você pode manter uma experiência de edição local rápida no Windows ou Linux enquanto uma máquina Mac cuida da compilação, da assinatura e das execuções no simulador. Este guia percorre as opções de arquitetura, a configuração, os comandos de build e as armadilhas que mais consomem tempo.
Por Que um Mac É Necessário
A cadeia de ferramentas da Apple só roda no macOS, e várias partes do fluxo de trabalho iOS dependem dela diretamente.
- Assinatura de código: as identidades de assinatura vivem no keychain do macOS e são usadas para produzir artefatos assinados.
- Perfis de provisionamento: os perfis que vinculam um app ID a dispositivos e certificados são gerenciados pela ferramenta da Apple.
- iOS Simulator: o Simulator é um aplicativo macOS; não há equivalente suportado no Linux ou Windows para fidelidade real de dispositivo.
- Cadeia de ferramentas xcodebuild: o compilador, o linker e os SDKs vêm dentro do Xcode no macOS.
- Envio à App Store: o upload para o App Store Connect exige a ferramenta da Apple, tipicamente via Transporter ou
altool/notarytoolno macOS.
Como todos esses itens são presos ao macOS, a pergunta prática não é se você precisa de um Mac, mas onde esse Mac vive.
Opções de Arquitetura
Não há uma única resposta melhor; a escolha certa depende do tamanho da sua equipe, do seu orçamento e da frequência com que você compila.
| Opção | Como funciona | Prós | Contras |
|---|---|---|---|
| Remote-SSH em um Mac | O Cursor se conecta via SSH; arquivos e terminal vivem no Mac | Fonte única da verdade, acesso completo à cadeia de ferramentas, Simulator real | Precisa de um Mac persistente e acessível |
| rsync / git push para um Mac | Edite localmente, envie para o Mac, compile lá | Funciona offline para edição, modelo mental simples | Deriva de sincronização, transferências grandes lentas, sem LSP ao vivo |
| Runners macOS de CI na nuvem | Envie para o Git, o CI compila em um runner macOS hospedado | Nenhum hardware para possuir, reproduzível, bom para releases | Não interativo, custo por minuto, partidas a frio |
| Provedores de Mac na nuvem | Alugue um Mac (MacStadium, AWS EC2 Mac) via SSH | Hardware real sob demanda, escalável | Custo por hora, sobrecarga de provisionamento e desmontagem |
Para o desenvolvimento do dia a dia, o Remote-SSH em um Mac costuma ser o melhor equilíbrio. O CI na nuvem se encaixa em pipelines de release. Provedores de Mac na nuvem se encaixam em equipes que querem uma frota sem possuir bancadas.
Use Remote-SSH para desenvolvimento interativo, CI na nuvem para releases e instâncias Mac na nuvem para escala ou surtos curtos. A maioria das equipes maduras usa pelo menos duas dessas opções, em vez de forçar uma única opção a fazer tudo.
Configurando um Mac Remoto
Estas etapas são executadas uma vez no Mac que atuará como servidor de build. Você precisa de direitos de admin e de uma conexão de rede estável.
1. Instale o Xcode e as Command Line Tools
Instale o Xcode pela Mac App Store e depois instale as command line tools para que xcodebuild e binários relacionados fiquem disponíveis:
xcode-select --install
# Point the toolchain at the full Xcode app, not the standalone CLT
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
2. Aceite a Licença
Em uma instalação nova, o xcodebuild se recusa a rodar até que a licença seja aceita:
sudo xcodebuild -license accept
3. Crie um Usuário de Build
Evite compilar como uma conta pessoal. Crie um usuário dedicado para que credenciais e keychains fiquem isolados:
# Create a build user (adjust the short name and full name)
sudo dscl . -create /Users/builduser
sudo dscl . -create /Users/builduser UserShell /bin/zsh
sudo dscl . -create /Users/builduser RealName "Build User"
sudo dscl . -create /Users/builduser UniqueID 510
sudo dscl . -create /Users/builduser PrimaryGroupID 20
sudo dscl . -create /Users/builduser NFSHomeDirectory /Users/builduser
sudo passwd builduser
4. Ative o Login Remoto
Ligue o SSH para que o Mac aceite conexões:
sudo systemsetup -setremotelogin on
5. Instale a Chave Pública SSH
Adicione sua chave pública local ao authorized_keys do usuário de build para que você possa se conectar sem senha:
mkdir -p /Users/builduser/.ssh
# Paste your public key (id_ed25519.pub) into authorized_keys
chmod 700 /Users/builduser/.ssh
chmod 600 /Users/builduser/.ssh/authorized_keys
chown -R builduser:staff /Users/builduser/.ssh
Um Mac de build exposto à internet não deve permitir login por senha para o usuário de build. Desative a autenticação por senha, restrinja os endereços de origem permitidos quando possível e mantenha a chave privada apenas na sua máquina local.
Conectando o Cursor via Remote-SSH
Assim que o Mac estiver acessível, conecte o Cursor a ele. O remoto passa a ser o ambiente onde extensões, terminais e builds são executados.
1. Instale a Extensão Remote-SSH
Abra o Cursor, vá para a visão de Extensões (Ctrl+Shift+X) e instale Remote - SSH (a extensão empacotada pela Microsoft está disponível no marketplace do Cursor).
2. Adicione uma Entrada no Config SSH
Edite seu config SSH local, tipicamente em ~/.ssh/config, e adicione um alias de host:
# ~/.ssh/config
Host mac-build
HostName 203.0.113.10
User builduser
IdentityFile ~/.ssh/id_ed25519
ServerAliveInterval 30
ServerAliveCountMax 4
TCPKeepAlive yes
As configurações de keep-alive reduzem desconexões em redes instáveis. Observe que o OpenSSH nativo do macOS não suporta mosh, então os keep-alives são sua primeira linha de defesa.
3. Conecte
Abra a Paleta de Comandos (Ctrl/Cmd+Shift+P) e execute Remote-SSH: Connect to Host, depois escolha mac-build. Como alternativa, execute em uma única etapa:
cursor --remote ssh-remote+mac-build /Users/builduser/projects/MyiOSApp
4. A Janela É Recarregada
O Cursor recarrega em uma janela remota. A barra de status mostra o nome do host remoto. Todos os terminais, tarefas e servidores de linguagem agora são executados no Mac, enquanto a interface e a renderização permanecem locais.
5. As Extensões São Instaladas no Remoto
Quando você abre um arquivo Swift, o Cursor solicita que você instale as extensões de linguagem no remoto. Aceite isso. A ferramenta Swift, os adaptadores de depuração e a extensão Swift pertencem ao Mac, não à sua máquina local.
Extensões apenas de interface, como temas, rodam localmente. Extensões que iniciam processos, incluindo o servidor de linguagem Swift e qualquer adaptador de depuração, rodam no Mac remoto. O Cursor gerencia a divisão para você se você deixar.
Editando Localmente e Compilando Remotamente
O padrão produtivo é: editar no Cursor, executar os builds no terminal integrado, que é um terminal no Mac. Os arquivos sempre vivem no Mac, então não há etapa de sincronização para derivar.
Build Básico
Compile um scheme para o destino genérico iOS, o que evita precisar de um dispositivo específico:
xcodebuild \
-project MyApp.xcodeproj \
-scheme MyApp \
-configuration Debug \
-destination 'generic/platform=iOS' \
build
Se você usa um workspace em vez de um project, troque -project por -workspace:
xcodebuild -workspace MyApp.xcworkspace -scheme MyApp -destination 'generic/platform=iOS' build
Arquivando para Release
Arquivar produz um .xcarchive que a etapa de exportação converte em um .ipa para distribuição:
xcodebuild \
-workspace MyApp.xcworkspace \
-scheme MyApp \
-configuration Release \
-destination 'generic/platform=iOS' \
-archivePath build/MyApp.xcarchive \
archive
Exportando o Arquivo
Após arquivar, exporte usando um plist de opções de exportação que descreve o método de distribuição:
xcodebuild -exportArchive \
-archivePath build/MyApp.xcarchive \
-exportPath build/export \
-exportOptionsPlist ExportOptions.plist
Um ExportOptions.plist para distribuição no estilo App Store se parece mais ou menos com isto:
<?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>method</key>
<string>app-store-connect</string>
<key>teamID</key>
<string>ABCDE12345</string>
<key>signingStyle</key>
<string>automatic</string>
<key>uploadSymbols</key>
<true/>
</dict>
</plist>
Compilar para generic/platform=iOS evita vincular o build a um UDID específico de dispositivo ou simulador, o que faz com que o mesmo comando funcione em qualquer máquina de build sem mudanças de configuração.
Executando Testes e Lendo Logs
Os testes são executados da mesma forma que os builds, apenas com a ação test e um destino concreto.
Executando Testes
xcodebuild test \
-workspace MyApp.xcworkspace \
-scheme MyApp \
-destination 'platform=iOS Simulator,name=iPhone 16,OS=latest'
Tornando os Logs Legíveis
A saída bruta do xcodebuild é enorme e difícil de ler. Envie-a por um formatador para reduzi-la a linhas legíveis:
# xcbeautify: modern, fast formatter
xcodebuild test -workspace MyApp.xcworkspace -scheme MyApp \
-destination 'platform=iOS Simulator,name=iPhone 16' | xcbeautify
# xcpretty: older but widely available alternative
xcodebuild test -workspace MyApp.xcworkspace -scheme MyApp \
-destination 'platform=iOS Simulator,name=iPhone 16' | xcpretty
Para um arquivo de log persistente mais um stream legível, use tee na saída:
xcodebuild build -scheme MyApp -destination 'generic/platform=iOS' \
2>&1 | tee build.log | xcbeautify
Como o terminal é remoto, o arquivo de log fica no Mac. Leia-o na mesma janela remota com o explorador de arquivos do Cursor, ou transmita-o localmente com uma segunda sessão SSH se preferir.
Capturando Capturas de Tela do Simulador
Como o Simulator roda no Mac, você o captura com xcrun simctl por SSH em vez de tirar uma captura de tela local.
Liste e Inicie um Simulador
# List available devices
xcrun simctl list devices available
# Boot a specific simulator by UDID or name
xcrun simctl boot "iPhone 16"
# Open the Simulator UI if a desktop session is available
open -a Simulator
Instale e Inicie Seu App
# Install a built .app onto the booted simulator
xcrun simctl install booted ./build/Build/Products/Debug-iphonesimulator/MyApp.app
# Launch it by bundle identifier
xcrun simctl launch booted com.example.MyApp
Capture uma Captura de Tela
# Save a screenshot to a file on the Mac
xcrun simctl io booted screenshot /Users/builduser/shots/home.png
Como esse arquivo está no remoto, abra-o diretamente na janela remota do Cursor. A visualização de imagens funciona por Remote-SSH, então você não precisa copiá-lo para baixo.
O simctl pode iniciar um simulador sem interface gráfica, mas alguns testes orientados por interface e cenários de captura de tela mais ricos esperam uma sessão de desktop com login ativo. Em um Mac headless, mantenha uma sessão persistente com login ativo ou espere limitações nos recursos de interface do Simulator.
Assinatura de Código
A assinatura é a parte mais propensa a falhar em uma máquina nova, então configure-a com cuidado.
Certificados no Keychain
As identidades de assinatura vivem no keychain do usuário de build. Importe um .p12 exportado da sua conta de desenvolvedor Apple:
security import /path/to/distribution.p12 \
-k /Users/builduser/Library/Keychains/login.keychain-db \
-P "p12-password" \
-T /usr/bin/codesign \
-T /usr/bin/security
As flags -T concedem acesso do codesign e do security à identidade importada, para que os builds não parem em um prompt de acesso.
Perfis de Provisionamento
Coloque os perfis onde o Xcode espera encontrá-los para o usuário de build:
mkdir -p /Users/builduser/Library/MobileDevice/Provisioning\ Profiles
cp ~/profiles/MyApp_AppStore.mobileprovision \
/Users/builduser/Library/MobileDevice/Provisioning\ Profiles/
Assinatura Automática vs Manual
- Assinatura automática: mais simples, exige que o usuário de build esteja conectado a uma conta de desenvolvedor Apple e tenha acesso de rede aos serviços da Apple. Boa para um único Mac confiável.
- Assinatura manual: mais reproduzível para CI. Você especifica
CODE_SIGN_STYLE=Manuale aponta para uma identidade e um perfil específicos, evitando comportamento dependente de conta.
xcodebuild archive \
-workspace MyApp.xcworkspace \
-scheme MyApp \
-destination 'generic/platform=iOS' \
CODE_SIGN_STYLE=Manual \
CODE_SIGN_IDENTITY="Apple Distribution: Example Corp (ABCDE12345)" \
PROVISIONING_PROFILE_SPECIFIER="MyApp AppStore Profile" \
-archivePath build/MyApp.xcarchive
Em um Mac sem supervisão, desbloqueie o keychain de login antes de compilar, para que o codesign consiga alcançar a identidade sem um prompt gráfico. Mantenha a senha de desbloqueio em um cofre de segredos, nunca em um script commitado.
Comparação de Custos
Os preços mudam, então trate o que segue como faixas aproximadas para planejamento, e não como cotações atuais.
| Opção | Custo mensal aproximado | Observações |
|---|---|---|
| Possuir um Mac mini como servidor de build | Compra do hardware e depois quase zero recorrente | Mais barato em uso constante; você cuida da manutenção |
| Provedor de Mac na nuvem (MacStadium, EC2 Mac) | Por hora, muitas vezes algumas centenas por mês em uso 24/7 | Escala para cima ou para baixo; sobrecarga de provisionamento |
| Runners macOS de CI na nuvem | Por minuto, cobrado apenas durante o build | Mais barato para builds ocasionais; partidas a frio atrapalham |
| rsync / git para um Mac compartilhado | Apenas o custo do Mac | Bom para indivíduos, desajeitado para equipes |
Um Mac mini dedicado normalmente vence para uma equipe pequena que compila ao longo do dia. O CI na nuvem vence quando os builds são infrequentes ou estritamente na hora do release. Instâncias Mac na nuvem vencem quando você precisa de capacidade sob demanda e não quer possuir hardware.
Armadilhas Comuns
A maior parte da dor do build remoto vem de um pequeno conjunto de erros recorrentes. Verifique estes antes de depurar qualquer coisa exótica.
Licença Não Aceita
O xcodebuild falha imediatamente com um erro de licença. Corrija uma vez com:
sudo xcodebuild -license accept
Versão Errada do Xcode Selecionada
Múltiplas instalações do Xcode confundem a cadeia de ferramentas. Selecione a pretendida explicitamente:
sudo xcode-select -s /Applications/Xcode-16.app/Contents/Developer
xcodebuild -version
Identidade de Assinatura Ausente
Um build que arquiva mas falha ao exportar geralmente não tem identidade ou perfil correspondente. Confirme o que o keychain realmente contém:
security find-identity -v -p codesigning
Se a lista estiver vazia para a identidade de que você precisa, importe o .p12 novamente e verifique se o perfil corresponde ao bundle identifier do app.
Sincronização Lenta
Se você estiver sincronizando código em vez de editar por SSH, prefira git a rsync. O git transfere deltas e dá histórico e tratamento de conflitos; o rsync move árvores inteiras e deriva em silêncio. Quando você edita diretamente por Remote-SSH, não há sincronização alguma, que é a opção mais rápida.
Quedas de Rede Quebrando o SSH
Builds longos em um link instável podem derrubar a conexão e matar o build. Mitigue com keep-alives de SSH no seu config e execute builds longos dentro de tmux ou screen no Mac para que sobrevivam a uma desconexão:
tmux new-session -s build 'xcodebuild archive -workspace MyApp.xcworkspace -scheme MyApp -destination "generic/platform=iOS" -archivePath build/MyApp.xcarchive'
Reconecte depois e reanexe com tmux attach -t build.
Simulador Não Rodando em Modo Headless
Alguns fluxos de teste presumem um Simulator visível. Em um Mac headless, inicie o simulador com simctl e mantenha uma sessão de desktop com login ativo ou aceite cobertura reduzida da interface do Simulator.
Se você compila por uma rede que não controla totalmente, execute todo xcodebuild longo dentro de tmux. O build continua rodando no Mac independentemente de sua sessão SSH sobreviver, e você pode reanexar para assistir à conclusão.
Builds remotos transformam uma restrição rígida de plataforma em uma escolha de fluxo de trabalho. Escolha a arquitetura que se encaixa na sua equipe, configure o Mac uma vez e deixe o Remote-SSH do Cursor manter a experiência de edição rápida enquanto o Mac faz o trabalho que o macOS exige.