Package:postgresql-common Verison:273.pgdg24.04+1 *Dear Community,*
I have been exploring an issue where pg_lsclusters reports the default port from postgresql.conf, rather than the actual port in use when a PostgreSQL cluster is started with a custom port using pg_ctlcluster (e.g., -o "-p < port>"). While this behavior is understandable given permission constraints on postmaster.pid, it makes tracking running clusters more difficult. To address this, I have created a *workaround script* that accurately detects running PostgreSQL instances, retrieves their actual ports, and presents a corrected cluster listing. This ensures that administrators can see the correct ports in use, even when pg_lsclusters does not reflect them. I would appreciate any guidance on how we might resolve this as a permanent improvement within pg_ctl or pg_lsclusters. Please find the workaround patch below Looking forward to your thoughts and suggestions. Best regards, Akash #!/usr/bin/perl -w use strict; use warnings; # ANSI color codes for output my $WHITE = "\033[37m"; my $GREEN = "\033[32m"; my $RED = "\033[31m"; my $RESET = "\033[0m"; # Check if user has sudo privileges my $HAS_SUDO = `sudo -n true 2>/dev/null && echo "yes" || echo "no"`; chomp($HAS_SUDO); $HAS_SUDO = $HAS_SUDO eq "yes"; # Fetch all running PostgreSQL processes once my %running_ports; if ($HAS_SUDO) { my $output = `sudo ss -tulnp 2>/dev/null | grep postgres`; foreach my $line (split /\n/, $output) { if ($line =~ /:(\d+)\s/) { $running_ports{$1} = 1; } } } # Function to get port from postmaster.pid file sub get_port_from_pid_file { my ($data_dir) = @_; my $pid_file = "$data_dir/postmaster.pid"; return undef unless -r $pid_file; my $port = `head -n 4 $pid_file | tail -n 1 2>/dev/null`; chomp($port); return $port if $port =~ /^\d+$/; return undef; } # Function to get cluster owner sub get_cluster_owner { my ($port) = @_; return "<unknown>" unless $HAS_SUDO; # Try to get owner from process first my $output = `sudo lsof -i :$port -n -P 2>/dev/null | grep LISTEN | awk '{print \$3}' | head -1 2>/dev/null`; chomp($output); # If we couldn't get the owner from the process, it might be because of sudo # In that case, we check the postgres process owner if (!$output || $output eq "") { $output = `sudo ps -p \$(sudo lsof -i :$port -t 2>/dev/null | head -1) -o user= 2>/dev/null`; chomp($output); } return $output || "<unknown>"; } # Function to get PostgreSQL version sub get_postgres_version { my ($port) = @_; return "<unknown>" unless $HAS_SUDO; my $version = `sudo -u postgres psql -p $port -t -c "SHOW server_version;" 2>/dev/null`; chomp($version); $version =~ s/^\s+|\s+$//g; $version =~ s/^(\d+).*/$1/; return $version || "<unknown>"; } # Function to check cluster status sub is_cluster_running { my ($port) = @_; return exists $running_ports{$port} ? "online" : "down"; } # Function to get data directory sub get_data_directory { my ($port) = @_; return "<unknown>" unless $HAS_SUDO; my $pid = `sudo lsof -i :$port -t 2>/dev/null`; chomp($pid); return "<unknown>" unless $pid; my $dir = `sudo lsof -p $pid 2>/dev/null | grep 'cwd' | awk '{print \$9}' | tail -n 1 2>/dev/null`; chomp($dir); return $dir || "<unknown>"; } # Function to get configured port from postgresql.conf sub get_configured_port { my ($version, $cluster_name) = @_; return "<unknown>" unless $HAS_SUDO; my $config_file = "/etc/postgresql/$version/$cluster_name/postgresql.conf"; my $port = `sudo grep -E '^port\\s*=\\s*[0-9]+' $config_file 2>/dev/null | awk -F '=' '{print \$2}' | tr -d ' ' | tr -d '#'`; chomp($port); $port =~ s/\(changerequiresrestart\)//g; return $port || "<unknown>"; } # Function to list all PostgreSQL clusters (including stopped ones) sub get_all_clusters { my %clusters; my @version_paths = glob("/var/lib/postgresql/*"); foreach my $version_path (@version_paths) { if ($version_path =~ /(\d+)$/) { my $version = $1; my @cluster_dirs = glob("$version_path/*"); foreach my $dir (@cluster_dirs) { if ($dir =~ /([^\/]+)$/) { my $cluster_name = $1; my $config_port = get_configured_port($version, $cluster_name); my $config_file = "/etc/postgresql/$version/$cluster_name/postgresql.conf"; my $pid_port = get_port_from_pid_file($dir); $clusters{"$version:$cluster_name"} = { version => $version, cluster_name => $cluster_name, config_port => $config_port, config_file => $config_file, data_dir => $dir, port => $pid_port || $config_port, status => $pid_port ? "online" : "down", owner => "<unknown>" }; } } } } return %clusters; } # Get all known clusters my %all_clusters = get_all_clusters(); # Collect running PostgreSQL instances (only for clusters without pid file info) foreach my $port (keys %running_ports) { my $owner = get_cluster_owner($port); my $data_dir = get_data_directory($port); my $version = get_postgres_version($port); my $status = is_cluster_running($port); next unless $data_dir =~ m{^/var/lib/postgresql/\d+/}; my ($cluster_name) = $data_dir =~ m{^/var/lib/postgresql/\d+/([^/]+)}; $cluster_name ||= "main"; my $key = "$version:$cluster_name"; if (exists $all_clusters{$key}) { if ($all_clusters{$key}{status} eq "down") { $all_clusters{$key}{port} = $port; $all_clusters{$key}{status} = $status; $all_clusters{$key}{owner} = $owner; } else { # Update owner for running clusters $all_clusters{$key}{owner} = $owner; } } else { $all_clusters{$key} = { version => $version, cluster_name => $cluster_name, config_port => "<unknown>", config_file => "<unknown>", data_dir => $data_dir, port => $port, status => $status, owner => $owner }; } } # Update ownership info for all online clusters foreach my $key (keys %all_clusters) { my $cluster = $all_clusters{$key}; if ($cluster->{status} eq "online" && $cluster->{owner} eq "<unknown>") { $cluster->{owner} = get_cluster_owner($cluster->{port}); } } # Update owner for stopped clusters foreach my $key (keys %all_clusters) { my $cluster = $all_clusters{$key}; if ($cluster->{status} eq "down" && $cluster->{owner} eq "<unknown>") { my $data_dir = $cluster->{data_dir}; if (-d $data_dir) { my $owner = `ls -ld $data_dir 2>/dev/null | awk '{print \$3}'`; chomp($owner); $cluster->{owner} = $owner if $owner; } } } # Prepare output data my $output = "Ver Cluster Running-Port Config-Port Status Owner Data-Directory Config-File\n"; foreach my $key (sort keys %all_clusters) { my $cluster = $all_clusters{$key}; my $color = ($cluster->{status} eq "online") ? $GREEN : $RED; $output .= $color . join(" ", @{$cluster}{qw(version cluster_name port config_port status owner data_dir config_file)}) . "${RESET}\n"; } # Output with proper alignment if (!keys %all_clusters) { print "No PostgreSQL clusters detected.\n"; } else { if (open(my $pipe, "| column -t")) { print $pipe $output; close($pipe); } else { print $output; } }