Add standalone TestFlight retry job
Some checks failed
Build Site / Next.js Build (push) Waiting to run
Lint Governance / BEP Metadata (push) Waiting to run
Build Rust / Cargo Test (push) Has been cancelled

This commit is contained in:
Conrad Kramer 2026-06-07 19:01:31 -07:00
parent a075f5ca34
commit 86a60371d6
4 changed files with 101 additions and 87 deletions

View file

@ -472,44 +472,7 @@ jobs:
TESTFLIGHT_GROUPS: ${{ github.event.inputs.testflight_groups || '' }} TESTFLIGHT_GROUPS: ${{ github.event.inputs.testflight_groups || '' }}
TESTFLIGHT_INTERNAL_GROUP: Internal TESTFLIGHT_INTERNAL_GROUP: Internal
IOS_USES_NON_EXEMPT_ENCRYPTION: ${{ github.event.inputs.ios_uses_non_exempt_encryption || 'false' }} IOS_USES_NON_EXEMPT_ENCRYPTION: ${{ github.event.inputs.ios_uses_non_exempt_encryption || 'false' }}
run: | run: Scripts/ci/distribute-testflight.sh
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"
wait_processing_timeout="${TESTFLIGHT_WAIT_PROCESSING_TIMEOUT_SECONDS:-7200}"
case "$wait_processing_timeout" in
""|*[!0-9]*)
echo "::error ::TESTFLIGHT_WAIT_PROCESSING_TIMEOUT_SECONDS must be an integer number of seconds." >&2
exit 1
;;
esac
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:${wait_processing_timeout}"
"distribute_external:${TESTFLIGHT_DISTRIBUTE_EXTERNAL:-false}"
"uses_non_exempt_encryption:${IOS_USES_NON_EXEMPT_ENCRYPTION:-false}"
)
if [[ -n "${TESTFLIGHT_GROUPS:-}" ]]; then
args+=("groups:${TESTFLIGHT_GROUPS}")
elif [[ "${TESTFLIGHT_DISTRIBUTE_EXTERNAL:-false}" != "true" && -n "${TESTFLIGHT_INTERNAL_GROUP:-}" ]]; then
args+=("groups:${TESTFLIGHT_INTERNAL_GROUP}")
fi
nix run nixpkgs#fastlane -- "${args[@]}"
rm -f "$api_key_json" "$key_file"
publish-sparkle: publish-sparkle:
name: Publish Sparkle name: Publish Sparkle

View file

@ -222,7 +222,7 @@ jobs:
release-ios-testflight: release-ios-testflight:
name: Release (TestFlight iOS Testers) name: Release (TestFlight iOS Testers)
needs: upload-app-store needs: upload-app-store
if: ${{ always() && (github.event.inputs.upload_app_store == 'true' || github.event.inputs.distribute_testflight == 'true') && (github.event.inputs.upload_app_store != 'true' || needs.upload-app-store.result == 'success') }} if: ${{ github.event.inputs.upload_app_store == 'true' }}
runs-on: namespace-profile-linux-medium runs-on: namespace-profile-linux-medium
steps: steps:
- name: Checkout - name: Checkout
@ -256,55 +256,45 @@ jobs:
TESTFLIGHT_GROUPS: ${{ github.event.inputs.testflight_groups || '' }} TESTFLIGHT_GROUPS: ${{ github.event.inputs.testflight_groups || '' }}
TESTFLIGHT_INTERNAL_GROUP: Internal TESTFLIGHT_INTERNAL_GROUP: Internal
IOS_USES_NON_EXEMPT_ENCRYPTION: ${{ github.event.inputs.ios_uses_non_exempt_encryption || 'false' }} IOS_USES_NON_EXEMPT_ENCRYPTION: ${{ github.event.inputs.ios_uses_non_exempt_encryption || 'false' }}
run: | run: Scripts/ci/distribute-testflight.sh
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}" distribute-ios-testflight:
if [[ -z "$distribute_external" ]]; then name: Distribute (TestFlight iOS Testers)
distribute_external="false" if: ${{ github.event.inputs.upload_app_store != 'true' && github.event.inputs.distribute_testflight == 'true' }}
fi runs-on: namespace-profile-linux-medium
uses_non_exempt="${IOS_USES_NON_EXEMPT_ENCRYPTION:-false}" steps:
if [[ -z "$uses_non_exempt" ]]; then - name: Checkout
uses_non_exempt="false" uses: https://code.forgejo.org/actions/checkout@v4
fi with:
wait_processing_timeout="${TESTFLIGHT_WAIT_PROCESSING_TIMEOUT_SECONDS:-7200}" token: ${{ github.token }}
case "$wait_processing_timeout" in fetch-depth: 0
""|*[!0-9]*)
echo "::error ::TESTFLIGHT_WAIT_PROCESSING_TIMEOUT_SECONDS must be an integer number of seconds." >&2
exit 1
;;
esac
args=( - name: Ensure Nix
run pilot shell: bash
"api_key_path:${api_key_json}" run: bash Scripts/ci/ensure-nix.sh
"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:${wait_processing_timeout}"
"distribute_external:${distribute_external}"
"uses_non_exempt_encryption:${uses_non_exempt}"
)
if [[ -n "${TESTFLIGHT_GROUPS}" ]]; then - name: Configure Age
args+=("groups:${TESTFLIGHT_GROUPS}") shell: bash
elif [[ "$distribute_external" != "true" && -n "${TESTFLIGHT_INTERNAL_GROUP:-}" ]]; then env:
args+=("groups:${TESTFLIGHT_INTERNAL_GROUP}") AGE_FORGE_SSH_KEY: ${{ secrets.AGE_FORGE_SSH_KEY }}
fi run: Scripts/resolve-age-identity.sh >> "$GITHUB_ENV"
nix run nixpkgs#fastlane -- "${args[@]}" - name: Decrypt Release Secrets
rm -f "$api_key_json" "$key_file" 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: Scripts/ci/distribute-testflight.sh
upload-sparkle: upload-sparkle:
name: Upload (Sparkle) name: Upload (Sparkle)

