Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
225 changes: 225 additions & 0 deletions .github/workflows/build-pg-focal.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
name: Build PostgreSQL core (focal)

# Builds PostgreSQL *core* .deb packages for Ubuntu 20.04 (focal) by rebuilding
# the official Debian source package (PGDG dropped focal binaries upstream), then
# signs them with debsigs (--sign=maint) using the existing packaging key.
#
# One parameterized pipeline covers every focal-buildable major (PG 13..17): a
# matrix builds them all by default; workflow_dispatch can target a single major
# and pin its minor / orig sha256.
#
# This is intentionally a *standalone* pipeline: it does not use the extension
# build flow (citus_package / pg_buildext / the build-package.yml matrix), which
# assumes PostgreSQL itself comes from PGDG.

on:
workflow_dispatch:
inputs:
pg_major:
description: "PostgreSQL major to build"
required: true
type: choice
default: "all"
options:
- "all"
- "13"
- "14"
- "15"
- "16"
- "17"
pg_upstream_version:
description: "Optional: pin the minor (e.g. 16.15). Blank = latest. Only used when a single major is selected."
required: false
default: ""
pg_orig_sha256:
description: "Optional: pin sha256 of the orig.tar.bz2 (blank = auto-resolve from the official .dsc). Only used when a single major is selected."
required: false
default: ""
run_tests:
description: "Run upstream regression suite (1=yes, slower)"
required: false
default: "0"
push:
branches:
- pg-focal

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
setup:
name: Resolve build matrix
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.matrix.outputs.matrix }}
steps:
- name: Compute pg_major matrix
id: matrix
run: |
sel="${{ github.event.inputs.pg_major }}"
if [ -z "${sel}" ] || [ "${sel}" = "all" ]; then
echo 'matrix={"pg_major":["13","14","15","16","17"]}' >> "$GITHUB_OUTPUT"
else
echo "matrix={\"pg_major\":[\"${sel}\"]}" >> "$GITHUB_OUTPUT"
fi

build-and-sign:
needs: setup
name: Build & sign PG${{ matrix.pg_major }} (focal)
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix: ${{ fromJSON(needs.setup.outputs.matrix) }}
env:
PACKAGING_SECRET_KEY: ${{ secrets.PACKAGING_SECRET_KEY }}
PACKAGING_PASSPHRASE: ${{ secrets.PACKAGING_PASSPHRASE }}
PG_MAJOR: ${{ matrix.pg_major }}
# Minor / sha pins only make sense for a single explicitly-selected major;
# ignore them on the "all" matrix so each major still auto-resolves latest.
PG_UPSTREAM_VERSION: ${{ github.event.inputs.pg_major != 'all' && github.event.inputs.pg_upstream_version || '' }}
PG_ORIG_SHA256: ${{ github.event.inputs.pg_major != 'all' && github.event.inputs.pg_orig_sha256 || '' }}
RUN_TESTS: ${{ github.event.inputs.run_tests || '0' }}
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USER_NAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}

- name: Build focal builder image
run: |
docker build -t focal-pg-builder \
-f dockerfiles/focal-pg-builder/Dockerfile .

- name: Build PostgreSQL ${{ matrix.pg_major }} packages
run: |
mkdir -p packages
docker run --rm \
-e PG_MAJOR="${PG_MAJOR}" \
-e PG_UPSTREAM_VERSION="${PG_UPSTREAM_VERSION}" \
-e PG_ORIG_SHA256="${PG_ORIG_SHA256}" \
-e RUN_TESTS="${RUN_TESTS}" \
-v "${PWD}/packages:/packages" \
focal-pg-builder
echo "Built packages:"
ls -1 "packages/focal/pg${PG_MAJOR}"/*.deb

- name: Sign packages (debsigs --sign=maint)
# Use the prebuilt, deployed debsigner image (the one all Citus signing
# uses), not a locally built copy of dockerfiles/debsigner, which has
# drifted from it. Mirrors tools.packaging_automation.citus_package.
#
# The signer's entrypoint signs exactly "/packages/*/*.deb" (one dir
# level deep), so mount the parent of the per-major output dir: with
# "${PWD}/packages/focal:/packages" the debs land at /packages/pg<major>/*.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/pg${PG_MAJOR}"/*.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: Upload signed packages
uses: actions/upload-artifact@v4
with:
name: postgresql-${{ matrix.pg_major }}-focal-deb
path: |
packages/focal/pg${{ matrix.pg_major }}/*.deb
packages/focal/pg${{ matrix.pg_major }}/*.changes
packages/focal/pg${{ matrix.pg_major }}/*.buildinfo
if-no-files-found: error

assemble:
needs: build-and-sign
name: Assemble combined co-installable set
runs-on: ubuntu-latest
steps:
- name: Download all per-major package sets
uses: actions/download-artifact@v4
with:
pattern: postgresql-*-focal-deb
path: per-major # -> per-major/postgresql-<major>-focal-deb/*.deb

- name: Assemble de-duplicated set
# PostgreSQL builds six shared, single-instance system libraries from
# *every* major's source -- libpq5, libpq-dev, libpgtypes3, libecpg6,
# libecpg-dev, libecpg-compat3 (plus their -dbgsym). They share one
# package name but carry a per-major version, so they cannot be
# co-installed; the newest copy satisfies every major's ">=" dependency.
# Keep all per-major packages from every major, but keep the shared
# libraries only from the highest major present. The result installs
# 13..17 side by side with no conflicts. Packages stay byte-identical
# (already signed) -- we only copy, never repackage.
run: |
set -euo pipefail
shared_re='^(libpq5|libpq-dev|libpgtypes3|libecpg6|libecpg-dev|libecpg-compat3)(-dbgsym)?$'

# Discover the majors we actually received and pick the highest.
majors="$(find per-major -maxdepth 1 -type d -name 'postgresql-*-focal-deb' \
| sed -E 's#.*/postgresql-([0-9]+)-focal-deb#\1#' | sort -n)"
[ -n "${majors}" ] || { echo "::error::no per-major artifacts found" >&2; exit 1; }
newest="$(echo "${majors}" | tail -1)"
echo "majors present: $(echo ${majors} | tr '\n' ' '); shared libs taken from PG${newest}"

