summaryrefslogtreecommitdiffstats
path: root/pg_ctlcluster
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-06 01:48:40 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-06 01:48:40 +0000
commit73e1fa4347d6c81c6e64054957ab436fa358ced1 (patch)
treef1cf86462dba1a85afd6005fca0a45b7e574c23c /pg_ctlcluster
parentInitial commit. (diff)
downloadpostgresql-common-upstream.tar.xz
postgresql-common-upstream.zip
Adding upstream version 200+deb10u5.upstream/200+deb10u5upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'pg_ctlcluster')
-rwxr-xr-xpg_ctlcluster658
1 files changed, 658 insertions, 0 deletions
diff --git a/pg_ctlcluster b/pg_ctlcluster
new file mode 100755
index 0000000..d296cfe
--- /dev/null
+++ b/pg_ctlcluster
@@ -0,0 +1,658 @@
+#!/usr/bin/perl -wT
+
+# multiversion/cluster aware pg_ctl wrapper; this also supplies the correct
+# configuration parameters to 'start', and makes sure that postgres really
+# stops on 'stop'.
+#
+# (C) 2005-2009 Martin Pitt <mpitt@debian.org>
+# (C) 2009 Cyril Bouthors <cyril@bouthors.org>
+# (C) 2013-2019 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 Getopt::Long;
+use POSIX qw/setsid dup2 :sys_wait_h/;
+use PgCommon;
+use Fcntl qw(SEEK_SET O_RDWR O_CREAT O_EXCL);
+use POSIX qw(lchown);
+
+my ($version, $cluster, $pg_ctl, $force);
+my (@postgres_auxoptions, @pg_ctl_opts_from_cli);
+my (%postgresql_conf, %info);
+my $mode = 'fast'; # default shutdown mode
+my $foreground = 0; # don't daemonize, use postgres instead of pg_ctl
+my $stdlog = 0; # when run in foreground, still log to the default logfile
+
+# untaint environment
+$ENV{'PATH'} = '/sbin:/bin:/usr/sbin:/usr/bin';
+delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
+chdir '/';
+
+# If a pid file is already present, delete it if it is stale/invalid, or exit
+# with a notice if it belongs to an already running postgres.
+sub start_check_pid_file {
+ my $pidfile = $info{'pgdata'}.'/postmaster.pid';
+ if (PgCommon::check_pidfile_running $pidfile) {
+ print "Cluster is already running.\n";
+ exit 2;
+ }
+
+ # Remove invalid or stale PID file
+ if (-e $pidfile) {
+ unlink $pidfile;
+ print "Removed stale pid file.\n";
+ }
+}
+
+# Check if a pid file is not present or it is invalid. If so, clean up/abort.
+sub stop_check_pid_file {
+ my $pidfile = $info{'pgdata'}.'/postmaster.pid';
+ my $pid = read_pidfile $pidfile;
+ return if (defined $pid && PgCommon::check_pidfile_running $pidfile);
+ if ($info{'running'}) {
+ error 'pid file is invalid, please manually kill the stale server process.';
+ }
+
+ # Remove invalid or stale PID file
+ if (-e $pidfile) {
+ unlink $pidfile;
+ print "Removed stale pid file.\n";
+ }
+ print "Cluster is not running.\n";
+ exit 2;
+}
+
+# check if a cluster reliably connects or fails
+# Arguments: <version> <cluster> <port> <socket dir>
+sub cluster_port_ready {
+ my ($v, $c, $p, $sd) = @_;
+ my $psql = get_program_path 'psql', $v;
+ error 'cluster_port_ready: could not find psql binary' unless $psql;
+ my $n = 0;
+ my $result = 0;
+
+
+ # probe until we get three successful or failed connections in a row
+ $ENV{'PGPASSWORD'} = 'foo'; # prevent hangs if superuser cannot connect without password
+ my $out;
+ while ($n < ($result ? 10 : 3)) {
+ select undef, undef, undef, 0.5;
+ $out = `$psql -h '$sd' --port $p -Xl 2>&1 >/dev/null`;
+
+ if ($? == $result) {
+ $n++;
+ } else {
+ $n = 0;
+ }
+ $result = $?;
+ }
+
+ if ($out =~ 'FATAL:') {
+ print STDERR "Warning: connection to the database failed, disabling startup checks:\n$out\n";
+ return cluster_port_running $v, $c, $p;
+ }
+ return !$result;
+}
+
+sub start {
+ start_check_pid_file;
+
+ # check conflicting port
+ if (cluster_port_running $version, $cluster, $info{'port'}) {
+ my $sockdir = get_cluster_socketdir $version, $cluster;
+ error("Port conflict: another instance is already running on $sockdir with port $info{'port'}");
+ }
+
+ # prepare environment (empty except for content of 'environment', and LANG)
+ %ENV = read_cluster_conf_file $version, $cluster, 'environment';
+ # set LANG so non-ascii chars in the server log are not replaced by '?' (affected are non-session contexts)
+ unless (exists $ENV{LANG}) {
+ my $lc_messages = PgCommon::get_conf_value $version, $cluster, 'postgresql.conf', 'lc_messages';
+ $ENV{LANG} = $lc_messages if $lc_messages;
+ }
+ # 9.5 configures OOM killer using env vars
+ if ($version >= 9.5) {
+ $ENV{PG_OOM_ADJUST_FILE} = "/proc/self/oom_score_adj" unless (exists $ENV{PG_OOM_ADJUST_FILE});
+ # PG_OOM_ADJUST_VALUE defaults to 0, but can be overridden here
+ }
+
+ my $postgres_opts = '';
+ my $usd = $version >= 9.3 ? 'unix_socket_directories' : 'unix_socket_directory';
+ if (!(PgCommon::get_conf_value $version, $cluster, 'postgresql.conf', $usd)) {
+ $postgres_opts .= "-c $usd=\"$info{'socketdir'}\"";
+ }
+
+ my $cdir = $info{'configdir'};
+ $postgres_opts .= " -c config_file=\"$cdir/postgresql.conf\"";
+ if (!(PgCommon::get_conf_value $version, $cluster, 'postgresql.conf', 'hba_file')) {
+ $postgres_opts .= " -c hba_file=\"$cdir/pg_hba.conf\"";
+ }
+ if (!(PgCommon::get_conf_value $version, $cluster, 'postgresql.conf', 'ident_file')) {
+ $postgres_opts .= " -c ident_file=\"$cdir/pg_ident.conf\"";
+ }
+
+ if ((-d '/var/run/postgresql') && !defined (PgCommon::get_conf_value $version, $cluster, 'postgresql.conf', 'external_pid_file')) {
+ # check whether /var/run/postgresql/ is writable as the cluster owner
+ my $vrp_writable;
+ if ($> == 0) {
+ change_ugid $info{'owneruid'}, $info{'ownergid'};
+ $vrp_writable = -w '/var/run/postgresql';
+ $< = $> = 0;
+ $( = $) = 0;
+ } else {
+ $vrp_writable = -w '/var/run/postgresql';
+ }
+ if ($vrp_writable) {
+ $postgres_opts .= " -c external_pid_file=\"/var/run/postgresql/$version-$cluster.pid\"";
+ }
+ }
+
+ $postgres_opts .= ' ' . (join ' ', @postgres_auxoptions);
+ ($postgres_opts) = $postgres_opts =~ /(.*)/; # untaint
+
+ if ($foreground) {
+ if ($stdlog and $info{'logfile'}) {
+ my $fd = POSIX::open($info{logfile}, POSIX::O_WRONLY|POSIX::O_APPEND|POSIX::O_CREAT) or error "Could not open logfile $info{logfile}";
+ dup2($fd, 1);
+ dup2($fd, 2);
+ }
+ my $postgres = get_program_path 'postgres', $version;
+ error "Could not find postgres executable for version $version" unless ($postgres);
+ exec '/bin/sh', '-c', "exec $postgres $postgres_opts" or
+ error "Executing $postgres failed: $!"
+ }
+
+ # only supply or default logfile path when none is given explicitly in
+ # postgresql.conf
+ my @options = ($pg_ctl, 'start', '-D', $info{'pgdata'});
+ my $logsize = 0;
+ if ($info{'logfile'}) {
+ push @options, ('-l', $info{'logfile'});
+ # remember current size of the log
+ $logsize = (stat $info{'logfile'})[7] || 0; # ignore stat errors
+ }
+
+ push @options, @pg_ctl_opts_from_cli if @pg_ctl_opts_from_cli;
+
+ my %pg_ctl_opts_from_file = read_cluster_conf_file $version, $cluster, 'pg_ctl.conf';
+ push @options, split(' ', $pg_ctl_opts_from_file{'pg_ctl_options'})
+ if defined $pg_ctl_opts_from_file{'pg_ctl_options'} and $pg_ctl_opts_from_file{'pg_ctl_options'} ne '';
+
+ push @options, ('-s', '-o', $postgres_opts);
+
+ if (fork) {
+ wait;
+ if ($?) {
+ my $exit = $? >> 8;
+ print STDERR "Error: $pg_ctl @options exited with status $exit: $!\n";
+ my $currlogsize = (stat $info{'logfile'})[7] if $info{'logfile'} && -r $info{'logfile'};
+ if ($currlogsize) {
+ open LOG, $info{'logfile'} or
+ error "Could not open log file " . $info{'logfile'};
+ seek LOG, $logsize, SEEK_SET;
+ print STDERR $_ while <LOG>;
+ }
+ exit $exit;
+ }
+ } else {
+ setsid or error "could not start session: $!";
+ if ($info{'logfile'}) {
+ my $fd = POSIX::open($info{'logfile'}, POSIX::O_WRONLY|POSIX::O_APPEND|POSIX::O_CREAT) or error "Could not open logfile $info{'logfile'}";
+ dup2($fd, 1);
+ dup2($fd, 2);
+ }
+ exec $pg_ctl @options or error "could not exec $pg_ctl @options: $!";
+ }
+
+ # wait a bit until the socket exists
+ my $success = 0;
+ my $currlogsize = 0;
+ my $pidfile = $info{'pgdata'}.'/postmaster.pid';
+ for (my $attempt = 0; $attempt < 60; $attempt++) {
+ select (undef, undef, undef, 0.5);
+ $currlogsize = (stat $info{'logfile'})[7] if $info{'logfile'} && -r $info{'logfile'};
+ if (cluster_port_running $version, $cluster, $info{'port'}) {
+ $success = 1;
+ last;
+ }
+
+ # if postgres wrote something, but the process does not exist any
+ # more, there must be a problem and we can stop immediately
+ last if ($currlogsize > $logsize && !PgCommon::check_pidfile_running $pidfile);
+ }
+
+ # OK, the server runs, now wait until it stabilized
+ if ($success) {
+ $success = cluster_port_ready $version, $cluster, $info{'port'}, $info{'socketdir'};
+ }
+
+ if (!$success) {
+ if ($currlogsize) {
+ print STDERR "The PostgreSQL server failed to start. Please check the log output:\n";
+ open LOG, $info{'logfile'} or
+ error "Could not open log file " . $info{'logfile'};
+ seek LOG, $logsize, SEEK_SET;
+ print STDERR $_ while <LOG>;
+ } else {
+ print STDERR "The PostgreSQL server failed to start. Please check the log output.\n";
+ }
+ exit 1;
+ }
+
+ return 0;
+}
+
+sub stop {
+ stop_check_pid_file;
+ my $result = 1;
+
+ my @options = ('stop', '-s', '-w', '-D', $info{'pgdata'});
+
+ push @options, @pg_ctl_opts_from_cli if @pg_ctl_opts_from_cli;
+
+ my %pg_ctl_opts_from_file = read_cluster_conf_file $version, $cluster, 'pg_ctl.conf';
+ push @options, split(' ', $pg_ctl_opts_from_file{'pg_ctl_options'})
+ if defined $pg_ctl_opts_from_file{'pg_ctl_options'} and $pg_ctl_opts_from_file{'pg_ctl_options'} ne '';
+
+ if (!fork()) {
+ close STDOUT;
+ exec $pg_ctl, @options, '-m', ($force ? 'fast' : $mode);
+ } else {
+ wait;
+ $result = $? >> 8;
+ }
+
+ # try harder if forced and server hasn't stopped yet
+ if ($force and -f $info{'pgdata'}.'/postmaster.pid') {
+ print "(does not shutdown gracefully, now stopping immediately)\n";
+ $result = system $pg_ctl, @options, '-m', 'immediate';
+ }
+
+ # external_pid_file files are currently not removed by postgres itself
+ if ($result == 0) {
+ unlink "/var/run/postgresql/$version-$cluster.pid";
+ }
+
+ return $result;
+}
+
+sub restart {
+ my $result;
+
+ if ($info{'running'}) {
+ $result = stop;
+ return $result if $result;
+ }
+ return start;
+}
+
+sub reload {
+ exec $pg_ctl, '-D', $info{'pgdata'}, '-s', @pg_ctl_opts_from_cli, 'reload';
+}
+
+sub status {
+ exec $pg_ctl, '-D', $info{'pgdata'}, 'status';
+}
+
+sub promote {
+ exec $pg_ctl, '-D', $info{'pgdata'}, '-s', @pg_ctl_opts_from_cli, 'promote';
+}
+
+#
+# main
+#
+
+my ($skip_systemctl_redirect, $bindir);
+
+exit 1 unless GetOptions ('o|options=s' => \@postgres_auxoptions,
+ 'f|force' => \$force,
+ 'm|mode=s' => \$mode,
+ 'foreground' => \$foreground,
+ 'skip-systemctl-redirect' => \$skip_systemctl_redirect,
+ 'stdlog' => \$stdlog,
+ 'bindir=s' => \$bindir,
+);
+
+if ($mode =~ /^(s(mart)?|f(ast)?|i(mmediate)?)$/) {
+ $mode = $1; # untaint
+} else {
+ print "Invalid -m mode, valid are: smart fast immediate\n";
+ exit 1;
+}
+
+($bindir) = $bindir =~ /^(\/.*)$/ if $bindir; # untaint
+
+# accept both "version cluster action" and "version[-/]cluster action"
+if (@ARGV < 2 or (@ARGV < 3 and $ARGV[0] !~ m![-/]!)) {
+ print "Usage: $0 <version> <cluster> <action>\n";
+ exit 1;
+}
+
+$version = shift @ARGV;
+if ($version =~ m!^(\d+\.?\d)[-/](.+)!) {
+ ($version, $cluster) = ($1, $2);
+} else {
+ $cluster = shift @ARGV;
+}
+my $action = shift @ARGV;
+
+@pg_ctl_opts_from_cli=();
+foreach my $argv (@ARGV) {
+ push @pg_ctl_opts_from_cli, $argv =~ /(.*)/; # untaint
+}
+
+($version) = $version =~ /^(\d+\.?\d+)$/; # untaint
+($cluster) = $cluster =~ /^([^'"\s]+)$/; # untaint
+error 'specified cluster does not exist' unless $version && $cluster && cluster_exists $version, $cluster;
+%info = cluster_info ($version, $cluster);
+
+unless ($action eq 'stop') {
+ # check if cluster is disabled in start.conf
+ error 'Cluster is disabled' if $info{'start'} eq 'disabled';
+}
+
+unless (-d $info{'pgdata'} && defined $info{'owneruid'}) {
+ error $info{'pgdata'} . ' is not accessible or does not exist';
+}
+
+# check that owner uid/gid is valid
+unless (getpwuid $info{'owneruid'}) {
+ error 'The cluster is owned by user id '.$info{'owneruid'}.' which does not exist'
+}
+unless (getgrgid $info{'ownergid'}) {
+ error 'The cluster is owned by group id '.$info{'ownergid'}.' which does not exist'
+}
+# owneruid and configuid need to match, unless configuid is root
+if (($< == 0 or $> == 0) and $info{'configuid'} != 0 and
+ $info{'configuid'} != $info{'owneruid'}) {
+ my $configowner = (getpwuid $info{'configuid'})[0] || "(unknown)";
+ my $dataowner = (getpwuid $info{'owneruid'})[0];
+ error "Config owner ($configowner:$info{configuid}) and data owner ($dataowner:$info{owneruid}) do not match, and config owner is not root";
+}
+
+# redirect the request through systemd
+if (not $skip_systemctl_redirect and getppid() != 1 and # not run from init
+ -d '/run/systemd/system' and not exists $ENV{_SYSTEMCTL_SKIP_REDIRECT} and # systemd is running
+ not $foreground and # foreground operation not requested
+ not exists $ENV{'PG_CLUSTER_CONF_ROOT'} and # not handling user clusters
+ $action =~ /^(start|stop|restart)$/ # redirect only these actions
+) {
+ $action = $1; # untaint
+ system 'systemctl', 'is-active', '-q', "postgresql\@$version-$cluster";
+ my $unit_active = $? == 0;
+
+ # if extra options are given, proceed with pg_ctlcluster with a warning
+ if (@postgres_auxoptions != 0 or @pg_ctl_opts_from_cli != 0 or $bindir) { # extra options given
+ print "Notice: extra pg_ctl/postgres options given, bypassing systemctl for $action operation\n" if (-t 1);
+ # if we are root, redirect to systemctl unless stopping a cluster running outside systemd
+ } elsif ($> == 0) {
+ if ($action eq 'stop' and not $unit_active) {
+ # proceed with pg_ctlcluster
+ } else {
+ #print "Redirecting $action request to systemctl\n" if (-t 1);
+ system 'systemctl', $action, "postgresql\@$version-$cluster";
+ exit $? >> 8;
+ # program end
+ }
+ # as non-root, raise an error on restarting a cluster running from systemd
+ } elsif ($action eq 'restart' and $unit_active) {
+ error "cluster is running from systemd, can only restart it as root. Try instead:\n sudo systemctl $action postgresql\@$version-$cluster";
+ # program end
+ # otherwise just raise a warning on start and restart as non-root
+ } elsif (-t 1) {
+ if ($action =~ /start/) {
+ print "Warning: the cluster will not be running as a systemd service. Consider using systemctl:\n";
+ } elsif ($unit_active) { # on stop, warn when running from systemd
+ print "Warning: stopping the cluster using pg_ctlcluster will mark the systemd unit as failed. Consider using systemctl:\n";
+ }
+ print " sudo systemctl $action postgresql\@$version-$cluster\n";
+ }
+}
+
+# recreate missing standard log dir
+if ($> == 0 && ! -e '/var/log/postgresql' &&
+ $info{'logfile'} =~ m!^/var/log/postgresql!) {
+ system 'install', '-d', '-m', '1775', '-o', 'root', '-g', 'postgres', '/var/log/postgresql';
+}
+
+# recreate missing log file
+if ($action ne 'stop' && $info{'logfile'} && ! -e $info{'logfile'}) {
+ if ($> == 0) { # drop privileges; this is important if logfile
+ # was determined via an /etc/postgresql/.../log symlink
+ change_ugid $info{'owneruid'}, $info{'ownergid'};
+ }
+ sysopen (L, $info{'logfile'}, O_RDWR|O_CREAT|O_EXCL) or
+ error 'Could not create log file ' . $info{'logfile'};
+ close L;
+ chmod 0640, $info{'logfile'};
+ $< = $> = 0; # will silently fail if we were not root before, that's intended
+ $( = $) = 0;
+ if ($info{'owneruid'} < 500) {
+ my $g = (getgrnam 'adm')[2];
+ lchown $info{'owneruid'}, $g, $info{'logfile'} if (defined $g);
+ }
+}
+
+if ($action ne 'stop') {
+ # recreate /var/run/postgresql while possibly still running as root
+ if (! -d '/var/run/postgresql') {
+ system 'install', '-d', '-m', 2775, '-o', 'postgres', '-g', 'postgres', '/var/run/postgresql';
+ }
+
+ # allow creating socket directories below /var/run/postgresql for any user
+ if ($info{socketdir} =~ m!^(/var)/run/postgresql/[\w_.-]+$! and ! -d $info{socketdir}) {
+ if (mkdir $info{socketdir}, 02775) { # don't use "install" here as it would allow stealing existing directories
+ chown $info{owneruid}, $info{ownergid}, $info{socketdir};
+ } else {
+ error "Could not create $info{socketdir}: $!";
+ }
+ }
+
+ # allow creating stats_temp_directory below /var/run/postgresql for any user
+ if ($info{config}->{stats_temp_directory} and $info{config}->{stats_temp_directory}=~ m!^(/var)/run/postgresql/[\w_.-]+$! and ! -d $info{config}->{stats_temp_directory}) {
+ if (mkdir $info{config}->{stats_temp_directory}, 0750) { # don't use "install" here as it would allow stealing existing directories
+ chown $info{owneruid}, $info{ownergid}, $info{config}->{stats_temp_directory};
+ } else {
+ error "Could not create $info{config}->{stats_temp_directory}: $!";
+ }
+ }
+}
+
+if ($> == 0) {
+ # have postgres start with increased OOM killer protection; 9.0 and
+ # later has builtin support for resetting the adjustment of child processes
+ if ($action eq 'start' and $version >= '9.0' and not $PgCommon::rpm) {
+ if (-w '/proc/self/oom_score_adj') {
+ open F, '>/proc/self/oom_score_adj';
+ print F "-900\n";
+ close F;
+ }
+ }
+
+ change_ugid $info{'owneruid'}, $info{'ownergid'};
+}
+
+# we are running as the cluster owner now
+
+if( $> != $info{'owneruid'} ) {
+ error 'You must run this program as the cluster owner ('.
+ (getpwuid $info{'owneruid'})[0].') or root';
+}
+
+# create socket directory (if it wasn't already created in /var/run/postgresql by the code above)
+if ($action ne 'stop' && ! -d $info{socketdir}) {
+ system 'install', '-d', '-m', 2775, $info{socketdir};
+}
+
+# create stats_temp_directory (if it wasn't already created in /var/run/postgresql by the code above)
+if ($action ne 'stop' && $info{config}->{stats_temp_directory} && ! -d $info{config}->{stats_temp_directory}) {
+ system 'install', '-d', '-m', 750, $info{config}->{stats_temp_directory};
+}
+
+$pg_ctl = $bindir ? "$bindir/pg_ctl" : get_program_path ('pg_ctl', $version);
+error "Could not find pg_ctl executable for version $version" unless ($pg_ctl);
+
+if ($action =~ /^(start|stop|restart|reload|status|promote)$/) {
+ no strict 'refs';
+ exit &$action;
+} else {
+ error 'Invalid action (must be one of: start stop restart reload status promote)';
+}
+
+__END__
+
+=head1 NAME
+
+pg_ctlcluster - start/stop/restart/reload a PostgreSQL cluster
+
+=head1 SYNOPSIS
+
+B<pg_ctlcluster> [I<options>] I<cluster-version> I<cluster-name> I<action> [B<--> I<pg_ctl options>]
+
+where I<action> = B<start>|B<stop>|B<restart>|B<reload>|B<status>|B<promote>
+
+=head1 DESCRIPTION
+
+This program controls the B<postgres> server for a particular cluster. It
+essentially wraps the L<pg_ctl(1)> command. It determines the cluster version
+and data path and calls the right version of B<pg_ctl> with appropriate
+configuration parameters and paths.
+
+You have to start this program as the user who owns the database cluster or as
+root.
+
+To ease integration with B<systemd> operation, the alternative syntax
+"B<pg_ctlcluster> I<version>B<->I<cluster> I<action>" is also supported.
+
+=head1 ACTIONS
+
+=over 4
+
+=item B<start>
+
+A log file for this specific cluster is created if it does not exist yet (by
+default,
+C</var/log/postgresql/postgresql->I<cluster-version>C<->I<cluster-name>C<.log>),
+and a PostgreSQL server process (L<postgres(1)>) is started on it. Exits with
+0 on success, with 2 if the server is already running, and with 1 on other
+failure conditions.
+
+=item B<stop>
+
+Stops the L<postgres(1)> server of the given cluster. By default, "fast"
+shutdown mode is used.
+
+=item B<restart>
+
+Stops the server if it is running and starts it (again).
+
+=item B<reload>
+
+Causes the configuration files to be re-read without a full shutdown of the
+server.
+
+=item B<status>
+
+Checks whether a server is running. If it is, the PID and the command line
+options that were used to invoke it are displayed.
+
+=item B<promote>
+
+Commands a running standby server to exit recovery and begin read-write
+operations.
+
+=back
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-f>|B<--force>
+
+For B<stop> and B<restart>, the "fast" mode is used which rolls back all active
+transactions, disconnects clients immediately and thus shuts down cleanly. If
+that does not work, shutdown is attempted again in "immediate" mode, which can
+leave the cluster in an inconsistent state and thus will lead to a recovery run
+at the next start. If this still does not help, the B<postgres> process is
+killed. Exits with 0 on success, with 2 if the server is not running, and with
+1 on other failure conditions. This mode should only be used when the machine
+is about to be shut down.
+
+=item B<-m>|B<--mode> [B<smart>|B<fast>|B<immediate>]
+
+Shutdown mode to use for B<stop> and B<restart> actions, default is B<fast>.
+See pg_ctl(1) for documentation.
+
+=item B<--foreground>
+
+Start B<postgres> in foreground, without daemonizing via B<pg_ctl>.
+
+=item B<--stdlog>
+
+When B<--foreground> is in use, redirect stderr to the standard logfile in
+C</var/log/postgresql/>. (Default when not run in foreground.)
+
+=item B<--skip-systemctl-redirect>
+
+When running as root, B<pg_ctlcluster> redirects actions to B<systemctl> so
+running clusters are properly supervised by B<systemd>. This option skips the
+redirect; it is used in the B<postgresql@.service> unit file. The redirect is
+also skipped if additional B<postgres> or B<pg_ctl> options are provided.
+
+=item B<--bindir> I<directory>
+
+Path to B<pg_ctl>. (Default is C</usr/lib/postgresql/>I<version>C</bin>.)
+
+=item B<-o>|B<--options> I<option>
+
+Pass given I<option> as command line option to the C<postgres> process. It is
+possible to specify B<-o> multiple times. See L<postgres(1)> for a
+description of valid options.
+
+=item I<pg_ctl options>
+
+Pass given I<pg_ctl options> as command line options to B<pg_ctl>. See L<pg_ctl(1)>
+for a description of valid options.
+
+=back
+
+=head1 FILES
+
+=over 4
+
+=item C</etc/postgresql/>I<cluster-version>C</>I<cluster-name>C</pg_ctl.conf>
+
+This configuration file contains cluster specific options to be passed to
+L<pg_ctl(1)>.
+
+=item C</etc/postgresql/>I<cluster-version>C</>I<cluster-name>C</start.conf>
+
+This configuration file controls the start/stop behavior of the cluster. See
+section "STARTUP CONTROL" in L<pg_createcluster(8)> for details.
+
+=back
+
+=head1 BUGS
+
+Changing the port number on startup using B<-o -p> will not work as it breaks
+the checks for running clusters.
+
+=head1 SEE ALSO
+
+L<pg_createcluster(8)>, L<pg_ctl(1)>, L<pg_wrapper(1)>, L<pg_lsclusters(1)>,
+L<postgres(1)>
+
+=head1 AUTHOR
+
+Martin Pitt L<E<lt>mpitt@debian.orgE<gt>>
+