View file

@ -0,0 +1,60 @@
#!/usr/bin/env bash
set -euo pipefail
Scripts/ci/check-release-config.sh app-store
build_number="${BUILD_NUMBER:?BUILD_NUMBER is required}"
distribute_external="${TESTFLIGHT_DISTRIBUTE_EXTERNAL:-false}"
testflight_groups="${TESTFLIGHT_GROUPS:-}"
testflight_internal_group="${TESTFLIGHT_INTERNAL_GROUP:-Internal}"
uses_non_exempt="${IOS_USES_NON_EXEMPT_ENCRYPTION:-false}"
wait_processing_timeout="${TESTFLIGHT_WAIT_PROCESSING_TIMEOUT_SECONDS:-7200}"
if [[ -z "$distribute_external" ]]; then
distribute_external="false"
fi
if [[ -z "$uses_non_exempt" ]]; then
uses_non_exempt="false"
fi
case "$wait_processing_timeout" in
""|*[!0-9]*)
echo "::error ::TESTFLIGHT_WAIT_PROCESSING_TIMEOUT_SECONDS must be an integer number of seconds." >&2
exit 1
;;
esac
api_key_json="$(mktemp "${RUNNER_TEMP:-/tmp}/burrow-appstore-api-key.XXXXXX.json")"
key_file="$(mktemp "${RUNNER_TEMP:-/tmp}/burrow-appstore-key.XXXXXX.p8")"
cleanup() {
rm -f "$api_key_json" "$key_file"
}
trap cleanup EXIT
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"
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:${wait_processing_timeout}"
"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[@]}"

View file

@ -195,9 +195,10 @@ The same change also makes the forge itself the release authority: release tags
`apple-distribute-testers` and `publish-store-uploads` wait up to 7200 `apple-distribute-testers` and `publish-store-uploads` wait up to 7200
seconds by default and allow Forgejo variable seconds by default and allow Forgejo variable
`TESTFLIGHT_WAIT_PROCESSING_TIMEOUT_SECONDS` to tune that ceiling without a `TESTFLIGHT_WAIT_PROCESSING_TIMEOUT_SECONDS` to tune that ceiling without a
repo change. `publish-store-uploads` can also run with repo change. `publish-store-uploads` has a separate no-`needs`
`distribute_testflight=true` and `upload_app_store=false` to distribute an `distribute-ios-testflight` retry job for `distribute_testflight=true` and
already-uploaded build without re-uploading or rebuilding the IPA. `upload_app_store=false`, so skipped upload/signing jobs cannot suppress
distribution of an already-uploaded build.
- Add `infra/releases` as the OpenTofu boundary for the GCS release and package repository backup buckets. Garage is the required S3-compatible primary distribution target after host bootstrap. `infra/identity` remains the boundary for Google KMS signing keys, the Authentik WIF pool/provider, and the runner service account. - Add `infra/releases` as the OpenTofu boundary for the GCS release and package repository backup buckets. Garage is the required S3-compatible primary distribution target after host bootstrap. `infra/identity` remains the boundary for Google KMS signing keys, the Authentik WIF pool/provider, and the runner service account.
- Serve `releases.burrow.net` and `packages.burrow.net` from the forge Caddy - Serve `releases.burrow.net` and `packages.burrow.net` from the forge Caddy
edge as stable Burrow domains backed by the GCS release and package buckets. edge as stable Burrow domains backed by the GCS release and package buckets.