PostgreSQL 9.6 - Quick setup and installation on Ubuntu 18.04

Introduction

In this article, a PostgreSQL 9.6 instance is installed and customized (port, database and configuration files…) on Ubuntu 18.04.

PostgreSQL Schema

Preparing the environment

User postgres

As user root, create the user postgres (group dba, id 10005)

root@vpsfrsqlpac1% useradd postgres -g dba -d /opt/postgres -m -s /bin/bash -u 10005

Binaries are installed in the directory /opt/postgres with the appropriate owner and rights :

root@vpsfrsqlpac1% mkdir /opt/postgres
root@vpsfrsqlpac1% chown postgres:dba /opt/postgres

The database will be installed in the directory /sqlpac/postgres/srvpg1 :

root@vpsfrsqlpac1% mkdir /sqlpac/postgres
root@vpsfrsqlpac1% chown postgres:dba /sqlpac/postgres
postgres@vpsfrsqlpac1% mkdir /sqlpac/postgres/srvpg1

Postgresql 9.6 distribution installation

Download the archive file PostgreSQL 9.6 for Linux 64 bits from EnterpriseDB. The distribution (version 9.6.15) is installed in the directory /opt/postgres/pgsql-9.6.15 :

postgres@vpsfrsqlpac1:/opt/postgres% gunzip postgresql-9.6.15-1-linux-x64-binaries.tar.gz

postgres@vpsfrsqlpac1:/opt/postgres% mkdir pgsql-9.6.15
postgres@vpsfrsqlpac1:/opt/postgres% tar xvf postgresql-9.6.15-2-linux-x64-binaries.tar \
                                              -C /opt/postgres/pgsql-9.6.15 \
                                              --strip-components=1

A symbolic link pgsql-9.6 is created for an ease of use managing minor versions changes.

postgres@vpsfrsqlpac1:/opt/postgres% ln -fs  pgsql-9.6.15 pgsql-9.6

For the user postgres, the environment variables below are initialized :

postgres@vpsfrsqlpac1:/opt/postgres%
          
export PATH=/opt/postgres/pgsql-9.6/bin
export LD_LIBRARY_PATH=/opt/postgres/pgsql-9.6/lib
export PGLIB=/opt/postgres/pgsql-9.6/lib

# Database directory
export PGDATA=/sqlpac/postgres/srvpg1

export PGBINHOME=/opt/postgres/pgsql-9.6

Database initialization

With the environment variables previously defined ($PATH, $PGLIB…), run the utility pg_ctl with the option intidb from the directory where the database will be installed ($PGDATA : /sqlpac/postgres/srvpg1). No extra options are specified here (collations, timezones…), default ones will be applied.

postgres@vpsfrsqlpac1:/sqlpac/postgres/srvpg1% pg_ctl initdb

The files belonging to this database system will be owned by user "postgres".
This user must also own the server process.

The database cluster will be initialized with locale "en_US.UTF-8".
The default database encoding has accordingly been set to "UTF8".
The default text search configuration will be set to "english".

Data page checksums are disabled.

fixing permissions on existing directory /sqlpac/postgres/srvpg1 ... ok
creating subdirectories ... ok
selecting default max_connections ... 100
selecting default shared_buffers ... 128MB
selecting default timezone ... Europe/Paris
selecting dynamic shared memory implementation ... posix
creating configuration files ... ok
running bootstrap script ... ok
performing post-bootstrap initialization ... ok
syncing data to disk ... ok

WARNING: enabling "trust" authentication for local connections
You can change this by editing pg_hba.conf or using the option -A, or
--auth-local and --auth-host, the next time you run initdb.

Success. You can now start the database server using:

    /opt/postgres/pgsql-9.6.15/bin/pg_ctl -D /sqlpac/postgres/srvpg1 -l logfile start

The instance structure is initialized.

postgres@vpsfrsqlpac1:/sqlpac/postgres/srvpg1% ls

base     pg_commit_ts  pg_ident.conf  pg_notify    pg_snapshots  pg_subtrans  PG_VERSION            postgresql.conf
global   pg_dynshmem   pg_logical     pg_replslot  pg_stat       pg_tblspc    pg_xlog
pg_clog  pg_hba.conf   pg_multixact   pg_serial    pg_stat_tmp   pg_twophase  postgresql.auto.conf

Normalization

For this new instance srvpg1 : 2 environment variables are created as well as the associated directories.

$CFG /opt/postgres/dba/srvpg1/cfg Configuration files (postgresql.conf, pg_hba.conf, pg_ident.conf…)
$LOG /opt/postgres/dba/srvpg1/log Log files

In the standard adopted, the configuration files postgresql.conf, pg_ident.conf and pg_hba.conf are moved from the database directory $PGDATA to the directory $CFG.

