Postgres.pm 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. package Troglodyne::API::Postgres;
  2. use strict;
  3. use warnings;
  4. use Cpanel::LoadModule::Custom;
  5. our $dir = '/var/cpanel/logs/troglodyne/pgupgrade';
  6. sub get_postgresql_versions {
  7. Cpanel::LoadModule::Custom::load_perl_module('Troglodyne::CpPostgreSQL');
  8. require Cpanel::PostgresUtils;
  9. my @ver_arr = ( Cpanel::PostgresUtils::get_version() );
  10. my $running = eval { readlink("$dir/INSTALL_IN_PROGRESS"); } || 0;
  11. return {
  12. 'installed_version' => { 'major' => $ver_arr[0], 'minor' => $ver_arr[1] },
  13. 'minimum_supported_version' => $Troglodyne::CpPostgreSQL::MINIMUM_SUPPORTED_VERSION,
  14. 'available_versions' => \%Troglodyne::CpPostgreSQL::SUPPORTED_VERSIONS_MAP,
  15. 'eol_versions' => \%Troglodyne::CpPostgreSQL::CP_UNSUPPORTED_VERSIONS_MAP,
  16. 'install_currently_running' => $running,
  17. };
  18. }
  19. sub enable_community_repositories {
  20. Cpanel::LoadModule::Custom::load_perl_module('Troglodyne::CpPostgreSQL');
  21. require Cpanel::Sys::OS;
  22. my $centos_ver = substr( Cpanel::Sys::OS::getreleaseversion(), 0, 1 );
  23. my $repo_rpm_url = $Troglodyne::CpPostgreSQL::REPO_RPM_URLS{$centos_ver};
  24. # TODO Use Cpanel::SafeRun::Object to run the install?
  25. require Capture::Tiny;
  26. my @cmd = qw{/bin/rpm -q pgdg-redhat-repo};
  27. my ( $stdout, $stderr, $ret ) = Capture::Tiny::capture( sub {
  28. system(@cmd);
  29. });
  30. my $installed = !$ret;
  31. if( !$installed ) {
  32. @cmd = ( qw{/bin/rpm -i}, $repo_rpm_url );
  33. ( $stdout, $stderr, $ret ) = Capture::Tiny::capture( sub {
  34. system(@cmd);
  35. } );
  36. }
  37. return {
  38. 'last_yum_command' => join( " ", @cmd ),
  39. 'already_installed' => $installed,
  40. 'stdout' => $stdout,
  41. 'stderr' => $stderr,
  42. 'exit_code' => $ret,
  43. };
  44. }
  45. sub start_postgres_install {
  46. my ( $args_hr ) = @_;
  47. my $version = $args_hr->{'version'};
  48. require Cpanel::Mkdir;
  49. Cpanel::Mkdir::ensure_directory_existence_and_mode( $dir, 0711 );
  50. require Cpanel::FileUtils::Touch;
  51. my $time = time;
  52. my $lgg = "$dir/pgupgrade-to-$version-at-$time.log";
  53. Cpanel::FileUtils::Touch::touch_if_not_exists($lgg);
  54. require Cpanel::Autodie;
  55. Cpanel::Autodie::unlink_if_exists("$dir/last");
  56. require Cpanel::Chdir;
  57. {
  58. my $chdir = Cpanel::Chdir->new($dir);
  59. symlink( "pgupgrade-to-$version-at-$time.log", "last" );
  60. }
  61. # OK. We are logging, now return the log loc after kicking it off.
  62. # Yeah, yeah, I'm forking twice. who cares
  63. require Cpanel::Daemonizer::Tiny;
  64. my $pid = Cpanel::Daemonizer::Tiny::run_as_daemon( \&_real_install, $version, $lgg );
  65. symlink( $pid, "$dir/INSTALL_IN_PROGRESS" ) if $pid;
  66. return {
  67. 'log' => $lgg,
  68. 'pid' => $pid,
  69. };
  70. }
  71. sub _real_install {
  72. my ( $ver2install, $log ) = @_;
  73. require Cpanel::AccessIds::ReducedPrivileges;
  74. my $no_period_version = $ver2install =~ s/\.//r;
  75. my @RPMS = (
  76. "postgresql$no_period_version",
  77. "postgresql$no_period_version-server",
  78. );
  79. # TODO: Use Cpanel::Yum::Install based module, let all that stuff handle this "for you".
  80. open( my $lh, ">", $log ) or return _cleanup("255");
  81. select $lh;
  82. $| = 1;
  83. select $lh;
  84. print $lh "Beginning install...\n";
  85. # Check for CCS. Temporarily disable it if so.
  86. my ( $ccs_installed, $err );
  87. {
  88. local $@;
  89. eval {
  90. require Cpanel::RPM;
  91. $ccs_installed = Cpanel::RPM->new()->get_version('cpanel-ccs-calendarserver');
  92. $ccs_installed = $ccs_installed->{'cpanel-ccs-calendarserver'};
  93. };
  94. $err = $@;
  95. }
  96. if($err) {
  97. print $lh "[ERROR] $err\n";
  98. return _cleanup('255');
  99. }
  100. print $lh "[DEBUG] CCS Installed? $ccs_installed\n";
  101. if($ccs_installed) {
  102. print $lh "\ncpanel-ccs-calendarserver is installed.\nDisabling the service while the upgrade is in process.\n\n";
  103. require Whostmgr::Services;
  104. Whostmgr::Services::disable('cpanel-ccs');
  105. }
  106. require Cpanel::SafeRun::Object;
  107. my $exit = _saferun( $lh, 'yum', qw{install -y}, @RPMS );
  108. return _cleanup("$exit") if $exit;
  109. # Init the DB
  110. {
  111. local $@;
  112. eval {
  113. my $pants_on_the_ground = Cpanel::AccessIds::ReducedPrivileges->new('postgres');
  114. $exit = _saferun( $lh, "/usr/pgsql-$ver2install/bin/initdb", '-D', "/var/lib/pgsql/$ver2install/data/" );
  115. };
  116. $err = $@;
  117. }
  118. if($err) {
  119. print $lh "[ERROR] $err\n";
  120. return _cleanup('255');
  121. }
  122. return _cleanup("$exit") if $exit;
  123. require File::Slurper;
  124. # Move some bullcrap out of the way if we're on old PGs
  125. require Cpanel::PostgresUtils;
  126. my @cur_ver = ( Cpanel::PostgresUtils::get_version() );
  127. my $str_ver = join( '.', @cur_ver );
  128. if( $str_ver + 0 < 9.4 ) {
  129. print $lh "\n\nInstalled version is less than 9.4 ($str_ver), Implementing workaround in pg_ctl to ensure pg_upgrade works...\n";
  130. require File::Copy;
  131. print $lh "Backing up /usr/bin/pg_ctl to /usr/bin/pg_ctl.orig\n";
  132. File::Copy::copy('/usr/bin/pg_ctl','/usr/bin/pg_ctl.orig') or do {
  133. print $lh "Backup of /usr/bin/pg_ctl to /usr/bin/pg_ctl.orig failed: $!\n";
  134. return _cleanup("255");
  135. };
  136. print $lh "[DEBUG] Got to reading\n";
  137. local $@;
  138. my $pg_ctl_contents = eval { File::Slurper::read_binary("/usr/bin/pg_ctl") };
  139. if($@) {
  140. print $lh "[ERROR] Read from /usr/bin/pg_ctl failed: $@\n";
  141. return _cleanup('255');
  142. }
  143. print $lh "[DEBUG] READ IN\n";
  144. $pg_ctl_contents =~ s/unix_socket_directory/unix_socket_directories/g;
  145. eval { File::Slurper::write_binary( "/usr/bin/pg_ctl", $pg_ctl_contents ); };
  146. if($@) {
  147. print $lh "[ERROR] Write to /usr/bin/pg_ctl failed: $@\n";
  148. return _cleanup('255');
  149. }
  150. print $lh "Workaround should be in place now. Proceeding with pg_upgrade.\n\n";
  151. }
  152. print $lh "[DEBUG] Uprade cluster\n";
  153. # Upgrade the cluster
  154. # /usr/pgsql-9.6/bin/pg_upgrade --old-datadir /var/lib/pgsql/data/ --new-datadir /var/lib/pgsql/9.6/data/ --old-bindir /usr/bin/ --new-bindir /usr/pgsql-9.6/bin/
  155. my ( $old_datadir, $old_bindir ) = ( $str_ver + 0 < 9.5 ) ? ( '/var/lib/pgsql/data', '/usr/bin' ) : ( "/var/lib/pgsql/$str_ver/data/", "/usr/pgsql-$str_ver/bin/" );
  156. {
  157. local $@;
  158. eval {
  159. my $pants_on_the_ground = Cpanel::AccessIds::ReducedPrivileges->new('postgres');
  160. $exit = _saferun( $lh, "/usr/pgsql-$ver2install/bin/pg_upgrade",
  161. '--old-datadir', $old_datadir,
  162. '--new-datadir', "/var/lib/pgsql/$ver2install/data/",
  163. '--old-bindir', $old_bindir,
  164. '--new_bindir', "/usr/pgsql-$ver2install/bin/",
  165. );
  166. };
  167. $err = $@;
  168. }
  169. if($err) {
  170. print $lh "[ERROR] $err\n";
  171. return _cleanup('255');
  172. }
  173. return _cleanup("$exit") if $exit;
  174. # Start the server.
  175. $exit = _saferun( $lh, qw{systemctl start}, "postgresql-$ver2install" );
  176. return _cleanup("$exit") if $exit;
  177. if( $ccs_installed ) {
  178. print $lh "\n\nNow upgrading PG cluster for cpanel-ccs-calendarserver...\n";
  179. my $ccs_pg_datadir = '/opt/cpanel-ccs/data/Data/Database/cluster';
  180. print $lh "Old PG datadir is being moved to '$ccs_pg_datadir.old'...\n";
  181. rename( $ccs_pg_datadir, "$ccs_pg_datadir.old" );
  182. mkdir($ccs_pg_datadir);
  183. # Init the DB
  184. {
  185. local $@;
  186. eval {
  187. my $pants_on_the_ground = Cpanel::AccessIds::ReducedPrivileges->new('cpanel-ccs');
  188. local $ENV{'PGSETUP_INITDB_OPTIONS'} = "-U caldav --locale=C -E=UTF8";
  189. $exit = _saferun( $lh, "/usr/pgsql-$ver2install/bin/initdb", '-D', $ccs_pg_datadir );
  190. };
  191. $err = $@;
  192. }
  193. if($err) {
  194. print $lh "[ERROR] $err\n";
  195. return _cleanup('255');
  196. }
  197. return _cleanup("$exit") if $exit;
  198. # Upgrade the DB
  199. {
  200. local $@;
  201. eval {
  202. my $pants_on_the_ground = Cpanel::AccessIds::ReducedPrivileges->new('cpanel-ccs');
  203. $exit = _saferun( $lh, "/usr/pgsql-$ver2install/bin/pg_upgrade",
  204. '--old-datadir', "$ccs_pg_datadir.old",
  205. '--new-datadir', $ccs_pg_datadir,
  206. '--old-bindir', $old_bindir,
  207. '--new_bindir', "/usr/pgsql-$ver2install/bin/",
  208. qw{-c -U caldav},
  209. );
  210. };
  211. $err = $@;
  212. }
  213. if($err) {
  214. print $lh "[ERROR] $err\n";
  215. return _cleanup('255');
  216. }
  217. return _cleanup("$exit") if $exit;
  218. }
  219. if( $str_ver + 0 < 9.4 ) {
  220. print $lh "\n\nWorkaround resulted in successful start of the server. Reverting workaround changes to pg_ctl...\n\n";
  221. rename( '/usr/bin/pg_ctl.orig', '/usr/bin/pg_ctl' ) or do {
  222. print $lh "Restore of /usr/bin/pg_ctl.orig to /usr/bin/pg_ctl failed: $!\n";
  223. return _cleanup("255");
  224. };
  225. }
  226. print $lh "\n\nNow cleaning up old postgresql version...\n";
  227. my $svc2remove = ( $str_ver + 0 < 9.5 ) ? 'postgresql' : "postgresql-$str_ver";
  228. $exit = _saferun( $lh, qw{systemctl disable}, $svc2remove );
  229. return _cleanup("$exit") if $exit;
  230. $exit = _saferun( $lh, qw{yum -y remove}, $svc2remove );
  231. return _cleanup("$exit") if $exit;
  232. print $lh "\n\nNow enabling postgresql-$ver2install on startup...\n";
  233. $exit = _saferun( $lh, qw{systemctl enable}, "postgresql-$ver2install" );
  234. return _cleanup("$exit") if $exit;
  235. # Update alternatives. Should be fine to use --auto, as no other alternatives will exist for the installed version.
  236. # Create alternatives for pg_ctl, etc. as those don't get made by the RPM.
  237. print $lh "\n\nUpdating alternatives to ensure the newly installed version is considered canonical...\n";
  238. my @normie_alts = qw{pg_ctl initdb pg_config pg_upgrade};
  239. my @manual_alts = qw{clusterdb createdb createuser dropdb droplang dropuser pg_basebackup pg_dump pg_dumpall pg_restore psql psql-reindexdb vaccumdb};
  240. foreach my $alt ( @normie_alts ) {
  241. $exit = _saferun( $lh, qw{update-alternatives --install}, "/usr/bin/$alt", "pgsql-$alt", "/usr/pgsql-$ver2install/bin/$alt", "50" );
  242. return _cleanup("$exit") if $exit;
  243. $exit = _saferun( $lh, qw{update-alternatives --auto}, "pgsql-$alt" );
  244. return _cleanup("$exit") if $exit;
  245. }
  246. foreach my $alt ( @manual_alts ) {
  247. $exit = _saferun( $lh, qw{update-alternatives --auto}, "pgsql-$alt" );
  248. return _cleanup("$exit") if $exit;
  249. $exit = _saferun( $lh, qw{update-alternatives --auto}, "pgsql-${alt}man" );
  250. return _cleanup("$exit") if $exit;
  251. }
  252. print $lh "\n\nWriting new .bash_profile for the 'postgres' user...\n";
  253. my $bash_profile = "[ -f /etc/profile ] && source /etc/profile
  254. PGDATA=/var/lib/pgsql/$ver2install/data
  255. export PGDATA
  256. [ -f /var/lib/pgsql/.pgsql_profile ] && source /var/lib/pgsql/.pgsql_profile
  257. export PATH=\$PATH:/usr/pgsql-$ver2install/bin\n";
  258. File::Slurper::write_text( '/var/lib/pgsql/.bash_profile', $bash_profile );
  259. if($ccs_installed) {
  260. File::Slurper::write_text( '/opt/cpanel-ccs/.bash_profile', $bash_profile );
  261. print $lh "\nRe-Enabling cpanel-ccs-calendarserver...\n\n";
  262. require Whostmgr::Services;
  263. Whostmgr::Services::enable('cpanel-ccs');
  264. }
  265. return _cleanup("0");
  266. }
  267. sub _saferun {
  268. my ( $lh, $prog, @args ) = @_;
  269. my $run_result = Cpanel::SafeRun::Object->new(
  270. 'program' => $prog,
  271. 'args' => [ @args ],
  272. 'stdout' => $lh,
  273. 'stderr' => $lh,
  274. );
  275. my $exit = $run_result->error_code() || 0;
  276. return $exit;
  277. }
  278. sub _cleanup {
  279. eval { symlink( $_[0], "$dir/INSTALL_EXIT_CODE" ); };
  280. unlink("$dir/INSTALL_IN_PROGRESS");
  281. return;
  282. }
  283. # Elegance??? Websocket??? Nah. EZ mode actibated
  284. sub get_latest_upgradelog_messages {
  285. my ( $args_hr ) = @_;
  286. my $child_exit;
  287. my $in_progress = -l "$dir/INSTALL_IN_PROGRESS";
  288. if(!$in_progress) {
  289. $child_exit = readlink("$dir/INSTALL_EXIT_CODE");
  290. }
  291. # XXX validate log arg? Don't want arbitrary file reads?
  292. # read from it using seek and tell to control
  293. open( my $rh, "<", $args_hr->{'log'} );
  294. seek( $rh, $args_hr->{'start'}, 0 ) if $args_hr->{'start'};
  295. my $content = '';
  296. while( my $line = <$rh> ) {
  297. $content .= $line;
  298. }
  299. my $pos = tell($rh);
  300. close($rh);
  301. return {
  302. 'in_progress' => $in_progress,
  303. 'child_exit' => $child_exit,
  304. 'next' => $pos,
  305. 'new_content' => $content,
  306. }
  307. }
  308. 1;