#!/bin/bash # # postgresql-setup - Initialization and upgrade operations for PostgreSQL test -z "$PATH" && export PATH="/sbin:/usr/sbin:/bin:/usr/bin" test x"$PGSETUP_DEBUG" != x && set -x && PS4='${LINENO}: ' # Directory containing the postmaster executable PGENGINE=@bindir@ # Distribution README file README_DIST=@README_DIST@ # Home directory of postgres user POSTGRES_HOMEDIR=@POSTGRES_HOMEDIR@ # Convenient tool-aliases SU_POSTGRES="@SU_POSTGRES@" # The where PostgreSQL server listens by default PGPORT_DEF=5432 . "@rawpkgdatadir@/library.sh" # We upgrade by default from system's default PostgreSQL installation option_upgradefrom="@NAME_DEFAULT_PREV_SERVICE@" USAGE_STRING=$"\ Usage: $0 MODE_OPTION [--unit=UNIT_NAME] [OPTION...] Script is aimed to help sysadmin with basic database cluster administration. Usually, \"@NAME_BINARYBASE@-setup --initdb\" and \"@NAME_BINARYBASE@-setup --upgrade\" is enough, however there are other options described below. For more info and howto/when use this script please look at the documentation file $README_DIST. Available operation mode: --initdb Create a new PostgreSQL database cluster. This is usually the first action you perform after PostgreSQL server installation. --upgrade Upgrade database cluster for new major version of PostgreSQL server. See the --upgrade-from option for more info. Options: --unit=UNIT_NAME The UNIT_NAME is used to select proper systemd's unit configuration. For example, if you want to work with unit 'postgresql@com_example.service', you should use 'postgresql@com_example' (without trailing .service string). When no UNIT_NAME is explicitly passed, the 'postgresql' string is used by default. --port=PORT port where the server will listen for connections --datadir=PATH Specify absolute path to DB data directory, only use with --new-systemd-unit. --new-systemd-unit Pre-generate system'd configuration in drop-in directory if the unit is not yet configured, requires non-default --unit specified and explicit --datadir and --port. --upgrade-ids Print list of available IDs of upgrade scenarios to standard output. --upgrade-from=ID Specify id \"old\" postgresql stack to upgrade from. List of available IDs can be listed by --upgrade-ids. Default is '$option_upgradefrom'. Other options: --help show this help --version show version of this package --debug show basic debugging information Environment: PGSETUP_INITDB_OPTIONS Options carried by this variable are passed to subsequent call of \`initdb\` binary (see man initdb(1)). This variable is used also during 'upgrade' mode because the new cluster is actually re-initialized from the old one. PGSETUP_PGUPGRADE_OPTIONS Options in this variable are passed next to the subsequent call of \`pg_upgrade\`. For more info about possible options please look at man pg_upgrade(1). PGSETUP_DEBUG Set to '1' if you want to see very verbose shell debugging output." print_version() { echo "@NAME_BINARYBASE@-setup @VERSION@" echo $"Built against PostgreSQL version @PGVERSION@." } # code shared between initdb and upgrade actions perform_initdb() { if [ ! -e "$pgdata" ]; then mkdir "$pgdata" || return 1 chown postgres:postgres "$pgdata" chmod go-rwx "$pgdata" fi # Clean up SELinux tagging for pgdata [ -x /sbin/restorecon ] && /sbin/restorecon "$pgdata" # Create the initdb log file if needed if [ ! -e "$initdb_log" -a ! -h "$initdb_log" ]; then touch "$initdb_log" || return 1 chown postgres:postgres "$initdb_log" chmod go-rwx "$initdb_log" [ -x /sbin/restorecon ] && /sbin/restorecon "$initdb_log" fi # Initialize the database initdbcmd="@SCL_SOURCE@" initdbcmd+=" $PGENGINE/initdb --pgdata='$pgdata' --auth='ident'" initdbcmd+=" $PGSETUP_INITDB_OPTIONS" $SU_POSTGRES -c "$initdbcmd" >> "$initdb_log" 2>&1 < /dev/null # Create directory for postmaster log files mkdir "$pgdata/pg_log" chown postgres:postgres "$pgdata/pg_log" chmod go-rwx "$pgdata/pg_log" [ -x /sbin/restorecon ] && /sbin/restorecon "$pgdata/pg_log" # This if-fork is just to not unnecessarily overwrite what upstream # generates by initdb (upstream implicitly uses PGPORT_DEF). if test "$pgport" != "$PGPORT_DEF"; then local pgconf="$pgdata/postgresql.conf" sed -i "s|^[[:space:]#]*port[[:space:]]=[^#]*|port = $pgport |g" \ "$pgconf" \ && grep "^port = " "$pgconf" >/dev/null if test $? -ne 0; then error "can not change port in $pgdata/postgresql.conf" return 1 fi fi if [ -f "$pgdata/PG_VERSION" ]; then return 0 fi return 1 } initdb() { if [ -f "$pgdata/PG_VERSION" ]; then error $"Data directory $pgdata is not empty!" script_result=1 else port_info= test "$pgport" != "$PGPORT_DEF" \ && port_info=$", listening on port '$pgport'" info $"Initializing database in '$pgdata'$port_info" if perform_initdb; then info $"Initialized, logs are in ${initdb_log}" else error $"Initializing database failed, possibly see $initdb_log" script_result=1 fi fi } upgrade() { local inplace=false test "$pgdata" = "$upgradefrom_data" && inplace=true debug "running inplace upgrade: $inplace" # must see previous version in PG_VERSION local old_data_version="`cat "$upgradefrom_data/PG_VERSION"`" if [ ! -f "$upgradefrom_data/PG_VERSION" -o \ x"$old_data_version" != x"$upgradefrom_major" ] then error $"Cannot upgrade because the database in $upgradefrom_data is of" error_q $"version $old_data_version but it should be $upgradefrom_major" exit 1 fi if [ ! -x "$PGENGINE/pg_upgrade" ]; then error $"Please install the @NAME_PACKAGE@-upgrade package." exit 5 fi # Set up log file for pg_upgrade rm -f "$upgrade_log" touch "$upgrade_log" || die "can't write into $upgrade_log file" chown postgres:postgres "$upgrade_log" chmod go-rwx "$upgrade_log" [ -x /sbin/restorecon ] && /sbin/restorecon "$upgrade_log" # Move old DB to pgdataold if $inplace; then pgdataold="${pgdata}-old" rm -rf "$pgdataold" mv "$pgdata" "$pgdataold" || exit 1 else pgdataold="$upgradefrom_data" fi # Create configuration file for upgrade process HBA_CONF_BACKUP="$pgdataold/pg_hba.conf.@NAME_BINARYBASE@-setup.`date +%s`" HBA_CONF_BACKUP_EXISTS=0 if [ ! -f $HBA_CONF_BACKUP ]; then mv "$pgdataold/pg_hba.conf" "$HBA_CONF_BACKUP" HBA_CONF_BACKUP_EXISTS=1 # For fluent upgrade 'postgres' user should be able to connect # to any database without password. Temporarily, no other type # of connection is needed. echo "local all postgres ident" > "$pgdataold/pg_hba.conf" fi info $"Upgrading database." scls_upgrade_hacks= test -n "$upgradefrom_scls" && { debug "scls [$upgradefrom_scls] will be enabled" scls_upgrade_hacks="source scl_source enable $upgradefrom_scls ;" } test -n "$upgradefrom_redhat_sockets_hack" && { debug "upgrading from redhat server" socket_hacks="export REDHAT_PGUPGRADE_FROM_RHEL=yes ;" } # Create empty new-format database if perform_initdb; then # Do the upgrade $SU_POSTGRES -c "\ @SCL_SOURCE@ $scls_upgrade_hacks \ $socket_hacks \ $PGENGINE/pg_upgrade \ '--old-bindir=$upgradefrom_engine' \ '--new-bindir=$PGENGINE' \ '--old-datadir=$pgdataold' \ '--new-datadir=$pgdata' \ --link \ '--old-port=$PGPORT' '--new-port=$PGPORT' \ @PG_UPGRADE_BIN_USER_OPT@=postgres \ $PGSETUP_PGUPGRADE_OPTIONS" \ >> "$upgrade_log" 2>&1 < /dev/null if [ $? -ne 0 ]; then # pg_upgrade failed script_result=1 fi else # initdb failed script_result=1 fi # Move back the backed-up pg_hba.conf regardless of the script_result. if [ x$HBA_CONF_BACKUP_EXISTS = x1 ]; then mv -f "$HBA_CONF_BACKUP" "$pgdataold/pg_hba.conf" fi if [ $script_result -eq 0 ]; then info $"Upgraded OK." warn $"The configuration files were replaced by default configuration." warn $"The previous configuration and data are stored in folder" warn $pgdataold. else # Clean up after failure. rm -rf "$pgdata" $inplace && mv "$pgdataold" "$pgdata" error $"failed" fi info $"See $upgrade_log for details." } generate_systemd_dropin() { local service="$1" local dropindir="@systemduserunitsdir@/$service.service.d" local dropin="$dropindir/30-@NAME_BINARYBASE@-setup.conf" test -e "$dropindir" \ && die "The systemd drop-in directory '$dropindir' exists already" mkdir -p "$dropindir" \ || die "Can not create '$dropindir'" cat < "$dropin" || die "Can not write to '$dropin'" [Service] Environment=PGDATA=$pgdata EOF reload_systemd="systemctl daemon-reload" $reload_systemd || die $"Can not perform '$reload_systemd'" info $"The '$option_service' configured in '$dropindir' directory" } handle_service_env() { local service="$1" local systemd_env="$(systemctl show -p Environment "${service}.service")" \ || { return; } for env_var in `echo "$systemd_env" | sed 's/^Environment=//'`; do # If one variable name is defined multiple times the last definition wins. case "$env_var" in PGDATA=*) unit_pgdata="${env_var##PGDATA=}" debug "unit's datadir: '$unit_pgdata'" ;; PGPORT=*) unit_pgport="${env_var##PGPORT=}" debug "unit's pgport: $unit_pgport" ;; esac done } handle_service_envfiles() { local mode="$1" local service="$2" local envfiles="$(systemctl show -p EnvironmentFiles "${service}.service")"\ || return test -z "$envfiles" && return envfiles=$(echo $envfiles | \ sed -e 's/^EnvironmentFile=//' \ -e 's| ([^)]*)$||' ) # Read the file names line-by-line (spaces may be inside) while read line; do debug "trying to read '$line' env file" if test ! -r "$line"; then error "Can not read EnvironmentFile '$line' specified" error_q "in ${service}.service" fi # Note that the env file parser in systemd does not perform exactly the # same job. unset PGPORT PGDATA . "$line" envfile_pgdata="$PGDATA" envfile_pgport="$PGPORT" unset PGPORT PGDATA done <<<"$envfiles" } handle_pgconf() { local datadir="$1" local conffile="$datadir/postgresql.conf" debug "postgresql.conf: $conffile" test -r "$conffile" || { error "config file $conffile is not readable or does not exist" return 1 } local sp='[[:space:]]' local sed_expr="s/^$sp*port$sp*=$sp\([0-9]\+\).*/\1/p" rv=0 conf_pgport=`sed -n "$sed_expr" $conffile | tail -1` || rv=1 test -n "$conf_pgport" && debug "postgresql.conf pgport: $conf_pgport" return $rv } service_configuration() { local data= local port= local unit_pgport= local unit_pgdata= local envfile_pgport= local envfile_pgdata= local mode="$1" datavar="$2" portvar="$3" service="$4" debug "running service_configuration() for $mode" # Well, until the bug #1139148 is not resolved somehow, we need to stay ugly # and parse Environment= and EnvironmentFile= statements. local service="$service" test upgrade = "$mode" && service="$option_upgradefrom" handle_service_env "$service" handle_service_envfiles "$option_mode" "$service" test -n "$unit_pgdata" && set_var "$datavar" "$unit_pgdata" test -n "$envfile_pgdata" && set_var "$datavar" "$envfile_pgdata" # skip for the first run test initdb = "$mode" && return set_var data "\$$datavar" handle_pgconf "$data" test -n "$conf_pgport" && set_var "$portvar" "$conf_pgport" test -n "$unit_pgport" && set_var "$portvar" "$unit_pgport" test -n "$envfile_pgport" && set_var "$portvar" "$envfile_pgport" } # # Alow users to use the old style arguments like # 'postgresql-setup initdb $SERVICE_NAME'. case "$1" in initdb|upgrade) action="--$1" shift warn "using obsoleted argument syntax, try --help" old_long_args="help,usage,version,debug" oldargs=`getopt -o "" -l "$old_long_args" -n "old-options" -- "$@"` \ || die "can't parse old arguments" eval set -- "$oldargs" additional_opts= while true; do case "$1" in --version|--help|--usage|--debug) additional_opts="$additional_opts $1" shift ;; --) shift break ;; esac done service="@NAME_SERVICE@" if test -n "$1"; then service=$1 shift fi set -- $additional_opts "$action" --unit "$service" "$@" warn "arguments transformed to: ${0##*/} $*" esac # # postgresql-setup arguments are parsed into those variables option_mode=none option_service="@NAME_SERVICE@" option_port= option_pgdata= option_debug=0 # Content of EnvironmentFile= files fills those: envfile_pgdata= envfile_pgport= # Configuration from (/etc/systemd/system/$option_service.service) fills those # variables. unit_pgdata= unit_pgport= # Configuration from postgresql.conf: conf_pgport= # Key variables. Try to fill them by postgresql.conf, Environment= statement in # service file or EnvironmentFile= content (the later mentioned has more # priority). pgdata=default pgport=default ## PARSE SCRIPT ARGUMENTS ## short_opts="" long_opts="\ initdb,upgrade,\ new-systemd-unit,upgrade-ids,\ unit:,service:,port:,datadir:,upgrade-from:,\ debug,\ version,help,usage" args=`getopt -o "$short_opts" -l "$long_opts" -n "@NAME_BINARYBASE@-setup" -- "$@"` \ || die "can't parse arguments" eval set -- "$args" parse_fail=0 while true; do case "$1" in --initdb|--upgrade) if test "$option_mode" != none; then error "bad argument $1, mode already specified: --$option_mode" parse_fail=1 else option_mode=${1##--} fi shift ;; --unit|--service) option_service=$2 shift 2 ;; --port) option_port=$2 shift 2 ;; --new-systemd-unit) option_systemd_config=yes shift ;; --datadir) option_pgdata=$2 shift 2 ;; --debug) option_debug=1 shift ;; --help|--usage) echo "$USAGE_STRING" exit 0 ;; --upgrade-from) option_upgradefrom="$2" shift 2 ;; --upgrade-ids) parse_upgrade_setup help exit 0 ;; --version) print_version exit 0 ;; --) shift break ;; *) die "author's fault: option $1 not handled" break ;; esac done test $parse_fail -ne 0 && die "can't parse arguments" test "$option_mode" = none \ && die "no mode specified, use --initdb or --upgrade, or --help" if ! parse_upgrade_setup config "$option_upgradefrom"; then if test upgrade = "$option_mode"; then die $"bad --upgrade-from parameter '$option_upgradefrom'" fi fi ## GATHER THE SETUP FIRST ## initdb_log="$POSTGRES_HOMEDIR/initdb_${option_service}.log" upgrade_log="$POSTGRES_HOMEDIR/upgrade_${option_service}.log" debug "mode used: $option_mode" debug "service name: $option_service" root_prereq # load service's pgdata service_configuration initdb pgdata UNUSED "$option_service" # Check that nothing breaks --new-systemd-unit if test "$option_systemd_config" = yes; then if test "$option_service" = "@NAME_SERVICE@"; then die $"Default unit '@NAME_SERVICE@.service' should not need --new-systemd-unit" elif test "$pgdata" != default; then die $"Option --new-systemd-unit failed, is '$option_service.service'"\ $"already configured to '$pgdata'?" elif test -z "$option_pgdata"; then die $"Option --new-systemd-unit requires --datadir" fi # pgdata == default && option_pgdata is set pgdata="$option_pgdata" elif test -n "$option_pgdata"; then warn $"--datadir option is ignored, either use --new-systemd-unit" warn_q $"option, or configure the systemd unit manually." fi test "$pgdata" = default \ && die $"no db datadir (PGDATA) configured for '$option_service.service'" [[ "$pgdata" =~ ^/.* ]] \ || die $"the PostgreSQL datadir not absolute path: '$pgdata', try --debug" test "$option_systemd_config" = yes \ && generate_systemd_dropin "$option_service" ## GATHER DATA FROM INITIALIZED DATADIR ## test -n "$option_port" && pgport=$option_port if test upgrade = "$option_mode"; then upgradefrom_data="$upgradefrom_data_default" service_configuration upgrade upgradefrom_data pgport "$option_upgradefrom" test -n "$option_port" -a "$option_port" != "$pgport" \ && warn "Old pgport $pgport has bigger priority than --pgport value." fi # We expect that for upgrade - the previous stack was in working state (thus # running on the default port). test "$option_mode" = upgrade -a "$pgport" = default \ && pgport=$PGPORT_DEF # This is mostly for 'initdb'. We assume that the default port is $PGPORT_DEF # if not set explicitly for default service name 'postgresql'. if test "$pgport" = default -a "$option_service" = "@NAME_SERVICE@"; then debug $"Using the default port '$PGPORT_DEF'" pgport=$PGPORT_DEF fi test "$pgport" = default \ && die $"\ Port is not set by postgresql.conf nor by --port." [[ "$option_port" =~ ^[0-9]*$ ]] \ || die $"port set to '$option_port', must be integer number" ## LAST CHECK THE SETUP ## nr_option=NeedDaemonReload nr_out="`systemctl show -p $nr_option $option_service.service 2>/dev/null`" if [[ "$nr_out" != "$nr_option=no" ]]; then error $"Note that systemd configuration for '$option_service' changed." error_q $"You need to perform 'systemctl daemon-reload' otherwise the" error_q $"results of this script can be inadequate." exit 1 fi # These variables are read by underlying utilites, rather export them. export PGDATA=$pgdata export PGPORT=$pgport debug "final pgdata: $pgdata" debug "final pgport: $pgport" script_result=0 # See how we were called. case "$option_mode" in initdb) initdb ;; upgrade) upgrade ;; *) echo >&2 "$USAGE_STRING" exit 2 esac exit $script_result