name: CI on: release: types: [published] pull_request: branches: - '**' paths-ignore: - '**.md' merge_group: push: branches: - develop paths-ignore: - '**.md' concurrency: # merge_group runs group by the merge-group ref so a recreated group cancels the stale run; # push/release runs group by run_id on purpose (never cancel a deploy in progress) group: ${{ github.workflow }}-${{ github.head_ref || (github.event_name == 'merge_group' && github.ref) || github.run_id }} cancel-in-progress: true env: TOOL_NODE_FLAGS: ${{ vars.TOOL_NODE_FLAGS }} permissions: contents: read actions: read jobs: release-versions: name: ⚙️ Variables Setup runs-on: ubuntu-24.04-arm permissions: contents: read pull-requests: read outputs: release: ${{ steps.by-tag.outputs.release }} latest-release: ${{ steps.latest.outputs.latest-release }} gh-docker-tag: ${{ steps.docker.outputs.gh-docker-tag }} lowercase-repo: ${{ steps.var.outputs.lowercase-repo }} node-version: ${{ steps.var.outputs.node-version }} deno-version: ${{ steps.var.outputs.deno-version }} source-hash: ${{ steps.source.outputs.hash }} fips-skip: ${{ steps.fips.outputs.fips-skip }} packages-build-cache-key: ${{ steps.ci-cache-keys.outputs.packages-build-cache-key }} meteor-rc-cache-key: ${{ steps.ci-cache-keys.outputs.meteor-rc-cache-key }} github-actions-changed: ${{ steps.diff.outputs.actions-changed }} # this is 100% intentional, secrets are not available for forks, so ee-tests will always fail # to avoid this, we are using a dummy license, expiring at 2027-07-01 enterprise-license: MK+bpK5NveUuNlWGaQXGoy+8b74Luet82M3ZGcBB8b5P9Y+m67NEtpW64dc1d5lEWi6d0nFjCjtCMneVD7bKxodz/Cml8URKEo5P7cQb/9wmeT0MzAhYNaRFZlIGkZ3ITF59pDV2u4HZuosEDJikVRwnaJ5ZoU/pOsHSPUPhTyGNIqLeKynODtUpfwDdIKEmHxpf2yVkKjgRiIJmbWjM6A4k+MNNYXWVXHzye7GggqWVg/ZcT7nKU1CCadpLhTJiIrgrrPzil1G5DQ4xnLs3Q2tu2dILSDiW5OYw/ywu2yCMicTjMq4MLL5SXDQJj6WoJzZ54HosbvsDzOXvsdC9gI1CjhPL2uRuvC8XLrzn3vL2UgXnifzD1VrLTtdZ+aSADveqtlzYlRWtqoUFBbNw8o+YVHdhbZGR0beMoAyRbHi5EMpxpad3L+NyztUIT/Uh/IjQ/C2SQZ6jB0GKPBOPxFLN56FNhTGrffLFR++TVoBu0Iquc7kajWkNit3bVbZvbx+oFcVW2PcjQ/+i2jpJjbgtUFUKrTKxGMAXTWoDzIQQ35zNzGAy268IM4Ymp5JmsVEnBOEUkbF9yx6fzkO6xZhpsHf0muklnW0kA+Tlore/TUrBWh1/RwWlQeZlxM5NyWoRM5onQmr/k/4BmObtL1Hpmbk8oMG29z89xtE9y/4= steps: - name: Github Info run: | echo "GITHUB_ACTION: $GITHUB_ACTION" echo "GITHUB_ACTOR: $GITHUB_ACTOR" echo "GITHUB_REF: $GITHUB_REF" echo "GITHUB_HEAD_REF: $GITHUB_HEAD_REF" echo "GITHUB_BASE_REF: $GITHUB_BASE_REF" echo "github.event_name: ${{ github.event_name }}" cat "$GITHUB_EVENT_PATH" - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 # with: # sparse-checkout: | # package.json # .tool-versions # sparse-checkout-cone-mode: false # ref: ${{ github.ref }} - id: source run: | ls -la tar -cf /tmp/RocketChat-source.tar \ --sort=name \ --owner=0 --group=0 \ --mtime='1970-01-01' \ --exclude='.github' \ --exclude='.git' \ . SOURCE_HASH=$(sha256sum /tmp/RocketChat-source.tar | awk '{ print $1 }')-v9 # Uncomment the following line to include the run ID in the hash and disable caching between runs # SOURCE_HASH=$(sha256sum /tmp/RocketChat-source.tar | awk '{ print $1 }')-${{ github.run_id }} echo "hash=${SOURCE_HASH}" echo "hash=${SOURCE_HASH}" >> "$GITHUB_OUTPUT" - id: var run: | LOWERCASE_REPOSITORY=$(echo "${{ github.repository_owner }}" | tr "[:upper:]" "[:lower:]") echo "LOWERCASE_REPOSITORY: ${LOWERCASE_REPOSITORY}" echo "lowercase-repo=${LOWERCASE_REPOSITORY}" >> "$GITHUB_OUTPUT" NODE_VERSION=$(node -p "require('./package.json').volta.node") echo "NODE_VERSION: ${NODE_VERSION}" echo "node-version=${NODE_VERSION}" >> "$GITHUB_OUTPUT" DENO_VERSION=$(awk '$1=="deno"{ print $2 }' .tool-versions) echo "DENO_VERSION: ${DENO_VERSION}" echo "deno-version=${DENO_VERSION}" >> "$GITHUB_OUTPUT" - id: ci-cache-keys run: | set -euo pipefail PACKAGES_HASH=$(cat yarn.lock .yarnrc.yml package.json turbo.json | sha256sum | awk '{print $1}') echo "packages-build-cache-key=${PACKAGES_HASH}" >> "$GITHUB_OUTPUT" METEOR_HASH=$(git archive HEAD apps/meteor | sha256sum | awk '{print $1}') echo "meteor-rc-cache-key=${METEOR_HASH}" >> "$GITHUB_OUTPUT" - id: by-tag run: | if echo "$GITHUB_REF_NAME" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$' ; then RELEASE="latest" elif echo "$GITHUB_REF_NAME" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+-rc\.[0-9]+$' ; then RELEASE="release-candidate" fi echo "RELEASE: ${RELEASE}" echo "release=${RELEASE}" >> "$GITHUB_OUTPUT" - id: latest run: | LATEST_RELEASE="$( git -c 'versionsort.suffix=-' ls-remote -t --exit-code --refs --sort=-v:refname "https://github.com/$GITHUB_REPOSITORY" '*' | awk -F/ '$NF !~ /rc|beta/ { print $NF; exit }' )" echo "LATEST_RELEASE: ${LATEST_RELEASE}" echo "latest-release=${LATEST_RELEASE}" >> "$GITHUB_OUTPUT" - id: docker run: | if [[ '${{ github.event_name }}' == 'pull_request' ]]; then DOCKER_TAG="pr-${{ github.event.number }}" else DOCKER_TAG=$GITHUB_REF_NAME fi # Docker tags cannot contain '/'; merge queue refs do (e.g. gh-readonly-queue/develop/pr-123-sha) DOCKER_TAG="${DOCKER_TAG//\//-}" echo "DOCKER_TAG: ${DOCKER_TAG}" echo "gh-docker-tag=${DOCKER_TAG}" >> "$GITHUB_OUTPUT" # How much of the FIPS suite to skip: # all - build no fips images and run no fips tests # ui-suite - build the images, run the api suites, skip the expensive UI shards (default) # none - skip nothing, the full fips suite runs # Forks skip 'all': the fips base image (rocketchat/dhi-node) sits in a private DockerHub # repo and secrets are unavailable to forks, so the pull could never succeed. - id: fips env: EVENT_NAME: ${{ github.event_name }} BASE_REPO: ${{ github.repository }} PR_HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }} REPO_IS_FORK: ${{ github.event.repository.fork }} HAS_FIPS_LABEL: ${{ contains(github.event.pull_request.labels.*.name, 'fips') }} run: | if [[ "$EVENT_NAME" == 'pull_request' ]]; then if [[ "$PR_HEAD_REPO" == "$BASE_REPO" ]]; then IS_FORK='false' else IS_FORK='true' fi else IS_FORK="$REPO_IS_FORK" fi echo "EVENT_NAME: ${EVENT_NAME} / IS_FORK: ${IS_FORK}" if [[ "$IS_FORK" == 'true' ]]; then FIPS_SKIP='all' elif [[ "$HAS_FIPS_LABEL" == 'true' ]]; then FIPS_SKIP='none' else FIPS_SKIP='ui-suite' fi echo "FIPS_SKIP: ${FIPS_SKIP}" echo "fips-skip=${FIPS_SKIP}" >> "$GITHUB_OUTPUT" - id: diff env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_PR_NUM: ${{ github.event.number }} run: | if gh pr diff --name-only --repo "${GITHUB_REPOSITORY}" "${GH_PR_NUM}" | grep -q -e .github/workflows -e .github/actions; then echo "Workflow changes detected" echo "actions-changed=true" >> "$GITHUB_OUTPUT" fi test-guard: name: ⚙️ Test Guard runs-on: ubuntu-24.04-arm permissions: checks: read outputs: skip-tests: ${{ steps.check.outputs.skip-tests }} steps: # Commits only reach develop through the merge queue, where the full test # suite already ran on this exact SHA — skip re-running it on the push event. # Direct pushes (admin/revert) have no successful "Tests Done" check, so they # still get the full suite. continue-on-error keeps this fail-open: if the # API call fails, tests run. - name: Check if this commit already passed tests id: check if: github.event_name == 'push' continue-on-error: true env: GH_TOKEN: ${{ github.token }} run: | # --paginate applies the jq filter per page, so emit names and count with # wc -l to get a single integer across pages passed=$(gh api "repos/${{ github.repository }}/commits/${{ github.sha }}/check-runs" --paginate \ --jq '.check_runs[] | select(.name == "✅ Tests Done" and .conclusion == "success") | .name' | wc -l) if [[ "$passed" -gt 0 ]]; then echo "Tests already passed for ${{ github.sha }} in the merge queue, skipping test jobs" echo "skip-tests=true" >> "$GITHUB_OUTPUT" else echo "skip-tests=false" >> "$GITHUB_OUTPUT" fi notify-draft-services: name: 🚀 Notify external services - draft runs-on: ubuntu-24.04-arm needs: [release-versions] if: github.event_name == 'release' steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: sparse-checkout: | package.json sparse-checkout-cone-mode: false ref: ${{ github.ref }} - name: Register release on cloud as Draft if: github.event_name == 'release' env: UPDATE_TOKEN: ${{ secrets.UPDATE_TOKEN }} run: | REPO_VERSION=$(node -p "require('./package.json').version") if [[ '${{ github.event_name }}' = 'release' ]]; then GIT_TAG="${GITHUB_REF#*tags/}" GIT_BRANCH="" ARTIFACT_NAME="${REPO_VERSION}" RC_VERSION=$GIT_TAG if [[ '${{ needs.release-versions.outputs.release }}' = 'release-candidate' ]]; then RC_RELEASE=candidate elif [[ '${{ needs.release-versions.outputs.release }}' = 'latest' ]]; then RC_RELEASE=stable fi else GIT_TAG="" GIT_BRANCH="${GITHUB_REF#*heads/}" ARTIFACT_NAME="${REPO_VERSION}.$GITHUB_SHA" RC_VERSION="${REPO_VERSION}" RC_RELEASE=develop fi; curl -H "Content-Type: application/json" -H "X-Update-Token: $UPDATE_TOKEN" -d \ "{\"nodeVersion\": \"${{ needs.release-versions.outputs.node-version }}\", \"denoVersion\": \"${{ needs.release-versions.outputs.deno-version }}\",\"compatibleMongoVersions\": [\"8.0\"], \"commit\": \"$GITHUB_SHA\", \"tag\": \"$RC_VERSION\", \"branch\": \"$GIT_BRANCH\", \"artifactName\": \"$ARTIFACT_NAME\", \"releaseType\": \"draft\", \"draftAs\": \"$RC_RELEASE\"}" \ https://releases.rocket.chat/update packages-build: name: 📦 Build Packages needs: [release-versions] runs-on: ubuntu-24.04-arm steps: - name: Cache build uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 id: packages-cache-build with: path: | /tmp/RocketChat-packages-build.tar.gz key: ${{ runner.arch }}-${{ runner.os }}-packages-build-${{ needs.release-versions.outputs.source-hash }} - name: Cache observability (packages-build) run: | { echo "### 📦 packages-build cache" echo "- **hit**: \`${{ steps.packages-cache-build.outputs.cache-hit }}\`" echo "- **key**: \`${{ needs.release-versions.outputs.packages-build-cache-key }}\`" } >> "$GITHUB_STEP_SUMMARY" - name: Set Swap Space uses: pierotofy/set-swap-space@49819abfb41bd9b44fb781159c033dba90353a7c # v1.0 if: steps.packages-cache-build.outputs.cache-hit != 'true' with: swap-size-gb: 4 - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 if: steps.packages-cache-build.outputs.cache-hit != 'true' - name: Setup NodeJS id: setup-node-packages uses: ./.github/actions/setup-node if: steps.packages-cache-build.outputs.cache-hit != 'true' with: node-version: ${{ needs.release-versions.outputs.node-version }} deno-version: ${{ needs.release-versions.outputs.deno-version }} cache-modules: true install: true NPM_TOKEN: ${{ secrets.NPM_TOKEN }} HARDENED_MODE: '1' - name: Cache observability (node_modules) if: steps.packages-cache-build.outputs.cache-hit != 'true' run: | { echo "### 🟢 node_modules cache" echo "- **deno cache hit**: \`${{ steps.setup-node-packages.outputs.cache-deno-hit }}\`" echo "- **node_modules size**: \`$(du -sh node_modules 2>/dev/null | cut -f1 || echo 'n/a')\`" } >> "$GITHUB_STEP_SUMMARY" - name: Cache vite uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 if: steps.packages-cache-build.outputs.cache-hit != 'true' with: path: ./node_modules/.vite key: vite-local-cache-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('package.json') }} restore-keys: | vite-local-cache-${{ runner.arch }}-${{ runner.os }}- - uses: rharkor/caching-for-turbo@2238fae6eb9a9936f92356f54cb3660200d105e7 # v2.5.1 if: steps.packages-cache-build.outputs.cache-hit != 'true' - name: Build Rocket.Chat Packages if: steps.packages-cache-build.outputs.cache-hit != 'true' run: yarn build - name: Archive packages build output if: steps.packages-cache-build.outputs.cache-hit != 'true' run: | git ls-files -oi --exclude-standard -- ':(exclude)node_modules/*' ':(exclude)**/node_modules/*' ':(exclude)**/.meteor/*' ':(exclude)**/.turbo/*' ':(exclude).turbo/*' ':(exclude)**/.yarn/*' ':(exclude).yarn/*' ':(exclude).git/*' | tar -czf /tmp/RocketChat-packages-build.tar.gz -T - - name: Upload packages build artifact uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: packages-build path: /tmp/RocketChat-packages-build.tar.gz retention-days: 5 - name: Store turbo build if: steps.packages-cache-build.outputs.cache-hit != 'true' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: turbo-build path: .turbo/cache overwrite: true include-hidden-files: true build: name: 📦 Meteor Build (${{ matrix.type }}) needs: [release-versions, packages-build] runs-on: ubuntu-24.04-arm strategy: fail-fast: false matrix: type: - production - coverage exclude: - type: ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'production' || '' }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: ./.github/actions/meteor-build with: node-version: ${{ needs.release-versions.outputs.node-version }} deno-version: ${{ needs.release-versions.outputs.deno-version }} source-hash: ${{ needs.release-versions.outputs.packages-build-cache-key }}-${{ needs.release-versions.outputs.meteor-rc-cache-key }} type: ${{ matrix.type }} build-gh-docker: name: 🚢 Build Docker needs: [build, release-versions] runs-on: ubuntu-24.04${{ matrix.arch == 'arm64' && '-arm' || '' }} env: DOCKER_TAG: ${{ needs.release-versions.outputs.gh-docker-tag }}-${{ matrix.arch }} LOWERCASE_REPOSITORY: ${{ needs.release-versions.outputs.lowercase-repo }} strategy: fail-fast: false matrix: arch: [arm64, amd64] service: [ [authorization-service, queue-worker-service, ddp-streamer-service], [account-service, presence-service, omnichannel-transcript-service], [rocketchat], ] type: - ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }} - fips exclude: - arch: arm64 type: fips # drop the whole fips leg when there is nothing downstream to consume it (fork PRs) - type: ${{ needs.release-versions.outputs.fips-skip == 'all' && 'fips' || '' }} include: - arch: amd64 service: [rocketchat] type: coverage - arch: arm64 service: [rocketchat] type: coverage steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: ./.github/actions/restore-packages # we only build and publish the actual docker images if not a PR from a fork - name: Image ${{ matrix.service[0] }} uses: ./.github/actions/build-docker if: github.actor != 'dependabot[bot]' env: # add suffix for the extra images with coverage if building for production DOCKER_TAG_SUFFIX_ROCKETCHAT: ${{ matrix.type == 'coverage' && (github.event_name == 'release' || github.ref == 'refs/heads/develop') && '-cov' || '' }} with: CR_USER: ${{ secrets.CR_USER }} CR_PAT: ${{ secrets.CR_PAT }} DOCKER_USER: ${{ secrets.DOCKER_USER }} DOCKER_PASS: ${{ secrets.DOCKER_PASS }} deno-version: ${{ needs.release-versions.outputs.deno-version }} arch: ${{ matrix.arch }} service: ${{ matrix.service[0] }} type: ${{ matrix.type }} publish-image: ${{ github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'release' || github.ref == 'refs/heads/develop' }} - name: Image ${{ matrix.service[1] || '"skipped"' }} uses: ./.github/actions/build-docker if: matrix.service[1] && github.actor != 'dependabot[bot]' env: DOCKER_TAG_SUFFIX_ROCKETCHAT: ${{ matrix.type == 'coverage' && '-cov' || '' }} with: CR_USER: ${{ secrets.CR_USER }} CR_PAT: ${{ secrets.CR_PAT }} DOCKER_USER: ${{ secrets.DOCKER_USER }} DOCKER_PASS: ${{ secrets.DOCKER_PASS }} deno-version: ${{ needs.release-versions.outputs.deno-version }} arch: ${{ matrix.arch }} service: ${{ matrix.service[1] }} type: ${{ matrix.type }} publish-image: ${{ github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'release' || github.ref == 'refs/heads/develop' }} setup-docker: false - name: Image ${{ matrix.service[2] || '"skipped"' }} uses: ./.github/actions/build-docker if: matrix.service[2] && github.actor != 'dependabot[bot]' env: DOCKER_TAG_SUFFIX_ROCKETCHAT: ${{ matrix.type == 'coverage' && '-cov' || '' }} with: CR_USER: ${{ secrets.CR_USER }} CR_PAT: ${{ secrets.CR_PAT }} DOCKER_USER: ${{ secrets.DOCKER_USER }} DOCKER_PASS: ${{ secrets.DOCKER_PASS }} deno-version: ${{ needs.release-versions.outputs.deno-version }} arch: ${{ matrix.arch }} service: ${{ matrix.service[2] }} type: ${{ matrix.type }} publish-image: ${{ github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'release' || github.ref == 'refs/heads/develop' }} setup-docker: false - name: Image ${{ matrix.service[3] || '"skipped"' }} uses: ./.github/actions/build-docker if: matrix.service[3] && github.actor != 'dependabot[bot]' env: DOCKER_TAG_SUFFIX_ROCKETCHAT: ${{ matrix.type == 'coverage' && '-cov' || '' }} with: CR_USER: ${{ secrets.CR_USER }} CR_PAT: ${{ secrets.CR_PAT }} DOCKER_USER: ${{ secrets.DOCKER_USER }} DOCKER_PASS: ${{ secrets.DOCKER_PASS }} deno-version: ${{ needs.release-versions.outputs.deno-version }} arch: ${{ matrix.arch }} service: ${{ matrix.service[3] }} type: ${{ matrix.type }} publish-image: ${{ github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'release' || github.ref == 'refs/heads/develop' }} setup-docker: false build-gh-docker-publish: name: 🚢 Publish Docker Images (ghcr.io) needs: [build-gh-docker, release-versions] runs-on: ubuntu-24.04-arm env: DOCKER_TAG: ${{ needs.release-versions.outputs.gh-docker-tag }} LOWERCASE_REPOSITORY: ${{ needs.release-versions.outputs.lowercase-repo }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 if: github.actor != 'dependabot[bot]' && (github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'release' || github.ref == 'refs/heads/develop') with: sparse-checkout: | docker-compose-ci.yml docker-compose-ci.fips.yml sparse-checkout-cone-mode: false ref: ${{ github.ref }} - name: Login to GitHub Container Registry if: github.actor != 'dependabot[bot]' && (github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'release' || github.ref == 'refs/heads/develop') uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 with: registry: ghcr.io username: ${{ secrets.CR_USER }} password: ${{ secrets.CR_PAT }} - name: Download manifests if: github.actor != 'dependabot[bot]' && (github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'release' || github.ref == 'refs/heads/develop') uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: pattern: manifests-* path: /tmp/manifests merge-multiple: true - name: Create and push multi-arch manifests if: github.actor != 'dependabot[bot]' && (github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'release' || github.ref == 'refs/heads/develop') run: | set -o xtrace shopt -s nullglob for service_dir in /tmp/manifests/*; do [[ -d "$service_dir" ]] || continue service="$(basename "$service_dir")" echo "Creating manifest for $service" # Extract digests from manifest.json files # shellcheck disable=SC2016 mapfile -t refs < <( find "$service_dir" -type f -name 'manifest.json' -print0 \ | xargs -0 -I{} jq -r '.Descriptor.digest as $digest | .Ref | split("@")[0] + "@" + $digest' {} ) echo "Digest for ${service}: ${refs[*]}" # Get image name from docker-compose-ci.yml since rocketchat image is different from service name (rocket.chat) if [ "$service" == "rocketchat-cov" ]; then IMAGE=$(docker compose -f docker-compose-ci.yml config --format json 2>/dev/null | jq -r --arg s "rocketchat" '.services[$s].image')-cov elif [[ "$service" == *"-fips" ]]; then base_service="${service%-fips}" IMAGE=$(docker compose -f docker-compose-ci.yml config --format json 2>/dev/null | jq -r --arg s "$base_service" '.services[$s].image')-fips else IMAGE=$(docker compose -f docker-compose-ci.yml config --format json 2>/dev/null | jq -r --arg s "$service" '.services[$s].image') fi echo "$IMAGE" docker buildx imagetools create \ --debug \ --annotation "manifest-descriptor:org.opencontainers.image.description=Build run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \ --tag "${IMAGE}" \ --tag "${IMAGE}-gha-run-${{ github.run_id }}" \ "${refs[@]}" done track-image-sizes: name: 📦 Track Image Sizes needs: [build-gh-docker-publish, release-versions] runs-on: ubuntu-24.04-arm if: github.event_name == 'pull_request' || github.ref == 'refs/heads/develop' permissions: pull-requests: write contents: write steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Track Docker image sizes uses: ./.github/actions/docker-image-size-tracker if: github.actor != 'dependabot[bot]' && (github.ref == 'refs/heads/develop' || github.event.pull_request.head.repo.full_name == github.repository) with: github-token: ${{ secrets.GITHUB_TOKEN }} ci-pat: ${{ secrets.CI_PAT }} registry: ghcr.io repository: ${{ needs.release-versions.outputs.lowercase-repo }} tag: ${{ needs.release-versions.outputs.gh-docker-tag }} baseline-tag: develop size-thresholds: '{"rocketchat":{"mb":11}}' actionlint: name: 🔎 Code Check - Action Lint needs: [release-versions] if: needs.release-versions.outputs.github-actions-changed runs-on: ubuntu-24.04-arm env: ACTIONLINT_VERSION: 1.7.12 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Get actionlint shell: bash run: | TAR_NAME="actionlint_${ACTIONLINT_VERSION}_$(echo "${RUNNER_OS}_${RUNNER_ARCH}" | tr '[:upper:]' '[:lower:]').tar.gz" echo "Downloading ${TAR_NAME}..." curl -fsSLO "https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/${TAR_NAME}" curl -fsSLo checksums.txt "https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/actionlint_${ACTIONLINT_VERSION}_checksums.txt" grep " ${TAR_NAME}$" "checksums.txt" | sha256sum -c - tar -xzf "${TAR_NAME}" actionlint - name: Run actionlint shell: bash run: | ./actionlint -color # gating `checks` also skips every e2e test job (they all `need` it); # test-storybook and test-unit don't, so they carry the same gate below checks: needs: [release-versions, packages-build, test-guard] if: needs.test-guard.outputs.skip-tests != 'true' name: 🔎 Code Check uses: ./.github/workflows/ci-code-check.yml with: node-version: ${{ needs.release-versions.outputs.node-version }} deno-version: ${{ needs.release-versions.outputs.deno-version }} test-storybook: name: 🔨 Test Storybook needs: [packages-build, release-versions, test-guard] if: needs.test-guard.outputs.skip-tests != 'true' uses: ./.github/workflows/ci-test-storybook.yml with: node-version: ${{ needs.release-versions.outputs.node-version }} deno-version: ${{ needs.release-versions.outputs.deno-version }} secrets: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} test-unit: name: 🔨 Test Unit needs: [packages-build, release-versions, test-guard] if: needs.test-guard.outputs.skip-tests != 'true' uses: ./.github/workflows/ci-test-unit.yml with: node-version: ${{ needs.release-versions.outputs.node-version }} deno-version: ${{ needs.release-versions.outputs.deno-version }} enterprise-license: ${{ needs.release-versions.outputs.enterprise-license }} secrets: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} test-api: name: 🔨 Test API (CE) needs: [checks, build-gh-docker-publish, release-versions] uses: ./.github/workflows/ci-test-e2e.yml with: type: api release: ce node-version: ${{ needs.release-versions.outputs.node-version }} deno-version: ${{ needs.release-versions.outputs.deno-version }} lowercase-repo: ${{ needs.release-versions.outputs.lowercase-repo }} gh-docker-tag: ${{ needs.release-versions.outputs.gh-docker-tag }} secrets: CR_USER: ${{ secrets.CR_USER }} CR_PAT: ${{ secrets.CR_PAT }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} test-api-livechat: name: 🔨 Test API Livechat (CE) needs: [checks, build-gh-docker-publish, release-versions] uses: ./.github/workflows/ci-test-e2e.yml with: type: api-livechat release: ce node-version: ${{ needs.release-versions.outputs.node-version }} deno-version: ${{ needs.release-versions.outputs.deno-version }} lowercase-repo: ${{ needs.release-versions.outputs.lowercase-repo }} gh-docker-tag: ${{ needs.release-versions.outputs.gh-docker-tag }} secrets: CR_USER: ${{ secrets.CR_USER }} CR_PAT: ${{ secrets.CR_PAT }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} test-ui: name: 🔨 Test UI (CE) needs: [checks, build-gh-docker-publish, release-versions] uses: ./.github/workflows/ci-test-e2e.yml with: type: ui release: ce transporter: 'nats://nats:4222' enterprise-license: ${{ needs.release-versions.outputs.enterprise-license }} shard: '[1, 2, 3, 4]' total-shard: 4 node-version: ${{ needs.release-versions.outputs.node-version }} deno-version: ${{ needs.release-versions.outputs.deno-version }} lowercase-repo: ${{ needs.release-versions.outputs.lowercase-repo }} gh-docker-tag: ${{ needs.release-versions.outputs.gh-docker-tag }} retries: ${{ (github.event_name == 'release' || github.event_name == 'merge_group' || github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master') && 2 || 0 }} secrets: CR_USER: ${{ secrets.CR_USER }} CR_PAT: ${{ secrets.CR_PAT }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} QASE_API_TOKEN: ${{ secrets.QASE_API_TOKEN }} REPORTER_ROCKETCHAT_API_KEY: ${{ secrets.REPORTER_ROCKETCHAT_API_KEY }} REPORTER_ROCKETCHAT_URL: ${{ secrets.REPORTER_ROCKETCHAT_URL }} REPORTER_JIRA_ROCKETCHAT_API_KEY: ${{ secrets.REPORTER_JIRA_ROCKETCHAT_API_KEY }} test-api-ee: name: 🔨 Test API (EE) needs: [checks, build-gh-docker-publish, release-versions] uses: ./.github/workflows/ci-test-e2e.yml with: type: api release: ee transporter: 'nats://nats:4222' enterprise-license: ${{ needs.release-versions.outputs.enterprise-license }} mongodb-version: "['8.0']" coverage: '8.0' node-version: ${{ needs.release-versions.outputs.node-version }} deno-version: ${{ needs.release-versions.outputs.deno-version }} lowercase-repo: ${{ needs.release-versions.outputs.lowercase-repo }} gh-docker-tag: ${{ needs.release-versions.outputs.gh-docker-tag }} secrets: CR_USER: ${{ secrets.CR_USER }} CR_PAT: ${{ secrets.CR_PAT }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} test-api-livechat-ee: name: 🔨 Test API Livechat (EE) needs: [checks, build-gh-docker-publish, release-versions] uses: ./.github/workflows/ci-test-e2e.yml with: type: api-livechat release: ee transporter: 'nats://nats:4222' enterprise-license: ${{ needs.release-versions.outputs.enterprise-license }} mongodb-version: "['8.0']" coverage: '8.0' node-version: ${{ needs.release-versions.outputs.node-version }} deno-version: ${{ needs.release-versions.outputs.deno-version }} lowercase-repo: ${{ needs.release-versions.outputs.lowercase-repo }} gh-docker-tag: ${{ needs.release-versions.outputs.gh-docker-tag }} secrets: CR_USER: ${{ secrets.CR_USER }} CR_PAT: ${{ secrets.CR_PAT }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} test-api-apps-deno-ee: name: 🔨 Test API Apps (deno-runtime - EE) needs: [checks, build-gh-docker-publish, release-versions] uses: ./.github/workflows/ci-test-e2e.yml with: type: api-apps-deno release: ee transporter: 'nats://nats:4222' enterprise-license: ${{ needs.release-versions.outputs.enterprise-license }} mongodb-version: "['8.0']" coverage: '8.0' node-version: ${{ needs.release-versions.outputs.node-version }} deno-version: ${{ needs.release-versions.outputs.deno-version }} lowercase-repo: ${{ needs.release-versions.outputs.lowercase-repo }} gh-docker-tag: ${{ needs.release-versions.outputs.gh-docker-tag }} secrets: CR_USER: ${{ secrets.CR_USER }} CR_PAT: ${{ secrets.CR_PAT }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} test-ui-ee: name: 🔨 Test UI (EE) needs: [checks, build-gh-docker-publish, release-versions] uses: ./.github/workflows/ci-test-e2e.yml with: type: ui release: ee transporter: 'nats://nats:4222' enterprise-license: ${{ needs.release-versions.outputs.enterprise-license }} shard: '[1, 2, 3, 4, 5]' total-shard: 5 mongodb-version: "['8.0']" coverage: '8.0' node-version: ${{ needs.release-versions.outputs.node-version }} deno-version: ${{ needs.release-versions.outputs.deno-version }} lowercase-repo: ${{ needs.release-versions.outputs.lowercase-repo }} gh-docker-tag: ${{ needs.release-versions.outputs.gh-docker-tag }} retries: ${{ (github.event_name == 'release' || github.event_name == 'merge_group' || github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master') && 2 || 0 }} secrets: CR_USER: ${{ secrets.CR_USER }} CR_PAT: ${{ secrets.CR_PAT }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} QASE_API_TOKEN: ${{ secrets.QASE_API_TOKEN }} REPORTER_ROCKETCHAT_API_KEY: ${{ secrets.REPORTER_ROCKETCHAT_API_KEY }} REPORTER_ROCKETCHAT_URL: ${{ secrets.REPORTER_ROCKETCHAT_URL }} CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} REPORTER_JIRA_ROCKETCHAT_API_KEY: ${{ secrets.REPORTER_JIRA_ROCKETCHAT_API_KEY }} test-api-fips: name: 🔨 Test API (FIPS) if: needs.release-versions.outputs.fips-skip != 'all' needs: [checks, build-gh-docker-publish, release-versions] uses: ./.github/workflows/ci-test-e2e.yml with: type: api release: ee fips: true transporter: 'nats://nats:4222' enterprise-license: ${{ needs.release-versions.outputs.enterprise-license }} mongodb-version: "['8.0']" node-version: ${{ needs.release-versions.outputs.node-version }} deno-version: ${{ needs.release-versions.outputs.deno-version }} lowercase-repo: ${{ needs.release-versions.outputs.lowercase-repo }} gh-docker-tag: ${{ needs.release-versions.outputs.gh-docker-tag }} secrets: CR_USER: ${{ secrets.CR_USER }} CR_PAT: ${{ secrets.CR_PAT }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} test-api-livechat-fips: name: 🔨 Test API Livechat (FIPS) if: needs.release-versions.outputs.fips-skip != 'all' needs: [checks, build-gh-docker-publish, release-versions] uses: ./.github/workflows/ci-test-e2e.yml with: type: api-livechat release: ee fips: true transporter: 'nats://nats:4222' enterprise-license: ${{ needs.release-versions.outputs.enterprise-license }} mongodb-version: "['8.0']" node-version: ${{ needs.release-versions.outputs.node-version }} deno-version: ${{ needs.release-versions.outputs.deno-version }} lowercase-repo: ${{ needs.release-versions.outputs.lowercase-repo }} gh-docker-tag: ${{ needs.release-versions.outputs.gh-docker-tag }} secrets: CR_USER: ${{ secrets.CR_USER }} CR_PAT: ${{ secrets.CR_PAT }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} test-ui-fips: name: 🔨 Test UI (FIPS) # only the 'fips' label opts a PR into the UI shards if: needs.release-versions.outputs.fips-skip == 'none' needs: [checks, build-gh-docker-publish, release-versions] uses: ./.github/workflows/ci-test-e2e.yml with: type: ui release: ee fips: true transporter: 'nats://nats:4222' enterprise-license: ${{ needs.release-versions.outputs.enterprise-license }} shard: '[1, 2, 3, 4, 5]' total-shard: 5 mongodb-version: "['8.0']" node-version: ${{ needs.release-versions.outputs.node-version }} deno-version: ${{ needs.release-versions.outputs.deno-version }} lowercase-repo: ${{ needs.release-versions.outputs.lowercase-repo }} gh-docker-tag: ${{ needs.release-versions.outputs.gh-docker-tag }} retries: ${{ (github.event_name == 'release' || github.event_name == 'merge_group' || github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master') && 2 || 0 }} secrets: CR_USER: ${{ secrets.CR_USER }} CR_PAT: ${{ secrets.CR_PAT }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} QASE_API_TOKEN: ${{ secrets.QASE_API_TOKEN }} REPORTER_ROCKETCHAT_API_KEY: ${{ secrets.REPORTER_ROCKETCHAT_API_KEY }} REPORTER_ROCKETCHAT_URL: ${{ secrets.REPORTER_ROCKETCHAT_URL }} REPORTER_JIRA_ROCKETCHAT_API_KEY: ${{ secrets.REPORTER_JIRA_ROCKETCHAT_API_KEY }} test-federation-matrix: name: 🔨 Test Federation Matrix needs: [checks, build-gh-docker-publish, packages-build, release-versions] runs-on: ubuntu-24.04 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Setup NodeJS uses: ./.github/actions/setup-node with: node-version: ${{ needs.release-versions.outputs.node-version }} deno-version: ${{ needs.release-versions.outputs.deno-version }} cache-modules: true install: true - uses: rharkor/caching-for-turbo@2238fae6eb9a9936f92356f54cb3660200d105e7 # v2.5.1 - name: Restore turbo build uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 continue-on-error: true with: name: turbo-build path: .turbo/cache - name: Build packages run: yarn build - name: Login to GitHub Container Registry if: (github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'release' || github.ref == 'refs/heads/develop') && github.actor != 'dependabot[bot]' uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 with: registry: ghcr.io username: ${{ secrets.CR_USER }} password: ${{ secrets.CR_PAT }} - name: Configure /etc/hosts for federation services run: | sudo -- sh -c "echo '127.0.0.1 hs1' >> /etc/hosts" sudo -- sh -c "echo '127.0.0.1 rc1' >> /etc/hosts" # Download Docker images from build artifacts - name: Download Docker images uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 if: github.event.pull_request.head.repo.full_name != github.repository && github.event_name != 'release' && github.ref != 'refs/heads/develop' with: pattern: 'docker-image-rocketchat-amd64-coverage' path: /tmp/docker-images merge-multiple: true # Load Docker images - name: Load Docker images if: github.event.pull_request.head.repo.full_name != github.repository && github.event_name != 'release' && github.ref != 'refs/heads/develop' shell: bash run: | set -o xtrace # Load all downloaded images for image_file in /tmp/docker-images/*.tar; do if [ -f "$image_file" ]; then echo "Loading image from $image_file" docker load -i "$image_file" rm "$image_file" fi done # List loaded images docker images - name: Start federation services working-directory: ./ee/packages/federation-matrix env: ROCKETCHAT_IMAGE: ghcr.io/${{ needs.release-versions.outputs.lowercase-repo }}/rocket.chat:${{ needs.release-versions.outputs.gh-docker-tag }}-amd64 ENTERPRISE_LICENSE_RC1: ZAikY+LLaal7mT6RNYxpyWEmMQyucrl50/7pYBXqHczc90j+RLwF+T0xuCT2pIpKMC5DxcZ1TtkV6MYJk5whrwmap+mQ0FV+VpILJlL0i4T21K4vMfzZXTWm/pzcAy2fMTUNH+mUA9HTBD6lYYh40KnbGXPAd80VbZk0MO/WbWBm2dOT0YCwfvlRyurRqkDAQrftLaffzCNUsMKk0fh+MKs73UDHZQDp1yvs7WoGpPu5ZVi5mTBOt3ZKVz5KjGfClLwJptFPmW1w6nKelAiJBDPpjcX1ylfjxpnBoixko7uN52zlyaeoAYwfRcdDLnZ8k0Ou6tui/vTQUXjGIjHw2AhMaKwonn4E9LYpuA1KEXt08qJL5J3ZtjSCV1T+A9Z3zFhhLgp5dxP/PPUbxDn/P8XKp7nXM9duIfcCMlnea7V8ixEyCHwwvKQaXVVidcsUGtB8CwS0GlsAEBLOzqMehuQUK2rdQ4WgEz3AYveikeVvSzgBHvyXsxssWAThc0Mht0eEJqdDhUB2QeZ2WmPsaSSD639Z4WgjSUoR0zh8bfqepH+2XRcUryXe2yN+iU+3POzi9wfg0k65MxXT8pBg3PD5RHnR8oflEP0tpZts33JiBhYRxX3MKplAFm4dMuphTsDJTh+e534pT7IPuZF79QSVaLEWZfVVVb7nGFtmMwA= QASE_TESTOPS_JEST_API_TOKEN: ${{ secrets.QASE_TESTOPS_JEST_API_TOKEN }} PR_NUMBER: ${{ github.event.number }} run: yarn test:integration --start-containers-only --image "${ROCKETCHAT_IMAGE}" - name: Run federation integration tests working-directory: ./ee/packages/federation-matrix env: QASE_TESTOPS_JEST_API_TOKEN: ${{ secrets.QASE_TESTOPS_JEST_API_TOKEN }} PR_NUMBER: ${{ github.event.number }} run: yarn test:integration --ci - name: Show federation integration tests logs if: failure() working-directory: ./ee/packages/federation-matrix run: yarn test:integration --logs - name: Show mongo logs if tests failed if: failure() working-directory: ./ee/packages/federation-matrix run: docker compose -f docker-compose.test.yml logs mongo report-coverage: name: 📊 Report Coverage runs-on: ubuntu-24.04 needs: [release-versions, test-api-ee, test-api-livechat-ee, test-ui-ee] steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Use Node.js uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: ${{ needs.release-versions.outputs.node-version }} - name: Restore coverage folder uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: pattern: coverage-* path: /tmp/coverage merge-multiple: true - name: Generate lcov report run: | set -o xtrace npx nyc report --reporter=lcovonly --report-dir=/tmp/coverage_report/api --temp-dir=/tmp/coverage/api npx nyc report --reporter=lcovonly --report-dir=/tmp/coverage_report/ui --temp-dir=/tmp/coverage/ui - name: Store coverage-reports uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: reports-coverage path: /tmp/coverage_report include-hidden-files: true - name: Report API coverage uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 with: files: /tmp/coverage_report/api/lcov.info working-directory: . flags: e2e-api verbose: true token: ${{ secrets.CODECOV_TOKEN }} # tests don't re-run on the develop push, so the merge queue run (same SHA # that lands on develop) provides the develop coverage baseline override_branch: ${{ github.event_name == 'merge_group' && github.event.merge_group.base_ref == 'refs/heads/develop' && 'develop' || '' }} - name: Report UI coverage uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 with: files: /tmp/coverage_report/ui/lcov.info working-directory: . flags: e2e verbose: true token: ${{ secrets.CODECOV_TOKEN }} override_branch: ${{ github.event_name == 'merge_group' && github.event.merge_group.base_ref == 'refs/heads/develop' && 'develop' || '' }} tests-done: name: ✅ Tests Done runs-on: ubuntu-24.04-arm needs: [test-guard, checks, test-unit, test-api, test-ui, test-api-ee, test-ui-ee, test-api-livechat, test-api-livechat-ee, test-api-apps-deno-ee, test-api-fips, test-api-livechat-fips, test-ui-fips, test-federation-matrix] if: always() && needs.test-guard.outputs.skip-tests != 'true' steps: - name: Test finish aggregation run: | if [[ '${{ needs.checks.result }}' != 'success' ]]; then exit 1 fi if [[ '${{ needs.test-unit.result }}' != 'success' ]]; then exit 1 fi if [[ '${{ needs.test-api.result }}' != 'success' ]]; then exit 1 fi if [[ '${{ needs.test-ui.result }}' != 'success' ]]; then exit 1 fi if [[ '${{ needs.test-api-ee.result }}' != 'success' ]]; then exit 1 fi if [[ '${{ needs.test-ui-ee.result }}' != 'success' ]]; then exit 1 fi if [[ '${{ needs.test-api-livechat.result }}' != 'success' ]]; then exit 1 fi if [[ '${{ needs.test-api-livechat-ee.result }}' != 'success' ]]; then exit 1 fi if [[ '${{ needs.test-api-apps-deno-ee.result }}' != 'success' ]]; then exit 1 fi # the fips jobs are gated by the release-versions 'fips-skip' output, so any of them # can legitimately report 'skipped' (always on fork PRs, and test-ui-fips unless the # PR carries the 'fips' label) if [[ '${{ needs.test-api-fips.result }}' != 'success' && '${{ needs.test-api-fips.result }}' != 'skipped' ]]; then exit 1 fi if [[ '${{ needs.test-api-livechat-fips.result }}' != 'success' && '${{ needs.test-api-livechat-fips.result }}' != 'skipped' ]]; then exit 1 fi if [[ '${{ needs.test-ui-fips.result }}' != 'success' && '${{ needs.test-ui-fips.result }}' != 'skipped' ]]; then exit 1 fi if [[ '${{ needs.test-federation-matrix.result }}' != 'success' ]]; then exit 1 fi echo finished deploy: name: 🚀 Publish build assets runs-on: ubuntu-24.04-arm if: github.event_name == 'release' || github.ref == 'refs/heads/develop' needs: [build-gh-docker-publish, release-versions] steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: sparse-checkout: | package.json sparse-checkout-cone-mode: false ref: ${{ github.ref }} - name: Restore build uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: build-production path: /tmp/build - name: Publish assets env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_DEFAULT_REGION: 'us-east-1' GPG_PASSWORD: ${{ secrets.GPG_PASSWORD }} run: | REPO_VERSION=$(node -p "require('./package.json').version") if [[ "${{ github.event_name }}" = 'release' ]]; then ARTIFACT_NAME="${REPO_VERSION}" else ARTIFACT_NAME="${REPO_VERSION}.$GITHUB_SHA" fi; ROCKET_DEPLOY_DIR="/tmp/deploy" FILENAME="$ROCKET_DEPLOY_DIR/rocket.chat-$ARTIFACT_NAME.tgz" aws s3 cp s3://rocketchat/sign.key.gpg .github/sign.key.gpg mkdir -p "$ROCKET_DEPLOY_DIR" cp .github/sign.key.gpg /tmp gpg --yes --batch --passphrase="$GPG_PASSWORD" /tmp/sign.key.gpg gpg --allow-secret-key-import --import /tmp/sign.key rm /tmp/sign.key ln -s /tmp/build/Rocket.Chat.tar.gz "$FILENAME" gpg --armor --detach-sign "$FILENAME" aws s3 cp "$ROCKET_DEPLOY_DIR/" s3://download.rocket.chat/build/ --recursive docker-image-publish: name: 🚀 Publish Docker Images (DockerHub) runs-on: ubuntu-24.04-arm needs: [deploy, release-versions] env: DOCKER_TAG: ${{ needs.release-versions.outputs.gh-docker-tag }} LOWERCASE_REPOSITORY: ${{ needs.release-versions.outputs.lowercase-repo }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: sparse-checkout: | docker-compose-ci.yml docker-compose-ci.fips.yml sparse-checkout-cone-mode: false ref: ${{ github.ref }} - name: Login to DockerHub uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 with: username: ${{ secrets.DOCKER_USER }} password: ${{ secrets.DOCKER_PASS }} - name: Login to GitHub Container Registry uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 with: registry: ghcr.io username: ${{ secrets.CR_USER }} password: ${{ secrets.CR_PAT }} - name: Download manifests if: github.actor != 'dependabot[bot]' && (github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'release' || github.ref == 'refs/heads/develop') uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: pattern: manifests-* path: /tmp/manifests merge-multiple: true - name: Publish Docker images run: | set -euo pipefail set -o xtrace shopt -s nullglob # sudo apt-get update -y # sudo apt-get install -y skopeo # 'develop' or 'tag' DOCKER_TAG=$GITHUB_REF_NAME DOCKER_TAG="${DOCKER_TAG//\//-}" declare -a TAGS=() # tag specific tag version TAGS+=("$DOCKER_TAG") if [[ $GITHUB_REF == refs/tags/* ]]; then RELEASE="${{ needs.release-versions.outputs.release }}" echo "RELEASE: $RELEASE" if [[ "$RELEASE" == 'latest' ]]; then if [[ '${{ needs.release-versions.outputs.latest-release }}' == "$GITHUB_REF_NAME" ]]; then TAGS+=("$RELEASE") fi else TAGS+=("$RELEASE") fi fi # commit hash COMMIT_SHA="sha-${GITHUB_SHA:0:7}" echo "COMMIT_SHA: ${COMMIT_SHA}" TAGS+=("${COMMIT_SHA}") echo "Tags: ${TAGS[*]}" # get first tag as primary PRIMARY="${TAGS[0]}" for service_dir in /tmp/manifests/*; do [[ -d "$service_dir" ]] || continue service="$(basename "$service_dir")" if [ "$service" == "rocketchat-cov" ]; then continue fi echo "Promoting $service" # FIPS images publish to the same repo as their base service, distinguished by a # '-fips' tag suffix (matching GHCR), not a separate '-fips' repository. # Get image name from docker-compose-ci.yml since rocketchat image is different from service name (rocket.chat) TAG_SUFFIX='' if [[ "$service" == *"-fips" ]]; then base_service="${service%-fips}" TAG_SUFFIX='-fips' SRC=$(docker compose -f docker-compose-ci.yml -f docker-compose-ci.fips.yml config --format json 2>/dev/null | jq -r --arg s "$base_service" '.services[$s].image') else base_service="$service" SRC=$(docker compose -f docker-compose-ci.yml config --format json 2>/dev/null | jq -r --arg s "$service" '.services[$s].image') fi if [[ "${base_service}" == 'rocketchat' ]]; then IMAGE_NAME="${{ needs.release-versions.outputs.lowercase-repo }}/rocket.chat" else IMAGE_NAME="${{ needs.release-versions.outputs.lowercase-repo }}/${base_service}" fi DEST_REPO="docker.io/${IMAGE_NAME}" echo "Copying $SRC to ${DEST_REPO}:${PRIMARY}${TAG_SUFFIX}" skopeo copy --all \ "docker://${SRC}" \ "docker://${DEST_REPO}:${PRIMARY}${TAG_SUFFIX}" # copy additional tags if (( ${#TAGS[@]} > 1 )); then for t in "${TAGS[@]:1}"; do echo "Copying $SRC to ${DEST_REPO}:${t}${TAG_SUFFIX}" skopeo copy --all \ "docker://${SRC}" \ "docker://${DEST_REPO}:${t}${TAG_SUFFIX}" done fi done notify-services: name: 🚀 Notify external services runs-on: ubuntu-24.04-arm needs: - docker-image-publish - release-versions steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: sparse-checkout: | package.json sparse-checkout-cone-mode: false ref: ${{ github.ref }} - name: Releases service env: UPDATE_TOKEN: ${{ secrets.UPDATE_TOKEN }} run: | REPO_VERSION=$(node -p "require('./package.json').version") if [[ '${{ github.event_name }}' = 'release' ]]; then GIT_TAG="${GITHUB_REF#*tags/}" GIT_BRANCH="" ARTIFACT_NAME="${REPO_VERSION}" RC_VERSION=$GIT_TAG if [[ '${{ needs.release-versions.outputs.release }}' = 'release-candidate' ]]; then RC_RELEASE=candidate elif [[ '${{ needs.release-versions.outputs.release }}' = 'latest' ]]; then RC_RELEASE=stable fi else GIT_TAG="" GIT_BRANCH="${GITHUB_REF#*heads/}" ARTIFACT_NAME="${REPO_VERSION}.$GITHUB_SHA" RC_VERSION="${REPO_VERSION}" RC_RELEASE=develop fi; curl -H "Content-Type: application/json" -H "X-Update-Token: $UPDATE_TOKEN" -d \ "{\"nodeVersion\": \"${{ needs.release-versions.outputs.node-version }}\", \"denoVersion\": \"${{ needs.release-versions.outputs.deno-version }}\", \"compatibleMongoVersions\": [\"8.0\"], \"commit\": \"$GITHUB_SHA\", \"tag\": \"$RC_VERSION\", \"branch\": \"$GIT_BRANCH\", \"artifactName\": \"$ARTIFACT_NAME\", \"releaseType\": \"$RC_RELEASE\"}" \ https://releases.rocket.chat/update # Makes build fail if the release isn't there curl --fail "https://releases.rocket.chat/$RC_VERSION/info" docs-update: name: Update Version Durability if: github.event_name == 'release' needs: - docker-image-publish uses: ./.github/workflows/update-version-durability.yml with: LTS_VERSIONS: ${{ vars.LTS_VERSIONS }} secrets: CI_PAT: ${{ secrets.CI_PAT }} D360_TOKEN: ${{ secrets.D360_TOKEN }} D360_USER_ID: ${{ secrets.D360_USER_ID }}