github.com/diadata-org/diadata@v1.4.593/deployments/config/postgres-docker-entrypoint.sh (about)

     1  #!/usr/bin/env bash
     2  echo "Starting entrypoint script (${BASH_SOURCE[0]}) ..."
     3  echo "Current user: $(whoami)"
     4  echo "Current working directory: $(pwd)"
     5  echo; echo "Injected scripts /docker-entrypoint-initdb.d/:"
     6  ls -la /docker-entrypoint-initdb.d/
     7  echo; echo "Injected volumes (/mnt/):"
     8  ls -la /mnt/
     9  set -Eeo pipefail
    10  # TODO swap to -Eeuo pipefail above (after handling all potentially-unset variables)
    11  
    12  # usage: file_env VAR [DEFAULT]
    13  #    ie: file_env 'XYZ_DB_PASSWORD' 'example'
    14  # (will allow for "$XYZ_DB_PASSWORD_FILE" to fill in the value of
    15  #  "$XYZ_DB_PASSWORD" from a file, especially for Docker's secrets feature)
    16  file_env() {
    17  	local var="$1"
    18  	local fileVar="${var}_FILE"
    19  	local def="${2:-}"
    20  	if [ "${!var:-}" ] && [ "${!fileVar:-}" ]; then
    21  		printf >&2 'error: both %s and %s are set (but are exclusive)\n' "$var" "$fileVar"
    22  		exit 1
    23  	fi
    24  	local val="$def"
    25  	if [ "${!var:-}" ]; then
    26  		val="${!var}"
    27  	elif [ "${!fileVar:-}" ]; then
    28  		val="$(< "${!fileVar}")"
    29  	fi
    30  	export "$var"="$val"
    31  	unset "$fileVar"
    32  }
    33  
    34  # check to see if this file is being run or sourced from another script
    35  _is_sourced() {
    36  	# https://unix.stackexchange.com/a/215279
    37  	[ "${#FUNCNAME[@]}" -ge 2 ] \
    38  		&& [ "${FUNCNAME[0]}" = '_is_sourced' ] \
    39  		&& [ "${FUNCNAME[1]}" = 'source' ]
    40  }
    41  
    42  # used to create initial postgres directories and if run as root, ensure ownership to the "postgres" user
    43  docker_create_db_directories() {
    44  	local user; user="$(id -u)"
    45  
    46  	mkdir -p "$PGDATA"
    47  	# 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)
    48  	chmod 00700 "$PGDATA" || :
    49  
    50  	# ignore failure since it will be fine when using the image provided directory; see also https://github.com/docker-library/postgres/pull/289
    51  	mkdir -p /var/run/postgresql || :
    52  	chmod 03775 /var/run/postgresql || :
    53  
    54  	# Create the transaction log directory before initdb is run so the directory is owned by the correct user
    55  	if [ -n "${POSTGRES_INITDB_WALDIR:-}" ]; then
    56  		mkdir -p "$POSTGRES_INITDB_WALDIR"
    57  		if [ "$user" = '0' ]; then
    58  			find "$POSTGRES_INITDB_WALDIR" \! -user postgres -exec chown postgres '{}' +
    59  		fi
    60  		chmod 700 "$POSTGRES_INITDB_WALDIR"
    61  	fi
    62  
    63  	# allow the container to be started with `--user`
    64  	if [ "$user" = '0' ]; then
    65  		find "$PGDATA" \! -user postgres -exec chown postgres '{}' +
    66  		find /var/run/postgresql \! -user postgres -exec chown postgres '{}' +
    67  	fi
    68  }
    69  
    70  # initialize empty PGDATA directory with new database via 'initdb'
    71  # arguments to `initdb` can be passed via POSTGRES_INITDB_ARGS or as arguments to this function
    72  # `initdb` automatically creates the "postgres", "template0", and "template1" dbnames
    73  # this is also where the database user is created, specified by `POSTGRES_USER` env
    74  docker_init_database_dir() {
    75  	# "initdb" is particular about the current user existing in "/etc/passwd", so we use "nss_wrapper" to fake that if necessary
    76  	# see https://github.com/docker-library/postgres/pull/253, https://github.com/docker-library/postgres/issues/359, https://cwrap.org/nss_wrapper.html
    77  	local uid; uid="$(id -u)"
    78  	if ! getent passwd "$uid" &> /dev/null; then
    79  		# see if we can find a suitable "libnss_wrapper.so" (https://salsa.debian.org/sssd-team/nss-wrapper/-/commit/b9925a653a54e24d09d9b498a2d913729f7abb15)
    80  		local wrapper
    81  		for wrapper in {/usr,}/lib{/*,}/libnss_wrapper.so; do
    82  			if [ -s "$wrapper" ]; then
    83  				NSS_WRAPPER_PASSWD="$(mktemp)"
    84  				NSS_WRAPPER_GROUP="$(mktemp)"
    85  				export LD_PRELOAD="$wrapper" NSS_WRAPPER_PASSWD NSS_WRAPPER_GROUP
    86  				local gid; gid="$(id -g)"
    87  				printf 'postgres:x:%s:%s:PostgreSQL:%s:/bin/false\n' "$uid" "$gid" "$PGDATA" > "$NSS_WRAPPER_PASSWD"
    88  				printf 'postgres:x:%s:\n' "$gid" > "$NSS_WRAPPER_GROUP"
    89  				break
    90  			fi
    91  		done
    92  	fi
    93  
    94  	if [ -n "${POSTGRES_INITDB_WALDIR:-}" ]; then
    95  		set -- --waldir "$POSTGRES_INITDB_WALDIR" "$@"
    96  	fi
    97  
    98  	# --pwfile refuses to handle a properly-empty file (hence the "\n"): https://github.com/docker-library/postgres/issues/1025
    99  	eval 'initdb --username="$POSTGRES_USER" --pwfile=<(printf "%s\n" "$POSTGRES_PASSWORD") '"$POSTGRES_INITDB_ARGS"' "$@"'
   100  
   101  	# unset/cleanup "nss_wrapper" bits
   102  	if [[ "${LD_PRELOAD:-}" == */libnss_wrapper.so ]]; then
   103  		rm -f "$NSS_WRAPPER_PASSWD" "$NSS_WRAPPER_GROUP"
   104  		unset LD_PRELOAD NSS_WRAPPER_PASSWD NSS_WRAPPER_GROUP
   105  	fi
   106  }
   107  
   108  # print large warning if POSTGRES_PASSWORD is long
   109  # error if both POSTGRES_PASSWORD is empty and POSTGRES_HOST_AUTH_METHOD is not 'trust'
   110  # print large warning if POSTGRES_HOST_AUTH_METHOD is set to 'trust'
   111  # assumes database is not set up, ie: [ -z "$DATABASE_ALREADY_EXISTS" ]
   112  docker_verify_minimum_env() {
   113  	# check password first so we can output the warning before postgres
   114  	# messes it up
   115  	if [ "${#POSTGRES_PASSWORD}" -ge 100 ]; then
   116  		cat >&2 <<-'EOWARN'
   117  
   118  			WARNING: The supplied POSTGRES_PASSWORD is 100+ characters.
   119  
   120  			  This will not work if used via PGPASSWORD with "psql".
   121  
   122  			  https://www.postgresql.org/message-id/flat/E1Rqxp2-0004Qt-PL%40wrigleys.postgresql.org (BUG #6412)
   123  			  https://github.com/docker-library/postgres/issues/507
   124  
   125  		EOWARN
   126  	fi
   127  	if [ -z "$POSTGRES_PASSWORD" ] && [ 'trust' != "$POSTGRES_HOST_AUTH_METHOD" ]; then
   128  		# The - option suppresses leading tabs but *not* spaces. :)
   129  		cat >&2 <<-'EOE'
   130  			Error: Database is uninitialized and superuser password is not specified.
   131  			       You must specify POSTGRES_PASSWORD to a non-empty value for the
   132  			       superuser. For example, "-e POSTGRES_PASSWORD=password" on "docker run".
   133  
   134  			       You may also use "POSTGRES_HOST_AUTH_METHOD=trust" to allow all
   135  			       connections without a password. This is *not* recommended.
   136  
   137  			       See PostgreSQL documentation about "trust":
   138  			       https://www.postgresql.org/docs/current/auth-trust.html
   139  		EOE
   140  		exit 1
   141  	fi
   142  	if [ 'trust' = "$POSTGRES_HOST_AUTH_METHOD" ]; then
   143  		cat >&2 <<-'EOWARN'
   144  			********************************************************************************
   145  			WARNING: POSTGRES_HOST_AUTH_METHOD has been set to "trust". This will allow
   146  			         anyone with access to the Postgres port to access your database without
   147  			         a password, even if POSTGRES_PASSWORD is set. See PostgreSQL
   148  			         documentation about "trust":
   149  			         https://www.postgresql.org/docs/current/auth-trust.html
   150  			         In Docker's default configuration, this is effectively any other
   151  			         container on the same system.
   152  
   153  			         It is not recommended to use POSTGRES_HOST_AUTH_METHOD=trust. Replace
   154  			         it with "-e POSTGRES_PASSWORD=password" instead to set a password in
   155  			         "docker run".
   156  			********************************************************************************
   157  		EOWARN
   158  	fi
   159  }
   160  
   161  # usage: docker_process_init_files [file [file [...]]]
   162  #    ie: docker_process_init_files /always-initdb.d/*
   163  # process initializer files, based on file extensions and permissions
   164  docker_process_init_files() {
   165  	# psql here for backwards compatibility "${psql[@]}"
   166  	psql=( docker_process_sql )
   167  
   168  	printf '\n'
   169  	local f
   170  	for f; do
   171  		case "$f" in
   172  			*.sh)
   173  				# https://github.com/docker-library/postgres/issues/450#issuecomment-393167936
   174  				# https://github.com/docker-library/postgres/pull/452
   175  				if [ -x "$f" ]; then
   176  					printf '%s: running %s\n' "$0" "$f"
   177  					"$f"
   178  				else
   179  					printf '%s: sourcing %s\n' "$0" "$f"
   180  					. "$f"
   181  				fi
   182  				;;
   183  			*.sql)     printf '%s: running %s\n' "$0" "$f"; docker_process_sql -f "$f"; printf '\n' ;;
   184  			*.sql.gz)  printf '%s: running %s\n' "$0" "$f"; gunzip -c "$f" | docker_process_sql; printf '\n' ;;
   185  			*.sql.xz)  printf '%s: running %s\n' "$0" "$f"; xzcat "$f" | docker_process_sql; printf '\n' ;;
   186  			*.sql.zst) printf '%s: running %s\n' "$0" "$f"; zstd -dc "$f" | docker_process_sql; printf '\n' ;;
   187  			*)         printf '%s: ignoring %s\n' "$0" "$f" ;;
   188  		esac
   189  		printf '\n'
   190  	done
   191  }
   192  
   193  # Execute sql script, passed via stdin (or -f flag of pqsl)
   194  # usage: docker_process_sql [psql-cli-args]
   195  #    ie: docker_process_sql --dbname=mydb <<<'INSERT ...'
   196  #    ie: docker_process_sql -f my-file.sql
   197  #    ie: docker_process_sql <my-file.sql
   198  docker_process_sql() {
   199  	local query_runner=( psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --no-password --no-psqlrc )
   200  	if [ -n "$POSTGRES_DB" ]; then
   201  		query_runner+=( --dbname "$POSTGRES_DB" )
   202  	fi
   203  
   204  	PGHOST= PGHOSTADDR= "${query_runner[@]}" "$@"
   205  }
   206  
   207  # create initial database
   208  # uses environment variables for input: POSTGRES_DB
   209  docker_setup_db() {
   210  	local dbAlreadyExists
   211  	dbAlreadyExists="$(
   212  		POSTGRES_DB= docker_process_sql --dbname postgres --set db="$POSTGRES_DB" --tuples-only <<-'EOSQL'
   213  			SELECT 1 FROM pg_database WHERE datname = :'db' ;
   214  		EOSQL
   215  	)"
   216  	if [ -z "$dbAlreadyExists" ]; then
   217  		POSTGRES_DB= docker_process_sql --dbname postgres --set db="$POSTGRES_DB" <<-'EOSQL'
   218  			CREATE DATABASE :"db" ;
   219  		EOSQL
   220  		printf '\n'
   221  	fi
   222  }
   223  
   224  # Loads various settings that are used elsewhere in the script
   225  # This should be called before any other functions
   226  docker_setup_env() {
   227  	file_env 'POSTGRES_PASSWORD'
   228  
   229  	file_env 'POSTGRES_USER' 'postgres'
   230  	file_env 'POSTGRES_DB' "$POSTGRES_USER"
   231  	file_env 'POSTGRES_INITDB_ARGS'
   232  	: "${POSTGRES_HOST_AUTH_METHOD:=}"
   233  
   234  	declare -g DATABASE_ALREADY_EXISTS
   235  	# look specifically for PG_VERSION, as it is expected in the DB dir
   236  	if [ -s "$PGDATA/PG_VERSION" ]; then
   237  		DATABASE_ALREADY_EXISTS='true'
   238  	fi
   239  }
   240  
   241  # append POSTGRES_HOST_AUTH_METHOD to pg_hba.conf for "host" connections
   242  # all arguments will be passed along as arguments to `postgres` for getting the value of 'password_encryption'
   243  pg_setup_hba_conf() {
   244  	# default authentication method is md5 on versions before 14
   245  	# https://www.postgresql.org/about/news/postgresql-14-released-2318/
   246  	if [ "$1" = 'postgres' ]; then
   247  		shift
   248  	fi
   249  	local auth
   250  	# check the default/configured encryption and use that as the auth method
   251  	auth="$(postgres -C password_encryption "$@")"
   252  	: "${POSTGRES_HOST_AUTH_METHOD:=$auth}"
   253  	{
   254  		printf '\n'
   255  		if [ 'trust' = "$POSTGRES_HOST_AUTH_METHOD" ]; then
   256  			printf '# warning trust is enabled for all connections\n'
   257  			printf '# see https://www.postgresql.org/docs/12/auth-trust.html\n'
   258  		fi
   259  		printf 'host all all all %s\n' "$POSTGRES_HOST_AUTH_METHOD"
   260  	} >> "$PGDATA/pg_hba.conf"
   261  }
   262  
   263  # start socket-only postgresql server for setting up or running scripts
   264  # all arguments will be passed along as arguments to `postgres` (via pg_ctl)
   265  docker_temp_server_start() {
   266  	if [ "$1" = 'postgres' ]; then
   267  		shift
   268  	fi
   269  
   270  	# internal start of server in order to allow setup using psql client
   271  	# does not listen on external TCP/IP and waits until start finishes
   272  	set -- "$@" -c listen_addresses='' -p "${PGPORT:-5432}"
   273  
   274  	PGUSER="${PGUSER:-$POSTGRES_USER}" \
   275  	pg_ctl -D "$PGDATA" \
   276  		-o "$(printf '%q ' "$@")" \
   277  		-w start
   278  }
   279  
   280  # stop postgresql server after done setting up user and running scripts
   281  docker_temp_server_stop() {
   282  	PGUSER="${PGUSER:-postgres}" \
   283  	pg_ctl -D "$PGDATA" -m fast -w stop
   284  }
   285  
   286  # check arguments for an option that would cause postgres to stop
   287  # return true if there is one
   288  _pg_want_help() {
   289  	local arg
   290  	for arg; do
   291  		case "$arg" in
   292  			# postgres --help | grep 'then exit'
   293  			# leaving out -C on purpose since it always fails and is unhelpful:
   294  			# postgres: could not access the server configuration file "/var/lib/postgresql/data/postgresql.conf": No such file or directory
   295  			-'?'|--help|--describe-config|-V|--version)
   296  				return 0
   297  				;;
   298  		esac
   299  	done
   300  	return 1
   301  }
   302  
   303  _main() {
   304  	# if first arg looks like a flag, assume we want to run postgres server
   305  	if [ "${1:0:1}" = '-' ]; then
   306  		set -- postgres "$@"
   307  	fi
   308  
   309  	if [ "$1" = 'postgres' ] && ! _pg_want_help "$@"; then
   310  		docker_setup_env
   311  		# setup data directories and permissions (when run as root)
   312  		docker_create_db_directories
   313  		if [ "$(id -u)" = '0' ]; then
   314  			# then restart script as postgres user
   315  			exec su-exec postgres "$BASH_SOURCE" "$@"
   316  		fi
   317  
   318  		# only run initialization on an empty data directory
   319  		if [ -z "$DATABASE_ALREADY_EXISTS" ]; then
   320  			docker_verify_minimum_env
   321  
   322  			# check dir permissions to reduce likelihood of half-initialized database
   323  			ls /docker-entrypoint-initdb.d/ > /dev/null
   324  
   325  			docker_init_database_dir
   326  			pg_setup_hba_conf "$@"
   327  
   328  			# PGPASSWORD is required for psql when authentication is required for 'local' connections via pg_hba.conf and is otherwise harmless
   329  			# e.g. when '--auth=md5' or '--auth-local=md5' is used in POSTGRES_INITDB_ARGS
   330  			export PGPASSWORD="${PGPASSWORD:-$POSTGRES_PASSWORD}"
   331  			docker_temp_server_start "$@"
   332  
   333  			docker_setup_db
   334  			docker_process_init_files /docker-entrypoint-initdb.d/*
   335  
   336  			docker_temp_server_stop
   337  			unset PGPASSWORD
   338  
   339  			cat <<-'EOM'
   340  
   341  				PostgreSQL init process complete; ready for start up.
   342  
   343  			EOM
   344  		else
   345  			cat <<-'EOM'
   346  
   347  				PostgreSQL Database directory appears to contain a database; Skipping initialization
   348  
   349  			EOM
   350  		fi
   351  	fi
   352  }
   353  
   354  if ! _is_sourced; then
   355  	_main "$@"
   356  fi