379 lines
15 KiB
YAML
379 lines
15 KiB
YAML
name: "Publish: Release Uploads"
|
|
run-name: "Publish: Release Uploads (Build ${{ inputs.build_number }})"
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
build_number:
|
|
description: Build number to publish
|
|
required: true
|
|
upload_app_store:
|
|
description: Upload iOS/macOS/visionOS artifacts to App Store Connect
|
|
required: false
|
|
default: "false"
|
|
distribute_testflight:
|
|
description: Distribute an already-uploaded iOS build to TestFlight
|
|
required: false
|
|
default: "false"
|
|
upload_sparkle:
|
|
description: Publish Sparkle artifacts
|
|
required: false
|
|
default: "true"
|
|
upload_microsoft_store:
|
|
description: Upload Windows package to Microsoft Partner Center beta lane
|
|
required: false
|
|
default: "false"
|
|
sparkle_channel:
|
|
description: Sparkle channel, defaults to build-<number>
|
|
required: false
|
|
default: ""
|
|
sparkle_point_main:
|
|
description: Point Sparkle main appcast/default channel to selected channel
|
|
required: false
|
|
default: "true"
|
|
testflight_distribute_external:
|
|
description: Distribute to external TestFlight groups
|
|
required: false
|
|
default: "false"
|
|
testflight_groups:
|
|
description: Optional comma-separated TestFlight groups for external distribution
|
|
required: false
|
|
default: ""
|
|
ios_uses_non_exempt_encryption:
|
|
description: Set true only when the iOS build uses non-exempt encryption
|
|
required: false
|
|
default: "false"
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
concurrency:
|
|
group: burrow-publish-store-uploads-${{ inputs.build_number }}
|
|
cancel-in-progress: true
|
|
|
|
env:
|
|
BURROW_RELEASE_GCS_BUCKET: ${{ vars.BURROW_RELEASE_GCS_BUCKET || 'burrow-net-releases' }}
|
|
BURROW_GOOGLE_PROJECT_ID: ${{ vars.GOOGLE_PROJECT_ID || 'project-88c23ce9-918a-470a-b33' }}
|
|
GOOGLE_CLOUD_PROJECT: ${{ vars.GOOGLE_PROJECT_ID || 'project-88c23ce9-918a-470a-b33' }}
|
|
BURROW_GOOGLE_PROJECT_NUMBER: ${{ vars.GOOGLE_PROJECT_NUMBER || '416198671487' }}
|
|
BURROW_GOOGLE_WIF_POOL_ID: ${{ vars.BURROW_GOOGLE_WIF_POOL_ID || 'burrow-authentik' }}
|
|
BURROW_GOOGLE_WIF_PROVIDER_ID: ${{ vars.BURROW_GOOGLE_WIF_PROVIDER_ID || 'forgejo-runners' }}
|
|
BURROW_GOOGLE_WIF_SERVICE_ACCOUNT: ${{ vars.BURROW_GOOGLE_WIF_SERVICE_ACCOUNT || 'burrow-forgejo-runner@project-88c23ce9-918a-470a-b33.iam.gserviceaccount.com' }}
|
|
BURROW_AUTHENTIK_WIF_ISSUER: ${{ vars.BURROW_AUTHENTIK_WIF_ISSUER || 'https://auth.burrow.net/application/o/google-cloud/' }}
|
|
BURROW_AUTHENTIK_WIF_AUDIENCE: ${{ vars.BURROW_AUTHENTIK_WIF_AUDIENCE || 'google-wif.burrow.net' }}
|
|
BURROW_AUTHENTIK_WIF_CLIENT_ID: ${{ vars.BURROW_AUTHENTIK_WIF_CLIENT_ID || 'google-wif.burrow.net' }}
|
|
BURROW_AUTHENTIK_WIF_CLIENT_SECRET: ${{ secrets.BURROW_AUTHENTIK_WIF_CLIENT_SECRET }}
|
|
BURROW_NIX_CACHE_URL: ${{ vars.BURROW_NIX_CACHE_URL || 'https://nix.burrow.net/burrow' }}
|
|
BURROW_NIX_CACHE_PUBLIC_KEY: ${{ vars.BURROW_NIX_CACHE_PUBLIC_KEY }}
|
|
BURROW_APPLE_BUNDLE_ID: ${{ vars.BURROW_APPLE_BUNDLE_ID || 'net.burrow.app' }}
|
|
BURROW_APPLE_NETWORK_EXTENSION_BUNDLE_ID: ${{ vars.BURROW_APPLE_NETWORK_EXTENSION_BUNDLE_ID || 'net.burrow.app.network' }}
|
|
BURROW_IOS_DISTRIBUTION_CERTIFICATE_ID: ${{ vars.BURROW_IOS_DISTRIBUTION_CERTIFICATE_ID || '3G42677598' }}
|
|
BURROW_DEVELOPER_ID_CERTIFICATE_ID: ${{ vars.BURROW_DEVELOPER_ID_CERTIFICATE_ID || '9JKN6HXBHC' }}
|
|
BURROW_APPLE_SIGNING_MODE: ${{ vars.BURROW_APPLE_SIGNING_MODE || 'google-kms-staged' }}
|
|
BURROW_NOTARIZE_MACOS: ${{ vars.BURROW_NOTARIZE_MACOS || 'true' }}
|
|
BURROW_SPARKLE_SIGN_WITH_KMS: ${{ vars.BURROW_SPARKLE_SIGN_WITH_KMS || 'true' }}
|
|
BURROW_SPARKLE_KMS_KEY: ${{ vars.BURROW_SPARKLE_KMS_KEY || 'sparkle-ed25519' }}
|
|
BURROW_SPARKLE_KMS_VERSION: ${{ vars.BURROW_SPARKLE_KMS_VERSION || '1' }}
|
|
BURROW_KMS_LOCATION: ${{ vars.BURROW_KMS_LOCATION || 'global' }}
|
|
BURROW_KMS_KEYRING: ${{ vars.BURROW_KMS_KEYRING || 'burrow-identity' }}
|
|
TESTFLIGHT_WAIT_PROCESSING_TIMEOUT_SECONDS: ${{ vars.TESTFLIGHT_WAIT_PROCESSING_TIMEOUT_SECONDS || '7200' }}
|
|
|
|
jobs:
|
|
sign-apple-kms:
|
|
name: Sign (Apple KMS)
|
|
if: ${{ inputs.upload_app_store == 'true' || inputs.upload_sparkle == 'true' }}
|
|
runs-on: namespace-profile-linux-medium
|
|
steps:
|
|
- name: Checkout
|
|
uses: https://code.forgejo.org/actions/checkout@v4
|
|
with:
|
|
token: ${{ github.token }}
|
|
fetch-depth: 0
|
|
|
|
- name: Ensure Nix
|
|
shell: bash
|
|
run: bash Scripts/ci/ensure-nix.sh
|
|
|
|
- name: Configure Age
|
|
shell: bash
|
|
env:
|
|
AGE_FORGE_SSH_KEY: ${{ secrets.AGE_FORGE_SSH_KEY }}
|
|
run: Scripts/resolve-age-identity.sh >> "$GITHUB_ENV"
|
|
|
|
- name: Decrypt Release Secrets
|
|
uses: ./.forgejo/actions/decrypt-release-secrets
|
|
|
|
- name: Validate App Store Credentials
|
|
shell: bash
|
|
run: Scripts/ci/check-release-config.sh app-store
|
|
|
|
- name: Download Staged Apple Artifacts
|
|
shell: bash
|
|
env:
|
|
BUILD_NUMBER: ${{ inputs.build_number }}
|
|
run: |
|
|
set -euo pipefail
|
|
nix develop .#ci -c Scripts/ci/google-wif-auth.sh
|
|
mkdir -p "dist/builds/${BUILD_NUMBER}/apple"
|
|
nix develop .#ci -c gcloud storage rsync --recursive "gs://${BURROW_RELEASE_GCS_BUCKET}/builds/${BUILD_NUMBER}/apple" "dist/builds/${BUILD_NUMBER}/apple"
|
|
find "dist/builds/${BUILD_NUMBER}/apple" -type f -print | sort
|
|
|
|
- name: Sync Apple Provisioning Profiles
|
|
shell: bash
|
|
run: nix develop .#ci -c Scripts/ci/sync-apple-provisioning-profiles.sh all
|
|
|
|
- name: Sign Apple Artifacts With KMS
|
|
shell: bash
|
|
env:
|
|
BUILD_NUMBER: ${{ inputs.build_number }}
|
|
SPARKLE_CHANNEL_INPUT: ${{ inputs.sparkle_channel }}
|
|
run: |
|
|
set -euo pipefail
|
|
channel="${SPARKLE_CHANNEL_INPUT:-}"
|
|
if [[ -z "$channel" ]]; then
|
|
channel="build-${BUILD_NUMBER}"
|
|
fi
|
|
SPARKLE_CHANNEL="$channel" nix develop .#ci -c Scripts/apple/sign-release-artifacts-kms.sh all
|
|
|
|
- name: Upload Signed Apple Artifacts
|
|
shell: bash
|
|
env:
|
|
BUILD_NUMBER: ${{ inputs.build_number }}
|
|
run: nix develop .#ci -c Scripts/ci/upload-release-storage.sh
|
|
|
|
upload-app-store:
|
|
name: Upload (App Store Connect)
|
|
needs: sign-apple-kms
|
|
if: ${{ inputs.upload_app_store == 'true' }}
|
|
runs-on: namespace-profile-macos-large
|
|
steps:
|
|
- name: Checkout
|
|
uses: https://code.forgejo.org/actions/checkout@v4
|
|
with:
|
|
token: ${{ github.token }}
|
|
fetch-depth: 0
|
|
|
|
- name: Ensure Nix
|
|
shell: bash
|
|
run: bash Scripts/ci/ensure-nix.sh
|
|
|
|
- name: Configure Age
|
|
shell: bash
|
|
env:
|
|
AGE_FORGE_SSH_KEY: ${{ secrets.AGE_FORGE_SSH_KEY }}
|
|
run: Scripts/resolve-age-identity.sh >> "$GITHUB_ENV"
|
|
|
|
- name: Decrypt Release Secrets
|
|
uses: ./.forgejo/actions/decrypt-release-secrets
|
|
|
|
- name: Validate App Store Credentials
|
|
shell: bash
|
|
run: Scripts/ci/check-release-config.sh app-store
|
|
|
|
- name: Download Apple Artifacts From GCS
|
|
shell: bash
|
|
env:
|
|
BUILD_NUMBER: ${{ inputs.build_number }}
|
|
run: |
|
|
set -euo pipefail
|
|
nix develop .#ci -c Scripts/ci/google-wif-auth.sh
|
|
mkdir -p publish/apple
|
|
nix develop .#ci -c gcloud storage rsync --recursive "gs://${BURROW_RELEASE_GCS_BUCKET}/builds/${BUILD_NUMBER}/apple" publish/apple
|
|
find publish/apple -type f -print | sort
|
|
|
|
- name: Upload Apple Packages
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
Scripts/ci/check-release-config.sh app-store
|
|
key_dir="${RUNNER_TEMP:-/tmp}/burrow-asc"
|
|
altool_key_dir="${HOME}/.appstoreconnect/private_keys"
|
|
mkdir -p "$key_dir" "$altool_key_dir"
|
|
key_path="$key_dir/AuthKey_${ASC_API_KEY_ID}.p8"
|
|
if [[ -n "${ASC_API_KEY_PATH:-}" && -f "${ASC_API_KEY_PATH:-}" ]]; then
|
|
cp "$ASC_API_KEY_PATH" "$key_path"
|
|
else
|
|
printf '%s' "$ASC_API_KEY_BASE64" | base64 --decode > "$key_path"
|
|
fi
|
|
cp "$key_path" "$altool_key_dir/AuthKey_${ASC_API_KEY_ID}.p8"
|
|
chmod 600 "$key_path" "$altool_key_dir/AuthKey_${ASC_API_KEY_ID}.p8"
|
|
shopt -s nullglob
|
|
artifacts=()
|
|
for artifact in publish/apple/*.ipa publish/apple/*.pkg; do
|
|
[[ "$artifact" == *.unsigned.ipa ]] && continue
|
|
artifacts+=("$artifact")
|
|
done
|
|
if (( ${#artifacts[@]} == 0 )); then
|
|
echo "::error ::No Apple upload artifacts found for build ${{ inputs.build_number }}."
|
|
exit 1
|
|
fi
|
|
for artifact in "${artifacts[@]}"; do
|
|
upload_type="ios"
|
|
if [[ "$artifact" == *.pkg ]]; then
|
|
upload_type="macos"
|
|
fi
|
|
xcrun altool --upload-app \
|
|
--type "$upload_type" \
|
|
--file "$artifact" \
|
|
--apiKey "$ASC_API_KEY_ID" \
|
|
--apiIssuer "$ASC_API_ISSUER_ID"
|
|
done
|
|
|
|
release-ios-testflight:
|
|
name: Release (TestFlight iOS Testers)
|
|
needs: upload-app-store
|
|
if: ${{ inputs.upload_app_store == 'true' }}
|
|
runs-on: namespace-profile-linux-testflight
|
|
steps:
|
|
- name: Checkout
|
|
uses: https://code.forgejo.org/actions/checkout@v4
|
|
with:
|
|
token: ${{ github.token }}
|
|
fetch-depth: 0
|
|
|
|
- name: Ensure Nix
|
|
shell: bash
|
|
run: bash Scripts/ci/ensure-nix.sh
|
|
|
|
- name: Configure Age
|
|
shell: bash
|
|
env:
|
|
AGE_FORGE_SSH_KEY: ${{ secrets.AGE_FORGE_SSH_KEY }}
|
|
run: Scripts/resolve-age-identity.sh >> "$GITHUB_ENV"
|
|
|
|
- name: Decrypt Release Secrets
|
|
uses: ./.forgejo/actions/decrypt-release-secrets
|
|
|
|
- name: Validate App Store Credentials
|
|
shell: bash
|
|
run: Scripts/ci/check-release-config.sh app-store
|
|
|
|
- name: Distribute iOS Build To TestFlight
|
|
shell: bash
|
|
env:
|
|
BUILD_NUMBER: ${{ inputs.build_number }}
|
|
TESTFLIGHT_DISTRIBUTE_EXTERNAL: ${{ inputs.testflight_distribute_external || 'false' }}
|
|
TESTFLIGHT_GROUPS: ${{ inputs.testflight_groups || '' }}
|
|
TESTFLIGHT_INTERNAL_GROUP: Internal
|
|
IOS_USES_NON_EXEMPT_ENCRYPTION: ${{ inputs.ios_uses_non_exempt_encryption || 'false' }}
|
|
run: Scripts/ci/distribute-testflight.sh
|
|
|
|
distribute-ios-testflight:
|
|
name: Distribute (TestFlight iOS Testers)
|
|
if: ${{ inputs.upload_app_store != 'true' && inputs.distribute_testflight == 'true' }}
|
|
runs-on: namespace-profile-linux-testflight
|
|
steps:
|
|
- name: Checkout
|
|
uses: https://code.forgejo.org/actions/checkout@v4
|
|
with:
|
|
token: ${{ github.token }}
|
|
fetch-depth: 0
|
|
|
|
- name: Ensure Nix
|
|
shell: bash
|
|
run: bash Scripts/ci/ensure-nix.sh
|
|
|
|
- name: Configure Age
|
|
shell: bash
|
|
env:
|
|
AGE_FORGE_SSH_KEY: ${{ secrets.AGE_FORGE_SSH_KEY }}
|
|
run: Scripts/resolve-age-identity.sh >> "$GITHUB_ENV"
|
|
|
|
- name: Decrypt Release Secrets
|
|
uses: ./.forgejo/actions/decrypt-release-secrets
|
|
|
|
- name: Validate App Store Credentials
|
|
shell: bash
|
|
run: Scripts/ci/check-release-config.sh app-store
|
|
|
|
- name: Distribute iOS Build To TestFlight
|
|
shell: bash
|
|
env:
|
|
BUILD_NUMBER: ${{ inputs.build_number }}
|
|
TESTFLIGHT_DISTRIBUTE_EXTERNAL: ${{ inputs.testflight_distribute_external || 'false' }}
|
|
TESTFLIGHT_GROUPS: ${{ inputs.testflight_groups || '' }}
|
|
TESTFLIGHT_INTERNAL_GROUP: Internal
|
|
IOS_USES_NON_EXEMPT_ENCRYPTION: ${{ inputs.ios_uses_non_exempt_encryption || 'false' }}
|
|
run: Scripts/ci/distribute-testflight.sh
|
|
|
|
upload-sparkle:
|
|
name: Upload (Sparkle)
|
|
needs: sign-apple-kms
|
|
if: ${{ inputs.upload_sparkle == 'true' }}
|
|
runs-on: namespace-profile-linux-medium
|
|
steps:
|
|
- name: Checkout
|
|
uses: https://code.forgejo.org/actions/checkout@v4
|
|
with:
|
|
token: ${{ github.token }}
|
|
fetch-depth: 0
|
|
|
|
- name: Resolve Sparkle Channel
|
|
id: channel
|
|
shell: bash
|
|
env:
|
|
BUILD_NUMBER: ${{ inputs.build_number }}
|
|
run: |
|
|
set -euo pipefail
|
|
channel="${{ inputs.sparkle_channel }}"
|
|
if [[ -z "$channel" ]]; then
|
|
channel="build-${BUILD_NUMBER}"
|
|
fi
|
|
echo "channel=$channel" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Ensure Nix
|
|
shell: bash
|
|
run: bash Scripts/ci/ensure-nix.sh
|
|
|
|
- name: Publish Sparkle Channel
|
|
shell: bash
|
|
env:
|
|
BUILD_NUMBER: ${{ inputs.build_number }}
|
|
CHANNEL: ${{ steps.channel.outputs.channel }}
|
|
SPARKLE_POINT_MAIN: ${{ inputs.sparkle_point_main }}
|
|
run: |
|
|
set -euo pipefail
|
|
nix develop .#ci -c Scripts/ci/google-wif-auth.sh
|
|
mkdir -p "publish/sparkle/${CHANNEL}"
|
|
nix develop .#ci -c gcloud storage rsync --recursive "gs://${BURROW_RELEASE_GCS_BUCKET}/builds/${BUILD_NUMBER}/sparkle/${CHANNEL}" "publish/sparkle/${CHANNEL}" || true
|
|
if [[ ! -f "publish/sparkle/${CHANNEL}/appcast.xml" ]]; then
|
|
echo "::warning ::No Sparkle appcast staged for build ${BUILD_NUMBER}; skipping Sparkle publish."
|
|
find "publish/sparkle/${CHANNEL}" -maxdepth 2 -type f -print || true
|
|
exit 0
|
|
fi
|
|
nix develop .#ci -c gcloud storage rsync --recursive "publish/sparkle/${CHANNEL}" "gs://${BURROW_RELEASE_GCS_BUCKET}/sparkle/${CHANNEL}"
|
|
if [[ "$SPARKLE_POINT_MAIN" == "true" ]]; then
|
|
printf '%s\n' "$CHANNEL" > main-channel.txt
|
|
nix develop .#ci -c gcloud storage cp main-channel.txt "gs://${BURROW_RELEASE_GCS_BUCKET}/sparkle/main-channel.txt"
|
|
nix develop .#ci -c gcloud storage cp "publish/sparkle/${CHANNEL}/appcast.xml" "gs://${BURROW_RELEASE_GCS_BUCKET}/sparkle/appcast.xml"
|
|
nix develop .#ci -c gcloud storage rsync --recursive "publish/sparkle/${CHANNEL}" "gs://${BURROW_RELEASE_GCS_BUCKET}/sparkle/default"
|
|
fi
|
|
|
|
upload-microsoft-store:
|
|
name: Upload (Microsoft Store Beta)
|
|
if: ${{ inputs.upload_microsoft_store == 'true' }}
|
|
runs-on: namespace-profile-windows-large
|
|
steps:
|
|
- name: Checkout
|
|
uses: https://code.forgejo.org/actions/checkout@v4
|
|
with:
|
|
token: ${{ github.token }}
|
|
fetch-depth: 0
|
|
|
|
- name: Validate Microsoft Store Credentials
|
|
shell: pwsh
|
|
env:
|
|
MICROSOFT_TENANT_ID: ${{ secrets.MICROSOFT_TENANT_ID }}
|
|
MICROSOFT_CLIENT_ID: ${{ secrets.MICROSOFT_CLIENT_ID }}
|
|
MICROSOFT_CLIENT_SECRET: ${{ secrets.MICROSOFT_CLIENT_SECRET }}
|
|
run: |
|
|
foreach ($name in "MICROSOFT_TENANT_ID","MICROSOFT_CLIENT_ID","MICROSOFT_CLIENT_SECRET") {
|
|
if ([string]::IsNullOrWhiteSpace([Environment]::GetEnvironmentVariable($name))) {
|
|
throw "missing required environment variable: $name"
|
|
}
|
|
}
|
|
|
|
- name: Stop Until Store Package Exists
|
|
shell: pwsh
|
|
run: |
|
|
Write-Host "::warning ::Burrow only builds a Windows Rust stub today; Microsoft Store package upload is wired for credentials but waits on MSIX packaging."
|