mirror of
https://github.com/sudosylabs/vnidrop.git
synced 2026-08-05 18:39:55 +02:00
460 lines
16 KiB
YAML
460 lines
16 KiB
YAML
name: Release
|
|
|
|
on:
|
|
push:
|
|
tags:
|
|
- "v*.*.*"
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
concurrency:
|
|
group: vnidrop-release
|
|
cancel-in-progress: false
|
|
|
|
jobs:
|
|
preflight:
|
|
name: Verify release tag
|
|
if: ${{ vars.RELEASE_PIPELINE_ENABLED == 'true' }}
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 10
|
|
outputs:
|
|
version: ${{ steps.version.outputs.app }}
|
|
android_code: ${{ steps.version.outputs.android_code }}
|
|
|
|
steps:
|
|
- name: Checkout release history
|
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
with:
|
|
fetch-depth: 0
|
|
persist-credentials: false
|
|
|
|
- name: Verify canonical beta tag on current master
|
|
id: version
|
|
run: |
|
|
set -euo pipefail
|
|
packaging/version/resolve-version.sh verify >/dev/null
|
|
version="$(packaging/version/resolve-version.sh product)"
|
|
channel="$(packaging/version/resolve-version.sh channel)"
|
|
master_sha="$(git rev-parse origin/master)"
|
|
if [ "$GITHUB_SHA" != "$master_sha" ]; then
|
|
echo "Release tags must point at the current master commit" >&2
|
|
exit 1
|
|
fi
|
|
if [ "$channel" != "beta" ]; then
|
|
echo "Only beta closed-testing releases are enabled" >&2
|
|
exit 1
|
|
fi
|
|
echo "app=$version" >> "$GITHUB_OUTPUT"
|
|
echo "android_code=$(packaging/version/resolve-version.sh android-code)" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Refuse an existing GitHub Release
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
run: |
|
|
if gh release view "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
|
|
echo "GitHub Release $GITHUB_REF_NAME already exists" >&2
|
|
exit 1
|
|
fi
|
|
|
|
linux:
|
|
name: Linux packages
|
|
needs: preflight
|
|
uses: ./.github/workflows/linux-packages.yml
|
|
|
|
windows:
|
|
name: Windows Store package
|
|
needs: preflight
|
|
uses: ./.github/workflows/windows-store.yml
|
|
|
|
macos:
|
|
name: Signed and notarized macOS package
|
|
needs: preflight
|
|
uses: ./.github/workflows/apple-release.yml
|
|
secrets: inherit
|
|
|
|
android:
|
|
name: Signed Android package
|
|
needs: preflight
|
|
uses: ./.github/workflows/android-release.yml
|
|
secrets: inherit
|
|
|
|
play-closed-testing:
|
|
name: Stage Play closed-testing draft
|
|
needs:
|
|
- preflight
|
|
- linux
|
|
- windows
|
|
- macos
|
|
- android
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 20
|
|
environment: play-closed-testing
|
|
permissions:
|
|
contents: read
|
|
id-token: write
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- name: Download signed Android artifacts
|
|
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
|
|
with:
|
|
name: vnidrop-${{ needs.preflight.outputs.version }}-android-release
|
|
path: build/release/android
|
|
|
|
- name: Validate closed-testing configuration
|
|
env:
|
|
WORKLOAD_IDENTITY_PROVIDER: ${{ vars.GCP_WORKLOAD_IDENTITY_PROVIDER }}
|
|
PLAY_SERVICE_ACCOUNT: ${{ vars.GCP_PLAY_SERVICE_ACCOUNT }}
|
|
PLAY_PACKAGE_NAME: ${{ vars.PLAY_PACKAGE_NAME }}
|
|
PLAY_CLOSED_TRACK: ${{ vars.PLAY_CLOSED_TRACK }}
|
|
PLAY_APP_SIGNING_CERT_SHA256: ${{ vars.PLAY_APP_SIGNING_CERT_SHA256 }}
|
|
run: |
|
|
for name in \
|
|
WORKLOAD_IDENTITY_PROVIDER \
|
|
PLAY_SERVICE_ACCOUNT \
|
|
PLAY_PACKAGE_NAME \
|
|
PLAY_CLOSED_TRACK \
|
|
PLAY_APP_SIGNING_CERT_SHA256; do
|
|
if [ -z "${!name:-}" ]; then
|
|
echo "Missing Play closed-testing configuration: $name" >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
case "${PLAY_CLOSED_TRACK,,}" in
|
|
production|*:production)
|
|
echo "Production Play tracks are forbidden" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
if [ "$PLAY_PACKAGE_NAME" != "com.vnidrop.app" ]; then
|
|
echo "Unexpected Play package name: $PLAY_PACKAGE_NAME" >&2
|
|
exit 1
|
|
fi
|
|
|
|
- name: Authenticate to Google with GitHub OIDC
|
|
id: google-auth
|
|
uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3
|
|
with:
|
|
workload_identity_provider: ${{ vars.GCP_WORKLOAD_IDENTITY_PROVIDER }}
|
|
service_account: ${{ vars.GCP_PLAY_SERVICE_ACCOUNT }}
|
|
token_format: access_token
|
|
access_token_scopes: https://www.googleapis.com/auth/androidpublisher
|
|
|
|
- name: Stage AAB and download Play-signed APK
|
|
env:
|
|
GOOGLE_PLAY_ACCESS_TOKEN: ${{ steps.google-auth.outputs.access_token }}
|
|
PLAY_PACKAGE_NAME: ${{ vars.PLAY_PACKAGE_NAME }}
|
|
PLAY_CLOSED_TRACK: ${{ vars.PLAY_CLOSED_TRACK }}
|
|
PLAY_APP_SIGNING_CERT_SHA256: ${{ vars.PLAY_APP_SIGNING_CERT_SHA256 }}
|
|
VERSION: ${{ needs.preflight.outputs.version }}
|
|
VERSION_CODE: ${{ needs.preflight.outputs.android_code }}
|
|
run: |
|
|
set -euo pipefail
|
|
shopt -s nullglob
|
|
bundles=(build/release/android/*.aab)
|
|
if [ "${#bundles[@]}" -ne 1 ]; then
|
|
echo "Expected exactly one signed AAB" >&2
|
|
exit 1
|
|
fi
|
|
mkdir -p build/release/play
|
|
python3 packaging/android/publish_play.py \
|
|
--bundle "${bundles[0]}" \
|
|
--package-name "$PLAY_PACKAGE_NAME" \
|
|
--track "$PLAY_CLOSED_TRACK" \
|
|
--version-code "$VERSION_CODE" \
|
|
--release-name "$VERSION" \
|
|
--expected-app-certificate "$PLAY_APP_SIGNING_CERT_SHA256" \
|
|
--apk-output "build/release/play/VniDrop-${VERSION}-${VERSION_CODE}-play-universal.apk" \
|
|
--metadata-output build/release/play/play-release.json
|
|
|
|
- name: Set up Android SDK verification tools
|
|
uses: android-actions/setup-android@9fc6c4e9069bf8d3d10b2204b1fb8f6ef7065407 # v3
|
|
with:
|
|
packages: "platform-tools build-tools;36.0.0"
|
|
|
|
- name: Verify Play-signed universal APK
|
|
env:
|
|
EXPECTED_CERT_SHA256: ${{ vars.PLAY_APP_SIGNING_CERT_SHA256 }}
|
|
VERSION: ${{ needs.preflight.outputs.version }}
|
|
VERSION_CODE: ${{ needs.preflight.outputs.android_code }}
|
|
run: |
|
|
set -euo pipefail
|
|
apk="build/release/play/VniDrop-${VERSION}-${VERSION_CODE}-play-universal.apk"
|
|
apkanalyzer_path="$(
|
|
find "$ANDROID_SDK_ROOT/cmdline-tools" -type f -name apkanalyzer -perm -111 |
|
|
sort -r |
|
|
head -1
|
|
)"
|
|
if [ -z "$apkanalyzer_path" ]; then
|
|
echo "apkanalyzer was not found" >&2
|
|
exit 1
|
|
fi
|
|
packaging/android/verify-apk-signature.sh \
|
|
"$apk" \
|
|
"$EXPECTED_CERT_SHA256" \
|
|
>/dev/null
|
|
if [ "$("$apkanalyzer_path" manifest application-id "$apk")" != "com.vnidrop.app" ]; then
|
|
echo "Play APK package name mismatch" >&2
|
|
exit 1
|
|
fi
|
|
if [ "$("$apkanalyzer_path" manifest version-name "$apk")" != "$VERSION" ]; then
|
|
echo "Play APK version name mismatch" >&2
|
|
exit 1
|
|
fi
|
|
if [ "$("$apkanalyzer_path" manifest version-code "$apk")" != "$VERSION_CODE" ]; then
|
|
echo "Play APK version code mismatch" >&2
|
|
exit 1
|
|
fi
|
|
(
|
|
cd build/release/play
|
|
sha256sum \
|
|
"VniDrop-${VERSION}-${VERSION_CODE}-play-universal.apk" \
|
|
play-release.json \
|
|
> SHA256SUMS
|
|
)
|
|
|
|
- name: Upload Play-signed APK
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
with:
|
|
name: vnidrop-${{ needs.preflight.outputs.version }}-android-play
|
|
path: build/release/play/
|
|
if-no-files-found: error
|
|
retention-days: 90
|
|
compression-level: 0
|
|
|
|
publish-microsoft-store:
|
|
name: Submit Microsoft Store update
|
|
needs:
|
|
- preflight
|
|
- linux
|
|
- windows
|
|
- macos
|
|
- play-closed-testing
|
|
runs-on: windows-2025
|
|
timeout-minutes: 30
|
|
environment: microsoft-store
|
|
permissions:
|
|
contents: read
|
|
|
|
steps:
|
|
- name: Download Windows Store package
|
|
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
|
|
with:
|
|
name: vnidrop-${{ needs.preflight.outputs.version }}-windows-store-x64
|
|
path: build/release/windows
|
|
|
|
- name: Validate Microsoft Store configuration
|
|
id: store-package
|
|
shell: pwsh
|
|
env:
|
|
AZURE_AD_TENANT_ID: ${{ secrets.AZURE_AD_TENANT_ID }}
|
|
AZURE_AD_APPLICATION_CLIENT_ID: ${{ secrets.AZURE_AD_APPLICATION_CLIENT_ID }}
|
|
AZURE_AD_APPLICATION_SECRET: ${{ secrets.AZURE_AD_APPLICATION_SECRET }}
|
|
SELLER_ID: ${{ secrets.SELLER_ID }}
|
|
MICROSOFT_STORE_PRODUCT_ID: ${{ vars.MICROSOFT_STORE_PRODUCT_ID }}
|
|
run: |
|
|
$configuration = @{
|
|
AZURE_AD_TENANT_ID = $env:AZURE_AD_TENANT_ID
|
|
AZURE_AD_APPLICATION_CLIENT_ID = $env:AZURE_AD_APPLICATION_CLIENT_ID
|
|
AZURE_AD_APPLICATION_SECRET = $env:AZURE_AD_APPLICATION_SECRET
|
|
SELLER_ID = $env:SELLER_ID
|
|
MICROSOFT_STORE_PRODUCT_ID = $env:MICROSOFT_STORE_PRODUCT_ID
|
|
}
|
|
foreach ($entry in $configuration.GetEnumerator()) {
|
|
if ([string]::IsNullOrWhiteSpace($entry.Value) -or $entry.Value -eq "REPLACE_ME") {
|
|
throw "Missing Microsoft Store configuration: $($entry.Key)"
|
|
}
|
|
}
|
|
if ($env:MICROSOFT_STORE_PRODUCT_ID -ne "9NJ5Q0FG7TGL") {
|
|
throw "Unexpected Microsoft Store product ID: $env:MICROSOFT_STORE_PRODUCT_ID"
|
|
}
|
|
$packages = @(
|
|
Get-ChildItem build/release/windows -File -Filter *.msixupload -Recurse
|
|
)
|
|
if ($packages.Count -ne 1) {
|
|
throw "Expected exactly one msixupload package, found $($packages.Count)"
|
|
}
|
|
"path=$($packages[0].FullName)" >> $env:GITHUB_OUTPUT
|
|
|
|
- name: Set up Microsoft Store Developer CLI
|
|
uses: microsoft/microsoft-store-apppublisher@15abd1c50fcc164b19cb240fb04ef3c49bf715a2 # v1.1
|
|
with:
|
|
version: v0.3.9
|
|
|
|
- name: Authenticate and verify Store access
|
|
shell: pwsh
|
|
env:
|
|
AZURE_AD_TENANT_ID: ${{ secrets.AZURE_AD_TENANT_ID }}
|
|
AZURE_AD_APPLICATION_CLIENT_ID: ${{ secrets.AZURE_AD_APPLICATION_CLIENT_ID }}
|
|
AZURE_AD_APPLICATION_SECRET: ${{ secrets.AZURE_AD_APPLICATION_SECRET }}
|
|
SELLER_ID: ${{ secrets.SELLER_ID }}
|
|
MICROSOFT_STORE_PRODUCT_ID: ${{ vars.MICROSOFT_STORE_PRODUCT_ID }}
|
|
run: |
|
|
msstore reconfigure `
|
|
--tenantId "$env:AZURE_AD_TENANT_ID" `
|
|
--sellerId "$env:SELLER_ID" `
|
|
--clientId "$env:AZURE_AD_APPLICATION_CLIENT_ID" `
|
|
--clientSecret "$env:AZURE_AD_APPLICATION_SECRET"
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "Microsoft Store authentication failed"
|
|
}
|
|
msstore settings --enableTelemetry false
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "Failed to disable Microsoft Store CLI telemetry"
|
|
}
|
|
msstore apps get "$env:MICROSOFT_STORE_PRODUCT_ID"
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "The Microsoft Store application is not accessible"
|
|
}
|
|
|
|
- name: Publish package to Microsoft Store
|
|
shell: pwsh
|
|
env:
|
|
MICROSOFT_STORE_PRODUCT_ID: ${{ vars.MICROSOFT_STORE_PRODUCT_ID }}
|
|
STORE_PACKAGE: ${{ steps.store-package.outputs.path }}
|
|
run: |
|
|
msstore publish "$env:STORE_PACKAGE" `
|
|
--appId "$env:MICROSOFT_STORE_PRODUCT_ID"
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "Microsoft Store package publication failed"
|
|
}
|
|
|
|
- name: Summarize Store submission
|
|
shell: pwsh
|
|
env:
|
|
VERSION: ${{ needs.preflight.outputs.version }}
|
|
MICROSOFT_STORE_PRODUCT_ID: ${{ vars.MICROSOFT_STORE_PRODUCT_ID }}
|
|
run: |
|
|
"### Microsoft Store submission" >> $env:GITHUB_STEP_SUMMARY
|
|
"- App version: $env:VERSION" >> $env:GITHUB_STEP_SUMMARY
|
|
"- Product ID: $env:MICROSOFT_STORE_PRODUCT_ID" >> $env:GITHUB_STEP_SUMMARY
|
|
"- Package submitted for certification" >> $env:GITHUB_STEP_SUMMARY
|
|
|
|
publish-github:
|
|
name: Publish coordinated GitHub Release
|
|
needs:
|
|
- preflight
|
|
- linux
|
|
- windows
|
|
- macos
|
|
- play-closed-testing
|
|
- publish-microsoft-store
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 20
|
|
permissions:
|
|
contents: write
|
|
id-token: write
|
|
attestations: write
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- name: Download Debian package
|
|
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
|
|
with:
|
|
name: vnidrop-${{ needs.preflight.outputs.version }}-linux-deb-x64
|
|
path: build/release/downloads/deb
|
|
|
|
- name: Download RPM package
|
|
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
|
|
with:
|
|
name: vnidrop-${{ needs.preflight.outputs.version }}-linux-rpm-x64
|
|
path: build/release/downloads/rpm
|
|
|
|
- name: Download macOS package
|
|
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
|
|
with:
|
|
name: vnidrop-${{ needs.preflight.outputs.version }}-macos-dmg
|
|
path: build/release/downloads/macos
|
|
|
|
- name: Download Windows Store package
|
|
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
|
|
with:
|
|
name: vnidrop-${{ needs.preflight.outputs.version }}-windows-store-x64
|
|
path: build/release/downloads/windows
|
|
|
|
- name: Download Play-signed APK
|
|
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
|
|
with:
|
|
name: vnidrop-${{ needs.preflight.outputs.version }}-android-play
|
|
path: build/release/downloads/play
|
|
|
|
- name: Verify and assemble public release assets
|
|
run: packaging/release/assemble-release.sh
|
|
|
|
- name: Attest release provenance
|
|
uses: actions/attest@f7c74d28b9d84cb8768d0b8ca14a4bac6ef463e6 # v4
|
|
with:
|
|
subject-path: build/release/final/*
|
|
|
|
- name: Create GitHub Release
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
run: |
|
|
gh release create "$GITHUB_REF_NAME" \
|
|
build/release/final/* \
|
|
--repo "$GITHUB_REPOSITORY" \
|
|
--verify-tag \
|
|
--title "VniDrop ${{ needs.preflight.outputs.version }}" \
|
|
--generate-notes
|
|
|
|
update-homebrew:
|
|
name: Update Homebrew cask
|
|
needs:
|
|
- preflight
|
|
- macos
|
|
- publish-github
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 15
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- name: Download macOS package
|
|
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
|
|
with:
|
|
name: vnidrop-${{ needs.preflight.outputs.version }}-macos-dmg
|
|
path: dist
|
|
|
|
- name: Render Homebrew cask
|
|
env:
|
|
VERSION: ${{ needs.preflight.outputs.version }}
|
|
run: |
|
|
set -euo pipefail
|
|
sha="$(sha256sum "dist/VniDrop-${VERSION}.dmg" | cut -d' ' -f1)"
|
|
sed -e "s/^ version \".*\"/ version \"${VERSION}\"/" \
|
|
-e "s/^ sha256 \".*\"/ sha256 \"${sha}\"/" \
|
|
packaging/homebrew/vnidrop.rb > /tmp/vnidrop.rb
|
|
|
|
- name: Push cask to tap
|
|
env:
|
|
TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}
|
|
VERSION: ${{ needs.preflight.outputs.version }}
|
|
run: |
|
|
set -euo pipefail
|
|
git clone \
|
|
"https://x-access-token:${TAP_TOKEN}@github.com/sudosylabs/homebrew-vnidrop.git" \
|
|
tap
|
|
mkdir -p tap/Casks
|
|
cp /tmp/vnidrop.rb tap/Casks/vnidrop.rb
|
|
cd tap
|
|
git config user.name "vnidrop-release-bot"
|
|
git config user.email "release-bot@users.noreply.github.com"
|
|
git add Casks/vnidrop.rb
|
|
git commit -m "vnidrop ${VERSION}" || {
|
|
echo "Homebrew cask already matches ${VERSION}"
|
|
exit 0
|
|
}
|
|
git push
|