postgres@vpsfrsqlpac1:/sqlpac/postgres/srvpg1% mv $PGDATA/postgresql.conf $CFG
postgres@vpsfrsqlpac1:/sqlpac/postgres/srvpg1% mv $PGDATA/pg_ident.conf $CFG
postgres@vpsfrsqlpac1:/sqlpac/postgres/srvpg1% mv $PGDATA/pg_hba.conf $CFG

Relocating the configuration files. Why this choice ? A human error can occur at any time (rm -rf *, chown…), installing the configuration files in a custom path outside the database data files directory is safier. This is not the case for the file recovery.conf (PostgreSQL <= version 11) and the files standby.signal and recovery.signal (PostgreSQL >= version 12) : these files must be installed in the root database directory $PGDATA. The file postgresql.auto.conf must also reside in the directory $PGDATA, but this file should not be edited manually and only managed through the command ALTER SYSTEM.

The postgresql.conf file is modified in order to apply the customization : database directory, configuration and log files directories, port, WAL files archiving… .

$CFG/postgresql.conf
data_directory = '/sqlpac/postgres/srvpg1'                        
hba_file = '/opt/postgres/dba/srvpg1/cfg/pg_hba.conf'        
ident_file = '/opt/postgres/dba/srvpg1/cfg/pg_ident.conf'

listen_addresses = '*'
port = 30001

max_connections = 100

# Archiving

archive_mode = on
archive_command = 'test ! -f /backup/postgres/log_archive/srvpg1/%f && cp %p /backup/postgres/log_archive/srvpg1/%f'

# Log
log_destination = 'stderr, csvlog'                        
logging_collector = on          
log_directory = '/opt/postgres/dba/srvpg1/log'               
log_filename = 'srvpg1.log'
log_min_messages = info
log_line_prefix = '%m '    #   %m = timestamp with milliseconds
log_statement = 'ddl'
log_timezone = 'Europe/Paris'

datestyle = 'iso, mdy'
timezone = 'Europe/Paris'
lc_messages = 'en_US.UTF-8'
lc_monetary = 'en_US.UTF-8'
lc_numeric = 'en_US.UTF-8'
lc_time = 'en_US.UTF-8'

Use pg_ctl to start the database:

postgres@vpsfrsqlpac1% pg_ctl start -D $CFG 

LOG:  MultiXact member wraparound protections are now enabled
LOG:  database system is ready to accept connections
LOG:  autovacuum launcher started

To stop, restart the database with pg_ctl :

postgres@vpsfrsqlpac1% pg_ctl stop -D $CFG -m fast
postgres@vpsfrsqlpac1% pg_ctl restart -D $CFG -m fast

By default, the local user postgres is allowed to connect (definition in the file pg_hba.conf):

pg_hba.conf
# "local" is for Unix domain socket connections only
local   all             all                                     trust

Use psql to connect. When the port is not the default one (5432), add the option -p <port>

postgres@vpsfrsqlpac1% psql

postgres@vpsfrsqlpac1% psql -p 30001

psql.bin (9.6.15)
Type "help" for help.

Cannot read termcap database;
using dumb terminal settings.
postgres=#

Sourcing a PostgreSQL environment

In a mutualized PostgreSQL environment on a host : multiple instances and versions… One can setup a bash script sourcing the environment for a PostgreSQL instance.

The default profile for the user postgres is the following :

$HOME/.profile
## Custom profile
export DBA=$HOME/dba
export ETC=$DBA/etc
export PATH=$DBA/bin:$PATH

For each PostgreSQL instance, a configuration file $ETC/servername.pgq is created, it will initialize all the useful environment variables. Example for srvpg1 / PostgreSQL 9.6 :

source srvpg1.pgq
$PATH PATH=/opt/postgres/pgsql-9.6/bin:$PATH $LD_LIBRARY_PATH LD_LIBRARY_PATH=/opt/postgres/pgsql-9.6/lib :$LD_LIBRARY_PATH
$PGSRVNAME srvpg1 Instance Name $PGVERSION 9.6 Version $PGPORT 30001 Port $PGBINHOME /opt/postgres/pgsql-9.6 PostgreSQL Distribution directory $PGLIB /opt/postgres/pgsql-9.6/lib PostgreSQL Librairies $PGDATA /sqlpac/postgres/srvpg1 Database files directory $PGWALDIR /sqlpac/postgres/srvpg1/pg_xlog WAL files directory $PGPASSFILE /opt/postgres/dba/srvpg1/.srvpg1.pass Password file $CFG /opt/postgres/dba/srvpg1/cfg Configuration files directory (postgresql.conf…) $LOG /opt/postgres/dba/srvpg1/log Log files directory $DMP /backup/postgres/srvpg1 Backup directory $ARC /backup/postgres/log_archive/srvpg1 WAL archives directory

