From d5a4fd3eee8cbf10acf2b2247ad161c8510f8d86 Mon Sep 17 00:00:00 2001 From: blits Date: Tue, 6 Oct 2026 18:22:34 +0400 Subject: [PATCH 1/2] fix(docker): build from the release tarball, on a pinned Debian, after the release exists - The Dockerfile piped main's install.sh, which now requires RELEASE_VERSION and .sha256 sidecars that no release ships, so the next image build would fail. The release tarball is already laid out as the Debian Postgres paths: download it for the target arch and unpack it into /. - postgres: moved to Debian trixie (glibc 2.41). Swapping a bookworm data directory (glibc 2.36, every image up to v0.1.0-docker) onto it changes text collation: Postgres warns about a collation version mismatch and text indexes need a REINDEX. Build both bases and tag - and --; the existing tags keep pointing at trixie. - The image job waited only for the build job, while the assets it downloads come from the release job. - Readme: the tags, and ALTER EXTENSION typeid UPDATE right after switching images (0.1.0's min/max functions are gone from the new library until the update runs). Co-Authored-By: Claude Opus 5.5 --- .github/workflows/release.yaml | 21 +++++++++++++++++---- Dockerfile | 21 +++++++++++++++++---- Readme.md | 4 ++++ 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 0debcbe..950e3f2 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -152,12 +152,15 @@ jobs: # 2. Build & push the multi-arch Docker image # ------------------------------------------------------------- build-and-push-docker: - needs: build-and-publish + # The image downloads the release's assets, so the release must exist first. + needs: release runs-on: ubuntu-latest strategy: fail-fast: false matrix: pg_version: [14, 15, 16, 17, 18] + # Both Debian bases of the official postgres image: a data directory must keep its glibc (collation). + debian: [bookworm, trixie] steps: - uses: actions/checkout@v7 @@ -175,15 +178,23 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Release version + id: version + run: echo "release=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT" + - name: Docker meta id: meta uses: docker/metadata-action@v6 with: images: ${{ env.DOCKER_IMAGE }} + # `-` follows the latest release, `--` pins one (the one to deploy). + # The bare ``, `latest` and `` tags stay as they were: trixie, so nobody's glibc changes. tags: | - type=raw,value=${{ matrix.pg_version }} - type=raw,value=latest,enable=${{ matrix.pg_version == 18 }} - type=raw,value=${{ github.ref_name }},enable=${{ matrix.pg_version == 18 }} + type=raw,value=${{ matrix.pg_version }}-${{ matrix.debian }} + type=raw,value=${{ github.ref_name }}-${{ matrix.pg_version }}-${{ matrix.debian }} + type=raw,value=${{ matrix.pg_version }},enable=${{ matrix.debian == 'trixie' }} + type=raw,value=latest,enable=${{ matrix.pg_version == 18 && matrix.debian == 'trixie' }} + type=raw,value=${{ github.ref_name }},enable=${{ matrix.pg_version == 18 && matrix.debian == 'trixie' }} - name: Build & push uses: docker/build-push-action@v7 @@ -193,6 +204,8 @@ jobs: platforms: linux/amd64,linux/arm64 build-args: | PG_VERSION=${{ matrix.pg_version }} + DEBIAN=${{ matrix.debian }} + RELEASE_VERSION=${{ steps.version.outputs.release }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} diff --git a/Dockerfile b/Dockerfile index bdbb587..9a5a2b9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,21 @@ ARG PG_VERSION=18 -FROM postgres:${PG_VERSION} +# The Debian release under Postgres. Pin it to keep a data directory's glibc: another glibc collates text differently, +# so text indexes built before would need a REINDEX (Postgres warns "collation version mismatch"). +ARG DEBIAN=trixie +FROM postgres:${PG_VERSION}-${DEBIAN} -RUN apt update && apt install -y curl -RUN curl -sSL https://raw.githubusercontent.com/blitss/typeid-postgres/main/install.sh | bash +ARG PG_VERSION +# The release whose binaries to install, without the leading "v" (e.g. 0.4.1). +ARG RELEASE_VERSION +# Set by buildx: amd64 | arm64, as the release assets are named. +ARG TARGETARCH + +# The release tarball is laid out as the Debian Postgres paths (usr/lib/postgresql//lib, usr/share/postgresql//extension). +RUN test -n "${RELEASE_VERSION}" || { echo "build-arg RELEASE_VERSION is required (e.g. 0.4.1)" >&2; exit 1; } \ + && apt-get update && apt-get install -y --no-install-recommends curl ca-certificates && rm -rf /var/lib/apt/lists/* \ + && curl -fsSL --retry 3 "https://github.com/blitss/typeid-postgres/releases/download/v${RELEASE_VERSION}/typeid-pg${PG_VERSION}-linux-${TARGETARCH}.tar.gz" \ + | tar -xz -C / \ + && test -f "/usr/lib/postgresql/${PG_VERSION}/lib/typeid.so" # Enable the extension -RUN echo "shared_preload_libraries = 'typeid'" >> /usr/share/postgresql/postgresql.conf.sample \ No newline at end of file +RUN echo "shared_preload_libraries = 'typeid'" >> /usr/share/postgresql/postgresql.conf.sample diff --git a/Readme.md b/Readme.md index e35d753..89a6bb6 100644 --- a/Readme.md +++ b/Readme.md @@ -49,6 +49,10 @@ There are multiple ways to use Postgres with TypeID extension: docker pull ghcr.io/blitss/typeid-pg:latest ``` +Tags: `-` follows the latest release (e.g. `16-bookworm`), `--` pins one (e.g. `v0.4.1-16-bookworm`). The bare ``, `latest` and `` tags are the trixie builds. For an existing data directory keep the Debian release it was created on: a different glibc collates text differently, and Postgres then warns about a collation version mismatch (text indexes need a `REINDEX`). The images before 0.2.0 (`v0.1.0-docker`) are bookworm. + +After switching to a newer image, run `ALTER EXTENSION typeid UPDATE;` in each database right away: the new library no longer exports some functions older versions' SQL objects point to (`min`/`max` over typeids fail until the update), and the update brings the IMMUTABLE comparisons (0.2.0, without them Postgres never hash- or merge-joins on a typeid) and the operators' selectivity estimators (0.4.0). + You can see how it's built [here](https://github.com/blitss/typeid-postgres/blob/main/Dockerfile) 2) By using install script and downloading pre-built extension (must have Postgres installed and `pg_config` exposed in path) From c13f1a4c99f705af1c6ee2d5949dd400b4037d58 Mon Sep 17 00:00:00 2001 From: blits Date: Tue, 6 Oct 2026 18:31:49 +0400 Subject: [PATCH 2/2] fix(release): ship the archives install.sh expects, and let it accept any release - Package lib/ and share/extension/ (files only) with a `.sha256` sidecar; the tarballs carried the build host's Debian paths and no checksum, so install.sh refused every published release. - install.sh checks the archive by pattern instead of a fixed member list (which stopped at 0.4.0): one library (.so or .dylib), the control file, typeid--.sql required, other typeid--*.sql allowed. - The image installs with install.sh again (copied from the build context, RELEASE_VERSION passed). - test-migrations installs the latest release with install.sh when it has a checksum, else unpacks the old Debian-layout tarball; it called install.sh without RELEASE_VERSION, so it failed on every PR. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/release.yaml | 31 +++++++++++-- .github/workflows/test-migrations.yaml | 17 ++++++- Dockerfile | 15 ++---- install.sh | 64 ++++++++++++-------------- 4 files changed, 76 insertions(+), 51 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 950e3f2..e84f666 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -133,19 +133,41 @@ jobs: echo "ARCH=$ARCH" >> $GITHUB_ENV # --- Tarball artifact -------------------------------------------- + # The layout install.sh expects, independent of the build host's paths: lib/ and + # share/extension/, files only (no directory entries), plus a `.sha256` + # sidecar (" "). - name: Package Extension + shell: bash + env: + COPYFILE_DISABLE: "1" # no macOS ._ entries run: | + set -euo pipefail + if [ "${{ runner.os }}" == "Linux" ]; then + PG_CONFIG_PATH="/usr/lib/postgresql/${{ matrix.pg_version }}/bin/pg_config" + else + PG_CONFIG_PATH="/opt/homebrew/opt/postgresql@${{ matrix.pg_version }}/bin/pg_config" + fi + PKG="target/release/${{ env.EXTENSION_NAME }}-pg${{ matrix.pg_version }}" + STAGE="$(mktemp -d)" + mkdir -p "$STAGE/lib" "$STAGE/share/extension" + cp "$PKG$("$PG_CONFIG_PATH" --pkglibdir)"/${{ env.EXTENSION_NAME }}.{so,dylib} "$STAGE/lib/" 2>/dev/null || true + [ "$(ls "$STAGE/lib" | wc -l)" -eq 1 ] || { echo "::error::expected exactly one library in $PKG"; ls -R "$PKG"; exit 1; } + cp "$PKG$("$PG_CONFIG_PATH" --sharedir)"/extension/${{ env.EXTENSION_NAME }}* "$STAGE/share/extension/" + ASSET="${{ env.EXTENSION_NAME }}-pg${{ matrix.pg_version }}-${{ env.LOWERCASE_OS }}-${{ env.ARCH }}.tar.gz" mkdir -p release - tar -czvf \ - release/${{ env.EXTENSION_NAME }}-pg${{ matrix.pg_version }}-${{ env.LOWERCASE_OS }}-${{ env.ARCH }}.tar.gz \ - -C target/release/${{ env.EXTENSION_NAME }}-pg${{ matrix.pg_version }} . + (cd "$STAGE" && find lib share -type f | LC_ALL=C sort | tar -czf "$GITHUB_WORKSPACE/release/$ASSET" -T -) + cd release + if command -v sha256sum >/dev/null; then sha256sum "$ASSET" > "$ASSET.sha256"; else shasum -a 256 "$ASSET" > "$ASSET.sha256"; fi + tar -tzf "$ASSET"; cat "$ASSET.sha256" # --- Upload artifact for later ----------------------------------- - name: Upload artifact uses: actions/upload-artifact@v7 with: name: release-assets-pg${{ matrix.pg_version }}-${{ env.LOWERCASE_OS }}-${{ env.ARCH }} - path: release/*.tar.gz + path: | + release/*.tar.gz + release/*.tar.gz.sha256 retention-days: 29 # ------------------------------------------------------------- @@ -295,5 +317,6 @@ jobs: draft: false files: | release/*.tar.gz + release/*.tar.gz.sha256 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test-migrations.yaml b/.github/workflows/test-migrations.yaml index 9a40fb4..a9f241a 100644 --- a/.github/workflows/test-migrations.yaml +++ b/.github/workflows/test-migrations.yaml @@ -27,9 +27,22 @@ jobs: sudo apt-get install postgresql-16 postgresql-server-dev-16 - name: Install previous latest version + env: + GH_TOKEN: ${{ github.token }} run: | - # Install.sh will by default install the latest stable release that we'll need to test against. - sudo bash install.sh + set -euo pipefail + # The latest published release is what installs upgrade from. + VERSION="$(gh release view --repo "${{ github.repository }}" --json tagName --jq .tagName)" + VERSION="${VERSION#v}" + ASSET="typeid-pg16-linux-amd64.tar.gz" + BASE="https://github.com/${{ github.repository }}/releases/download/v${VERSION}" + if curl -fsIL "$BASE/$ASSET.sha256" >/dev/null; then + sudo RELEASE_VERSION="$VERSION" bash install.sh + else + # Releases up to 0.4.1 ship no checksum and lay the tarball out as the Debian paths. + curl -fsSL "$BASE/$ASSET" | sudo tar -xz -C / + fi + echo "previous version: $VERSION" - name: Start PostgreSQL and create test database run: | diff --git a/Dockerfile b/Dockerfile index 9a5a2b9..d78515d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,18 +4,13 @@ ARG PG_VERSION=18 ARG DEBIAN=trixie FROM postgres:${PG_VERSION}-${DEBIAN} -ARG PG_VERSION -# The release whose binaries to install, without the leading "v" (e.g. 0.4.1). +# The release to install, without the leading "v" (e.g. 0.4.2); its assets must exist (the release job runs first). ARG RELEASE_VERSION -# Set by buildx: amd64 | arm64, as the release assets are named. -ARG TARGETARCH -# The release tarball is laid out as the Debian Postgres paths (usr/lib/postgresql//lib, usr/share/postgresql//extension). -RUN test -n "${RELEASE_VERSION}" || { echo "build-arg RELEASE_VERSION is required (e.g. 0.4.1)" >&2; exit 1; } \ - && apt-get update && apt-get install -y --no-install-recommends curl ca-certificates && rm -rf /var/lib/apt/lists/* \ - && curl -fsSL --retry 3 "https://github.com/blitss/typeid-postgres/releases/download/v${RELEASE_VERSION}/typeid-pg${PG_VERSION}-linux-${TARGETARCH}.tar.gz" \ - | tar -xz -C / \ - && test -f "/usr/lib/postgresql/${PG_VERSION}/lib/typeid.so" +RUN apt-get update && apt-get install -y --no-install-recommends curl ca-certificates && rm -rf /var/lib/apt/lists/* +COPY install.sh /tmp/install.sh +RUN test -n "${RELEASE_VERSION}" || { echo "build-arg RELEASE_VERSION is required (e.g. 0.4.2)" >&2; exit 1; } \ + && RELEASE_VERSION="${RELEASE_VERSION}" bash /tmp/install.sh && rm /tmp/install.sh # Enable the extension RUN echo "shared_preload_libraries = 'typeid'" >> /usr/share/postgresql/postgresql.conf.sample diff --git a/install.sh b/install.sh index 132966f..14d2f2b 100755 --- a/install.sh +++ b/install.sh @@ -111,10 +111,6 @@ EXTENSION_DIR="$SHARE_DIR/extension" ASSET_BASENAME="typeid-pg${PG_MAJOR}-${OS}-${ARCH}.tar.gz" CHECKSUM_BASENAME="${ASSET_BASENAME}.sha256" -LIBRARY_NAME=typeid.so -if [ "$OS" = darwin ]; then - LIBRARY_NAME=typeid.dylib -fi if [ "$OS" = linux ]; then require_command sha256sum @@ -185,16 +181,12 @@ DIGEST=${CHECKSUM_LINE:0:64} "${CHECKSUM_COMMAND[@]}" "$CHECKSUM_BASENAME" ) -EXPECTED_MEMBERS=( - "lib/$LIBRARY_NAME" +# The archive holds files only: one library (lib/typeid.so or lib/typeid.dylib), the control file, the full script of +# RELEASE_VERSION and any number of other versioned / upgrade scripts (share/extension/typeid--*.sql). +SQL_MEMBER_PATTERN='^share/extension/typeid--[0-9A-Za-z.+-]+(--[0-9A-Za-z.+-]+)?\.sql$' +REQUIRED_MEMBERS=( "share/extension/typeid.control" - "share/extension/typeid--0.1.0.sql" - "share/extension/typeid--0.2.0.sql" - "share/extension/typeid--0.3.0.sql" - "share/extension/typeid--0.4.0.sql" - "share/extension/typeid--0.1.0--0.2.0.sql" - "share/extension/typeid--0.2.0--0.3.0.sql" - "share/extension/typeid--0.3.0--0.4.0.sql" + "share/extension/typeid--${RELEASE_VERSION}.sql" ) MEMBER_LIST="$TMP_DIR/members" @@ -213,14 +205,16 @@ while IFS= read -r member || [ -n "$member" ]; do [[ ! "$member" =~ (^|/)\.\.(/|$) ]] || die "Archive contains a parent-path component: $member" - expected=false - for expected_member in "${EXPECTED_MEMBERS[@]}"; do - if [ "$member" = "$expected_member" ]; then - expected=true - break - fi - done - [ "$expected" = true ] || die "Archive contains an unexpected member: $member" + case "$member" in + lib/typeid.so | lib/typeid.dylib) + [ -z "${LIBRARY_MEMBER:-}" ] || die "Archive contains more than one library" + LIBRARY_MEMBER=$member + ;; + share/extension/typeid.control) ;; + *) + [[ "$member" =~ $SQL_MEMBER_PATTERN ]] || die "Archive contains an unexpected member: $member" + ;; + esac case "$SEEN_MEMBERS" in *$'\n'"$member"$'\n'*) die "Archive contains a duplicate member: $member" ;; @@ -228,12 +222,11 @@ while IFS= read -r member || [ -n "$member" ]; do SEEN_MEMBERS+="$member"$'\n' done <"$MEMBER_LIST" -[ "$MEMBER_COUNT" -eq "${#EXPECTED_MEMBERS[@]}" ] || - die "Archive is missing one or more required members" -for expected_member in "${EXPECTED_MEMBERS[@]}"; do +[ -n "${LIBRARY_MEMBER:-}" ] || die "Archive is missing the library (lib/typeid.so or lib/typeid.dylib)" +for required_member in "${REQUIRED_MEMBERS[@]}"; do case "$SEEN_MEMBERS" in - *$'\n'"$expected_member"$'\n'*) ;; - *) die "Archive is missing required member: $expected_member" ;; + *$'\n'"$required_member"$'\n'*) ;; + *) die "Archive is missing required member: $required_member" ;; esac done @@ -251,17 +244,18 @@ install -d -m 0700 "$EXTRACT_DIR" tar -xzf "$ARCHIVE_PATH" -C "$EXTRACT_DIR" || die "Unable to extract archive" -for expected_member in "${EXPECTED_MEMBERS[@]}"; do - extracted_path="$EXTRACT_DIR/$expected_member" +while IFS= read -r member || [ -n "$member" ]; do + extracted_path="$EXTRACT_DIR/$member" [ -f "$extracted_path" ] && [ ! -L "$extracted_path" ] || - die "Extracted member is not a regular file: $expected_member" -done + die "Extracted member is not a regular file: $member" +done <"$MEMBER_LIST" -install -m 0755 "$EXTRACT_DIR/lib/$LIBRARY_NAME" "$LIB_DIR/$LIBRARY_NAME" -for expected_member in "${EXPECTED_MEMBERS[@]:1}"; do - extension_name=${expected_member##*/} - install -m 0644 "$EXTRACT_DIR/$expected_member" "$EXTENSION_DIR/$extension_name" -done +install -m 0755 "$EXTRACT_DIR/$LIBRARY_MEMBER" "$LIB_DIR/${LIBRARY_MEMBER#lib/}" +while IFS= read -r member || [ -n "$member" ]; do + case "$member" in + share/extension/*) install -m 0644 "$EXTRACT_DIR/$member" "$EXTENSION_DIR/${member##*/}" ;; + esac +done <"$MEMBER_LIST" printf 'TypeID %s installed for PostgreSQL %s (%s/%s).\n' \ "$RELEASE_VERSION" "$PG_MAJOR" "$OS" "$ARCH" \ No newline at end of file