From 3188ce3925313395415c13840e1c9e9567872011 Mon Sep 17 00:00:00 2001 From: Jean-Paul Argudo Date: Fri, 30 Oct 2026 10:08:55 +0100 Subject: [PATCH 01/22] https://linear.app/supabase/issue/MPG-1283/harden-a-bit-preparesh --- .../pg_upgrade_scripts/prepare.sh | 105 ++++++++++++++++-- 1 file changed, 95 insertions(+), 10 deletions(-) diff --git a/ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh b/ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh index 7d7eb98903..62619c7fa1 100755 --- a/ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh +++ b/ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh @@ -1,15 +1,100 @@ #! /usr/bin/env bash -## This script is runs in advance of the database version upgrade, on the newly -## launched instance which will eventually be promoted to become the primary -## database instance once the upgrade successfully completes, terminating the -## previous (source) instance. -## The following commands safely stop the Postgres service and unmount -## the data disk off the newly launched instance, to be re-attached to the -## source instance and run the upgrade there. -set -euo pipefail +# This script is runs in advance of the database version upgrade, on the newly +# launched instance which will eventually be promoted to become the primary +# database instance once the upgrade successfully completes, terminating the +# previous (source) instance. The following commands safely stop the Postgres +# service and unmount the data disk off the newly launched instance, to be +# re-attached to the source instance and run the upgrade there. +## Code hardened see +## https://linear.app/supabase/issue/MPG-1283/harden-a-bit-preparesh + +set -Eeuo pipefail + +SCRIPT_DIR=$(dirname -- "$0") + +# Define the log file BEFORE anything else and create it now, +# so it exists even if a later step (including sourcing common.sh) fails. +LOG_FILE="/var/log/pg-upgrade-prepare.log" +touch "$LOG_FILE" +chmod 640 "$LOG_FILE" + +# From here on, everything written to stdout/stderr (log messages, but also +# errors from systemctl, install, sync, etc.) is appended to the log file. +exec >>"$LOG_FILE" 2>&1 + +# Source the shared helpers (log and ship_logs functions). If this fails, +# "log" is not available yet, so we write a timestamped message by hand +# before exiting. +# shellcheck disable=SC1091 +source "$SCRIPT_DIR/common.sh" || { + echo "$(date -u '+%Y-%m-%d %H:%M:%S UTC') cannot source $SCRIPT_DIR/common.sh" + exit 1 +} + +# ship_logs reads these variables, and "set -u" would abort the script on an +# unset variable. Default them to empty (existing values are kept) so that +# ship_logs can handle the "no reporting key" case itself. +: "${REPORTING_ANON_KEY:=}" +: "${REPORTING_PROJECT_REF:=}" + +# Log any unexpected failure (set -e) with the line number and failing command, +# so that no error can end the script silently. +trap 'log "prepare.sh FAILED at line $LINENO: \"$BASH_COMMAND\" (exit code $?)"' ERR + +# Runs on every exit (success, explicit "exit 1" or set -e failure). +# It uploads the log file, but never changes the script's exit code and never +# lets an upload failure turn into a script failure. +on_exit() { + local rc=$? + trap - EXIT ERR # avoid re-entering the traps from inside this handler + # uploading the log in REPORTING_PROJECT_REF (see common.sh) + ship_logs "$LOG_FILE" || log "Log upload failed (non-blocking)." + exit "$rc" +} +trap on_exit EXIT + +# starting log +log "prepare.sh started." + +# check that /data is actually mounted +mountpoint -q /data || { log "/data is not mounted"; exit 1; } + +# stop postgresql: "systemctl stop blocks until the unit has fully stopped" +# https://linear.app/supabase/issue/MPG-1283/harden-a-bit-preparesh#comment-cea7446f +# so there is no need to check pg_isready afterwards... systemctl stop postgresql -cp /etc/postgresql-custom/pgsodium_root.key /data/pgsodium_root.key -umount /data +# ...but let's still verify that the service is explicitly stopped +if systemctl is-active --quiet postgresql; then + log "postgresql service is still active!"; exit 1 +fi + +# ...and that there are no remaining postgres processes +if pgrep -x postgres >/dev/null; then + log "postgres processes are still alive!"; exit 1 +fi + +# copy the pgsodium root key with proper owner and permissions +install -m 600 -o postgres -g postgres \ + /etc/postgresql-custom/pgsodium_root.key /data/pgsodium_root.key + +# flush cached writes to persistent storage +sync + +# Unmount /data. Capture the native umount error message and route it through +# "log" so that it is timestamped like the rest of the log entries. +if ! umount_out=$(umount /data 2>&1); then + log "Unable to unmount /data: $umount_out" + # List the processes still using /data ("|| true" so a fuser failure + # does not trigger the ERR trap and mask the real cause) + log "Processes using /data:" + log "$(fuser -vm /data 2>&1 || true)" + exit 1 +fi + +# final log if everything went well +# (we don't get here unless there were no previous errors) +log "prepare.sh ended with no errors." +# ship_logs is then called automatically by the EXIT trap (on_exit) From 44b4ce641028ac00e90ce2123a500ad16a3448c3 Mon Sep 17 00:00:00 2001 From: Jean-Paul Argudo Date: Fri, 30 Oct 2026 16:02:20 +0100 Subject: [PATCH 02/22] https://linear.app/supabase/issue/MPG-1283/harden-a-bit-preparesh Takes into account the script can be called many times, so the logfile has to be "ship"ed only once --- .../pg_upgrade_scripts/prepare.sh | 63 +++++++++++++------ 1 file changed, 44 insertions(+), 19 deletions(-) diff --git a/ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh b/ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh index 62619c7fa1..674a5e4105 100755 --- a/ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh +++ b/ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh @@ -10,26 +10,41 @@ ## Code hardened see ## https://linear.app/supabase/issue/MPG-1283/harden-a-bit-preparesh +#! /usr/bin/env bash set -Eeuo pipefail SCRIPT_DIR=$(dirname -- "$0") -# Define the log file BEFORE anything else and create it now, -# so it exists even if a later step (including sourcing common.sh) fails. +# The log file is never truncated: when the script is retried by the caller, +# each attempt appends to the same file. It is created right away so that it +# exists even if a later step fails. LOG_FILE="/var/log/pg-upgrade-prepare.log" touch "$LOG_FILE" chmod 640 "$LOG_FILE" +# Detect a retry BEFORE writing anything: if "started" is already in the +# file, a previous attempt has run and failed. +RETRY=0 +if grep -qF "prepare.sh started" "$LOG_FILE"; then + RETRY=1 +fi + # From here on, everything written to stdout/stderr (log messages, but also # errors from systemctl, install, sync, etc.) is appended to the log file. exec >>"$LOG_FILE" 2>&1 -# Source the shared helpers (log and ship_logs functions). If this fails, -# "log" is not available yet, so we write a timestamped message by hand -# before exiting. +# Minimal fallback so that "log" exists even if common.sh cannot be sourced. +# common.sh redefines it with the real implementation. +log() { echo "$(date -u '+%Y-%m-%d %H:%M:%S UTC') $*"; } + +# Log any unexpected failure (set -e) with the line number and failing command, +# so that no error can end the script silently. +trap 'log "prepare.sh FAILED at line $LINENO: \"$BASH_COMMAND\" (exit code $?)"' ERR + +# Source the shared helpers (log and ship_logs functions) # shellcheck disable=SC1091 source "$SCRIPT_DIR/common.sh" || { - echo "$(date -u '+%Y-%m-%d %H:%M:%S UTC') cannot source $SCRIPT_DIR/common.sh" + log "cannot source $SCRIPT_DIR/common.sh" exit 1 } @@ -39,23 +54,35 @@ source "$SCRIPT_DIR/common.sh" || { : "${REPORTING_ANON_KEY:=}" : "${REPORTING_PROJECT_REF:=}" -# Log any unexpected failure (set -e) with the line number and failing command, -# so that no error can end the script silently. -trap 'log "prepare.sh FAILED at line $LINENO: \"$BASH_COMMAND\" (exit code $?)"' ERR - # Runs on every exit (success, explicit "exit 1" or set -e failure). -# It uploads the log file, but never changes the script's exit code and never -# lets an upload failure turn into a script failure. +# - success: final message + single upload of the log file +# - failure: nothing more is written (the error is already in the log) and +# nothing is uploaded: the script cannot know whether the caller will +# retry, so the caller handles the "gave up" case. +# The script's exit code is always preserved for the caller. on_exit() { local rc=$? trap - EXIT ERR # avoid re-entering the traps from inside this handler - # uploading the log in REPORTING_PROJECT_REF (see common.sh) - ship_logs "$LOG_FILE" || log "Log upload failed (non-blocking)." + + if [ "$rc" -eq 0 ]; then + if [ "$RETRY" -eq 0 ]; then + log "prepare.sh ended with no errors." + else + log "prepare.sh finally ended without error this time." + fi + # Single upload of the log file; never fatal + if declare -F ship_logs >/dev/null; then + ship_logs "$LOG_FILE" || log "Log upload failed (non-blocking)." + else + log "ship_logs is not available, log upload skipped." + fi + fi exit "$rc" } trap on_exit EXIT -# starting log +# Written at every attempt. It is also the marker used above to detect +# retries, so do not change its wording. log "prepare.sh started." # check that /data is actually mounted @@ -94,7 +121,5 @@ if ! umount_out=$(umount /data 2>&1); then exit 1 fi -# final log if everything went well -# (we don't get here unless there were no previous errors) -log "prepare.sh ended with no errors." -# ship_logs is then called automatically by the EXIT trap (on_exit) +# Reaching this point means success: the final message and the log upload +# are handled by the EXIT trap (on_exit). From c073699ba95d6975c7ec642f3d73f790c1f0d7e6 Mon Sep 17 00:00:00 2001 From: Jean-Paul Argudo Date: Thu, 5 Nov 2026 08:47:21 +0100 Subject: [PATCH 03/22] feat: adds checksums on SOURCE PG cluster gated to size <20Gb (max 10 minutes) --- .../pg_upgrade_scripts/common.sh | 8 +- .../pg_upgrade_scripts/initiate.sh | 131 ++++++++++++++---- .../pg_upgrade_scripts/start_guard.sh | 13 +- 3 files changed, 120 insertions(+), 32 deletions(-) diff --git a/ansible/files/admin_api_scripts/pg_upgrade_scripts/common.sh b/ansible/files/admin_api_scripts/pg_upgrade_scripts/common.sh index f82cf5b63b..671c39237e 100755 --- a/ansible/files/admin_api_scripts/pg_upgrade_scripts/common.sh +++ b/ansible/files/admin_api_scripts/pg_upgrade_scripts/common.sh @@ -1,8 +1,14 @@ #! /usr/bin/env bash -# Common functions and variables used by initiate.sh and complete.sh +# Common functions and variables used by +# prepare.sh, initiate.sh and complete.sh +#staging/prod REPORTING_PROJECT_REF="ihmaxnjpcccasmrbkpvo" + +#localdev +#REPORTING_PROJECT_REF="ditigwgjchkdxjxijzza" + REPORTING_CREDENTIALS_FILE="/root/upgrade-reporting-credentials" REPORTING_ANON_KEY="" diff --git a/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh b/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh index b8b4ad92cb..446369412b 100755 --- a/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh +++ b/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh @@ -133,12 +133,18 @@ cleanup() { # can fail under set -e, or the restart below would be refused. unblock_postgres_start || log "WARNING: failed to lift the postgres start block; check /run/systemd/system/postgresql.service.d" + # jparem : $LOG_FILE exists, even if directory + # ${MOUNT_POINT}/pgdata/pg_upgrade_output.d/ does not! + # so removing the ship-ing of the log here rather than the conditional block + # down there.. + # https://linear.app/supabase/issue/MPG-1531/initiatesh-call-ship-logs-no-matter-what + ship_logs "$LOG_FILE" || true + if [ -d "${MOUNT_POINT}/pgdata/pg_upgrade_output.d/" ]; then log "Copying pg_upgrade output to /var/log" cp -R "${MOUNT_POINT}/pgdata/pg_upgrade_output.d/" /var/log/ || true chown -R postgres:postgres /var/log/pg_upgrade_output.d/ chmod -R 0750 /var/log/pg_upgrade_output.d/ - ship_logs "$LOG_FILE" || true tail -n +1 /var/log/pg_upgrade_output.d/*/* >/var/log/pg_upgrade_output.d/pg_upgrade.log || true ship_logs "/var/log/pg_upgrade_output.d/pg_upgrade.log" || true fi @@ -586,21 +592,6 @@ EXTRA_NIX_CONF export LD_LIBRARY_PATH="${PGLIBNEW}" fi - log "9. Creating new data directory, initializing database" - chown -R postgres:postgres "$MOUNT_POINT/" - rm -rf "${PGDATANEW:?}/" - - if [ "$IS_NIX_UPGRADE" = "true" ]; then - if [[ ${PGVERSION%%.*} -ge 16 ]]; then - LC_ALL=en_US.UTF-8 LC_CTYPE=en_US.UTF-8 LC_COLLATE=en_US.UTF-8 LANGUAGE=en_US.UTF-8 LANG=en_US.UTF-8 LOCALE_ARCHIVE=/usr/lib/locale/locale-archive su -c ". /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh && $PGBINNEW/initdb --allow-group-access $CHECKSUM_FLAG --encoding=$SERVER_ENCODING --locale-provider=icu --icu-locale=en_US.UTF-8 -L $PGSHARENEW -D $PGDATANEW/ --username=supabase_admin" -s "$SHELL" postgres - else - LC_ALL=en_US.UTF-8 LC_CTYPE=$SERVER_LC_CTYPE LC_COLLATE=$SERVER_LC_COLLATE LANGUAGE=en_US.UTF-8 LANG=en_US.UTF-8 LOCALE_ARCHIVE=/usr/lib/locale/locale-archive su -c ". /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh && $PGBINNEW/initdb --allow-group-access $CHECKSUM_FLAG --encoding=$SERVER_ENCODING --lc-collate=$SERVER_LC_COLLATE --lc-ctype=$SERVER_LC_CTYPE -L $PGSHARENEW -D $PGDATANEW/ --username=supabase_admin" -s "$SHELL" postgres - fi - else - su -c "$PGBINNEW/initdb --allow-group-access $CHECKSUM_FLAG -L $PGSHARENEW -D $PGDATANEW/ --username=supabase_admin" -s "$SHELL" postgres - - fi - # This line avoids the need to supply the supabase_admin password on the old # instance, since pg_upgrade connects to the db as supabase_admin using unix # sockets, which is gated behind scram-sha-256 per pg_hba.conf.j2. The new @@ -625,6 +616,10 @@ $(cat /etc/postgresql/pg_hba.conf)" >/etc/postgresql/pg_hba.conf chown postgres:postgres "$TMP_CONFIG" + # The site profile link (/nix/var/nix/profiles/site/lib/postgresql) holds extensions (supautils) built for the old major version, which the new cluster can't load. + # Without this config both clusters load the copies bundled with their own postgres package from $libdir. + sed -i '/^dynamic_library_path/d' "$TMP_CONFIG" + UPGRADE_COMMAND=$( cat < 20 Gb!" + + fi + + else + + log "9.1 checksums already present on SOURCE" + + fi + + log "10. Creating new data directory, initializing database" + chown -R postgres:postgres "$MOUNT_POINT/" + rm -rf "${PGDATANEW:?}/" + + if [ "$IS_NIX_UPGRADE" = "true" ]; then + if [[ ${PGVERSION%%.*} -ge 16 ]]; then + LC_ALL=en_US.UTF-8 LC_CTYPE=en_US.UTF-8 LC_COLLATE=en_US.UTF-8 LANGUAGE=en_US.UTF-8 LANG=en_US.UTF-8 LOCALE_ARCHIVE=/usr/lib/locale/locale-archive su -c ". /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh && $PGBINNEW/initdb --allow-group-access $CHECKSUM_FLAG --encoding=$SERVER_ENCODING --locale-provider=icu --icu-locale=en_US.UTF-8 -L $PGSHARENEW -D $PGDATANEW/ --username=supabase_admin" -s "$SHELL" postgres + else + LC_ALL=en_US.UTF-8 LC_CTYPE=$SERVER_LC_CTYPE LC_COLLATE=$SERVER_LC_COLLATE LANGUAGE=en_US.UTF-8 LANG=en_US.UTF-8 LOCALE_ARCHIVE=/usr/lib/locale/locale-archive su -c ". /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh && $PGBINNEW/initdb --allow-group-access $CHECKSUM_FLAG --encoding=$SERVER_ENCODING --lc-collate=$SERVER_LC_COLLATE --lc-ctype=$SERVER_LC_CTYPE -L $PGSHARENEW -D $PGDATANEW/ --username=supabase_admin" -s "$SHELL" postgres + fi + else + su -c "$PGBINNEW/initdb --allow-group-access $CHECKSUM_FLAG -L $PGSHARENEW -D $PGDATANEW/ --username=supabase_admin" -s "$SHELL" postgres + fi + + log "11. pg_upgrade --check(ing) compatibility of old/new clusters" + if [[ ${PGVERSION%%.*} -ge 16 ]]; then + GRN_PLUGINS_DIR=/var/lib/postgresql/.nix-profile/lib/groonga/plugins LC_ALL=en_US.UTF-8 LANGUAGE=en_US.UTF-8 LANG=en_US.UTF-8 LOCALE_ARCHIVE=/usr/lib/locale/locale-archive su -pc "$UPGRADE_COMMAND --check" -s "$SHELL" postgres + else + GRN_PLUGINS_DIR=/var/lib/postgresql/.nix-profile/lib/groonga/plugins LC_ALL=en_US.UTF-8 LC_CTYPE=$SERVER_LC_CTYPE LC_COLLATE=$SERVER_LC_COLLATE LANGUAGE=en_US.UTF-8 LANG=en_US.UTF-8 LOCALE_ARCHIVE=/usr/lib/locale/locale-archive su -pc "$UPGRADE_COMMAND --check" -s "$SHELL" postgres + fi + start_pid_lock_watcher - # Start the old PostgreSQL instance with version-specific options + # Start the pg_upgrade from PGDATAOLD to PGDATANEW.. + log "12. pg_upgrade PGDATAOLD to PGDATANEW" if [[ ${PGVERSION%%.*} -ge 16 ]]; then GRN_PLUGINS_DIR=/var/lib/postgresql/.nix-profile/lib/groonga/plugins LC_ALL=en_US.UTF-8 LANGUAGE=en_US.UTF-8 LANG=en_US.UTF-8 LOCALE_ARCHIVE=/usr/lib/locale/locale-archive su -pc "$UPGRADE_COMMAND" -s "$SHELL" postgres else @@ -682,7 +759,7 @@ EOF fi # copying custom configurations - log "11. Copying custom configurations" + log "13. Copying custom configurations" mkdir -p "$MOUNT_POINT/conf" cp -R /etc/postgresql-custom/* "$MOUNT_POINT/conf/" # removing supautils config as to allow the latest one provided by the latest image to be used @@ -693,12 +770,12 @@ EOF rm -f "$MOUNT_POINT/conf/wal-g.conf" # copy sql files generated by pg_upgrade - log "12. Copying sql files generated by pg_upgrade" + log "14. Copying sql files generated by pg_upgrade" mkdir -p "$MOUNT_POINT/sql" cp /tmp/pg_upgrade/*.sql "$MOUNT_POINT/sql/" || true chown -R postgres:postgres "$MOUNT_POINT/sql/" - log "13. Cleaning up" + log "15. Cleaning up" cleanup "complete" } diff --git a/ansible/files/admin_api_scripts/pg_upgrade_scripts/start_guard.sh b/ansible/files/admin_api_scripts/pg_upgrade_scripts/start_guard.sh index 231a06040b..c40fe69567 100755 --- a/ansible/files/admin_api_scripts/pg_upgrade_scripts/start_guard.sh +++ b/ansible/files/admin_api_scripts/pg_upgrade_scripts/start_guard.sh @@ -4,10 +4,15 @@ # file copy. Sourced by initiate.sh and tests/pg_upgrade/test-start-guard.sh. # Expects log, IS_CI, PGDATAOLD and PGDATANEW from the caller. -# From step 10 until cleanup, nothing may start the source postgres: pg_upgrade -# copies the data files without a postmaster holding the directory, and a start -# in that window (Salt's `service.running`, adminapi, Restart=always, ...) -# yields a copy that is inconsistent with the dump. Two independent guards: +# From step 9 until cleanup, nothing may start the source postgres: +# +# If it has to run, "pg_checksums -e" will operate on the source postgres, and +# this one has to stay stopped during the whole operation +# +# pg_upgrade copies the data files without a postmaster holding the directory, +# and a start in that window (Salt's `service.running`, adminapi, +# Restart=always, ...) yields a copy that is inconsistent with the dump. Two +# independent guards: # 1. a runtime systemd drop-in that makes `systemctl start` fail while this # script or pg_upgrade is alive (a SIGKILLed script must not leave the # source blocked until a reboot); From 147e9eff4f8b63def27a3df2e1553be4e9bf8b62 Mon Sep 17 00:00:00 2001 From: Jean-Paul Argudo Date: Thu, 5 Nov 2026 09:15:47 +0100 Subject: [PATCH 04/22] Replacing "fuser" (not disponible on AMIs) by "ls -l /proc/.." --- ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh b/ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh index 674a5e4105..9b6e6a9b73 100755 --- a/ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh +++ b/ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh @@ -117,7 +117,7 @@ if ! umount_out=$(umount /data 2>&1); then # List the processes still using /data ("|| true" so a fuser failure # does not trigger the ERR trap and mask the real cause) log "Processes using /data:" - log "$(fuser -vm /data 2>&1 || true)" + log "$(ls -l /proc/* 2>/dev/null | grep "data" || true)" exit 1 fi From 550b969ab51a320a80ba8fe52c979e4e4a447aab Mon Sep 17 00:00:00 2001 From: Jean-Paul Argudo Date: Thu, 5 Nov 2026 14:50:33 +0100 Subject: [PATCH 05/22] Trying to pass nix builds --- ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh b/ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh index 9b6e6a9b73..a873e7718a 100755 --- a/ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh +++ b/ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh @@ -117,7 +117,7 @@ if ! umount_out=$(umount /data 2>&1); then # List the processes still using /data ("|| true" so a fuser failure # does not trigger the ERR trap and mask the real cause) log "Processes using /data:" - log "$(ls -l /proc/* 2>/dev/null | grep "data" || true)" + log "$(ls -l /proc/* 2>/dev/null | grep 'data' || true)" exit 1 fi From a162cb6e82adfdaf6263a6e917ad845fc9f944e2 Mon Sep 17 00:00:00 2001 From: Jean-Paul Argudo Date: Mon, 9 Nov 2026 14:25:47 +0100 Subject: [PATCH 06/22] Update ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Márton Boros --- .../files/admin_api_scripts/pg_upgrade_scripts/initiate.sh | 4 ---- 1 file changed, 4 deletions(-) diff --git a/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh b/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh index bd013848ae..e31a74af1d 100755 --- a/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh +++ b/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh @@ -620,10 +620,6 @@ $(cat /etc/postgresql/pg_hba.conf)" >/etc/postgresql/pg_hba.conf chown postgres:postgres "$TMP_CONFIG" - # The site profile link (/nix/var/nix/profiles/site/lib/postgresql) holds extensions (supautils) built for the old major version, which the new cluster can't load. - # Without this config both clusters load the copies bundled with their own postgres package from $libdir. - sed -i '/^dynamic_library_path/d' "$TMP_CONFIG" - UPGRADE_COMMAND=$( cat < Date: Mon, 9 Nov 2026 15:10:49 +0100 Subject: [PATCH 07/22] Update ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Márton Boros --- ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh | 4 ---- 1 file changed, 4 deletions(-) diff --git a/ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh b/ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh index a873e7718a..a34f53080f 100755 --- a/ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh +++ b/ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh @@ -7,10 +7,6 @@ # service and unmount the data disk off the newly launched instance, to be # re-attached to the source instance and run the upgrade there. -## Code hardened see -## https://linear.app/supabase/issue/MPG-1283/harden-a-bit-preparesh - -#! /usr/bin/env bash set -Eeuo pipefail SCRIPT_DIR=$(dirname -- "$0") From 9fe27884bbcf08efa1c119657d6e96171e54e797 Mon Sep 17 00:00:00 2001 From: Jean-Paul Argudo Date: Mon, 9 Nov 2026 15:40:48 +0100 Subject: [PATCH 08/22] Update ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Márton Boros --- ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh b/ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh index a34f53080f..5ed521369f 100755 --- a/ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh +++ b/ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh @@ -113,7 +113,7 @@ if ! umount_out=$(umount /data 2>&1); then # List the processes still using /data ("|| true" so a fuser failure # does not trigger the ERR trap and mask the real cause) log "Processes using /data:" - log "$(ls -l /proc/* 2>/dev/null | grep 'data' || true)" + log "$(ls -l /proc/*/fd /proc/*/cwd 2>/dev/null | grep ' /data' || true)" exit 1 fi From 2dd9c017a7486cbe798847f43aa482c9e30e739e Mon Sep 17 00:00:00 2001 From: Jean-Paul Argudo Date: Mon, 9 Nov 2026 16:35:23 +0100 Subject: [PATCH 09/22] Update ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Márton Boros --- .../pg_upgrade_scripts/prepare.sh | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh b/ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh index 5ed521369f..12c8e19ed2 100755 --- a/ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh +++ b/ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh @@ -61,17 +61,10 @@ on_exit() { trap - EXIT ERR # avoid re-entering the traps from inside this handler if [ "$rc" -eq 0 ]; then - if [ "$RETRY" -eq 0 ]; then - log "prepare.sh ended with no errors." - else - log "prepare.sh finally ended without error this time." - fi - # Single upload of the log file; never fatal - if declare -F ship_logs >/dev/null; then - ship_logs "$LOG_FILE" || log "Log upload failed (non-blocking)." - else - log "ship_logs is not available, log upload skipped." - fi + log "prepare.sh ended with no errors." + ship_logs "$LOG_FILE" completion || log "Log upload failed (non-blocking)." + else + ship_logs "$LOG_FILE" prepare-failed || log "Log upload failed (non-blocking)." fi exit "$rc" } From ba6416974e048dbc62fb8fc2d52adc93c1182bb7 Mon Sep 17 00:00:00 2001 From: Jean-Paul Argudo Date: Mon, 9 Nov 2026 17:32:52 +0100 Subject: [PATCH 10/22] prepare.sh and initiate.sh reviewed with Marton's comments --- .../pg_upgrade_scripts/common.sh | 2 +- .../pg_upgrade_scripts/initiate.sh | 114 +++++++++--------- .../pg_upgrade_scripts/prepare.sh | 89 ++++++++------ .../pg_upgrade_scripts/start_guard.sh | 6 +- 4 files changed, 116 insertions(+), 95 deletions(-) diff --git a/ansible/files/admin_api_scripts/pg_upgrade_scripts/common.sh b/ansible/files/admin_api_scripts/pg_upgrade_scripts/common.sh index 671c39237e..1cbe9f29df 100755 --- a/ansible/files/admin_api_scripts/pg_upgrade_scripts/common.sh +++ b/ansible/files/admin_api_scripts/pg_upgrade_scripts/common.sh @@ -1,6 +1,6 @@ #! /usr/bin/env bash -# Common functions and variables used by +# Common functions and variables used by # prepare.sh, initiate.sh and complete.sh #staging/prod diff --git a/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh b/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh index e31a74af1d..1523f9126a 100755 --- a/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh +++ b/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh @@ -133,11 +133,11 @@ cleanup() { # can fail under set -e, or the restart below would be refused. unblock_postgres_start || log "WARNING: failed to lift the postgres start block; check /run/systemd/system/postgresql.service.d" - # jparem : $LOG_FILE exists, even if directory - # ${MOUNT_POINT}/pgdata/pg_upgrade_output.d/ does not! - # so removing the ship-ing of the log here rather than the conditional block - # down there.. - # https://linear.app/supabase/issue/MPG-1531/initiatesh-call-ship-logs-no-matter-what + # jparem : $LOG_FILE exists, even if directory + # ${MOUNT_POINT}/pgdata/pg_upgrade_output.d/ does not! + # so removing the ship-ing of the log here rather than the conditional block + # down there.. + # https://linear.app/supabase/issue/MPG-1531/initiatesh-call-ship-logs-no-matter-what ship_logs "$LOG_FILE" || true if [ -d "${MOUNT_POINT}/pgdata/pg_upgrade_output.d/" ]; then @@ -657,71 +657,71 @@ EOF CI_stop_postgres fi - #CHECKSUM_FLAG="--data-checksums" if the SOURCE cluster has checksums - #CHECKSUM_FLAG="" if the SOURCE doesn't have them (yet) - # in this case the following code adds them + #CHECKSUM_FLAG="--data-checksums" if the SOURCE cluster has checksums + #CHECKSUM_FLAG="" if the SOURCE doesn't have them (yet) + # in this case the following code adds them - if [[ "$CHECKSUM_FLAG" == "" ]]; then + if [[ $CHECKSUM_FLAG == "" ]]; then - # old db hasn't got checksum, we will enable it its overall size - # (${PGDATAOLD}) is <= 20 Gb + # old db hasn't got checksum, we will enable it its overall size + # (${PGDATAOLD}) is <= 20 Gb - # 20 gb = 20 x 1024 x 1024 = 20971520 - MAX_SIZE=20971520 - - PGDATAOLD_SIZE=$(du -sk "${PGDATAOLD}" | cut -f1) + # 20 gb = 20 x 1024 x 1024 = 20971520 + MAX_SIZE=20971520 - if [[ "$PGDATAOLD_SIZE" -le "$MAX_SIZE" ]]; then + PGDATAOLD_SIZE=$(du -sk "${PGDATAOLD}" | cut -f1) - log "9.1 adding checksums to SOURCE PG (<20 Gb)" + if [[ $PGDATAOLD_SIZE -le $MAX_SIZE ]]; then - # we're adding a timer: the "pg_checksum -e" operation on ${PGDATAOLD} must - # not run for more than 10 minutes otherwise we exist this code on error - # ofc, if the pg_checksums detects a physical corruption, we have to exit - # the program with a consistent message explaining the corruption - - MAX_DURATION="10m" - KILL_AFTER="15s" + log "9.1 adding checksums to SOURCE PG (<20 Gb)" - timeout -k "${KILL_AFTER}" "${MAX_DURATION}" ${PGBINOLD}/pg_checksums -e "${PGDATAOLD}" || STATUS=$? + # we're adding a timer: the "pg_checksum -e" operation on ${PGDATAOLD} must + # not run for more than 10 minutes otherwise we exist this code on error + # ofc, if the pg_checksums detects a physical corruption, we have to exit + # the program with a consistent message explaining the corruption - # if everything is fine, $STATUS hasn't been defined, - # so we initialize it with 0 because it's a success - STATUS=${STATUS:-0} + MAX_DURATION="10m" + KILL_AFTER="15s" + + timeout -k "${KILL_AFTER}" "${MAX_DURATION}" "${PGBINOLD}"/pg_checksums -e "${PGDATAOLD}" || STATUS=$? + + # if everything is fine, $STATUS hasn't been defined, + # so we initialize it with 0 because it's a success + STATUS=${STATUS:-0} # we'll manage ERROR codes down there, to actually log anything # properly, for later diagnostic reading the $LOG_FILE into project # where it has been shipped ship_logs() in common.sh - case $STATUS in - 0) - log "adding checksums SUCCEEDED." - ;; - 124|137) - log "adding checksums FAILED. Max time (${MAX_DURATION}) reached! Process killed." - exit 124 - ;; - *) - log "adding checksum FAILED with Error code ${STATUS}" - exit "$STATUS" - ;; - esac - - # we're here if everything went well with STATUS=0 - # so now, checksums are enabled on the SOURCE! - CHECKSUM_FLAG="--data-checksums" - - else - - log "9.1 did *not* add checksum to SOURCE PG: size $PGDATAOLD_SIZE > 20 Gb!" - - fi - - else - - log "9.1 checksums already present on SOURCE" - - fi + case $STATUS in + 0) + log "adding checksums SUCCEEDED." + ;; + 124 | 137) + log "adding checksums FAILED. Max time (${MAX_DURATION}) reached! Process killed." + exit 124 + ;; + *) + log "adding checksum FAILED with Error code ${STATUS}" + exit "$STATUS" + ;; + esac + + # we're here if everything went well with STATUS=0 + # so now, checksums are enabled on the SOURCE! + CHECKSUM_FLAG="--data-checksums" + + else + + log "9.1 did *not* add checksum to SOURCE PG: size $PGDATAOLD_SIZE > 20 Gb!" + + fi + + else + + log "9.1 checksums already present on SOURCE" + + fi log "10. Creating new data directory, initializing database" chown -R postgres:postgres "$MOUNT_POINT/" diff --git a/ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh b/ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh index 12c8e19ed2..81842addde 100755 --- a/ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh +++ b/ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh @@ -1,6 +1,6 @@ #! /usr/bin/env bash -# This script is runs in advance of the database version upgrade, on the newly +# This script runs in advance of the database version upgrade, on the newly # launched instance which will eventually be promoted to become the primary # database instance once the upgrade successfully completes, terminating the # previous (source) instance. The following commands safely stop the Postgres @@ -18,16 +18,9 @@ LOG_FILE="/var/log/pg-upgrade-prepare.log" touch "$LOG_FILE" chmod 640 "$LOG_FILE" -# Detect a retry BEFORE writing anything: if "started" is already in the -# file, a previous attempt has run and failed. -RETRY=0 -if grep -qF "prepare.sh started" "$LOG_FILE"; then - RETRY=1 -fi - # From here on, everything written to stdout/stderr (log messages, but also -# errors from systemctl, install, sync, etc.) is appended to the log file. -exec >>"$LOG_FILE" 2>&1 +# errors from systemctl, install, sync, etc.) is written to the log file. +exec >"$LOG_FILE" 2>&1 # Minimal fallback so that "log" exists even if common.sh cannot be sourced. # common.sh redefines it with the real implementation. @@ -40,8 +33,8 @@ trap 'log "prepare.sh FAILED at line $LINENO: \"$BASH_COMMAND\" (exit code $?)"' # Source the shared helpers (log and ship_logs functions) # shellcheck disable=SC1091 source "$SCRIPT_DIR/common.sh" || { - log "cannot source $SCRIPT_DIR/common.sh" - exit 1 + log "cannot source $SCRIPT_DIR/common.sh" + exit 1 } # ship_logs reads these variables, and "set -u" would abort the script on an @@ -57,44 +50,48 @@ source "$SCRIPT_DIR/common.sh" || { # retry, so the caller handles the "gave up" case. # The script's exit code is always preserved for the caller. on_exit() { - local rc=$? - trap - EXIT ERR # avoid re-entering the traps from inside this handler - - if [ "$rc" -eq 0 ]; then - log "prepare.sh ended with no errors." - ship_logs "$LOG_FILE" completion || log "Log upload failed (non-blocking)." - else - ship_logs "$LOG_FILE" prepare-failed || log "Log upload failed (non-blocking)." - fi - exit "$rc" + local rc=$? + trap - EXIT ERR # avoid re-entering the traps from inside this handler + + if [ "$rc" -eq 0 ]; then + log "prepare.sh ended with no errors." + ship_logs "$LOG_FILE" completion || log "Log upload failed (non-blocking)." + else + ship_logs "$LOG_FILE" prepare-failed || log "Log upload failed (non-blocking)." + fi + exit "$rc" } trap on_exit EXIT # Written at every attempt. It is also the marker used above to detect # retries, so do not change its wording. -log "prepare.sh started." +log "prepare.sh started" # check that /data is actually mounted -mountpoint -q /data || { log "/data is not mounted"; exit 1; } +mountpoint -q /data || { + log "/data is not mounted" + exit 1 +} # stop postgresql: "systemctl stop blocks until the unit has fully stopped" -# https://linear.app/supabase/issue/MPG-1283/harden-a-bit-preparesh#comment-cea7446f # so there is no need to check pg_isready afterwards... systemctl stop postgresql # ...but let's still verify that the service is explicitly stopped if systemctl is-active --quiet postgresql; then - log "postgresql service is still active!"; exit 1 + log "postgresql service is still active!" + exit 1 fi # ...and that there are no remaining postgres processes if pgrep -x postgres >/dev/null; then - log "postgres processes are still alive!"; exit 1 + log "postgres processes are still alive!" + exit 1 fi # copy the pgsodium root key with proper owner and permissions install -m 600 -o postgres -g postgres \ - /etc/postgresql-custom/pgsodium_root.key /data/pgsodium_root.key + /etc/postgresql-custom/pgsodium_root.key /data/pgsodium_root.key # flush cached writes to persistent storage sync @@ -102,12 +99,36 @@ sync # Unmount /data. Capture the native umount error message and route it through # "log" so that it is timestamped like the rest of the log entries. if ! umount_out=$(umount /data 2>&1); then - log "Unable to unmount /data: $umount_out" - # List the processes still using /data ("|| true" so a fuser failure - # does not trigger the ERR trap and mask the real cause) - log "Processes using /data:" - log "$(ls -l /proc/*/fd /proc/*/cwd 2>/dev/null | grep ' /data' || true)" - exit 1 + log "Unable to unmount /data: $umount_out" + # List the processes still using /data ("|| true" so a fuser failure + # does not trigger the ERR trap and mask the real cause) + log "Processes using /data:" + + # activate nullglob to avoid errors if /proc is not accessible + shopt -s nullglob + + # go thru /proc on numeric PIDs + for pid_dir in /proc/[0-9]*/; do + + # extract pid from dir name + pid="${pid_dir#/proc/}" + pid="${pid%/}" + + # secure read from symbolink link cwd + if cwd=$(readlink -f "$pid_dir/cwd" 2>/dev/null); then + if [[ $cwd == "/data" || $cwd == "/data"/* ]]; then + # get user and name of the current command + stat_info=$(stat -c "%U" "$pid_dir" 2>/dev/null || true) + cmd_info=$(cat "$pid_dir/comm" 2>/dev/null || true) + + if [[ -n $stat_info && -n $cmd_info ]]; then + log "user: ${stat_info} pid: ${pid} command: ${cmd_info}" + fi + fi + fi + done + + exit 1 fi # Reaching this point means success: the final message and the log upload diff --git a/ansible/files/admin_api_scripts/pg_upgrade_scripts/start_guard.sh b/ansible/files/admin_api_scripts/pg_upgrade_scripts/start_guard.sh index c40fe69567..1537a5c736 100755 --- a/ansible/files/admin_api_scripts/pg_upgrade_scripts/start_guard.sh +++ b/ansible/files/admin_api_scripts/pg_upgrade_scripts/start_guard.sh @@ -4,9 +4,9 @@ # file copy. Sourced by initiate.sh and tests/pg_upgrade/test-start-guard.sh. # Expects log, IS_CI, PGDATAOLD and PGDATANEW from the caller. -# From step 9 until cleanup, nothing may start the source postgres: -# -# If it has to run, "pg_checksums -e" will operate on the source postgres, and +# From step 9 until cleanup, nothing may start the source postgres: +# +# If it has to run, "pg_checksums -e" will operate on the source postgres, and # this one has to stay stopped during the whole operation # # pg_upgrade copies the data files without a postmaster holding the directory, From 9bafb6db5f2d3ef56d44ed9beaf5ed2b23ce99d2 Mon Sep 17 00:00:00 2001 From: Jean-Paul Argudo Date: Mon, 9 Nov 2026 17:38:14 +0100 Subject: [PATCH 11/22] initiate.sh : remove useless comments --- ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh b/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh index 1523f9126a..97c59d6d7f 100755 --- a/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh +++ b/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh @@ -133,11 +133,10 @@ cleanup() { # can fail under set -e, or the restart below would be refused. unblock_postgres_start || log "WARNING: failed to lift the postgres start block; check /run/systemd/system/postgresql.service.d" - # jparem : $LOG_FILE exists, even if directory + # $LOG_FILE exists, even if directory # ${MOUNT_POINT}/pgdata/pg_upgrade_output.d/ does not! # so removing the ship-ing of the log here rather than the conditional block # down there.. - # https://linear.app/supabase/issue/MPG-1531/initiatesh-call-ship-logs-no-matter-what ship_logs "$LOG_FILE" || true if [ -d "${MOUNT_POINT}/pgdata/pg_upgrade_output.d/" ]; then From 03549bb462a75c59c72a873dc92215e89c214aab Mon Sep 17 00:00:00 2001 From: Jean-Paul Argudo Date: Tue, 10 Nov 2026 09:55:46 +0100 Subject: [PATCH 12/22] initiate.sh: remove unnecessary 2nd argument to ship_logs (its hardcoded!). prepare.sh : set back ship_log of $LOG_FILE to the proper place --- .../files/admin_api_scripts/pg_upgrade_scripts/initiate.sh | 6 ++---- .../files/admin_api_scripts/pg_upgrade_scripts/prepare.sh | 4 ++-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh b/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh index 97c59d6d7f..fdce52869a 100755 --- a/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh +++ b/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh @@ -133,10 +133,7 @@ cleanup() { # can fail under set -e, or the restart below would be refused. unblock_postgres_start || log "WARNING: failed to lift the postgres start block; check /run/systemd/system/postgresql.service.d" - # $LOG_FILE exists, even if directory - # ${MOUNT_POINT}/pgdata/pg_upgrade_output.d/ does not! - # so removing the ship-ing of the log here rather than the conditional block - # down there.. + # ship $LOG_FILE content to reporting project (see common.sh) ship_logs "$LOG_FILE" || true if [ -d "${MOUNT_POINT}/pgdata/pg_upgrade_output.d/" ]; then @@ -222,6 +219,7 @@ on_exit() { fi fi unblock_postgres_start || log "WARNING: failed to lift the postgres start block; check /run/systemd/system/postgresql.service.d" + exit "$exit_code" } diff --git a/ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh b/ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh index 81842addde..8018fe1ddf 100755 --- a/ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh +++ b/ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh @@ -55,9 +55,9 @@ on_exit() { if [ "$rc" -eq 0 ]; then log "prepare.sh ended with no errors." - ship_logs "$LOG_FILE" completion || log "Log upload failed (non-blocking)." + ship_logs "$LOG_FILE" || log "Log upload failed (non-blocking)." else - ship_logs "$LOG_FILE" prepare-failed || log "Log upload failed (non-blocking)." + ship_logs "$LOG_FILE" || log "Log upload failed (non-blocking)." fi exit "$rc" } From a9270b49687632e66ad324d7084de73d673399c3 Mon Sep 17 00:00:00 2001 From: Jean-Paul Argudo Date: Tue, 10 Nov 2026 14:24:20 +0100 Subject: [PATCH 13/22] Apply suggestion from @brainrake MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Márton Boros --- ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh b/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh index d66ff11557..340a08db52 100755 --- a/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh +++ b/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh @@ -201,7 +201,7 @@ EOF } # EXIT trap: any exit that did not go through cleanup() (set -u abort, TERM/INT) -# still restores the source, since step 10 left it stopped and disabled. If +# still restores the source, since step 9 left it stopped and disabled. If # cleanup() already ran (or died part way under set -e), only make sure the # start block is gone. on_exit() { From b8048a009b53de9195f947634b9a4b6ec5fa29a7 Mon Sep 17 00:00:00 2001 From: Jean-Paul Argudo Date: Tue, 10 Nov 2026 14:25:35 +0100 Subject: [PATCH 14/22] Apply suggestion from @brainrake MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Márton Boros --- .../files/admin_api_scripts/pg_upgrade_scripts/initiate.sh | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh b/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh index 340a08db52..0134fe3ef8 100755 --- a/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh +++ b/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh @@ -672,10 +672,7 @@ EOF log "9.1 adding checksums to SOURCE PG (<20 Gb)" - # we're adding a timer: the "pg_checksum -e" operation on ${PGDATAOLD} must - # not run for more than 10 minutes otherwise we exist this code on error - # ofc, if the pg_checksums detects a physical corruption, we have to exit - # the program with a consistent message explaining the corruption + # pg_checksums -e does not verify existing pages. A run over MAX_DURATION fails the upgrade. MAX_DURATION="10m" KILL_AFTER="15s" From 505dca8d4a16923c0df2f8cb103e90259a366f12 Mon Sep 17 00:00:00 2001 From: Jean-Paul Argudo Date: Thu, 12 Nov 2026 07:34:57 +0100 Subject: [PATCH 15/22] Apply suggestion from @mmlb I don't get much the change, since that's explained in the comment, but since it's the same for me, accepted! Co-authored-by: Manuel Mendez <708570+mmlb@users.noreply.github.com> --- ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh b/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh index 0134fe3ef8..099d3c4536 100755 --- a/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh +++ b/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh @@ -664,7 +664,7 @@ EOF # (${PGDATAOLD}) is <= 20 Gb # 20 gb = 20 x 1024 x 1024 = 20971520 - MAX_SIZE=20971520 + MAX_SIZE=$((20 * 1024 * 1024)) PGDATAOLD_SIZE=$(du -sk "${PGDATAOLD}" | cut -f1) From e58bacb23bd60552e2970d1f717f82f7e89501ac Mon Sep 17 00:00:00 2001 From: Jean-Paul Argudo Date: Thu, 12 Nov 2026 07:36:00 +0100 Subject: [PATCH 16/22] Apply suggestion from @mmlb I feel like you want the bash code to be more readable here. Good point. Accepted. Thanks! Co-authored-by: Manuel Mendez <708570+mmlb@users.noreply.github.com> --- .../files/admin_api_scripts/pg_upgrade_scripts/initiate.sh | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh b/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh index 099d3c4536..092c6e310e 100755 --- a/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh +++ b/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh @@ -677,12 +677,9 @@ EOF MAX_DURATION="10m" KILL_AFTER="15s" + STATUS=0 timeout -k "${KILL_AFTER}" "${MAX_DURATION}" "${PGBINOLD}"/pg_checksums -e "${PGDATAOLD}" || STATUS=$? - # if everything is fine, $STATUS hasn't been defined, - # so we initialize it with 0 because it's a success - STATUS=${STATUS:-0} - # we'll manage ERROR codes down there, to actually log anything # properly, for later diagnostic reading the $LOG_FILE into project # where it has been shipped ship_logs() in common.sh From 4944edbe0cb7850f66672d08446262abd6c23359 Mon Sep 17 00:00:00 2001 From: Jean-Paul Argudo Date: Thu, 12 Nov 2026 07:36:41 +0100 Subject: [PATCH 17/22] Apply suggestion from @brainrake MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Same remark as before: code is more readable this way. Accepted. Thanks! Co-authored-by: Márton Boros --- .../files/admin_api_scripts/pg_upgrade_scripts/initiate.sh | 7 ------- 1 file changed, 7 deletions(-) diff --git a/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh b/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh index 092c6e310e..7298d23117 100755 --- a/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh +++ b/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh @@ -674,16 +674,9 @@ EOF # pg_checksums -e does not verify existing pages. A run over MAX_DURATION fails the upgrade. - MAX_DURATION="10m" - KILL_AFTER="15s" - STATUS=0 timeout -k "${KILL_AFTER}" "${MAX_DURATION}" "${PGBINOLD}"/pg_checksums -e "${PGDATAOLD}" || STATUS=$? - # we'll manage ERROR codes down there, to actually log anything - # properly, for later diagnostic reading the $LOG_FILE into project - # where it has been shipped ship_logs() in common.sh - case $STATUS in 0) log "adding checksums SUCCEEDED." From 2793c589196e151d4a5440043db2ceba6185d862 Mon Sep 17 00:00:00 2001 From: Jean-Paul Argudo Date: Thu, 12 Nov 2026 07:37:37 +0100 Subject: [PATCH 18/22] Apply suggestion from @brainrake MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Yes, you're 100% right. I forgot to update the comment here in the hurry of applying changes... Thank you very much. Co-authored-by: Márton Boros --- ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh b/ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh index 8018fe1ddf..e3553f1e42 100755 --- a/ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh +++ b/ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh @@ -11,9 +11,7 @@ set -Eeuo pipefail SCRIPT_DIR=$(dirname -- "$0") -# The log file is never truncated: when the script is retried by the caller, -# each attempt appends to the same file. It is created right away so that it -# exists even if a later step fails. +# Each attempt overwrites the log. Failed attempts are shipped before the retry. LOG_FILE="/var/log/pg-upgrade-prepare.log" touch "$LOG_FILE" chmod 640 "$LOG_FILE" From 9fa8256fff1097cbcae155faf1cf3c07cb85118c Mon Sep 17 00:00:00 2001 From: Jean-Paul Argudo Date: Thu, 12 Nov 2026 07:38:59 +0100 Subject: [PATCH 19/22] Apply suggestion from @brainrake MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Yes, it was in the case common.sh is not accessible, but.. You're right. Accepted. Thanks. Co-authored-by: Márton Boros --- .../files/admin_api_scripts/pg_upgrade_scripts/prepare.sh | 6 ------ 1 file changed, 6 deletions(-) diff --git a/ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh b/ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh index e3553f1e42..63a3b5be32 100755 --- a/ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh +++ b/ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh @@ -35,12 +35,6 @@ source "$SCRIPT_DIR/common.sh" || { exit 1 } -# ship_logs reads these variables, and "set -u" would abort the script on an -# unset variable. Default them to empty (existing values are kept) so that -# ship_logs can handle the "no reporting key" case itself. -: "${REPORTING_ANON_KEY:=}" -: "${REPORTING_PROJECT_REF:=}" - # Runs on every exit (success, explicit "exit 1" or set -e failure). # - success: final message + single upload of the log file # - failure: nothing more is written (the error is already in the log) and From 9d238b776a9c439dc34d48abdfb0095ac2936a30 Mon Sep 17 00:00:00 2001 From: Jean-Paul Argudo Date: Thu, 12 Nov 2026 07:42:37 +0100 Subject: [PATCH 20/22] Apply batched suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Much better... Thank you very much! Co-authored-by: Márton Boros Co-authored-by: Manuel Mendez <708570+mmlb@users.noreply.github.com> --- .../pg_upgrade_scripts/prepare.sh | 49 +++++-------------- 1 file changed, 11 insertions(+), 38 deletions(-) diff --git a/ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh b/ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh index 63a3b5be32..28545ed243 100755 --- a/ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh +++ b/ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh @@ -35,28 +35,16 @@ source "$SCRIPT_DIR/common.sh" || { exit 1 } -# Runs on every exit (success, explicit "exit 1" or set -e failure). -# - success: final message + single upload of the log file -# - failure: nothing more is written (the error is already in the log) and -# nothing is uploaded: the script cannot know whether the caller will -# retry, so the caller handles the "gave up" case. -# The script's exit code is always preserved for the caller. on_exit() { local rc=$? - trap - EXIT ERR # avoid re-entering the traps from inside this handler - - if [ "$rc" -eq 0 ]; then - log "prepare.sh ended with no errors." - ship_logs "$LOG_FILE" || log "Log upload failed (non-blocking)." - else - ship_logs "$LOG_FILE" || log "Log upload failed (non-blocking)." - fi + trap - EXIT ERR + + [ "$rc" -eq 0 ] && log "prepare.sh ended with no errors." + ship_logs "$LOG_FILE" || log "Log upload failed (non-blocking)." exit "$rc" } trap on_exit EXIT -# Written at every attempt. It is also the marker used above to detect -# retries, so do not change its wording. log "prepare.sh started" # check that /data is actually mounted @@ -92,33 +80,18 @@ sync # "log" so that it is timestamped like the rest of the log entries. if ! umount_out=$(umount /data 2>&1); then log "Unable to unmount /data: $umount_out" - # List the processes still using /data ("|| true" so a fuser failure - # does not trigger the ERR trap and mask the real cause) + # List the processes whose cwd is under /data. log "Processes using /data:" # activate nullglob to avoid errors if /proc is not accessible shopt -s nullglob - # go thru /proc on numeric PIDs - for pid_dir in /proc/[0-9]*/; do - - # extract pid from dir name - pid="${pid_dir#/proc/}" - pid="${pid%/}" - - # secure read from symbolink link cwd - if cwd=$(readlink -f "$pid_dir/cwd" 2>/dev/null); then - if [[ $cwd == "/data" || $cwd == "/data"/* ]]; then - # get user and name of the current command - stat_info=$(stat -c "%U" "$pid_dir" 2>/dev/null || true) - cmd_info=$(cat "$pid_dir/comm" 2>/dev/null || true) - - if [[ -n $stat_info && -n $cmd_info ]]; then - log "user: ${stat_info} pid: ${pid} command: ${cmd_info}" - fi - fi - fi - done + QUOTING_STYLE=literal stat -c '%U %N' /proc/[0-9]*/cwd 2>/dev/null | + sed -nE 's|^(\S+) /proc/([0-9]+)/cwd -> /data(/.*)?$|\1 \2|p' | + while read -r user pid; do + comm=$(cat "/proc/$pid/comm" 2>/dev/null) || continue + log "user: ${user} pid: ${pid} command: ${comm}" + done exit 1 fi From 0fd2701f2973991cc4aefae08ee4f2a02fafd15c Mon Sep 17 00:00:00 2001 From: Jean-Paul Argudo Date: Thu, 12 Nov 2026 16:46:14 +0100 Subject: [PATCH 21/22] Apply suggestion from @brainrake MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Márton Boros --- .../admin_api_scripts/pg_upgrade_scripts/prepare.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh b/ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh index 28545ed243..87f16c2a9d 100755 --- a/ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh +++ b/ansible/files/admin_api_scripts/pg_upgrade_scripts/prepare.sh @@ -87,11 +87,11 @@ if ! umount_out=$(umount /data 2>&1); then shopt -s nullglob QUOTING_STYLE=literal stat -c '%U %N' /proc/[0-9]*/cwd 2>/dev/null | - sed -nE 's|^(\S+) /proc/([0-9]+)/cwd -> /data(/.*)?$|\1 \2|p' | - while read -r user pid; do - comm=$(cat "/proc/$pid/comm" 2>/dev/null) || continue - log "user: ${user} pid: ${pid} command: ${comm}" - done + sed -nE 's|^(\S+) /proc/([0-9]+)/cwd -> /data(/.*)?$|\1 \2|p' | + while read -r user pid; do + comm=$(cat "/proc/$pid/comm" 2>/dev/null) || continue + log "user: ${user} pid: ${pid} command: ${comm}" + done || true exit 1 fi From bb81eeff31966c2864eb7e5b0c4e704f4b44c194 Mon Sep 17 00:00:00 2001 From: Jean-Paul Argudo Date: Thu, 12 Nov 2026 16:46:45 +0100 Subject: [PATCH 22/22] Apply suggestion from @brainrake MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Márton Boros --- ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh b/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh index 7298d23117..3b15259c74 100755 --- a/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh +++ b/ansible/files/admin_api_scripts/pg_upgrade_scripts/initiate.sh @@ -674,6 +674,8 @@ EOF # pg_checksums -e does not verify existing pages. A run over MAX_DURATION fails the upgrade. + MAX_DURATION="10m" + KILL_AFTER="15s" STATUS=0 timeout -k "${KILL_AFTER}" "${MAX_DURATION}" "${PGBINOLD}"/pg_checksums -e "${PGDATAOLD}" || STATUS=$?