From 64a177e3ed7dbd4bee6fc893273d344e272f30de Mon Sep 17 00:00:00 2001 From: Andy Pfister Date: Thu, 14 Nov 2024 16:01:23 +0100 Subject: [PATCH] Fix re-applying existing file permissions --- Dockerfile.alpine | 5 +- Dockerfile.bookworm | 5 +- docker-entrypoint.sh | 720 +++------------------------------- pgautoupgrade-postupgrade.sh | 28 +- postgres-docker-entrypoint.sh | 671 +++++++++++++++++++++++++++++++ 5 files changed, 728 insertions(+), 701 deletions(-) create mode 100755 postgres-docker-entrypoint.sh diff --git a/Dockerfile.alpine b/Dockerfile.alpine index aee1f09..58f4866 100644 --- a/Dockerfile.alpine +++ b/Dockerfile.alpine @@ -185,12 +185,11 @@ RUN apk update && \ # Pass the PG build target through to the running image ENV PGTARGET=${PGTARGET} -# Copy across the post-upgrade shell script -COPY pgautoupgrade-postupgrade.sh pgautoupgrade-healthcheck.sh /usr/local/bin/ +# Copy across all our shell scripts +COPY pgautoupgrade-postupgrade.sh pgautoupgrade-healthcheck.sh postgres-docker-entrypoint.sh docker-entrypoint.sh /usr/local/bin/ # Set up the script run by the container when it starts WORKDIR /var/lib/postgresql -COPY docker-entrypoint.sh /usr/local/bin/ ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"] HEALTHCHECK CMD /usr/local/bin/pgautoupgrade-healthcheck.sh diff --git a/Dockerfile.bookworm b/Dockerfile.bookworm index b4f2c6a..84d1835 100644 --- a/Dockerfile.bookworm +++ b/Dockerfile.bookworm @@ -180,12 +180,11 @@ RUN apt update && \ # Pass the PG build target through to the running image ENV PGTARGET=${PGTARGET} -# Copy across the post-upgrade shell script -COPY pgautoupgrade-postupgrade.sh pgautoupgrade-healthcheck.sh /usr/local/bin/ +# Copy across all our shell scripts +COPY pgautoupgrade-postupgrade.sh pgautoupgrade-healthcheck.sh postgres-docker-entrypoint.sh docker-entrypoint.sh /usr/local/bin/ # Set up the script run by the container when it starts WORKDIR /var/lib/postgresql -COPY docker-entrypoint.sh /usr/local/bin/ ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"] HEALTHCHECK CMD /usr/local/bin/pgautoupgrade-healthcheck.sh diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index 58c654b..7002446 100755 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -2,675 +2,57 @@ set -Eeo pipefail # TODO swap to -Eeuo pipefail above (after handling all potentially-unset variables) -# Define the path to the upgrade lock file using PGDATA if set, otherwise default -UPGRADE_LOCK_FILE="${PGDATA:-/var/lib/postgresql/data}/upgrade_in_progress.lock" - -# usage: file_env VAR [DEFAULT] -# ie: file_env 'XYZ_DB_PASSWORD' 'example' -# (will allow for "$XYZ_DB_PASSWORD_FILE" to fill in the value of -# "$XYZ_DB_PASSWORD" from a file, especially for Docker's secrets feature) -file_env() { - local var="$1" - local fileVar="${var}_FILE" - local def="${2:-}" - if [ "${!var:-}" ] && [ "${!fileVar:-}" ]; then - printf >&2 'error: both %s and %s are set (but are exclusive)\n' "$var" "$fileVar" - exit 1 - fi - local val="$def" - if [ "${!var:-}" ]; then - val="${!var}" - elif [ "${!fileVar:-}" ]; then - val="$(< "${!fileVar}")" - fi - export "$var"="$val" - unset "$fileVar" -} - -# check to see if this file is being run or sourced from another script -_is_sourced() { - # https://unix.stackexchange.com/a/215279 - [ "${#FUNCNAME[@]}" -ge 2 ] \ - && [ "${FUNCNAME[0]}" = '_is_sourced' ] \ - && [ "${FUNCNAME[1]}" = 'source' ] -} - -# used to create initial postgres directories and if run as root, ensure ownership to the "postgres" user -docker_create_db_directories() { - local user; user="$(id -u)" - - mkdir -p "$PGDATA" - # ignore failure since there are cases where we can't chmod (and PostgreSQL might fail later anyhow - it's picky about permissions of this directory) - chmod 00700 "$PGDATA" || : - - # ignore failure since it will be fine when using the image provided directory; see also https://github.com/docker-library/postgres/pull/289 - mkdir -p /var/run/postgresql || : - chmod 03775 /var/run/postgresql || : - - # Create the transaction log directory before initdb is run so the directory is owned by the correct user - if [ -n "${POSTGRES_INITDB_WALDIR:-}" ]; then - mkdir -p "$POSTGRES_INITDB_WALDIR" - if [ "$user" = '0' ]; then - find "$POSTGRES_INITDB_WALDIR" \! -user postgres -exec chown postgres '{}' + - fi - chmod 700 "$POSTGRES_INITDB_WALDIR" - fi - - # allow the container to be started with `--user` - if [ "$user" = '0' ]; then - find "$PGDATA" \! -user postgres -exec chown postgres '{}' + - find /var/run/postgresql \! -user postgres -exec chown postgres '{}' + - fi -} - -# initialize empty PGDATA directory with new database via 'initdb' -# arguments to `initdb` can be passed via POSTGRES_INITDB_ARGS or as arguments to this function -# `initdb` automatically creates the "postgres", "template0", and "template1" dbnames -# this is also where the database user is created, specified by `POSTGRES_USER` env -docker_init_database_dir() { - # "initdb" is particular about the current user existing in "/etc/passwd", so we use "nss_wrapper" to fake that if necessary - # see https://github.com/docker-library/postgres/pull/253, https://github.com/docker-library/postgres/issues/359, https://cwrap.org/nss_wrapper.html - local uid; uid="$(id -u)" - if ! getent passwd "$uid" &> /dev/null; then - # see if we can find a suitable "libnss_wrapper.so" (https://salsa.debian.org/sssd-team/nss-wrapper/-/commit/b9925a653a54e24d09d9b498a2d913729f7abb15) - local wrapper - for wrapper in {/usr,}/lib{/*,}/libnss_wrapper.so; do - if [ -s "$wrapper" ]; then - NSS_WRAPPER_PASSWD="$(mktemp)" - NSS_WRAPPER_GROUP="$(mktemp)" - export LD_PRELOAD="$wrapper" NSS_WRAPPER_PASSWD NSS_WRAPPER_GROUP - local gid; gid="$(id -g)" - printf 'postgres:x:%s:%s:PostgreSQL:%s:/bin/false\n' "$uid" "$gid" "$PGDATA" > "$NSS_WRAPPER_PASSWD" - printf 'postgres:x:%s:\n' "$gid" > "$NSS_WRAPPER_GROUP" - break - fi - done - fi - - if [ -n "${POSTGRES_INITDB_WALDIR:-}" ]; then - set -- --waldir "$POSTGRES_INITDB_WALDIR" "$@" - fi - - # --pwfile refuses to handle a properly-empty file (hence the "\n"): https://github.com/docker-library/postgres/issues/1025 - eval 'initdb --username="$POSTGRES_USER" --pwfile=<(printf "%s\n" "$POSTGRES_PASSWORD") '"$POSTGRES_INITDB_ARGS"' "$@"' - - # unset/cleanup "nss_wrapper" bits - if [[ "${LD_PRELOAD:-}" == */libnss_wrapper.so ]]; then - rm -f "$NSS_WRAPPER_PASSWD" "$NSS_WRAPPER_GROUP" - unset LD_PRELOAD NSS_WRAPPER_PASSWD NSS_WRAPPER_GROUP - fi -} - -# print large warning if POSTGRES_PASSWORD is long -# error if both POSTGRES_PASSWORD is empty and POSTGRES_HOST_AUTH_METHOD is not 'trust' -# print large warning if POSTGRES_HOST_AUTH_METHOD is set to 'trust' -# assumes database is not set up, ie: [ -z "$DATABASE_ALREADY_EXISTS" ] -docker_verify_minimum_env() { - # check password first so we can output the warning before postgres - # messes it up - if [ "${#POSTGRES_PASSWORD}" -ge 100 ]; then - cat >&2 <<-'EOWARN' - - WARNING: The supplied POSTGRES_PASSWORD is 100+ characters. - - This will not work if used via PGPASSWORD with "psql". - - https://www.postgresql.org/message-id/flat/E1Rqxp2-0004Qt-PL%40wrigleys.postgresql.org (BUG #6412) - https://github.com/docker-library/postgres/issues/507 - - EOWARN - fi - if [ -z "$POSTGRES_PASSWORD" ] && [ 'trust' != "$POSTGRES_HOST_AUTH_METHOD" ]; then - # The - option suppresses leading tabs but *not* spaces. :) - cat >&2 <<-'EOE' - Error: Database is uninitialized and superuser password is not specified. - You must specify POSTGRES_PASSWORD to a non-empty value for the - superuser. For example, "-e POSTGRES_PASSWORD=password" on "docker run". - - You may also use "POSTGRES_HOST_AUTH_METHOD=trust" to allow all - connections without a password. This is *not* recommended. - - See PostgreSQL documentation about "trust": - https://www.postgresql.org/docs/current/auth-trust.html - EOE - exit 1 - fi - if [ 'trust' = "$POSTGRES_HOST_AUTH_METHOD" ]; then - cat >&2 <<-'EOWARN' - ******************************************************************************** - WARNING: POSTGRES_HOST_AUTH_METHOD has been set to "trust". This will allow - anyone with access to the Postgres port to access your database without - a password, even if POSTGRES_PASSWORD is set. See PostgreSQL - documentation about "trust": - https://www.postgresql.org/docs/current/auth-trust.html - In Docker's default configuration, this is effectively any other - container on the same system. - - It is not recommended to use POSTGRES_HOST_AUTH_METHOD=trust. Replace - it with "-e POSTGRES_PASSWORD=password" instead to set a password in - "docker run". - ******************************************************************************** - EOWARN - fi -} - -# usage: docker_process_init_files [file [file [...]]] -# ie: docker_process_init_files /always-initdb.d/* -# process initializer files, based on file extensions and permissions -docker_process_init_files() { - # psql here for backwards compatibility "${psql[@]}" - psql=( docker_process_sql ) - - printf '\n' - local f - for f; do - case "$f" in - *.sh) - # https://github.com/docker-library/postgres/issues/450#issuecomment-393167936 - # https://github.com/docker-library/postgres/pull/452 - if [ -x "$f" ]; then - printf '%s: running %s\n' "$0" "$f" - "$f" - else - printf '%s: sourcing %s\n' "$0" "$f" - . "$f" - fi - ;; - *.sql) printf '%s: running %s\n' "$0" "$f"; docker_process_sql -f "$f"; printf '\n' ;; - *.sql.gz) printf '%s: running %s\n' "$0" "$f"; gunzip -c "$f" | docker_process_sql; printf '\n' ;; - *.sql.xz) printf '%s: running %s\n' "$0" "$f"; xzcat "$f" | docker_process_sql; printf '\n' ;; - *.sql.zst) printf '%s: running %s\n' "$0" "$f"; zstd -dc "$f" | docker_process_sql; printf '\n' ;; - *) printf '%s: ignoring %s\n' "$0" "$f" ;; - esac - printf '\n' - done -} - -# Execute sql script, passed via stdin (or -f flag of pqsl) -# usage: docker_process_sql [psql-cli-args] -# ie: docker_process_sql --dbname=mydb <<<'INSERT ...' -# ie: docker_process_sql -f my-file.sql -# ie: docker_process_sql > "$PGDATA/pg_hba.conf" -} - -# start socket-only postgresql server for setting up or running scripts -# all arguments will be passed along as arguments to `postgres` (via pg_ctl) -docker_temp_server_start() { - if [ "$1" = 'postgres' ]; then - shift - fi - - # internal start of server in order to allow setup using psql client - # does not listen on external TCP/IP and waits until start finishes - set -- "$@" -c listen_addresses='' -p "${PGPORT:-5432}" - - PGUSER="${PGUSER:-$POSTGRES_USER}" \ - pg_ctl -D "$PGDATA" \ - -o "$(printf '%q ' "$@")" \ - -w start -} - -# stop postgresql server after done setting up user and running scripts -docker_temp_server_stop() { - PGUSER="${PGUSER:-postgres}" \ - pg_ctl -D "$PGDATA" -m fast -w stop -} - -# Initialise PG data directory in a temp location with a specific locale -initdb_locale() { - echo "Initialising PostgreSQL ${PGTARGET} data directory" - bin_path=$(get_bin_path) - ${bin_path}/initdb --username="${POSTGRES_USER}" ${POSTGRES_INITDB_ARGS} ${PGDATA}/new/ -} - -# check arguments for an option that would cause postgres to stop -# return true if there is one -_pg_want_help() { - local arg - for arg; do - case "$arg" in - # postgres --help | grep 'then exit' - # leaving out -C on purpose since it always fails and is unhelpful: - # postgres: could not access the server configuration file "/var/lib/postgresql/data/postgresql.conf": No such file or directory - -'?'|--help|--describe-config|-V|--version) - return 0 - ;; - esac - done - return 1 -} - -get_bin_path() { - if [ -f /etc/alpine-release ]; then - echo "/usr/local/bin" - else - echo "/usr/lib/postgresql/${PGTARGET}/bin" - fi -} - -# Function to create the upgrade lock file -create_upgrade_lock_file() { - echo "Creating upgrade lock file at $UPGRADE_LOCK_FILE" - touch "$UPGRADE_LOCK_FILE" -} - -# Function to remove the upgrade lock file -remove_upgrade_lock_file() { - echo "Removing upgrade lock file at $UPGRADE_LOCK_FILE" - rm -f "$UPGRADE_LOCK_FILE" -} - -_main() { - # if first arg looks like a flag, assume we want to run postgres server - if [ "${1:0:1}" = '-' ]; then - set -- postgres "$@" - fi - - if [ "$1" = 'postgres' ] && ! _pg_want_help "$@"; then - docker_setup_env - # setup data directories and permissions (when run as root) - docker_create_db_directories - if [ "$(id -u)" = '0' ]; then - exec gosu postgres "$BASH_SOURCE" "$@" - fi - - # only run initialization on an empty data directory - if [ -z "$DATABASE_ALREADY_EXISTS" ]; then - docker_verify_minimum_env - - # check dir permissions to reduce likelihood of half-initialized database - ls /docker-entrypoint-initdb.d/ > /dev/null - - docker_init_database_dir - pg_setup_hba_conf "$@" - - # PGPASSWORD is required for psql when authentication is required for 'local' connections via pg_hba.conf and is otherwise harmless - # e.g. when '--auth=md5' or '--auth-local=md5' is used in POSTGRES_INITDB_ARGS - export PGPASSWORD="${PGPASSWORD:-$POSTGRES_PASSWORD}" - docker_temp_server_start "$@" - - docker_setup_db - docker_process_init_files /docker-entrypoint-initdb.d/* - - docker_temp_server_stop - unset PGPASSWORD - - cat <<-'EOM' - - PostgreSQL init process complete; ready for start up. - - EOM - else - cat <<-'EOM' - - PostgreSQL Database directory appears to contain a database; Skipping initialization - - EOM - fi - fi - - # For development of pgautoupgrade. This spot leaves the container running, prior to the pgautoupgrade scripting - # executing - local UPGRADE_PERFORMED=0 - if [ "x${PGAUTO_DEVEL}" = "xbefore" ]; then - echo "--------------------------------------------------------------------------" - echo "In pgautoupgrade development mode, paused prior to pgautoupgrade scripting" - echo "--------------------------------------------------------------------------" - while :; do - sleep 5 - done - else - ### The main pgautoupgrade scripting starts here ### - - echo "************************************" - echo "PostgreSQL data directory: ${PGDATA}" - echo "************************************" - - # Get the version of the PostgreSQL data files - local PGVER=${PGTARGET} - if [ -s "${PGDATA}/PG_VERSION" ]; then - PGVER=$(cat "${PGDATA}/PG_VERSION") - fi - - # If the version of PostgreSQL data files doesn't match our desired version, then upgrade them - if [ "${PGVER}" != "${PGTARGET}" ]; then - create_upgrade_lock_file - # Ensure the database files are a version we can upgrade - local RECOGNISED=0 - local OLDPATH=unset - if [ "${PGVER}" = "9.5" ] || [ "${PGVER}" = "9.6" ] || [ "${PGVER}" = "10" ] || [ "${PGVER}" = "11" ] || [ "${PGVER}" = "12" ]; then - RECOGNISED=1 - fi - if [ "${PGTARGET}" -gt 13 ] && [ "${PGVER}" = "13" ]; then - RECOGNISED=1 - fi - if [ "${PGTARGET}" -gt 14 ] && [ "${PGVER}" = "14" ]; then - RECOGNISED=1 - fi - if [ "${PGTARGET}" -gt 15 ] && [ "${PGVER}" = "15" ]; then - RECOGNISED=1 - fi - if [ "${PGTARGET}" -gt 16 ] && [ "${PGVER}" = "16" ]; then - RECOGNISED=1 - fi - if [ "${RECOGNISED}" -eq 1 ]; then - OLDPATH="/usr/local-pg${PGVER}" - echo "*******************************************************************************************" - echo "Performing PG upgrade on version ${PGVER} database files. Upgrading to version ${PGTARGET}" - echo "*******************************************************************************************" - else - echo "****************************************************************************" - echo "Unrecognised version of PostgreSQL database files found, aborting completely" - echo "****************************************************************************" - exit 9 - fi - - # Check for presence of old/new directories, indicating a failed previous autoupgrade - echo "----------------------------------------------------------------------" - echo "Checking for left over artifacts from a failed previous autoupgrade..." - echo "----------------------------------------------------------------------" - local OLD="${PGDATA}/old" - local NEW="${PGDATA}/new" - if [ -d "${OLD}" ]; then - echo "*****************************************" - echo "Left over OLD directory found. Aborting." - echo "*****************************************" - exit 10 - fi - if [ -d "${NEW}" ]; then - echo "*****************************************" - echo "Left over NEW directory found. Aborting." - echo "*****************************************" - exit 11 - fi - echo "-------------------------------------------------------------------------------" - echo "No artifacts found from a failed previous autoupgrade. Continuing the process." - echo "-------------------------------------------------------------------------------" - - EXISTING_POSTGRESQL_CONF=0 - EXISTING_PG_HBA_CONF=0 - EXISTING_PGDATA_PERMISSIONS=$(stat -c %a "$PGDATA") - EXISTING_PGDATA_OWNER_GROUP=$(stat -c "%u:%g" "$PGDATA") - - if test -f "${PGDATA}"; then - EXISTING_POSTGRESQL_CONF=1 - else - echo "-------------------------------------------------------------------------------" - echo "The Postgres data directory at ${PGDATA} is missing a postgresql.conf file. Copying a standard version of ours." - echo "-------------------------------------------------------------------------------" - cp -f /opt/pgautoupgrade/postgresql.conf "${PGDATA}/postgresql.conf" - fi - - if test -f "${PGDATA}"; then - EXISTING_PG_HBA_CONF=1 - else - echo "-------------------------------------------------------------------------------" - echo "The Postgres data directory at ${PGDATA} is missing a pg_hba.conf file. Copying a standard version of ours." - echo "-------------------------------------------------------------------------------" - cp -f /opt/pgautoupgrade/pg_hba.conf "${PGDATA}/pg_hba.conf" - fi - - # Don't automatically abort on non-0 exit status, as that messes with these upcoming mv commands - set +e - - # Move the PostgreSQL data files into a subdirectory of the mount point - echo "---------------------------------------" - echo "Creating OLD temporary directory ${OLD}" - echo "---------------------------------------" - mkdir "${OLD}" - if [ ! -d "${OLD}" ]; then - echo "*********************************************************************" - echo "Creation of temporary directory '${OLD}' failed. Aborting completely" - echo "*********************************************************************" - exit 7 - fi - echo "--------------------------------------------" - echo "Creating OLD temporary directory is complete" - echo "--------------------------------------------" - - echo "-------------------------------------------------------" - echo "Moving existing data files into OLD temporary directory" - echo "-------------------------------------------------------" - mv -v "${PGDATA}"/* "${OLD}" - echo "-------------------------------------------------------------------" - echo "Moving existing data files into OLD temporary directory is complete" - echo "-------------------------------------------------------------------" - - echo "---------------------------------------" - echo "Creating NEW temporary directory ${NEW}" - echo "---------------------------------------" - mkdir "${NEW}" - if [ ! -d "${NEW}" ]; then - echo "********************************************************************" - echo "Creation of temporary directory '${NEW}' failed. Aborting completely" - echo "********************************************************************" - # With a failure at this point we should be able to move the old data back - # to its original location - mv -v "${OLD}"/* "${PGDATA}" - rmdir old - exit 8 - fi - echo "--------------------------------------------" - echo "Creating NEW temporary directory is complete" - echo "--------------------------------------------" - - echo "-----------------------------------------------------" - echo "Changing permissions of temporary directories to 0700" - echo "-----------------------------------------------------" - chmod 0700 "${OLD}" "${NEW}" - echo "---------------------------------------------------------" - echo "Changing permissions of temporary directories is complete" - echo "---------------------------------------------------------" - - # Return the error handling back to automatically aborting on non-0 exit status - set -e - - # If no initdb arguments were passed to us from the environment, then work out something valid ourselves - if [ "x${POSTGRES_INITDB_ARGS}" != "x" ]; then - echo "------------------------------------------------------------------------------" - echo "Using initdb arguments passed in from the environment: ${POSTGRES_INITDB_ARGS}" - echo "------------------------------------------------------------------------------" - else - echo "-------------------------------------------------" - echo "Remove postmaster.pid file from PG data directory" - echo "-------------------------------------------------" - rm -f "${OLD}"/postmaster.pid - - echo "------------------------------------" - echo "Determining our own initdb arguments" - echo "------------------------------------" - local COLLATE=unset - local CTYPE=unset - local ENCODING=unset - - ENCODING=$(echo 'SHOW SERVER_ENCODING' | "${OLDPATH}/bin/postgres" --single -D "${OLD}" "${POSTGRES_DB}" | grep 'server_encoding = "' | cut -d '"' -f 2) - - # LC_COLLATE and LC_TYPE have been removed with PG v16 - # https://www.postgresql.org/docs/release/16.0/ - if [ "${PGVER}" -lt 16 ]; then - COLLATE=$(echo 'SHOW LC_COLLATE' | "${OLDPATH}/bin/postgres" --single -D "${OLD}" "${POSTGRES_DB}" | grep 'lc_collate = "' | cut -d '"' -f 2) - CTYPE=$(echo 'SHOW LC_CTYPE' | "${OLDPATH}/bin/postgres" --single -D "${OLD}" "${POSTGRES_DB}" | grep 'lc_ctype = "' | cut -d '"' -f 2) - - POSTGRES_INITDB_ARGS="--locale=${COLLATE} --lc-collate=${COLLATE} --lc-ctype=${CTYPE} --encoding=${ENCODING}" - else - POSTGRES_INITDB_ARGS="--encoding=${ENCODING}" - fi - - echo "---------------------------------------------------------------" - echo "The initdb arguments we determined are: ${POSTGRES_INITDB_ARGS}" - echo "---------------------------------------------------------------" - fi - - # Initialise the new PostgreSQL database directory - echo "--------------------------------------------------------------------------------------------------------------------" - echo "Old database using collation settings: '${POSTGRES_INITDB_ARGS}'. Initialising new database with those settings too" - echo "--------------------------------------------------------------------------------------------------------------------" - initdb_locale "${POSTGRES_INITDB_ARGS}" - echo "------------------------------------" - echo "New database initialisation complete" - echo "------------------------------------" - - # Change into the PostgreSQL database directory, to avoid a pg_upgrade error about write permissions - cd "${PGDATA}" - - # Run the pg_upgrade command itself - echo "---------------------------------------" - echo "Running pg_upgrade command, from $(pwd)" - echo "---------------------------------------" - bin_path=$(get_bin_path) - "${bin_path}/pg_upgrade" --username="${POSTGRES_USER}" --link -d "${OLD}" -D "${NEW}" -b "${OLDPATH}/bin" -B "${bin_path}" --socketdir="/var/run/postgresql" - echo "--------------------------------------" - echo "Running pg_upgrade command is complete" - echo "--------------------------------------" - - # Move the new database files into place - echo "-----------------------------------------------------" - echo "Moving the new database files to the active directory" - echo "-----------------------------------------------------" - mv -v "${NEW}"/* "${PGDATA}" - echo "-----------------------------------------" - echo "Moving the new database files is complete" - echo "-----------------------------------------" - - # Re-use the pg_hba.conf and pg_ident.conf from the old data directory - echo "--------------------------------------------------------------" - echo "Copying the old pg_hba and pg_ident configuration files across" - echo "--------------------------------------------------------------" - cp -f "${OLD}/pg_hba.conf" "${OLD}/pg_ident.conf" "${PGDATA}" - echo "-------------------------------------------------------------------" - echo "Copying the old pg_hba and pg_ident configuration files is complete" - echo "-------------------------------------------------------------------" - - # Remove the left over database files - echo "---------------------------------" - echo "Removing left over database files" - echo "---------------------------------" - rm -rf "${OLD}" "${NEW}" ~/delete_old_cluster.sh - echo "---------------------------------------------" - echo "Removing left over database files is complete" - echo "---------------------------------------------" - - UPGRADE_PERFORMED=1 - - echo "***************************************************************************************" - echo "Automatic upgrade process finished upgrading the data format to PostgreSQL ${PGTARGET}." - echo "The database has not yet been reindexed nor updated the query planner stats. Those " - echo "will be done by a background task shortly. " - echo "***************************************************************************************" - remove_upgrade_lock_file - fi - - ### The main pgautoupgrade scripting ends here ### - fi - - # For development of pgautoupgrade. This spot leaves the container running, after the pgautoupgrade scripting has - # executed, but without subsequently running the PostgreSQL server - if [ "x${PGAUTO_DEVEL}" = "xserver" ]; then - echo "-------------------------------------------------------------------" - echo "In pgautoupgrade development mode, paused after main pg_upgrade has" - echo "run, but before database server and post-upgrade tasks have started" - echo "-------------------------------------------------------------------" - while :; do - sleep 5 - done - else - # If the upgrade process ran, then we need to launch the post-upgrade script in the background while PG runs - if [ "${UPGRADE_PERFORMED}" -eq 1 ]; then - /usr/local/bin/pgautoupgrade-postupgrade.sh "${PGDATA}" "${POSTGRES_DB}" "${PGAUTO_ONESHOT}" "$EXISTING_POSTGRESQL_CONF" "$EXISTING_PG_HBA_CONF" "$EXISTING_PGDATA_PERMISSIONS" "$EXISTING_PGDATA_OWNER_GROUP" 2>&1 & - echo "****************************" - echo "Post upgrade script launched" - echo "****************************" - - # Start PostgreSQL - exec "$@" - else - # If no upgrade was performed, then we start PostgreSQL as per normal as long as "one shot" mode wasn't requested - if [ "x${PGAUTO_ONESHOT}" = "xyes" ]; then - echo "***********************************************************************************" - echo "'One shot' automatic upgrade was requested, so exiting as there is no upgrade to do" - echo "If you're seeing this message and expecting an upgrade to be happening, it probably" - echo "means the container is being started in a loop and a previous run already did it :)" - echo "***********************************************************************************" - else - # Start PostgreSQL - exec "$@" - fi - fi - fi - - # Run a sync before exiting, just to ensure everything is flushed to disk before docker terminates the process - sync -} - -# Check if an upgrade lock file exists at script start and exit if it does -if [ -f "$UPGRADE_LOCK_FILE" ]; then - echo "Upgrade lock file already exists, indicating an incomplete previous upgrade. Exiting." - exit 1 +EXISTING_PG_HBA_CONF=0 +EXISTING_PGDATA_PERMISSIONS=$(stat -c %a "$PGDATA") +EXISTING_PGDATA_OWNER_GROUP=$(stat -c "%u:%g" "$PGDATA") +EXISTING_POSTGRESQL_CONF=0 + +# if PGDATA exists, the database directory is likely already initialized +# if coming from a Bitnami image, we need to inject a postgresql.conf and pg_hba.conf file +# and if they requested "one shot" mode, we will remove it again so they can continue to use the Bitnami image +if [ -d "$PGDATA" ]; then + if [test -f "${PGDATA}/postgresql.conf"]; then + EXISTING_POSTGRESQL_CONF=1 + else + echo "-------------------------------------------------------------------------------" + echo "The Postgres data directory at ${PGDATA} is missing a postgresql.conf file. Copying a standard version of ours." + echo "-------------------------------------------------------------------------------" + cp -f /opt/pgautoupgrade/postgresql.conf "${PGDATA}/postgresql.conf" + fi + + if [test -f "${PGDATA}/pg_hba.conf"]; then + EXISTING_PG_HBA_CONF=1 + else + echo "-------------------------------------------------------------------------------" + echo "The Postgres data directory at ${PGDATA} is missing a pg_hba.conf file. Copying a standard version of ours." + echo "-------------------------------------------------------------------------------" + cp -f /opt/pgautoupgrade/pg_hba.conf "${PGDATA}/pg_hba.conf" + fi fi -if ! _is_sourced; then - _main "$@" +/usr/local/bin/postgres-docker-entrypoint.sh "$@" + +if [ "x${PGAUTO_ONESHOT}" = "xyes" ]; then + if [ "$EXISTING_POSTGRESQL_CONF" = "0" ]; then + echo "-------------------------------------------------------------------------------" + echo "Removing postgresql.conf from ${PGDATA}, as it was not provided by the data directory before the upgrade." + echo "-------------------------------------------------------------------------------" + rm -rf "${PGDATA}/postgresql.conf" + fi + + if [ "$EXISTING_PG_HBA_CONF" = "0" ]; then + echo "-------------------------------------------------------------------------------" + echo "Removing pg_hba.conf from ${PGDATA}, as it was not provided by the data directory before the upgrade." + echo "-------------------------------------------------------------------------------" + rm -rf "${PGDATA}/pg_hba.conf" + fi + + echo "-------------------------------------------------------------------------------" + echo "Restoring original data permissions to ${PGDATA}" + echo "-------------------------------------------------------------------------------" + chmod -R $EXISTING_PGDATA_PERMISSIONS "$PGDATA" + chown -R $EXISTING_PGDATA_OWNER_GROUP "$PGDATA" fi + +# Run a sync before exiting, just to ensure everything is flushed to disk before docker terminates the process +sync diff --git a/pgautoupgrade-postupgrade.sh b/pgautoupgrade-postupgrade.sh index c6bc644..92e5d24 100755 --- a/pgautoupgrade-postupgrade.sh +++ b/pgautoupgrade-postupgrade.sh @@ -2,18 +2,14 @@ set -e -if [ $# -ne 7 ]; then - echo "Required number of arguments not passed to post upgrade script. 7 expected, $# received" +if [ $# -ne 3 ]; then + echo "Required number of arguments not passed to post upgrade script. 3 expected, $# received" exit 1 fi PGDATA=$1 POSTGRES_DB=$2 PGAUTO_ONESHOT=$3 -EXISTING_POSTGRESQL_CONF=$4 -EXISTING_PG_HBA_CONF=$5 -EXISTING_PGDATA_PERMISSIONS=$6 -EXISTING_PGDATA_OWNER_GROUP=$7 # Wait for PostgreSQL to start and become available COUNT=0 @@ -87,27 +83,7 @@ if [ "x${PGAUTO_ONESHOT}" = "xyes" ]; then echo "'One shot' automatic upgrade was requested, so exiting now that the post upgrade tasks have finished" echo "****************************************************************************************************" - if [ "$EXISTING_POSTGRESQL_CONF" = "0" ]; then - echo "-------------------------------------------------------------------------------" - echo "Removing postgresql.conf from ${PGDATA}, as it was not provided by the data directory before the upgrade." - echo "-------------------------------------------------------------------------------" - rm -rf "${PGDATA}/postgresql.conf" - fi - - if [ "$EXISTING_PG_HBA_CONF" = "0" ]; then - echo "-------------------------------------------------------------------------------" - echo "Removing pg_hba.conf from ${PGDATA}, as it was not provided by the data directory before the upgrade." - echo "-------------------------------------------------------------------------------" - rm -rf "${PGDATA}/pg_hba.conf" - fi - pg_ctl stop -D "${PGDATA}" - - echo "-------------------------------------------------------------------------------" - echo "Restoring original data permissions to ${PGDATA}" - echo "-------------------------------------------------------------------------------" - chmod -R "$EXISTING_PGDATA_PERMISSIONS" "$PGDATA" - chown -R "$EXISTING_PGDATA_OWNER_GROUP" "$PGDATA" else echo "*************************************************************************************************" echo "Post upgrade tasks have finished successfully. PostgreSQL should now be fully updated and online" diff --git a/postgres-docker-entrypoint.sh b/postgres-docker-entrypoint.sh new file mode 100755 index 0000000..a4299e4 --- /dev/null +++ b/postgres-docker-entrypoint.sh @@ -0,0 +1,671 @@ +#!/usr/bin/env bash +set -Eeo pipefail +# TODO swap to -Eeuo pipefail above (after handling all potentially-unset variables) + +# Define the path to the upgrade lock file using PGDATA if set, otherwise default +UPGRADE_LOCK_FILE="${PGDATA:-/var/lib/postgresql/data}/upgrade_in_progress.lock" + +# usage: file_env VAR [DEFAULT] +# ie: file_env 'XYZ_DB_PASSWORD' 'example' +# (will allow for "$XYZ_DB_PASSWORD_FILE" to fill in the value of +# "$XYZ_DB_PASSWORD" from a file, especially for Docker's secrets feature) +file_env() { + local var="$1" + local fileVar="${var}_FILE" + local def="${2:-}" + if [ "${!var:-}" ] && [ "${!fileVar:-}" ]; then + printf >&2 'error: both %s and %s are set (but are exclusive)\n' "$var" "$fileVar" + exit 1 + fi + local val="$def" + if [ "${!var:-}" ]; then + val="${!var}" + elif [ "${!fileVar:-}" ]; then + val="$(< "${!fileVar}")" + fi + export "$var"="$val" + unset "$fileVar" +} + +# check to see if this file is being run or sourced from another script +_is_sourced() { + # https://unix.stackexchange.com/a/215279 + [ "${#FUNCNAME[@]}" -ge 2 ] \ + && [ "${FUNCNAME[0]}" = '_is_sourced' ] \ + && [ "${FUNCNAME[1]}" = 'source' ] +} + +# used to create initial postgres directories and if run as root, ensure ownership to the "postgres" user +docker_create_db_directories() { + local user; user="$(id -u)" + + mkdir -p "$PGDATA" + # ignore failure since there are cases where we can't chmod (and PostgreSQL might fail later anyhow - it's picky about permissions of this directory) + chmod 00700 "$PGDATA" || : + + # ignore failure since it will be fine when using the image provided directory; see also https://github.com/docker-library/postgres/pull/289 + mkdir -p /var/run/postgresql || : + chmod 03775 /var/run/postgresql || : + + # Create the transaction log directory before initdb is run so the directory is owned by the correct user + if [ -n "${POSTGRES_INITDB_WALDIR:-}" ]; then + mkdir -p "$POSTGRES_INITDB_WALDIR" + if [ "$user" = '0' ]; then + find "$POSTGRES_INITDB_WALDIR" \! -user postgres -exec chown postgres '{}' + + fi + chmod 700 "$POSTGRES_INITDB_WALDIR" + fi + + # allow the container to be started with `--user` + if [ "$user" = '0' ]; then + find "$PGDATA" \! -user postgres -exec chown postgres '{}' + + find /var/run/postgresql \! -user postgres -exec chown postgres '{}' + + fi +} + +# initialize empty PGDATA directory with new database via 'initdb' +# arguments to `initdb` can be passed via POSTGRES_INITDB_ARGS or as arguments to this function +# `initdb` automatically creates the "postgres", "template0", and "template1" dbnames +# this is also where the database user is created, specified by `POSTGRES_USER` env +docker_init_database_dir() { + # "initdb" is particular about the current user existing in "/etc/passwd", so we use "nss_wrapper" to fake that if necessary + # see https://github.com/docker-library/postgres/pull/253, https://github.com/docker-library/postgres/issues/359, https://cwrap.org/nss_wrapper.html + local uid; uid="$(id -u)" + if ! getent passwd "$uid" &> /dev/null; then + # see if we can find a suitable "libnss_wrapper.so" (https://salsa.debian.org/sssd-team/nss-wrapper/-/commit/b9925a653a54e24d09d9b498a2d913729f7abb15) + local wrapper + for wrapper in {/usr,}/lib{/*,}/libnss_wrapper.so; do + if [ -s "$wrapper" ]; then + NSS_WRAPPER_PASSWD="$(mktemp)" + NSS_WRAPPER_GROUP="$(mktemp)" + export LD_PRELOAD="$wrapper" NSS_WRAPPER_PASSWD NSS_WRAPPER_GROUP + local gid; gid="$(id -g)" + printf 'postgres:x:%s:%s:PostgreSQL:%s:/bin/false\n' "$uid" "$gid" "$PGDATA" > "$NSS_WRAPPER_PASSWD" + printf 'postgres:x:%s:\n' "$gid" > "$NSS_WRAPPER_GROUP" + break + fi + done + fi + + if [ -n "${POSTGRES_INITDB_WALDIR:-}" ]; then + set -- --waldir "$POSTGRES_INITDB_WALDIR" "$@" + fi + + # --pwfile refuses to handle a properly-empty file (hence the "\n"): https://github.com/docker-library/postgres/issues/1025 + eval 'initdb --username="$POSTGRES_USER" --pwfile=<(printf "%s\n" "$POSTGRES_PASSWORD") '"$POSTGRES_INITDB_ARGS"' "$@"' + + # unset/cleanup "nss_wrapper" bits + if [[ "${LD_PRELOAD:-}" == */libnss_wrapper.so ]]; then + rm -f "$NSS_WRAPPER_PASSWD" "$NSS_WRAPPER_GROUP" + unset LD_PRELOAD NSS_WRAPPER_PASSWD NSS_WRAPPER_GROUP + fi +} + +# print large warning if POSTGRES_PASSWORD is long +# error if both POSTGRES_PASSWORD is empty and POSTGRES_HOST_AUTH_METHOD is not 'trust' +# print large warning if POSTGRES_HOST_AUTH_METHOD is set to 'trust' +# assumes database is not set up, ie: [ -z "$DATABASE_ALREADY_EXISTS" ] +docker_verify_minimum_env() { + # check password first so we can output the warning before postgres + # messes it up + if [ "${#POSTGRES_PASSWORD}" -ge 100 ]; then + cat >&2 <<-'EOWARN' + + WARNING: The supplied POSTGRES_PASSWORD is 100+ characters. + + This will not work if used via PGPASSWORD with "psql". + + https://www.postgresql.org/message-id/flat/E1Rqxp2-0004Qt-PL%40wrigleys.postgresql.org (BUG #6412) + https://github.com/docker-library/postgres/issues/507 + + EOWARN + fi + if [ -z "$POSTGRES_PASSWORD" ] && [ 'trust' != "$POSTGRES_HOST_AUTH_METHOD" ]; then + # The - option suppresses leading tabs but *not* spaces. :) + cat >&2 <<-'EOE' + Error: Database is uninitialized and superuser password is not specified. + You must specify POSTGRES_PASSWORD to a non-empty value for the + superuser. For example, "-e POSTGRES_PASSWORD=password" on "docker run". + + You may also use "POSTGRES_HOST_AUTH_METHOD=trust" to allow all + connections without a password. This is *not* recommended. + + See PostgreSQL documentation about "trust": + https://www.postgresql.org/docs/current/auth-trust.html + EOE + exit 1 + fi + if [ 'trust' = "$POSTGRES_HOST_AUTH_METHOD" ]; then + cat >&2 <<-'EOWARN' + ******************************************************************************** + WARNING: POSTGRES_HOST_AUTH_METHOD has been set to "trust". This will allow + anyone with access to the Postgres port to access your database without + a password, even if POSTGRES_PASSWORD is set. See PostgreSQL + documentation about "trust": + https://www.postgresql.org/docs/current/auth-trust.html + In Docker's default configuration, this is effectively any other + container on the same system. + + It is not recommended to use POSTGRES_HOST_AUTH_METHOD=trust. Replace + it with "-e POSTGRES_PASSWORD=password" instead to set a password in + "docker run". + ******************************************************************************** + EOWARN + fi +} + +# usage: docker_process_init_files [file [file [...]]] +# ie: docker_process_init_files /always-initdb.d/* +# process initializer files, based on file extensions and permissions +docker_process_init_files() { + # psql here for backwards compatibility "${psql[@]}" + psql=( docker_process_sql ) + + printf '\n' + local f + for f; do + case "$f" in + *.sh) + # https://github.com/docker-library/postgres/issues/450#issuecomment-393167936 + # https://github.com/docker-library/postgres/pull/452 + if [ -x "$f" ]; then + printf '%s: running %s\n' "$0" "$f" + "$f" + else + printf '%s: sourcing %s\n' "$0" "$f" + . "$f" + fi + ;; + *.sql) printf '%s: running %s\n' "$0" "$f"; docker_process_sql -f "$f"; printf '\n' ;; + *.sql.gz) printf '%s: running %s\n' "$0" "$f"; gunzip -c "$f" | docker_process_sql; printf '\n' ;; + *.sql.xz) printf '%s: running %s\n' "$0" "$f"; xzcat "$f" | docker_process_sql; printf '\n' ;; + *.sql.zst) printf '%s: running %s\n' "$0" "$f"; zstd -dc "$f" | docker_process_sql; printf '\n' ;; + *) printf '%s: ignoring %s\n' "$0" "$f" ;; + esac + printf '\n' + done +} + +# Execute sql script, passed via stdin (or -f flag of pqsl) +# usage: docker_process_sql [psql-cli-args] +# ie: docker_process_sql --dbname=mydb <<<'INSERT ...' +# ie: docker_process_sql -f my-file.sql +# ie: docker_process_sql > "$PGDATA/pg_hba.conf" +} + +# start socket-only postgresql server for setting up or running scripts +# all arguments will be passed along as arguments to `postgres` (via pg_ctl) +docker_temp_server_start() { + if [ "$1" = 'postgres' ]; then + shift + fi + + # internal start of server in order to allow setup using psql client + # does not listen on external TCP/IP and waits until start finishes + set -- "$@" -c listen_addresses='' -p "${PGPORT:-5432}" + + PGUSER="${PGUSER:-$POSTGRES_USER}" \ + pg_ctl -D "$PGDATA" \ + -o "$(printf '%q ' "$@")" \ + -w start +} + +# stop postgresql server after done setting up user and running scripts +docker_temp_server_stop() { + PGUSER="${PGUSER:-postgres}" \ + pg_ctl -D "$PGDATA" -m fast -w stop +} + +# Initialise PG data directory in a temp location with a specific locale +initdb_locale() { + echo "Initialising PostgreSQL ${PGTARGET} data directory" + bin_path=$(get_bin_path) + ${bin_path}/initdb --username="${POSTGRES_USER}" ${POSTGRES_INITDB_ARGS} ${PGDATA}/new/ +} + +# check arguments for an option that would cause postgres to stop +# return true if there is one +_pg_want_help() { + local arg + for arg; do + case "$arg" in + # postgres --help | grep 'then exit' + # leaving out -C on purpose since it always fails and is unhelpful: + # postgres: could not access the server configuration file "/var/lib/postgresql/data/postgresql.conf": No such file or directory + -'?'|--help|--describe-config|-V|--version) + return 0 + ;; + esac + done + return 1 +} + +get_bin_path() { + if [ -f /etc/alpine-release ]; then + echo "/usr/local/bin" + else + echo "/usr/lib/postgresql/${PGTARGET}/bin" + fi +} + +# Function to create the upgrade lock file +create_upgrade_lock_file() { + echo "Creating upgrade lock file at $UPGRADE_LOCK_FILE" + touch "$UPGRADE_LOCK_FILE" +} + +# Function to remove the upgrade lock file +remove_upgrade_lock_file() { + echo "Removing upgrade lock file at $UPGRADE_LOCK_FILE" + rm -f "$UPGRADE_LOCK_FILE" +} + +_main() { + # if first arg looks like a flag, assume we want to run postgres server + if [ "${1:0:1}" = '-' ]; then + set -- postgres "$@" + fi + + if [ "$1" = 'postgres' ] && ! _pg_want_help "$@"; then + docker_setup_env + # setup data directories and permissions (when run as root) + docker_create_db_directories + if [ "$(id -u)" = '0' ]; then + exec gosu postgres "$BASH_SOURCE" "$@" + fi + + # only run initialization on an empty data directory + if [ -z "$DATABASE_ALREADY_EXISTS" ]; then + docker_verify_minimum_env + + # check dir permissions to reduce likelihood of half-initialized database + ls /docker-entrypoint-initdb.d/ > /dev/null + + docker_init_database_dir + pg_setup_hba_conf "$@" + + # PGPASSWORD is required for psql when authentication is required for 'local' connections via pg_hba.conf and is otherwise harmless + # e.g. when '--auth=md5' or '--auth-local=md5' is used in POSTGRES_INITDB_ARGS + export PGPASSWORD="${PGPASSWORD:-$POSTGRES_PASSWORD}" + docker_temp_server_start "$@" + + docker_setup_db + docker_process_init_files /docker-entrypoint-initdb.d/* + + docker_temp_server_stop + unset PGPASSWORD + + cat <<-'EOM' + + PostgreSQL init process complete; ready for start up. + + EOM + else + cat <<-'EOM' + + PostgreSQL Database directory appears to contain a database; Skipping initialization + + EOM + fi + fi + + # For development of pgautoupgrade. This spot leaves the container running, prior to the pgautoupgrade scripting + # executing + local UPGRADE_PERFORMED=0 + if [ "x${PGAUTO_DEVEL}" = "xbefore" ]; then + echo "--------------------------------------------------------------------------" + echo "In pgautoupgrade development mode, paused prior to pgautoupgrade scripting" + echo "--------------------------------------------------------------------------" + while :; do + sleep 5 + done + else + ### The main pgautoupgrade scripting starts here ### + + echo "************************************" + echo "PostgreSQL data directory: ${PGDATA}" + echo "************************************" + + # Get the version of the PostgreSQL data files + local PGVER=${PGTARGET} + if [ -s "${PGDATA}/PG_VERSION" ]; then + PGVER=$(cat "${PGDATA}/PG_VERSION") + fi + + # If the version of PostgreSQL data files doesn't match our desired version, then upgrade them + if [ "${PGVER}" != "${PGTARGET}" ]; then + create_upgrade_lock_file + # Ensure the database files are a version we can upgrade + local RECOGNISED=0 + local OLDPATH=unset + if [ "${PGVER}" = "9.5" ] || [ "${PGVER}" = "9.6" ] || [ "${PGVER}" = "10" ] || [ "${PGVER}" = "11" ] || [ "${PGVER}" = "12" ]; then + RECOGNISED=1 + fi + if [ "${PGTARGET}" -gt 13 ] && [ "${PGVER}" = "13" ]; then + RECOGNISED=1 + fi + if [ "${PGTARGET}" -gt 14 ] && [ "${PGVER}" = "14" ]; then + RECOGNISED=1 + fi + if [ "${PGTARGET}" -gt 15 ] && [ "${PGVER}" = "15" ]; then + RECOGNISED=1 + fi + if [ "${PGTARGET}" -gt 16 ] && [ "${PGVER}" = "16" ]; then + RECOGNISED=1 + fi + if [ "${RECOGNISED}" -eq 1 ]; then + OLDPATH="/usr/local-pg${PGVER}" + echo "*******************************************************************************************" + echo "Performing PG upgrade on version ${PGVER} database files. Upgrading to version ${PGTARGET}" + echo "*******************************************************************************************" + else + echo "****************************************************************************" + echo "Unrecognised version of PostgreSQL database files found, aborting completely" + echo "****************************************************************************" + exit 9 + fi + + # Check for presence of old/new directories, indicating a failed previous autoupgrade + echo "----------------------------------------------------------------------" + echo "Checking for left over artifacts from a failed previous autoupgrade..." + echo "----------------------------------------------------------------------" + local OLD="${PGDATA}/old" + local NEW="${PGDATA}/new" + if [ -d "${OLD}" ]; then + echo "*****************************************" + echo "Left over OLD directory found. Aborting." + echo "*****************************************" + exit 10 + fi + if [ -d "${NEW}" ]; then + echo "*****************************************" + echo "Left over NEW directory found. Aborting." + echo "*****************************************" + exit 11 + fi + echo "-------------------------------------------------------------------------------" + echo "No artifacts found from a failed previous autoupgrade. Continuing the process." + echo "-------------------------------------------------------------------------------" + + export EXISTING_POSTGRESQL_CONF=0 + export EXISTING_PG_HBA_CONF=0 + + if test -f "${PGDATA}/postgresql.conf"; then + EXISTING_POSTGRESQL_CONF=1 + else + echo "-------------------------------------------------------------------------------" + echo "The Postgres data directory at ${PGDATA} is missing a postgresql.conf file. Copying a standard version of ours." + echo "-------------------------------------------------------------------------------" + cp -f /opt/pgautoupgrade/postgresql.conf "${PGDATA}/postgresql.conf" + fi + + if test -f "${PGDATA}/pg_hba.conf"; then + EXISTING_PG_HBA_CONF=1 + else + echo "-------------------------------------------------------------------------------" + echo "The Postgres data directory at ${PGDATA} is missing a pg_hba.conf file. Copying a standard version of ours." + echo "-------------------------------------------------------------------------------" + cp -f /opt/pgautoupgrade/pg_hba.conf "${PGDATA}/pg_hba.conf" + fi + + # Don't automatically abort on non-0 exit status, as that messes with these upcoming mv commands + set +e + + # Move the PostgreSQL data files into a subdirectory of the mount point + echo "---------------------------------------" + echo "Creating OLD temporary directory ${OLD}" + echo "---------------------------------------" + mkdir "${OLD}" + if [ ! -d "${OLD}" ]; then + echo "*********************************************************************" + echo "Creation of temporary directory '${OLD}' failed. Aborting completely" + echo "*********************************************************************" + exit 7 + fi + echo "--------------------------------------------" + echo "Creating OLD temporary directory is complete" + echo "--------------------------------------------" + + echo "-------------------------------------------------------" + echo "Moving existing data files into OLD temporary directory" + echo "-------------------------------------------------------" + mv -v "${PGDATA}"/* "${OLD}" + echo "-------------------------------------------------------------------" + echo "Moving existing data files into OLD temporary directory is complete" + echo "-------------------------------------------------------------------" + + echo "---------------------------------------" + echo "Creating NEW temporary directory ${NEW}" + echo "---------------------------------------" + mkdir "${NEW}" + if [ ! -d "${NEW}" ]; then + echo "********************************************************************" + echo "Creation of temporary directory '${NEW}' failed. Aborting completely" + echo "********************************************************************" + # With a failure at this point we should be able to move the old data back + # to its original location + mv -v "${OLD}"/* "${PGDATA}" + rmdir old + exit 8 + fi + echo "--------------------------------------------" + echo "Creating NEW temporary directory is complete" + echo "--------------------------------------------" + + echo "-----------------------------------------------------" + echo "Changing permissions of temporary directories to 0700" + echo "-----------------------------------------------------" + chmod 0700 "${OLD}" "${NEW}" + echo "---------------------------------------------------------" + echo "Changing permissions of temporary directories is complete" + echo "---------------------------------------------------------" + + # Return the error handling back to automatically aborting on non-0 exit status + set -e + + # If no initdb arguments were passed to us from the environment, then work out something valid ourselves + if [ "x${POSTGRES_INITDB_ARGS}" != "x" ]; then + echo "------------------------------------------------------------------------------" + echo "Using initdb arguments passed in from the environment: ${POSTGRES_INITDB_ARGS}" + echo "------------------------------------------------------------------------------" + else + echo "-------------------------------------------------" + echo "Remove postmaster.pid file from PG data directory" + echo "-------------------------------------------------" + rm -f "${OLD}"/postmaster.pid + + echo "------------------------------------" + echo "Determining our own initdb arguments" + echo "------------------------------------" + local COLLATE=unset + local CTYPE=unset + local ENCODING=unset + + ENCODING=$(echo 'SHOW SERVER_ENCODING' | "${OLDPATH}/bin/postgres" --single -D "${OLD}" "${POSTGRES_DB}" | grep 'server_encoding = "' | cut -d '"' -f 2) + + # LC_COLLATE and LC_TYPE have been removed with PG v16 + # https://www.postgresql.org/docs/release/16.0/ + if [ "${PGVER}" -lt 16 ]; then + COLLATE=$(echo 'SHOW LC_COLLATE' | "${OLDPATH}/bin/postgres" --single -D "${OLD}" "${POSTGRES_DB}" | grep 'lc_collate = "' | cut -d '"' -f 2) + CTYPE=$(echo 'SHOW LC_CTYPE' | "${OLDPATH}/bin/postgres" --single -D "${OLD}" "${POSTGRES_DB}" | grep 'lc_ctype = "' | cut -d '"' -f 2) + + POSTGRES_INITDB_ARGS="--locale=${COLLATE} --lc-collate=${COLLATE} --lc-ctype=${CTYPE} --encoding=${ENCODING}" + else + POSTGRES_INITDB_ARGS="--encoding=${ENCODING}" + fi + + echo "---------------------------------------------------------------" + echo "The initdb arguments we determined are: ${POSTGRES_INITDB_ARGS}" + echo "---------------------------------------------------------------" + fi + + # Initialise the new PostgreSQL database directory + echo "--------------------------------------------------------------------------------------------------------------------" + echo "Old database using collation settings: '${POSTGRES_INITDB_ARGS}'. Initialising new database with those settings too" + echo "--------------------------------------------------------------------------------------------------------------------" + initdb_locale "${POSTGRES_INITDB_ARGS}" + echo "------------------------------------" + echo "New database initialisation complete" + echo "------------------------------------" + + # Change into the PostgreSQL database directory, to avoid a pg_upgrade error about write permissions + cd "${PGDATA}" + + # Run the pg_upgrade command itself + echo "---------------------------------------" + echo "Running pg_upgrade command, from $(pwd)" + echo "---------------------------------------" + bin_path=$(get_bin_path) + "${bin_path}/pg_upgrade" --username="${POSTGRES_USER}" --link -d "${OLD}" -D "${NEW}" -b "${OLDPATH}/bin" -B "${bin_path}" --socketdir="/var/run/postgresql" + echo "--------------------------------------" + echo "Running pg_upgrade command is complete" + echo "--------------------------------------" + + # Move the new database files into place + echo "-----------------------------------------------------" + echo "Moving the new database files to the active directory" + echo "-----------------------------------------------------" + mv -v "${NEW}"/* "${PGDATA}" + echo "-----------------------------------------" + echo "Moving the new database files is complete" + echo "-----------------------------------------" + + # Re-use the pg_hba.conf and pg_ident.conf from the old data directory + echo "--------------------------------------------------------------" + echo "Copying the old pg_hba and pg_ident configuration files across" + echo "--------------------------------------------------------------" + cp -f "${OLD}/pg_hba.conf" "${OLD}/pg_ident.conf" "${PGDATA}" + echo "-------------------------------------------------------------------" + echo "Copying the old pg_hba and pg_ident configuration files is complete" + echo "-------------------------------------------------------------------" + + # Remove the left over database files + echo "---------------------------------" + echo "Removing left over database files" + echo "---------------------------------" + rm -rf "${OLD}" "${NEW}" ~/delete_old_cluster.sh + echo "---------------------------------------------" + echo "Removing left over database files is complete" + echo "---------------------------------------------" + + UPGRADE_PERFORMED=1 + + echo "***************************************************************************************" + echo "Automatic upgrade process finished upgrading the data format to PostgreSQL ${PGTARGET}." + echo "The database has not yet been reindexed nor updated the query planner stats. Those " + echo "will be done by a background task shortly. " + echo "***************************************************************************************" + remove_upgrade_lock_file + fi + + ### The main pgautoupgrade scripting ends here ### + fi + + # For development of pgautoupgrade. This spot leaves the container running, after the pgautoupgrade scripting has + # executed, but without subsequently running the PostgreSQL server + if [ "x${PGAUTO_DEVEL}" = "xserver" ]; then + echo "-------------------------------------------------------------------" + echo "In pgautoupgrade development mode, paused after main pg_upgrade has" + echo "run, but before database server and post-upgrade tasks have started" + echo "-------------------------------------------------------------------" + while :; do + sleep 5 + done + else + # If the upgrade process ran, then we need to launch the post-upgrade script in the background while PG runs + if [ "${UPGRADE_PERFORMED}" -eq 1 ]; then + /usr/local/bin/pgautoupgrade-postupgrade.sh "${PGDATA}" "${POSTGRES_DB}" "${PGAUTO_ONESHOT}" 2>&1 & + echo "****************************" + echo "Post upgrade script launched" + echo "****************************" + + # Start PostgreSQL + exec "$@" + else + # If no upgrade was performed, then we start PostgreSQL as per normal as long as "one shot" mode wasn't requested + if [ "x${PGAUTO_ONESHOT}" = "xyes" ]; then + echo "***********************************************************************************" + echo "'One shot' automatic upgrade was requested, so exiting as there is no upgrade to do" + echo "If you're seeing this message and expecting an upgrade to be happening, it probably" + echo "means the container is being started in a loop and a previous run already did it :)" + echo "***********************************************************************************" + else + # Start PostgreSQL + exec "$@" + fi + fi + fi +} + +# Check if an upgrade lock file exists at script start and exit if it does +if [ -f "$UPGRADE_LOCK_FILE" ]; then + echo "Upgrade lock file already exists, indicating an incomplete previous upgrade. Exiting." + exit 1 +fi + +if ! _is_sourced; then + _main "$@" +fi