#!/usr/bin/perl -wT # Completely delete a PostgreSQL cluster. Fails if there is still a server # process attached. # # (C) 2005-2009 Martin Pitt # (C) 2015-2021 Christoph Berg # # 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] \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 () { 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 [B<--stop>] I I =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. If the configuration directory (CICI) is empty after this, it is removed as well. An empty socket directory other than B or B 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, L =head1 AUTHOR Martin Pitt Lmpitt@debian.orgE>