Pular para o conteúdo
Development

Colocando o pipeline de release do Greenhouse pra rodar no Mac Mini de uma câmera de segurança

Por Victor Da Luz
tauricimacosinfrastructuredev-loggreenhouse

O Greenhouse precisava de um pipeline de release: dar push numa tag, receber um .dmg assinado. A documentação do Tauri faz isso parecer um trabalho de cinco minutos - adicione um workflow do GitHub Actions, aponte para macos-latest, pronto. Só que eu não rodo runners hospedados pelo GitHub aqui. Tudo no meu homelab roda em hardware que eu possuo, incluindo o CI.

Essa única restrição transformou um trabalho de cinco minutos num projeto de infraestrutura de verdade e, no caminho, numa história genuína de debugging.

Achando um Mac que não estava fazendo nada

Compilar um app macOS exige um Mac de verdade. Eu já tinha um do lado do servidor: um Mac Mini rodando o Frigate, o NVR de detecção de objetos da minha câmera de segurança. Antes de mexer nele, conferi a carga real, em vez de simplesmente presumir que havia folga - e não havia. 75-80% de CPU sustentado nos 8 núcleos, continuamente, por causa do trabalho de detecção em tempo real. Adicionar um compilador Rust em cima disso é uma disputa de recursos de verdade, não hipotética.

Eu não tinha um segundo Mac ocioso sobrando, então fiz uma troca deliberada em vez de fingir que a disputa não existia: registrei o build runner na mesma máquina, mas limitei sua prioridade de agendamento (nice mais um tipo de processo em background) para que ele ceda CPU ao sistema de câmeras sob carga. Releases são eventos raros, disparados por tag - builds ocasionalmente mais lentos são um preço justo por não comprar hardware novo.

O build que levou dezesseis minutos para falhar

Primeira execução de teste de verdade. Dei push numa tag, vi o job ser pego, vi o cargo compilar toda a árvore de dependências do Tauri a partir de um cache frio - tauri, wry, rusqlite empacotado a partir de fonte C, tudo. Dez minutos de compilação legítima. Depois chegou na etapa de empacotar o .dmg de fato e simplesmente parou ali. Seis minutos depois: Finder got an error: AppleEvent timed out.

Acontece que o empacotador de DMG do Tauri não apenas compacta arquivos num disk image - ele comanda o Finder via AppleScript para definir onde o ícone do app e o atalho da pasta Applications devem ficar na janela do instalador. Isso exige uma concessão de permissão única, o tipo de diálogo que o macOS abre perguntando “Allow Terminal to control Finder?”. Numa máquina em que ninguém está sentado na frente, não há quem clique em Allow. O diálogo nunca renderiza ou expira sem resposta, e o build inteiro falha.

Reproduzi o mesmo erro exato na minha própria máquina interativa primeiro, especificamente para descartar “sem sessão gráfica” como causa - e mesmo assim falhou, com um Finder ativo e um login de verdade. Isso me mostrou que não tinha nada a ver com ser headless. Era um gate de consentimento único, não uma limitação fundamental de capacidade.

A correção que não corrigiu nada

O remédio documentado é definir CI=true, que diz ao empacotador do Tauri para adicionar uma flag que pula a etapa de AppleScript inteiramente. Eu defini. Não funcionou. Mesmo timeout, mesma falha, aos dezesseis minutos.

Em vez de chutar de novo, fui ler o código-fonte de verdade - tanto o código Rust do empacotador quanto a GitHub Action em JavaScript que o envolve, não posts de blog descrevendo isso de segunda mão. A lógica do empacotador era exatamente o que eu esperava: pular a etapa de AppleScript quando CI for true. Mas a Action que eu usava para conduzir o build inteiro tinha opinião própria. Ela define uma segunda variável, mais específica, que sobrescreve silenciosamente a minha primeira, justamente para que builds nos runners Mac hospedados pelo próprio GitHub - que têm permissões de Automation funcionando de verdade - mantenham seu layout de ícones mais bonito. Um default razoável para o caso comum. Exatamente errado para o meu.

Verifiquei a correção de fato do jeito chato e confiável antes de confiar nela no CI: entrei via SSH na máquina de build, rodei o mesmo comando de build à mão com as duas variáveis definidas corretamente, e vi um .dmg real, válido e assinado sair do outro lado. Só depois disso dei push numa segunda tag de teste pelo pipeline de verdade. Dezesseis minutos depois: um build verde, um release rascunho no GitHub, e um instalador funcionando, produzido por um Mac Mini que passa os outros 99% do tempo vigiando meu quintal atrás de guaxinins.

O que eu diria pra quem esbarrar nisso

Se o seu build macOS do Tauri der timeout na etapa do .dmg com um erro de AppleEvent, e você já estiver construindo através do tauri-apps/tauri-action, definir só CI=true não vai salvar você. Também é preciso remover explicitamente TAURI_BUNDLER_DMG_IGNORE_CI. Encontrei uma nota já existente na minha própria base de conhecimento, de uma versão anterior e menor desse mesmo problema - escrita meses atrás, antes de eu ter achado a correção de verdade - que só dizia “faça o build sem DMG, ou faça num desktop de verdade.” Não estava exatamente errada. Só parava uma camada antes da resposta real. Voltei lá e corrigi em vez de escrever uma nota nova do lado da antiga, incompleta.

Leitura relacionada

Development

O scan que se ofereceu para importar a si mesmo

Um scanner de importação que encontrou a própria estrutura interna do app, e depois voltou a oferecer uma pasta que tinha acabado de importar, duas versões da mesma conversa que faltava entre camadas.

Ler
Development

A pasta que ficou parada

Uma revisão de código encontrou um invariante que a feature de adotar/importar do Greenhouse quebrava silenciosamente, e a correção que tornou tudo chato de novo.

Ler

Você também pode achar útil

AdGuard

AdGuard para iOS

Bloqueio de anúncios e rastreadores em todo o sistema no iOS, sem necessidade de um servidor DNS separado.

Como afiliado da AdGuard, ganho com compras qualificadas.

Saiba mais
Proton

Proton Mail

E-mail criptografado de ponta a ponta, com arquitetura de acesso zero.

Como parceiro da Proton, ganho com compras qualificadas dos serviços de privacidade e segurança da Proton (Pass, Mail, VPN, Drive).

Saiba mais
Proton

Proton Pass

Gerenciador de senhas focado em privacidade, da equipe por trás do Proton Mail.

Como parceiro da Proton, ganho com compras qualificadas dos serviços de privacidade e segurança da Proton (Pass, Mail, VPN, Drive).

Saiba mais