.github/workflows/desktop_cd.yaml #1097
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
| # https://docs.crabnebula.dev/cloud/ci/tauri-v2-workflow/ | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| channel: | |
| description: "Release channel" | |
| required: true | |
| type: choice | |
| options: | |
| - staging | |
| - stable | |
| candidate_sha: | |
| description: "Immutable candidate commit to check out" | |
| required: false | |
| type: string | |
| default: "" | |
| publish: | |
| description: "Legacy compatibility input; stable publication must use desktop_publish" | |
| type: boolean | |
| default: false | |
| include_linux: | |
| description: "Build and include Linux beta artifacts" | |
| type: boolean | |
| default: true | |
| include_windows: | |
| description: "Build and include Windows artifacts" | |
| type: boolean | |
| default: true | |
| version: | |
| description: "Explicit semantic version required for stable releases" | |
| required: false | |
| type: string | |
| default: "" | |
| concurrency: | |
| group: ${{ inputs.channel == 'stable' && 'desktop-stable-release' || format('{0}-{1}-{2}', github.workflow, github.ref, inputs.channel) }} | |
| cancel-in-progress: ${{ inputs.channel != 'stable' }} | |
| env: | |
| CN_APPLICATION: "fastrepl/hyprnote2" | |
| RELEASE_CHANNEL: ${{ inputs.channel }} | |
| TAURI_CONF_PATH: ./src-tauri/tauri.conf.${{ inputs.channel }}.json | |
| NODE_OPTIONS: "--max-old-space-size=4096" | |
| jobs: | |
| compute-version: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| version: ${{ steps.version.outputs.version }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ inputs.candidate_sha || github.sha }} | |
| lfs: true | |
| fetch-depth: 0 | |
| fetch-tags: true | |
| - run: | | |
| git remote set-url origin "https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git" | |
| git fetch --tags --force | |
| - if: ${{ inputs.channel == 'stable' }} | |
| run: | | |
| if [[ "${{ inputs.publish }}" == "true" ]]; then | |
| echo "::error::desktop_cd only builds stable candidates; publish through desktop_publish after QA" | |
| exit 1 | |
| fi | |
| git fetch origin main --no-tags | |
| TARGET=$(git rev-parse HEAD) | |
| MAIN=$(git rev-parse origin/main) | |
| if [[ "$TARGET" != "$MAIN" ]]; then | |
| echo "::error::Stable releases must run from the current main commit ($MAIN), got $TARGET" | |
| exit 1 | |
| fi | |
| - uses: ./.github/actions/doxxer_install | |
| - id: version | |
| env: | |
| INPUT_VERSION: ${{ inputs.version }} | |
| RELEASE_CHANNEL: ${{ inputs.channel }} | |
| run: | | |
| if [[ "$RELEASE_CHANNEL" == "stable" ]]; then | |
| if [[ ! "$INPUT_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then | |
| echo "::error::Stable releases require an explicit semantic version such as 1.4.0" | |
| exit 1 | |
| fi | |
| VERSION="$INPUT_VERSION" | |
| if [[ ! -f "packages/changelog/content/$VERSION.md" ]]; then | |
| echo "::error::Missing changelog for stable version $VERSION" | |
| exit 1 | |
| fi | |
| elif [[ -n "$INPUT_VERSION" ]]; then | |
| VERSION="$INPUT_VERSION" | |
| elif [[ "$RELEASE_CHANNEL" == "staging" ]]; then | |
| VERSION=$(doxxer --config doxxer.desktop.toml next dev) | |
| fi | |
| echo "version=$VERSION" >> $GITHUB_OUTPUT | |
| echo "Computed version: $VERSION" | |
| cn-draft: | |
| if: ${{ inputs.channel != 'staging' }} | |
| needs: compute-version | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ inputs.candidate_sha || github.sha }} | |
| - run: ./scripts/version.sh "./apps/desktop/src-tauri/tauri.conf.json" "${{ needs.compute-version.outputs.version }}" | |
| - if: ${{ inputs.include_windows }} | |
| name: Verify stable signing configuration | |
| env: | |
| AZURE_CLIENT_ID: ${{ vars.AZURE_CLIENT_ID }} | |
| AZURE_TENANT_ID: ${{ vars.AZURE_TENANT_ID }} | |
| AZURE_SUBSCRIPTION_ID: ${{ vars.AZURE_SUBSCRIPTION_ID }} | |
| AZURE_ARTIFACT_SIGNING_ENDPOINT: ${{ vars.AZURE_ARTIFACT_SIGNING_ENDPOINT }} | |
| AZURE_ARTIFACT_SIGNING_ACCOUNT_NAME: ${{ vars.AZURE_ARTIFACT_SIGNING_ACCOUNT_NAME }} | |
| AZURE_ARTIFACT_SIGNING_CERTIFICATE_PROFILE_NAME: ${{ vars.AZURE_ARTIFACT_SIGNING_CERTIFICATE_PROFILE_NAME }} | |
| WINDOWS_SIGNER_ORGANIZATION: ${{ vars.WINDOWS_SIGNER_ORGANIZATION }} | |
| run: | | |
| missing=() | |
| [[ -n "$AZURE_CLIENT_ID" ]] || missing+=("AZURE_CLIENT_ID") | |
| [[ -n "$AZURE_TENANT_ID" ]] || missing+=("AZURE_TENANT_ID") | |
| [[ -n "$AZURE_SUBSCRIPTION_ID" ]] || missing+=("AZURE_SUBSCRIPTION_ID") | |
| [[ -n "$AZURE_ARTIFACT_SIGNING_ENDPOINT" ]] || missing+=("AZURE_ARTIFACT_SIGNING_ENDPOINT") | |
| [[ -n "$AZURE_ARTIFACT_SIGNING_ACCOUNT_NAME" ]] || missing+=("AZURE_ARTIFACT_SIGNING_ACCOUNT_NAME") | |
| [[ -n "$AZURE_ARTIFACT_SIGNING_CERTIFICATE_PROFILE_NAME" ]] || missing+=("AZURE_ARTIFACT_SIGNING_CERTIFICATE_PROFILE_NAME") | |
| [[ -n "$WINDOWS_SIGNER_ORGANIZATION" ]] || missing+=("WINDOWS_SIGNER_ORGANIZATION") | |
| if [[ ${#missing[@]} -gt 0 ]]; then | |
| echo "::error::Stable Windows release signing configuration is missing: ${missing[*]}" | |
| exit 1 | |
| fi | |
| - uses: ./.github/actions/cn_release | |
| with: | |
| cmd: draft | |
| app: ${{ env.CN_APPLICATION }} | |
| key: ${{ secrets.CN_API_KEY }} | |
| channel: ${{ env.RELEASE_CHANNEL }} | |
| framework: tauri | |
| working-directory: ./apps/desktop | |
| build-macos: | |
| needs: [compute-version, cn-draft] | |
| if: ${{ !cancelled() && (needs.cn-draft.result == 'success' || needs.cn-draft.result == 'skipped') }} | |
| permissions: | |
| contents: write | |
| runs-on: depot-macos-26 | |
| strategy: | |
| fail-fast: true | |
| matrix: | |
| include: ${{ inputs.channel == 'staging' && fromJSON('[{"target":"aarch64-apple-darwin","arch":"aarch64","artifact_name":"silicon"}]') || fromJSON('[{"target":"aarch64-apple-darwin","arch":"aarch64","artifact_name":"silicon"},{"target":"x86_64-apple-darwin","arch":"x86_64","artifact_name":"intel"}]') }} | |
| defaults: | |
| run: | |
| shell: bash | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ inputs.candidate_sha || github.sha }} | |
| lfs: true | |
| fetch-depth: 0 | |
| fetch-tags: true | |
| submodules: recursive | |
| - run: | | |
| git remote set-url origin "https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git" | |
| git fetch --tags --force | |
| - uses: ./.github/actions/macos_tcc | |
| - run: ./scripts/version.sh "./apps/desktop/src-tauri/tauri.conf.json" "${{ needs.compute-version.outputs.version }}" | |
| - uses: ./.github/actions/install_desktop_deps | |
| with: | |
| target: macos | |
| - uses: ./.github/actions/rust_install | |
| with: | |
| platform: macos | |
| - run: echo "SDKROOT=$(xcrun --sdk macosx --show-sdk-path)" >> $GITHUB_ENV | |
| - uses: ./.github/actions/pnpm_install | |
| - run: pnpm -F ui build | |
| - run: | | |
| TAURI_ENV_TARGET_TRIPLE=${{ matrix.target }} cargo xtask prepare-binaries | |
| ./scripts/sidecar.sh "./apps/desktop/${{ env.TAURI_CONF_PATH }}" "binaries/char-chrome-native-host" | |
| ./scripts/sidecar.sh "./apps/desktop/${{ env.TAURI_CONF_PATH }}" "binaries/check-permissions" | |
| ./scripts/sidecar.sh "./apps/desktop/${{ env.TAURI_CONF_PATH }}" "resources/cli/anarlog-cli" | |
| - uses: ./.github/actions/apple_cert | |
| id: apple-cert | |
| with: | |
| apple-certificate: ${{ secrets.APPLE_CERTIFICATE }} | |
| apple-certificate-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} | |
| keychain-password: ${{ secrets.KEYCHAIN_PASSWORD }} | |
| - run: | | |
| CLOUDSYNC_DYLIB="../../crates/cloudsync/vendor/cloudsync/macos/${{ matrix.arch }}/cloudsync.dylib" | |
| codesign --force --sign "${{ steps.apple-cert.outputs.cert-id }}" --timestamp --options runtime "$CLOUDSYNC_DYLIB" | |
| codesign --verify --strict --verbose=2 "$CLOUDSYNC_DYLIB" | |
| working-directory: ./apps/desktop | |
| - run: | | |
| BUILD_ARGS=(--target "${{ matrix.target }}" --config "${{ env.TAURI_CONF_PATH }}" --verbose) | |
| if [[ "${{ matrix.target }}" == "x86_64-apple-darwin" ]]; then | |
| BUILD_ARGS+=(--config ./src-tauri/tauri.conf.macos-intel.json) | |
| fi | |
| if [[ "${{ inputs.channel }}" == "staging" ]]; then | |
| BUILD_ARGS+=(--features devtools) | |
| fi | |
| pnpm -F desktop tauri build "${BUILD_ARGS[@]}" | |
| env: | |
| # https://v2.tauri.app/reference/environment-variables/ | |
| CI: false | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| POSTHOG_API_KEY: ${{ secrets.POSTHOG_API_KEY }} | |
| VITE_POSTHOG_API_KEY: ${{ secrets.POSTHOG_API_KEY }} | |
| SENTRY_DSN: ${{ secrets.SENTRY_DSN_HYPRNOTE_2 }} | |
| CARGO_PROFILE_RELEASE_DEBUG: line-tables-only | |
| APP_VERSION: ${{ needs.compute-version.outputs.version }} | |
| APPLE_ID: ${{ secrets.APPLE_ID }} | |
| APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} | |
| APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} | |
| APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} | |
| APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} | |
| APPLE_SIGNING_IDENTITY: ${{ steps.apple-cert.outputs.cert-id }} | |
| TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} | |
| TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} | |
| VITE_SUPABASE_URL: ${{ secrets.VITE_SUPABASE_URL }} | |
| VITE_SUPABASE_ANON_KEY: ${{ secrets.VITE_SUPABASE_ANON_KEY }} | |
| VITE_APP_URL: "https://anarlog.so" | |
| VITE_API_URL: "https://api.anarlog.so" | |
| VITE_PRO_PRODUCT_ID: ${{ secrets.VITE_PRO_PRODUCT_ID }} | |
| VITE_APP_VERSION: ${{ needs.compute-version.outputs.version }} | |
| - if: ${{ inputs.channel == 'stable' }} | |
| id: macos-symbols | |
| name: Generate and verify macOS debug symbols | |
| run: | | |
| shopt -s nullglob | |
| app_dir="apps/desktop/src-tauri/target/${{ matrix.target }}/release/bundle/macos" | |
| apps=("$app_dir"/*.app) | |
| if [[ ${#apps[@]} -ne 1 ]]; then | |
| echo "::error::Expected one macOS app for ${{ matrix.target }}, found ${#apps[@]}" | |
| exit 1 | |
| fi | |
| binary="${apps[0]}/Contents/MacOS/anarlog" | |
| dsym="apps/desktop/src-tauri/target/${{ matrix.target }}/release/anarlog.dSYM" | |
| dsymutil "$binary" -o "$dsym" | |
| binary_uuid=$(dwarfdump --uuid "$binary" | awk '{print $2}') | |
| dsym_uuid=$(dwarfdump --uuid "$dsym" | awk '{print $2}') | |
| if [[ -z "$binary_uuid" || "$binary_uuid" != "$dsym_uuid" ]]; then | |
| echo "::error::dSYM UUID does not match the packaged binary" | |
| exit 1 | |
| fi | |
| echo "Verified dSYM UUID $dsym_uuid" | |
| echo "path=$dsym" >> "$GITHUB_OUTPUT" | |
| - if: ${{ inputs.channel == 'stable' }} | |
| uses: ./.github/actions/sentry_cli | |
| - if: ${{ inputs.channel == 'stable' }} | |
| name: Upload macOS debug symbols to Sentry | |
| env: | |
| INFISICAL_PROJECT_ID: ${{ secrets.INFISICAL_PROJECT_ID }} | |
| INFISICAL_TOKEN: ${{ secrets.INFISICAL_SENTRY_TOKEN }} | |
| run: | | |
| response=$( | |
| curl --fail --silent --show-error --get \ | |
| --header "Authorization: Bearer $INFISICAL_TOKEN" \ | |
| --data-urlencode "projectId=$INFISICAL_PROJECT_ID" \ | |
| --data-urlencode "environment=prod" \ | |
| --data-urlencode "secretPath=/anarlog/github" \ | |
| --data-urlencode "expandSecretReferences=false" \ | |
| "https://app.infisical.com/api/v4/secrets/SENTRY_AUTH_TOKEN" | |
| ) | |
| if ! SENTRY_AUTH_TOKEN=$(jq -er '.secret.secretValue | select(length > 0)' <<< "$response"); then | |
| echo "::error::Missing SENTRY_AUTH_TOKEN in Infisical prod:/anarlog/github" | |
| exit 1 | |
| fi | |
| export SENTRY_AUTH_TOKEN | |
| sentry-cli debug-files upload \ | |
| --org fastrepl \ | |
| --project hyprnote2 \ | |
| --include-sources \ | |
| "${{ steps.macos-symbols.outputs.path }}" | |
| - if: ${{ inputs.channel == 'stable' }} | |
| name: Verify macOS updater signature | |
| run: | | |
| shopt -s nullglob | |
| updater_dir="apps/desktop/src-tauri/target/${{ matrix.target }}/release/bundle/macos" | |
| apps=("$updater_dir"/*.app) | |
| artifacts=("$updater_dir"/*.app.tar.gz) | |
| signatures=("$updater_dir"/*.app.tar.gz.sig) | |
| if [[ ${#apps[@]} -ne 1 || ${#artifacts[@]} -ne 1 || ${#signatures[@]} -ne 1 ]]; then | |
| echo "Expected one macOS app, updater artifact, and signature for ${{ matrix.target }}" | |
| exit 1 | |
| fi | |
| app_version=$(plutil -extract CFBundleShortVersionString raw -o - "${apps[0]}/Contents/Info.plist") | |
| if [[ "$app_version" != "${{ needs.compute-version.outputs.version }}" ]]; then | |
| echo "Expected macOS app version ${{ needs.compute-version.outputs.version }}, got $app_version" | |
| exit 1 | |
| fi | |
| cargo run --locked -p updater-core --bin verify-updater-signature -- \ | |
| "${artifacts[0]}" \ | |
| "${signatures[0]}" \ | |
| apps/desktop/src-tauri/tauri.conf.json | |
| - id: find-dmg | |
| shell: bash | |
| run: | | |
| DMG_FILE=$(find "apps/desktop/src-tauri/target/${{ matrix.target }}/release/bundle/dmg/" -name "*.dmg" -type f | head -1) | |
| if [[ -z "$DMG_FILE" ]]; then | |
| echo "::error::No DMG found in bundle output" | |
| exit 1 | |
| fi | |
| echo "path=$DMG_FILE" >> $GITHUB_OUTPUT | |
| - uses: ./.github/actions/macos_notarize_dmg | |
| with: | |
| dmg-path: ${{ steps.find-dmg.outputs.path }} | |
| signing-identity: ${{ steps.apple-cert.outputs.cert-id }} | |
| apple-id: ${{ secrets.APPLE_ID }} | |
| apple-password: ${{ secrets.APPLE_PASSWORD }} | |
| apple-team-id: ${{ secrets.APPLE_TEAM_ID }} | |
| - run: | | |
| mkdir -p target/release/ | |
| find target/${{ matrix.target }}/release -type f -not -path "*/\.*" -exec cp {} target/release/ \; | |
| shell: bash | |
| working-directory: ./apps/desktop/src-tauri | |
| - if: ${{ inputs.channel != 'staging' }} | |
| uses: ./.github/actions/cn_release | |
| with: | |
| cmd: upload | |
| app: ${{ env.CN_APPLICATION }} | |
| key: ${{ secrets.CN_API_KEY }} | |
| channel: ${{ env.RELEASE_CHANNEL }} | |
| framework: tauri | |
| working-directory: ./apps/desktop | |
| - if: ${{ inputs.channel == 'staging' }} | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: hyprnote-staging-macos-${{ matrix.artifact_name }} | |
| path: apps/desktop/src-tauri/target/${{ matrix.target }}/release/bundle/dmg/*.dmg | |
| retention-days: 3 | |
| - if: ${{ inputs.channel == 'staging' }} | |
| run: | | |
| TIMESTAMP=$(date -u +"%Y%m%d%H%M%S") | |
| COMMIT_HASH=$(git rev-parse --short HEAD) | |
| DMG_FILE=$(find "apps/desktop/src-tauri/target/${{ matrix.target }}/release/bundle/dmg/" -name "*.dmg" -type f) | |
| FILENAME="hyprnote-staging-${TIMESTAMP}-${COMMIT_HASH}-macos-${{ matrix.arch }}.dmg" | |
| cp "$DMG_FILE" "$FILENAME" | |
| aws s3 cp "$FILENAME" \ | |
| "s3://hyprnote-build/desktop/staging/$FILENAME" \ | |
| --endpoint-url ${{ secrets.CLOUDFLARE_R2_ENDPOINT_URL }} \ | |
| --region auto | |
| env: | |
| AWS_ACCESS_KEY_ID: ${{ secrets.CLOUDFLARE_R2_ACCESS_KEY_ID }} | |
| AWS_SECRET_ACCESS_KEY: ${{ secrets.CLOUDFLARE_R2_SECRET_ACCESS_KEY }} | |
| build-linux: | |
| needs: [compute-version, cn-draft] | |
| if: ${{ inputs.include_linux && !cancelled() && (needs.cn-draft.result == 'success' || needs.cn-draft.result == 'skipped') }} | |
| strategy: | |
| fail-fast: true | |
| matrix: | |
| include: | |
| - runner: depot-ubuntu-24.04-8 | |
| target: x86_64-unknown-linux-gnu | |
| rust_platform: linux-x86_64 | |
| artifact_name: x64 | |
| debian_arch: amd64 | |
| file_arch: x86-64 | |
| docker_arch: amd64 | |
| - runner: depot-ubuntu-24.04-arm-8 | |
| target: aarch64-unknown-linux-gnu | |
| rust_platform: linux-aarch64 | |
| artifact_name: arm64 | |
| debian_arch: arm64 | |
| file_arch: ARM aarch64 | |
| docker_arch: arm64 | |
| runs-on: ${{ matrix.runner }} | |
| defaults: | |
| run: | |
| shell: bash | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ inputs.candidate_sha || github.sha }} | |
| lfs: true | |
| fetch-depth: 0 | |
| fetch-tags: true | |
| submodules: recursive | |
| - run: ./scripts/version.sh "./apps/desktop/src-tauri/tauri.conf.json" "${{ needs.compute-version.outputs.version }}" | |
| - uses: ./.github/actions/install_desktop_deps | |
| with: | |
| target: linux | |
| - uses: ./.github/actions/rust_install | |
| with: | |
| platform: ${{ matrix.rust_platform }} | |
| - uses: ./.github/actions/pnpm_install | |
| - run: pnpm -F ui build | |
| - run: | | |
| TAURI_ENV_TARGET_TRIPLE=${{ matrix.target }} cargo xtask prepare-binaries | |
| ./scripts/sidecar.sh "./apps/desktop/${{ env.TAURI_CONF_PATH }}" "binaries/char-chrome-native-host" | |
| ./scripts/sidecar.sh "./apps/desktop/${{ env.TAURI_CONF_PATH }}" "resources/cli/anarlog-cli" | |
| - run: | | |
| BUILD_ARGS=( | |
| --ci | |
| --bundles appimage,deb | |
| --target "${{ matrix.target }}" | |
| --config "${{ env.TAURI_CONF_PATH }}" | |
| --verbose | |
| ) | |
| if [[ "${{ inputs.channel }}" == "staging" ]]; then | |
| BUILD_ARGS+=(--features devtools) | |
| fi | |
| pnpm -F desktop tauri build "${BUILD_ARGS[@]}" | |
| env: | |
| CARGO_PROFILE_RELEASE_DEBUG: line-tables-only | |
| POSTHOG_API_KEY: ${{ secrets.POSTHOG_API_KEY }} | |
| VITE_POSTHOG_API_KEY: ${{ secrets.POSTHOG_API_KEY }} | |
| SENTRY_DSN: ${{ secrets.SENTRY_DSN_HYPRNOTE_2 }} | |
| APP_VERSION: ${{ needs.compute-version.outputs.version }} | |
| TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} | |
| TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} | |
| VITE_SUPABASE_URL: ${{ secrets.VITE_SUPABASE_URL }} | |
| VITE_SUPABASE_ANON_KEY: ${{ secrets.VITE_SUPABASE_ANON_KEY }} | |
| VITE_APP_URL: https://anarlog.so | |
| VITE_API_URL: https://api.anarlog.so | |
| VITE_PRO_PRODUCT_ID: ${{ secrets.VITE_PRO_PRODUCT_ID }} | |
| VITE_APP_VERSION: ${{ needs.compute-version.outputs.version }} | |
| - if: ${{ inputs.channel == 'stable' }} | |
| uses: ./.github/actions/sentry_cli | |
| - if: ${{ inputs.channel == 'stable' }} | |
| name: Upload Linux debug symbols to Sentry | |
| env: | |
| INFISICAL_PROJECT_ID: ${{ secrets.INFISICAL_PROJECT_ID }} | |
| INFISICAL_TOKEN: ${{ secrets.INFISICAL_SENTRY_TOKEN }} | |
| run: | | |
| binary="apps/desktop/src-tauri/target/${{ matrix.target }}/release/anarlog" | |
| sentry-cli debug-files check "$binary" | |
| response=$( | |
| curl --fail --silent --show-error --get \ | |
| --header "Authorization: Bearer $INFISICAL_TOKEN" \ | |
| --data-urlencode "projectId=$INFISICAL_PROJECT_ID" \ | |
| --data-urlencode "environment=prod" \ | |
| --data-urlencode "secretPath=/anarlog/github" \ | |
| --data-urlencode "expandSecretReferences=false" \ | |
| "https://app.infisical.com/api/v4/secrets/SENTRY_AUTH_TOKEN" | |
| ) | |
| if ! SENTRY_AUTH_TOKEN=$(jq -er '.secret.secretValue | select(length > 0)' <<< "$response"); then | |
| echo "::error::Missing SENTRY_AUTH_TOKEN in Infisical prod:/anarlog/github" | |
| exit 1 | |
| fi | |
| export SENTRY_AUTH_TOKEN | |
| sentry-cli debug-files upload \ | |
| --org fastrepl \ | |
| --project hyprnote2 \ | |
| --include-sources \ | |
| "$binary" | |
| - run: | | |
| shopt -s nullglob | |
| appimage_dir="apps/desktop/src-tauri/target/${{ matrix.target }}/release/bundle/appimage" | |
| deb_dir="apps/desktop/src-tauri/target/${{ matrix.target }}/release/bundle/deb" | |
| appimages=("$appimage_dir"/*.AppImage) | |
| debs=("$deb_dir"/*.deb) | |
| appimage_signatures=("$appimage_dir"/*.AppImage.sig) | |
| deb_signatures=("$deb_dir"/*.deb.sig) | |
| if [[ ${#appimages[@]} -ne 1 || ${#debs[@]} -ne 1 ]]; then | |
| echo "Expected one AppImage and one .deb for ${{ matrix.target }}" | |
| exit 1 | |
| fi | |
| if [[ ${#appimage_signatures[@]} -ne 1 || ${#deb_signatures[@]} -ne 1 ]]; then | |
| echo "Expected AppImage and .deb updater signatures for ${{ matrix.target }}" | |
| exit 1 | |
| fi | |
| package_arch=$(dpkg-deb --field "${debs[0]}" Architecture) | |
| if [[ "$package_arch" != "${{ matrix.debian_arch }}" ]]; then | |
| echo "Expected Debian architecture ${{ matrix.debian_arch }}, got $package_arch" | |
| exit 1 | |
| fi | |
| package_version=$(dpkg-deb --field "${debs[0]}" Version) | |
| if [[ "$package_version" != "${{ needs.compute-version.outputs.version }}" ]]; then | |
| echo "Expected Debian version ${{ needs.compute-version.outputs.version }}, got $package_version" | |
| exit 1 | |
| fi | |
| if [[ "$(basename "${appimages[0]}")" != *"${{ needs.compute-version.outputs.version }}"* ]]; then | |
| echo "AppImage filename does not contain version ${{ needs.compute-version.outputs.version }}" | |
| exit 1 | |
| fi | |
| if ! file "${appimages[0]}" | grep -Fq "${{ matrix.file_arch }}"; then | |
| file "${appimages[0]}" | |
| echo "AppImage architecture does not match ${{ matrix.target }}" | |
| exit 1 | |
| fi | |
| if [[ "${{ inputs.channel }}" == "stable" ]]; then | |
| cargo run --locked -p updater-core --bin verify-updater-signature -- \ | |
| "${appimages[0]}" \ | |
| "${appimage_signatures[0]}" \ | |
| apps/desktop/src-tauri/tauri.conf.json | |
| cargo run --locked -p updater-core --bin verify-updater-signature -- \ | |
| "${debs[0]}" \ | |
| "${deb_signatures[0]}" \ | |
| apps/desktop/src-tauri/tauri.conf.json | |
| fi | |
| bundles=("$appimage_dir"/* "$deb_dir"/*) | |
| for bundle in "${bundles[@]}"; do | |
| [[ -f "$bundle" && "$bundle" != *.sha256 ]] || continue | |
| # Record the basename, like the CI job does, so `sha256sum -c` works | |
| # against the uploaded artifact instead of a CI-only path. | |
| name=$(basename "$bundle") | |
| (cd "$(dirname "$bundle")" && sha256sum "$name" > "$name.sha256") | |
| done | |
| - run: | | |
| deb=$(find "apps/desktop/src-tauri/target/${{ matrix.target }}/release/bundle/deb" -maxdepth 1 -name '*.deb' -type f -print -quit) | |
| binary_name="${{ inputs.channel == 'staging' && 'anarlog-staging' || 'anarlog' }}" | |
| docker run --rm \ | |
| --platform linux/${{ matrix.docker_arch }} \ | |
| --volume "$PWD:/workspace:ro" \ | |
| --env DEB_PATH="/workspace/$deb" \ | |
| --env BINARY_PATH="/usr/bin/$binary_name" \ | |
| ubuntu:24.04 \ | |
| bash -euxo pipefail -c ' | |
| export DEBIAN_FRONTEND=noninteractive | |
| apt-get update | |
| apt-get install -y "$DEB_PATH" | |
| apt-get install -y dbus-x11 xvfb | |
| set +e | |
| timeout --signal=TERM 20s xvfb-run -a dbus-run-session -- "$BINARY_PATH" >/tmp/anarlog.log 2>&1 | |
| status=$? | |
| set -e | |
| cat /tmp/anarlog.log | |
| if [[ $status -ne 124 ]]; then | |
| echo "Anarlog exited before the smoke timeout with status $status" | |
| exit 1 | |
| fi | |
| ' | |
| - run: | | |
| mkdir -p target/release | |
| find target/${{ matrix.target }}/release/bundle/appimage target/${{ matrix.target }}/release/bundle/deb \ | |
| -maxdepth 1 -type f -exec cp {} target/release/ \; | |
| working-directory: ./apps/desktop/src-tauri | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: anarlog-${{ inputs.channel }}-linux-${{ matrix.artifact_name }} | |
| path: | | |
| apps/desktop/src-tauri/target/${{ matrix.target }}/release/bundle/appimage/* | |
| apps/desktop/src-tauri/target/${{ matrix.target }}/release/bundle/deb/* | |
| if-no-files-found: error | |
| retention-days: 7 | |
| - if: ${{ inputs.channel != 'staging' }} | |
| uses: ./.github/actions/cn_release | |
| with: | |
| cmd: upload | |
| app: ${{ env.CN_APPLICATION }} | |
| key: ${{ secrets.CN_API_KEY }} | |
| channel: ${{ env.RELEASE_CHANNEL }} | |
| framework: tauri | |
| working-directory: ./apps/desktop | |
| - if: ${{ inputs.channel == 'staging' }} | |
| run: | | |
| timestamp=$(date -u +"%Y%m%d%H%M%S") | |
| commit_hash=$(git rev-parse --short HEAD) | |
| prefix="anarlog-staging-${timestamp}-${commit_hash}-linux-${{ matrix.artifact_name }}" | |
| upload_dir=$(mktemp -d) | |
| for bundle_dir in appimage deb; do | |
| for file in apps/desktop/src-tauri/target/${{ matrix.target }}/release/bundle/$bundle_dir/*; do | |
| [[ -f "$file" ]] || continue | |
| cp "$file" "$upload_dir/${prefix}-$(basename "$file")" | |
| done | |
| done | |
| aws s3 cp "$upload_dir/" \ | |
| s3://hyprnote-build/desktop/staging/ \ | |
| --recursive \ | |
| --endpoint-url ${{ secrets.CLOUDFLARE_R2_ENDPOINT_URL }} \ | |
| --region auto | |
| env: | |
| AWS_ACCESS_KEY_ID: ${{ secrets.CLOUDFLARE_R2_ACCESS_KEY_ID }} | |
| AWS_SECRET_ACCESS_KEY: ${{ secrets.CLOUDFLARE_R2_SECRET_ACCESS_KEY }} | |
| build-windows: | |
| needs: [compute-version, cn-draft] | |
| if: ${{ inputs.include_windows && !cancelled() && (needs.cn-draft.result == 'success' || needs.cn-draft.result == 'skipped') }} | |
| permissions: | |
| contents: read | |
| id-token: write | |
| runs-on: windows-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ inputs.candidate_sha || github.sha }} | |
| lfs: true | |
| fetch-depth: 0 | |
| fetch-tags: true | |
| submodules: recursive | |
| - run: ./scripts/version.sh "./apps/desktop/src-tauri/tauri.conf.json" "${{ needs.compute-version.outputs.version }}" | |
| shell: bash | |
| - uses: ./.github/actions/rust_install | |
| with: | |
| platform: windows | |
| - uses: ./.github/actions/pnpm_install | |
| - run: pnpm -F ui build | |
| - shell: bash | |
| run: | | |
| cargo build --locked --release -p anarlog-cli --target x86_64-pc-windows-msvc | |
| cp \ | |
| target/x86_64-pc-windows-msvc/release/anarlog.exe \ | |
| apps/desktop/src-tauri/resources/cli/anarlog-cli-x86_64-pc-windows-msvc.exe | |
| ./scripts/sidecar.sh \ | |
| "./apps/desktop/${{ env.TAURI_CONF_PATH }}" \ | |
| resources/cli/anarlog-cli | |
| - name: Build Windows app | |
| shell: bash | |
| run: | | |
| BUILD_ARGS=( | |
| --ci | |
| --target x86_64-pc-windows-msvc | |
| --config "${{ env.TAURI_CONF_PATH }}" | |
| --verbose | |
| ) | |
| if [[ "${{ inputs.channel }}" == "staging" ]]; then | |
| BUILD_ARGS+=(--bundles nsis --features devtools --no-sign) | |
| else | |
| BUILD_ARGS+=(--no-bundle --no-sign) | |
| fi | |
| pnpm -F desktop tauri build "${BUILD_ARGS[@]}" | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| POSTHOG_API_KEY: ${{ secrets.POSTHOG_API_KEY }} | |
| VITE_POSTHOG_API_KEY: ${{ secrets.POSTHOG_API_KEY }} | |
| SENTRY_DSN: ${{ secrets.SENTRY_DSN_HYPRNOTE_2 }} | |
| APP_VERSION: ${{ needs.compute-version.outputs.version }} | |
| TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} | |
| TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} | |
| VITE_SUPABASE_URL: ${{ secrets.VITE_SUPABASE_URL }} | |
| VITE_SUPABASE_ANON_KEY: ${{ secrets.VITE_SUPABASE_ANON_KEY }} | |
| VITE_APP_URL: https://anarlog.so | |
| VITE_API_URL: https://api.anarlog.so | |
| VITE_PRO_PRODUCT_ID: ${{ secrets.VITE_PRO_PRODUCT_ID }} | |
| VITE_APP_VERSION: ${{ needs.compute-version.outputs.version }} | |
| - if: ${{ inputs.channel == 'stable' }} | |
| name: Sign in to Azure | |
| uses: azure/login@v3 | |
| with: | |
| client-id: ${{ vars.AZURE_CLIENT_ID }} | |
| tenant-id: ${{ vars.AZURE_TENANT_ID }} | |
| subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }} | |
| - if: ${{ inputs.channel == 'stable' }} | |
| name: Sign Windows binaries | |
| uses: azure/artifact-signing-action@v2 | |
| with: | |
| endpoint: ${{ vars.AZURE_ARTIFACT_SIGNING_ENDPOINT }} | |
| signing-account-name: ${{ vars.AZURE_ARTIFACT_SIGNING_ACCOUNT_NAME }} | |
| certificate-profile-name: ${{ vars.AZURE_ARTIFACT_SIGNING_CERTIFICATE_PROFILE_NAME }} | |
| files: | | |
| ${{ github.workspace }}\apps\desktop\src-tauri\target\x86_64-pc-windows-msvc\release\anarlog.exe | |
| ${{ github.workspace }}\apps\desktop\src-tauri\resources\cli\anarlog-cli-x86_64-pc-windows-msvc.exe | |
| file-digest: SHA256 | |
| timestamp-rfc3161: http://timestamp.acs.microsoft.com | |
| timestamp-digest: SHA256 | |
| - if: ${{ inputs.channel == 'stable' }} | |
| name: Bundle signed Windows app | |
| shell: bash | |
| env: | |
| TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} | |
| TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} | |
| run: | | |
| pnpm -F desktop tauri bundle \ | |
| --ci \ | |
| --bundles nsis \ | |
| --target x86_64-pc-windows-msvc \ | |
| --config "${{ env.TAURI_CONF_PATH }}" \ | |
| --verbose \ | |
| --no-sign | |
| - if: ${{ inputs.channel == 'stable' }} | |
| id: windows-installer | |
| name: Locate Windows installer | |
| shell: pwsh | |
| run: | | |
| $bundleDir = "apps/desktop/src-tauri/target/x86_64-pc-windows-msvc/release/bundle/nsis" | |
| $installers = @(Get-ChildItem $bundleDir -Filter "*.exe") | |
| if ($installers.Count -ne 1) { | |
| throw "Expected one NSIS installer in $bundleDir, found $($installers.Count)" | |
| } | |
| "path=$($installers[0].FullName)" | | |
| Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append | |
| - if: ${{ inputs.channel == 'stable' }} | |
| name: Sign Windows installer | |
| uses: azure/artifact-signing-action@v2 | |
| with: | |
| endpoint: ${{ vars.AZURE_ARTIFACT_SIGNING_ENDPOINT }} | |
| signing-account-name: ${{ vars.AZURE_ARTIFACT_SIGNING_ACCOUNT_NAME }} | |
| certificate-profile-name: ${{ vars.AZURE_ARTIFACT_SIGNING_CERTIFICATE_PROFILE_NAME }} | |
| files: ${{ steps.windows-installer.outputs.path }} | |
| file-digest: SHA256 | |
| timestamp-rfc3161: http://timestamp.acs.microsoft.com | |
| timestamp-digest: SHA256 | |
| - if: ${{ inputs.channel == 'stable' }} | |
| name: Sign Windows updater artifact | |
| shell: pwsh | |
| env: | |
| TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} | |
| TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} | |
| run: | | |
| $installer = "${{ steps.windows-installer.outputs.path }}" | |
| $signaturePath = "$installer.sig" | |
| Remove-Item $signaturePath -Force -ErrorAction SilentlyContinue | |
| pnpm -F desktop tauri signer sign $installer | |
| if ($LASTEXITCODE -ne 0 -or -not (Test-Path $signaturePath)) { | |
| throw "Could not create the Windows updater signature" | |
| } | |
| - shell: pwsh | |
| env: | |
| EXPECTED_VERSION: ${{ needs.compute-version.outputs.version }} | |
| RELEASE_CHANNEL: ${{ inputs.channel }} | |
| WINDOWS_SIGNER_ORGANIZATION: ${{ vars.WINDOWS_SIGNER_ORGANIZATION }} | |
| run: | | |
| $config = Get-Content "apps/desktop/src-tauri/tauri.conf.json" | ConvertFrom-Json | |
| if ($config.version -ne $env:EXPECTED_VERSION) { | |
| throw "Expected app version $env:EXPECTED_VERSION, got $($config.version)" | |
| } | |
| $bundleDir = "apps/desktop/src-tauri/target/x86_64-pc-windows-msvc/release/bundle/nsis" | |
| $installers = @(Get-ChildItem $bundleDir -Filter "*.exe") | |
| if ($installers.Count -ne 1) { | |
| throw "Expected one NSIS installer in $bundleDir, found $($installers.Count)" | |
| } | |
| if ($installers[0].Name -notmatch [regex]::Escape($env:EXPECTED_VERSION)) { | |
| throw "Installer filename does not contain version $env:EXPECTED_VERSION" | |
| } | |
| if ($env:RELEASE_CHANNEL -eq "stable") { | |
| $updaterSignatures = @(Get-ChildItem $bundleDir -Filter "*.exe.sig") | |
| $expectedUpdaterSignature = "$($installers[0].FullName).sig" | |
| if ( | |
| $updaterSignatures.Count -ne 1 -or | |
| $updaterSignatures[0].FullName -ne $expectedUpdaterSignature | |
| ) { | |
| throw "Expected the NSIS installer and its updater signature for the stable release" | |
| } | |
| $appBinary = "apps/desktop/src-tauri/target/x86_64-pc-windows-msvc/release/anarlog.exe" | |
| $sidecarBinary = "apps/desktop/src-tauri/resources/cli/anarlog-cli-x86_64-pc-windows-msvc.exe" | |
| foreach ($signedFile in @($appBinary, $sidecarBinary, $installers[0].FullName)) { | |
| $signature = Get-AuthenticodeSignature $signedFile | |
| if ($signature.Status -ne "Valid") { | |
| throw "Invalid Authenticode signature for $signedFile`: $($signature.Status)" | |
| } | |
| if (-not $signature.SignerCertificate) { | |
| throw "Authenticode signer certificate is missing for $signedFile" | |
| } | |
| $signerOrganization = $signature.SignerCertificate.GetNameInfo( | |
| [System.Security.Cryptography.X509Certificates.X509NameType]::SimpleName, | |
| $false | |
| ) | |
| if ($signerOrganization -ne $env:WINDOWS_SIGNER_ORGANIZATION) { | |
| throw "Unexpected Authenticode signer '$signerOrganization' for $signedFile" | |
| } | |
| if (-not $signature.TimeStamperCertificate) { | |
| throw "Authenticode signature is missing a trusted timestamp for $signedFile" | |
| } | |
| } | |
| } | |
| $hash = (Get-FileHash -Algorithm SHA256 $installers[0].FullName).Hash.ToLowerInvariant() | |
| "$hash $($installers[0].Name)" | | |
| Out-File "$($installers[0].FullName).sha256" -Encoding ascii -NoNewline | |
| - if: ${{ inputs.channel == 'stable' }} | |
| name: Verify Windows updater signature | |
| shell: bash | |
| run: | | |
| shopt -s nullglob | |
| bundle_dir="apps/desktop/src-tauri/target/x86_64-pc-windows-msvc/release/bundle/nsis" | |
| installers=("$bundle_dir"/*.exe) | |
| cargo run --locked -p updater-core --bin verify-updater-signature -- \ | |
| "${installers[0]}" \ | |
| "${installers[0]}.sig" \ | |
| apps/desktop/src-tauri/tauri.conf.json | |
| - shell: pwsh | |
| run: | | |
| $source = "apps/desktop/src-tauri/target/x86_64-pc-windows-msvc/release/bundle/nsis" | |
| $destination = "apps/desktop/src-tauri/target/release" | |
| New-Item -ItemType Directory -Force -Path $destination | Out-Null | |
| Copy-Item "$source/*" $destination | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: anarlog-${{ inputs.channel }}-windows-x64 | |
| path: apps/desktop/src-tauri/target/x86_64-pc-windows-msvc/release/bundle/nsis/* | |
| if-no-files-found: error | |
| retention-days: 7 | |
| - if: ${{ inputs.channel != 'staging' }} | |
| uses: ./.github/actions/cn_release | |
| with: | |
| cmd: upload | |
| app: ${{ env.CN_APPLICATION }} | |
| key: ${{ secrets.CN_API_KEY }} | |
| channel: ${{ env.RELEASE_CHANNEL }} | |
| framework: tauri | |
| working-directory: ./apps/desktop | |
| verify-cn-release: | |
| if: ${{ inputs.channel != 'staging' && !cancelled() && needs.build-macos.result == 'success' && (!inputs.include_linux || needs.build-linux.result == 'success') && (!inputs.include_windows || needs.build-windows.result == 'success') }} | |
| needs: [compute-version, build-macos, build-windows, build-linux] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ inputs.candidate_sha || github.sha }} | |
| - id: release | |
| uses: ./.github/actions/cn_release | |
| with: | |
| raw: >- | |
| release show ${{ env.CN_APPLICATION }} ${{ needs.compute-version.outputs.version }} | |
| --channel ${{ env.RELEASE_CHANNEL }} | |
| key: ${{ secrets.CN_API_KEY }} | |
| - env: | |
| EXPECTED_VERSION: ${{ needs.compute-version.outputs.version }} | |
| RELEASE: ${{ steps.release.outputs.raw }} | |
| run: | | |
| ACTUAL_VERSION=$(echo "$RELEASE" | jq -r '.version // empty') | |
| if [[ "$ACTUAL_VERSION" != "$EXPECTED_VERSION" ]]; then | |
| echo "::error::Expected CrabNebula release $EXPECTED_VERSION, got $ACTUAL_VERSION" | |
| exit 1 | |
| fi | |
| EXPECTED_PLATFORMS=( | |
| "dmg-aarch64" | |
| "dmg-x86_64" | |
| ) | |
| if [[ "${{ inputs.include_linux }}" == "true" ]]; then | |
| EXPECTED_PLATFORMS+=( | |
| "appimage-x86_64" | |
| "debian-x86_64" | |
| "appimage-aarch64" | |
| "debian-aarch64" | |
| ) | |
| fi | |
| if [[ "${{ inputs.include_windows }}" == "true" ]]; then | |
| EXPECTED_PLATFORMS+=("nsis-x86_64") | |
| fi | |
| INVALID_PLATFORMS=() | |
| for platform in "${EXPECTED_PLATFORMS[@]}"; do | |
| if ! echo "$RELEASE" | jq -e --arg platform "$platform" \ | |
| '[.assets[]? | select(.publicPlatform == $platform and ((.size // 0) | tonumber) > 0)] | length == 1' > /dev/null; then | |
| INVALID_PLATFORMS+=("$platform") | |
| fi | |
| done | |
| if [[ "${#INVALID_PLATFORMS[@]}" -gt 0 ]]; then | |
| echo "::error::CrabNebula release $EXPECTED_VERSION has missing, duplicate, or empty download assets: ${INVALID_PLATFORMS[*]}" | |
| exit 1 | |
| fi | |
| EXPECTED_UPDATE_PLATFORMS=( | |
| "darwin-aarch64" | |
| "darwin-x86_64" | |
| ) | |
| if [[ "${{ inputs.include_linux }}" == "true" ]]; then | |
| EXPECTED_UPDATE_PLATFORMS+=( | |
| "linux-x86_64-appimage" | |
| "linux-x86_64-deb" | |
| "linux-aarch64-appimage" | |
| "linux-aarch64-deb" | |
| ) | |
| fi | |
| if [[ "${{ inputs.include_windows }}" == "true" ]]; then | |
| EXPECTED_UPDATE_PLATFORMS+=("windows-x86_64-nsis") | |
| fi | |
| INVALID_UPDATE_PLATFORMS=() | |
| for platform in "${EXPECTED_UPDATE_PLATFORMS[@]}"; do | |
| if ! echo "$RELEASE" | jq -e --arg platform "$platform" \ | |
| '[.assets[]? | select(.updatePlatform == $platform and ((.signature // "") | length) > 0 and ((.size // 0) | tonumber) > 0)] | length == 1' > /dev/null; then | |
| INVALID_UPDATE_PLATFORMS+=("$platform") | |
| fi | |
| done | |
| if [[ "${#INVALID_UPDATE_PLATFORMS[@]}" -gt 0 ]]; then | |
| echo "::error::CrabNebula release $EXPECTED_VERSION has missing, duplicate, unsigned, or empty updater assets: ${INVALID_UPDATE_PLATFORMS[*]}" | |
| exit 1 | |
| fi | |
| - name: Record release provenance | |
| env: | |
| CN_ASSET_ID: ${{ steps.release.outputs.cli-asset-id }} | |
| CN_SHA256: ${{ steps.release.outputs.cli-sha256 }} | |
| CN_VERSION: ${{ steps.release.outputs.cli-version }} | |
| RELEASE: ${{ steps.release.outputs.raw }} | |
| VERSION: ${{ needs.compute-version.outputs.version }} | |
| run: | | |
| mkdir -p release-provenance | |
| printf '%s' "$RELEASE" > "$RUNNER_TEMP/crabnebula-release.json" | |
| node scripts/desktop-release-provenance.mjs create \ | |
| --release "$RUNNER_TEMP/crabnebula-release.json" \ | |
| --output release-provenance/manifest.json \ | |
| --version "$VERSION" \ | |
| --candidate-sha "$GITHUB_SHA" \ | |
| --run-id "$GITHUB_RUN_ID" \ | |
| --cn-version "$CN_VERSION" \ | |
| --cn-asset-id "$CN_ASSET_ID" \ | |
| --cn-sha256 "$CN_SHA256" | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: desktop-release-provenance-${{ needs.compute-version.outputs.version }}-${{ github.sha }} | |
| path: release-provenance/manifest.json | |
| if-no-files-found: error | |
| retention-days: 30 |