desktop-release-r2 #18
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: desktop-release-r2 | |
| run-name: desktop-release-r2 | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| release_tag: | |
| description: Optional existing release tag to publish from during a manual run. | |
| required: false | |
| type: string | |
| release_channel: | |
| description: Channel to publish when running the workflow manually. | |
| required: false | |
| default: stable | |
| type: choice | |
| options: | |
| - stable | |
| - beta | |
| release: | |
| types: | |
| - published | |
| - prereleased | |
| permissions: | |
| contents: write | |
| jobs: | |
| macos-r2-release: | |
| runs-on: macos-latest | |
| env: | |
| TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} | |
| MAABARIUM_UPDATE_PUBKEY: ${{ vars.MAABARIUM_UPDATE_PUBKEY || secrets.MAABARIUM_UPDATE_PUBKEY }} | |
| MAABARIUM_UPDATE_BASE_URL: ${{ vars.MAABARIUM_UPDATE_BASE_URL }} | |
| APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} | |
| APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} | |
| APPLE_SIGNING_IDENTITY: ${{ vars.APPLE_SIGNING_IDENTITY || secrets.APPLE_SIGNING_IDENTITY }} | |
| APPLE_ID: ${{ secrets.APPLE_ID }} | |
| APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} | |
| APPLE_TEAM_ID: ${{ vars.APPLE_TEAM_ID || secrets.APPLE_TEAM_ID }} | |
| APPLE_PROVIDER_SHORT_NAME: ${{ vars.APPLE_PROVIDER_SHORT_NAME || secrets.APPLE_PROVIDER_SHORT_NAME }} | |
| R2_BUCKET: ${{ vars.CLOUDFLARE_R2_BUCKET }} | |
| R2_ENDPOINT: ${{ vars.CLOUDFLARE_R2_ENDPOINT }} | |
| AWS_ACCESS_KEY_ID: ${{ secrets.CLOUDFLARE_R2_ACCESS_KEY_ID }} | |
| AWS_SECRET_ACCESS_KEY: ${{ secrets.CLOUDFLARE_R2_SECRET_ACCESS_KEY }} | |
| AWS_EC2_METADATA_DISABLED: "true" | |
| steps: | |
| - name: Resolve release context | |
| id: release-context | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| if [[ "${GITHUB_EVENT_NAME}" == "release" ]]; then | |
| echo "tag=${{ github.event.release.tag_name }}" >> "$GITHUB_OUTPUT" | |
| echo "ref=refs/tags/${{ github.event.release.tag_name }}" >> "$GITHUB_OUTPUT" | |
| echo "upload_release=true" >> "$GITHUB_OUTPUT" | |
| if [[ "${{ github.event.release.prerelease }}" == "true" ]]; then | |
| echo "channel=beta" >> "$GITHUB_OUTPUT" | |
| echo "publish_root_manifest=false" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "channel=stable" >> "$GITHUB_OUTPUT" | |
| echo "publish_root_manifest=true" >> "$GITHUB_OUTPUT" | |
| fi | |
| elif [[ -n "${{ inputs.release_tag || '' }}" ]]; then | |
| echo "tag=${{ inputs.release_tag }}" >> "$GITHUB_OUTPUT" | |
| echo "ref=refs/tags/${{ inputs.release_tag }}" >> "$GITHUB_OUTPUT" | |
| echo "upload_release=true" >> "$GITHUB_OUTPUT" | |
| echo "channel=${{ inputs.release_channel || 'stable' }}" >> "$GITHUB_OUTPUT" | |
| if [[ "${{ inputs.release_channel || 'stable' }}" == "stable" ]]; then | |
| echo "publish_root_manifest=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "publish_root_manifest=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| else | |
| echo "tag=" >> "$GITHUB_OUTPUT" | |
| echo "ref=${GITHUB_REF}" >> "$GITHUB_OUTPUT" | |
| echo "upload_release=false" >> "$GITHUB_OUTPUT" | |
| echo "channel=${{ inputs.release_channel || 'stable' }}" >> "$GITHUB_OUTPUT" | |
| if [[ "${{ inputs.release_channel || 'stable' }}" == "stable" ]]; then | |
| echo "publish_root_manifest=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "publish_root_manifest=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| fi | |
| - uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ steps.release-context.outputs.ref }} | |
| - uses: pnpm/action-setup@v4 | |
| with: | |
| version: 10 | |
| run_install: false | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22 | |
| cache: pnpm | |
| cache-dependency-path: crates/maabarium-desktop/pnpm-lock.yaml | |
| - uses: dtolnay/rust-toolchain@stable | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| workspaces: | | |
| . -> target | |
| - name: Validate release configuration | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| : "${TAURI_SIGNING_PRIVATE_KEY:?TAURI_SIGNING_PRIVATE_KEY must be configured}" | |
| : "${MAABARIUM_UPDATE_PUBKEY:?MAABARIUM_UPDATE_PUBKEY must be configured}" | |
| : "${MAABARIUM_UPDATE_BASE_URL:?MAABARIUM_UPDATE_BASE_URL must be configured}" | |
| : "${APPLE_CERTIFICATE:?APPLE_CERTIFICATE must be configured}" | |
| : "${APPLE_CERTIFICATE_PASSWORD:?APPLE_CERTIFICATE_PASSWORD must be configured}" | |
| : "${APPLE_ID:?APPLE_ID must be configured}" | |
| : "${APPLE_PASSWORD:?APPLE_PASSWORD must be configured}" | |
| : "${APPLE_TEAM_ID:?APPLE_TEAM_ID must be configured}" | |
| : "${R2_BUCKET:?CLOUDFLARE_R2_BUCKET must be configured}" | |
| : "${R2_ENDPOINT:?CLOUDFLARE_R2_ENDPOINT must be configured}" | |
| : "${AWS_ACCESS_KEY_ID:?CLOUDFLARE_R2_ACCESS_KEY_ID must be configured}" | |
| : "${AWS_SECRET_ACCESS_KEY:?CLOUDFLARE_R2_SECRET_ACCESS_KEY must be configured}" | |
| node "$GITHUB_WORKSPACE/crates/maabarium-desktop/scripts/validate-updater-pubkey.mjs" --github-env | |
| - name: Install desktop dependencies | |
| run: cd crates/maabarium-desktop && pnpm install --frozen-lockfile | |
| - name: Validate updater signing keypair | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| node "$GITHUB_WORKSPACE/crates/maabarium-desktop/scripts/validate-updater-keypair.mjs" | |
| - name: Import Apple signing certificate into keychain | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| CERT_PATH="$RUNNER_TEMP/maabarium-apple-signing-cert.p12" | |
| KEYCHAIN_PATH="$RUNNER_TEMP/maabarium-signing.keychain-db" | |
| KEYCHAIN_PASSWORD="$(openssl rand -hex 24)" | |
| printf '%s' "$APPLE_CERTIFICATE" | base64 --decode > "$CERT_PATH" | |
| security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" | |
| security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH" | |
| security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" | |
| security import "$CERT_PATH" \ | |
| -k "$KEYCHAIN_PATH" \ | |
| -P "$APPLE_CERTIFICATE_PASSWORD" \ | |
| -T /usr/bin/codesign \ | |
| -T /usr/bin/security \ | |
| -T /usr/bin/productbuild | |
| security set-key-partition-list \ | |
| -S apple-tool:,apple:,codesign: \ | |
| -s \ | |
| -k "$KEYCHAIN_PASSWORD" \ | |
| "$KEYCHAIN_PATH" | |
| EXISTING_KEYCHAINS=() | |
| while IFS= read -r keychain; do | |
| EXISTING_KEYCHAINS+=("${keychain//\"/}") | |
| done < <(security list-keychains -d user) | |
| security list-keychains -d user -s "$KEYCHAIN_PATH" "${EXISTING_KEYCHAINS[@]}" | |
| security default-keychain -d user -s "$KEYCHAIN_PATH" | |
| - name: Build signed Tauri bundle | |
| env: | |
| TAURI_SIGNING_PRIVATE_KEY_PASSWORD_SECRET: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} | |
| MAABARIUM_REQUIRE_APPLE_CLI_SIGNING: "1" | |
| MAABARIUM_UPDATE_CHANNEL: ${{ steps.release-context.outputs.channel }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| export TAURI_SIGNING_PRIVATE_KEY_PASSWORD="${TAURI_SIGNING_PRIVATE_KEY_PASSWORD_SECRET:-}" | |
| TAURI_UPDATER_PUBKEY="$(if [[ -n "${MAABARIUM_UPDATE_PUBKEY_FILE:-}" ]]; then node "$GITHUB_WORKSPACE/crates/maabarium-desktop/scripts/normalize-updater-key.mjs" --file "$MAABARIUM_UPDATE_PUBKEY_FILE" --format wrapped; else node "$GITHUB_WORKSPACE/crates/maabarium-desktop/scripts/normalize-updater-key.mjs" --value "$MAABARIUM_UPDATE_PUBKEY" --format wrapped; fi)" | |
| export TAURI_CONFIG="$(node -e 'process.stdout.write(JSON.stringify({ productName: "Maabarium-Console", bundle: { targets: ["app"], macOS: { entitlements: "Entitlements.plist" } }, plugins: { updater: { pubkey: process.argv[1] } } }));' "$TAURI_UPDATER_PUBKEY")" | |
| cd crates/maabarium-desktop | |
| pnpm tauri build \ | |
| --ci \ | |
| --config "$TAURI_CONFIG" | |
| - name: Verify macOS desktop entitlements | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| "$GITHUB_WORKSPACE/crates/maabarium-desktop/scripts/verify-macos-entitlements.sh" \ | |
| "$GITHUB_WORKSPACE/target/release/bundle/macos/Maabarium-Console.app" | |
| - name: Stage updater bundle and manifest | |
| env: | |
| RELEASE_CHANNEL: ${{ steps.release-context.outputs.channel }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| DESKTOP_DIR="$GITHUB_WORKSPACE/crates/maabarium-desktop" | |
| BUNDLE_DIR="$GITHUB_WORKSPACE/target/release/bundle/macos" | |
| RELEASE_DIR="$DESKTOP_DIR/release" | |
| STAGING_DIR="$RELEASE_DIR/staging" | |
| MANIFEST_DIR="$RELEASE_DIR/$RELEASE_CHANNEL" | |
| case "$(uname -m)" in | |
| arm64|aarch64) | |
| PLATFORM_KEY="darwin-aarch64" | |
| CLI_TARGET_TRIPLE="aarch64-apple-darwin" | |
| ;; | |
| x86_64) | |
| PLATFORM_KEY="darwin-x86_64" | |
| CLI_TARGET_TRIPLE="x86_64-apple-darwin" | |
| ;; | |
| *) | |
| echo "Unsupported macOS runner architecture: $(uname -m)" >&2 | |
| exit 1 | |
| ;; | |
| esac | |
| UPDATER_BUNDLE="$(find "$BUNDLE_DIR" -maxdepth 1 -name "*.app.tar.gz" -print -quit)" | |
| if [[ -z "$UPDATER_BUNDLE" ]]; then | |
| echo "No macOS updater bundle was produced under $BUNDLE_DIR" >&2 | |
| exit 1 | |
| fi | |
| SIG_PATH="${UPDATER_BUNDLE}.sig" | |
| if [[ ! -f "$SIG_PATH" ]]; then | |
| echo "Missing updater signature for $UPDATER_BUNDLE" >&2 | |
| exit 1 | |
| fi | |
| CLI_BINARY="$GITHUB_WORKSPACE/target/cli-bundle/$CLI_TARGET_TRIPLE/release/maabarium" | |
| if [[ ! -f "$CLI_BINARY" ]]; then | |
| echo "No bundled CLI binary was produced at $CLI_BINARY" >&2 | |
| exit 1 | |
| fi | |
| mkdir -p "$STAGING_DIR/$PLATFORM_KEY" | |
| mkdir -p "$MANIFEST_DIR" | |
| BUNDLE_NAME="$(basename "$UPDATER_BUNDLE")" | |
| RELEASE_BUNDLE_NAME="${BUNDLE_NAME// /-}" | |
| RELEASE_SIG_NAME="${RELEASE_BUNDLE_NAME}.sig" | |
| CLI_ARCHIVE_NAME="maabarium-${PLATFORM_KEY}.tar.gz" | |
| cp "$UPDATER_BUNDLE" "$STAGING_DIR/$PLATFORM_KEY/$RELEASE_BUNDLE_NAME" | |
| cp "$SIG_PATH" "$STAGING_DIR/$PLATFORM_KEY/$RELEASE_SIG_NAME" | |
| tar -C "$(dirname "$CLI_BINARY")" -czf "$STAGING_DIR/$PLATFORM_KEY/$CLI_ARCHIVE_NAME" "$(basename "$CLI_BINARY")" | |
| if [[ "$RELEASE_BUNDLE_NAME" != "$BUNDLE_NAME" ]]; then | |
| echo "Publishing updater bundle as $RELEASE_BUNDLE_NAME" | |
| fi | |
| cd "$DESKTOP_DIR" | |
| pnpm build:release-manifest -- \ | |
| --base-url "$MAABARIUM_UPDATE_BASE_URL" \ | |
| --channel "$RELEASE_CHANNEL" \ | |
| --artifacts-dir "$STAGING_DIR" \ | |
| --output "$MANIFEST_DIR/latest.json" \ | |
| --platform "$PLATFORM_KEY=$PLATFORM_KEY/$RELEASE_BUNDLE_NAME" \ | |
| --cli-platform "$PLATFORM_KEY=$PLATFORM_KEY/$CLI_ARCHIVE_NAME" | |
| - name: Generate install.sh from updater metadata base URL | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| node "$GITHUB_WORKSPACE/scripts/release/generate-install-script.mjs" \ | |
| --base-url "$MAABARIUM_UPDATE_BASE_URL" \ | |
| --output "$GITHUB_WORKSPACE/crates/maabarium-desktop/release/install.sh" | |
| chmod +x "$GITHUB_WORKSPACE/crates/maabarium-desktop/release/install.sh" | |
| - name: Upload release artifacts to workflow | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: desktop-release-r2-macos | |
| path: | | |
| crates/maabarium-desktop/release/install.sh | |
| crates/maabarium-desktop/release/stable/latest.json | |
| crates/maabarium-desktop/release/beta/latest.json | |
| crates/maabarium-desktop/release/staging/** | |
| - name: Upload desktop artifacts to GitHub Release | |
| if: steps.release-context.outputs.upload_release == 'true' | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| DESKTOP_DIR="$GITHUB_WORKSPACE/crates/maabarium-desktop" | |
| release_assets=() | |
| manifest_path="$DESKTOP_DIR/release/${{ steps.release-context.outputs.channel }}/latest.json" | |
| while IFS= read -r asset_path; do | |
| release_assets+=("$asset_path") | |
| done < <(find "$DESKTOP_DIR/release/staging" -type f | sort) | |
| gh release upload "${{ steps.release-context.outputs.tag }}" \ | |
| "$manifest_path" \ | |
| "$DESKTOP_DIR/release/install.sh" \ | |
| "${release_assets[@]}" \ | |
| --clobber | |
| - name: Publish bundles and latest.json to Cloudflare R2 | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| DESKTOP_DIR="$GITHUB_WORKSPACE/crates/maabarium-desktop" | |
| aws s3 sync "$DESKTOP_DIR/release/staging/" "s3://$R2_BUCKET/" --delete --endpoint-url "$R2_ENDPOINT" | |
| aws s3 cp "$DESKTOP_DIR/release/${{ steps.release-context.outputs.channel }}/latest.json" "s3://$R2_BUCKET/${{ steps.release-context.outputs.channel }}/latest.json" --endpoint-url "$R2_ENDPOINT" | |
| if [[ "${{ steps.release-context.outputs.publish_root_manifest }}" == "true" ]]; then | |
| aws s3 cp "$DESKTOP_DIR/release/${{ steps.release-context.outputs.channel }}/latest.json" "s3://$R2_BUCKET/latest.json" --endpoint-url "$R2_ENDPOINT" | |
| fi | |
| aws s3 cp "$DESKTOP_DIR/release/install.sh" "s3://$R2_BUCKET/install.sh" --endpoint-url "$R2_ENDPOINT" --content-type "text/x-shellscript; charset=utf-8" |