Bundle do binário opencode
Este documento explica como o sidecar sst/opencode
é empacotado dentro do Kognar Platform e o que o reviewer humano precisa
validar antes de assinar/notarizar o DMG distribuído.
Task de origem:
.ksdd/tasks/feature-opencode-coding-agent/002-bundle-opencode-binary.mdSpike upstream:.planning/spikes/opencode-litellm-compat.md
1. Versão alvo e modelo de distribuição
| Item | Valor |
|---|---|
| Versão pinada | v1.15.10 |
| Asset usado (arm64) | opencode-darwin-arm64.zip |
| Asset usado (x64) | opencode-darwin-x64.zip |
| Formato | Mach-O single-file (Bun-compiled standalone) |
| Tamanho zip (arm64) | ~35 MB |
| Tamanho descompactado (arm64) | ~102 MB |
| Assinatura upstream | adhoc — precisa re-sign na build Kognar |
| Licença | MIT — atribuir em "About" (task 017) |
A versão é pinada em dois lugares que precisam ficar em sincronia:
scripts/fetch-opencode.mjs→ constanteOPENCODE_VERSION+ mapCHECKSUMSelectron/utils/opencodeBinary.ts→ constanteOPENCODE_VERSION
Ao bumpar a versão:
- Atualize
OPENCODE_VERSIONnos dois arquivos. - Atualize os hashes SHA256 em
CHECKSUMS(encontre-os emhttps://github.com/sst/opencode/releases/tag/vX.Y.Z). - Rode
npm run fetch:opencode -- --forcelocalmente e teste smoke (opencode --version).
2. Como o bundle funciona
2.1 Em dev (npm run dev)
npm install→postinstallchamascripts/fetch-opencode.mjs.- Script detecta
darwin-arm64, baixa o zip do GitHub Releases, verifica SHA256, descompacta pararesources/opencode/bin/opencode, marca +x, escreveresources/opencode/.versionpara cache. electron/utils/opencodeBinary.ts → getOpencodeBinaryPath()resolve para<repoRoot>/resources/opencode/bin/opencodequandoapp.isPackaged === false.resources/opencode/está em.gitignore— nunca é commitado.
Se você precisar forçar re-download:
npm run fetch:opencode -- --force
2.2 No build de produção (npm run package:mac:arm64)
- O script de package chama
npm run fetch:opencodeantes do electron-builder rodar, garantindo queresources/opencode/bin/opencodeexiste localmente. - electron-builder copia o diretório para o
.appviabuild.extraResources(configurado empackage.json), com filtrobin/**/*— o.versionfica fora do bundle. - Layout final no
.app:Kognar Platform.app/Contents/Resources/opencode/bin/opencode - O hook
scripts/afterPack-opencode.cjsroda automaticamente entre o copy e o code-sign do bundle externo:- Strip da assinatura ad-hoc do upstream.
- Re-sign com a Developer ID identity resolvida pelo electron-builder.
- Aplica
--options runtime+ as mesmas entitlements do app principal. - Verifica com
codesign --verify --strict.
electron/utils/opencodeBinary.ts → getOpencodeBinaryPath()resolve paraprocess.resourcesPath + '/opencode/bin/opencode'quandoapp.isPackaged === true.
Se a Developer ID não estiver disponível (build local sem certs), o hook emite um warning e segue — o
.appproduzido funciona mas não passa notarization.
2.3 Entitlements
build/entitlements.mac.plist ganhou duas entradas necessárias para o Bun
runtime embutido no binário:
| Entitlement | Por quê |
|---|---|
cs.disable-library-validation | Bun carrega libs do sistema com Team ID diferente da Apple/da Kognar |
cs.allow-dyld-environment-variables | Loader do Bun ajusta DYLD_* na boot |
allow-jit e allow-unsigned-executable-memory já estavam presentes para o
Electron — também são exigidos pelo Bun.
3. Como o reviewer humano valida o DMG
Estes passos exigem certs Apple Developer + API key de notarization, então não podem ser executados pelo agente automatizado.
# 1. Build full com sign + notarize (já configurado em scripts/deploy.mjs)
CSC_LINK=... CSC_KEY_PASSWORD=... \
APPLE_ID=... APPLE_APP_SPECIFIC_PASSWORD=... APPLE_TEAM_ID=... \
npm run package:mac:arm64
# 2. Confirmar que o binário está no DMG
hdiutil attach release/Kognar\ Platform-*.dmg
ls -lh "/Volumes/Kognar Platform/Kognar Platform.app/Contents/Resources/opencode/bin/opencode"
# → esperado: -rwxr-xr-x ... ~102M ... opencode
# 3. Verificar signature do binário interno
codesign -dvv "/Volumes/Kognar Platform/Kognar Platform.app/Contents/Resources/opencode/bin/opencode"
# → esperado: TeamIdentifier=<Kognar Team ID>, Signature=<DeveloperID>, NÃO 'adhoc'
# 4. Verificar bundle inteiro
codesign --verify --deep --strict --verbose=2 "/Volumes/Kognar Platform/Kognar Platform.app"
# → esperado: valid on disk + satisfies its Designated Requirement
# 5. spctl (Gatekeeper)
spctl -a -vv "/Volumes/Kognar Platform/Kognar Platform.app"
# → esperado: accepted, source=Notarized Developer ID
# 6. Cleanup
hdiutil detach "/Volumes/Kognar Platform"
Se qualquer passo falhar, recorra à seção 5 ("Troubleshooting").
4. Impacto no tamanho do DMG
Medição feita após o primeiro npm run fetch:opencode em darwin-arm64:
| Componente | Tamanho |
|---|---|
resources/opencode/bin/opencode (descompactado) | ~102 MB |
| Adicionado ao DMG (após compressão LZFSE do hdiutil) | ~40–50 MB esperado |
O alerta da FEATURE §11 ("se passar de +50 MB, avaliar download lazy") fica no limite. Acompanhar o tamanho real do DMG após a primeira build de release e registrar em PR (se ultrapassar significativamente, abrir tarefa de v2 para download lazy on first run, conforme FEATURE §2.2).
5. Troubleshooting
Notarization falha com The binary is not signed with a valid Developer ID certificate
O hook afterPack-opencode.cjs não encontrou a identity. Confirme uma das:
CSC_LINK+CSC_KEY_PASSWORDno env (electron-builder convention).CSC_NAMEapontando para a identity exata no keychain.- Identity já importada no keychain de login e visível com
security find-identity -v -p codesigning.
npm run fetch:opencode falha com checksum mismatch
GitHub provavelmente reempacotou o asset (raro). Compare o hash atual em
https://github.com/sst/opencode/releases/tag/v1.15.10 e atualize
CHECKSUMS em scripts/fetch-opencode.mjs. Não desative a verificação.
npm run fetch:opencode em CI Linux/Windows pula o download
Esperado — o feature é macOS-only em v1 (FEATURE §2.2). O script imprime
warning e exits 0 para não quebrar npm install em runners não-mac.
Em runtime: isOpencodeAvailable() retorna false mesmo após install
Verifique:
ls -l resources/opencode/bin/opencode # dev
ls -l /Applications/Kognar\ Platform.app/Contents/Resources/opencode/bin/opencode # prod
Permissões devem incluir x para o owner. Em dev, rerode
npm run fetch:opencode -- --force.