From af76d06c2df675554caa5685727f36739a5fe4db Mon Sep 17 00:00:00 2001 From: notplants Date: Thu, 18 Jun 2026 22:12:57 +0000 Subject: [PATCH] feat(db): use pgautoupgrade image instead of pgvector + bespoke pg_upgrade discourse core does not use the vector extension, so the pgvector image is unnecessary. Switch db to pgautoupgrade/pgautoupgrade:17-trixie, which handles major-version upgrades (old binaries, initdb, pg_upgrade) itself. pgautoupgrade assumes the old cluster's install user (oid 10) equals POSTGRES_USER and fails pg_upgrade otherwise (issue #115). A thin wrapper entrypoint detects the old install user when an upgrade is pending and exports POSTGRES_USER so pgautoupgrade matches it. Replaces the 64-line hand-rolled pg_upgrade entrypoint. --- abra.sh | 2 +- cc-db-entrypoint.sh | 35 ++++++++++++++++++++ compose.yml | 16 +++++----- entrypoint.postgres.sh.tmpl | 64 ------------------------------------- 4 files changed, 44 insertions(+), 73 deletions(-) create mode 100755 cc-db-entrypoint.sh delete mode 100644 entrypoint.postgres.sh.tmpl diff --git a/abra.sh b/abra.sh index 2fae15c..4ec5e19 100644 --- a/abra.sh +++ b/abra.sh @@ -1,4 +1,4 @@ -export DB_ENTRYPOINT_VERSION=v3 +export DB_ENTRYPOINT_VERSION=v4 export PG_BACKUP_VERSION=v2 export APP_ENTRYPOINT_VERSION=v2 export APP_INSTALL_SSL_VERSION=v1 diff --git a/cc-db-entrypoint.sh b/cc-db-entrypoint.sh new file mode 100755 index 0000000..f522602 --- /dev/null +++ b/cc-db-entrypoint.sh @@ -0,0 +1,35 @@ +#!/bin/bash +# Co-op Cloud wrapper around pgautoupgrade's entrypoint. +# +# pgautoupgrade runs pg_upgrade assuming the old cluster's bootstrap superuser +# (the "install user", oid 10) equals $POSTGRES_USER. That is not always true +# (e.g. a cluster bootstrapped with the default "postgres" superuser and a +# separate "discourse" app role), and a mismatch makes pg_upgrade's consistency +# check fail (pgautoupgrade issue #115). When a major-version upgrade is pending, +# detect the old cluster's real install user and run the upgrade as that user; +# pgautoupgrade handles everything else (old binaries, initdb, pg_upgrade). +set -e + +if [ -s "$PGDATA/PG_VERSION" ]; then + OLD_VERSION="$(cat "$PGDATA/PG_VERSION")" + TARGET_VERSION="${PGTARGET%%.*}" + + if [ -n "$OLD_VERSION" ] && [ -n "$TARGET_VERSION" ] && [ "$OLD_VERSION" != "$TARGET_VERSION" ]; then + # query the old cluster in single-user mode (no socket/auth needed) with + # pgautoupgrade's bundled old-version binaries + OLD_POSTGRES="/usr/local-pg${OLD_VERSION}/bin/postgres" + if [ -x "$OLD_POSTGRES" ]; then + INSTALL_USER="$(echo 'select rolname from pg_authid where oid = 10;' \ + | gosu postgres "$OLD_POSTGRES" --single -D "$PGDATA" template1 2>/dev/null \ + | sed -n 's/.*rolname = "\([^"]*\)".*/\1/p' | head -n1)" + if [ -n "$INSTALL_USER" ]; then + echo "cc-db-entrypoint: old cluster (pg$OLD_VERSION) install user is '$INSTALL_USER'; running the pg$TARGET_VERSION upgrade as that user" + export POSTGRES_USER="$INSTALL_USER" + else + echo "cc-db-entrypoint: WARNING could not detect old install user; letting pgautoupgrade use POSTGRES_USER=$POSTGRES_USER" + fi + fi + fi +fi + +exec /usr/local/bin/docker-entrypoint.sh "$@" diff --git a/compose.yml b/compose.yml index 5a3e717..80907ca 100644 --- a/compose.yml +++ b/compose.yml @@ -63,7 +63,7 @@ services: start_period: 25m db: - image: pgvector/pgvector:pg17 + image: pgautoupgrade/pgautoupgrade:17-trixie networks: - internal secrets: @@ -72,25 +72,26 @@ services: - 'postgresql_data:/var/lib/postgresql/data' configs: - source: db_entrypoint - target: /docker-entrypoint.sh + target: /usr/local/bin/cc-db-entrypoint.sh mode: 0555 - source: pg_backup target: /pg_backup.sh mode: 0555 - entrypoint: /docker-entrypoint.sh + entrypoint: /usr/local/bin/cc-db-entrypoint.sh environment: - POSTGRES_HOST_AUTH_METHOD=trust - POSTGRES_USER=discourse - POSTGRES_DB=discourse - POSTGRES_PASSWORD_FILE=/run/secrets/db_password + - PGTARGET=17 healthcheck: test: "pg_isready -U discourse -d discourse" interval: 30s timeout: 10s retries: 5 - # generous: a postgres major-version upgrade (apt install + pg_upgrade) runs - # in the entrypoint before the server accepts connections — don't let the - # healthcheck kill an in-progress migration + # generous: pgautoupgrade may run an in-place pg_upgrade on deploy before + # the server accepts connections — don't let the healthcheck kill an + # in-progress migration start_period: 10m deploy: labels: @@ -140,8 +141,7 @@ configs: file: migrate-uploads.sh db_entrypoint: name: ${STACK_NAME}_db_entrypoint_${DB_ENTRYPOINT_VERSION} - file: entrypoint.postgres.sh.tmpl - template_driver: golang + file: cc-db-entrypoint.sh pg_backup: name: ${STACK_NAME}_pg_backup_${PG_BACKUP_VERSION} file: pg_backup.sh diff --git a/entrypoint.postgres.sh.tmpl b/entrypoint.postgres.sh.tmpl deleted file mode 100644 index cbd032b..0000000 --- a/entrypoint.postgres.sh.tmpl +++ /dev/null @@ -1,64 +0,0 @@ -#!/bin/bash - -set -e - -OLDDATA=$PGDATA/old_data -NEWDATA=$PGDATA/new_data - -echo "Running as $(id)" - -# The migration uses $OLDDATA/$NEWDATA as scratch and removes them when it -# finishes; a leftover *empty* one means a run was interrupted before any data -# moved (data still intact at $PGDATA) so we clear it and retry, while a -# *non-empty* one means data may live only there, so we stop for manual recovery. -for scratch in $OLDDATA $NEWDATA; do - if [ -d "$scratch" ] && [ -n "$(ls -A "$scratch")" ]; then - echo "FATAL: $scratch exists and is not empty - a previous migration did not" - echo "complete and the data may only exist there. manual recovery necessary." - exit 1 - fi -done -rm -rf $OLDDATA $NEWDATA - -if [ -f $PGDATA/PG_VERSION ]; then - DATA_VERSION=$(cat $PGDATA/PG_VERSION) - - if [ -n "$DATA_VERSION" -a "$PG_MAJOR" != "$DATA_VERSION" ]; then - echo "postgres data version $DATA_VERSION found, but need $PG_MAJOR. Starting migration" - echo "Installing postgres $DATA_VERSION" - sed -i "s/$/ $DATA_VERSION/" /etc/apt/sources.list.d/pgdg.list - apt-get update && apt-get install -y --no-install-recommends \ - postgresql-$DATA_VERSION \ - && rm -rf /var/lib/apt/lists/* - # pg_upgrade must run as the old cluster's bootstrap superuser (the "install - # user", oid 10), and the new cluster must be initialised with that same - # user. It is not necessarily $POSTGRES_USER (e.g. clusters created with the - # default "postgres" superuser and a separate app role), so read it from the - # old cluster: briefly start it and ask, connecting as the app role we know. - PGBIN=/usr/lib/postgresql/$DATA_VERSION/bin - gosu postgres $PGBIN/pg_ctl -D $PGDATA -w \ - -o "-c listen_addresses= -c unix_socket_directories=/tmp" start - INSTALL_USER=$(gosu postgres psql -h /tmp -U "$POSTGRES_USER" -d postgres -tAc \ - "select rolname from pg_roles where oid = 10") - gosu postgres $PGBIN/pg_ctl -D $PGDATA -w stop - echo "old cluster install user: $INSTALL_USER" - echo "shuffling around" - gosu postgres mkdir $OLDDATA $NEWDATA - chmod 700 $OLDDATA $NEWDATA - mv $PGDATA/* $OLDDATA/ || true - echo "running initdb" - # abuse entrypoint script for initdb by making server error out; initialise - # the new cluster with the same superuser as the old one so pg_upgrade matches - gosu postgres bash -c "export PGDATA=$NEWDATA POSTGRES_USER=$INSTALL_USER ; /usr/local/bin/docker-entrypoint.sh --invalid-arg || true" - echo "running pg_upgrade" - cd /tmp - gosu postgres pg_upgrade --link -b /usr/lib/postgresql/$DATA_VERSION/bin -d $OLDDATA -D $NEWDATA -U $INSTALL_USER - cp $OLDDATA/pg_hba.conf $NEWDATA/ - mv $NEWDATA/* $PGDATA - rm -rf $OLDDATA - rmdir $NEWDATA - echo "migration complete" - fi -fi - -/usr/local/bin/docker-entrypoint.sh postgres