name: Release run-name: Release v${{ inputs.release_version }} on: workflow_dispatch: inputs: release_version: description: "Release Version (example: '2025.2.1')" required: true env: _REGISTRY: ghcr.io/bitwarden concurrency: group: ${{ github.workflow }} cancel-in-progress: false permissions: contents: read jobs: self-host-version-check: name: Check Self-Host Version Input if: github.ref_name == 'main' runs-on: ubuntu-24.04 steps: - name: Checkout repo uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: persist-credentials: false - name: Get Latest Self-Host Version id: get-self-host uses: bitwarden/gh-actions/get-release-version@main with: repository: bitwarden/self-host - name: Check Release Version env: RELEASE_VERSION: ${{ inputs.release_version }} PREVIOUS_RELEASE_VERSION: ${{ steps.get-self-host.outputs.version }} run: | if [ "${RELEASE_VERSION}" == "${PREVIOUS_RELEASE_VERSION}" ]; then echo "[!] Already released v${RELEASE_VERSION}. Please bump the version to continue." exit 1 fi update-versions: name: "Update versions" runs-on: ubuntu-24.04 needs: self-host-version-check permissions: contents: write id-token: write outputs: core_release_tag: ${{ steps.update-core-version.outputs.tag }} core_version_changed: ${{ steps.update-core-version.outputs.changed }} web_release_tag: ${{ steps.update-web-version.outputs.tag }} web_version_changed: ${{ steps.update-web-version.outputs.changed }} key_connector_release_tag: ${{ steps.update-key-connector-version.outputs.tag }} key_connector_version_changed: ${{ steps.update-key-connector-version.outputs.changed }} updated_version_commit_hash: ${{ steps.push-changes.outputs.commit_hash }} steps: - 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-org-bitwarden secrets: "BW-GHAPP-ID,BW-GHAPP-KEY" - name: Log out from Azure uses: bitwarden/gh-actions/azure-logout@main - name: Generate GH App token uses: actions/create-github-app-token@0f859bf9e69e887678d5bbfbee594437cb440ffe # v2.1.0 id: app-token with: app-id: ${{ steps.get-kv-secrets.outputs.BW-GHAPP-ID }} private-key: ${{ steps.get-kv-secrets.outputs.BW-GHAPP-KEY }} permission-contents: write # for pushing commits - name: Checkout Branch uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: ref: main token: ${{ steps.app-token.outputs.token }} persist-credentials: true - name: Get Latest Core Version id: get-core uses: bitwarden/gh-actions/get-release-version@main with: repository: bitwarden/server trim: true - name: Update Core version id: update-core-version env: NEW_VERSION: ${{ steps.get-core.outputs.version }} run: | OLD_VERSION=$(jq -r '.versions.coreVersion' version.json) sed -i -e "/^\s*COREVERSION\s*=\s*/s/[0-9]\+.[0-9]\+.[0-9]\+/$NEW_VERSION/" bitwarden.sh sed -i -e "/^\s*\$coreVersion\s*=\s*/s/[0-9]\+.[0-9]\+.[0-9]\+/$NEW_VERSION/" bitwarden.ps1 sed -i -e '/"coreVersion":/ s/"coreVersion":[^,]*/"coreVersion":"'$NEW_VERSION'"/' version.json if [[ "$OLD_VERSION" != "$NEW_VERSION" ]]; then echo "Core version changed from '$OLD_VERSION' to '$NEW_VERSION'" >> "$GITHUB_STEP_SUMMARY" echo "changed=true" >> "$GITHUB_OUTPUT" fi echo "tag=$NEW_VERSION" >> "$GITHUB_OUTPUT" - name: Get Latest Web Version id: get-web uses: bitwarden/gh-actions/get-release-version@main with: repository: bitwarden/clients monorepo: true monorepo-project: web trim: true - name: Update Web version id: update-web-version env: NEW_VERSION: ${{ steps.get-web.outputs.version }} run: | OLD_VERSION=$(jq -r '.versions.webVersion' version.json) sed -i -e "/^\s*WEBVERSION\s*=\s*/s/[0-9]\+.[0-9]\+.[0-9]\+/$NEW_VERSION/" bitwarden.sh sed -i -e "/^\s*\$webVersion\s*=\s*/s/[0-9]\+.[0-9]\+.[0-9]\+/$NEW_VERSION/" bitwarden.ps1 sed -i -e '/"webVersion":/ s/"webVersion":[^,]*/"webVersion":"'$NEW_VERSION'"/' version.json if [[ "$OLD_VERSION" != "$NEW_VERSION" ]]; then echo "Web version changed from '$OLD_VERSION' to '$NEW_VERSION'" >> "$GITHUB_STEP_SUMMARY" echo "changed=true" >> "$GITHUB_OUTPUT" fi echo "tag=$NEW_VERSION" >> "$GITHUB_OUTPUT" - name: Get Latest Key Connector Version id: get-key-connector uses: bitwarden/gh-actions/get-release-version@main with: repository: bitwarden/key-connector trim: true - name: Update Key Connector version id: update-key-connector-version env: NEW_VERSION: ${{ steps.get-key-connector.outputs.version }} run: | OLD_VERSION=$(jq -r '.versions.keyConnectorVersion' version.json) sed -i -e "/^\s*KEYCONNECTORVERSION\s*=\s*/s/[0-9]\+.[0-9]\+.[0-9]\+/$NEW_VERSION/" bitwarden.sh sed -i -e "/^\s*\$keyConnectorVersion\s*=\s*/s/[0-9]\+.[0-9]\+.[0-9]\+/$NEW_VERSION/" bitwarden.ps1 sed -i -e '/"keyConnectorVersion":/ s/"keyConnectorVersion":[^,]*/"keyConnectorVersion":"'$NEW_VERSION'"/' version.json if [[ "$OLD_VERSION" != "$NEW_VERSION" ]]; then echo "Key Connector version changed from '$OLD_VERSION' to '$NEW_VERSION'" >> "$GITHUB_STEP_SUMMARY" echo "changed=true" >> "$GITHUB_OUTPUT" fi echo "tag=$NEW_VERSION" >> "$GITHUB_OUTPUT" - name: Update docker-compose.yml with new version env: CORE_VERSION: ${{ steps.update-core-version.outputs.tag }} run: | sed -i -e "s|lite:\${TAG:-[^}]*}|lite:\${TAG:-$CORE_VERSION}|" bitwarden-lite/docker-compose.yml echo ":pencil: Updated docker-compose.yml TAG to $CORE_VERSION" >> "$GITHUB_STEP_SUMMARY" - name: Check if version changed id: version-changed run: | if [ -n "$(git status --porcelain)" ]; then echo "changes_to_commit=true" >> "$GITHUB_OUTPUT" else echo "changes_to_commit=FALSE" >> "$GITHUB_OUTPUT" echo "No changes to commit!"; fi - name: Configure Git if: ${{ steps.version-changed.outputs.changes_to_commit == 'true' }} run: | git config --local user.email "178206702+bw-ghapp[bot]@users.noreply.github.com" git config --local user.name "bw-ghapp[bot]" - name: Commit files if: ${{ steps.version-changed.outputs.changes_to_commit == 'true' }} run: git commit -m "Updated core, web, and key-connector versions" -a - name: Push changes id: push-changes if: ${{ steps.version-changed.outputs.changes_to_commit == 'true' }} run: | git push echo "commit_hash=$(git log -1 --format='%H')" >> "$GITHUB_OUTPUT" release-github: name: Create GitHub Release runs-on: ubuntu-24.04 needs: - self-host-version-check - update-versions permissions: contents: write steps: - name: Checkout repo uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: fetch-depth: 0 persist-credentials: false ref: ${{ needs.update-versions.outputs.updated_version_commit_hash }} - name: Prepare release notes id: prepare-release-notes env: CORE_RELEASE_TAG: ${{ needs.update-versions.outputs.core_release_tag }} CORE_VERSION_CHANGED: ${{ needs.update-versions.outputs.core_version_changed }} WEB_RELEASE_TAG: ${{ needs.update-versions.outputs.web_release_tag }} WEB_VERSION_CHANGED: ${{ needs.update-versions.outputs.web_version_changed }} KEY_CONNECTOR_RELEASE_TAG: ${{ needs.update-versions.outputs.key_connector_release_tag }} KEY_CONNECTOR_VERSION_CHANGED: ${{ needs.update-versions.outputs.key_connector_version_changed }} run: | RELEASE_NOTES="" if [ -n "${CORE_VERSION_CHANGED}" ]; then RELEASE_NOTES+="Update Core version to [v${CORE_RELEASE_TAG}](https://github.com/bitwarden/server/releases/tag/v${CORE_RELEASE_TAG})" fi if [ -n "${WEB_VERSION_CHANGED}" ]; then if [ -n "$RELEASE_NOTES" ]; then RELEASE_NOTES+=$'\n' fi RELEASE_NOTES+="Update Web version to [v${WEB_RELEASE_TAG}](https://github.com/bitwarden/clients/releases/tag/web-v${WEB_RELEASE_TAG})" fi if [ -n "${KEY_CONNECTOR_VERSION_CHANGED}" ]; then if [ -n "$RELEASE_NOTES" ]; then RELEASE_NOTES+=$'\n' fi RELEASE_NOTES+="Update Key Connector version to [v${KEY_CONNECTOR_RELEASE_TAG}](https://github.com/bitwarden/key-connector/releases/tag/v${KEY_CONNECTOR_RELEASE_TAG})" fi ( echo 'RELEASE_NOTES<> "$GITHUB_OUTPUT" - name: Create GitHub release uses: ncipollo/release-action@bcfe5470707e8832e12347755757cec0eb3c22af # v1.18.0 with: artifacts: 'bitwarden.sh, run.sh, bitwarden.ps1, run.ps1, version.json' tag: "v${{ inputs.release_version }}" name: "Version ${{ inputs.release_version }}" body: ${{ steps.prepare-release-notes.outputs.RELEASE_NOTES }} token: ${{ secrets.GITHUB_TOKEN }} commit: ${{ needs.update-versions.outputs.updated_version_commit_hash }} draft: false release-s3: name: Upload version.json runs-on: ubuntu-24.04 needs: - update-versions - release-github permissions: contents: read id-token: write steps: - name: Checkout repo uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: persist-credentials: false ref: ${{ needs.update-versions.outputs.updated_version_commit_hash }} - 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: Retrieve secrets id: retrieve-secrets uses: bitwarden/gh-actions/get-keyvault-secrets@main with: keyvault: "bitwarden-ci" secrets: "aws-selfhost-version-access-id, aws-selfhost-version-access-key, aws-selfhost-version-bucket-name" - name: Log out from Azure uses: bitwarden/gh-actions/azure-logout@main - name: Upload version.json to S3 bucket env: AWS_ACCESS_KEY_ID: ${{ steps.retrieve-secrets.outputs.aws-selfhost-version-access-id }} AWS_SECRET_ACCESS_KEY: ${{ steps.retrieve-secrets.outputs.aws-selfhost-version-access-key }} AWS_DEFAULT_REGION: 'us-east-1' AWS_S3_BUCKET_NAME: ${{ steps.retrieve-secrets.outputs.aws-selfhost-version-bucket-name }} run: | aws s3 cp version.json "$AWS_S3_BUCKET_NAME" \ --acl "public-read" \ --quiet tag-push-latest-images: name: Release ${{ matrix.image_name }} image runs-on: ubuntu-24.04 timeout-minutes: 45 needs: - update-versions - release-github env: _PROJECT_NAME: ${{ matrix.image_name }} permissions: id-token: write packages: write strategy: fail-fast: false matrix: include: - image_name: admin - image_name: api - image_name: attachments - image_name: billing - image_name: events - image_name: eventsprocessor - image_name: icons - image_name: identity - image_name: mssql - image_name: mssqlmigratorutility - image_name: nginx - image_name: notifications - image_name: scim - image_name: setup - image_name: sso - image_name: web steps: - name: Checkout repo uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: persist-credentials: false - name: Set up release tag id: image-setup env: CORE_RELEASE_TAG: ${{ needs.update-versions.outputs.core_release_tag }} WEB_RELEASE_TAG: ${{ needs.update-versions.outputs.web_release_tag }} run: | if [[ "${_PROJECT_NAME}" == "web" ]]; then echo "release_tag=$WEB_RELEASE_TAG" >> "$GITHUB_OUTPUT" else echo "release_tag=$CORE_RELEASE_TAG" >> "$GITHUB_OUTPUT" fi - name: Login to GitHub Container Registry uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Push latest image id: push-image env: RELEASE_TAG: ${{ steps.image-setup.outputs.release_tag }} run: | skopeo copy --all "docker://${_REGISTRY}/${_PROJECT_NAME}:${RELEASE_TAG}" "docker://${_REGISTRY}/${_PROJECT_NAME}:latest" # Get digests for signing LATEST_DIGEST=$(skopeo inspect "docker://${_REGISTRY}/${_PROJECT_NAME}:latest" --format '{{.Digest}}') echo "latest_digest=$LATEST_DIGEST" >> "$GITHUB_OUTPUT" - name: Install Cosign uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0 - name: Sign image with Cosign env: LATEST_DIGEST: ${{ steps.push-image.outputs.latest_digest }} run: cosign sign --yes "${_REGISTRY}/${_PROJECT_NAME}@$LATEST_DIGEST" - name: Log out of GHCR run: docker logout ghcr.io build-lite-image: name: Build Bitwarden lite image uses: ./.github/workflows/build-bitwarden-lite.yml needs: update-versions permissions: contents: read id-token: write packages: write security-events: write with: self_host_repo_ref: ${{ needs.update-versions.outputs.updated_version_commit_hash }} use_latest_core_version: true use_latest_web_version: true secrets: inherit release-bitwarden-lite: name: Release Bitwarden lite runs-on: ubuntu-24.04 timeout-minutes: 30 needs: - update-versions - build-lite-image env: _CORE_VERSION: ${{ needs.update-versions.outputs.core_release_tag }} permissions: id-token: write packages: write steps: - name: Login to GitHub Container Registry uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Install Cosign uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0 - name: Copy version tag to latest id: copy-lite-image run: | skopeo copy --all "docker://${_REGISTRY}/lite:$_CORE_VERSION" "docker://${_REGISTRY}/lite:latest" echo ":white_check_mark: Promoted Bitwarden lite $_CORE_VERSION to latest" >> "$GITHUB_STEP_SUMMARY" # Get digest for signing LATEST_DIGEST=$(skopeo inspect "docker://${_REGISTRY}/lite:latest" --format '{{.Digest}}') echo "latest_digest=$LATEST_DIGEST" >> "$GITHUB_OUTPUT" - name: Sign latest image with Cosign env: LATEST_DIGEST: ${{ steps.copy-lite-image.outputs.latest_digest }} run: cosign sign --yes "${_REGISTRY}/lite@$LATEST_DIGEST" - name: Log out of ghcr.io run: docker logout ghcr.io trigger-workflows: name: Trigger workflows runs-on: ubuntu-24.04 needs: - tag-push-latest-images - release-bitwarden-lite permissions: id-token: write steps: - 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-org-bitwarden secrets: "BW-GHAPP-ID,BW-GHAPP-KEY" - name: Log out from Azure uses: bitwarden/gh-actions/azure-logout@main - name: Generate GH App token uses: actions/create-github-app-token@0f859bf9e69e887678d5bbfbee594437cb440ffe # v2.1.0 id: app-token with: app-id: ${{ steps.get-kv-secrets.outputs.BW-GHAPP-ID }} private-key: ${{ steps.get-kv-secrets.outputs.BW-GHAPP-KEY }} permission-actions: write - name: Trigger release-digital-ocean workflow uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: RELEASE_TAG: v${{ inputs.release_version }} RELEASE_VERSION: ${{ inputs.release_version }} with: github-token: ${{ steps.app-token.outputs.token }} script: | await github.rest.actions.createWorkflowDispatch({ owner: 'bitwarden', repo: 'self-host', workflow_id: 'release-digital-ocean.yml', ref: process.env.RELEASE_TAG, inputs: { release_version: process.env.RELEASE_VERSION } }); - name: Trigger release-aws workflow uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: RELEASE_TAG: v${{ inputs.release_version }} RELEASE_VERSION: ${{ inputs.release_version }} with: github-token: ${{ steps.app-token.outputs.token }} script: | await github.rest.actions.createWorkflowDispatch({ owner: 'bitwarden', repo: 'self-host', workflow_id: 'release-aws.yml', ref: process.env.RELEASE_TAG, inputs: { release_version: process.env.RELEASE_VERSION } }); - name: Trigger release-azure workflow uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: RELEASE_TAG: v${{ inputs.release_version }} RELEASE_VERSION: ${{ inputs.release_version }} with: github-token: ${{ steps.app-token.outputs.token }} script: | await github.rest.actions.createWorkflowDispatch({ owner: 'bitwarden', repo: 'self-host', workflow_id: 'release-azure.yml', ref: process.env.RELEASE_TAG, inputs: { release_version: process.env.RELEASE_VERSION } }); - name: Trigger release-digital-ocean-lite workflow uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: RELEASE_TAG: v${{ inputs.release_version }} RELEASE_VERSION: ${{ inputs.release_version }} with: github-token: ${{ steps.app-token.outputs.token }} script: | await github.rest.actions.createWorkflowDispatch({ owner: 'bitwarden', repo: 'self-host', workflow_id: 'release-digital-ocean-lite.yml', ref: process.env.RELEASE_TAG, inputs: { release_version: process.env.RELEASE_VERSION } }); - name: Trigger release-aws-lite workflow uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: RELEASE_TAG: v${{ inputs.release_version }} RELEASE_VERSION: ${{ inputs.release_version }} with: github-token: ${{ steps.app-token.outputs.token }} script: | await github.rest.actions.createWorkflowDispatch({ owner: 'bitwarden', repo: 'self-host', workflow_id: 'release-aws-lite.yml', ref: process.env.RELEASE_TAG, inputs: { release_version: process.env.RELEASE_VERSION } }); - name: Trigger release-azure-lite workflow uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: RELEASE_TAG: v${{ inputs.release_version }} RELEASE_VERSION: ${{ inputs.release_version }} with: github-token: ${{ steps.app-token.outputs.token }} script: | await github.rest.actions.createWorkflowDispatch({ owner: 'bitwarden', repo: 'self-host', workflow_id: 'release-azure-lite.yml', ref: process.env.RELEASE_TAG, inputs: { release_version: process.env.RELEASE_VERSION } });