summaryrefslogtreecommitdiffstats
path: root/pg_createcluster
diff options
context:
space:
mode:
Diffstat (limited to 'pg_createcluster')
-rwxr-xr-xpg_createcluster952
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>>