Some checks failed
Cache: Publish Nix / Publish Nix Cache (push) Waiting to run
Build Rust / Cargo Test (push) Successful in 4m14s
Build Site / Next.js Build (push) Failing after 6s
Infra: OpenTofu / OpenTofu (grafana) (push) Successful in 5s
Lint Governance / BEP Metadata (push) Successful in 0s
Build: Android / Android Rust Core Stub (push) Failing after 23s
299 lines
12 KiB
YAML
299 lines
12 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"
|
|
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-${{ github.event.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' }}
|
|
|
|
jobs:
|
|
upload-app-store:
|
|
name: Upload (App Store Connect)
|
|
if: ${{ github.event.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: ${{ github.event.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=(publish/apple/*.ipa publish/apple/*.pkg)
|
|
if (( ${#artifacts[@]} == 0 )); then
|
|
echo "::error ::No Apple upload artifacts found for build ${{ github.event.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: ${{ github.event.inputs.upload_app_store == '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: Distribute iOS Build To TestFlight
|
|
shell: bash
|
|
env:
|
|
BUILD_NUMBER: ${{ github.event.inputs.build_number }}
|
|
TESTFLIGHT_DISTRIBUTE_EXTERNAL: ${{ github.event.inputs.testflight_distribute_external || 'false' }}
|
|
TESTFLIGHT_GROUPS: ${{ github.event.inputs.testflight_groups || '' }}
|
|
TESTFLIGHT_INTERNAL_GROUP: Internal
|
|
IOS_USES_NON_EXEMPT_ENCRYPTION: ${{ github.event.inputs.ios_uses_non_exempt_encryption || 'false' }}
|
|
run: |
|
|
set -euo pipefail
|
|
Scripts/ci/check-release-config.sh app-store
|
|
api_key_json="$(mktemp "${RUNNER_TEMP:-/tmp}/burrow-appstore-api-key.XXXXXX.json")"
|
|
key_file="$(mktemp "${RUNNER_TEMP:-/tmp}/burrow-appstore-key.XXXXXX.p8")"
|
|
if [[ -n "${ASC_API_KEY_PATH:-}" && -f "${ASC_API_KEY_PATH:-}" ]]; then
|
|
cp "$ASC_API_KEY_PATH" "$key_file"
|
|
else
|
|
printf '%s' "$ASC_API_KEY_BASE64" | base64 --decode > "$key_file"
|
|
fi
|
|
KEY_FILE="$key_file" python3 -c 'import json, os, pathlib; print(json.dumps({"key_id": os.environ["ASC_API_KEY_ID"], "issuer_id": os.environ["ASC_API_ISSUER_ID"], "key": pathlib.Path(os.environ["KEY_FILE"]).read_text(), "duration": 1200, "in_house": False}))' > "$api_key_json"
|
|
|
|
distribute_external="${TESTFLIGHT_DISTRIBUTE_EXTERNAL:-false}"
|
|
if [[ -z "$distribute_external" ]]; then
|
|
distribute_external="false"
|
|
fi
|
|
uses_non_exempt="${IOS_USES_NON_EXEMPT_ENCRYPTION:-false}"
|
|
if [[ -z "$uses_non_exempt" ]]; then
|
|
uses_non_exempt="false"
|
|
fi
|
|
|
|
args=(
|
|
run pilot
|
|
"api_key_path:${api_key_json}"
|
|
"app_identifier:${BURROW_APPLE_BUNDLE_ID:-net.burrow.app}"
|
|
"app_platform:ios"
|
|
"distribute_only:true"
|
|
"build_number:${BUILD_NUMBER}"
|
|
"wait_processing_interval:30"
|
|
"wait_processing_timeout_duration:1800"
|
|
"distribute_external:${distribute_external}"
|
|
"uses_non_exempt_encryption:${uses_non_exempt}"
|
|
)
|
|
|
|
if [[ -n "${TESTFLIGHT_GROUPS}" ]]; then
|
|
args+=("groups:${TESTFLIGHT_GROUPS}")
|
|
elif [[ "$distribute_external" != "true" && -n "${TESTFLIGHT_INTERNAL_GROUP:-}" ]]; then
|
|
args+=("groups:${TESTFLIGHT_INTERNAL_GROUP}")
|
|
fi
|
|
|
|
nix run nixpkgs#fastlane -- "${args[@]}"
|
|
rm -f "$api_key_json" "$key_file"
|
|
|
|
upload-sparkle:
|
|
name: Upload (Sparkle)
|
|
if: ${{ github.event.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: ${{ github.event.inputs.build_number }}
|
|
run: |
|
|
set -euo pipefail
|
|
channel="${{ github.event.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: ${{ github.event.inputs.build_number }}
|
|
CHANNEL: ${{ steps.channel.outputs.channel }}
|
|
SPARKLE_POINT_MAIN: ${{ github.event.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: ${{ github.event.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."
|