From 3cdec1156ee5b5aaaeb84f8a2dacb20d3fb48f02 Mon Sep 17 00:00:00 2001 From: danilapog Date: Tue, 21 Jul 2026 13:16:30 +0000 Subject: [PATCH] Feat: add actions for secure rebuild ds images --- .../scripts/update-secure-rebuild-config.sh | 218 +++++++ .github/secure-rebuild.json | 195 ++++++ .github/workflows/rebuild.yml | 224 ------- .github/workflows/secure-rebuild.yml | 565 ++++++++++++++++++ .github/workflows/stable-build.yml | 428 ++++++++++--- Dockerfile.secure | 16 + production.dockerfile | 13 - 7 files changed, 1338 insertions(+), 321 deletions(-) create mode 100644 .github/scripts/update-secure-rebuild-config.sh create mode 100644 .github/secure-rebuild.json delete mode 100644 .github/workflows/rebuild.yml create mode 100644 .github/workflows/secure-rebuild.yml create mode 100644 Dockerfile.secure diff --git a/.github/scripts/update-secure-rebuild-config.sh b/.github/scripts/update-secure-rebuild-config.sh new file mode 100644 index 0000000..3042f26 --- /dev/null +++ b/.github/scripts/update-secure-rebuild-config.sh @@ -0,0 +1,218 @@ +#!/usr/bin/env bash +# Usage: +# update-secure-rebuild-config.sh \ +# \ +# \ +# \ +# + +set -euo pipefail + +if [ "$#" -ne 4 ]; then + echo "Usage: $0 " >&2 + exit 2 +fi + +SOURCE_TAG="$1" +RELEASE_TAG="$2" +RELEASE_NUMBER="$3" +EDITIONS_JSON="$4" + +CONFIG="${CONFIG:-.github/secure-rebuild.json}" + +if ! [[ "${RELEASE_NUMBER}" =~ ^[1-9][0-9]*$ ]]; then + echo "Error: release_number must be a positive integer" >&2 + exit 2 +fi + +if ! [[ "${RELEASE_TAG}" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "Error: release_tag must have format X.Y.Z.N" >&2 + exit 2 +fi + +if [ ! -f "${CONFIG}" ]; then + echo "Error: config not found: ${CONFIG}" >&2 + exit 1 +fi + +# Expected format: +# { +# "-ee": { "latest": true }, +# "-de": { "latest": true } +# } +if ! jq -e ' + type == "object" + and all( + .[]; + type == "object" + and has("latest") + and (.latest | type == "boolean") + ) +' >/dev/null 2>&1 <<< "${EDITIONS_JSON}"; then + echo 'Error: editions_json must look like {"-ee":{"latest":true}}' >&2 + exit 2 +fi + +if ! jq -e ' + type == "object" + and (.releases | type == "object") +' "${CONFIG}" >/dev/null 2>&1; then + echo "Error: ${CONFIG} must contain a releases object" >&2 + exit 1 +fi + +# For release_tag 9.6.1.1: +# MINOR = 9.6.1 +# SHORTER = 9.6 +# MAJOR = 9 +MINOR="${RELEASE_TAG%.*}" +SHORTER="${MINOR%.*}" +MAJOR="${RELEASE_TAG%%.*}" +MAJOR_KEY="v${MAJOR}" + +TMP_FILE="$(mktemp "${CONFIG}.tmp.XXXXXX")" + +cleanup() { + rm -f "${TMP_FILE}" +} + +trap cleanup EXIT + +jq \ + --arg key "${MAJOR_KEY}" \ + --arg src "${SOURCE_TAG}" \ + --arg rel "${RELEASE_TAG}" \ + --arg s1 "${MINOR}" \ + --arg s2 "${SHORTER}" \ + --argjson release_number "${RELEASE_NUMBER}" \ + --argjson eds "${EDITIONS_JSON}" \ +' + # Strips latest only for editions present in the new $eds. + # Uses exact key comparison via index() so that empty key "" + # does not match "-ee" or "-de". + def strip_latest_for_editions($eds): + ($eds | keys) as $edition_keys + | + map( + .editions |= with_entries( + .key as $edition_key + | + if ($edition_keys | index($edition_key)) != null + then .value.latest = false + else . + end + ) + ); + + # Removes a short tag by exact match. + def remove_exact_short_tag($tag): + map( + .short_tags |= map( + select(. != $tag) + ) + ); + + # For subsequent rebuild releases, removes short tags + # from the previous record of the same patch version. + # Example: when adding 9.6.1.2, removes "9.6.1" and "9.6" from 9.6.1.1. + def remove_patch_short_tags($s1; $s2): + map( + if (.short_tags | index($s1)) != null + then + .short_tags |= map( + select(. != $s1 and . != $s2) + ) + else + . + end + ); + + def new_record($src; $rel; $s1; $s2; $eds): + { + source_tag: $src, + release_tag: $rel, + short_tags: [$s1, $s2], + editions: $eds + }; + + # Remember whether the major branch existed before changes. + (.releases | has($key)) as $major_existed + | + + # Idempotency guard: remove any existing record with the same release_tag + # to avoid duplicates on re-run. + .releases |= with_entries( + .value |= map( + select(.release_tag != $rel) + ) + ) + | + + if $major_existed then + + # Major branch already exists (e.g. v9). + .releases[$key] = ( + .releases[$key] + + # First release of a new minor (release_number == 1): + # remove the short minor tag (e.g. "9.6") from the previous record. + # Subsequent rebuild release: + # remove both "9.6.1" and "9.6" from the previous record + # of the same patch version. + | + if $release_number == 1 + then remove_exact_short_tag($s2) + else remove_patch_short_tags($s1; $s2) + end + + # Strip latest only for editions included in the new release. + | + strip_latest_for_editions($eds) + ) + | + + # Prepend the new release record. + .releases[$key] = [ + new_record($src; $rel; $s1; $s2; $eds) + ] + .releases[$key] + + else + + # A new major branch is being created (e.g. first v10). + # Strip latest for the corresponding editions across all existing major branches. + .releases |= with_entries( + .value |= strip_latest_for_editions($eds) + ) + | + + .releases[$key] = [ + new_record($src; $rel; $s1; $s2; $eds) + ] + | + + # Keep only the two most recent major branches. + .releases = ( + .releases + | to_entries + | sort_by( + .key + | ltrimstr("v") + | tonumber + ) + | reverse + | .[0:2] + | from_entries + ) + + end +' "${CONFIG}" > "${TMP_FILE}" + +# Validate that the result is valid JSON. +jq -e . "${TMP_FILE}" >/dev/null + +mv "${TMP_FILE}" "${CONFIG}" +trap - EXIT + +echo "Updated ${CONFIG}:" +cat "${CONFIG}" + diff --git a/.github/secure-rebuild.json b/.github/secure-rebuild.json new file mode 100644 index 0000000..93199f2 --- /dev/null +++ b/.github/secure-rebuild.json @@ -0,0 +1,195 @@ +{ + "releases": { + "v9": [ + { + "source_tag": "9.4.1.15", + "release_tag": "9.4.1.2", + "short_tags": [ + "9.4.1", + "9.4" + ], + "editions": { + "-ee": { + "latest": true + }, + "-de": { + "latest": true + } + } + }, + { + "source_tag": "9.4.1.15", + "release_tag": "9.4.1.1", + "short_tags": [], + "editions": { + "-ee": { + "latest": false + }, + "-de": { + "latest": false + } + } + }, + { + "source_tag": "9.4.0.129", + "release_tag": "9.4.0.2", + "short_tags": [ + "9.4.0" + ], + "editions": { + "-ee": { + "latest": false + }, + "-de": { + "latest": false + } + } + }, + { + "source_tag": "9.4.0.129", + "release_tag": "9.4.0.1", + "short_tags": [], + "editions": { + "": { + "latest": true + }, + "-ee": { + "latest": false + }, + "-de": { + "latest": false + } + } + }, + { + "source_tag": "9.3.1.10", + "release_tag": "9.3.1.2", + "short_tags": [ + "9.3.1", + "9.3" + ], + "editions": { + "": { + "latest": false + }, + "-ee": { + "latest": false + }, + "-de": { + "latest": false + } + } + }, + { + "source_tag": "9.3.1.8", + "release_tag": "9.3.1.1", + "short_tags": [], + "editions": { + "": { + "latest": false + }, + "-ee": { + "latest": false + }, + "-de": { + "latest": false + } + } + }, + { + "source_tag": "9.3.0.138", + "release_tag": "9.3.0.1", + "short_tags": [ + "9.3.0" + ], + "editions": { + "": { + "latest": false + }, + "-ee": { + "latest": false + }, + "-de": { + "latest": false + } + } + }, + { + "source_tag": "9.2.1.8", + "release_tag": "9.2.1.1", + "short_tags": [ + "9.2.1", + "9.2" + ], + "editions": { + "": { + "latest": false + }, + "-ee": { + "latest": false + }, + "-de": { + "latest": false + } + } + }, + { + "source_tag": "9.2.0.98", + "release_tag": "9.2.0.1", + "short_tags": [ + "9.2.0" + ], + "editions": { + "": { + "latest": false + }, + "-ee": { + "latest": false + }, + "-de": { + "latest": false + } + } + }, + { + "source_tag": "9.1.0.168", + "release_tag": "9.1.0.1", + "short_tags": [ + "9.1.0", + "9.1" + ], + "editions": { + "": { + "latest": false + }, + "-ee": { + "latest": false + }, + "-de": { + "latest": false + } + } + }, + { + "source_tag": "9.0.4.50", + "release_tag": "9.0.4.1", + "short_tags": [ + "9.0.4", + "9.0" + ], + "editions": { + "": { + "latest": false + }, + "-ee": { + "latest": false + }, + "-de": { + "latest": false + } + } + } + ] + } +} + diff --git a/.github/workflows/rebuild.yml b/.github/workflows/rebuild.yml deleted file mode 100644 index 953eefd..0000000 --- a/.github/workflows/rebuild.yml +++ /dev/null @@ -1,224 +0,0 @@ ---- -name: Rebuild Docker-Documentserver - -run-name: > - Rebuild DocumentServer with secure updates for repo: ${{ github.event.inputs.repo }} - -on: - workflow_dispatch: - inputs: - repo: - type: choice - description: Please, choose upload repo.. - options: - - '4test' - - 'stable' - -permissions: - # All other permissions are set to none - contents: read - # Technically read access while waiting for images should be more than enough. However, - # there is a bug in GitHub Actions/Packages and in case private repositories are used, you get a permission - # denied error when attempting to just pull private image, changing the token permission to write solves the - # issue. This is not dangerous, because if it is for "ONLYOFFICE/Docker-DocumentServer", only maintainers can use ds-rebuild.yaml - # If it is for a fork, then the token is read-only anyway. - packages: read - -env: - COMPANY_NAME: "onlyoffice" - PRODUCT_NAME: "documentserver" - REGISTRY_URL: "https://hub.docker.com/v2/repositories" - -jobs: - rebuild-info: - name: "Rebuild-info" - runs-on: "ubuntu-22.04" - env: - REPO_INPUTS: ${{ github.event.inputs.repo }} - EVENT: ${{ github.event_name }} - outputs: - stable-versions: ${{ steps.selective-checks.outputs.stable-versions }} - ucs-versions: ${{ steps.selective-checks.outputs.ucs-versions }} - minor-tags: ${{ steps.selective-checks.outputs.minor-tags }} - ucs-rebuild-condition: ${{ steps.selective-checks.outputs.ucs-rebuild-condition }} - prefix-name: ${{ steps.selective-checks.outputs.prefix-name }} - repo: ${{ steps.selective-checks.outputs.repo }} - steps: - - name: Selective checks - id: selective-checks - run: | - set -e - - REPO=${REPO_INPUTS:-"4test"} - - if [ "${REPO}" == "stable" ]; then - UCS_REBUILD=true - UCS_VERSIONS=($(curl -s -H -X ${REGISTRY_URL}/${COMPANY_NAME}/${PRODUCT_NAME}-ucs/tags/?page_size=100 | \ - jq -r '.results|.[]|.name' | grep -oxE '[0-9]{1,}.[0-9]{1,}.[0-9]{1,}.1' || true)) - echo "ucs-versions=$(jq -c -n '$ARGS.positional' --args "${UCS_VERSIONS[@]}")" >> "$GITHUB_OUTPUT" - elif - [ "${REPO}" == "4test" ]; then - UCS_REBUILD=false - PREFIX_NAME=4testing- - fi - - STABLE_VERSIONS=($(curl -s -H -X ${REGISTRY_URL}/${COMPANY_NAME}/${PRODUCT_NAME}/tags/?page_size=100 | \ - jq -r '.results|.[]|.name' | grep -oxE '[0-9]{1,}.[0-9]{1,}.[0-9]{1,}.1' || true)) - - # When rebuilding stable versions of the document server, - # it is necessary to determine the version from which the - # minor x.x tag will need to be pushed. - - VERSIONS=(${STABLE_VERSIONS[@]}) - for i in {1..10}; do - if [ -z "${VERSIONS}" ]; then - break - else - TEMPLATE=${VERSIONS[0]%.*.*} - TEMPLATE_MINOR=$(printf -- '%s\n' "${VERSIONS[@]}" | grep -o -m 1 "${VERSIONS[0]%.*.*}.[0-9].[0-9]") - MINOR_TAGS+=(${TEMPLATE_MINOR%.*}) - - for v in ${MINOR_TAGS[@]}; do - VERSIONS=(${VERSIONS[@]//${v%.*}.*.*}) - done - fi - done - - echo "Stable releases that will be rebuilded" - echo "--------------------------------------" - echo "${STABLE_VERSIONS[@]}" - echo - echo - echo "Ucs releases that will be rebuilded" - echo "-----------------------------------" - echo "${UCS_VERSIONS[@]}" - - echo "stable-versions=$(jq -c -n '$ARGS.positional' --args "${STABLE_VERSIONS[@]}")" >> "$GITHUB_OUTPUT" - echo "minor-tags=${MINOR_TAGS[@]}" >> "$GITHUB_OUTPUT" - echo "ucs-rebuild-condition=${UCS_REBUILD}" >> "$GITHUB_OUTPUT" - echo "prefix-name=${PREFIX_NAME}" >> "$GITHUB_OUTPUT" - echo "repo=${REPO}" >> "$GITHUB_OUTPUT" - shell: bash - - re-build-stable: - name: "Rebuild stable:${{ matrix.version }} ${{ matrix.edition }}" - needs: [rebuild-info] - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - type: ["stable"] - edition: ["", "-ee", "-de"] - version: ${{fromJSON(needs.rebuild-info.outputs.stable-versions)}} - steps: - - name: Checkout code - uses: actions/checkout@v3 - - name: Set up QEMU - uses: docker/setup-qemu-action@v2 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - name: Login to Docker Hub - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKER_HUB_USERNAME }} - password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} - # Determines the new build number based - # on data from the hub.docker registry - - name: Declare release number - id: release-number - env: - REBUILD_VERSION: ${{ matrix.version }} - run: | - MINOR_VERSION=${REBUILD_VERSION%.*} - LAST_RELEASE=$(curl -s -H -X ${REGISTRY_URL}/${COMPANY_NAME}/${PRODUCT_NAME}/tags/?page_size=100 \ - | jq -r '.results|.[]|.name' | grep -Eo -m1 "${MINOR_VERSION}.[0-9]{1,}") - LAST_RELEASE=${LAST_RELEASE#*.*.*.} - echo "release-number=$((LAST_RELEASE+1))" >> "$GITHUB_OUTPUT" - shell: bash - # Note: Rebuilding images with an - # extra layer to update security and - # all dependencies. Update tags got +1 to previous release. - - name: Re-build documentserver-stable - env: - MINOR_TAGS_ST: ${{ needs.rebuild-info.outputs.minor-tags }} - VERSION: ${{ matrix.version }} - RELEASE_NUMBER: ${{ steps.release-number.outputs.release-number }} - PREFIX_NAME: ${{ needs.rebuild-info.outputs.prefix-name }} - REPO: ${{ needs.rebuild-info.outputs.repo }} - PRODUCT_EDITION: ${{ matrix.edition }} - run: | - set -eux - export PULL_TAG=${VERSION} - export TAG=${VERSION%.*}.${RELEASE_NUMBER} - export SHORTER_TAG=${VERSION%.*} - export SHORTEST_TAG=${VERSION%.*.*} - - if [ "${REPO}" == "stable" ]; then - MINOR_TAGS=(${MINOR_TAGS_ST}) - for v in ${MINOR_TAGS[@]}; do - if [ "${SHORTER_TAG}" == "${v}" ]; then - export PUSH_MAJOR="true" - fi - done - if [ "${SHORTER_TAG}" == "${MINOR_TAGS[0]}" ]; then - export LATEST="true" - fi - fi - docker buildx bake -f docker-bake.hcl documentserver-stable-rebuild --push - shell: bash - re-build-ucs: - name: "Rebuild ucs: ${{ matrix.version }} ${{ matrix.edition }}" - if: needs.rebuild-info.outputs.ucs-rebuild-condition == 'true' - needs: [rebuild-info] - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - type: ["ucs"] - edition: ["", "-ee"] - version: ${{fromJSON(needs.rebuild-info.outputs.ucs-versions)}} - steps: - - name: Checkout code - uses: actions/checkout@v3 - - name: Set up QEMU - uses: docker/setup-qemu-action@v2 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - name: Login to Docker Hub - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKER_HUB_USERNAME }} - password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} - # Determines the new build number based - # on data from the hub.docker registry - - name: Declare release number - id: release-number - env: - REBUILD_VERSION: ${{ matrix.version }} - run: | - MINOR_VERSION=${REBUILD_VERSION%.*} - LAST_RELEASE=$(curl -s -H -X ${REGISTRY_URL}/${COMPANY_NAME}/${PRODUCT_NAME}/tags/?page_size=100 \ - | jq -r '.results|.[]|.name' | grep -Eo -m1 "${MINOR_VERSION}.[0-9]{1,}") - LAST_RELEASE=${LAST_RELEASE#*.*.*.} - echo "release-number=$((LAST_RELEASE+1))" >> "$GITHUB_OUTPUT" - shell: bash - # Note: Rebuilding images with an - # extra layer to update security and - # all dependencies. Update tags +1 to previous release. - - name: Re-build documentserver-ucs - env: - VERSION: ${{ matrix.version }} - RELEASE_NUMBER: ${{ steps.release-number.outputs.release-number }} - PRODUCT_EDITION: ${{ matrix.edition }} - run: | - set -eux - export PULL_TAG=${VERSION} - export TAG=${VERSION%.*}.${RELEASE_NUMBER} - export SHORTER_TAG=${VERSION%.*} - export SHORTEST_TAG=${VERSION%.*.*} - - export UCS_REBUILD=true - export UCS_PREFIX=-ucs - - docker buildx bake -f docker-bake.hcl documentserver-stable-rebuild --push - shell: bash diff --git a/.github/workflows/secure-rebuild.yml b/.github/workflows/secure-rebuild.yml new file mode 100644 index 0000000..977edc9 --- /dev/null +++ b/.github/workflows/secure-rebuild.yml @@ -0,0 +1,565 @@ +name: Secure rebuild + +run-name: >- + Secure rebuild [ + ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run && 'DRY-RUN' || 'PUBLISH' }} + ] + +on: + schedule: + - cron: '0 9 1,15 * *' + workflow_dispatch: + inputs: + dry_run: + type: boolean + description: "Dry run (build & test, do not retag release)" + required: false + default: false + +concurrency: + group: secure-rebuild + cancel-in-progress: false + +env: + COMPANY_NAME: "onlyoffice" + PRODUCT_NAME: "documentserver" + REBUILD_CONFIG: ".github/secure-rebuild.json" + +jobs: + prepare: + name: Prepare rebuild matrices + runs-on: ubuntu-latest + + outputs: + build_matrix: ${{ steps.matrix.outputs.build_matrix }} + retag_matrix: ${{ steps.matrix.outputs.retag_matrix }} + + steps: + - name: Checkout code + uses: actions/checkout@v7 + + - name: Validate rebuild config + run: | + set -euo pipefail + + if [ ! -f "${REBUILD_CONFIG}" ]; then + echo "Rebuild config was not found: ${REBUILD_CONFIG}" >&2 + exit 1 + fi + + jq -e ' + type == "object" + and (.releases | type == "object") + and all( + .releases[]; + type == "array" + and all( + .[]; + (.source_tag | type == "string") + and (.source_tag | length > 0) + and (.release_tag | type == "string") + and (.release_tag | length > 0) + and (.short_tags | type == "array") + and all(.short_tags[]; type == "string") + and (.editions | type == "object") + and (.editions | length > 0) + and all( + .editions[]; + type == "object" + and has("latest") + and (.latest | type == "boolean") + ) + ) + ) + ' "${REBUILD_CONFIG}" >/dev/null + + - name: Create build and retag matrices + id: matrix + run: | + set -euo pipefail + + BUILD="$(jq -c ' + { + include: ( + [ + .releases + | to_entries[] + | .value[] + | . as $release + | $release.editions + | to_entries[] + | { + source_tag: $release.source_tag, + test_tag: ( + $release.release_tag + | split(".") + | .[0:3] + | join(".") + ), + edition: .key + } + ] + | unique_by([ + .source_tag, + .edition + ]) + ) + } + ' "${REBUILD_CONFIG}")" + + RETAG="$(jq -c ' + { + include: [ + .releases + | to_entries[] + | .value[] + | . as $release + | $release.editions + | to_entries[] + | { + source_tag: $release.source_tag, + release_tag: $release.release_tag, + short_tags: ( + $release.short_tags + | join(",") + ), + is_latest: ( + .value.latest + | tostring + ), + edition: .key + } + ] + } + ' "${REBUILD_CONFIG}")" + + BUILD_COUNT="$(jq '.include | length' <<< "${BUILD}")" + RETAG_COUNT="$(jq '.include | length' <<< "${RETAG}")" + + if [ "${BUILD_COUNT}" -eq 0 ]; then + echo "Build matrix is empty" >&2 + exit 1 + fi + + if [ "${RETAG_COUNT}" -eq 0 ]; then + echo "Retag matrix is empty" >&2 + exit 1 + fi + + echo "Build matrix contains ${BUILD_COUNT} entries:" + jq . <<< "${BUILD}" + + echo "Retag matrix contains ${RETAG_COUNT} entries:" + jq . <<< "${RETAG}" + + echo "build_matrix=${BUILD}" >> "${GITHUB_OUTPUT}" + echo "retag_matrix=${RETAG}" >> "${GITHUB_OUTPUT}" + + rebuild: + name: "Rebuild ${{ matrix.source_tag }}${{ matrix.edition }}" + needs: prepare + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: ${{ fromJSON(needs.prepare.outputs.build_matrix) }} + + steps: + - name: Checkout code + uses: actions/checkout@v7 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v4 + + - name: Login to Docker Hub + uses: docker/login-action@v4 + with: + username: ${{ secrets.DOCKER_HUB_USERNAME }} + password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} + + - name: Set up Node.js + uses: actions/setup-node@v6 + with: + node-version: "20" + + - name: Checkout tests + env: + GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} + TEST_TAG: ${{ matrix.test_tag }} + run: | + set -euo pipefail + + git clone \ + "https://${GITEA_TOKEN}@git.onlyoffice.com/ONLYOFFICE-QA/Dep.Tests.git" \ + dep-tests + + cd dep-tests + + if git checkout "refs/tags/${TEST_TAG}" 2>/dev/null; then + echo "Checked out test tag ${TEST_TAG}" + else + echo "Test tag ${TEST_TAG} was not found" >&2 + echo "Using fallback commit aa2d423f18" >&2 + git checkout aa2d423f18 + fi + + df -h + + - name: Initialize submodules + working-directory: dep-tests + env: + GITHUB_TOKEN: ${{ secrets.TOKEN }} + run: | + set -euo pipefail + + git config --global \ + url."https://${GITHUB_TOKEN}@github.com/".insteadOf \ + "https://github.com/" + + git submodule update \ + --init + + git config --global --unset url."https://${GITHUB_TOKEN}@github.com/".insteadOf + + - name: Install dependencies + working-directory: dep-tests/puppeteer/engine + run: | + df -h + npm install --silent --no-progress > /dev/null 2>&1 || { + echo "Dependency installation failed" + exit 1 + } + + - name: mv Dep.Tests under context + run: | + mv dep-tests ../ + + - name: Build secure-patched image + id: build + env: + SOURCE_TAG: ${{ matrix.source_tag }} + EDITION: ${{ matrix.edition }} + run: | + set -euo pipefail + + SOURCE_IMAGE="${COMPANY_NAME}/4testing-${PRODUCT_NAME}${EDITION}:${SOURCE_TAG}" + PATCHED_IMAGE="${COMPANY_NAME}/4testing-${PRODUCT_NAME}${EDITION}:${SOURCE_TAG}-secure" + + echo "Source image: ${SOURCE_IMAGE}" + echo "Patched image: ${PATCHED_IMAGE}" + + docker buildx imagetools inspect "${SOURCE_IMAGE}" + + docker buildx build \ + --platform "linux/amd64,linux/arm64" \ + --build-arg "SOURCE_IMAGE=${SOURCE_IMAGE}" \ + --tag "${PATCHED_IMAGE}" \ + --file Dockerfile.secure \ + --push \ + . + + echo "patched_image=${PATCHED_IMAGE}" >> "${GITHUB_OUTPUT}" + + - name: Return dep.tests + run: | + mv ../dep-tests ./ + + - name: Clean-up + run: | + set -euo pipefail + + docker rm --force documentserver 2>/dev/null || true + docker buildx prune -af + docker image prune -af + docker builder prune -af + + - name: Pull test image + env: + IMAGE: ${{ steps.build.outputs.patched_image }} + run: | + set -euo pipefail + + for attempt in $(seq 1 30); do + if docker pull "${IMAGE}"; then + echo "Image pulled successfully" + exit 0 + fi + + if [ "${attempt}" -eq 30 ]; then + echo "Image was not available after 5 minutes" >&2 + exit 1 + fi + + echo "Image is not available yet, retrying in 10 seconds" + sleep 10 + done + + - name: Start test container + env: + IMAGE: ${{ steps.build.outputs.patched_image }} + run: | + set -euo pipefail + + docker run \ + --detach \ + --publish 80:80 \ + --name documentserver \ + "${IMAGE}" + + - name: Wait for healthcheck + run: | + set -euo pipefail + + for attempt in $(seq 1 30); do + RESULT="$( + curl \ + --fail \ + --silent \ + --show-error \ + http://localhost/healthcheck \ + 2>/dev/null \ + || true + )" + + echo "Healthcheck response: ${RESULT}" + + if [ "${RESULT}" = "true" ]; then + echo "Healthcheck passed" + exit 0 + fi + + if [ "${attempt}" -eq 30 ]; then + echo "Healthcheck failed after 5 minutes" >&2 + docker logs documentserver || true + exit 1 + fi + + sleep 10 + done + + - name: Start example service + run: | + set -euo pipefail + + docker exec documentserver \ + supervisorctl start ds:example + + for attempt in $(seq 1 30); do + if curl \ + --fail \ + --silent \ + --show-error \ + http://localhost/example/ \ + >/dev/null 2>&1; then + echo "Example service is ready" + exit 0 + fi + + if [ "${attempt}" -eq 30 ]; then + echo "Example service did not become ready" >&2 + docker logs documentserver || true + exit 1 + fi + + sleep 5 + done + + - name: Start Xvfb + run: | + set -euo pipefail + + Xvfb :99 \ + -screen 0 1920x1080x24 \ + >"${RUNNER_TEMP}/xvfb.log" \ + 2>&1 & + + sleep 60 + echo "DISPLAY=:99" >> "${GITHUB_ENV}" + + - name: Install Chrome + uses: browser-actions/setup-chrome@v2 + with: + chrome-version: "120" + + - name: Run tests with retryes + uses: nick-fields/retry@v4 + with: + timeout_minutes: 20 + max_attempts: 3 + retry_wait_seconds: 120 + command: | + set -uo pipefail + cd dep-tests/puppeteer/engine + OUTPUT_FILE="../test-output.log" + RESULT=0 + + : > "${OUTPUT_FILE}" + + tests=( + "tests/desktop/word/smoke/open-close-file-tab.js" + "tests/desktop/word/smoke/check_search_while_drawing.js" + "tests/desktop/spreadsheets/smoke/check_replace_all.js" + "tests/desktop/spreadsheets/smoke/data_entry_use_tab_button.js" + "tests/desktop/pdf/smoke/check_find_for_a_specific_pdf.js" + "tests/desktop/pdf/smoke/check_undo_after_add_text_comment.js" + ) + + for test in "${tests[@]}"; do + echo "Running ${test}" \ + | tee -a "${OUTPUT_FILE}" + + python3 run.py \ + --config config_chrome_linux.json \ + "${test}" \ + --config_params \ + "runOptions.disableAnimation=true" \ + "testOptions.url=http://localhost/example" \ + 'puppeteerOptions.browserArgs.args=["--no-sandbox","--disable-setuid-sandbox"]' \ + 2>&1 \ + | tee -a "${OUTPUT_FILE}" + + TEST_RESULT="${PIPESTATUS[0]}" + + if [ "${TEST_RESULT}" -ne 0 ]; then + echo "Test failed with exit code ${TEST_RESULT}: ${test}" \ + | tee -a "${OUTPUT_FILE}" + RESULT=1 + fi + done + + exit "${RESULT}" + + - name: Print test output + if: always() + run: | + if [ -f dep-tests/puppeteer/test-output.log ]; then + cat dep-tests/puppeteer/test-output.log + else + echo "Test output file was not created" + fi + + - name: Print test reports + if: always() + run: | + if [ ! -d dep-tests/puppeteer/out ]; then + echo "Test report directory was not created" + exit 0 + fi + + find dep-tests/puppeteer/out \ + -type f \ + -name "report*.html" \ + -print0 \ + | sort -z \ + | while IFS= read -r -d "" report; do + echo "=== ${report} ===" + cat "${report}" + echo + done + + - name: Upload test artifacts + if: ${{ false }} + uses: actions/upload-artifact@v4 + with: + name: smoke-tests-${{ matrix.source_tag }}${{ matrix.edition }} + path: | + dep-tests/puppeteer/test-output.log + dep-tests/puppeteer/out/ + if-no-files-found: warn + retention-days: 14 + + - name: Remove test container + if: ${{ false }} + run: | + docker rm \ + --force \ + documentserver \ + 2>/dev/null \ + || true + + retag: + name: "Retag ${{ matrix.release_tag }}${{ matrix.edition }}" + needs: + - prepare + - rebuild + + if: >- + ${{ + github.event_name != 'workflow_dispatch' + || !inputs.dry_run + }} + + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: ${{ fromJSON(needs.prepare.outputs.retag_matrix) }} + + steps: + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v4 + + - name: Login to Docker Hub + uses: docker/login-action@v4 + with: + username: ${{ secrets.DOCKER_HUB_USERNAME }} + password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} + + - name: Create multi-arch manifest with release tags + uses: nick-fields/retry@v4 + env: + SOURCE_TAG: ${{ matrix.source_tag }} + RELEASE_TAG: ${{ matrix.release_tag }} + SHORT_TAGS: ${{ matrix.short_tags }} + IS_LATEST: ${{ matrix.is_latest }} + EDITION: ${{ matrix.edition }} + PRODUCT_NAME: ${{ env.PRODUCT_NAME }} + with: + timeout_minutes: 20 + max_attempts: 3 + retry_wait_seconds: 60 + command: | + set -euo pipefail + + RELEASE_IMAGE="${COMPANY_NAME}/${PRODUCT_NAME}${EDITION}" + SRC_IMAGE="${COMPANY_NAME}/4testing-${PRODUCT_NAME}${EDITION}:${SOURCE_TAG}-secure" + + echo "Source image: ${SRC_IMAGE}" + echo "Release image: ${RELEASE_IMAGE}" + echo "Release tag: ${RELEASE_TAG}" + echo "Short tags: ${SHORT_TAGS}" + echo "Latest: ${IS_LATEST}" + + docker buildx imagetools inspect "${SRC_IMAGE}" + + PUSH_TAGS=( + --tag "${RELEASE_IMAGE}:${RELEASE_TAG}" + ) + + if [ -n "${SHORT_TAGS}" ]; then + IFS=',' read -r -a SHORTS <<< "${SHORT_TAGS}" + + for tag in "${SHORTS[@]}"; do + if [ -n "${tag}" ]; then + PUSH_TAGS+=( + --tag "${RELEASE_IMAGE}:${tag}" + ) + fi + done + fi + + if [ "${IS_LATEST}" = "true" ]; then + PUSH_TAGS+=( + --tag "${RELEASE_IMAGE}:latest" + ) + fi + + docker buildx imagetools create \ + "${PUSH_TAGS[@]}" \ + "${SRC_IMAGE}" + + docker buildx imagetools inspect \ + "${RELEASE_IMAGE}:${RELEASE_TAG}" diff --git a/.github/workflows/stable-build.yml b/.github/workflows/stable-build.yml index 8f08461..bb2e507 100644 --- a/.github/workflows/stable-build.yml +++ b/.github/workflows/stable-build.yml @@ -31,9 +31,9 @@ on: env: COMPANY_NAME: "onlyoffice" PRODUCT_NAME: "documentserver" - VERSION: ${{ github.event.inputs.tag }} - RELEASE_NUMBER: ${{ github.event.inputs.release_number }} - LATEST: ${{ github.event.inputs.latest }} + VERSION: ${{ inputs.tag }} + RELEASE_NUMBER: ${{ inputs.release_number }} + LATEST: ${{ inputs.latest }} jobs: setup: @@ -41,26 +41,32 @@ jobs: runs-on: ubuntu-latest outputs: editions: ${{ steps.set.outputs.editions }} - editions_ucs: ${{ steps.set.outputs.editions_ucs }} steps: - name: Build editions list id: set shell: bash env: - INPUT_C: ${{ github.event.inputs.community }} - INPUT_E: ${{ github.event.inputs.enterprise }} + INPUT_C: ${{ inputs.community }} + INPUT_E: ${{ inputs.enterprise }} run: | - set -eux - editions=$(jq -nc \ - --argjson c "$INPUT_C" \ - --argjson e "$INPUT_E" \ - '[ if $c then "" else empty end, - if $e then ("-ee", "-de") else empty end ]') - editions_ucs=$(echo "$editions" | jq -c 'map(select(. != "-de"))') - echo "editions=$editions" >> "$GITHUB_OUTPUT" - echo "editions_ucs=$editions_ucs" >> "$GITHUB_OUTPUT" - echo "Matrix editions: $editions" - echo "Matrix editions (UCS): $editions_ucs" + set -euo pipefail + + editions="$(jq -nc \ + --argjson community "${INPUT_C}" \ + --argjson enterprise "${INPUT_E}" \ + '[ + if $community then "" else empty end, + if $enterprise then "-ee", "-de" else empty end + ]' + )" + + if [ "$(jq 'length' <<< "${editions}")" -eq 0 ]; then + echo "At least one edition must be selected" >&2 + exit 1 + fi + + echo "editions=${editions}" >> "${GITHUB_OUTPUT}" + echo "Matrix editions: ${editions}" build: name: "Release image: DocumentServer${{ matrix.edition }}" @@ -74,35 +80,33 @@ jobs: edition: ${{ fromJSON(needs.setup.outputs.editions) }} steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v7 - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - name: Login to Docker Hub - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: username: ${{ secrets.DOCKER_HUB_USERNAME }} password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} - name: Build documentserver-release env: - TARGET: ${{ matrix.images }} PRODUCT_EDITION: ${{ matrix.edition }} run: | - set -eux - TESTING_IMAGE=${COMPANY_NAME}/4testing-${PRODUCT_NAME}${PRODUCT_EDITION} - export PRODUCT_EDITION - export PULL_TAG=${VERSION} + set -eux + export SOURCE_IMAGE=${COMPANY_NAME}/4testing-${PRODUCT_NAME}${PRODUCT_EDITION}:${VERSION} + export RELEASE_IMAGE=${COMPANY_NAME}/${PRODUCT_NAME}${PRODUCT_EDITION} export TAG=${VERSION%.*}.${RELEASE_NUMBER} export SHORTER_TAG=${VERSION%.*} export SHORTEST_TAG=${VERSION%.*.*} - docker buildx bake --sbom=true --provenance=mode=max -f docker-bake.hcl "${TARGET}" --push - echo "DONE: Build success >> exit with 0" - exit 0 + PUSH_TAGS=( + -t "${RELEASE_IMAGE}:${TAG}" + -t "${RELEASE_IMAGE}:${SHORTER_TAG}" + -t "${RELEASE_IMAGE}:${SHORTEST_TAG}" + ) + if [ "${LATEST}" = "true" ]; then + PUSH_TAGS+=( "-t" "$RELEASE_IMAGE:latest" ) + fi + docker buildx imagetools create "${PUSH_TAGS[@]}" "${SOURCE_IMAGE}" shell: bash # Disable for now @@ -110,7 +114,7 @@ jobs: # https://github.com/peter-evans/dockerhub-description/issues/294 # #- name: Update Docker Hub README - # uses: peter-evans/dockerhub-description@v4 + # uses: peter-evans/dockerhub-description@v5 # with: # username: ${{ secrets.DOCKER_HUB_USERNAME }} # password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} @@ -123,26 +127,25 @@ jobs: if: ${{ github.event.inputs.enterprise == 'true' }} steps: - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: actions/checkout@v7 - name: Login to Docker Hub - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: username: ${{ secrets.DOCKER_HUB_USERNAME_ENTERPRISE }} password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN_ENTERPRISE }} - - name: release 4enterpise - shell: bash - run: | - export TAG=${VERSION%.*}.${RELEASE_NUMBER} - docker buildx imagetools create --tag onlyoffice4enterprise/documentserver-ee:${TAG} \ - onlyoffice/4testing-documentserver-ee:${VERSION} + - name: Create release image tag + uses: nick-fields/retry@v4 + with: + timeout_minutes: 10 + max_attempts: 3 + retry_wait_seconds: 60 + command: | + export TAG=${VERSION%.*}.${RELEASE_NUMBER} + docker buildx imagetools create \ + --tag onlyoffice4enterprise/documentserver-ee:${TAG} \ + onlyoffice/4testing-documentserver-ee:${VERSION} build-nonexample: name: "Release image: DocumentServer${{ matrix.edition }}-nonExample" @@ -156,16 +159,16 @@ jobs: edition: ${{ fromJSON(needs.setup.outputs.editions) }} steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + uses: docker/setup-qemu-action@v4 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v4 - name: Login to Docker Hub - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: username: ${{ secrets.DOCKER_HUB_USERNAME }} password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} @@ -181,41 +184,298 @@ jobs: docker buildx bake --sbom=true --provenance=mode=max -f docker-bake.hcl "${TARGET}" --push shell: bash - build-ucs-ubuntu20: - name: "Release image: DocumentServer${{ matrix.edition }}-ucs" + update-secure-rebuild-config: + name: "Update secure-rebuild config" runs-on: ubuntu-latest - if: ${{ needs.setup.outputs.editions_ucs != '[]' }} - needs: [setup] - strategy: - fail-fast: false - matrix: - edition: ${{ fromJSON(needs.setup.outputs.editions_ucs) }} - platform: ["amd64"] + permissions: + contents: write + pull-requests: write + needs: [build] steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to Docker Hub - uses: docker/login-action@v3 + - uses: actions/checkout@v7 with: - username: ${{ secrets.DOCKER_HUB_USERNAME }} - password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} + fetch-depth: 0 - - name: build UCS + - name: Compute release metadata + id: release env: - PACKAGE_BASEURL: ${{ secrets.REPO_BASEURL }} - PRODUCT_EDITION: ${{ matrix.edition }} - PLATFORM: linux/${{ matrix.platform }} + VERSION: ${{ inputs.tag }} + RELEASE_NUMBER: ${{ inputs.release_number }} run: | - set -eux - export DOCKERFILE=Dockerfile - export BASE_VERSION=20.04 - export PG_VERSION=12 - export PACKAGE_SUFFIX= - export TAG=${VERSION%.*}.${RELEASE_NUMBER} - export PACKAGE_VERSION=$( echo ${VERSION} | sed -E 's/(.*)\./\1-/') - docker buildx bake --sbom=true --provenance=mode=max -f docker-bake.hcl documentserver-ucs --push - shell: bash + set -euo pipefail + + BASE="${VERSION%.*}" + + echo "source_tag=${VERSION}" >> "${GITHUB_OUTPUT}" + echo "release_tag=${BASE}.${RELEASE_NUMBER}" >> "${GITHUB_OUTPUT}" + + - name: Update JSON + env: + COMMUNITY: ${{ inputs.community }} + ENTERPRISE: ${{ inputs.enterprise }} + LATEST: ${{ inputs.latest }} + SOURCE_TAG: ${{ steps.release.outputs.source_tag }} + RELEASE_TAG: ${{ steps.release.outputs.release_tag }} + RELEASE_NUMBER: ${{ inputs.release_number }} + run: | + set -euo pipefail + + EDITIONS_JSON="$(jq -cn \ + --argjson community "${COMMUNITY}" \ + --argjson enterprise "${ENTERPRISE}" \ + --argjson latest "${LATEST}" \ + '{ + editions: ( + {} + + ( + if $community + then {"": {latest: $latest}} + else {} + end + ) + + ( + if $enterprise + then { + "-ee": {latest: $latest}, + "-de": {latest: $latest} + } + else {} + end + ) + ) + } + | .editions' + )" + + if [ "$(jq 'length' <<< "${EDITIONS_JSON}")" -eq 0 ]; then + echo "No editions were selected" >&2 + exit 1 + fi + + chmod +x .github/scripts/update-secure-rebuild-config.sh + + .github/scripts/update-secure-rebuild-config.sh \ + "${SOURCE_TAG}" \ + "${RELEASE_TAG}" \ + "${RELEASE_NUMBER}" \ + "${EDITIONS_JSON}" + + - name: Push branch and open PR in Gitea + env: + GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} + GITEA_URL: ${{ secrets.GITEA_URL }} + GITEA_REPO: ${{ secrets.GITEA_REPO }} + RELEASE_TAG: ${{ steps.release.outputs.release_tag }} + SOURCE_TAG: ${{ steps.release.outputs.source_tag }} + GITHUB_SOURCE_BRANCH: ${{ github.ref_name }} + run: | + set -euo pipefail + + CONFIG_FILE=".github/secure-rebuild.json" + BRANCH="secure-rebuild-config/${RELEASE_TAG}" + WORK_DIR="${RUNNER_TEMP}/gitea-repo" + RESPONSE_FILE="$(mktemp)" + + cleanup() { + rm -f "${RESPONSE_FILE}" + rm -rf "${WORK_DIR}" + } + + trap cleanup EXIT + + if [ ! -f "${CONFIG_FILE}" ]; then + echo "Config file was not found: ${CONFIG_FILE}" >&2 + exit 1 + fi + + if [ -z "${GITEA_TOKEN}" ]; then + echo "GITEA_TOKEN is empty" >&2 + exit 1 + fi + + if [ -z "${GITEA_URL}" ]; then + echo "GITEA_URL is empty" >&2 + exit 1 + fi + + if [ -z "${GITEA_REPO}" ]; then + echo "GITEA_REPO is empty" >&2 + exit 1 + fi + + GITEA_URL="${GITEA_URL%/}" + GITEA_HOST="${GITEA_URL#https://}" + GITEA_HOST="${GITEA_HOST#http://}" + + AUTHENTICATED_REPO_URL="https://x-token:${GITEA_TOKEN}@${GITEA_HOST}/${GITEA_REPO}.git" + PUBLIC_REPO_URL="${GITEA_URL}/${GITEA_REPO}.git" + API_URL="${GITEA_URL}/api/v1/repos/${GITEA_REPO}/pulls" + + BASE_BRANCH="${GITHUB_SOURCE_BRANCH#refs/heads/}" + + if [ -z "${BASE_BRANCH}" ]; then + BASE_BRANCH="master" + fi + + if git ls-remote \ + --exit-code \ + --heads \ + "${AUTHENTICATED_REPO_URL}" \ + "refs/heads/${BASE_BRANCH}" \ + >/dev/null 2>&1; then + echo "Using matching Gitea branch as PR base: ${BASE_BRANCH}" + else + echo "Branch ${BASE_BRANCH} does not exist in Gitea" + BASE_BRANCH="master" + echo "Using fallback PR base: ${BASE_BRANCH}" + fi + + if ! git ls-remote \ + --exit-code \ + --heads \ + "${AUTHENTICATED_REPO_URL}" \ + "refs/heads/${BASE_BRANCH}" \ + >/dev/null 2>&1; then + echo "Base branch does not exist in Gitea: ${BASE_BRANCH}" >&2 + exit 1 + fi + + rm -rf "${WORK_DIR}" + + git clone \ + --branch "${BASE_BRANCH}" \ + --single-branch \ + "${AUTHENTICATED_REPO_URL}" \ + "${WORK_DIR}" + + cd "${WORK_DIR}" + + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + git checkout -B "${BRANCH}" + + mkdir -p "$(dirname "${CONFIG_FILE}")" + + cp \ + "${GITHUB_WORKSPACE}/${CONFIG_FILE}" \ + "${CONFIG_FILE}" + + jq -e . "${CONFIG_FILE}" >/dev/null + + git add "${CONFIG_FILE}" + + if git diff --cached --quiet; then + echo "The secure rebuild config is already up to date" + exit 0 + fi + + git commit \ + -m "chore: update secure-rebuild config for ${RELEASE_TAG}" + + git push \ + origin \ + "HEAD:refs/heads/${BRANCH}" \ + --force + + HTTP_CODE="$(curl \ + --silent \ + --show-error \ + --output "${RESPONSE_FILE}" \ + --write-out "%{http_code}" \ + --get "${API_URL}" \ + --header "Accept: application/json" \ + --header "Authorization: token ${GITEA_TOKEN}" \ + --data-urlencode "state=open" \ + --data-urlencode "limit=50" + )" + + if [ "${HTTP_CODE}" -lt 200 ] || + [ "${HTTP_CODE}" -ge 300 ]; then + echo "Failed to query existing pull requests. HTTP status: ${HTTP_CODE}" >&2 + jq . "${RESPONSE_FILE}" >&2 || cat "${RESPONSE_FILE}" >&2 + exit 1 + fi + + EXISTING_PR_URL="$(jq -r \ + --arg branch "${BRANCH}" \ + --arg base "${BASE_BRANCH}" \ + ' + map( + select( + .head.ref == $branch + and .base.ref == $base + ) + ) + | first + | .html_url // empty + ' "${RESPONSE_FILE}" + )" + + if [ -n "${EXISTING_PR_URL}" ]; then + echo "Pull request already exists: ${EXISTING_PR_URL}" + exit 0 + fi + + PAYLOAD="$(jq -n \ + --arg title "chore: update secure-rebuild config for ${RELEASE_TAG}" \ + --arg head "${BRANCH}" \ + --arg base "${BASE_BRANCH}" \ + --arg body "Auto-generated after release of ${RELEASE_TAG} from source image tag ${SOURCE_TAG}." \ + '{ + title: $title, + head: $head, + base: $base, + body: $body + }' + )" + + HTTP_CODE="$(curl \ + --silent \ + --show-error \ + --output "${RESPONSE_FILE}" \ + --write-out "%{http_code}" \ + --request POST "${API_URL}" \ + --header "Accept: application/json" \ + --header "Content-Type: application/json" \ + --header "Authorization: token ${GITEA_TOKEN}" \ + --data "${PAYLOAD}" + )" + + if [ "${HTTP_CODE}" -lt 200 ] || + [ "${HTTP_CODE}" -ge 300 ]; then + echo "Failed to create pull request. HTTP status: ${HTTP_CODE}" >&2 + jq . "${RESPONSE_FILE}" >&2 || cat "${RESPONSE_FILE}" >&2 + exit 1 + fi + + PR_NUMBER="$(jq -r '.number // empty' "${RESPONSE_FILE}")" + PR_URL="$(jq -r '.html_url // empty' "${RESPONSE_FILE}")" + + echo "Pull request created: #${PR_NUMBER}" + echo "Pull request base branch: ${BASE_BRANCH}" + + if [ -n "${PR_URL}" ]; then + echo "Pull request URL: ${PR_URL}" + else + echo "Repository URL: ${PUBLIC_REPO_URL}" + fi + + # The same step for GITHUB. + # Uncomment if need to open PR in github repository. + - name: Open PR Github + if: ${{ false }} + uses: peter-evans/create-pull-request@v6 + with: + token: ${{ secrets.GITHUB_TOKEN }} + branch: "secure-rebuild-config/${{ steps.release.outputs.release_tag }}" + base: ${{ github.ref_name }} + commit-message: "chore: update secure-rebuild config for ${{ steps.release.outputs.release_tag }}" + title: "Update secure-rebuild config for ${{ steps.release.outputs.release_tag }}" + body: | + Auto-generated after release of `${{ steps.release.outputs.release_tag }}` + (source 4testing tag: `${{ steps.release.outputs.source_tag }}`, + release number: `${{ github.event.inputs.release_number }}`). + + Please review and merge so next scheduled secure-rebuild picks up the new source. + delete-branch: true diff --git a/Dockerfile.secure b/Dockerfile.secure new file mode 100644 index 0000000..42e7f53 --- /dev/null +++ b/Dockerfile.secure @@ -0,0 +1,16 @@ +ARG SOURCE_IMAGE +FROM ${SOURCE_IMAGE} + +ENV DEBIAN_FRONTEND=noninteractive +ENV ACCEPT_EULA=Y + +RUN set -eux; \ + printf '#!/bin/sh\nexit 101\n' > /usr/sbin/policy-rc.d; \ + chmod +x /usr/sbin/policy-rc.d; \ + apt-get update; \ + apt-get -o Dpkg::Options::="--force-confold" \ + -o Dpkg::Options::="--force-confdef" \ + -y upgrade; \ + rm -f /usr/sbin/policy-rc.d; \ + apt-get clean; \ + rm -rf /var/lib/apt/lists/* diff --git a/production.dockerfile b/production.dockerfile index 24c6569..172b3fb 100644 --- a/production.dockerfile +++ b/production.dockerfile @@ -2,19 +2,6 @@ ARG PULL_TAG=latest ARG COMPANY_NAME=onlyoffice ARG PRODUCT_EDITION= -### Rebuild arguments -ARG UCS_PREFIX= -ARG IMAGE=${COMPANY_NAME}/documentserver${PRODUCT_EDITION}${UCS_PREFIX}:${PULL_TAG} - -### Build main-release ### - -FROM ${COMPANY_NAME}/4testing-documentserver${PRODUCT_EDITION}:${PULL_TAG} as documentserver-stable - -### Rebuild stable images with secure updates -FROM ${IMAGE} as documentserver-stable-rebuild -RUN echo "This is rebuild" \ - && apt-get update -y \ - && apt-get upgrade -y ### Build nonexample ###