From a1255210f88532a48202393a853c769a7f5fb260 Mon Sep 17 00:00:00 2001 From: Volkan Cetin Date: Fri, 21 Aug 2026 12:00:21 +0300 Subject: [PATCH 1/2] Generalize focal PostGIS build to a PG12-16 pipeline --- .github/workflows/build-postgis-focal.yml | 174 +++++++++++++ dockerfiles/focal-postgis-builder/Dockerfile | 70 ++++++ scripts/build_postgis_focal | 245 +++++++++++++++++++ scripts/smoke_test_focal_postgis_debs | 143 +++++++++++ 4 files changed, 632 insertions(+) create mode 100644 .github/workflows/build-postgis-focal.yml create mode 100644 dockerfiles/focal-postgis-builder/Dockerfile create mode 100644 scripts/build_postgis_focal create mode 100644 scripts/smoke_test_focal_postgis_debs diff --git a/.github/workflows/build-postgis-focal.yml b/.github/workflows/build-postgis-focal.yml new file mode 100644 index 00000000..a2900e57 --- /dev/null +++ b/.github/workflows/build-postgis-focal.yml @@ -0,0 +1,174 @@ +name: Build PostGIS (focal) + +# Builds PostGIS .deb packages for Ubuntu 20.04 (focal) from upstream source +# (PGDG removed focal entirely -- focal-pgdg 404s and the frozen archive mirror +# stops at PostGIS 3.5.3), then signs them with debsigs (--sign=maint) using the +# existing packaging key. +# +# Unlike build-pg-focal.yml there is no per-major matrix: the PostGIS Debian +# packaging is multi-version by design (debian/pgversions + pg_buildext), so one +# source build emits postgresql--postgis-3 for every requested major in a +# single pass. That also means no assemble/de-duplicate job is needed -- the +# shared packages are produced exactly once. +# +# PG11 is deliberately unsupported: PostGIS 3.6 requires PostgreSQL 12+. + +on: + workflow_dispatch: + inputs: + postgis_version: + description: "PostGIS upstream version to build (e.g. 3.6.4)" + required: true + default: "3.6.4" + pg_versions: + description: "Space-separated PostgreSQL majors (PostGIS 3.6 supports 12+)" + required: false + default: "12 13 14 15 16" + postgis_sha256: + description: "sha256 of postgis-.tar.gz. Blank = use the pinned default in scripts/build_postgis_focal (only valid for that version)." + required: false + default: "" + run_tests: + description: "Run upstream regression suite (1=yes, much slower)" + required: false + default: "0" + push: + branches: + - postgis-focal + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build-and-sign: + name: Build & sign PostGIS ${{ github.event.inputs.postgis_version || '3.6.4' }} (focal) + runs-on: ubuntu-latest + env: + PACKAGING_SECRET_KEY: ${{ secrets.PACKAGING_SECRET_KEY }} + PACKAGING_PASSPHRASE: ${{ secrets.PACKAGING_PASSPHRASE }} + POSTGIS_VERSION: ${{ github.event.inputs.postgis_version || '3.6.4' }} + PG_VERSIONS: ${{ github.event.inputs.pg_versions || '12 13 14 15 16' }} + POSTGIS_SHA256: ${{ github.event.inputs.postgis_sha256 || '' }} + RUN_TESTS: ${{ github.event.inputs.run_tests || '0' }} + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Login to Docker Hub + uses: docker/login-action@v4 + with: + username: ${{ secrets.DOCKERHUB_USER_NAME }} + password: ${{ secrets.DOCKERHUB_PASSWORD }} + + - name: Build focal PostGIS builder image + run: | + docker build -t focal-postgis-builder \ + -f dockerfiles/focal-postgis-builder/Dockerfile . + + - name: Build PostGIS packages + run: | + mkdir -p packages + docker run --rm \ + -e POSTGIS_VERSION="${POSTGIS_VERSION}" \ + -e PG_VERSIONS="${PG_VERSIONS}" \ + -e POSTGIS_SHA256="${POSTGIS_SHA256}" \ + -e RUN_TESTS="${RUN_TESTS}" \ + -v "${PWD}/packages:/packages" \ + focal-postgis-builder + echo "Built packages:" + ls -1 packages/focal/postgis/*.deb + + - name: Sign packages (debsigs --sign=maint) + # Use the prebuilt, deployed debsigner image (the one all Citus signing + # uses). Its entrypoint signs exactly "/packages/*/*.deb" (one dir level + # deep), so mount the parent of the output dir: with + # "${PWD}/packages/focal:/packages" the debs land at + # /packages/postgis/*.deb, which is what that glob expects. + run: | + if [ -z "${PACKAGING_SECRET_KEY}" ] || [ -z "${PACKAGING_PASSPHRASE}" ]; then + echo "::error::PACKAGING_SECRET_KEY / PACKAGING_PASSPHRASE secrets are not set" >&2 + exit 1 + fi + printf '%s' "${PACKAGING_PASSPHRASE}" | docker run --rm -i \ + -e PACKAGING_SECRET_KEY \ + -e PACKAGING_PASSPHRASE \ + -v "${PWD}/packages/focal:/packages" \ + citusdata/packaging:debsigner + + - name: Verify signatures are embedded + run: | + rc=0 + for deb in packages/focal/postgis/*.deb; do + if ar t "$deb" | grep -q '^_gpgmaint$'; then + echo "signed: $deb" + else + echo "::error::missing _gpgmaint signature in $deb" >&2 + rc=1 + fi + done + exit $rc + + - name: Verify the set is self-contained + # Two invariants that have broken consumers before: + # * every runtime must ship its own extension control file, so the + # package can never be paired with another version's SQL + # * no runtime may depend on a separate -scripts package + run: | + rc=0 + for v in ${PG_VERSIONS}; do + deb="$(ls packages/focal/postgis/postgresql-${v}-postgis-3_*.deb)" + if ! dpkg-deb -c "$deb" | grep -q 'extension/postgis-[0-9.]*\.control'; then + echo "::error::PG${v} runtime ships no extension control file" >&2; rc=1 + fi + if dpkg-deb -f "$deb" Depends | grep -q 'postgis-3-scripts'; then + echo "::error::PG${v} runtime still depends on a -scripts package" >&2; rc=1 + fi + done + exit $rc + + - name: Upload signed packages + uses: actions/upload-artifact@v4 + with: + name: postgis-focal-deb + path: | + packages/focal/postgis/*.deb + packages/focal/postgis/*.changes + packages/focal/postgis/*.buildinfo + if-no-files-found: error + + install-smoke-test: + needs: build-and-sign + name: Install smoke test (focal) + runs-on: ubuntu-latest + env: + PG_VERSIONS: ${{ github.event.inputs.pg_versions || '12 13 14 15 16' }} + POSTGIS_VERSION: ${{ github.event.inputs.postgis_version || '3.6.4' }} + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Download built packages + uses: actions/download-artifact@v4 + with: + name: postgis-focal-deb + path: debs + + - name: Install and verify in a clean focal container + # The jobs above only prove the packages exist, are signed and are + # self-contained -- not that the extension can actually be created. This + # installs the set into a stock ubuntu:20.04 twice: clean, and over + # PGDG's PostGIS 3.5.3 (the in-place upgrade path), then runs + # CREATE EXTENSION on every major. + run: | + docker run --rm \ + -v "${PWD}/debs:/debs:ro" \ + -v "${PWD}/scripts/smoke_test_focal_postgis_debs:/usr/local/bin/smoke_test_focal_postgis_debs:ro" \ + -e DEBS_DIR=/debs \ + -e PG_VERSIONS="${PG_VERSIONS}" \ + -e EXPECTED_POSTGIS="${POSTGIS_VERSION}" \ + ubuntu:20.04 \ + /usr/local/bin/smoke_test_focal_postgis_debs diff --git a/dockerfiles/focal-postgis-builder/Dockerfile b/dockerfiles/focal-postgis-builder/Dockerfile new file mode 100644 index 00000000..8c09bfb2 --- /dev/null +++ b/dockerfiles/focal-postgis-builder/Dockerfile @@ -0,0 +1,70 @@ +# vim:set ft=dockerfile: +# +# Builder image for PostGIS packages targeting Ubuntu 20.04 (focal). One image +# builds every focal-buildable PostgreSQL major at once (PG 12..16 by default); +# unlike PostgreSQL core, the PostGIS Debian packaging is multi-version by +# design (debian/pgversions + pg_buildext), so a single source build emits +# postgresql--postgis-3 for each major in one pass. +# +# Why this exists: +# apt.postgresql.org (PGDG) no longer ships focal binaries -- focal-pgdg 404s +# and the frozen apt-archive mirror tops out at PostGIS 3.5.3. Anything newer +# (e.g. 3.6.x for a CVE fix) has to be rebuilt from upstream source. +# +# Strategy (validated): +# - Upstream source: postgis-.tar.gz from download.osgeo.org, +# sha256-pinned. +# - Debian packaging: the frozen focal-era debian/ from PGDG's last focal +# postgis source package (3.5.3+dfsg-1~exp1.pgdg20.04+1), fetched with +# `apt-get source` so it is authenticated by the archive's signed Release. +# - Build tooling restored from the PGDG *archive*, which keeps the removed +# focal-pgdg suite. +# +# The heavy lifting lives in scripts/build_postgis_focal (the entrypoint). +FROM ubuntu:20.04 +ARG DEBIAN_FRONTEND=noninteractive + +# PGDG repository signing key fingerprint: +# B97B 0AFC AA1A 47F0 44F2 44A0 7FCC 7D46 ACCC 4CF8 +RUN set -ex; \ + apt-get update; \ + apt-get install -y --no-install-recommends ca-certificates curl gnupg; \ + install -d /usr/share/keyrings; \ + curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc \ + | gpg --dearmor -o /usr/share/keyrings/pgdg-archive.gpg; \ + # 'main' carries the build tooling; each PostgreSQL major is a separate + # component. PostGIS 3.6 supports PG12+, and Marlin still ships PG12, so + # component 12 is listed here even though focal-pg-builder omits it. + echo "deb [signed-by=/usr/share/keyrings/pgdg-archive.gpg] https://apt-archive.postgresql.org/pub/repos/apt focal-pgdg main 12 13 14 15 16" \ + > /etc/apt/sources.list.d/pgdg-archive.list; \ + echo "deb-src [signed-by=/usr/share/keyrings/pgdg-archive.gpg] https://apt-archive.postgresql.org/pub/repos/apt focal-pgdg main 12 13 14 15 16" \ + >> /etc/apt/sources.list.d/pgdg-archive.list; \ + # 'universe' holds the geo stack (libgeos-dev, libgdal-dev, libsfcgal-dev) + sed -i 's/^# deb \(.*universe\)/deb \1/' /etc/apt/sources.list; \ + sed -i 's/^# deb-src \(.*\)/deb-src \1/' /etc/apt/sources.list; \ + apt-get update; \ + # base build tooling; per-build Build-Depends are resolved at run time by + # scripts/build_postgis_focal via mk-build-deps against debian/control. + apt-get install -y --no-install-recommends \ + build-essential \ + devscripts \ + equivs \ + fakeroot \ + quilt \ + dpkg-dev \ + debhelper \ + dh-exec \ + postgresql-common-dev \ + postgresql-server-dev-all \ + xz-utils; \ + rm -rf /var/lib/apt/lists/* + +# Fail the image build early if the archived focal-pgdg debhelper (>= 13) is not +# what we picked up (debhelper-compat (= 13) is required by the packaging). +RUN dpkg-query -W -f='${Package} ${Version}\n' debhelper postgresql-common-dev dh-exec + +COPY scripts/build_postgis_focal /usr/local/bin/build_postgis_focal +RUN chmod +x /usr/local/bin/build_postgis_focal + +VOLUME /packages +ENTRYPOINT ["/usr/local/bin/build_postgis_focal"] diff --git a/scripts/build_postgis_focal b/scripts/build_postgis_focal new file mode 100644 index 00000000..6b174447 --- /dev/null +++ b/scripts/build_postgis_focal @@ -0,0 +1,245 @@ +#!/bin/bash +# +# build_postgis_focal -- rebuild PostGIS Debian packages for Ubuntu 20.04 (focal). +# +# PGDG removed focal entirely (focal-pgdg 404s; the frozen apt-archive mirror +# stops at PostGIS 3.5.3), so a newer PostGIS on focal has to be rebuilt from +# upstream source. +# +# Approach (see dockerfiles/focal-postgis-builder/Dockerfile for the rationale): +# upstream postgis-.tar.gz + the frozen focal-era debian/ packaging +# from PGDG's last focal postgis source package +# -> dpkg-buildpackage inside a focal environment with the archived focal-pgdg +# build tooling available. +# +# Unlike PostgreSQL core, one source build emits packages for *every* PostgreSQL +# major at once (debian/pgversions drives pg_buildext), so there is no per-major +# matrix here. +# +# Two deliberate deviations from PGDG's package layout, both load-bearing: +# +# 1. The arch-independent -scripts package is folded into the runtime package. +# Upstream splits it so the SQL can be shared across architectures; we build +# amd64 only, so there is no benefit -- and the runtime's dependency on +# -scripts is *unversioned*, so a partial update silently pairs a new .so +# with old SQL. One package makes that impossible. +# +# 2. Extension control files are named postgis-.control rather +# than postgis-3.control, registered with update-alternatives at a priority +# above PGDG's 30. PGDG's -scripts prerm runs +# update-alternatives --remove ... postgis-3.control +# on removal; if we shared that path it would drop the last alternative and +# delete the postgis.control symlink, leaving a system where dpkg -l and +# default_version both look correct while CREATE EXTENSION postgis fails +# with "Could not open extension control file". +# +# Output: unsigned *.deb in ${OUTPUT_DIR} (default /packages/focal/postgis). +# Signing is a separate step performed by the debsigner image (debsigs +# --sign=maint), so the build and the signing key never live in the same +# container. +# +# Examples: +# build_postgis_focal # 3.6.4 for PG12..16 +# POSTGIS_VERSION=3.6.5 build_postgis_focal +# PG_VERSIONS="15 16" RUN_TESTS=1 build_postgis_focal + +set -euo pipefail + +# ---------------------------------------------------------------------------- +# Inputs (override via env) +# ---------------------------------------------------------------------------- +POSTGIS_VERSION="${POSTGIS_VERSION:-3.6.4}" + +# PostGIS 3.6 supports PostgreSQL 12-18. PG11 is intentionally unsupported here: +# it must stay on PGDG's PostGIS 3.3.x. +PG_VERSIONS="${PG_VERSIONS:-12 13 14 15 16}" + +# The last focal postgis source package PGDG published. Its debian/ is frozen +# (focal is EOL); override only if the archive is ever re-touched. +PACKAGING_SRC_VERSION="${PACKAGING_SRC_VERSION:-3.5.3+dfsg-1~exp1.pgdg20.04+1}" + +# sha256 of the upstream tarball. Pinned by default: download.osgeo.org serves +# no detached signature, so this is the only integrity check available and it +# must not silently degrade to "whatever was served". +POSTGIS_SHA256="${POSTGIS_SHA256:-ed8dc6679f1e06f7b113592b04cde2a7e00f1b1e681294c8ca2204058990cec6}" + +DEB_REVISION="${DEB_REVISION:-1.citus20.04+1}" +TARGET_VERSION="${TARGET_VERSION:-${POSTGIS_VERSION}-${DEB_REVISION}}" + +# Suffix for the extension control files / alternatives path. Defaults to the +# upstream MAJOR.MINOR so it never collides with PGDG's "-3". +CONTROL_SUFFIX="${CONTROL_SUFFIX:-${POSTGIS_VERSION%.*}}" +ALT_PRIORITY="${ALT_PRIORITY:-36}" + +# Set RUN_TESTS=1 to run the upstream regression suite (much slower). +RUN_TESTS="${RUN_TESTS:-0}" + +OUTPUT_DIR="${OUTPUT_DIR:-/packages/focal/postgis}" +WORK="${WORK_DIR:-/build}" +UPSTREAM_URL="${UPSTREAM_URL:-https://download.osgeo.org/postgis/source/postgis-${POSTGIS_VERSION}.tar.gz}" + +export DEBEMAIL="${DEBEMAIL:-packaging@citusdata.com}" +export DEBFULLNAME="${DEBFULLNAME:-Citus Data}" +export DEBIAN_FRONTEND=noninteractive + +echo "==> Building PostGIS ${POSTGIS_VERSION} as ${TARGET_VERSION} for PG${PG_VERSIONS// /,} (focal)" + +mkdir -p "${WORK}" "${OUTPUT_DIR}" +cd "${WORK}" + +echo "==> [1/7] Fetch frozen focal debian/ packaging (postgis ${PACKAGING_SRC_VERSION})" +apt-get update +# apt-get source validates against the archive's signed Release file. +apt-get source "postgis=${PACKAGING_SRC_VERSION}" +PKG_DIR="$(find "${WORK}" -maxdepth 1 -type d -name 'postgis-*+dfsg' | head -1)" +[ -n "${PKG_DIR}" ] || { echo "ERROR: could not locate unpacked packaging source" >&2; exit 1; } + +echo "==> [2/7] Fetch and verify upstream postgis-${POSTGIS_VERSION}.tar.gz" +curl -4 -fsSL -o "postgis-${POSTGIS_VERSION}.tar.gz" "${UPSTREAM_URL}" +echo "${POSTGIS_SHA256} postgis-${POSTGIS_VERSION}.tar.gz" | sha256sum -c - + +SRCDIR="${WORK}/postgis-${POSTGIS_VERSION}" +rm -rf "${SRCDIR}" +tar xzf "postgis-${POSTGIS_VERSION}.tar.gz" +cp -a "${PKG_DIR}/debian" "${SRCDIR}/debian" +cd "${SRCDIR}" + +echo "==> [3/7] Triage quilt patches against the new upstream" +KEPT="" +while read -r patch; do + [ -z "${patch}" ] && continue + case "${patch}" in \#*) continue ;; esac + if patch -p1 --dry-run --silent < "debian/patches/${patch}" >/dev/null 2>&1; then + echo " keep ${patch}"; KEPT="${KEPT}${patch}\n" + else + echo " drop ${patch} (does not apply to ${POSTGIS_VERSION})" + fi +done < debian/patches/series +printf "%b" "${KEPT}" > debian/patches/series + +echo "==> [4/7] Target PostgreSQL majors: ${PG_VERSIONS}" +printf '%s\n' ${PG_VERSIONS} > debian/pgversions + +echo "==> [5/7] Fold -scripts into the runtime package" + +# The runtime package now ships the SQL/control tree as well as the libraries. +cat > debian/postgresql-generic-postgis.install.in <<'EOF' +usr/lib/postgresql/@PGVERSION@/lib +usr/share/postgresql/@PGVERSION@ +EOF + +rm -f debian/postgresql-generic-postgis-scripts.*.in + +cat > debian/postgresql-generic-postgis.postinst.in < debian/postgresql-generic-postgis.prerm.in < debian/control.in.new +mv debian/control.in.new debian/control.in + +# The runtime absorbs -scripts: drop the dependency, take over its files, and +# Conflicts (not Breaks) so apt removes the old package in the same transaction +# instead of leaving it to be removed later. +sed -i '/^ *postgresql-PGVERSION-postgis-3-scripts,$/d' debian/control.in +perl -0pi -e 's{^Provides: postgresql-PGVERSION-postgis$}{Provides: postgresql-PGVERSION-postgis,\n postgresql-PGVERSION-postgis-3-scripts,\n postgresql-PGVERSION-postgis-scripts\nReplaces: postgresql-PGVERSION-postgis-3-scripts\nConflicts: postgresql-PGVERSION-postgis-3-scripts}m' debian/control.in + +# debian/rules generates per-major debhelper files and post-processes the +# -scripts staging dirs; repoint both at the merged package. The control-file +# rename uses CONTROL_SUFFIX while the package name keeps MAJOR_VERSION. +sed -i 's|for SUFFIX in .install .lintian-overrides -scripts.install -scripts.lintian-overrides -scripts.postinst -scripts.prerm;|for SUFFIX in .install .lintian-overrides .postinst .prerm;|' debian/rules +sed -i 's|-postgis-\*-scripts/usr/share|-postgis-*/usr/share|g' debian/rules +sed -i "s|-\$(MAJOR_VERSION).control|-${CONTROL_SUFFIX}.control|g" debian/rules + +echo " binary packages now declared:" +grep '^Package:' debian/control.in | sed 's/^/ /' + +echo "==> [6/7] Set version to ${TARGET_VERSION} and install Build-Depends" +dch --newversion "${TARGET_VERSION}" --distribution focal --force-distribution \ + "Rebuild of PostGIS ${POSTGIS_VERSION} for focal (upstream PGDG focal-pgdg discontinued)." + +mk-build-deps --install --remove \ + --tool 'apt-get -o Debug::pkgProblemResolver=yes --yes --no-install-recommends' \ + debian/control + +BUILD_OPTIONS="parallel=$(nproc)" +if [ "${RUN_TESTS}" != "1" ]; then + BUILD_OPTIONS="${BUILD_OPTIONS} nocheck" +fi + +echo "==> [7/7] Build binary packages (DEB_BUILD_OPTIONS='${BUILD_OPTIONS}')" +export DEB_BUILD_OPTIONS="${BUILD_OPTIONS}" +dpkg-buildpackage -b -uc -us + +echo "==> Collect artifacts into ${OUTPUT_DIR}" +for v in ${PG_VERSIONS}; do + cp -v "${WORK}"/postgresql-${v}-postgis-*_*.deb "${OUTPUT_DIR}/" + # Debug-symbol packages are emitted as .ddeb on Ubuntu. Ship them as .deb (the + # on-disk format is identical) so they flow through the same signing, + # verification and publishing as everything else. Consumers commonly install + # with an `*.deb` glob, which does not match `.ddeb`; because a dbgsym depends + # on its runtime with an exact `=` version, silently dropping it strands the + # previously installed dbgsym and breaks `apt --fix-broken install`. + for ddeb in "${WORK}"/postgresql-${v}-postgis-*-dbgsym_*.ddeb; do + [ -e "${ddeb}" ] || continue + cp -v "${ddeb}" "${OUTPUT_DIR}/$(basename "${ddeb}" .ddeb).deb" + done +done +cp -v "${WORK}"/*.buildinfo "${WORK}"/*.changes "${OUTPUT_DIR}/" 2>/dev/null || true + +echo "==> Verify runtime <-> dbgsym pairing" +for v in ${PG_VERSIONS}; do + rt="$(dpkg-deb -f "${OUTPUT_DIR}"/postgresql-${v}-postgis-3_*.deb Version)" + dep="$(dpkg-deb -f "${OUTPUT_DIR}"/postgresql-${v}-postgis-3-dbgsym_*.deb Depends)" + if [ "${dep}" != "postgresql-${v}-postgis-3 (= ${rt})" ]; then + echo "ERROR: PG${v} dbgsym wants '${dep}' but runtime is '${rt}'" >&2 + exit 1 + fi + echo " PG${v} OK (${rt})" +done + +echo "==> DONE. Packages:" +ls -1 "${OUTPUT_DIR}"/*.deb diff --git a/scripts/smoke_test_focal_postgis_debs b/scripts/smoke_test_focal_postgis_debs new file mode 100644 index 00000000..eacaa0bf --- /dev/null +++ b/scripts/smoke_test_focal_postgis_debs @@ -0,0 +1,143 @@ +#!/bin/bash +# +# smoke_test_focal_postgis_debs -- install the built PostGIS .deb set inside a +# clean Ubuntu 20.04 (focal) container and prove it actually works. +# +# The build pipeline already checks the packages exist, are signed, and pair +# correctly with their dbgsym. None of that proves the extension is usable. The +# failure mode this exists to catch is specific and silent: if the +# update-alternatives registration is wrong, `dpkg -l` reports the new version, +# `default_version` in the control file reads correctly, and the right contrib +# directory is on disk -- while CREATE EXTENSION postgis fails with +# Could not open extension control file ".../postgis.control" +# because the symlink was never created (or was removed with PGDG's -scripts +# package). A package-version assertion alone scores that as a successful patch. +# +# It therefore tests both paths that matter: +# A. clean install on a stock focal + PGDG PostgreSQL +# B. install *over* PGDG's PostGIS 3.5.3 (runtime + dbgsym + scripts), which +# is what an in-place upgrade on an existing host actually does +# +# Usage (inside a stock ubuntu:20.04 container, as root): +# DEBS_DIR=/debs smoke_test_focal_postgis_debs +# +# Overridable via environment: +# DEBS_DIR directory holding the *.deb set (default /debs) +# PG_VERSIONS majors to verify (default "12 13 14 15 16") +# EXPECTED_POSTGIS expected postgis_lib_version() (default 3.6.4) +# OLD_POSTGIS PGDG version to upgrade from in test B (default 3.5.3+dfsg-1~exp1.pgdg20.04+1) +# PGDG_ARCHIVE_SUITE archive suite to enable (default focal-pgdg) + +set -uo pipefail + +DEBS_DIR="${DEBS_DIR:-/debs}" +PG_VERSIONS="${PG_VERSIONS:-12 13 14 15 16}" +EXPECTED_POSTGIS="${EXPECTED_POSTGIS:-3.6.4}" +OLD_POSTGIS="${OLD_POSTGIS:-3.5.3+dfsg-1~exp1.pgdg20.04+1}" +PGDG_ARCHIVE_SUITE="${PGDG_ARCHIVE_SUITE:-focal-pgdg}" +PGDG_ARCHIVE_URL="${PGDG_ARCHIVE_URL:-https://apt-archive.postgresql.org/pub/repos/apt}" + +export DEBIAN_FRONTEND=noninteractive +export LANG=C.UTF-8 + +fail=0 +note() { echo "==> $*"; } +err() { echo "::error::$*" >&2; fail=1; } + +shopt -s nullglob +debs=("${DEBS_DIR}"/*.deb) +shopt -u nullglob +[ ${#debs[@]} -gt 0 ] || { echo "ERROR: no .deb files found in ${DEBS_DIR}" >&2; exit 1; } + +note "[1/6] Preparing container environment (${#debs[@]} packages)" +# postgresql-NN.postinst ends in `invoke-rc.d postgresql start $VERSION` under +# `set -e`, and there is no systemd in a container. A policy-rc.d denying the +# action makes invoke-rc.d return 0 so the postinst still creates the cluster; +# clusters are started explicitly below. +printf '#!/bin/sh\nexit 101\n' > /usr/sbin/policy-rc.d +chmod +x /usr/sbin/policy-rc.d + +apt-get update -qq +apt-get install -y -qq --no-install-recommends ca-certificates curl gnupg >/dev/null + +note "[2/6] Enabling the PGDG archive" +install -d /usr/share/keyrings +curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc \ + | gpg --dearmor -o /usr/share/keyrings/pgdg-archive.gpg +echo "deb [signed-by=/usr/share/keyrings/pgdg-archive.gpg] ${PGDG_ARCHIVE_URL} ${PGDG_ARCHIVE_SUITE} main 12 13 14 15 16" \ + > /etc/apt/sources.list.d/pgdg-archive.list +sed -i 's/^# deb \(.*universe\)/deb \1/' /etc/apt/sources.list +apt-get update -qq + +psql_v() { su postgres -c "psql --cluster $1/main -qtAX -c \"$2\"" 2>&1; } + +ensure_cluster() { + pg_lsclusters -h | awk '{print $1"/"$2}' | grep -qx "$1/main" \ + || pg_createcluster "$1" main >/dev/null 2>&1 + pg_ctlcluster "$1" main start >/dev/null 2>&1 +} + +note "[3/6] Scenario A: clean install" +for v in ${PG_VERSIONS}; do + apt-get install -y -qq postgresql-${v} >/dev/null 2>&1 +done +dpkg -i --force-all "${DEBS_DIR}"/*.deb >/dev/null 2>&1 +if ! apt-get --fix-broken install -y >/dev/null 2>&1; then + err "apt --fix-broken install failed after clean install" +fi + +for v in ${PG_VERSIONS}; do + ensure_cluster "${v}" + psql_v "${v}" "CREATE EXTENSION postgis;" >/dev/null 2>&1 + lib="$(psql_v "${v}" "SELECT postgis_lib_version();")" + ext="$(psql_v "${v}" "SELECT extversion FROM pg_extension WHERE extname='postgis';")" + alt="$(readlink -f /usr/share/postgresql/${v}/extension/postgis.control 2>/dev/null | xargs -r basename)" + if [ "${lib}" = "${EXPECTED_POSTGIS}" ] && [ "${ext}" = "${EXPECTED_POSTGIS}" ]; then + echo " PG${v} OK ext=${ext} lib=${lib} alternatives->${alt}" + else + err "PG${v} clean install: ext='${ext}' lib='${lib}' (expected ${EXPECTED_POSTGIS})" + fi + pg_ctlcluster "${v}" main stop >/dev/null 2>&1 +done + +note "[4/6] Scenario B: upgrade over PGDG PostGIS ${OLD_POSTGIS}" +for v in ${PG_VERSIONS}; do + apt-get remove -y -qq postgresql-${v}-postgis-3 >/dev/null 2>&1 + pg_dropcluster "${v}" main >/dev/null 2>&1 + pg_createcluster "${v}" main >/dev/null 2>&1 + apt-get install -y -qq --allow-downgrades \ + postgresql-${v}-postgis-3=${OLD_POSTGIS} \ + postgresql-${v}-postgis-3-dbgsym=${OLD_POSTGIS} \ + postgresql-${v}-postgis-3-scripts=${OLD_POSTGIS} >/dev/null 2>&1 +done + +# Mirrors how consumers apply the set: force-all dpkg, then let apt settle. +dpkg -i --force-all "${DEBS_DIR}"/*.deb >/dev/null 2>&1 +if ! apt-get --fix-broken install -y >/dev/null 2>&1; then + err "apt --fix-broken install failed after upgrade over ${OLD_POSTGIS}" +fi + +leftover="$(dpkg -l | awk '/postgresql-[0-9]+-postgis-3-scripts/ && $1=="ii" {print $2}')" +[ -z "${leftover}" ] || err "old -scripts package(s) survived the upgrade: ${leftover}" + +note "[5/6] Verifying the extension works after upgrade" +for v in ${PG_VERSIONS}; do + ensure_cluster "${v}" + out="$(psql_v "${v}" "CREATE EXTENSION postgis;")" + ext="$(psql_v "${v}" "SELECT extversion FROM pg_extension WHERE extname='postgis';")" + lib="$(psql_v "${v}" "SELECT postgis_lib_version();")" + area="$(psql_v "${v}" "SELECT round(ST_Area(ST_GeomFromText('POLYGON((0 0,1 0,1 1,0 1,0 0))',4326)::geography));")" + if [ "${ext}" = "${EXPECTED_POSTGIS}" ] && [ "${lib}" = "${EXPECTED_POSTGIS}" ] && [ "${area}" = "12308778361" ]; then + echo " PG${v} OK ext=${ext} lib=${lib} area=${area}" + else + err "PG${v} after upgrade: ext='${ext}' lib='${lib}' area='${area}' ${out}" + fi + pg_ctlcluster "${v}" main stop >/dev/null 2>&1 +done + +note "[6/6] Result" +if [ "${fail}" -ne 0 ]; then + echo "SMOKE TEST FAILED" >&2 + exit 1 +fi +echo "SMOKE TEST PASSED" From cdd323b01ae8094ec70c4cee99ee78c65743c976 Mon Sep 17 00:00:00 2001 From: Volkan Cetin Date: Fri, 21 Aug 2026 12:55:35 +0300 Subject: [PATCH 2/2] Make focal PostGIS scripts executable --- scripts/build_postgis_focal | 0 scripts/smoke_test_focal_postgis_debs | 0 2 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 scripts/build_postgis_focal mode change 100644 => 100755 scripts/smoke_test_focal_postgis_debs diff --git a/scripts/build_postgis_focal b/scripts/build_postgis_focal old mode 100644 new mode 100755 diff --git a/scripts/smoke_test_focal_postgis_debs b/scripts/smoke_test_focal_postgis_debs old mode 100644 new mode 100755