|
| 1 | +#!/usr/bin/env bash |
| 2 | +set -euo pipefail |
| 3 | + |
| 4 | +OLD_VER="$(cat "${PGDATA}/PG_VERSION")" |
| 5 | +NEW_VER="${PG_MAJOR}" |
| 6 | + |
| 7 | +if [ "$OLD_VER" = "$NEW_VER" ]; then |
| 8 | + exit 0 |
| 9 | +fi |
| 10 | + |
| 11 | +if [ "$OLD_VER" -gt "$NEW_VER" ]; then |
| 12 | + echo "pg-auto-upgrade: refusing to downgrade PGDATA from PG${OLD_VER} to PG${NEW_VER}" >&2 |
| 13 | + exit 1 |
| 14 | +fi |
| 15 | + |
| 16 | +OLD_BIN="/usr/lib/postgresql/${OLD_VER}/bin" |
| 17 | +NEW_BIN="/usr/lib/postgresql/${NEW_VER}/bin" |
| 18 | + |
| 19 | +if [ ! -x "$OLD_BIN/pg_upgrade" ]; then |
| 20 | + echo "pg-auto-upgrade: PG${OLD_VER} binaries are not installed in this image; cannot upgrade." >&2 |
| 21 | + echo "pg-auto-upgrade: rebuild with LEGACY_PG_VERSIONS including ${OLD_VER}." >&2 |
| 22 | + exit 1 |
| 23 | +fi |
| 24 | + |
| 25 | +echo "pg-auto-upgrade: migrating PGDATA from PG${OLD_VER} to PG${NEW_VER}" |
| 26 | + |
| 27 | +# Stage both clusters as subdirectories of PGDATA itself. This keeps them |
| 28 | +# on the same filesystem (required for pg_upgrade --link) regardless of |
| 29 | +# whether the user mounted the volume at PGDATA or its parent. |
| 30 | +TS="$(date -u +%Y%m%dT%H%M%SZ)" |
| 31 | +SUBDIR_OLD="${PGDATA}/.upgrade-old-${TS}" |
| 32 | +SUBDIR_NEW="${PGDATA}/.upgrade-new-${TS}" |
| 33 | + |
| 34 | +mkdir -p "$SUBDIR_OLD" "$SUBDIR_NEW" |
| 35 | +chmod 700 "$SUBDIR_OLD" "$SUBDIR_NEW" |
| 36 | + |
| 37 | +# Roll back only failures that happen before pg_upgrade --link has run. |
| 38 | +ROLLBACK_SAFE=1 |
| 39 | +on_failure() { |
| 40 | + if [ "$ROLLBACK_SAFE" != "1" ]; then |
| 41 | + return |
| 42 | + fi |
| 43 | + echo "pg-auto-upgrade: failed before pg_upgrade ran; restoring PGDATA contents" >&2 |
| 44 | + rm -rf "$SUBDIR_NEW" 2>/dev/null || true |
| 45 | + if [ -d "$SUBDIR_OLD" ]; then |
| 46 | + find "$SUBDIR_OLD" -mindepth 1 -maxdepth 1 \ |
| 47 | + -exec mv -t "$PGDATA" {} + 2>/dev/null || true |
| 48 | + rmdir "$SUBDIR_OLD" 2>/dev/null || true |
| 49 | + fi |
| 50 | +} |
| 51 | +trap on_failure ERR |
| 52 | + |
| 53 | +# Move existing cluster contents into SUBDIR_OLD (skip the upgrade subdirs themselves). |
| 54 | +find "$PGDATA" -mindepth 1 -maxdepth 1 \ |
| 55 | + ! -path "$SUBDIR_OLD" ! -path "$SUBDIR_NEW" \ |
| 56 | + -exec mv -t "$SUBDIR_OLD" {} + |
| 57 | + |
| 58 | +# Match the new cluster's data-checksum setting to the old cluster's; |
| 59 | +# pg_upgrade refuses if they differ. |
| 60 | +OLD_CHECKSUM_VER="$("$OLD_BIN/pg_controldata" "$SUBDIR_OLD" \ |
| 61 | + | awk -F': *' '/Data page checksum version/{print $2}')" |
| 62 | +if [ "${OLD_CHECKSUM_VER:-0}" -gt 0 ]; then |
| 63 | + CHECKSUM_ARG="--data-checksums" |
| 64 | +else |
| 65 | + CHECKSUM_ARG="--no-data-checksums" |
| 66 | +fi |
| 67 | + |
| 68 | +"$NEW_BIN/initdb" \ |
| 69 | + --username="${POSTGRES_USER:-postgres}" \ |
| 70 | + "$CHECKSUM_ARG" \ |
| 71 | + -c max_connections=1000 \ |
| 72 | + -D "$SUBDIR_NEW" |
| 73 | + |
| 74 | +cd /tmp |
| 75 | +ROLLBACK_SAFE=0 |
| 76 | +"$NEW_BIN/pg_upgrade" \ |
| 77 | + --old-bindir="$OLD_BIN" \ |
| 78 | + --new-bindir="$NEW_BIN" \ |
| 79 | + --old-datadir="$SUBDIR_OLD" \ |
| 80 | + --new-datadir="$SUBDIR_NEW" \ |
| 81 | + --link |
| 82 | + |
| 83 | +# Promote the new cluster's files into PGDATA up front, so that even if |
| 84 | +# a later step fails the volume contains a startable cluster. |
| 85 | +find "$SUBDIR_NEW" -mindepth 1 -maxdepth 1 -exec mv -t "$PGDATA" {} + |
| 86 | +rmdir "$SUBDIR_NEW" |
| 87 | + |
| 88 | +# Refresh collation versions: the OS glibc may differ from when the old |
| 89 | +# cluster was created (e.g. bullseye -> trixie), which silently breaks |
| 90 | +# b-tree ordering. REINDEX rebuilds indexes against current collation |
| 91 | +# rules; REFRESH updates the catalog's recorded version. |
| 92 | +refresh_collations() { |
| 93 | + local pg_temp_port=50432 |
| 94 | + local pg_temp_log=/tmp/pg_post_upgrade.log |
| 95 | + |
| 96 | + "$NEW_BIN/pg_ctl" -D "$PGDATA" \ |
| 97 | + -o "-c listen_addresses='' -c unix_socket_directories=/tmp -p $pg_temp_port" \ |
| 98 | + -l "$pg_temp_log" -w start |
| 99 | + |
| 100 | + local psql=("$NEW_BIN/psql" -h /tmp -p "$pg_temp_port" \ |
| 101 | + -U "${POSTGRES_USER:-postgres}" -X -At -v ON_ERROR_STOP=1) |
| 102 | + |
| 103 | + local dbs |
| 104 | + dbs="$("${psql[@]}" -d postgres -c " |
| 105 | + SELECT datname FROM pg_database |
| 106 | + WHERE datallowconn AND datname <> 'template0' |
| 107 | + AND datcollversion IS DISTINCT FROM pg_database_collation_actual_version(oid) |
| 108 | + ")" || dbs="" |
| 109 | + |
| 110 | + local db |
| 111 | + while read -r db; do |
| 112 | + [ -n "$db" ] || continue |
| 113 | + echo "pg-auto-upgrade: REINDEX + REFRESH for database $db" |
| 114 | + "${psql[@]}" -d "$db" -c "REINDEX DATABASE \"$db\"" |
| 115 | + "${psql[@]}" -d "$db" -c "ALTER DATABASE \"$db\" REFRESH COLLATION VERSION" |
| 116 | + "${psql[@]}" -d "$db" <<'SQL' |
| 117 | +DO $$ |
| 118 | +DECLARE r record; |
| 119 | +BEGIN |
| 120 | + FOR r IN SELECT n.nspname, c.collname |
| 121 | + FROM pg_collation c JOIN pg_namespace n ON c.collnamespace = n.oid |
| 122 | + WHERE c.collversion IS NOT NULL |
| 123 | + AND c.collversion <> pg_collation_actual_version(c.oid) |
| 124 | + LOOP |
| 125 | + EXECUTE format('ALTER COLLATION %I.%I REFRESH VERSION', r.nspname, r.collname); |
| 126 | + END LOOP; |
| 127 | +END |
| 128 | +$$; |
| 129 | +SQL |
| 130 | + done <<< "$dbs" |
| 131 | + |
| 132 | + "$NEW_BIN/pg_ctl" -D "$PGDATA" -w stop |
| 133 | +} |
| 134 | + |
| 135 | +if ! refresh_collations; then |
| 136 | + echo "pg-auto-upgrade: collation refresh failed; the cluster is upgraded but" >&2 |
| 137 | + echo "pg-auto-upgrade: you should manually REINDEX and REFRESH affected databases." >&2 |
| 138 | + "$NEW_BIN/pg_ctl" -D "$PGDATA" -w stop 2>/dev/null || true |
| 139 | +fi |
| 140 | + |
| 141 | +# Carry user-defined config and ALTER SYSTEM settings forward. |
| 142 | +for f in postgresql.conf postgresql.auto.conf pg_hba.conf pg_ident.conf; do |
| 143 | + if [ -f "$SUBDIR_OLD/$f" ]; then |
| 144 | + cp "$SUBDIR_OLD/$f" "$PGDATA/$f" |
| 145 | + fi |
| 146 | +done |
| 147 | + |
| 148 | +# Re-apply image defaults that should survive an upgrade. |
| 149 | +sed -ri "s/^#?max_connections\s*=.*/max_connections = 1000/" "$PGDATA/postgresql.conf" |
| 150 | + |
| 151 | +echo "pg-auto-upgrade: PG${OLD_VER} -> PG${NEW_VER} complete" |
| 152 | +echo "pg-auto-upgrade: pre-upgrade files kept at $SUBDIR_OLD — delete after verifying the new cluster" |
0 commit comments