Browse Source

codesign cli on macos by building it on macos

Brendan Allan 3 weeks ago
parent
commit
b0bd728275

+ 100 - 6
.github/workflows/publish.yml

@@ -67,7 +67,7 @@ jobs:
       tag: ${{ steps.version.outputs.tag }}
       repo: ${{ steps.version.outputs.repo }}
 
-  build-cli:
+  build-cli-linux-win:
     needs: version
     runs-on: blacksmith-4vcpu-ubuntu-2404
     if: github.repository == 'anomalyco/opencode'
@@ -94,17 +94,111 @@ jobs:
           OPENCODE_RELEASE: ${{ needs.version.outputs.release }}
           GH_REPO: ${{ needs.version.outputs.repo }}
           GH_TOKEN: ${{ steps.committer.outputs.token }}
+          OPENCODE_BUILD_OS: linux,win32
+          OPENCODE_SKIP_RELEASE_UPLOAD: "1"
+
+      - uses: actions/upload-artifact@v4
+        with:
+          name: opencode-cli-linux-win
+          path: packages/opencode/dist
+
+  build-cli-darwin:
+    needs: version
+    runs-on: macos-latest
+    if: github.repository == 'anomalyco/opencode'
+    steps:
+      - uses: actions/checkout@v3
+        with:
+          fetch-tags: true
+
+      - uses: ./.github/actions/setup-bun
+
+      - name: Setup git committer
+        id: committer
+        uses: ./.github/actions/setup-git-committer
+        with:
+          opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
+          opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}
+
+      - uses: apple-actions/import-codesign-certs@v2
+        with:
+          keychain: build
+          p12-file-base64: ${{ secrets.APPLE_CERTIFICATE }}
+          p12-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
+
+      - name: Resolve signing identity
+        run: |
+          CERT_INFO=$(security find-identity -v -p codesigning build.keychain | grep "Developer ID Application")
+          CERT_ID=$(echo "$CERT_INFO" | awk -F'"' '{print $2}')
+          if [ -z "$CERT_ID" ]; then
+            echo "Developer ID Application identity not found"
+            exit 1
+          fi
+          echo "CERT_ID=$CERT_ID" >> $GITHUB_ENV
+
+      - name: Build
+        id: build
+        run: |
+          ./packages/opencode/script/build.ts
+        env:
+          OPENCODE_VERSION: ${{ needs.version.outputs.version }}
+          OPENCODE_RELEASE: ${{ needs.version.outputs.release }}
+          GH_REPO: ${{ needs.version.outputs.repo }}
+          GH_TOKEN: ${{ steps.committer.outputs.token }}
+          APPLE_SIGNING_IDENTITY: ${{ env.CERT_ID }}
+          OPENCODE_BUILD_OS: darwin
+          OPENCODE_SKIP_RELEASE_UPLOAD: "1"
+
+      - name: Verify darwin signatures
+        run: |
+          for file in packages/opencode/dist/opencode-darwin-*/bin/opencode; do
+            codesign -vvv --verify "$file"
+          done
+
+      - uses: actions/upload-artifact@v4
+        with:
+          name: opencode-cli-darwin
+          path: packages/opencode/dist
+
+  build-cli-merge:
+    needs:
+      - version
+      - build-cli-linux-win
+      - build-cli-darwin
+    runs-on: blacksmith-4vcpu-ubuntu-2404
+    if: github.repository == 'anomalyco/opencode'
+    steps:
+      - uses: actions/checkout@v3
+
+      - uses: ./.github/actions/setup-bun
+
+      - name: Setup git committer
+        id: committer
+        uses: ./.github/actions/setup-git-committer
+        with:
+          opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
+          opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}
+
+      - uses: actions/download-artifact@v4
+        with:
+          pattern: opencode-cli-*
+          path: packages/opencode/dist
+          merge-multiple: true
+
+      - name: Upload CLI release assets
+        if: needs.version.outputs.release
+        run: gh release upload v${{ needs.version.outputs.version }} ./packages/opencode/dist/*.zip ./packages/opencode/dist/*.tar.gz --clobber --repo ${{ needs.version.outputs.repo }}
+        env:
+          GH_TOKEN: ${{ steps.committer.outputs.token }}
 
       - uses: actions/upload-artifact@v4
         with:
           name: opencode-cli
           path: packages/opencode/dist
-    outputs:
-      version: ${{ needs.version.outputs.version }}
 
   build-tauri:
     needs:
-      - build-cli
+      - build-cli-merge
       - version
     continue-on-error: false
     strategy:
@@ -248,7 +342,7 @@ jobs:
 
   build-electron:
     needs:
-      - build-cli
+      - build-cli-merge
       - version
     continue-on-error: false
     strategy:
@@ -372,7 +466,7 @@ jobs:
   publish:
     needs:
       - version
-      - build-cli
+      - build-cli-merge
       - build-tauri
       - build-electron
     runs-on: blacksmith-4vcpu-ubuntu-2404

+ 51 - 2
packages/opencode/script/build.ts

@@ -59,6 +59,10 @@ console.log(`Loaded ${migrations.length} migrations`)
 const singleFlag = process.argv.includes("--single")
 const baselineFlag = process.argv.includes("--baseline")
 const skipInstall = process.argv.includes("--skip-install")
+const skipUpload = process.argv.includes("--skip-release-upload") || process.env.OPENCODE_SKIP_RELEASE_UPLOAD === "1"
+const sign = process.env.APPLE_SIGNING_IDENTITY
+const entitlements = process.env.OPENCODE_CODESIGN_ENTITLEMENTS || path.join(dir, "script/entitlements.plist")
+const raw = process.env.OPENCODE_BUILD_OS?.trim()
 
 const allTargets: {
   os: string
@@ -144,6 +148,31 @@ const targets = singleFlag
     })
   : allTargets
 
+const os = raw
+  ? Array.from(
+      new Set(
+        raw
+          .split(",")
+          .map((x) => x.trim())
+          .filter(Boolean),
+      ),
+    )
+  : undefined
+
+if (os) {
+  const set = new Set(allTargets.map((item) => item.os))
+  const bad = os.filter((item) => !set.has(item))
+  if (bad.length > 0) {
+    throw new Error(`Invalid OPENCODE_BUILD_OS value: ${bad.join(", ")}`)
+  }
+}
+
+const list = os ? targets.filter((item) => os.includes(item.os)) : targets
+
+if (list.length === 0) {
+  throw new Error("No build targets selected")
+}
+
 await $`rm -rf dist`
 
 const binaries: Record<string, string> = {}
@@ -151,7 +180,7 @@ if (!skipInstall) {
   await $`bun install --os="*" --cpu="*" @opentui/core@${pkg.dependencies["@opentui/core"]}`
   await $`bun install --os="*" --cpu="*" @parcel/watcher@${pkg.dependencies["@parcel/watcher"]}`
 }
-for (const item of targets) {
+for (const item of list) {
   const name = [
     pkg.name,
     // changing to win32 flags npm for some reason
@@ -199,6 +228,24 @@ for (const item of targets) {
     },
   })
 
+  if (item.os === "darwin") {
+    if (Script.release && process.platform !== "darwin") {
+      throw new Error("darwin release binaries must be built on macOS runners")
+    }
+    if (Script.release && !sign) {
+      throw new Error("APPLE_SIGNING_IDENTITY is required for darwin release binaries")
+    }
+    if (process.platform === "darwin" && sign) {
+      if (!fs.existsSync(entitlements)) {
+        throw new Error(`Codesign entitlements file not found: ${entitlements}`)
+      }
+      const file = `dist/${name}/bin/opencode`
+      console.log(`codesigning ${name}`)
+      await $`codesign --entitlements ${entitlements} -vvvv --deep --sign ${sign} ${file} --force`
+      await $`codesign -vvv --verify ${file}`
+    }
+  }
+
   // Smoke test: only run if binary is for current platform
   if (item.os === process.platform && item.arch === process.arch && !item.abi) {
     const binaryPath = `dist/${name}/bin/opencode`
@@ -236,7 +283,9 @@ if (Script.release) {
       await $`zip -r ../../${key}.zip *`.cwd(`dist/${key}/bin`)
     }
   }
-  await $`gh release upload v${Script.version} ./dist/*.zip ./dist/*.tar.gz --clobber --repo ${process.env.GH_REPO}`
+  if (!skipUpload) {
+    await $`gh release upload v${Script.version} ./dist/*.zip ./dist/*.tar.gz --clobber --repo ${process.env.GH_REPO}`
+  }
 }
 
 export { binaries }

+ 16 - 0
packages/opencode/script/entitlements.plist

@@ -0,0 +1,16 @@
+<?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>com.apple.security.cs.allow-jit</key>
+    <true/>
+    <key>com.apple.security.cs.allow-unsigned-executable-memory</key>
+    <true/>
+    <key>com.apple.security.cs.disable-executable-page-protection</key>
+    <true/>
+    <key>com.apple.security.cs.allow-dyld-environment-variables</key>
+    <true/>
+    <key>com.apple.security.cs.disable-library-validation</key>
+    <true/>
+</dict>
+</plist>