diff --git a/.github/workflows/build-bitwarden-lite.yml b/.github/workflows/build-bitwarden-lite.yml index 76b4391..8a45fb2 100644 --- a/.github/workflows/build-bitwarden-lite.yml +++ b/.github/workflows/build-bitwarden-lite.yml @@ -28,8 +28,9 @@ on: - ".github/workflows/build-bitwarden-lite.yml" - "bitwarden-lite/**" -env: - _AZ_REGISTRY: bitwardenprod.azurecr.io +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true permissions: contents: read @@ -84,6 +85,7 @@ jobs: build-docker: name: Build Docker image runs-on: ubuntu-24.04 + timeout-minutes: 60 needs: setup permissions: id-token: write @@ -116,14 +118,20 @@ jobs: if [[ $SERVER_REF =~ ^refs/tags/v(.+)$ ]]; then IMAGE_TAG="${BASH_REMATCH[1]}" else - IMAGE_TAG="${SERVER_REF#refs/heads/}" + IMAGE_TAG=$(echo "${SERVER_REF#refs/heads/}" | \ + tr '[:upper:]' '[:lower:]' | \ + sed -E 's/[^a-z0-9._-]+/-/g; s/-+/-/g; s/^-+|-+$//g' | \ + cut -c1-128 | \ + sed -E 's/[.-]$//') fi if [[ "$IMAGE_TAG" == "main" ]]; then IMAGE_TAG=dev - elif [[ ("$IMAGE_TAG" == "rc") || ("$IMAGE_TAG" == "hotfix-rc") ]]; then - # This if statement can be removed upon release so that 'rc' and 'hotfix-rc' tags are generated correctly. - IMAGE_TAG=beta + fi + + if [[ -z "$IMAGE_TAG" ]]; then + echo "ERROR: Failed to generate valid IMAGE_TAG from SERVER_REF: $SERVER_REF" + exit 1 fi echo "Using $IMAGE_TAG for build" @@ -180,9 +188,11 @@ jobs: id: set-web-artifact-path run: | WEB_ARTIFACT=$(find . -name "web-*-selfhosted-DEV.zip" | head -1) - if [[ -n "${WEB_ARTIFACT}" ]]; then - echo "path=${WEB_ARTIFACT}" >> "$GITHUB_OUTPUT" + if [[ -z "${WEB_ARTIFACT}" ]]; then + echo "ERROR: No web artifact found for dev build" + exit 1 fi + echo "path=${WEB_ARTIFACT}" >> "$GITHUB_OUTPUT" - name: Build and push Docker image id: build-docker @@ -205,8 +215,8 @@ jobs: - name: Sign image with Cosign env: DIGEST: ${{ steps.build-docker.outputs.digest }} - IMAGE_TAG: ghcr.io/bitwarden/lite:${{ steps.tag.outputs.image_tag }} - run: cosign sign --yes "${IMAGE_TAG}@${DIGEST}" + IMAGE: ghcr.io/bitwarden/lite:${{ steps.tag.outputs.image_tag }} + run: cosign sign --yes "${IMAGE}@${DIGEST}" - name: Scan Docker image id: container-scan @@ -225,48 +235,3 @@ jobs: - name: Log out of GHCR run: docker logout ghcr.io - - - check-failures: - name: Check for failures - if: always() - runs-on: ubuntu-24.04 - needs: build-docker - permissions: - id-token: write - steps: - - name: Check if any job failed - if: | - (github.ref == 'refs/heads/main' || - github.ref == 'refs/heads/rc' || - github.ref == 'refs/heads/hotfix-rc') && - contains(needs.*.result, 'failure') - run: exit 1 - - - name: Log in to Azure - if: failure() - 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 - if: failure() - with: - keyvault: "bitwarden-ci" - secrets: "devops-alerts-slack-webhook-url" - - - name: Log out from Azure - if: failure() - uses: bitwarden/gh-actions/azure-logout@main - - - name: Notify Slack on failure - uses: act10ns/slack@44541246747a30eb3102d87f7a4cc5471b0ffb7d # v2.1.0 - if: failure() - env: - SLACK_WEBHOOK_URL: ${{ steps.retrieve-secrets.outputs.devops-alerts-slack-webhook-url }} - with: - status: ${{ job.status }} diff --git a/.github/workflows/cleanup-container-images.yml b/.github/workflows/cleanup-container-images.yml new file mode 100644 index 0000000..4018a1a --- /dev/null +++ b/.github/workflows/cleanup-container-images.yml @@ -0,0 +1,60 @@ +name: Cleanup Container Images + +on: + delete: + +concurrency: + group: ${{ github.workflow }}-${{ github.event.ref }} + cancel-in-progress: false + +jobs: + cleanup-images: + name: Delete branch container images + if: | + github.event.ref != 'main' && + github.event.ref != 'rc' && + github.event.ref != 'hotfix-rc' + runs-on: ubuntu-24.04 + permissions: + packages: write + steps: + - name: Generate image tag to delete + id: tag + env: + EVENT_REF: ${{ github.event.ref }} + run: | + # Sanitize deleted branch name to match build workflow tag generation + BRANCH_NAME="${EVENT_REF}" + IMAGE_TAG=$(echo "$BRANCH_NAME" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9._-]+/-/g; s/-+/-/g; s/^-+|-+$//g' | cut -c1-128 | sed -E 's/[.-]$//') + + if [[ -z "$IMAGE_TAG" ]]; then + echo "ERROR: Failed to generate valid IMAGE_TAG from EVENT_REF: $EVENT_REF" + exit 1 + fi + + echo "tag=$IMAGE_TAG" >> "$GITHUB_OUTPUT" + + - name: Delete container image version + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + IMAGE_TAG: ${{ steps.tag.outputs.tag }} + run: | + # Get the version ID for this specific tag + VERSION_ID=$(gh api \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "/orgs/bitwarden/packages/container/lite/versions" \ + --jq ".[] | select(.metadata.container.tags[] == \"$IMAGE_TAG\") | .id" \ + | head -1) + + if [[ -n "$VERSION_ID" ]]; then + echo "Deleting image with tag: $IMAGE_TAG (version ID: $VERSION_ID)" + gh api \ + --method DELETE \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "/orgs/bitwarden/packages/container/lite/versions/$VERSION_ID" + echo "Successfully deleted image" + else + echo "No image found with tag: $IMAGE_TAG" + fi diff --git a/.github/workflows/release-digital-ocean.yml b/.github/workflows/release-digital-ocean.yml index 16bb08c..d8b8134 100644 --- a/.github/workflows/release-digital-ocean.yml +++ b/.github/workflows/release-digital-ocean.yml @@ -10,6 +10,10 @@ on: workflow_dispatch: +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: false + permissions: contents: read @@ -17,6 +21,7 @@ jobs: build-image: name: Build Image runs-on: ubuntu-24.04 + timeout-minutes: 90 permissions: contents: read id-token: write @@ -46,9 +51,11 @@ jobs: - name: Set version from version.json id: set-version run: | - VERSION=$(grep '^ *"coreVersion":' version.json \ - | awk -F\: '{ print $2 }' \ - | sed -e 's/,$//' -e 's/^"//' -e 's/"$//') + VERSION=$(jq -r '.versions.coreVersion' version.json) + if [[ -z "$VERSION" ]]; then + echo "ERROR: Failed to extract coreVersion from version.json" + exit 1 + fi echo "version=$VERSION" >> "$GITHUB_OUTPUT" - name: Set up Hashicorp Packer diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 627a901..911a342 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,6 +11,10 @@ on: env: _AZ_REGISTRY: bitwardenprod.azurecr.io +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: false + permissions: contents: read @@ -306,8 +310,9 @@ jobs: tag-push-latest-images: - name: Tag and push ${{ matrix.project_name }} image with release version and latest + name: Release ${{ matrix.project_name }} image runs-on: ubuntu-24.04 + timeout-minutes: 45 needs: - update-versions - release-github @@ -315,7 +320,7 @@ jobs: id-token: write packages: write strategy: - fail-fast: true + fail-fast: false matrix: include: - project_name: Admin @@ -379,7 +384,6 @@ jobs: PROJECT_NAME: ${{ steps.image-setup.outputs.project_name }} RELEASE_TAG: ${{ steps.image-setup.outputs.release_tag }} run: | - skopeo --version skopeo login "$_AZ_REGISTRY" -u 00000000-0000-0000-0000-000000000000 -p "$(az acr login --expose-token --name "${_AZ_REGISTRY%.azurecr.io}" | jq -r .accessToken)" skopeo copy --all "docker://$_AZ_REGISTRY/$PROJECT_NAME:$RELEASE_TAG" "docker://ghcr.io/bitwarden/$PROJECT_NAME:$RELEASE_TAG" skopeo copy --all "docker://$_AZ_REGISTRY/$PROJECT_NAME:latest" "docker://ghcr.io/bitwarden/$PROJECT_NAME:latest" @@ -393,9 +397,7 @@ jobs: cosign sign --yes "ghcr.io/bitwarden/$PROJECT_NAME:latest" - name: Log out of Docker - run: | - docker logout ghcr.io - docker logout "$_AZ_REGISTRY" + run: docker logout ghcr.io "$_AZ_REGISTRY" - name: Log out from Azure uses: bitwarden/gh-actions/azure-logout@main @@ -404,20 +406,14 @@ jobs: release-bitwarden-lite: name: Release Bitwarden lite runs-on: ubuntu-24.04 + timeout-minutes: 30 needs: update-versions env: - _RELEASE_VERSION: ${{ inputs.release_version }}-beta # TODO: remove `-beta` after GA + _CORE_VERSION: ${{ needs.update-versions.outputs.core_release_tag }} permissions: id-token: write packages: 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: Login to GitHub Container Registry uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0 with: @@ -428,46 +424,16 @@ jobs: - name: Install Cosign uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0 - - name: Push version and latest image + - name: Copy version tag to latest run: | - skopeo --version - skopeo login "$_AZ_REGISTRY" -u 00000000-0000-0000-0000-000000000000 -p "$(az acr login --expose-token --name "${_AZ_REGISTRY%.azurecr.io}" | jq -r .accessToken)" - skopeo copy --all "docker://$_AZ_REGISTRY/lite:beta" "docker://ghcr.io/bitwarden/lite:$_RELEASE_VERSION" - skopeo copy --all "docker://$_AZ_REGISTRY/lite:beta" "docker://ghcr.io/bitwarden/lite:beta" # TODO: Delete after GA - # skopeo copy --all "docker://$_AZ_REGISTRY/lite:beta" "docker://ghcr.io/bitwarden/lite:latest" # TODO: uncomment after GA + skopeo copy --all "docker://ghcr.io/bitwarden/lite:$_CORE_VERSION" "docker://ghcr.io/bitwarden/lite:latest" + echo ":white_check_mark: Promoted Bitwarden lite $_CORE_VERSION to latest" >> "$GITHUB_STEP_SUMMARY" - - name: Sign image with Cosign - run: | - cosign sign --yes "ghcr.io/bitwarden/lite:$_RELEASE_VERSION" - cosign sign --yes "ghcr.io/bitwarden/lite:latest" + - name: Sign latest image with Cosign + run: cosign sign --yes "ghcr.io/bitwarden/lite:latest" - - name: Log out of skopeo and ghcr.io - run: | - skopeo logout --all - docker logout ghcr.io - - ########## ACR PROD ########## - - name: Login to Azure ACR - run: az acr login -n "${_AZ_REGISTRY%.azurecr.io}" - - - name: Pull latest project image - run: docker pull "$_AZ_REGISTRY/lite:beta" - - - name: Tag version and latest - run: | - docker tag "$_AZ_REGISTRY/lite:beta" "$_AZ_REGISTRY/lite:$_RELEASE_VERSION" - docker tag "$_AZ_REGISTRY/lite:beta" "$_AZ_REGISTRY/lite:latest" - - - name: Push version and latest image - run: | - docker push "$_AZ_REGISTRY/lite:$_RELEASE_VERSION" - docker push "$_AZ_REGISTRY/lite:latest" - - - name: Log out of Docker - run: docker logout "$_AZ_REGISTRY" - - - name: Log out from Azure - uses: bitwarden/gh-actions/azure-logout@main + - name: Log out of ghcr.io + run: docker logout ghcr.io trigger-workflows: diff --git a/bitwarden-lite/.dockerignore b/bitwarden-lite/.dockerignore new file mode 100644 index 0000000..3c572bb --- /dev/null +++ b/bitwarden-lite/.dockerignore @@ -0,0 +1,53 @@ +# Git files +.git +.gitignore +.gitattributes + +# CI/CD +.github + +# Documentation +*.md +README +LICENSE + +# IDE and editor files +.vscode +.idea +*.swp +*.swo +*~ +.DS_Store + +# Logs +*.log +logs/ + +# Temporary files +tmp/ +temp/ +*.tmp + +# Build artifacts (if any local builds exist) +*.exe +*.dll +*.so +*.dylib + +# Editor config +.editorconfig + +# Test files +**/test/ +**/tests/ +**/*_test.go +**/*_test.py +**/*.test.js + +# Node modules (if any exist locally) +**/node_modules/ + +# Environment files +.env +.env.* +!.env.example diff --git a/bitwarden-lite/.env.example b/bitwarden-lite/.env.example index 06a987d..05aa3cf 100644 --- a/bitwarden-lite/.env.example +++ b/bitwarden-lite/.env.example @@ -1,3 +1,3 @@ COMPOSE_PROJECT_NAME=bitwarden -REGISTRY=bitwarden +REGISTRY=ghcr.io/bitwarden TAG=dev diff --git a/bitwarden-lite/supervisord/admin.ini b/bitwarden-lite/supervisord/admin.ini index 113da5e..7e52170 100644 --- a/bitwarden-lite/supervisord/admin.ini +++ b/bitwarden-lite/supervisord/admin.ini @@ -4,6 +4,9 @@ autorestart=true command=/usr/bin/dotnet "Admin.dll" directory=/app/Admin environment=ASPNETCORE_URLS="http://+:5000" +priority=3 redirect_stderr=true startsecs=15 stdout_logfile=/var/log/bitwarden/admin.log +stdout_logfile_maxbytes=10485760 +stdout_logfile_backups=5 diff --git a/bitwarden-lite/supervisord/api.ini b/bitwarden-lite/supervisord/api.ini index 410e1d8..f0081d9 100644 --- a/bitwarden-lite/supervisord/api.ini +++ b/bitwarden-lite/supervisord/api.ini @@ -4,6 +4,9 @@ autorestart=true command=/usr/bin/dotnet "Api.dll" directory=/app/Api environment=ASPNETCORE_URLS="http://+:5001" +priority=2 redirect_stderr=true startsecs=15 stdout_logfile=/var/log/bitwarden/api.log +stdout_logfile_maxbytes=10485760 +stdout_logfile_backups=5 diff --git a/bitwarden-lite/supervisord/events.ini b/bitwarden-lite/supervisord/events.ini index 32093d2..4a772a5 100644 --- a/bitwarden-lite/supervisord/events.ini +++ b/bitwarden-lite/supervisord/events.ini @@ -4,6 +4,9 @@ autorestart=true command=/usr/bin/dotnet "Events.dll" directory=/app/Events environment=ASPNETCORE_URLS="http://+:5003" +priority=3 redirect_stderr=true startsecs=15 stdout_logfile=/var/log/bitwarden/events.log +stdout_logfile_maxbytes=10485760 +stdout_logfile_backups=5 diff --git a/bitwarden-lite/supervisord/icons.ini b/bitwarden-lite/supervisord/icons.ini index 36489e8..dad5883 100644 --- a/bitwarden-lite/supervisord/icons.ini +++ b/bitwarden-lite/supervisord/icons.ini @@ -4,6 +4,9 @@ autorestart=true command=/usr/bin/dotnet "Icons.dll" directory=/app/Icons environment=ASPNETCORE_URLS="http://+:5004" +priority=3 redirect_stderr=true startsecs=15 stdout_logfile=/var/log/bitwarden/icons.log +stdout_logfile_maxbytes=10485760 +stdout_logfile_backups=5 diff --git a/bitwarden-lite/supervisord/identity.ini b/bitwarden-lite/supervisord/identity.ini index 4b1600c..cb0268f 100644 --- a/bitwarden-lite/supervisord/identity.ini +++ b/bitwarden-lite/supervisord/identity.ini @@ -8,3 +8,5 @@ priority=1 redirect_stderr=true startsecs=15 stdout_logfile=/var/log/bitwarden/identity.log +stdout_logfile_maxbytes=10485760 +stdout_logfile_backups=5 diff --git a/bitwarden-lite/supervisord/notifications.ini b/bitwarden-lite/supervisord/notifications.ini index 2744ff7..e87c7e0 100644 --- a/bitwarden-lite/supervisord/notifications.ini +++ b/bitwarden-lite/supervisord/notifications.ini @@ -4,6 +4,9 @@ autorestart=true command=/usr/bin/dotnet "Notifications.dll" directory=/app/Notifications environment=ASPNETCORE_URLS="http://+:5006" +priority=3 redirect_stderr=true startsecs=15 stdout_logfile=/var/log/bitwarden/notifications.log +stdout_logfile_maxbytes=10485760 +stdout_logfile_backups=5 diff --git a/bitwarden-lite/supervisord/scim.ini b/bitwarden-lite/supervisord/scim.ini index 11d00d4..f64d67d 100644 --- a/bitwarden-lite/supervisord/scim.ini +++ b/bitwarden-lite/supervisord/scim.ini @@ -4,6 +4,9 @@ autorestart=true command=/usr/bin/dotnet "Scim.dll" directory=/app/Scim environment=ASPNETCORE_URLS="http://+:5002" +priority=4 redirect_stderr=true startsecs=15 stdout_logfile=/var/log/bitwarden/scim.log +stdout_logfile_maxbytes=10485760 +stdout_logfile_backups=5 diff --git a/bitwarden-lite/supervisord/sso.ini b/bitwarden-lite/supervisord/sso.ini index cb29c73..a0ba657 100644 --- a/bitwarden-lite/supervisord/sso.ini +++ b/bitwarden-lite/supervisord/sso.ini @@ -4,6 +4,9 @@ autorestart=true command=/usr/bin/dotnet "Sso.dll" directory=/app/Sso environment=ASPNETCORE_URLS="http://+:5007" +priority=4 redirect_stderr=true startsecs=15 stdout_logfile=/var/log/bitwarden/sso.log +stdout_logfile_maxbytes=10485760 +stdout_logfile_backups=5