diff options
Diffstat (limited to 'pg_dropcluster')
-rwxr-xr-x | pg_dropcluster | 226 |
1 files changed, 226 insertions, 0 deletions
diff --git a/pg_dropcluster b/pg_dropcluster new file mode 100755 index 0000000..9b2c7d0 --- /dev/null +++ b/pg_dropcluster @@ -0,0 +1,226 @@ +#!/usr/bin/perl -wT + +# Completely delete a PostgreSQL cluster. Fails if there is still a server +# process attached. +# +# (C) 2005-2009 Martin Pitt <mpitt@debian.org> +# (C) 2015-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 Getopt::Long; +use PgCommon; + +# untaint environment +$ENV{'PATH'} = '/bin:/usr/bin'; +delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'}; + +my $stopserver = 0; +exit 1 unless GetOptions ('stop-server' => \$stopserver, 'stop' => \$stopserver); + +# command line options +if ($#ARGV != 1) { + print "Usage: $0 [--stop] <version> <cluster>\n"; + exit 1; +} + +my ($version) = $ARGV[0] =~ /^(\d+\.?\d+)$/; +my ($cluster) = $ARGV[1] =~ /^([-.\w]+)$/; +error 'invalid version' unless defined $version; +error 'invalid cluster name' unless defined $cluster; + +my %info; +my $c; # configuration directory +my $startconf; + +if (cluster_exists $version, $cluster) { + %info = cluster_info ($version, $cluster); + validate_cluster_owner \%info if ($info{pgdata} && -d $info{pgdata}); # ignore missing data directory + + if ($info{'running'}) { + if ($stopserver) { + if ($info{'pgdata'} && -d $info{'pgdata'}) { + if (system ('pg_ctlcluster', $version, $cluster, 'stop')) { + error 'could not stop server, aborting'; + } + } else { + print STDERR "Warning: corrupted cluster: data directory does not exist any more, but server is still running; you have to manually kill the postgres process\n"; + } + } else { + error 'This cluster is still running. Stop it or supply the --stop option'; + } + } + $c = $info{'configdir'}; + $startconf = $info{'start'}; +} else { + $c = "/etc/postgresql/$version/$cluster"; + + # check if we have a broken cluster, clean up if necessary + -d $c or error 'specified cluster does not exist'; +} + +# disable systemd-enabled clusters +if (not exists $ENV{'PG_CLUSTER_CONF_ROOT'} and -d '/run/systemd/system') { + if ($> == 0) { + system "systemctl", "disable", "postgresql\@$version-$cluster"; + system "systemctl", "disable", "--now", "pg_receivewal\@$version-$cluster"; + system "systemctl", "disable", "--now", "pg_basebackup\@$version-$cluster.timer"; + system "systemctl", "disable", "--now", "pg_dump\@$version-$cluster.timer"; + } +} + +if ($info{'pgdata'} && -d $info{'pgdata'}) { + # remove custom wal directory + if ($info{waldir} and -d $info{waldir}) { + my $walowner = (stat($info{waldir}))[4]; + if (defined $walowner and $walowner == $info{owneruid}) { + my $result = system 'rm', '-r', '--', $info{waldir}; + if ($result) { + print STDERR "Warning: could not remove wal directory $info{waldir}"; + } + } else { + print STDERR "Warning: wal directory $info{waldir} is not owned by uid $info{owneruid}, not removing\n"; + } + } + + # remove tablespace subdirectories that belong to our version + for my $link (glob "$info{pgdata}/pg_tblspc/*") { + next unless (-l $link); + my $tblspc = readlink $link; + my $tblspcowner = (stat($tblspc))[4]; + if (defined $tblspcowner and $tblspcowner == $info{owneruid}) { + if ($version >= 9.0) { + for my $dir (glob "$tblspc/PG_${version}_*") { + my $dirowner = (stat($dir))[4]; + if (defined $dirowner and $dirowner == $info{owneruid}) { + my $result = system 'rm', '-r', '--', ($dir =~ /(.*)/); # untaint + if ($result) { + print STDERR "Warning: could not remove tablespace directory $dir"; + } + } else { + print STDERR "Warning: tablespace subdirectory $dir (in tablespace linked from $link) is not owned by uid $info{owneruid}, not removing\n"; + } + } + } else { # before 9.0 + if (open my $fh, '<', "$tblspc/PG_VERSION") { + my $v = <$fh>; + chomp $v; + close $fh; + if ($v eq $version) { + $tblspc =~ /(.*)/; # untaint + my $result = system "rm -r -- $1/*"; + } else { + print STDERR "Warning: tablespace directory $tblspc (linked from $link) is from PostgreSQL version $v, not removing\n"; + } + } else { + print STDERR "Warning: tablespace directory $tblspc (linked from $link) is not a PostgreSQL directory, not removing\n"; + } + } + } else { + print STDERR "Warning: tablespace directory $tblspc (linked from $link) is not owned by uid $info{owneruid}, not considering\n"; + } + } + + # remove pgdata + my $result = system 'rm', '-r', $info{'pgdata'}; + if ($result) { + if (! -w ($info{'pgdata'} . '/..')) { + error 'you might need to run this program with root privileges'; + } + exit $result; + } +} else { + print STDERR "Warning: corrupted cluster: data directory does not exist\n"; +} + +# remove config +unlink $c.'/pg_hba.conf', $c.'/pg_ident.conf', $c.'/postgresql.conf', + $c.'/start.conf', $c.'/log', $c.'/autovacuum_log', $c.'/pgdata', + $c.'/environment', $c.'/pg_ctl.conf'; +rmdir $_ foreach (map { /(.*)/ && $1 } glob "$c/*"); # remove empty conf.d directories + +unlink $info{'logfile'} if defined ($info{'logfile'}); +if ($info{'socketdir'} and $info{'socketdir'} !~ /^(\/tmp|\/var\/run\/postgresql)\/?$/) { + rmdir $info{'socketdir'}; +} +rmdir $c; +rmdir "/etc/postgresql/$version"; +rmdir "/var/lib/postgresql/$version/$cluster"; +rmdir "/var/lib/postgresql/$version"; +unlink "/var/log/postgresql/postgresql-$version-$cluster.log"; +# remove logrotated files +foreach my $f (</var/log/postgresql/postgresql-$version-$cluster.log.[1-9]*>) { + unlink ($f =~ /(.*)/); # untaint +} + +# remove stats_temp_directory +my $statstempdir = $info{config}->{stats_temp_directory}; +if ($statstempdir) { + my $statsowner = (stat($statstempdir))[4]; + if (defined $statsowner and defined $info{owneruid} and $statsowner == $info{owneruid}) { + foreach my $f (<$statstempdir/*.stat>) { + unlink ($f =~ /(.*)/); # untaint + } + rmdir $statstempdir; + } +} + +# notify systemd when an autostarted cluster went away +if (not exists $ENV{'PG_CLUSTER_CONF_ROOT'} and $startconf and $startconf eq 'auto' and -d '/run/systemd/system') { + if ($> == 0) { + system 'systemctl daemon-reload'; + } elsif (-t 1) { + print "Warning: systemd was not informed about the removed cluster yet. Operations like \"service postgresql start\" might fail. 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"; +} + +exit 0; + +__END__ + +=head1 NAME + +pg_dropcluster - completely delete a PostgreSQL cluster + +=head1 SYNOPSIS + +B<pg_dropcluster> [B<--stop>] I<cluster-version> I<cluster-name> + +=head1 DESCRIPTION + +This program removes all files that belong to a given PostgreSQL cluster; that +includes the data, wal, and tablespace directories, the log file, and all configuration files that +were created by L<pg_createcluster(1)>. If the configuration directory +(C</etc/postgresql/>I<version>C</>I<cluster>) is empty after this, it is +removed as well. +An empty socket directory other than B</var/run/postgresql> or B</tmp> is +also removed. + +Usually a cluster which still has a running server attached will not be +deleted. To override this, the B<--stop> option forces a server shutdown +before the files are removed. + +=head1 SEE ALSO + +L<pg_createcluster(1)>, L<pg_ctlcluster(1)> + +=head1 AUTHOR + +Martin Pitt L<E<lt>mpitt@debian.orgE<gt>> + |