diff --git a/.github/workflows/bundle-goose2.yml b/.github/workflows/bundle-goose2.yml index 1784e7bde6..814ffbfa80 100644 --- a/.github/workflows/bundle-goose2.yml +++ b/.github/workflows/bundle-goose2.yml @@ -38,6 +38,11 @@ on: required: false default: "" type: string + windows-signing: + description: "Whether to perform Windows signing via Azure Trusted Signing" + required: false + default: false + type: boolean cli-run-id: description: > Run ID of a prior build-cli.yml workflow run to download the goose @@ -125,7 +130,7 @@ jobs: - name: Cache Rust dependencies if: inputs.cli-run-id == '' - uses: Swatinem/rust-cache@v2 + uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 with: key: goose2-macos-arm64 @@ -175,13 +180,11 @@ jobs: certificate-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} # ── Tauri bundle ── - - name: Check disk space before bundle - run: df -h - - name: Bundle Goose 2 (pnpm tauri build) env: + APPLE_SIGNING_IDENTITY: ${{ inputs.signing && 'Developer ID Application' || '' }} APPLE_ID: ${{ inputs.signing && secrets.APPLE_ID || '' }} - APPLE_ID_PASSWORD: ${{ inputs.signing && secrets.APPLE_ID_PASSWORD || '' }} + APPLE_PASSWORD: ${{ inputs.signing && secrets.APPLE_ID_PASSWORD || '' }} APPLE_TEAM_ID: ${{ inputs.signing && secrets.APPLE_TEAM_ID || '' }} working-directory: ui/goose2 run: | @@ -291,7 +294,7 @@ jobs: - name: Cache Rust dependencies if: inputs.cli-run-id == '' - uses: Swatinem/rust-cache@v2 + uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 with: key: goose2-macos-x86_64 @@ -360,8 +363,9 @@ jobs: # ── Tauri bundle (cross-compile for Intel) ── - name: Bundle Goose 2 for Intel env: + APPLE_SIGNING_IDENTITY: ${{ inputs.signing && 'Developer ID Application' || '' }} APPLE_ID: ${{ inputs.signing && secrets.APPLE_ID || '' }} - APPLE_ID_PASSWORD: ${{ inputs.signing && secrets.APPLE_ID_PASSWORD || '' }} + APPLE_PASSWORD: ${{ inputs.signing && secrets.APPLE_ID_PASSWORD || '' }} APPLE_TEAM_ID: ${{ inputs.signing && secrets.APPLE_TEAM_ID || '' }} working-directory: ui/goose2 run: | @@ -477,7 +481,7 @@ jobs: - name: Cache Rust dependencies if: inputs.cli-run-id == '' - uses: Swatinem/rust-cache@v2 + uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 with: key: goose2-linux-x86_64 @@ -564,6 +568,7 @@ jobs: runs-on: windows-latest timeout-minutes: 60 permissions: + id-token: write contents: read actions: read steps: @@ -621,7 +626,7 @@ jobs: - name: Cache Rust dependencies if: inputs.cli-run-id == '' - uses: Swatinem/rust-cache@v2 + uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 with: key: goose2-windows-x86_64 @@ -697,3 +702,70 @@ jobs: name: Goose2-windows-x64-msi path: ui/goose2/src-tauri/target/x86_64-pc-windows-msvc/release/bundle/msi/*.msi if-no-files-found: warn + + sign-windows: + name: "Sign Windows installers" + needs: bundle-windows + if: inputs.windows-signing + runs-on: windows-latest + environment: signing + permissions: + id-token: write + contents: read + actions: read + steps: + - name: Download NSIS installer + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7 + with: + name: Goose2-windows-x64-nsis + path: unsigned/nsis + + - name: Download MSI installer + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7 + with: + name: Goose2-windows-x64-msi + path: unsigned/msi + + - name: Azure login + uses: azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v2 + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + + - name: Sign Windows installers with Azure Trusted Signing + uses: azure/trusted-signing-action@db7a3a6bd3912025c705162fb7475389f5b69ec6 # v1 + with: + endpoint: ${{ secrets.AZURE_SIGNING_ENDPOINT }} + trusted-signing-account-name: ${{ secrets.AZURE_SIGNING_ACCOUNT_NAME }} + certificate-profile-name: ${{ secrets.AZURE_CERTIFICATE_PROFILE_NAME }} + files-folder: ${{ github.workspace }}/unsigned + files-folder-filter: exe,msi + files-folder-recurse: true + + - name: Verify signed installers + shell: pwsh + run: | + $files = Get-ChildItem -Path unsigned -Recurse -Include *.exe,*.msi + foreach ($file in $files) { + Write-Output "Verifying signature: $($file.FullName)" + $sig = Get-AuthenticodeSignature $file.FullName + if ($sig.Status -ne "Valid") { + throw "Signature invalid for $($file.Name): $($sig.Status)" + } + Write-Output "✅ Signature valid: $($file.Name)" + } + + - name: Upload signed NSIS installer + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: Goose2-windows-x64-nsis-signed + path: unsigned/nsis/*.exe + if-no-files-found: error + + - name: Upload signed MSI installer + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: Goose2-windows-x64-msi-signed + path: unsigned/msi/*.msi + if-no-files-found: error diff --git a/.github/workflows/release-goose2.yml b/.github/workflows/release-goose2.yml new file mode 100644 index 0000000000..bc1d8e4ae7 --- /dev/null +++ b/.github/workflows/release-goose2.yml @@ -0,0 +1,192 @@ +on: + push: + tags: + - "v2.*" + workflow_dispatch: + inputs: + version: + description: "Version string (e.g. 2.0.0-rc.1). Used when testing from a branch." + required: true + type: string + cli-run-id: + description: "Run ID of a build-cli workflow to pull goose binaries from (skips CLI build step)" + required: false + type: string + default: "" + +name: "Release Goose 2" + +permissions: + id-token: write # Sigstore OIDC signing + Azure OIDC (Windows signing) + contents: write # Creating releases + actions/checkout + actions: read # Downloading artifacts across workflow runs + attestations: write # SLSA build provenance attestations + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + prepare-version: + name: Prepare Version + runs-on: ubuntu-latest + outputs: + version: ${{ steps.set-version.outputs.version }} + steps: + - name: Extract version + id: set-version + run: | + if [ -n "${{ inputs.version }}" ]; then + VERSION="${{ inputs.version }}" + else + # Strip the leading "v" from the tag (e.g. v2.0.0 → 2.0.0) + VERSION="${GITHUB_REF_NAME#v}" + fi + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "Release version: $VERSION" + + build-cli: + if: inputs.cli-run-id == '' + needs: [prepare-version] + uses: ./.github/workflows/build-cli.yml + with: + version: ${{ needs.prepare-version.outputs.version }} + + bundle-goose2: + needs: [prepare-version, build-cli] + if: ${{ !cancelled() && needs.prepare-version.result == 'success' && (needs.build-cli.result == 'success' || needs.build-cli.result == 'skipped') }} + uses: ./.github/workflows/bundle-goose2.yml + permissions: + id-token: write + contents: read + actions: read + with: + version: ${{ needs.prepare-version.outputs.version }} + signing: true + windows-signing: true + environment: signing + cli-run-id: ${{ inputs.cli-run-id != '' && inputs.cli-run-id || github.run_id }} + secrets: inherit + + install-script: + name: Upload Install Script + runs-on: ubuntu-latest + if: inputs.cli-run-id == '' + needs: [build-cli] + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: download_cli.sh + path: download_cli.sh + + release: + name: Release + runs-on: ubuntu-latest + needs: [prepare-version, build-cli, install-script, bundle-goose2] + if: ${{ !cancelled() && needs.bundle-goose2.result == 'success' }} + permissions: + contents: write + id-token: write + actions: read + attestations: write + steps: + - name: Download CLI artifacts + if: needs.build-cli.result == 'success' + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7 + with: + pattern: goose-* + merge-multiple: true + path: release + + - name: Download install script + if: needs.install-script.result == 'success' + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7 + with: + name: download_cli.sh + path: release + + - name: Download macOS ARM64 + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7 + with: + name: Goose2-darwin-arm64 + path: release + + - name: Download macOS Intel + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7 + with: + name: Goose2-darwin-x64 + path: release + + - name: Download Linux .deb + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7 + with: + name: Goose2-linux-x64-deb + path: release + continue-on-error: true + + - name: Download Linux AppImage + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7 + with: + name: Goose2-linux-x64-appimage + path: release + continue-on-error: true + + - name: Download Linux RPM + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7 + with: + name: Goose2-linux-x64-rpm + path: release + continue-on-error: true + + - name: Download signed Windows NSIS installer + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7 + with: + name: Goose2-windows-x64-nsis-signed + path: release + + - name: Download signed Windows MSI installer + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7 + with: + name: Goose2-windows-x64-msi-signed + path: release + + - name: List downloaded artifacts + run: | + echo "=== All release artifacts ===" + find release -type f | sort + + - name: Attest build provenance + uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3 + with: + subject-path: | + release/goose-*.tar.bz2 + release/goose-*.tar.gz + release/goose-*.zip + release/*.dmg + release/*.exe + release/*.msi + release/*.deb + release/*.rpm + release/*.AppImage + release/download_cli.sh + + # Create/update the versioned pre-release (e.g. v2.0.0) + - name: Release versioned + uses: ncipollo/release-action@339a81892b84b4eeb0f6e744e4574d79d0d9b8dd # v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + prerelease: true + artifacts: | + release/goose-*.tar.bz2 + release/goose-*.tar.gz + release/goose-*.zip + release/*.dmg + release/*.exe + release/*.msi + release/*.deb + release/*.rpm + release/*.AppImage + release/download_cli.sh + allowUpdates: true + omitBody: true diff --git a/ui/goose2/src-tauri/entitlements.plist b/ui/goose2/src-tauri/entitlements.plist new file mode 100644 index 0000000000..a5f008eaaa --- /dev/null +++ b/ui/goose2/src-tauri/entitlements.plist @@ -0,0 +1,22 @@ + + + + + com.apple.security.cs.allow-jit + + com.apple.security.cs.allow-unsigned-executable-memory + + com.apple.security.cs.allow-dyld-environment-variables + + com.apple.security.network.client + + com.apple.security.network.server + + com.apple.security.device.audio-input + + com.apple.security.files.user-selected.read-write + + com.apple.security.files.downloads.read-write + + + diff --git a/ui/goose2/src-tauri/icons/icon.ico b/ui/goose2/src-tauri/icons/icon.ico index db5b7751d2..83c727d9a3 100644 Binary files a/ui/goose2/src-tauri/icons/icon.ico and b/ui/goose2/src-tauri/icons/icon.ico differ diff --git a/ui/goose2/src-tauri/tauri.conf.json b/ui/goose2/src-tauri/tauri.conf.json index 11f0ef4aef..8b555dad1a 100644 --- a/ui/goose2/src-tauri/tauri.conf.json +++ b/ui/goose2/src-tauri/tauri.conf.json @@ -44,6 +44,10 @@ "icons/icon.icns", "icons/icon.ico" ], - "externalBin": ["../../../target/release/goose"] + "externalBin": ["../../../target/release/goose"], + "macOS": { + "entitlements": "entitlements.plist", + "hardenedRuntime": true + } } }