Skip to content

Add CodeBoarding architecture analysis #2

Add CodeBoarding architecture analysis

Add CodeBoarding architecture analysis #2

Workflow file for this run

name: Build
on:
pull_request:
types: [opened, synchronize]
push:
branches:
- "main"
- "rc"
- "hotfix-rc"
workflow_call:
workflow_dispatch:
permissions:
contents: read
env:
_AZ_REGISTRY: "bitwardenprod.azurecr.io"
_GHCR_REGISTRY: "ghcr.io/bitwarden"
_GITHUB_PR_REPO_NAME: ${{ github.event.pull_request.head.repo.full_name }}
jobs:
lint:
name: Lint
runs-on: ubuntu-22.04
steps:
- name: Check out repo
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ github.event.pull_request.head.sha }}
persist-credentials: false
- name: Set up .NET
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
- name: Verify format
run: dotnet format --verify-no-changes
build-artifacts:
name: Build Docker images
runs-on: ubuntu-22.04
needs: lint
outputs:
has_secrets: ${{ steps.check-secrets.outputs.has_secrets }}
permissions:
security-events: write
id-token: write
packages: write
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
include:
- project_name: Admin
base_path: ./src
dotnet: true
node: true
- project_name: Api
base_path: ./src
dotnet: true
- project_name: Attachments
base_path: ./util
- project_name: Billing
base_path: ./src
dotnet: true
- project_name: Events
base_path: ./src
dotnet: true
- project_name: EventsProcessor
base_path: ./src
dotnet: true
- project_name: Icons
base_path: ./src
dotnet: true
- project_name: Identity
base_path: ./src
dotnet: true
- project_name: MsSql
base_path: ./util
- project_name: MsSqlMigratorUtility
base_path: ./util
dotnet: true
- project_name: Nginx
base_path: ./util
- project_name: Notifications
base_path: ./src
dotnet: true
- project_name: Scim
base_path: ./bitwarden_license/src
dotnet: true
- project_name: SeederApi
base_path: ./util
platforms: linux/amd64,linux/arm64
dotnet: true
- project_name: Setup
base_path: ./util
dotnet: true
- project_name: Sso
base_path: ./bitwarden_license/src
dotnet: true
steps:
- name: Check secrets
id: check-secrets
run: |
has_secrets=${{ secrets.AZURE_CLIENT_ID != '' }}
echo "has_secrets=$has_secrets" >> "$GITHUB_OUTPUT"
- name: Check out repo
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ github.event.pull_request.head.sha }}
persist-credentials: false
- name: Check branch to publish
env:
PUBLISH_BRANCHES: "main,rc,hotfix-rc"
id: publish-branch-check
run: |
IFS="," read -a publish_branches <<< "$PUBLISH_BRANCHES"
if [[ " ${publish_branches[*]} " =~ " ${GITHUB_REF:11} " ]]; then
echo "is_publish_branch=true" >> "$GITHUB_ENV"
else
echo "is_publish_branch=false" >> "$GITHUB_ENV"
fi
- name: Set up .NET
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
- name: Set up Node
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
cache: "npm"
cache-dependency-path: "**/package-lock.json"
node-version: "24"
- name: Print environment
run: |
whoami
dotnet --info
node --version
npm --version
echo "GitHub ref: $GITHUB_REF"
echo "GitHub event: $GITHUB_EVENT"
- name: Build node
if: ${{ matrix.node }}
working-directory: ${{ matrix.base_path }}/${{ matrix.project_name }}
run: |
npm ci
npm run build
- name: Publish project
working-directory: ${{ matrix.base_path }}/${{ matrix.project_name }}
if: ${{ matrix.dotnet }}
run: |
echo "Publish"
dotnet publish -c "Release" -o obj/build-output/publish
cd obj/build-output/publish
zip -r ${{ matrix.project_name }}.zip .
mv ${{ matrix.project_name }}.zip ../../../
pwd
ls -atlh ../../../
- name: Upload project artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: ${{ matrix.dotnet }}
with:
name: ${{ matrix.project_name }}.zip
path: ${{ matrix.base_path }}/${{ matrix.project_name }}/${{ matrix.project_name }}.zip
if-no-files-found: error
########## Set up Docker ##########
- name: Set up QEMU emulators
uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
########## Registries ##########
- name: Log in to GHCR
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- 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: Log in to ACR
run: az acr login -n bitwardenprod
########## Generate image tag and build Docker image ##########
- name: Generate Docker image tag
id: tag
run: |
if [[ "${GITHUB_EVENT_NAME}" == "pull_request" || "${GITHUB_EVENT_NAME}" == "pull_request_target" ]]; then
IMAGE_TAG=$(echo "${GITHUB_HEAD_REF}" | sed "s/[^a-zA-Z0-9]/-/g") # Sanitize branch name to alphanumeric only
else
IMAGE_TAG=$(echo "${GITHUB_REF:11}" | sed "s#/#-#g")
fi
if [[ "${{ github.event.pull_request.head.repo.fork }}" == "true" ]]; then
SANITIZED_REPO_NAME=$(echo "$_GITHUB_PR_REPO_NAME" | sed "s/[^a-zA-Z0-9]/-/g") # Sanitize repo name to alphanumeric only
IMAGE_TAG=$SANITIZED_REPO_NAME-$IMAGE_TAG # Add repo name to the tag
fi
if [[ "$IMAGE_TAG" == "main" ]]; then
IMAGE_TAG=dev
fi
IMAGE_TAG=${IMAGE_TAG:0:128} # Limit image tags to 128 chars
echo "image_tag=$IMAGE_TAG" >> "$GITHUB_OUTPUT"
echo "### :mega: Docker Image Tag: $IMAGE_TAG" >> "$GITHUB_STEP_SUMMARY"
- name: Set up project name
id: setup
run: |
PROJECT_NAME=$(echo "${{ matrix.project_name }}" | awk '{print tolower($0)}')
echo "Matrix name: ${{ matrix.project_name }}"
echo "PROJECT_NAME: $PROJECT_NAME"
echo "project_name=$PROJECT_NAME" >> "$GITHUB_OUTPUT"
echo "platforms: ${{ matrix.platforms }}" >> "$GITHUB_STEP_SUMMARY"
- name: Generate image tags(s)
id: image-tags
env:
IMAGE_TAG: ${{ steps.tag.outputs.image_tag }}
PROJECT_NAME: ${{ steps.setup.outputs.project_name }}
SHA: ${{ github.sha }}
run: |
# Always push to ACR
ACR_TAG="${_AZ_REGISTRY}/${PROJECT_NAME}:${IMAGE_TAG}"
TAGS="${ACR_TAG}"
echo "primary_tag=${ACR_TAG}" >> "$GITHUB_OUTPUT"
# Additionally push rc and hotfix-rc tags to GHCR
if [[ "${IMAGE_TAG}" == "rc" || "${IMAGE_TAG}" == "hotfix-rc" ]]; then
TAGS=$TAGS",${_GHCR_REGISTRY}/${PROJECT_NAME}:${IMAGE_TAG}"
fi
if [[ "${IMAGE_TAG}" == "dev" ]]; then
SHORT_SHA=$(git rev-parse --short "${SHA}")
TAGS=$TAGS",${_AZ_REGISTRY}/${PROJECT_NAME}:dev-${SHORT_SHA}"
TAGS=$TAGS",${_GHCR_REGISTRY}/${PROJECT_NAME}:dev"
TAGS=$TAGS",${_GHCR_REGISTRY}/${PROJECT_NAME}:dev-${SHORT_SHA}"
fi
echo "tags=$TAGS" >> "$GITHUB_OUTPUT"
- name: Set platforms
id: platforms
run: |
PLATFORMS="${{ matrix.platforms }}"
if [ -z "$PLATFORMS" ]; then
PLATFORMS="linux/amd64,linux/arm/v7,linux/arm64"
fi
echo "platforms=$PLATFORMS" >> "$GITHUB_OUTPUT"
- name: Get Azure Key Vault secrets
id: get-kv-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@main
with:
keyvault: gh-org-bitwarden
secrets: "DOCKERHUB-OAT"
- name: Login to Docker Hub
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
with:
username: bitwarden
password: ${{ steps.get-kv-secrets.outputs.DOCKERHUB-OAT }}
- name: Build Docker image
id: build-artifacts
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
with:
context: .
file: ${{ matrix.base_path }}/${{ matrix.project_name }}/Dockerfile
platforms: ${{ steps.platforms.outputs.platforms }}
push: true
tags: ${{ steps.image-tags.outputs.tags }}
- name: Generate image manifest fragment
env:
PROJECT_NAME: ${{ steps.setup.outputs.project_name }}
IMAGE_TAG: ${{ steps.tag.outputs.image_tag }}
DIGEST: ${{ steps.build-artifacts.outputs.digest }}
TAGS: ${{ steps.image-tags.outputs.tags }}
run: |
fragment='{}'
IFS=',' read -r -a tags_array <<< "$TAGS"
for tag in "${tags_array[@]}"; do
image_uri="${tag%:*}" # strip ":tag" -> full repo URI, e.g. ghcr.io/bitwarden/api
host="${tag%%/*}" # registry host, e.g. bitwardenprod.azurecr.io / ghcr.io
base_domain=$(echo "$host" | awk -F. 'NF>=2{printf "%s.%s",$(NF-1),$NF} NF<2{printf "%s",$0}')
fragment=$(echo "$fragment" | jq \
--arg k "${PROJECT_NAME}-${base_domain}" \
--arg image "$image_uri" \
--arg tag "$IMAGE_TAG" \
--arg digest "$DIGEST" \
'. + {($k): {type: "container_image", image: $image, tag: $tag, digest: $digest}}')
done
mkdir -p image-manifest-fragments
echo "$fragment" > "image-manifest-fragments/${PROJECT_NAME}.json"
- name: Upload image manifest fragment
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: image-manifest-fragment-${{ steps.setup.outputs.project_name }}
path: image-manifest-fragments/${{ steps.setup.outputs.project_name }}.json
if-no-files-found: error
- name: Install Cosign
if: github.event_name != 'pull_request' && env.is_publish_branch == 'true'
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
- name: Sign image with Cosign
if: github.event_name != 'pull_request' && env.is_publish_branch == 'true'
env:
DIGEST: ${{ steps.build-artifacts.outputs.digest }}
TAGS: ${{ steps.image-tags.outputs.tags }}
run: |
IFS=',' read -r -a tags_array <<< "${TAGS}"
images=()
for tag in "${tags_array[@]}"; do
images+=("${tag}@${DIGEST}")
done
cosign sign --yes ${images[@]}
echo "images=${images[*]}" >> "$GITHUB_OUTPUT"
- name: Scan Docker image
id: container-scan
uses: anchore/scan-action@e1165082ffb1fe366ebaf02d8526e7c4989ea9d2 # v7.4.0
with:
image: ${{ steps.image-tags.outputs.primary_tag }}
fail-build: false
output-format: sarif
- name: Upload Grype results to GitHub
uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
with:
sarif_file: ${{ steps.container-scan.outputs.sarif }}
sha: ${{ contains(github.event_name, 'pull_request') && github.event.pull_request.head.sha || github.sha }}
ref: ${{ contains(github.event_name, 'pull_request') && format('refs/pull/{0}/head', github.event.pull_request.number) || github.ref }}
- name: Log out from Docker Hub and GHCR
run: |
docker logout docker.io
docker logout ghcr.io
- name: Log out from Azure
uses: bitwarden/gh-actions/azure-logout@main
image-manifest:
name: Build image manifest
runs-on: ubuntu-22.04
needs: build-artifacts
permissions:
contents: read
actions: write # delete the intermediate fragment artifacts after aggregating
steps:
- name: Download image manifest fragments
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
pattern: image-manifest-fragment-*
path: image-manifest-fragments
merge-multiple: true
- name: Combine image manifest fragments
id: combine
run: |
additional_artifacts=$(jq -s 'add' image-manifest-fragments/*.json)
{
echo "additional_artifacts<<EOF"
echo "$additional_artifacts"
echo "EOF"
} >> "$GITHUB_OUTPUT"
- name: Build and upload artifact manifest
uses: bitwarden/gh-actions/artifact-manifest@main
with:
mode: upload
additional_artifacts: ${{ steps.combine.outputs.additional_artifacts }}
- name: Delete intermediate fragment artifacts
env:
GH_TOKEN: ${{ github.token }}
run: |
for f in image-manifest-fragments/*.json; do
name="image-manifest-fragment-$(basename "$f" .json)"
id=$(gh api "repos/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/artifacts?name=${name}" --jq '.artifacts[0].id // empty')
if [ -n "$id" ]; then
gh api --method DELETE "repos/${GITHUB_REPOSITORY}/actions/artifacts/${id}"
echo "Deleted fragment artifact ${name} (${id})"
fi
done
upload:
name: Upload
runs-on: ubuntu-22.04
needs: build-artifacts
permissions:
id-token: write
actions: read
steps:
- name: Check out repo
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ github.event.pull_request.head.sha }}
persist-credentials: false
- name: Set up .NET
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
- name: Log in to GHCR
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Make Docker stubs
if: |
github.event_name != 'pull_request'
&& (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc')
run: |
# Set proper setup image based on branch
case "$GITHUB_REF" in
"refs/heads/main")
SETUP_IMAGE="${_GHCR_REGISTRY}/setup:dev"
;;
"refs/heads/rc")
SETUP_IMAGE="${_GHCR_REGISTRY}/setup:rc"
;;
"refs/heads/hotfix-rc")
SETUP_IMAGE="${_GHCR_REGISTRY}/setup:hotfix-rc"
;;
esac
STUB_OUTPUT=$(pwd)/docker-stub
# Run setup
docker run -i --rm --name setup -v "$STUB_OUTPUT/US:/bitwarden" "$SETUP_IMAGE" \
/app/Setup -stub 1 -install 1 -domain bitwarden.example.com -os lin -cloud-region US
docker run -i --rm --name setup -v "$STUB_OUTPUT/EU:/bitwarden" "$SETUP_IMAGE" \
/app/Setup -stub 1 -install 1 -domain bitwarden.example.com -os lin -cloud-region EU
sudo chown -R "$(whoami):$(whoami)" "$STUB_OUTPUT"
# Remove extra directories and files
rm -rf "$STUB_OUTPUT/US/letsencrypt"
rm -rf "$STUB_OUTPUT/EU/letsencrypt"
rm "$STUB_OUTPUT/US/env/uid.env" "$STUB_OUTPUT/US/config.yml"
rm "$STUB_OUTPUT/EU/env/uid.env" "$STUB_OUTPUT/EU/config.yml"
# Create uid environment files
touch "$STUB_OUTPUT/US/env/uid.env"
touch "$STUB_OUTPUT/EU/env/uid.env"
# Zip up the Docker stub files
cd docker-stub/US; zip -r ../../docker-stub-US.zip ./*; cd ../..
cd docker-stub/EU; zip -r ../../docker-stub-EU.zip ./*; cd ../..
- name: Log out from GHCR
run: docker logout ghcr.io
- name: Upload Docker stub US artifact
if: |
github.event_name != 'pull_request'
&& (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc')
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: docker-stub-US.zip
path: docker-stub-US.zip
if-no-files-found: error
- name: Upload Docker stub EU artifact
if: |
github.event_name != 'pull_request'
&& (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/rc' || github.ref == 'refs/heads/hotfix-rc')
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: docker-stub-EU.zip
path: docker-stub-EU.zip
if-no-files-found: error
- name: Build Swagger files
run: |
cd ./dev
pwsh ./generate_openapi_files.ps1
- name: Upload Public API Swagger artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: swagger.json
path: api.public.json
if-no-files-found: error
- name: Upload Internal API Swagger artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: internal.json
path: api.json
if-no-files-found: error
- name: Upload Identity Swagger artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: identity.json
path: identity.json
if-no-files-found: error
build-mssqlmigratorutility:
name: Build MSSQL migrator utility
runs-on: ubuntu-22.04
needs: lint
defaults:
run:
shell: bash
working-directory: "util/MsSqlMigratorUtility"
strategy:
fail-fast: false
matrix:
target:
- osx-x64
- linux-x64
- win-x64
steps:
- name: Check out repo
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ github.event.pull_request.head.sha }}
persist-credentials: false
- name: Set up .NET
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0
- name: Print environment
run: |
whoami
dotnet --info
echo "GitHub ref: $GITHUB_REF"
echo "GitHub event: $GITHUB_EVENT"
- name: Publish project
run: |
dotnet publish -c "Release" -o obj/build-output/publish -r ${{ matrix.target }} -p:PublishSingleFile=true \
-p:IncludeNativeLibrariesForSelfExtract=true --self-contained true
- name: Upload project artifact for Windows
if: ${{ contains(matrix.target, 'win') == true }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: MsSqlMigratorUtility-${{ matrix.target }}
path: util/MsSqlMigratorUtility/obj/build-output/publish/MsSqlMigratorUtility.exe
if-no-files-found: error
- name: Upload project artifact
if: ${{ contains(matrix.target, 'win') == false }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: MsSqlMigratorUtility-${{ matrix.target }}
path: util/MsSqlMigratorUtility/obj/build-output/publish/MsSqlMigratorUtility
if-no-files-found: error
bitwarden-lite-build:
name: Trigger Bitwarden lite build
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-22.04
needs: build-artifacts
permissions:
id-token: write
steps:
- name: Compute Bitwarden lite server tag
id: server-tag
uses: bitwarden/gh-actions/sanitize-image-tag@main
with:
ref: ${{ github.head_ref || github.ref }}
fork_repo: ${{ github.event.pull_request.head.repo.fork == true && github.event.pull_request.head.repo.full_name || '' }}
- name: Compute Bitwarden lite override tag
id: override-tag
uses: bitwarden/gh-actions/sanitize-image-tag@main
with:
ref: ${{ github.head_ref || github.ref }}
fork_repo: ${{ github.event.pull_request.head.repo.fork == true && github.event.pull_request.head.repo.full_name || '' }}
prefix: "server-"
- 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@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.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 }}
owner: ${{ github.repository_owner }}
repositories: self-host
- name: Trigger Bitwarden lite build
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
SERVER_BRANCH: ${{ github.head_ref || github.ref_name }}
SERVER_TAG: ${{ steps.server-tag.outputs.tag }}
OVERRIDE_TAG: ${{ steps.override-tag.outputs.tag }}
with:
github-token: ${{ steps.app-token.outputs.token }}
script: |
await github.rest.actions.createWorkflowDispatch({
owner: 'bitwarden',
repo: 'self-host',
workflow_id: 'build-bitwarden-lite.yml',
ref: 'main',
inputs: {
server_branch: process.env.SERVER_BRANCH,
server_tag: process.env.SERVER_TAG,
override_tag: process.env.OVERRIDE_TAG
}
});
trigger-k8s-deploy:
name: Trigger K8s deploy
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
runs-on: ubuntu-22.04
needs:
- build-artifacts
- image-manifest
permissions:
id-token: write
steps:
- name: Trigger deployment
uses: bitwarden/gh-actions/trigger-actions@main
with:
azure_subscription_id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
azure_tenant_id: ${{ secrets.AZURE_TENANT_ID }}
azure_client_id: ${{ secrets.AZURE_CLIENT_ID }}
task: deploy-server-dev
description: "Triggered by server build on main"