diff options
Diffstat (limited to 'pg_createcluster')
-rwxr-xr-x | pg_createcluster | 952 |
1 files changed, 952 insertions, 0 deletions
diff --git a/pg_createcluster b/pg_createcluster new file mode 100755 index 0000000..4f9eb6a --- /dev/null +++ b/pg_createcluster @@ -0,0 +1,952 @@ +#!/usr/bin/perl -w + +# Create new PostgreSQL cluster or integrate an existing data directory into +# the postgresql-common infrastructure. +# +# (C) 2005-2013 Martin Pitt <mpitt@debian.org> +# (C) 2012-2021 Christoph Berg <myon@debian.org> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +use strict; +use warnings; +use PgCommon; +use Getopt::Long; +use POSIX qw/lchown setlocale LC_ALL LC_CTYPE/; + +$ENV{'PATH'} = '/bin:/usr/bin'; # untaint + +my @postgres_user = getpwnam 'postgres'; +my %defaultconf; +my $explicit_auth_config = 0; +my $quiet; + +chdir '/'; + +# call initdb +sub init_db { + my ($version, $cluster, $datadir, $owneruid, $ownergid, $local_method, $host_method, $initdb_opts_from_cli) = @_; + $datadir = readlink $datadir if (-l $datadir); + + if (system 'install', '-d', '-o', $owneruid, '-g', $ownergid, $datadir) { + error 'could not create data directory; you might need to run this program with root privileges'; + } + + # disable copy-on-write semantics for PostgreSQL data on btrfs and similar; + # this fails on file systems which don't support it, so ignore errors + system "chattr +C '$datadir' 2>/dev/null"; + + my @initdb = (get_program_path 'initdb', $version); + die 'Internal error: could not determine initdb path' unless $initdb[0]; + push @initdb, ('-D', $datadir); + if (my $waldir = $defaultconf{'waldir'} || $defaultconf{'xlogdir'}) { + my $wal = $version >= 10 ? 'wal' : 'xlog'; # renamed in PG 10 + push @initdb, ("--${wal}dir", replace_v_c ($waldir, $version, $cluster)); + } + unless ($explicit_auth_config) { + if ($version >= '9.2') { + push @initdb, ('--auth-local', $local_method); + push @initdb, ('--auth-host', $host_method); + } else { + # trust is the default, but set it explicitly to mute a warning from initdb + # the actual method will be filled in by setup_pg_hba() + push @initdb, ('-A', 'trust'); + } + } + + # cluster startup message + if ($version >= 14) { + push @initdb, '--no-instructions'; + } else { + # ask initdb to print a different cluster start command (Debian patch) + $ENV{CLUSTER_START_COMMAND} = "pg_ctlcluster $version $cluster start"; + } + + # options from config and command line + if (my $options = $defaultconf{'initdb_options'}) { + push @initdb, split (/\s+/, replace_v_c ($options, $version, $cluster)); + } + push @initdb, @$initdb_opts_from_cli; + + if (fork) { + wait; + error 'initdb failed' if $?; + } else{ + change_ugid $owneruid, $ownergid; + print "@initdb\n" unless ($quiet); + close STDOUT if ($quiet); # suppress initdb output + exec @initdb; + } +} + +# move a file to a directory with defined permissions; set <postgresql.conf option> in +# postgresql.conf. +# Arguments: <source file> <target dir> <uid> <gid> <perms> <postgresql.conf option> +sub move_conffile { + my ($file, $target, $version, $cluster, $uid, $gid, $perms, $confopt) = @_; + my $realfile = $file; + while (-l $realfile) { + $realfile = readlink $realfile; + } + if (-e $file) { + install_file $realfile, $target, $uid, $gid, $perms; + unlink $file, $realfile; + + my @pathcomps = split ('/', $file); + $target .= '/' . $pathcomps[-1]; + PgCommon::set_conf_value $version, $cluster, 'postgresql.conf', $confopt, $target if $confopt; + } else { + error "move_conffile: required configuration file $realfile does not exist"; + } +} + +# Set up the default pg_hba.conf file: +# - Add a "local all" entry to pg_hba.conf for the db superuser before all +# other entries. +# - Change default authentication for host entries to md5. +sub setup_pg_hba { + my ($version, $owneruid, $confdir, $local_method, $host_method) = @_; + my $user = (getpwuid $owneruid)[0]; + my $fname = "$confdir/pg_hba.conf"; + my $su_comment = " +# DO NOT DISABLE! +# If you change this first entry you will need to make sure that the +# database superuser can access the database using some other method. +# Noninteractive access to all databases is required during automatic +# maintenance (custom daily cronjobs, replication, and similar tasks). +# +# Database administrative login by Unix domain socket +"; + + open my $hba, '<', $fname or error "could not open $fname for reading"; + my $search = 1; + my @lines; + while (my $line = <$hba>) { + # add superuser entry before column description line + if ($search && $line =~ /#.*TYPE\s+DATABASE/) { + push @lines, $su_comment; + push @lines, sprintf "%-7s %-15s %-39s %s\n", 'local', 'all', $user, $local_method; + push @lines, "\n"; + $search = 0; + } + + if ($version < '9.2' and not $explicit_auth_config) { + # default authentication for Unix socket connections + if ($line =~ /^#?local/) { + $line =~ s/trust/$local_method/; + } + + # default authentication for TCP connections + if ($line =~ /^#?host/) { + $line =~ s/trust/$host_method/; + } + } + + push @lines, $line; + } + close $hba; + + error "setup_pg_hba: did not find insert position" if $search; + + open my $new_hba, '>', $fname or error "could not open $fname for writing"; + foreach (@lines) { + print $new_hba $_; + } + close $new_hba; +} + +# +# Execution starts here +# + +# this flag gets set when we started creating the cluster, and thus we need to +# clean up on errors +my $cleanup_cruft = 0; + +# command line arguments + +my $startconf = ''; +my @pgoptions; +my $createclusterconf = "$PgCommon::common_confdir/createcluster.conf"; +my $environmentfile = "$PgCommon::common_confdir/environment"; +my ($owneruid, $ownergid, $socketdir, $datadir, $custom_logfile, $start, $port); +my ($encoding, $locale, $lc_collate, $lc_ctype, $lc_messages, $lc_monetary, $lc_numeric, $lc_time); +my ($no_status); + +exit 1 unless GetOptions ('u|user=s' => \$owneruid, 'g|group=s' => \$ownergid, + 's|socketdir=s' => \$socketdir, 'd|datadir=s' => \$datadir, + 'start' => \$start, 'e|encoding=s' => \$encoding, + 'l|logfile=s' => \$custom_logfile, 'start-conf=s' => \$startconf, + 'o|pgoption=s' => sub { $_[1] =~ /(.*?)=(.*)/ or error ("No '=' in pgoption '$_[1]'"); + push @pgoptions, [$1, $2]; + }, + 'createclusterconf=s' => \$createclusterconf, + 'environment=s' => \$environmentfile, + 'no-status' => \$no_status, + 'p|port=i' => \$port, + 'q|quiet' => \$quiet, + 'locale=s' => \$locale, + 'lc-collate=s' => \$lc_collate, 'lc-ctype=s' => \$lc_ctype, + 'lc-messages=s' => \$lc_messages, 'lc-monetary=s' => \$lc_monetary, + 'lc-numeric=s' => \$lc_numeric, 'lc-time=s' => \$lc_time); + +# read defaults from /etc/postgresql-common/createcluster.conf +%defaultconf = PgCommon::read_conf_file ($createclusterconf); +# process --pgoption parameters +foreach my $guc (@pgoptions) { + if ($guc->[1] eq '') { + delete $defaultconf{$guc->[0]}; + } else { + $defaultconf{$guc->[0]} = $guc->[1]; + } +} + +$explicit_auth_config = 1 if ($defaultconf{initdb_options} and $defaultconf{initdb_options} =~ /(^| )(-\w*A|--auth\b)/); + +# check validity of locale +unless (setlocale (LC_ALL, "")) { + my $env = join "\n", + map { " $_: $ENV{$_}" } + grep { /^(LC_|LANG)/ } sort keys %ENV; + error ("The locale requested by the environment is invalid:\n$env") +} + +if (@ARGV < 2) { + print "Usage: $0 [options] <version> <cluster name> [-- <initdb options>] + +Options: + -u <uid> cluster owner and superuser (default: 'postgres') + -g <gid> group for data files (default: primary group of owner) + -d <dir> data directory (default: + /var/lib/postgresql/<version>/<cluster name>) + -s <dir> socket directory (default: /var/run/postgresql for clusters + owned by 'postgres', /tmp for other clusters) + -l <dir> path to desired log file (default: + /var/log/postgresql/postgresql-<version>-<cluster>.log) + --locale <encoding> + set cluster locale (default: inherit from environment) + --lc-collate/ctype/messages/monetary/numeric/time <locale> + like --locale, but only set for a particular category + -e <encoding> Default encoding (default: derived from locale) + -p <port> port number (default: next free port starting from 5432) + --start start the cluster after creating it + --start-conf auto|manual|disabled + Set automatic startup behaviour in start.conf (default: 'auto') + --createclusterconf=file alternative createcluster.conf to use + --environment=file alternative environment file to use + <initdb options> other options to pass to initdb +"; + exit 1; +} + +$startconf ||= $defaultconf{'start_conf'} || 'auto'; +error "Invalid --start-conf value: $startconf" if $startconf ne 'auto' && + $startconf ne 'manual' && $startconf ne 'disabled'; + +if ($owneruid) { + $owneruid = (getpwnam $owneruid)[2] unless $owneruid =~ /^\d+$/; +} elsif ($> == 0) { + $owneruid = getpwnam 'postgres'; + error 'User postgres does not exist' unless $owneruid; +} else +{ + $owneruid = $>; +} + +if ($ownergid) { + $ownergid = (getgrnam $ownergid)[2] unless $ownergid =~ /^\d+$/; +} else { + $ownergid = (getpwuid $owneruid)[3]; +} + +error 'clusters must not be owned by root' unless $owneruid; + +my ($version) = $ARGV[0] =~ /^(\d+\.?\d+)$/; +error "invalid version '$ARGV[0]'" unless defined $version; +my ($cluster) = $ARGV[1] =~ /^([-.\w]+)$/; +error "invalid cluster name '$ARGV[1]'" unless defined $cluster; +if ($cluster =~ /-/ and -t 1) { + print "Warning: cluster names containing dashes (-) will cause problems when running from systemd. Continuing anyway\n"; +} +splice @ARGV, 0, 2; + +my @initdb_opts_from_cli; +# options passed through to initdb +push @initdb_opts_from_cli, ('--encoding', $encoding) if $encoding; +push @initdb_opts_from_cli, ('--locale', $locale) if $locale; +push @initdb_opts_from_cli, ('--lc-collate', $lc_collate) if $lc_collate; +push @initdb_opts_from_cli, ('--lc-ctype', $lc_ctype) if $lc_ctype; +push @initdb_opts_from_cli, ('--lc-messages', $lc_messages) if $lc_messages; +push @initdb_opts_from_cli, ('--lc-monetary', $lc_monetary) if $lc_monetary; +push @initdb_opts_from_cli, ('--lc-numeric', $lc_numeric) if $lc_numeric; +push @initdb_opts_from_cli, ('--lc-time', $lc_time) if $lc_time; + +# initdb options passed after -- +foreach my $argv (@ARGV) { + # the user passed an authentication method, don't mess with initdb and pg_hba.conf + if ($argv =~ /^(-\w*A|--auth\b)/) { # -A --auth --auth-host --auth-local + $explicit_auth_config = 1; + } + push @initdb_opts_from_cli, $argv =~ /(.*)/; # untaint +} + +# pg_hba.conf authentication settings +my $local_method = $version >= 9.1 ? 'peer' : + ($version >= 8.4 ? 'ident' : + 'ident sameuser'); +my $host_method = $version >= 14 ? 'scram-sha-256' : 'md5'; + +# create parent of data directory if missing +my $datadirp_created; +if (!defined $datadir) { + $datadir = replace_v_c ($defaultconf{'data_directory'} || "/var/lib/postgresql/%v/%c", $version, $cluster); + $datadir =~ s!/+$!!; + my $pd = $datadir; + $pd =~ s!/[^/]*$!!; + + # ensure that the version data dir is owned by postgres as well, so that + # it can be administrated without root permissions + if (!stat $pd) { + my @install = qw(install -d); + push @install, '-o', $postgres_user[2], '-g', $postgres_user[3] if ($> == 0); + system @install, $pd; + $datadirp_created = $pd; # clean up in case of error + } +} +my $confdirp = "$PgCommon::confroot/$version"; +my $confdir = "$confdirp/$cluster"; + +# some sanity checks +error "no initdb program for version $version found" unless get_program_path 'initdb', $version; +error 'cluster configuration already exists' + if -e "$confdir/postgresql.conf" || -e "$confdir/pg_hba.conf"; + +if (defined $port) { + error 'port must be a positive integer between 1024 and 65535' + unless $port =~ /^\d+/ && $port >= 1024 && $port <= 65535; +} else { + $port = next_free_port; +} + +# create configuration directory +if (!stat $confdirp) { + my @install = qw(install -d); + push @install, '-o', $postgres_user[2], '-g', $postgres_user[3] if ($> == 0); + system @install, $confdirp; +} +# now we created the first new directory for this cluster and start to rollback +# on error +$cleanup_cruft = 1; + +error 'could not create configuration directory; you might ' . + 'need to run this program with root privileges' if system ('install', '-d', $confdir); + +# check whether we have an already existing cluster; check version and +# determine owner in this case +my $newcluster = 0; + +if (-f "$datadir/PG_VERSION") { + open my $fh, '<', "$datadir/PG_VERSION" or error "could not open $datadir/PG_VERSION"; + chomp(my $existingver = <$fh>); + close $fh; + + ($owneruid, $ownergid) = (stat "$datadir/PG_VERSION")[4,5]; + if ($existingver == $version) { + print "Configuring already existing cluster (configuration: $confdir, data: $datadir, owner: $owneruid:$ownergid)\n"; + } else { + error "$datadir contains a version $existingver cluster, but $version was requested"; + } +} else { + print "Creating new PostgreSQL cluster $version/$cluster ...\n"; + init_db $version, $cluster, $datadir, $owneruid, $ownergid, + $local_method, $host_method, \@initdb_opts_from_cli; + $newcluster = 1; +} + +# create default "start" file +set_cluster_start_conf $version, $cluster, $startconf; + +# create default pg_ctl.conf file +set_cluster_pg_ctl_conf $version, $cluster, ''; + +# move conffiles, setup permissions +move_conffile "$datadir/postgresql.conf", $confdir, $version, $cluster, $owneruid, $ownergid, '644'; +move_conffile "$datadir/pg_hba.conf", $confdir, $version, $cluster, $owneruid, $ownergid, '640', 'hba_file'; +move_conffile "$datadir/pg_ident.conf", $confdir, $version, $cluster, $owneruid, $ownergid, '640', 'ident_file'; +foreach my $f ($datadir, $confdir, "$confdir/start.conf", "$confdir/pg_ctl.conf") { + lchown $owneruid, $ownergid, $f or error "lchown $f: $!"; +} + +PgCommon::set_conf_value $version, $cluster, 'postgresql.conf', 'data_directory', $datadir; + +# add access for database superuser +setup_pg_hba $version, $owneruid, $confdir, $local_method, $host_method + if $newcluster and not $explicit_auth_config; + +# configure socket directory +if (! $socketdir && ! -e '/var/run/postgresql') { + system 'install', '-d', '-o', $postgres_user[2], '-g', $postgres_user[3], '/var/run/postgresql'; +} +if ($socketdir && ! -e $socketdir) { + if (system 'install', '-d', '-m', '0755', '-o', $owneruid, '-g', $ownergid, $socketdir) { + error "could not create socket directory $socketdir"; + } +} +my $orig_euid = $>; +my $orig_egid = $); +$) = $ownergid; +$> = $owneruid; +unless ($socketdir) { + if ($version < 9.4 and $PgCommon::rpm) { + $socketdir = '/tmp'; # PGDG 9.3 and earlier defaults to /tmp in libpq et al. + } elsif (-w '/var/run/postgresql') { + $socketdir = '/var/run/postgresql'; + } else { + $socketdir='/tmp'; + } +} +set_cluster_socketdir $version, $cluster, $socketdir; +$> = $orig_euid; +$) = $orig_egid; + +set_cluster_port $version, $cluster, $port; + +# create log file +if (! -d '/var/log/postgresql') { + mkdir '/var/log/postgresql' or + error "could not create log directory; you might need to run this program with root privileges"; + chmod 01775, '/var/log/postgresql'; + lchown 0, $postgres_user[3], '/var/log/postgresql'; +} +my $real_logfile = $custom_logfile || "/var/log/postgresql/postgresql-$version-$cluster.log"; +error "logfile $real_logfile is a directory, not a file" if (-d $real_logfile); +if (! -e $real_logfile) { + open my $fh, '>>', $real_logfile or error "could not create log file $real_logfile"; +} +chmod 0640, $real_logfile; +my $g; +if ($owneruid < 500) { + $g = (getgrnam 'adm')[2]; +} else { + $g = $ownergid; +} +lchown $owneruid, $g, $real_logfile; +# if we are using a non-default log file, create a log symlink +if ($custom_logfile) { + symlink $real_logfile, "$confdir/log"; + lchown $owneruid, $ownergid, "$confdir/log"; +} + +# SSL configuration +my $want_ssl = PgCommon::config_bool($defaultconf{ssl} || 'on'); +# older versions (<= 9.1 as of 2019-03) do not support ssl anymore +my $postgres = get_program_path('postgres', $version); +my $ldd = `ldd $postgres 2>/dev/null`; +$want_ssl = 0 if ($ldd and $ldd !~ /libssl/); + +# Check whether we can access the SSL private key as the cluster owner +my $ssl_key_file = '/etc/ssl/private/ssl-cert-snakeoil.key'; +my $ssl_cert_file = '/etc/ssl/certs/ssl-cert-snakeoil.pem'; +my $ssl_ca_file = "$PgCommon::common_confdir/root.crt"; +my $ssl_key_access; +my ($uid, $euid, $gid, $egid) = ($<, $>, $(, $)); +change_ugid $owneruid, $ownergid; +$ssl_key_access = -r $ssl_key_file; + +# check for stats_temp_directory access +delete $defaultconf{stats_temp_directory} if ($version < 8.4 or $version >= 15); +if ($defaultconf{stats_temp_directory}) { + my $stats_temp_directory = replace_v_c ($defaultconf{stats_temp_directory}, $version, $cluster); + $stats_temp_directory =~ s!/$!!; # strip trailing slash + my $stats_temp_parent = $stats_temp_directory; + $stats_temp_parent =~ s!/[^/]+$!!; + if (-d $stats_temp_directory) { + if (! -w $stats_temp_directory) { + print "Warning: The selected stats_temp_directory $stats_temp_directory +is not writable for the cluster owner. Not adding this setting in +postgresql.conf.\n"; + delete $defaultconf{stats_temp_directory}; + } + } elsif (! -d $stats_temp_parent) { + print "Warning: The parent $stats_temp_parent of the selected +stats_temp_directory does not exist. Not adding this setting in +postgresql.conf.\n"; + delete $defaultconf{stats_temp_directory}; + } elsif (! -w $stats_temp_parent) { + print "Warning: The parent $stats_temp_parent of the selected +stats_temp_directory is not writable for the cluster owner. Not adding this +setting in postgresql.conf.\n"; + delete $defaultconf{stats_temp_directory}; + } + # create the stats directory now. pg_ctlcluster would create it anyway, but + # when using pg_upgradecluster -m upgrade, it is not run before the cluster + # is started for the first time + if ($defaultconf{stats_temp_directory}) { + system 'install', '-d', '-m', '750', '-o', $owneruid, '-g', $ownergid, $stats_temp_directory; + } +} + +$> = $euid; +$< = $uid; +$( = $gid; +$) = $egid; +die "changing euid back: $!" if $> != $euid; +die "changing egid back: $!" if $) != $egid; + +# enable SSL if we have the snakeoil default certificate +if ($want_ssl && $newcluster && -e $ssl_cert_file && $ssl_key_access) { + if ($version >= '9.2') { + PgCommon::set_conf_value $version, $cluster, 'postgresql.conf', + 'ssl_cert_file', $ssl_cert_file; + PgCommon::set_conf_value $version, $cluster, 'postgresql.conf', + 'ssl_key_file', $ssl_key_file; + } else { + symlink $ssl_cert_file, "$datadir/server.crt"; + symlink $ssl_key_file, "$datadir/server.key"; + } + + PgCommon::set_conf_value $version, $cluster, 'postgresql.conf', 'ssl', 'on'; +} + +# SSL client certificate CA +if ($want_ssl && $newcluster && -e $ssl_ca_file) { + # check if we have a cert in there or just the boilerplate installed by our postinst + open my $fh, '<', $ssl_ca_file or error "could not open $ssl_ca_file for reading"; + my $val; + read $fh, $val, 4096; + if ($val =~ /^-----BEGIN CERTIFICATE-----/m) { + if ($version >= '9.2') { + PgCommon::set_conf_value $version, $cluster, 'postgresql.conf', + 'ssl_ca_file', $ssl_ca_file; + } else { + symlink $ssl_ca_file, $datadir.'/root.crt'; + } + } +} + +# SSL client certificate revocation list +if ($want_ssl && $newcluster && -e "$PgCommon::common_confdir/root.crl") { + if ($version >= '9.2') { + PgCommon::set_conf_value $version, $cluster, 'postgresql.conf', + 'ssl_crl_file', "$PgCommon::common_confdir/root.crl"; + } else { + symlink "$PgCommon::common_confdir/root.crl", $datadir.'/root.crl'; + } +} + +# create default (empty) environment file +my $defaultenv = "# environment variables for postgres processes +# This file has the same syntax as postgresql.conf: +# VARIABLE = simple_value +# VARIABLE2 = 'any value!' +# I. e. you need to enclose any value which does not only consist of letters, +# numbers, and '-', '_', '.' in single quotes. Shell commands are not +# evaluated. +"; +if (-e $environmentfile) { + open my $env, '<', $environmentfile or error "could not read environment file $environmentfile"; + local $/; # slurp mode + $defaultenv = <$env>; +} +$defaultenv = replace_v_c ($defaultenv, $version, $cluster); +open my $env, '>', "$confdir/environment" or error "could not create environment file $confdir/environment"; +print $env $defaultenv; +close $env; +chmod 0644, "$confdir/environment"; +lchown $owneruid, $ownergid, "$confdir/environment"; + +$cleanup_cruft = 0; + +# configure to create external PID file +if ($socketdir eq '/var/run/postgresql') { + PgCommon::set_conf_value $version, $cluster, 'postgresql.conf', 'external_pid_file', "/var/run/postgresql/$version-$cluster.pid"; +} + +# handle other createcluster.conf parameters, including --pgoption parameters +foreach my $guc (sort keys %defaultconf) { + next if $guc =~ /^(create_main_cluster|start_conf|data_directory|waldir|xlogdir|initdb_options|ssl)$/; + next if $guc eq 'logging_collector' and $version < 8.3; + next if $guc eq 'cluster_name' and $version < 9.5; + my $val = replace_v_c ($defaultconf{$guc}, $version, $cluster); + $guc =~ s/^add_include/include/; # remove harness from include directives in createcluster.conf + if ($guc eq 'include_dir') { + next if ($version < 9.3); + if ($val =~ /^[\w.]+$/ and not -e "$confdir/$val") { # create directory relative to new config directory + mkdir "$confdir/$val", 0755; + lchown $owneruid, $ownergid, "$confdir/$val"; + } + } + PgCommon::set_conf_value $version, $cluster, 'postgresql.conf', $guc, $val; +} + +# notify systemd about the new cluster +if (not exists $ENV{'PG_CLUSTER_CONF_ROOT'} and $startconf eq 'auto' and -d '/run/systemd/system') { + if ($> == 0) { + system 'systemctl daemon-reload'; + } elsif (-t 1) { + print "Warning: systemd does not know about the new cluster yet. Operations like \"service postgresql start\" will not handle it. To fix, run:\n"; + print " sudo systemctl daemon-reload\n"; + } +} + +# notify apt about the new cluster +if (not exists $ENV{'PG_CLUSTER_CONF_ROOT'} and $> == 0) { + system "/usr/share/postgresql-common/pg_updateaptconfig"; +} + +# start it if requested +if ($start) { + system 'pg_ctlcluster', $version, $cluster, 'start'; + die "Could not start cluster\n" if ($?); +} + +# finally, show the cluster we created +system 'pg_lsclusters', $version, $cluster unless ($quiet or $no_status); + +END { + # clean up cruft if something went wrong + if ($cleanup_cruft && defined $version && defined $cluster) { + system "pg_dropcluster $version $cluster 2>/dev/null"; + rmdir $datadirp_created if ($datadirp_created); # clean up after early errors which pg_dropcluster doesn't handle + exit 1; + } +} + +__END__ + +=head1 NAME + +pg_createcluster - create a new PostgreSQL cluster + +=head1 SYNOPSIS + +B<pg_createcluster> [I<options>] I<version> I<name> [B<--> I<initdb options>] + +=head1 DESCRIPTION + +B<pg_createcluster> creates a new PostgreSQL server cluster (i. e. a +collection of databases served by a L<postgres(1)> instance) and +integrates it into the multi-version/multi-cluster architecture of the +B<postgresql-common> package. + +Every cluster is uniquely identified by its version and name. The name can be +arbitrary. The default cluster that is created on installation of a server +package is C<main>. However, you might wish to create other clusters for +testing, with other superusers, a cluster for each user on a shared server, +etc. C<pg_createcluster> will abort with an error if you try to create a +cluster with a name that already exists for that version. + +For compatibility with B<systemd> service units, the cluster name should not +contain any dashes (B<->). B<pg_ctlcluster> will warn about the problem, but +succeed with the operation. + +Given a major PostgreSQL I<version> (like "8.2" or "8.3") and a cluster +I<name>, it creates the necessary configuration files in +C</etc/postgresql/>I<version>C</>I<name>C</>; in particular these are +C<postgresql.conf>, C<pg_ident.conf>, C<pg_hba.conf>, a postgresql-common +specific configuration file C<start.conf> (see B<STARTUP CONTROL> below), +C<pg_ctl.conf>, and a symbolic link C<log> which points to the log file (by +default, C</var/log/postgresql/postgresql->I<version>C<->I<name>C<.log>). + +C<postgresql.conf> is automatically adapted to use the next available port, i. +e. the first port (starting from 5432) which is not yet used by an already +existing cluster. + +If the data directory does not yet exist, PostgreSQL's L<initdb(1)> command is +used to generate a new cluster structure. If the data directory already exists, +it is integrated into the B<postgresql-common> structure by moving the +configuration file and setting the data_directory option. Please note that this +I<only> works for data directories which were created directly with B<initdb>, i. +e. all the configuration files (C<postgresql.conf> etc.) must be present in the +data directory. + +If a custom socket directory is given and it does not exist, it is created. + +If the log file does not exist, it is created. In any case the permissions are +adjusted to allow write access to the cluster owner. Please note that +C<postgresql.conf> can be customized to specify C<log_directory> and/or +C<log_filename>; if at least one of these options is present, then the symbolic +link C<log> in the cluster configuration directory is ignored. + +If the default snakeoil SSL certificate exists +(C</etc/ssl/certs/ssl-cert-snakeoil.pem> and +C</etc/ssl/private/ssl-cert-snakeoil.key>), and the C<postgres> user is in the +C<ssl-cert> Unix group, B<pg_createcluster> configures the cluster to use this +certificate, and enables SSL. Therefore all clusters will use the same SSL +certificate by default. For versions up to 9.1, symlinks in the data directory +will be created (C<server.crt> and C<server.key>); for 9.2 and later, the +appropriate C<postgresql.conf> options will be set (C<ssl_cert_file> and +C<ssl_key_file>). Of course you can replace this with a cluster specific +certificate. Similarly for C</etc/postgresql-common/root.crt> and +C</etc/postgresql-common/root.crl>, these files will be configured as client +certificate CA and revocation list, when present. (C<root.crt> is initially a +placeholder that will only be used if real certificates are added to the file.) + +=head1 OPTIONS + +=over 4 + +=item B<-u> I<user>, B<--user=>I<user> + +Set the user who owns the cluster and becomes the database superuser to the +given name or uid. By default, this is the user B<postgres>. A cluster must +not be owned by root. + +=item B<-g> I<group>, B<--group=>I<group> + +Change the group of the cluster related data files. By default this will be the +primary group of the database owner. + +=item B<-d> I<dir>, B<--datadir=>I<dir> + +Explicitly set the data directory path, which is used to store all the actual +databases and tables. This will become quite big (easily in the order of five +times the amount of actual data stored in the cluster). Defaults to +C</var/lib/postgresql/>I<version>C</>I<cluster>. + +=item B<-s> I<dir>, B<--socketdir=>I<dir> + +Explicitly set the directory where the L<postgres(1)> server stores the Unix +socket for local connections. Defaults to C</var/run/postgresql/> for clusters +owned by the user B<postgres>, and C</tmp> for clusters owned by other users. +Please be aware that C</tmp> is an unsafe directory since everybody can create +a socket there and impersonate the database server. If the given directory does +not exist, it is created with appropriate permissions. + +=item B<-l> I<path>, B<--logfile=>I<path> + +Explicitly set the path for the L<postgres(1)> server log file. Defaults to +C</var/log/postgresql/postgresql->I<version>C<->I<cluster>C<.log>. + +=item B<--locale=>I<locale> + +Set the default locale for the database cluster. If this option is not +specified, the locale is inherited from the environment that +B<pg_createcluster> runs in. + +=item B<--lc-collate=>I<locale> + +=item B<--lc-ctype=>I<locale> + +=item B<--lc-messages=>I<locale> + +=item B<--lc-monetary=>I<locale> + +=item B<--lc-numeric=>I<locale> + +=item B<--lc-time=>I<locale> + +Like B<--locale>, but only sets the locale in the specified category. + +=item B<-e> I<encoding>, B<--encoding=>I<encoding> + +Select the encoding of the template database. This will also be the default +encoding of any database you create later, unless you override it there. The +default is derived from the locale, or SQL_ASCII if that does not work. The +character sets supported by the PostgreSQL server are described in the +documentation. + +B<Note>: It is not recommended to set this option directly! Set the locale +instead. + +=item B<-p> I<port>, B<--port=>I<port> + +Select the port the new cluster listens on (for the Unix socket and the TCP +port); this must be a number between 1024 and 65535, since PostgreSQL does not +run as root and thus needs an unprivileged port number. By default the next +free port starting from 5432 is assigned. + +=item B<-q> B<--quiet> B<--no-status> + +Suppress output from B<initdb> and (or only) the cluster status message at the +end of the output. + +=item B<--start> + +Immediately start a server for the cluster after creating it (i. e. call +C<pg_ctlcluster> I<version cluster> C<start> on it). By default, the cluster is +not started. + +=item B<--start-conf=>B<auto>|B<manual>|B<disabled> + +Set the initial value in the C<start.conf> configuration file. See B<STARTUP +CONTROL> below. By default, B<auto> is used, which means that the cluster is +handled by C</etc/init.d/postgresql>, i. e. starts and stops +automatically on system boot. + +=item B<-o> I<guc>B<=>I<value>, B<--pgoption> I<guc>B<=>I<value> + +Configuration option to set in the new C<postgresql.conf> file. + +=item B<--createclusterconf=>I<file> + +Alternative B<createcluster.conf> file to use. Default is +C</etc/postgresql-common/createcluster.conf> (or +C<$PGSYSCONFDIR/createcluster.conf>). + +=item B<--environment=>I<file> + +Alternative default B<environment> file to use. Default is +C</etc/postgresql-common/environment> (or C<$PGSYSCONFDIR/environment>). +If the file is missing, a placeholder string is used. +%v and %c are replaced; see DEFAULT VALUES below. + +=item B<--> I<initdb options> + +Options passed directly to L<initdb(1)>. + +Per default, B<pg_createcluster> will update the C<pg_hba.conf> file generated +by initdb to use peer authentication on local (unix) connections, and md5 on +TCP (host) connections. If explicit authentication config is included here +(B<-A>, B<--auth>, B<--auth-host>, B<--auth-local>), the C<pg_hba.conf> file +will be left untouched. + +I<Note:> If only one of B<--auth-host> and B<--auth-local> is provided, the +other setting will default to B<trust> as per B<initdb>'s defaults, opening a +potential security risk. + +=back + +=head1 STARTUP CONTROL + +The C<start.conf> file in the cluster configuration directory controls the +start/stop behavior of that cluster's postgres process. The file can contain +comment lines (started with '#'), empty lines, and must have exactly one +line with one of the following keywords: + +=over 4 + +=item B<auto> + +The postgres process is started/stopped automatically in the init script. + +When running from B<systemd>, the cluster is started/stopped when +B<postgresql.service> is started/stopped. +This is also the default if the file is missing. + +=item B<manual> + +The postgres process is not handled by the init script, but manually +controlling the cluster with L<pg_ctlcluster(1)> is permitted. + +When running from B<systemd>, the cluster is not started automatically when +B<postgresql.service> is started. However, stopping/restarting +B<postgresql.service> will stop/restart the cluster. The cluster can be started +using B<systemctl start postgresql@>I<version>B<->I<cluster>. + +=item B<disabled> + +Neither the init script, L<pg_ctlcluster(1)>, nor B<postgresql@.service> are permitted to start/stop the +cluster. Please be aware that this will not stop the cluster owner from calling +lower level tools to control the postgres process; this option is only meant +to prevent accidents during maintenance, not more. + +=back + +When running from B<systemd>, invoke B<systemctl daemon-reload> after editing +C<start.conf>. + +The C<pg_ctl.conf> file in the cluster configuration directory can contain +additional options passed to B<pg_ctl> of that cluster. + +=head1 DEFAULT VALUES + +Some default values used by B<pg_createcluster> can be modified in +C</etc/postgresql-common/createcluster.conf>. Occurrences of B<%v> are replaced +by the major version number, and B<%c> by the cluster name. Use B<%%> for a +literal B<%>. + +=over 4 + +=item B<create_main_cluster> (Default: B<true>) + +Create a B<main> cluster when a new postgresql-NN server package is installed. + +=item B<start_conf> (Default: B<auto>) + +Default C<start.conf> value to use. + +=back + +=over 4 + +=item B<data_directory> (Default: B</var/lib/postgresql/%v/%c>) + +Default data directory. + +=item B<waldir|xlogdir> (Default: unset) + +Default directory for transaction logs. When used, B<initdb> will create a +symlink from C<pg_wal> (PostgreSQL 9.6 and earlier: C<pg_xlog>) in the data +directory to this location. Unset by default, i.e. transaction logs remain in +the data directory. Both spellings of this option are accepted, and translated +to the correct initdb invocation depending on the cluster version. + +=item B<initdb_options> (Default: unset) + +Other options to pass to B<initdb>. + +=item Other options + +All other options listed are copied into the new cluster's postgresql.conf, e.g.: + + listen_addresses = '*' + log_line_prefix = '%%t ' + +Some postgresql.conf options are treated specially: + +=over 4 + +=item B<ssl> + +Only added to postgresql.conf if the default snakeoil certificates exist and +are readable for the cluster owner as detailed above. + +=item B<stats_temp_directory> + +Only added to postgresql.conf if existing, and writable for the cluster owner, +or else if the parent directory is writable. Not used on PostgreSQL 15 or later. + +=back + +=item Include files + +=over 4 + +=item B<include> + +=item B<include_if_exists> + +=item B<include_dir> + +B<createcluster.conf> supports the same include directives as +B<postgresql.conf>. + +=item B<add_include> + +=item B<add_include_if_exists> + +=item B<add_include_dir> + +To add include directives to the new postgresql.conf file, use the B<add_*> +directives. The B<add_> prefix is removed. + +=back + +=back + +=head1 SEE ALSO + +L<initdb(1)>, L<pg_ctlcluster(8)>, L<pg_lsclusters(1)>, L<pg_wrapper(1)> + +=head1 AUTHORS + +Martin Pitt L<E<lt>mpitt@debian.orgE<gt>>, Christoph Berg L<E<lt>myon@debian.orgE<gt>> |