From a7e8b4a43f2b63b233ef7e7402566cd35b68374e Mon Sep 17 00:00:00 2001 From: Peter Smythe Date: Sun, 18 Jan 2026 10:21:27 +0200 Subject: [PATCH 01/30] Add GitHub Actions workflows for PR validation and multi-arch image publishing. --- .../{docker.yml => pr-validation.yml} | 0 .github/workflows/publish.yml | 359 ++++++++++++++++++ 2 files changed, 359 insertions(+) rename .github/workflows/{docker.yml => pr-validation.yml} (100%) create mode 100644 .github/workflows/publish.yml diff --git a/.github/workflows/docker.yml b/.github/workflows/pr-validation.yml similarity index 100% rename from .github/workflows/docker.yml rename to .github/workflows/pr-validation.yml diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 00000000..37120c12 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,359 @@ +name: Build and Publish Multi-Arch GeoServer Images + +on: + workflow_dispatch: + inputs: + version: + description: 'GeoServer version (e.g., 2.28.1, 2.27-SNAPSHOT, 3.0-RC)' + required: true + type: string + build_number: + description: 'Build number (optional, defaults to "github")' + required: false + type: string + default: 'github' + +env: + MAIN_VERSION: "3.0" # Update when main branch changes + STABLE_VERSION: "2.28" + MAINTENANCE_VERSION: "2.27" + +jobs: + # ============================================================ + # JOB 1: Prepare build parameters + # ============================================================ + prepare: + name: Prepare Build Parameters + runs-on: ubuntu-latest + outputs: + major: ${{ steps.parse.outputs.major }} + minor: ${{ steps.parse.outputs.minor }} + base_image: ${{ steps.parse.outputs.base_image }} + branch: ${{ steps.parse.outputs.branch }} + is_nightly: ${{ steps.parse.outputs.is_nightly }} + war_url: ${{ steps.parse.outputs.war_url }} + stable_plugin_url: ${{ steps.parse.outputs.stable_plugin_url }} + community_plugin_url: ${{ steps.parse.outputs.community_plugin_url }} + primary_tag: ${{ steps.parse.outputs.primary_tag }} + additional_tags: ${{ steps.parse.outputs.additional_tags }} + + steps: + - name: Parse Version and Determine Build Parameters + id: parse + run: | + VERSION="${{ inputs.version }}" + BUILD="${{ inputs.build_number }}" + + echo "Parsing version: $VERSION" + + # Extract major.minor version + if [[ $VERSION =~ ^([0-9]+)\.([0-9]+) ]]; then + MAJOR="${BASH_REMATCH[1]}" + MINOR="${BASH_REMATCH[2]}" + echo "major=$MAJOR" >> $GITHUB_OUTPUT + echo "minor=$MINOR" >> $GITHUB_OUTPUT + else + echo "ERROR: Unable to parse version $VERSION" + exit 1 + fi + + # Determine base image based on version + if [[ "$VERSION" == "3"* ]]; then + BASE_IMAGE="tomcat:11.0-jdk21-temurin-noble" + elif [[ "$VERSION" == "2.28"* ]]; then + BASE_IMAGE="tomcat:9.0-jdk21-temurin-noble" + elif [[ "$VERSION" == "2.27"* ]] || [[ "$VERSION" == "2.26"* ]]; then + BASE_IMAGE="tomcat:9.0-jdk17-temurin-noble" + else + BASE_IMAGE="tomcat:9.0-jdk11-temurin-noble" + fi + echo "base_image=$BASE_IMAGE" >> $GITHUB_OUTPUT + + # Determine branch and tag based on version pattern + if [[ "$VERSION" == *"-M"* ]]; then + # Milestone release (e.g., 2.28-M0) + BRANCH="$VERSION" + PRIMARY_TAG="$VERSION" + IS_NIGHTLY="true" + elif [[ "$VERSION" == *"-RC"* ]]; then + # Release candidate (e.g., 2.28-RC) + BRANCH="$VERSION" + PRIMARY_TAG="$VERSION" + IS_NIGHTLY="true" + elif [[ "$VERSION" == "${{ env.MAIN_VERSION }}"* ]]; then + # Main branch (e.g., 3.0-SNAPSHOT or 3.0.0) + if [[ "$VERSION" == *"-SNAPSHOT"* ]]; then + BRANCH="main" + PRIMARY_TAG="${{ env.MAIN_VERSION }}.x" + IS_NIGHTLY="true" + else + BRANCH="main" + PRIMARY_TAG="$VERSION" + IS_NIGHTLY="false" + fi + else + # Stable or maintenance branch + if [[ "$VERSION" == *"-SNAPSHOT"* ]]; then + BRANCH="${MAJOR}.${MINOR}.x" + PRIMARY_TAG="${MAJOR}.${MINOR}.x" + IS_NIGHTLY="true" + else + BRANCH="${MAJOR}.${MINOR}.x" + PRIMARY_TAG="$VERSION" + IS_NIGHTLY="false" + fi + fi + + echo "branch=$BRANCH" >> $GITHUB_OUTPUT + echo "is_nightly=$IS_NIGHTLY" >> $GITHUB_OUTPUT + echo "primary_tag=$PRIMARY_TAG" >> $GITHUB_OUTPUT + + # Determine download URLs + if [[ "$IS_NIGHTLY" == "true" ]]; then + WAR_URL="https://build.geoserver.org/geoserver/$BRANCH/geoserver-$BRANCH-latest-war.zip" + STABLE_PLUGIN_URL="https://build.geoserver.org/geoserver/$BRANCH/ext-latest" + COMMUNITY_PLUGIN_URL="https://build.geoserver.org/geoserver/$BRANCH/community-latest" + else + WAR_URL="https://downloads.sourceforge.net/project/geoserver/GeoServer/$VERSION/geoserver-$VERSION-war.zip" + STABLE_PLUGIN_URL="" # TODO: fix url + COMMUNITY_PLUGIN_URL="" + fi + + echo "war_url=$WAR_URL" >> $GITHUB_OUTPUT + echo "stable_plugin_url=$STABLE_PLUGIN_URL" >> $GITHUB_OUTPUT + echo "community_plugin_url=$COMMUNITY_PLUGIN_URL" >> $GITHUB_OUTPUT + + # Determine additional tags based on version pattern + ADDITIONAL_TAGS="" + + # Add series-latest tag (e.g., 2.28-latest) + if [[ "$IS_NIGHTLY" == "false" ]]; then + ADDITIONAL_TAGS="${MAJOR}.${MINOR}-latest" + fi + + # Add semantic tags for main/stable/maintenance releases + if [[ "$VERSION" == "${MAIN_VERSION}."* ]] && [[ "$IS_NIGHTLY" == "false" ]]; then + ADDITIONAL_TAGS="$ADDITIONAL_TAGS,latest" + elif [[ "$VERSION" == "${STABLE_VERSION}."* ]] && [[ "$IS_NIGHTLY" == "false" ]]; then + ADDITIONAL_TAGS="$ADDITIONAL_TAGS,stable-latest" + elif [[ "$VERSION" == "${MAINTENANCE_VERSION}."* ]] && [[ "$IS_NIGHTLY" == "false" ]]; then + ADDITIONAL_TAGS="$ADDITIONAL_TAGS,maintenance-latest" + fi + + # Add nightly tags for snapshot builds + if [[ "$VERSION" == "${MAIN_VERSION}-SNAPSHOT" ]]; then + ADDITIONAL_TAGS="$ADDITIONAL_TAGS,latest" + elif [[ "$VERSION" == "${STABLE_VERSION}-SNAPSHOT" ]]; then + ADDITIONAL_TAGS="$ADDITIONAL_TAGS,stable-nightly" + elif [[ "$VERSION" == "${MAINTENANCE_VERSION}-SNAPSHOT" ]]; then + ADDITIONAL_TAGS="$ADDITIONAL_TAGS,maintenance-nightly" + fi + + # Clean up leading comma + ADDITIONAL_TAGS=$(echo "$ADDITIONAL_TAGS" | sed 's/^,//') + + echo "additional_tags=$ADDITIONAL_TAGS" >> $GITHUB_OUTPUT + + echo "============================================" + echo "Build Configuration:" + echo " Version: $VERSION" + echo " Branch: $BRANCH" + echo " Base Image: $BASE_IMAGE" + echo " Is Nightly: $IS_NIGHTLY" + echo " Primary Tag: $PRIMARY_TAG" + echo " Additional Tags: $ADDITIONAL_TAGS" + echo " WAR URL: $WAR_URL" + echo "============================================" + + # ============================================================ + # JOB 2: Build images on native runners (matrix) + # ============================================================ + build: + name: Build ${{ matrix.platform }} ${{ matrix.gdal && 'with GDAL' || 'without GDAL' }} + runs-on: ${{ matrix.runner }} + needs: prepare + strategy: + fail-fast: false + matrix: + include: + # AMD64 builds + - platform: linux/amd64 + runner: ubuntu-latest + arch: amd64 + gdal: false + - platform: linux/amd64 + runner: ubuntu-latest + arch: amd64 + gdal: true + # ARM64 builds + - platform: linux/arm64 + runner: ubuntu-24.04-arm + arch: arm64 + gdal: false + - platform: linux/arm64 + runner: ubuntu-24.04-arm + arch: arm64 + gdal: true + + steps: + - name: Checkout repository + 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 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Download GeoServer WAR + run: | + mkdir -p geoserver + cd geoserver + wget -c "${{ needs.prepare.outputs.war_url }}" + + - name: Build and export digest + id: build + uses: docker/build-push-action@v5 + with: + context: . + platforms: ${{ matrix.platform }} + build-args: | + GS_VERSION=${{ inputs.version }} + GS_BUILD=${{ inputs.build_number }} + BUILD_GDAL=${{ matrix.gdal }} + GEOSERVER_BASE_IMAGE=${{ needs.prepare.outputs.base_image }} + ${{ needs.prepare.outputs.is_nightly == 'true' && format('WAR_ZIP_URL={0}', needs.prepare.outputs.war_url) || '' }} + ${{ needs.prepare.outputs.is_nightly == 'true' && format('STABLE_PLUGIN_URL={0}', needs.prepare.outputs.stable_plugin_url) || '' }} + ${{ needs.prepare.outputs.is_nightly == 'true' && format('COMMUNITY_PLUGIN_URL={0}', needs.prepare.outputs.community_plugin_url) || '' }} + outputs: type=image,name=petersmythe/geoserver-TEST,push-by-digest=true,name-canonical=true,push=true + + - name: Export digest + run: | + mkdir -p /tmp/digests + digest="${{ steps.build.outputs.digest }}" + touch "/tmp/digests/${digest#sha256:}" + + - name: Upload digest + uses: actions/upload-artifact@v4 + with: + name: digests-${{ matrix.arch }}-${{ matrix.gdal && 'gdal' || 'nogdal' }} + path: /tmp/digests/* + if-no-files-found: error + retention-days: 1 + + # ============================================================ + # JOB 3: Merge digests into multi-arch manifests + # ============================================================ + merge: + name: Create Multi-Arch Manifests + runs-on: ubuntu-latest + needs: + - prepare + - build + + steps: + - name: Download all digests (no GDAL) + uses: actions/download-artifact@v4 + with: + pattern: digests-*-nogdal + merge-multiple: true + path: /tmp/digests-nogdal + + - name: Download all digests (with GDAL) + uses: actions/download-artifact@v4 + with: + pattern: digests-*-gdal + merge-multiple: true + path: /tmp/digests-gdal + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Create manifest list and push (no GDAL) + working-directory: /tmp/digests-nogdal + run: | + PRIMARY_TAG="${{ needs.prepare.outputs.primary_tag }}" + ADDITIONAL_TAGS="${{ needs.prepare.outputs.additional_tags }}" + + # Build tag list + TAGS="petersmythe/geoserver-TEST:$PRIMARY_TAG" + + if [ -n "$ADDITIONAL_TAGS" ]; then + IFS=',' read -ra TAG_ARRAY <<< "$ADDITIONAL_TAGS" + for tag in "${TAG_ARRAY[@]}"; do + TAGS="$TAGS petersmythe/geoserver-TEST:$tag" + done + fi + + # Create manifest + docker buildx imagetools create $(printf -- '-t %s ' $TAGS) \ + $(printf 'petersmythe/geoserver-TEST@sha256:%s ' *) + + echo "Created manifest for tags: $TAGS" + + - name: Create manifest list and push (with GDAL) + working-directory: /tmp/digests-gdal + run: | + PRIMARY_TAG="${{ needs.prepare.outputs.primary_tag }}-gdal" + ADDITIONAL_TAGS="${{ needs.prepare.outputs.additional_tags }}" + + # Build tag list with -gdal suffix + TAGS="petersmythe/geoserver-TEST:$PRIMARY_TAG" + + if [ -n "$ADDITIONAL_TAGS" ]; then + IFS=',' read -ra TAG_ARRAY <<< "$ADDITIONAL_TAGS" + for tag in "${TAG_ARRAY[@]}"; do + TAGS="$TAGS petersmythe/geoserver-TEST:$tag-gdal" + done + fi + + # Create manifest + docker buildx imagetools create $(printf -- '-t %s ' $TAGS) \ + $(printf 'petersmythe/geoserver-TEST@sha256:%s ' *) + + echo "Created manifest for tags: $TAGS" + + - name: Inspect manifests + run: | + docker buildx imagetools inspect petersmythe/geoserver-TEST:${{ needs.prepare.outputs.primary_tag }} + docker buildx imagetools inspect petersmythe/geoserver-TEST:${{ needs.prepare.outputs.primary_tag }}-gdal + + # ============================================================ + # JOB 4: FUTURE - Publish to OSGeo repo (dual publishing) + # ============================================================ + publish-osgeo: + name: Publish to OSGeo Registry (Future) + runs-on: ubuntu-latest + needs: merge + if: false # Enable when ready to dual-publish + + steps: + - name: Login to OSGeo Registry + uses: docker/login-action@v3 + with: + registry: geoserver-docker.osgeo.org + username: ${{ secrets.OSGEO_USERNAME }} + password: ${{ secrets.OSGEO_TOKEN }} + + - name: Tag and push to OSGeo + run: | + # Pull from petersmythe, retag, push to OSGeo + docker pull petersmythe/geoserver-TEST:${{ needs.prepare.outputs.primary_tag }} + docker tag petersmythe/geoserver-TEST:${{ needs.prepare.outputs.primary_tag }} \ + geoserver-docker.osgeo.org/geoserver:${{ needs.prepare.outputs.primary_tag }} + docker push geoserver-docker.osgeo.org/geoserver:${{ needs.prepare.outputs.primary_tag }} + + # Same for GDAL variant + docker pull petersmythe/geoserver-TEST:${{ needs.prepare.outputs.primary_tag }}-gdal + docker tag petersmythe/geoserver-TEST:${{ needs.prepare.outputs.primary_tag }}-gdal \ + geoserver-docker.osgeo.org/geoserver:${{ needs.prepare.outputs.primary_tag }}-gdal + docker push geoserver-docker.osgeo.org/geoserver:${{ needs.prepare.outputs.primary_tag }}-gdal From 79fe930472a8673e8e4b03c1e196919e01dd5ff7 Mon Sep 17 00:00:00 2001 From: Peter Smythe Date: Sun, 18 Jan 2026 10:46:57 +0200 Subject: [PATCH 02/30] CI: run PR validation on pull_request events --- .github/workflows/pages.yml | 2 +- .github/workflows/pr-validation.yml | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index a822f69c..1f20485c 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -1,4 +1,4 @@ -name: Pages +name: Build docs and deploy to GitHub Pages on: push: paths: diff --git a/.github/workflows/pr-validation.yml b/.github/workflows/pr-validation.yml index 06035cf2..e4447e26 100644 --- a/.github/workflows/pr-validation.yml +++ b/.github/workflows/pr-validation.yml @@ -1,5 +1,7 @@ -name: build +name: Validate a PR on push to master on: + pull_request: + types: [opened, synchronize, reopened] push: branches: - master From 1f6873421d7dcfb5473c60bb207e36d9c3d49392 Mon Sep 17 00:00:00 2001 From: Peter Smythe Date: Sun, 18 Jan 2026 10:51:40 +0200 Subject: [PATCH 03/30] ci: add temporary push trigger for ci/publish-test and fallback version for push runs --- .github/workflows/publish.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 37120c12..ee05db30 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -12,6 +12,9 @@ on: required: false type: string default: 'github' + push: + branches: + - ci/publish-test env: MAIN_VERSION: "3.0" # Update when main branch changes @@ -43,6 +46,13 @@ jobs: run: | VERSION="${{ inputs.version }}" BUILD="${{ inputs.build_number }}" + + # If triggered by a push (test), allow a sensible default so this can be run without workflow_dispatch inputs + if [ -z "$VERSION" ] && [ "${GITHUB_EVENT_NAME}" = "push" ]; then + VERSION="3.0-SNAPSHOT" + BUILD="${GITHUB_RUN_NUMBER:-github}" + echo "No version input (push trigger). Falling back to VERSION=$VERSION BUILD=$BUILD" + fi echo "Parsing version: $VERSION" From 3bfe13211a050fbdc81645066cef03dcc8a69265 Mon Sep 17 00:00:00 2001 From: Peter Smythe Date: Sun, 18 Jan 2026 11:00:02 +0200 Subject: [PATCH 04/30] ci: use lowercase image name (geoserver-test) to satisfy Docker naming rules --- .github/workflows/publish.yml | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index ee05db30..9a7e8d39 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -238,7 +238,7 @@ jobs: ${{ needs.prepare.outputs.is_nightly == 'true' && format('WAR_ZIP_URL={0}', needs.prepare.outputs.war_url) || '' }} ${{ needs.prepare.outputs.is_nightly == 'true' && format('STABLE_PLUGIN_URL={0}', needs.prepare.outputs.stable_plugin_url) || '' }} ${{ needs.prepare.outputs.is_nightly == 'true' && format('COMMUNITY_PLUGIN_URL={0}', needs.prepare.outputs.community_plugin_url) || '' }} - outputs: type=image,name=petersmythe/geoserver-TEST,push-by-digest=true,name-canonical=true,push=true + outputs: type=image,name=petersmythe/geoserver-test,push-by-digest=true,name-canonical=true,push=true - name: Export digest run: | @@ -295,18 +295,18 @@ jobs: ADDITIONAL_TAGS="${{ needs.prepare.outputs.additional_tags }}" # Build tag list - TAGS="petersmythe/geoserver-TEST:$PRIMARY_TAG" + TAGS="petersmythe/geoserver-test:$PRIMARY_TAG" if [ -n "$ADDITIONAL_TAGS" ]; then IFS=',' read -ra TAG_ARRAY <<< "$ADDITIONAL_TAGS" for tag in "${TAG_ARRAY[@]}"; do - TAGS="$TAGS petersmythe/geoserver-TEST:$tag" + TAGS="$TAGS petersmythe/geoserver-test:$tag" done fi # Create manifest docker buildx imagetools create $(printf -- '-t %s ' $TAGS) \ - $(printf 'petersmythe/geoserver-TEST@sha256:%s ' *) + $(printf 'petersmythe/geoserver-test@sha256:%s ' *) echo "Created manifest for tags: $TAGS" @@ -317,25 +317,25 @@ jobs: ADDITIONAL_TAGS="${{ needs.prepare.outputs.additional_tags }}" # Build tag list with -gdal suffix - TAGS="petersmythe/geoserver-TEST:$PRIMARY_TAG" + TAGS="petersmythe/geoserver-test:$PRIMARY_TAG" if [ -n "$ADDITIONAL_TAGS" ]; then IFS=',' read -ra TAG_ARRAY <<< "$ADDITIONAL_TAGS" for tag in "${TAG_ARRAY[@]}"; do - TAGS="$TAGS petersmythe/geoserver-TEST:$tag-gdal" + TAGS="$TAGS petersmythe/geoserver-test:$tag-gdal" done fi # Create manifest docker buildx imagetools create $(printf -- '-t %s ' $TAGS) \ - $(printf 'petersmythe/geoserver-TEST@sha256:%s ' *) + $(printf 'petersmythe/geoserver-test@sha256:%s ' *) echo "Created manifest for tags: $TAGS" - name: Inspect manifests run: | - docker buildx imagetools inspect petersmythe/geoserver-TEST:${{ needs.prepare.outputs.primary_tag }} - docker buildx imagetools inspect petersmythe/geoserver-TEST:${{ needs.prepare.outputs.primary_tag }}-gdal + docker buildx imagetools inspect petersmythe/geoserver-test:${{ needs.prepare.outputs.primary_tag }} + docker buildx imagetools inspect petersmythe/geoserver-test:${{ needs.prepare.outputs.primary_tag }}-gdal # ============================================================ # JOB 4: FUTURE - Publish to OSGeo repo (dual publishing) @@ -357,13 +357,13 @@ jobs: - name: Tag and push to OSGeo run: | # Pull from petersmythe, retag, push to OSGeo - docker pull petersmythe/geoserver-TEST:${{ needs.prepare.outputs.primary_tag }} - docker tag petersmythe/geoserver-TEST:${{ needs.prepare.outputs.primary_tag }} \ + docker pull petersmythe/geoserver-test:${{ needs.prepare.outputs.primary_tag }} + docker tag petersmythe/geoserver-test:${{ needs.prepare.outputs.primary_tag }} \ geoserver-docker.osgeo.org/geoserver:${{ needs.prepare.outputs.primary_tag }} docker push geoserver-docker.osgeo.org/geoserver:${{ needs.prepare.outputs.primary_tag }} # Same for GDAL variant - docker pull petersmythe/geoserver-TEST:${{ needs.prepare.outputs.primary_tag }}-gdal - docker tag petersmythe/geoserver-TEST:${{ needs.prepare.outputs.primary_tag }}-gdal \ + docker pull petersmythe/geoserver-test:${{ needs.prepare.outputs.primary_tag }}-gdal + docker tag petersmythe/geoserver-test:${{ needs.prepare.outputs.primary_tag }}-gdal \ geoserver-docker.osgeo.org/geoserver:${{ needs.prepare.outputs.primary_tag }}-gdal docker push geoserver-docker.osgeo.org/geoserver:${{ needs.prepare.outputs.primary_tag }}-gdal From 6f30e7a8eaba66d80b5acbcb7902ba0f42335f72 Mon Sep 17 00:00:00 2001 From: Peter Smythe Date: Sun, 18 Jan 2026 12:46:15 +0200 Subject: [PATCH 05/30] Check GEOSERVER_VERSION missing --- .github/workflows/pr-validation.yml | 14 +++--- .github/workflows/publish.yml | 10 ++-- install-extensions.sh | 76 ++++++++++++++++++++++++----- 3 files changed, 78 insertions(+), 22 deletions(-) diff --git a/.github/workflows/pr-validation.yml b/.github/workflows/pr-validation.yml index e4447e26..fdef9f50 100644 --- a/.github/workflows/pr-validation.yml +++ b/.github/workflows/pr-validation.yml @@ -8,15 +8,17 @@ on: jobs: build: - name: Build (${{ matrix.name }}) + name: Build (${{ matrix.display_name }}) runs-on: ubuntu-latest strategy: matrix: include: - - build_gdal: false - name: default - build_gdal: true - name: gdal + name: with-gdal + display_name: "with GDAL" + - build_gdal: false + name: without-gdal + display_name: "without GDAL" steps: - name: Checkout code uses: actions/checkout@v6 @@ -42,7 +44,7 @@ jobs: # Integration Tests - Start container and perform basic health checks - name: Start GeoServer container run: | - if [[ "${{ matrix.name }}" == "gdal" ]]; then + if [[ "${{ matrix.build_gdal }}" == "true" ]]; then # Start GDAL image with extension installation docker run -d --name geoserver-test-${{ matrix.name }} \ -p 8080:8080 \ @@ -117,7 +119,7 @@ jobs: - name: Test gdal specific functionality run: | - if [[ "${{ matrix.name }}" == "gdal" ]]; then + if [[ "${{ matrix.build_gdal }}" == "true" ]]; then echo "Testing GDAL-specific functionality..." # Check GDAL installation docker exec geoserver-test-${{ matrix.name }} which gdalinfo || echo "GDAL tools check" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 9a7e8d39..cefa30de 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -125,8 +125,8 @@ jobs: COMMUNITY_PLUGIN_URL="https://build.geoserver.org/geoserver/$BRANCH/community-latest" else WAR_URL="https://downloads.sourceforge.net/project/geoserver/GeoServer/$VERSION/geoserver-$VERSION-war.zip" - STABLE_PLUGIN_URL="" # TODO: fix url - COMMUNITY_PLUGIN_URL="" + STABLE_PLUGIN_URL="https://downloads.sourceforge.net/project/geoserver/GeoServer/${VERSION}/extensions" + COMMUNITY_PLUGIN_URL="https://build.geoserver.org/geoserver/${BRANCH}/community-latest" fi echo "war_url=$WAR_URL" >> $GITHUB_OUTPUT @@ -235,9 +235,9 @@ jobs: GS_BUILD=${{ inputs.build_number }} BUILD_GDAL=${{ matrix.gdal }} GEOSERVER_BASE_IMAGE=${{ needs.prepare.outputs.base_image }} - ${{ needs.prepare.outputs.is_nightly == 'true' && format('WAR_ZIP_URL={0}', needs.prepare.outputs.war_url) || '' }} - ${{ needs.prepare.outputs.is_nightly == 'true' && format('STABLE_PLUGIN_URL={0}', needs.prepare.outputs.stable_plugin_url) || '' }} - ${{ needs.prepare.outputs.is_nightly == 'true' && format('COMMUNITY_PLUGIN_URL={0}', needs.prepare.outputs.community_plugin_url) || '' }} + WAR_ZIP_URL=${{ needs.prepare.outputs.war_url }} + STABLE_PLUGIN_URL=${{ needs.prepare.outputs.stable_plugin_url }} + COMMUNITY_PLUGIN_URL=${{ needs.prepare.outputs.community_plugin_url }} outputs: type=image,name=petersmythe/geoserver-test,push-by-digest=true,name-canonical=true,push=true - name: Export digest diff --git a/install-extensions.sh b/install-extensions.sh index fea456bd..07d9bd6b 100644 --- a/install-extensions.sh +++ b/install-extensions.sh @@ -1,19 +1,63 @@ #!/bin/bash # Inspired by https://github.com/kartoza/docker-geoserver +# Helper: normalize a URL by stripping any trailing slash +normalize_url() { + echo "${1%/}" +} + +# If GEOSERVER_VERSION is not set, try to infer it from the plugin URLs +# e.g. https://build.geoserver.org/geoserver//ext-latest/ -> +if [ -z "${GEOSERVER_VERSION}" ]; then + if [ -n "${STABLE_PLUGIN_URL}" ]; then + VERSION=$(echo "${STABLE_PLUGIN_URL}" | sed -n 's#.*/geoserver/\([^/]*\)/.*#\1#p') + fi + if [ -z "${VERSION}" ] && [ -n "${COMMUNITY_PLUGIN_URL}" ]; then + VERSION=$(echo "${COMMUNITY_PLUGIN_URL}" | sed -n 's#.*/geoserver/\([^/]*\)/.*#\1#p') + fi + if [ -n "${VERSION}" ]; then + GEOSERVER_VERSION="${VERSION}" + echo "Inferred GEOSERVER_VERSION=${GEOSERVER_VERSION} from plugin URL" + else + echo "Warning: GEOSERVER_VERSION is not set and could not be inferred from plugin URLs" + fi +fi + function download_extension() { URL=$1 EXTENSION=$2 - DOWNLOAD_FILE="${ADDITIONAL_LIBS_DIR}geoserver-${GEOSERVER_VERSION}-${EXTENSION}-plugin.zip" + DOWNLOAD_DIR="${ADDITIONAL_LIBS_DIR%/}/" + DOWNLOAD_FILE="${DOWNLOAD_DIR}geoserver-${GEOSERVER_VERSION}-${EXTENSION}-plugin.zip" if [ -e "$DOWNLOAD_FILE" ]; then echo "$DOWNLOAD_FILE already exists. Skipping download." else if curl --output /dev/null --silent --head --fail "${URL}"; then echo -e "\nDownloading ${EXTENSION} extension from ${URL} to ${DOWNLOAD_FILE}" - wget --progress=bar:force:noscroll -c --no-check-certificate "${URL}" -O ${DOWNLOAD_FILE} - else + wget --progress=bar:force:noscroll -c --no-check-certificate "${URL}" -O "${DOWNLOAD_FILE}" + else echo "URL does not exist: ${URL}" + # Try to discover an actual plugin file at the base URL and use it + BASE_URL="${URL%/geoserver-*-${EXTENSION}-plugin.zip}" + if [ -n "${BASE_URL}" ]; then + echo "Attempting to discover plugin filename from ${BASE_URL}/" + LISTING=$(curl -s "${BASE_URL}/" || true) + FILE=$(echo "${LISTING}" | grep -oE "geoserver-[^/]*-${EXTENSION}-plugin.zip" | head -n 1 || true) + if [ -n "${FILE}" ]; then + echo "Found candidate file: ${FILE}" + NEW_URL="${BASE_URL}/${FILE}" + # extract version (e.g., geoserver-3.0-SNAPSHOT-authkey-plugin.zip -> 3.0-SNAPSHOT) + VERSION=$(echo "${FILE}" | sed -n 's/^geoserver-\(.*\)-'"${EXTENSION}"'-plugin\.zip$/\1/p') + if [ -n "${VERSION}" ]; then + GEOSERVER_VERSION="${VERSION}" + echo "Resolved GEOSERVER_VERSION=${GEOSERVER_VERSION} from ${FILE}" + fi + echo -e "\nDownloading ${EXTENSION} extension from ${NEW_URL} to ${DOWNLOAD_FILE}" + wget --progress=bar:force:noscroll -c --no-check-certificate "${NEW_URL}" -O "${DOWNLOAD_FILE}" + else + echo "No matching plugin found at ${BASE_URL}/" + fi + fi fi fi } @@ -22,15 +66,23 @@ function download_extension() { if [ "$INSTALL_EXTENSIONS" = "true" ]; then echo "Starting download of extensions" if [ ! -d "$ADDITIONAL_LIBS_DIR" ]; then - mkdir -p $ADDITIONAL_LIBS_DIR + mkdir -p "$ADDITIONAL_LIBS_DIR" fi + BASE_STABLE_URL=$(normalize_url "${STABLE_PLUGIN_URL}") + BASE_COMM_URL=$(normalize_url "${COMMUNITY_PLUGIN_URL}") + for EXTENSION in $(echo "${STABLE_EXTENSIONS}" | tr ',' ' '); do - URL="${STABLE_PLUGIN_URL}/geoserver-${GEOSERVER_VERSION}-${EXTENSION}-plugin.zip" - download_extension ${URL} ${EXTENSION} + EXTENSION=$(echo "${EXTENSION}" | xargs) + [ -z "$EXTENSION" ] && continue + URL="${BASE_STABLE_URL}/geoserver-${GEOSERVER_VERSION}-${EXTENSION}-plugin.zip" + download_extension "${URL}" "${EXTENSION}" done + for EXTENSION in $(echo "${COMMUNITY_EXTENSIONS}" | tr ',' ' '); do - URL="${COMMUNITY_PLUGIN_URL}/geoserver-${GEOSERVER_VERSION}-${EXTENSION}-plugin.zip" - download_extension ${URL} ${EXTENSION} + EXTENSION=$(echo "${EXTENSION}" | xargs) + [ -z "$EXTENSION" ] && continue + URL="${BASE_COMM_URL}/geoserver-${GEOSERVER_VERSION}-${EXTENSION}-plugin.zip" + download_extension "${URL}" "${EXTENSION}" done echo "Finished download of extensions" fi @@ -38,14 +90,16 @@ fi # Install the extensions echo "Starting installation of extensions" for EXTENSION in $(echo "${STABLE_EXTENSIONS},${COMMUNITY_EXTENSIONS}" | tr ',' ' '); do - ADDITIONAL_LIB=${ADDITIONAL_LIBS_DIR}geoserver-${GEOSERVER_VERSION}-${EXTENSION}-plugin.zip + EXTENSION=$(echo "${EXTENSION}" | xargs) + [ -z "$EXTENSION" ] && continue + ADDITIONAL_LIB="${ADDITIONAL_LIBS_DIR%/}/geoserver-${GEOSERVER_VERSION}-${EXTENSION}-plugin.zip" [ -e "$ADDITIONAL_LIB" ] || continue if [[ $ADDITIONAL_LIB == *.zip ]]; then - unzip -q -o -d ${GEOSERVER_LIB_DIR} ${ADDITIONAL_LIB} "*.jar" + unzip -q -o -d "${GEOSERVER_LIB_DIR}" "${ADDITIONAL_LIB}" "*.jar" echo "Installed all jar files from ${ADDITIONAL_LIB}" elif [[ $ADDITIONAL_LIB == *.jar ]]; then - cp ${ADDITIONAL_LIB} ${GEOSERVER_LIB_DIR} + cp "${ADDITIONAL_LIB}" "${GEOSERVER_LIB_DIR}" echo "Installed ${ADDITIONAL_LIB}" else echo "Skipping ${ADDITIONAL_LIB}: unknown file extension." From f006d975069caad492f0e67680c3ded0fe8a7704 Mon Sep 17 00:00:00 2001 From: Peter Smythe Date: Sun, 18 Jan 2026 13:12:27 +0200 Subject: [PATCH 06/30] ci: enhance plugin discovery and download process in install-extensions.sh --- install-extensions.sh | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/install-extensions.sh b/install-extensions.sh index 07d9bd6b..c49dc091 100644 --- a/install-extensions.sh +++ b/install-extensions.sh @@ -42,16 +42,20 @@ function download_extension() { if [ -n "${BASE_URL}" ]; then echo "Attempting to discover plugin filename from ${BASE_URL}/" LISTING=$(curl -s "${BASE_URL}/" || true) - FILE=$(echo "${LISTING}" | grep -oE "geoserver-[^/]*-${EXTENSION}-plugin.zip" | head -n 1 || true) + # flatten and extract the href value for the matching plugin file + LISTING_ONE=$(echo "${LISTING}" | tr '\n' ' ') + FILE=$(echo "${LISTING_ONE}" | sed -n 's/.*href="\([^" ]*'"${EXTENSION}"'-plugin\.zip\)".*/\1/p' | head -n 1 || true) + # ensure we only have a bare filename + FILE=$(basename "${FILE}") if [ -n "${FILE}" ]; then echo "Found candidate file: ${FILE}" NEW_URL="${BASE_URL}/${FILE}" - # extract version (e.g., geoserver-3.0-SNAPSHOT-authkey-plugin.zip -> 3.0-SNAPSHOT) VERSION=$(echo "${FILE}" | sed -n 's/^geoserver-\(.*\)-'"${EXTENSION}"'-plugin\.zip$/\1/p') if [ -n "${VERSION}" ]; then GEOSERVER_VERSION="${VERSION}" echo "Resolved GEOSERVER_VERSION=${GEOSERVER_VERSION} from ${FILE}" fi + DOWNLOAD_FILE="${DOWNLOAD_DIR}${FILE}" echo -e "\nDownloading ${EXTENSION} extension from ${NEW_URL} to ${DOWNLOAD_FILE}" wget --progress=bar:force:noscroll -c --no-check-certificate "${NEW_URL}" -O "${DOWNLOAD_FILE}" else @@ -92,7 +96,8 @@ echo "Starting installation of extensions" for EXTENSION in $(echo "${STABLE_EXTENSIONS},${COMMUNITY_EXTENSIONS}" | tr ',' ' '); do EXTENSION=$(echo "${EXTENSION}" | xargs) [ -z "$EXTENSION" ] && continue - ADDITIONAL_LIB="${ADDITIONAL_LIBS_DIR%/}/geoserver-${GEOSERVER_VERSION}-${EXTENSION}-plugin.zip" + # find any downloaded plugin matching the extension name (handles discovered filenames) + ADDITIONAL_LIB=$(ls -1 "${ADDITIONAL_LIBS_DIR%/}"/geoserver-*-${EXTENSION}-plugin.zip 2>/dev/null | head -n 1 || true) [ -e "$ADDITIONAL_LIB" ] || continue if [[ $ADDITIONAL_LIB == *.zip ]]; then From cc000897041a3c78b77345b013006fdf366d0a70 Mon Sep 17 00:00:00 2001 From: Peter Smythe Date: Wed, 21 Jan 2026 09:53:54 +0200 Subject: [PATCH 07/30] ci: update tagging logic for nightly and release versions in publish workflow --- .github/workflows/publish.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index cefa30de..f322aa51 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -143,7 +143,7 @@ jobs: # Add semantic tags for main/stable/maintenance releases if [[ "$VERSION" == "${MAIN_VERSION}."* ]] && [[ "$IS_NIGHTLY" == "false" ]]; then - ADDITIONAL_TAGS="$ADDITIONAL_TAGS,latest" + ADDITIONAL_TAGS="$ADDITIONAL_TAGS" elif [[ "$VERSION" == "${STABLE_VERSION}."* ]] && [[ "$IS_NIGHTLY" == "false" ]]; then ADDITIONAL_TAGS="$ADDITIONAL_TAGS,stable-latest" elif [[ "$VERSION" == "${MAINTENANCE_VERSION}."* ]] && [[ "$IS_NIGHTLY" == "false" ]]; then @@ -152,7 +152,7 @@ jobs: # Add nightly tags for snapshot builds if [[ "$VERSION" == "${MAIN_VERSION}-SNAPSHOT" ]]; then - ADDITIONAL_TAGS="$ADDITIONAL_TAGS,latest" + ADDITIONAL_TAGS="$ADDITIONAL_TAGS,nightly,latest" elif [[ "$VERSION" == "${STABLE_VERSION}-SNAPSHOT" ]]; then ADDITIONAL_TAGS="$ADDITIONAL_TAGS,stable-nightly" elif [[ "$VERSION" == "${MAINTENANCE_VERSION}-SNAPSHOT" ]]; then From 8ab24a0bb827b562e354b78431b0dbab719c4511 Mon Sep 17 00:00:00 2001 From: Peter Smythe Date: Wed, 28 Jan 2026 18:53:52 +0200 Subject: [PATCH 08/30] ci: enhance publish workflow with dynamic Docker repository and version extraction --- .github/workflows/publish.yml | 78 ++++++++++++++++++++++------------- install-extensions.sh | 6 ++- 2 files changed, 53 insertions(+), 31 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index f322aa51..49dd3a0b 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -20,6 +20,7 @@ env: MAIN_VERSION: "3.0" # Update when main branch changes STABLE_VERSION: "2.28" MAINTENANCE_VERSION: "2.27" + DOCKER_REPO: "petersmythe/geoserver-test" # Change to docker.osgeo.org/geoserver for production jobs: # ============================================================ @@ -29,6 +30,8 @@ jobs: name: Prepare Build Parameters runs-on: ubuntu-latest outputs: + version: ${{ steps.parse.outputs.version }} + build_number: ${{ steps.parse.outputs.build_number }} major: ${{ steps.parse.outputs.major }} minor: ${{ steps.parse.outputs.minor }} base_image: ${{ steps.parse.outputs.base_image }} @@ -54,6 +57,10 @@ jobs: echo "No version input (push trigger). Falling back to VERSION=$VERSION BUILD=$BUILD" fi + # Output version and build number for use in other jobs + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "build_number=$BUILD" >> $GITHUB_OUTPUT + echo "Parsing version: $VERSION" # Extract major.minor version @@ -231,14 +238,14 @@ jobs: context: . platforms: ${{ matrix.platform }} build-args: | - GS_VERSION=${{ inputs.version }} - GS_BUILD=${{ inputs.build_number }} + GS_VERSION=${{ needs.prepare.outputs.version }} + GS_BUILD=${{ needs.prepare.outputs.build_number }} BUILD_GDAL=${{ matrix.gdal }} GEOSERVER_BASE_IMAGE=${{ needs.prepare.outputs.base_image }} WAR_ZIP_URL=${{ needs.prepare.outputs.war_url }} STABLE_PLUGIN_URL=${{ needs.prepare.outputs.stable_plugin_url }} COMMUNITY_PLUGIN_URL=${{ needs.prepare.outputs.community_plugin_url }} - outputs: type=image,name=petersmythe/geoserver-test,push-by-digest=true,name-canonical=true,push=true + outputs: type=image,name=${{ env.DOCKER_REPO }},push-by-digest=true,name-canonical=true,push=true - name: Export digest run: | @@ -295,18 +302,18 @@ jobs: ADDITIONAL_TAGS="${{ needs.prepare.outputs.additional_tags }}" # Build tag list - TAGS="petersmythe/geoserver-test:$PRIMARY_TAG" + TAGS="${{ env.DOCKER_REPO }}:$PRIMARY_TAG" if [ -n "$ADDITIONAL_TAGS" ]; then IFS=',' read -ra TAG_ARRAY <<< "$ADDITIONAL_TAGS" for tag in "${TAG_ARRAY[@]}"; do - TAGS="$TAGS petersmythe/geoserver-test:$tag" + TAGS="$TAGS ${{ env.DOCKER_REPO }}:$tag" done fi # Create manifest docker buildx imagetools create $(printf -- '-t %s ' $TAGS) \ - $(printf 'petersmythe/geoserver-test@sha256:%s ' *) + $(printf '${{ env.DOCKER_REPO }}@sha256:%s ' *) echo "Created manifest for tags: $TAGS" @@ -317,53 +324,66 @@ jobs: ADDITIONAL_TAGS="${{ needs.prepare.outputs.additional_tags }}" # Build tag list with -gdal suffix - TAGS="petersmythe/geoserver-test:$PRIMARY_TAG" + TAGS="${{ env.DOCKER_REPO }}:$PRIMARY_TAG" if [ -n "$ADDITIONAL_TAGS" ]; then IFS=',' read -ra TAG_ARRAY <<< "$ADDITIONAL_TAGS" for tag in "${TAG_ARRAY[@]}"; do - TAGS="$TAGS petersmythe/geoserver-test:$tag-gdal" + TAGS="$TAGS ${{ env.DOCKER_REPO }}:$tag-gdal" done fi # Create manifest docker buildx imagetools create $(printf -- '-t %s ' $TAGS) \ - $(printf 'petersmythe/geoserver-test@sha256:%s ' *) + $(printf '${{ env.DOCKER_REPO }}@sha256:%s ' *) echo "Created manifest for tags: $TAGS" - name: Inspect manifests run: | - docker buildx imagetools inspect petersmythe/geoserver-test:${{ needs.prepare.outputs.primary_tag }} - docker buildx imagetools inspect petersmythe/geoserver-test:${{ needs.prepare.outputs.primary_tag }}-gdal + docker buildx imagetools inspect ${{ env.DOCKER_REPO }}:${{ needs.prepare.outputs.primary_tag }} + docker buildx imagetools inspect ${{ env.DOCKER_REPO }}:${{ needs.prepare.outputs.primary_tag }}-gdal # ============================================================ - # JOB 4: FUTURE - Publish to OSGeo repo (dual publishing) + # JOB 4: TEST - Copy multi-arch manifests (testing buildx imagetools) # ============================================================ publish-osgeo: - name: Publish to OSGeo Registry (Future) + name: Test Multi-Arch Manifest Copy runs-on: ubuntu-latest needs: merge - if: false # Enable when ready to dual-publish + if: true # Enabled for testing multi-arch manifest copy steps: - - name: Login to OSGeo Registry + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Docker Hub uses: docker/login-action@v3 with: - registry: geoserver-docker.osgeo.org - username: ${{ secrets.OSGEO_USERNAME }} - password: ${{ secrets.OSGEO_TOKEN }} + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Tag and push to OSGeo + - name: Test copying multi-arch manifests to alternate tags run: | - # Pull from petersmythe, retag, push to OSGeo - docker pull petersmythe/geoserver-test:${{ needs.prepare.outputs.primary_tag }} - docker tag petersmythe/geoserver-test:${{ needs.prepare.outputs.primary_tag }} \ - geoserver-docker.osgeo.org/geoserver:${{ needs.prepare.outputs.primary_tag }} - docker push geoserver-docker.osgeo.org/geoserver:${{ needs.prepare.outputs.primary_tag }} + # Test: Copy multi-arch manifest to alternate tag (simulates OSGeo publish) + # Using -test-copy suffix to avoid overwriting existing tags + docker buildx imagetools create \ + -t ${{ env.DOCKER_REPO }}:${{ needs.prepare.outputs.primary_tag }}-test-copy \ + ${{ env.DOCKER_REPO }}:${{ needs.prepare.outputs.primary_tag }} + + # Copy multi-arch manifest for the GDAL variant + docker buildx imagetools create \ + -t ${{ env.DOCKER_REPO }}:${{ needs.prepare.outputs.primary_tag }}-gdal-test-copy \ + ${{ env.DOCKER_REPO }}:${{ needs.prepare.outputs.primary_tag }}-gdal + + echo "Successfully copied multi-arch manifests to test tags." + echo "Verify at: https://hub.docker.com/r/${{ env.DOCKER_REPO }}/tags" + + # Inspect to confirm multi-arch + echo "" + echo "Inspecting copied manifest (no GDAL):" + docker buildx imagetools inspect ${{ env.DOCKER_REPO }}:${{ needs.prepare.outputs.primary_tag }}-test-copy - # Same for GDAL variant - docker pull petersmythe/geoserver-test:${{ needs.prepare.outputs.primary_tag }}-gdal - docker tag petersmythe/geoserver-test:${{ needs.prepare.outputs.primary_tag }}-gdal \ - geoserver-docker.osgeo.org/geoserver:${{ needs.prepare.outputs.primary_tag }}-gdal - docker push geoserver-docker.osgeo.org/geoserver:${{ needs.prepare.outputs.primary_tag }}-gdal + echo "" + echo "Inspecting copied manifest (with GDAL):" + docker buildx imagetools inspect ${{ env.DOCKER_REPO }}:${{ needs.prepare.outputs.primary_tag }}-gdal-test-copy diff --git a/install-extensions.sh b/install-extensions.sh index c49dc091..7df6cbf9 100644 --- a/install-extensions.sh +++ b/install-extensions.sh @@ -26,6 +26,8 @@ fi function download_extension() { URL=$1 EXTENSION=$2 + # Escape EXTENSION for safe use inside sed regular expressions + EXTENSION_REGEX_ESCAPED=$(printf '%s\n' "${EXTENSION}" | sed 's/[][\\.^$*+?{}|()]/\\&/g') DOWNLOAD_DIR="${ADDITIONAL_LIBS_DIR%/}/" DOWNLOAD_FILE="${DOWNLOAD_DIR}geoserver-${GEOSERVER_VERSION}-${EXTENSION}-plugin.zip" @@ -44,13 +46,13 @@ function download_extension() { LISTING=$(curl -s "${BASE_URL}/" || true) # flatten and extract the href value for the matching plugin file LISTING_ONE=$(echo "${LISTING}" | tr '\n' ' ') - FILE=$(echo "${LISTING_ONE}" | sed -n 's/.*href="\([^" ]*'"${EXTENSION}"'-plugin\.zip\)".*/\1/p' | head -n 1 || true) + FILE=$(echo "${LISTING_ONE}" | sed -n 's/.*href="\([^" ]*'"${EXTENSION_REGEX_ESCAPED}"'-plugin\\.zip\)".*/\1/p' | head -n 1 || true) # ensure we only have a bare filename FILE=$(basename "${FILE}") if [ -n "${FILE}" ]; then echo "Found candidate file: ${FILE}" NEW_URL="${BASE_URL}/${FILE}" - VERSION=$(echo "${FILE}" | sed -n 's/^geoserver-\(.*\)-'"${EXTENSION}"'-plugin\.zip$/\1/p') + VERSION=$(echo "${FILE}" | sed -n 's/^geoserver-\(.*\)-'"${EXTENSION_REGEX_ESCAPED}"'-plugin\\.zip$/\1/p') if [ -n "${VERSION}" ]; then GEOSERVER_VERSION="${VERSION}" echo "Resolved GEOSERVER_VERSION=${GEOSERVER_VERSION} from ${FILE}" From 200f16def42d33549bb5afb5c80d5a63438b438e Mon Sep 17 00:00:00 2001 From: Peter Smythe Date: Wed, 28 Jan 2026 20:16:11 +0200 Subject: [PATCH 09/30] ci: adjust job dependencies in publish workflow --- .github/workflows/publish.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 49dd3a0b..d39db2b0 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -57,6 +57,12 @@ jobs: echo "No version input (push trigger). Falling back to VERSION=$VERSION BUILD=$BUILD" fi + # If BUILD is empty, use a default + if [ -z "$BUILD" ]; then + BUILD="${GITHUB_RUN_NUMBER:-github}" + echo "Build number not provided. Using BUILD=$BUILD" + fi + # Output version and build number for use in other jobs echo "version=$VERSION" >> $GITHUB_OUTPUT echo "build_number=$BUILD" >> $GITHUB_OUTPUT @@ -350,7 +356,9 @@ jobs: publish-osgeo: name: Test Multi-Arch Manifest Copy runs-on: ubuntu-latest - needs: merge + needs: + - prepare + - merge if: true # Enabled for testing multi-arch manifest copy steps: From 3a6b5cf735e4b80e2e0803b9f334ced53e37660a Mon Sep 17 00:00:00 2001 From: Peter Smythe Date: Fri, 30 Jan 2026 16:47:40 +0200 Subject: [PATCH 10/30] ci: refine tagging logic for stable and maintenance releases in publish workflow --- .github/workflows/publish.yml | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index d39db2b0..ca82fac2 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -154,21 +154,20 @@ jobs: ADDITIONAL_TAGS="${MAJOR}.${MINOR}-latest" fi - # Add semantic tags for main/stable/maintenance releases - if [[ "$VERSION" == "${MAIN_VERSION}."* ]] && [[ "$IS_NIGHTLY" == "false" ]]; then - ADDITIONAL_TAGS="$ADDITIONAL_TAGS" - elif [[ "$VERSION" == "${STABLE_VERSION}."* ]] && [[ "$IS_NIGHTLY" == "false" ]]; then + # Add semantic tags for stable/maintenance releases only + # (main releases get x.y-latest but no semantic tag since they're experimental) + if [[ "$VERSION" == "${STABLE_VERSION}."* ]] && [[ "$IS_NIGHTLY" == "false" ]]; then ADDITIONAL_TAGS="$ADDITIONAL_TAGS,stable-latest" elif [[ "$VERSION" == "${MAINTENANCE_VERSION}."* ]] && [[ "$IS_NIGHTLY" == "false" ]]; then ADDITIONAL_TAGS="$ADDITIONAL_TAGS,maintenance-latest" fi # Add nightly tags for snapshot builds - if [[ "$VERSION" == "${MAIN_VERSION}-SNAPSHOT" ]]; then - ADDITIONAL_TAGS="$ADDITIONAL_TAGS,nightly,latest" - elif [[ "$VERSION" == "${STABLE_VERSION}-SNAPSHOT" ]]; then + if [[ "$VERSION" == "${MAIN_VERSION}."*"-SNAPSHOT" ]]; then + ADDITIONAL_TAGS="$ADDITIONAL_TAGS,nightly" + elif [[ "$VERSION" == "${STABLE_VERSION}."*"-SNAPSHOT" ]]; then ADDITIONAL_TAGS="$ADDITIONAL_TAGS,stable-nightly" - elif [[ "$VERSION" == "${MAINTENANCE_VERSION}-SNAPSHOT" ]]; then + elif [[ "$VERSION" == "${MAINTENANCE_VERSION}."*"-SNAPSHOT" ]]; then ADDITIONAL_TAGS="$ADDITIONAL_TAGS,maintenance-nightly" fi @@ -359,7 +358,7 @@ jobs: needs: - prepare - merge - if: true # Enabled for testing multi-arch manifest copy + if: false # Disabled - testing complete steps: - name: Set up Docker Buildx From 865a2f376ed5309647ad125e2df65d616cc07ef4 Mon Sep 17 00:00:00 2001 From: Peter Smythe Date: Fri, 30 Jan 2026 16:50:06 +0200 Subject: [PATCH 11/30] ci: enhance plugin discovery process in install-extensions.sh with improved validation and error handling --- install-extensions.sh | 58 +++++++++++++++++++++++++++++-------------- 1 file changed, 40 insertions(+), 18 deletions(-) diff --git a/install-extensions.sh b/install-extensions.sh index 7df6cbf9..6c357089 100644 --- a/install-extensions.sh +++ b/install-extensions.sh @@ -43,25 +43,47 @@ function download_extension() { BASE_URL="${URL%/geoserver-*-${EXTENSION}-plugin.zip}" if [ -n "${BASE_URL}" ]; then echo "Attempting to discover plugin filename from ${BASE_URL}/" - LISTING=$(curl -s "${BASE_URL}/" || true) - # flatten and extract the href value for the matching plugin file - LISTING_ONE=$(echo "${LISTING}" | tr '\n' ' ') - FILE=$(echo "${LISTING_ONE}" | sed -n 's/.*href="\([^" ]*'"${EXTENSION_REGEX_ESCAPED}"'-plugin\\.zip\)".*/\1/p' | head -n 1 || true) - # ensure we only have a bare filename - FILE=$(basename "${FILE}") - if [ -n "${FILE}" ]; then - echo "Found candidate file: ${FILE}" - NEW_URL="${BASE_URL}/${FILE}" - VERSION=$(echo "${FILE}" | sed -n 's/^geoserver-\(.*\)-'"${EXTENSION_REGEX_ESCAPED}"'-plugin\\.zip$/\1/p') - if [ -n "${VERSION}" ]; then - GEOSERVER_VERSION="${VERSION}" - echo "Resolved GEOSERVER_VERSION=${GEOSERVER_VERSION} from ${FILE}" - fi - DOWNLOAD_FILE="${DOWNLOAD_DIR}${FILE}" - echo -e "\nDownloading ${EXTENSION} extension from ${NEW_URL} to ${DOWNLOAD_FILE}" - wget --progress=bar:force:noscroll -c --no-check-certificate "${NEW_URL}" -O "${DOWNLOAD_FILE}" + LISTING=$(curl -fsS "${BASE_URL}/" 2>/dev/null || true) + if [ -z "${LISTING}" ]; then + echo "Unable to retrieve directory listing from ${BASE_URL}/; skipping automatic plugin discovery." else - echo "No matching plugin found at ${BASE_URL}/" + # flatten and extract the href value for the matching plugin file + LISTING_ONE=$(echo "${LISTING}" | tr '\n' ' ') + FILE=$(echo "${LISTING_ONE}" | sed -n 's/.*href="\([^" ]*'"${EXTENSION_REGEX_ESCAPED}"'-plugin\\.zip\)".*/\1/p' | head -n 1 || true) + + # Basic sanity checks before using the discovered value + if [ -n "${FILE}" ]; then + # Reject absolute URLs or paths with slashes (we only expect a simple filename) + if echo "${FILE}" | grep -qE '://' || echo "${FILE}" | grep -q '/'; then + echo "Discovered candidate '${FILE}' is not a simple filename; skipping." + FILE="" + fi + fi + + if [ -n "${FILE}" ]; then + # Ensure we only have a bare filename + FILE=$(basename "${FILE}") + # Validate filename against expected pattern: geoserver---plugin.zip + if ! echo "${FILE}" | grep -qE '^geoserver-[^-][^/]*-'"${EXTENSION_REGEX_ESCAPED}"'-plugin\.zip$'; then + echo "Discovered candidate filename '${FILE}' does not match expected pattern; skipping." + FILE="" + fi + fi + + if [ -n "${FILE}" ]; then + echo "Found candidate file: ${FILE}" + NEW_URL="${BASE_URL}/${FILE}" + VERSION=$(echo "${FILE}" | sed -n 's/^geoserver-\(.*\)-'"${EXTENSION_REGEX_ESCAPED}"'-plugin\\.zip$/\1/p') + if [ -n "${VERSION}" ]; then + GEOSERVER_VERSION="${VERSION}" + echo "Resolved GEOSERVER_VERSION=${GEOSERVER_VERSION} from ${FILE}" + fi + DOWNLOAD_FILE="${DOWNLOAD_DIR}${FILE}" + echo -e "\nDownloading ${EXTENSION} extension from ${NEW_URL} to ${DOWNLOAD_FILE}" + wget --progress=bar:force:noscroll -c --no-check-certificate "${NEW_URL}" -O "${DOWNLOAD_FILE}" + else + echo "No matching plugin found at ${BASE_URL}/" + fi fi fi fi From 293972c1636801786a2b9ad98d31e41c2020825a Mon Sep 17 00:00:00 2001 From: Peter Smythe Date: Fri, 30 Jan 2026 16:52:37 +0200 Subject: [PATCH 12/30] ci: remove --no-check-certificate option from wget commands in download_extension function --- install-extensions.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/install-extensions.sh b/install-extensions.sh index 6c357089..5727defa 100644 --- a/install-extensions.sh +++ b/install-extensions.sh @@ -36,7 +36,7 @@ function download_extension() { else if curl --output /dev/null --silent --head --fail "${URL}"; then echo -e "\nDownloading ${EXTENSION} extension from ${URL} to ${DOWNLOAD_FILE}" - wget --progress=bar:force:noscroll -c --no-check-certificate "${URL}" -O "${DOWNLOAD_FILE}" + wget --progress=bar:force:noscroll -c "${URL}" -O "${DOWNLOAD_FILE}" else echo "URL does not exist: ${URL}" # Try to discover an actual plugin file at the base URL and use it @@ -80,7 +80,7 @@ function download_extension() { fi DOWNLOAD_FILE="${DOWNLOAD_DIR}${FILE}" echo -e "\nDownloading ${EXTENSION} extension from ${NEW_URL} to ${DOWNLOAD_FILE}" - wget --progress=bar:force:noscroll -c --no-check-certificate "${NEW_URL}" -O "${DOWNLOAD_FILE}" + wget --progress=bar:force:noscroll -c "${NEW_URL}" -O "${DOWNLOAD_FILE}" else echo "No matching plugin found at ${BASE_URL}/" fi From 8d72c190aef88f16f215f32b00feb6ccbcf05fad Mon Sep 17 00:00:00 2001 From: Peter Smythe Date: Fri, 30 Jan 2026 16:53:47 +0200 Subject: [PATCH 13/30] ci: add error handling to version parsing step in publish workflow --- .github/workflows/publish.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index ca82fac2..ab4479cd 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -47,6 +47,8 @@ jobs: - name: Parse Version and Determine Build Parameters id: parse run: | + set -euo pipefail + VERSION="${{ inputs.version }}" BUILD="${{ inputs.build_number }}" From c863313c0009a16ed29712288af8ded35487c4d9 Mon Sep 17 00:00:00 2001 From: Peter Smythe Date: Fri, 30 Jan 2026 16:56:41 +0200 Subject: [PATCH 14/30] ci: add validation step for GeoServer WAR URL accessibility in publish workflow --- .github/workflows/publish.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index ab4479cd..6c76dbc3 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -188,6 +188,16 @@ jobs: echo " Additional Tags: $ADDITIONAL_TAGS" echo " WAR URL: $WAR_URL" echo "============================================" + + - name: Validate GeoServer WAR URL + run: | + set -euo pipefail + echo "Validating GeoServer WAR URL: ${{ steps.parse.outputs.war_url }}" + if ! wget --spider "${{ steps.parse.outputs.war_url }}"; then + echo "ERROR: GeoServer WAR URL is not accessible: ${{ steps.parse.outputs.war_url }}" >&2 + exit 1 + fi + echo "GeoServer WAR URL is accessible" # ============================================================ # JOB 2: Build images on native runners (matrix) From 05ae945b5ac1534fd2bf92c043f5b0c4b199f3ee Mon Sep 17 00:00:00 2001 From: Peter Smythe Date: Fri, 30 Jan 2026 17:03:49 +0200 Subject: [PATCH 15/30] ci: update GeoServer version series configuration comments in publish workflow --- .github/workflows/publish.yml | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 6c76dbc3..8463cc8f 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -17,7 +17,32 @@ on: - ci/publish-test env: - MAIN_VERSION: "3.0" # Update when main branch changes + # ------------------------------------------------------------------ + # GeoServer Version Series Configuration + # + # These variables define the currently supported GeoServer release series. + # They MUST be updated when a new major or minor series is released (every 6 months). + # + # MAIN_VERSION: Current development series (main branch, experimental) + # STABLE_VERSION: Current stable release series (recommended for production) + # MAINTENANCE_VERSION: Previous stable series (receives critical fixes only) + # + # These values determine: + # - Docker image tags (e.g., "nightly", "stable-latest", "maintenance-latest") + # - Base Tomcat/JDK images for each series + # - Download URLs for WAR files and plugins + # - Git branch mappings for nightly builds + # + # When a new series is released (e.g., 3.1 becomes main): + # 1. Update MAIN_VERSION to the new series (e.g., "3.1") + # 2. Update STABLE_VERSION to the previous main (e.g., "3.0") + # 3. Update MAINTENANCE_VERSION to the previous stable (e.g., "2.28") + # 4. Update base image selection logic if JDK requirements change (line 89) + # + # WARNING: Incorrect values will cause images to be tagged incorrectly, + # use wrong base images, and download from incorrect plugin URLs. + # ------------------------------------------------------------------ + MAIN_VERSION: "3.0" STABLE_VERSION: "2.28" MAINTENANCE_VERSION: "2.27" DOCKER_REPO: "petersmythe/geoserver-test" # Change to docker.osgeo.org/geoserver for production From dae5e3e28a6a404cec498b570b0d126a9dd2aa89 Mon Sep 17 00:00:00 2001 From: Peter Smythe Date: Fri, 30 Jan 2026 17:06:01 +0200 Subject: [PATCH 16/30] ci: refine GEOSERVER_VERSION resolution logic in download_extension function --- install-extensions.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install-extensions.sh b/install-extensions.sh index 5727defa..e4ce05f2 100644 --- a/install-extensions.sh +++ b/install-extensions.sh @@ -74,7 +74,7 @@ function download_extension() { echo "Found candidate file: ${FILE}" NEW_URL="${BASE_URL}/${FILE}" VERSION=$(echo "${FILE}" | sed -n 's/^geoserver-\(.*\)-'"${EXTENSION_REGEX_ESCAPED}"'-plugin\\.zip$/\1/p') - if [ -n "${VERSION}" ]; then + if [ -n "${VERSION}" ] && [ -z "${GEOSERVER_VERSION}" ]; then GEOSERVER_VERSION="${VERSION}" echo "Resolved GEOSERVER_VERSION=${GEOSERVER_VERSION} from ${FILE}" fi From bb24672b9c3dc7e585438c101ae2d98ba1adf91f Mon Sep 17 00:00:00 2001 From: Peter Smythe Date: Fri, 30 Jan 2026 17:10:25 +0200 Subject: [PATCH 17/30] ci: add validation for digest files existence in manifest creation steps --- .github/workflows/publish.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 8463cc8f..619309ea 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -340,6 +340,17 @@ jobs: - name: Create manifest list and push (no GDAL) working-directory: /tmp/digests-nogdal run: | + set -euo pipefail + + # Validate digest files exist + DIGEST_COUNT=$(ls -1 2>/dev/null | wc -l) + if [ "$DIGEST_COUNT" -eq 0 ]; then + echo "ERROR: No digest files found in /tmp/digests-nogdal" >&2 + echo "Expected digests from build jobs (amd64 and arm64)" >&2 + exit 1 + fi + echo "Found $DIGEST_COUNT digest file(s)" + PRIMARY_TAG="${{ needs.prepare.outputs.primary_tag }}" ADDITIONAL_TAGS="${{ needs.prepare.outputs.additional_tags }}" @@ -362,6 +373,17 @@ jobs: - name: Create manifest list and push (with GDAL) working-directory: /tmp/digests-gdal run: | + set -euo pipefail + + # Validate digest files exist + DIGEST_COUNT=$(ls -1 2>/dev/null | wc -l) + if [ "$DIGEST_COUNT" -eq 0 ]; then + echo "ERROR: No digest files found in /tmp/digests-gdal" >&2 + echo "Expected digests from build jobs (amd64 and arm64)" >&2 + exit 1 + fi + echo "Found $DIGEST_COUNT digest file(s)" + PRIMARY_TAG="${{ needs.prepare.outputs.primary_tag }}-gdal" ADDITIONAL_TAGS="${{ needs.prepare.outputs.additional_tags }}" From 89b16f339700b8d5137995af4801e0a9217806cf Mon Sep 17 00:00:00 2001 From: Peter Smythe Date: Fri, 30 Jan 2026 17:12:53 +0200 Subject: [PATCH 18/30] ci: enhance version inference and download logic for GeoServer extensions --- install-extensions.sh | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/install-extensions.sh b/install-extensions.sh index e4ce05f2..e7e084a6 100644 --- a/install-extensions.sh +++ b/install-extensions.sh @@ -6,12 +6,14 @@ normalize_url() { echo "${1%/}" } -# If GEOSERVER_VERSION is not set, try to infer it from the plugin URLs -# e.g. https://build.geoserver.org/geoserver//ext-latest/ -> +# Version inference: Extract GeoServer version from plugin URLs if not explicitly set +# This handles cases where GEOSERVER_VERSION is not provided as a build arg if [ -z "${GEOSERVER_VERSION}" ]; then + # Try extracting version from STABLE_PLUGIN_URL (e.g., .../geoserver/2.28.x/ext-latest/ -> 2.28.x) if [ -n "${STABLE_PLUGIN_URL}" ]; then VERSION=$(echo "${STABLE_PLUGIN_URL}" | sed -n 's#.*/geoserver/\([^/]*\)/.*#\1#p') fi + # Fallback to COMMUNITY_PLUGIN_URL if stable URL didn't yield a version if [ -z "${VERSION}" ] && [ -n "${COMMUNITY_PLUGIN_URL}" ]; then VERSION=$(echo "${COMMUNITY_PLUGIN_URL}" | sed -n 's#.*/geoserver/\([^/]*\)/.*#\1#p') fi @@ -26,7 +28,7 @@ fi function download_extension() { URL=$1 EXTENSION=$2 - # Escape EXTENSION for safe use inside sed regular expressions + # Escape special regex characters in extension name for safe use in sed patterns EXTENSION_REGEX_ESCAPED=$(printf '%s\n' "${EXTENSION}" | sed 's/[][\\.^$*+?{}|()]/\\&/g') DOWNLOAD_DIR="${ADDITIONAL_LIBS_DIR%/}/" DOWNLOAD_FILE="${DOWNLOAD_DIR}geoserver-${GEOSERVER_VERSION}-${EXTENSION}-plugin.zip" @@ -34,12 +36,14 @@ function download_extension() { if [ -e "$DOWNLOAD_FILE" ]; then echo "$DOWNLOAD_FILE already exists. Skipping download." else + # Try downloading from expected URL first if curl --output /dev/null --silent --head --fail "${URL}"; then echo -e "\nDownloading ${EXTENSION} extension from ${URL} to ${DOWNLOAD_FILE}" wget --progress=bar:force:noscroll -c "${URL}" -O "${DOWNLOAD_FILE}" else echo "URL does not exist: ${URL}" - # Try to discover an actual plugin file at the base URL and use it + # Fallback: scrape directory listing to discover actual filename + # This handles cases where version format in filename differs from expected BASE_URL="${URL%/geoserver-*-${EXTENSION}-plugin.zip}" if [ -n "${BASE_URL}" ]; then echo "Attempting to discover plugin filename from ${BASE_URL}/" @@ -47,13 +51,13 @@ function download_extension() { if [ -z "${LISTING}" ]; then echo "Unable to retrieve directory listing from ${BASE_URL}/; skipping automatic plugin discovery." else - # flatten and extract the href value for the matching plugin file + # Parse HTML to extract href matching the extension plugin pattern LISTING_ONE=$(echo "${LISTING}" | tr '\n' ' ') FILE=$(echo "${LISTING_ONE}" | sed -n 's/.*href="\([^" ]*'"${EXTENSION_REGEX_ESCAPED}"'-plugin\\.zip\)".*/\1/p' | head -n 1 || true) # Basic sanity checks before using the discovered value if [ -n "${FILE}" ]; then - # Reject absolute URLs or paths with slashes (we only expect a simple filename) + # Security: reject absolute URLs or paths (only accept simple filenames) if echo "${FILE}" | grep -qE '://' || echo "${FILE}" | grep -q '/'; then echo "Discovered candidate '${FILE}' is not a simple filename; skipping." FILE="" @@ -63,7 +67,7 @@ function download_extension() { if [ -n "${FILE}" ]; then # Ensure we only have a bare filename FILE=$(basename "${FILE}") - # Validate filename against expected pattern: geoserver---plugin.zip + # Validate filename matches expected pattern: geoserver---plugin.zip if ! echo "${FILE}" | grep -qE '^geoserver-[^-][^/]*-'"${EXTENSION_REGEX_ESCAPED}"'-plugin\.zip$'; then echo "Discovered candidate filename '${FILE}' does not match expected pattern; skipping." FILE="" @@ -73,6 +77,7 @@ function download_extension() { if [ -n "${FILE}" ]; then echo "Found candidate file: ${FILE}" NEW_URL="${BASE_URL}/${FILE}" + # Extract version from discovered filename if GEOSERVER_VERSION is not yet set VERSION=$(echo "${FILE}" | sed -n 's/^geoserver-\(.*\)-'"${EXTENSION_REGEX_ESCAPED}"'-plugin\\.zip$/\1/p') if [ -n "${VERSION}" ] && [ -z "${GEOSERVER_VERSION}" ]; then GEOSERVER_VERSION="${VERSION}" @@ -120,7 +125,7 @@ echo "Starting installation of extensions" for EXTENSION in $(echo "${STABLE_EXTENSIONS},${COMMUNITY_EXTENSIONS}" | tr ',' ' '); do EXTENSION=$(echo "${EXTENSION}" | xargs) [ -z "$EXTENSION" ] && continue - # find any downloaded plugin matching the extension name (handles discovered filenames) + # Find downloaded plugin (handles both expected and discovered filenames) ADDITIONAL_LIB=$(ls -1 "${ADDITIONAL_LIBS_DIR%/}"/geoserver-*-${EXTENSION}-plugin.zip 2>/dev/null | head -n 1 || true) [ -e "$ADDITIONAL_LIB" ] || continue From 5b3843e85e6d24d3aaa16860bc14988d1f590a44 Mon Sep 17 00:00:00 2001 From: Peter Smythe Date: Fri, 30 Jan 2026 17:21:27 +0200 Subject: [PATCH 19/30] ci: add validation for extension names during installation --- install-extensions.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/install-extensions.sh b/install-extensions.sh index e7e084a6..3ac385b1 100644 --- a/install-extensions.sh +++ b/install-extensions.sh @@ -125,6 +125,11 @@ echo "Starting installation of extensions" for EXTENSION in $(echo "${STABLE_EXTENSIONS},${COMMUNITY_EXTENSIONS}" | tr ',' ' '); do EXTENSION=$(echo "${EXTENSION}" | xargs) [ -z "$EXTENSION" ] && continue + # Validate extension name contains only safe characters (lowercase letters, numbers, hyphens, underscores) + if ! [[ "$EXTENSION" =~ ^[a-z0-9_-]+$ ]]; then + echo "WARNING: Skipping invalid extension name: ${EXTENSION}" >&2 + continue + fi # Find downloaded plugin (handles both expected and discovered filenames) ADDITIONAL_LIB=$(ls -1 "${ADDITIONAL_LIBS_DIR%/}"/geoserver-*-${EXTENSION}-plugin.zip 2>/dev/null | head -n 1 || true) [ -e "$ADDITIONAL_LIB" ] || continue From 9e8c64910ca0df93b9d75c4ba4592686c6eeb541 Mon Sep 17 00:00:00 2001 From: Peter Smythe Date: Fri, 30 Jan 2026 17:21:55 +0200 Subject: [PATCH 20/30] ci: enhance download reliability by adding retry logic to wget commands --- install-extensions.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/install-extensions.sh b/install-extensions.sh index 3ac385b1..fd2e020b 100644 --- a/install-extensions.sh +++ b/install-extensions.sh @@ -39,7 +39,7 @@ function download_extension() { # Try downloading from expected URL first if curl --output /dev/null --silent --head --fail "${URL}"; then echo -e "\nDownloading ${EXTENSION} extension from ${URL} to ${DOWNLOAD_FILE}" - wget --progress=bar:force:noscroll -c "${URL}" -O "${DOWNLOAD_FILE}" + wget --progress=bar:force:noscroll --tries=3 -c "${URL}" -O "${DOWNLOAD_FILE}" else echo "URL does not exist: ${URL}" # Fallback: scrape directory listing to discover actual filename @@ -85,7 +85,7 @@ function download_extension() { fi DOWNLOAD_FILE="${DOWNLOAD_DIR}${FILE}" echo -e "\nDownloading ${EXTENSION} extension from ${NEW_URL} to ${DOWNLOAD_FILE}" - wget --progress=bar:force:noscroll -c "${NEW_URL}" -O "${DOWNLOAD_FILE}" + wget --progress=bar:force:noscroll --tries=3 -c "${NEW_URL}" -O "${DOWNLOAD_FILE}" else echo "No matching plugin found at ${BASE_URL}/" fi From 921bb1de6c59db1dd59c4ee9d58b688012db1263 Mon Sep 17 00:00:00 2001 From: Peter Smythe Date: Fri, 30 Jan 2026 17:25:39 +0200 Subject: [PATCH 21/30] ci: set timeouts for build jobs to improve workflow reliability --- .github/workflows/publish.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 619309ea..55905873 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -54,6 +54,7 @@ jobs: prepare: name: Prepare Build Parameters runs-on: ubuntu-latest + timeout-minutes: 10 outputs: version: ${{ steps.parse.outputs.version }} build_number: ${{ steps.parse.outputs.build_number }} @@ -231,6 +232,7 @@ jobs: name: Build ${{ matrix.platform }} ${{ matrix.gdal && 'with GDAL' || 'without GDAL' }} runs-on: ${{ matrix.runner }} needs: prepare + timeout-minutes: 60 strategy: fail-fast: false matrix: @@ -312,6 +314,7 @@ jobs: needs: - prepare - build + timeout-minutes: 15 steps: - name: Download all digests (no GDAL) @@ -417,6 +420,7 @@ jobs: needs: - prepare - merge + timeout-minutes: 15 if: false # Disabled - testing complete steps: From e41e149d7be768f8d5d31b9385b938db2766f8fb Mon Sep 17 00:00:00 2001 From: Peter Smythe Date: Fri, 30 Jan 2026 17:26:02 +0200 Subject: [PATCH 22/30] ci: add concurrency settings to manage workflow execution --- .github/workflows/publish.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 55905873..fab466bd 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -16,6 +16,10 @@ on: branches: - ci/publish-test +concurrency: + group: publish-${{ github.event.inputs.version || 'push-test' }} + cancel-in-progress: true + env: # ------------------------------------------------------------------ # GeoServer Version Series Configuration From c6bbb60b8bd156a1d370c88f23a503a8501d75e7 Mon Sep 17 00:00:00 2001 From: Peter Smythe Date: Fri, 30 Jan 2026 17:32:56 +0200 Subject: [PATCH 23/30] ci: enhance publish workflow with job summary generation and caching options --- .github/workflows/publish.yml | 67 ++++++++++++++++++++++++++++++++++- install-extensions.sh | 1 + 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index fab466bd..4e3b33a4 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -20,6 +20,12 @@ concurrency: group: publish-${{ github.event.inputs.version || 'push-test' }} cancel-in-progress: true +permissions: + contents: read + packages: write + id-token: write + attestations: write + env: # ------------------------------------------------------------------ # GeoServer Version Series Configuration @@ -292,7 +298,11 @@ jobs: GEOSERVER_BASE_IMAGE=${{ needs.prepare.outputs.base_image }} WAR_ZIP_URL=${{ needs.prepare.outputs.war_url }} STABLE_PLUGIN_URL=${{ needs.prepare.outputs.stable_plugin_url }} - COMMUNITY_PLUGIN_URL=${{ needs.prepare.outputs.community_plugin_url }} + COMMUNITY_PLUGIN_URL=${{ needs.prepare.outputs.community_plugin_url }} + cache-from: type=gha,scope=build-${{ matrix.arch }}-${{ matrix.gdal && 'gdal' || 'nogdal' }} + cache-to: type=gha,mode=max,scope=build-${{ matrix.arch }}-${{ matrix.gdal && 'gdal' || 'nogdal' }} + provenance: true + sbom: true outputs: type=image,name=${{ env.DOCKER_REPO }},push-by-digest=true,name-canonical=true,push=true - name: Export digest @@ -414,6 +424,61 @@ jobs: run: | docker buildx imagetools inspect ${{ env.DOCKER_REPO }}:${{ needs.prepare.outputs.primary_tag }} docker buildx imagetools inspect ${{ env.DOCKER_REPO }}:${{ needs.prepare.outputs.primary_tag }}-gdal + + - name: Generate Job Summary + run: | + cat >> $GITHUB_STEP_SUMMARY << 'EOF' + ## GeoServer Multi-Arch Build Summary + + ### Build Information + - **Version**: ${{ needs.prepare.outputs.version }} + - **Build Number**: ${{ needs.prepare.outputs.build_number }} + - **Base Image**: ${{ needs.prepare.outputs.base_image }} + - **Is Nightly**: ${{ needs.prepare.outputs.is_nightly }} + + ### Images Created + + #### Without GDAL + - Primary: [${{ env.DOCKER_REPO }}:${{ needs.prepare.outputs.primary_tag }}](https://hub.docker.com/r/${{ env.DOCKER_REPO }}/tags) + EOF + + # Add additional tags if present + ADDITIONAL_TAGS="${{ needs.prepare.outputs.additional_tags }}" + if [ -n "$ADDITIONAL_TAGS" ]; then + IFS=',' read -ra TAG_ARRAY <<< "$ADDITIONAL_TAGS" + for tag in "${TAG_ARRAY[@]}"; do + echo "- Additional: [${{ env.DOCKER_REPO }}:$tag](https://hub.docker.com/r/${{ env.DOCKER_REPO }}/tags)" >> $GITHUB_STEP_SUMMARY + done + fi + + cat >> $GITHUB_STEP_SUMMARY << 'EOF' + + #### With GDAL + - Primary: [${{ env.DOCKER_REPO }}:${{ needs.prepare.outputs.primary_tag }}-gdal](https://hub.docker.com/r/${{ env.DOCKER_REPO }}/tags) + EOF + + if [ -n "$ADDITIONAL_TAGS" ]; then + IFS=',' read -ra TAG_ARRAY <<< "$ADDITIONAL_TAGS" + for tag in "${TAG_ARRAY[@]}"; do + echo "- Additional: [${{ env.DOCKER_REPO }}:$tag-gdal](https://hub.docker.com/r/${{ env.DOCKER_REPO }}/tags)" >> $GITHUB_STEP_SUMMARY + done + fi + + cat >> $GITHUB_STEP_SUMMARY << 'EOF' + + ### Architectures + - linux/amd64 + - linux/arm64 + + ### Pull Commands + ```bash + # Without GDAL + docker pull ${{ env.DOCKER_REPO }}:${{ needs.prepare.outputs.primary_tag }} + + # With GDAL + docker pull ${{ env.DOCKER_REPO }}:${{ needs.prepare.outputs.primary_tag }}-gdal + ``` + EOF # ============================================================ # JOB 4: TEST - Copy multi-arch manifests (testing buildx imagetools) diff --git a/install-extensions.sh b/install-extensions.sh index fd2e020b..7b9a78c9 100644 --- a/install-extensions.sh +++ b/install-extensions.sh @@ -47,6 +47,7 @@ function download_extension() { BASE_URL="${URL%/geoserver-*-${EXTENSION}-plugin.zip}" if [ -n "${BASE_URL}" ]; then echo "Attempting to discover plugin filename from ${BASE_URL}/" + # Curl failure is tolerated (|| true) since directory scraping is optional fallback LISTING=$(curl -fsS "${BASE_URL}/" 2>/dev/null || true) if [ -z "${LISTING}" ]; then echo "Unable to retrieve directory listing from ${BASE_URL}/; skipping automatic plugin discovery." From 0814b1b8c63730a57319da9016a10202c321aea2 Mon Sep 17 00:00:00 2001 From: Peter Smythe Date: Tue, 24 Feb 2026 12:49:25 +0200 Subject: [PATCH 24/30] ci: update Docker registry configuration and add secondary registry support --- .github/workflows/publish.yml | 98 +++++++++++++++++++++-------------- 1 file changed, 60 insertions(+), 38 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 4e3b33a4..1db768c7 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -55,7 +55,10 @@ env: MAIN_VERSION: "3.0" STABLE_VERSION: "2.28" MAINTENANCE_VERSION: "2.27" - DOCKER_REPO: "petersmythe/geoserver-test" # Change to docker.osgeo.org/geoserver for production + # Primary registry: pushed to during build, receives personal account credentials + PRIMARY_REGISTRY: "petersmythe/geoserver-test" + # Secondary registry: mirrored after build, receives OSGeo credentials + SECONDARY_REGISTRY: "docker.osgeo.org/geoserver" jobs: # ============================================================ @@ -273,7 +276,7 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - - name: Login to Docker Hub + - name: Login to Primary Registry (Docker Hub) uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} @@ -303,7 +306,7 @@ jobs: cache-to: type=gha,mode=max,scope=build-${{ matrix.arch }}-${{ matrix.gdal && 'gdal' || 'nogdal' }} provenance: true sbom: true - outputs: type=image,name=${{ env.DOCKER_REPO }},push-by-digest=true,name-canonical=true,push=true + outputs: type=image,name=${{ env.PRIMARY_REGISTRY }},push-by-digest=true,name-canonical=true,push=true - name: Export digest run: | @@ -348,13 +351,13 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - - name: Login to Docker Hub + - name: Login to Primary Registry (Docker Hub) uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Create manifest list and push (no GDAL) + - name: Create manifest list and push to Primary Registry (no GDAL) working-directory: /tmp/digests-nogdal run: | set -euo pipefail @@ -371,19 +374,19 @@ jobs: PRIMARY_TAG="${{ needs.prepare.outputs.primary_tag }}" ADDITIONAL_TAGS="${{ needs.prepare.outputs.additional_tags }}" - # Build tag list - TAGS="${{ env.DOCKER_REPO }}:$PRIMARY_TAG" + # Build tag list for primary registry + TAGS="${{ env.PRIMARY_REGISTRY }}:$PRIMARY_TAG" if [ -n "$ADDITIONAL_TAGS" ]; then IFS=',' read -ra TAG_ARRAY <<< "$ADDITIONAL_TAGS" for tag in "${TAG_ARRAY[@]}"; do - TAGS="$TAGS ${{ env.DOCKER_REPO }}:$tag" + TAGS="$TAGS ${{ env.PRIMARY_REGISTRY }}:$tag" done fi - # Create manifest + # Create manifest and push to primary registry docker buildx imagetools create $(printf -- '-t %s ' $TAGS) \ - $(printf '${{ env.DOCKER_REPO }}@sha256:%s ' *) + $(printf '${{ env.PRIMARY_REGISTRY }}@sha256:%s ' *) echo "Created manifest for tags: $TAGS" @@ -422,8 +425,8 @@ jobs: - name: Inspect manifests run: | - docker buildx imagetools inspect ${{ env.DOCKER_REPO }}:${{ needs.prepare.outputs.primary_tag }} - docker buildx imagetools inspect ${{ env.DOCKER_REPO }}:${{ needs.prepare.outputs.primary_tag }}-gdal + docker buildx imagetools inspect ${{ env.PRIMARY_REGISTRY }}:${{ needs.prepare.outputs.primary_tag }} + docker buildx imagetools inspect ${{ env.PRIMARY_REGISTRY }}:${{ needs.prepare.outputs.primary_tag }}-gdal - name: Generate Job Summary run: | @@ -439,7 +442,7 @@ jobs: ### Images Created #### Without GDAL - - Primary: [${{ env.DOCKER_REPO }}:${{ needs.prepare.outputs.primary_tag }}](https://hub.docker.com/r/${{ env.DOCKER_REPO }}/tags) + - Primary: [${{ env.PRIMARY_REGISTRY }}:${{ needs.prepare.outputs.primary_tag }}](https://hub.docker.com/r/${{ env.PRIMARY_REGISTRY }}/tags) EOF # Add additional tags if present @@ -447,20 +450,20 @@ jobs: if [ -n "$ADDITIONAL_TAGS" ]; then IFS=',' read -ra TAG_ARRAY <<< "$ADDITIONAL_TAGS" for tag in "${TAG_ARRAY[@]}"; do - echo "- Additional: [${{ env.DOCKER_REPO }}:$tag](https://hub.docker.com/r/${{ env.DOCKER_REPO }}/tags)" >> $GITHUB_STEP_SUMMARY + echo "- Additional: [${{ env.PRIMARY_REGISTRY }}:$tag](https://hub.docker.com/r/${{ env.PRIMARY_REGISTRY }}/tags)" >> $GITHUB_STEP_SUMMARY done fi cat >> $GITHUB_STEP_SUMMARY << 'EOF' #### With GDAL - - Primary: [${{ env.DOCKER_REPO }}:${{ needs.prepare.outputs.primary_tag }}-gdal](https://hub.docker.com/r/${{ env.DOCKER_REPO }}/tags) + - Primary: [${{ env.PRIMARY_REGISTRY }}:${{ needs.prepare.outputs.primary_tag }}-gdal](https://hub.docker.com/r/${{ env.PRIMARY_REGISTRY }}/tags) EOF if [ -n "$ADDITIONAL_TAGS" ]; then IFS=',' read -ra TAG_ARRAY <<< "$ADDITIONAL_TAGS" for tag in "${TAG_ARRAY[@]}"; do - echo "- Additional: [${{ env.DOCKER_REPO }}:$tag-gdal](https://hub.docker.com/r/${{ env.DOCKER_REPO }}/tags)" >> $GITHUB_STEP_SUMMARY + echo "- Additional: [${{ env.PRIMARY_REGISTRY }}:$tag-gdal](https://hub.docker.com/r/${{ env.PRIMARY_REGISTRY }}/tags)" >> $GITHUB_STEP_SUMMARY done fi @@ -473,56 +476,75 @@ jobs: ### Pull Commands ```bash # Without GDAL - docker pull ${{ env.DOCKER_REPO }}:${{ needs.prepare.outputs.primary_tag }} + docker pull ${{ env.PRIMARY_REGISTRY }}:${{ needs.prepare.outputs.primary_tag }} # With GDAL - docker pull ${{ env.DOCKER_REPO }}:${{ needs.prepare.outputs.primary_tag }}-gdal + docker pull ${{ env.PRIMARY_REGISTRY }}:${{ needs.prepare.outputs.primary_tag }}-gdal ``` EOF # ============================================================ - # JOB 4: TEST - Copy multi-arch manifests (testing buildx imagetools) + # JOB 4: Publish to Secondary Registry # ============================================================ - publish-osgeo: - name: Test Multi-Arch Manifest Copy + publish-secondary: + name: Mirror Images to Secondary Registry runs-on: ubuntu-latest needs: - prepare - merge timeout-minutes: 15 - if: false # Disabled - testing complete steps: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - - name: Login to Docker Hub + - name: Login to Secondary Registry (OSGeo) uses: docker/login-action@v3 with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} + registry: docker.osgeo.org + username: ${{ secrets.OSGEO_REPO_USERNAME }} + password: ${{ secrets.OSGEO_REPO_PASSWORD }} - - name: Test copying multi-arch manifests to alternate tags + - name: Copy multi-arch manifests to secondary registry run: | - # Test: Copy multi-arch manifest to alternate tag (simulates OSGeo publish) - # Using -test-copy suffix to avoid overwriting existing tags + # Copy multi-arch manifest from primary to secondary registry docker buildx imagetools create \ - -t ${{ env.DOCKER_REPO }}:${{ needs.prepare.outputs.primary_tag }}-test-copy \ - ${{ env.DOCKER_REPO }}:${{ needs.prepare.outputs.primary_tag }} + -t ${{ env.SECONDARY_REGISTRY }}:${{ needs.prepare.outputs.primary_tag }} \ + ${{ env.PRIMARY_REGISTRY }}:${{ needs.prepare.outputs.primary_tag }} + + # Apply additional tags if present + ADDITIONAL_TAGS="${{ needs.prepare.outputs.additional_tags }}" + if [ -n "$ADDITIONAL_TAGS" ]; then + IFS=',' read -ra TAG_ARRAY <<< "$ADDITIONAL_TAGS" + for tag in "${TAG_ARRAY[@]}"; do + docker buildx imagetools create \ + -t ${{ env.SECONDARY_REGISTRY }}:${tag} \ + ${{ env.PRIMARY_REGISTRY }}:${tag} + done + fi # Copy multi-arch manifest for the GDAL variant docker buildx imagetools create \ - -t ${{ env.DOCKER_REPO }}:${{ needs.prepare.outputs.primary_tag }}-gdal-test-copy \ - ${{ env.DOCKER_REPO }}:${{ needs.prepare.outputs.primary_tag }}-gdal + -t ${{ env.SECONDARY_REGISTRY }}:${{ needs.prepare.outputs.primary_tag }}-gdal \ + ${{ env.PRIMARY_REGISTRY }}:${{ needs.prepare.outputs.primary_tag }}-gdal + + if [ -n "$ADDITIONAL_TAGS" ]; then + IFS=',' read -ra TAG_ARRAY <<< "$ADDITIONAL_TAGS" + for tag in "${TAG_ARRAY[@]}"; do + docker buildx imagetools create \ + -t ${{ env.SECONDARY_REGISTRY }}:${tag}-gdal \ + ${{ env.PRIMARY_REGISTRY }}:${tag}-gdal + done + fi - echo "Successfully copied multi-arch manifests to test tags." - echo "Verify at: https://hub.docker.com/r/${{ env.DOCKER_REPO }}/tags" + echo "Successfully copied multi-arch manifests to ${{ env.SECONDARY_REGISTRY }}." + echo "Verify at: https://hub.docker.com/r/${{ env.SECONDARY_REGISTRY }}/tags" # Inspect to confirm multi-arch echo "" - echo "Inspecting copied manifest (no GDAL):" - docker buildx imagetools inspect ${{ env.DOCKER_REPO }}:${{ needs.prepare.outputs.primary_tag }}-test-copy + echo "Inspecting primary manifest:" + docker buildx imagetools inspect ${{ env.SECONDARY_REGISTRY }}:${{ needs.prepare.outputs.primary_tag }} echo "" - echo "Inspecting copied manifest (with GDAL):" - docker buildx imagetools inspect ${{ env.DOCKER_REPO }}:${{ needs.prepare.outputs.primary_tag }}-gdal-test-copy + echo "Inspecting primary manifest (with GDAL):" + docker buildx imagetools inspect ${{ env.SECONDARY_REGISTRY }}:${{ needs.prepare.outputs.primary_tag }}-gdal From d121b69689e90f85282e8a5241b8e0a1b29428cf Mon Sep 17 00:00:00 2001 From: Peter Smythe Date: Tue, 24 Feb 2026 13:05:33 +0200 Subject: [PATCH 25/30] ci: update Docker registry references in publish workflow --- .github/workflows/publish.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 1db768c7..c36ae3e6 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -408,18 +408,18 @@ jobs: ADDITIONAL_TAGS="${{ needs.prepare.outputs.additional_tags }}" # Build tag list with -gdal suffix - TAGS="${{ env.DOCKER_REPO }}:$PRIMARY_TAG" + TAGS="${{ env.PRIMARY_REGISTRY }}:$PRIMARY_TAG" if [ -n "$ADDITIONAL_TAGS" ]; then IFS=',' read -ra TAG_ARRAY <<< "$ADDITIONAL_TAGS" for tag in "${TAG_ARRAY[@]}"; do - TAGS="$TAGS ${{ env.DOCKER_REPO }}:$tag-gdal" + TAGS="$TAGS ${{ env.PRIMARY_REGISTRY }}:$tag-gdal" done fi # Create manifest docker buildx imagetools create $(printf -- '-t %s ' $TAGS) \ - $(printf '${{ env.DOCKER_REPO }}@sha256:%s ' *) + $(printf '${{ env.PRIMARY_REGISTRY }}@sha256:%s ' *) echo "Created manifest for tags: $TAGS" From a2e972918f514da461a836057e9cb2181316a8a8 Mon Sep 17 00:00:00 2001 From: Peter Smythe Date: Tue, 24 Feb 2026 13:23:50 +0200 Subject: [PATCH 26/30] ci: fix snapshot tag conditions for versioning in publish workflow --- .github/workflows/publish.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index c36ae3e6..43c9d438 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -204,11 +204,11 @@ jobs: fi # Add nightly tags for snapshot builds - if [[ "$VERSION" == "${MAIN_VERSION}."*"-SNAPSHOT" ]]; then + if [[ "$VERSION" == "${MAIN_VERSION}"*"-SNAPSHOT" ]]; then ADDITIONAL_TAGS="$ADDITIONAL_TAGS,nightly" - elif [[ "$VERSION" == "${STABLE_VERSION}."*"-SNAPSHOT" ]]; then + elif [[ "$VERSION" == "${STABLE_VERSION}"*"-SNAPSHOT" ]]; then ADDITIONAL_TAGS="$ADDITIONAL_TAGS,stable-nightly" - elif [[ "$VERSION" == "${MAINTENANCE_VERSION}."*"-SNAPSHOT" ]]; then + elif [[ "$VERSION" == "${MAINTENANCE_VERSION}"*"-SNAPSHOT" ]]; then ADDITIONAL_TAGS="$ADDITIONAL_TAGS,maintenance-nightly" fi From 9f39b395233872f5b30ba6d41ba3b612a7bc9385 Mon Sep 17 00:00:00 2001 From: Peter Smythe Date: Tue, 24 Feb 2026 14:42:08 +0200 Subject: [PATCH 27/30] ci: update secondary Docker registry URL in publish workflow --- .github/workflows/publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 43c9d438..602ab701 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -58,7 +58,7 @@ env: # Primary registry: pushed to during build, receives personal account credentials PRIMARY_REGISTRY: "petersmythe/geoserver-test" # Secondary registry: mirrored after build, receives OSGeo credentials - SECONDARY_REGISTRY: "docker.osgeo.org/geoserver" + SECONDARY_REGISTRY: "geoserver-docker.osgeo.org/geoserver" jobs: # ============================================================ From 301a80f5b8267832cfce1ab5d6db3ed49070e5ff Mon Sep 17 00:00:00 2001 From: Peter Smythe Date: Tue, 24 Feb 2026 14:52:28 +0200 Subject: [PATCH 28/30] ci: disable ARM64 builds in publish workflow due to unavailable runner --- .github/workflows/publish.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 602ab701..25eb6243 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -259,15 +259,15 @@ jobs: runner: ubuntu-latest arch: amd64 gdal: true - # ARM64 builds - - platform: linux/arm64 - runner: ubuntu-24.04-arm - arch: arm64 - gdal: false - - platform: linux/arm64 - runner: ubuntu-24.04-arm - arch: arm64 - gdal: true + # ARM64 builds (disabled - runner not available) + # - platform: linux/arm64 + # runner: ubuntu-24.04-arm + # arch: arm64 + # gdal: false + # - platform: linux/arm64 + # runner: ubuntu-24.04-arm + # arch: arm64 + # gdal: true steps: - name: Checkout repository From c8ef87e9b33880583d2d2ed7e2373a3a64ae2673 Mon Sep 17 00:00:00 2001 From: Peter Smythe Date: Tue, 24 Feb 2026 15:05:12 +0200 Subject: [PATCH 29/30] ci: update secondary Docker registry URL in publish workflow --- .github/workflows/publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 25eb6243..bbc4c328 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -501,7 +501,7 @@ jobs: - name: Login to Secondary Registry (OSGeo) uses: docker/login-action@v3 with: - registry: docker.osgeo.org + registry: geoserver-docker.osgeo.org username: ${{ secrets.OSGEO_REPO_USERNAME }} password: ${{ secrets.OSGEO_REPO_PASSWORD }} From a7f62c385f84653a5eca436fa4562f8079c20874 Mon Sep 17 00:00:00 2001 From: Peter Smythe Date: Tue, 24 Feb 2026 15:41:31 +0200 Subject: [PATCH 30/30] ci: enable ARM64 builds in publish workflow --- .github/workflows/publish.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index bbc4c328..22cb6da2 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -259,15 +259,15 @@ jobs: runner: ubuntu-latest arch: amd64 gdal: true - # ARM64 builds (disabled - runner not available) - # - platform: linux/arm64 - # runner: ubuntu-24.04-arm - # arch: arm64 - # gdal: false - # - platform: linux/arm64 - # runner: ubuntu-24.04-arm - # arch: arm64 - # gdal: true + # ARM64 builds + - platform: linux/arm64 + runner: ubuntu-24.04-arm + arch: arm64 + gdal: false + - platform: linux/arm64 + runner: ubuntu-24.04-arm + arch: arm64 + gdal: true steps: - name: Checkout repository