name: Create GitHub Release on: workflow_dispatch: inputs: artifact-run-id: description: "GitHub Action Run ID containing artifacts" required: true type: string release-ticket-id: description: "Release Ticket ID - e.g. RELEASE-1762" required: true type: string fdroid-only: description: "Optional. Create a Password Manager release containing only the F-Droid artifacts." required: false default: false type: boolean env: ARTIFACTS_PATH: artifacts jobs: create-release: name: Create GitHub Release runs-on: ubuntu-24.04 permissions: contents: write id-token: write steps: - name: Check out repository uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 persist-credentials: true - name: Log inputs to job summary uses: ./.github/actions/log-inputs with: inputs: ${{ toJson(inputs) }} - name: Get branch from workflow run id: get_release_branch env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} ARTIFACT_RUN_ID: ${{ inputs.artifact-run-id }} run: | workflow_data=$(gh run view "$ARTIFACT_RUN_ID" --json headBranch,workflowName) release_branch=$(echo "$workflow_data" | jq -r .headBranch) workflow_name=$(echo "$workflow_data" | jq -r .workflowName) # branch protection check if [[ "$release_branch" != "main" && ! "$release_branch" =~ ^release/ ]]; then echo "::error::Branch '$release_branch' is not 'main' or a release branch starting with 'release/'. Releases must be created from protected branches." exit 1 fi echo "🔖 Release branch: $release_branch" echo "🔖 Workflow name: $workflow_name" echo "release_branch=$release_branch" >> "$GITHUB_OUTPUT" echo "workflow_name=$workflow_name" >> "$GITHUB_OUTPUT" case "$workflow_name" in *"Password Manager"* | "Build") app_name="Password Manager" app_name_suffix="bwpm" ;; *"Authenticator"*) app_name="Authenticator" app_name_suffix="bwa" ;; *) echo "::error::Unknown workflow name: $workflow_name" exit 1 ;; esac echo "🔖 App name: $app_name" echo "🔖 App name suffix: $app_name_suffix" echo "app_name=$app_name" >> "$GITHUB_OUTPUT" echo "app_name_suffix=$app_name_suffix" >> "$GITHUB_OUTPUT" - name: Validate F-Droid only release if: ${{ inputs.fdroid-only }} env: _APP_NAME_SUFFIX: ${{ steps.get_release_branch.outputs.app_name_suffix }} run: | if [[ "$_APP_NAME_SUFFIX" != "bwpm" ]]; then echo "::error::F-Droid only releases are only supported for Password Manager." exit 1 fi - name: Get version info from run logs and set release tag name id: get_release_info env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} ARTIFACT_RUN_ID: ${{ inputs.artifact-run-id }} _APP_NAME_SUFFIX: ${{ steps.get_release_branch.outputs.app_name_suffix }} run: | workflow_log=$(gh run view "$ARTIFACT_RUN_ID" --log) version_number_with_trailing_dot=$(grep -m 1 "Setting version code to" <<< "$workflow_log" | sed 's/.*Setting version code to //') version_number=${version_number_with_trailing_dot%.} # remove trailing dot version_name_with_trailing_dot=$(grep -m 1 "Setting version name to" <<< "$workflow_log" | sed 's/.*Setting version name to //') version_name=${version_name_with_trailing_dot%.} # remove trailing dot if [[ -z "$version_name" ]]; then echo "::warning::Version name not found. Using default value - 0.0.0" version_name="0.0.0" else echo "✅ Found version name: $version_name" fi if [[ -z "$version_number" ]]; then echo "::warning::Version number not found. Using default value - 0" version_number="0" else echo "✅ Found version number: $version_number" fi echo "version_number=$version_number" >> "$GITHUB_OUTPUT" echo "version_name=$version_name" >> "$GITHUB_OUTPUT" tag_name="v$version_name-$_APP_NAME_SUFFIX" # e.g. v2025.6.0-bwpm echo "🔖 New tag name: $tag_name" echo "tag_name=$tag_name" >> "$GITHUB_OUTPUT" last_release_tag=$(git tag -l --sort=-authordate | grep "$_APP_NAME_SUFFIX" | head -n 1) echo "🔖 Last release tag: $last_release_tag" echo "last_release_tag=$last_release_tag" >> "$GITHUB_OUTPUT" - name: Download artifacts env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} ARTIFACT_RUN_ID: ${{ inputs.artifact-run-id }} _APP_NAME_SUFFIX: ${{ steps.get_release_branch.outputs.app_name_suffix }} _FDROID_ONLY: ${{ inputs.fdroid-only }} run: | gh run download "$ARTIFACT_RUN_ID" -D "$ARTIFACTS_PATH" file_count=$(find "$ARTIFACTS_PATH" -type f | wc -l) echo "Downloaded $file_count file(s)." if [ "$file_count" -gt 0 ]; then echo "Downloaded files:" find "$ARTIFACTS_PATH" -type f fi # Artifacts that will be included in the release. Everything else is removed. # gh extracts each artifact into a subdirectory named after the artifact. artifacts_to_keep=( "version-info" ) if [[ "$_APP_NAME_SUFFIX" == "bwpm" ]]; then artifacts_to_keep+=( "com.x8bit.bitwarden-fdroid.apk" "com.x8bit.bitwarden-fdroid.apk-sha256.txt" ) if [[ "$_FDROID_ONLY" != "true" ]]; then artifacts_to_keep+=( "com.x8bit.bitwarden.apk" "com.x8bit.bitwarden.apk-sha256.txt" ) fi elif [[ "$_APP_NAME_SUFFIX" == "bwa" ]]; then artifacts_to_keep+=( "com.bitwarden.authenticator.apk" "authenticator-android-apk-sha256.txt" ) else echo "::error::Unknown app name suffix: $_APP_NAME_SUFFIX" exit 1 fi for artifact_dir in "$ARTIFACTS_PATH"/*/; do artifact=$(basename "$artifact_dir") keep=false for k in "${artifacts_to_keep[@]}"; do if [[ "$artifact" == "$k" ]]; then keep=true break fi done if [[ "$keep" == false ]]; then rm -rf -- "$artifact_dir" fi done echo "🔖 Removed artifacts not in the keep list." missing=0 for artifact in "${artifacts_to_keep[@]}"; do if [[ -z "$(find "$ARTIFACTS_PATH/$artifact" -type f 2>/dev/null)" ]]; then echo "::error::Artifact '$artifact' not found in run $ARTIFACT_RUN_ID." missing=1 fi done if [[ "$missing" -ne 0 ]]; then exit 1 fi echo "" echo "🔖 Files to be included in the release:" find "$ARTIFACTS_PATH" -type f - name: Log in to Azure uses: bitwarden/gh-actions/azure-login@main with: subscription_id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} tenant_id: ${{ secrets.AZURE_TENANT_ID }} client_id: ${{ secrets.AZURE_CLIENT_ID }} - name: Get Azure Key Vault secrets id: get-kv-secrets uses: bitwarden/gh-actions/get-keyvault-secrets@main with: keyvault: gh-android secrets: "JIRA-API-EMAIL,JIRA-API-TOKEN,JIRA-CLOUD-ID" - name: Log out from Azure uses: bitwarden/gh-actions/azure-logout@main - name: Get product release notes id: get_release_notes env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} ARTIFACT_RUN_ID: ${{ inputs.artifact-run-id }} _VERSION_NAME: ${{ steps.get_release_info.outputs.version_name }} _RELEASE_TICKET_ID: ${{ inputs.release-ticket-id }} _JIRA_CLOUD_ID: ${{ steps.get-kv-secrets.outputs.JIRA-CLOUD-ID }} _JIRA_API_EMAIL: ${{ steps.get-kv-secrets.outputs.JIRA-API-EMAIL }} _JIRA_API_TOKEN: ${{ steps.get-kv-secrets.outputs.JIRA-API-TOKEN }} run: | echo "Getting product release notes..." # capture output and exit code so this step continues even if we can't retrieve release notes. script_exit_code=0 product_release_notes=$(python3 .github/scripts/jira-get-release-notes/jira_release_notes.py "$_RELEASE_TICKET_ID" "$_JIRA_CLOUD_ID" "$_JIRA_API_EMAIL" "$_JIRA_API_TOKEN") || script_exit_code=$? echo "--------------------------------" if [[ $script_exit_code -ne 0 || -z "$product_release_notes" ]]; then echo "Script Output: $product_release_notes" echo "::warning::Failed to fetch release notes from Jira. Check script logs for more details." product_release_notes="" else echo "✅ Product release notes:" echo "$product_release_notes" fi echo "$product_release_notes" > product_release_notes.txt - name: Create Release id: create_release env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} _APP_NAME: ${{ steps.get_release_branch.outputs.app_name }} _VERSION_NAME: ${{ steps.get_release_info.outputs.version_name }} _VERSION_NUMBER: ${{ steps.get_release_info.outputs.version_number }} _TARGET_COMMIT: ${{ steps.get_release_branch.outputs.release_branch }} _TAG_NAME: ${{ steps.get_release_info.outputs.tag_name }} _LAST_RELEASE_TAG: ${{ steps.get_release_info.outputs.last_release_tag }} _FDROID_ONLY: ${{ inputs.fdroid-only }} run: | is_latest_release=false if [[ "$_APP_NAME" == "Password Manager" ]]; then is_latest_release=true fi release_title="$_APP_NAME $_VERSION_NAME ($_VERSION_NUMBER)" # F-Droid only releases must not be marked latest, since they lack the standard Play Store APK. if [[ "$_FDROID_ONLY" == "true" ]]; then is_latest_release=false release_title="$release_title (F-Droid)" fi echo "⌛️ Creating release $release_title on $_TARGET_COMMIT" release_url=$(gh release create "$_TAG_NAME" \ --title "$release_title" \ --target "$_TARGET_COMMIT" \ --generate-notes \ --notes-start-tag "$_LAST_RELEASE_TAG" \ --latest=$is_latest_release \ --draft \ "$ARTIFACTS_PATH/*/*") # Extract release tag from URL release_id_from_url=$(echo "$release_url" | sed 's/.*\/tag\///') echo "release_id_from_url=$release_id_from_url" >> "$GITHUB_OUTPUT" echo "url=$release_url" >> "$GITHUB_OUTPUT" echo "✅ Release created: $release_url" echo "🔖 Release ID from URL: $release_id_from_url" - name: Update Release Description id: update_release_description env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} ARTIFACT_RUN_ID: ${{ inputs.artifact-run-id }} _VERSION_NAME: ${{ steps.get_release_info.outputs.version_name }} _RELEASE_ID: ${{ steps.create_release.outputs.release_id_from_url }} run: | echo "Getting current release body. Release ID: $_RELEASE_ID" current_body=$(gh release view "$_RELEASE_ID" --json body --jq .body) product_release_notes=$(cat product_release_notes.txt) # Update release description with product release notes and builds source updated_body="# Overview ${product_release_notes} ${current_body} **Builds Source:** https://github.com/${{ github.repository }}/actions/runs/$ARTIFACT_RUN_ID" new_release_url=$(gh release edit "$_RELEASE_ID" --notes "$updated_body") # draft release links change after editing echo "release_url=$new_release_url" >> "$GITHUB_OUTPUT" - name: Add Release Summary env: _RELEASE_TAG: ${{ steps.get_release_info.outputs.tag_name }} _LAST_RELEASE_TAG: ${{ steps.get_release_info.outputs.last_release_tag }} _VERSION_NAME: ${{ steps.get_release_info.outputs.version_name }} _VERSION_NUMBER: ${{ steps.get_release_info.outputs.version_number }} _RELEASE_BRANCH: ${{ steps.get_release_branch.outputs.release_branch }} _RELEASE_URL: ${{ steps.update_release_description.outputs.release_url }} run: | { echo "# :fish_cake: Release ready at:" echo "$_RELEASE_URL" echo "" } >> "$GITHUB_STEP_SUMMARY" if [[ "$_VERSION_NAME" == "0.0.0" || "$_VERSION_NUMBER" == "0" ]]; then { echo "> [!CAUTION]" echo "> Version name or number wasn't previously found and a default value was used. You'll need to manually update the release Title, Tag and Description, specifically, the \"Full Changelog\" link." echo "" } >> "$GITHUB_STEP_SUMMARY" fi { echo ":clipboard: Confirm that the defined GitHub Release options are correct:" echo " * :bookmark: New tag name: \`$_RELEASE_TAG\`" echo " * :palm_tree: Target branch: \`$_RELEASE_BRANCH\`" echo " * :ocean: Previous tag set in the description \"Full Changelog\" link: \`$_LAST_RELEASE_TAG\`" echo " * :white_check_mark: Description has automated release notes and they match the commits in the release branch" echo "> [!NOTE]" echo "> Commits directly pushed to branches without a Pull Request won't appear in the automated release notes." } >> "$GITHUB_STEP_SUMMARY"