mkdir -p postgresql-all-focal
for m in ${majors}; do
for deb in per-major/postgresql-${m}-focal-deb/*.deb; do
[ -e "${deb}" ] || continue
pkg="$(dpkg-deb -f "${deb}" Package)"
if [[ "${pkg}" =~ ${shared_re} ]] && [ "${m}" != "${newest}" ]; then
echo " skip shared ${pkg} from PG${m} (kept from PG${newest})"
continue
fi
cp "${deb}" postgresql-all-focal/
done
done

echo "==> Assembled $(ls postgresql-all-focal/*.deb | wc -l) packages"
echo "==> Shared libraries in the combined set (must be exactly one version each):"
for deb in postgresql-all-focal/*.deb; do
pkg="$(dpkg-deb -f "${deb}" Package)"
if [[ "${pkg}" =~ ${shared_re} ]]; then
dpkg-deb -f "${deb}" Package Version | tr '\n' ' '; echo
fi
done | sort -u

- name: Verify the combined set has no duplicate package names
run: |
set -euo pipefail
dupes="$(for deb in postgresql-all-focal/*.deb; do dpkg-deb -f "${deb}" Package; done \
| sort | uniq -d)"
if [ -n "${dupes}" ]; then
echo "::error::duplicate package names in combined set (would conflict on install):" >&2
echo "${dupes}" >&2
exit 1
fi
echo "OK: every package name appears exactly once"

- name: Upload combined co-installable set
uses: actions/upload-artifact@v4
with:
name: postgresql-all-focal
path: postgresql-all-focal/*.deb
if-no-files-found: error
67 changes: 67 additions & 0 deletions dockerfiles/focal-pg-builder/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# vim:set ft=dockerfile:
#
# Generic builder image for PostgreSQL *core* packages targeting Ubuntu 20.04
# (focal). One image serves every focal-buildable major (PG 13, 14, 15, 16, 17);
# the major is selected at run time via PG_MAJOR (see scripts/build_pg_focal).
#
# Why this exists:
# apt.postgresql.org (PGDG) no longer ships PostgreSQL binaries for focal
# (focal reached EOL standard support 2025-04). To get a newer minor on focal
# we rebuild the official Debian source package ourselves.
#
# Strategy (validated):
# - Upstream tarball: postgresql-<major>_<NEW>.orig.tar.bz2 (from the live pool)
# - Debian packaging: the frozen focal-era debian/ (the last
# "<major>.<x>-N.pgdg20.04+1" PGDG built for focal), because its build profile
# uses focal's *default* toolchain (clang/llvm-dev = LLVM 10), so the
# resulting JIT depends on focal's libllvm10 (installable on focal), unlike
# newer packaging which requires clang-19/llvm-19.
# - Build tooling (debhelper 13, dh-exec, postgresql-common-dev) is restored
# from the PGDG *archive* (apt-archive.postgresql.org), which keeps the
# removed focal-pgdg suite.
#
# The heavy lifting lives in scripts/build_pg_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; \
# The focal-pgdg suite exposes a 'main' component (build tooling) plus a
# per-major component for each PostgreSQL major. List every focal-buildable
# major so the same image works for PG 13..17.
echo "deb [signed-by=/usr/share/keyrings/pgdg-archive.gpg] https://apt-archive.postgresql.org/pub/repos/apt focal-pgdg main 13 14 15 16 17" \
> /etc/apt/sources.list.d/pgdg-archive.list; \
# make sure 'universe' is enabled (clang / llvm-dev live there on focal)
sed -i 's/^# deb \(.*universe\)/deb \1/' /etc/apt/sources.list; \
apt-get update; \
# base build tooling; the per-build Build-Depends are resolved at run time
# by scripts/build_pg_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 \
xz-utils \
bzip2; \
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_pg_focal /usr/local/bin/build_pg_focal
RUN chmod +x /usr/local/bin/build_pg_focal

VOLUME /packages
ENTRYPOINT ["/usr/local/bin/build_pg_focal"]
Loading
Loading