Some of the environment variables defined are system PostgreSQL variables : $PGLIB, $PGDATA, $PGPORT, $PGPASSFILE. For the complete liste of PostgreSQL environment variables : Environment Variables | Chapter 33. libpq - C Library

Only the version $PGVERSION is defined in the file srvpg1.pgq, all the other environment variables are computed in the bash script $DBA/etc/defaults_srv.bash.

$ETC/srvpg1.pgq
# PostgreSQL Server Instance Name
[ -z "$PGSRVNAME" ] && export PGSRVNAME=$(basename ${BASH_SOURCE[0]%.*})
# PostgreSQL  version
export PGVERSION=9.6

source $DBA/etc/defaults_srv.bash
$ETC/defaults_srv.bash
#!/bin/bash
#

# PGBINHOME
PGBINHOME=/opt/postgres/pgsql-${PGVERSION}
export PGBINHOME

#
# Setting PATH and LD_LIBRARY_PATH Environment
LD_LIBRARY_PATH=$PGBINHOME/lib:$LD_LIBRARY_PATH
PATH=$PGBINHOME/bin:$PATH
export PATH LD_LIBRARY_PATH

export CFG=$DBA/$PGSRVNAME/cfg
export LOG=$DBA/$PGSRVNAME/log
export DMP=`readlink $DBA/$PGSRVNAME/dmp`
export ARC=`readlink $DBA/$PGSRVNAME/arc`

# Checking postgresql.conf file
[ ! -f ${CFG}/postgresql.conf ] && echo "Could not find postgresql.conf in $CFG, exiting..." && exit 3

#
# PG Variables
PGLIB=$PGBINHOME/lib

PGDATA=`cat $CFG/postgresql.conf | grep 'data_directory' | sed "s/ *= */=/g" | tr -d "'" | awk '{print $1}' | awk -F"=" '{print $2}'`

PGPORT=$(cat $CFG/postgresql.conf | grep -i '^port' | sed "s/ *= */=/g" | awk '{print $1}' | awk -F"=" '{print $2}')

[ -z "$PGPORT" ] && PGPORT=5432

PGPASSFILE=$DBA/$PGSRVNAME/.$PGSRVNAME.pass

# WAL DIR
# Automatic detection depending on the PostgreSQL version
PGWALDIR=$PGDATA/pg_wal   # By default pg_wal, PGSQL > 9
MAJOR_VERSION=`echo ${PGVERSION} | awk -F"." '{print $1}'`
[ $MAJOR_VERSION -lt 10 ] && PGWALDIR=$PGDATA/pg_xlog

export PGLIB PGDATA PGPORT PGWALDIR PGPASSFILE

For interactive Xterm sessions, useful aliases can be defined in the script .bash_profile, especially the alias p which opens a PSQL session without prompting a password :

$HOME/.bash_profile
if [ ! -z "$PGSRVNAME" ]; then
        PS1="\e[0;33m\u\e[m@\e[0;33m\h\e[m [\e[0;31m$PGSRVNAME\e[m] :\w > "

        # Title : 0 minimized, 1 maximized
        PROMPT_COMMAND='echo -ne "\033]0;${PGSRVNAME}@${HOSTNAME} ${PWD}\007"'

        # Alias
        
        alias p="psql"
        
        alias godat="cd $PGDATA"
        alias gowal="cd $PGWALDIR"
        alias godmp="cd $DMP"

        # Alias config file
        alias gocfg="cd $CFG"
        alias mcfg="vi ${CFG}/postgresql.conf"

        # Alias log files
        alias golog="cd ${LOG}"
        alias tlog="tail -50 ${LOG}/${PGSRVNAME}.log"
        alias flog="tail -f ${LOG}/${PGSRVNAME}.log"
        alias vlog="view ${LOG}/${PGSRVNAME}.log"


fi

If local connections methods are not set to trust in the configuration file pg_hba.conf due to the mutualization with other users on the host, check the secured permission 600 is given to the password file ($PGPASSFILE), otherwise psql raises a warning and the password is prompted.

WARNING: password file "/opt/postgres/dba/srvpg1/.srvpg1.pass" has group or world access;
permissions should be u=rw (0600) or less
Password:
postgres@vpsfrsqlpac1% chmod 600 /opt/postgres/dba/srvpg1/.srvpg1.pass

The syntax in the password file is very easy :

HOST:PORT:DB_NAME:USER:PASSWORD

localhost:30001:postgres:postgres:**************

There is no encryption or obfuscation of passwords in the password file (not yet), as is the case with --login-path and mysql_config_editor for MySQL, but securing this file with the permission 600 is largely enough.