From e9ed1b266ba8d2bf0fc1bb783c4f2b3ab12e6cb0 Mon Sep 17 00:00:00 2001 From: cutemeli Date: Wed, 7 Jan 2026 20:30:10 +0100 Subject: [PATCH] updated from server --- .../PHP74_17/php74-ubt24.04-x86_64.inf3 | 47 + root/parallels/PHP74_17/release.inf3 | 31 + .../PHP80_17/php80-ubt24.04-x86_64.inf3 | 45 + root/parallels/PHP80_17/release.inf3 | 32 + .../PHP81_17/php81-ubt24.04-x86_64.inf3 | 45 + root/parallels/PHP81_17/release.inf3 | 31 + .../PHP82_17/php82-ubt24.04-x86_64.inf3 | 45 + root/parallels/PHP82_17/release.inf3 | 31 + .../PHP83_17/php83-ubt24.04-x86_64.inf3 | 45 + root/parallels/PHP83_17/release.inf3 | 31 + .../PHP84_17/php84-ubt24.04-x86_64.inf3 | 45 + root/parallels/PHP84_17/release.inf3 | 29 + .../PHP85_17/php85-ubt24.04-x86_64.inf3 | 44 + root/parallels/PHP85_17/release.inf3 | 29 + root/parallels/apache.inf3 | 27 + root/parallels/billing.inf3 | 1396 +++++++++ root/parallels/mysql.inf3 | 42 + root/parallels/nginx.inf3 | 419 +++ root/parallels/php52.inf3 | 57 + root/parallels/php53.inf3 | 58 + root/parallels/php54.inf3 | 59 + root/parallels/php55.inf3 | 59 + root/parallels/php56.inf3 | 71 + root/parallels/php70.inf3 | 66 + root/parallels/php71.inf3 | 41 + root/parallels/php72.inf3 | 40 + root/parallels/php73.inf3 | 40 + root/parallels/php74.inf3 | 24 + root/parallels/php80.inf3 | 13 + root/parallels/php81.inf3 | 18 + root/parallels/php82.inf3 | 14 + root/parallels/php83.inf3 | 11 + root/parallels/plesk.inf3 | 2791 +++++++++++++++++ root/parallels/pmm.inf3 | 36 + .../pool/PSA_18.0.64_15680/release.inf3 | 34 + .../pool/PSA_18.0.65_15919/release.inf3 | 34 + .../pool/PSA_18.0.66_16134/release.inf3 | 34 + .../pool/PSA_18.0.67_16409/release.inf3 | 35 + .../pool/PSA_18.0.68_16616/release.inf3 | 35 + .../pool/PSA_18.0.69_17010/release.inf3 | 33 + .../pool/PSA_18.0.70_17357/release.inf3 | 33 + .../pool/PSA_18.0.71_17351/release.inf3 | 35 + .../examiners/congratulations.sh | 50 + .../examiners/disk_space_check.sh | 532 ++++ .../examiners/package_manager_check.sh | 224 ++ .../examiners/php_launcher.sh | 38 + .../examiners/py_launcher.sh | 30 + .../examiners/repository_check.sh | 782 +++++ .../examiners/save-installation-info.php | 61 + .../PSA_18.0.72_17583/examiners/sh_cmd.sh | 7 + .../examiners/tune_memory_swap.sh | 287 ++ .../plesk-18.0.72-ubt24.04-x86_64.inf3 | 927 ++++++ .../pool/PSA_18.0.72_17583/release.inf3 | 35 + .../pool/PSA_18.0.73_17652/release.inf3 | 36 + .../pool/PSA_18.0.73_17686/release.inf3 | 36 + .../pool/PSA_18.0.73_17695/release.inf3 | 36 + .../examiners/check_broken_timezone.sh | 255 ++ .../examiners/congratulations.sh | 50 + .../examiners/disk_space_check.sh | 542 ++++ .../examiners/license_key_check.php | 111 + .../examiners/package_manager_check.sh | 224 ++ .../examiners/panel_preupgrade_checker.php | 2401 ++++++++++++++ .../examiners/php_launcher.sh | 38 + .../examiners/plesk_preupgrade_checker.log | 68 + .../examiners/py_launcher.sh | 30 + .../examiners/repository_check.sh | 782 +++++ .../PSA_18.0.73_17725/examiners/sh_cmd.sh | 7 + .../plesk-18.0.73-ubt24.04-x86_64.inf3 | 927 ++++++ .../pool/PSA_18.0.73_17725/release.inf3 | 36 + .../examiners/check_broken_timezone.sh | 255 ++ .../examiners/disk_space_check.sh | 542 ++++ .../examiners/license_key_check.php | 111 + .../examiners/package_manager_check.sh | 224 ++ .../examiners/panel_preupgrade_checker.php | 2401 ++++++++++++++ .../examiners/php_launcher.sh | 38 + .../examiners/plesk_preupgrade_checker.log | 3 + .../examiners/py_launcher.sh | 30 + .../examiners/repository_check.sh | 782 +++++ .../PSA_18.0.73_17940/examiners/sh_cmd.sh | 7 + .../plesk-18.0.73-ubt24.04-x86_64.inf3 | 927 ++++++ .../pool/PSA_18.0.73_17940/release.inf3 | 36 + .../examiners/check_broken_timezone.sh | 255 ++ .../examiners/disk_space_check.sh | 542 ++++ .../examiners/license_key_check.php | 111 + .../examiners/package_manager_check.sh | 224 ++ .../examiners/panel_preupgrade_checker.php | 2401 ++++++++++++++ .../examiners/php_launcher.sh | 38 + .../examiners/plesk_preupgrade_checker.log | 3 + .../examiners/py_launcher.sh | 30 + .../examiners/repository_check.sh | 782 +++++ .../PSA_18.0.73_17971/examiners/sh_cmd.sh | 7 + .../plesk-18.0.73-ubt24.04-x86_64.inf3 | 927 ++++++ .../pool/PSA_18.0.73_17971/release.inf3 | 36 + .../pool/PSA_18.0.74_17897/release.inf3 | 36 + .../pool/PSA_18.0.74_17941/release.inf3 | 36 + .../pool/PSA_18.0.74_17968/release.inf3 | 36 + .../examiners/check_broken_timezone.sh | 255 ++ .../examiners/congratulations.sh | 50 + .../examiners/disk_space_check.sh | 542 ++++ .../examiners/license_key_check.php | 111 + .../examiners/package_manager_check.sh | 224 ++ .../examiners/panel_preupgrade_checker.php | 2441 ++++++++++++++ .../examiners/php_launcher.sh | 38 + .../examiners/plesk_preupgrade_checker.log | 68 + .../examiners/py_launcher.sh | 30 + .../examiners/repository_check.sh | 782 +++++ .../PSA_18.0.74_18022/examiners/sh_cmd.sh | 7 + .../plesk-18.0.74-ubt24.04-x86_64.inf3 | 928 ++++++ .../pool/PSA_18.0.74_18022/release.inf3 | 36 + .../pool/PSA_18.0.75_18153/release.inf3 | 36 + .../pool/WPB_18.0.55_74/release.inf3 | 25 + .../pool/WPB_18.0.59_78/release.inf3 | 27 + .../pool/WPB_18.0.69_87/release.inf3 | 25 + .../sitebuilder-18.0.69-deball-all.inf3 | 28 + .../pool/WPB_18.0.69_88/release.inf3 | 27 + .../sitebuilder-18.0.69-deball-all.inf3 | 28 + root/parallels/pp-sitebuilder.inf3 | 712 +++++ root/parallels/products.inf3 | 51 + root/parallels/report-update | Bin 0 -> 392468 bytes root/parallels/setemplates.inf3 | 128 + root/parallels/sitebuilder.inf3 | 163 + 121 files changed, 32219 insertions(+) create mode 100644 root/parallels/PHP74_17/php74-ubt24.04-x86_64.inf3 create mode 100644 root/parallels/PHP74_17/release.inf3 create mode 100644 root/parallels/PHP80_17/php80-ubt24.04-x86_64.inf3 create mode 100644 root/parallels/PHP80_17/release.inf3 create mode 100644 root/parallels/PHP81_17/php81-ubt24.04-x86_64.inf3 create mode 100644 root/parallels/PHP81_17/release.inf3 create mode 100644 root/parallels/PHP82_17/php82-ubt24.04-x86_64.inf3 create mode 100644 root/parallels/PHP82_17/release.inf3 create mode 100644 root/parallels/PHP83_17/php83-ubt24.04-x86_64.inf3 create mode 100644 root/parallels/PHP83_17/release.inf3 create mode 100644 root/parallels/PHP84_17/php84-ubt24.04-x86_64.inf3 create mode 100644 root/parallels/PHP84_17/release.inf3 create mode 100644 root/parallels/PHP85_17/php85-ubt24.04-x86_64.inf3 create mode 100644 root/parallels/PHP85_17/release.inf3 create mode 100644 root/parallels/apache.inf3 create mode 100644 root/parallels/billing.inf3 create mode 100644 root/parallels/mysql.inf3 create mode 100644 root/parallels/nginx.inf3 create mode 100644 root/parallels/php52.inf3 create mode 100644 root/parallels/php53.inf3 create mode 100644 root/parallels/php54.inf3 create mode 100644 root/parallels/php55.inf3 create mode 100644 root/parallels/php56.inf3 create mode 100644 root/parallels/php70.inf3 create mode 100644 root/parallels/php71.inf3 create mode 100644 root/parallels/php72.inf3 create mode 100644 root/parallels/php73.inf3 create mode 100644 root/parallels/php74.inf3 create mode 100644 root/parallels/php80.inf3 create mode 100644 root/parallels/php81.inf3 create mode 100644 root/parallels/php82.inf3 create mode 100644 root/parallels/php83.inf3 create mode 100644 root/parallels/plesk.inf3 create mode 100644 root/parallels/pmm.inf3 create mode 100644 root/parallels/pool/PSA_18.0.64_15680/release.inf3 create mode 100644 root/parallels/pool/PSA_18.0.65_15919/release.inf3 create mode 100644 root/parallels/pool/PSA_18.0.66_16134/release.inf3 create mode 100644 root/parallels/pool/PSA_18.0.67_16409/release.inf3 create mode 100644 root/parallels/pool/PSA_18.0.68_16616/release.inf3 create mode 100644 root/parallels/pool/PSA_18.0.69_17010/release.inf3 create mode 100644 root/parallels/pool/PSA_18.0.70_17357/release.inf3 create mode 100644 root/parallels/pool/PSA_18.0.71_17351/release.inf3 create mode 100755 root/parallels/pool/PSA_18.0.72_17583/examiners/congratulations.sh create mode 100755 root/parallels/pool/PSA_18.0.72_17583/examiners/disk_space_check.sh create mode 100755 root/parallels/pool/PSA_18.0.72_17583/examiners/package_manager_check.sh create mode 100755 root/parallels/pool/PSA_18.0.72_17583/examiners/php_launcher.sh create mode 100755 root/parallels/pool/PSA_18.0.72_17583/examiners/py_launcher.sh create mode 100755 root/parallels/pool/PSA_18.0.72_17583/examiners/repository_check.sh create mode 100644 root/parallels/pool/PSA_18.0.72_17583/examiners/save-installation-info.php create mode 100755 root/parallels/pool/PSA_18.0.72_17583/examiners/sh_cmd.sh create mode 100755 root/parallels/pool/PSA_18.0.72_17583/examiners/tune_memory_swap.sh create mode 100644 root/parallels/pool/PSA_18.0.72_17583/plesk-18.0.72-ubt24.04-x86_64.inf3 create mode 100644 root/parallels/pool/PSA_18.0.72_17583/release.inf3 create mode 100644 root/parallels/pool/PSA_18.0.73_17652/release.inf3 create mode 100644 root/parallels/pool/PSA_18.0.73_17686/release.inf3 create mode 100644 root/parallels/pool/PSA_18.0.73_17695/release.inf3 create mode 100755 root/parallels/pool/PSA_18.0.73_17725/examiners/check_broken_timezone.sh create mode 100755 root/parallels/pool/PSA_18.0.73_17725/examiners/congratulations.sh create mode 100755 root/parallels/pool/PSA_18.0.73_17725/examiners/disk_space_check.sh create mode 100644 root/parallels/pool/PSA_18.0.73_17725/examiners/license_key_check.php create mode 100755 root/parallels/pool/PSA_18.0.73_17725/examiners/package_manager_check.sh create mode 100644 root/parallels/pool/PSA_18.0.73_17725/examiners/panel_preupgrade_checker.php create mode 100755 root/parallels/pool/PSA_18.0.73_17725/examiners/php_launcher.sh create mode 100644 root/parallels/pool/PSA_18.0.73_17725/examiners/plesk_preupgrade_checker.log create mode 100755 root/parallels/pool/PSA_18.0.73_17725/examiners/py_launcher.sh create mode 100755 root/parallels/pool/PSA_18.0.73_17725/examiners/repository_check.sh create mode 100755 root/parallels/pool/PSA_18.0.73_17725/examiners/sh_cmd.sh create mode 100644 root/parallels/pool/PSA_18.0.73_17725/plesk-18.0.73-ubt24.04-x86_64.inf3 create mode 100644 root/parallels/pool/PSA_18.0.73_17725/release.inf3 create mode 100755 root/parallels/pool/PSA_18.0.73_17940/examiners/check_broken_timezone.sh create mode 100755 root/parallels/pool/PSA_18.0.73_17940/examiners/disk_space_check.sh create mode 100644 root/parallels/pool/PSA_18.0.73_17940/examiners/license_key_check.php create mode 100755 root/parallels/pool/PSA_18.0.73_17940/examiners/package_manager_check.sh create mode 100644 root/parallels/pool/PSA_18.0.73_17940/examiners/panel_preupgrade_checker.php create mode 100755 root/parallels/pool/PSA_18.0.73_17940/examiners/php_launcher.sh create mode 100644 root/parallels/pool/PSA_18.0.73_17940/examiners/plesk_preupgrade_checker.log create mode 100755 root/parallels/pool/PSA_18.0.73_17940/examiners/py_launcher.sh create mode 100755 root/parallels/pool/PSA_18.0.73_17940/examiners/repository_check.sh create mode 100755 root/parallels/pool/PSA_18.0.73_17940/examiners/sh_cmd.sh create mode 100644 root/parallels/pool/PSA_18.0.73_17940/plesk-18.0.73-ubt24.04-x86_64.inf3 create mode 100644 root/parallels/pool/PSA_18.0.73_17940/release.inf3 create mode 100755 root/parallels/pool/PSA_18.0.73_17971/examiners/check_broken_timezone.sh create mode 100755 root/parallels/pool/PSA_18.0.73_17971/examiners/disk_space_check.sh create mode 100644 root/parallels/pool/PSA_18.0.73_17971/examiners/license_key_check.php create mode 100755 root/parallels/pool/PSA_18.0.73_17971/examiners/package_manager_check.sh create mode 100644 root/parallels/pool/PSA_18.0.73_17971/examiners/panel_preupgrade_checker.php create mode 100755 root/parallels/pool/PSA_18.0.73_17971/examiners/php_launcher.sh create mode 100644 root/parallels/pool/PSA_18.0.73_17971/examiners/plesk_preupgrade_checker.log create mode 100755 root/parallels/pool/PSA_18.0.73_17971/examiners/py_launcher.sh create mode 100755 root/parallels/pool/PSA_18.0.73_17971/examiners/repository_check.sh create mode 100755 root/parallels/pool/PSA_18.0.73_17971/examiners/sh_cmd.sh create mode 100644 root/parallels/pool/PSA_18.0.73_17971/plesk-18.0.73-ubt24.04-x86_64.inf3 create mode 100644 root/parallels/pool/PSA_18.0.73_17971/release.inf3 create mode 100644 root/parallels/pool/PSA_18.0.74_17897/release.inf3 create mode 100644 root/parallels/pool/PSA_18.0.74_17941/release.inf3 create mode 100644 root/parallels/pool/PSA_18.0.74_17968/release.inf3 create mode 100755 root/parallels/pool/PSA_18.0.74_18022/examiners/check_broken_timezone.sh create mode 100755 root/parallels/pool/PSA_18.0.74_18022/examiners/congratulations.sh create mode 100755 root/parallels/pool/PSA_18.0.74_18022/examiners/disk_space_check.sh create mode 100644 root/parallels/pool/PSA_18.0.74_18022/examiners/license_key_check.php create mode 100755 root/parallels/pool/PSA_18.0.74_18022/examiners/package_manager_check.sh create mode 100644 root/parallels/pool/PSA_18.0.74_18022/examiners/panel_preupgrade_checker.php create mode 100755 root/parallels/pool/PSA_18.0.74_18022/examiners/php_launcher.sh create mode 100644 root/parallels/pool/PSA_18.0.74_18022/examiners/plesk_preupgrade_checker.log create mode 100755 root/parallels/pool/PSA_18.0.74_18022/examiners/py_launcher.sh create mode 100755 root/parallels/pool/PSA_18.0.74_18022/examiners/repository_check.sh create mode 100755 root/parallels/pool/PSA_18.0.74_18022/examiners/sh_cmd.sh create mode 100644 root/parallels/pool/PSA_18.0.74_18022/plesk-18.0.74-ubt24.04-x86_64.inf3 create mode 100644 root/parallels/pool/PSA_18.0.74_18022/release.inf3 create mode 100644 root/parallels/pool/PSA_18.0.75_18153/release.inf3 create mode 100644 root/parallels/pool/WPB_18.0.55_74/release.inf3 create mode 100644 root/parallels/pool/WPB_18.0.59_78/release.inf3 create mode 100644 root/parallels/pool/WPB_18.0.69_87/release.inf3 create mode 100644 root/parallels/pool/WPB_18.0.69_87/sitebuilder-18.0.69-deball-all.inf3 create mode 100644 root/parallels/pool/WPB_18.0.69_88/release.inf3 create mode 100644 root/parallels/pool/WPB_18.0.69_88/sitebuilder-18.0.69-deball-all.inf3 create mode 100644 root/parallels/pp-sitebuilder.inf3 create mode 100644 root/parallels/products.inf3 create mode 100644 root/parallels/report-update create mode 100644 root/parallels/setemplates.inf3 create mode 100644 root/parallels/sitebuilder.inf3 diff --git a/root/parallels/PHP74_17/php74-ubt24.04-x86_64.inf3 b/root/parallels/PHP74_17/php74-ubt24.04-x86_64.inf3 new file mode 100644 index 0000000000..f2d4834cfb --- /dev/null +++ b/root/parallels/PHP74_17/php74-ubt24.04-x86_64.inf3 @@ -0,0 +1,47 @@ + + + -----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v2.0.22 (GNU/Linux) mQGiBFXAUiARBAC9lDsyU9L2dZRyhOp27adDZ99+tfPq9L0aX1xmZvpF+rO6FEa1 HCEi1blRq/icL784esCEuEe/k6eGeqKgOv6E05ZG9txvP/vhUofFJX6vAHsiScKN QXwZQQqM2Fz3oPqeN/7zOE/dfASrTC8fET7J7+d+KNOOnDSSxIG9XJbGTwCg0BCd 2vNB4CduMm2oyTVNGcWXmCMEAIFJALIEEeNlevXvBxBPv86DB3eFdLUgYJNlwIfJ aDCFr9plRNb6O/MMGPFPsp113r4E+o1M8bW5RBfZgnxXH+6Xi3i/kMBh9GtovAv+ qe4lMJQ+t7cFDedvsgsmigq7cl0cmSwe00zhYzT4t3lEg5F3l543Wwxk/kwQj2wp t5cCA/9rAUUCZ4ewdO74tiuF5nmTb+lHNvIq4EKXacgA5gsVVMTw/SH8G0m0z+YH 3xJuVJaO18+6OrdCjGzmzJ78k8j6z1fuAZVWWaUK+XtjP6mn/ZsjwvrIGMxrbJ85 B/v+j4W7MOjIHmD37PScSibK56ItlQHjd2y0S6jy+e4UFIOnA7QbUGxlc2sgVGVh bSA8aW5mb0BwbGVzay5jb20+iGAEExECACAFAlXAUiACGwMGCwkIBwMCBBUCCAME FgIDAQIeAQIXgAAKCRC9EaaqkUvfflh7AKCDstpjOFDR1FarF2BU/sA6+rFteQCg xHjO3vh+i2QiTnGlBhOUlCxuNFq5Ag0EVcBSJxAIAID5a6RrDkEIkaAc6u+BJJsp Rychg18z+IdHPUrBABEeT7vCmH5KISP1bdhur8vgeDdFanhLjTjC7mYJ5OJnb3ZM nl/L8B5uz/RQ8i7Sv/buwr69h/llVElkeOyx2SkkIdsHLPNXuxydZfADfz5B5Zjx R7IVepDxEeA755rCQd3alAwk6lA0Iy+LCdCaNIGpzzC6j7goLeGE1tAoG3J0Lvja xXGgTmqHHhImGjc875wngrDRo4yDu6Tfwi6b0RCfbkm6wgEWGvDwzSXz1+7iWRAC kyagGrZPOqKJiKBfj+hVMnflB3EFZd2j5hfWl35U4j18U6v6JkxKIn0DvD5+cz8A BAsH/RVWit+s/zXJbxd9U07EPbVS+ujZrhannBgV6xpMuGWDbl1QcMWXoQXU0zK4 Gr44UmHbe3h1F38GeMzELjfMYi8XAMvOQkC5i8Clv9jHJwfHxt8wcA/tE4kPtRNx KhDhh3i2sZTg123h0EpEOlMvrVoboFyH8K7BN+KdEl3YtWgmbM9zhOO8R7LFGQ6m VxxL4BlX7QNZ9cpDQ5sNTiPu4pij++oFoSaquXZKGXOO3KvTBQHHceps+w1cpYXv EGzNjxvTvPmBYXZAVlu8dOInDMfl8wwctAmHXxPeh0kTGRlZT4QHJs1pJVgNuQbP pjqmxx1SGEnYmE/TNDP+J1F7GISISQQYEQIACQUCVcBSJwIbDAAKCRC9EaaqkUvf fmn2AJ9q31HpLe/9sHWQHAL/ul6qeSNIJwCeK2hBWeBcx0iC/ISo8T/EX8KTCcw= =NB3w -----END PGP PUBLIC KEY BLOCK----- + + + plesk-php74 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/PHP74_17/release.inf3 b/root/parallels/PHP74_17/release.inf3 new file mode 100644 index 0000000000..1315112a7d --- /dev/null +++ b/root/parallels/PHP74_17/release.inf3 @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/PHP80_17/php80-ubt24.04-x86_64.inf3 b/root/parallels/PHP80_17/php80-ubt24.04-x86_64.inf3 new file mode 100644 index 0000000000..d786c45d72 --- /dev/null +++ b/root/parallels/PHP80_17/php80-ubt24.04-x86_64.inf3 @@ -0,0 +1,45 @@ + + + -----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v2.0.22 (GNU/Linux) mQGiBFXAUiARBAC9lDsyU9L2dZRyhOp27adDZ99+tfPq9L0aX1xmZvpF+rO6FEa1 HCEi1blRq/icL784esCEuEe/k6eGeqKgOv6E05ZG9txvP/vhUofFJX6vAHsiScKN QXwZQQqM2Fz3oPqeN/7zOE/dfASrTC8fET7J7+d+KNOOnDSSxIG9XJbGTwCg0BCd 2vNB4CduMm2oyTVNGcWXmCMEAIFJALIEEeNlevXvBxBPv86DB3eFdLUgYJNlwIfJ aDCFr9plRNb6O/MMGPFPsp113r4E+o1M8bW5RBfZgnxXH+6Xi3i/kMBh9GtovAv+ qe4lMJQ+t7cFDedvsgsmigq7cl0cmSwe00zhYzT4t3lEg5F3l543Wwxk/kwQj2wp t5cCA/9rAUUCZ4ewdO74tiuF5nmTb+lHNvIq4EKXacgA5gsVVMTw/SH8G0m0z+YH 3xJuVJaO18+6OrdCjGzmzJ78k8j6z1fuAZVWWaUK+XtjP6mn/ZsjwvrIGMxrbJ85 B/v+j4W7MOjIHmD37PScSibK56ItlQHjd2y0S6jy+e4UFIOnA7QbUGxlc2sgVGVh bSA8aW5mb0BwbGVzay5jb20+iGAEExECACAFAlXAUiACGwMGCwkIBwMCBBUCCAME FgIDAQIeAQIXgAAKCRC9EaaqkUvfflh7AKCDstpjOFDR1FarF2BU/sA6+rFteQCg xHjO3vh+i2QiTnGlBhOUlCxuNFq5Ag0EVcBSJxAIAID5a6RrDkEIkaAc6u+BJJsp Rychg18z+IdHPUrBABEeT7vCmH5KISP1bdhur8vgeDdFanhLjTjC7mYJ5OJnb3ZM nl/L8B5uz/RQ8i7Sv/buwr69h/llVElkeOyx2SkkIdsHLPNXuxydZfADfz5B5Zjx R7IVepDxEeA755rCQd3alAwk6lA0Iy+LCdCaNIGpzzC6j7goLeGE1tAoG3J0Lvja xXGgTmqHHhImGjc875wngrDRo4yDu6Tfwi6b0RCfbkm6wgEWGvDwzSXz1+7iWRAC kyagGrZPOqKJiKBfj+hVMnflB3EFZd2j5hfWl35U4j18U6v6JkxKIn0DvD5+cz8A BAsH/RVWit+s/zXJbxd9U07EPbVS+ujZrhannBgV6xpMuGWDbl1QcMWXoQXU0zK4 Gr44UmHbe3h1F38GeMzELjfMYi8XAMvOQkC5i8Clv9jHJwfHxt8wcA/tE4kPtRNx KhDhh3i2sZTg123h0EpEOlMvrVoboFyH8K7BN+KdEl3YtWgmbM9zhOO8R7LFGQ6m VxxL4BlX7QNZ9cpDQ5sNTiPu4pij++oFoSaquXZKGXOO3KvTBQHHceps+w1cpYXv EGzNjxvTvPmBYXZAVlu8dOInDMfl8wwctAmHXxPeh0kTGRlZT4QHJs1pJVgNuQbP pjqmxx1SGEnYmE/TNDP+J1F7GISISQQYEQIACQUCVcBSJwIbDAAKCRC9EaaqkUvf fmn2AJ9q31HpLe/9sHWQHAL/ul6qeSNIJwCeK2hBWeBcx0iC/ISo8T/EX8KTCcw= =NB3w -----END PGP PUBLIC KEY BLOCK----- + + + plesk-php80 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/PHP80_17/release.inf3 b/root/parallels/PHP80_17/release.inf3 new file mode 100644 index 0000000000..f30f04f5df --- /dev/null +++ b/root/parallels/PHP80_17/release.inf3 @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/PHP81_17/php81-ubt24.04-x86_64.inf3 b/root/parallels/PHP81_17/php81-ubt24.04-x86_64.inf3 new file mode 100644 index 0000000000..0192cbad7c --- /dev/null +++ b/root/parallels/PHP81_17/php81-ubt24.04-x86_64.inf3 @@ -0,0 +1,45 @@ + + + -----BEGIN PGP PUBLIC KEY BLOCK----- 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 VNnMRY8n =Hv0N -----END PGP PUBLIC KEY BLOCK----- + + + plesk-php81 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/PHP81_17/release.inf3 b/root/parallels/PHP81_17/release.inf3 new file mode 100644 index 0000000000..fafd274c4c --- /dev/null +++ b/root/parallels/PHP81_17/release.inf3 @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/PHP82_17/php82-ubt24.04-x86_64.inf3 b/root/parallels/PHP82_17/php82-ubt24.04-x86_64.inf3 new file mode 100644 index 0000000000..9e8e5496a0 --- /dev/null +++ b/root/parallels/PHP82_17/php82-ubt24.04-x86_64.inf3 @@ -0,0 +1,45 @@ + + + -----BEGIN PGP PUBLIC KEY BLOCK----- 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 VNnMRY8n =Hv0N -----END PGP PUBLIC KEY BLOCK----- + + + plesk-php82 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/PHP82_17/release.inf3 b/root/parallels/PHP82_17/release.inf3 new file mode 100644 index 0000000000..5e58d7feb8 --- /dev/null +++ b/root/parallels/PHP82_17/release.inf3 @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/PHP83_17/php83-ubt24.04-x86_64.inf3 b/root/parallels/PHP83_17/php83-ubt24.04-x86_64.inf3 new file mode 100644 index 0000000000..bf4528203a --- /dev/null +++ b/root/parallels/PHP83_17/php83-ubt24.04-x86_64.inf3 @@ -0,0 +1,45 @@ + + + -----BEGIN PGP PUBLIC KEY BLOCK----- mQGNBGfIt/cBDADGVazaP3jWndhBaSljtWGtGqrRjNVnsu5YPtOsmOgQ0x7VZQft C/LpT5QnOVip5DBfAUBbxLzZ0C6/YP4+7yJRcAbecuFEwln02AeiE7tzQu8P8cvC V4VTTKcdWzEhKMaoSS1tiIKGVGPuQcYwAvhY5pcrFgMypYOOsLjZtR0oOrmqpMlC x2JMmD6gwGONzNv3EungSV8QVE7sgyttmuCUR2QlbCJQjNWpkgvstNxXRvWiuvrK gGNVdd14r5juOv3PA2TwWsEFUR8hfK7eqtDYo8BS9HigUkjI35B/CWxi55mgAXDq Xdwtc79dWGvnCruFmTVp6W3kTEwPXC0SphHAqE4r8+HoKX3fMXb7oddqwYXUCOuS z7xan1KctOe/c5Y9EbERjBLdr4sJrOkJv91PBuL7Scz33o7lHKCXrvuVQmLhRvT1 rG2D6/Ya/WaFFWI8z8MqINZgMtwzmcow/xapj8c6e1lgOblQ0j1qiiptQTuIoC49 JgZTFr3A6mcYOrEAEQEAAbQbUGxlc2sgVGVhbSA8aW5mb0BwbGVzay5jb20+iQHO BBMBCgA4FiEEbBkTJQiO2DphjsDC6SmQRc5VDlcFAmfIt/cCGwMFCwkIBwIGFQoJ CAsCBBYCAwECHgECF4AACgkQ6SmQRc5VDld7pwv9FrqzISuXHelFotpDXcqPqcWQ W97mi4dkyo9dY+UBFXqprPaC9+mM9HW7a+lZSgWdxc+CY2MrbcIXfdnaJmJWJGqc dvW122hjQRe7ClrwRAL06HDj5yhMHqhFPUbb8a+PoKb1d8vRQHHrLpUhcpwhsLr5 aZFZop3NKN3ktPQiqoMPAHBuG4Aag6puG9BZS4jBvTJXvD9JAd7wQkxvPW/BJvBK ILlOrs/6UTdgIDNv8qlUt77vS1s6RpGVJXRhjj9J1f6Lfg2xJZMO0fLqOxgUjSrG jV1r6tnS6pxi0onXJsSmMEli4wsZpnotr35Vwu9Eekb6KTq5K05YJxnqi6G2qFY7 nRpXSvfjYJ+MDP3a3fhryqfFd6lQdnuNv4XMBRnwr6VJNzsRg/xkYlPkDZ2dbXVl AwUTIX6Uw6F8ToUE8v/KGNHEiLycCv2Szk/nLawr3aLCfijgxTaP+RzUUb44ex/k nm6at9hCZbNknBGcMPXb6Y6MTSOQKhmpR4n+a4KluQGNBGfIt/cBDACtcVnLn1ye JFEhPja0IJE4AxmVLGGWHKLBLGqyoONwAi9LA/+kfTL0MhhM4Ib8dmg4N7HfTROd HvhjlsRLnqBoTuPyz8Jh1oxkmM3gYGAR10GulqNNXLWNVdqJjtfRKLGZr5MhsCdb i7tKA42/hWqqKVmCGEkc5IOl0kd8qvCPM/vqFvHYBxF5Ov5aUhSTwQBVbrcsU1Qc K491VjCk1Fw1BpV3sj0pYs2MPaR0k3A3pMLG6oMI900wt/wiZMjNSyFCxhEYFrLR t7qkuLcN+LZ94USiowPP04QxaDj5mFnQ+O0n4UAKRJ9/uHGbhCFuej1/DkB9urP0 SGbte51v2KisuWG/nBkg119gQeXKLIGNC5aE2TTQBTaEBL09teDeQMg8TbQlu6v/ AIFpgrwckmvAk6afaWpAZ0GTNZ0DQL1wD6m8E8T4JFcVIQ+C1IzKu6OE7KKMzyjg crI9HMLpGSEOzRfR334nSYsWFS88XW6msltMNWn3jNSLOQ+1Xf+RN3cAEQEAAYkB tQQYAQoAIBYhBGwZEyUIjtg6YY7AwukpkEXOVQ5XBQJnyLf3AhsMAAoJEOkpkEXO VQ5XoooL91q50qxg/09vV1GldlFBF1eFEUsSVwOYoGKtsRzebWEdGc8Ze4Cks5fq CQipKjPC1kmShocshFBYKDRChiXk+b/djK0U1aEaRZYP/ro953yfXVnV68WeoiJ4 EIH9qXMzDcMn58fVEvz9EYyk8b3VcBru+0TgCvWrNVJBd7DF8YJXs2rSAfhu5Sdf P4uL9hhhF1TWPJjFG3L4gW8Ah9vgmaU9uQhIP3e3ANWxOtEhjhnnO8noJCxELKeS tTve7EYpscuixfOXPwmY3zJATXLt/+QJAcnGasFcTkw/XFvGOOZJ/7mx+GUhD23D AjsA3ozjL3FLS/v7A4rYEUc/dClX3lMKwEK7ZVNtmtt1WsbuHX/Py/R5XhyA3V1W JOwV1Mgnmu8BS62JcWY6oB0mhc3uGd6Tgs1ZkeisnBsi0Oi4YQ8Ms0v1NZHXgwtL JbRkcLFAL8rErnC0728220B+2Aik4DHZZI0M7Fre7QPWiU9a1R7AUCxsgQfEum5m VNnMRY8n =Hv0N -----END PGP PUBLIC KEY BLOCK----- + + + plesk-php83 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/PHP83_17/release.inf3 b/root/parallels/PHP83_17/release.inf3 new file mode 100644 index 0000000000..b1ac4c412e --- /dev/null +++ b/root/parallels/PHP83_17/release.inf3 @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/PHP84_17/php84-ubt24.04-x86_64.inf3 b/root/parallels/PHP84_17/php84-ubt24.04-x86_64.inf3 new file mode 100644 index 0000000000..5a2e82b6b8 --- /dev/null +++ b/root/parallels/PHP84_17/php84-ubt24.04-x86_64.inf3 @@ -0,0 +1,45 @@ + + + -----BEGIN PGP PUBLIC KEY BLOCK----- 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 VNnMRY8n =Hv0N -----END PGP PUBLIC KEY BLOCK----- + + + plesk-php84 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/PHP84_17/release.inf3 b/root/parallels/PHP84_17/release.inf3 new file mode 100644 index 0000000000..535532209f --- /dev/null +++ b/root/parallels/PHP84_17/release.inf3 @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/PHP85_17/php85-ubt24.04-x86_64.inf3 b/root/parallels/PHP85_17/php85-ubt24.04-x86_64.inf3 new file mode 100644 index 0000000000..a816358858 --- /dev/null +++ b/root/parallels/PHP85_17/php85-ubt24.04-x86_64.inf3 @@ -0,0 +1,44 @@ + + + -----BEGIN PGP PUBLIC KEY BLOCK----- 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 VNnMRY8n =Hv0N -----END PGP PUBLIC KEY BLOCK----- + + + plesk-php85 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/PHP85_17/release.inf3 b/root/parallels/PHP85_17/release.inf3 new file mode 100644 index 0000000000..ca9b34d027 --- /dev/null +++ b/root/parallels/PHP85_17/release.inf3 @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/apache.inf3 b/root/parallels/apache.inf3 new file mode 100644 index 0000000000..f7091b2669 --- /dev/null +++ b/root/parallels/apache.inf3 @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/billing.inf3 b/root/parallels/billing.inf3 new file mode 100644 index 0000000000..19e1e2730b --- /dev/null +++ b/root/parallels/billing.inf3 @@ -0,0 +1,1396 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/mysql.inf3 b/root/parallels/mysql.inf3 new file mode 100644 index 0000000000..40a8a814f7 --- /dev/null +++ b/root/parallels/mysql.inf3 @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/nginx.inf3 b/root/parallels/nginx.inf3 new file mode 100644 index 0000000000..553bdc5c9a --- /dev/null +++ b/root/parallels/nginx.inf3 @@ -0,0 +1,419 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/php52.inf3 b/root/parallels/php52.inf3 new file mode 100644 index 0000000000..7d793d752f --- /dev/null +++ b/root/parallels/php52.inf3 @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/php53.inf3 b/root/parallels/php53.inf3 new file mode 100644 index 0000000000..4feb25d441 --- /dev/null +++ b/root/parallels/php53.inf3 @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/php54.inf3 b/root/parallels/php54.inf3 new file mode 100644 index 0000000000..cedf26e643 --- /dev/null +++ b/root/parallels/php54.inf3 @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/php55.inf3 b/root/parallels/php55.inf3 new file mode 100644 index 0000000000..40f7262b40 --- /dev/null +++ b/root/parallels/php55.inf3 @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/php56.inf3 b/root/parallels/php56.inf3 new file mode 100644 index 0000000000..36b8dbf6f0 --- /dev/null +++ b/root/parallels/php56.inf3 @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/php70.inf3 b/root/parallels/php70.inf3 new file mode 100644 index 0000000000..1d73090db0 --- /dev/null +++ b/root/parallels/php70.inf3 @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/php71.inf3 b/root/parallels/php71.inf3 new file mode 100644 index 0000000000..d356bdab82 --- /dev/null +++ b/root/parallels/php71.inf3 @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/php72.inf3 b/root/parallels/php72.inf3 new file mode 100644 index 0000000000..5cb7c10d3a --- /dev/null +++ b/root/parallels/php72.inf3 @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/php73.inf3 b/root/parallels/php73.inf3 new file mode 100644 index 0000000000..6eca461f6c --- /dev/null +++ b/root/parallels/php73.inf3 @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/php74.inf3 b/root/parallels/php74.inf3 new file mode 100644 index 0000000000..433403b955 --- /dev/null +++ b/root/parallels/php74.inf3 @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + diff --git a/root/parallels/php80.inf3 b/root/parallels/php80.inf3 new file mode 100644 index 0000000000..344acca646 --- /dev/null +++ b/root/parallels/php80.inf3 @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/root/parallels/php81.inf3 b/root/parallels/php81.inf3 new file mode 100644 index 0000000000..078e2cff73 --- /dev/null +++ b/root/parallels/php81.inf3 @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + diff --git a/root/parallels/php82.inf3 b/root/parallels/php82.inf3 new file mode 100644 index 0000000000..a0a62fe64d --- /dev/null +++ b/root/parallels/php82.inf3 @@ -0,0 +1,14 @@ + + + + + + + + + + + + diff --git a/root/parallels/php83.inf3 b/root/parallels/php83.inf3 new file mode 100644 index 0000000000..fdfe9d84d0 --- /dev/null +++ b/root/parallels/php83.inf3 @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/root/parallels/plesk.inf3 b/root/parallels/plesk.inf3 new file mode 100644 index 0000000000..84a51bd23a --- /dev/null +++ b/root/parallels/plesk.inf3 @@ -0,0 +1,2791 @@ + + + + + + + + + + psa + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 4.5.0 + + + 2.2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 4.5.0 + + + 2.2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 4.5.0 + + + 2.2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 4.5.0 + + + 2.2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 4.5.0 + + + 2.2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 4.5.0 + + + 2.2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 4.5.0 + + + 2.2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 4.5.0 + + + 2.2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 4.5.0 + + + 2.2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 4.5.0 + + + 2.2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 4.5.0 + + + 2.2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 4.5.0 + + + 2.2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 4.1.0 + + + 2.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 4.1.0 + + + 2.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 4.1.0 + + + 2.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/pmm.inf3 b/root/parallels/pmm.inf3 new file mode 100644 index 0000000000..c7c475b2ef --- /dev/null +++ b/root/parallels/pmm.inf3 @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/pool/PSA_18.0.64_15680/release.inf3 b/root/parallels/pool/PSA_18.0.64_15680/release.inf3 new file mode 100644 index 0000000000..556189cd0b --- /dev/null +++ b/root/parallels/pool/PSA_18.0.64_15680/release.inf3 @@ -0,0 +1,34 @@ + + + + + + + + + psa + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/pool/PSA_18.0.65_15919/release.inf3 b/root/parallels/pool/PSA_18.0.65_15919/release.inf3 new file mode 100644 index 0000000000..ab20c611ba --- /dev/null +++ b/root/parallels/pool/PSA_18.0.65_15919/release.inf3 @@ -0,0 +1,34 @@ + + + + + + + + + psa + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/pool/PSA_18.0.66_16134/release.inf3 b/root/parallels/pool/PSA_18.0.66_16134/release.inf3 new file mode 100644 index 0000000000..d96c745d10 --- /dev/null +++ b/root/parallels/pool/PSA_18.0.66_16134/release.inf3 @@ -0,0 +1,34 @@ + + + + + + + + + psa + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/pool/PSA_18.0.67_16409/release.inf3 b/root/parallels/pool/PSA_18.0.67_16409/release.inf3 new file mode 100644 index 0000000000..2d4ac12030 --- /dev/null +++ b/root/parallels/pool/PSA_18.0.67_16409/release.inf3 @@ -0,0 +1,35 @@ + + + + + + + + + psa + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/pool/PSA_18.0.68_16616/release.inf3 b/root/parallels/pool/PSA_18.0.68_16616/release.inf3 new file mode 100644 index 0000000000..da76ca53df --- /dev/null +++ b/root/parallels/pool/PSA_18.0.68_16616/release.inf3 @@ -0,0 +1,35 @@ + + + + + + + + + psa + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/pool/PSA_18.0.69_17010/release.inf3 b/root/parallels/pool/PSA_18.0.69_17010/release.inf3 new file mode 100644 index 0000000000..bf8edd5d8f --- /dev/null +++ b/root/parallels/pool/PSA_18.0.69_17010/release.inf3 @@ -0,0 +1,33 @@ + + + + + + + + + psa + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/pool/PSA_18.0.70_17357/release.inf3 b/root/parallels/pool/PSA_18.0.70_17357/release.inf3 new file mode 100644 index 0000000000..f735ca4357 --- /dev/null +++ b/root/parallels/pool/PSA_18.0.70_17357/release.inf3 @@ -0,0 +1,33 @@ + + + + + + + + + psa + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/pool/PSA_18.0.71_17351/release.inf3 b/root/parallels/pool/PSA_18.0.71_17351/release.inf3 new file mode 100644 index 0000000000..8419c3264e --- /dev/null +++ b/root/parallels/pool/PSA_18.0.71_17351/release.inf3 @@ -0,0 +1,35 @@ + + + + + + + + + psa + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/pool/PSA_18.0.72_17583/examiners/congratulations.sh b/root/parallels/pool/PSA_18.0.72_17583/examiners/congratulations.sh new file mode 100755 index 0000000000..907c5ba782 --- /dev/null +++ b/root/parallels/pool/PSA_18.0.72_17583/examiners/congratulations.sh @@ -0,0 +1,50 @@ +#!/bin/bash +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +out() +{ + echo -e "\t$*" >&2 +} + +print_urls() +{ + plesk login 2>/dev/null | sed -e $'s|^|\t * |' >&2 +} + +print_congratulations() +{ + local mode="$1" # 'install' or 'upgrade' + local process= + [ "$mode" = "install" ] && process="installation" || process="upgrade" + + out + out " Congratulations!" + out + out "The $process has been finished. Plesk is now running on your server." + out + if [ "$mode" = "install" ]; then + out "To complete the configuration process, browse either of URLs:" + print_urls + out + fi + out "Use the username 'admin' to log in. To log in as 'admin', use the 'plesk login' command." + out "You can also log in as 'root' using your 'root' password." + out + out "Use the 'plesk' command to manage the server. Run 'plesk help' for more info." + out + out "Use the following commands to start and stop the Plesk web interface:" + out "'systemctl start psa.service' and 'systemctl stop psa.service' respectively." + out + if [ "$mode" = "install" ]; then + out "If you would like to migrate your subscriptions from other hosting panel" + out "or older Plesk version to this server, please check out our assistance" + out "options: https://www.plesk.com/professional-services/" + out + fi +} + +unset GREP_OPTIONS + +print_congratulations "$1" +# Force showing text when used as AI post-examiner +exit 1 diff --git a/root/parallels/pool/PSA_18.0.72_17583/examiners/disk_space_check.sh b/root/parallels/pool/PSA_18.0.72_17583/examiners/disk_space_check.sh new file mode 100755 index 0000000000..b6238e41a0 --- /dev/null +++ b/root/parallels/pool/PSA_18.0.72_17583/examiners/disk_space_check.sh @@ -0,0 +1,532 @@ +#!/bin/bash +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +[ -z "$PLESK_INSTALLER_DEBUG" ] || set -x +[ -z "$PLESK_INSTALLER_STRICT_MODE" ] || set -e + +export LC_ALL=C +unset GREP_OPTIONS + +RET_SUCCESS=0 +RET_WARN=1 +RET_FATAL=2 + +is_function_defined() +{ + local fn="$1" + case "$(type $fn 2>/dev/null)" in + *function*) + return 0 + ;; + esac + return 1 +} + +# @params are tags in format "key=value" +# Report body (human readable information) is read from stdin +# and copied to stderr. +make_error_report() +{ + local report_file="${PLESK_INSTALLER_ERROR_REPORT:-}" + + local python_bin= + for bin in "/opt/psa/bin/python" "/usr/local/psa/bin/python" "/usr/bin/python2" "/opt/psa/bin/py3-python" "/usr/local/psa/bin/py3-python" "/usr/libexec/platform-python" "/usr/bin/python3"; do + if [ -x "$bin" ]; then + python_bin="$bin" + break + fi + done + + if [ -n "$report_file" -a -x "$python_bin" ]; then + "$python_bin" -c 'import sys, json +report_file = sys.argv[1] +error = sys.stdin.read() + +sys.stderr.write(error) + +data = { + "error": error, +} + +for tag in sys.argv[2:]: + k, v = tag.split("=", 1) + data[k] = v + +with open(report_file, "a") as f: + json.dump(data, f) + f.write("\n") +' "$report_file" "date=$(date --utc --iso-8601=ns)" "$@" + else + cat - >&2 + fi +} + +detect_platform() +{ + . /etc/os-release + os_name="$ID" + os_version="${VERSION_ID%%.*}" + os_arch="$(uname -m)" + if [ -e /etc/debian_version ]; then + case "$os_arch" in + x86_64) pkg_arch="amd64" ;; + aarch64) pkg_arch="arm64" ;; + esac + if [ -n "$VERSION_CODENAME" ]; then + os_codename="$VERSION_CODENAME" + else + case "$os_name$os_version" in + debian10) os_codename="buster" ;; + debian11) os_codename="bullseye" ;; + debian12) os_codename="bookworm" ;; + ubuntu18) os_codename="bionic" ;; + ubuntu20) os_codename="focal" ;; + ubuntu22) os_codename="jammy" ;; + ubuntu24) os_codename="noble" ;; + esac + fi + fi + + case "$os_name$os_version" in + rhel7|centos7|cloudlinux7|virtuozzo7) + package_manager="yum" + ;; + rhel*|centos*|cloudlinux*|almalinux*|rocky*) + package_manager="dnf" + ;; + debian*|ubuntu*) + package_manager="apt" + ;; + esac + + if [ "$os_name" = "ubuntu" -o "$os_name" = "debian" ]; then + PRODUCT_ROOT_D="/opt/psa" + else + PRODUCT_ROOT_D="/usr/local/psa" + fi +} + +has_os_impl_function() +{ + local prefix="$1" + local fn="${prefix}_${os_name}${os_version}" + is_function_defined "$fn" +} + +call_os_impl_function() +{ + local prefix="$1" + shift + local fn="${prefix}_${os_name}${os_version}" + "$fn" "$@" +} + +skip_checker_on_flag() +{ + local name="$1" + local flag="$2" + + if [ -f "$flag" ]; then + echo "$name was skipped due to flag file." >&2 + exit $RET_SUCCESS + fi +} + +skip_checker_on_env() +{ + local name="$1" + local env="$2" + + if [ -n "$env" ]; then + echo "$name was skipped due to environment variable." >&2 + exit $RET_SUCCESS + fi +} + +checker_main() +{ + local fnprefix="$1" + shift + + detect_platform + # try to execute checker only if all attributes are detected + [ -n "$os_name" -a -n "$os_version" ] || return $RET_SUCCESS + + for checker in "${fnprefix}_${os_name}${os_version}" "${fnprefix}_${os_name}" "${fnprefix}"; do + if is_function_defined "$checker"; then + local rc=$RET_SUCCESS + "$checker" "$@" || rc=$? + [ "$(( $rc & $RET_FATAL ))" = "0" ] || return $RET_FATAL + [ "$(( $rc & $RET_WARN ))" = "0" ] || return $RET_WARN + return $rc + fi + done + return $RET_SUCCESS +} + +#!/bin/sh +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +# If env variable PLESK_INSTALLER_ERROR_REPORT=path_to_file is specified then in case of error +# disk_space_check.sh writes single line json report into it with the following fields: +# - "stage": "diskspacecheck" +# - "level": "error" +# - "errtype": "notenoughdiskspace" +# - "volume": volume with not enough diskspace (e.g. "/") +# - "required": required diskspace on the volume, human readable (e.g. "600 MB") +# - "available": available diskspace on the volume, human readable (e.g. "255 MB") +# - "needtofree": amount of diskspace which should be freed on the volume, human readable (e.g. "345 MB") +# - "date": time of error occurance ("2020-03-24T06:59:43,127545441+0000") +# - "error": human readable error message ("There is not enough disk space available in the / directory.") + +# Required values below for Full installation are in MB. See 'du -cs -BM /*' and 'df -Pm'. + +required_disk_space_cloudlinux7() +{ + case "$1" in + /opt) echo 900 ;; + /usr) echo 4400 ;; + /var) echo 600 ;; + /tmp) echo 100 ;; + esac +} + +required_disk_space_cloudlinux8() +{ + case "$1" in + /opt) echo 1200 ;; + /usr) echo 4400 ;; + /var) echo 700 ;; + /tmp) echo 100 ;; + esac +} + +required_disk_space_centos7() +{ + case "$1" in + /opt) echo 900 ;; + /usr) echo 4100 ;; + /var) echo 600 ;; + /tmp) echo 100 ;; + esac +} + +required_disk_space_centos8() +{ + case "$1" in + /opt) echo 900 ;; + /usr) echo 4500 ;; + /var) echo 800 ;; + /tmp) echo 100 ;; + esac +} + +required_disk_space_virtuozzo7() +{ + required_disk_space_centos7 "$1" +} + +required_disk_space_rhel7() +{ + required_disk_space_centos7 "$1" +} + +required_disk_space_rhel8() +{ + required_disk_space_centos8 "$1" +} + +required_disk_space_almalinux8() +{ + required_disk_space_centos8 "$1" +} + +required_disk_space_rocky8() +{ + required_disk_space_centos8 "$1" +} + +required_disk_space_rhel9() +{ + case "$1" in + /opt) echo 500 ;; + /usr) echo 4000 ;; + /var) echo 800 ;; + /tmp) echo 100 ;; + esac +} + +required_disk_space_almalinux9() +{ + required_disk_space_rhel9 "$1" +} + +required_disk_space_almalinux10() +{ + required_disk_space_almalinux9 "$1" +} + +required_disk_space_cloudlinux9() +{ + required_disk_space_rhel9 "$1" +} + +required_disk_space_debian10() +{ + case "$1" in + /opt) echo 1800 ;; + /usr) echo 2300 ;; + /var) echo 1700 ;; + /tmp) echo 100 ;; + esac +} + +required_disk_space_debian11() +{ + case "$1" in + /opt) echo 1500 ;; + /usr) echo 3100 ;; + /var) echo 1800 ;; + /tmp) echo 100 ;; + esac +} + +required_disk_space_debian12() +{ + case "$1" in + /opt) echo 2700 ;; + /usr) echo 2500 ;; + /var) echo 2200 ;; + /tmp) echo 100 ;; + esac +} + +required_disk_space_ubuntu18() +{ + case "$1" in + /opt) echo 900 ;; + /usr) echo 1800 ;; + /var) echo 600 ;; + /tmp) echo 100 ;; + esac +} + +required_disk_space_ubuntu20() +{ + case "$1" in + /opt) echo 1800 ;; + /usr) echo 2900 ;; + /var) echo 1600 ;; + /tmp) echo 100 ;; + esac +} + +required_disk_space_ubuntu22() +{ + case "$1" in + /opt) echo 1800 ;; + /usr) echo 3900 ;; + /var) echo 1900 ;; + /tmp) echo 100 ;; + esac +} + +required_disk_space_ubuntu24() +{ + case "$1" in + /opt) echo 3200 ;; + /usr) echo 1800 ;; + /var) echo 2400 ;; + /tmp) echo 100 ;; + esac +} + +required_update_upgrade_disk_space() +{ + case "$1" in + /opt) echo 100 ;; + /usr) echo 300 ;; + /var) echo 600 ;; + /tmp) echo 100 ;; + esac +} + +clean_tmp() +{ + local volume="$1" + local path="/tmp" + is_path_on_volume "$path" "$volume" || return 0 + + echo "Cleaning $path via 'systemd-tmpfiles --clean --prefix $path'" + systemd-tmpfiles --clean --prefix "$path" 2>&1 +} + +clean_yum() +{ + local volume="$1" + local path="/var/cache/yum" + is_path_on_volume "$path" "$volume" || return 0 + + echo "Cleaning $path via 'yum clean all'" + yum clean all 2>&1 + + # The command above doesn't clean untracked repos (missing in configuration), clean if left > 2 Mb + [ "`du -sm "$path" | awk '{ print $1 }'`" -gt 2 ] || return 0 + echo "Cleaning $path via 'rm -rf $path/*'" + rm -rf "$path"/* 2>&1 +} + +clean_dnf() +{ + local volume="$1" + local path="/var/cache/dnf" + is_path_on_volume "$path" "$volume" || return 0 + + echo "Cleaning $path via 'dnf clean all'" + dnf clean all 2>&1 +} + +clean_apt() +{ + local volume="$1" + local path="/var/cache/apt" + is_path_on_volume "$path" "$volume" || return 0 + + echo "Cleaning $path via 'apt-get clean'" + apt-get clean 2>&1 +} + +clean_journal() +{ + local volume="$1" + local path="/var/log/journal" + is_path_on_volume "$path" "$volume" || return 0 + + # Note that --rotate may cause more space to be freed, but may also cause more space to be used + echo "Cleaning $path via 'journalctl --vacuum-time 1d'" + journalctl --vacuum-time 1d 2>&1 +} + +clean_ext_packages() +{ + local volume="$1" + local path="$PRODUCT_ROOT_D/var/modules-packages" + is_path_on_volume "$path" "$volume" || return 0 + + echo "Cleaning $path via 'rm -rf $path/*'" + rm -rf "$path"/* 2>&1 +} + +# @param $1 target directory +mount_point() +{ + df -Pm $1 | awk 'NR==2 { print $6 }' +} + +# @param $1 target directory +available_disk_space() +{ + df -Pm $1 | awk 'NR==2 { print $4 }' +} + +is_path_on_volume() +{ + local path="$1" + local volume="$2" + [ -d "$path" ] && [ "`mount_point "$path"`" = "$volume" ] +} + +# @param $1 target directory +# @param $2 mode (install/upgrade/update) +req_disk_space() +{ + if [ "$2" != "install" ]; then + required_update_upgrade_disk_space "$1" + return + fi + + has_os_impl_function "required_disk_space" || { + echo "There are no requirements defined for $os_name$os_version." >&2 + echo "Disk space check cannot be performed." >&2 + exit $RET_WARN + } + call_os_impl_function "required_disk_space" "$1" +} + +human_readable_size() +{ + echo "$1" | awk ' + function human(x) { + s = "MGTEPYZ"; + while (x >= 1000 && length(s) > 1) { + x /= 1024; s = substr(s, 2); + } + # 0.05 below will make sure the value is rounded up + return sprintf("%.1f %sB", x + 0.05, substr(s, 1, 1)); + } + { print human($1); }' +} + +# @param $1 target directory +# @param $2 required disk space +# @param $3 check only flag (don't emit errors) +check_available_disk_space() +{ + local volume="$1" + local required="$2" + local check_only="${3:-}" + local available="$(available_disk_space "$volume")" + if [ "$available" -lt "$required" ]; then + local needtofree + needtofree="`human_readable_size $((required - available))`" + [ -n "$check_only" ] || + make_error_report 'stage=diskspacecheck' 'level=error' 'errtype=notenoughdiskspace' \ + "volume=$volume" "required=$required MB" "available=$available MB" "needtofree=$needtofree" \ + <<-EOL + There is not enough disk space available in the $1 directory. + You need to free up $needtofree. + EOL + return "$RET_FATAL" + fi +} + +# @param $1 target directory +# @param $2 required disk space +clean_and_check_available_disk_space() +{ + if [ -n "$PLESK_INSTALLER_FORCE_CLEAN_DISK_SPACE" ] || ! check_available_disk_space "$@" --check-only; then + clean_disk_space "$1" + check_available_disk_space "$@" + fi +} + +# Cleans up disk space on the volume +clean_disk_space() +{ + local volume="$1" + for cleanup_func in clean_tmp clean_yum clean_dnf clean_apt clean_journal clean_ext_packages; do + "$cleanup_func" "$volume" + done +} + +# @param $1 mode (install/upgrade/update) +clean_and_check_disk_space() +{ + local mode="$1" + local shared=0 + + for target_directory in /opt /usr /var /tmp; do + local required=$(req_disk_space "$target_directory" "$mode") + [ -n "$required" ] || return "$RET_WARN" + + if is_path_on_volume "$target_directory" "/"; then + shared="$((shared + required))" + else + clean_and_check_available_disk_space "$target_directory" "$required" || return $? + fi + done + + clean_and_check_available_disk_space "/" "$shared" || return $? +} + +checker_main 'clean_and_check_disk_space' "$1" diff --git a/root/parallels/pool/PSA_18.0.72_17583/examiners/package_manager_check.sh b/root/parallels/pool/PSA_18.0.72_17583/examiners/package_manager_check.sh new file mode 100755 index 0000000000..b089061d97 --- /dev/null +++ b/root/parallels/pool/PSA_18.0.72_17583/examiners/package_manager_check.sh @@ -0,0 +1,224 @@ +#!/bin/bash +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +[ -z "$PLESK_INSTALLER_DEBUG" ] || set -x +[ -z "$PLESK_INSTALLER_STRICT_MODE" ] || set -e + +export LC_ALL=C +unset GREP_OPTIONS + +RET_SUCCESS=0 +RET_WARN=1 +RET_FATAL=2 + +is_function_defined() +{ + local fn="$1" + case "$(type $fn 2>/dev/null)" in + *function*) + return 0 + ;; + esac + return 1 +} + +# @params are tags in format "key=value" +# Report body (human readable information) is read from stdin +# and copied to stderr. +make_error_report() +{ + local report_file="${PLESK_INSTALLER_ERROR_REPORT:-}" + + local python_bin= + for bin in "/opt/psa/bin/python" "/usr/local/psa/bin/python" "/usr/bin/python2" "/opt/psa/bin/py3-python" "/usr/local/psa/bin/py3-python" "/usr/libexec/platform-python" "/usr/bin/python3"; do + if [ -x "$bin" ]; then + python_bin="$bin" + break + fi + done + + if [ -n "$report_file" -a -x "$python_bin" ]; then + "$python_bin" -c 'import sys, json +report_file = sys.argv[1] +error = sys.stdin.read() + +sys.stderr.write(error) + +data = { + "error": error, +} + +for tag in sys.argv[2:]: + k, v = tag.split("=", 1) + data[k] = v + +with open(report_file, "a") as f: + json.dump(data, f) + f.write("\n") +' "$report_file" "date=$(date --utc --iso-8601=ns)" "$@" + else + cat - >&2 + fi +} + +detect_platform() +{ + . /etc/os-release + os_name="$ID" + os_version="${VERSION_ID%%.*}" + os_arch="$(uname -m)" + if [ -e /etc/debian_version ]; then + case "$os_arch" in + x86_64) pkg_arch="amd64" ;; + aarch64) pkg_arch="arm64" ;; + esac + if [ -n "$VERSION_CODENAME" ]; then + os_codename="$VERSION_CODENAME" + else + case "$os_name$os_version" in + debian10) os_codename="buster" ;; + debian11) os_codename="bullseye" ;; + debian12) os_codename="bookworm" ;; + ubuntu18) os_codename="bionic" ;; + ubuntu20) os_codename="focal" ;; + ubuntu22) os_codename="jammy" ;; + ubuntu24) os_codename="noble" ;; + esac + fi + fi + + case "$os_name$os_version" in + rhel7|centos7|cloudlinux7|virtuozzo7) + package_manager="yum" + ;; + rhel*|centos*|cloudlinux*|almalinux*|rocky*) + package_manager="dnf" + ;; + debian*|ubuntu*) + package_manager="apt" + ;; + esac + + if [ "$os_name" = "ubuntu" -o "$os_name" = "debian" ]; then + PRODUCT_ROOT_D="/opt/psa" + else + PRODUCT_ROOT_D="/usr/local/psa" + fi +} + +has_os_impl_function() +{ + local prefix="$1" + local fn="${prefix}_${os_name}${os_version}" + is_function_defined "$fn" +} + +call_os_impl_function() +{ + local prefix="$1" + shift + local fn="${prefix}_${os_name}${os_version}" + "$fn" "$@" +} + +skip_checker_on_flag() +{ + local name="$1" + local flag="$2" + + if [ -f "$flag" ]; then + echo "$name was skipped due to flag file." >&2 + exit $RET_SUCCESS + fi +} + +skip_checker_on_env() +{ + local name="$1" + local env="$2" + + if [ -n "$env" ]; then + echo "$name was skipped due to environment variable." >&2 + exit $RET_SUCCESS + fi +} + +checker_main() +{ + local fnprefix="$1" + shift + + detect_platform + # try to execute checker only if all attributes are detected + [ -n "$os_name" -a -n "$os_version" ] || return $RET_SUCCESS + + for checker in "${fnprefix}_${os_name}${os_version}" "${fnprefix}_${os_name}" "${fnprefix}"; do + if is_function_defined "$checker"; then + local rc=$RET_SUCCESS + "$checker" "$@" || rc=$? + [ "$(( $rc & $RET_FATAL ))" = "0" ] || return $RET_FATAL + [ "$(( $rc & $RET_WARN ))" = "0" ] || return $RET_WARN + return $rc + fi + done + return $RET_SUCCESS +} + +#!/bin/sh +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +check_package_manager_deb_based() +{ + local output= + output="`dpkg --audit 2>&1`" || output="$output"$'\n'"'dpkg --audit' finished with error code $?." + + if [ -n "$output" ]; then + make_error_report 'stage=packagemanagercheck' 'level=error' 'errtype=brokenpackages' <<-EOL + The system package manager reports the following problems: + + $output + + To continue with the installation, you need to resolve these issues + using the procedure below: + + 1. Make sure you have a full server snapshot. Although the + following steps are usually safe, they can still cause + data loss or irreversible changes. + 2. Run 'dpkg --configure -a'. This command can fix some of the + issues. However, it may fail. Regardless if it fails or not, + proceed with the following steps. + 3. Run 'PLESK_INSTALLER_SKIP_PACKAGE_MANAGER_CHECK=1 plesk installer update --skip-cleanup'. + Instead of 'update', you may need to use the command you used + previously (for example, 'upgrade' or 'install'). + 4. The next step depends on the outcome of the previous one: + - If step 3 was completed with the "You already have the latest + version of product(s) and all the selected components installed. + Installation will not continue." message, + run 'plesk repair installation'. + - If step 3 failed, run 'dpkg --audit'. This command can show you + packages that need to be reinstalled. To reinstall them, run + 'apt-get install --reinstall '. + 5. Run 'plesk installer update' to revert temporary changes and + validate that the issues are resolved. If the command fails or + triggers this check again, contact Plesk support. + + For more information, see + https://support.plesk.com/hc/en-us/articles/12871173047447-Plesk-update-on-Debian-Ubuntu-fails-dpkg-was-interrupted-you-must-manually-run-dpkg-configure-a-to-correct-the-problem + EOL + return "$RET_FATAL" + fi +} + +check_package_manager_debian() +{ + check_package_manager_deb_based +} + +check_package_manager_ubuntu() +{ + check_package_manager_deb_based +} + +skip_checker_on_env "Package manager check" "$PLESK_INSTALLER_SKIP_PACKAGE_MANAGER_CHECK" +skip_checker_on_flag "Package manager check" "/tmp/plesk-installer-skip-package-manager-check.flag" +checker_main 'check_package_manager' "$@" diff --git a/root/parallels/pool/PSA_18.0.72_17583/examiners/php_launcher.sh b/root/parallels/pool/PSA_18.0.72_17583/examiners/php_launcher.sh new file mode 100755 index 0000000000..70ebd0f0c6 --- /dev/null +++ b/root/parallels/pool/PSA_18.0.72_17583/examiners/php_launcher.sh @@ -0,0 +1,38 @@ +#!/bin/sh +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +die() +{ + echo $* + exit 1 +} + +[ -n "$1" ] || die "Usage: $0 php_script [args...]" + +[ "X${PLESK_INSTALLER_DEBUG}" = "X" ] || set -x +[ "X${PLESK_INSTALLER_STRICT_MODE}" = "X" ] || set -e + +php_bin= + +lookup() +{ + [ -z "$php_bin" ] || return + + local paths="$1" + local name="$2" + + for path in $paths; do + if [ -x "$path/$name" ]; then + php_bin="$path/$name" + break + fi + done +} + +lookup "/usr/local/psa/admin/bin /opt/psa/admin/bin" "php" +lookup "/usr/local/psa/bin /opt/psa/bin" "sw-engine-pleskrun" + +[ -n "$php_bin" ] || \ + die "Unable to locate the sw-engine PHP interpreter to execute the script. Make sure that Parallels Plesk Panel is installed on this server." + +exec "${php_bin}" "$@" diff --git a/root/parallels/pool/PSA_18.0.72_17583/examiners/py_launcher.sh b/root/parallels/pool/PSA_18.0.72_17583/examiners/py_launcher.sh new file mode 100755 index 0000000000..96dc215391 --- /dev/null +++ b/root/parallels/pool/PSA_18.0.72_17583/examiners/py_launcher.sh @@ -0,0 +1,30 @@ +#!/bin/sh +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +die() +{ + echo "$*" + exit 1 +} + +[ -f "$1" ] || die "Usage: $0 PEX [args...]" + +[ "X${PLESK_INSTALLER_DEBUG}" = "X" ] || set -x +[ "X${PLESK_INSTALLER_STRICT_MODE}" = "X" ] || set -e + +find_python_bin() +{ + local bin + for bin in "/opt/psa/bin/py3-python" "/usr/local/psa/bin/py3-python" "/usr/libexec/platform-python" "/usr/bin/python3" "/opt/psa/bin/python" "/usr/local/psa/bin/python" "/usr/bin/python2"; do + [ -x "$bin" ] || continue + python_bin="$bin" + return 0 + done + + return 1 +} + +find_python_bin || + die "Unable to locate Python interpreter to execute the script." + +exec "$python_bin" "$@" diff --git a/root/parallels/pool/PSA_18.0.72_17583/examiners/repository_check.sh b/root/parallels/pool/PSA_18.0.72_17583/examiners/repository_check.sh new file mode 100755 index 0000000000..090f121ea1 --- /dev/null +++ b/root/parallels/pool/PSA_18.0.72_17583/examiners/repository_check.sh @@ -0,0 +1,782 @@ +#!/bin/bash +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +[ -z "$PLESK_INSTALLER_DEBUG" ] || set -x +[ -z "$PLESK_INSTALLER_STRICT_MODE" ] || set -e + +export LC_ALL=C +unset GREP_OPTIONS + +RET_SUCCESS=0 +RET_WARN=1 +RET_FATAL=2 + +is_function_defined() +{ + local fn="$1" + case "$(type $fn 2>/dev/null)" in + *function*) + return 0 + ;; + esac + return 1 +} + +# @params are tags in format "key=value" +# Report body (human readable information) is read from stdin +# and copied to stderr. +make_error_report() +{ + local report_file="${PLESK_INSTALLER_ERROR_REPORT:-}" + + local python_bin= + for bin in "/opt/psa/bin/python" "/usr/local/psa/bin/python" "/usr/bin/python2" "/opt/psa/bin/py3-python" "/usr/local/psa/bin/py3-python" "/usr/libexec/platform-python" "/usr/bin/python3"; do + if [ -x "$bin" ]; then + python_bin="$bin" + break + fi + done + + if [ -n "$report_file" -a -x "$python_bin" ]; then + "$python_bin" -c 'import sys, json +report_file = sys.argv[1] +error = sys.stdin.read() + +sys.stderr.write(error) + +data = { + "error": error, +} + +for tag in sys.argv[2:]: + k, v = tag.split("=", 1) + data[k] = v + +with open(report_file, "a") as f: + json.dump(data, f) + f.write("\n") +' "$report_file" "date=$(date --utc --iso-8601=ns)" "$@" + else + cat - >&2 + fi +} + +detect_platform() +{ + . /etc/os-release + os_name="$ID" + os_version="${VERSION_ID%%.*}" + os_arch="$(uname -m)" + if [ -e /etc/debian_version ]; then + case "$os_arch" in + x86_64) pkg_arch="amd64" ;; + aarch64) pkg_arch="arm64" ;; + esac + if [ -n "$VERSION_CODENAME" ]; then + os_codename="$VERSION_CODENAME" + else + case "$os_name$os_version" in + debian10) os_codename="buster" ;; + debian11) os_codename="bullseye" ;; + debian12) os_codename="bookworm" ;; + ubuntu18) os_codename="bionic" ;; + ubuntu20) os_codename="focal" ;; + ubuntu22) os_codename="jammy" ;; + ubuntu24) os_codename="noble" ;; + esac + fi + fi + + case "$os_name$os_version" in + rhel7|centos7|cloudlinux7|virtuozzo7) + package_manager="yum" + ;; + rhel*|centos*|cloudlinux*|almalinux*|rocky*) + package_manager="dnf" + ;; + debian*|ubuntu*) + package_manager="apt" + ;; + esac + + if [ "$os_name" = "ubuntu" -o "$os_name" = "debian" ]; then + PRODUCT_ROOT_D="/opt/psa" + else + PRODUCT_ROOT_D="/usr/local/psa" + fi +} + +has_os_impl_function() +{ + local prefix="$1" + local fn="${prefix}_${os_name}${os_version}" + is_function_defined "$fn" +} + +call_os_impl_function() +{ + local prefix="$1" + shift + local fn="${prefix}_${os_name}${os_version}" + "$fn" "$@" +} + +skip_checker_on_flag() +{ + local name="$1" + local flag="$2" + + if [ -f "$flag" ]; then + echo "$name was skipped due to flag file." >&2 + exit $RET_SUCCESS + fi +} + +skip_checker_on_env() +{ + local name="$1" + local env="$2" + + if [ -n "$env" ]; then + echo "$name was skipped due to environment variable." >&2 + exit $RET_SUCCESS + fi +} + +checker_main() +{ + local fnprefix="$1" + shift + + detect_platform + # try to execute checker only if all attributes are detected + [ -n "$os_name" -a -n "$os_version" ] || return $RET_SUCCESS + + for checker in "${fnprefix}_${os_name}${os_version}" "${fnprefix}_${os_name}" "${fnprefix}"; do + if is_function_defined "$checker"; then + local rc=$RET_SUCCESS + "$checker" "$@" || rc=$? + [ "$(( $rc & $RET_FATAL ))" = "0" ] || return $RET_FATAL + [ "$(( $rc & $RET_WARN ))" = "0" ] || return $RET_WARN + return $rc + fi + done + return $RET_SUCCESS +} + +#!/bin/sh +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +# If env variable PLESK_INSTALLER_ERROR_REPORT=path_to_file is specified then in case of error +# repository_check.sh writes single line json report into it with the following fields: +# - "stage": "repositorycheck" +# - "level": "error" +# - "errtype" is one of the following: +# * "reponotcached" - repository is not cached (mostly due to unavailability). +# * "reponotenabled" - required repository is not enabled. +# * "reponotsupported" - unsupported repository is enabled. +# * "configmanagernotinstalled" - dnf config-manager is disabled. +# - "repo": repository name. +# - "date": time of error occurance ("2020-03-24T06:59:43,127545441+0000") +# - "error": human readable error message. + +report_no_repo() +{ + local repo="$1" + + make_error_report 'stage=repositorycheck' 'level=error' 'errtype=reponotenabled' "repo=$repo" <<-EOL + Plesk installation requires '$repo' OS repository to be enabled. + Make sure it is available and enabled, then try again. + EOL +} + +report_no_repo_cache() +{ + local repo="$1" + + make_error_report 'stage=repositorycheck' 'level=error' 'errtype=reponotcached' "repo=$repo" <<-EOL + Unable to create $package_manager cache for '$repo' OS repository. + Make sure the repository is available, otherwise either disable it or fix its configuration, then try again. + EOL +} + +report_unsupported_repo() +{ + local repo="$1" + + make_error_report 'stage=repositorycheck' 'level=error' 'errtype=reponotsupported' "repo=$repo" <<-EOL + Plesk installation doesn't support '$repo' OS repository. + Make sure it is disabled, then try again. + EOL +} + +report_rh_no_config_manager() +{ + local target + case "$package_manager" in + yum) + target="yum-utils package" + ;; + dnf) + target="config-manager dnf plugin" + ;; + esac + + make_error_report 'stage=repositorycheck' 'level=error' 'errtype=configmanagernotinstalled' <<-EOL + Failed to install $target. + Make sure repositories configuration of $package_manager package manager is correct + (use '$package_manager repolist --verbose' to get its actual state), then try again. + EOL +} + +check_rh_broken_repos() +{ + local rh_enabled_repos rh_available_repos + + # 1. `yum repolist` and `dnf repolist` list all repos + # which were enabled before last cache creation + # even if cache for them was not created. + # If some repo is misconfigured and cache was created with `skip_if_unavailable=1` + # then such repo will be listed anyway despite on cache state. + # If some repo was enabled after last cache creation + # then `repolist --cacheonly` will fail. + # 2. `yum repolist --verbose` and `dnf repoinfo` list only repos + # which were successfully cached before. + # These commands fail if at least one repo is not available + # and the 'skip_if_unavailable' flag is not set. + case "$package_manager" in + yum) + rh_enabled_repos="$( + { + yum repolist enabled --cacheonly -q 2>/dev/null \ + || yum repolist enabled -q --setopt='*.skip_if_unavailable=1' + } | sed -n -e '1d' -e 's/^\*\?!\?\([^/[:space:]]\+\).*/\1/p' + )" || return $RET_FATAL + + rh_available_repos="$( + yum repolist enabled --verbose --cacheonly -q --setopt='*.skip_if_unavailable=1' \ + | sed -n -e 's/^Repo-id\s*:\s*\([^/[:space:]]\+\).*/\1/p' + )" || return $RET_FATAL + ;; + dnf) + rh_enabled_repos="$( + { + dnf repolist --enabled --cacheonly -q 2>/dev/null \ + || dnf repolist --enabled -q --setopt='*.skip_if_unavailable=1' + } | sed -n -e '1d' -e 's/^!\?\(\S\+\).*/\1/p' + )" || return $RET_FATAL + + rh_available_repos="$( \ + dnf repoinfo --enabled --cacheonly -q --setopt='*.skip_if_unavailable=1' \ + | sed -n -e 's|^Repo-id\s*:\s*\(\S\+\)\s*$|\1|p' + )" || return $RET_FATAL + ;; + esac + + local rh_enabled_repos_f="$(mktemp /tmp/plesk-installer.preupgrade_checker.XXXXXX)" + echo "$rh_enabled_repos" | sort > "$rh_enabled_repos_f" + local rh_available_repos_f="$(mktemp /tmp/plesk-installer.preupgrade_checker.XXXXXX)" + echo "$rh_available_repos" | sort > "$rh_available_repos_f" + + local repo rc=0 + for repo in $(comm -23 "$rh_enabled_repos_f" "$rh_available_repos_f"); do + report_no_repo_cache "$repo" + rc=$RET_WARN + done + + rm -f "$rh_enabled_repos_f" "$rh_available_repos_f" + + return $rc +} + +has_rh_enabled_repo() +{ + local repo="$1" + + # Try to get list of repos from cache first. + # If some repo was enabled after last cache creation + # or some repo is unavailable the query from cache will fail. + # Try to fetch actual metadata in this case. + case "$package_manager" in + yum) + # Repo-id may end with OS version and/or architecture + # if baseurl of the repo refers to $releasever and/or $basearch variables + # eg 'epel/7/x86_64', 'epel/7', 'epel/x86_64' + { + yum repolist enabled --verbose --cacheonly -q 2>/dev/null \ + || yum repolist enabled --verbose -q --setopt='*.skip_if_unavailable=1' + } | grep -E -q "^Repo-id\s*: $repo(/.+)?\s*$" + ;; + dnf) + # note: --noplugins may cause failure and empty output on RedHat + { + dnf repoinfo --enabled --cacheonly -q 2>/dev/null \ + || dnf repoinfo --enabled -q --setopt='*.skip_if_unavailable=1' + } | grep -E -q "^Repo-id\s*: $repo\s*$" + ;; + esac +} + +has_rh_config_manager() +{ + case "$package_manager" in + yum) yum-config-manager --help >/dev/null 2>&1 ;; + dnf) dnf config-manager --help >/dev/null 2>&1 ;; + esac +} + +install_rh_config_manager() +{ + case "$package_manager" in + yum) yum install --disablerepo 'PLESK_*' -q -y 'yum-utils' --setopt='*.skip_if_unavailable=1' ;; + dnf) dnf install --disablerepo 'PLESK_*' -q -y 'dnf-command(config-manager)' --setopt='*.skip_if_unavailable=1' ;; + esac +} + +check_rh_config_manager() +{ + if ! has_rh_config_manager && ! install_rh_config_manager; then + report_rh_no_config_manager + return $RET_FATAL + fi +} + +enable_rh_repo() +{ + case "$package_manager" in + yum) yum-config-manager --enable "$@" && has_rh_enabled_repo "$@" ;; + dnf) dnf config-manager --set-enabled "$@" && has_rh_enabled_repo "$@" ;; + esac +} + +enable_sm_repo() +{ + ! has_rh_enabled_repo "$@" || return 0 + subscription-manager repos --enable "$@" || return $? + # On RedHat 8 above command may return 0 on failure with "Repositories disabled by configuration." + has_rh_enabled_repo "$@" +} + +check_epel() +{ + ! enable_rh_repo "epel" || return 0 + + # try to install epel-release from centos/extras or plesk/thirdparty repo + # and then try to update it to last version shipped by epel itself + # to make package upgradable with pum + "$package_manager" install --disablerepo 'PLESK_*' -q -y 'epel-release' --setopt='*.skip_if_unavailable=1' 2>/dev/null \ + || "$package_manager" install --disablerepo='*' --enablerepo 'PLESK_18_*-thirdparty' -q -y 'epel-release' \ + || "$package_manager" install -q -y "https://dl.fedoraproject.org/pub/epel/epel-release-latest-$os_version.noarch.rpm" \ + && "$package_manager" update -q -y 'epel-release' --setopt='*.skip_if_unavailable=1' 2>/dev/null + + # Ensure any other EPEL repos have cache for subsequent check for broken repos (AL9) + local epel_repos="$( + [ "$package_manager" != "dnf" ] || { + dnf repolist --enabled --cacheonly -q 2>/dev/null || + dnf repolist --enabled -q --setopt='*.skip_if_unavailable=1' + } | sed -n -e '1d' -e 's/^!\?\(epel\S\+\).*/\1/p' + )" + for repo in $epel_repos; do + "$package_manager" makecache --repo "$repo" -q + done + + ! has_rh_enabled_repo "epel" || return 0 + + report_no_repo "epel" + return $RET_FATAL +} + +check_codeready() +{ + local repo_rhel="codeready-builder-for-rhel-$os_version-$os_arch-rpms" + local repo_rhui="codeready-builder-for-rhel-$os_version-rhui-rpms" + local repo_rhui_alt="codeready-builder-for-rhel-$os_version-$os_arch-rhui-rpms" + local repo_rhui_alt2="rhui-codeready-builder-for-rhel-$os_version-$os_arch-rhui-rpms" + + ! enable_sm_repo "$repo_rhel" || return 0 + ! enable_rh_repo "$repo_rhui" || return 0 + ! enable_rh_repo "$repo_rhui_alt" || return 0 + ! enable_rh_repo "$repo_rhui_alt2" || return 0 + + report_no_repo "$repo_rhel" + return $RET_FATAL +} + +check_optional() +{ + local repo_rhel="rhel-$os_version-server-optional-rpms" + local repo_rhui="rhel-$os_version-server-rhui-optional-rpms" + + ! enable_sm_repo "$repo_rhel" || return 0 + ! enable_rh_repo "$repo_rhui" || return 0 + + report_no_repo "$repo_rhel" + return $RET_FATAL +} + +check_repos_rhel9() +{ + check_rh_config_manager || return $? + + local rc=0 + + check_epel || rc="$(( $rc | $? ))" + check_codeready || rc="$(( $rc | $? ))" + check_rh_broken_repos || rc="$(( $rc | $? ))" + + return $rc +} + +check_repos_almalinux9() +{ + check_rh_config_manager || return $? + + local rc=0 + check_epel || rc="$(( $rc | $? ))" + check_rh_broken_repos || rc="$(( $rc | $? ))" + + # powertools is renamed to crb since AlmaLinux 9 + ! enable_rh_repo "crb" || return $rc + + report_no_repo "crb" + return $RET_FATAL +} + +check_repos_cloudlinux9() +{ + check_repos_almalinux9 "$@" +} + +check_repos_almalinux10() +{ + check_repos_almalinux9 "$@" +} + +check_repos_centos8() +{ + check_rh_config_manager || return $? + + local rc=0 + check_epel || rc="$(( $rc | $? ))" + check_rh_broken_repos || rc="$(( $rc | $? ))" + + # names of repos are lowercased since 8.3 + ! enable_rh_repo "powertools" || return $rc + ! enable_rh_repo "PowerTools" || return $rc + + report_no_repo "powertools" + return $RET_FATAL +} + +check_repos_cloudlinux8() +{ + check_rh_config_manager || return $? + + local rc=0 + check_epel || rc="$(( $rc | $? ))" + check_rh_broken_repos || rc="$(( $rc | $? ))" + + # names of repos are changed since 8.5 + ! enable_rh_repo "powertools" || return $rc + ! enable_rh_repo "cloudlinux-PowerTools" || return $rc + + report_no_repo "powertools" + return $RET_FATAL +} + +check_repos_rhel8() +{ + check_rh_config_manager || return $? + + local rc=0 + check_epel || rc="$(( $rc | $? ))" + check_rh_broken_repos || rc="$(( $rc | $? ))" + + [ "$1" = "install" ] || return $rc + + check_codeready || rc="$(( $rc | $? ))" + + return $rc +} + +check_repos_almalinux8() +{ + check_repos_centos8 "$@" +} + +check_repos_rocky8() +{ + check_repos_centos8 "$@" +} + +check_repos_rhel7() +{ + check_rh_config_manager || return $? + + local rc=0 + + check_epel || rc="$(( $rc | $? ))" + check_optional || rc="$(( $rc | $? ))" + check_rh_broken_repos || rc="$(( $rc | $? ))" + + return $rc +} + +check_repos_centos7_based() +{ + check_rh_config_manager || return $? + + local rc=0 + + check_epel || rc="$(( $rc | $? ))" + check_rh_broken_repos || rc="$(( $rc | $? ))" + + return $rc +} + +sed_escape() +{ + # Note: this is not a full implementation + echo -n "$1" | sed -e 's|\.|\\.|g' +} + +switch_eol_centos_repos() +{ + local old_mirrorlist_host="mirrorlist.centos.org" + local old_host="mirror.centos.org" + local new_host="vault.centos.org" + + grep -qFw "$old_host" /etc/yum.repos.d/CentOS-*.repo 2>/dev/null || return 0 + local backup="`mktemp -d "/tmp/yum.repos.d-$(date --rfc-3339=date)-XXXXXX"`" + ! [ -d "$backup" ] || cp -raT /etc/yum.repos.d "$backup" || : + + sed -i \ + -e "s|^\s*\(mirrorlist\b[^/]*//`sed_escape "$old_mirrorlist_host"`/.*\)$|#\1|" \ + -e "s|^#*\s*baseurl\b\([^/]*\)//`sed_escape "$old_host"`/\(.*\)$|baseurl\1//$new_host/\2|" \ + /etc/yum.repos.d/CentOS-*.repo + echo "YUM package manager repositories were backed up to '$backup' and switched from $old_host to $new_host ." >&2 +} + +check_repos_centos7() +{ + switch_eol_centos_repos + + check_repos_centos7_based "$@" +} + +check_repos_cloudlinux7() +{ + check_repos_centos7_based "$@" +} + +check_repos_virtuozzo7() +{ + check_repos_centos7_based "$@" +} + +find_apt_repo() +{ + local repo="$1" + + local dist_tag= + ! [ "$os_name" = "ubuntu" ] || dist_tag="a" + ! [ "$os_name" = "debian" ] || dist_tag="n" + + if [ -z "$_apt_cache_policy" ]; then + # extract info of each available release as a string which consists of 'tag=value' + # filter out releases with priority less or equal to 100 + _apt_cache_policy="$( + apt-cache policy \ + | grep "b=$pkg_arch" \ + | grep -Eo '([a-z]=[^,]+,?)*' \ + )" + fi + + local l="$(echo "$repo" | cut -f1 -d'/')" + local d="$(echo "$repo" | cut -f2 -d'/')" + local c="$(echo "$repo" | cut -f3 -d'/')" + + # try to find releases by distribution and component + echo "$_apt_cache_policy" \ + | grep -E "(^|,)l=$l(,|$)" \ + | grep -E "(^|,)$dist_tag=$d(,|$)" \ + | grep -E "(^|,)c=$c(,|$)" \ + | while IFS="$(printf '\n')" read rel && [ -n "$rel" ]; do + l="$(echo "$rel" | grep -Eo "(^|,)l=[^,]+" | cut -f2 -d"=")" + d="$(echo "$rel" | grep -Eo "(^|,)$dist_tag=[^,]+" | cut -f2 -d"=")" + c="$(echo "$rel" | grep -Eo "(^|,)c=[^,]+" | cut -f2 -d"=")" + echo "$l/$d/$c" + done +} + +apt_install_packages() +{ + DEBIAN_FRONTEND=noninteractive LANG=C PATH=/usr/sbin:/usr/bin:/sbin:/bin \ + apt-get -qq --assume-yes -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold -o APT::Install-Recommends=no \ + install "$@" +} + +# Takes a list of suites and disables them in APT sources. +# Multiline deb822 format is supported. +disable_apt_suites_deb822() +{ + local python3=/usr/bin/python3 + + "$python3" -c 'import aptsources.sourceslist' 2>/dev/null || + apt_install_packages python3-apt + + "$python3" -c ' +import sys + +from aptsources.sourceslist import SourcesList + + +suites_to_disable=set(sys.argv[1:]) + +sources_list = SourcesList(deb822=True) + +sources_changed = False +for src in sources_list: + if src.invalid: + continue + suites = getattr(src, "suites", ()) + if not suites: + continue + new_suites = [s for s in suites if s not in suites_to_disable] + if len(new_suites) != len(suites): + sources_changed = True + if len(new_suites) == 0: + src.disabled = True + else: + src.suites = new_suites + +if sources_changed: + sources_list.save() +' "$@" + + # Since we have changed the repositories list, we should re-read _apt_cache_policy on a next call + # of the find_apt_repo function. Hence we have to reset the value of the variable + _apt_cache_policy="" +} + +disable_apt_repo() +{ + local repos_to_disable="$(find_apt_repo "$1" | cut -d '/' -f 2,3 | sort | uniq)" + if [ -z "$repos_to_disable" ]; then + return 0 + fi + + echo "$repos_to_disable" \ + | while IFS= read -r repo_to_disable && [ -n "$repo_to_disable" ]; do + local distrib=${repo_to_disable%%/*} + local component=${repo_to_disable##*/} + find /etc/apt -name "*.list" -exec \ + sed -i -e "/^\s*#/! s/.*\s$distrib\s\+$component\b/# &/" {} + + done + + # Since we have changed the repositories list, we should re-read _apt_cache_policy on a next call + # of the find_apt_repo function. Hence we have to reset the value of the variable + _apt_cache_policy="" + + return 0 +} + +check_required_apt_repo() +{ + local repo="$1" + [ -z "$(find_apt_repo "$repo")" ] || return 0 + report_no_repo "$repo" + return $RET_FATAL +} + +check_unsupported_apt_repos_ubuntu() +{ + [ -n "$os_codename" ] || return 0 + local mode="$1" + + local repos="$( + find_apt_repo "Ubuntu/[^,]+/[^,]+" | grep -v "Ubuntu/$os_codename.*/.*" + find_apt_repo "Debian[^,]*/[^,]+/[^,]+" + )" + [ -n "$repos" ] || return 0 + + echo "$repos" | while IFS="$(printf '\n')" read repo; do + report_unsupported_repo "$repo" + done + + [ "$mode" = "install" ] || return $RET_WARN + return $RET_FATAL +} + +check_repos_ubuntu18() +{ + [ -n "$os_codename" ] || return 0 + local mode="$1" + local rc=0 + + check_required_apt_repo "Ubuntu/$os_codename/main" || rc="$(( $rc | $? ))" + check_required_apt_repo "Ubuntu/$os_codename/universe" || rc="$(( $rc | $? ))" + check_required_apt_repo "Ubuntu/$os_codename-updates/main" || rc="$(( $rc | $? ))" + check_required_apt_repo "Ubuntu/$os_codename-updates/universe" || rc="$(( $rc | $? ))" + check_unsupported_apt_repos_ubuntu "$mode" || rc="$(( $rc | $? ))" + + return $rc +} + + +check_repos_ubuntu() +{ + [ -n "$os_codename" ] || return 0 + local mode="$1" + local rc=0 + + check_required_apt_repo "Ubuntu/$os_codename/main" || rc="$(( $rc | $? ))" + check_required_apt_repo "Ubuntu/$os_codename/universe" || rc="$(( $rc | $? ))" + check_unsupported_apt_repos_ubuntu "$mode" || rc="$(( $rc | $? ))" + + return $rc +} + +check_unsupported_apt_repos_debian() +{ + [ -n "$os_codename" ] || return 0 + local mode="$1" + + local repos="$( + find_apt_repo "Debian Backports/$os_codename-backports/[^,]+" + find_apt_repo "Debian[^,]*/[^,]+/[^,]+" | grep -v "Debian.*/$os_codename.*/.*" + find_apt_repo "Ubuntu/[^,]+/[^,]+" + )" + [ -n "$repos" ] || return 0 + + echo "$repos" | while IFS="$(printf '\n')" read repo; do + report_unsupported_repo "$repo" + done + + [ "$mode" = "install" ] || return $RET_WARN + return $RET_FATAL +} + +check_repos_debian() +{ + [ -n "$os_codename" ] || return 0 + local mode="$1" + local rc=0 + + if [ "$os_name" = "debian" -a "$os_version" -ge 12 ]; then + disable_apt_suites_deb822 "$os_codename-backports" + else + disable_apt_repo "Debian Backports/$os_codename-backports/[^,]+" + fi + + check_required_apt_repo "Debian/$os_codename/main" || rc="$(( $rc | $? ))" + check_unsupported_apt_repos_debian "$mode" || rc="$(( $rc | $? ))" + + return $rc +} + +# --- + +skip_checker_on_flag "Repository check" "/tmp/plesk-installer-skip-repository-check.flag" + +checker_main 'check_repos' "$1" diff --git a/root/parallels/pool/PSA_18.0.72_17583/examiners/save-installation-info.php b/root/parallels/pool/PSA_18.0.72_17583/examiners/save-installation-info.php new file mode 100644 index 0000000000..748049f4fd --- /dev/null +++ b/root/parallels/pool/PSA_18.0.72_17583/examiners/save-installation-info.php @@ -0,0 +1,61 @@ +allowed_commands = [ + [ + CU_OPT_LONG => 'save', + CU_OPT_PARAM => false, + CU_OPT_DESC => 'Save info about Plesk installation', + ], + ]; + + $this->allowed_options = [ + [ + CU_OPT_LONG => 'mode', + CU_OPT_PARAM => true, + ], + [ + CU_OPT_LONG => 'preset', + CU_OPT_PARAM => true, + ], + [ + CU_OPT_LONG => 'arguments', + CU_OPT_PARAM => true, + ], + ]; + } + + protected function _saveCommand($mode, $preset, $arguments) + { + put_param('installation_mode', $this->getMode($mode)); + put_param('installation_preset', $preset); + put_param('installation_arguments', $arguments); + put_param('installation_finish', time()); + } + + private function getMode($mode) + { + if (!$this->os->isUnix()) { + return $mode; + } + if (empty(getenv('PLESK_ONE_CLICK_INSTALLER'))) { + return $mode; + } + return 'ONE_CLICK'; + } +} + +$app = new InstallationInfo(); +$app->runFromCli(); diff --git a/root/parallels/pool/PSA_18.0.72_17583/examiners/sh_cmd.sh b/root/parallels/pool/PSA_18.0.72_17583/examiners/sh_cmd.sh new file mode 100755 index 0000000000..ed95d0acbb --- /dev/null +++ b/root/parallels/pool/PSA_18.0.72_17583/examiners/sh_cmd.sh @@ -0,0 +1,7 @@ +#!/bin/sh +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +[ "X${PLESK_INSTALLER_DEBUG}" = "X" ] || set -x +[ "X${PLESK_INSTALLER_STRICT_MODE}" = "X" ] || set -e + +exec "$@" diff --git a/root/parallels/pool/PSA_18.0.72_17583/examiners/tune_memory_swap.sh b/root/parallels/pool/PSA_18.0.72_17583/examiners/tune_memory_swap.sh new file mode 100755 index 0000000000..daea81631e --- /dev/null +++ b/root/parallels/pool/PSA_18.0.72_17583/examiners/tune_memory_swap.sh @@ -0,0 +1,287 @@ +#!/bin/bash +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +[ -z "$PLESK_INSTALLER_DEBUG" ] || set -x +[ -z "$PLESK_INSTALLER_STRICT_MODE" ] || set -e + +export LC_ALL=C +unset GREP_OPTIONS + +RET_SUCCESS=0 +RET_WARN=1 +RET_FATAL=2 + +is_function_defined() +{ + local fn="$1" + case "$(type $fn 2>/dev/null)" in + *function*) + return 0 + ;; + esac + return 1 +} + +detect_platform() +{ + . /etc/os-release + os_name="$ID" + os_version="${VERSION_ID%%.*}" + os_arch="$(uname -m)" + if [ -e /etc/debian_version ]; then + case "$os_arch" in + x86_64) pkg_arch="amd64" ;; + aarch64) pkg_arch="arm64" ;; + esac + if [ -n "$VERSION_CODENAME" ]; then + os_codename="$VERSION_CODENAME" + else + case "$os_name$os_version" in + debian10) os_codename="buster" ;; + debian11) os_codename="bullseye" ;; + debian12) os_codename="bookworm" ;; + ubuntu18) os_codename="bionic" ;; + ubuntu20) os_codename="focal" ;; + ubuntu22) os_codename="jammy" ;; + ubuntu24) os_codename="noble" ;; + esac + fi + fi + + case "$os_name$os_version" in + rhel7|centos7|cloudlinux7|virtuozzo7) + package_manager="yum" + ;; + rhel*|centos*|cloudlinux*|almalinux*|rocky*) + package_manager="dnf" + ;; + debian*|ubuntu*) + package_manager="apt" + ;; + esac + + if [ "$os_name" = "ubuntu" -o "$os_name" = "debian" ]; then + PRODUCT_ROOT_D="/opt/psa" + else + PRODUCT_ROOT_D="/usr/local/psa" + fi +} + +checker_main() +{ + local fnprefix="$1" + shift + + detect_platform + # try to execute checker only if all attributes are detected + [ -n "$os_name" -a -n "$os_version" ] || return $RET_SUCCESS + + for checker in "${fnprefix}_${os_name}${os_version}" "${fnprefix}_${os_name}" "${fnprefix}"; do + if is_function_defined "$checker"; then + local rc=$RET_SUCCESS + "$checker" "$@" || rc=$? + [ "$(( $rc & $RET_FATAL ))" = "0" ] || return $RET_FATAL + [ "$(( $rc & $RET_WARN ))" = "0" ] || return $RET_WARN + return $rc + fi + done + return $RET_SUCCESS +} + +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +# echo message to product log and console (always visible) +pp_echo() +{ + if [ -n "$product_log" ] ; then + echo "$@" >> "$product_log" 2>&1 + fi + echo "$@" >&2 +} + +detect_vz() +{ + [ -z "$PLESK_VZ_RESULT" ] || return $PLESK_VZ_RESULT + + PLESK_VZ_RESULT=1 + PLESK_VZ=0 + PLESK_VE_HW_NODE=0 + PLESK_VZ_TYPE= + + local issue_file="/etc/issue" + local vzcheck_file="/proc/self/status" + [ -f "$vzcheck_file" ] || return 1 + + local env_id=`sed -ne 's|^envID\:[[:space:]]*\([[:digit:]]\+\)$|\1|p' "$vzcheck_file"` + [ -n "$env_id" ] || return 1 + if [ "$env_id" = "0" ]; then + # Either VZ/OpenVZ HW node or unjailed CloudLinux + PLESK_VE_HW_NODE=1 + return 1 + fi + + if grep -q "CloudLinux" "$issue_file" >/dev/null 2>&1 ; then + return 1 + fi + + if [ -f "/proc/vz/veredir" ]; then + PLESK_VZ_TYPE="vz" + elif [ -d "/proc/vz" ]; then + PLESK_VZ_TYPE="openvz" + fi + + PLESK_VZ=1 + PLESK_VZ_RESULT=0 + return 0 +} + +# detects lxc and docker containers +detect_lxc() +{ + [ -z "$PLESK_LXC_RESULT" ] || return $PLESK_LXC_RESULT + PLESK_LXC_RESULT=1 + PLESK_LXC=0 + if { [ -f /proc/1/cgroup ] && grep -q 'docker\|lxc' /proc/1/cgroup; } || \ + { [ -f /proc/1/environ ] && cat /proc/1/environ | tr \\0 \\n | grep -q "container=lxc"; }; + then + PLESK_LXC_RESULT=0 + PLESK_LXC=1 + fi + return "$PLESK_LXC_RESULT" +} +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. +# vim:ft=sh + +set_file_swap_params() +{ + local pleskswaprc='/etc/pleskswaprc' + [ ! -f "$pleskswaprc" ] || . /etc/pleskswaprc + [ -n "$PLESK_SWAP_PATH" ] || PLESK_SWAP_PATH='/pleskswap' + [ -n "$PLESK_SWAP_SIZE" ] || PLESK_SWAP_SIZE='1G' + [ -n "$PLESK_REQUIRED_MEMORY" ] || PLESK_REQUIRED_MEMORY='1G' + + FSTAB='/etc/fstab' +} + +file_swap_is_required() +{ + local total_mem_mib=$(LC_ALL=C LANG=C free -m -t | awk '/^Total:/ { print $2 }') + local required_mem_mib="`units2units $PLESK_REQUIRED_MEMORY M`" + [ "$total_mem_mib" -lt "$required_mem_mib" ] || return 1 + pp_echo "Total amount of memory is less than minimal required size (${total_mem_mib}M < ${required_mem_mib}M)" + return 0 +} + +file_swap_is_switched_off() +{ + case "${PLESK_SWAP:-}" in + 0|false|disable) + pp_echo "Swapfile creation is disabled: envirinment vaiable \$PLESK_SWAP='$PLESK_SWAP'." + return 0 + ;; + esac + + if [ -f "/etc/pleskswapdisable" ]; then + pp_echo "Swapfile creation is disabled: file '/etc/pleskswapdisable' is present." + return 0 + fi + + detect_vz + if [ "$PLESK_VZ" = "1" ]; then + pp_echo "Swapfile creation is disabled: installation into Virtuozzo container." + return 0 + fi + + detect_lxc + if [ "$PLESK_LXC" = "1" ]; then + pp_echo "Swapfile creation is disabled: installation into Docker/LXC container." + return 0 + fi + + return 1 +} + +file_swap_enable() +{ + if file_swap_status; then + echo "Error: Plesk swapfile is already enabled." >&2 + return 1 + fi + + local swap_size_mb="`units2units $PLESK_SWAP_SIZE M`" + + pp_echo "===> Enable swapfile in $PLESK_SWAP_PATH" + dd if=/dev/zero of="$PLESK_SWAP_PATH" bs=1M count="$swap_size_mb" status=none || return 1 + chmod 0600 "$PLESK_SWAP_PATH" || return 1 + mkswap "$PLESK_SWAP_PATH" || return 1 + if ! grep -qw "^$PLESK_SWAP_PATH" "${FSTAB}"; then + cp -f "${FSTAB}" "${FSTAB}.saved_by_plesk" + echo "$PLESK_SWAP_PATH none swap sw 0 0" >> "${FSTAB}" + fi + if swapon "$PLESK_SWAP_PATH"; then + rm -f "${FSTAB}.saved_by_plesk" + return 0 + else + [ ! -f "${FSTAB}.saved_by_plesk" ] || mv -f "${FSTAB}.saved_by_plesk" "${FSTAB}" + return 1 + fi +} + +file_swap_status() +{ + [ -f "$PLESK_SWAP_PATH" ] || return 1 + grep -qw "^$PLESK_SWAP_PATH" "${FSTAB}" || return 1 + return 0 +} + +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +units2units() { + local bytes + local n="${1%%[^0-9]*}" + case "${1,,}" in + *[0-9]) bytes="$1" ;; + *k|*kib) bytes="$(( $n * 1024 ** 1 ))" ;; + *m|*mib) bytes="$(( $n * 1024 ** 2 ))" ;; + *g|*gib) bytes="$(( $n * 1024 ** 3 ))" ;; + *t|*tib) bytes="$(( $n * 1024 ** 4 ))" ;; + *kb) bytes="$(( $n * 1000 ** 1 ))" ;; + *mb) bytes="$(( $n * 1000 ** 2 ))" ;; + *gb) bytes="$(( $n * 1000 ** 3 ))" ;; + *tb) bytes="$(( $n * 1000 ** 4 ))" ;; + *) echo "units2units: incorrect value '$1'" >&2; exit 1 ;; + esac + case "${2,,}" in + k|kib) echo $(( $bytes / 1024 ** 1 )) ;; + m|mib) echo $(( $bytes / 1024 ** 2 )) ;; + g|gib) echo $(( $bytes / 1024 ** 3 )) ;; + t|tib) echo $(( $bytes / 1024 ** 4 )) ;; + kb) echo $(( $bytes / 1000 ** 1 )) ;; + mb) echo $(( $bytes / 1000 ** 2 )) ;; + gb) echo $(( $bytes / 1000 ** 3 )) ;; + tb) echo $(( $bytes / 1000 ** 4 )) ;; + "") echo $bytes ;; + *) echo "Unknown unit: $2" >&2; exit 1 ;; + esac +} + +#!/bin/sh +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +tune_memory_swap() +{ + local mode="$1" + [ "$mode" = 'install' ] || return 0 # clean install only + set_file_swap_params + file_swap_is_required || return 0 + ! file_swap_is_switched_off || return 0 # disabled by admin + ! file_swap_status || return 0 # already enabled + if ! file_swap_enable; then + pp_echo "Failed to enable swapfile. Installation may fail or freeze due to insufficient memory." + return "$RET_WARN" + fi +} + +product_log= +product_problems_log= +checker_main 'tune_memory_swap' "$1" diff --git a/root/parallels/pool/PSA_18.0.72_17583/plesk-18.0.72-ubt24.04-x86_64.inf3 b/root/parallels/pool/PSA_18.0.72_17583/plesk-18.0.72-ubt24.04-x86_64.inf3 new file mode 100644 index 0000000000..41a00736ae --- /dev/null +++ b/root/parallels/pool/PSA_18.0.72_17583/plesk-18.0.72-ubt24.04-x86_64.inf3 @@ -0,0 +1,927 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -----BEGIN PGP PUBLIC KEY BLOCK----- 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 VNnMRY8n =Hv0N -----END PGP PUBLIC KEY BLOCK----- + + + psa + + + + + + + + + + + + + + + + + + + + + + + + + + + + + mysqlgroup + l10n + proftpd + webservers + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + imapservers + + + + + + + + + + + + + + + + + + + imapservers + + + + + + + + + + + + + + mailman + spamassassin + drweb + sophos + courier + dovecot + + + + + + + + + + + + + + + + + + + + + + mailservers + + + + + + + + + + + + + + + + + + + + mailservers + + + + + + + + + + + + + + + + + + panel + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + + + + + + + + php8.3 + + + + + + + + + + + webservers + + + + + + + + + + + + + + + + + + + + + panel + + + + + + + + + + + + + panel + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + panel + + + + + + + + + + + + + + + + + + + + panel + + + + + + + panel + + + + + + + + panel + + + + + + + panel + passenger + + + + + + ruby + + + + + + + panel + + + + + + panel + + + + + + panel + passenger + + + + + + + + + + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + panel + roundcube + postfix + dovecot + mod_fcgid + proftpd + webalizer + awstats + webservers + nginx + mysqlgroup + l10n + bind + wp-toolkit + advisor + git + xovi + imunify360 + fail2ban + modsecurity + sslit + letsencrypt + repair-kit + composer + monitoring + log-browser + ssh-terminal + site-import + sitejet + ntp-timesync + php8.3 + php8.4 + mfa + configurations-troubleshooter + + + panel + roundcube + postfix + dovecot + mod_fcgid + proftpd + webalizer + awstats + webservers + nginx + mysqlgroup + l10n + bind + wp-toolkit + advisor + git + xovi + imunify360 + fail2ban + modsecurity + sslit + letsencrypt + repair-kit + composer + monitoring + log-browser + ssh-terminal + site-import + sitejet + ntp-timesync + php8.1 + php8.2 + php8.3 + php8.4 + mfa + configurations-troubleshooter + resctrl + drweb + postgresql + spamassassin + ruby + gems-pre + nodejs + pmm + psa-firewall + watchdog + passenger + phpgroup + sophos + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/pool/PSA_18.0.72_17583/release.inf3 b/root/parallels/pool/PSA_18.0.72_17583/release.inf3 new file mode 100644 index 0000000000..7cf5f49dae --- /dev/null +++ b/root/parallels/pool/PSA_18.0.72_17583/release.inf3 @@ -0,0 +1,35 @@ + + + + + + + + + psa + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/pool/PSA_18.0.73_17652/release.inf3 b/root/parallels/pool/PSA_18.0.73_17652/release.inf3 new file mode 100644 index 0000000000..4fb000283d --- /dev/null +++ b/root/parallels/pool/PSA_18.0.73_17652/release.inf3 @@ -0,0 +1,36 @@ + + + + + + + + + psa + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/pool/PSA_18.0.73_17686/release.inf3 b/root/parallels/pool/PSA_18.0.73_17686/release.inf3 new file mode 100644 index 0000000000..4fb000283d --- /dev/null +++ b/root/parallels/pool/PSA_18.0.73_17686/release.inf3 @@ -0,0 +1,36 @@ + + + + + + + + + psa + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/pool/PSA_18.0.73_17695/release.inf3 b/root/parallels/pool/PSA_18.0.73_17695/release.inf3 new file mode 100644 index 0000000000..4fb000283d --- /dev/null +++ b/root/parallels/pool/PSA_18.0.73_17695/release.inf3 @@ -0,0 +1,36 @@ + + + + + + + + + psa + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/pool/PSA_18.0.73_17725/examiners/check_broken_timezone.sh b/root/parallels/pool/PSA_18.0.73_17725/examiners/check_broken_timezone.sh new file mode 100755 index 0000000000..ee862642be --- /dev/null +++ b/root/parallels/pool/PSA_18.0.73_17725/examiners/check_broken_timezone.sh @@ -0,0 +1,255 @@ +#!/bin/bash +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +[ -z "$PLESK_INSTALLER_DEBUG" ] || set -x +[ -z "$PLESK_INSTALLER_STRICT_MODE" ] || set -e + +export LC_ALL=C +unset GREP_OPTIONS + +RET_SUCCESS=0 +RET_WARN=1 +RET_FATAL=2 + +is_function_defined() +{ + local fn="$1" + case "$(type $fn 2>/dev/null)" in + *function*) + return 0 + ;; + esac + return 1 +} + +# @params are tags in format "key=value" +# Report body (human readable information) is read from stdin +# and copied to stderr. +make_error_report() +{ + local report_file="${PLESK_INSTALLER_ERROR_REPORT:-}" + + local python_bin= + for bin in "/opt/psa/bin/python" "/usr/local/psa/bin/python" "/usr/bin/python2" "/opt/psa/bin/py3-python" "/usr/local/psa/bin/py3-python" "/usr/libexec/platform-python" "/usr/bin/python3"; do + if [ -x "$bin" ]; then + python_bin="$bin" + break + fi + done + + if [ -n "$report_file" -a -x "$python_bin" ]; then + "$python_bin" -c 'import sys, json +report_file = sys.argv[1] +error = sys.stdin.read() + +sys.stderr.write(error) + +data = { + "error": error, +} + +for tag in sys.argv[2:]: + k, v = tag.split("=", 1) + data[k] = v + +with open(report_file, "a") as f: + json.dump(data, f) + f.write("\n") +' "$report_file" "date=$(date --utc --iso-8601=ns)" "$@" + else + cat - >&2 + fi +} + +detect_platform() +{ + . /etc/os-release + os_name="$ID" + os_version="${VERSION_ID%%.*}" + os_arch="$(uname -m)" + if [ -e /etc/debian_version ]; then + case "$os_arch" in + x86_64) pkg_arch="amd64" ;; + aarch64) pkg_arch="arm64" ;; + esac + if [ -n "$VERSION_CODENAME" ]; then + os_codename="$VERSION_CODENAME" + else + case "$os_name$os_version" in + debian10) os_codename="buster" ;; + debian11) os_codename="bullseye" ;; + debian12) os_codename="bookworm" ;; + ubuntu18) os_codename="bionic" ;; + ubuntu20) os_codename="focal" ;; + ubuntu22) os_codename="jammy" ;; + ubuntu24) os_codename="noble" ;; + esac + fi + fi + + case "$os_name$os_version" in + rhel7|centos7|cloudlinux7|virtuozzo7) + package_manager="yum" + ;; + rhel*|centos*|cloudlinux*|almalinux*|rocky*) + package_manager="dnf" + ;; + debian*|ubuntu*) + package_manager="apt" + ;; + esac + + if [ "$os_name" = "ubuntu" -o "$os_name" = "debian" ]; then + PRODUCT_ROOT_D="/opt/psa" + else + PRODUCT_ROOT_D="/usr/local/psa" + fi +} + +has_os_impl_function() +{ + local prefix="$1" + local fn="${prefix}_${os_name}${os_version}" + is_function_defined "$fn" +} + +call_os_impl_function() +{ + local prefix="$1" + shift + local fn="${prefix}_${os_name}${os_version}" + "$fn" "$@" +} + +skip_checker_on_flag() +{ + local name="$1" + local flag="$2" + + if [ -f "$flag" ]; then + echo "$name was skipped due to flag file." >&2 + exit $RET_SUCCESS + fi +} + +skip_checker_on_env() +{ + local name="$1" + local env="$2" + + if [ -n "$env" ]; then + echo "$name was skipped due to environment variable." >&2 + exit $RET_SUCCESS + fi +} + +checker_main() +{ + local fnprefix="$1" + shift + + detect_platform + # try to execute checker only if all attributes are detected + [ -n "$os_name" -a -n "$os_version" ] || return $RET_SUCCESS + + for checker in "${fnprefix}_${os_name}${os_version}" "${fnprefix}_${os_name}" "${fnprefix}"; do + if is_function_defined "$checker"; then + local rc=$RET_SUCCESS + "$checker" "$@" || rc=$? + [ "$(( $rc & $RET_FATAL ))" = "0" ] || return $RET_FATAL + [ "$(( $rc & $RET_WARN ))" = "0" ] || return $RET_WARN + return $rc + fi + done + return $RET_SUCCESS +} + +#!/bin/sh +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +# If env variable PLESK_INSTALLER_ERROR_REPORT=path_to_file is specified then in case of error +# check-broken-tz.sh writes single line json report into it with the following fields: +# - "stage": "timezonefix" +# - "level": "error" +# - "errtype": "failure" +# - "date": time of error occurance ("2024-07-24T06:59:43,127545441+0000") +# - "error": human readable error message + +report_dpkg_configure_fail() +{ + local pkgname="$1" + make_error_report 'stage=timezonefix' 'level=error' 'errtype=dpkgconfigurefailed' <<-EOL + Could not configure the packages ( $pkgname ). See https://support.plesk.com/hc/en-us/articles/24721507961623-Plesk-provides-error-on-update-Package-tzdata-is-not-configured-yet for more details. + EOL +} + +report_get_tz_fail() +{ + make_error_report 'stage=timezonefix' 'level=error' 'errtype=gettzfailed' <<-EOL + Could not get the system timezone. See https://support.plesk.com/hc/en-us/articles/24721507961623-Plesk-provides-error-on-update-Package-tzdata-is-not-configured-yet for more details. + EOL +} + +report_set_tz_fail() +{ + local tz="$1" + + make_error_report 'stage=timezonefix' 'level=error' 'errtype=settzfailed' <<-EOL + Could not set the system timezone ( $tz ). See https://support.plesk.com/hc/en-us/articles/24721507961623-Plesk-provides-error-on-update-Package-tzdata-is-not-configured-yet for more details. + EOL +} + +get_current_tz() +{ + [ -L /etc/localtime ] || return 1 + + local tz + tz="$(readlink -m /etc/localtime)" || return 1 + [ -f "$tz" ] || return 1 + case "$tz" in + /usr/share/zoneinfo/*) ;; + *) return 1;; + esac + tz="${tz#/usr/share/zoneinfo/}" + [ -n "$tz" ] || return 1 + + echo -n "${tz}" +} + +check_timezone_ubuntu() +{ + [ -n "$os_codename" ] || return 0 + local mode="$1" + + # PPP-65676: Plesk update fails on ubuntu if timezone is CET + if dpkg-query --showformat='${db:Status-Status}\n' --show 'tzdata' | grep -wq 'half-configured'; then + local origtz + origtz=$(get_current_tz) + if [ $? != 0 ]; then + report_get_tz_fail + return $RET_WARN + fi + if ! timedatectl set-timezone 'Etc/UTC'; then + timedatectl set-timezone "$origtz" + report_set_tz_fail 'Etc/UTC' + return $RET_WARN + fi + if ! dpkg --configure 'tzdata'; then + timedatectl set-timezone "$origtz" + report_dpkg_configure_fail 'tzdata' + return $RET_WARN + fi + if ! timedatectl set-timezone "$origtz"; then + report_set_tz_fail "$origtz" + return $RET_WARN + fi + fi + + return 0 +} + +# --- + +skip_checker_on_flag "Broken timezone check" "/tmp/plesk-installer-skip-check-broken-timezone.flag" + +checker_main 'check_timezone' "$1" diff --git a/root/parallels/pool/PSA_18.0.73_17725/examiners/congratulations.sh b/root/parallels/pool/PSA_18.0.73_17725/examiners/congratulations.sh new file mode 100755 index 0000000000..907c5ba782 --- /dev/null +++ b/root/parallels/pool/PSA_18.0.73_17725/examiners/congratulations.sh @@ -0,0 +1,50 @@ +#!/bin/bash +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +out() +{ + echo -e "\t$*" >&2 +} + +print_urls() +{ + plesk login 2>/dev/null | sed -e $'s|^|\t * |' >&2 +} + +print_congratulations() +{ + local mode="$1" # 'install' or 'upgrade' + local process= + [ "$mode" = "install" ] && process="installation" || process="upgrade" + + out + out " Congratulations!" + out + out "The $process has been finished. Plesk is now running on your server." + out + if [ "$mode" = "install" ]; then + out "To complete the configuration process, browse either of URLs:" + print_urls + out + fi + out "Use the username 'admin' to log in. To log in as 'admin', use the 'plesk login' command." + out "You can also log in as 'root' using your 'root' password." + out + out "Use the 'plesk' command to manage the server. Run 'plesk help' for more info." + out + out "Use the following commands to start and stop the Plesk web interface:" + out "'systemctl start psa.service' and 'systemctl stop psa.service' respectively." + out + if [ "$mode" = "install" ]; then + out "If you would like to migrate your subscriptions from other hosting panel" + out "or older Plesk version to this server, please check out our assistance" + out "options: https://www.plesk.com/professional-services/" + out + fi +} + +unset GREP_OPTIONS + +print_congratulations "$1" +# Force showing text when used as AI post-examiner +exit 1 diff --git a/root/parallels/pool/PSA_18.0.73_17725/examiners/disk_space_check.sh b/root/parallels/pool/PSA_18.0.73_17725/examiners/disk_space_check.sh new file mode 100755 index 0000000000..1fdfb44037 --- /dev/null +++ b/root/parallels/pool/PSA_18.0.73_17725/examiners/disk_space_check.sh @@ -0,0 +1,542 @@ +#!/bin/bash +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +[ -z "$PLESK_INSTALLER_DEBUG" ] || set -x +[ -z "$PLESK_INSTALLER_STRICT_MODE" ] || set -e + +export LC_ALL=C +unset GREP_OPTIONS + +RET_SUCCESS=0 +RET_WARN=1 +RET_FATAL=2 + +is_function_defined() +{ + local fn="$1" + case "$(type $fn 2>/dev/null)" in + *function*) + return 0 + ;; + esac + return 1 +} + +# @params are tags in format "key=value" +# Report body (human readable information) is read from stdin +# and copied to stderr. +make_error_report() +{ + local report_file="${PLESK_INSTALLER_ERROR_REPORT:-}" + + local python_bin= + for bin in "/opt/psa/bin/python" "/usr/local/psa/bin/python" "/usr/bin/python2" "/opt/psa/bin/py3-python" "/usr/local/psa/bin/py3-python" "/usr/libexec/platform-python" "/usr/bin/python3"; do + if [ -x "$bin" ]; then + python_bin="$bin" + break + fi + done + + if [ -n "$report_file" -a -x "$python_bin" ]; then + "$python_bin" -c 'import sys, json +report_file = sys.argv[1] +error = sys.stdin.read() + +sys.stderr.write(error) + +data = { + "error": error, +} + +for tag in sys.argv[2:]: + k, v = tag.split("=", 1) + data[k] = v + +with open(report_file, "a") as f: + json.dump(data, f) + f.write("\n") +' "$report_file" "date=$(date --utc --iso-8601=ns)" "$@" + else + cat - >&2 + fi +} + +detect_platform() +{ + . /etc/os-release + os_name="$ID" + os_version="${VERSION_ID%%.*}" + os_arch="$(uname -m)" + if [ -e /etc/debian_version ]; then + case "$os_arch" in + x86_64) pkg_arch="amd64" ;; + aarch64) pkg_arch="arm64" ;; + esac + if [ -n "$VERSION_CODENAME" ]; then + os_codename="$VERSION_CODENAME" + else + case "$os_name$os_version" in + debian10) os_codename="buster" ;; + debian11) os_codename="bullseye" ;; + debian12) os_codename="bookworm" ;; + ubuntu18) os_codename="bionic" ;; + ubuntu20) os_codename="focal" ;; + ubuntu22) os_codename="jammy" ;; + ubuntu24) os_codename="noble" ;; + esac + fi + fi + + case "$os_name$os_version" in + rhel7|centos7|cloudlinux7|virtuozzo7) + package_manager="yum" + ;; + rhel*|centos*|cloudlinux*|almalinux*|rocky*) + package_manager="dnf" + ;; + debian*|ubuntu*) + package_manager="apt" + ;; + esac + + if [ "$os_name" = "ubuntu" -o "$os_name" = "debian" ]; then + PRODUCT_ROOT_D="/opt/psa" + else + PRODUCT_ROOT_D="/usr/local/psa" + fi +} + +has_os_impl_function() +{ + local prefix="$1" + local fn="${prefix}_${os_name}${os_version}" + is_function_defined "$fn" +} + +call_os_impl_function() +{ + local prefix="$1" + shift + local fn="${prefix}_${os_name}${os_version}" + "$fn" "$@" +} + +skip_checker_on_flag() +{ + local name="$1" + local flag="$2" + + if [ -f "$flag" ]; then + echo "$name was skipped due to flag file." >&2 + exit $RET_SUCCESS + fi +} + +skip_checker_on_env() +{ + local name="$1" + local env="$2" + + if [ -n "$env" ]; then + echo "$name was skipped due to environment variable." >&2 + exit $RET_SUCCESS + fi +} + +checker_main() +{ + local fnprefix="$1" + shift + + detect_platform + # try to execute checker only if all attributes are detected + [ -n "$os_name" -a -n "$os_version" ] || return $RET_SUCCESS + + for checker in "${fnprefix}_${os_name}${os_version}" "${fnprefix}_${os_name}" "${fnprefix}"; do + if is_function_defined "$checker"; then + local rc=$RET_SUCCESS + "$checker" "$@" || rc=$? + [ "$(( $rc & $RET_FATAL ))" = "0" ] || return $RET_FATAL + [ "$(( $rc & $RET_WARN ))" = "0" ] || return $RET_WARN + return $rc + fi + done + return $RET_SUCCESS +} + +#!/bin/sh +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +# If env variable PLESK_INSTALLER_ERROR_REPORT=path_to_file is specified then in case of error +# disk_space_check.sh writes single line json report into it with the following fields: +# - "stage": "diskspacecheck" +# - "level": "error" +# - "errtype": "notenoughdiskspace" +# - "volume": volume with not enough diskspace (e.g. "/") +# - "required": required diskspace on the volume, human readable (e.g. "600 MB") +# - "available": available diskspace on the volume, human readable (e.g. "255 MB") +# - "needtofree": amount of diskspace which should be freed on the volume, human readable (e.g. "345 MB") +# - "date": time of error occurance ("2020-03-24T06:59:43,127545441+0000") +# - "error": human readable error message ("There is not enough disk space available in the / directory.") + +# Required values below for Full installation are in MB. See 'du -cs -BM /*' and 'df -Pm'. + +required_disk_space_cloudlinux7() +{ + case "$1" in + /opt) echo 900 ;; + /usr) echo 4400 ;; + /var) echo 600 ;; + /tmp) echo 100 ;; + esac +} + +required_disk_space_cloudlinux8() +{ + case "$1" in + /opt) echo 1200 ;; + /usr) echo 4400 ;; + /var) echo 700 ;; + /tmp) echo 100 ;; + esac +} + +required_disk_space_centos7() +{ + case "$1" in + /opt) echo 900 ;; + /usr) echo 4100 ;; + /var) echo 600 ;; + /tmp) echo 100 ;; + esac +} + +required_disk_space_centos8() +{ + case "$1" in + /opt) echo 900 ;; + /usr) echo 4500 ;; + /var) echo 800 ;; + /tmp) echo 100 ;; + esac +} + +required_disk_space_virtuozzo7() +{ + required_disk_space_centos7 "$1" +} + +required_disk_space_rhel7() +{ + required_disk_space_centos7 "$1" +} + +required_disk_space_rhel8() +{ + required_disk_space_centos8 "$1" +} + +required_disk_space_almalinux8() +{ + required_disk_space_centos8 "$1" +} + +required_disk_space_rocky8() +{ + required_disk_space_centos8 "$1" +} + +required_disk_space_rhel9() +{ + case "$1" in + /opt) echo 500 ;; + /usr) echo 4000 ;; + /var) echo 800 ;; + /tmp) echo 100 ;; + esac +} + +required_disk_space_almalinux9() +{ + required_disk_space_rhel9 "$1" +} + +required_disk_space_almalinux10() +{ + required_disk_space_almalinux9 "$1" +} + +required_disk_space_cloudlinux9() +{ + required_disk_space_rhel9 "$1" +} + +required_disk_space_debian10() +{ + case "$1" in + /opt) echo 1800 ;; + /usr) echo 2300 ;; + /var) echo 1700 ;; + /tmp) echo 100 ;; + esac +} + +required_disk_space_debian11() +{ + case "$1" in + /opt) echo 1500 ;; + /usr) echo 3100 ;; + /var) echo 1800 ;; + /tmp) echo 100 ;; + esac +} + +required_disk_space_debian12() +{ + case "$1" in + /opt) echo 2700 ;; + /usr) echo 2500 ;; + /var) echo 2200 ;; + /tmp) echo 100 ;; + esac +} + +required_disk_space_debian13() +{ + case "$1" in + /opt) echo 2700 ;; + /usr) echo 2500 ;; + /var) echo 2200 ;; + /tmp) echo 100 ;; + esac +} + +required_disk_space_ubuntu18() +{ + case "$1" in + /opt) echo 900 ;; + /usr) echo 1800 ;; + /var) echo 600 ;; + /tmp) echo 100 ;; + esac +} + +required_disk_space_ubuntu20() +{ + case "$1" in + /opt) echo 1800 ;; + /usr) echo 2900 ;; + /var) echo 1600 ;; + /tmp) echo 100 ;; + esac +} + +required_disk_space_ubuntu22() +{ + case "$1" in + /opt) echo 1800 ;; + /usr) echo 3900 ;; + /var) echo 1900 ;; + /tmp) echo 100 ;; + esac +} + +required_disk_space_ubuntu24() +{ + case "$1" in + /opt) echo 3200 ;; + /usr) echo 1800 ;; + /var) echo 2400 ;; + /tmp) echo 100 ;; + esac +} + +required_update_upgrade_disk_space() +{ + case "$1" in + /opt) echo 100 ;; + /usr) echo 300 ;; + /var) echo 600 ;; + /tmp) echo 100 ;; + esac +} + +clean_tmp() +{ + local volume="$1" + local path="/tmp" + is_path_on_volume "$path" "$volume" || return 0 + + echo "Cleaning $path via 'systemd-tmpfiles --clean --prefix $path'" + systemd-tmpfiles --clean --prefix "$path" 2>&1 +} + +clean_yum() +{ + local volume="$1" + local path="/var/cache/yum" + is_path_on_volume "$path" "$volume" || return 0 + + echo "Cleaning $path via 'yum clean all'" + yum clean all 2>&1 + + # The command above doesn't clean untracked repos (missing in configuration), clean if left > 2 Mb + [ "`du -sm "$path" | awk '{ print $1 }'`" -gt 2 ] || return 0 + echo "Cleaning $path via 'rm -rf $path/*'" + rm -rf "$path"/* 2>&1 +} + +clean_dnf() +{ + local volume="$1" + local path="/var/cache/dnf" + is_path_on_volume "$path" "$volume" || return 0 + + echo "Cleaning $path via 'dnf clean all'" + dnf clean all 2>&1 +} + +clean_apt() +{ + local volume="$1" + local path="/var/cache/apt" + is_path_on_volume "$path" "$volume" || return 0 + + echo "Cleaning $path via 'apt-get clean'" + apt-get clean 2>&1 +} + +clean_journal() +{ + local volume="$1" + local path="/var/log/journal" + is_path_on_volume "$path" "$volume" || return 0 + + # Note that --rotate may cause more space to be freed, but may also cause more space to be used + echo "Cleaning $path via 'journalctl --vacuum-time 1d'" + journalctl --vacuum-time 1d 2>&1 +} + +clean_ext_packages() +{ + local volume="$1" + local path="$PRODUCT_ROOT_D/var/modules-packages" + is_path_on_volume "$path" "$volume" || return 0 + + echo "Cleaning $path via 'rm -rf $path/*'" + rm -rf "$path"/* 2>&1 +} + +# @param $1 target directory +mount_point() +{ + df -Pm $1 | awk 'NR==2 { print $6 }' +} + +# @param $1 target directory +available_disk_space() +{ + df -Pm $1 | awk 'NR==2 { print $4 }' +} + +is_path_on_volume() +{ + local path="$1" + local volume="$2" + [ -d "$path" ] && [ "`mount_point "$path"`" = "$volume" ] +} + +# @param $1 target directory +# @param $2 mode (install/upgrade/update) +req_disk_space() +{ + if [ "$2" != "install" ]; then + required_update_upgrade_disk_space "$1" + return + fi + + has_os_impl_function "required_disk_space" || { + echo "There are no requirements defined for $os_name$os_version." >&2 + echo "Disk space check cannot be performed." >&2 + exit $RET_WARN + } + call_os_impl_function "required_disk_space" "$1" +} + +human_readable_size() +{ + echo "$1" | awk ' + function human(x) { + s = "MGTEPYZ"; + while (x >= 1000 && length(s) > 1) { + x /= 1024; s = substr(s, 2); + } + # 0.05 below will make sure the value is rounded up + return sprintf("%.1f %sB", x + 0.05, substr(s, 1, 1)); + } + { print human($1); }' +} + +# @param $1 target directory +# @param $2 required disk space +# @param $3 check only flag (don't emit errors) +check_available_disk_space() +{ + local volume="$1" + local required="$2" + local check_only="${3:-}" + local available="$(available_disk_space "$volume")" + if [ "$available" -lt "$required" ]; then + local needtofree + needtofree="`human_readable_size $((required - available))`" + [ -n "$check_only" ] || + make_error_report 'stage=diskspacecheck' 'level=error' 'errtype=notenoughdiskspace' \ + "volume=$volume" "required=$required MB" "available=$available MB" "needtofree=$needtofree" \ + <<-EOL + There is not enough disk space available in the $1 directory. + You need to free up $needtofree. + EOL + return "$RET_FATAL" + fi +} + +# @param $1 target directory +# @param $2 required disk space +clean_and_check_available_disk_space() +{ + if [ -n "$PLESK_INSTALLER_FORCE_CLEAN_DISK_SPACE" ] || ! check_available_disk_space "$@" --check-only; then + clean_disk_space "$1" + check_available_disk_space "$@" + fi +} + +# Cleans up disk space on the volume +clean_disk_space() +{ + local volume="$1" + for cleanup_func in clean_tmp clean_yum clean_dnf clean_apt clean_journal clean_ext_packages; do + "$cleanup_func" "$volume" + done +} + +# @param $1 mode (install/upgrade/update) +clean_and_check_disk_space() +{ + local mode="$1" + local shared=0 + + for target_directory in /opt /usr /var /tmp; do + local required=$(req_disk_space "$target_directory" "$mode") + [ -n "$required" ] || return "$RET_WARN" + + if is_path_on_volume "$target_directory" "/"; then + shared="$((shared + required))" + else + clean_and_check_available_disk_space "$target_directory" "$required" || return $? + fi + done + + clean_and_check_available_disk_space "/" "$shared" || return $? +} + +checker_main 'clean_and_check_disk_space' "$1" diff --git a/root/parallels/pool/PSA_18.0.73_17725/examiners/license_key_check.php b/root/parallels/pool/PSA_18.0.73_17725/examiners/license_key_check.php new file mode 100644 index 0000000000..917e44709f --- /dev/null +++ b/root/parallels/pool/PSA_18.0.73_17725/examiners/license_key_check.php @@ -0,0 +1,111 @@ += 10.0.0 */ +if (!is_array($vers)) { + $vers = [$vers]; +} + +$match = false; +foreach ($vers as $ver) { + if (!is_array($ver)) { + $match |= strtok($ver, ".") == strtok($targetVersion, "."); + } else { + $match |= ("any" == $ver[0] || version_compare($ver[0], $targetVersion) <= 0) && + ("any" == $ver[1] || version_compare($ver[1], $targetVersion) >= 0); + } +} + +if ($match) { + fwrite(STDERR, "You do not need to upgrade the current license key.\n"); + fwrite(STDOUT, "License upgrade check to $targetVersion can be skipped.\n"); + fwrite(STDOUT, "Plesk versions compatible with the license key: " . preg_replace('/\n\s*/', '', var_export($vers, true)) . "\n"); + finish(0); +} + +if (!function_exists('ka_is_key_upgrade_available')) { + // Plesk 17.0 + fwrite(STDERR, "Cannot check whether Plesk license key upgrade is available.\n"); + finish(1, false); +} + +$si = getServerInfo(); +$result = ka_is_key_upgrade_available($prod, $targetVersion, $si); + +$isConfused = false; +switch ($result['code']) { + case RESULT_LICENSE_OK: + fwrite(STDERR, "The licensing server accepted the key upgrade request.\n"); + fwrite(STDERR, "License upgrade to $targetVersion is available.\n"); + fwrite(STDERR, "Response from the licensing server: {$result['message']}\n"); + finish(0); + case RESULT_NETWORK_PROBLEM: + fwrite(STDERR, "Unable to connect to the licensing server to check if license upgrade is available.\n"); + fwrite(STDERR, "Error message: {$result['message']}\n"); + finish(2, false); + case RESULT_LICENSE_PROBLEM: + fwrite(STDERR, "Warning: Your Plesk license key cannot be upgraded.\n"); + fwrite(STDERR, "Response from the licensing server: {$result['message']}\n"); + finish(2); + default: + $isConfused = true; + // fall-through + case RESULT_ERROR: + // This includes "Software Update Service (SUS) is not found for the given license key" case, but also many others. + fwrite(STDERR, "Failed to check whether a new license key is available.\n"); + fwrite(STDERR, "Error message: {$result['message']}\n"); + if ($isConfused) { + fwrite(STDERR, "Error code: {$result['code']}\n"); + } + finish(2, !$isConfused); +} diff --git a/root/parallels/pool/PSA_18.0.73_17725/examiners/package_manager_check.sh b/root/parallels/pool/PSA_18.0.73_17725/examiners/package_manager_check.sh new file mode 100755 index 0000000000..b089061d97 --- /dev/null +++ b/root/parallels/pool/PSA_18.0.73_17725/examiners/package_manager_check.sh @@ -0,0 +1,224 @@ +#!/bin/bash +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +[ -z "$PLESK_INSTALLER_DEBUG" ] || set -x +[ -z "$PLESK_INSTALLER_STRICT_MODE" ] || set -e + +export LC_ALL=C +unset GREP_OPTIONS + +RET_SUCCESS=0 +RET_WARN=1 +RET_FATAL=2 + +is_function_defined() +{ + local fn="$1" + case "$(type $fn 2>/dev/null)" in + *function*) + return 0 + ;; + esac + return 1 +} + +# @params are tags in format "key=value" +# Report body (human readable information) is read from stdin +# and copied to stderr. +make_error_report() +{ + local report_file="${PLESK_INSTALLER_ERROR_REPORT:-}" + + local python_bin= + for bin in "/opt/psa/bin/python" "/usr/local/psa/bin/python" "/usr/bin/python2" "/opt/psa/bin/py3-python" "/usr/local/psa/bin/py3-python" "/usr/libexec/platform-python" "/usr/bin/python3"; do + if [ -x "$bin" ]; then + python_bin="$bin" + break + fi + done + + if [ -n "$report_file" -a -x "$python_bin" ]; then + "$python_bin" -c 'import sys, json +report_file = sys.argv[1] +error = sys.stdin.read() + +sys.stderr.write(error) + +data = { + "error": error, +} + +for tag in sys.argv[2:]: + k, v = tag.split("=", 1) + data[k] = v + +with open(report_file, "a") as f: + json.dump(data, f) + f.write("\n") +' "$report_file" "date=$(date --utc --iso-8601=ns)" "$@" + else + cat - >&2 + fi +} + +detect_platform() +{ + . /etc/os-release + os_name="$ID" + os_version="${VERSION_ID%%.*}" + os_arch="$(uname -m)" + if [ -e /etc/debian_version ]; then + case "$os_arch" in + x86_64) pkg_arch="amd64" ;; + aarch64) pkg_arch="arm64" ;; + esac + if [ -n "$VERSION_CODENAME" ]; then + os_codename="$VERSION_CODENAME" + else + case "$os_name$os_version" in + debian10) os_codename="buster" ;; + debian11) os_codename="bullseye" ;; + debian12) os_codename="bookworm" ;; + ubuntu18) os_codename="bionic" ;; + ubuntu20) os_codename="focal" ;; + ubuntu22) os_codename="jammy" ;; + ubuntu24) os_codename="noble" ;; + esac + fi + fi + + case "$os_name$os_version" in + rhel7|centos7|cloudlinux7|virtuozzo7) + package_manager="yum" + ;; + rhel*|centos*|cloudlinux*|almalinux*|rocky*) + package_manager="dnf" + ;; + debian*|ubuntu*) + package_manager="apt" + ;; + esac + + if [ "$os_name" = "ubuntu" -o "$os_name" = "debian" ]; then + PRODUCT_ROOT_D="/opt/psa" + else + PRODUCT_ROOT_D="/usr/local/psa" + fi +} + +has_os_impl_function() +{ + local prefix="$1" + local fn="${prefix}_${os_name}${os_version}" + is_function_defined "$fn" +} + +call_os_impl_function() +{ + local prefix="$1" + shift + local fn="${prefix}_${os_name}${os_version}" + "$fn" "$@" +} + +skip_checker_on_flag() +{ + local name="$1" + local flag="$2" + + if [ -f "$flag" ]; then + echo "$name was skipped due to flag file." >&2 + exit $RET_SUCCESS + fi +} + +skip_checker_on_env() +{ + local name="$1" + local env="$2" + + if [ -n "$env" ]; then + echo "$name was skipped due to environment variable." >&2 + exit $RET_SUCCESS + fi +} + +checker_main() +{ + local fnprefix="$1" + shift + + detect_platform + # try to execute checker only if all attributes are detected + [ -n "$os_name" -a -n "$os_version" ] || return $RET_SUCCESS + + for checker in "${fnprefix}_${os_name}${os_version}" "${fnprefix}_${os_name}" "${fnprefix}"; do + if is_function_defined "$checker"; then + local rc=$RET_SUCCESS + "$checker" "$@" || rc=$? + [ "$(( $rc & $RET_FATAL ))" = "0" ] || return $RET_FATAL + [ "$(( $rc & $RET_WARN ))" = "0" ] || return $RET_WARN + return $rc + fi + done + return $RET_SUCCESS +} + +#!/bin/sh +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +check_package_manager_deb_based() +{ + local output= + output="`dpkg --audit 2>&1`" || output="$output"$'\n'"'dpkg --audit' finished with error code $?." + + if [ -n "$output" ]; then + make_error_report 'stage=packagemanagercheck' 'level=error' 'errtype=brokenpackages' <<-EOL + The system package manager reports the following problems: + + $output + + To continue with the installation, you need to resolve these issues + using the procedure below: + + 1. Make sure you have a full server snapshot. Although the + following steps are usually safe, they can still cause + data loss or irreversible changes. + 2. Run 'dpkg --configure -a'. This command can fix some of the + issues. However, it may fail. Regardless if it fails or not, + proceed with the following steps. + 3. Run 'PLESK_INSTALLER_SKIP_PACKAGE_MANAGER_CHECK=1 plesk installer update --skip-cleanup'. + Instead of 'update', you may need to use the command you used + previously (for example, 'upgrade' or 'install'). + 4. The next step depends on the outcome of the previous one: + - If step 3 was completed with the "You already have the latest + version of product(s) and all the selected components installed. + Installation will not continue." message, + run 'plesk repair installation'. + - If step 3 failed, run 'dpkg --audit'. This command can show you + packages that need to be reinstalled. To reinstall them, run + 'apt-get install --reinstall '. + 5. Run 'plesk installer update' to revert temporary changes and + validate that the issues are resolved. If the command fails or + triggers this check again, contact Plesk support. + + For more information, see + https://support.plesk.com/hc/en-us/articles/12871173047447-Plesk-update-on-Debian-Ubuntu-fails-dpkg-was-interrupted-you-must-manually-run-dpkg-configure-a-to-correct-the-problem + EOL + return "$RET_FATAL" + fi +} + +check_package_manager_debian() +{ + check_package_manager_deb_based +} + +check_package_manager_ubuntu() +{ + check_package_manager_deb_based +} + +skip_checker_on_env "Package manager check" "$PLESK_INSTALLER_SKIP_PACKAGE_MANAGER_CHECK" +skip_checker_on_flag "Package manager check" "/tmp/plesk-installer-skip-package-manager-check.flag" +checker_main 'check_package_manager' "$@" diff --git a/root/parallels/pool/PSA_18.0.73_17725/examiners/panel_preupgrade_checker.php b/root/parallels/pool/PSA_18.0.73_17725/examiners/panel_preupgrade_checker.php new file mode 100644 index 0000000000..181534e61e --- /dev/null +++ b/root/parallels/pool/PSA_18.0.73_17725/examiners/panel_preupgrade_checker.php @@ -0,0 +1,2401 @@ +_checkMainIP(); //:INFO: Checking for main IP address https://support.plesk.com/hc/articles/12377857361687 + $this->_checkMySQLDatabaseUserRoot(); //:INFO: Plesk user "root" for MySQL database servers have not access to phpMyAdmin https://support.plesk.com/hc/articles/12378148229399 + $this->_checkProftpdIPv6(); //:INFO: #94489 FTP service proftpd cannot be started by xinetd if IPv6 is disabled https://support.plesk.com/hc/articles/12377796102807 + $this->_checkSwCollectdIntervalSetting(); //:INFO: #105405 https://support.plesk.com/hc/articles/213362569 + $this->_checkApacheStatus(); + $this->_checkImmutableBitOnPleskFiles(); //:INFO: #128414 https://support.plesk.com/hc/articles/12377589682327 + $this->_checkAbilityToChmodInDumpd(); //:INFO: ERROR while trying to backup MySQL database. https://support.plesk.com/hc/en-us/articles/12377010033943 + $this->_checkIpcollectionReference(); //:INFO: #72751 https://support.plesk.com/hc/articles/12377666752279 + $this->_checkApsApplicationContext(); //:INFO: Broken contexts of the APS applications can lead to errors at building Apache web server configuration https://support.plesk.com/hc/articles/12377671820311 + $this->_checkApsTablesInnoDB(); + $this->_checkCustomWebServerConfigTemplates(); //:INFO: #PPPM-1195 https://support.plesk.com/hc/articles/12377740416151 + $this->_checkMixedCaseDomainIssues(); //:INFO: #PPPM-4284 https://support.plesk.com/hc/en-us/articles/12377171904151 + + if (PleskOS::isDebLike()) { + $this->_checkSymLinkToOptPsa(); //:INFO: Check that symbolic link /usr/local/psa actually exists on Debian-like OSes https://support.plesk.com/hc/articles/12377511731991 + } + + if (Util::isVz()) { + $this->_checkShmPagesLimit(); //:INFO: PSA service does not start. Unable to allocate shared memory segment. https://support.plesk.com/hc/articles/12377744827927 + + if (PleskOS::isRedHatLike()) { + $this->_checkMailDriversConflict(); //:INFO: #PPPM-955 https://support.plesk.com/hc/articles/12378148767895 + } + } + } + + $this->_checkForCryptPasswords(); + $this->_checkMysqlServersTable(); //:INFO: Checking existing table mysql.servers + $this->_checkPleskTCPPorts(); //:INFO: Check the availability of Plesk TCP ports https://support.plesk.com/hc/articles/12377821243159 + + if (Util::isWindows()) { + $this->_checkPhprcSystemVariable(); //:INFO: #PPPM-294 Checking for PHPRC system variable + $this->_unknownISAPIfilters(); //:INFO: Checking for unknown ISAPI filters and show warning https://support.plesk.com/hc/articles/213913765 + $this->_checkMSVCR(); //:INFO: Just warning about possible issues related to Microsoft Visual C++ Redistributable Packages https://support.plesk.com/hc/articles/115000201014 + $this->_checkIisFcgiDllVersion(); //:INFO: Check iisfcgi.dll file version https://support.plesk.com/hc/articles/12378148258199 + $this->_checkCDONTSmailrootFolder(); //:INFO: After upgrade Plesk change permissions on folder of Collaboration Data Objects (CDO) for NTS (CDONTS) to default, https://support.plesk.com/hc/articles/12377887661335 + $this->_checkNullClientLogin(); //:INFO: #118963 https://support.plesk.com/hc/articles/12378122202391 + $this->checkDomainControllerLocation(); //:INFO: https://support.plesk.com/hc/articles/12377107094167 + } + } + + //:INFO: PSA service does not start. Unable to allocate shared memory segment. https://support.plesk.com/hc/articles/12377744827927 + function _checkShmPagesLimit() + { + $log = Log::getInstance("Checking for limit shmpages", true); + $ubc = Util::getUserBeanCounters(); + if ((int)$ubc['shmpages']['limit'] < 40960) { + $log->emergency("Virtuozzo Container set the \"shmpages\" limit to {$ubc['shmpages']['limit']}, which is too low. This may cause the sw-engine service not to start. To resolve this issue, refer to the article at https://support.plesk.com/hc/articles/12377744827927"); + $log->resultWarning(); + return; + } + + $log->resultOk(); + } + + //:INFO: #PPPM-294 + function _checkPhprcSystemVariable() + { + $log = Log::getInstance("Checking for PHPRC system variable", true); + + $phprc = getenv('PHPRC'); + + if ($phprc) { + $log->emergency('The environment variable PHPRC is present in the system. This variable may lead to upgrade failure. Please delete this variable from the system environment.'); + $log->resultWarning(); + return; + } + + $log->resultOk(); + } + + //:INFO: ERROR while trying to backup MySQL database. https://support.plesk.com/hc/en-us/articles/12377010033943 + function _checkAbilityToChmodInDumpd() + { + $log = Log::getInstance("Checking the possibility to change the permissions of files in the DUMP_D directory", true); + + $dump_d = Util::getSettingFromPsaConf('DUMP_D'); + if (is_null($dump_d)) { + $log->warning('Unable to obtain the path to the directory defined by the DUMP_D parameter. Check that the DUMP_D parameter is set in the /etc/psa/psa.conf file.'); + $log->resultWarning(); + return; + } + + $file = $dump_d . '/pre_upgrade_test_checkAbilityToChmodInDumpd'; + + if (false === file_put_contents($file, 'test')) { + $log->emergency('Unable to write in the ' . $dump_d . ' directory. The upgrade procedure will fail. Check that the folder exists and you have write permissions for it, and repeat upgrading. '); + $log->resultWarning(); + return; + } else { + $result = @chmod($file, 0600); + unlink($file); + if (!$result) { + $log->emergency( + 'Unable to change the permissions of files in the ' . $dump_d . ' directory. ' + . 'The upgrade procedure will fail. Please refer to https://support.plesk.com/hc/articles/12377010033943 for details.' + ); + $log->resultError(); + return; + } + } + $log->resultOk(); + } + + //:INFO: #128414 https://support.plesk.com/hc/articles/12377589682327 + function _checkImmutableBitOnPleskFiles() + { + $log = Log::getInstance("Checking Panel files for the immutable bit attribute"); + + $cmd = 'lsattr -R /usr/local/psa/ 2>/dev/null |awk \'{split($1, a, ""); if (a[5] == "i") {print;}}\''; + $output = Util::exec($cmd, $code); + $files = explode('\n', $output); + + if (!empty($output)) { + $log->info('The immutable bit attribute of the following Panel files can interrupt the upgrade procedure:'); + foreach ($files as $file) { + $log->info($file); + } + $log->emergency('Files with the immutable bit attribute were found. Please check https://support.plesk.com/hc/articles/12377589682327 for details.'); + $log->resultWarning(); + return; + } + + $log->resultOk(); + } + + //:INFO: #PPPM-1195 https://support.plesk.com/hc/articles/12377740416151 + function _checkCustomWebServerConfigTemplates() + { + $log = Log::getInstance("Checking for custom web server configuration templates"); + $pleskDir = Util::getSettingFromPsaConf('PRODUCT_ROOT_D'); + $customTemplatesPath = $pleskDir . '/admin/conf/templates/custom'; + + if (is_dir($customTemplatesPath)) { + $log->warning("Directory {$customTemplatesPath} for custom web server configuration templates was found. Custom templates might be incompatible with a new Plesk version, and this might lead to failure to generate web server configuration files. Remove the directory to get rid of this warning. " + . "Please check https://support.plesk.com/hc/articles/12377740416151 for details."); + $log->resultWarning(); + return; + } + $log->resultOk(); + } + + //:INFO: #PPPM-955 https://support.plesk.com/hc/articles/12378148767895 + function _checkMailDriversConflict() + { + $log = Log::getInstance("Checking for a Plesk mail drivers conflict"); + + if (((true === PackageManager::isInstalled('psa-mail-pc-driver') || true === PackageManager::isInstalled('plesk-mail-pc-driver')) + && true === PackageManager::isInstalled('psa-qmail')) + || ((true === PackageManager::isInstalled('psa-mail-pc-driver') || true === PackageManager::isInstalled('plesk-mail-pc-driver')) + && true === PackageManager::isInstalled('psa-qmail-rblsmtpd'))) { + $log->warning("Plesk upgrade by EZ templates failed if psa-mail-pc-driver and psa-qmail or psa-qmail-rblsmtpd packages are installed. " + . "Please check https://support.plesk.com/hc/articles/12378148767895 for details."); + $log->resultWarning(); + return; + } + + $log->resultOk(); + } + + //:INFO: #118963 https://support.plesk.com/hc/articles/12378122202391 + function _checkNullClientLogin() + { + $log = Log::getInstance("Checking for accounts with empty user names"); + + $mysql = PleskDb::getInstance(); + $sql = "SELECT domains.id, domains.name, clients.login FROM domains LEFT JOIN clients ON clients.id=domains.cl_id WHERE clients.login is NULL"; + $nullLogins = $mysql->fetchAll($sql); + + if (!empty($nullLogins)) { + $log->warning('There are accounts with empty user names. This problem can cause the backup or migration operation to fail. Please see https://support.plesk.com/hc/articles/12378122202391 for the solution.'); + $log->resultWarning(); + return; + } + + $log->resultOk(); + } + + //:INFO: #105405 https://support.plesk.com/hc/articles/213362569 + function _checkSwCollectdIntervalSetting() + { + $log = Log::getInstance("Checking the 'Interval' parameter in the sw-collectd configuration file"); + + $collectd_config = '/etc/sw-collectd/collectd.conf'; + if (file_exists($collectd_config)) { + if (!is_file($collectd_config) || !is_readable($collectd_config)) + return; + + $config_content = Util::readfileToArray($collectd_config); + if ($config_content) { + foreach ($config_content as $line) { + if (preg_match('/Interval\s*\d+$/', $line, $match)) { + if (preg_match('/Interval\s*10$/', $line, $match)) { + $log->warning('If you leave the default value of the "Interval" parameter in the ' . $collectd_config . ', sw-collectd may heavily load the system. Please see https://support.plesk.com/hc/articles/213362569 for details.'); + $log->resultWarning(); + return; + } + $log->resultOk(); + return; + } + } + $log->warning('If you leave the default value of the "Interval" parameter in the ' . $collectd_config . ', sw-collectd may heavily load the system. Please see https://support.plesk.com/hc/articles/213362569 for details.'); + $log->resultWarning(); + return; + } + } + } + + private function _checkApacheStatus() + { + $log = Log::getInstance("Checking Apache status"); + + $apacheCtl = file_exists('/usr/sbin/apache2ctl') ? '/usr/sbin/apache2ctl' : '/usr/sbin/apachectl'; + + if (!is_executable($apacheCtl)) { + return; + } + + $resultCode = 0; + Util::Exec("$apacheCtl -t 2>/dev/null", $resultCode); + + if (0 !== $resultCode) { + $log->error("The Apache configuration is broken. Run '$apacheCtl -t' to see the detailed info."); + $log->resultError(); + return; + } + + $log->resultOk(); + } + + //:INFO: #72751 https://support.plesk.com/hc/articles/12377666752279 + function _checkIpcollectionReference() + { + $log = Log::getInstance("Checking consistency of the IP addresses list in the Panel database"); + + $mysql = PleskDb::getInstance(); + $sql = "SELECT 1 FROM ip_pool, clients, IpAddressesCollections, domains, DomainServices, IP_Addresses WHERE DomainServices.ipCollectionId = IpAddressesCollections.ipCollectionId AND domains.id=DomainServices.dom_id AND clients.id=domains.cl_id AND ipAddressId NOT IN (select id from IP_Addresses) AND IP_Addresses.id = ip_pool.ip_address_id AND pool_id = ip_pool.id GROUP BY pool_id"; + $brokenIps = $mysql->fetchAll($sql); + $sql = "select 1 from DomainServices, domains, clients, ip_pool where ipCollectionId not in (select IpAddressesCollections.ipCollectionId from IpAddressesCollections) and domains.id=DomainServices.dom_id and clients.id = domains.cl_id and ip_pool.id = clients.pool_id and DomainServices.type='web' group by ipCollectionId"; + $brokenCollections = $mysql->fetchAll($sql); + + if (!empty($brokenIps) || !empty($brokenCollections)) { + $log->warning('Some database entries related to Panel IP addresses are corrupted. Please see https://support.plesk.com/hc/articles/12377666752279 for the solution.'); + $log->resultWarning(); + return; + } + + $log->resultOk(); + } + + //:INFO: Broken contexts of the APS applications can lead to errors at building Apache web server configuration https://support.plesk.com/hc/articles/12377671820311 + function _checkApsApplicationContext() + { + $log = Log::getInstance("Checking installed APS applications"); + $mysql = PleskDb::getInstance(); + $sql = "SELECT * FROM apsContexts WHERE (pleskType = 'hosting' OR pleskType = 'subdomain') AND subscriptionId = 0"; + $brokenContexts = $mysql->fetchAll($sql); + + if (!empty($brokenContexts)) { + $log->warning('Some database entries related to the installed APS applications are corrupted. Please see https://support.plesk.com/hc/articles/12377671820311 for the solution.'); + $log->resultWarning(); + return; + } + $log->resultOk(); + } + + //:INFO: #94489 FTP service proftpd cannot be started by xinetd if IPv6 is disabled https://support.plesk.com/hc/articles/12377796102807 + function _checkProftpdIPv6() + { + $log = Log::getInstance("Checking proftpd settings"); + + $inet6 = '/proc/net/if_inet6'; + if (!file_exists($inet6) || !@file_get_contents($inet6)) { + $proftpd_config = '/etc/xinetd.d/ftp_psa'; + if (!is_file($proftpd_config) || !is_readable($proftpd_config)) + return null; + + $config_content = Util::readfileToArray($proftpd_config); + if ($config_content) { + for ($i=0; $i<=count($config_content)-1; $i++) { + if (preg_match('/flags.+IPv6$/', $config_content[$i], $match)) { + $log->warning('The proftpd FTP service will fail to start in case the support for IPv6 is disabled on the server. Please check https://support.plesk.com/hc/articles/12377796102807 for details.'); + $log->resultWarning(); + return; + } + } + } + } + $log->resultOk(); + } + + //:INFO: Check the availability of Plesk Panel TCP ports + function _checkPleskTCPPorts() + { + $log = Log::getInstance('Checking the availability of Plesk Panel TCP ports'); + + $plesk_ports = array('8880' => 'Plesk Panel non-secure HTTP port', '8443' => 'Plesk Panel secure HTTPS port'); + + $mysql = PleskDb::getInstance(); + $sql = "select ip_address from IP_Addresses"; + $ip_addresses = $mysql->fetchAll($sql); + $warning = false; + if (count($ip_addresses)>0) { + if (Util::isLinux()) { + $ipv4 = Util::getIPv4ListOnLinux(); + $ipv6 = Util::getIPv6ListOnLinux(); + if ($ipv6) { + $ipsInSystem = array_merge($ipv4, $ipv6); + } else { + $ipsInSystem = $ipv4; + } + } else { + $ipsInSystem = Util::getIPListOnWindows(); + } + foreach ($ip_addresses as $ip) { + foreach ($plesk_ports as $port => $description) { + if (PleskValidator::validateIPv4($ip['ip_address']) && in_array($ip['ip_address'], $ipsInSystem)) { + $fp = @fsockopen($ip['ip_address'], $port, $errno, $errstr, 1); + } elseif (PleskValidator::validateIPv6($ip['ip_address']) && in_array($ip['ip_address'], $ipsInSystem)) { + $fp = @fsockopen('[' . $ip['ip_address'] . ']', $port, $errno, $errstr, 1); + } else { + $log->warning('IP address registered in Plesk is invalid or broken: ' . $ip['ip_address']); + $log->resultWarning(); + return; + } + if (!$fp) { + // $errno 110 means "timed out", 111 means "refused" + $log->info('Unable to connect to IP address ' . $ip['ip_address'] . ' on ' . $description . ' ' . $port . ': ' . $errstr); + $warning = true; + } + } + } + } + if ($warning) { + $log->warning('Unable to connect to some Plesk ports. Please see ' . LOG_PATH . ' for details. Find the full list of the required open ports at https://support.plesk.com/hc/articles/12377821243159 '); + $log->resultWarning(); + return; + } + $log->resultOk(); + } + + //:INFO: Plesk user "root" for MySQL database servers have not access to phpMyAdmin https://support.plesk.com/hc/articles/12378148229399 + function _checkMySQLDatabaseUserRoot() + { + $log = Log::getInstance('Checking existence of Plesk user "root" for MariaDB/MySQL database servers'); + + $psaroot = Util::getSettingFromPsaConf('PRODUCT_ROOT_D'); + + if (PleskVersion::is_below_17_9()) { + $phpMyAdminConfFile = $psaroot . '/admin/htdocs/domains/databases/phpMyAdmin/libraries/config.default.php'; + } else { + $phpMyAdminConfFile = $psaroot . '/phpMyAdmin/libraries/config.default.php'; + } + + if (file_exists($phpMyAdminConfFile)) { + $phpMyAdminConfFileContent = file_get_contents($phpMyAdminConfFile); + if (!preg_match("/\[\'AllowRoot\'\]\s*=\s*true\s*\;/", $phpMyAdminConfFileContent)) { + $mysql = PleskDb::getInstance(); + $sql = "select login, data_bases.name as db_name, displayName as domain_name from db_users, data_bases, domains where db_users.db_id = data_bases.id and data_bases.dom_id = domains.id and data_bases.type = 'mysql' and login = 'root'"; + $dbusers = $mysql->fetchAll($sql); + + foreach ($dbusers as $user) { + $log->warning('The database user "' . $user['login'] . '" (database "' . $user['db_name'] . '" at "' . $user['domain_name'] . '") has no access to phpMyAdmin. Please check https://support.plesk.com/hc/articles/12378148229399 for more details.'); + $log->resultWarning(); + return; + } + } + } + + $log->resultOk(); + } + + //:INFO: After upgrade Plesk change permissions on folder of Collaboration Data Objects (CDO) for NTS (CDONTS) to default, https://support.plesk.com/hc/articles/12377887661335 + function _checkCDONTSmailrootFolder() + { + $log = Log::getInstance('Checking for CDONTS mailroot folder'); + + $mailroot = Util::getSystemDisk() . 'inetpub\mailroot\pickup'; + + if (is_dir($mailroot)) { + $log->warning('After upgrade you have to add write permissions to psacln group on folder ' . $mailroot . '. Please, check https://support.plesk.com/hc/articles/12377887661335 for more details.'); + $log->resultWarning(); + return; + } + $log->resultOk(); + } + + //:INFO: Check iisfcgi.dll file version https://support.plesk.com/hc/articles/12378148258199 + function _checkIisFcgiDllVersion() + { + $log = Log::getInstance("Checking the iisfcgi.dll file version"); + + $windir = Util::getSystemRoot(); + $iisfcgi = $windir . '\system32\inetsrv\iisfcgi.dll'; + if (file_exists($iisfcgi)) { + $version = Util::getFileVersion($iisfcgi); + if (version_compare($version, '7.5.0', '>') + && version_compare($version, '7.5.7600.16632', '<')) { + $log->warning('File iisfcgi.dll version ' . $version . ' is outdated. Please, check article https://support.plesk.com/hc/articles/12378148258199 for details'); + return; + } + } + $log->resultOk(); + } + + //:INFO: Checking for main IP address https://support.plesk.com/hc/articles/12377857361687 + function _checkMainIP() + { + $log = Log::getInstance("Checking for main IP address"); + + $mysql = PleskDb::getInstance(); + $sql = 'select * from IP_Addresses'; + $ips = $mysql->fetchAll($sql); + $mainexists = false; + foreach ($ips as $ip) { + if (isset($ip['main'])) { + if ($ip['main'] == 'true') { + $mainexists = true; + } + } else { + $log->info('No field "main" in table IP_Addresses.'); + $log->resultOk(); + return; + } + } + + if (!$mainexists) { + $warn = 'Unable to find "main" IP address in psa database. Please, check https://support.plesk.com/hc/articles/12377857361687 for more details.'; + $log->warning($warn); + $log->resultWarning(); + return; + } + $log->resultOk(); + } + + //:INFO: Checking existing table mysql.servers https://support.plesk.com/hc/articles/12377850098455 + function _checkMysqlServersTable() + { + $log = Log::getInstance('Checking table "servers" in database "mysql"'); + + $mySQLServerVersion = Util::getMySQLServerVersion(); + if (version_compare($mySQLServerVersion, '5.1.0', '>=')) { + $credentials = Util::getDefaultClientMySQLServerCredentials(); + + if (preg_match('/AES-128-CBC/', $credentials['admin_password'])) { + $log->info('The administrator\'s password for the default MariaDB/MySQL server is encrypted.'); + return; + } + + $mysql = new DbClientMysql($credentials['host'], $credentials['admin_login'], $credentials['admin_password'] , 'information_schema', $credentials['port']); + if (!$mysql->hasErrors()) { + $sql = 'SELECT * FROM information_schema.TABLES WHERE TABLE_SCHEMA="mysql" and TABLE_NAME="servers"'; + $servers = $mysql->fetchAll($sql); + if (empty($servers)) { + $warn = 'The table "servers" in the database "mysql" does not exist. Please check https://support.plesk.com/hc/articles/12377850098455 for details.'; + $log->warning($warn); + $log->resultWarning(); + return; + } + } + } + $log->resultOk(); + } + + //:INFO: Check that there is symbolic link /usr/local/psa on /opt/psa on Debian-like Oses https://support.plesk.com/hc/articles/12377511731991 + function _checkSymLinkToOptPsa() + { + $log = Log::getInstance('Checking symbolic link /usr/local/psa on /opt/psa'); + + $link = @realpath('/usr/local/psa/version'); + if (!preg_match('/\/opt\/psa\/version/', $link, $macthes)) { + $warn = "The symbolic link /usr/local/psa does not exist or has wrong destination. Read article https://support.plesk.com/hc/articles/12377511731991 to fix the issue."; + $log->warning($warn); + $log->resultWarning(); + return; + } + $log->resultOk(); + } + + //:INFO: Checking for unknown ISAPI filters and show warning https://support.plesk.com/hc/articles/213913765 + function _unknownISAPIfilters() + { + $log = Log::getInstance('Detecting installed ISAPI filters'); + + if (Util::isUnknownISAPIfilters()) { + $warn = 'Please read carefully article https://support.plesk.com/hc/articles/213913765, for avoiding possible problems caused by unknown ISAPI filters.'; + $log->warning($warn); + $log->resultWarning(); + + return; + } + $log->resultOk(); + } + + //:INFO: Warning about possible issues related to Microsoft Visual C++ Redistributable Packages ?https://support.plesk.com/hc/articles/115000201014 + function _checkMSVCR() + { + $log = Log::getInstance('Microsoft Visual C++ Redistributable Packages'); + + $warn = 'Please read carefully article https://support.plesk.com/hc/articles/115000201014, for avoiding possible problems caused by Microsoft Visual C++ Redistributable Packages.'; + $log->info($warn); + + return; + } + + function _checkForCryptPasswords() + { + //:INFO: Prevent potential problem with E: Couldn't configure pre-depend plesk-core for psa-firewall, probably a dependency cycle. + $log = Log::getInstance('Detecting if encrypted passwords are used'); + + $db = PleskDb::getInstance(); + $sql = "SELECT COUNT(*) AS cnt FROM accounts WHERE type='crypt' AND password not like '$%';"; + $r = $db->fetchAll($sql); + + if ($r[0]['cnt'] != '0') + { + $warn = 'There are ' . $r[0]['cnt'] . ' accounts with passwords encrypted using a deprecated algorithm. Please refer to https://support.plesk.com/hc/articles/12377596588311 for the instructions about how to change the password type to plain.'; + + $log->warning($warn); + $log->resultWarning(); + return; + } + $log->resultOk(); + } + + function _checkApsTablesInnoDB() + { + $log = Log::getInstance('Checking if apsc database tables have InnoDB engine'); + + $db = PleskDb::getInstance(); + $apsDatabase = $db->fetchOne("select val from misc where param = 'aps_database'"); + $sql = "SELECT TABLE_NAME FROM information_schema.TABLES where TABLE_SCHEMA = '$apsDatabase' and ENGINE = 'MyISAM'"; + $myISAMTables = $db->fetchAll($sql); + if (!empty($myISAMTables)) { + $myISAMTablesList = implode(', ', array_map('reset', $myISAMTables)); + $warn = 'The are tables in apsc database with MyISAM engine: ' . $myISAMTablesList . '. It would be updated to InnoDB engine.'; + $log->warning($warn); + $log->resultWarning(); + return; + } + $log->resultOk(); + } + + function _checkMixedCaseDomainIssues() + { + $log = Log::getInstance("Checking for domains with mixed case names", true); + $db = PleskDb::getInstance(); + + + $domains = $db->fetchAll("select id, name, displayName from domains"); + $problemDomains = array(); + foreach ($domains as $domain) { + if (strtolower($domain['name']) == $domain['name']) { + continue; + } + $problemDomains[] = $domain; + } + if (count($problemDomains)) { + $msg = "Found one or more domains with mixed case names. Such domains may have trouble working with the \"FPM application server by Apache\" handler.\n" . + implode("\n", array_map(function($row) { + return "{$row['id']}\t{$row['displayName']}\t{$row['name']}"; + }, $problemDomains)) . "\n" . + "A manual fix can be applied to resolve the issue. Read https://support.plesk.com/hc/en-us/articles/12377171904151 for details."; + $log->warning($msg); + $log->resultWarning(); + return; + } + $log->resultOk(); + } + + private function checkDomainControllerLocation() + { + $log = Log::getInstance("Checking for Active Directory Domain Controller and Plesk on the same server", true); + $cmd = '"' . rtrim(Util::getPleskRootPath(), '\\') . '\admin\bin\serverconf.exe" --list'; + $output = Util::exec($cmd, $code); + if (preg_match("/IS_DOMAIN_CONTROLLER:\s*true/", $output)) { + $log->warning('Active Directory Domain Controller and Plesk are on the same server. Read https://support.plesk.com/hc/articles/12377107094167 for details.'); + $log->resultWarning(); + } else { + $log->resultOk(); + } + } +} + +class Plesk175Requirements +{ + public function validate() + { + if (PleskInstallation::isInstalled() && PleskVersion::is_below_17_5() && Util::isLinux()) { + //:INFO: Check that DUMP_TMP_D is not inside of (or equal to) DUMP_D + $this->_checkDumpTmpD(); + } + } + + public function _checkDumpTmpD() + { + $log = Log::getInstance('Checking the DUMP_TMP_D directory'); + + $dumpD = Util::getSettingFromPsaConf('DUMP_D'); + if (is_null($dumpD)) { + $log->warning('Unable to obtain the path to the directory defined by the DUMP_D parameter. Check that the DUMP_D parameter is set in the /etc/psa/psa.conf file.'); + $log->resultWarning(); + return; + } + $dumpTmpD = Util::getSettingFromPsaConf('DUMP_TMP_D'); + if (is_null($dumpTmpD)) { + $log->warning('Unable to obtain the path to the directory defined by the DUMP_TMP_D parameter. Check that the DUMP_TMP_D parameter is set in the /etc/psa/psa.conf file.'); + $log->resultWarning(); + return; + } + + if (strpos(rtrim($dumpTmpD, '/') . '/', rtrim($dumpD, '/') . '/') === 0) { + $log->error(sprintf('The directory DUMP_TMP_D = %s should not be inside of (or equal to) the directory DUMP_D = %s. Fix these parameters in the /etc/psa/psa.conf file.', $dumpTmpD, $dumpD)); + $log->resultError(); + } + + $log->resultOk(); + } +} + +class Plesk178Requirements +{ + public function validate() + { + if (PleskInstallation::isInstalled() && Util::isWindows() && PleskVersion::is_below_17_9()) { + $this->_checkPleskVhostsDir(); + } + + if (PleskVersion::is_below_17_8()) { + $this->checkTomcat(); + $this->checkMultiServer(); + } + } + + private function checkTomcat() + { + if (Util::isWindows()) { + $tomcatRegBase = '\\PLESK\\PSA Config\\Config\\Packages\\tomcat\\tomcat'; + /* Supported versions on windows are tomcat5 and tomcat7 */ + $key = '/v InstallDir'; + $isInstalled = Util::regQuery($tomcatRegBase . '7', $key, true) || Util::regQuery($tomcatRegBase . '5', $key, true); + } else { + $isInstalled = PackageManager::isInstalled('psa-tomcat-configurator'); + } + + if ($isInstalled + || (PleskDb::getInstance()->fetchOne('show tables like \'WebApps\'') + && PleskDb::getInstance()->fetchOne('select count(*) from WebApps')) + ) { + $log = Log::getInstance('Checking Apache Tomcat installation'); + $message = <<warning($message); + } + } + + private function checkMultiServer() + { + if (!PleskModule::isMultiServer()) { + return; + } + + $log = Log::getInstance('Checking Plesk Multi Server installation'); + $message = <<emergency($message); + $log->resultError(); + } + + private function _checkPleskVhostsDir() + { + $log = Log::getInstance('Checking mount volume for HTTPD_VHOSTS_D directory'); + + $vhostsDir = rtrim(Util::regPleskQuery('HTTPD_VHOSTS_D'), "\\"); + Util::exec("mountvol \"{$vhostsDir}\" /L", $code); + if ($code == 0) { + $msg = "A disk volume is mounted to the {$vhostsDir} directory." . + " It will be unmounted during the Plesk upgrade." . + " As a result, all hosted websites will become unavailable." . + " Make sure to remount the volume to the {$vhostsDir} directory after the upgrade."; + $log->emergency($msg); + $log->resultError(); + return; + } + + $log->resultOk(); + } +} + +class Plesk18Requirements +{ + public function validate() + { + if (PleskInstallation::isInstalled()) { + if (Util::isLinux() && PleskOS::isRedHatLike()) { + $this->_checkYumDuplicates(); + } + $this->checkPdUsersLoginCollation(); + $this->checkDomainsGuidCollation(); + $this->checkClientsGuidCollation(); + $this->checkModSecurityModules(); + $this->checkIsFirewallPackageConfigured(); + $this->checkDefaultDnsServerComponent(); + } + } + + private function checkModSecurityModules() + { + /* Issue actual for Plesk for Windows below 18.0.32 (ModSecurity 2.9.3 and below) */ + if (!Util::isWindows() || PleskVersion::is_18_0_32_or_above()) { + return; + } + + $log = Log::getInstance('Checking the status of ModSecurity IIS modules'); + $modules = Util::exec(Util::getSystemRoot() . '\system32\inetsrv\AppCmd.exe list module', $code); + if ($code) { + $log->warning('Unable to get the list of IIS modules.'); + } else { + if (strpos($modules, 'ModSecurity IIS (32bits)') === false && strpos($modules, 'ModSecurity IIS (64bits)') === false) { + $log->error('Either 32-bit or 64-bit ModSecurity IIS module is absent.'); + $log->resultError(); + } + } + } + + // INFO: PPP-46440 checking package duplicates https://support.plesk.com/hc/articles/12377586286615 + private function _checkYumDuplicates() + { + $log = Log::getInstance('Checking for RPM packages duplicates'); + if (!file_exists("/usr/bin/package-cleanup")) + { + $log->info("package-cleanup is not found. Check for duplicates was skipped"); + return; + } + + $output = Util::exec("/usr/bin/package-cleanup --cacheonly -q --dupes", $code); + if ($code != 0) + { + // some repos may have no cache at this point or may be broken at all + // retry with cache recreation and skipping broken repos + $output = Util::exec("/usr/bin/package-cleanup -q --dupes --setopt='*.skip_if_unavailable=1'", $code); + } + if ($code != 0) + { + $message = "Unable to detect package duplicates: /usr/bin/package-cleanup --dupes returns $code." . + "Output is:\n$output"; + $log->warning($message); + $log->resultWarning(); + return; + } + + if (empty($output)) { + return; + } + + $message = "Your package system contains duplicated packages, which can lead to broken Plesk update:\n\n" . + "$output\n\n" . + "Please check https://support.plesk.com/hc/articles/12377586286615 for more details."; + + $log->error($message); + $log->resultError(); + } + + private function checkPdUsersLoginCollation() + { + $log = Log::getInstance('Checking for Protected Directory Users with duplicates in login field.'); + $duplicates = PleskDb::getInstance()->fetchAll( + 'SELECT pd_id, LOWER(login) AS login_ci, COUNT(*) AS duplicates FROM pd_users' . + ' GROUP BY pd_id, login_ci' . + ' HAVING duplicates > 1' + ); + if (!empty($duplicates)) { + $log->error( + "Duplicate logins of Protected Directory Users were found in the database:\n\n" . + implode("\n", array_column($duplicates, 'login_ci')) . "\n\n" . + "Please check https://support.plesk.com/hc/en-us/articles/360014743900 for more details." + ); + $log->resultError(); + } + } + + private function checkDomainsGuidCollation() + { + $log = Log::getInstance('Checking "domains" table with duplicates in guid field.'); + $duplicates = PleskDb::getInstance()->fetchAll( + 'SELECT guid, COUNT(*) AS duplicates FROM domains' + . ' GROUP BY guid' + . ' HAVING duplicates > 1' + ); + + if (!empty($duplicates)) { + $log->error( + "Duplicate guid were found in the 'domains' table:\n\n" . + implode("\n", array_column($duplicates, 'guid')) . "\n\n" + . "Please check https://support.plesk.com/hc/en-us/articles/12377018323351 for more details." + ); + $log->resultError(); + } + } + + private function checkClientsGuidCollation() + { + $log = Log::getInstance('Checking "clients" table with duplicates in guid field.'); + $duplicates = PleskDb::getInstance()->fetchAll( + 'SELECT guid, COUNT(*) AS duplicates FROM clients' + . ' GROUP BY guid' + . ' HAVING duplicates > 1' + ); + + if (!empty($duplicates)) { + $log->error( + "Duplicate guid were found in the 'clients' table:\n\n" . + implode("\n", array_column($duplicates, 'guid')) . "\n\n" + . "Please check https://support.plesk.com/hc/en-us/articles/360016801679 for more details." + ); + $log->resultError(); + } + } + + private function checkIsFirewallPackageConfigured() + { + if (!Util::isLinux()) { + return; + } + + $log = Log::getInstance("Checking if any rules are configured in the Firewall extension"); + $db = PleskDb::getInstance(); + if ($db->fetchOne("SHOW TABLES LIKE 'module_firewall_rules'") + && $db->fetchOne("SELECT COUNT(*) FROM module_firewall_rules") + ) { + $message = "Plesk Firewall no longer stores its configuration in Plesk database since " + . "Plesk Obsidian 18.0.52. Since you're upgrading to the latest version late, " + . "if you wish to retain the Plesk Firewall extension and its configuration, " + . "you need to follow additional steps after the upgrade. Please check " + . "https://support.plesk.com/hc/en-us/articles/16198248236311 for more details."; + $log->warning($message); + } + } + + private function checkDefaultDnsServerComponent() + { + if (Util::isLinux()) { + return; + } + + $path = '\\PLESK\\PSA Config\\Config\\Packages\\dnsserver'; + $key = '/ve'; + + if ('bind' === Util::regQuery($path, $key, true)) { + $log = Log::getInstance("Checking default DNS server component"); + $message = <<emergency($message); + $log->resultError(); + } + } +} + +class PleskModule +{ + public static function isInstalledWatchdog() + { + return PleskModule::_isInstalled('watchdog'); + } + + public static function isInstalledFileServer() + { + return PleskModule::_isInstalled('fileserver'); + } + + public static function isInstalledFirewall() + { + return PleskModule::_isInstalled('firewall'); + } + + public static function isInstalledVpn() + { + return PleskModule::_isInstalled('vpn'); + } + + public static function isMultiServer() + { + return PleskModule::_isInstalled('plesk-multi-server') || + PleskModule::_isInstalled('plesk-multi-server-node'); + } + + protected static function _isInstalled($module) + { + $sql = "SELECT * FROM Modules WHERE name = '{$module}'"; + + $pleskDb = PleskDb::getInstance(); + $row = $pleskDb->fetchRow($sql); + + return (empty($row) ? false : true); + } +} + +class PleskInstallation +{ + public static function validate() + { + if (!self::isInstalled()) { + $log = Log::getInstance('Checking for Plesk installation'); + $log->step('Plesk installation is not found. You will have no problems with upgrade, go on and install ' + . PleskVersion::getLatestPleskVersionAsString() . ' (https://www.plesk.com/)'); + return; + } + self::detectVersion(); + } + + public static function isInstalled() + { + $rootPath = Util::getPleskRootPath(); + if (empty($rootPath) || !file_exists($rootPath)) { + return false; + } + return true; + } + + private static function detectVersion() + { + $log = Log::getInstance('Installed Plesk version/build: ' . PleskVersion::getVersionAndBuild(), false); + + $currentVersion = PleskVersion::getVersion(); + if (version_compare($currentVersion, PLESK_VERSION, 'eq')) { + $err = 'You have already installed the latest version ' . PleskVersion::getLatestPleskVersionAsString() . '. '; + $err .= 'Tool must be launched prior to upgrade to ' . PleskVersion::getLatestPleskVersionAsString() . ' for the purpose of getting a report on potential problems with the upgrade.'; + $log->info($err); + exit(0); + } + + if (!PleskVersion::isUpgradeSupportedVersion()) { + $err = 'Unable to find Plesk 17.x. '; + $err .= 'Tool must be launched prior to upgrade to ' . PleskVersion::getLatestPleskVersionAsString() . ' for the purpose of getting a report on potential problems with the upgrade.'; + fatal($err); + } + } +} + +class PleskVersion +{ + const PLESK_17_MIN_VERSION = '13.0.0'; /* historically it has been started as 13.0 */ + + const PLESK_17_MAX_VERSION = '17.9.13'; + + const PLESK_18_MIN_VERSION = '18.0.14'; + + public static function is17x_or_above() + { + return version_compare(self::getVersion(), self::PLESK_17_MIN_VERSION, '>='); + } + + public static function is_below_17_5() + { + return version_compare(self::getVersion(), '17.5.0', '<'); + } + + public static function is_below_17_8() + { + return version_compare(self::getVersion(), '17.8.0', '<'); + } + + public static function is_below_17_9() + { + return version_compare(self::getVersion(), '17.9.0', '<'); + } + + public static function is_18_0_32_or_above() + { + return version_compare(self::getVersion(), '18.0.32', '>='); + } + + public static function getVersion() + { + $version = self::getVersionAndBuild(); + if (!preg_match('/([0-9]+[.][0-9]+[.][0-9]+)/', $version, $matches)) { + fatal("Incorrect Plesk version format. Current version: {$version}"); + } + return $matches[1]; + } + + public static function getVersionAndBuild() + { + $versionPath = Util::getPleskRootPath().'/version'; + if (!file_exists($versionPath)) { + fatal("Plesk version file is not exists $versionPath"); + } + $version = file_get_contents($versionPath); + $version = trim($version); + return $version; + } + + public static function getLatestPleskVersionAsString() + { + return 'Plesk ' . PLESK_VERSION; + } + + public static function isUpgradeSupportedVersion() + { + return self::is17x_or_above(); + } +} + +class Log +{ + private $errors; + private $warnings; + private $emergency; + private $logfile; + private $step; + private $step_header; + + /** @var array */ + private $errorsContent = []; + + /** @var array */ + private $warningsContent = []; + + public static function getInstance($step_msg = '', $step_number = true) + { + static $_instance = null; + if (is_null($_instance)) { + $_instance = new Log(); + } + if ($step_msg) { + $_instance->step($step_msg, $step_number); + } + + return $_instance; + } + + private function __construct() + { + $this->log_init(); + @unlink($this->logfile); + } + + private function log_init() + { + $this->step = 0; + $this->errors = 0; + $this->warnings = 0; + $this->emergency = 0; + $this->logfile = LOG_PATH; + $this->step_header = "Unknown step is running"; + } + + public function getErrors() + { + return $this->errors; + } + + public function getWarnings() + { + return $this->warnings; + } + + public function getEmergency() + { + return $this->emergency; + } + + public function fatal($msg) + { + $this->errors++; + + $this->errorsContent[] = $msg; + $content = $this->get_log_string($msg, 'FATAL_ERROR'); + fwrite(STDERR, $content); + $this->write($content); + } + + public function error($msg) + { + $this->errors++; + + $this->errorsContent[] = $msg; + $content = $this->get_log_string($msg, 'ERROR'); + fwrite(STDERR, $content); + $this->write($content); + } + + public function warning($msg) + { + $this->warnings++; + + $this->warningsContent[] = $msg; + $content = $this->get_log_string($msg, 'WARNING'); + fwrite(STDERR, $content); + $this->write($content); + } + + public function emergency($msg) + { + $this->emergency++; + + $this->errorsContent[] = $msg; + $content = $this->get_log_string($msg, 'EMERGENCY'); + fwrite(STDERR, $content); + $this->write($content); + } + + public function step($msg, $useNumber=false) + { + $this->step_header = $msg; + + echo PHP_EOL; + $this->write(PHP_EOL); + + if ($useNumber) { + $msg = "STEP " . $this->step . ": {$msg}..."; + $this->step++; + } else { + $msg = "{$msg}..."; + } + + $this->info($msg); + } + + public function resultOk() + { + $this->info('Result: OK'); + } + + public function resultWarning() + { + $this->info('Result: WARNING'); + } + + public function resultError() + { + $this->info('Result: ERROR'); + } + + public function info($msg) + { + $content = $this->get_log_string($msg, 'INFO'); + echo $content; + $this->write($content); + } + + public function debug($msg) + { + $this->write($this->get_log_string($msg, 'DEBUG')); + } + + public function dumpStatistics() + { + $errors = $this->errors + $this->emergency; + $str = "Errors found: $errors; Warnings found: {$this->warnings}"; + echo PHP_EOL . $str . PHP_EOL . PHP_EOL; + } + + private function get_log_string($msg, $type) + { + if (getenv('VZ_UPGRADE_SCRIPT')) { + switch ($type) { + case 'FATAL_ERROR': + case 'ERROR': + case 'WARNING': + case 'EMERGENCY': + $content = "[{$type}]: {$this->step_header} DESC: {$msg}" . PHP_EOL; + break; + default: + $content = "[{$type}]: {$msg}" . PHP_EOL; + } + } else if (getenv('AUTOINSTALLER_VERSION')) { + $content = "{$type}: {$msg}" . PHP_EOL; + } else { + $date = date('Y-m-d h:i:s'); + $content = "[{$date}][{$type}] {$msg}" . PHP_EOL; + } + + return $content; + } + + public function write($content, $file = null, $mode='a+') + { + $logfile = $file ? $file : $this->logfile; + $fp = fopen($logfile, $mode); + fwrite($fp, $content); + fclose($fp); + } + + private function getJsonFileName() + { + return (Util::isWindows() ? + rtrim(Util::regPleskQuery('PRODUCT_DATA_D'), "\\") : + Util::getSettingFromPsaConf('PRODUCT_ROOT_D') + ) . '/var/' . LOG_JSON; + } + + public function writeJsonFile() + { + $data = [ + 'version' => PRE_UPGRADE_SCRIPT_VERSION, + 'errorsFound' => $this->errors + $this->emergency, + 'errors' => $this->errorsContent, + 'warningsFound' => $this->warnings, + 'warnings' => $this->warningsContent, + ]; + file_put_contents($this->getJsonFileName(), json_encode($data)); + } +} + +class PleskDb +{ + var $_db = null; + + public function __construct($dbParams) + { + switch($dbParams['db_type']) { + case 'mysql': + $this->_db = new DbMysql( + $dbParams['host'], $dbParams['login'], $dbParams['passwd'], $dbParams['db'], $dbParams['port'] + ); + break; + + case 'jet': + $this->_db = new DbJet($dbParams['db']); + break; + + case 'mssql': + $this->_db = new DbMsSql( + $dbParams['host'], $dbParams['login'], $dbParams['passwd'], $dbParams['db'], $dbParams['port'] + ); + break; + + default: + fatal("{$dbParams['db_type']} is not implemented yet"); + break; + } + } + + public static function getInstance() + { + global $options; + static $_instance = array(); + + $dbParams['db_type']= Util::getPleskDbType(); + $dbParams['db'] = Util::getPleskDbName(); + $dbParams['port'] = Util::getPleskDbPort(); + $dbParams['login'] = Util::getPleskDbLogin(); + $dbParams['passwd'] = Util::getPleskDbPassword($options->getDbPasswd()); + $dbParams['host'] = Util::getPleskDbHost(); + + $dbId = md5(implode("\n", $dbParams)); + + $_instance[$dbId] = new PleskDb($dbParams); + + return $_instance[$dbId]; + } + + function fetchOne($sql) + { + if (DEBUG) { + $log = Log::getInstance(); + $log->info($sql); + } + return $this->_db->fetchOne($sql); + } + + function fetchRow($sql) + { + $res = $this->fetchAll($sql); + if (is_array($res) && isset($res[0])) { + return $res[0]; + } + return array(); + } + + function fetchAll($sql) + { + if (DEBUG) { + $log = Log::getInstance(); + $log->info($sql); + } + return $this->_db->fetchAll($sql); + } +} + +class DbMysql +{ + var $_dbHandler; + + public function __construct($host, $user, $passwd, $database, $port) + { + if ( extension_loaded('mysql') ) { + $this->_dbHandler = @mysql_connect("{$host}:{$port}", $user, $passwd); + if (!is_resource($this->_dbHandler)) { + $mysqlError = mysql_error(); + if (stristr($mysqlError, 'access denied for user')) { + $errMsg = 'Given is incorrect. ' . $mysqlError; + } else { + $errMsg = 'Unable to connect database. The reason of problem: ' . $mysqlError . PHP_EOL; + } + $this->_logError($errMsg); + } + @mysql_select_db($database, $this->_dbHandler); + } else if ( extension_loaded('mysqli') ) { + + // forbid using MYSQLI_REPORT_STRICT to handle mysqli errors via error codes + mysqli_report(MYSQLI_REPORT_ERROR); + + $this->_dbHandler = @mysqli_connect($host, $user, $passwd, $database, $port); + if (!$this->_dbHandler) { + $mysqlError = mysqli_connect_error(); + if (stristr($mysqlError, 'access denied for user')) { + $errMsg = 'Given is incorrect. ' . $mysqlError; + } else { + $errMsg = 'Unable to connect database. The reason of problem: ' . $mysqlError . PHP_EOL; + } + $this->_logError($errMsg); + } + } else { + fatal("No MariaDB/MySQL extension is available"); + } + } + + function fetchAll($sql) + { + if ( extension_loaded('mysql') ) { + $res = mysql_query($sql, $this->_dbHandler); + if (!is_resource($res)) { + $this->_logError('Unable to execute query. Error: ' . mysql_error($this->_dbHandler)); + } + $rowset = array(); + while ($row = mysql_fetch_assoc($res)) { + $rowset[] = $row; + } + return $rowset; + } else if ( extension_loaded('mysqli') ) { + $res = $this->_dbHandler->query($sql); + if ($res === false) { + $this->_logError('Unable to execute query. Error: ' . mysqli_error($this->_dbHandler)); + } + $rowset = array(); + while ($row = mysqli_fetch_assoc($res)) { + $rowset[] = $row; + } + return $rowset; + } else { + fatal("No MariaDB/MySQL extension is available"); + } + } + + function fetchOne($sql) + { + if ( extension_loaded('mysql') ) { + $res = mysql_query($sql, $this->_dbHandler); + if (!is_resource($res)) { + $this->_logError('Unable to execute query. Error: ' . mysql_error($this->_dbHandler)); + } + $row = mysql_fetch_row($res); + return isset($row[0]) ? $row[0] : null; + } else if ( extension_loaded('mysqli') ) { + $res = $this->_dbHandler->query($sql); + if ($res === false) { + $this->_logError('Unable to execute query. Error: ' . mysqli_error($this->_dbHandler)); + } + $row = mysqli_fetch_row($res); + return isset($row[0]) ? $row[0] : null; + } else { + fatal("No MariaDB/MySQL extension is available"); + } + } + + function query($sql) + { + if ( extension_loaded('mysql') ) { + $res = mysql_query($sql, $this->_dbHandler); + if ($res === false ) { + $this->_logError('Unable to execute query. Error: ' . mysql_error($this->_dbHandler) ); + } + return $res; + } else if ( extension_loaded('mysqli') ) { + $res = $this->_dbHandler->query($sql); + if ($res === false ) { + $this->_logError('Unable to execute query. Error: ' . mysqli_error($this->_dbHandler) ); + } + return $res; + } else { + fatal("No MariaDB/MySQL extension is available"); + } + } + + function _logError($message) + { + fatal("[MYSQL ERROR] $message"); + } +} + +class DbClientMysql extends DbMysql +{ + var $errors = array(); + + function _logError($message) + { + $message = "[MYSQL ERROR] $message"; + $log = Log::getInstance(); + $log->warning($message); + $this->errors[] = $message; + } + + function hasErrors() { + return count($this->errors) > 0; + } +} + +class DbJet +{ + var $_dbHandler = null; + + public function __construct($dbPath) + { + $dsn = "Provider='Microsoft.Jet.OLEDB.4.0';Data Source={$dbPath}"; + $this->_dbHandler = new COM("ADODB.Connection", NULL, CP_UTF8); + if (!$this->_dbHandler) { + $this->_logError('Unable to init ADODB.Connection'); + } + + $this->_dbHandler->open($dsn); + } + + function fetchAll($sql) + { + $result_id = $this->_dbHandler->execute($sql); + if (!$result_id) { + $this->_logError('Unable to execute sql query ' . $sql); + } + if ($result_id->BOF && !$result_id->EOF) { + $result_id->MoveFirst(); + } + if ($result_id->EOF) { + return array(); + } + + $rowset = array(); + while(!$result_id->EOF) { + $row = array(); + for ($i=0;$i<$result_id->Fields->count;$i++) { + $field = $result_id->Fields($i); + $row[$field->Name] = (string)$field->value; + } + $result_id->MoveNext(); + $rowset[] = $row; + } + return $rowset; + } + + function fetchOne($sql) + { + $result_id = $this->_dbHandler->execute($sql); + if (!$result_id) { + $this->_logError('Unable to execute sql query ' . $sql); + } + if ($result_id->BOF && !$result_id->EOF) { + $result_id->MoveFirst(); + } + if ($result_id->EOF) { + return null; + } + $field = $result_id->Fields(0); + $result = $field->value; + + return (string)$result; + } + + function _logError($message) + { + fatal("[JET ERROR] $message"); + } +} + +class DbMsSql extends DbJet +{ + public function __construct($host, $user, $passwd, $database, $port) + { + $dsn = "Provider=SQLOLEDB.1;Initial Catalog={$database};Data Source={$host}"; + $this->_dbHandler = new COM("ADODB.Connection", NULL, CP_UTF8); + if (!$this->_dbHandler) { + $this->_logError('Unable to init ADODB.Connection'); + } + $this->_dbHandler->open($dsn, $user, $passwd); + } + + function _logError($message) + { + fatal("[MSSQL ERROR] $message"); + } +} + +class Util +{ + const DSN_INI_PATH_UNIX = '/etc/psa/private/dsn.ini'; + + /** @var array */ + private static $_dsnIni; + + public static function isWindows() + { + if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { + return true; + } + return false; + } + + public static function isLinux() + { + return !Util::isWindows(); + } + + public static function isVz() + { + $vz = false; + if (Util::isLinux()) { + if (file_exists('/proc/vz/veredir')) { + $vz = true; + } + } else { + $reg = 'REG QUERY "HKLM\SOFTWARE\SWsoft\Virtuozzo" 2>nul'; + Util::exec($reg, $code); + if ($code==0) { + $vz = true; + } + } + return $vz; + } + + public static function getArch() + { + global $arch; + if (!empty($arch)) + return $arch; + + $arch = 'i386'; + if (Util::isLinux()) { + $cmd = 'uname -m'; + $x86_64 = 'x86_64'; + $output = Util::exec($cmd, $code); + if (!empty($output) && stristr($output, $x86_64)) { + $arch = 'x86_64'; + } + } else { + $arch = 'x86_64'; + } + return $arch; + } + + public static function getHostname() + { + if (Util::isLinux()) { + $cmd = 'hostname -f'; + } else { + $cmd = 'hostname'; + } + $hostname = Util::exec($cmd, $code); + + if (empty($hostname)) { + $err = 'Command: ' . $cmd . ' returns: ' . $hostname . "\n"; + $err .= 'Hostname is not defined and configured. Unable to get hostname. Server should have properly configured hostname and it should be resolved locally.'; + fatal($err); + } + + return $hostname; + } + + public static function getIPList($lo=false) + { + if (Util::isLinux()) { + $ipList = Util::getIPv4ListOnLinux(); + foreach ($ipList as $key => $ip) { + if (!$lo && substr($ip, 0, 3) == '127') { + unset($ipList[$key]); + continue; + } + trim($ip); + } + $ipList = array_values($ipList); + } else { + $cmd = 'hostname'; + $hostname = Util::exec($cmd, $code); + $ip = gethostbyname($hostname); + $res = ($ip != $hostname) ? true : false; + if (!$res) { + fatal('Unable to retrieve IP address'); + } + $ipList = array(trim($ip)); + } + return $ipList; + } + + public static function getIPv6ListOnLinux() + { + return Util::grepCommandOutput(array( + array('bin' => 'ip', 'command' => '%PATH% addr list', 'regexp' => '#inet6 ([^ /]+)#'), + array('bin' => 'ifconfig', 'command' => '%PATH% -a', 'regexp' => '#inet6 (?:addr: ?)?([A-F0-9:]+)#i'), + )); + } + + public static function getIPv4ListOnLinux() + { + $commands = array( + array('bin' => 'ip', 'command' => '%PATH% addr list', 'regexp' => '#inet ([^ /]+)#'), + array('bin' => 'ifconfig', 'command' => '%PATH% -a', 'regexp' => '#inet (?:addr: ?)?([\d\.]+)#'), + ); + if (!($list = Util::grepCommandOutput($commands))) { + fatal('Unable to get IP address'); + } + return $list; + } + + public static function grepCommandOutput($cmds) + { + foreach ($cmds as $cmd) { + if ($fullPath = Util::lookupCommand($cmd['bin'])) { + $output = Util::exec(str_replace("%PATH%", $fullPath, $cmd['command']), $code); + if (preg_match_all($cmd['regexp'], $output, $matches)) { + return $matches[1]; + } + } + } + return false; + } + + public static function getIPListOnWindows() + { + $cmd = 'wmic.exe path win32_NetworkAdapterConfiguration get IPaddress'; + $output = Util::exec($cmd, $code); + if (!preg_match_all('/"(.*?)"/', $output, $matches)) { + fatal('Unable to get IP address'); + } + return $matches[1]; + } + + public static function getPleskRootPath() + { + global $_pleskRootPath; + if (empty($_pleskRootPath)) { + if (Util::isLinux()) { + if (PleskOS::isDebLike()) { + $_pleskRootPath = '/opt/psa'; + } else { + $_pleskRootPath = '/usr/local/psa'; + } + } + if (Util::isWindows()) { + $_pleskRootPath = Util::regPleskQuery('PRODUCT_ROOT_D', true); + } + } + return $_pleskRootPath; + } + + public static function getPleskDbName() + { + $dbName = 'psa'; + if (Util::isWindows()) { + $dbName = Util::regPleskQuery('mySQLDBName'); + } else { + $dsnDbname = Util::_getDsnConfigValue('dbname'); + if ($dsnDbname) { + $dbName = $dsnDbname; + } + } + return $dbName; + } + + public static function getPleskDbLogin() + { + $dbLogin = 'admin'; + if (Util::isWindows()) { + $dbLogin = Util::regPleskQuery('PLESK_DATABASE_LOGIN'); + } else { + $dsnLogin = Util::_getDsnConfigValue('username'); + if ($dsnLogin) { + $dbLogin = $dsnLogin; + } + } + return $dbLogin; + } + + public static function getPleskDbPassword($dbPassword) + { + if (Util::isLinux()) { + $dsnPassword = Util::_getDsnConfigValue('password'); + if ($dsnPassword) { + $dbPassword = $dsnPassword; + } + } + return $dbPassword; + } + + public static function getPleskDbType() + { + $dbType = 'mysql'; + if (Util::isWindows()) { + $dbType = strtolower(Util::regPleskQuery('PLESK_DATABASE_PROVIDER_NAME')); + } + return $dbType; + } + + public static function getPleskDbHost() + { + $dbHost = 'localhost'; + if (Util::isWindows()) { + $dbProvider = strtolower(Util::regPleskQuery('PLESK_DATABASE_PROVIDER_NAME')); + if ($dbProvider == 'mysql' || $dbProvider == 'mssql') { + $dbHost = Util::regPleskQuery('MySQL_DB_HOST'); + } + } else { + $dsnHost = Util::_getDsnConfigValue('host'); + if ($dsnHost) { + $dbHost = $dsnHost; + } + } + return $dbHost; + } + + public static function getPleskDbPort() + { + $dbPort = '3306'; + if (Util::isWindows()) { + $dbPort = Util::regPleskQuery('MYSQL_PORT'); + } else { + $dsnPort = Util::_getDsnConfigValue('port'); + if ($dsnPort) { + $dbPort = $dsnPort; + } + } + return $dbPort; + } + + private static function _getDsnConfigValue($param) + { + if (Util::isWindows()) { + return null; + } + + if (is_null(self::$_dsnIni)) { + if (!is_file(self::DSN_INI_PATH_UNIX)) { + self::$_dsnIni = false; + return null; + } + self::$_dsnIni = parse_ini_file(self::DSN_INI_PATH_UNIX, true); + } + + if (!self::$_dsnIni) { + return null; + } + if (!array_key_exists('plesk', self::$_dsnIni)) { + return null; + } + if (!array_key_exists($param, self::$_dsnIni['plesk'])) { + return null; + } + return self::$_dsnIni['plesk'][$param]; + } + + public static function regPleskQuery($key, $returnResult=false) + { + $reg = 'REG QUERY "HKLM\SOFTWARE\Wow6432Node\Plesk\Psa Config\Config" /v '.$key; + $output = Util::exec($reg, $code); + + if ($code) { + $log = Log::getInstance(); + $log->info($reg); + $log->info($output); + if ($returnResult) { + return false; + } else { + fatal("Unable to get '$key' from registry"); + } + } + + if (!preg_match("/\w+\s+REG_SZ\s+(.*)/i", trim($output), $matches)) { + fatal('Unable to macth registry value by key '.$key.'. Output: ' . trim($output)); + } + + return $matches[1]; + } + + public static function regQuery($path, $key, $returnResult = false) + { + $reg = 'REG QUERY "HKLM\SOFTWARE\Wow6432Node' . $path . '" '.$key; + $output = Util::exec($reg, $code); + + if ($code) { + $log = Log::getInstance(); + $log->info($reg); + $log->info($output); + if ($returnResult) { + return false; + } else { + fatal("Unable to get '$key' from registry"); + } + } + + if (!preg_match("/\s+REG_SZ(\s+)?(.*)/i", trim($output), $matches)) { + fatal('Unable to match registry value by key '.$key.'. Output: ' . trim($output)); + } + + return $matches[2]; + } + + public static function lookupCommand($cmd, $exit = false, $path = '/bin:/usr/bin:/usr/local/bin:/usr/sbin:/sbin:/usr/local/sbin') + { + $dirs = explode(':', $path); + foreach ($dirs as $dir) { + $util = $dir . '/' . $cmd; + if (is_executable($util)) { + return $util; + } + } + if ($exit) { + fatal("{$cmd}: command not found"); + } + return false; + } + + public static function getSystemDisk() + { + $cmd = 'echo %SYSTEMROOT%'; + $output = Util::exec($cmd, $code); + return substr($output, 0, 3); + } + + public static function getSystemRoot() + { + $cmd = 'echo %SYSTEMROOT%'; + $output = Util::exec($cmd, $code); + return $output; + } + + public static function getFileVersion($file) + { + $fso = new COM("Scripting.FileSystemObject"); + $version = $fso->GetFileVersion($file); + $fso = null; + return $version; + } + + public static function isUnknownISAPIfilters() + { + if (PleskVersion::is17x_or_above()) { + return false; + } + + $log = Log::getInstance(); + + $isUnknownISAPI = false; + $knownISAPI = array ("ASP\\.Net.*", "sitepreview", "COMPRESSION", "jakarta"); + + foreach ($knownISAPI as &$value) { + $value = strtoupper($value); + } + $cmd='cscript ' . Util::getSystemDisk() . 'inetpub\AdminScripts\adsutil.vbs ENUM W3SVC/FILTERS'; + $output = Util::exec($cmd, $code); + + if ($code!=0) { + $log->info("Unable to get ISAPI filters. Error: " . $output); + return false; + } + if (!preg_match_all('/FILTERS\/(.*)]/', trim($output), $matches)) { + $log->info($output); + $log->info("Unable to get ISAPI filters from output: " . $output); + return false; + } + foreach ($matches[1] as $ISAPI) { + $valid = false; + foreach ($knownISAPI as $knownPattern) { + if (preg_match("/$knownPattern/i", $ISAPI)) { + $valid = true; + break; + } + } + if (! $valid ) { + $log->warning("Unknown ISAPI filter detected in IIS: " . $ISAPI); + $isUnknownISAPI = true; + } + } + + return $isUnknownISAPI; + } + + /** + * @return string + */ + public static function getMySQLServerVersion() + { + $credentials = Util::getDefaultClientMySQLServerCredentials(); + + if (preg_match('/AES-128-CBC/', $credentials['admin_password'])) { + Log::getInstance()->info('The administrator\'s password for the default MariaDB/MySQL server is encrypted.'); + + return ''; + } + + $mysql = new DbClientMysql( + $credentials['host'], + $credentials['admin_login'], + $credentials['admin_password'], + 'information_schema', + $credentials['port'] + ); + + if (!$mysql->hasErrors()) { + $sql = 'select version()'; + $mySQLversion = $mysql->fetchOne($sql); + if (!preg_match("/(\d{1,})\.(\d{1,})\.(\d{1,})/", trim($mySQLversion), $matches)) { + fatal('Unable to match MariaDB/MySQL server version.'); + } + + return $matches[0]; + } + + return ''; + } + + public static function getDefaultClientMySQLServerCredentials() + { + $db = PleskDb::getInstance(); + $sql = "SELECT val FROM misc WHERE param='default_server_mysql'"; + $defaultServerMysqlId = $db->fetchOne($sql); + if ($defaultServerMysqlId) { + $where = "id={$defaultServerMysqlId}"; + } else { + $where = "type='mysql' AND host='localhost'"; + } + $sql = "SELECT ds.host, ds.port, ds.admin_login, ds.admin_password FROM DatabaseServers ds WHERE {$where}"; + $clientDBServerCredentials = $db->fetchAll($sql)[0]; + if ($clientDBServerCredentials['host'] === 'localhost' && Util::isLinux()) { + $clientDBServerCredentials['admin_password'] = Util::retrieveAdminMySQLDbPassword(); + } + if (empty($clientDBServerCredentials['port'])) { + $clientDBServerCredentials['port'] = self::getPleskDbPort(); + } + + return $clientDBServerCredentials; + } + + public static function retrieveAdminMySQLDbPassword() + { + return Util::isLinux() + ? trim( Util::readfile("/etc/psa/.psa.shadow") ) + : null; + } + + public static function exec($cmd, &$code) + { + $log = Log::getInstance(); + + if (!$cmd) { + $log->info('Unable to execute a blank command. Please see ' . LOG_PATH . ' for details.'); + + $debugBacktrace = ""; + foreach (debug_backtrace() as $i => $obj) { + $debugBacktrace .= "#{$i} {$obj['file']}:{$obj['line']} {$obj['function']} ()\n"; + } + $log->debug("Unable to execute a blank command. The stack trace:\n{$debugBacktrace}"); + $code = 1; + return ''; + } + exec($cmd, $output, $code); + return trim(implode("\n", $output)); + } + + public static function readfile($file) + { + if (!is_file($file) || !is_readable($file)) { + return null; + } + $lines = file($file); + return $lines === false + ? null + : trim(implode("\n", $lines)); + } + + public static function readfileToArray($file) + { + if (!is_file($file) || !is_readable($file)) { + return null; + } + $lines = file($file); + return $lines === false + ? null + : $lines; + } + + public static function getSettingFromPsaConf($setting) + { + $file = '/etc/psa/psa.conf'; + if (!is_file($file) || !is_readable($file)) + return null; + $lines = file($file); + if ($lines === false) + return null; + foreach ($lines as $line) { + if (preg_match("/^{$setting}\s.*/", $line, $match_setting)) { + if (preg_match("/[\s].*/i", $match_setting[0], $match_value)) { + $value = trim($match_value[0]); + return $value; + } + } + } + return null; + } + + public static function getPhpIni() + { + if (Util::isLinux()) { + // Debian/Ubuntu /etc/php5/apache2/php.ini /etc/php5/conf.d/ + // SuSE /etc/php5/apache2/php.ini /etc/php5/conf.d/ + // CentOS 4/5 /etc/php.ini /etc/php.d + if (PleskOS::isRedHatLike()) { + $phpini = Util::readfileToArray('/etc/php.ini'); + } else { + $phpini = Util::readfileToArray('/etc/php5/apache2/php.ini'); + } + } + + return $phpini; + } + + public static function getUserBeanCounters() + { + if (!Util::isLinux()) { + + return false; + } + $user_beancounters = array(); + $ubRaw = Util::readfileToArray('/proc/user_beancounters'); + + if (!$ubRaw) { + + return false; + } + for ($i=2; $i<=count($ubRaw)-1; $i++) { + + if (preg_match('/^.+?:?.+?\b(\w+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)/', $ubRaw[$i], $limit_name)) { + + $user_beancounters[trim($limit_name[1])] = array( + 'held' => (int)$limit_name[2], + 'maxheld' => (int)$limit_name[3], + 'barrier' => (int)$limit_name[4], + 'limit' => (int)$limit_name[5], + 'failcnt' => (int)$limit_name[6] + ); + } + } + + return $user_beancounters; + } +} + +class PackageManager +{ + public static function buildListCmdLine($glob) + { + if (PleskOS::isRedHatLike()) { + $cmd = "rpm -qa --queryformat '%{NAME} %{VERSION}-%{RELEASE} %{ARCH}\\n'"; + } elseif (PleskOS::isDebLike()) { + $cmd = "dpkg-query --show --showformat '\${Package} \${Version} \${Architecture}\\n'"; + } else { + return false; + } + + if (!empty($glob)) { + $cmd .= " '" . $glob . "' 2>/dev/null"; + } + + return $cmd; + } + + /* + * Fetches a list of installed packages that match given criteria. + * string $glob - Glob (wildcard) pattern for coarse-grained packages selection from system package management backend. Empty $glob will fetch everything. + * string $regexp - Package name regular expression for a fine-grained filtering of the results. + * returns array of hashes with keys 'name', 'version' and 'arch', or false on error. + */ + public static function listInstalled($glob, $regexp = null) + { + $cmd = PackageManager::buildListCmdLine($glob); + if (!$cmd) { + return array(); + } + + $output = Util::exec($cmd, $code); + if ($code != 0) { + return false; + } + + $packages = array(); + $lines = explode("\n", $output); + foreach ($lines as $line) { + @list($pkgName, $pkgVersion, $pkgArch) = explode(" ", $line); + if (empty($pkgName) || empty($pkgVersion) || empty($pkgArch)) + continue; + if (!empty($regexp) && !preg_match($regexp, $pkgName)) + continue; + $packages[] = array( + 'name' => $pkgName, + 'version' => $pkgVersion, + 'arch' => $pkgArch + ); + } + + return $packages; + } + + public static function isInstalled($glob, $regexp = null) + { + $packages = PackageManager::listInstalled($glob, $regexp); + return !empty($packages); + } +} + +class Package +{ + function getManager($field, $package) + { + $redhat = 'rpm -q --queryformat \'%{' . $field . '}\n\' ' . $package; + $debian = 'dpkg-query --show --showformat=\'${' . $field . '}\n\' '. $package . ' 2> /dev/null'; + + if (PleskOS::isRedHatLike()) { + $manager = $redhat; + } elseif (PleskOS::isDebLike()) { + $manager = $debian; + } else { + return false; + } + + return $manager; + } + + /* DPKG doesn't supports ${Release} + * + */ + + function getRelease($package) + { + $manager = Package::getManager('Release', $package); + + if (!$manager) { + return false; + } + + $release = Util::exec($manager, $code); + if (!$code === 0) { + return false; + } + return $release; + } + + function getVersion($package) + { + $manager = Package::getManager('Version', $package); + + if (!$manager) { + return false; + } + + $version = Util::exec($manager, $code); + if (!$code === 0) { + return false; + } + return $version; + } + +} + +class PleskOS +{ + public static function isDebLike() + { + return is_file("/etc/debian_version"); + } + + public static function isRedHatLike() + { + return is_file("/etc/redhat-release"); + } + + public static function catEtcIssue() + { + $cmd = 'cat /etc/issue'; + $output = Util::exec($cmd, $code); + + return $output; + } + + public static function detectSystem() + { + $log = Log::getInstance('Detect system configuration'); + $log->info('OS: ' . (Util::isLinux() ? PleskOS::catEtcIssue() : 'Windows')); + $log->info('Arch: ' . Util::getArch()); + } +} + +class PleskValidator +{ + public static function validateIPv4($value) + { + $ip2long = ip2long($value); + if ($ip2long === false) { + return false; + } + + return $value == long2ip($ip2long); + } + + public static function validateIPv6($value) + { + if (strlen($value) < 3) { + return $value == '::'; + } + + if (strpos($value, '.')) { + $lastcolon = strrpos($value, ':'); + if (!($lastcolon && PleskValidator::validateIPv4(substr($value, $lastcolon + 1)))) { + return false; + } + + $value = substr($value, 0, $lastcolon) . ':0:0'; + } + + if (strpos($value, '::') === false) { + return preg_match('/\A(?:[a-f0-9]{1,4}:){7}[a-f0-9]{1,4}\z/i', $value); + } + + $colonCount = substr_count($value, ':'); + if ($colonCount < 8) { + return preg_match('/\A(?::|(?:[a-f0-9]{1,4}:)+):(?:(?:[a-f0-9]{1,4}:)*[a-f0-9]{1,4})?\z/i', $value); + } + + // special case with ending or starting double colon + if ($colonCount == 8) { + return preg_match('/\A(?:::)?(?:[a-f0-9]{1,4}:){6}[a-f0-9]{1,4}(?:::)?\z/i', $value); + } + + return false; + } +} + +class CheckRequirements +{ + function validate() + { + if (!PleskInstallation::isInstalled()) { + //:INFO: skip chking mysql extension if plesk is not installed + return; + } + + $reqExts = array(); + foreach ($reqExts as $name) { + $status = extension_loaded($name); + if (!$status) { + $this->_fail("PHP extension {$name} is not installed"); + } + } + } + + function _fail($errMsg) + { + echo '===Checking requirements===' . PHP_EOL; + echo PHP_EOL . 'Error: ' . $errMsg . PHP_EOL; + exit(1); + } +} + +class GetOpt +{ + var $_argv; + var $_adminDbPasswd; + + public function __construct() + { + $this->_argv = $_SERVER['argv']; + if (empty($this->_argv[1]) && Util::isLinux()) { + $this->_adminDbPasswd = Util::retrieveAdminMySQLDbPassword(); + } else { + $this->_adminDbPasswd = $this->_argv[1]; + } + } + + public function validate() + { + if (empty($this->_adminDbPasswd) && PleskInstallation::isInstalled()) { + echo 'Please specify Plesk database password'; + $this->_helpUsage(); + } + } + + public function getDbPasswd() + { + return $this->_adminDbPasswd; + } + + public function _helpUsage() + { + echo PHP_EOL . "Usage: {$this->_argv[0]} " . PHP_EOL; + exit(1); + } +} + +function fatal($msg) +{ + $log = Log::getInstance(); + $log->fatal($msg); + exit(1); +} + +$log = Log::getInstance(); + +//:INFO: Validate options +$options = new GetOpt(); +$options->validate(); + +//:INFO: Validate PHP requirements, need to make sure that PHP extensions are installed +$checkRequirements = new CheckRequirements(); +$checkRequirements->validate(); + +//:INFO: Validate Plesk installation +PleskInstallation::validate(); + +//:INFO: Detect system +$pleskOs = new PleskOS(); +$pleskOs->detectSystem(); + +//:INFO: Need to make sure that given db password is valid +if (PleskInstallation::isInstalled()) { + $log->step('Validating the database password'); + $pleskDb = PleskDb::getInstance(); + $log->resultOk(); +} + +//:INFO: Dump script version +$log->step('Pre-Upgrade analyzer version: ' . PRE_UPGRADE_SCRIPT_VERSION); + +//:INFO: Validate known OS specific issues with recommendation to avoid bugs in Plesk +$pleskKnownIssues = new Plesk17KnownIssues(); +$pleskKnownIssues->validate(); + +$plesk175Requirements = new Plesk175Requirements(); +$plesk175Requirements->validate(); + +$plesk178Requirements = new Plesk178Requirements(); +$plesk178Requirements->validate(); + +$plesk18Requirements = new Plesk18Requirements(); +$plesk18Requirements->validate(); + +$log->dumpStatistics(); +$log->writeJsonFile(); + +if ($log->getEmergency() > 0) { + exit(2); +} + +if ($log->getErrors() > 0 || $log->getWarnings() > 0) { + exit(1); +} +// vim:set et ts=4 sts=4 sw=4: diff --git a/root/parallels/pool/PSA_18.0.73_17725/examiners/php_launcher.sh b/root/parallels/pool/PSA_18.0.73_17725/examiners/php_launcher.sh new file mode 100755 index 0000000000..70ebd0f0c6 --- /dev/null +++ b/root/parallels/pool/PSA_18.0.73_17725/examiners/php_launcher.sh @@ -0,0 +1,38 @@ +#!/bin/sh +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +die() +{ + echo $* + exit 1 +} + +[ -n "$1" ] || die "Usage: $0 php_script [args...]" + +[ "X${PLESK_INSTALLER_DEBUG}" = "X" ] || set -x +[ "X${PLESK_INSTALLER_STRICT_MODE}" = "X" ] || set -e + +php_bin= + +lookup() +{ + [ -z "$php_bin" ] || return + + local paths="$1" + local name="$2" + + for path in $paths; do + if [ -x "$path/$name" ]; then + php_bin="$path/$name" + break + fi + done +} + +lookup "/usr/local/psa/admin/bin /opt/psa/admin/bin" "php" +lookup "/usr/local/psa/bin /opt/psa/bin" "sw-engine-pleskrun" + +[ -n "$php_bin" ] || \ + die "Unable to locate the sw-engine PHP interpreter to execute the script. Make sure that Parallels Plesk Panel is installed on this server." + +exec "${php_bin}" "$@" diff --git a/root/parallels/pool/PSA_18.0.73_17725/examiners/plesk_preupgrade_checker.log b/root/parallels/pool/PSA_18.0.73_17725/examiners/plesk_preupgrade_checker.log new file mode 100644 index 0000000000..ad2a8ba715 --- /dev/null +++ b/root/parallels/pool/PSA_18.0.73_17725/examiners/plesk_preupgrade_checker.log @@ -0,0 +1,68 @@ + +INFO: Installed Plesk version/build: 18.0.72 Ubuntu 24.04 1800250915.05... + +INFO: STEP 0: Detect system configuration... +INFO: OS: Ubuntu 24.04.3 LTS \n \l +INFO: Arch: x86_64 + +INFO: Validating the database password... +INFO: Result: OK + +INFO: Pre-Upgrade analyzer version: 18.0.73.0... + +INFO: STEP 1: Checking for main IP address... +INFO: Result: OK + +INFO: STEP 2: Checking existence of Plesk user "root" for MariaDB/MySQL database servers... +INFO: Result: OK + +INFO: STEP 3: Checking proftpd settings... +INFO: Result: OK + +INFO: STEP 4: Checking the 'Interval' parameter in the sw-collectd configuration file... +INFO: Result: OK + +INFO: STEP 5: Checking Apache status... +INFO: Result: OK + +INFO: STEP 6: Checking Panel files for the immutable bit attribute... +INFO: Result: OK + +INFO: STEP 7: Checking the possibility to change the permissions of files in the DUMP_D directory... +INFO: Result: OK + +INFO: STEP 8: Checking consistency of the IP addresses list in the Panel database... +INFO: Result: OK + +INFO: STEP 9: Checking installed APS applications... +INFO: Result: OK + +INFO: STEP 10: Checking if apsc database tables have InnoDB engine... +INFO: Result: OK + +INFO: STEP 11: Checking for custom web server configuration templates... +INFO: Result: OK + +INFO: STEP 12: Checking for domains with mixed case names... +INFO: Result: OK + +INFO: STEP 13: Checking symbolic link /usr/local/psa on /opt/psa... +INFO: Result: OK + +INFO: STEP 14: Detecting if encrypted passwords are used... +INFO: Result: OK + +INFO: STEP 15: Checking table "servers" in database "mysql"... +INFO: The administrator's password for the default MariaDB/MySQL server is encrypted. +INFO: Result: OK + +INFO: STEP 16: Checking the availability of Plesk Panel TCP ports... +INFO: Result: OK + +INFO: STEP 17: Checking for Protected Directory Users with duplicates in login field.... + +INFO: STEP 18: Checking "domains" table with duplicates in guid field.... + +INFO: STEP 19: Checking "clients" table with duplicates in guid field.... + +INFO: STEP 20: Checking if any rules are configured in the Firewall extension... diff --git a/root/parallels/pool/PSA_18.0.73_17725/examiners/py_launcher.sh b/root/parallels/pool/PSA_18.0.73_17725/examiners/py_launcher.sh new file mode 100755 index 0000000000..96dc215391 --- /dev/null +++ b/root/parallels/pool/PSA_18.0.73_17725/examiners/py_launcher.sh @@ -0,0 +1,30 @@ +#!/bin/sh +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +die() +{ + echo "$*" + exit 1 +} + +[ -f "$1" ] || die "Usage: $0 PEX [args...]" + +[ "X${PLESK_INSTALLER_DEBUG}" = "X" ] || set -x +[ "X${PLESK_INSTALLER_STRICT_MODE}" = "X" ] || set -e + +find_python_bin() +{ + local bin + for bin in "/opt/psa/bin/py3-python" "/usr/local/psa/bin/py3-python" "/usr/libexec/platform-python" "/usr/bin/python3" "/opt/psa/bin/python" "/usr/local/psa/bin/python" "/usr/bin/python2"; do + [ -x "$bin" ] || continue + python_bin="$bin" + return 0 + done + + return 1 +} + +find_python_bin || + die "Unable to locate Python interpreter to execute the script." + +exec "$python_bin" "$@" diff --git a/root/parallels/pool/PSA_18.0.73_17725/examiners/repository_check.sh b/root/parallels/pool/PSA_18.0.73_17725/examiners/repository_check.sh new file mode 100755 index 0000000000..090f121ea1 --- /dev/null +++ b/root/parallels/pool/PSA_18.0.73_17725/examiners/repository_check.sh @@ -0,0 +1,782 @@ +#!/bin/bash +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +[ -z "$PLESK_INSTALLER_DEBUG" ] || set -x +[ -z "$PLESK_INSTALLER_STRICT_MODE" ] || set -e + +export LC_ALL=C +unset GREP_OPTIONS + +RET_SUCCESS=0 +RET_WARN=1 +RET_FATAL=2 + +is_function_defined() +{ + local fn="$1" + case "$(type $fn 2>/dev/null)" in + *function*) + return 0 + ;; + esac + return 1 +} + +# @params are tags in format "key=value" +# Report body (human readable information) is read from stdin +# and copied to stderr. +make_error_report() +{ + local report_file="${PLESK_INSTALLER_ERROR_REPORT:-}" + + local python_bin= + for bin in "/opt/psa/bin/python" "/usr/local/psa/bin/python" "/usr/bin/python2" "/opt/psa/bin/py3-python" "/usr/local/psa/bin/py3-python" "/usr/libexec/platform-python" "/usr/bin/python3"; do + if [ -x "$bin" ]; then + python_bin="$bin" + break + fi + done + + if [ -n "$report_file" -a -x "$python_bin" ]; then + "$python_bin" -c 'import sys, json +report_file = sys.argv[1] +error = sys.stdin.read() + +sys.stderr.write(error) + +data = { + "error": error, +} + +for tag in sys.argv[2:]: + k, v = tag.split("=", 1) + data[k] = v + +with open(report_file, "a") as f: + json.dump(data, f) + f.write("\n") +' "$report_file" "date=$(date --utc --iso-8601=ns)" "$@" + else + cat - >&2 + fi +} + +detect_platform() +{ + . /etc/os-release + os_name="$ID" + os_version="${VERSION_ID%%.*}" + os_arch="$(uname -m)" + if [ -e /etc/debian_version ]; then + case "$os_arch" in + x86_64) pkg_arch="amd64" ;; + aarch64) pkg_arch="arm64" ;; + esac + if [ -n "$VERSION_CODENAME" ]; then + os_codename="$VERSION_CODENAME" + else + case "$os_name$os_version" in + debian10) os_codename="buster" ;; + debian11) os_codename="bullseye" ;; + debian12) os_codename="bookworm" ;; + ubuntu18) os_codename="bionic" ;; + ubuntu20) os_codename="focal" ;; + ubuntu22) os_codename="jammy" ;; + ubuntu24) os_codename="noble" ;; + esac + fi + fi + + case "$os_name$os_version" in + rhel7|centos7|cloudlinux7|virtuozzo7) + package_manager="yum" + ;; + rhel*|centos*|cloudlinux*|almalinux*|rocky*) + package_manager="dnf" + ;; + debian*|ubuntu*) + package_manager="apt" + ;; + esac + + if [ "$os_name" = "ubuntu" -o "$os_name" = "debian" ]; then + PRODUCT_ROOT_D="/opt/psa" + else + PRODUCT_ROOT_D="/usr/local/psa" + fi +} + +has_os_impl_function() +{ + local prefix="$1" + local fn="${prefix}_${os_name}${os_version}" + is_function_defined "$fn" +} + +call_os_impl_function() +{ + local prefix="$1" + shift + local fn="${prefix}_${os_name}${os_version}" + "$fn" "$@" +} + +skip_checker_on_flag() +{ + local name="$1" + local flag="$2" + + if [ -f "$flag" ]; then + echo "$name was skipped due to flag file." >&2 + exit $RET_SUCCESS + fi +} + +skip_checker_on_env() +{ + local name="$1" + local env="$2" + + if [ -n "$env" ]; then + echo "$name was skipped due to environment variable." >&2 + exit $RET_SUCCESS + fi +} + +checker_main() +{ + local fnprefix="$1" + shift + + detect_platform + # try to execute checker only if all attributes are detected + [ -n "$os_name" -a -n "$os_version" ] || return $RET_SUCCESS + + for checker in "${fnprefix}_${os_name}${os_version}" "${fnprefix}_${os_name}" "${fnprefix}"; do + if is_function_defined "$checker"; then + local rc=$RET_SUCCESS + "$checker" "$@" || rc=$? + [ "$(( $rc & $RET_FATAL ))" = "0" ] || return $RET_FATAL + [ "$(( $rc & $RET_WARN ))" = "0" ] || return $RET_WARN + return $rc + fi + done + return $RET_SUCCESS +} + +#!/bin/sh +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +# If env variable PLESK_INSTALLER_ERROR_REPORT=path_to_file is specified then in case of error +# repository_check.sh writes single line json report into it with the following fields: +# - "stage": "repositorycheck" +# - "level": "error" +# - "errtype" is one of the following: +# * "reponotcached" - repository is not cached (mostly due to unavailability). +# * "reponotenabled" - required repository is not enabled. +# * "reponotsupported" - unsupported repository is enabled. +# * "configmanagernotinstalled" - dnf config-manager is disabled. +# - "repo": repository name. +# - "date": time of error occurance ("2020-03-24T06:59:43,127545441+0000") +# - "error": human readable error message. + +report_no_repo() +{ + local repo="$1" + + make_error_report 'stage=repositorycheck' 'level=error' 'errtype=reponotenabled' "repo=$repo" <<-EOL + Plesk installation requires '$repo' OS repository to be enabled. + Make sure it is available and enabled, then try again. + EOL +} + +report_no_repo_cache() +{ + local repo="$1" + + make_error_report 'stage=repositorycheck' 'level=error' 'errtype=reponotcached' "repo=$repo" <<-EOL + Unable to create $package_manager cache for '$repo' OS repository. + Make sure the repository is available, otherwise either disable it or fix its configuration, then try again. + EOL +} + +report_unsupported_repo() +{ + local repo="$1" + + make_error_report 'stage=repositorycheck' 'level=error' 'errtype=reponotsupported' "repo=$repo" <<-EOL + Plesk installation doesn't support '$repo' OS repository. + Make sure it is disabled, then try again. + EOL +} + +report_rh_no_config_manager() +{ + local target + case "$package_manager" in + yum) + target="yum-utils package" + ;; + dnf) + target="config-manager dnf plugin" + ;; + esac + + make_error_report 'stage=repositorycheck' 'level=error' 'errtype=configmanagernotinstalled' <<-EOL + Failed to install $target. + Make sure repositories configuration of $package_manager package manager is correct + (use '$package_manager repolist --verbose' to get its actual state), then try again. + EOL +} + +check_rh_broken_repos() +{ + local rh_enabled_repos rh_available_repos + + # 1. `yum repolist` and `dnf repolist` list all repos + # which were enabled before last cache creation + # even if cache for them was not created. + # If some repo is misconfigured and cache was created with `skip_if_unavailable=1` + # then such repo will be listed anyway despite on cache state. + # If some repo was enabled after last cache creation + # then `repolist --cacheonly` will fail. + # 2. `yum repolist --verbose` and `dnf repoinfo` list only repos + # which were successfully cached before. + # These commands fail if at least one repo is not available + # and the 'skip_if_unavailable' flag is not set. + case "$package_manager" in + yum) + rh_enabled_repos="$( + { + yum repolist enabled --cacheonly -q 2>/dev/null \ + || yum repolist enabled -q --setopt='*.skip_if_unavailable=1' + } | sed -n -e '1d' -e 's/^\*\?!\?\([^/[:space:]]\+\).*/\1/p' + )" || return $RET_FATAL + + rh_available_repos="$( + yum repolist enabled --verbose --cacheonly -q --setopt='*.skip_if_unavailable=1' \ + | sed -n -e 's/^Repo-id\s*:\s*\([^/[:space:]]\+\).*/\1/p' + )" || return $RET_FATAL + ;; + dnf) + rh_enabled_repos="$( + { + dnf repolist --enabled --cacheonly -q 2>/dev/null \ + || dnf repolist --enabled -q --setopt='*.skip_if_unavailable=1' + } | sed -n -e '1d' -e 's/^!\?\(\S\+\).*/\1/p' + )" || return $RET_FATAL + + rh_available_repos="$( \ + dnf repoinfo --enabled --cacheonly -q --setopt='*.skip_if_unavailable=1' \ + | sed -n -e 's|^Repo-id\s*:\s*\(\S\+\)\s*$|\1|p' + )" || return $RET_FATAL + ;; + esac + + local rh_enabled_repos_f="$(mktemp /tmp/plesk-installer.preupgrade_checker.XXXXXX)" + echo "$rh_enabled_repos" | sort > "$rh_enabled_repos_f" + local rh_available_repos_f="$(mktemp /tmp/plesk-installer.preupgrade_checker.XXXXXX)" + echo "$rh_available_repos" | sort > "$rh_available_repos_f" + + local repo rc=0 + for repo in $(comm -23 "$rh_enabled_repos_f" "$rh_available_repos_f"); do + report_no_repo_cache "$repo" + rc=$RET_WARN + done + + rm -f "$rh_enabled_repos_f" "$rh_available_repos_f" + + return $rc +} + +has_rh_enabled_repo() +{ + local repo="$1" + + # Try to get list of repos from cache first. + # If some repo was enabled after last cache creation + # or some repo is unavailable the query from cache will fail. + # Try to fetch actual metadata in this case. + case "$package_manager" in + yum) + # Repo-id may end with OS version and/or architecture + # if baseurl of the repo refers to $releasever and/or $basearch variables + # eg 'epel/7/x86_64', 'epel/7', 'epel/x86_64' + { + yum repolist enabled --verbose --cacheonly -q 2>/dev/null \ + || yum repolist enabled --verbose -q --setopt='*.skip_if_unavailable=1' + } | grep -E -q "^Repo-id\s*: $repo(/.+)?\s*$" + ;; + dnf) + # note: --noplugins may cause failure and empty output on RedHat + { + dnf repoinfo --enabled --cacheonly -q 2>/dev/null \ + || dnf repoinfo --enabled -q --setopt='*.skip_if_unavailable=1' + } | grep -E -q "^Repo-id\s*: $repo\s*$" + ;; + esac +} + +has_rh_config_manager() +{ + case "$package_manager" in + yum) yum-config-manager --help >/dev/null 2>&1 ;; + dnf) dnf config-manager --help >/dev/null 2>&1 ;; + esac +} + +install_rh_config_manager() +{ + case "$package_manager" in + yum) yum install --disablerepo 'PLESK_*' -q -y 'yum-utils' --setopt='*.skip_if_unavailable=1' ;; + dnf) dnf install --disablerepo 'PLESK_*' -q -y 'dnf-command(config-manager)' --setopt='*.skip_if_unavailable=1' ;; + esac +} + +check_rh_config_manager() +{ + if ! has_rh_config_manager && ! install_rh_config_manager; then + report_rh_no_config_manager + return $RET_FATAL + fi +} + +enable_rh_repo() +{ + case "$package_manager" in + yum) yum-config-manager --enable "$@" && has_rh_enabled_repo "$@" ;; + dnf) dnf config-manager --set-enabled "$@" && has_rh_enabled_repo "$@" ;; + esac +} + +enable_sm_repo() +{ + ! has_rh_enabled_repo "$@" || return 0 + subscription-manager repos --enable "$@" || return $? + # On RedHat 8 above command may return 0 on failure with "Repositories disabled by configuration." + has_rh_enabled_repo "$@" +} + +check_epel() +{ + ! enable_rh_repo "epel" || return 0 + + # try to install epel-release from centos/extras or plesk/thirdparty repo + # and then try to update it to last version shipped by epel itself + # to make package upgradable with pum + "$package_manager" install --disablerepo 'PLESK_*' -q -y 'epel-release' --setopt='*.skip_if_unavailable=1' 2>/dev/null \ + || "$package_manager" install --disablerepo='*' --enablerepo 'PLESK_18_*-thirdparty' -q -y 'epel-release' \ + || "$package_manager" install -q -y "https://dl.fedoraproject.org/pub/epel/epel-release-latest-$os_version.noarch.rpm" \ + && "$package_manager" update -q -y 'epel-release' --setopt='*.skip_if_unavailable=1' 2>/dev/null + + # Ensure any other EPEL repos have cache for subsequent check for broken repos (AL9) + local epel_repos="$( + [ "$package_manager" != "dnf" ] || { + dnf repolist --enabled --cacheonly -q 2>/dev/null || + dnf repolist --enabled -q --setopt='*.skip_if_unavailable=1' + } | sed -n -e '1d' -e 's/^!\?\(epel\S\+\).*/\1/p' + )" + for repo in $epel_repos; do + "$package_manager" makecache --repo "$repo" -q + done + + ! has_rh_enabled_repo "epel" || return 0 + + report_no_repo "epel" + return $RET_FATAL +} + +check_codeready() +{ + local repo_rhel="codeready-builder-for-rhel-$os_version-$os_arch-rpms" + local repo_rhui="codeready-builder-for-rhel-$os_version-rhui-rpms" + local repo_rhui_alt="codeready-builder-for-rhel-$os_version-$os_arch-rhui-rpms" + local repo_rhui_alt2="rhui-codeready-builder-for-rhel-$os_version-$os_arch-rhui-rpms" + + ! enable_sm_repo "$repo_rhel" || return 0 + ! enable_rh_repo "$repo_rhui" || return 0 + ! enable_rh_repo "$repo_rhui_alt" || return 0 + ! enable_rh_repo "$repo_rhui_alt2" || return 0 + + report_no_repo "$repo_rhel" + return $RET_FATAL +} + +check_optional() +{ + local repo_rhel="rhel-$os_version-server-optional-rpms" + local repo_rhui="rhel-$os_version-server-rhui-optional-rpms" + + ! enable_sm_repo "$repo_rhel" || return 0 + ! enable_rh_repo "$repo_rhui" || return 0 + + report_no_repo "$repo_rhel" + return $RET_FATAL +} + +check_repos_rhel9() +{ + check_rh_config_manager || return $? + + local rc=0 + + check_epel || rc="$(( $rc | $? ))" + check_codeready || rc="$(( $rc | $? ))" + check_rh_broken_repos || rc="$(( $rc | $? ))" + + return $rc +} + +check_repos_almalinux9() +{ + check_rh_config_manager || return $? + + local rc=0 + check_epel || rc="$(( $rc | $? ))" + check_rh_broken_repos || rc="$(( $rc | $? ))" + + # powertools is renamed to crb since AlmaLinux 9 + ! enable_rh_repo "crb" || return $rc + + report_no_repo "crb" + return $RET_FATAL +} + +check_repos_cloudlinux9() +{ + check_repos_almalinux9 "$@" +} + +check_repos_almalinux10() +{ + check_repos_almalinux9 "$@" +} + +check_repos_centos8() +{ + check_rh_config_manager || return $? + + local rc=0 + check_epel || rc="$(( $rc | $? ))" + check_rh_broken_repos || rc="$(( $rc | $? ))" + + # names of repos are lowercased since 8.3 + ! enable_rh_repo "powertools" || return $rc + ! enable_rh_repo "PowerTools" || return $rc + + report_no_repo "powertools" + return $RET_FATAL +} + +check_repos_cloudlinux8() +{ + check_rh_config_manager || return $? + + local rc=0 + check_epel || rc="$(( $rc | $? ))" + check_rh_broken_repos || rc="$(( $rc | $? ))" + + # names of repos are changed since 8.5 + ! enable_rh_repo "powertools" || return $rc + ! enable_rh_repo "cloudlinux-PowerTools" || return $rc + + report_no_repo "powertools" + return $RET_FATAL +} + +check_repos_rhel8() +{ + check_rh_config_manager || return $? + + local rc=0 + check_epel || rc="$(( $rc | $? ))" + check_rh_broken_repos || rc="$(( $rc | $? ))" + + [ "$1" = "install" ] || return $rc + + check_codeready || rc="$(( $rc | $? ))" + + return $rc +} + +check_repos_almalinux8() +{ + check_repos_centos8 "$@" +} + +check_repos_rocky8() +{ + check_repos_centos8 "$@" +} + +check_repos_rhel7() +{ + check_rh_config_manager || return $? + + local rc=0 + + check_epel || rc="$(( $rc | $? ))" + check_optional || rc="$(( $rc | $? ))" + check_rh_broken_repos || rc="$(( $rc | $? ))" + + return $rc +} + +check_repos_centos7_based() +{ + check_rh_config_manager || return $? + + local rc=0 + + check_epel || rc="$(( $rc | $? ))" + check_rh_broken_repos || rc="$(( $rc | $? ))" + + return $rc +} + +sed_escape() +{ + # Note: this is not a full implementation + echo -n "$1" | sed -e 's|\.|\\.|g' +} + +switch_eol_centos_repos() +{ + local old_mirrorlist_host="mirrorlist.centos.org" + local old_host="mirror.centos.org" + local new_host="vault.centos.org" + + grep -qFw "$old_host" /etc/yum.repos.d/CentOS-*.repo 2>/dev/null || return 0 + local backup="`mktemp -d "/tmp/yum.repos.d-$(date --rfc-3339=date)-XXXXXX"`" + ! [ -d "$backup" ] || cp -raT /etc/yum.repos.d "$backup" || : + + sed -i \ + -e "s|^\s*\(mirrorlist\b[^/]*//`sed_escape "$old_mirrorlist_host"`/.*\)$|#\1|" \ + -e "s|^#*\s*baseurl\b\([^/]*\)//`sed_escape "$old_host"`/\(.*\)$|baseurl\1//$new_host/\2|" \ + /etc/yum.repos.d/CentOS-*.repo + echo "YUM package manager repositories were backed up to '$backup' and switched from $old_host to $new_host ." >&2 +} + +check_repos_centos7() +{ + switch_eol_centos_repos + + check_repos_centos7_based "$@" +} + +check_repos_cloudlinux7() +{ + check_repos_centos7_based "$@" +} + +check_repos_virtuozzo7() +{ + check_repos_centos7_based "$@" +} + +find_apt_repo() +{ + local repo="$1" + + local dist_tag= + ! [ "$os_name" = "ubuntu" ] || dist_tag="a" + ! [ "$os_name" = "debian" ] || dist_tag="n" + + if [ -z "$_apt_cache_policy" ]; then + # extract info of each available release as a string which consists of 'tag=value' + # filter out releases with priority less or equal to 100 + _apt_cache_policy="$( + apt-cache policy \ + | grep "b=$pkg_arch" \ + | grep -Eo '([a-z]=[^,]+,?)*' \ + )" + fi + + local l="$(echo "$repo" | cut -f1 -d'/')" + local d="$(echo "$repo" | cut -f2 -d'/')" + local c="$(echo "$repo" | cut -f3 -d'/')" + + # try to find releases by distribution and component + echo "$_apt_cache_policy" \ + | grep -E "(^|,)l=$l(,|$)" \ + | grep -E "(^|,)$dist_tag=$d(,|$)" \ + | grep -E "(^|,)c=$c(,|$)" \ + | while IFS="$(printf '\n')" read rel && [ -n "$rel" ]; do + l="$(echo "$rel" | grep -Eo "(^|,)l=[^,]+" | cut -f2 -d"=")" + d="$(echo "$rel" | grep -Eo "(^|,)$dist_tag=[^,]+" | cut -f2 -d"=")" + c="$(echo "$rel" | grep -Eo "(^|,)c=[^,]+" | cut -f2 -d"=")" + echo "$l/$d/$c" + done +} + +apt_install_packages() +{ + DEBIAN_FRONTEND=noninteractive LANG=C PATH=/usr/sbin:/usr/bin:/sbin:/bin \ + apt-get -qq --assume-yes -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold -o APT::Install-Recommends=no \ + install "$@" +} + +# Takes a list of suites and disables them in APT sources. +# Multiline deb822 format is supported. +disable_apt_suites_deb822() +{ + local python3=/usr/bin/python3 + + "$python3" -c 'import aptsources.sourceslist' 2>/dev/null || + apt_install_packages python3-apt + + "$python3" -c ' +import sys + +from aptsources.sourceslist import SourcesList + + +suites_to_disable=set(sys.argv[1:]) + +sources_list = SourcesList(deb822=True) + +sources_changed = False +for src in sources_list: + if src.invalid: + continue + suites = getattr(src, "suites", ()) + if not suites: + continue + new_suites = [s for s in suites if s not in suites_to_disable] + if len(new_suites) != len(suites): + sources_changed = True + if len(new_suites) == 0: + src.disabled = True + else: + src.suites = new_suites + +if sources_changed: + sources_list.save() +' "$@" + + # Since we have changed the repositories list, we should re-read _apt_cache_policy on a next call + # of the find_apt_repo function. Hence we have to reset the value of the variable + _apt_cache_policy="" +} + +disable_apt_repo() +{ + local repos_to_disable="$(find_apt_repo "$1" | cut -d '/' -f 2,3 | sort | uniq)" + if [ -z "$repos_to_disable" ]; then + return 0 + fi + + echo "$repos_to_disable" \ + | while IFS= read -r repo_to_disable && [ -n "$repo_to_disable" ]; do + local distrib=${repo_to_disable%%/*} + local component=${repo_to_disable##*/} + find /etc/apt -name "*.list" -exec \ + sed -i -e "/^\s*#/! s/.*\s$distrib\s\+$component\b/# &/" {} + + done + + # Since we have changed the repositories list, we should re-read _apt_cache_policy on a next call + # of the find_apt_repo function. Hence we have to reset the value of the variable + _apt_cache_policy="" + + return 0 +} + +check_required_apt_repo() +{ + local repo="$1" + [ -z "$(find_apt_repo "$repo")" ] || return 0 + report_no_repo "$repo" + return $RET_FATAL +} + +check_unsupported_apt_repos_ubuntu() +{ + [ -n "$os_codename" ] || return 0 + local mode="$1" + + local repos="$( + find_apt_repo "Ubuntu/[^,]+/[^,]+" | grep -v "Ubuntu/$os_codename.*/.*" + find_apt_repo "Debian[^,]*/[^,]+/[^,]+" + )" + [ -n "$repos" ] || return 0 + + echo "$repos" | while IFS="$(printf '\n')" read repo; do + report_unsupported_repo "$repo" + done + + [ "$mode" = "install" ] || return $RET_WARN + return $RET_FATAL +} + +check_repos_ubuntu18() +{ + [ -n "$os_codename" ] || return 0 + local mode="$1" + local rc=0 + + check_required_apt_repo "Ubuntu/$os_codename/main" || rc="$(( $rc | $? ))" + check_required_apt_repo "Ubuntu/$os_codename/universe" || rc="$(( $rc | $? ))" + check_required_apt_repo "Ubuntu/$os_codename-updates/main" || rc="$(( $rc | $? ))" + check_required_apt_repo "Ubuntu/$os_codename-updates/universe" || rc="$(( $rc | $? ))" + check_unsupported_apt_repos_ubuntu "$mode" || rc="$(( $rc | $? ))" + + return $rc +} + + +check_repos_ubuntu() +{ + [ -n "$os_codename" ] || return 0 + local mode="$1" + local rc=0 + + check_required_apt_repo "Ubuntu/$os_codename/main" || rc="$(( $rc | $? ))" + check_required_apt_repo "Ubuntu/$os_codename/universe" || rc="$(( $rc | $? ))" + check_unsupported_apt_repos_ubuntu "$mode" || rc="$(( $rc | $? ))" + + return $rc +} + +check_unsupported_apt_repos_debian() +{ + [ -n "$os_codename" ] || return 0 + local mode="$1" + + local repos="$( + find_apt_repo "Debian Backports/$os_codename-backports/[^,]+" + find_apt_repo "Debian[^,]*/[^,]+/[^,]+" | grep -v "Debian.*/$os_codename.*/.*" + find_apt_repo "Ubuntu/[^,]+/[^,]+" + )" + [ -n "$repos" ] || return 0 + + echo "$repos" | while IFS="$(printf '\n')" read repo; do + report_unsupported_repo "$repo" + done + + [ "$mode" = "install" ] || return $RET_WARN + return $RET_FATAL +} + +check_repos_debian() +{ + [ -n "$os_codename" ] || return 0 + local mode="$1" + local rc=0 + + if [ "$os_name" = "debian" -a "$os_version" -ge 12 ]; then + disable_apt_suites_deb822 "$os_codename-backports" + else + disable_apt_repo "Debian Backports/$os_codename-backports/[^,]+" + fi + + check_required_apt_repo "Debian/$os_codename/main" || rc="$(( $rc | $? ))" + check_unsupported_apt_repos_debian "$mode" || rc="$(( $rc | $? ))" + + return $rc +} + +# --- + +skip_checker_on_flag "Repository check" "/tmp/plesk-installer-skip-repository-check.flag" + +checker_main 'check_repos' "$1" diff --git a/root/parallels/pool/PSA_18.0.73_17725/examiners/sh_cmd.sh b/root/parallels/pool/PSA_18.0.73_17725/examiners/sh_cmd.sh new file mode 100755 index 0000000000..ed95d0acbb --- /dev/null +++ b/root/parallels/pool/PSA_18.0.73_17725/examiners/sh_cmd.sh @@ -0,0 +1,7 @@ +#!/bin/sh +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +[ "X${PLESK_INSTALLER_DEBUG}" = "X" ] || set -x +[ "X${PLESK_INSTALLER_STRICT_MODE}" = "X" ] || set -e + +exec "$@" diff --git a/root/parallels/pool/PSA_18.0.73_17725/plesk-18.0.73-ubt24.04-x86_64.inf3 b/root/parallels/pool/PSA_18.0.73_17725/plesk-18.0.73-ubt24.04-x86_64.inf3 new file mode 100644 index 0000000000..30083b61b3 --- /dev/null +++ b/root/parallels/pool/PSA_18.0.73_17725/plesk-18.0.73-ubt24.04-x86_64.inf3 @@ -0,0 +1,927 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -----BEGIN PGP PUBLIC KEY BLOCK----- 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 VNnMRY8n =Hv0N -----END PGP PUBLIC KEY BLOCK----- + + + psa + + + + + + + + + + + + + + + + + + + + + + + + + + + + + mysqlgroup + l10n + proftpd + webservers + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + imapservers + + + + + + + + + + + + + + + + + + + imapservers + + + + + + + + + + + + + + mailman + spamassassin + drweb + sophos + courier + dovecot + + + + + + + + + + + + + + + + + + + + + + mailservers + + + + + + + + + + + + + + + + + + + + mailservers + + + + + + + + + + + + + + + + + + panel + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + + + + + + + + php8.3 + + + + + + + + + + + webservers + + + + + + + + + + + + + + + + + + + + + panel + + + + + + + + + + + + + panel + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + panel + + + + + + + + + + + + + + + + + + + + panel + + + + + + + panel + + + + + + + + panel + + + + + + + panel + passenger + + + + + + ruby + + + + + + + panel + + + + + + panel + + + + + + panel + passenger + + + + + + + + + + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + panel + roundcube + postfix + dovecot + mod_fcgid + proftpd + webalizer + awstats + webservers + nginx + mysqlgroup + l10n + bind + wp-toolkit + advisor + git + xovi + imunify360 + fail2ban + modsecurity + sslit + letsencrypt + repair-kit + composer + monitoring + log-browser + ssh-terminal + site-import + sitejet + ntp-timesync + php8.3 + php8.4 + mfa + configurations-troubleshooter + + + panel + roundcube + postfix + dovecot + mod_fcgid + proftpd + webalizer + awstats + webservers + nginx + mysqlgroup + l10n + bind + wp-toolkit + advisor + git + xovi + imunify360 + fail2ban + modsecurity + sslit + letsencrypt + repair-kit + composer + monitoring + log-browser + ssh-terminal + site-import + sitejet + ntp-timesync + php8.1 + php8.2 + php8.3 + php8.4 + mfa + configurations-troubleshooter + resctrl + drweb + postgresql + spamassassin + ruby + gems-pre + nodejs + pmm + psa-firewall + watchdog + passenger + phpgroup + sophos + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/pool/PSA_18.0.73_17725/release.inf3 b/root/parallels/pool/PSA_18.0.73_17725/release.inf3 new file mode 100644 index 0000000000..4fb000283d --- /dev/null +++ b/root/parallels/pool/PSA_18.0.73_17725/release.inf3 @@ -0,0 +1,36 @@ + + + + + + + + + psa + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/pool/PSA_18.0.73_17940/examiners/check_broken_timezone.sh b/root/parallels/pool/PSA_18.0.73_17940/examiners/check_broken_timezone.sh new file mode 100755 index 0000000000..ee862642be --- /dev/null +++ b/root/parallels/pool/PSA_18.0.73_17940/examiners/check_broken_timezone.sh @@ -0,0 +1,255 @@ +#!/bin/bash +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +[ -z "$PLESK_INSTALLER_DEBUG" ] || set -x +[ -z "$PLESK_INSTALLER_STRICT_MODE" ] || set -e + +export LC_ALL=C +unset GREP_OPTIONS + +RET_SUCCESS=0 +RET_WARN=1 +RET_FATAL=2 + +is_function_defined() +{ + local fn="$1" + case "$(type $fn 2>/dev/null)" in + *function*) + return 0 + ;; + esac + return 1 +} + +# @params are tags in format "key=value" +# Report body (human readable information) is read from stdin +# and copied to stderr. +make_error_report() +{ + local report_file="${PLESK_INSTALLER_ERROR_REPORT:-}" + + local python_bin= + for bin in "/opt/psa/bin/python" "/usr/local/psa/bin/python" "/usr/bin/python2" "/opt/psa/bin/py3-python" "/usr/local/psa/bin/py3-python" "/usr/libexec/platform-python" "/usr/bin/python3"; do + if [ -x "$bin" ]; then + python_bin="$bin" + break + fi + done + + if [ -n "$report_file" -a -x "$python_bin" ]; then + "$python_bin" -c 'import sys, json +report_file = sys.argv[1] +error = sys.stdin.read() + +sys.stderr.write(error) + +data = { + "error": error, +} + +for tag in sys.argv[2:]: + k, v = tag.split("=", 1) + data[k] = v + +with open(report_file, "a") as f: + json.dump(data, f) + f.write("\n") +' "$report_file" "date=$(date --utc --iso-8601=ns)" "$@" + else + cat - >&2 + fi +} + +detect_platform() +{ + . /etc/os-release + os_name="$ID" + os_version="${VERSION_ID%%.*}" + os_arch="$(uname -m)" + if [ -e /etc/debian_version ]; then + case "$os_arch" in + x86_64) pkg_arch="amd64" ;; + aarch64) pkg_arch="arm64" ;; + esac + if [ -n "$VERSION_CODENAME" ]; then + os_codename="$VERSION_CODENAME" + else + case "$os_name$os_version" in + debian10) os_codename="buster" ;; + debian11) os_codename="bullseye" ;; + debian12) os_codename="bookworm" ;; + ubuntu18) os_codename="bionic" ;; + ubuntu20) os_codename="focal" ;; + ubuntu22) os_codename="jammy" ;; + ubuntu24) os_codename="noble" ;; + esac + fi + fi + + case "$os_name$os_version" in + rhel7|centos7|cloudlinux7|virtuozzo7) + package_manager="yum" + ;; + rhel*|centos*|cloudlinux*|almalinux*|rocky*) + package_manager="dnf" + ;; + debian*|ubuntu*) + package_manager="apt" + ;; + esac + + if [ "$os_name" = "ubuntu" -o "$os_name" = "debian" ]; then + PRODUCT_ROOT_D="/opt/psa" + else + PRODUCT_ROOT_D="/usr/local/psa" + fi +} + +has_os_impl_function() +{ + local prefix="$1" + local fn="${prefix}_${os_name}${os_version}" + is_function_defined "$fn" +} + +call_os_impl_function() +{ + local prefix="$1" + shift + local fn="${prefix}_${os_name}${os_version}" + "$fn" "$@" +} + +skip_checker_on_flag() +{ + local name="$1" + local flag="$2" + + if [ -f "$flag" ]; then + echo "$name was skipped due to flag file." >&2 + exit $RET_SUCCESS + fi +} + +skip_checker_on_env() +{ + local name="$1" + local env="$2" + + if [ -n "$env" ]; then + echo "$name was skipped due to environment variable." >&2 + exit $RET_SUCCESS + fi +} + +checker_main() +{ + local fnprefix="$1" + shift + + detect_platform + # try to execute checker only if all attributes are detected + [ -n "$os_name" -a -n "$os_version" ] || return $RET_SUCCESS + + for checker in "${fnprefix}_${os_name}${os_version}" "${fnprefix}_${os_name}" "${fnprefix}"; do + if is_function_defined "$checker"; then + local rc=$RET_SUCCESS + "$checker" "$@" || rc=$? + [ "$(( $rc & $RET_FATAL ))" = "0" ] || return $RET_FATAL + [ "$(( $rc & $RET_WARN ))" = "0" ] || return $RET_WARN + return $rc + fi + done + return $RET_SUCCESS +} + +#!/bin/sh +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +# If env variable PLESK_INSTALLER_ERROR_REPORT=path_to_file is specified then in case of error +# check-broken-tz.sh writes single line json report into it with the following fields: +# - "stage": "timezonefix" +# - "level": "error" +# - "errtype": "failure" +# - "date": time of error occurance ("2024-07-24T06:59:43,127545441+0000") +# - "error": human readable error message + +report_dpkg_configure_fail() +{ + local pkgname="$1" + make_error_report 'stage=timezonefix' 'level=error' 'errtype=dpkgconfigurefailed' <<-EOL + Could not configure the packages ( $pkgname ). See https://support.plesk.com/hc/en-us/articles/24721507961623-Plesk-provides-error-on-update-Package-tzdata-is-not-configured-yet for more details. + EOL +} + +report_get_tz_fail() +{ + make_error_report 'stage=timezonefix' 'level=error' 'errtype=gettzfailed' <<-EOL + Could not get the system timezone. See https://support.plesk.com/hc/en-us/articles/24721507961623-Plesk-provides-error-on-update-Package-tzdata-is-not-configured-yet for more details. + EOL +} + +report_set_tz_fail() +{ + local tz="$1" + + make_error_report 'stage=timezonefix' 'level=error' 'errtype=settzfailed' <<-EOL + Could not set the system timezone ( $tz ). See https://support.plesk.com/hc/en-us/articles/24721507961623-Plesk-provides-error-on-update-Package-tzdata-is-not-configured-yet for more details. + EOL +} + +get_current_tz() +{ + [ -L /etc/localtime ] || return 1 + + local tz + tz="$(readlink -m /etc/localtime)" || return 1 + [ -f "$tz" ] || return 1 + case "$tz" in + /usr/share/zoneinfo/*) ;; + *) return 1;; + esac + tz="${tz#/usr/share/zoneinfo/}" + [ -n "$tz" ] || return 1 + + echo -n "${tz}" +} + +check_timezone_ubuntu() +{ + [ -n "$os_codename" ] || return 0 + local mode="$1" + + # PPP-65676: Plesk update fails on ubuntu if timezone is CET + if dpkg-query --showformat='${db:Status-Status}\n' --show 'tzdata' | grep -wq 'half-configured'; then + local origtz + origtz=$(get_current_tz) + if [ $? != 0 ]; then + report_get_tz_fail + return $RET_WARN + fi + if ! timedatectl set-timezone 'Etc/UTC'; then + timedatectl set-timezone "$origtz" + report_set_tz_fail 'Etc/UTC' + return $RET_WARN + fi + if ! dpkg --configure 'tzdata'; then + timedatectl set-timezone "$origtz" + report_dpkg_configure_fail 'tzdata' + return $RET_WARN + fi + if ! timedatectl set-timezone "$origtz"; then + report_set_tz_fail "$origtz" + return $RET_WARN + fi + fi + + return 0 +} + +# --- + +skip_checker_on_flag "Broken timezone check" "/tmp/plesk-installer-skip-check-broken-timezone.flag" + +checker_main 'check_timezone' "$1" diff --git a/root/parallels/pool/PSA_18.0.73_17940/examiners/disk_space_check.sh b/root/parallels/pool/PSA_18.0.73_17940/examiners/disk_space_check.sh new file mode 100755 index 0000000000..1fdfb44037 --- /dev/null +++ b/root/parallels/pool/PSA_18.0.73_17940/examiners/disk_space_check.sh @@ -0,0 +1,542 @@ +#!/bin/bash +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +[ -z "$PLESK_INSTALLER_DEBUG" ] || set -x +[ -z "$PLESK_INSTALLER_STRICT_MODE" ] || set -e + +export LC_ALL=C +unset GREP_OPTIONS + +RET_SUCCESS=0 +RET_WARN=1 +RET_FATAL=2 + +is_function_defined() +{ + local fn="$1" + case "$(type $fn 2>/dev/null)" in + *function*) + return 0 + ;; + esac + return 1 +} + +# @params are tags in format "key=value" +# Report body (human readable information) is read from stdin +# and copied to stderr. +make_error_report() +{ + local report_file="${PLESK_INSTALLER_ERROR_REPORT:-}" + + local python_bin= + for bin in "/opt/psa/bin/python" "/usr/local/psa/bin/python" "/usr/bin/python2" "/opt/psa/bin/py3-python" "/usr/local/psa/bin/py3-python" "/usr/libexec/platform-python" "/usr/bin/python3"; do + if [ -x "$bin" ]; then + python_bin="$bin" + break + fi + done + + if [ -n "$report_file" -a -x "$python_bin" ]; then + "$python_bin" -c 'import sys, json +report_file = sys.argv[1] +error = sys.stdin.read() + +sys.stderr.write(error) + +data = { + "error": error, +} + +for tag in sys.argv[2:]: + k, v = tag.split("=", 1) + data[k] = v + +with open(report_file, "a") as f: + json.dump(data, f) + f.write("\n") +' "$report_file" "date=$(date --utc --iso-8601=ns)" "$@" + else + cat - >&2 + fi +} + +detect_platform() +{ + . /etc/os-release + os_name="$ID" + os_version="${VERSION_ID%%.*}" + os_arch="$(uname -m)" + if [ -e /etc/debian_version ]; then + case "$os_arch" in + x86_64) pkg_arch="amd64" ;; + aarch64) pkg_arch="arm64" ;; + esac + if [ -n "$VERSION_CODENAME" ]; then + os_codename="$VERSION_CODENAME" + else + case "$os_name$os_version" in + debian10) os_codename="buster" ;; + debian11) os_codename="bullseye" ;; + debian12) os_codename="bookworm" ;; + ubuntu18) os_codename="bionic" ;; + ubuntu20) os_codename="focal" ;; + ubuntu22) os_codename="jammy" ;; + ubuntu24) os_codename="noble" ;; + esac + fi + fi + + case "$os_name$os_version" in + rhel7|centos7|cloudlinux7|virtuozzo7) + package_manager="yum" + ;; + rhel*|centos*|cloudlinux*|almalinux*|rocky*) + package_manager="dnf" + ;; + debian*|ubuntu*) + package_manager="apt" + ;; + esac + + if [ "$os_name" = "ubuntu" -o "$os_name" = "debian" ]; then + PRODUCT_ROOT_D="/opt/psa" + else + PRODUCT_ROOT_D="/usr/local/psa" + fi +} + +has_os_impl_function() +{ + local prefix="$1" + local fn="${prefix}_${os_name}${os_version}" + is_function_defined "$fn" +} + +call_os_impl_function() +{ + local prefix="$1" + shift + local fn="${prefix}_${os_name}${os_version}" + "$fn" "$@" +} + +skip_checker_on_flag() +{ + local name="$1" + local flag="$2" + + if [ -f "$flag" ]; then + echo "$name was skipped due to flag file." >&2 + exit $RET_SUCCESS + fi +} + +skip_checker_on_env() +{ + local name="$1" + local env="$2" + + if [ -n "$env" ]; then + echo "$name was skipped due to environment variable." >&2 + exit $RET_SUCCESS + fi +} + +checker_main() +{ + local fnprefix="$1" + shift + + detect_platform + # try to execute checker only if all attributes are detected + [ -n "$os_name" -a -n "$os_version" ] || return $RET_SUCCESS + + for checker in "${fnprefix}_${os_name}${os_version}" "${fnprefix}_${os_name}" "${fnprefix}"; do + if is_function_defined "$checker"; then + local rc=$RET_SUCCESS + "$checker" "$@" || rc=$? + [ "$(( $rc & $RET_FATAL ))" = "0" ] || return $RET_FATAL + [ "$(( $rc & $RET_WARN ))" = "0" ] || return $RET_WARN + return $rc + fi + done + return $RET_SUCCESS +} + +#!/bin/sh +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +# If env variable PLESK_INSTALLER_ERROR_REPORT=path_to_file is specified then in case of error +# disk_space_check.sh writes single line json report into it with the following fields: +# - "stage": "diskspacecheck" +# - "level": "error" +# - "errtype": "notenoughdiskspace" +# - "volume": volume with not enough diskspace (e.g. "/") +# - "required": required diskspace on the volume, human readable (e.g. "600 MB") +# - "available": available diskspace on the volume, human readable (e.g. "255 MB") +# - "needtofree": amount of diskspace which should be freed on the volume, human readable (e.g. "345 MB") +# - "date": time of error occurance ("2020-03-24T06:59:43,127545441+0000") +# - "error": human readable error message ("There is not enough disk space available in the / directory.") + +# Required values below for Full installation are in MB. See 'du -cs -BM /*' and 'df -Pm'. + +required_disk_space_cloudlinux7() +{ + case "$1" in + /opt) echo 900 ;; + /usr) echo 4400 ;; + /var) echo 600 ;; + /tmp) echo 100 ;; + esac +} + +required_disk_space_cloudlinux8() +{ + case "$1" in + /opt) echo 1200 ;; + /usr) echo 4400 ;; + /var) echo 700 ;; + /tmp) echo 100 ;; + esac +} + +required_disk_space_centos7() +{ + case "$1" in + /opt) echo 900 ;; + /usr) echo 4100 ;; + /var) echo 600 ;; + /tmp) echo 100 ;; + esac +} + +required_disk_space_centos8() +{ + case "$1" in + /opt) echo 900 ;; + /usr) echo 4500 ;; + /var) echo 800 ;; + /tmp) echo 100 ;; + esac +} + +required_disk_space_virtuozzo7() +{ + required_disk_space_centos7 "$1" +} + +required_disk_space_rhel7() +{ + required_disk_space_centos7 "$1" +} + +required_disk_space_rhel8() +{ + required_disk_space_centos8 "$1" +} + +required_disk_space_almalinux8() +{ + required_disk_space_centos8 "$1" +} + +required_disk_space_rocky8() +{ + required_disk_space_centos8 "$1" +} + +required_disk_space_rhel9() +{ + case "$1" in + /opt) echo 500 ;; + /usr) echo 4000 ;; + /var) echo 800 ;; + /tmp) echo 100 ;; + esac +} + +required_disk_space_almalinux9() +{ + required_disk_space_rhel9 "$1" +} + +required_disk_space_almalinux10() +{ + required_disk_space_almalinux9 "$1" +} + +required_disk_space_cloudlinux9() +{ + required_disk_space_rhel9 "$1" +} + +required_disk_space_debian10() +{ + case "$1" in + /opt) echo 1800 ;; + /usr) echo 2300 ;; + /var) echo 1700 ;; + /tmp) echo 100 ;; + esac +} + +required_disk_space_debian11() +{ + case "$1" in + /opt) echo 1500 ;; + /usr) echo 3100 ;; + /var) echo 1800 ;; + /tmp) echo 100 ;; + esac +} + +required_disk_space_debian12() +{ + case "$1" in + /opt) echo 2700 ;; + /usr) echo 2500 ;; + /var) echo 2200 ;; + /tmp) echo 100 ;; + esac +} + +required_disk_space_debian13() +{ + case "$1" in + /opt) echo 2700 ;; + /usr) echo 2500 ;; + /var) echo 2200 ;; + /tmp) echo 100 ;; + esac +} + +required_disk_space_ubuntu18() +{ + case "$1" in + /opt) echo 900 ;; + /usr) echo 1800 ;; + /var) echo 600 ;; + /tmp) echo 100 ;; + esac +} + +required_disk_space_ubuntu20() +{ + case "$1" in + /opt) echo 1800 ;; + /usr) echo 2900 ;; + /var) echo 1600 ;; + /tmp) echo 100 ;; + esac +} + +required_disk_space_ubuntu22() +{ + case "$1" in + /opt) echo 1800 ;; + /usr) echo 3900 ;; + /var) echo 1900 ;; + /tmp) echo 100 ;; + esac +} + +required_disk_space_ubuntu24() +{ + case "$1" in + /opt) echo 3200 ;; + /usr) echo 1800 ;; + /var) echo 2400 ;; + /tmp) echo 100 ;; + esac +} + +required_update_upgrade_disk_space() +{ + case "$1" in + /opt) echo 100 ;; + /usr) echo 300 ;; + /var) echo 600 ;; + /tmp) echo 100 ;; + esac +} + +clean_tmp() +{ + local volume="$1" + local path="/tmp" + is_path_on_volume "$path" "$volume" || return 0 + + echo "Cleaning $path via 'systemd-tmpfiles --clean --prefix $path'" + systemd-tmpfiles --clean --prefix "$path" 2>&1 +} + +clean_yum() +{ + local volume="$1" + local path="/var/cache/yum" + is_path_on_volume "$path" "$volume" || return 0 + + echo "Cleaning $path via 'yum clean all'" + yum clean all 2>&1 + + # The command above doesn't clean untracked repos (missing in configuration), clean if left > 2 Mb + [ "`du -sm "$path" | awk '{ print $1 }'`" -gt 2 ] || return 0 + echo "Cleaning $path via 'rm -rf $path/*'" + rm -rf "$path"/* 2>&1 +} + +clean_dnf() +{ + local volume="$1" + local path="/var/cache/dnf" + is_path_on_volume "$path" "$volume" || return 0 + + echo "Cleaning $path via 'dnf clean all'" + dnf clean all 2>&1 +} + +clean_apt() +{ + local volume="$1" + local path="/var/cache/apt" + is_path_on_volume "$path" "$volume" || return 0 + + echo "Cleaning $path via 'apt-get clean'" + apt-get clean 2>&1 +} + +clean_journal() +{ + local volume="$1" + local path="/var/log/journal" + is_path_on_volume "$path" "$volume" || return 0 + + # Note that --rotate may cause more space to be freed, but may also cause more space to be used + echo "Cleaning $path via 'journalctl --vacuum-time 1d'" + journalctl --vacuum-time 1d 2>&1 +} + +clean_ext_packages() +{ + local volume="$1" + local path="$PRODUCT_ROOT_D/var/modules-packages" + is_path_on_volume "$path" "$volume" || return 0 + + echo "Cleaning $path via 'rm -rf $path/*'" + rm -rf "$path"/* 2>&1 +} + +# @param $1 target directory +mount_point() +{ + df -Pm $1 | awk 'NR==2 { print $6 }' +} + +# @param $1 target directory +available_disk_space() +{ + df -Pm $1 | awk 'NR==2 { print $4 }' +} + +is_path_on_volume() +{ + local path="$1" + local volume="$2" + [ -d "$path" ] && [ "`mount_point "$path"`" = "$volume" ] +} + +# @param $1 target directory +# @param $2 mode (install/upgrade/update) +req_disk_space() +{ + if [ "$2" != "install" ]; then + required_update_upgrade_disk_space "$1" + return + fi + + has_os_impl_function "required_disk_space" || { + echo "There are no requirements defined for $os_name$os_version." >&2 + echo "Disk space check cannot be performed." >&2 + exit $RET_WARN + } + call_os_impl_function "required_disk_space" "$1" +} + +human_readable_size() +{ + echo "$1" | awk ' + function human(x) { + s = "MGTEPYZ"; + while (x >= 1000 && length(s) > 1) { + x /= 1024; s = substr(s, 2); + } + # 0.05 below will make sure the value is rounded up + return sprintf("%.1f %sB", x + 0.05, substr(s, 1, 1)); + } + { print human($1); }' +} + +# @param $1 target directory +# @param $2 required disk space +# @param $3 check only flag (don't emit errors) +check_available_disk_space() +{ + local volume="$1" + local required="$2" + local check_only="${3:-}" + local available="$(available_disk_space "$volume")" + if [ "$available" -lt "$required" ]; then + local needtofree + needtofree="`human_readable_size $((required - available))`" + [ -n "$check_only" ] || + make_error_report 'stage=diskspacecheck' 'level=error' 'errtype=notenoughdiskspace' \ + "volume=$volume" "required=$required MB" "available=$available MB" "needtofree=$needtofree" \ + <<-EOL + There is not enough disk space available in the $1 directory. + You need to free up $needtofree. + EOL + return "$RET_FATAL" + fi +} + +# @param $1 target directory +# @param $2 required disk space +clean_and_check_available_disk_space() +{ + if [ -n "$PLESK_INSTALLER_FORCE_CLEAN_DISK_SPACE" ] || ! check_available_disk_space "$@" --check-only; then + clean_disk_space "$1" + check_available_disk_space "$@" + fi +} + +# Cleans up disk space on the volume +clean_disk_space() +{ + local volume="$1" + for cleanup_func in clean_tmp clean_yum clean_dnf clean_apt clean_journal clean_ext_packages; do + "$cleanup_func" "$volume" + done +} + +# @param $1 mode (install/upgrade/update) +clean_and_check_disk_space() +{ + local mode="$1" + local shared=0 + + for target_directory in /opt /usr /var /tmp; do + local required=$(req_disk_space "$target_directory" "$mode") + [ -n "$required" ] || return "$RET_WARN" + + if is_path_on_volume "$target_directory" "/"; then + shared="$((shared + required))" + else + clean_and_check_available_disk_space "$target_directory" "$required" || return $? + fi + done + + clean_and_check_available_disk_space "/" "$shared" || return $? +} + +checker_main 'clean_and_check_disk_space' "$1" diff --git a/root/parallels/pool/PSA_18.0.73_17940/examiners/license_key_check.php b/root/parallels/pool/PSA_18.0.73_17940/examiners/license_key_check.php new file mode 100644 index 0000000000..917e44709f --- /dev/null +++ b/root/parallels/pool/PSA_18.0.73_17940/examiners/license_key_check.php @@ -0,0 +1,111 @@ += 10.0.0 */ +if (!is_array($vers)) { + $vers = [$vers]; +} + +$match = false; +foreach ($vers as $ver) { + if (!is_array($ver)) { + $match |= strtok($ver, ".") == strtok($targetVersion, "."); + } else { + $match |= ("any" == $ver[0] || version_compare($ver[0], $targetVersion) <= 0) && + ("any" == $ver[1] || version_compare($ver[1], $targetVersion) >= 0); + } +} + +if ($match) { + fwrite(STDERR, "You do not need to upgrade the current license key.\n"); + fwrite(STDOUT, "License upgrade check to $targetVersion can be skipped.\n"); + fwrite(STDOUT, "Plesk versions compatible with the license key: " . preg_replace('/\n\s*/', '', var_export($vers, true)) . "\n"); + finish(0); +} + +if (!function_exists('ka_is_key_upgrade_available')) { + // Plesk 17.0 + fwrite(STDERR, "Cannot check whether Plesk license key upgrade is available.\n"); + finish(1, false); +} + +$si = getServerInfo(); +$result = ka_is_key_upgrade_available($prod, $targetVersion, $si); + +$isConfused = false; +switch ($result['code']) { + case RESULT_LICENSE_OK: + fwrite(STDERR, "The licensing server accepted the key upgrade request.\n"); + fwrite(STDERR, "License upgrade to $targetVersion is available.\n"); + fwrite(STDERR, "Response from the licensing server: {$result['message']}\n"); + finish(0); + case RESULT_NETWORK_PROBLEM: + fwrite(STDERR, "Unable to connect to the licensing server to check if license upgrade is available.\n"); + fwrite(STDERR, "Error message: {$result['message']}\n"); + finish(2, false); + case RESULT_LICENSE_PROBLEM: + fwrite(STDERR, "Warning: Your Plesk license key cannot be upgraded.\n"); + fwrite(STDERR, "Response from the licensing server: {$result['message']}\n"); + finish(2); + default: + $isConfused = true; + // fall-through + case RESULT_ERROR: + // This includes "Software Update Service (SUS) is not found for the given license key" case, but also many others. + fwrite(STDERR, "Failed to check whether a new license key is available.\n"); + fwrite(STDERR, "Error message: {$result['message']}\n"); + if ($isConfused) { + fwrite(STDERR, "Error code: {$result['code']}\n"); + } + finish(2, !$isConfused); +} diff --git a/root/parallels/pool/PSA_18.0.73_17940/examiners/package_manager_check.sh b/root/parallels/pool/PSA_18.0.73_17940/examiners/package_manager_check.sh new file mode 100755 index 0000000000..b089061d97 --- /dev/null +++ b/root/parallels/pool/PSA_18.0.73_17940/examiners/package_manager_check.sh @@ -0,0 +1,224 @@ +#!/bin/bash +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +[ -z "$PLESK_INSTALLER_DEBUG" ] || set -x +[ -z "$PLESK_INSTALLER_STRICT_MODE" ] || set -e + +export LC_ALL=C +unset GREP_OPTIONS + +RET_SUCCESS=0 +RET_WARN=1 +RET_FATAL=2 + +is_function_defined() +{ + local fn="$1" + case "$(type $fn 2>/dev/null)" in + *function*) + return 0 + ;; + esac + return 1 +} + +# @params are tags in format "key=value" +# Report body (human readable information) is read from stdin +# and copied to stderr. +make_error_report() +{ + local report_file="${PLESK_INSTALLER_ERROR_REPORT:-}" + + local python_bin= + for bin in "/opt/psa/bin/python" "/usr/local/psa/bin/python" "/usr/bin/python2" "/opt/psa/bin/py3-python" "/usr/local/psa/bin/py3-python" "/usr/libexec/platform-python" "/usr/bin/python3"; do + if [ -x "$bin" ]; then + python_bin="$bin" + break + fi + done + + if [ -n "$report_file" -a -x "$python_bin" ]; then + "$python_bin" -c 'import sys, json +report_file = sys.argv[1] +error = sys.stdin.read() + +sys.stderr.write(error) + +data = { + "error": error, +} + +for tag in sys.argv[2:]: + k, v = tag.split("=", 1) + data[k] = v + +with open(report_file, "a") as f: + json.dump(data, f) + f.write("\n") +' "$report_file" "date=$(date --utc --iso-8601=ns)" "$@" + else + cat - >&2 + fi +} + +detect_platform() +{ + . /etc/os-release + os_name="$ID" + os_version="${VERSION_ID%%.*}" + os_arch="$(uname -m)" + if [ -e /etc/debian_version ]; then + case "$os_arch" in + x86_64) pkg_arch="amd64" ;; + aarch64) pkg_arch="arm64" ;; + esac + if [ -n "$VERSION_CODENAME" ]; then + os_codename="$VERSION_CODENAME" + else + case "$os_name$os_version" in + debian10) os_codename="buster" ;; + debian11) os_codename="bullseye" ;; + debian12) os_codename="bookworm" ;; + ubuntu18) os_codename="bionic" ;; + ubuntu20) os_codename="focal" ;; + ubuntu22) os_codename="jammy" ;; + ubuntu24) os_codename="noble" ;; + esac + fi + fi + + case "$os_name$os_version" in + rhel7|centos7|cloudlinux7|virtuozzo7) + package_manager="yum" + ;; + rhel*|centos*|cloudlinux*|almalinux*|rocky*) + package_manager="dnf" + ;; + debian*|ubuntu*) + package_manager="apt" + ;; + esac + + if [ "$os_name" = "ubuntu" -o "$os_name" = "debian" ]; then + PRODUCT_ROOT_D="/opt/psa" + else + PRODUCT_ROOT_D="/usr/local/psa" + fi +} + +has_os_impl_function() +{ + local prefix="$1" + local fn="${prefix}_${os_name}${os_version}" + is_function_defined "$fn" +} + +call_os_impl_function() +{ + local prefix="$1" + shift + local fn="${prefix}_${os_name}${os_version}" + "$fn" "$@" +} + +skip_checker_on_flag() +{ + local name="$1" + local flag="$2" + + if [ -f "$flag" ]; then + echo "$name was skipped due to flag file." >&2 + exit $RET_SUCCESS + fi +} + +skip_checker_on_env() +{ + local name="$1" + local env="$2" + + if [ -n "$env" ]; then + echo "$name was skipped due to environment variable." >&2 + exit $RET_SUCCESS + fi +} + +checker_main() +{ + local fnprefix="$1" + shift + + detect_platform + # try to execute checker only if all attributes are detected + [ -n "$os_name" -a -n "$os_version" ] || return $RET_SUCCESS + + for checker in "${fnprefix}_${os_name}${os_version}" "${fnprefix}_${os_name}" "${fnprefix}"; do + if is_function_defined "$checker"; then + local rc=$RET_SUCCESS + "$checker" "$@" || rc=$? + [ "$(( $rc & $RET_FATAL ))" = "0" ] || return $RET_FATAL + [ "$(( $rc & $RET_WARN ))" = "0" ] || return $RET_WARN + return $rc + fi + done + return $RET_SUCCESS +} + +#!/bin/sh +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +check_package_manager_deb_based() +{ + local output= + output="`dpkg --audit 2>&1`" || output="$output"$'\n'"'dpkg --audit' finished with error code $?." + + if [ -n "$output" ]; then + make_error_report 'stage=packagemanagercheck' 'level=error' 'errtype=brokenpackages' <<-EOL + The system package manager reports the following problems: + + $output + + To continue with the installation, you need to resolve these issues + using the procedure below: + + 1. Make sure you have a full server snapshot. Although the + following steps are usually safe, they can still cause + data loss or irreversible changes. + 2. Run 'dpkg --configure -a'. This command can fix some of the + issues. However, it may fail. Regardless if it fails or not, + proceed with the following steps. + 3. Run 'PLESK_INSTALLER_SKIP_PACKAGE_MANAGER_CHECK=1 plesk installer update --skip-cleanup'. + Instead of 'update', you may need to use the command you used + previously (for example, 'upgrade' or 'install'). + 4. The next step depends on the outcome of the previous one: + - If step 3 was completed with the "You already have the latest + version of product(s) and all the selected components installed. + Installation will not continue." message, + run 'plesk repair installation'. + - If step 3 failed, run 'dpkg --audit'. This command can show you + packages that need to be reinstalled. To reinstall them, run + 'apt-get install --reinstall '. + 5. Run 'plesk installer update' to revert temporary changes and + validate that the issues are resolved. If the command fails or + triggers this check again, contact Plesk support. + + For more information, see + https://support.plesk.com/hc/en-us/articles/12871173047447-Plesk-update-on-Debian-Ubuntu-fails-dpkg-was-interrupted-you-must-manually-run-dpkg-configure-a-to-correct-the-problem + EOL + return "$RET_FATAL" + fi +} + +check_package_manager_debian() +{ + check_package_manager_deb_based +} + +check_package_manager_ubuntu() +{ + check_package_manager_deb_based +} + +skip_checker_on_env "Package manager check" "$PLESK_INSTALLER_SKIP_PACKAGE_MANAGER_CHECK" +skip_checker_on_flag "Package manager check" "/tmp/plesk-installer-skip-package-manager-check.flag" +checker_main 'check_package_manager' "$@" diff --git a/root/parallels/pool/PSA_18.0.73_17940/examiners/panel_preupgrade_checker.php b/root/parallels/pool/PSA_18.0.73_17940/examiners/panel_preupgrade_checker.php new file mode 100644 index 0000000000..181534e61e --- /dev/null +++ b/root/parallels/pool/PSA_18.0.73_17940/examiners/panel_preupgrade_checker.php @@ -0,0 +1,2401 @@ +_checkMainIP(); //:INFO: Checking for main IP address https://support.plesk.com/hc/articles/12377857361687 + $this->_checkMySQLDatabaseUserRoot(); //:INFO: Plesk user "root" for MySQL database servers have not access to phpMyAdmin https://support.plesk.com/hc/articles/12378148229399 + $this->_checkProftpdIPv6(); //:INFO: #94489 FTP service proftpd cannot be started by xinetd if IPv6 is disabled https://support.plesk.com/hc/articles/12377796102807 + $this->_checkSwCollectdIntervalSetting(); //:INFO: #105405 https://support.plesk.com/hc/articles/213362569 + $this->_checkApacheStatus(); + $this->_checkImmutableBitOnPleskFiles(); //:INFO: #128414 https://support.plesk.com/hc/articles/12377589682327 + $this->_checkAbilityToChmodInDumpd(); //:INFO: ERROR while trying to backup MySQL database. https://support.plesk.com/hc/en-us/articles/12377010033943 + $this->_checkIpcollectionReference(); //:INFO: #72751 https://support.plesk.com/hc/articles/12377666752279 + $this->_checkApsApplicationContext(); //:INFO: Broken contexts of the APS applications can lead to errors at building Apache web server configuration https://support.plesk.com/hc/articles/12377671820311 + $this->_checkApsTablesInnoDB(); + $this->_checkCustomWebServerConfigTemplates(); //:INFO: #PPPM-1195 https://support.plesk.com/hc/articles/12377740416151 + $this->_checkMixedCaseDomainIssues(); //:INFO: #PPPM-4284 https://support.plesk.com/hc/en-us/articles/12377171904151 + + if (PleskOS::isDebLike()) { + $this->_checkSymLinkToOptPsa(); //:INFO: Check that symbolic link /usr/local/psa actually exists on Debian-like OSes https://support.plesk.com/hc/articles/12377511731991 + } + + if (Util::isVz()) { + $this->_checkShmPagesLimit(); //:INFO: PSA service does not start. Unable to allocate shared memory segment. https://support.plesk.com/hc/articles/12377744827927 + + if (PleskOS::isRedHatLike()) { + $this->_checkMailDriversConflict(); //:INFO: #PPPM-955 https://support.plesk.com/hc/articles/12378148767895 + } + } + } + + $this->_checkForCryptPasswords(); + $this->_checkMysqlServersTable(); //:INFO: Checking existing table mysql.servers + $this->_checkPleskTCPPorts(); //:INFO: Check the availability of Plesk TCP ports https://support.plesk.com/hc/articles/12377821243159 + + if (Util::isWindows()) { + $this->_checkPhprcSystemVariable(); //:INFO: #PPPM-294 Checking for PHPRC system variable + $this->_unknownISAPIfilters(); //:INFO: Checking for unknown ISAPI filters and show warning https://support.plesk.com/hc/articles/213913765 + $this->_checkMSVCR(); //:INFO: Just warning about possible issues related to Microsoft Visual C++ Redistributable Packages https://support.plesk.com/hc/articles/115000201014 + $this->_checkIisFcgiDllVersion(); //:INFO: Check iisfcgi.dll file version https://support.plesk.com/hc/articles/12378148258199 + $this->_checkCDONTSmailrootFolder(); //:INFO: After upgrade Plesk change permissions on folder of Collaboration Data Objects (CDO) for NTS (CDONTS) to default, https://support.plesk.com/hc/articles/12377887661335 + $this->_checkNullClientLogin(); //:INFO: #118963 https://support.plesk.com/hc/articles/12378122202391 + $this->checkDomainControllerLocation(); //:INFO: https://support.plesk.com/hc/articles/12377107094167 + } + } + + //:INFO: PSA service does not start. Unable to allocate shared memory segment. https://support.plesk.com/hc/articles/12377744827927 + function _checkShmPagesLimit() + { + $log = Log::getInstance("Checking for limit shmpages", true); + $ubc = Util::getUserBeanCounters(); + if ((int)$ubc['shmpages']['limit'] < 40960) { + $log->emergency("Virtuozzo Container set the \"shmpages\" limit to {$ubc['shmpages']['limit']}, which is too low. This may cause the sw-engine service not to start. To resolve this issue, refer to the article at https://support.plesk.com/hc/articles/12377744827927"); + $log->resultWarning(); + return; + } + + $log->resultOk(); + } + + //:INFO: #PPPM-294 + function _checkPhprcSystemVariable() + { + $log = Log::getInstance("Checking for PHPRC system variable", true); + + $phprc = getenv('PHPRC'); + + if ($phprc) { + $log->emergency('The environment variable PHPRC is present in the system. This variable may lead to upgrade failure. Please delete this variable from the system environment.'); + $log->resultWarning(); + return; + } + + $log->resultOk(); + } + + //:INFO: ERROR while trying to backup MySQL database. https://support.plesk.com/hc/en-us/articles/12377010033943 + function _checkAbilityToChmodInDumpd() + { + $log = Log::getInstance("Checking the possibility to change the permissions of files in the DUMP_D directory", true); + + $dump_d = Util::getSettingFromPsaConf('DUMP_D'); + if (is_null($dump_d)) { + $log->warning('Unable to obtain the path to the directory defined by the DUMP_D parameter. Check that the DUMP_D parameter is set in the /etc/psa/psa.conf file.'); + $log->resultWarning(); + return; + } + + $file = $dump_d . '/pre_upgrade_test_checkAbilityToChmodInDumpd'; + + if (false === file_put_contents($file, 'test')) { + $log->emergency('Unable to write in the ' . $dump_d . ' directory. The upgrade procedure will fail. Check that the folder exists and you have write permissions for it, and repeat upgrading. '); + $log->resultWarning(); + return; + } else { + $result = @chmod($file, 0600); + unlink($file); + if (!$result) { + $log->emergency( + 'Unable to change the permissions of files in the ' . $dump_d . ' directory. ' + . 'The upgrade procedure will fail. Please refer to https://support.plesk.com/hc/articles/12377010033943 for details.' + ); + $log->resultError(); + return; + } + } + $log->resultOk(); + } + + //:INFO: #128414 https://support.plesk.com/hc/articles/12377589682327 + function _checkImmutableBitOnPleskFiles() + { + $log = Log::getInstance("Checking Panel files for the immutable bit attribute"); + + $cmd = 'lsattr -R /usr/local/psa/ 2>/dev/null |awk \'{split($1, a, ""); if (a[5] == "i") {print;}}\''; + $output = Util::exec($cmd, $code); + $files = explode('\n', $output); + + if (!empty($output)) { + $log->info('The immutable bit attribute of the following Panel files can interrupt the upgrade procedure:'); + foreach ($files as $file) { + $log->info($file); + } + $log->emergency('Files with the immutable bit attribute were found. Please check https://support.plesk.com/hc/articles/12377589682327 for details.'); + $log->resultWarning(); + return; + } + + $log->resultOk(); + } + + //:INFO: #PPPM-1195 https://support.plesk.com/hc/articles/12377740416151 + function _checkCustomWebServerConfigTemplates() + { + $log = Log::getInstance("Checking for custom web server configuration templates"); + $pleskDir = Util::getSettingFromPsaConf('PRODUCT_ROOT_D'); + $customTemplatesPath = $pleskDir . '/admin/conf/templates/custom'; + + if (is_dir($customTemplatesPath)) { + $log->warning("Directory {$customTemplatesPath} for custom web server configuration templates was found. Custom templates might be incompatible with a new Plesk version, and this might lead to failure to generate web server configuration files. Remove the directory to get rid of this warning. " + . "Please check https://support.plesk.com/hc/articles/12377740416151 for details."); + $log->resultWarning(); + return; + } + $log->resultOk(); + } + + //:INFO: #PPPM-955 https://support.plesk.com/hc/articles/12378148767895 + function _checkMailDriversConflict() + { + $log = Log::getInstance("Checking for a Plesk mail drivers conflict"); + + if (((true === PackageManager::isInstalled('psa-mail-pc-driver') || true === PackageManager::isInstalled('plesk-mail-pc-driver')) + && true === PackageManager::isInstalled('psa-qmail')) + || ((true === PackageManager::isInstalled('psa-mail-pc-driver') || true === PackageManager::isInstalled('plesk-mail-pc-driver')) + && true === PackageManager::isInstalled('psa-qmail-rblsmtpd'))) { + $log->warning("Plesk upgrade by EZ templates failed if psa-mail-pc-driver and psa-qmail or psa-qmail-rblsmtpd packages are installed. " + . "Please check https://support.plesk.com/hc/articles/12378148767895 for details."); + $log->resultWarning(); + return; + } + + $log->resultOk(); + } + + //:INFO: #118963 https://support.plesk.com/hc/articles/12378122202391 + function _checkNullClientLogin() + { + $log = Log::getInstance("Checking for accounts with empty user names"); + + $mysql = PleskDb::getInstance(); + $sql = "SELECT domains.id, domains.name, clients.login FROM domains LEFT JOIN clients ON clients.id=domains.cl_id WHERE clients.login is NULL"; + $nullLogins = $mysql->fetchAll($sql); + + if (!empty($nullLogins)) { + $log->warning('There are accounts with empty user names. This problem can cause the backup or migration operation to fail. Please see https://support.plesk.com/hc/articles/12378122202391 for the solution.'); + $log->resultWarning(); + return; + } + + $log->resultOk(); + } + + //:INFO: #105405 https://support.plesk.com/hc/articles/213362569 + function _checkSwCollectdIntervalSetting() + { + $log = Log::getInstance("Checking the 'Interval' parameter in the sw-collectd configuration file"); + + $collectd_config = '/etc/sw-collectd/collectd.conf'; + if (file_exists($collectd_config)) { + if (!is_file($collectd_config) || !is_readable($collectd_config)) + return; + + $config_content = Util::readfileToArray($collectd_config); + if ($config_content) { + foreach ($config_content as $line) { + if (preg_match('/Interval\s*\d+$/', $line, $match)) { + if (preg_match('/Interval\s*10$/', $line, $match)) { + $log->warning('If you leave the default value of the "Interval" parameter in the ' . $collectd_config . ', sw-collectd may heavily load the system. Please see https://support.plesk.com/hc/articles/213362569 for details.'); + $log->resultWarning(); + return; + } + $log->resultOk(); + return; + } + } + $log->warning('If you leave the default value of the "Interval" parameter in the ' . $collectd_config . ', sw-collectd may heavily load the system. Please see https://support.plesk.com/hc/articles/213362569 for details.'); + $log->resultWarning(); + return; + } + } + } + + private function _checkApacheStatus() + { + $log = Log::getInstance("Checking Apache status"); + + $apacheCtl = file_exists('/usr/sbin/apache2ctl') ? '/usr/sbin/apache2ctl' : '/usr/sbin/apachectl'; + + if (!is_executable($apacheCtl)) { + return; + } + + $resultCode = 0; + Util::Exec("$apacheCtl -t 2>/dev/null", $resultCode); + + if (0 !== $resultCode) { + $log->error("The Apache configuration is broken. Run '$apacheCtl -t' to see the detailed info."); + $log->resultError(); + return; + } + + $log->resultOk(); + } + + //:INFO: #72751 https://support.plesk.com/hc/articles/12377666752279 + function _checkIpcollectionReference() + { + $log = Log::getInstance("Checking consistency of the IP addresses list in the Panel database"); + + $mysql = PleskDb::getInstance(); + $sql = "SELECT 1 FROM ip_pool, clients, IpAddressesCollections, domains, DomainServices, IP_Addresses WHERE DomainServices.ipCollectionId = IpAddressesCollections.ipCollectionId AND domains.id=DomainServices.dom_id AND clients.id=domains.cl_id AND ipAddressId NOT IN (select id from IP_Addresses) AND IP_Addresses.id = ip_pool.ip_address_id AND pool_id = ip_pool.id GROUP BY pool_id"; + $brokenIps = $mysql->fetchAll($sql); + $sql = "select 1 from DomainServices, domains, clients, ip_pool where ipCollectionId not in (select IpAddressesCollections.ipCollectionId from IpAddressesCollections) and domains.id=DomainServices.dom_id and clients.id = domains.cl_id and ip_pool.id = clients.pool_id and DomainServices.type='web' group by ipCollectionId"; + $brokenCollections = $mysql->fetchAll($sql); + + if (!empty($brokenIps) || !empty($brokenCollections)) { + $log->warning('Some database entries related to Panel IP addresses are corrupted. Please see https://support.plesk.com/hc/articles/12377666752279 for the solution.'); + $log->resultWarning(); + return; + } + + $log->resultOk(); + } + + //:INFO: Broken contexts of the APS applications can lead to errors at building Apache web server configuration https://support.plesk.com/hc/articles/12377671820311 + function _checkApsApplicationContext() + { + $log = Log::getInstance("Checking installed APS applications"); + $mysql = PleskDb::getInstance(); + $sql = "SELECT * FROM apsContexts WHERE (pleskType = 'hosting' OR pleskType = 'subdomain') AND subscriptionId = 0"; + $brokenContexts = $mysql->fetchAll($sql); + + if (!empty($brokenContexts)) { + $log->warning('Some database entries related to the installed APS applications are corrupted. Please see https://support.plesk.com/hc/articles/12377671820311 for the solution.'); + $log->resultWarning(); + return; + } + $log->resultOk(); + } + + //:INFO: #94489 FTP service proftpd cannot be started by xinetd if IPv6 is disabled https://support.plesk.com/hc/articles/12377796102807 + function _checkProftpdIPv6() + { + $log = Log::getInstance("Checking proftpd settings"); + + $inet6 = '/proc/net/if_inet6'; + if (!file_exists($inet6) || !@file_get_contents($inet6)) { + $proftpd_config = '/etc/xinetd.d/ftp_psa'; + if (!is_file($proftpd_config) || !is_readable($proftpd_config)) + return null; + + $config_content = Util::readfileToArray($proftpd_config); + if ($config_content) { + for ($i=0; $i<=count($config_content)-1; $i++) { + if (preg_match('/flags.+IPv6$/', $config_content[$i], $match)) { + $log->warning('The proftpd FTP service will fail to start in case the support for IPv6 is disabled on the server. Please check https://support.plesk.com/hc/articles/12377796102807 for details.'); + $log->resultWarning(); + return; + } + } + } + } + $log->resultOk(); + } + + //:INFO: Check the availability of Plesk Panel TCP ports + function _checkPleskTCPPorts() + { + $log = Log::getInstance('Checking the availability of Plesk Panel TCP ports'); + + $plesk_ports = array('8880' => 'Plesk Panel non-secure HTTP port', '8443' => 'Plesk Panel secure HTTPS port'); + + $mysql = PleskDb::getInstance(); + $sql = "select ip_address from IP_Addresses"; + $ip_addresses = $mysql->fetchAll($sql); + $warning = false; + if (count($ip_addresses)>0) { + if (Util::isLinux()) { + $ipv4 = Util::getIPv4ListOnLinux(); + $ipv6 = Util::getIPv6ListOnLinux(); + if ($ipv6) { + $ipsInSystem = array_merge($ipv4, $ipv6); + } else { + $ipsInSystem = $ipv4; + } + } else { + $ipsInSystem = Util::getIPListOnWindows(); + } + foreach ($ip_addresses as $ip) { + foreach ($plesk_ports as $port => $description) { + if (PleskValidator::validateIPv4($ip['ip_address']) && in_array($ip['ip_address'], $ipsInSystem)) { + $fp = @fsockopen($ip['ip_address'], $port, $errno, $errstr, 1); + } elseif (PleskValidator::validateIPv6($ip['ip_address']) && in_array($ip['ip_address'], $ipsInSystem)) { + $fp = @fsockopen('[' . $ip['ip_address'] . ']', $port, $errno, $errstr, 1); + } else { + $log->warning('IP address registered in Plesk is invalid or broken: ' . $ip['ip_address']); + $log->resultWarning(); + return; + } + if (!$fp) { + // $errno 110 means "timed out", 111 means "refused" + $log->info('Unable to connect to IP address ' . $ip['ip_address'] . ' on ' . $description . ' ' . $port . ': ' . $errstr); + $warning = true; + } + } + } + } + if ($warning) { + $log->warning('Unable to connect to some Plesk ports. Please see ' . LOG_PATH . ' for details. Find the full list of the required open ports at https://support.plesk.com/hc/articles/12377821243159 '); + $log->resultWarning(); + return; + } + $log->resultOk(); + } + + //:INFO: Plesk user "root" for MySQL database servers have not access to phpMyAdmin https://support.plesk.com/hc/articles/12378148229399 + function _checkMySQLDatabaseUserRoot() + { + $log = Log::getInstance('Checking existence of Plesk user "root" for MariaDB/MySQL database servers'); + + $psaroot = Util::getSettingFromPsaConf('PRODUCT_ROOT_D'); + + if (PleskVersion::is_below_17_9()) { + $phpMyAdminConfFile = $psaroot . '/admin/htdocs/domains/databases/phpMyAdmin/libraries/config.default.php'; + } else { + $phpMyAdminConfFile = $psaroot . '/phpMyAdmin/libraries/config.default.php'; + } + + if (file_exists($phpMyAdminConfFile)) { + $phpMyAdminConfFileContent = file_get_contents($phpMyAdminConfFile); + if (!preg_match("/\[\'AllowRoot\'\]\s*=\s*true\s*\;/", $phpMyAdminConfFileContent)) { + $mysql = PleskDb::getInstance(); + $sql = "select login, data_bases.name as db_name, displayName as domain_name from db_users, data_bases, domains where db_users.db_id = data_bases.id and data_bases.dom_id = domains.id and data_bases.type = 'mysql' and login = 'root'"; + $dbusers = $mysql->fetchAll($sql); + + foreach ($dbusers as $user) { + $log->warning('The database user "' . $user['login'] . '" (database "' . $user['db_name'] . '" at "' . $user['domain_name'] . '") has no access to phpMyAdmin. Please check https://support.plesk.com/hc/articles/12378148229399 for more details.'); + $log->resultWarning(); + return; + } + } + } + + $log->resultOk(); + } + + //:INFO: After upgrade Plesk change permissions on folder of Collaboration Data Objects (CDO) for NTS (CDONTS) to default, https://support.plesk.com/hc/articles/12377887661335 + function _checkCDONTSmailrootFolder() + { + $log = Log::getInstance('Checking for CDONTS mailroot folder'); + + $mailroot = Util::getSystemDisk() . 'inetpub\mailroot\pickup'; + + if (is_dir($mailroot)) { + $log->warning('After upgrade you have to add write permissions to psacln group on folder ' . $mailroot . '. Please, check https://support.plesk.com/hc/articles/12377887661335 for more details.'); + $log->resultWarning(); + return; + } + $log->resultOk(); + } + + //:INFO: Check iisfcgi.dll file version https://support.plesk.com/hc/articles/12378148258199 + function _checkIisFcgiDllVersion() + { + $log = Log::getInstance("Checking the iisfcgi.dll file version"); + + $windir = Util::getSystemRoot(); + $iisfcgi = $windir . '\system32\inetsrv\iisfcgi.dll'; + if (file_exists($iisfcgi)) { + $version = Util::getFileVersion($iisfcgi); + if (version_compare($version, '7.5.0', '>') + && version_compare($version, '7.5.7600.16632', '<')) { + $log->warning('File iisfcgi.dll version ' . $version . ' is outdated. Please, check article https://support.plesk.com/hc/articles/12378148258199 for details'); + return; + } + } + $log->resultOk(); + } + + //:INFO: Checking for main IP address https://support.plesk.com/hc/articles/12377857361687 + function _checkMainIP() + { + $log = Log::getInstance("Checking for main IP address"); + + $mysql = PleskDb::getInstance(); + $sql = 'select * from IP_Addresses'; + $ips = $mysql->fetchAll($sql); + $mainexists = false; + foreach ($ips as $ip) { + if (isset($ip['main'])) { + if ($ip['main'] == 'true') { + $mainexists = true; + } + } else { + $log->info('No field "main" in table IP_Addresses.'); + $log->resultOk(); + return; + } + } + + if (!$mainexists) { + $warn = 'Unable to find "main" IP address in psa database. Please, check https://support.plesk.com/hc/articles/12377857361687 for more details.'; + $log->warning($warn); + $log->resultWarning(); + return; + } + $log->resultOk(); + } + + //:INFO: Checking existing table mysql.servers https://support.plesk.com/hc/articles/12377850098455 + function _checkMysqlServersTable() + { + $log = Log::getInstance('Checking table "servers" in database "mysql"'); + + $mySQLServerVersion = Util::getMySQLServerVersion(); + if (version_compare($mySQLServerVersion, '5.1.0', '>=')) { + $credentials = Util::getDefaultClientMySQLServerCredentials(); + + if (preg_match('/AES-128-CBC/', $credentials['admin_password'])) { + $log->info('The administrator\'s password for the default MariaDB/MySQL server is encrypted.'); + return; + } + + $mysql = new DbClientMysql($credentials['host'], $credentials['admin_login'], $credentials['admin_password'] , 'information_schema', $credentials['port']); + if (!$mysql->hasErrors()) { + $sql = 'SELECT * FROM information_schema.TABLES WHERE TABLE_SCHEMA="mysql" and TABLE_NAME="servers"'; + $servers = $mysql->fetchAll($sql); + if (empty($servers)) { + $warn = 'The table "servers" in the database "mysql" does not exist. Please check https://support.plesk.com/hc/articles/12377850098455 for details.'; + $log->warning($warn); + $log->resultWarning(); + return; + } + } + } + $log->resultOk(); + } + + //:INFO: Check that there is symbolic link /usr/local/psa on /opt/psa on Debian-like Oses https://support.plesk.com/hc/articles/12377511731991 + function _checkSymLinkToOptPsa() + { + $log = Log::getInstance('Checking symbolic link /usr/local/psa on /opt/psa'); + + $link = @realpath('/usr/local/psa/version'); + if (!preg_match('/\/opt\/psa\/version/', $link, $macthes)) { + $warn = "The symbolic link /usr/local/psa does not exist or has wrong destination. Read article https://support.plesk.com/hc/articles/12377511731991 to fix the issue."; + $log->warning($warn); + $log->resultWarning(); + return; + } + $log->resultOk(); + } + + //:INFO: Checking for unknown ISAPI filters and show warning https://support.plesk.com/hc/articles/213913765 + function _unknownISAPIfilters() + { + $log = Log::getInstance('Detecting installed ISAPI filters'); + + if (Util::isUnknownISAPIfilters()) { + $warn = 'Please read carefully article https://support.plesk.com/hc/articles/213913765, for avoiding possible problems caused by unknown ISAPI filters.'; + $log->warning($warn); + $log->resultWarning(); + + return; + } + $log->resultOk(); + } + + //:INFO: Warning about possible issues related to Microsoft Visual C++ Redistributable Packages ?https://support.plesk.com/hc/articles/115000201014 + function _checkMSVCR() + { + $log = Log::getInstance('Microsoft Visual C++ Redistributable Packages'); + + $warn = 'Please read carefully article https://support.plesk.com/hc/articles/115000201014, for avoiding possible problems caused by Microsoft Visual C++ Redistributable Packages.'; + $log->info($warn); + + return; + } + + function _checkForCryptPasswords() + { + //:INFO: Prevent potential problem with E: Couldn't configure pre-depend plesk-core for psa-firewall, probably a dependency cycle. + $log = Log::getInstance('Detecting if encrypted passwords are used'); + + $db = PleskDb::getInstance(); + $sql = "SELECT COUNT(*) AS cnt FROM accounts WHERE type='crypt' AND password not like '$%';"; + $r = $db->fetchAll($sql); + + if ($r[0]['cnt'] != '0') + { + $warn = 'There are ' . $r[0]['cnt'] . ' accounts with passwords encrypted using a deprecated algorithm. Please refer to https://support.plesk.com/hc/articles/12377596588311 for the instructions about how to change the password type to plain.'; + + $log->warning($warn); + $log->resultWarning(); + return; + } + $log->resultOk(); + } + + function _checkApsTablesInnoDB() + { + $log = Log::getInstance('Checking if apsc database tables have InnoDB engine'); + + $db = PleskDb::getInstance(); + $apsDatabase = $db->fetchOne("select val from misc where param = 'aps_database'"); + $sql = "SELECT TABLE_NAME FROM information_schema.TABLES where TABLE_SCHEMA = '$apsDatabase' and ENGINE = 'MyISAM'"; + $myISAMTables = $db->fetchAll($sql); + if (!empty($myISAMTables)) { + $myISAMTablesList = implode(', ', array_map('reset', $myISAMTables)); + $warn = 'The are tables in apsc database with MyISAM engine: ' . $myISAMTablesList . '. It would be updated to InnoDB engine.'; + $log->warning($warn); + $log->resultWarning(); + return; + } + $log->resultOk(); + } + + function _checkMixedCaseDomainIssues() + { + $log = Log::getInstance("Checking for domains with mixed case names", true); + $db = PleskDb::getInstance(); + + + $domains = $db->fetchAll("select id, name, displayName from domains"); + $problemDomains = array(); + foreach ($domains as $domain) { + if (strtolower($domain['name']) == $domain['name']) { + continue; + } + $problemDomains[] = $domain; + } + if (count($problemDomains)) { + $msg = "Found one or more domains with mixed case names. Such domains may have trouble working with the \"FPM application server by Apache\" handler.\n" . + implode("\n", array_map(function($row) { + return "{$row['id']}\t{$row['displayName']}\t{$row['name']}"; + }, $problemDomains)) . "\n" . + "A manual fix can be applied to resolve the issue. Read https://support.plesk.com/hc/en-us/articles/12377171904151 for details."; + $log->warning($msg); + $log->resultWarning(); + return; + } + $log->resultOk(); + } + + private function checkDomainControllerLocation() + { + $log = Log::getInstance("Checking for Active Directory Domain Controller and Plesk on the same server", true); + $cmd = '"' . rtrim(Util::getPleskRootPath(), '\\') . '\admin\bin\serverconf.exe" --list'; + $output = Util::exec($cmd, $code); + if (preg_match("/IS_DOMAIN_CONTROLLER:\s*true/", $output)) { + $log->warning('Active Directory Domain Controller and Plesk are on the same server. Read https://support.plesk.com/hc/articles/12377107094167 for details.'); + $log->resultWarning(); + } else { + $log->resultOk(); + } + } +} + +class Plesk175Requirements +{ + public function validate() + { + if (PleskInstallation::isInstalled() && PleskVersion::is_below_17_5() && Util::isLinux()) { + //:INFO: Check that DUMP_TMP_D is not inside of (or equal to) DUMP_D + $this->_checkDumpTmpD(); + } + } + + public function _checkDumpTmpD() + { + $log = Log::getInstance('Checking the DUMP_TMP_D directory'); + + $dumpD = Util::getSettingFromPsaConf('DUMP_D'); + if (is_null($dumpD)) { + $log->warning('Unable to obtain the path to the directory defined by the DUMP_D parameter. Check that the DUMP_D parameter is set in the /etc/psa/psa.conf file.'); + $log->resultWarning(); + return; + } + $dumpTmpD = Util::getSettingFromPsaConf('DUMP_TMP_D'); + if (is_null($dumpTmpD)) { + $log->warning('Unable to obtain the path to the directory defined by the DUMP_TMP_D parameter. Check that the DUMP_TMP_D parameter is set in the /etc/psa/psa.conf file.'); + $log->resultWarning(); + return; + } + + if (strpos(rtrim($dumpTmpD, '/') . '/', rtrim($dumpD, '/') . '/') === 0) { + $log->error(sprintf('The directory DUMP_TMP_D = %s should not be inside of (or equal to) the directory DUMP_D = %s. Fix these parameters in the /etc/psa/psa.conf file.', $dumpTmpD, $dumpD)); + $log->resultError(); + } + + $log->resultOk(); + } +} + +class Plesk178Requirements +{ + public function validate() + { + if (PleskInstallation::isInstalled() && Util::isWindows() && PleskVersion::is_below_17_9()) { + $this->_checkPleskVhostsDir(); + } + + if (PleskVersion::is_below_17_8()) { + $this->checkTomcat(); + $this->checkMultiServer(); + } + } + + private function checkTomcat() + { + if (Util::isWindows()) { + $tomcatRegBase = '\\PLESK\\PSA Config\\Config\\Packages\\tomcat\\tomcat'; + /* Supported versions on windows are tomcat5 and tomcat7 */ + $key = '/v InstallDir'; + $isInstalled = Util::regQuery($tomcatRegBase . '7', $key, true) || Util::regQuery($tomcatRegBase . '5', $key, true); + } else { + $isInstalled = PackageManager::isInstalled('psa-tomcat-configurator'); + } + + if ($isInstalled + || (PleskDb::getInstance()->fetchOne('show tables like \'WebApps\'') + && PleskDb::getInstance()->fetchOne('select count(*) from WebApps')) + ) { + $log = Log::getInstance('Checking Apache Tomcat installation'); + $message = <<warning($message); + } + } + + private function checkMultiServer() + { + if (!PleskModule::isMultiServer()) { + return; + } + + $log = Log::getInstance('Checking Plesk Multi Server installation'); + $message = <<emergency($message); + $log->resultError(); + } + + private function _checkPleskVhostsDir() + { + $log = Log::getInstance('Checking mount volume for HTTPD_VHOSTS_D directory'); + + $vhostsDir = rtrim(Util::regPleskQuery('HTTPD_VHOSTS_D'), "\\"); + Util::exec("mountvol \"{$vhostsDir}\" /L", $code); + if ($code == 0) { + $msg = "A disk volume is mounted to the {$vhostsDir} directory." . + " It will be unmounted during the Plesk upgrade." . + " As a result, all hosted websites will become unavailable." . + " Make sure to remount the volume to the {$vhostsDir} directory after the upgrade."; + $log->emergency($msg); + $log->resultError(); + return; + } + + $log->resultOk(); + } +} + +class Plesk18Requirements +{ + public function validate() + { + if (PleskInstallation::isInstalled()) { + if (Util::isLinux() && PleskOS::isRedHatLike()) { + $this->_checkYumDuplicates(); + } + $this->checkPdUsersLoginCollation(); + $this->checkDomainsGuidCollation(); + $this->checkClientsGuidCollation(); + $this->checkModSecurityModules(); + $this->checkIsFirewallPackageConfigured(); + $this->checkDefaultDnsServerComponent(); + } + } + + private function checkModSecurityModules() + { + /* Issue actual for Plesk for Windows below 18.0.32 (ModSecurity 2.9.3 and below) */ + if (!Util::isWindows() || PleskVersion::is_18_0_32_or_above()) { + return; + } + + $log = Log::getInstance('Checking the status of ModSecurity IIS modules'); + $modules = Util::exec(Util::getSystemRoot() . '\system32\inetsrv\AppCmd.exe list module', $code); + if ($code) { + $log->warning('Unable to get the list of IIS modules.'); + } else { + if (strpos($modules, 'ModSecurity IIS (32bits)') === false && strpos($modules, 'ModSecurity IIS (64bits)') === false) { + $log->error('Either 32-bit or 64-bit ModSecurity IIS module is absent.'); + $log->resultError(); + } + } + } + + // INFO: PPP-46440 checking package duplicates https://support.plesk.com/hc/articles/12377586286615 + private function _checkYumDuplicates() + { + $log = Log::getInstance('Checking for RPM packages duplicates'); + if (!file_exists("/usr/bin/package-cleanup")) + { + $log->info("package-cleanup is not found. Check for duplicates was skipped"); + return; + } + + $output = Util::exec("/usr/bin/package-cleanup --cacheonly -q --dupes", $code); + if ($code != 0) + { + // some repos may have no cache at this point or may be broken at all + // retry with cache recreation and skipping broken repos + $output = Util::exec("/usr/bin/package-cleanup -q --dupes --setopt='*.skip_if_unavailable=1'", $code); + } + if ($code != 0) + { + $message = "Unable to detect package duplicates: /usr/bin/package-cleanup --dupes returns $code." . + "Output is:\n$output"; + $log->warning($message); + $log->resultWarning(); + return; + } + + if (empty($output)) { + return; + } + + $message = "Your package system contains duplicated packages, which can lead to broken Plesk update:\n\n" . + "$output\n\n" . + "Please check https://support.plesk.com/hc/articles/12377586286615 for more details."; + + $log->error($message); + $log->resultError(); + } + + private function checkPdUsersLoginCollation() + { + $log = Log::getInstance('Checking for Protected Directory Users with duplicates in login field.'); + $duplicates = PleskDb::getInstance()->fetchAll( + 'SELECT pd_id, LOWER(login) AS login_ci, COUNT(*) AS duplicates FROM pd_users' . + ' GROUP BY pd_id, login_ci' . + ' HAVING duplicates > 1' + ); + if (!empty($duplicates)) { + $log->error( + "Duplicate logins of Protected Directory Users were found in the database:\n\n" . + implode("\n", array_column($duplicates, 'login_ci')) . "\n\n" . + "Please check https://support.plesk.com/hc/en-us/articles/360014743900 for more details." + ); + $log->resultError(); + } + } + + private function checkDomainsGuidCollation() + { + $log = Log::getInstance('Checking "domains" table with duplicates in guid field.'); + $duplicates = PleskDb::getInstance()->fetchAll( + 'SELECT guid, COUNT(*) AS duplicates FROM domains' + . ' GROUP BY guid' + . ' HAVING duplicates > 1' + ); + + if (!empty($duplicates)) { + $log->error( + "Duplicate guid were found in the 'domains' table:\n\n" . + implode("\n", array_column($duplicates, 'guid')) . "\n\n" + . "Please check https://support.plesk.com/hc/en-us/articles/12377018323351 for more details." + ); + $log->resultError(); + } + } + + private function checkClientsGuidCollation() + { + $log = Log::getInstance('Checking "clients" table with duplicates in guid field.'); + $duplicates = PleskDb::getInstance()->fetchAll( + 'SELECT guid, COUNT(*) AS duplicates FROM clients' + . ' GROUP BY guid' + . ' HAVING duplicates > 1' + ); + + if (!empty($duplicates)) { + $log->error( + "Duplicate guid were found in the 'clients' table:\n\n" . + implode("\n", array_column($duplicates, 'guid')) . "\n\n" + . "Please check https://support.plesk.com/hc/en-us/articles/360016801679 for more details." + ); + $log->resultError(); + } + } + + private function checkIsFirewallPackageConfigured() + { + if (!Util::isLinux()) { + return; + } + + $log = Log::getInstance("Checking if any rules are configured in the Firewall extension"); + $db = PleskDb::getInstance(); + if ($db->fetchOne("SHOW TABLES LIKE 'module_firewall_rules'") + && $db->fetchOne("SELECT COUNT(*) FROM module_firewall_rules") + ) { + $message = "Plesk Firewall no longer stores its configuration in Plesk database since " + . "Plesk Obsidian 18.0.52. Since you're upgrading to the latest version late, " + . "if you wish to retain the Plesk Firewall extension and its configuration, " + . "you need to follow additional steps after the upgrade. Please check " + . "https://support.plesk.com/hc/en-us/articles/16198248236311 for more details."; + $log->warning($message); + } + } + + private function checkDefaultDnsServerComponent() + { + if (Util::isLinux()) { + return; + } + + $path = '\\PLESK\\PSA Config\\Config\\Packages\\dnsserver'; + $key = '/ve'; + + if ('bind' === Util::regQuery($path, $key, true)) { + $log = Log::getInstance("Checking default DNS server component"); + $message = <<emergency($message); + $log->resultError(); + } + } +} + +class PleskModule +{ + public static function isInstalledWatchdog() + { + return PleskModule::_isInstalled('watchdog'); + } + + public static function isInstalledFileServer() + { + return PleskModule::_isInstalled('fileserver'); + } + + public static function isInstalledFirewall() + { + return PleskModule::_isInstalled('firewall'); + } + + public static function isInstalledVpn() + { + return PleskModule::_isInstalled('vpn'); + } + + public static function isMultiServer() + { + return PleskModule::_isInstalled('plesk-multi-server') || + PleskModule::_isInstalled('plesk-multi-server-node'); + } + + protected static function _isInstalled($module) + { + $sql = "SELECT * FROM Modules WHERE name = '{$module}'"; + + $pleskDb = PleskDb::getInstance(); + $row = $pleskDb->fetchRow($sql); + + return (empty($row) ? false : true); + } +} + +class PleskInstallation +{ + public static function validate() + { + if (!self::isInstalled()) { + $log = Log::getInstance('Checking for Plesk installation'); + $log->step('Plesk installation is not found. You will have no problems with upgrade, go on and install ' + . PleskVersion::getLatestPleskVersionAsString() . ' (https://www.plesk.com/)'); + return; + } + self::detectVersion(); + } + + public static function isInstalled() + { + $rootPath = Util::getPleskRootPath(); + if (empty($rootPath) || !file_exists($rootPath)) { + return false; + } + return true; + } + + private static function detectVersion() + { + $log = Log::getInstance('Installed Plesk version/build: ' . PleskVersion::getVersionAndBuild(), false); + + $currentVersion = PleskVersion::getVersion(); + if (version_compare($currentVersion, PLESK_VERSION, 'eq')) { + $err = 'You have already installed the latest version ' . PleskVersion::getLatestPleskVersionAsString() . '. '; + $err .= 'Tool must be launched prior to upgrade to ' . PleskVersion::getLatestPleskVersionAsString() . ' for the purpose of getting a report on potential problems with the upgrade.'; + $log->info($err); + exit(0); + } + + if (!PleskVersion::isUpgradeSupportedVersion()) { + $err = 'Unable to find Plesk 17.x. '; + $err .= 'Tool must be launched prior to upgrade to ' . PleskVersion::getLatestPleskVersionAsString() . ' for the purpose of getting a report on potential problems with the upgrade.'; + fatal($err); + } + } +} + +class PleskVersion +{ + const PLESK_17_MIN_VERSION = '13.0.0'; /* historically it has been started as 13.0 */ + + const PLESK_17_MAX_VERSION = '17.9.13'; + + const PLESK_18_MIN_VERSION = '18.0.14'; + + public static function is17x_or_above() + { + return version_compare(self::getVersion(), self::PLESK_17_MIN_VERSION, '>='); + } + + public static function is_below_17_5() + { + return version_compare(self::getVersion(), '17.5.0', '<'); + } + + public static function is_below_17_8() + { + return version_compare(self::getVersion(), '17.8.0', '<'); + } + + public static function is_below_17_9() + { + return version_compare(self::getVersion(), '17.9.0', '<'); + } + + public static function is_18_0_32_or_above() + { + return version_compare(self::getVersion(), '18.0.32', '>='); + } + + public static function getVersion() + { + $version = self::getVersionAndBuild(); + if (!preg_match('/([0-9]+[.][0-9]+[.][0-9]+)/', $version, $matches)) { + fatal("Incorrect Plesk version format. Current version: {$version}"); + } + return $matches[1]; + } + + public static function getVersionAndBuild() + { + $versionPath = Util::getPleskRootPath().'/version'; + if (!file_exists($versionPath)) { + fatal("Plesk version file is not exists $versionPath"); + } + $version = file_get_contents($versionPath); + $version = trim($version); + return $version; + } + + public static function getLatestPleskVersionAsString() + { + return 'Plesk ' . PLESK_VERSION; + } + + public static function isUpgradeSupportedVersion() + { + return self::is17x_or_above(); + } +} + +class Log +{ + private $errors; + private $warnings; + private $emergency; + private $logfile; + private $step; + private $step_header; + + /** @var array */ + private $errorsContent = []; + + /** @var array */ + private $warningsContent = []; + + public static function getInstance($step_msg = '', $step_number = true) + { + static $_instance = null; + if (is_null($_instance)) { + $_instance = new Log(); + } + if ($step_msg) { + $_instance->step($step_msg, $step_number); + } + + return $_instance; + } + + private function __construct() + { + $this->log_init(); + @unlink($this->logfile); + } + + private function log_init() + { + $this->step = 0; + $this->errors = 0; + $this->warnings = 0; + $this->emergency = 0; + $this->logfile = LOG_PATH; + $this->step_header = "Unknown step is running"; + } + + public function getErrors() + { + return $this->errors; + } + + public function getWarnings() + { + return $this->warnings; + } + + public function getEmergency() + { + return $this->emergency; + } + + public function fatal($msg) + { + $this->errors++; + + $this->errorsContent[] = $msg; + $content = $this->get_log_string($msg, 'FATAL_ERROR'); + fwrite(STDERR, $content); + $this->write($content); + } + + public function error($msg) + { + $this->errors++; + + $this->errorsContent[] = $msg; + $content = $this->get_log_string($msg, 'ERROR'); + fwrite(STDERR, $content); + $this->write($content); + } + + public function warning($msg) + { + $this->warnings++; + + $this->warningsContent[] = $msg; + $content = $this->get_log_string($msg, 'WARNING'); + fwrite(STDERR, $content); + $this->write($content); + } + + public function emergency($msg) + { + $this->emergency++; + + $this->errorsContent[] = $msg; + $content = $this->get_log_string($msg, 'EMERGENCY'); + fwrite(STDERR, $content); + $this->write($content); + } + + public function step($msg, $useNumber=false) + { + $this->step_header = $msg; + + echo PHP_EOL; + $this->write(PHP_EOL); + + if ($useNumber) { + $msg = "STEP " . $this->step . ": {$msg}..."; + $this->step++; + } else { + $msg = "{$msg}..."; + } + + $this->info($msg); + } + + public function resultOk() + { + $this->info('Result: OK'); + } + + public function resultWarning() + { + $this->info('Result: WARNING'); + } + + public function resultError() + { + $this->info('Result: ERROR'); + } + + public function info($msg) + { + $content = $this->get_log_string($msg, 'INFO'); + echo $content; + $this->write($content); + } + + public function debug($msg) + { + $this->write($this->get_log_string($msg, 'DEBUG')); + } + + public function dumpStatistics() + { + $errors = $this->errors + $this->emergency; + $str = "Errors found: $errors; Warnings found: {$this->warnings}"; + echo PHP_EOL . $str . PHP_EOL . PHP_EOL; + } + + private function get_log_string($msg, $type) + { + if (getenv('VZ_UPGRADE_SCRIPT')) { + switch ($type) { + case 'FATAL_ERROR': + case 'ERROR': + case 'WARNING': + case 'EMERGENCY': + $content = "[{$type}]: {$this->step_header} DESC: {$msg}" . PHP_EOL; + break; + default: + $content = "[{$type}]: {$msg}" . PHP_EOL; + } + } else if (getenv('AUTOINSTALLER_VERSION')) { + $content = "{$type}: {$msg}" . PHP_EOL; + } else { + $date = date('Y-m-d h:i:s'); + $content = "[{$date}][{$type}] {$msg}" . PHP_EOL; + } + + return $content; + } + + public function write($content, $file = null, $mode='a+') + { + $logfile = $file ? $file : $this->logfile; + $fp = fopen($logfile, $mode); + fwrite($fp, $content); + fclose($fp); + } + + private function getJsonFileName() + { + return (Util::isWindows() ? + rtrim(Util::regPleskQuery('PRODUCT_DATA_D'), "\\") : + Util::getSettingFromPsaConf('PRODUCT_ROOT_D') + ) . '/var/' . LOG_JSON; + } + + public function writeJsonFile() + { + $data = [ + 'version' => PRE_UPGRADE_SCRIPT_VERSION, + 'errorsFound' => $this->errors + $this->emergency, + 'errors' => $this->errorsContent, + 'warningsFound' => $this->warnings, + 'warnings' => $this->warningsContent, + ]; + file_put_contents($this->getJsonFileName(), json_encode($data)); + } +} + +class PleskDb +{ + var $_db = null; + + public function __construct($dbParams) + { + switch($dbParams['db_type']) { + case 'mysql': + $this->_db = new DbMysql( + $dbParams['host'], $dbParams['login'], $dbParams['passwd'], $dbParams['db'], $dbParams['port'] + ); + break; + + case 'jet': + $this->_db = new DbJet($dbParams['db']); + break; + + case 'mssql': + $this->_db = new DbMsSql( + $dbParams['host'], $dbParams['login'], $dbParams['passwd'], $dbParams['db'], $dbParams['port'] + ); + break; + + default: + fatal("{$dbParams['db_type']} is not implemented yet"); + break; + } + } + + public static function getInstance() + { + global $options; + static $_instance = array(); + + $dbParams['db_type']= Util::getPleskDbType(); + $dbParams['db'] = Util::getPleskDbName(); + $dbParams['port'] = Util::getPleskDbPort(); + $dbParams['login'] = Util::getPleskDbLogin(); + $dbParams['passwd'] = Util::getPleskDbPassword($options->getDbPasswd()); + $dbParams['host'] = Util::getPleskDbHost(); + + $dbId = md5(implode("\n", $dbParams)); + + $_instance[$dbId] = new PleskDb($dbParams); + + return $_instance[$dbId]; + } + + function fetchOne($sql) + { + if (DEBUG) { + $log = Log::getInstance(); + $log->info($sql); + } + return $this->_db->fetchOne($sql); + } + + function fetchRow($sql) + { + $res = $this->fetchAll($sql); + if (is_array($res) && isset($res[0])) { + return $res[0]; + } + return array(); + } + + function fetchAll($sql) + { + if (DEBUG) { + $log = Log::getInstance(); + $log->info($sql); + } + return $this->_db->fetchAll($sql); + } +} + +class DbMysql +{ + var $_dbHandler; + + public function __construct($host, $user, $passwd, $database, $port) + { + if ( extension_loaded('mysql') ) { + $this->_dbHandler = @mysql_connect("{$host}:{$port}", $user, $passwd); + if (!is_resource($this->_dbHandler)) { + $mysqlError = mysql_error(); + if (stristr($mysqlError, 'access denied for user')) { + $errMsg = 'Given is incorrect. ' . $mysqlError; + } else { + $errMsg = 'Unable to connect database. The reason of problem: ' . $mysqlError . PHP_EOL; + } + $this->_logError($errMsg); + } + @mysql_select_db($database, $this->_dbHandler); + } else if ( extension_loaded('mysqli') ) { + + // forbid using MYSQLI_REPORT_STRICT to handle mysqli errors via error codes + mysqli_report(MYSQLI_REPORT_ERROR); + + $this->_dbHandler = @mysqli_connect($host, $user, $passwd, $database, $port); + if (!$this->_dbHandler) { + $mysqlError = mysqli_connect_error(); + if (stristr($mysqlError, 'access denied for user')) { + $errMsg = 'Given is incorrect. ' . $mysqlError; + } else { + $errMsg = 'Unable to connect database. The reason of problem: ' . $mysqlError . PHP_EOL; + } + $this->_logError($errMsg); + } + } else { + fatal("No MariaDB/MySQL extension is available"); + } + } + + function fetchAll($sql) + { + if ( extension_loaded('mysql') ) { + $res = mysql_query($sql, $this->_dbHandler); + if (!is_resource($res)) { + $this->_logError('Unable to execute query. Error: ' . mysql_error($this->_dbHandler)); + } + $rowset = array(); + while ($row = mysql_fetch_assoc($res)) { + $rowset[] = $row; + } + return $rowset; + } else if ( extension_loaded('mysqli') ) { + $res = $this->_dbHandler->query($sql); + if ($res === false) { + $this->_logError('Unable to execute query. Error: ' . mysqli_error($this->_dbHandler)); + } + $rowset = array(); + while ($row = mysqli_fetch_assoc($res)) { + $rowset[] = $row; + } + return $rowset; + } else { + fatal("No MariaDB/MySQL extension is available"); + } + } + + function fetchOne($sql) + { + if ( extension_loaded('mysql') ) { + $res = mysql_query($sql, $this->_dbHandler); + if (!is_resource($res)) { + $this->_logError('Unable to execute query. Error: ' . mysql_error($this->_dbHandler)); + } + $row = mysql_fetch_row($res); + return isset($row[0]) ? $row[0] : null; + } else if ( extension_loaded('mysqli') ) { + $res = $this->_dbHandler->query($sql); + if ($res === false) { + $this->_logError('Unable to execute query. Error: ' . mysqli_error($this->_dbHandler)); + } + $row = mysqli_fetch_row($res); + return isset($row[0]) ? $row[0] : null; + } else { + fatal("No MariaDB/MySQL extension is available"); + } + } + + function query($sql) + { + if ( extension_loaded('mysql') ) { + $res = mysql_query($sql, $this->_dbHandler); + if ($res === false ) { + $this->_logError('Unable to execute query. Error: ' . mysql_error($this->_dbHandler) ); + } + return $res; + } else if ( extension_loaded('mysqli') ) { + $res = $this->_dbHandler->query($sql); + if ($res === false ) { + $this->_logError('Unable to execute query. Error: ' . mysqli_error($this->_dbHandler) ); + } + return $res; + } else { + fatal("No MariaDB/MySQL extension is available"); + } + } + + function _logError($message) + { + fatal("[MYSQL ERROR] $message"); + } +} + +class DbClientMysql extends DbMysql +{ + var $errors = array(); + + function _logError($message) + { + $message = "[MYSQL ERROR] $message"; + $log = Log::getInstance(); + $log->warning($message); + $this->errors[] = $message; + } + + function hasErrors() { + return count($this->errors) > 0; + } +} + +class DbJet +{ + var $_dbHandler = null; + + public function __construct($dbPath) + { + $dsn = "Provider='Microsoft.Jet.OLEDB.4.0';Data Source={$dbPath}"; + $this->_dbHandler = new COM("ADODB.Connection", NULL, CP_UTF8); + if (!$this->_dbHandler) { + $this->_logError('Unable to init ADODB.Connection'); + } + + $this->_dbHandler->open($dsn); + } + + function fetchAll($sql) + { + $result_id = $this->_dbHandler->execute($sql); + if (!$result_id) { + $this->_logError('Unable to execute sql query ' . $sql); + } + if ($result_id->BOF && !$result_id->EOF) { + $result_id->MoveFirst(); + } + if ($result_id->EOF) { + return array(); + } + + $rowset = array(); + while(!$result_id->EOF) { + $row = array(); + for ($i=0;$i<$result_id->Fields->count;$i++) { + $field = $result_id->Fields($i); + $row[$field->Name] = (string)$field->value; + } + $result_id->MoveNext(); + $rowset[] = $row; + } + return $rowset; + } + + function fetchOne($sql) + { + $result_id = $this->_dbHandler->execute($sql); + if (!$result_id) { + $this->_logError('Unable to execute sql query ' . $sql); + } + if ($result_id->BOF && !$result_id->EOF) { + $result_id->MoveFirst(); + } + if ($result_id->EOF) { + return null; + } + $field = $result_id->Fields(0); + $result = $field->value; + + return (string)$result; + } + + function _logError($message) + { + fatal("[JET ERROR] $message"); + } +} + +class DbMsSql extends DbJet +{ + public function __construct($host, $user, $passwd, $database, $port) + { + $dsn = "Provider=SQLOLEDB.1;Initial Catalog={$database};Data Source={$host}"; + $this->_dbHandler = new COM("ADODB.Connection", NULL, CP_UTF8); + if (!$this->_dbHandler) { + $this->_logError('Unable to init ADODB.Connection'); + } + $this->_dbHandler->open($dsn, $user, $passwd); + } + + function _logError($message) + { + fatal("[MSSQL ERROR] $message"); + } +} + +class Util +{ + const DSN_INI_PATH_UNIX = '/etc/psa/private/dsn.ini'; + + /** @var array */ + private static $_dsnIni; + + public static function isWindows() + { + if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { + return true; + } + return false; + } + + public static function isLinux() + { + return !Util::isWindows(); + } + + public static function isVz() + { + $vz = false; + if (Util::isLinux()) { + if (file_exists('/proc/vz/veredir')) { + $vz = true; + } + } else { + $reg = 'REG QUERY "HKLM\SOFTWARE\SWsoft\Virtuozzo" 2>nul'; + Util::exec($reg, $code); + if ($code==0) { + $vz = true; + } + } + return $vz; + } + + public static function getArch() + { + global $arch; + if (!empty($arch)) + return $arch; + + $arch = 'i386'; + if (Util::isLinux()) { + $cmd = 'uname -m'; + $x86_64 = 'x86_64'; + $output = Util::exec($cmd, $code); + if (!empty($output) && stristr($output, $x86_64)) { + $arch = 'x86_64'; + } + } else { + $arch = 'x86_64'; + } + return $arch; + } + + public static function getHostname() + { + if (Util::isLinux()) { + $cmd = 'hostname -f'; + } else { + $cmd = 'hostname'; + } + $hostname = Util::exec($cmd, $code); + + if (empty($hostname)) { + $err = 'Command: ' . $cmd . ' returns: ' . $hostname . "\n"; + $err .= 'Hostname is not defined and configured. Unable to get hostname. Server should have properly configured hostname and it should be resolved locally.'; + fatal($err); + } + + return $hostname; + } + + public static function getIPList($lo=false) + { + if (Util::isLinux()) { + $ipList = Util::getIPv4ListOnLinux(); + foreach ($ipList as $key => $ip) { + if (!$lo && substr($ip, 0, 3) == '127') { + unset($ipList[$key]); + continue; + } + trim($ip); + } + $ipList = array_values($ipList); + } else { + $cmd = 'hostname'; + $hostname = Util::exec($cmd, $code); + $ip = gethostbyname($hostname); + $res = ($ip != $hostname) ? true : false; + if (!$res) { + fatal('Unable to retrieve IP address'); + } + $ipList = array(trim($ip)); + } + return $ipList; + } + + public static function getIPv6ListOnLinux() + { + return Util::grepCommandOutput(array( + array('bin' => 'ip', 'command' => '%PATH% addr list', 'regexp' => '#inet6 ([^ /]+)#'), + array('bin' => 'ifconfig', 'command' => '%PATH% -a', 'regexp' => '#inet6 (?:addr: ?)?([A-F0-9:]+)#i'), + )); + } + + public static function getIPv4ListOnLinux() + { + $commands = array( + array('bin' => 'ip', 'command' => '%PATH% addr list', 'regexp' => '#inet ([^ /]+)#'), + array('bin' => 'ifconfig', 'command' => '%PATH% -a', 'regexp' => '#inet (?:addr: ?)?([\d\.]+)#'), + ); + if (!($list = Util::grepCommandOutput($commands))) { + fatal('Unable to get IP address'); + } + return $list; + } + + public static function grepCommandOutput($cmds) + { + foreach ($cmds as $cmd) { + if ($fullPath = Util::lookupCommand($cmd['bin'])) { + $output = Util::exec(str_replace("%PATH%", $fullPath, $cmd['command']), $code); + if (preg_match_all($cmd['regexp'], $output, $matches)) { + return $matches[1]; + } + } + } + return false; + } + + public static function getIPListOnWindows() + { + $cmd = 'wmic.exe path win32_NetworkAdapterConfiguration get IPaddress'; + $output = Util::exec($cmd, $code); + if (!preg_match_all('/"(.*?)"/', $output, $matches)) { + fatal('Unable to get IP address'); + } + return $matches[1]; + } + + public static function getPleskRootPath() + { + global $_pleskRootPath; + if (empty($_pleskRootPath)) { + if (Util::isLinux()) { + if (PleskOS::isDebLike()) { + $_pleskRootPath = '/opt/psa'; + } else { + $_pleskRootPath = '/usr/local/psa'; + } + } + if (Util::isWindows()) { + $_pleskRootPath = Util::regPleskQuery('PRODUCT_ROOT_D', true); + } + } + return $_pleskRootPath; + } + + public static function getPleskDbName() + { + $dbName = 'psa'; + if (Util::isWindows()) { + $dbName = Util::regPleskQuery('mySQLDBName'); + } else { + $dsnDbname = Util::_getDsnConfigValue('dbname'); + if ($dsnDbname) { + $dbName = $dsnDbname; + } + } + return $dbName; + } + + public static function getPleskDbLogin() + { + $dbLogin = 'admin'; + if (Util::isWindows()) { + $dbLogin = Util::regPleskQuery('PLESK_DATABASE_LOGIN'); + } else { + $dsnLogin = Util::_getDsnConfigValue('username'); + if ($dsnLogin) { + $dbLogin = $dsnLogin; + } + } + return $dbLogin; + } + + public static function getPleskDbPassword($dbPassword) + { + if (Util::isLinux()) { + $dsnPassword = Util::_getDsnConfigValue('password'); + if ($dsnPassword) { + $dbPassword = $dsnPassword; + } + } + return $dbPassword; + } + + public static function getPleskDbType() + { + $dbType = 'mysql'; + if (Util::isWindows()) { + $dbType = strtolower(Util::regPleskQuery('PLESK_DATABASE_PROVIDER_NAME')); + } + return $dbType; + } + + public static function getPleskDbHost() + { + $dbHost = 'localhost'; + if (Util::isWindows()) { + $dbProvider = strtolower(Util::regPleskQuery('PLESK_DATABASE_PROVIDER_NAME')); + if ($dbProvider == 'mysql' || $dbProvider == 'mssql') { + $dbHost = Util::regPleskQuery('MySQL_DB_HOST'); + } + } else { + $dsnHost = Util::_getDsnConfigValue('host'); + if ($dsnHost) { + $dbHost = $dsnHost; + } + } + return $dbHost; + } + + public static function getPleskDbPort() + { + $dbPort = '3306'; + if (Util::isWindows()) { + $dbPort = Util::regPleskQuery('MYSQL_PORT'); + } else { + $dsnPort = Util::_getDsnConfigValue('port'); + if ($dsnPort) { + $dbPort = $dsnPort; + } + } + return $dbPort; + } + + private static function _getDsnConfigValue($param) + { + if (Util::isWindows()) { + return null; + } + + if (is_null(self::$_dsnIni)) { + if (!is_file(self::DSN_INI_PATH_UNIX)) { + self::$_dsnIni = false; + return null; + } + self::$_dsnIni = parse_ini_file(self::DSN_INI_PATH_UNIX, true); + } + + if (!self::$_dsnIni) { + return null; + } + if (!array_key_exists('plesk', self::$_dsnIni)) { + return null; + } + if (!array_key_exists($param, self::$_dsnIni['plesk'])) { + return null; + } + return self::$_dsnIni['plesk'][$param]; + } + + public static function regPleskQuery($key, $returnResult=false) + { + $reg = 'REG QUERY "HKLM\SOFTWARE\Wow6432Node\Plesk\Psa Config\Config" /v '.$key; + $output = Util::exec($reg, $code); + + if ($code) { + $log = Log::getInstance(); + $log->info($reg); + $log->info($output); + if ($returnResult) { + return false; + } else { + fatal("Unable to get '$key' from registry"); + } + } + + if (!preg_match("/\w+\s+REG_SZ\s+(.*)/i", trim($output), $matches)) { + fatal('Unable to macth registry value by key '.$key.'. Output: ' . trim($output)); + } + + return $matches[1]; + } + + public static function regQuery($path, $key, $returnResult = false) + { + $reg = 'REG QUERY "HKLM\SOFTWARE\Wow6432Node' . $path . '" '.$key; + $output = Util::exec($reg, $code); + + if ($code) { + $log = Log::getInstance(); + $log->info($reg); + $log->info($output); + if ($returnResult) { + return false; + } else { + fatal("Unable to get '$key' from registry"); + } + } + + if (!preg_match("/\s+REG_SZ(\s+)?(.*)/i", trim($output), $matches)) { + fatal('Unable to match registry value by key '.$key.'. Output: ' . trim($output)); + } + + return $matches[2]; + } + + public static function lookupCommand($cmd, $exit = false, $path = '/bin:/usr/bin:/usr/local/bin:/usr/sbin:/sbin:/usr/local/sbin') + { + $dirs = explode(':', $path); + foreach ($dirs as $dir) { + $util = $dir . '/' . $cmd; + if (is_executable($util)) { + return $util; + } + } + if ($exit) { + fatal("{$cmd}: command not found"); + } + return false; + } + + public static function getSystemDisk() + { + $cmd = 'echo %SYSTEMROOT%'; + $output = Util::exec($cmd, $code); + return substr($output, 0, 3); + } + + public static function getSystemRoot() + { + $cmd = 'echo %SYSTEMROOT%'; + $output = Util::exec($cmd, $code); + return $output; + } + + public static function getFileVersion($file) + { + $fso = new COM("Scripting.FileSystemObject"); + $version = $fso->GetFileVersion($file); + $fso = null; + return $version; + } + + public static function isUnknownISAPIfilters() + { + if (PleskVersion::is17x_or_above()) { + return false; + } + + $log = Log::getInstance(); + + $isUnknownISAPI = false; + $knownISAPI = array ("ASP\\.Net.*", "sitepreview", "COMPRESSION", "jakarta"); + + foreach ($knownISAPI as &$value) { + $value = strtoupper($value); + } + $cmd='cscript ' . Util::getSystemDisk() . 'inetpub\AdminScripts\adsutil.vbs ENUM W3SVC/FILTERS'; + $output = Util::exec($cmd, $code); + + if ($code!=0) { + $log->info("Unable to get ISAPI filters. Error: " . $output); + return false; + } + if (!preg_match_all('/FILTERS\/(.*)]/', trim($output), $matches)) { + $log->info($output); + $log->info("Unable to get ISAPI filters from output: " . $output); + return false; + } + foreach ($matches[1] as $ISAPI) { + $valid = false; + foreach ($knownISAPI as $knownPattern) { + if (preg_match("/$knownPattern/i", $ISAPI)) { + $valid = true; + break; + } + } + if (! $valid ) { + $log->warning("Unknown ISAPI filter detected in IIS: " . $ISAPI); + $isUnknownISAPI = true; + } + } + + return $isUnknownISAPI; + } + + /** + * @return string + */ + public static function getMySQLServerVersion() + { + $credentials = Util::getDefaultClientMySQLServerCredentials(); + + if (preg_match('/AES-128-CBC/', $credentials['admin_password'])) { + Log::getInstance()->info('The administrator\'s password for the default MariaDB/MySQL server is encrypted.'); + + return ''; + } + + $mysql = new DbClientMysql( + $credentials['host'], + $credentials['admin_login'], + $credentials['admin_password'], + 'information_schema', + $credentials['port'] + ); + + if (!$mysql->hasErrors()) { + $sql = 'select version()'; + $mySQLversion = $mysql->fetchOne($sql); + if (!preg_match("/(\d{1,})\.(\d{1,})\.(\d{1,})/", trim($mySQLversion), $matches)) { + fatal('Unable to match MariaDB/MySQL server version.'); + } + + return $matches[0]; + } + + return ''; + } + + public static function getDefaultClientMySQLServerCredentials() + { + $db = PleskDb::getInstance(); + $sql = "SELECT val FROM misc WHERE param='default_server_mysql'"; + $defaultServerMysqlId = $db->fetchOne($sql); + if ($defaultServerMysqlId) { + $where = "id={$defaultServerMysqlId}"; + } else { + $where = "type='mysql' AND host='localhost'"; + } + $sql = "SELECT ds.host, ds.port, ds.admin_login, ds.admin_password FROM DatabaseServers ds WHERE {$where}"; + $clientDBServerCredentials = $db->fetchAll($sql)[0]; + if ($clientDBServerCredentials['host'] === 'localhost' && Util::isLinux()) { + $clientDBServerCredentials['admin_password'] = Util::retrieveAdminMySQLDbPassword(); + } + if (empty($clientDBServerCredentials['port'])) { + $clientDBServerCredentials['port'] = self::getPleskDbPort(); + } + + return $clientDBServerCredentials; + } + + public static function retrieveAdminMySQLDbPassword() + { + return Util::isLinux() + ? trim( Util::readfile("/etc/psa/.psa.shadow") ) + : null; + } + + public static function exec($cmd, &$code) + { + $log = Log::getInstance(); + + if (!$cmd) { + $log->info('Unable to execute a blank command. Please see ' . LOG_PATH . ' for details.'); + + $debugBacktrace = ""; + foreach (debug_backtrace() as $i => $obj) { + $debugBacktrace .= "#{$i} {$obj['file']}:{$obj['line']} {$obj['function']} ()\n"; + } + $log->debug("Unable to execute a blank command. The stack trace:\n{$debugBacktrace}"); + $code = 1; + return ''; + } + exec($cmd, $output, $code); + return trim(implode("\n", $output)); + } + + public static function readfile($file) + { + if (!is_file($file) || !is_readable($file)) { + return null; + } + $lines = file($file); + return $lines === false + ? null + : trim(implode("\n", $lines)); + } + + public static function readfileToArray($file) + { + if (!is_file($file) || !is_readable($file)) { + return null; + } + $lines = file($file); + return $lines === false + ? null + : $lines; + } + + public static function getSettingFromPsaConf($setting) + { + $file = '/etc/psa/psa.conf'; + if (!is_file($file) || !is_readable($file)) + return null; + $lines = file($file); + if ($lines === false) + return null; + foreach ($lines as $line) { + if (preg_match("/^{$setting}\s.*/", $line, $match_setting)) { + if (preg_match("/[\s].*/i", $match_setting[0], $match_value)) { + $value = trim($match_value[0]); + return $value; + } + } + } + return null; + } + + public static function getPhpIni() + { + if (Util::isLinux()) { + // Debian/Ubuntu /etc/php5/apache2/php.ini /etc/php5/conf.d/ + // SuSE /etc/php5/apache2/php.ini /etc/php5/conf.d/ + // CentOS 4/5 /etc/php.ini /etc/php.d + if (PleskOS::isRedHatLike()) { + $phpini = Util::readfileToArray('/etc/php.ini'); + } else { + $phpini = Util::readfileToArray('/etc/php5/apache2/php.ini'); + } + } + + return $phpini; + } + + public static function getUserBeanCounters() + { + if (!Util::isLinux()) { + + return false; + } + $user_beancounters = array(); + $ubRaw = Util::readfileToArray('/proc/user_beancounters'); + + if (!$ubRaw) { + + return false; + } + for ($i=2; $i<=count($ubRaw)-1; $i++) { + + if (preg_match('/^.+?:?.+?\b(\w+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)/', $ubRaw[$i], $limit_name)) { + + $user_beancounters[trim($limit_name[1])] = array( + 'held' => (int)$limit_name[2], + 'maxheld' => (int)$limit_name[3], + 'barrier' => (int)$limit_name[4], + 'limit' => (int)$limit_name[5], + 'failcnt' => (int)$limit_name[6] + ); + } + } + + return $user_beancounters; + } +} + +class PackageManager +{ + public static function buildListCmdLine($glob) + { + if (PleskOS::isRedHatLike()) { + $cmd = "rpm -qa --queryformat '%{NAME} %{VERSION}-%{RELEASE} %{ARCH}\\n'"; + } elseif (PleskOS::isDebLike()) { + $cmd = "dpkg-query --show --showformat '\${Package} \${Version} \${Architecture}\\n'"; + } else { + return false; + } + + if (!empty($glob)) { + $cmd .= " '" . $glob . "' 2>/dev/null"; + } + + return $cmd; + } + + /* + * Fetches a list of installed packages that match given criteria. + * string $glob - Glob (wildcard) pattern for coarse-grained packages selection from system package management backend. Empty $glob will fetch everything. + * string $regexp - Package name regular expression for a fine-grained filtering of the results. + * returns array of hashes with keys 'name', 'version' and 'arch', or false on error. + */ + public static function listInstalled($glob, $regexp = null) + { + $cmd = PackageManager::buildListCmdLine($glob); + if (!$cmd) { + return array(); + } + + $output = Util::exec($cmd, $code); + if ($code != 0) { + return false; + } + + $packages = array(); + $lines = explode("\n", $output); + foreach ($lines as $line) { + @list($pkgName, $pkgVersion, $pkgArch) = explode(" ", $line); + if (empty($pkgName) || empty($pkgVersion) || empty($pkgArch)) + continue; + if (!empty($regexp) && !preg_match($regexp, $pkgName)) + continue; + $packages[] = array( + 'name' => $pkgName, + 'version' => $pkgVersion, + 'arch' => $pkgArch + ); + } + + return $packages; + } + + public static function isInstalled($glob, $regexp = null) + { + $packages = PackageManager::listInstalled($glob, $regexp); + return !empty($packages); + } +} + +class Package +{ + function getManager($field, $package) + { + $redhat = 'rpm -q --queryformat \'%{' . $field . '}\n\' ' . $package; + $debian = 'dpkg-query --show --showformat=\'${' . $field . '}\n\' '. $package . ' 2> /dev/null'; + + if (PleskOS::isRedHatLike()) { + $manager = $redhat; + } elseif (PleskOS::isDebLike()) { + $manager = $debian; + } else { + return false; + } + + return $manager; + } + + /* DPKG doesn't supports ${Release} + * + */ + + function getRelease($package) + { + $manager = Package::getManager('Release', $package); + + if (!$manager) { + return false; + } + + $release = Util::exec($manager, $code); + if (!$code === 0) { + return false; + } + return $release; + } + + function getVersion($package) + { + $manager = Package::getManager('Version', $package); + + if (!$manager) { + return false; + } + + $version = Util::exec($manager, $code); + if (!$code === 0) { + return false; + } + return $version; + } + +} + +class PleskOS +{ + public static function isDebLike() + { + return is_file("/etc/debian_version"); + } + + public static function isRedHatLike() + { + return is_file("/etc/redhat-release"); + } + + public static function catEtcIssue() + { + $cmd = 'cat /etc/issue'; + $output = Util::exec($cmd, $code); + + return $output; + } + + public static function detectSystem() + { + $log = Log::getInstance('Detect system configuration'); + $log->info('OS: ' . (Util::isLinux() ? PleskOS::catEtcIssue() : 'Windows')); + $log->info('Arch: ' . Util::getArch()); + } +} + +class PleskValidator +{ + public static function validateIPv4($value) + { + $ip2long = ip2long($value); + if ($ip2long === false) { + return false; + } + + return $value == long2ip($ip2long); + } + + public static function validateIPv6($value) + { + if (strlen($value) < 3) { + return $value == '::'; + } + + if (strpos($value, '.')) { + $lastcolon = strrpos($value, ':'); + if (!($lastcolon && PleskValidator::validateIPv4(substr($value, $lastcolon + 1)))) { + return false; + } + + $value = substr($value, 0, $lastcolon) . ':0:0'; + } + + if (strpos($value, '::') === false) { + return preg_match('/\A(?:[a-f0-9]{1,4}:){7}[a-f0-9]{1,4}\z/i', $value); + } + + $colonCount = substr_count($value, ':'); + if ($colonCount < 8) { + return preg_match('/\A(?::|(?:[a-f0-9]{1,4}:)+):(?:(?:[a-f0-9]{1,4}:)*[a-f0-9]{1,4})?\z/i', $value); + } + + // special case with ending or starting double colon + if ($colonCount == 8) { + return preg_match('/\A(?:::)?(?:[a-f0-9]{1,4}:){6}[a-f0-9]{1,4}(?:::)?\z/i', $value); + } + + return false; + } +} + +class CheckRequirements +{ + function validate() + { + if (!PleskInstallation::isInstalled()) { + //:INFO: skip chking mysql extension if plesk is not installed + return; + } + + $reqExts = array(); + foreach ($reqExts as $name) { + $status = extension_loaded($name); + if (!$status) { + $this->_fail("PHP extension {$name} is not installed"); + } + } + } + + function _fail($errMsg) + { + echo '===Checking requirements===' . PHP_EOL; + echo PHP_EOL . 'Error: ' . $errMsg . PHP_EOL; + exit(1); + } +} + +class GetOpt +{ + var $_argv; + var $_adminDbPasswd; + + public function __construct() + { + $this->_argv = $_SERVER['argv']; + if (empty($this->_argv[1]) && Util::isLinux()) { + $this->_adminDbPasswd = Util::retrieveAdminMySQLDbPassword(); + } else { + $this->_adminDbPasswd = $this->_argv[1]; + } + } + + public function validate() + { + if (empty($this->_adminDbPasswd) && PleskInstallation::isInstalled()) { + echo 'Please specify Plesk database password'; + $this->_helpUsage(); + } + } + + public function getDbPasswd() + { + return $this->_adminDbPasswd; + } + + public function _helpUsage() + { + echo PHP_EOL . "Usage: {$this->_argv[0]} " . PHP_EOL; + exit(1); + } +} + +function fatal($msg) +{ + $log = Log::getInstance(); + $log->fatal($msg); + exit(1); +} + +$log = Log::getInstance(); + +//:INFO: Validate options +$options = new GetOpt(); +$options->validate(); + +//:INFO: Validate PHP requirements, need to make sure that PHP extensions are installed +$checkRequirements = new CheckRequirements(); +$checkRequirements->validate(); + +//:INFO: Validate Plesk installation +PleskInstallation::validate(); + +//:INFO: Detect system +$pleskOs = new PleskOS(); +$pleskOs->detectSystem(); + +//:INFO: Need to make sure that given db password is valid +if (PleskInstallation::isInstalled()) { + $log->step('Validating the database password'); + $pleskDb = PleskDb::getInstance(); + $log->resultOk(); +} + +//:INFO: Dump script version +$log->step('Pre-Upgrade analyzer version: ' . PRE_UPGRADE_SCRIPT_VERSION); + +//:INFO: Validate known OS specific issues with recommendation to avoid bugs in Plesk +$pleskKnownIssues = new Plesk17KnownIssues(); +$pleskKnownIssues->validate(); + +$plesk175Requirements = new Plesk175Requirements(); +$plesk175Requirements->validate(); + +$plesk178Requirements = new Plesk178Requirements(); +$plesk178Requirements->validate(); + +$plesk18Requirements = new Plesk18Requirements(); +$plesk18Requirements->validate(); + +$log->dumpStatistics(); +$log->writeJsonFile(); + +if ($log->getEmergency() > 0) { + exit(2); +} + +if ($log->getErrors() > 0 || $log->getWarnings() > 0) { + exit(1); +} +// vim:set et ts=4 sts=4 sw=4: diff --git a/root/parallels/pool/PSA_18.0.73_17940/examiners/php_launcher.sh b/root/parallels/pool/PSA_18.0.73_17940/examiners/php_launcher.sh new file mode 100755 index 0000000000..70ebd0f0c6 --- /dev/null +++ b/root/parallels/pool/PSA_18.0.73_17940/examiners/php_launcher.sh @@ -0,0 +1,38 @@ +#!/bin/sh +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +die() +{ + echo $* + exit 1 +} + +[ -n "$1" ] || die "Usage: $0 php_script [args...]" + +[ "X${PLESK_INSTALLER_DEBUG}" = "X" ] || set -x +[ "X${PLESK_INSTALLER_STRICT_MODE}" = "X" ] || set -e + +php_bin= + +lookup() +{ + [ -z "$php_bin" ] || return + + local paths="$1" + local name="$2" + + for path in $paths; do + if [ -x "$path/$name" ]; then + php_bin="$path/$name" + break + fi + done +} + +lookup "/usr/local/psa/admin/bin /opt/psa/admin/bin" "php" +lookup "/usr/local/psa/bin /opt/psa/bin" "sw-engine-pleskrun" + +[ -n "$php_bin" ] || \ + die "Unable to locate the sw-engine PHP interpreter to execute the script. Make sure that Parallels Plesk Panel is installed on this server." + +exec "${php_bin}" "$@" diff --git a/root/parallels/pool/PSA_18.0.73_17940/examiners/plesk_preupgrade_checker.log b/root/parallels/pool/PSA_18.0.73_17940/examiners/plesk_preupgrade_checker.log new file mode 100644 index 0000000000..854d0e8647 --- /dev/null +++ b/root/parallels/pool/PSA_18.0.73_17940/examiners/plesk_preupgrade_checker.log @@ -0,0 +1,3 @@ + +INFO: Installed Plesk version/build: 18.0.73 Ubuntu 24.04 1800251009.17... +INFO: You have already installed the latest version Plesk 18.0.73. Tool must be launched prior to upgrade to Plesk 18.0.73 for the purpose of getting a report on potential problems with the upgrade. diff --git a/root/parallels/pool/PSA_18.0.73_17940/examiners/py_launcher.sh b/root/parallels/pool/PSA_18.0.73_17940/examiners/py_launcher.sh new file mode 100755 index 0000000000..96dc215391 --- /dev/null +++ b/root/parallels/pool/PSA_18.0.73_17940/examiners/py_launcher.sh @@ -0,0 +1,30 @@ +#!/bin/sh +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +die() +{ + echo "$*" + exit 1 +} + +[ -f "$1" ] || die "Usage: $0 PEX [args...]" + +[ "X${PLESK_INSTALLER_DEBUG}" = "X" ] || set -x +[ "X${PLESK_INSTALLER_STRICT_MODE}" = "X" ] || set -e + +find_python_bin() +{ + local bin + for bin in "/opt/psa/bin/py3-python" "/usr/local/psa/bin/py3-python" "/usr/libexec/platform-python" "/usr/bin/python3" "/opt/psa/bin/python" "/usr/local/psa/bin/python" "/usr/bin/python2"; do + [ -x "$bin" ] || continue + python_bin="$bin" + return 0 + done + + return 1 +} + +find_python_bin || + die "Unable to locate Python interpreter to execute the script." + +exec "$python_bin" "$@" diff --git a/root/parallels/pool/PSA_18.0.73_17940/examiners/repository_check.sh b/root/parallels/pool/PSA_18.0.73_17940/examiners/repository_check.sh new file mode 100755 index 0000000000..090f121ea1 --- /dev/null +++ b/root/parallels/pool/PSA_18.0.73_17940/examiners/repository_check.sh @@ -0,0 +1,782 @@ +#!/bin/bash +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +[ -z "$PLESK_INSTALLER_DEBUG" ] || set -x +[ -z "$PLESK_INSTALLER_STRICT_MODE" ] || set -e + +export LC_ALL=C +unset GREP_OPTIONS + +RET_SUCCESS=0 +RET_WARN=1 +RET_FATAL=2 + +is_function_defined() +{ + local fn="$1" + case "$(type $fn 2>/dev/null)" in + *function*) + return 0 + ;; + esac + return 1 +} + +# @params are tags in format "key=value" +# Report body (human readable information) is read from stdin +# and copied to stderr. +make_error_report() +{ + local report_file="${PLESK_INSTALLER_ERROR_REPORT:-}" + + local python_bin= + for bin in "/opt/psa/bin/python" "/usr/local/psa/bin/python" "/usr/bin/python2" "/opt/psa/bin/py3-python" "/usr/local/psa/bin/py3-python" "/usr/libexec/platform-python" "/usr/bin/python3"; do + if [ -x "$bin" ]; then + python_bin="$bin" + break + fi + done + + if [ -n "$report_file" -a -x "$python_bin" ]; then + "$python_bin" -c 'import sys, json +report_file = sys.argv[1] +error = sys.stdin.read() + +sys.stderr.write(error) + +data = { + "error": error, +} + +for tag in sys.argv[2:]: + k, v = tag.split("=", 1) + data[k] = v + +with open(report_file, "a") as f: + json.dump(data, f) + f.write("\n") +' "$report_file" "date=$(date --utc --iso-8601=ns)" "$@" + else + cat - >&2 + fi +} + +detect_platform() +{ + . /etc/os-release + os_name="$ID" + os_version="${VERSION_ID%%.*}" + os_arch="$(uname -m)" + if [ -e /etc/debian_version ]; then + case "$os_arch" in + x86_64) pkg_arch="amd64" ;; + aarch64) pkg_arch="arm64" ;; + esac + if [ -n "$VERSION_CODENAME" ]; then + os_codename="$VERSION_CODENAME" + else + case "$os_name$os_version" in + debian10) os_codename="buster" ;; + debian11) os_codename="bullseye" ;; + debian12) os_codename="bookworm" ;; + ubuntu18) os_codename="bionic" ;; + ubuntu20) os_codename="focal" ;; + ubuntu22) os_codename="jammy" ;; + ubuntu24) os_codename="noble" ;; + esac + fi + fi + + case "$os_name$os_version" in + rhel7|centos7|cloudlinux7|virtuozzo7) + package_manager="yum" + ;; + rhel*|centos*|cloudlinux*|almalinux*|rocky*) + package_manager="dnf" + ;; + debian*|ubuntu*) + package_manager="apt" + ;; + esac + + if [ "$os_name" = "ubuntu" -o "$os_name" = "debian" ]; then + PRODUCT_ROOT_D="/opt/psa" + else + PRODUCT_ROOT_D="/usr/local/psa" + fi +} + +has_os_impl_function() +{ + local prefix="$1" + local fn="${prefix}_${os_name}${os_version}" + is_function_defined "$fn" +} + +call_os_impl_function() +{ + local prefix="$1" + shift + local fn="${prefix}_${os_name}${os_version}" + "$fn" "$@" +} + +skip_checker_on_flag() +{ + local name="$1" + local flag="$2" + + if [ -f "$flag" ]; then + echo "$name was skipped due to flag file." >&2 + exit $RET_SUCCESS + fi +} + +skip_checker_on_env() +{ + local name="$1" + local env="$2" + + if [ -n "$env" ]; then + echo "$name was skipped due to environment variable." >&2 + exit $RET_SUCCESS + fi +} + +checker_main() +{ + local fnprefix="$1" + shift + + detect_platform + # try to execute checker only if all attributes are detected + [ -n "$os_name" -a -n "$os_version" ] || return $RET_SUCCESS + + for checker in "${fnprefix}_${os_name}${os_version}" "${fnprefix}_${os_name}" "${fnprefix}"; do + if is_function_defined "$checker"; then + local rc=$RET_SUCCESS + "$checker" "$@" || rc=$? + [ "$(( $rc & $RET_FATAL ))" = "0" ] || return $RET_FATAL + [ "$(( $rc & $RET_WARN ))" = "0" ] || return $RET_WARN + return $rc + fi + done + return $RET_SUCCESS +} + +#!/bin/sh +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +# If env variable PLESK_INSTALLER_ERROR_REPORT=path_to_file is specified then in case of error +# repository_check.sh writes single line json report into it with the following fields: +# - "stage": "repositorycheck" +# - "level": "error" +# - "errtype" is one of the following: +# * "reponotcached" - repository is not cached (mostly due to unavailability). +# * "reponotenabled" - required repository is not enabled. +# * "reponotsupported" - unsupported repository is enabled. +# * "configmanagernotinstalled" - dnf config-manager is disabled. +# - "repo": repository name. +# - "date": time of error occurance ("2020-03-24T06:59:43,127545441+0000") +# - "error": human readable error message. + +report_no_repo() +{ + local repo="$1" + + make_error_report 'stage=repositorycheck' 'level=error' 'errtype=reponotenabled' "repo=$repo" <<-EOL + Plesk installation requires '$repo' OS repository to be enabled. + Make sure it is available and enabled, then try again. + EOL +} + +report_no_repo_cache() +{ + local repo="$1" + + make_error_report 'stage=repositorycheck' 'level=error' 'errtype=reponotcached' "repo=$repo" <<-EOL + Unable to create $package_manager cache for '$repo' OS repository. + Make sure the repository is available, otherwise either disable it or fix its configuration, then try again. + EOL +} + +report_unsupported_repo() +{ + local repo="$1" + + make_error_report 'stage=repositorycheck' 'level=error' 'errtype=reponotsupported' "repo=$repo" <<-EOL + Plesk installation doesn't support '$repo' OS repository. + Make sure it is disabled, then try again. + EOL +} + +report_rh_no_config_manager() +{ + local target + case "$package_manager" in + yum) + target="yum-utils package" + ;; + dnf) + target="config-manager dnf plugin" + ;; + esac + + make_error_report 'stage=repositorycheck' 'level=error' 'errtype=configmanagernotinstalled' <<-EOL + Failed to install $target. + Make sure repositories configuration of $package_manager package manager is correct + (use '$package_manager repolist --verbose' to get its actual state), then try again. + EOL +} + +check_rh_broken_repos() +{ + local rh_enabled_repos rh_available_repos + + # 1. `yum repolist` and `dnf repolist` list all repos + # which were enabled before last cache creation + # even if cache for them was not created. + # If some repo is misconfigured and cache was created with `skip_if_unavailable=1` + # then such repo will be listed anyway despite on cache state. + # If some repo was enabled after last cache creation + # then `repolist --cacheonly` will fail. + # 2. `yum repolist --verbose` and `dnf repoinfo` list only repos + # which were successfully cached before. + # These commands fail if at least one repo is not available + # and the 'skip_if_unavailable' flag is not set. + case "$package_manager" in + yum) + rh_enabled_repos="$( + { + yum repolist enabled --cacheonly -q 2>/dev/null \ + || yum repolist enabled -q --setopt='*.skip_if_unavailable=1' + } | sed -n -e '1d' -e 's/^\*\?!\?\([^/[:space:]]\+\).*/\1/p' + )" || return $RET_FATAL + + rh_available_repos="$( + yum repolist enabled --verbose --cacheonly -q --setopt='*.skip_if_unavailable=1' \ + | sed -n -e 's/^Repo-id\s*:\s*\([^/[:space:]]\+\).*/\1/p' + )" || return $RET_FATAL + ;; + dnf) + rh_enabled_repos="$( + { + dnf repolist --enabled --cacheonly -q 2>/dev/null \ + || dnf repolist --enabled -q --setopt='*.skip_if_unavailable=1' + } | sed -n -e '1d' -e 's/^!\?\(\S\+\).*/\1/p' + )" || return $RET_FATAL + + rh_available_repos="$( \ + dnf repoinfo --enabled --cacheonly -q --setopt='*.skip_if_unavailable=1' \ + | sed -n -e 's|^Repo-id\s*:\s*\(\S\+\)\s*$|\1|p' + )" || return $RET_FATAL + ;; + esac + + local rh_enabled_repos_f="$(mktemp /tmp/plesk-installer.preupgrade_checker.XXXXXX)" + echo "$rh_enabled_repos" | sort > "$rh_enabled_repos_f" + local rh_available_repos_f="$(mktemp /tmp/plesk-installer.preupgrade_checker.XXXXXX)" + echo "$rh_available_repos" | sort > "$rh_available_repos_f" + + local repo rc=0 + for repo in $(comm -23 "$rh_enabled_repos_f" "$rh_available_repos_f"); do + report_no_repo_cache "$repo" + rc=$RET_WARN + done + + rm -f "$rh_enabled_repos_f" "$rh_available_repos_f" + + return $rc +} + +has_rh_enabled_repo() +{ + local repo="$1" + + # Try to get list of repos from cache first. + # If some repo was enabled after last cache creation + # or some repo is unavailable the query from cache will fail. + # Try to fetch actual metadata in this case. + case "$package_manager" in + yum) + # Repo-id may end with OS version and/or architecture + # if baseurl of the repo refers to $releasever and/or $basearch variables + # eg 'epel/7/x86_64', 'epel/7', 'epel/x86_64' + { + yum repolist enabled --verbose --cacheonly -q 2>/dev/null \ + || yum repolist enabled --verbose -q --setopt='*.skip_if_unavailable=1' + } | grep -E -q "^Repo-id\s*: $repo(/.+)?\s*$" + ;; + dnf) + # note: --noplugins may cause failure and empty output on RedHat + { + dnf repoinfo --enabled --cacheonly -q 2>/dev/null \ + || dnf repoinfo --enabled -q --setopt='*.skip_if_unavailable=1' + } | grep -E -q "^Repo-id\s*: $repo\s*$" + ;; + esac +} + +has_rh_config_manager() +{ + case "$package_manager" in + yum) yum-config-manager --help >/dev/null 2>&1 ;; + dnf) dnf config-manager --help >/dev/null 2>&1 ;; + esac +} + +install_rh_config_manager() +{ + case "$package_manager" in + yum) yum install --disablerepo 'PLESK_*' -q -y 'yum-utils' --setopt='*.skip_if_unavailable=1' ;; + dnf) dnf install --disablerepo 'PLESK_*' -q -y 'dnf-command(config-manager)' --setopt='*.skip_if_unavailable=1' ;; + esac +} + +check_rh_config_manager() +{ + if ! has_rh_config_manager && ! install_rh_config_manager; then + report_rh_no_config_manager + return $RET_FATAL + fi +} + +enable_rh_repo() +{ + case "$package_manager" in + yum) yum-config-manager --enable "$@" && has_rh_enabled_repo "$@" ;; + dnf) dnf config-manager --set-enabled "$@" && has_rh_enabled_repo "$@" ;; + esac +} + +enable_sm_repo() +{ + ! has_rh_enabled_repo "$@" || return 0 + subscription-manager repos --enable "$@" || return $? + # On RedHat 8 above command may return 0 on failure with "Repositories disabled by configuration." + has_rh_enabled_repo "$@" +} + +check_epel() +{ + ! enable_rh_repo "epel" || return 0 + + # try to install epel-release from centos/extras or plesk/thirdparty repo + # and then try to update it to last version shipped by epel itself + # to make package upgradable with pum + "$package_manager" install --disablerepo 'PLESK_*' -q -y 'epel-release' --setopt='*.skip_if_unavailable=1' 2>/dev/null \ + || "$package_manager" install --disablerepo='*' --enablerepo 'PLESK_18_*-thirdparty' -q -y 'epel-release' \ + || "$package_manager" install -q -y "https://dl.fedoraproject.org/pub/epel/epel-release-latest-$os_version.noarch.rpm" \ + && "$package_manager" update -q -y 'epel-release' --setopt='*.skip_if_unavailable=1' 2>/dev/null + + # Ensure any other EPEL repos have cache for subsequent check for broken repos (AL9) + local epel_repos="$( + [ "$package_manager" != "dnf" ] || { + dnf repolist --enabled --cacheonly -q 2>/dev/null || + dnf repolist --enabled -q --setopt='*.skip_if_unavailable=1' + } | sed -n -e '1d' -e 's/^!\?\(epel\S\+\).*/\1/p' + )" + for repo in $epel_repos; do + "$package_manager" makecache --repo "$repo" -q + done + + ! has_rh_enabled_repo "epel" || return 0 + + report_no_repo "epel" + return $RET_FATAL +} + +check_codeready() +{ + local repo_rhel="codeready-builder-for-rhel-$os_version-$os_arch-rpms" + local repo_rhui="codeready-builder-for-rhel-$os_version-rhui-rpms" + local repo_rhui_alt="codeready-builder-for-rhel-$os_version-$os_arch-rhui-rpms" + local repo_rhui_alt2="rhui-codeready-builder-for-rhel-$os_version-$os_arch-rhui-rpms" + + ! enable_sm_repo "$repo_rhel" || return 0 + ! enable_rh_repo "$repo_rhui" || return 0 + ! enable_rh_repo "$repo_rhui_alt" || return 0 + ! enable_rh_repo "$repo_rhui_alt2" || return 0 + + report_no_repo "$repo_rhel" + return $RET_FATAL +} + +check_optional() +{ + local repo_rhel="rhel-$os_version-server-optional-rpms" + local repo_rhui="rhel-$os_version-server-rhui-optional-rpms" + + ! enable_sm_repo "$repo_rhel" || return 0 + ! enable_rh_repo "$repo_rhui" || return 0 + + report_no_repo "$repo_rhel" + return $RET_FATAL +} + +check_repos_rhel9() +{ + check_rh_config_manager || return $? + + local rc=0 + + check_epel || rc="$(( $rc | $? ))" + check_codeready || rc="$(( $rc | $? ))" + check_rh_broken_repos || rc="$(( $rc | $? ))" + + return $rc +} + +check_repos_almalinux9() +{ + check_rh_config_manager || return $? + + local rc=0 + check_epel || rc="$(( $rc | $? ))" + check_rh_broken_repos || rc="$(( $rc | $? ))" + + # powertools is renamed to crb since AlmaLinux 9 + ! enable_rh_repo "crb" || return $rc + + report_no_repo "crb" + return $RET_FATAL +} + +check_repos_cloudlinux9() +{ + check_repos_almalinux9 "$@" +} + +check_repos_almalinux10() +{ + check_repos_almalinux9 "$@" +} + +check_repos_centos8() +{ + check_rh_config_manager || return $? + + local rc=0 + check_epel || rc="$(( $rc | $? ))" + check_rh_broken_repos || rc="$(( $rc | $? ))" + + # names of repos are lowercased since 8.3 + ! enable_rh_repo "powertools" || return $rc + ! enable_rh_repo "PowerTools" || return $rc + + report_no_repo "powertools" + return $RET_FATAL +} + +check_repos_cloudlinux8() +{ + check_rh_config_manager || return $? + + local rc=0 + check_epel || rc="$(( $rc | $? ))" + check_rh_broken_repos || rc="$(( $rc | $? ))" + + # names of repos are changed since 8.5 + ! enable_rh_repo "powertools" || return $rc + ! enable_rh_repo "cloudlinux-PowerTools" || return $rc + + report_no_repo "powertools" + return $RET_FATAL +} + +check_repos_rhel8() +{ + check_rh_config_manager || return $? + + local rc=0 + check_epel || rc="$(( $rc | $? ))" + check_rh_broken_repos || rc="$(( $rc | $? ))" + + [ "$1" = "install" ] || return $rc + + check_codeready || rc="$(( $rc | $? ))" + + return $rc +} + +check_repos_almalinux8() +{ + check_repos_centos8 "$@" +} + +check_repos_rocky8() +{ + check_repos_centos8 "$@" +} + +check_repos_rhel7() +{ + check_rh_config_manager || return $? + + local rc=0 + + check_epel || rc="$(( $rc | $? ))" + check_optional || rc="$(( $rc | $? ))" + check_rh_broken_repos || rc="$(( $rc | $? ))" + + return $rc +} + +check_repos_centos7_based() +{ + check_rh_config_manager || return $? + + local rc=0 + + check_epel || rc="$(( $rc | $? ))" + check_rh_broken_repos || rc="$(( $rc | $? ))" + + return $rc +} + +sed_escape() +{ + # Note: this is not a full implementation + echo -n "$1" | sed -e 's|\.|\\.|g' +} + +switch_eol_centos_repos() +{ + local old_mirrorlist_host="mirrorlist.centos.org" + local old_host="mirror.centos.org" + local new_host="vault.centos.org" + + grep -qFw "$old_host" /etc/yum.repos.d/CentOS-*.repo 2>/dev/null || return 0 + local backup="`mktemp -d "/tmp/yum.repos.d-$(date --rfc-3339=date)-XXXXXX"`" + ! [ -d "$backup" ] || cp -raT /etc/yum.repos.d "$backup" || : + + sed -i \ + -e "s|^\s*\(mirrorlist\b[^/]*//`sed_escape "$old_mirrorlist_host"`/.*\)$|#\1|" \ + -e "s|^#*\s*baseurl\b\([^/]*\)//`sed_escape "$old_host"`/\(.*\)$|baseurl\1//$new_host/\2|" \ + /etc/yum.repos.d/CentOS-*.repo + echo "YUM package manager repositories were backed up to '$backup' and switched from $old_host to $new_host ." >&2 +} + +check_repos_centos7() +{ + switch_eol_centos_repos + + check_repos_centos7_based "$@" +} + +check_repos_cloudlinux7() +{ + check_repos_centos7_based "$@" +} + +check_repos_virtuozzo7() +{ + check_repos_centos7_based "$@" +} + +find_apt_repo() +{ + local repo="$1" + + local dist_tag= + ! [ "$os_name" = "ubuntu" ] || dist_tag="a" + ! [ "$os_name" = "debian" ] || dist_tag="n" + + if [ -z "$_apt_cache_policy" ]; then + # extract info of each available release as a string which consists of 'tag=value' + # filter out releases with priority less or equal to 100 + _apt_cache_policy="$( + apt-cache policy \ + | grep "b=$pkg_arch" \ + | grep -Eo '([a-z]=[^,]+,?)*' \ + )" + fi + + local l="$(echo "$repo" | cut -f1 -d'/')" + local d="$(echo "$repo" | cut -f2 -d'/')" + local c="$(echo "$repo" | cut -f3 -d'/')" + + # try to find releases by distribution and component + echo "$_apt_cache_policy" \ + | grep -E "(^|,)l=$l(,|$)" \ + | grep -E "(^|,)$dist_tag=$d(,|$)" \ + | grep -E "(^|,)c=$c(,|$)" \ + | while IFS="$(printf '\n')" read rel && [ -n "$rel" ]; do + l="$(echo "$rel" | grep -Eo "(^|,)l=[^,]+" | cut -f2 -d"=")" + d="$(echo "$rel" | grep -Eo "(^|,)$dist_tag=[^,]+" | cut -f2 -d"=")" + c="$(echo "$rel" | grep -Eo "(^|,)c=[^,]+" | cut -f2 -d"=")" + echo "$l/$d/$c" + done +} + +apt_install_packages() +{ + DEBIAN_FRONTEND=noninteractive LANG=C PATH=/usr/sbin:/usr/bin:/sbin:/bin \ + apt-get -qq --assume-yes -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold -o APT::Install-Recommends=no \ + install "$@" +} + +# Takes a list of suites and disables them in APT sources. +# Multiline deb822 format is supported. +disable_apt_suites_deb822() +{ + local python3=/usr/bin/python3 + + "$python3" -c 'import aptsources.sourceslist' 2>/dev/null || + apt_install_packages python3-apt + + "$python3" -c ' +import sys + +from aptsources.sourceslist import SourcesList + + +suites_to_disable=set(sys.argv[1:]) + +sources_list = SourcesList(deb822=True) + +sources_changed = False +for src in sources_list: + if src.invalid: + continue + suites = getattr(src, "suites", ()) + if not suites: + continue + new_suites = [s for s in suites if s not in suites_to_disable] + if len(new_suites) != len(suites): + sources_changed = True + if len(new_suites) == 0: + src.disabled = True + else: + src.suites = new_suites + +if sources_changed: + sources_list.save() +' "$@" + + # Since we have changed the repositories list, we should re-read _apt_cache_policy on a next call + # of the find_apt_repo function. Hence we have to reset the value of the variable + _apt_cache_policy="" +} + +disable_apt_repo() +{ + local repos_to_disable="$(find_apt_repo "$1" | cut -d '/' -f 2,3 | sort | uniq)" + if [ -z "$repos_to_disable" ]; then + return 0 + fi + + echo "$repos_to_disable" \ + | while IFS= read -r repo_to_disable && [ -n "$repo_to_disable" ]; do + local distrib=${repo_to_disable%%/*} + local component=${repo_to_disable##*/} + find /etc/apt -name "*.list" -exec \ + sed -i -e "/^\s*#/! s/.*\s$distrib\s\+$component\b/# &/" {} + + done + + # Since we have changed the repositories list, we should re-read _apt_cache_policy on a next call + # of the find_apt_repo function. Hence we have to reset the value of the variable + _apt_cache_policy="" + + return 0 +} + +check_required_apt_repo() +{ + local repo="$1" + [ -z "$(find_apt_repo "$repo")" ] || return 0 + report_no_repo "$repo" + return $RET_FATAL +} + +check_unsupported_apt_repos_ubuntu() +{ + [ -n "$os_codename" ] || return 0 + local mode="$1" + + local repos="$( + find_apt_repo "Ubuntu/[^,]+/[^,]+" | grep -v "Ubuntu/$os_codename.*/.*" + find_apt_repo "Debian[^,]*/[^,]+/[^,]+" + )" + [ -n "$repos" ] || return 0 + + echo "$repos" | while IFS="$(printf '\n')" read repo; do + report_unsupported_repo "$repo" + done + + [ "$mode" = "install" ] || return $RET_WARN + return $RET_FATAL +} + +check_repos_ubuntu18() +{ + [ -n "$os_codename" ] || return 0 + local mode="$1" + local rc=0 + + check_required_apt_repo "Ubuntu/$os_codename/main" || rc="$(( $rc | $? ))" + check_required_apt_repo "Ubuntu/$os_codename/universe" || rc="$(( $rc | $? ))" + check_required_apt_repo "Ubuntu/$os_codename-updates/main" || rc="$(( $rc | $? ))" + check_required_apt_repo "Ubuntu/$os_codename-updates/universe" || rc="$(( $rc | $? ))" + check_unsupported_apt_repos_ubuntu "$mode" || rc="$(( $rc | $? ))" + + return $rc +} + + +check_repos_ubuntu() +{ + [ -n "$os_codename" ] || return 0 + local mode="$1" + local rc=0 + + check_required_apt_repo "Ubuntu/$os_codename/main" || rc="$(( $rc | $? ))" + check_required_apt_repo "Ubuntu/$os_codename/universe" || rc="$(( $rc | $? ))" + check_unsupported_apt_repos_ubuntu "$mode" || rc="$(( $rc | $? ))" + + return $rc +} + +check_unsupported_apt_repos_debian() +{ + [ -n "$os_codename" ] || return 0 + local mode="$1" + + local repos="$( + find_apt_repo "Debian Backports/$os_codename-backports/[^,]+" + find_apt_repo "Debian[^,]*/[^,]+/[^,]+" | grep -v "Debian.*/$os_codename.*/.*" + find_apt_repo "Ubuntu/[^,]+/[^,]+" + )" + [ -n "$repos" ] || return 0 + + echo "$repos" | while IFS="$(printf '\n')" read repo; do + report_unsupported_repo "$repo" + done + + [ "$mode" = "install" ] || return $RET_WARN + return $RET_FATAL +} + +check_repos_debian() +{ + [ -n "$os_codename" ] || return 0 + local mode="$1" + local rc=0 + + if [ "$os_name" = "debian" -a "$os_version" -ge 12 ]; then + disable_apt_suites_deb822 "$os_codename-backports" + else + disable_apt_repo "Debian Backports/$os_codename-backports/[^,]+" + fi + + check_required_apt_repo "Debian/$os_codename/main" || rc="$(( $rc | $? ))" + check_unsupported_apt_repos_debian "$mode" || rc="$(( $rc | $? ))" + + return $rc +} + +# --- + +skip_checker_on_flag "Repository check" "/tmp/plesk-installer-skip-repository-check.flag" + +checker_main 'check_repos' "$1" diff --git a/root/parallels/pool/PSA_18.0.73_17940/examiners/sh_cmd.sh b/root/parallels/pool/PSA_18.0.73_17940/examiners/sh_cmd.sh new file mode 100755 index 0000000000..ed95d0acbb --- /dev/null +++ b/root/parallels/pool/PSA_18.0.73_17940/examiners/sh_cmd.sh @@ -0,0 +1,7 @@ +#!/bin/sh +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +[ "X${PLESK_INSTALLER_DEBUG}" = "X" ] || set -x +[ "X${PLESK_INSTALLER_STRICT_MODE}" = "X" ] || set -e + +exec "$@" diff --git a/root/parallels/pool/PSA_18.0.73_17940/plesk-18.0.73-ubt24.04-x86_64.inf3 b/root/parallels/pool/PSA_18.0.73_17940/plesk-18.0.73-ubt24.04-x86_64.inf3 new file mode 100644 index 0000000000..120ba03792 --- /dev/null +++ b/root/parallels/pool/PSA_18.0.73_17940/plesk-18.0.73-ubt24.04-x86_64.inf3 @@ -0,0 +1,927 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -----BEGIN PGP PUBLIC KEY BLOCK----- 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 VNnMRY8n =Hv0N -----END PGP PUBLIC KEY BLOCK----- + + + psa + + + + + + + + + + + + + + + + + + + + + + + + + + + + + mysqlgroup + l10n + proftpd + webservers + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + imapservers + + + + + + + + + + + + + + + + + + + imapservers + + + + + + + + + + + + + + mailman + spamassassin + drweb + sophos + courier + dovecot + + + + + + + + + + + + + + + + + + + + + + mailservers + + + + + + + + + + + + + + + + + + + + mailservers + + + + + + + + + + + + + + + + + + panel + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + + + + + + + + php8.3 + + + + + + + + + + + webservers + + + + + + + + + + + + + + + + + + + + + panel + + + + + + + + + + + + + panel + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + panel + + + + + + + + + + + + + + + + + + + + panel + + + + + + + panel + + + + + + + + panel + + + + + + + panel + passenger + + + + + + ruby + + + + + + + panel + + + + + + panel + + + + + + panel + passenger + + + + + + + + + + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + panel + roundcube + postfix + dovecot + mod_fcgid + proftpd + webalizer + awstats + webservers + nginx + mysqlgroup + l10n + bind + wp-toolkit + advisor + git + xovi + imunify360 + fail2ban + modsecurity + sslit + letsencrypt + repair-kit + composer + monitoring + log-browser + ssh-terminal + site-import + sitejet + ntp-timesync + php8.3 + php8.4 + mfa + configurations-troubleshooter + + + panel + roundcube + postfix + dovecot + mod_fcgid + proftpd + webalizer + awstats + webservers + nginx + mysqlgroup + l10n + bind + wp-toolkit + advisor + git + xovi + imunify360 + fail2ban + modsecurity + sslit + letsencrypt + repair-kit + composer + monitoring + log-browser + ssh-terminal + site-import + sitejet + ntp-timesync + php8.1 + php8.2 + php8.3 + php8.4 + mfa + configurations-troubleshooter + resctrl + drweb + postgresql + spamassassin + ruby + gems-pre + nodejs + pmm + psa-firewall + watchdog + passenger + phpgroup + sophos + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/pool/PSA_18.0.73_17940/release.inf3 b/root/parallels/pool/PSA_18.0.73_17940/release.inf3 new file mode 100644 index 0000000000..4fb000283d --- /dev/null +++ b/root/parallels/pool/PSA_18.0.73_17940/release.inf3 @@ -0,0 +1,36 @@ + + + + + + + + + psa + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/pool/PSA_18.0.73_17971/examiners/check_broken_timezone.sh b/root/parallels/pool/PSA_18.0.73_17971/examiners/check_broken_timezone.sh new file mode 100755 index 0000000000..ee862642be --- /dev/null +++ b/root/parallels/pool/PSA_18.0.73_17971/examiners/check_broken_timezone.sh @@ -0,0 +1,255 @@ +#!/bin/bash +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +[ -z "$PLESK_INSTALLER_DEBUG" ] || set -x +[ -z "$PLESK_INSTALLER_STRICT_MODE" ] || set -e + +export LC_ALL=C +unset GREP_OPTIONS + +RET_SUCCESS=0 +RET_WARN=1 +RET_FATAL=2 + +is_function_defined() +{ + local fn="$1" + case "$(type $fn 2>/dev/null)" in + *function*) + return 0 + ;; + esac + return 1 +} + +# @params are tags in format "key=value" +# Report body (human readable information) is read from stdin +# and copied to stderr. +make_error_report() +{ + local report_file="${PLESK_INSTALLER_ERROR_REPORT:-}" + + local python_bin= + for bin in "/opt/psa/bin/python" "/usr/local/psa/bin/python" "/usr/bin/python2" "/opt/psa/bin/py3-python" "/usr/local/psa/bin/py3-python" "/usr/libexec/platform-python" "/usr/bin/python3"; do + if [ -x "$bin" ]; then + python_bin="$bin" + break + fi + done + + if [ -n "$report_file" -a -x "$python_bin" ]; then + "$python_bin" -c 'import sys, json +report_file = sys.argv[1] +error = sys.stdin.read() + +sys.stderr.write(error) + +data = { + "error": error, +} + +for tag in sys.argv[2:]: + k, v = tag.split("=", 1) + data[k] = v + +with open(report_file, "a") as f: + json.dump(data, f) + f.write("\n") +' "$report_file" "date=$(date --utc --iso-8601=ns)" "$@" + else + cat - >&2 + fi +} + +detect_platform() +{ + . /etc/os-release + os_name="$ID" + os_version="${VERSION_ID%%.*}" + os_arch="$(uname -m)" + if [ -e /etc/debian_version ]; then + case "$os_arch" in + x86_64) pkg_arch="amd64" ;; + aarch64) pkg_arch="arm64" ;; + esac + if [ -n "$VERSION_CODENAME" ]; then + os_codename="$VERSION_CODENAME" + else + case "$os_name$os_version" in + debian10) os_codename="buster" ;; + debian11) os_codename="bullseye" ;; + debian12) os_codename="bookworm" ;; + ubuntu18) os_codename="bionic" ;; + ubuntu20) os_codename="focal" ;; + ubuntu22) os_codename="jammy" ;; + ubuntu24) os_codename="noble" ;; + esac + fi + fi + + case "$os_name$os_version" in + rhel7|centos7|cloudlinux7|virtuozzo7) + package_manager="yum" + ;; + rhel*|centos*|cloudlinux*|almalinux*|rocky*) + package_manager="dnf" + ;; + debian*|ubuntu*) + package_manager="apt" + ;; + esac + + if [ "$os_name" = "ubuntu" -o "$os_name" = "debian" ]; then + PRODUCT_ROOT_D="/opt/psa" + else + PRODUCT_ROOT_D="/usr/local/psa" + fi +} + +has_os_impl_function() +{ + local prefix="$1" + local fn="${prefix}_${os_name}${os_version}" + is_function_defined "$fn" +} + +call_os_impl_function() +{ + local prefix="$1" + shift + local fn="${prefix}_${os_name}${os_version}" + "$fn" "$@" +} + +skip_checker_on_flag() +{ + local name="$1" + local flag="$2" + + if [ -f "$flag" ]; then + echo "$name was skipped due to flag file." >&2 + exit $RET_SUCCESS + fi +} + +skip_checker_on_env() +{ + local name="$1" + local env="$2" + + if [ -n "$env" ]; then + echo "$name was skipped due to environment variable." >&2 + exit $RET_SUCCESS + fi +} + +checker_main() +{ + local fnprefix="$1" + shift + + detect_platform + # try to execute checker only if all attributes are detected + [ -n "$os_name" -a -n "$os_version" ] || return $RET_SUCCESS + + for checker in "${fnprefix}_${os_name}${os_version}" "${fnprefix}_${os_name}" "${fnprefix}"; do + if is_function_defined "$checker"; then + local rc=$RET_SUCCESS + "$checker" "$@" || rc=$? + [ "$(( $rc & $RET_FATAL ))" = "0" ] || return $RET_FATAL + [ "$(( $rc & $RET_WARN ))" = "0" ] || return $RET_WARN + return $rc + fi + done + return $RET_SUCCESS +} + +#!/bin/sh +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +# If env variable PLESK_INSTALLER_ERROR_REPORT=path_to_file is specified then in case of error +# check-broken-tz.sh writes single line json report into it with the following fields: +# - "stage": "timezonefix" +# - "level": "error" +# - "errtype": "failure" +# - "date": time of error occurance ("2024-07-24T06:59:43,127545441+0000") +# - "error": human readable error message + +report_dpkg_configure_fail() +{ + local pkgname="$1" + make_error_report 'stage=timezonefix' 'level=error' 'errtype=dpkgconfigurefailed' <<-EOL + Could not configure the packages ( $pkgname ). See https://support.plesk.com/hc/en-us/articles/24721507961623-Plesk-provides-error-on-update-Package-tzdata-is-not-configured-yet for more details. + EOL +} + +report_get_tz_fail() +{ + make_error_report 'stage=timezonefix' 'level=error' 'errtype=gettzfailed' <<-EOL + Could not get the system timezone. See https://support.plesk.com/hc/en-us/articles/24721507961623-Plesk-provides-error-on-update-Package-tzdata-is-not-configured-yet for more details. + EOL +} + +report_set_tz_fail() +{ + local tz="$1" + + make_error_report 'stage=timezonefix' 'level=error' 'errtype=settzfailed' <<-EOL + Could not set the system timezone ( $tz ). See https://support.plesk.com/hc/en-us/articles/24721507961623-Plesk-provides-error-on-update-Package-tzdata-is-not-configured-yet for more details. + EOL +} + +get_current_tz() +{ + [ -L /etc/localtime ] || return 1 + + local tz + tz="$(readlink -m /etc/localtime)" || return 1 + [ -f "$tz" ] || return 1 + case "$tz" in + /usr/share/zoneinfo/*) ;; + *) return 1;; + esac + tz="${tz#/usr/share/zoneinfo/}" + [ -n "$tz" ] || return 1 + + echo -n "${tz}" +} + +check_timezone_ubuntu() +{ + [ -n "$os_codename" ] || return 0 + local mode="$1" + + # PPP-65676: Plesk update fails on ubuntu if timezone is CET + if dpkg-query --showformat='${db:Status-Status}\n' --show 'tzdata' | grep -wq 'half-configured'; then + local origtz + origtz=$(get_current_tz) + if [ $? != 0 ]; then + report_get_tz_fail + return $RET_WARN + fi + if ! timedatectl set-timezone 'Etc/UTC'; then + timedatectl set-timezone "$origtz" + report_set_tz_fail 'Etc/UTC' + return $RET_WARN + fi + if ! dpkg --configure 'tzdata'; then + timedatectl set-timezone "$origtz" + report_dpkg_configure_fail 'tzdata' + return $RET_WARN + fi + if ! timedatectl set-timezone "$origtz"; then + report_set_tz_fail "$origtz" + return $RET_WARN + fi + fi + + return 0 +} + +# --- + +skip_checker_on_flag "Broken timezone check" "/tmp/plesk-installer-skip-check-broken-timezone.flag" + +checker_main 'check_timezone' "$1" diff --git a/root/parallels/pool/PSA_18.0.73_17971/examiners/disk_space_check.sh b/root/parallels/pool/PSA_18.0.73_17971/examiners/disk_space_check.sh new file mode 100755 index 0000000000..1fdfb44037 --- /dev/null +++ b/root/parallels/pool/PSA_18.0.73_17971/examiners/disk_space_check.sh @@ -0,0 +1,542 @@ +#!/bin/bash +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +[ -z "$PLESK_INSTALLER_DEBUG" ] || set -x +[ -z "$PLESK_INSTALLER_STRICT_MODE" ] || set -e + +export LC_ALL=C +unset GREP_OPTIONS + +RET_SUCCESS=0 +RET_WARN=1 +RET_FATAL=2 + +is_function_defined() +{ + local fn="$1" + case "$(type $fn 2>/dev/null)" in + *function*) + return 0 + ;; + esac + return 1 +} + +# @params are tags in format "key=value" +# Report body (human readable information) is read from stdin +# and copied to stderr. +make_error_report() +{ + local report_file="${PLESK_INSTALLER_ERROR_REPORT:-}" + + local python_bin= + for bin in "/opt/psa/bin/python" "/usr/local/psa/bin/python" "/usr/bin/python2" "/opt/psa/bin/py3-python" "/usr/local/psa/bin/py3-python" "/usr/libexec/platform-python" "/usr/bin/python3"; do + if [ -x "$bin" ]; then + python_bin="$bin" + break + fi + done + + if [ -n "$report_file" -a -x "$python_bin" ]; then + "$python_bin" -c 'import sys, json +report_file = sys.argv[1] +error = sys.stdin.read() + +sys.stderr.write(error) + +data = { + "error": error, +} + +for tag in sys.argv[2:]: + k, v = tag.split("=", 1) + data[k] = v + +with open(report_file, "a") as f: + json.dump(data, f) + f.write("\n") +' "$report_file" "date=$(date --utc --iso-8601=ns)" "$@" + else + cat - >&2 + fi +} + +detect_platform() +{ + . /etc/os-release + os_name="$ID" + os_version="${VERSION_ID%%.*}" + os_arch="$(uname -m)" + if [ -e /etc/debian_version ]; then + case "$os_arch" in + x86_64) pkg_arch="amd64" ;; + aarch64) pkg_arch="arm64" ;; + esac + if [ -n "$VERSION_CODENAME" ]; then + os_codename="$VERSION_CODENAME" + else + case "$os_name$os_version" in + debian10) os_codename="buster" ;; + debian11) os_codename="bullseye" ;; + debian12) os_codename="bookworm" ;; + ubuntu18) os_codename="bionic" ;; + ubuntu20) os_codename="focal" ;; + ubuntu22) os_codename="jammy" ;; + ubuntu24) os_codename="noble" ;; + esac + fi + fi + + case "$os_name$os_version" in + rhel7|centos7|cloudlinux7|virtuozzo7) + package_manager="yum" + ;; + rhel*|centos*|cloudlinux*|almalinux*|rocky*) + package_manager="dnf" + ;; + debian*|ubuntu*) + package_manager="apt" + ;; + esac + + if [ "$os_name" = "ubuntu" -o "$os_name" = "debian" ]; then + PRODUCT_ROOT_D="/opt/psa" + else + PRODUCT_ROOT_D="/usr/local/psa" + fi +} + +has_os_impl_function() +{ + local prefix="$1" + local fn="${prefix}_${os_name}${os_version}" + is_function_defined "$fn" +} + +call_os_impl_function() +{ + local prefix="$1" + shift + local fn="${prefix}_${os_name}${os_version}" + "$fn" "$@" +} + +skip_checker_on_flag() +{ + local name="$1" + local flag="$2" + + if [ -f "$flag" ]; then + echo "$name was skipped due to flag file." >&2 + exit $RET_SUCCESS + fi +} + +skip_checker_on_env() +{ + local name="$1" + local env="$2" + + if [ -n "$env" ]; then + echo "$name was skipped due to environment variable." >&2 + exit $RET_SUCCESS + fi +} + +checker_main() +{ + local fnprefix="$1" + shift + + detect_platform + # try to execute checker only if all attributes are detected + [ -n "$os_name" -a -n "$os_version" ] || return $RET_SUCCESS + + for checker in "${fnprefix}_${os_name}${os_version}" "${fnprefix}_${os_name}" "${fnprefix}"; do + if is_function_defined "$checker"; then + local rc=$RET_SUCCESS + "$checker" "$@" || rc=$? + [ "$(( $rc & $RET_FATAL ))" = "0" ] || return $RET_FATAL + [ "$(( $rc & $RET_WARN ))" = "0" ] || return $RET_WARN + return $rc + fi + done + return $RET_SUCCESS +} + +#!/bin/sh +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +# If env variable PLESK_INSTALLER_ERROR_REPORT=path_to_file is specified then in case of error +# disk_space_check.sh writes single line json report into it with the following fields: +# - "stage": "diskspacecheck" +# - "level": "error" +# - "errtype": "notenoughdiskspace" +# - "volume": volume with not enough diskspace (e.g. "/") +# - "required": required diskspace on the volume, human readable (e.g. "600 MB") +# - "available": available diskspace on the volume, human readable (e.g. "255 MB") +# - "needtofree": amount of diskspace which should be freed on the volume, human readable (e.g. "345 MB") +# - "date": time of error occurance ("2020-03-24T06:59:43,127545441+0000") +# - "error": human readable error message ("There is not enough disk space available in the / directory.") + +# Required values below for Full installation are in MB. See 'du -cs -BM /*' and 'df -Pm'. + +required_disk_space_cloudlinux7() +{ + case "$1" in + /opt) echo 900 ;; + /usr) echo 4400 ;; + /var) echo 600 ;; + /tmp) echo 100 ;; + esac +} + +required_disk_space_cloudlinux8() +{ + case "$1" in + /opt) echo 1200 ;; + /usr) echo 4400 ;; + /var) echo 700 ;; + /tmp) echo 100 ;; + esac +} + +required_disk_space_centos7() +{ + case "$1" in + /opt) echo 900 ;; + /usr) echo 4100 ;; + /var) echo 600 ;; + /tmp) echo 100 ;; + esac +} + +required_disk_space_centos8() +{ + case "$1" in + /opt) echo 900 ;; + /usr) echo 4500 ;; + /var) echo 800 ;; + /tmp) echo 100 ;; + esac +} + +required_disk_space_virtuozzo7() +{ + required_disk_space_centos7 "$1" +} + +required_disk_space_rhel7() +{ + required_disk_space_centos7 "$1" +} + +required_disk_space_rhel8() +{ + required_disk_space_centos8 "$1" +} + +required_disk_space_almalinux8() +{ + required_disk_space_centos8 "$1" +} + +required_disk_space_rocky8() +{ + required_disk_space_centos8 "$1" +} + +required_disk_space_rhel9() +{ + case "$1" in + /opt) echo 500 ;; + /usr) echo 4000 ;; + /var) echo 800 ;; + /tmp) echo 100 ;; + esac +} + +required_disk_space_almalinux9() +{ + required_disk_space_rhel9 "$1" +} + +required_disk_space_almalinux10() +{ + required_disk_space_almalinux9 "$1" +} + +required_disk_space_cloudlinux9() +{ + required_disk_space_rhel9 "$1" +} + +required_disk_space_debian10() +{ + case "$1" in + /opt) echo 1800 ;; + /usr) echo 2300 ;; + /var) echo 1700 ;; + /tmp) echo 100 ;; + esac +} + +required_disk_space_debian11() +{ + case "$1" in + /opt) echo 1500 ;; + /usr) echo 3100 ;; + /var) echo 1800 ;; + /tmp) echo 100 ;; + esac +} + +required_disk_space_debian12() +{ + case "$1" in + /opt) echo 2700 ;; + /usr) echo 2500 ;; + /var) echo 2200 ;; + /tmp) echo 100 ;; + esac +} + +required_disk_space_debian13() +{ + case "$1" in + /opt) echo 2700 ;; + /usr) echo 2500 ;; + /var) echo 2200 ;; + /tmp) echo 100 ;; + esac +} + +required_disk_space_ubuntu18() +{ + case "$1" in + /opt) echo 900 ;; + /usr) echo 1800 ;; + /var) echo 600 ;; + /tmp) echo 100 ;; + esac +} + +required_disk_space_ubuntu20() +{ + case "$1" in + /opt) echo 1800 ;; + /usr) echo 2900 ;; + /var) echo 1600 ;; + /tmp) echo 100 ;; + esac +} + +required_disk_space_ubuntu22() +{ + case "$1" in + /opt) echo 1800 ;; + /usr) echo 3900 ;; + /var) echo 1900 ;; + /tmp) echo 100 ;; + esac +} + +required_disk_space_ubuntu24() +{ + case "$1" in + /opt) echo 3200 ;; + /usr) echo 1800 ;; + /var) echo 2400 ;; + /tmp) echo 100 ;; + esac +} + +required_update_upgrade_disk_space() +{ + case "$1" in + /opt) echo 100 ;; + /usr) echo 300 ;; + /var) echo 600 ;; + /tmp) echo 100 ;; + esac +} + +clean_tmp() +{ + local volume="$1" + local path="/tmp" + is_path_on_volume "$path" "$volume" || return 0 + + echo "Cleaning $path via 'systemd-tmpfiles --clean --prefix $path'" + systemd-tmpfiles --clean --prefix "$path" 2>&1 +} + +clean_yum() +{ + local volume="$1" + local path="/var/cache/yum" + is_path_on_volume "$path" "$volume" || return 0 + + echo "Cleaning $path via 'yum clean all'" + yum clean all 2>&1 + + # The command above doesn't clean untracked repos (missing in configuration), clean if left > 2 Mb + [ "`du -sm "$path" | awk '{ print $1 }'`" -gt 2 ] || return 0 + echo "Cleaning $path via 'rm -rf $path/*'" + rm -rf "$path"/* 2>&1 +} + +clean_dnf() +{ + local volume="$1" + local path="/var/cache/dnf" + is_path_on_volume "$path" "$volume" || return 0 + + echo "Cleaning $path via 'dnf clean all'" + dnf clean all 2>&1 +} + +clean_apt() +{ + local volume="$1" + local path="/var/cache/apt" + is_path_on_volume "$path" "$volume" || return 0 + + echo "Cleaning $path via 'apt-get clean'" + apt-get clean 2>&1 +} + +clean_journal() +{ + local volume="$1" + local path="/var/log/journal" + is_path_on_volume "$path" "$volume" || return 0 + + # Note that --rotate may cause more space to be freed, but may also cause more space to be used + echo "Cleaning $path via 'journalctl --vacuum-time 1d'" + journalctl --vacuum-time 1d 2>&1 +} + +clean_ext_packages() +{ + local volume="$1" + local path="$PRODUCT_ROOT_D/var/modules-packages" + is_path_on_volume "$path" "$volume" || return 0 + + echo "Cleaning $path via 'rm -rf $path/*'" + rm -rf "$path"/* 2>&1 +} + +# @param $1 target directory +mount_point() +{ + df -Pm $1 | awk 'NR==2 { print $6 }' +} + +# @param $1 target directory +available_disk_space() +{ + df -Pm $1 | awk 'NR==2 { print $4 }' +} + +is_path_on_volume() +{ + local path="$1" + local volume="$2" + [ -d "$path" ] && [ "`mount_point "$path"`" = "$volume" ] +} + +# @param $1 target directory +# @param $2 mode (install/upgrade/update) +req_disk_space() +{ + if [ "$2" != "install" ]; then + required_update_upgrade_disk_space "$1" + return + fi + + has_os_impl_function "required_disk_space" || { + echo "There are no requirements defined for $os_name$os_version." >&2 + echo "Disk space check cannot be performed." >&2 + exit $RET_WARN + } + call_os_impl_function "required_disk_space" "$1" +} + +human_readable_size() +{ + echo "$1" | awk ' + function human(x) { + s = "MGTEPYZ"; + while (x >= 1000 && length(s) > 1) { + x /= 1024; s = substr(s, 2); + } + # 0.05 below will make sure the value is rounded up + return sprintf("%.1f %sB", x + 0.05, substr(s, 1, 1)); + } + { print human($1); }' +} + +# @param $1 target directory +# @param $2 required disk space +# @param $3 check only flag (don't emit errors) +check_available_disk_space() +{ + local volume="$1" + local required="$2" + local check_only="${3:-}" + local available="$(available_disk_space "$volume")" + if [ "$available" -lt "$required" ]; then + local needtofree + needtofree="`human_readable_size $((required - available))`" + [ -n "$check_only" ] || + make_error_report 'stage=diskspacecheck' 'level=error' 'errtype=notenoughdiskspace' \ + "volume=$volume" "required=$required MB" "available=$available MB" "needtofree=$needtofree" \ + <<-EOL + There is not enough disk space available in the $1 directory. + You need to free up $needtofree. + EOL + return "$RET_FATAL" + fi +} + +# @param $1 target directory +# @param $2 required disk space +clean_and_check_available_disk_space() +{ + if [ -n "$PLESK_INSTALLER_FORCE_CLEAN_DISK_SPACE" ] || ! check_available_disk_space "$@" --check-only; then + clean_disk_space "$1" + check_available_disk_space "$@" + fi +} + +# Cleans up disk space on the volume +clean_disk_space() +{ + local volume="$1" + for cleanup_func in clean_tmp clean_yum clean_dnf clean_apt clean_journal clean_ext_packages; do + "$cleanup_func" "$volume" + done +} + +# @param $1 mode (install/upgrade/update) +clean_and_check_disk_space() +{ + local mode="$1" + local shared=0 + + for target_directory in /opt /usr /var /tmp; do + local required=$(req_disk_space "$target_directory" "$mode") + [ -n "$required" ] || return "$RET_WARN" + + if is_path_on_volume "$target_directory" "/"; then + shared="$((shared + required))" + else + clean_and_check_available_disk_space "$target_directory" "$required" || return $? + fi + done + + clean_and_check_available_disk_space "/" "$shared" || return $? +} + +checker_main 'clean_and_check_disk_space' "$1" diff --git a/root/parallels/pool/PSA_18.0.73_17971/examiners/license_key_check.php b/root/parallels/pool/PSA_18.0.73_17971/examiners/license_key_check.php new file mode 100644 index 0000000000..917e44709f --- /dev/null +++ b/root/parallels/pool/PSA_18.0.73_17971/examiners/license_key_check.php @@ -0,0 +1,111 @@ += 10.0.0 */ +if (!is_array($vers)) { + $vers = [$vers]; +} + +$match = false; +foreach ($vers as $ver) { + if (!is_array($ver)) { + $match |= strtok($ver, ".") == strtok($targetVersion, "."); + } else { + $match |= ("any" == $ver[0] || version_compare($ver[0], $targetVersion) <= 0) && + ("any" == $ver[1] || version_compare($ver[1], $targetVersion) >= 0); + } +} + +if ($match) { + fwrite(STDERR, "You do not need to upgrade the current license key.\n"); + fwrite(STDOUT, "License upgrade check to $targetVersion can be skipped.\n"); + fwrite(STDOUT, "Plesk versions compatible with the license key: " . preg_replace('/\n\s*/', '', var_export($vers, true)) . "\n"); + finish(0); +} + +if (!function_exists('ka_is_key_upgrade_available')) { + // Plesk 17.0 + fwrite(STDERR, "Cannot check whether Plesk license key upgrade is available.\n"); + finish(1, false); +} + +$si = getServerInfo(); +$result = ka_is_key_upgrade_available($prod, $targetVersion, $si); + +$isConfused = false; +switch ($result['code']) { + case RESULT_LICENSE_OK: + fwrite(STDERR, "The licensing server accepted the key upgrade request.\n"); + fwrite(STDERR, "License upgrade to $targetVersion is available.\n"); + fwrite(STDERR, "Response from the licensing server: {$result['message']}\n"); + finish(0); + case RESULT_NETWORK_PROBLEM: + fwrite(STDERR, "Unable to connect to the licensing server to check if license upgrade is available.\n"); + fwrite(STDERR, "Error message: {$result['message']}\n"); + finish(2, false); + case RESULT_LICENSE_PROBLEM: + fwrite(STDERR, "Warning: Your Plesk license key cannot be upgraded.\n"); + fwrite(STDERR, "Response from the licensing server: {$result['message']}\n"); + finish(2); + default: + $isConfused = true; + // fall-through + case RESULT_ERROR: + // This includes "Software Update Service (SUS) is not found for the given license key" case, but also many others. + fwrite(STDERR, "Failed to check whether a new license key is available.\n"); + fwrite(STDERR, "Error message: {$result['message']}\n"); + if ($isConfused) { + fwrite(STDERR, "Error code: {$result['code']}\n"); + } + finish(2, !$isConfused); +} diff --git a/root/parallels/pool/PSA_18.0.73_17971/examiners/package_manager_check.sh b/root/parallels/pool/PSA_18.0.73_17971/examiners/package_manager_check.sh new file mode 100755 index 0000000000..b089061d97 --- /dev/null +++ b/root/parallels/pool/PSA_18.0.73_17971/examiners/package_manager_check.sh @@ -0,0 +1,224 @@ +#!/bin/bash +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +[ -z "$PLESK_INSTALLER_DEBUG" ] || set -x +[ -z "$PLESK_INSTALLER_STRICT_MODE" ] || set -e + +export LC_ALL=C +unset GREP_OPTIONS + +RET_SUCCESS=0 +RET_WARN=1 +RET_FATAL=2 + +is_function_defined() +{ + local fn="$1" + case "$(type $fn 2>/dev/null)" in + *function*) + return 0 + ;; + esac + return 1 +} + +# @params are tags in format "key=value" +# Report body (human readable information) is read from stdin +# and copied to stderr. +make_error_report() +{ + local report_file="${PLESK_INSTALLER_ERROR_REPORT:-}" + + local python_bin= + for bin in "/opt/psa/bin/python" "/usr/local/psa/bin/python" "/usr/bin/python2" "/opt/psa/bin/py3-python" "/usr/local/psa/bin/py3-python" "/usr/libexec/platform-python" "/usr/bin/python3"; do + if [ -x "$bin" ]; then + python_bin="$bin" + break + fi + done + + if [ -n "$report_file" -a -x "$python_bin" ]; then + "$python_bin" -c 'import sys, json +report_file = sys.argv[1] +error = sys.stdin.read() + +sys.stderr.write(error) + +data = { + "error": error, +} + +for tag in sys.argv[2:]: + k, v = tag.split("=", 1) + data[k] = v + +with open(report_file, "a") as f: + json.dump(data, f) + f.write("\n") +' "$report_file" "date=$(date --utc --iso-8601=ns)" "$@" + else + cat - >&2 + fi +} + +detect_platform() +{ + . /etc/os-release + os_name="$ID" + os_version="${VERSION_ID%%.*}" + os_arch="$(uname -m)" + if [ -e /etc/debian_version ]; then + case "$os_arch" in + x86_64) pkg_arch="amd64" ;; + aarch64) pkg_arch="arm64" ;; + esac + if [ -n "$VERSION_CODENAME" ]; then + os_codename="$VERSION_CODENAME" + else + case "$os_name$os_version" in + debian10) os_codename="buster" ;; + debian11) os_codename="bullseye" ;; + debian12) os_codename="bookworm" ;; + ubuntu18) os_codename="bionic" ;; + ubuntu20) os_codename="focal" ;; + ubuntu22) os_codename="jammy" ;; + ubuntu24) os_codename="noble" ;; + esac + fi + fi + + case "$os_name$os_version" in + rhel7|centos7|cloudlinux7|virtuozzo7) + package_manager="yum" + ;; + rhel*|centos*|cloudlinux*|almalinux*|rocky*) + package_manager="dnf" + ;; + debian*|ubuntu*) + package_manager="apt" + ;; + esac + + if [ "$os_name" = "ubuntu" -o "$os_name" = "debian" ]; then + PRODUCT_ROOT_D="/opt/psa" + else + PRODUCT_ROOT_D="/usr/local/psa" + fi +} + +has_os_impl_function() +{ + local prefix="$1" + local fn="${prefix}_${os_name}${os_version}" + is_function_defined "$fn" +} + +call_os_impl_function() +{ + local prefix="$1" + shift + local fn="${prefix}_${os_name}${os_version}" + "$fn" "$@" +} + +skip_checker_on_flag() +{ + local name="$1" + local flag="$2" + + if [ -f "$flag" ]; then + echo "$name was skipped due to flag file." >&2 + exit $RET_SUCCESS + fi +} + +skip_checker_on_env() +{ + local name="$1" + local env="$2" + + if [ -n "$env" ]; then + echo "$name was skipped due to environment variable." >&2 + exit $RET_SUCCESS + fi +} + +checker_main() +{ + local fnprefix="$1" + shift + + detect_platform + # try to execute checker only if all attributes are detected + [ -n "$os_name" -a -n "$os_version" ] || return $RET_SUCCESS + + for checker in "${fnprefix}_${os_name}${os_version}" "${fnprefix}_${os_name}" "${fnprefix}"; do + if is_function_defined "$checker"; then + local rc=$RET_SUCCESS + "$checker" "$@" || rc=$? + [ "$(( $rc & $RET_FATAL ))" = "0" ] || return $RET_FATAL + [ "$(( $rc & $RET_WARN ))" = "0" ] || return $RET_WARN + return $rc + fi + done + return $RET_SUCCESS +} + +#!/bin/sh +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +check_package_manager_deb_based() +{ + local output= + output="`dpkg --audit 2>&1`" || output="$output"$'\n'"'dpkg --audit' finished with error code $?." + + if [ -n "$output" ]; then + make_error_report 'stage=packagemanagercheck' 'level=error' 'errtype=brokenpackages' <<-EOL + The system package manager reports the following problems: + + $output + + To continue with the installation, you need to resolve these issues + using the procedure below: + + 1. Make sure you have a full server snapshot. Although the + following steps are usually safe, they can still cause + data loss or irreversible changes. + 2. Run 'dpkg --configure -a'. This command can fix some of the + issues. However, it may fail. Regardless if it fails or not, + proceed with the following steps. + 3. Run 'PLESK_INSTALLER_SKIP_PACKAGE_MANAGER_CHECK=1 plesk installer update --skip-cleanup'. + Instead of 'update', you may need to use the command you used + previously (for example, 'upgrade' or 'install'). + 4. The next step depends on the outcome of the previous one: + - If step 3 was completed with the "You already have the latest + version of product(s) and all the selected components installed. + Installation will not continue." message, + run 'plesk repair installation'. + - If step 3 failed, run 'dpkg --audit'. This command can show you + packages that need to be reinstalled. To reinstall them, run + 'apt-get install --reinstall '. + 5. Run 'plesk installer update' to revert temporary changes and + validate that the issues are resolved. If the command fails or + triggers this check again, contact Plesk support. + + For more information, see + https://support.plesk.com/hc/en-us/articles/12871173047447-Plesk-update-on-Debian-Ubuntu-fails-dpkg-was-interrupted-you-must-manually-run-dpkg-configure-a-to-correct-the-problem + EOL + return "$RET_FATAL" + fi +} + +check_package_manager_debian() +{ + check_package_manager_deb_based +} + +check_package_manager_ubuntu() +{ + check_package_manager_deb_based +} + +skip_checker_on_env "Package manager check" "$PLESK_INSTALLER_SKIP_PACKAGE_MANAGER_CHECK" +skip_checker_on_flag "Package manager check" "/tmp/plesk-installer-skip-package-manager-check.flag" +checker_main 'check_package_manager' "$@" diff --git a/root/parallels/pool/PSA_18.0.73_17971/examiners/panel_preupgrade_checker.php b/root/parallels/pool/PSA_18.0.73_17971/examiners/panel_preupgrade_checker.php new file mode 100644 index 0000000000..181534e61e --- /dev/null +++ b/root/parallels/pool/PSA_18.0.73_17971/examiners/panel_preupgrade_checker.php @@ -0,0 +1,2401 @@ +_checkMainIP(); //:INFO: Checking for main IP address https://support.plesk.com/hc/articles/12377857361687 + $this->_checkMySQLDatabaseUserRoot(); //:INFO: Plesk user "root" for MySQL database servers have not access to phpMyAdmin https://support.plesk.com/hc/articles/12378148229399 + $this->_checkProftpdIPv6(); //:INFO: #94489 FTP service proftpd cannot be started by xinetd if IPv6 is disabled https://support.plesk.com/hc/articles/12377796102807 + $this->_checkSwCollectdIntervalSetting(); //:INFO: #105405 https://support.plesk.com/hc/articles/213362569 + $this->_checkApacheStatus(); + $this->_checkImmutableBitOnPleskFiles(); //:INFO: #128414 https://support.plesk.com/hc/articles/12377589682327 + $this->_checkAbilityToChmodInDumpd(); //:INFO: ERROR while trying to backup MySQL database. https://support.plesk.com/hc/en-us/articles/12377010033943 + $this->_checkIpcollectionReference(); //:INFO: #72751 https://support.plesk.com/hc/articles/12377666752279 + $this->_checkApsApplicationContext(); //:INFO: Broken contexts of the APS applications can lead to errors at building Apache web server configuration https://support.plesk.com/hc/articles/12377671820311 + $this->_checkApsTablesInnoDB(); + $this->_checkCustomWebServerConfigTemplates(); //:INFO: #PPPM-1195 https://support.plesk.com/hc/articles/12377740416151 + $this->_checkMixedCaseDomainIssues(); //:INFO: #PPPM-4284 https://support.plesk.com/hc/en-us/articles/12377171904151 + + if (PleskOS::isDebLike()) { + $this->_checkSymLinkToOptPsa(); //:INFO: Check that symbolic link /usr/local/psa actually exists on Debian-like OSes https://support.plesk.com/hc/articles/12377511731991 + } + + if (Util::isVz()) { + $this->_checkShmPagesLimit(); //:INFO: PSA service does not start. Unable to allocate shared memory segment. https://support.plesk.com/hc/articles/12377744827927 + + if (PleskOS::isRedHatLike()) { + $this->_checkMailDriversConflict(); //:INFO: #PPPM-955 https://support.plesk.com/hc/articles/12378148767895 + } + } + } + + $this->_checkForCryptPasswords(); + $this->_checkMysqlServersTable(); //:INFO: Checking existing table mysql.servers + $this->_checkPleskTCPPorts(); //:INFO: Check the availability of Plesk TCP ports https://support.plesk.com/hc/articles/12377821243159 + + if (Util::isWindows()) { + $this->_checkPhprcSystemVariable(); //:INFO: #PPPM-294 Checking for PHPRC system variable + $this->_unknownISAPIfilters(); //:INFO: Checking for unknown ISAPI filters and show warning https://support.plesk.com/hc/articles/213913765 + $this->_checkMSVCR(); //:INFO: Just warning about possible issues related to Microsoft Visual C++ Redistributable Packages https://support.plesk.com/hc/articles/115000201014 + $this->_checkIisFcgiDllVersion(); //:INFO: Check iisfcgi.dll file version https://support.plesk.com/hc/articles/12378148258199 + $this->_checkCDONTSmailrootFolder(); //:INFO: After upgrade Plesk change permissions on folder of Collaboration Data Objects (CDO) for NTS (CDONTS) to default, https://support.plesk.com/hc/articles/12377887661335 + $this->_checkNullClientLogin(); //:INFO: #118963 https://support.plesk.com/hc/articles/12378122202391 + $this->checkDomainControllerLocation(); //:INFO: https://support.plesk.com/hc/articles/12377107094167 + } + } + + //:INFO: PSA service does not start. Unable to allocate shared memory segment. https://support.plesk.com/hc/articles/12377744827927 + function _checkShmPagesLimit() + { + $log = Log::getInstance("Checking for limit shmpages", true); + $ubc = Util::getUserBeanCounters(); + if ((int)$ubc['shmpages']['limit'] < 40960) { + $log->emergency("Virtuozzo Container set the \"shmpages\" limit to {$ubc['shmpages']['limit']}, which is too low. This may cause the sw-engine service not to start. To resolve this issue, refer to the article at https://support.plesk.com/hc/articles/12377744827927"); + $log->resultWarning(); + return; + } + + $log->resultOk(); + } + + //:INFO: #PPPM-294 + function _checkPhprcSystemVariable() + { + $log = Log::getInstance("Checking for PHPRC system variable", true); + + $phprc = getenv('PHPRC'); + + if ($phprc) { + $log->emergency('The environment variable PHPRC is present in the system. This variable may lead to upgrade failure. Please delete this variable from the system environment.'); + $log->resultWarning(); + return; + } + + $log->resultOk(); + } + + //:INFO: ERROR while trying to backup MySQL database. https://support.plesk.com/hc/en-us/articles/12377010033943 + function _checkAbilityToChmodInDumpd() + { + $log = Log::getInstance("Checking the possibility to change the permissions of files in the DUMP_D directory", true); + + $dump_d = Util::getSettingFromPsaConf('DUMP_D'); + if (is_null($dump_d)) { + $log->warning('Unable to obtain the path to the directory defined by the DUMP_D parameter. Check that the DUMP_D parameter is set in the /etc/psa/psa.conf file.'); + $log->resultWarning(); + return; + } + + $file = $dump_d . '/pre_upgrade_test_checkAbilityToChmodInDumpd'; + + if (false === file_put_contents($file, 'test')) { + $log->emergency('Unable to write in the ' . $dump_d . ' directory. The upgrade procedure will fail. Check that the folder exists and you have write permissions for it, and repeat upgrading. '); + $log->resultWarning(); + return; + } else { + $result = @chmod($file, 0600); + unlink($file); + if (!$result) { + $log->emergency( + 'Unable to change the permissions of files in the ' . $dump_d . ' directory. ' + . 'The upgrade procedure will fail. Please refer to https://support.plesk.com/hc/articles/12377010033943 for details.' + ); + $log->resultError(); + return; + } + } + $log->resultOk(); + } + + //:INFO: #128414 https://support.plesk.com/hc/articles/12377589682327 + function _checkImmutableBitOnPleskFiles() + { + $log = Log::getInstance("Checking Panel files for the immutable bit attribute"); + + $cmd = 'lsattr -R /usr/local/psa/ 2>/dev/null |awk \'{split($1, a, ""); if (a[5] == "i") {print;}}\''; + $output = Util::exec($cmd, $code); + $files = explode('\n', $output); + + if (!empty($output)) { + $log->info('The immutable bit attribute of the following Panel files can interrupt the upgrade procedure:'); + foreach ($files as $file) { + $log->info($file); + } + $log->emergency('Files with the immutable bit attribute were found. Please check https://support.plesk.com/hc/articles/12377589682327 for details.'); + $log->resultWarning(); + return; + } + + $log->resultOk(); + } + + //:INFO: #PPPM-1195 https://support.plesk.com/hc/articles/12377740416151 + function _checkCustomWebServerConfigTemplates() + { + $log = Log::getInstance("Checking for custom web server configuration templates"); + $pleskDir = Util::getSettingFromPsaConf('PRODUCT_ROOT_D'); + $customTemplatesPath = $pleskDir . '/admin/conf/templates/custom'; + + if (is_dir($customTemplatesPath)) { + $log->warning("Directory {$customTemplatesPath} for custom web server configuration templates was found. Custom templates might be incompatible with a new Plesk version, and this might lead to failure to generate web server configuration files. Remove the directory to get rid of this warning. " + . "Please check https://support.plesk.com/hc/articles/12377740416151 for details."); + $log->resultWarning(); + return; + } + $log->resultOk(); + } + + //:INFO: #PPPM-955 https://support.plesk.com/hc/articles/12378148767895 + function _checkMailDriversConflict() + { + $log = Log::getInstance("Checking for a Plesk mail drivers conflict"); + + if (((true === PackageManager::isInstalled('psa-mail-pc-driver') || true === PackageManager::isInstalled('plesk-mail-pc-driver')) + && true === PackageManager::isInstalled('psa-qmail')) + || ((true === PackageManager::isInstalled('psa-mail-pc-driver') || true === PackageManager::isInstalled('plesk-mail-pc-driver')) + && true === PackageManager::isInstalled('psa-qmail-rblsmtpd'))) { + $log->warning("Plesk upgrade by EZ templates failed if psa-mail-pc-driver and psa-qmail or psa-qmail-rblsmtpd packages are installed. " + . "Please check https://support.plesk.com/hc/articles/12378148767895 for details."); + $log->resultWarning(); + return; + } + + $log->resultOk(); + } + + //:INFO: #118963 https://support.plesk.com/hc/articles/12378122202391 + function _checkNullClientLogin() + { + $log = Log::getInstance("Checking for accounts with empty user names"); + + $mysql = PleskDb::getInstance(); + $sql = "SELECT domains.id, domains.name, clients.login FROM domains LEFT JOIN clients ON clients.id=domains.cl_id WHERE clients.login is NULL"; + $nullLogins = $mysql->fetchAll($sql); + + if (!empty($nullLogins)) { + $log->warning('There are accounts with empty user names. This problem can cause the backup or migration operation to fail. Please see https://support.plesk.com/hc/articles/12378122202391 for the solution.'); + $log->resultWarning(); + return; + } + + $log->resultOk(); + } + + //:INFO: #105405 https://support.plesk.com/hc/articles/213362569 + function _checkSwCollectdIntervalSetting() + { + $log = Log::getInstance("Checking the 'Interval' parameter in the sw-collectd configuration file"); + + $collectd_config = '/etc/sw-collectd/collectd.conf'; + if (file_exists($collectd_config)) { + if (!is_file($collectd_config) || !is_readable($collectd_config)) + return; + + $config_content = Util::readfileToArray($collectd_config); + if ($config_content) { + foreach ($config_content as $line) { + if (preg_match('/Interval\s*\d+$/', $line, $match)) { + if (preg_match('/Interval\s*10$/', $line, $match)) { + $log->warning('If you leave the default value of the "Interval" parameter in the ' . $collectd_config . ', sw-collectd may heavily load the system. Please see https://support.plesk.com/hc/articles/213362569 for details.'); + $log->resultWarning(); + return; + } + $log->resultOk(); + return; + } + } + $log->warning('If you leave the default value of the "Interval" parameter in the ' . $collectd_config . ', sw-collectd may heavily load the system. Please see https://support.plesk.com/hc/articles/213362569 for details.'); + $log->resultWarning(); + return; + } + } + } + + private function _checkApacheStatus() + { + $log = Log::getInstance("Checking Apache status"); + + $apacheCtl = file_exists('/usr/sbin/apache2ctl') ? '/usr/sbin/apache2ctl' : '/usr/sbin/apachectl'; + + if (!is_executable($apacheCtl)) { + return; + } + + $resultCode = 0; + Util::Exec("$apacheCtl -t 2>/dev/null", $resultCode); + + if (0 !== $resultCode) { + $log->error("The Apache configuration is broken. Run '$apacheCtl -t' to see the detailed info."); + $log->resultError(); + return; + } + + $log->resultOk(); + } + + //:INFO: #72751 https://support.plesk.com/hc/articles/12377666752279 + function _checkIpcollectionReference() + { + $log = Log::getInstance("Checking consistency of the IP addresses list in the Panel database"); + + $mysql = PleskDb::getInstance(); + $sql = "SELECT 1 FROM ip_pool, clients, IpAddressesCollections, domains, DomainServices, IP_Addresses WHERE DomainServices.ipCollectionId = IpAddressesCollections.ipCollectionId AND domains.id=DomainServices.dom_id AND clients.id=domains.cl_id AND ipAddressId NOT IN (select id from IP_Addresses) AND IP_Addresses.id = ip_pool.ip_address_id AND pool_id = ip_pool.id GROUP BY pool_id"; + $brokenIps = $mysql->fetchAll($sql); + $sql = "select 1 from DomainServices, domains, clients, ip_pool where ipCollectionId not in (select IpAddressesCollections.ipCollectionId from IpAddressesCollections) and domains.id=DomainServices.dom_id and clients.id = domains.cl_id and ip_pool.id = clients.pool_id and DomainServices.type='web' group by ipCollectionId"; + $brokenCollections = $mysql->fetchAll($sql); + + if (!empty($brokenIps) || !empty($brokenCollections)) { + $log->warning('Some database entries related to Panel IP addresses are corrupted. Please see https://support.plesk.com/hc/articles/12377666752279 for the solution.'); + $log->resultWarning(); + return; + } + + $log->resultOk(); + } + + //:INFO: Broken contexts of the APS applications can lead to errors at building Apache web server configuration https://support.plesk.com/hc/articles/12377671820311 + function _checkApsApplicationContext() + { + $log = Log::getInstance("Checking installed APS applications"); + $mysql = PleskDb::getInstance(); + $sql = "SELECT * FROM apsContexts WHERE (pleskType = 'hosting' OR pleskType = 'subdomain') AND subscriptionId = 0"; + $brokenContexts = $mysql->fetchAll($sql); + + if (!empty($brokenContexts)) { + $log->warning('Some database entries related to the installed APS applications are corrupted. Please see https://support.plesk.com/hc/articles/12377671820311 for the solution.'); + $log->resultWarning(); + return; + } + $log->resultOk(); + } + + //:INFO: #94489 FTP service proftpd cannot be started by xinetd if IPv6 is disabled https://support.plesk.com/hc/articles/12377796102807 + function _checkProftpdIPv6() + { + $log = Log::getInstance("Checking proftpd settings"); + + $inet6 = '/proc/net/if_inet6'; + if (!file_exists($inet6) || !@file_get_contents($inet6)) { + $proftpd_config = '/etc/xinetd.d/ftp_psa'; + if (!is_file($proftpd_config) || !is_readable($proftpd_config)) + return null; + + $config_content = Util::readfileToArray($proftpd_config); + if ($config_content) { + for ($i=0; $i<=count($config_content)-1; $i++) { + if (preg_match('/flags.+IPv6$/', $config_content[$i], $match)) { + $log->warning('The proftpd FTP service will fail to start in case the support for IPv6 is disabled on the server. Please check https://support.plesk.com/hc/articles/12377796102807 for details.'); + $log->resultWarning(); + return; + } + } + } + } + $log->resultOk(); + } + + //:INFO: Check the availability of Plesk Panel TCP ports + function _checkPleskTCPPorts() + { + $log = Log::getInstance('Checking the availability of Plesk Panel TCP ports'); + + $plesk_ports = array('8880' => 'Plesk Panel non-secure HTTP port', '8443' => 'Plesk Panel secure HTTPS port'); + + $mysql = PleskDb::getInstance(); + $sql = "select ip_address from IP_Addresses"; + $ip_addresses = $mysql->fetchAll($sql); + $warning = false; + if (count($ip_addresses)>0) { + if (Util::isLinux()) { + $ipv4 = Util::getIPv4ListOnLinux(); + $ipv6 = Util::getIPv6ListOnLinux(); + if ($ipv6) { + $ipsInSystem = array_merge($ipv4, $ipv6); + } else { + $ipsInSystem = $ipv4; + } + } else { + $ipsInSystem = Util::getIPListOnWindows(); + } + foreach ($ip_addresses as $ip) { + foreach ($plesk_ports as $port => $description) { + if (PleskValidator::validateIPv4($ip['ip_address']) && in_array($ip['ip_address'], $ipsInSystem)) { + $fp = @fsockopen($ip['ip_address'], $port, $errno, $errstr, 1); + } elseif (PleskValidator::validateIPv6($ip['ip_address']) && in_array($ip['ip_address'], $ipsInSystem)) { + $fp = @fsockopen('[' . $ip['ip_address'] . ']', $port, $errno, $errstr, 1); + } else { + $log->warning('IP address registered in Plesk is invalid or broken: ' . $ip['ip_address']); + $log->resultWarning(); + return; + } + if (!$fp) { + // $errno 110 means "timed out", 111 means "refused" + $log->info('Unable to connect to IP address ' . $ip['ip_address'] . ' on ' . $description . ' ' . $port . ': ' . $errstr); + $warning = true; + } + } + } + } + if ($warning) { + $log->warning('Unable to connect to some Plesk ports. Please see ' . LOG_PATH . ' for details. Find the full list of the required open ports at https://support.plesk.com/hc/articles/12377821243159 '); + $log->resultWarning(); + return; + } + $log->resultOk(); + } + + //:INFO: Plesk user "root" for MySQL database servers have not access to phpMyAdmin https://support.plesk.com/hc/articles/12378148229399 + function _checkMySQLDatabaseUserRoot() + { + $log = Log::getInstance('Checking existence of Plesk user "root" for MariaDB/MySQL database servers'); + + $psaroot = Util::getSettingFromPsaConf('PRODUCT_ROOT_D'); + + if (PleskVersion::is_below_17_9()) { + $phpMyAdminConfFile = $psaroot . '/admin/htdocs/domains/databases/phpMyAdmin/libraries/config.default.php'; + } else { + $phpMyAdminConfFile = $psaroot . '/phpMyAdmin/libraries/config.default.php'; + } + + if (file_exists($phpMyAdminConfFile)) { + $phpMyAdminConfFileContent = file_get_contents($phpMyAdminConfFile); + if (!preg_match("/\[\'AllowRoot\'\]\s*=\s*true\s*\;/", $phpMyAdminConfFileContent)) { + $mysql = PleskDb::getInstance(); + $sql = "select login, data_bases.name as db_name, displayName as domain_name from db_users, data_bases, domains where db_users.db_id = data_bases.id and data_bases.dom_id = domains.id and data_bases.type = 'mysql' and login = 'root'"; + $dbusers = $mysql->fetchAll($sql); + + foreach ($dbusers as $user) { + $log->warning('The database user "' . $user['login'] . '" (database "' . $user['db_name'] . '" at "' . $user['domain_name'] . '") has no access to phpMyAdmin. Please check https://support.plesk.com/hc/articles/12378148229399 for more details.'); + $log->resultWarning(); + return; + } + } + } + + $log->resultOk(); + } + + //:INFO: After upgrade Plesk change permissions on folder of Collaboration Data Objects (CDO) for NTS (CDONTS) to default, https://support.plesk.com/hc/articles/12377887661335 + function _checkCDONTSmailrootFolder() + { + $log = Log::getInstance('Checking for CDONTS mailroot folder'); + + $mailroot = Util::getSystemDisk() . 'inetpub\mailroot\pickup'; + + if (is_dir($mailroot)) { + $log->warning('After upgrade you have to add write permissions to psacln group on folder ' . $mailroot . '. Please, check https://support.plesk.com/hc/articles/12377887661335 for more details.'); + $log->resultWarning(); + return; + } + $log->resultOk(); + } + + //:INFO: Check iisfcgi.dll file version https://support.plesk.com/hc/articles/12378148258199 + function _checkIisFcgiDllVersion() + { + $log = Log::getInstance("Checking the iisfcgi.dll file version"); + + $windir = Util::getSystemRoot(); + $iisfcgi = $windir . '\system32\inetsrv\iisfcgi.dll'; + if (file_exists($iisfcgi)) { + $version = Util::getFileVersion($iisfcgi); + if (version_compare($version, '7.5.0', '>') + && version_compare($version, '7.5.7600.16632', '<')) { + $log->warning('File iisfcgi.dll version ' . $version . ' is outdated. Please, check article https://support.plesk.com/hc/articles/12378148258199 for details'); + return; + } + } + $log->resultOk(); + } + + //:INFO: Checking for main IP address https://support.plesk.com/hc/articles/12377857361687 + function _checkMainIP() + { + $log = Log::getInstance("Checking for main IP address"); + + $mysql = PleskDb::getInstance(); + $sql = 'select * from IP_Addresses'; + $ips = $mysql->fetchAll($sql); + $mainexists = false; + foreach ($ips as $ip) { + if (isset($ip['main'])) { + if ($ip['main'] == 'true') { + $mainexists = true; + } + } else { + $log->info('No field "main" in table IP_Addresses.'); + $log->resultOk(); + return; + } + } + + if (!$mainexists) { + $warn = 'Unable to find "main" IP address in psa database. Please, check https://support.plesk.com/hc/articles/12377857361687 for more details.'; + $log->warning($warn); + $log->resultWarning(); + return; + } + $log->resultOk(); + } + + //:INFO: Checking existing table mysql.servers https://support.plesk.com/hc/articles/12377850098455 + function _checkMysqlServersTable() + { + $log = Log::getInstance('Checking table "servers" in database "mysql"'); + + $mySQLServerVersion = Util::getMySQLServerVersion(); + if (version_compare($mySQLServerVersion, '5.1.0', '>=')) { + $credentials = Util::getDefaultClientMySQLServerCredentials(); + + if (preg_match('/AES-128-CBC/', $credentials['admin_password'])) { + $log->info('The administrator\'s password for the default MariaDB/MySQL server is encrypted.'); + return; + } + + $mysql = new DbClientMysql($credentials['host'], $credentials['admin_login'], $credentials['admin_password'] , 'information_schema', $credentials['port']); + if (!$mysql->hasErrors()) { + $sql = 'SELECT * FROM information_schema.TABLES WHERE TABLE_SCHEMA="mysql" and TABLE_NAME="servers"'; + $servers = $mysql->fetchAll($sql); + if (empty($servers)) { + $warn = 'The table "servers" in the database "mysql" does not exist. Please check https://support.plesk.com/hc/articles/12377850098455 for details.'; + $log->warning($warn); + $log->resultWarning(); + return; + } + } + } + $log->resultOk(); + } + + //:INFO: Check that there is symbolic link /usr/local/psa on /opt/psa on Debian-like Oses https://support.plesk.com/hc/articles/12377511731991 + function _checkSymLinkToOptPsa() + { + $log = Log::getInstance('Checking symbolic link /usr/local/psa on /opt/psa'); + + $link = @realpath('/usr/local/psa/version'); + if (!preg_match('/\/opt\/psa\/version/', $link, $macthes)) { + $warn = "The symbolic link /usr/local/psa does not exist or has wrong destination. Read article https://support.plesk.com/hc/articles/12377511731991 to fix the issue."; + $log->warning($warn); + $log->resultWarning(); + return; + } + $log->resultOk(); + } + + //:INFO: Checking for unknown ISAPI filters and show warning https://support.plesk.com/hc/articles/213913765 + function _unknownISAPIfilters() + { + $log = Log::getInstance('Detecting installed ISAPI filters'); + + if (Util::isUnknownISAPIfilters()) { + $warn = 'Please read carefully article https://support.plesk.com/hc/articles/213913765, for avoiding possible problems caused by unknown ISAPI filters.'; + $log->warning($warn); + $log->resultWarning(); + + return; + } + $log->resultOk(); + } + + //:INFO: Warning about possible issues related to Microsoft Visual C++ Redistributable Packages ?https://support.plesk.com/hc/articles/115000201014 + function _checkMSVCR() + { + $log = Log::getInstance('Microsoft Visual C++ Redistributable Packages'); + + $warn = 'Please read carefully article https://support.plesk.com/hc/articles/115000201014, for avoiding possible problems caused by Microsoft Visual C++ Redistributable Packages.'; + $log->info($warn); + + return; + } + + function _checkForCryptPasswords() + { + //:INFO: Prevent potential problem with E: Couldn't configure pre-depend plesk-core for psa-firewall, probably a dependency cycle. + $log = Log::getInstance('Detecting if encrypted passwords are used'); + + $db = PleskDb::getInstance(); + $sql = "SELECT COUNT(*) AS cnt FROM accounts WHERE type='crypt' AND password not like '$%';"; + $r = $db->fetchAll($sql); + + if ($r[0]['cnt'] != '0') + { + $warn = 'There are ' . $r[0]['cnt'] . ' accounts with passwords encrypted using a deprecated algorithm. Please refer to https://support.plesk.com/hc/articles/12377596588311 for the instructions about how to change the password type to plain.'; + + $log->warning($warn); + $log->resultWarning(); + return; + } + $log->resultOk(); + } + + function _checkApsTablesInnoDB() + { + $log = Log::getInstance('Checking if apsc database tables have InnoDB engine'); + + $db = PleskDb::getInstance(); + $apsDatabase = $db->fetchOne("select val from misc where param = 'aps_database'"); + $sql = "SELECT TABLE_NAME FROM information_schema.TABLES where TABLE_SCHEMA = '$apsDatabase' and ENGINE = 'MyISAM'"; + $myISAMTables = $db->fetchAll($sql); + if (!empty($myISAMTables)) { + $myISAMTablesList = implode(', ', array_map('reset', $myISAMTables)); + $warn = 'The are tables in apsc database with MyISAM engine: ' . $myISAMTablesList . '. It would be updated to InnoDB engine.'; + $log->warning($warn); + $log->resultWarning(); + return; + } + $log->resultOk(); + } + + function _checkMixedCaseDomainIssues() + { + $log = Log::getInstance("Checking for domains with mixed case names", true); + $db = PleskDb::getInstance(); + + + $domains = $db->fetchAll("select id, name, displayName from domains"); + $problemDomains = array(); + foreach ($domains as $domain) { + if (strtolower($domain['name']) == $domain['name']) { + continue; + } + $problemDomains[] = $domain; + } + if (count($problemDomains)) { + $msg = "Found one or more domains with mixed case names. Such domains may have trouble working with the \"FPM application server by Apache\" handler.\n" . + implode("\n", array_map(function($row) { + return "{$row['id']}\t{$row['displayName']}\t{$row['name']}"; + }, $problemDomains)) . "\n" . + "A manual fix can be applied to resolve the issue. Read https://support.plesk.com/hc/en-us/articles/12377171904151 for details."; + $log->warning($msg); + $log->resultWarning(); + return; + } + $log->resultOk(); + } + + private function checkDomainControllerLocation() + { + $log = Log::getInstance("Checking for Active Directory Domain Controller and Plesk on the same server", true); + $cmd = '"' . rtrim(Util::getPleskRootPath(), '\\') . '\admin\bin\serverconf.exe" --list'; + $output = Util::exec($cmd, $code); + if (preg_match("/IS_DOMAIN_CONTROLLER:\s*true/", $output)) { + $log->warning('Active Directory Domain Controller and Plesk are on the same server. Read https://support.plesk.com/hc/articles/12377107094167 for details.'); + $log->resultWarning(); + } else { + $log->resultOk(); + } + } +} + +class Plesk175Requirements +{ + public function validate() + { + if (PleskInstallation::isInstalled() && PleskVersion::is_below_17_5() && Util::isLinux()) { + //:INFO: Check that DUMP_TMP_D is not inside of (or equal to) DUMP_D + $this->_checkDumpTmpD(); + } + } + + public function _checkDumpTmpD() + { + $log = Log::getInstance('Checking the DUMP_TMP_D directory'); + + $dumpD = Util::getSettingFromPsaConf('DUMP_D'); + if (is_null($dumpD)) { + $log->warning('Unable to obtain the path to the directory defined by the DUMP_D parameter. Check that the DUMP_D parameter is set in the /etc/psa/psa.conf file.'); + $log->resultWarning(); + return; + } + $dumpTmpD = Util::getSettingFromPsaConf('DUMP_TMP_D'); + if (is_null($dumpTmpD)) { + $log->warning('Unable to obtain the path to the directory defined by the DUMP_TMP_D parameter. Check that the DUMP_TMP_D parameter is set in the /etc/psa/psa.conf file.'); + $log->resultWarning(); + return; + } + + if (strpos(rtrim($dumpTmpD, '/') . '/', rtrim($dumpD, '/') . '/') === 0) { + $log->error(sprintf('The directory DUMP_TMP_D = %s should not be inside of (or equal to) the directory DUMP_D = %s. Fix these parameters in the /etc/psa/psa.conf file.', $dumpTmpD, $dumpD)); + $log->resultError(); + } + + $log->resultOk(); + } +} + +class Plesk178Requirements +{ + public function validate() + { + if (PleskInstallation::isInstalled() && Util::isWindows() && PleskVersion::is_below_17_9()) { + $this->_checkPleskVhostsDir(); + } + + if (PleskVersion::is_below_17_8()) { + $this->checkTomcat(); + $this->checkMultiServer(); + } + } + + private function checkTomcat() + { + if (Util::isWindows()) { + $tomcatRegBase = '\\PLESK\\PSA Config\\Config\\Packages\\tomcat\\tomcat'; + /* Supported versions on windows are tomcat5 and tomcat7 */ + $key = '/v InstallDir'; + $isInstalled = Util::regQuery($tomcatRegBase . '7', $key, true) || Util::regQuery($tomcatRegBase . '5', $key, true); + } else { + $isInstalled = PackageManager::isInstalled('psa-tomcat-configurator'); + } + + if ($isInstalled + || (PleskDb::getInstance()->fetchOne('show tables like \'WebApps\'') + && PleskDb::getInstance()->fetchOne('select count(*) from WebApps')) + ) { + $log = Log::getInstance('Checking Apache Tomcat installation'); + $message = <<warning($message); + } + } + + private function checkMultiServer() + { + if (!PleskModule::isMultiServer()) { + return; + } + + $log = Log::getInstance('Checking Plesk Multi Server installation'); + $message = <<emergency($message); + $log->resultError(); + } + + private function _checkPleskVhostsDir() + { + $log = Log::getInstance('Checking mount volume for HTTPD_VHOSTS_D directory'); + + $vhostsDir = rtrim(Util::regPleskQuery('HTTPD_VHOSTS_D'), "\\"); + Util::exec("mountvol \"{$vhostsDir}\" /L", $code); + if ($code == 0) { + $msg = "A disk volume is mounted to the {$vhostsDir} directory." . + " It will be unmounted during the Plesk upgrade." . + " As a result, all hosted websites will become unavailable." . + " Make sure to remount the volume to the {$vhostsDir} directory after the upgrade."; + $log->emergency($msg); + $log->resultError(); + return; + } + + $log->resultOk(); + } +} + +class Plesk18Requirements +{ + public function validate() + { + if (PleskInstallation::isInstalled()) { + if (Util::isLinux() && PleskOS::isRedHatLike()) { + $this->_checkYumDuplicates(); + } + $this->checkPdUsersLoginCollation(); + $this->checkDomainsGuidCollation(); + $this->checkClientsGuidCollation(); + $this->checkModSecurityModules(); + $this->checkIsFirewallPackageConfigured(); + $this->checkDefaultDnsServerComponent(); + } + } + + private function checkModSecurityModules() + { + /* Issue actual for Plesk for Windows below 18.0.32 (ModSecurity 2.9.3 and below) */ + if (!Util::isWindows() || PleskVersion::is_18_0_32_or_above()) { + return; + } + + $log = Log::getInstance('Checking the status of ModSecurity IIS modules'); + $modules = Util::exec(Util::getSystemRoot() . '\system32\inetsrv\AppCmd.exe list module', $code); + if ($code) { + $log->warning('Unable to get the list of IIS modules.'); + } else { + if (strpos($modules, 'ModSecurity IIS (32bits)') === false && strpos($modules, 'ModSecurity IIS (64bits)') === false) { + $log->error('Either 32-bit or 64-bit ModSecurity IIS module is absent.'); + $log->resultError(); + } + } + } + + // INFO: PPP-46440 checking package duplicates https://support.plesk.com/hc/articles/12377586286615 + private function _checkYumDuplicates() + { + $log = Log::getInstance('Checking for RPM packages duplicates'); + if (!file_exists("/usr/bin/package-cleanup")) + { + $log->info("package-cleanup is not found. Check for duplicates was skipped"); + return; + } + + $output = Util::exec("/usr/bin/package-cleanup --cacheonly -q --dupes", $code); + if ($code != 0) + { + // some repos may have no cache at this point or may be broken at all + // retry with cache recreation and skipping broken repos + $output = Util::exec("/usr/bin/package-cleanup -q --dupes --setopt='*.skip_if_unavailable=1'", $code); + } + if ($code != 0) + { + $message = "Unable to detect package duplicates: /usr/bin/package-cleanup --dupes returns $code." . + "Output is:\n$output"; + $log->warning($message); + $log->resultWarning(); + return; + } + + if (empty($output)) { + return; + } + + $message = "Your package system contains duplicated packages, which can lead to broken Plesk update:\n\n" . + "$output\n\n" . + "Please check https://support.plesk.com/hc/articles/12377586286615 for more details."; + + $log->error($message); + $log->resultError(); + } + + private function checkPdUsersLoginCollation() + { + $log = Log::getInstance('Checking for Protected Directory Users with duplicates in login field.'); + $duplicates = PleskDb::getInstance()->fetchAll( + 'SELECT pd_id, LOWER(login) AS login_ci, COUNT(*) AS duplicates FROM pd_users' . + ' GROUP BY pd_id, login_ci' . + ' HAVING duplicates > 1' + ); + if (!empty($duplicates)) { + $log->error( + "Duplicate logins of Protected Directory Users were found in the database:\n\n" . + implode("\n", array_column($duplicates, 'login_ci')) . "\n\n" . + "Please check https://support.plesk.com/hc/en-us/articles/360014743900 for more details." + ); + $log->resultError(); + } + } + + private function checkDomainsGuidCollation() + { + $log = Log::getInstance('Checking "domains" table with duplicates in guid field.'); + $duplicates = PleskDb::getInstance()->fetchAll( + 'SELECT guid, COUNT(*) AS duplicates FROM domains' + . ' GROUP BY guid' + . ' HAVING duplicates > 1' + ); + + if (!empty($duplicates)) { + $log->error( + "Duplicate guid were found in the 'domains' table:\n\n" . + implode("\n", array_column($duplicates, 'guid')) . "\n\n" + . "Please check https://support.plesk.com/hc/en-us/articles/12377018323351 for more details." + ); + $log->resultError(); + } + } + + private function checkClientsGuidCollation() + { + $log = Log::getInstance('Checking "clients" table with duplicates in guid field.'); + $duplicates = PleskDb::getInstance()->fetchAll( + 'SELECT guid, COUNT(*) AS duplicates FROM clients' + . ' GROUP BY guid' + . ' HAVING duplicates > 1' + ); + + if (!empty($duplicates)) { + $log->error( + "Duplicate guid were found in the 'clients' table:\n\n" . + implode("\n", array_column($duplicates, 'guid')) . "\n\n" + . "Please check https://support.plesk.com/hc/en-us/articles/360016801679 for more details." + ); + $log->resultError(); + } + } + + private function checkIsFirewallPackageConfigured() + { + if (!Util::isLinux()) { + return; + } + + $log = Log::getInstance("Checking if any rules are configured in the Firewall extension"); + $db = PleskDb::getInstance(); + if ($db->fetchOne("SHOW TABLES LIKE 'module_firewall_rules'") + && $db->fetchOne("SELECT COUNT(*) FROM module_firewall_rules") + ) { + $message = "Plesk Firewall no longer stores its configuration in Plesk database since " + . "Plesk Obsidian 18.0.52. Since you're upgrading to the latest version late, " + . "if you wish to retain the Plesk Firewall extension and its configuration, " + . "you need to follow additional steps after the upgrade. Please check " + . "https://support.plesk.com/hc/en-us/articles/16198248236311 for more details."; + $log->warning($message); + } + } + + private function checkDefaultDnsServerComponent() + { + if (Util::isLinux()) { + return; + } + + $path = '\\PLESK\\PSA Config\\Config\\Packages\\dnsserver'; + $key = '/ve'; + + if ('bind' === Util::regQuery($path, $key, true)) { + $log = Log::getInstance("Checking default DNS server component"); + $message = <<emergency($message); + $log->resultError(); + } + } +} + +class PleskModule +{ + public static function isInstalledWatchdog() + { + return PleskModule::_isInstalled('watchdog'); + } + + public static function isInstalledFileServer() + { + return PleskModule::_isInstalled('fileserver'); + } + + public static function isInstalledFirewall() + { + return PleskModule::_isInstalled('firewall'); + } + + public static function isInstalledVpn() + { + return PleskModule::_isInstalled('vpn'); + } + + public static function isMultiServer() + { + return PleskModule::_isInstalled('plesk-multi-server') || + PleskModule::_isInstalled('plesk-multi-server-node'); + } + + protected static function _isInstalled($module) + { + $sql = "SELECT * FROM Modules WHERE name = '{$module}'"; + + $pleskDb = PleskDb::getInstance(); + $row = $pleskDb->fetchRow($sql); + + return (empty($row) ? false : true); + } +} + +class PleskInstallation +{ + public static function validate() + { + if (!self::isInstalled()) { + $log = Log::getInstance('Checking for Plesk installation'); + $log->step('Plesk installation is not found. You will have no problems with upgrade, go on and install ' + . PleskVersion::getLatestPleskVersionAsString() . ' (https://www.plesk.com/)'); + return; + } + self::detectVersion(); + } + + public static function isInstalled() + { + $rootPath = Util::getPleskRootPath(); + if (empty($rootPath) || !file_exists($rootPath)) { + return false; + } + return true; + } + + private static function detectVersion() + { + $log = Log::getInstance('Installed Plesk version/build: ' . PleskVersion::getVersionAndBuild(), false); + + $currentVersion = PleskVersion::getVersion(); + if (version_compare($currentVersion, PLESK_VERSION, 'eq')) { + $err = 'You have already installed the latest version ' . PleskVersion::getLatestPleskVersionAsString() . '. '; + $err .= 'Tool must be launched prior to upgrade to ' . PleskVersion::getLatestPleskVersionAsString() . ' for the purpose of getting a report on potential problems with the upgrade.'; + $log->info($err); + exit(0); + } + + if (!PleskVersion::isUpgradeSupportedVersion()) { + $err = 'Unable to find Plesk 17.x. '; + $err .= 'Tool must be launched prior to upgrade to ' . PleskVersion::getLatestPleskVersionAsString() . ' for the purpose of getting a report on potential problems with the upgrade.'; + fatal($err); + } + } +} + +class PleskVersion +{ + const PLESK_17_MIN_VERSION = '13.0.0'; /* historically it has been started as 13.0 */ + + const PLESK_17_MAX_VERSION = '17.9.13'; + + const PLESK_18_MIN_VERSION = '18.0.14'; + + public static function is17x_or_above() + { + return version_compare(self::getVersion(), self::PLESK_17_MIN_VERSION, '>='); + } + + public static function is_below_17_5() + { + return version_compare(self::getVersion(), '17.5.0', '<'); + } + + public static function is_below_17_8() + { + return version_compare(self::getVersion(), '17.8.0', '<'); + } + + public static function is_below_17_9() + { + return version_compare(self::getVersion(), '17.9.0', '<'); + } + + public static function is_18_0_32_or_above() + { + return version_compare(self::getVersion(), '18.0.32', '>='); + } + + public static function getVersion() + { + $version = self::getVersionAndBuild(); + if (!preg_match('/([0-9]+[.][0-9]+[.][0-9]+)/', $version, $matches)) { + fatal("Incorrect Plesk version format. Current version: {$version}"); + } + return $matches[1]; + } + + public static function getVersionAndBuild() + { + $versionPath = Util::getPleskRootPath().'/version'; + if (!file_exists($versionPath)) { + fatal("Plesk version file is not exists $versionPath"); + } + $version = file_get_contents($versionPath); + $version = trim($version); + return $version; + } + + public static function getLatestPleskVersionAsString() + { + return 'Plesk ' . PLESK_VERSION; + } + + public static function isUpgradeSupportedVersion() + { + return self::is17x_or_above(); + } +} + +class Log +{ + private $errors; + private $warnings; + private $emergency; + private $logfile; + private $step; + private $step_header; + + /** @var array */ + private $errorsContent = []; + + /** @var array */ + private $warningsContent = []; + + public static function getInstance($step_msg = '', $step_number = true) + { + static $_instance = null; + if (is_null($_instance)) { + $_instance = new Log(); + } + if ($step_msg) { + $_instance->step($step_msg, $step_number); + } + + return $_instance; + } + + private function __construct() + { + $this->log_init(); + @unlink($this->logfile); + } + + private function log_init() + { + $this->step = 0; + $this->errors = 0; + $this->warnings = 0; + $this->emergency = 0; + $this->logfile = LOG_PATH; + $this->step_header = "Unknown step is running"; + } + + public function getErrors() + { + return $this->errors; + } + + public function getWarnings() + { + return $this->warnings; + } + + public function getEmergency() + { + return $this->emergency; + } + + public function fatal($msg) + { + $this->errors++; + + $this->errorsContent[] = $msg; + $content = $this->get_log_string($msg, 'FATAL_ERROR'); + fwrite(STDERR, $content); + $this->write($content); + } + + public function error($msg) + { + $this->errors++; + + $this->errorsContent[] = $msg; + $content = $this->get_log_string($msg, 'ERROR'); + fwrite(STDERR, $content); + $this->write($content); + } + + public function warning($msg) + { + $this->warnings++; + + $this->warningsContent[] = $msg; + $content = $this->get_log_string($msg, 'WARNING'); + fwrite(STDERR, $content); + $this->write($content); + } + + public function emergency($msg) + { + $this->emergency++; + + $this->errorsContent[] = $msg; + $content = $this->get_log_string($msg, 'EMERGENCY'); + fwrite(STDERR, $content); + $this->write($content); + } + + public function step($msg, $useNumber=false) + { + $this->step_header = $msg; + + echo PHP_EOL; + $this->write(PHP_EOL); + + if ($useNumber) { + $msg = "STEP " . $this->step . ": {$msg}..."; + $this->step++; + } else { + $msg = "{$msg}..."; + } + + $this->info($msg); + } + + public function resultOk() + { + $this->info('Result: OK'); + } + + public function resultWarning() + { + $this->info('Result: WARNING'); + } + + public function resultError() + { + $this->info('Result: ERROR'); + } + + public function info($msg) + { + $content = $this->get_log_string($msg, 'INFO'); + echo $content; + $this->write($content); + } + + public function debug($msg) + { + $this->write($this->get_log_string($msg, 'DEBUG')); + } + + public function dumpStatistics() + { + $errors = $this->errors + $this->emergency; + $str = "Errors found: $errors; Warnings found: {$this->warnings}"; + echo PHP_EOL . $str . PHP_EOL . PHP_EOL; + } + + private function get_log_string($msg, $type) + { + if (getenv('VZ_UPGRADE_SCRIPT')) { + switch ($type) { + case 'FATAL_ERROR': + case 'ERROR': + case 'WARNING': + case 'EMERGENCY': + $content = "[{$type}]: {$this->step_header} DESC: {$msg}" . PHP_EOL; + break; + default: + $content = "[{$type}]: {$msg}" . PHP_EOL; + } + } else if (getenv('AUTOINSTALLER_VERSION')) { + $content = "{$type}: {$msg}" . PHP_EOL; + } else { + $date = date('Y-m-d h:i:s'); + $content = "[{$date}][{$type}] {$msg}" . PHP_EOL; + } + + return $content; + } + + public function write($content, $file = null, $mode='a+') + { + $logfile = $file ? $file : $this->logfile; + $fp = fopen($logfile, $mode); + fwrite($fp, $content); + fclose($fp); + } + + private function getJsonFileName() + { + return (Util::isWindows() ? + rtrim(Util::regPleskQuery('PRODUCT_DATA_D'), "\\") : + Util::getSettingFromPsaConf('PRODUCT_ROOT_D') + ) . '/var/' . LOG_JSON; + } + + public function writeJsonFile() + { + $data = [ + 'version' => PRE_UPGRADE_SCRIPT_VERSION, + 'errorsFound' => $this->errors + $this->emergency, + 'errors' => $this->errorsContent, + 'warningsFound' => $this->warnings, + 'warnings' => $this->warningsContent, + ]; + file_put_contents($this->getJsonFileName(), json_encode($data)); + } +} + +class PleskDb +{ + var $_db = null; + + public function __construct($dbParams) + { + switch($dbParams['db_type']) { + case 'mysql': + $this->_db = new DbMysql( + $dbParams['host'], $dbParams['login'], $dbParams['passwd'], $dbParams['db'], $dbParams['port'] + ); + break; + + case 'jet': + $this->_db = new DbJet($dbParams['db']); + break; + + case 'mssql': + $this->_db = new DbMsSql( + $dbParams['host'], $dbParams['login'], $dbParams['passwd'], $dbParams['db'], $dbParams['port'] + ); + break; + + default: + fatal("{$dbParams['db_type']} is not implemented yet"); + break; + } + } + + public static function getInstance() + { + global $options; + static $_instance = array(); + + $dbParams['db_type']= Util::getPleskDbType(); + $dbParams['db'] = Util::getPleskDbName(); + $dbParams['port'] = Util::getPleskDbPort(); + $dbParams['login'] = Util::getPleskDbLogin(); + $dbParams['passwd'] = Util::getPleskDbPassword($options->getDbPasswd()); + $dbParams['host'] = Util::getPleskDbHost(); + + $dbId = md5(implode("\n", $dbParams)); + + $_instance[$dbId] = new PleskDb($dbParams); + + return $_instance[$dbId]; + } + + function fetchOne($sql) + { + if (DEBUG) { + $log = Log::getInstance(); + $log->info($sql); + } + return $this->_db->fetchOne($sql); + } + + function fetchRow($sql) + { + $res = $this->fetchAll($sql); + if (is_array($res) && isset($res[0])) { + return $res[0]; + } + return array(); + } + + function fetchAll($sql) + { + if (DEBUG) { + $log = Log::getInstance(); + $log->info($sql); + } + return $this->_db->fetchAll($sql); + } +} + +class DbMysql +{ + var $_dbHandler; + + public function __construct($host, $user, $passwd, $database, $port) + { + if ( extension_loaded('mysql') ) { + $this->_dbHandler = @mysql_connect("{$host}:{$port}", $user, $passwd); + if (!is_resource($this->_dbHandler)) { + $mysqlError = mysql_error(); + if (stristr($mysqlError, 'access denied for user')) { + $errMsg = 'Given is incorrect. ' . $mysqlError; + } else { + $errMsg = 'Unable to connect database. The reason of problem: ' . $mysqlError . PHP_EOL; + } + $this->_logError($errMsg); + } + @mysql_select_db($database, $this->_dbHandler); + } else if ( extension_loaded('mysqli') ) { + + // forbid using MYSQLI_REPORT_STRICT to handle mysqli errors via error codes + mysqli_report(MYSQLI_REPORT_ERROR); + + $this->_dbHandler = @mysqli_connect($host, $user, $passwd, $database, $port); + if (!$this->_dbHandler) { + $mysqlError = mysqli_connect_error(); + if (stristr($mysqlError, 'access denied for user')) { + $errMsg = 'Given is incorrect. ' . $mysqlError; + } else { + $errMsg = 'Unable to connect database. The reason of problem: ' . $mysqlError . PHP_EOL; + } + $this->_logError($errMsg); + } + } else { + fatal("No MariaDB/MySQL extension is available"); + } + } + + function fetchAll($sql) + { + if ( extension_loaded('mysql') ) { + $res = mysql_query($sql, $this->_dbHandler); + if (!is_resource($res)) { + $this->_logError('Unable to execute query. Error: ' . mysql_error($this->_dbHandler)); + } + $rowset = array(); + while ($row = mysql_fetch_assoc($res)) { + $rowset[] = $row; + } + return $rowset; + } else if ( extension_loaded('mysqli') ) { + $res = $this->_dbHandler->query($sql); + if ($res === false) { + $this->_logError('Unable to execute query. Error: ' . mysqli_error($this->_dbHandler)); + } + $rowset = array(); + while ($row = mysqli_fetch_assoc($res)) { + $rowset[] = $row; + } + return $rowset; + } else { + fatal("No MariaDB/MySQL extension is available"); + } + } + + function fetchOne($sql) + { + if ( extension_loaded('mysql') ) { + $res = mysql_query($sql, $this->_dbHandler); + if (!is_resource($res)) { + $this->_logError('Unable to execute query. Error: ' . mysql_error($this->_dbHandler)); + } + $row = mysql_fetch_row($res); + return isset($row[0]) ? $row[0] : null; + } else if ( extension_loaded('mysqli') ) { + $res = $this->_dbHandler->query($sql); + if ($res === false) { + $this->_logError('Unable to execute query. Error: ' . mysqli_error($this->_dbHandler)); + } + $row = mysqli_fetch_row($res); + return isset($row[0]) ? $row[0] : null; + } else { + fatal("No MariaDB/MySQL extension is available"); + } + } + + function query($sql) + { + if ( extension_loaded('mysql') ) { + $res = mysql_query($sql, $this->_dbHandler); + if ($res === false ) { + $this->_logError('Unable to execute query. Error: ' . mysql_error($this->_dbHandler) ); + } + return $res; + } else if ( extension_loaded('mysqli') ) { + $res = $this->_dbHandler->query($sql); + if ($res === false ) { + $this->_logError('Unable to execute query. Error: ' . mysqli_error($this->_dbHandler) ); + } + return $res; + } else { + fatal("No MariaDB/MySQL extension is available"); + } + } + + function _logError($message) + { + fatal("[MYSQL ERROR] $message"); + } +} + +class DbClientMysql extends DbMysql +{ + var $errors = array(); + + function _logError($message) + { + $message = "[MYSQL ERROR] $message"; + $log = Log::getInstance(); + $log->warning($message); + $this->errors[] = $message; + } + + function hasErrors() { + return count($this->errors) > 0; + } +} + +class DbJet +{ + var $_dbHandler = null; + + public function __construct($dbPath) + { + $dsn = "Provider='Microsoft.Jet.OLEDB.4.0';Data Source={$dbPath}"; + $this->_dbHandler = new COM("ADODB.Connection", NULL, CP_UTF8); + if (!$this->_dbHandler) { + $this->_logError('Unable to init ADODB.Connection'); + } + + $this->_dbHandler->open($dsn); + } + + function fetchAll($sql) + { + $result_id = $this->_dbHandler->execute($sql); + if (!$result_id) { + $this->_logError('Unable to execute sql query ' . $sql); + } + if ($result_id->BOF && !$result_id->EOF) { + $result_id->MoveFirst(); + } + if ($result_id->EOF) { + return array(); + } + + $rowset = array(); + while(!$result_id->EOF) { + $row = array(); + for ($i=0;$i<$result_id->Fields->count;$i++) { + $field = $result_id->Fields($i); + $row[$field->Name] = (string)$field->value; + } + $result_id->MoveNext(); + $rowset[] = $row; + } + return $rowset; + } + + function fetchOne($sql) + { + $result_id = $this->_dbHandler->execute($sql); + if (!$result_id) { + $this->_logError('Unable to execute sql query ' . $sql); + } + if ($result_id->BOF && !$result_id->EOF) { + $result_id->MoveFirst(); + } + if ($result_id->EOF) { + return null; + } + $field = $result_id->Fields(0); + $result = $field->value; + + return (string)$result; + } + + function _logError($message) + { + fatal("[JET ERROR] $message"); + } +} + +class DbMsSql extends DbJet +{ + public function __construct($host, $user, $passwd, $database, $port) + { + $dsn = "Provider=SQLOLEDB.1;Initial Catalog={$database};Data Source={$host}"; + $this->_dbHandler = new COM("ADODB.Connection", NULL, CP_UTF8); + if (!$this->_dbHandler) { + $this->_logError('Unable to init ADODB.Connection'); + } + $this->_dbHandler->open($dsn, $user, $passwd); + } + + function _logError($message) + { + fatal("[MSSQL ERROR] $message"); + } +} + +class Util +{ + const DSN_INI_PATH_UNIX = '/etc/psa/private/dsn.ini'; + + /** @var array */ + private static $_dsnIni; + + public static function isWindows() + { + if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { + return true; + } + return false; + } + + public static function isLinux() + { + return !Util::isWindows(); + } + + public static function isVz() + { + $vz = false; + if (Util::isLinux()) { + if (file_exists('/proc/vz/veredir')) { + $vz = true; + } + } else { + $reg = 'REG QUERY "HKLM\SOFTWARE\SWsoft\Virtuozzo" 2>nul'; + Util::exec($reg, $code); + if ($code==0) { + $vz = true; + } + } + return $vz; + } + + public static function getArch() + { + global $arch; + if (!empty($arch)) + return $arch; + + $arch = 'i386'; + if (Util::isLinux()) { + $cmd = 'uname -m'; + $x86_64 = 'x86_64'; + $output = Util::exec($cmd, $code); + if (!empty($output) && stristr($output, $x86_64)) { + $arch = 'x86_64'; + } + } else { + $arch = 'x86_64'; + } + return $arch; + } + + public static function getHostname() + { + if (Util::isLinux()) { + $cmd = 'hostname -f'; + } else { + $cmd = 'hostname'; + } + $hostname = Util::exec($cmd, $code); + + if (empty($hostname)) { + $err = 'Command: ' . $cmd . ' returns: ' . $hostname . "\n"; + $err .= 'Hostname is not defined and configured. Unable to get hostname. Server should have properly configured hostname and it should be resolved locally.'; + fatal($err); + } + + return $hostname; + } + + public static function getIPList($lo=false) + { + if (Util::isLinux()) { + $ipList = Util::getIPv4ListOnLinux(); + foreach ($ipList as $key => $ip) { + if (!$lo && substr($ip, 0, 3) == '127') { + unset($ipList[$key]); + continue; + } + trim($ip); + } + $ipList = array_values($ipList); + } else { + $cmd = 'hostname'; + $hostname = Util::exec($cmd, $code); + $ip = gethostbyname($hostname); + $res = ($ip != $hostname) ? true : false; + if (!$res) { + fatal('Unable to retrieve IP address'); + } + $ipList = array(trim($ip)); + } + return $ipList; + } + + public static function getIPv6ListOnLinux() + { + return Util::grepCommandOutput(array( + array('bin' => 'ip', 'command' => '%PATH% addr list', 'regexp' => '#inet6 ([^ /]+)#'), + array('bin' => 'ifconfig', 'command' => '%PATH% -a', 'regexp' => '#inet6 (?:addr: ?)?([A-F0-9:]+)#i'), + )); + } + + public static function getIPv4ListOnLinux() + { + $commands = array( + array('bin' => 'ip', 'command' => '%PATH% addr list', 'regexp' => '#inet ([^ /]+)#'), + array('bin' => 'ifconfig', 'command' => '%PATH% -a', 'regexp' => '#inet (?:addr: ?)?([\d\.]+)#'), + ); + if (!($list = Util::grepCommandOutput($commands))) { + fatal('Unable to get IP address'); + } + return $list; + } + + public static function grepCommandOutput($cmds) + { + foreach ($cmds as $cmd) { + if ($fullPath = Util::lookupCommand($cmd['bin'])) { + $output = Util::exec(str_replace("%PATH%", $fullPath, $cmd['command']), $code); + if (preg_match_all($cmd['regexp'], $output, $matches)) { + return $matches[1]; + } + } + } + return false; + } + + public static function getIPListOnWindows() + { + $cmd = 'wmic.exe path win32_NetworkAdapterConfiguration get IPaddress'; + $output = Util::exec($cmd, $code); + if (!preg_match_all('/"(.*?)"/', $output, $matches)) { + fatal('Unable to get IP address'); + } + return $matches[1]; + } + + public static function getPleskRootPath() + { + global $_pleskRootPath; + if (empty($_pleskRootPath)) { + if (Util::isLinux()) { + if (PleskOS::isDebLike()) { + $_pleskRootPath = '/opt/psa'; + } else { + $_pleskRootPath = '/usr/local/psa'; + } + } + if (Util::isWindows()) { + $_pleskRootPath = Util::regPleskQuery('PRODUCT_ROOT_D', true); + } + } + return $_pleskRootPath; + } + + public static function getPleskDbName() + { + $dbName = 'psa'; + if (Util::isWindows()) { + $dbName = Util::regPleskQuery('mySQLDBName'); + } else { + $dsnDbname = Util::_getDsnConfigValue('dbname'); + if ($dsnDbname) { + $dbName = $dsnDbname; + } + } + return $dbName; + } + + public static function getPleskDbLogin() + { + $dbLogin = 'admin'; + if (Util::isWindows()) { + $dbLogin = Util::regPleskQuery('PLESK_DATABASE_LOGIN'); + } else { + $dsnLogin = Util::_getDsnConfigValue('username'); + if ($dsnLogin) { + $dbLogin = $dsnLogin; + } + } + return $dbLogin; + } + + public static function getPleskDbPassword($dbPassword) + { + if (Util::isLinux()) { + $dsnPassword = Util::_getDsnConfigValue('password'); + if ($dsnPassword) { + $dbPassword = $dsnPassword; + } + } + return $dbPassword; + } + + public static function getPleskDbType() + { + $dbType = 'mysql'; + if (Util::isWindows()) { + $dbType = strtolower(Util::regPleskQuery('PLESK_DATABASE_PROVIDER_NAME')); + } + return $dbType; + } + + public static function getPleskDbHost() + { + $dbHost = 'localhost'; + if (Util::isWindows()) { + $dbProvider = strtolower(Util::regPleskQuery('PLESK_DATABASE_PROVIDER_NAME')); + if ($dbProvider == 'mysql' || $dbProvider == 'mssql') { + $dbHost = Util::regPleskQuery('MySQL_DB_HOST'); + } + } else { + $dsnHost = Util::_getDsnConfigValue('host'); + if ($dsnHost) { + $dbHost = $dsnHost; + } + } + return $dbHost; + } + + public static function getPleskDbPort() + { + $dbPort = '3306'; + if (Util::isWindows()) { + $dbPort = Util::regPleskQuery('MYSQL_PORT'); + } else { + $dsnPort = Util::_getDsnConfigValue('port'); + if ($dsnPort) { + $dbPort = $dsnPort; + } + } + return $dbPort; + } + + private static function _getDsnConfigValue($param) + { + if (Util::isWindows()) { + return null; + } + + if (is_null(self::$_dsnIni)) { + if (!is_file(self::DSN_INI_PATH_UNIX)) { + self::$_dsnIni = false; + return null; + } + self::$_dsnIni = parse_ini_file(self::DSN_INI_PATH_UNIX, true); + } + + if (!self::$_dsnIni) { + return null; + } + if (!array_key_exists('plesk', self::$_dsnIni)) { + return null; + } + if (!array_key_exists($param, self::$_dsnIni['plesk'])) { + return null; + } + return self::$_dsnIni['plesk'][$param]; + } + + public static function regPleskQuery($key, $returnResult=false) + { + $reg = 'REG QUERY "HKLM\SOFTWARE\Wow6432Node\Plesk\Psa Config\Config" /v '.$key; + $output = Util::exec($reg, $code); + + if ($code) { + $log = Log::getInstance(); + $log->info($reg); + $log->info($output); + if ($returnResult) { + return false; + } else { + fatal("Unable to get '$key' from registry"); + } + } + + if (!preg_match("/\w+\s+REG_SZ\s+(.*)/i", trim($output), $matches)) { + fatal('Unable to macth registry value by key '.$key.'. Output: ' . trim($output)); + } + + return $matches[1]; + } + + public static function regQuery($path, $key, $returnResult = false) + { + $reg = 'REG QUERY "HKLM\SOFTWARE\Wow6432Node' . $path . '" '.$key; + $output = Util::exec($reg, $code); + + if ($code) { + $log = Log::getInstance(); + $log->info($reg); + $log->info($output); + if ($returnResult) { + return false; + } else { + fatal("Unable to get '$key' from registry"); + } + } + + if (!preg_match("/\s+REG_SZ(\s+)?(.*)/i", trim($output), $matches)) { + fatal('Unable to match registry value by key '.$key.'. Output: ' . trim($output)); + } + + return $matches[2]; + } + + public static function lookupCommand($cmd, $exit = false, $path = '/bin:/usr/bin:/usr/local/bin:/usr/sbin:/sbin:/usr/local/sbin') + { + $dirs = explode(':', $path); + foreach ($dirs as $dir) { + $util = $dir . '/' . $cmd; + if (is_executable($util)) { + return $util; + } + } + if ($exit) { + fatal("{$cmd}: command not found"); + } + return false; + } + + public static function getSystemDisk() + { + $cmd = 'echo %SYSTEMROOT%'; + $output = Util::exec($cmd, $code); + return substr($output, 0, 3); + } + + public static function getSystemRoot() + { + $cmd = 'echo %SYSTEMROOT%'; + $output = Util::exec($cmd, $code); + return $output; + } + + public static function getFileVersion($file) + { + $fso = new COM("Scripting.FileSystemObject"); + $version = $fso->GetFileVersion($file); + $fso = null; + return $version; + } + + public static function isUnknownISAPIfilters() + { + if (PleskVersion::is17x_or_above()) { + return false; + } + + $log = Log::getInstance(); + + $isUnknownISAPI = false; + $knownISAPI = array ("ASP\\.Net.*", "sitepreview", "COMPRESSION", "jakarta"); + + foreach ($knownISAPI as &$value) { + $value = strtoupper($value); + } + $cmd='cscript ' . Util::getSystemDisk() . 'inetpub\AdminScripts\adsutil.vbs ENUM W3SVC/FILTERS'; + $output = Util::exec($cmd, $code); + + if ($code!=0) { + $log->info("Unable to get ISAPI filters. Error: " . $output); + return false; + } + if (!preg_match_all('/FILTERS\/(.*)]/', trim($output), $matches)) { + $log->info($output); + $log->info("Unable to get ISAPI filters from output: " . $output); + return false; + } + foreach ($matches[1] as $ISAPI) { + $valid = false; + foreach ($knownISAPI as $knownPattern) { + if (preg_match("/$knownPattern/i", $ISAPI)) { + $valid = true; + break; + } + } + if (! $valid ) { + $log->warning("Unknown ISAPI filter detected in IIS: " . $ISAPI); + $isUnknownISAPI = true; + } + } + + return $isUnknownISAPI; + } + + /** + * @return string + */ + public static function getMySQLServerVersion() + { + $credentials = Util::getDefaultClientMySQLServerCredentials(); + + if (preg_match('/AES-128-CBC/', $credentials['admin_password'])) { + Log::getInstance()->info('The administrator\'s password for the default MariaDB/MySQL server is encrypted.'); + + return ''; + } + + $mysql = new DbClientMysql( + $credentials['host'], + $credentials['admin_login'], + $credentials['admin_password'], + 'information_schema', + $credentials['port'] + ); + + if (!$mysql->hasErrors()) { + $sql = 'select version()'; + $mySQLversion = $mysql->fetchOne($sql); + if (!preg_match("/(\d{1,})\.(\d{1,})\.(\d{1,})/", trim($mySQLversion), $matches)) { + fatal('Unable to match MariaDB/MySQL server version.'); + } + + return $matches[0]; + } + + return ''; + } + + public static function getDefaultClientMySQLServerCredentials() + { + $db = PleskDb::getInstance(); + $sql = "SELECT val FROM misc WHERE param='default_server_mysql'"; + $defaultServerMysqlId = $db->fetchOne($sql); + if ($defaultServerMysqlId) { + $where = "id={$defaultServerMysqlId}"; + } else { + $where = "type='mysql' AND host='localhost'"; + } + $sql = "SELECT ds.host, ds.port, ds.admin_login, ds.admin_password FROM DatabaseServers ds WHERE {$where}"; + $clientDBServerCredentials = $db->fetchAll($sql)[0]; + if ($clientDBServerCredentials['host'] === 'localhost' && Util::isLinux()) { + $clientDBServerCredentials['admin_password'] = Util::retrieveAdminMySQLDbPassword(); + } + if (empty($clientDBServerCredentials['port'])) { + $clientDBServerCredentials['port'] = self::getPleskDbPort(); + } + + return $clientDBServerCredentials; + } + + public static function retrieveAdminMySQLDbPassword() + { + return Util::isLinux() + ? trim( Util::readfile("/etc/psa/.psa.shadow") ) + : null; + } + + public static function exec($cmd, &$code) + { + $log = Log::getInstance(); + + if (!$cmd) { + $log->info('Unable to execute a blank command. Please see ' . LOG_PATH . ' for details.'); + + $debugBacktrace = ""; + foreach (debug_backtrace() as $i => $obj) { + $debugBacktrace .= "#{$i} {$obj['file']}:{$obj['line']} {$obj['function']} ()\n"; + } + $log->debug("Unable to execute a blank command. The stack trace:\n{$debugBacktrace}"); + $code = 1; + return ''; + } + exec($cmd, $output, $code); + return trim(implode("\n", $output)); + } + + public static function readfile($file) + { + if (!is_file($file) || !is_readable($file)) { + return null; + } + $lines = file($file); + return $lines === false + ? null + : trim(implode("\n", $lines)); + } + + public static function readfileToArray($file) + { + if (!is_file($file) || !is_readable($file)) { + return null; + } + $lines = file($file); + return $lines === false + ? null + : $lines; + } + + public static function getSettingFromPsaConf($setting) + { + $file = '/etc/psa/psa.conf'; + if (!is_file($file) || !is_readable($file)) + return null; + $lines = file($file); + if ($lines === false) + return null; + foreach ($lines as $line) { + if (preg_match("/^{$setting}\s.*/", $line, $match_setting)) { + if (preg_match("/[\s].*/i", $match_setting[0], $match_value)) { + $value = trim($match_value[0]); + return $value; + } + } + } + return null; + } + + public static function getPhpIni() + { + if (Util::isLinux()) { + // Debian/Ubuntu /etc/php5/apache2/php.ini /etc/php5/conf.d/ + // SuSE /etc/php5/apache2/php.ini /etc/php5/conf.d/ + // CentOS 4/5 /etc/php.ini /etc/php.d + if (PleskOS::isRedHatLike()) { + $phpini = Util::readfileToArray('/etc/php.ini'); + } else { + $phpini = Util::readfileToArray('/etc/php5/apache2/php.ini'); + } + } + + return $phpini; + } + + public static function getUserBeanCounters() + { + if (!Util::isLinux()) { + + return false; + } + $user_beancounters = array(); + $ubRaw = Util::readfileToArray('/proc/user_beancounters'); + + if (!$ubRaw) { + + return false; + } + for ($i=2; $i<=count($ubRaw)-1; $i++) { + + if (preg_match('/^.+?:?.+?\b(\w+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)/', $ubRaw[$i], $limit_name)) { + + $user_beancounters[trim($limit_name[1])] = array( + 'held' => (int)$limit_name[2], + 'maxheld' => (int)$limit_name[3], + 'barrier' => (int)$limit_name[4], + 'limit' => (int)$limit_name[5], + 'failcnt' => (int)$limit_name[6] + ); + } + } + + return $user_beancounters; + } +} + +class PackageManager +{ + public static function buildListCmdLine($glob) + { + if (PleskOS::isRedHatLike()) { + $cmd = "rpm -qa --queryformat '%{NAME} %{VERSION}-%{RELEASE} %{ARCH}\\n'"; + } elseif (PleskOS::isDebLike()) { + $cmd = "dpkg-query --show --showformat '\${Package} \${Version} \${Architecture}\\n'"; + } else { + return false; + } + + if (!empty($glob)) { + $cmd .= " '" . $glob . "' 2>/dev/null"; + } + + return $cmd; + } + + /* + * Fetches a list of installed packages that match given criteria. + * string $glob - Glob (wildcard) pattern for coarse-grained packages selection from system package management backend. Empty $glob will fetch everything. + * string $regexp - Package name regular expression for a fine-grained filtering of the results. + * returns array of hashes with keys 'name', 'version' and 'arch', or false on error. + */ + public static function listInstalled($glob, $regexp = null) + { + $cmd = PackageManager::buildListCmdLine($glob); + if (!$cmd) { + return array(); + } + + $output = Util::exec($cmd, $code); + if ($code != 0) { + return false; + } + + $packages = array(); + $lines = explode("\n", $output); + foreach ($lines as $line) { + @list($pkgName, $pkgVersion, $pkgArch) = explode(" ", $line); + if (empty($pkgName) || empty($pkgVersion) || empty($pkgArch)) + continue; + if (!empty($regexp) && !preg_match($regexp, $pkgName)) + continue; + $packages[] = array( + 'name' => $pkgName, + 'version' => $pkgVersion, + 'arch' => $pkgArch + ); + } + + return $packages; + } + + public static function isInstalled($glob, $regexp = null) + { + $packages = PackageManager::listInstalled($glob, $regexp); + return !empty($packages); + } +} + +class Package +{ + function getManager($field, $package) + { + $redhat = 'rpm -q --queryformat \'%{' . $field . '}\n\' ' . $package; + $debian = 'dpkg-query --show --showformat=\'${' . $field . '}\n\' '. $package . ' 2> /dev/null'; + + if (PleskOS::isRedHatLike()) { + $manager = $redhat; + } elseif (PleskOS::isDebLike()) { + $manager = $debian; + } else { + return false; + } + + return $manager; + } + + /* DPKG doesn't supports ${Release} + * + */ + + function getRelease($package) + { + $manager = Package::getManager('Release', $package); + + if (!$manager) { + return false; + } + + $release = Util::exec($manager, $code); + if (!$code === 0) { + return false; + } + return $release; + } + + function getVersion($package) + { + $manager = Package::getManager('Version', $package); + + if (!$manager) { + return false; + } + + $version = Util::exec($manager, $code); + if (!$code === 0) { + return false; + } + return $version; + } + +} + +class PleskOS +{ + public static function isDebLike() + { + return is_file("/etc/debian_version"); + } + + public static function isRedHatLike() + { + return is_file("/etc/redhat-release"); + } + + public static function catEtcIssue() + { + $cmd = 'cat /etc/issue'; + $output = Util::exec($cmd, $code); + + return $output; + } + + public static function detectSystem() + { + $log = Log::getInstance('Detect system configuration'); + $log->info('OS: ' . (Util::isLinux() ? PleskOS::catEtcIssue() : 'Windows')); + $log->info('Arch: ' . Util::getArch()); + } +} + +class PleskValidator +{ + public static function validateIPv4($value) + { + $ip2long = ip2long($value); + if ($ip2long === false) { + return false; + } + + return $value == long2ip($ip2long); + } + + public static function validateIPv6($value) + { + if (strlen($value) < 3) { + return $value == '::'; + } + + if (strpos($value, '.')) { + $lastcolon = strrpos($value, ':'); + if (!($lastcolon && PleskValidator::validateIPv4(substr($value, $lastcolon + 1)))) { + return false; + } + + $value = substr($value, 0, $lastcolon) . ':0:0'; + } + + if (strpos($value, '::') === false) { + return preg_match('/\A(?:[a-f0-9]{1,4}:){7}[a-f0-9]{1,4}\z/i', $value); + } + + $colonCount = substr_count($value, ':'); + if ($colonCount < 8) { + return preg_match('/\A(?::|(?:[a-f0-9]{1,4}:)+):(?:(?:[a-f0-9]{1,4}:)*[a-f0-9]{1,4})?\z/i', $value); + } + + // special case with ending or starting double colon + if ($colonCount == 8) { + return preg_match('/\A(?:::)?(?:[a-f0-9]{1,4}:){6}[a-f0-9]{1,4}(?:::)?\z/i', $value); + } + + return false; + } +} + +class CheckRequirements +{ + function validate() + { + if (!PleskInstallation::isInstalled()) { + //:INFO: skip chking mysql extension if plesk is not installed + return; + } + + $reqExts = array(); + foreach ($reqExts as $name) { + $status = extension_loaded($name); + if (!$status) { + $this->_fail("PHP extension {$name} is not installed"); + } + } + } + + function _fail($errMsg) + { + echo '===Checking requirements===' . PHP_EOL; + echo PHP_EOL . 'Error: ' . $errMsg . PHP_EOL; + exit(1); + } +} + +class GetOpt +{ + var $_argv; + var $_adminDbPasswd; + + public function __construct() + { + $this->_argv = $_SERVER['argv']; + if (empty($this->_argv[1]) && Util::isLinux()) { + $this->_adminDbPasswd = Util::retrieveAdminMySQLDbPassword(); + } else { + $this->_adminDbPasswd = $this->_argv[1]; + } + } + + public function validate() + { + if (empty($this->_adminDbPasswd) && PleskInstallation::isInstalled()) { + echo 'Please specify Plesk database password'; + $this->_helpUsage(); + } + } + + public function getDbPasswd() + { + return $this->_adminDbPasswd; + } + + public function _helpUsage() + { + echo PHP_EOL . "Usage: {$this->_argv[0]} " . PHP_EOL; + exit(1); + } +} + +function fatal($msg) +{ + $log = Log::getInstance(); + $log->fatal($msg); + exit(1); +} + +$log = Log::getInstance(); + +//:INFO: Validate options +$options = new GetOpt(); +$options->validate(); + +//:INFO: Validate PHP requirements, need to make sure that PHP extensions are installed +$checkRequirements = new CheckRequirements(); +$checkRequirements->validate(); + +//:INFO: Validate Plesk installation +PleskInstallation::validate(); + +//:INFO: Detect system +$pleskOs = new PleskOS(); +$pleskOs->detectSystem(); + +//:INFO: Need to make sure that given db password is valid +if (PleskInstallation::isInstalled()) { + $log->step('Validating the database password'); + $pleskDb = PleskDb::getInstance(); + $log->resultOk(); +} + +//:INFO: Dump script version +$log->step('Pre-Upgrade analyzer version: ' . PRE_UPGRADE_SCRIPT_VERSION); + +//:INFO: Validate known OS specific issues with recommendation to avoid bugs in Plesk +$pleskKnownIssues = new Plesk17KnownIssues(); +$pleskKnownIssues->validate(); + +$plesk175Requirements = new Plesk175Requirements(); +$plesk175Requirements->validate(); + +$plesk178Requirements = new Plesk178Requirements(); +$plesk178Requirements->validate(); + +$plesk18Requirements = new Plesk18Requirements(); +$plesk18Requirements->validate(); + +$log->dumpStatistics(); +$log->writeJsonFile(); + +if ($log->getEmergency() > 0) { + exit(2); +} + +if ($log->getErrors() > 0 || $log->getWarnings() > 0) { + exit(1); +} +// vim:set et ts=4 sts=4 sw=4: diff --git a/root/parallels/pool/PSA_18.0.73_17971/examiners/php_launcher.sh b/root/parallels/pool/PSA_18.0.73_17971/examiners/php_launcher.sh new file mode 100755 index 0000000000..70ebd0f0c6 --- /dev/null +++ b/root/parallels/pool/PSA_18.0.73_17971/examiners/php_launcher.sh @@ -0,0 +1,38 @@ +#!/bin/sh +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +die() +{ + echo $* + exit 1 +} + +[ -n "$1" ] || die "Usage: $0 php_script [args...]" + +[ "X${PLESK_INSTALLER_DEBUG}" = "X" ] || set -x +[ "X${PLESK_INSTALLER_STRICT_MODE}" = "X" ] || set -e + +php_bin= + +lookup() +{ + [ -z "$php_bin" ] || return + + local paths="$1" + local name="$2" + + for path in $paths; do + if [ -x "$path/$name" ]; then + php_bin="$path/$name" + break + fi + done +} + +lookup "/usr/local/psa/admin/bin /opt/psa/admin/bin" "php" +lookup "/usr/local/psa/bin /opt/psa/bin" "sw-engine-pleskrun" + +[ -n "$php_bin" ] || \ + die "Unable to locate the sw-engine PHP interpreter to execute the script. Make sure that Parallels Plesk Panel is installed on this server." + +exec "${php_bin}" "$@" diff --git a/root/parallels/pool/PSA_18.0.73_17971/examiners/plesk_preupgrade_checker.log b/root/parallels/pool/PSA_18.0.73_17971/examiners/plesk_preupgrade_checker.log new file mode 100644 index 0000000000..7cadbf4cf5 --- /dev/null +++ b/root/parallels/pool/PSA_18.0.73_17971/examiners/plesk_preupgrade_checker.log @@ -0,0 +1,3 @@ + +INFO: Installed Plesk version/build: 18.0.73 Ubuntu 24.04 1800251117.15... +INFO: You have already installed the latest version Plesk 18.0.73. Tool must be launched prior to upgrade to Plesk 18.0.73 for the purpose of getting a report on potential problems with the upgrade. diff --git a/root/parallels/pool/PSA_18.0.73_17971/examiners/py_launcher.sh b/root/parallels/pool/PSA_18.0.73_17971/examiners/py_launcher.sh new file mode 100755 index 0000000000..96dc215391 --- /dev/null +++ b/root/parallels/pool/PSA_18.0.73_17971/examiners/py_launcher.sh @@ -0,0 +1,30 @@ +#!/bin/sh +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +die() +{ + echo "$*" + exit 1 +} + +[ -f "$1" ] || die "Usage: $0 PEX [args...]" + +[ "X${PLESK_INSTALLER_DEBUG}" = "X" ] || set -x +[ "X${PLESK_INSTALLER_STRICT_MODE}" = "X" ] || set -e + +find_python_bin() +{ + local bin + for bin in "/opt/psa/bin/py3-python" "/usr/local/psa/bin/py3-python" "/usr/libexec/platform-python" "/usr/bin/python3" "/opt/psa/bin/python" "/usr/local/psa/bin/python" "/usr/bin/python2"; do + [ -x "$bin" ] || continue + python_bin="$bin" + return 0 + done + + return 1 +} + +find_python_bin || + die "Unable to locate Python interpreter to execute the script." + +exec "$python_bin" "$@" diff --git a/root/parallels/pool/PSA_18.0.73_17971/examiners/repository_check.sh b/root/parallels/pool/PSA_18.0.73_17971/examiners/repository_check.sh new file mode 100755 index 0000000000..090f121ea1 --- /dev/null +++ b/root/parallels/pool/PSA_18.0.73_17971/examiners/repository_check.sh @@ -0,0 +1,782 @@ +#!/bin/bash +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +[ -z "$PLESK_INSTALLER_DEBUG" ] || set -x +[ -z "$PLESK_INSTALLER_STRICT_MODE" ] || set -e + +export LC_ALL=C +unset GREP_OPTIONS + +RET_SUCCESS=0 +RET_WARN=1 +RET_FATAL=2 + +is_function_defined() +{ + local fn="$1" + case "$(type $fn 2>/dev/null)" in + *function*) + return 0 + ;; + esac + return 1 +} + +# @params are tags in format "key=value" +# Report body (human readable information) is read from stdin +# and copied to stderr. +make_error_report() +{ + local report_file="${PLESK_INSTALLER_ERROR_REPORT:-}" + + local python_bin= + for bin in "/opt/psa/bin/python" "/usr/local/psa/bin/python" "/usr/bin/python2" "/opt/psa/bin/py3-python" "/usr/local/psa/bin/py3-python" "/usr/libexec/platform-python" "/usr/bin/python3"; do + if [ -x "$bin" ]; then + python_bin="$bin" + break + fi + done + + if [ -n "$report_file" -a -x "$python_bin" ]; then + "$python_bin" -c 'import sys, json +report_file = sys.argv[1] +error = sys.stdin.read() + +sys.stderr.write(error) + +data = { + "error": error, +} + +for tag in sys.argv[2:]: + k, v = tag.split("=", 1) + data[k] = v + +with open(report_file, "a") as f: + json.dump(data, f) + f.write("\n") +' "$report_file" "date=$(date --utc --iso-8601=ns)" "$@" + else + cat - >&2 + fi +} + +detect_platform() +{ + . /etc/os-release + os_name="$ID" + os_version="${VERSION_ID%%.*}" + os_arch="$(uname -m)" + if [ -e /etc/debian_version ]; then + case "$os_arch" in + x86_64) pkg_arch="amd64" ;; + aarch64) pkg_arch="arm64" ;; + esac + if [ -n "$VERSION_CODENAME" ]; then + os_codename="$VERSION_CODENAME" + else + case "$os_name$os_version" in + debian10) os_codename="buster" ;; + debian11) os_codename="bullseye" ;; + debian12) os_codename="bookworm" ;; + ubuntu18) os_codename="bionic" ;; + ubuntu20) os_codename="focal" ;; + ubuntu22) os_codename="jammy" ;; + ubuntu24) os_codename="noble" ;; + esac + fi + fi + + case "$os_name$os_version" in + rhel7|centos7|cloudlinux7|virtuozzo7) + package_manager="yum" + ;; + rhel*|centos*|cloudlinux*|almalinux*|rocky*) + package_manager="dnf" + ;; + debian*|ubuntu*) + package_manager="apt" + ;; + esac + + if [ "$os_name" = "ubuntu" -o "$os_name" = "debian" ]; then + PRODUCT_ROOT_D="/opt/psa" + else + PRODUCT_ROOT_D="/usr/local/psa" + fi +} + +has_os_impl_function() +{ + local prefix="$1" + local fn="${prefix}_${os_name}${os_version}" + is_function_defined "$fn" +} + +call_os_impl_function() +{ + local prefix="$1" + shift + local fn="${prefix}_${os_name}${os_version}" + "$fn" "$@" +} + +skip_checker_on_flag() +{ + local name="$1" + local flag="$2" + + if [ -f "$flag" ]; then + echo "$name was skipped due to flag file." >&2 + exit $RET_SUCCESS + fi +} + +skip_checker_on_env() +{ + local name="$1" + local env="$2" + + if [ -n "$env" ]; then + echo "$name was skipped due to environment variable." >&2 + exit $RET_SUCCESS + fi +} + +checker_main() +{ + local fnprefix="$1" + shift + + detect_platform + # try to execute checker only if all attributes are detected + [ -n "$os_name" -a -n "$os_version" ] || return $RET_SUCCESS + + for checker in "${fnprefix}_${os_name}${os_version}" "${fnprefix}_${os_name}" "${fnprefix}"; do + if is_function_defined "$checker"; then + local rc=$RET_SUCCESS + "$checker" "$@" || rc=$? + [ "$(( $rc & $RET_FATAL ))" = "0" ] || return $RET_FATAL + [ "$(( $rc & $RET_WARN ))" = "0" ] || return $RET_WARN + return $rc + fi + done + return $RET_SUCCESS +} + +#!/bin/sh +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +# If env variable PLESK_INSTALLER_ERROR_REPORT=path_to_file is specified then in case of error +# repository_check.sh writes single line json report into it with the following fields: +# - "stage": "repositorycheck" +# - "level": "error" +# - "errtype" is one of the following: +# * "reponotcached" - repository is not cached (mostly due to unavailability). +# * "reponotenabled" - required repository is not enabled. +# * "reponotsupported" - unsupported repository is enabled. +# * "configmanagernotinstalled" - dnf config-manager is disabled. +# - "repo": repository name. +# - "date": time of error occurance ("2020-03-24T06:59:43,127545441+0000") +# - "error": human readable error message. + +report_no_repo() +{ + local repo="$1" + + make_error_report 'stage=repositorycheck' 'level=error' 'errtype=reponotenabled' "repo=$repo" <<-EOL + Plesk installation requires '$repo' OS repository to be enabled. + Make sure it is available and enabled, then try again. + EOL +} + +report_no_repo_cache() +{ + local repo="$1" + + make_error_report 'stage=repositorycheck' 'level=error' 'errtype=reponotcached' "repo=$repo" <<-EOL + Unable to create $package_manager cache for '$repo' OS repository. + Make sure the repository is available, otherwise either disable it or fix its configuration, then try again. + EOL +} + +report_unsupported_repo() +{ + local repo="$1" + + make_error_report 'stage=repositorycheck' 'level=error' 'errtype=reponotsupported' "repo=$repo" <<-EOL + Plesk installation doesn't support '$repo' OS repository. + Make sure it is disabled, then try again. + EOL +} + +report_rh_no_config_manager() +{ + local target + case "$package_manager" in + yum) + target="yum-utils package" + ;; + dnf) + target="config-manager dnf plugin" + ;; + esac + + make_error_report 'stage=repositorycheck' 'level=error' 'errtype=configmanagernotinstalled' <<-EOL + Failed to install $target. + Make sure repositories configuration of $package_manager package manager is correct + (use '$package_manager repolist --verbose' to get its actual state), then try again. + EOL +} + +check_rh_broken_repos() +{ + local rh_enabled_repos rh_available_repos + + # 1. `yum repolist` and `dnf repolist` list all repos + # which were enabled before last cache creation + # even if cache for them was not created. + # If some repo is misconfigured and cache was created with `skip_if_unavailable=1` + # then such repo will be listed anyway despite on cache state. + # If some repo was enabled after last cache creation + # then `repolist --cacheonly` will fail. + # 2. `yum repolist --verbose` and `dnf repoinfo` list only repos + # which were successfully cached before. + # These commands fail if at least one repo is not available + # and the 'skip_if_unavailable' flag is not set. + case "$package_manager" in + yum) + rh_enabled_repos="$( + { + yum repolist enabled --cacheonly -q 2>/dev/null \ + || yum repolist enabled -q --setopt='*.skip_if_unavailable=1' + } | sed -n -e '1d' -e 's/^\*\?!\?\([^/[:space:]]\+\).*/\1/p' + )" || return $RET_FATAL + + rh_available_repos="$( + yum repolist enabled --verbose --cacheonly -q --setopt='*.skip_if_unavailable=1' \ + | sed -n -e 's/^Repo-id\s*:\s*\([^/[:space:]]\+\).*/\1/p' + )" || return $RET_FATAL + ;; + dnf) + rh_enabled_repos="$( + { + dnf repolist --enabled --cacheonly -q 2>/dev/null \ + || dnf repolist --enabled -q --setopt='*.skip_if_unavailable=1' + } | sed -n -e '1d' -e 's/^!\?\(\S\+\).*/\1/p' + )" || return $RET_FATAL + + rh_available_repos="$( \ + dnf repoinfo --enabled --cacheonly -q --setopt='*.skip_if_unavailable=1' \ + | sed -n -e 's|^Repo-id\s*:\s*\(\S\+\)\s*$|\1|p' + )" || return $RET_FATAL + ;; + esac + + local rh_enabled_repos_f="$(mktemp /tmp/plesk-installer.preupgrade_checker.XXXXXX)" + echo "$rh_enabled_repos" | sort > "$rh_enabled_repos_f" + local rh_available_repos_f="$(mktemp /tmp/plesk-installer.preupgrade_checker.XXXXXX)" + echo "$rh_available_repos" | sort > "$rh_available_repos_f" + + local repo rc=0 + for repo in $(comm -23 "$rh_enabled_repos_f" "$rh_available_repos_f"); do + report_no_repo_cache "$repo" + rc=$RET_WARN + done + + rm -f "$rh_enabled_repos_f" "$rh_available_repos_f" + + return $rc +} + +has_rh_enabled_repo() +{ + local repo="$1" + + # Try to get list of repos from cache first. + # If some repo was enabled after last cache creation + # or some repo is unavailable the query from cache will fail. + # Try to fetch actual metadata in this case. + case "$package_manager" in + yum) + # Repo-id may end with OS version and/or architecture + # if baseurl of the repo refers to $releasever and/or $basearch variables + # eg 'epel/7/x86_64', 'epel/7', 'epel/x86_64' + { + yum repolist enabled --verbose --cacheonly -q 2>/dev/null \ + || yum repolist enabled --verbose -q --setopt='*.skip_if_unavailable=1' + } | grep -E -q "^Repo-id\s*: $repo(/.+)?\s*$" + ;; + dnf) + # note: --noplugins may cause failure and empty output on RedHat + { + dnf repoinfo --enabled --cacheonly -q 2>/dev/null \ + || dnf repoinfo --enabled -q --setopt='*.skip_if_unavailable=1' + } | grep -E -q "^Repo-id\s*: $repo\s*$" + ;; + esac +} + +has_rh_config_manager() +{ + case "$package_manager" in + yum) yum-config-manager --help >/dev/null 2>&1 ;; + dnf) dnf config-manager --help >/dev/null 2>&1 ;; + esac +} + +install_rh_config_manager() +{ + case "$package_manager" in + yum) yum install --disablerepo 'PLESK_*' -q -y 'yum-utils' --setopt='*.skip_if_unavailable=1' ;; + dnf) dnf install --disablerepo 'PLESK_*' -q -y 'dnf-command(config-manager)' --setopt='*.skip_if_unavailable=1' ;; + esac +} + +check_rh_config_manager() +{ + if ! has_rh_config_manager && ! install_rh_config_manager; then + report_rh_no_config_manager + return $RET_FATAL + fi +} + +enable_rh_repo() +{ + case "$package_manager" in + yum) yum-config-manager --enable "$@" && has_rh_enabled_repo "$@" ;; + dnf) dnf config-manager --set-enabled "$@" && has_rh_enabled_repo "$@" ;; + esac +} + +enable_sm_repo() +{ + ! has_rh_enabled_repo "$@" || return 0 + subscription-manager repos --enable "$@" || return $? + # On RedHat 8 above command may return 0 on failure with "Repositories disabled by configuration." + has_rh_enabled_repo "$@" +} + +check_epel() +{ + ! enable_rh_repo "epel" || return 0 + + # try to install epel-release from centos/extras or plesk/thirdparty repo + # and then try to update it to last version shipped by epel itself + # to make package upgradable with pum + "$package_manager" install --disablerepo 'PLESK_*' -q -y 'epel-release' --setopt='*.skip_if_unavailable=1' 2>/dev/null \ + || "$package_manager" install --disablerepo='*' --enablerepo 'PLESK_18_*-thirdparty' -q -y 'epel-release' \ + || "$package_manager" install -q -y "https://dl.fedoraproject.org/pub/epel/epel-release-latest-$os_version.noarch.rpm" \ + && "$package_manager" update -q -y 'epel-release' --setopt='*.skip_if_unavailable=1' 2>/dev/null + + # Ensure any other EPEL repos have cache for subsequent check for broken repos (AL9) + local epel_repos="$( + [ "$package_manager" != "dnf" ] || { + dnf repolist --enabled --cacheonly -q 2>/dev/null || + dnf repolist --enabled -q --setopt='*.skip_if_unavailable=1' + } | sed -n -e '1d' -e 's/^!\?\(epel\S\+\).*/\1/p' + )" + for repo in $epel_repos; do + "$package_manager" makecache --repo "$repo" -q + done + + ! has_rh_enabled_repo "epel" || return 0 + + report_no_repo "epel" + return $RET_FATAL +} + +check_codeready() +{ + local repo_rhel="codeready-builder-for-rhel-$os_version-$os_arch-rpms" + local repo_rhui="codeready-builder-for-rhel-$os_version-rhui-rpms" + local repo_rhui_alt="codeready-builder-for-rhel-$os_version-$os_arch-rhui-rpms" + local repo_rhui_alt2="rhui-codeready-builder-for-rhel-$os_version-$os_arch-rhui-rpms" + + ! enable_sm_repo "$repo_rhel" || return 0 + ! enable_rh_repo "$repo_rhui" || return 0 + ! enable_rh_repo "$repo_rhui_alt" || return 0 + ! enable_rh_repo "$repo_rhui_alt2" || return 0 + + report_no_repo "$repo_rhel" + return $RET_FATAL +} + +check_optional() +{ + local repo_rhel="rhel-$os_version-server-optional-rpms" + local repo_rhui="rhel-$os_version-server-rhui-optional-rpms" + + ! enable_sm_repo "$repo_rhel" || return 0 + ! enable_rh_repo "$repo_rhui" || return 0 + + report_no_repo "$repo_rhel" + return $RET_FATAL +} + +check_repos_rhel9() +{ + check_rh_config_manager || return $? + + local rc=0 + + check_epel || rc="$(( $rc | $? ))" + check_codeready || rc="$(( $rc | $? ))" + check_rh_broken_repos || rc="$(( $rc | $? ))" + + return $rc +} + +check_repos_almalinux9() +{ + check_rh_config_manager || return $? + + local rc=0 + check_epel || rc="$(( $rc | $? ))" + check_rh_broken_repos || rc="$(( $rc | $? ))" + + # powertools is renamed to crb since AlmaLinux 9 + ! enable_rh_repo "crb" || return $rc + + report_no_repo "crb" + return $RET_FATAL +} + +check_repos_cloudlinux9() +{ + check_repos_almalinux9 "$@" +} + +check_repos_almalinux10() +{ + check_repos_almalinux9 "$@" +} + +check_repos_centos8() +{ + check_rh_config_manager || return $? + + local rc=0 + check_epel || rc="$(( $rc | $? ))" + check_rh_broken_repos || rc="$(( $rc | $? ))" + + # names of repos are lowercased since 8.3 + ! enable_rh_repo "powertools" || return $rc + ! enable_rh_repo "PowerTools" || return $rc + + report_no_repo "powertools" + return $RET_FATAL +} + +check_repos_cloudlinux8() +{ + check_rh_config_manager || return $? + + local rc=0 + check_epel || rc="$(( $rc | $? ))" + check_rh_broken_repos || rc="$(( $rc | $? ))" + + # names of repos are changed since 8.5 + ! enable_rh_repo "powertools" || return $rc + ! enable_rh_repo "cloudlinux-PowerTools" || return $rc + + report_no_repo "powertools" + return $RET_FATAL +} + +check_repos_rhel8() +{ + check_rh_config_manager || return $? + + local rc=0 + check_epel || rc="$(( $rc | $? ))" + check_rh_broken_repos || rc="$(( $rc | $? ))" + + [ "$1" = "install" ] || return $rc + + check_codeready || rc="$(( $rc | $? ))" + + return $rc +} + +check_repos_almalinux8() +{ + check_repos_centos8 "$@" +} + +check_repos_rocky8() +{ + check_repos_centos8 "$@" +} + +check_repos_rhel7() +{ + check_rh_config_manager || return $? + + local rc=0 + + check_epel || rc="$(( $rc | $? ))" + check_optional || rc="$(( $rc | $? ))" + check_rh_broken_repos || rc="$(( $rc | $? ))" + + return $rc +} + +check_repos_centos7_based() +{ + check_rh_config_manager || return $? + + local rc=0 + + check_epel || rc="$(( $rc | $? ))" + check_rh_broken_repos || rc="$(( $rc | $? ))" + + return $rc +} + +sed_escape() +{ + # Note: this is not a full implementation + echo -n "$1" | sed -e 's|\.|\\.|g' +} + +switch_eol_centos_repos() +{ + local old_mirrorlist_host="mirrorlist.centos.org" + local old_host="mirror.centos.org" + local new_host="vault.centos.org" + + grep -qFw "$old_host" /etc/yum.repos.d/CentOS-*.repo 2>/dev/null || return 0 + local backup="`mktemp -d "/tmp/yum.repos.d-$(date --rfc-3339=date)-XXXXXX"`" + ! [ -d "$backup" ] || cp -raT /etc/yum.repos.d "$backup" || : + + sed -i \ + -e "s|^\s*\(mirrorlist\b[^/]*//`sed_escape "$old_mirrorlist_host"`/.*\)$|#\1|" \ + -e "s|^#*\s*baseurl\b\([^/]*\)//`sed_escape "$old_host"`/\(.*\)$|baseurl\1//$new_host/\2|" \ + /etc/yum.repos.d/CentOS-*.repo + echo "YUM package manager repositories were backed up to '$backup' and switched from $old_host to $new_host ." >&2 +} + +check_repos_centos7() +{ + switch_eol_centos_repos + + check_repos_centos7_based "$@" +} + +check_repos_cloudlinux7() +{ + check_repos_centos7_based "$@" +} + +check_repos_virtuozzo7() +{ + check_repos_centos7_based "$@" +} + +find_apt_repo() +{ + local repo="$1" + + local dist_tag= + ! [ "$os_name" = "ubuntu" ] || dist_tag="a" + ! [ "$os_name" = "debian" ] || dist_tag="n" + + if [ -z "$_apt_cache_policy" ]; then + # extract info of each available release as a string which consists of 'tag=value' + # filter out releases with priority less or equal to 100 + _apt_cache_policy="$( + apt-cache policy \ + | grep "b=$pkg_arch" \ + | grep -Eo '([a-z]=[^,]+,?)*' \ + )" + fi + + local l="$(echo "$repo" | cut -f1 -d'/')" + local d="$(echo "$repo" | cut -f2 -d'/')" + local c="$(echo "$repo" | cut -f3 -d'/')" + + # try to find releases by distribution and component + echo "$_apt_cache_policy" \ + | grep -E "(^|,)l=$l(,|$)" \ + | grep -E "(^|,)$dist_tag=$d(,|$)" \ + | grep -E "(^|,)c=$c(,|$)" \ + | while IFS="$(printf '\n')" read rel && [ -n "$rel" ]; do + l="$(echo "$rel" | grep -Eo "(^|,)l=[^,]+" | cut -f2 -d"=")" + d="$(echo "$rel" | grep -Eo "(^|,)$dist_tag=[^,]+" | cut -f2 -d"=")" + c="$(echo "$rel" | grep -Eo "(^|,)c=[^,]+" | cut -f2 -d"=")" + echo "$l/$d/$c" + done +} + +apt_install_packages() +{ + DEBIAN_FRONTEND=noninteractive LANG=C PATH=/usr/sbin:/usr/bin:/sbin:/bin \ + apt-get -qq --assume-yes -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold -o APT::Install-Recommends=no \ + install "$@" +} + +# Takes a list of suites and disables them in APT sources. +# Multiline deb822 format is supported. +disable_apt_suites_deb822() +{ + local python3=/usr/bin/python3 + + "$python3" -c 'import aptsources.sourceslist' 2>/dev/null || + apt_install_packages python3-apt + + "$python3" -c ' +import sys + +from aptsources.sourceslist import SourcesList + + +suites_to_disable=set(sys.argv[1:]) + +sources_list = SourcesList(deb822=True) + +sources_changed = False +for src in sources_list: + if src.invalid: + continue + suites = getattr(src, "suites", ()) + if not suites: + continue + new_suites = [s for s in suites if s not in suites_to_disable] + if len(new_suites) != len(suites): + sources_changed = True + if len(new_suites) == 0: + src.disabled = True + else: + src.suites = new_suites + +if sources_changed: + sources_list.save() +' "$@" + + # Since we have changed the repositories list, we should re-read _apt_cache_policy on a next call + # of the find_apt_repo function. Hence we have to reset the value of the variable + _apt_cache_policy="" +} + +disable_apt_repo() +{ + local repos_to_disable="$(find_apt_repo "$1" | cut -d '/' -f 2,3 | sort | uniq)" + if [ -z "$repos_to_disable" ]; then + return 0 + fi + + echo "$repos_to_disable" \ + | while IFS= read -r repo_to_disable && [ -n "$repo_to_disable" ]; do + local distrib=${repo_to_disable%%/*} + local component=${repo_to_disable##*/} + find /etc/apt -name "*.list" -exec \ + sed -i -e "/^\s*#/! s/.*\s$distrib\s\+$component\b/# &/" {} + + done + + # Since we have changed the repositories list, we should re-read _apt_cache_policy on a next call + # of the find_apt_repo function. Hence we have to reset the value of the variable + _apt_cache_policy="" + + return 0 +} + +check_required_apt_repo() +{ + local repo="$1" + [ -z "$(find_apt_repo "$repo")" ] || return 0 + report_no_repo "$repo" + return $RET_FATAL +} + +check_unsupported_apt_repos_ubuntu() +{ + [ -n "$os_codename" ] || return 0 + local mode="$1" + + local repos="$( + find_apt_repo "Ubuntu/[^,]+/[^,]+" | grep -v "Ubuntu/$os_codename.*/.*" + find_apt_repo "Debian[^,]*/[^,]+/[^,]+" + )" + [ -n "$repos" ] || return 0 + + echo "$repos" | while IFS="$(printf '\n')" read repo; do + report_unsupported_repo "$repo" + done + + [ "$mode" = "install" ] || return $RET_WARN + return $RET_FATAL +} + +check_repos_ubuntu18() +{ + [ -n "$os_codename" ] || return 0 + local mode="$1" + local rc=0 + + check_required_apt_repo "Ubuntu/$os_codename/main" || rc="$(( $rc | $? ))" + check_required_apt_repo "Ubuntu/$os_codename/universe" || rc="$(( $rc | $? ))" + check_required_apt_repo "Ubuntu/$os_codename-updates/main" || rc="$(( $rc | $? ))" + check_required_apt_repo "Ubuntu/$os_codename-updates/universe" || rc="$(( $rc | $? ))" + check_unsupported_apt_repos_ubuntu "$mode" || rc="$(( $rc | $? ))" + + return $rc +} + + +check_repos_ubuntu() +{ + [ -n "$os_codename" ] || return 0 + local mode="$1" + local rc=0 + + check_required_apt_repo "Ubuntu/$os_codename/main" || rc="$(( $rc | $? ))" + check_required_apt_repo "Ubuntu/$os_codename/universe" || rc="$(( $rc | $? ))" + check_unsupported_apt_repos_ubuntu "$mode" || rc="$(( $rc | $? ))" + + return $rc +} + +check_unsupported_apt_repos_debian() +{ + [ -n "$os_codename" ] || return 0 + local mode="$1" + + local repos="$( + find_apt_repo "Debian Backports/$os_codename-backports/[^,]+" + find_apt_repo "Debian[^,]*/[^,]+/[^,]+" | grep -v "Debian.*/$os_codename.*/.*" + find_apt_repo "Ubuntu/[^,]+/[^,]+" + )" + [ -n "$repos" ] || return 0 + + echo "$repos" | while IFS="$(printf '\n')" read repo; do + report_unsupported_repo "$repo" + done + + [ "$mode" = "install" ] || return $RET_WARN + return $RET_FATAL +} + +check_repos_debian() +{ + [ -n "$os_codename" ] || return 0 + local mode="$1" + local rc=0 + + if [ "$os_name" = "debian" -a "$os_version" -ge 12 ]; then + disable_apt_suites_deb822 "$os_codename-backports" + else + disable_apt_repo "Debian Backports/$os_codename-backports/[^,]+" + fi + + check_required_apt_repo "Debian/$os_codename/main" || rc="$(( $rc | $? ))" + check_unsupported_apt_repos_debian "$mode" || rc="$(( $rc | $? ))" + + return $rc +} + +# --- + +skip_checker_on_flag "Repository check" "/tmp/plesk-installer-skip-repository-check.flag" + +checker_main 'check_repos' "$1" diff --git a/root/parallels/pool/PSA_18.0.73_17971/examiners/sh_cmd.sh b/root/parallels/pool/PSA_18.0.73_17971/examiners/sh_cmd.sh new file mode 100755 index 0000000000..ed95d0acbb --- /dev/null +++ b/root/parallels/pool/PSA_18.0.73_17971/examiners/sh_cmd.sh @@ -0,0 +1,7 @@ +#!/bin/sh +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +[ "X${PLESK_INSTALLER_DEBUG}" = "X" ] || set -x +[ "X${PLESK_INSTALLER_STRICT_MODE}" = "X" ] || set -e + +exec "$@" diff --git a/root/parallels/pool/PSA_18.0.73_17971/plesk-18.0.73-ubt24.04-x86_64.inf3 b/root/parallels/pool/PSA_18.0.73_17971/plesk-18.0.73-ubt24.04-x86_64.inf3 new file mode 100644 index 0000000000..3d5e3de915 --- /dev/null +++ b/root/parallels/pool/PSA_18.0.73_17971/plesk-18.0.73-ubt24.04-x86_64.inf3 @@ -0,0 +1,927 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -----BEGIN PGP PUBLIC KEY BLOCK----- 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 VNnMRY8n =Hv0N -----END PGP PUBLIC KEY BLOCK----- + + + psa + + + + + + + + + + + + + + + + + + + + + + + + + + + + + mysqlgroup + l10n + proftpd + webservers + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + imapservers + + + + + + + + + + + + + + + + + + + imapservers + + + + + + + + + + + + + + mailman + spamassassin + drweb + sophos + courier + dovecot + + + + + + + + + + + + + + + + + + + + + + mailservers + + + + + + + + + + + + + + + + + + + + mailservers + + + + + + + + + + + + + + + + + + panel + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + + + + + + + + php8.3 + + + + + + + + + + + webservers + + + + + + + + + + + + + + + + + + + + + panel + + + + + + + + + + + + + panel + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + panel + + + + + + + + + + + + + + + + + + + + panel + + + + + + + panel + + + + + + + + panel + + + + + + + panel + passenger + + + + + + ruby + + + + + + + panel + + + + + + panel + + + + + + panel + passenger + + + + + + + + + + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + panel + roundcube + postfix + dovecot + mod_fcgid + proftpd + webalizer + awstats + webservers + nginx + mysqlgroup + l10n + bind + wp-toolkit + advisor + git + xovi + imunify360 + fail2ban + modsecurity + sslit + letsencrypt + repair-kit + composer + monitoring + log-browser + ssh-terminal + site-import + sitejet + ntp-timesync + php8.3 + php8.4 + mfa + configurations-troubleshooter + + + panel + roundcube + postfix + dovecot + mod_fcgid + proftpd + webalizer + awstats + webservers + nginx + mysqlgroup + l10n + bind + wp-toolkit + advisor + git + xovi + imunify360 + fail2ban + modsecurity + sslit + letsencrypt + repair-kit + composer + monitoring + log-browser + ssh-terminal + site-import + sitejet + ntp-timesync + php8.1 + php8.2 + php8.3 + php8.4 + mfa + configurations-troubleshooter + resctrl + drweb + postgresql + spamassassin + ruby + gems-pre + nodejs + pmm + psa-firewall + watchdog + passenger + phpgroup + sophos + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/pool/PSA_18.0.73_17971/release.inf3 b/root/parallels/pool/PSA_18.0.73_17971/release.inf3 new file mode 100644 index 0000000000..4fb000283d --- /dev/null +++ b/root/parallels/pool/PSA_18.0.73_17971/release.inf3 @@ -0,0 +1,36 @@ + + + + + + + + + psa + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/pool/PSA_18.0.74_17897/release.inf3 b/root/parallels/pool/PSA_18.0.74_17897/release.inf3 new file mode 100644 index 0000000000..bcac0846cb --- /dev/null +++ b/root/parallels/pool/PSA_18.0.74_17897/release.inf3 @@ -0,0 +1,36 @@ + + + + + + + + + psa + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/pool/PSA_18.0.74_17941/release.inf3 b/root/parallels/pool/PSA_18.0.74_17941/release.inf3 new file mode 100644 index 0000000000..bcac0846cb --- /dev/null +++ b/root/parallels/pool/PSA_18.0.74_17941/release.inf3 @@ -0,0 +1,36 @@ + + + + + + + + + psa + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/pool/PSA_18.0.74_17968/release.inf3 b/root/parallels/pool/PSA_18.0.74_17968/release.inf3 new file mode 100644 index 0000000000..bcac0846cb --- /dev/null +++ b/root/parallels/pool/PSA_18.0.74_17968/release.inf3 @@ -0,0 +1,36 @@ + + + + + + + + + psa + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/pool/PSA_18.0.74_18022/examiners/check_broken_timezone.sh b/root/parallels/pool/PSA_18.0.74_18022/examiners/check_broken_timezone.sh new file mode 100755 index 0000000000..ee862642be --- /dev/null +++ b/root/parallels/pool/PSA_18.0.74_18022/examiners/check_broken_timezone.sh @@ -0,0 +1,255 @@ +#!/bin/bash +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +[ -z "$PLESK_INSTALLER_DEBUG" ] || set -x +[ -z "$PLESK_INSTALLER_STRICT_MODE" ] || set -e + +export LC_ALL=C +unset GREP_OPTIONS + +RET_SUCCESS=0 +RET_WARN=1 +RET_FATAL=2 + +is_function_defined() +{ + local fn="$1" + case "$(type $fn 2>/dev/null)" in + *function*) + return 0 + ;; + esac + return 1 +} + +# @params are tags in format "key=value" +# Report body (human readable information) is read from stdin +# and copied to stderr. +make_error_report() +{ + local report_file="${PLESK_INSTALLER_ERROR_REPORT:-}" + + local python_bin= + for bin in "/opt/psa/bin/python" "/usr/local/psa/bin/python" "/usr/bin/python2" "/opt/psa/bin/py3-python" "/usr/local/psa/bin/py3-python" "/usr/libexec/platform-python" "/usr/bin/python3"; do + if [ -x "$bin" ]; then + python_bin="$bin" + break + fi + done + + if [ -n "$report_file" -a -x "$python_bin" ]; then + "$python_bin" -c 'import sys, json +report_file = sys.argv[1] +error = sys.stdin.read() + +sys.stderr.write(error) + +data = { + "error": error, +} + +for tag in sys.argv[2:]: + k, v = tag.split("=", 1) + data[k] = v + +with open(report_file, "a") as f: + json.dump(data, f) + f.write("\n") +' "$report_file" "date=$(date --utc --iso-8601=ns)" "$@" + else + cat - >&2 + fi +} + +detect_platform() +{ + . /etc/os-release + os_name="$ID" + os_version="${VERSION_ID%%.*}" + os_arch="$(uname -m)" + if [ -e /etc/debian_version ]; then + case "$os_arch" in + x86_64) pkg_arch="amd64" ;; + aarch64) pkg_arch="arm64" ;; + esac + if [ -n "$VERSION_CODENAME" ]; then + os_codename="$VERSION_CODENAME" + else + case "$os_name$os_version" in + debian10) os_codename="buster" ;; + debian11) os_codename="bullseye" ;; + debian12) os_codename="bookworm" ;; + ubuntu18) os_codename="bionic" ;; + ubuntu20) os_codename="focal" ;; + ubuntu22) os_codename="jammy" ;; + ubuntu24) os_codename="noble" ;; + esac + fi + fi + + case "$os_name$os_version" in + rhel7|centos7|cloudlinux7|virtuozzo7) + package_manager="yum" + ;; + rhel*|centos*|cloudlinux*|almalinux*|rocky*) + package_manager="dnf" + ;; + debian*|ubuntu*) + package_manager="apt" + ;; + esac + + if [ "$os_name" = "ubuntu" -o "$os_name" = "debian" ]; then + PRODUCT_ROOT_D="/opt/psa" + else + PRODUCT_ROOT_D="/usr/local/psa" + fi +} + +has_os_impl_function() +{ + local prefix="$1" + local fn="${prefix}_${os_name}${os_version}" + is_function_defined "$fn" +} + +call_os_impl_function() +{ + local prefix="$1" + shift + local fn="${prefix}_${os_name}${os_version}" + "$fn" "$@" +} + +skip_checker_on_flag() +{ + local name="$1" + local flag="$2" + + if [ -f "$flag" ]; then + echo "$name was skipped due to flag file." >&2 + exit $RET_SUCCESS + fi +} + +skip_checker_on_env() +{ + local name="$1" + local env="$2" + + if [ -n "$env" ]; then + echo "$name was skipped due to environment variable." >&2 + exit $RET_SUCCESS + fi +} + +checker_main() +{ + local fnprefix="$1" + shift + + detect_platform + # try to execute checker only if all attributes are detected + [ -n "$os_name" -a -n "$os_version" ] || return $RET_SUCCESS + + for checker in "${fnprefix}_${os_name}${os_version}" "${fnprefix}_${os_name}" "${fnprefix}"; do + if is_function_defined "$checker"; then + local rc=$RET_SUCCESS + "$checker" "$@" || rc=$? + [ "$(( $rc & $RET_FATAL ))" = "0" ] || return $RET_FATAL + [ "$(( $rc & $RET_WARN ))" = "0" ] || return $RET_WARN + return $rc + fi + done + return $RET_SUCCESS +} + +#!/bin/sh +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +# If env variable PLESK_INSTALLER_ERROR_REPORT=path_to_file is specified then in case of error +# check-broken-tz.sh writes single line json report into it with the following fields: +# - "stage": "timezonefix" +# - "level": "error" +# - "errtype": "failure" +# - "date": time of error occurance ("2024-07-24T06:59:43,127545441+0000") +# - "error": human readable error message + +report_dpkg_configure_fail() +{ + local pkgname="$1" + make_error_report 'stage=timezonefix' 'level=error' 'errtype=dpkgconfigurefailed' <<-EOL + Could not configure the packages ( $pkgname ). See https://support.plesk.com/hc/en-us/articles/24721507961623-Plesk-provides-error-on-update-Package-tzdata-is-not-configured-yet for more details. + EOL +} + +report_get_tz_fail() +{ + make_error_report 'stage=timezonefix' 'level=error' 'errtype=gettzfailed' <<-EOL + Could not get the system timezone. See https://support.plesk.com/hc/en-us/articles/24721507961623-Plesk-provides-error-on-update-Package-tzdata-is-not-configured-yet for more details. + EOL +} + +report_set_tz_fail() +{ + local tz="$1" + + make_error_report 'stage=timezonefix' 'level=error' 'errtype=settzfailed' <<-EOL + Could not set the system timezone ( $tz ). See https://support.plesk.com/hc/en-us/articles/24721507961623-Plesk-provides-error-on-update-Package-tzdata-is-not-configured-yet for more details. + EOL +} + +get_current_tz() +{ + [ -L /etc/localtime ] || return 1 + + local tz + tz="$(readlink -m /etc/localtime)" || return 1 + [ -f "$tz" ] || return 1 + case "$tz" in + /usr/share/zoneinfo/*) ;; + *) return 1;; + esac + tz="${tz#/usr/share/zoneinfo/}" + [ -n "$tz" ] || return 1 + + echo -n "${tz}" +} + +check_timezone_ubuntu() +{ + [ -n "$os_codename" ] || return 0 + local mode="$1" + + # PPP-65676: Plesk update fails on ubuntu if timezone is CET + if dpkg-query --showformat='${db:Status-Status}\n' --show 'tzdata' | grep -wq 'half-configured'; then + local origtz + origtz=$(get_current_tz) + if [ $? != 0 ]; then + report_get_tz_fail + return $RET_WARN + fi + if ! timedatectl set-timezone 'Etc/UTC'; then + timedatectl set-timezone "$origtz" + report_set_tz_fail 'Etc/UTC' + return $RET_WARN + fi + if ! dpkg --configure 'tzdata'; then + timedatectl set-timezone "$origtz" + report_dpkg_configure_fail 'tzdata' + return $RET_WARN + fi + if ! timedatectl set-timezone "$origtz"; then + report_set_tz_fail "$origtz" + return $RET_WARN + fi + fi + + return 0 +} + +# --- + +skip_checker_on_flag "Broken timezone check" "/tmp/plesk-installer-skip-check-broken-timezone.flag" + +checker_main 'check_timezone' "$1" diff --git a/root/parallels/pool/PSA_18.0.74_18022/examiners/congratulations.sh b/root/parallels/pool/PSA_18.0.74_18022/examiners/congratulations.sh new file mode 100755 index 0000000000..907c5ba782 --- /dev/null +++ b/root/parallels/pool/PSA_18.0.74_18022/examiners/congratulations.sh @@ -0,0 +1,50 @@ +#!/bin/bash +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +out() +{ + echo -e "\t$*" >&2 +} + +print_urls() +{ + plesk login 2>/dev/null | sed -e $'s|^|\t * |' >&2 +} + +print_congratulations() +{ + local mode="$1" # 'install' or 'upgrade' + local process= + [ "$mode" = "install" ] && process="installation" || process="upgrade" + + out + out " Congratulations!" + out + out "The $process has been finished. Plesk is now running on your server." + out + if [ "$mode" = "install" ]; then + out "To complete the configuration process, browse either of URLs:" + print_urls + out + fi + out "Use the username 'admin' to log in. To log in as 'admin', use the 'plesk login' command." + out "You can also log in as 'root' using your 'root' password." + out + out "Use the 'plesk' command to manage the server. Run 'plesk help' for more info." + out + out "Use the following commands to start and stop the Plesk web interface:" + out "'systemctl start psa.service' and 'systemctl stop psa.service' respectively." + out + if [ "$mode" = "install" ]; then + out "If you would like to migrate your subscriptions from other hosting panel" + out "or older Plesk version to this server, please check out our assistance" + out "options: https://www.plesk.com/professional-services/" + out + fi +} + +unset GREP_OPTIONS + +print_congratulations "$1" +# Force showing text when used as AI post-examiner +exit 1 diff --git a/root/parallels/pool/PSA_18.0.74_18022/examiners/disk_space_check.sh b/root/parallels/pool/PSA_18.0.74_18022/examiners/disk_space_check.sh new file mode 100755 index 0000000000..1fdfb44037 --- /dev/null +++ b/root/parallels/pool/PSA_18.0.74_18022/examiners/disk_space_check.sh @@ -0,0 +1,542 @@ +#!/bin/bash +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +[ -z "$PLESK_INSTALLER_DEBUG" ] || set -x +[ -z "$PLESK_INSTALLER_STRICT_MODE" ] || set -e + +export LC_ALL=C +unset GREP_OPTIONS + +RET_SUCCESS=0 +RET_WARN=1 +RET_FATAL=2 + +is_function_defined() +{ + local fn="$1" + case "$(type $fn 2>/dev/null)" in + *function*) + return 0 + ;; + esac + return 1 +} + +# @params are tags in format "key=value" +# Report body (human readable information) is read from stdin +# and copied to stderr. +make_error_report() +{ + local report_file="${PLESK_INSTALLER_ERROR_REPORT:-}" + + local python_bin= + for bin in "/opt/psa/bin/python" "/usr/local/psa/bin/python" "/usr/bin/python2" "/opt/psa/bin/py3-python" "/usr/local/psa/bin/py3-python" "/usr/libexec/platform-python" "/usr/bin/python3"; do + if [ -x "$bin" ]; then + python_bin="$bin" + break + fi + done + + if [ -n "$report_file" -a -x "$python_bin" ]; then + "$python_bin" -c 'import sys, json +report_file = sys.argv[1] +error = sys.stdin.read() + +sys.stderr.write(error) + +data = { + "error": error, +} + +for tag in sys.argv[2:]: + k, v = tag.split("=", 1) + data[k] = v + +with open(report_file, "a") as f: + json.dump(data, f) + f.write("\n") +' "$report_file" "date=$(date --utc --iso-8601=ns)" "$@" + else + cat - >&2 + fi +} + +detect_platform() +{ + . /etc/os-release + os_name="$ID" + os_version="${VERSION_ID%%.*}" + os_arch="$(uname -m)" + if [ -e /etc/debian_version ]; then + case "$os_arch" in + x86_64) pkg_arch="amd64" ;; + aarch64) pkg_arch="arm64" ;; + esac + if [ -n "$VERSION_CODENAME" ]; then + os_codename="$VERSION_CODENAME" + else + case "$os_name$os_version" in + debian10) os_codename="buster" ;; + debian11) os_codename="bullseye" ;; + debian12) os_codename="bookworm" ;; + ubuntu18) os_codename="bionic" ;; + ubuntu20) os_codename="focal" ;; + ubuntu22) os_codename="jammy" ;; + ubuntu24) os_codename="noble" ;; + esac + fi + fi + + case "$os_name$os_version" in + rhel7|centos7|cloudlinux7|virtuozzo7) + package_manager="yum" + ;; + rhel*|centos*|cloudlinux*|almalinux*|rocky*) + package_manager="dnf" + ;; + debian*|ubuntu*) + package_manager="apt" + ;; + esac + + if [ "$os_name" = "ubuntu" -o "$os_name" = "debian" ]; then + PRODUCT_ROOT_D="/opt/psa" + else + PRODUCT_ROOT_D="/usr/local/psa" + fi +} + +has_os_impl_function() +{ + local prefix="$1" + local fn="${prefix}_${os_name}${os_version}" + is_function_defined "$fn" +} + +call_os_impl_function() +{ + local prefix="$1" + shift + local fn="${prefix}_${os_name}${os_version}" + "$fn" "$@" +} + +skip_checker_on_flag() +{ + local name="$1" + local flag="$2" + + if [ -f "$flag" ]; then + echo "$name was skipped due to flag file." >&2 + exit $RET_SUCCESS + fi +} + +skip_checker_on_env() +{ + local name="$1" + local env="$2" + + if [ -n "$env" ]; then + echo "$name was skipped due to environment variable." >&2 + exit $RET_SUCCESS + fi +} + +checker_main() +{ + local fnprefix="$1" + shift + + detect_platform + # try to execute checker only if all attributes are detected + [ -n "$os_name" -a -n "$os_version" ] || return $RET_SUCCESS + + for checker in "${fnprefix}_${os_name}${os_version}" "${fnprefix}_${os_name}" "${fnprefix}"; do + if is_function_defined "$checker"; then + local rc=$RET_SUCCESS + "$checker" "$@" || rc=$? + [ "$(( $rc & $RET_FATAL ))" = "0" ] || return $RET_FATAL + [ "$(( $rc & $RET_WARN ))" = "0" ] || return $RET_WARN + return $rc + fi + done + return $RET_SUCCESS +} + +#!/bin/sh +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +# If env variable PLESK_INSTALLER_ERROR_REPORT=path_to_file is specified then in case of error +# disk_space_check.sh writes single line json report into it with the following fields: +# - "stage": "diskspacecheck" +# - "level": "error" +# - "errtype": "notenoughdiskspace" +# - "volume": volume with not enough diskspace (e.g. "/") +# - "required": required diskspace on the volume, human readable (e.g. "600 MB") +# - "available": available diskspace on the volume, human readable (e.g. "255 MB") +# - "needtofree": amount of diskspace which should be freed on the volume, human readable (e.g. "345 MB") +# - "date": time of error occurance ("2020-03-24T06:59:43,127545441+0000") +# - "error": human readable error message ("There is not enough disk space available in the / directory.") + +# Required values below for Full installation are in MB. See 'du -cs -BM /*' and 'df -Pm'. + +required_disk_space_cloudlinux7() +{ + case "$1" in + /opt) echo 900 ;; + /usr) echo 4400 ;; + /var) echo 600 ;; + /tmp) echo 100 ;; + esac +} + +required_disk_space_cloudlinux8() +{ + case "$1" in + /opt) echo 1200 ;; + /usr) echo 4400 ;; + /var) echo 700 ;; + /tmp) echo 100 ;; + esac +} + +required_disk_space_centos7() +{ + case "$1" in + /opt) echo 900 ;; + /usr) echo 4100 ;; + /var) echo 600 ;; + /tmp) echo 100 ;; + esac +} + +required_disk_space_centos8() +{ + case "$1" in + /opt) echo 900 ;; + /usr) echo 4500 ;; + /var) echo 800 ;; + /tmp) echo 100 ;; + esac +} + +required_disk_space_virtuozzo7() +{ + required_disk_space_centos7 "$1" +} + +required_disk_space_rhel7() +{ + required_disk_space_centos7 "$1" +} + +required_disk_space_rhel8() +{ + required_disk_space_centos8 "$1" +} + +required_disk_space_almalinux8() +{ + required_disk_space_centos8 "$1" +} + +required_disk_space_rocky8() +{ + required_disk_space_centos8 "$1" +} + +required_disk_space_rhel9() +{ + case "$1" in + /opt) echo 500 ;; + /usr) echo 4000 ;; + /var) echo 800 ;; + /tmp) echo 100 ;; + esac +} + +required_disk_space_almalinux9() +{ + required_disk_space_rhel9 "$1" +} + +required_disk_space_almalinux10() +{ + required_disk_space_almalinux9 "$1" +} + +required_disk_space_cloudlinux9() +{ + required_disk_space_rhel9 "$1" +} + +required_disk_space_debian10() +{ + case "$1" in + /opt) echo 1800 ;; + /usr) echo 2300 ;; + /var) echo 1700 ;; + /tmp) echo 100 ;; + esac +} + +required_disk_space_debian11() +{ + case "$1" in + /opt) echo 1500 ;; + /usr) echo 3100 ;; + /var) echo 1800 ;; + /tmp) echo 100 ;; + esac +} + +required_disk_space_debian12() +{ + case "$1" in + /opt) echo 2700 ;; + /usr) echo 2500 ;; + /var) echo 2200 ;; + /tmp) echo 100 ;; + esac +} + +required_disk_space_debian13() +{ + case "$1" in + /opt) echo 2700 ;; + /usr) echo 2500 ;; + /var) echo 2200 ;; + /tmp) echo 100 ;; + esac +} + +required_disk_space_ubuntu18() +{ + case "$1" in + /opt) echo 900 ;; + /usr) echo 1800 ;; + /var) echo 600 ;; + /tmp) echo 100 ;; + esac +} + +required_disk_space_ubuntu20() +{ + case "$1" in + /opt) echo 1800 ;; + /usr) echo 2900 ;; + /var) echo 1600 ;; + /tmp) echo 100 ;; + esac +} + +required_disk_space_ubuntu22() +{ + case "$1" in + /opt) echo 1800 ;; + /usr) echo 3900 ;; + /var) echo 1900 ;; + /tmp) echo 100 ;; + esac +} + +required_disk_space_ubuntu24() +{ + case "$1" in + /opt) echo 3200 ;; + /usr) echo 1800 ;; + /var) echo 2400 ;; + /tmp) echo 100 ;; + esac +} + +required_update_upgrade_disk_space() +{ + case "$1" in + /opt) echo 100 ;; + /usr) echo 300 ;; + /var) echo 600 ;; + /tmp) echo 100 ;; + esac +} + +clean_tmp() +{ + local volume="$1" + local path="/tmp" + is_path_on_volume "$path" "$volume" || return 0 + + echo "Cleaning $path via 'systemd-tmpfiles --clean --prefix $path'" + systemd-tmpfiles --clean --prefix "$path" 2>&1 +} + +clean_yum() +{ + local volume="$1" + local path="/var/cache/yum" + is_path_on_volume "$path" "$volume" || return 0 + + echo "Cleaning $path via 'yum clean all'" + yum clean all 2>&1 + + # The command above doesn't clean untracked repos (missing in configuration), clean if left > 2 Mb + [ "`du -sm "$path" | awk '{ print $1 }'`" -gt 2 ] || return 0 + echo "Cleaning $path via 'rm -rf $path/*'" + rm -rf "$path"/* 2>&1 +} + +clean_dnf() +{ + local volume="$1" + local path="/var/cache/dnf" + is_path_on_volume "$path" "$volume" || return 0 + + echo "Cleaning $path via 'dnf clean all'" + dnf clean all 2>&1 +} + +clean_apt() +{ + local volume="$1" + local path="/var/cache/apt" + is_path_on_volume "$path" "$volume" || return 0 + + echo "Cleaning $path via 'apt-get clean'" + apt-get clean 2>&1 +} + +clean_journal() +{ + local volume="$1" + local path="/var/log/journal" + is_path_on_volume "$path" "$volume" || return 0 + + # Note that --rotate may cause more space to be freed, but may also cause more space to be used + echo "Cleaning $path via 'journalctl --vacuum-time 1d'" + journalctl --vacuum-time 1d 2>&1 +} + +clean_ext_packages() +{ + local volume="$1" + local path="$PRODUCT_ROOT_D/var/modules-packages" + is_path_on_volume "$path" "$volume" || return 0 + + echo "Cleaning $path via 'rm -rf $path/*'" + rm -rf "$path"/* 2>&1 +} + +# @param $1 target directory +mount_point() +{ + df -Pm $1 | awk 'NR==2 { print $6 }' +} + +# @param $1 target directory +available_disk_space() +{ + df -Pm $1 | awk 'NR==2 { print $4 }' +} + +is_path_on_volume() +{ + local path="$1" + local volume="$2" + [ -d "$path" ] && [ "`mount_point "$path"`" = "$volume" ] +} + +# @param $1 target directory +# @param $2 mode (install/upgrade/update) +req_disk_space() +{ + if [ "$2" != "install" ]; then + required_update_upgrade_disk_space "$1" + return + fi + + has_os_impl_function "required_disk_space" || { + echo "There are no requirements defined for $os_name$os_version." >&2 + echo "Disk space check cannot be performed." >&2 + exit $RET_WARN + } + call_os_impl_function "required_disk_space" "$1" +} + +human_readable_size() +{ + echo "$1" | awk ' + function human(x) { + s = "MGTEPYZ"; + while (x >= 1000 && length(s) > 1) { + x /= 1024; s = substr(s, 2); + } + # 0.05 below will make sure the value is rounded up + return sprintf("%.1f %sB", x + 0.05, substr(s, 1, 1)); + } + { print human($1); }' +} + +# @param $1 target directory +# @param $2 required disk space +# @param $3 check only flag (don't emit errors) +check_available_disk_space() +{ + local volume="$1" + local required="$2" + local check_only="${3:-}" + local available="$(available_disk_space "$volume")" + if [ "$available" -lt "$required" ]; then + local needtofree + needtofree="`human_readable_size $((required - available))`" + [ -n "$check_only" ] || + make_error_report 'stage=diskspacecheck' 'level=error' 'errtype=notenoughdiskspace' \ + "volume=$volume" "required=$required MB" "available=$available MB" "needtofree=$needtofree" \ + <<-EOL + There is not enough disk space available in the $1 directory. + You need to free up $needtofree. + EOL + return "$RET_FATAL" + fi +} + +# @param $1 target directory +# @param $2 required disk space +clean_and_check_available_disk_space() +{ + if [ -n "$PLESK_INSTALLER_FORCE_CLEAN_DISK_SPACE" ] || ! check_available_disk_space "$@" --check-only; then + clean_disk_space "$1" + check_available_disk_space "$@" + fi +} + +# Cleans up disk space on the volume +clean_disk_space() +{ + local volume="$1" + for cleanup_func in clean_tmp clean_yum clean_dnf clean_apt clean_journal clean_ext_packages; do + "$cleanup_func" "$volume" + done +} + +# @param $1 mode (install/upgrade/update) +clean_and_check_disk_space() +{ + local mode="$1" + local shared=0 + + for target_directory in /opt /usr /var /tmp; do + local required=$(req_disk_space "$target_directory" "$mode") + [ -n "$required" ] || return "$RET_WARN" + + if is_path_on_volume "$target_directory" "/"; then + shared="$((shared + required))" + else + clean_and_check_available_disk_space "$target_directory" "$required" || return $? + fi + done + + clean_and_check_available_disk_space "/" "$shared" || return $? +} + +checker_main 'clean_and_check_disk_space' "$1" diff --git a/root/parallels/pool/PSA_18.0.74_18022/examiners/license_key_check.php b/root/parallels/pool/PSA_18.0.74_18022/examiners/license_key_check.php new file mode 100644 index 0000000000..465fdbe9b8 --- /dev/null +++ b/root/parallels/pool/PSA_18.0.74_18022/examiners/license_key_check.php @@ -0,0 +1,111 @@ += 10.0.0 */ +if (!is_array($vers)) { + $vers = [$vers]; +} + +$match = false; +foreach ($vers as $ver) { + if (!is_array($ver)) { + $match |= strtok($ver, ".") == strtok($targetVersion, "."); + } else { + $match |= ("any" == $ver[0] || version_compare($ver[0], $targetVersion) <= 0) && + ("any" == $ver[1] || version_compare($ver[1], $targetVersion) >= 0); + } +} + +if ($match) { + fwrite(STDERR, "You do not need to upgrade the current license key.\n"); + fwrite(STDOUT, "License upgrade check to $targetVersion can be skipped.\n"); + fwrite(STDOUT, "Plesk versions compatible with the license key: " . preg_replace('/\n\s*/', '', var_export($vers, true)) . "\n"); + finish(0); +} + +if (!function_exists('ka_is_key_upgrade_available')) { + // Plesk 17.0 + fwrite(STDERR, "Cannot check whether Plesk license key upgrade is available.\n"); + finish(1, false); +} + +$si = getServerInfo(); +$result = ka_is_key_upgrade_available($prod, $targetVersion, $si); + +$isConfused = false; +switch ($result['code']) { + case RESULT_LICENSE_OK: + fwrite(STDERR, "The licensing server accepted the key upgrade request.\n"); + fwrite(STDERR, "License upgrade to $targetVersion is available.\n"); + fwrite(STDERR, "Response from the licensing server: {$result['message']}\n"); + finish(0); + case RESULT_NETWORK_PROBLEM: + fwrite(STDERR, "Unable to connect to the licensing server to check if license upgrade is available.\n"); + fwrite(STDERR, "Error message: {$result['message']}\n"); + finish(2, false); + case RESULT_LICENSE_PROBLEM: + fwrite(STDERR, "Warning: Your Plesk license key cannot be upgraded.\n"); + fwrite(STDERR, "Response from the licensing server: {$result['message']}\n"); + finish(2); + default: + $isConfused = true; + // fall-through + case RESULT_ERROR: + // This includes "Software Update Service (SUS) is not found for the given license key" case, but also many others. + fwrite(STDERR, "Failed to check whether a new license key is available.\n"); + fwrite(STDERR, "Error message: {$result['message']}\n"); + if ($isConfused) { + fwrite(STDERR, "Error code: {$result['code']}\n"); + } + finish(2, !$isConfused); +} diff --git a/root/parallels/pool/PSA_18.0.74_18022/examiners/package_manager_check.sh b/root/parallels/pool/PSA_18.0.74_18022/examiners/package_manager_check.sh new file mode 100755 index 0000000000..b089061d97 --- /dev/null +++ b/root/parallels/pool/PSA_18.0.74_18022/examiners/package_manager_check.sh @@ -0,0 +1,224 @@ +#!/bin/bash +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +[ -z "$PLESK_INSTALLER_DEBUG" ] || set -x +[ -z "$PLESK_INSTALLER_STRICT_MODE" ] || set -e + +export LC_ALL=C +unset GREP_OPTIONS + +RET_SUCCESS=0 +RET_WARN=1 +RET_FATAL=2 + +is_function_defined() +{ + local fn="$1" + case "$(type $fn 2>/dev/null)" in + *function*) + return 0 + ;; + esac + return 1 +} + +# @params are tags in format "key=value" +# Report body (human readable information) is read from stdin +# and copied to stderr. +make_error_report() +{ + local report_file="${PLESK_INSTALLER_ERROR_REPORT:-}" + + local python_bin= + for bin in "/opt/psa/bin/python" "/usr/local/psa/bin/python" "/usr/bin/python2" "/opt/psa/bin/py3-python" "/usr/local/psa/bin/py3-python" "/usr/libexec/platform-python" "/usr/bin/python3"; do + if [ -x "$bin" ]; then + python_bin="$bin" + break + fi + done + + if [ -n "$report_file" -a -x "$python_bin" ]; then + "$python_bin" -c 'import sys, json +report_file = sys.argv[1] +error = sys.stdin.read() + +sys.stderr.write(error) + +data = { + "error": error, +} + +for tag in sys.argv[2:]: + k, v = tag.split("=", 1) + data[k] = v + +with open(report_file, "a") as f: + json.dump(data, f) + f.write("\n") +' "$report_file" "date=$(date --utc --iso-8601=ns)" "$@" + else + cat - >&2 + fi +} + +detect_platform() +{ + . /etc/os-release + os_name="$ID" + os_version="${VERSION_ID%%.*}" + os_arch="$(uname -m)" + if [ -e /etc/debian_version ]; then + case "$os_arch" in + x86_64) pkg_arch="amd64" ;; + aarch64) pkg_arch="arm64" ;; + esac + if [ -n "$VERSION_CODENAME" ]; then + os_codename="$VERSION_CODENAME" + else + case "$os_name$os_version" in + debian10) os_codename="buster" ;; + debian11) os_codename="bullseye" ;; + debian12) os_codename="bookworm" ;; + ubuntu18) os_codename="bionic" ;; + ubuntu20) os_codename="focal" ;; + ubuntu22) os_codename="jammy" ;; + ubuntu24) os_codename="noble" ;; + esac + fi + fi + + case "$os_name$os_version" in + rhel7|centos7|cloudlinux7|virtuozzo7) + package_manager="yum" + ;; + rhel*|centos*|cloudlinux*|almalinux*|rocky*) + package_manager="dnf" + ;; + debian*|ubuntu*) + package_manager="apt" + ;; + esac + + if [ "$os_name" = "ubuntu" -o "$os_name" = "debian" ]; then + PRODUCT_ROOT_D="/opt/psa" + else + PRODUCT_ROOT_D="/usr/local/psa" + fi +} + +has_os_impl_function() +{ + local prefix="$1" + local fn="${prefix}_${os_name}${os_version}" + is_function_defined "$fn" +} + +call_os_impl_function() +{ + local prefix="$1" + shift + local fn="${prefix}_${os_name}${os_version}" + "$fn" "$@" +} + +skip_checker_on_flag() +{ + local name="$1" + local flag="$2" + + if [ -f "$flag" ]; then + echo "$name was skipped due to flag file." >&2 + exit $RET_SUCCESS + fi +} + +skip_checker_on_env() +{ + local name="$1" + local env="$2" + + if [ -n "$env" ]; then + echo "$name was skipped due to environment variable." >&2 + exit $RET_SUCCESS + fi +} + +checker_main() +{ + local fnprefix="$1" + shift + + detect_platform + # try to execute checker only if all attributes are detected + [ -n "$os_name" -a -n "$os_version" ] || return $RET_SUCCESS + + for checker in "${fnprefix}_${os_name}${os_version}" "${fnprefix}_${os_name}" "${fnprefix}"; do + if is_function_defined "$checker"; then + local rc=$RET_SUCCESS + "$checker" "$@" || rc=$? + [ "$(( $rc & $RET_FATAL ))" = "0" ] || return $RET_FATAL + [ "$(( $rc & $RET_WARN ))" = "0" ] || return $RET_WARN + return $rc + fi + done + return $RET_SUCCESS +} + +#!/bin/sh +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +check_package_manager_deb_based() +{ + local output= + output="`dpkg --audit 2>&1`" || output="$output"$'\n'"'dpkg --audit' finished with error code $?." + + if [ -n "$output" ]; then + make_error_report 'stage=packagemanagercheck' 'level=error' 'errtype=brokenpackages' <<-EOL + The system package manager reports the following problems: + + $output + + To continue with the installation, you need to resolve these issues + using the procedure below: + + 1. Make sure you have a full server snapshot. Although the + following steps are usually safe, they can still cause + data loss or irreversible changes. + 2. Run 'dpkg --configure -a'. This command can fix some of the + issues. However, it may fail. Regardless if it fails or not, + proceed with the following steps. + 3. Run 'PLESK_INSTALLER_SKIP_PACKAGE_MANAGER_CHECK=1 plesk installer update --skip-cleanup'. + Instead of 'update', you may need to use the command you used + previously (for example, 'upgrade' or 'install'). + 4. The next step depends on the outcome of the previous one: + - If step 3 was completed with the "You already have the latest + version of product(s) and all the selected components installed. + Installation will not continue." message, + run 'plesk repair installation'. + - If step 3 failed, run 'dpkg --audit'. This command can show you + packages that need to be reinstalled. To reinstall them, run + 'apt-get install --reinstall '. + 5. Run 'plesk installer update' to revert temporary changes and + validate that the issues are resolved. If the command fails or + triggers this check again, contact Plesk support. + + For more information, see + https://support.plesk.com/hc/en-us/articles/12871173047447-Plesk-update-on-Debian-Ubuntu-fails-dpkg-was-interrupted-you-must-manually-run-dpkg-configure-a-to-correct-the-problem + EOL + return "$RET_FATAL" + fi +} + +check_package_manager_debian() +{ + check_package_manager_deb_based +} + +check_package_manager_ubuntu() +{ + check_package_manager_deb_based +} + +skip_checker_on_env "Package manager check" "$PLESK_INSTALLER_SKIP_PACKAGE_MANAGER_CHECK" +skip_checker_on_flag "Package manager check" "/tmp/plesk-installer-skip-package-manager-check.flag" +checker_main 'check_package_manager' "$@" diff --git a/root/parallels/pool/PSA_18.0.74_18022/examiners/panel_preupgrade_checker.php b/root/parallels/pool/PSA_18.0.74_18022/examiners/panel_preupgrade_checker.php new file mode 100644 index 0000000000..ce983f8614 --- /dev/null +++ b/root/parallels/pool/PSA_18.0.74_18022/examiners/panel_preupgrade_checker.php @@ -0,0 +1,2441 @@ +_checkMainIP(); //:INFO: Checking for main IP address https://support.plesk.com/hc/articles/12377857361687 + $this->_checkMySQLDatabaseUserRoot(); //:INFO: Plesk user "root" for MySQL database servers have not access to phpMyAdmin https://support.plesk.com/hc/articles/12378148229399 + $this->_checkProftpdIPv6(); //:INFO: #94489 FTP service proftpd cannot be started by xinetd if IPv6 is disabled https://support.plesk.com/hc/articles/12377796102807 + $this->_checkSwCollectdIntervalSetting(); //:INFO: #105405 https://support.plesk.com/hc/articles/213362569 + $this->_checkApacheStatus(); + $this->_checkImmutableBitOnPleskFiles(); //:INFO: #128414 https://support.plesk.com/hc/articles/12377589682327 + $this->_checkAbilityToChmodInDumpd(); //:INFO: ERROR while trying to backup MySQL database. https://support.plesk.com/hc/en-us/articles/12377010033943 + $this->_checkIpcollectionReference(); //:INFO: #72751 https://support.plesk.com/hc/articles/12377666752279 + $this->_checkApsApplicationContext(); //:INFO: Broken contexts of the APS applications can lead to errors at building Apache web server configuration https://support.plesk.com/hc/articles/12377671820311 + $this->_checkApsTablesInnoDB(); + $this->_checkCustomWebServerConfigTemplates(); //:INFO: #PPPM-1195 https://support.plesk.com/hc/articles/12377740416151 + $this->_checkMixedCaseDomainIssues(); //:INFO: #PPPM-4284 https://support.plesk.com/hc/en-us/articles/12377171904151 + + if (PleskOS::isDebLike()) { + $this->_checkSymLinkToOptPsa(); //:INFO: Check that symbolic link /usr/local/psa actually exists on Debian-like OSes https://support.plesk.com/hc/articles/12377511731991 + } + + if (Util::isVz()) { + $this->_checkShmPagesLimit(); //:INFO: PSA service does not start. Unable to allocate shared memory segment. https://support.plesk.com/hc/articles/12377744827927 + + if (PleskOS::isRedHatLike()) { + $this->_checkMailDriversConflict(); //:INFO: #PPPM-955 https://support.plesk.com/hc/articles/12378148767895 + } + } + } + + $this->_checkForCryptPasswords(); + $this->_checkMysqlServersTable(); //:INFO: Checking existing table mysql.servers + $this->_checkPleskTCPPorts(); //:INFO: Check the availability of Plesk TCP ports https://support.plesk.com/hc/articles/12377821243159 + + if (Util::isWindows()) { + $this->_checkPhprcSystemVariable(); //:INFO: #PPPM-294 Checking for PHPRC system variable + $this->_unknownISAPIfilters(); //:INFO: Checking for unknown ISAPI filters and show warning https://support.plesk.com/hc/articles/213913765 + $this->_checkMSVCR(); //:INFO: Just warning about possible issues related to Microsoft Visual C++ Redistributable Packages https://support.plesk.com/hc/articles/115000201014 + $this->_checkIisFcgiDllVersion(); //:INFO: Check iisfcgi.dll file version https://support.plesk.com/hc/articles/12378148258199 + $this->_checkCDONTSmailrootFolder(); //:INFO: After upgrade Plesk change permissions on folder of Collaboration Data Objects (CDO) for NTS (CDONTS) to default, https://support.plesk.com/hc/articles/12377887661335 + $this->_checkNullClientLogin(); //:INFO: #118963 https://support.plesk.com/hc/articles/12378122202391 + $this->checkDomainControllerLocation(); //:INFO: https://support.plesk.com/hc/articles/12377107094167 + } + } + + //:INFO: PSA service does not start. Unable to allocate shared memory segment. https://support.plesk.com/hc/articles/12377744827927 + function _checkShmPagesLimit() + { + $log = Log::getInstance("Checking for limit shmpages", true); + $ubc = Util::getUserBeanCounters(); + if ((int)$ubc['shmpages']['limit'] < 40960) { + $log->emergency("Virtuozzo Container set the \"shmpages\" limit to {$ubc['shmpages']['limit']}, which is too low. This may cause the sw-engine service not to start. To resolve this issue, refer to the article at https://support.plesk.com/hc/articles/12377744827927"); + $log->resultWarning(); + return; + } + + $log->resultOk(); + } + + //:INFO: #PPPM-294 + function _checkPhprcSystemVariable() + { + $log = Log::getInstance("Checking for PHPRC system variable", true); + + $phprc = getenv('PHPRC'); + + if ($phprc) { + $log->emergency('The environment variable PHPRC is present in the system. This variable may lead to upgrade failure. Please delete this variable from the system environment.'); + $log->resultWarning(); + return; + } + + $log->resultOk(); + } + + //:INFO: ERROR while trying to backup MySQL database. https://support.plesk.com/hc/en-us/articles/12377010033943 + function _checkAbilityToChmodInDumpd() + { + $log = Log::getInstance("Checking the possibility to change the permissions of files in the DUMP_D directory", true); + + $dump_d = Util::getSettingFromPsaConf('DUMP_D'); + if (is_null($dump_d)) { + $log->warning('Unable to obtain the path to the directory defined by the DUMP_D parameter. Check that the DUMP_D parameter is set in the /etc/psa/psa.conf file.'); + $log->resultWarning(); + return; + } + + $file = $dump_d . '/pre_upgrade_test_checkAbilityToChmodInDumpd'; + + if (false === file_put_contents($file, 'test')) { + $log->emergency('Unable to write in the ' . $dump_d . ' directory. The upgrade procedure will fail. Check that the folder exists and you have write permissions for it, and repeat upgrading. '); + $log->resultWarning(); + return; + } else { + $result = @chmod($file, 0600); + unlink($file); + if (!$result) { + $log->emergency( + 'Unable to change the permissions of files in the ' . $dump_d . ' directory. ' + . 'The upgrade procedure will fail. Please refer to https://support.plesk.com/hc/articles/12377010033943 for details.' + ); + $log->resultError(); + return; + } + } + $log->resultOk(); + } + + //:INFO: #128414 https://support.plesk.com/hc/articles/12377589682327 + function _checkImmutableBitOnPleskFiles() + { + $log = Log::getInstance("Checking Panel files for the immutable bit attribute"); + + $cmd = 'lsattr -R /usr/local/psa/ 2>/dev/null |awk \'{split($1, a, ""); if (a[5] == "i") {print;}}\''; + $output = Util::exec($cmd, $code); + $files = explode('\n', $output); + + if (!empty($output)) { + $log->info('The immutable bit attribute of the following Panel files can interrupt the upgrade procedure:'); + foreach ($files as $file) { + $log->info($file); + } + $log->emergency('Files with the immutable bit attribute were found. Please check https://support.plesk.com/hc/articles/12377589682327 for details.'); + $log->resultWarning(); + return; + } + + $log->resultOk(); + } + + //:INFO: #PPPM-1195 https://support.plesk.com/hc/articles/12377740416151 + function _checkCustomWebServerConfigTemplates() + { + $log = Log::getInstance("Checking for custom web server configuration templates"); + $pleskDir = Util::getSettingFromPsaConf('PRODUCT_ROOT_D'); + $customTemplatesPath = $pleskDir . '/admin/conf/templates/custom'; + + if (is_dir($customTemplatesPath)) { + $log->warning("Directory {$customTemplatesPath} for custom web server configuration templates was found. Custom templates might be incompatible with a new Plesk version, and this might lead to failure to generate web server configuration files. Remove the directory to get rid of this warning. " + . "Please check https://support.plesk.com/hc/articles/12377740416151 for details."); + $log->resultWarning(); + return; + } + $log->resultOk(); + } + + //:INFO: #PPPM-955 https://support.plesk.com/hc/articles/12378148767895 + function _checkMailDriversConflict() + { + $log = Log::getInstance("Checking for a Plesk mail drivers conflict"); + + if (((true === PackageManager::isInstalled('psa-mail-pc-driver') || true === PackageManager::isInstalled('plesk-mail-pc-driver')) + && true === PackageManager::isInstalled('psa-qmail')) + || ((true === PackageManager::isInstalled('psa-mail-pc-driver') || true === PackageManager::isInstalled('plesk-mail-pc-driver')) + && true === PackageManager::isInstalled('psa-qmail-rblsmtpd'))) { + $log->warning("Plesk upgrade by EZ templates failed if psa-mail-pc-driver and psa-qmail or psa-qmail-rblsmtpd packages are installed. " + . "Please check https://support.plesk.com/hc/articles/12378148767895 for details."); + $log->resultWarning(); + return; + } + + $log->resultOk(); + } + + //:INFO: #118963 https://support.plesk.com/hc/articles/12378122202391 + function _checkNullClientLogin() + { + $log = Log::getInstance("Checking for accounts with empty user names"); + + $mysql = PleskDb::getInstance(); + $sql = "SELECT domains.id, domains.name, clients.login FROM domains LEFT JOIN clients ON clients.id=domains.cl_id WHERE clients.login is NULL"; + $nullLogins = $mysql->fetchAll($sql); + + if (!empty($nullLogins)) { + $log->warning('There are accounts with empty user names. This problem can cause the backup or migration operation to fail. Please see https://support.plesk.com/hc/articles/12378122202391 for the solution.'); + $log->resultWarning(); + return; + } + + $log->resultOk(); + } + + //:INFO: #105405 https://support.plesk.com/hc/articles/213362569 + function _checkSwCollectdIntervalSetting() + { + $log = Log::getInstance("Checking the 'Interval' parameter in the sw-collectd configuration file"); + + $collectd_config = '/etc/sw-collectd/collectd.conf'; + if (file_exists($collectd_config)) { + if (!is_file($collectd_config) || !is_readable($collectd_config)) + return; + + $config_content = Util::readfileToArray($collectd_config); + if ($config_content) { + foreach ($config_content as $line) { + if (preg_match('/Interval\s*\d+$/', $line, $match)) { + if (preg_match('/Interval\s*10$/', $line, $match)) { + $log->warning('If you leave the default value of the "Interval" parameter in the ' . $collectd_config . ', sw-collectd may heavily load the system. Please see https://support.plesk.com/hc/articles/213362569 for details.'); + $log->resultWarning(); + return; + } + $log->resultOk(); + return; + } + } + $log->warning('If you leave the default value of the "Interval" parameter in the ' . $collectd_config . ', sw-collectd may heavily load the system. Please see https://support.plesk.com/hc/articles/213362569 for details.'); + $log->resultWarning(); + return; + } + } + } + + private function _checkApacheStatus() + { + $log = Log::getInstance("Checking Apache status"); + + $apacheCtl = file_exists('/usr/sbin/apache2ctl') ? '/usr/sbin/apache2ctl' : '/usr/sbin/apachectl'; + + if (!is_executable($apacheCtl)) { + return; + } + + $resultCode = 0; + Util::Exec("$apacheCtl -t 2>/dev/null", $resultCode); + + if (0 !== $resultCode) { + $log->error("The Apache configuration is broken. Run '$apacheCtl -t' to see the detailed info."); + $log->resultError(); + return; + } + + $log->resultOk(); + } + + //:INFO: #72751 https://support.plesk.com/hc/articles/12377666752279 + function _checkIpcollectionReference() + { + $log = Log::getInstance("Checking consistency of the IP addresses list in the Panel database"); + + $mysql = PleskDb::getInstance(); + $sql = "SELECT 1 FROM ip_pool, clients, IpAddressesCollections, domains, DomainServices, IP_Addresses WHERE DomainServices.ipCollectionId = IpAddressesCollections.ipCollectionId AND domains.id=DomainServices.dom_id AND clients.id=domains.cl_id AND ipAddressId NOT IN (select id from IP_Addresses) AND IP_Addresses.id = ip_pool.ip_address_id AND pool_id = ip_pool.id GROUP BY pool_id"; + $brokenIps = $mysql->fetchAll($sql); + $sql = "select 1 from DomainServices, domains, clients, ip_pool where ipCollectionId not in (select IpAddressesCollections.ipCollectionId from IpAddressesCollections) and domains.id=DomainServices.dom_id and clients.id = domains.cl_id and ip_pool.id = clients.pool_id and DomainServices.type='web' group by ipCollectionId"; + $brokenCollections = $mysql->fetchAll($sql); + + if (!empty($brokenIps) || !empty($brokenCollections)) { + $log->warning('Some database entries related to Panel IP addresses are corrupted. Please see https://support.plesk.com/hc/articles/12377666752279 for the solution.'); + $log->resultWarning(); + return; + } + + $log->resultOk(); + } + + //:INFO: Broken contexts of the APS applications can lead to errors at building Apache web server configuration https://support.plesk.com/hc/articles/12377671820311 + function _checkApsApplicationContext() + { + $log = Log::getInstance("Checking installed APS applications"); + $mysql = PleskDb::getInstance(); + $sql = "SELECT * FROM apsContexts WHERE (pleskType = 'hosting' OR pleskType = 'subdomain') AND subscriptionId = 0"; + $brokenContexts = $mysql->fetchAll($sql); + + if (!empty($brokenContexts)) { + $log->warning('Some database entries related to the installed APS applications are corrupted. Please see https://support.plesk.com/hc/articles/12377671820311 for the solution.'); + $log->resultWarning(); + return; + } + $log->resultOk(); + } + + //:INFO: #94489 FTP service proftpd cannot be started by xinetd if IPv6 is disabled https://support.plesk.com/hc/articles/12377796102807 + function _checkProftpdIPv6() + { + $log = Log::getInstance("Checking proftpd settings"); + + $inet6 = '/proc/net/if_inet6'; + if (!file_exists($inet6) || !@file_get_contents($inet6)) { + $proftpd_config = '/etc/xinetd.d/ftp_psa'; + if (!is_file($proftpd_config) || !is_readable($proftpd_config)) + return null; + + $config_content = Util::readfileToArray($proftpd_config); + if ($config_content) { + for ($i=0; $i<=count($config_content)-1; $i++) { + if (preg_match('/flags.+IPv6$/', $config_content[$i], $match)) { + $log->warning('The proftpd FTP service will fail to start in case the support for IPv6 is disabled on the server. Please check https://support.plesk.com/hc/articles/12377796102807 for details.'); + $log->resultWarning(); + return; + } + } + } + } + $log->resultOk(); + } + + //:INFO: Check the availability of Plesk Panel TCP ports + function _checkPleskTCPPorts() + { + $log = Log::getInstance('Checking the availability of Plesk Panel TCP ports'); + + $plesk_ports = array('8880' => 'Plesk Panel non-secure HTTP port', '8443' => 'Plesk Panel secure HTTPS port'); + + $mysql = PleskDb::getInstance(); + $sql = "select ip_address from IP_Addresses"; + $ip_addresses = $mysql->fetchAll($sql); + $warning = false; + if (count($ip_addresses)>0) { + if (Util::isLinux()) { + $ipv4 = Util::getIPv4ListOnLinux(); + $ipv6 = Util::getIPv6ListOnLinux(); + if ($ipv6) { + $ipsInSystem = array_merge($ipv4, $ipv6); + } else { + $ipsInSystem = $ipv4; + } + } else { + $ipsInSystem = Util::getIPListOnWindows(); + } + foreach ($ip_addresses as $ip) { + foreach ($plesk_ports as $port => $description) { + if (PleskValidator::validateIPv4($ip['ip_address']) && in_array($ip['ip_address'], $ipsInSystem)) { + $fp = @fsockopen($ip['ip_address'], $port, $errno, $errstr, 1); + } elseif (PleskValidator::validateIPv6($ip['ip_address']) && in_array($ip['ip_address'], $ipsInSystem)) { + $fp = @fsockopen('[' . $ip['ip_address'] . ']', $port, $errno, $errstr, 1); + } else { + $log->warning('IP address registered in Plesk is invalid or broken: ' . $ip['ip_address']); + $log->resultWarning(); + return; + } + if (!$fp) { + // $errno 110 means "timed out", 111 means "refused" + $log->info('Unable to connect to IP address ' . $ip['ip_address'] . ' on ' . $description . ' ' . $port . ': ' . $errstr); + $warning = true; + } + } + } + } + if ($warning) { + $log->warning('Unable to connect to some Plesk ports. Please see ' . LOG_PATH . ' for details. Find the full list of the required open ports at https://support.plesk.com/hc/articles/12377821243159 '); + $log->resultWarning(); + return; + } + $log->resultOk(); + } + + //:INFO: Plesk user "root" for MySQL database servers have not access to phpMyAdmin https://support.plesk.com/hc/articles/12378148229399 + function _checkMySQLDatabaseUserRoot() + { + $log = Log::getInstance('Checking existence of Plesk user "root" for MariaDB/MySQL database servers'); + + $psaroot = Util::getSettingFromPsaConf('PRODUCT_ROOT_D'); + + if (PleskVersion::is_below_17_9()) { + $phpMyAdminConfFile = $psaroot . '/admin/htdocs/domains/databases/phpMyAdmin/libraries/config.default.php'; + } else { + $phpMyAdminConfFile = $psaroot . '/phpMyAdmin/libraries/config.default.php'; + } + + if (file_exists($phpMyAdminConfFile)) { + $phpMyAdminConfFileContent = file_get_contents($phpMyAdminConfFile); + if (!preg_match("/\[\'AllowRoot\'\]\s*=\s*true\s*\;/", $phpMyAdminConfFileContent)) { + $mysql = PleskDb::getInstance(); + $sql = "select login, data_bases.name as db_name, displayName as domain_name from db_users, data_bases, domains where db_users.db_id = data_bases.id and data_bases.dom_id = domains.id and data_bases.type = 'mysql' and login = 'root'"; + $dbusers = $mysql->fetchAll($sql); + + foreach ($dbusers as $user) { + $log->warning('The database user "' . $user['login'] . '" (database "' . $user['db_name'] . '" at "' . $user['domain_name'] . '") has no access to phpMyAdmin. Please check https://support.plesk.com/hc/articles/12378148229399 for more details.'); + $log->resultWarning(); + return; + } + } + } + + $log->resultOk(); + } + + //:INFO: After upgrade Plesk change permissions on folder of Collaboration Data Objects (CDO) for NTS (CDONTS) to default, https://support.plesk.com/hc/articles/12377887661335 + function _checkCDONTSmailrootFolder() + { + $log = Log::getInstance('Checking for CDONTS mailroot folder'); + + $mailroot = Util::getSystemDisk() . 'inetpub\mailroot\pickup'; + + if (is_dir($mailroot)) { + $log->warning('After upgrade you have to add write permissions to psacln group on folder ' . $mailroot . '. Please, check https://support.plesk.com/hc/articles/12377887661335 for more details.'); + $log->resultWarning(); + return; + } + $log->resultOk(); + } + + //:INFO: Check iisfcgi.dll file version https://support.plesk.com/hc/articles/12378148258199 + function _checkIisFcgiDllVersion() + { + $log = Log::getInstance("Checking the iisfcgi.dll file version"); + + $windir = Util::getSystemRoot(); + $iisfcgi = $windir . '\system32\inetsrv\iisfcgi.dll'; + if (file_exists($iisfcgi)) { + $version = Util::getFileVersion($iisfcgi); + if (version_compare($version, '7.5.0', '>') + && version_compare($version, '7.5.7600.16632', '<')) { + $log->warning('File iisfcgi.dll version ' . $version . ' is outdated. Please, check article https://support.plesk.com/hc/articles/12378148258199 for details'); + return; + } + } + $log->resultOk(); + } + + //:INFO: Checking for main IP address https://support.plesk.com/hc/articles/12377857361687 + function _checkMainIP() + { + $log = Log::getInstance("Checking for main IP address"); + + $mysql = PleskDb::getInstance(); + $sql = 'select * from IP_Addresses'; + $ips = $mysql->fetchAll($sql); + $mainexists = false; + foreach ($ips as $ip) { + if (isset($ip['main'])) { + if ($ip['main'] == 'true') { + $mainexists = true; + } + } else { + $log->info('No field "main" in table IP_Addresses.'); + $log->resultOk(); + return; + } + } + + if (!$mainexists) { + $warn = 'Unable to find "main" IP address in psa database. Please, check https://support.plesk.com/hc/articles/12377857361687 for more details.'; + $log->warning($warn); + $log->resultWarning(); + return; + } + $log->resultOk(); + } + + //:INFO: Checking existing table mysql.servers https://support.plesk.com/hc/articles/12377850098455 + function _checkMysqlServersTable() + { + $log = Log::getInstance('Checking table "servers" in database "mysql"'); + + $mySQLServerVersion = Util::getMySQLServerVersion(); + if (version_compare($mySQLServerVersion, '5.1.0', '>=')) { + $credentials = Util::getDefaultClientMySQLServerCredentials(); + + if (preg_match('/AES-128-CBC/', $credentials['admin_password'])) { + $log->info('The administrator\'s password for the default MariaDB/MySQL server is encrypted.'); + return; + } + + $mysql = new DbClientMysql($credentials['host'], $credentials['admin_login'], $credentials['admin_password'] , 'information_schema', $credentials['port']); + if (!$mysql->hasErrors()) { + $sql = 'SELECT * FROM information_schema.TABLES WHERE TABLE_SCHEMA="mysql" and TABLE_NAME="servers"'; + $servers = $mysql->fetchAll($sql); + if (empty($servers)) { + $warn = 'The table "servers" in the database "mysql" does not exist. Please check https://support.plesk.com/hc/articles/12377850098455 for details.'; + $log->warning($warn); + $log->resultWarning(); + return; + } + } + } + $log->resultOk(); + } + + //:INFO: Check that there is symbolic link /usr/local/psa on /opt/psa on Debian-like Oses https://support.plesk.com/hc/articles/12377511731991 + function _checkSymLinkToOptPsa() + { + $log = Log::getInstance('Checking symbolic link /usr/local/psa on /opt/psa'); + + $link = @realpath('/usr/local/psa/version'); + if (!preg_match('/\/opt\/psa\/version/', $link, $macthes)) { + $warn = "The symbolic link /usr/local/psa does not exist or has wrong destination. Read article https://support.plesk.com/hc/articles/12377511731991 to fix the issue."; + $log->warning($warn); + $log->resultWarning(); + return; + } + $log->resultOk(); + } + + //:INFO: Checking for unknown ISAPI filters and show warning https://support.plesk.com/hc/articles/213913765 + function _unknownISAPIfilters() + { + $log = Log::getInstance('Detecting installed ISAPI filters'); + + if (Util::isUnknownISAPIfilters()) { + $warn = 'Please read carefully article https://support.plesk.com/hc/articles/213913765, for avoiding possible problems caused by unknown ISAPI filters.'; + $log->warning($warn); + $log->resultWarning(); + + return; + } + $log->resultOk(); + } + + //:INFO: Warning about possible issues related to Microsoft Visual C++ Redistributable Packages ?https://support.plesk.com/hc/articles/115000201014 + function _checkMSVCR() + { + $log = Log::getInstance('Microsoft Visual C++ Redistributable Packages'); + + $warn = 'Please read carefully article https://support.plesk.com/hc/articles/115000201014, for avoiding possible problems caused by Microsoft Visual C++ Redistributable Packages.'; + $log->info($warn); + + return; + } + + function _checkForCryptPasswords() + { + //:INFO: Prevent potential problem with E: Couldn't configure pre-depend plesk-core for psa-firewall, probably a dependency cycle. + $log = Log::getInstance('Detecting if encrypted passwords are used'); + + $db = PleskDb::getInstance(); + $sql = "SELECT COUNT(*) AS cnt FROM accounts WHERE type='crypt' AND password not like '$%';"; + $r = $db->fetchAll($sql); + + if ($r[0]['cnt'] != '0') + { + $warn = 'There are ' . $r[0]['cnt'] . ' accounts with passwords encrypted using a deprecated algorithm. Please refer to https://support.plesk.com/hc/articles/12377596588311 for the instructions about how to change the password type to plain.'; + + $log->warning($warn); + $log->resultWarning(); + return; + } + $log->resultOk(); + } + + function _checkApsTablesInnoDB() + { + $log = Log::getInstance('Checking if apsc database tables have InnoDB engine'); + + $db = PleskDb::getInstance(); + $apsDatabase = $db->fetchOne("select val from misc where param = 'aps_database'"); + $sql = "SELECT TABLE_NAME FROM information_schema.TABLES where TABLE_SCHEMA = '$apsDatabase' and ENGINE = 'MyISAM'"; + $myISAMTables = $db->fetchAll($sql); + if (!empty($myISAMTables)) { + $myISAMTablesList = implode(', ', array_map('reset', $myISAMTables)); + $warn = 'The are tables in apsc database with MyISAM engine: ' . $myISAMTablesList . '. It would be updated to InnoDB engine.'; + $log->warning($warn); + $log->resultWarning(); + return; + } + $log->resultOk(); + } + + function _checkMixedCaseDomainIssues() + { + $log = Log::getInstance("Checking for domains with mixed case names", true); + $db = PleskDb::getInstance(); + + + $domains = $db->fetchAll("select id, name, displayName from domains"); + $problemDomains = array(); + foreach ($domains as $domain) { + if (strtolower($domain['name']) == $domain['name']) { + continue; + } + $problemDomains[] = $domain; + } + if (count($problemDomains)) { + $msg = "Found one or more domains with mixed case names. Such domains may have trouble working with the \"FPM application server by Apache\" handler.\n" . + implode("\n", array_map(function($row) { + return "{$row['id']}\t{$row['displayName']}\t{$row['name']}"; + }, $problemDomains)) . "\n" . + "A manual fix can be applied to resolve the issue. Read https://support.plesk.com/hc/en-us/articles/12377171904151 for details."; + $log->warning($msg); + $log->resultWarning(); + return; + } + $log->resultOk(); + } + + private function checkDomainControllerLocation() + { + $log = Log::getInstance("Checking for Active Directory Domain Controller and Plesk on the same server", true); + $cmd = '"' . rtrim(Util::getPleskRootPath(), '\\') . '\admin\bin\serverconf.exe" --list'; + $output = Util::exec($cmd, $code); + if (preg_match("/IS_DOMAIN_CONTROLLER:\s*true/", $output)) { + $log->warning('Active Directory Domain Controller and Plesk are on the same server. Read https://support.plesk.com/hc/articles/12377107094167 for details.'); + $log->resultWarning(); + } else { + $log->resultOk(); + } + } +} + +class Plesk175Requirements +{ + public function validate() + { + if (PleskInstallation::isInstalled() && PleskVersion::is_below_17_5() && Util::isLinux()) { + //:INFO: Check that DUMP_TMP_D is not inside of (or equal to) DUMP_D + $this->_checkDumpTmpD(); + } + } + + public function _checkDumpTmpD() + { + $log = Log::getInstance('Checking the DUMP_TMP_D directory'); + + $dumpD = Util::getSettingFromPsaConf('DUMP_D'); + if (is_null($dumpD)) { + $log->warning('Unable to obtain the path to the directory defined by the DUMP_D parameter. Check that the DUMP_D parameter is set in the /etc/psa/psa.conf file.'); + $log->resultWarning(); + return; + } + $dumpTmpD = Util::getSettingFromPsaConf('DUMP_TMP_D'); + if (is_null($dumpTmpD)) { + $log->warning('Unable to obtain the path to the directory defined by the DUMP_TMP_D parameter. Check that the DUMP_TMP_D parameter is set in the /etc/psa/psa.conf file.'); + $log->resultWarning(); + return; + } + + if (strpos(rtrim($dumpTmpD, '/') . '/', rtrim($dumpD, '/') . '/') === 0) { + $log->error(sprintf('The directory DUMP_TMP_D = %s should not be inside of (or equal to) the directory DUMP_D = %s. Fix these parameters in the /etc/psa/psa.conf file.', $dumpTmpD, $dumpD)); + $log->resultError(); + } + + $log->resultOk(); + } +} + +class Plesk178Requirements +{ + public function validate() + { + if (PleskInstallation::isInstalled() && Util::isWindows() && PleskVersion::is_below_17_9()) { + $this->_checkPleskVhostsDir(); + } + + if (PleskVersion::is_below_17_8()) { + $this->checkTomcat(); + $this->checkMultiServer(); + } + } + + private function checkTomcat() + { + if (Util::isWindows()) { + $tomcatRegBase = '\\PLESK\\PSA Config\\Config\\Packages\\tomcat\\tomcat'; + /* Supported versions on windows are tomcat5 and tomcat7 */ + $key = '/v InstallDir'; + $isInstalled = Util::regQuery($tomcatRegBase . '7', $key, true) || Util::regQuery($tomcatRegBase . '5', $key, true); + } else { + $isInstalled = PackageManager::isInstalled('psa-tomcat-configurator'); + } + + if ($isInstalled + || (PleskDb::getInstance()->fetchOne('show tables like \'WebApps\'') + && PleskDb::getInstance()->fetchOne('select count(*) from WebApps')) + ) { + $log = Log::getInstance('Checking Apache Tomcat installation'); + $message = <<warning($message); + } + } + + private function checkMultiServer() + { + if (!PleskModule::isMultiServer()) { + return; + } + + $log = Log::getInstance('Checking Plesk Multi Server installation'); + $message = <<emergency($message); + $log->resultError(); + } + + private function _checkPleskVhostsDir() + { + $log = Log::getInstance('Checking mount volume for HTTPD_VHOSTS_D directory'); + + $vhostsDir = rtrim(Util::regPleskQuery('HTTPD_VHOSTS_D'), "\\"); + Util::exec("mountvol \"{$vhostsDir}\" /L", $code); + if ($code == 0) { + $msg = "A disk volume is mounted to the {$vhostsDir} directory." . + " It will be unmounted during the Plesk upgrade." . + " As a result, all hosted websites will become unavailable." . + " Make sure to remount the volume to the {$vhostsDir} directory after the upgrade."; + $log->emergency($msg); + $log->resultError(); + return; + } + + $log->resultOk(); + } +} + +class Plesk18Requirements +{ + public function validate() + { + if (PleskInstallation::isInstalled()) { + if (Util::isLinux() && PleskOS::isRedHatLike()) { + $this->_checkYumDuplicates(); + } + $this->checkPdUsersLoginCollation(); + $this->checkDomainsGuidCollation(); + $this->checkClientsGuidCollation(); + $this->checkModSecurityModules(); + $this->checkIsFirewallPackageConfigured(); + $this->checkDefaultDnsServerComponent(); + } + } + + private function checkModSecurityModules() + { + /* Issue actual for Plesk for Windows below 18.0.32 (ModSecurity 2.9.3 and below) */ + if (!Util::isWindows() || PleskVersion::is_18_0_32_or_above()) { + return; + } + + $log = Log::getInstance('Checking the status of ModSecurity IIS modules'); + $modules = Util::exec(Util::getSystemRoot() . '\system32\inetsrv\AppCmd.exe list module', $code); + if ($code) { + $log->warning('Unable to get the list of IIS modules.'); + } else { + if (strpos($modules, 'ModSecurity IIS (32bits)') === false && strpos($modules, 'ModSecurity IIS (64bits)') === false) { + $log->error('Either 32-bit or 64-bit ModSecurity IIS module is absent.'); + $log->resultError(); + } + } + } + + // INFO: PPP-46440 checking package duplicates https://support.plesk.com/hc/articles/12377586286615 + private function _checkYumDuplicates() + { + $log = Log::getInstance('Checking for RPM packages duplicates'); + if (!file_exists("/usr/bin/package-cleanup")) + { + $log->info("package-cleanup is not found. Check for duplicates was skipped"); + return; + } + + $output = Util::exec("/usr/bin/package-cleanup --cacheonly -q --dupes", $code); + if ($code != 0) + { + // some repos may have no cache at this point or may be broken at all + // retry with cache recreation and skipping broken repos + $output = Util::exec("/usr/bin/package-cleanup -q --dupes --setopt='*.skip_if_unavailable=1'", $code); + } + if ($code != 0) + { + $message = "Unable to detect package duplicates: /usr/bin/package-cleanup --dupes returns $code." . + "Output is:\n$output"; + $log->warning($message); + $log->resultWarning(); + return; + } + + if (empty($output)) { + return; + } + + $message = "Your package system contains duplicated packages, which can lead to broken Plesk update:\n\n" . + "$output\n\n" . + "Please check https://support.plesk.com/hc/articles/12377586286615 for more details."; + + $log->error($message); + $log->resultError(); + } + + private function checkPdUsersLoginCollation() + { + $log = Log::getInstance('Checking for Protected Directory Users with duplicates in login field.'); + $duplicates = PleskDb::getInstance()->fetchAll( + 'SELECT pd_id, LOWER(login) AS login_ci, COUNT(*) AS duplicates FROM pd_users' . + ' GROUP BY pd_id, login_ci' . + ' HAVING duplicates > 1' + ); + if (!empty($duplicates)) { + $log->error( + "Duplicate logins of Protected Directory Users were found in the database:\n\n" . + implode("\n", array_column($duplicates, 'login_ci')) . "\n\n" . + "Please check https://support.plesk.com/hc/en-us/articles/360014743900 for more details." + ); + $log->resultError(); + } + } + + private function checkDomainsGuidCollation() + { + $log = Log::getInstance('Checking "domains" table with duplicates in guid field.'); + $duplicates = PleskDb::getInstance()->fetchAll( + 'SELECT guid, COUNT(*) AS duplicates FROM domains' + . ' GROUP BY guid' + . ' HAVING duplicates > 1' + ); + + if (!empty($duplicates)) { + $log->error( + "Duplicate guid were found in the 'domains' table:\n\n" . + implode("\n", array_column($duplicates, 'guid')) . "\n\n" + . "Please check https://support.plesk.com/hc/en-us/articles/12377018323351 for more details." + ); + $log->resultError(); + } + } + + private function checkClientsGuidCollation() + { + $log = Log::getInstance('Checking "clients" table with duplicates in guid field.'); + $duplicates = PleskDb::getInstance()->fetchAll( + 'SELECT guid, COUNT(*) AS duplicates FROM clients' + . ' GROUP BY guid' + . ' HAVING duplicates > 1' + ); + + if (!empty($duplicates)) { + $log->error( + "Duplicate guid were found in the 'clients' table:\n\n" . + implode("\n", array_column($duplicates, 'guid')) . "\n\n" + . "Please check https://support.plesk.com/hc/en-us/articles/360016801679 for more details." + ); + $log->resultError(); + } + } + + private function checkIsFirewallPackageConfigured() + { + if (!Util::isLinux()) { + return; + } + + $log = Log::getInstance("Checking if any rules are configured in the Firewall extension"); + $db = PleskDb::getInstance(); + if ($db->fetchOne("SHOW TABLES LIKE 'module_firewall_rules'") + && $db->fetchOne("SELECT COUNT(*) FROM module_firewall_rules") + ) { + $message = "Plesk Firewall no longer stores its configuration in Plesk database since " + . "Plesk Obsidian 18.0.52. Since you're upgrading to the latest version late, " + . "if you wish to retain the Plesk Firewall extension and its configuration, " + . "you need to follow additional steps after the upgrade. Please check " + . "https://support.plesk.com/hc/en-us/articles/16198248236311 for more details."; + $log->warning($message); + } + } + + private function checkDefaultDnsServerComponent() + { + if (Util::isLinux()) { + return; + } + + $path = '\\PLESK\\PSA Config\\Config\\Packages\\dnsserver'; + $key = '/ve'; + + if ('bind' === Util::regQuery($path, $key, true)) { + $log = Log::getInstance("Checking default DNS server component"); + $message = <<emergency($message); + $log->resultError(); + } + } +} + +class PleskModule +{ + public static function isInstalledWatchdog() + { + return PleskModule::_isInstalled('watchdog'); + } + + public static function isInstalledFileServer() + { + return PleskModule::_isInstalled('fileserver'); + } + + public static function isInstalledFirewall() + { + return PleskModule::_isInstalled('firewall'); + } + + public static function isInstalledVpn() + { + return PleskModule::_isInstalled('vpn'); + } + + public static function isMultiServer() + { + return PleskModule::_isInstalled('plesk-multi-server') || + PleskModule::_isInstalled('plesk-multi-server-node'); + } + + protected static function _isInstalled($module) + { + $sql = "SELECT * FROM Modules WHERE name = '{$module}'"; + + $pleskDb = PleskDb::getInstance(); + $row = $pleskDb->fetchRow($sql); + + return (empty($row) ? false : true); + } +} + +class PleskInstallation +{ + public static function validate() + { + if (!self::isInstalled()) { + $log = Log::getInstance('Checking for Plesk installation'); + $log->step('Plesk installation is not found. You will have no problems with upgrade, go on and install ' + . PleskVersion::getLatestPleskVersionAsString() . ' (https://www.plesk.com/)'); + return; + } + self::detectVersion(); + } + + public static function isInstalled() + { + $rootPath = Util::getPleskRootPath(); + if (empty($rootPath) || !file_exists($rootPath)) { + return false; + } + return true; + } + + private static function detectVersion() + { + $log = Log::getInstance('Installed Plesk version/build: ' . PleskVersion::getVersionAndBuild(), false); + + $currentVersion = PleskVersion::getVersion(); + if (version_compare($currentVersion, PLESK_VERSION, 'eq')) { + $err = 'You have already installed the latest version ' . PleskVersion::getLatestPleskVersionAsString() . '. '; + $err .= 'Tool must be launched prior to upgrade to ' . PleskVersion::getLatestPleskVersionAsString() . ' for the purpose of getting a report on potential problems with the upgrade.'; + $log->info($err); + exit(0); + } + + if (!PleskVersion::isUpgradeSupportedVersion()) { + $err = 'Unable to find Plesk 17.x. '; + $err .= 'Tool must be launched prior to upgrade to ' . PleskVersion::getLatestPleskVersionAsString() . ' for the purpose of getting a report on potential problems with the upgrade.'; + fatal($err); + } + } +} + +class PleskVersion +{ + const PLESK_17_MIN_VERSION = '13.0.0'; /* historically it has been started as 13.0 */ + + const PLESK_17_MAX_VERSION = '17.9.13'; + + const PLESK_18_MIN_VERSION = '18.0.14'; + + public static function is17x_or_above() + { + return version_compare(self::getVersion(), self::PLESK_17_MIN_VERSION, '>='); + } + + public static function is_below_17_5() + { + return version_compare(self::getVersion(), '17.5.0', '<'); + } + + public static function is_below_17_8() + { + return version_compare(self::getVersion(), '17.8.0', '<'); + } + + public static function is_below_17_9() + { + return version_compare(self::getVersion(), '17.9.0', '<'); + } + + public static function is_18_0_32_or_above() + { + return version_compare(self::getVersion(), '18.0.32', '>='); + } + + public static function getVersion() + { + $version = self::getVersionAndBuild(); + if (!preg_match('/([0-9]+[.][0-9]+[.][0-9]+)/', $version, $matches)) { + fatal("Incorrect Plesk version format. Current version: {$version}"); + } + return $matches[1]; + } + + public static function getVersionAndBuild() + { + $versionPath = Util::getPleskRootPath().'/version'; + if (!file_exists($versionPath)) { + fatal("Plesk version file is not exists $versionPath"); + } + $version = file_get_contents($versionPath); + $version = trim($version); + return $version; + } + + public static function getLatestPleskVersionAsString() + { + return 'Plesk ' . PLESK_VERSION; + } + + public static function isUpgradeSupportedVersion() + { + return self::is17x_or_above(); + } +} + +class Log +{ + private $errors; + private $warnings; + private $emergency; + private $logfile; + private $step; + private $step_header; + + /** @var array */ + private $errorsContent = []; + + /** @var array */ + private $warningsContent = []; + + public static function getInstance($step_msg = '', $step_number = true) + { + static $_instance = null; + if (is_null($_instance)) { + $_instance = new Log(); + } + if ($step_msg) { + $_instance->step($step_msg, $step_number); + } + + return $_instance; + } + + private function __construct() + { + $this->log_init(); + @unlink($this->logfile); + } + + private function log_init() + { + $this->step = 0; + $this->errors = 0; + $this->warnings = 0; + $this->emergency = 0; + $this->logfile = LOG_PATH; + $this->step_header = "Unknown step is running"; + } + + public function getErrors() + { + return $this->errors; + } + + public function getWarnings() + { + return $this->warnings; + } + + public function getEmergency() + { + return $this->emergency; + } + + public function fatal($msg) + { + $this->errors++; + + $this->errorsContent[] = $msg; + $content = $this->get_log_string($msg, 'FATAL_ERROR'); + fwrite(STDERR, $content); + $this->write($content); + } + + public function error($msg) + { + $this->errors++; + + $this->errorsContent[] = $msg; + $content = $this->get_log_string($msg, 'ERROR'); + fwrite(STDERR, $content); + $this->write($content); + } + + public function warning($msg) + { + $this->warnings++; + + $this->warningsContent[] = $msg; + $content = $this->get_log_string($msg, 'WARNING'); + fwrite(STDERR, $content); + $this->write($content); + } + + public function emergency($msg) + { + $this->emergency++; + + $this->errorsContent[] = $msg; + $content = $this->get_log_string($msg, 'EMERGENCY'); + fwrite(STDERR, $content); + $this->write($content); + } + + public function step($msg, $useNumber=false) + { + $this->step_header = $msg; + + echo PHP_EOL; + $this->write(PHP_EOL); + + if ($useNumber) { + $msg = "STEP " . $this->step . ": {$msg}..."; + $this->step++; + } else { + $msg = "{$msg}..."; + } + + $this->info($msg); + } + + public function resultOk() + { + $this->info('Result: OK'); + } + + public function resultWarning() + { + $this->info('Result: WARNING'); + } + + public function resultError() + { + $this->info('Result: ERROR'); + } + + public function info($msg) + { + $content = $this->get_log_string($msg, 'INFO'); + echo $content; + $this->write($content); + } + + public function debug($msg) + { + $this->write($this->get_log_string($msg, 'DEBUG')); + } + + public function dumpStatistics() + { + $errors = $this->errors + $this->emergency; + $str = "Errors found: $errors; Warnings found: {$this->warnings}"; + echo PHP_EOL . $str . PHP_EOL . PHP_EOL; + } + + private function get_log_string($msg, $type) + { + if (getenv('VZ_UPGRADE_SCRIPT')) { + switch ($type) { + case 'FATAL_ERROR': + case 'ERROR': + case 'WARNING': + case 'EMERGENCY': + $content = "[{$type}]: {$this->step_header} DESC: {$msg}" . PHP_EOL; + break; + default: + $content = "[{$type}]: {$msg}" . PHP_EOL; + } + } else if (getenv('AUTOINSTALLER_VERSION')) { + $content = "{$type}: {$msg}" . PHP_EOL; + } else { + $date = date('Y-m-d h:i:s'); + $content = "[{$date}][{$type}] {$msg}" . PHP_EOL; + } + + return $content; + } + + public function write($content, $file = null, $mode='a+') + { + $logfile = $file ? $file : $this->logfile; + $fp = fopen($logfile, $mode); + fwrite($fp, $content); + fclose($fp); + } + + private function getJsonFileName() + { + return (Util::isWindows() ? + rtrim(Util::regPleskQuery('PRODUCT_DATA_D'), "\\") : + Util::getSettingFromPsaConf('PRODUCT_ROOT_D') + ) . '/var/' . LOG_JSON; + } + + public function writeJsonFile() + { + $data = [ + 'version' => PRE_UPGRADE_SCRIPT_VERSION, + 'errorsFound' => $this->errors + $this->emergency, + 'errors' => $this->errorsContent, + 'warningsFound' => $this->warnings, + 'warnings' => $this->warningsContent, + ]; + file_put_contents($this->getJsonFileName(), json_encode($data)); + } +} + +class PleskDb +{ + var $_db = null; + + public function __construct($dbParams) + { + switch($dbParams['db_type']) { + case 'mysql': + $this->_db = new DbMysql( + $dbParams['host'], $dbParams['login'], $dbParams['passwd'], $dbParams['db'], $dbParams['port'] + ); + break; + + case 'jet': + $this->_db = new DbJet($dbParams['db']); + break; + + case 'mssql': + $this->_db = new DbMsSql( + $dbParams['host'], $dbParams['login'], $dbParams['passwd'], $dbParams['db'], $dbParams['port'] + ); + break; + + default: + fatal("{$dbParams['db_type']} is not implemented yet"); + break; + } + } + + public static function getInstance() + { + global $options; + static $_instance = array(); + + $dbParams['db_type']= Util::getPleskDbType(); + $dbParams['db'] = Util::getPleskDbName(); + $dbParams['port'] = Util::getPleskDbPort(); + $dbParams['login'] = Util::getPleskDbLogin(); + $dbParams['passwd'] = Util::getPleskDbPassword($options->getDbPasswd()); + $dbParams['host'] = Util::getPleskDbHost(); + + $dbId = md5(implode("\n", $dbParams)); + + $_instance[$dbId] = new PleskDb($dbParams); + + return $_instance[$dbId]; + } + + function fetchOne($sql) + { + if (DEBUG) { + $log = Log::getInstance(); + $log->info($sql); + } + return $this->_db->fetchOne($sql); + } + + function fetchRow($sql) + { + $res = $this->fetchAll($sql); + if (is_array($res) && isset($res[0])) { + return $res[0]; + } + return array(); + } + + function fetchAll($sql) + { + if (DEBUG) { + $log = Log::getInstance(); + $log->info($sql); + } + return $this->_db->fetchAll($sql); + } +} + +class DbMysql +{ + var $_dbHandler; + + public function __construct($host, $user, $passwd, $database, $port) + { + if ( extension_loaded('mysql') ) { + $this->_dbHandler = @mysql_connect("{$host}:{$port}", $user, $passwd); + if (!is_resource($this->_dbHandler)) { + $mysqlError = mysql_error(); + if (stristr($mysqlError, 'access denied for user')) { + $errMsg = 'Given is incorrect. ' . $mysqlError; + } else { + $errMsg = 'Unable to connect database. The reason of problem: ' . $mysqlError . PHP_EOL; + } + $this->_logError($errMsg); + } + @mysql_select_db($database, $this->_dbHandler); + } else if ( extension_loaded('mysqli') ) { + + // forbid using MYSQLI_REPORT_STRICT to handle mysqli errors via error codes + mysqli_report(MYSQLI_REPORT_ERROR); + + $this->_dbHandler = @mysqli_connect($host, $user, $passwd, $database, $port); + if (!$this->_dbHandler) { + $mysqlError = mysqli_connect_error(); + if (stristr($mysqlError, 'access denied for user')) { + $errMsg = 'Given is incorrect. ' . $mysqlError; + } else { + $errMsg = 'Unable to connect database. The reason of problem: ' . $mysqlError . PHP_EOL; + } + $this->_logError($errMsg); + } + } else { + fatal("No MariaDB/MySQL extension is available"); + } + } + + function fetchAll($sql) + { + if ( extension_loaded('mysql') ) { + $res = mysql_query($sql, $this->_dbHandler); + if (!is_resource($res)) { + $this->_logError('Unable to execute query. Error: ' . mysql_error($this->_dbHandler)); + } + $rowset = array(); + while ($row = mysql_fetch_assoc($res)) { + $rowset[] = $row; + } + return $rowset; + } else if ( extension_loaded('mysqli') ) { + $res = $this->_dbHandler->query($sql); + if ($res === false) { + $this->_logError('Unable to execute query. Error: ' . mysqli_error($this->_dbHandler)); + } + $rowset = array(); + while ($row = mysqli_fetch_assoc($res)) { + $rowset[] = $row; + } + return $rowset; + } else { + fatal("No MariaDB/MySQL extension is available"); + } + } + + function fetchOne($sql) + { + if ( extension_loaded('mysql') ) { + $res = mysql_query($sql, $this->_dbHandler); + if (!is_resource($res)) { + $this->_logError('Unable to execute query. Error: ' . mysql_error($this->_dbHandler)); + } + $row = mysql_fetch_row($res); + return isset($row[0]) ? $row[0] : null; + } else if ( extension_loaded('mysqli') ) { + $res = $this->_dbHandler->query($sql); + if ($res === false) { + $this->_logError('Unable to execute query. Error: ' . mysqli_error($this->_dbHandler)); + } + $row = mysqli_fetch_row($res); + return isset($row[0]) ? $row[0] : null; + } else { + fatal("No MariaDB/MySQL extension is available"); + } + } + + function query($sql) + { + if ( extension_loaded('mysql') ) { + $res = mysql_query($sql, $this->_dbHandler); + if ($res === false ) { + $this->_logError('Unable to execute query. Error: ' . mysql_error($this->_dbHandler) ); + } + return $res; + } else if ( extension_loaded('mysqli') ) { + $res = $this->_dbHandler->query($sql); + if ($res === false ) { + $this->_logError('Unable to execute query. Error: ' . mysqli_error($this->_dbHandler) ); + } + return $res; + } else { + fatal("No MariaDB/MySQL extension is available"); + } + } + + function _logError($message) + { + fatal("[MYSQL ERROR] $message"); + } +} + +class DbClientMysql extends DbMysql +{ + var $errors = array(); + + function _logError($message) + { + $message = "[MYSQL ERROR] $message"; + $log = Log::getInstance(); + $log->warning($message); + $this->errors[] = $message; + } + + function hasErrors() { + return count($this->errors) > 0; + } +} + +class DbJet +{ + var $_dbHandler = null; + + public function __construct($dbPath) + { + $dsn = "Provider='Microsoft.Jet.OLEDB.4.0';Data Source={$dbPath}"; + $this->_dbHandler = new COM("ADODB.Connection", NULL, CP_UTF8); + if (!$this->_dbHandler) { + $this->_logError('Unable to init ADODB.Connection'); + } + + $this->_dbHandler->open($dsn); + } + + function fetchAll($sql) + { + $result_id = $this->_dbHandler->execute($sql); + if (!$result_id) { + $this->_logError('Unable to execute sql query ' . $sql); + } + if ($result_id->BOF && !$result_id->EOF) { + $result_id->MoveFirst(); + } + if ($result_id->EOF) { + return array(); + } + + $rowset = array(); + while(!$result_id->EOF) { + $row = array(); + for ($i=0;$i<$result_id->Fields->count;$i++) { + $field = $result_id->Fields($i); + $row[$field->Name] = (string)$field->value; + } + $result_id->MoveNext(); + $rowset[] = $row; + } + return $rowset; + } + + function fetchOne($sql) + { + $result_id = $this->_dbHandler->execute($sql); + if (!$result_id) { + $this->_logError('Unable to execute sql query ' . $sql); + } + if ($result_id->BOF && !$result_id->EOF) { + $result_id->MoveFirst(); + } + if ($result_id->EOF) { + return null; + } + $field = $result_id->Fields(0); + $result = $field->value; + + return (string)$result; + } + + function _logError($message) + { + fatal("[JET ERROR] $message"); + } +} + +class DbMsSql extends DbJet +{ + public function __construct($host, $user, $passwd, $database, $port) + { + $dsn = "Provider=SQLOLEDB.1;Initial Catalog={$database};Data Source={$host}"; + $this->_dbHandler = new COM("ADODB.Connection", NULL, CP_UTF8); + if (!$this->_dbHandler) { + $this->_logError('Unable to init ADODB.Connection'); + } + $this->_dbHandler->open($dsn, $user, $passwd); + } + + function _logError($message) + { + fatal("[MSSQL ERROR] $message"); + } +} + +class IpParser +{ + public static function getIpListFromIpconfigOutput($output) + { + $ips = []; + foreach ($output as $line) { + $line = trim($line); + + // IPv4 + if (preg_match('/\b(\d{1,3}\.){3}\d{1,3}\b/', $line, $m)) { + // no mask + if (!preg_match('/^255\.255\.255\.\d{1,3}$/', $m[0])) { + $ips[] = $m[0]; + } + } + + // IPv6 + if (preg_match('/\b([0-9a-f]{0,4}:){2,7}[0-9a-f]{0,4}\b/i', $line, $m)) { + // no local + if (stripos($m[0], 'fe80') !== 0) { + $ips[] = $m[0]; + } + } + } + return $ips; + } +} + +class Util +{ + const DSN_INI_PATH_UNIX = '/etc/psa/private/dsn.ini'; + + /** @var array */ + private static $_dsnIni; + + public static function isWindows() + { + if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { + return true; + } + return false; + } + + public static function isLinux() + { + return !Util::isWindows(); + } + + public static function isVz() + { + $vz = false; + if (Util::isLinux()) { + if (file_exists('/proc/vz/veredir')) { + $vz = true; + } + } else { + $reg = 'REG QUERY "HKLM\SOFTWARE\SWsoft\Virtuozzo" 2>nul'; + Util::exec($reg, $code); + if ($code==0) { + $vz = true; + } + } + return $vz; + } + + public static function getArch() + { + global $arch; + if (!empty($arch)) + return $arch; + + $arch = 'i386'; + if (Util::isLinux()) { + $cmd = 'uname -m'; + $x86_64 = 'x86_64'; + $output = Util::exec($cmd, $code); + if (!empty($output) && stristr($output, $x86_64)) { + $arch = 'x86_64'; + } + } else { + $arch = 'x86_64'; + } + return $arch; + } + + public static function getHostname() + { + if (Util::isLinux()) { + $cmd = 'hostname -f'; + } else { + $cmd = 'hostname'; + } + $hostname = Util::exec($cmd, $code); + + if (empty($hostname)) { + $err = 'Command: ' . $cmd . ' returns: ' . $hostname . "\n"; + $err .= 'Hostname is not defined and configured. Unable to get hostname. Server should have properly configured hostname and it should be resolved locally.'; + fatal($err); + } + + return $hostname; + } + + public static function getIPList($lo=false) + { + if (Util::isLinux()) { + $ipList = Util::getIPv4ListOnLinux(); + foreach ($ipList as $key => $ip) { + if (!$lo && substr($ip, 0, 3) == '127') { + unset($ipList[$key]); + continue; + } + trim($ip); + } + $ipList = array_values($ipList); + } else { + $cmd = 'hostname'; + $hostname = Util::exec($cmd, $code); + $ip = gethostbyname($hostname); + $res = ($ip != $hostname) ? true : false; + if (!$res) { + fatal('Unable to retrieve IP address'); + } + $ipList = array(trim($ip)); + } + return $ipList; + } + + public static function getIPv6ListOnLinux() + { + return Util::grepCommandOutput(array( + array('bin' => 'ip', 'command' => '%PATH% addr list', 'regexp' => '#inet6 ([^ /]+)#'), + array('bin' => 'ifconfig', 'command' => '%PATH% -a', 'regexp' => '#inet6 (?:addr: ?)?([A-F0-9:]+)#i'), + )); + } + + public static function getIPv4ListOnLinux() + { + $commands = array( + array('bin' => 'ip', 'command' => '%PATH% addr list', 'regexp' => '#inet ([^ /]+)#'), + array('bin' => 'ifconfig', 'command' => '%PATH% -a', 'regexp' => '#inet (?:addr: ?)?([\d\.]+)#'), + ); + if (!($list = Util::grepCommandOutput($commands))) { + fatal('Unable to get IP address'); + } + return $list; + } + + public static function grepCommandOutput($cmds) + { + foreach ($cmds as $cmd) { + if ($fullPath = Util::lookupCommand($cmd['bin'])) { + $output = Util::exec(str_replace("%PATH%", $fullPath, $cmd['command']), $code); + if (preg_match_all($cmd['regexp'], $output, $matches)) { + return $matches[1]; + } + } + } + return false; + } + + public static function getIPListOnWindows() + { + $lastLine = exec('ipconfig', $output, $code); + if (false === $lastLine) { + $error = error_get_last(); + $message = isset($error['message']) ? $error['message'] : 'Unknown error'; + fatal("Unable to get IP address: {$message}"); + } + if ($code === 0 && $ips = IpParser::getIpListFromIpconfigOutput($output)) { + return $ips; + } + fatal("Unable to get IP address"); + } + + public static function getPleskRootPath() + { + global $_pleskRootPath; + if (empty($_pleskRootPath)) { + if (Util::isLinux()) { + if (PleskOS::isDebLike()) { + $_pleskRootPath = '/opt/psa'; + } else { + $_pleskRootPath = '/usr/local/psa'; + } + } + if (Util::isWindows()) { + $_pleskRootPath = Util::regPleskQuery('PRODUCT_ROOT_D', true); + } + } + return $_pleskRootPath; + } + + public static function getPleskDbName() + { + $dbName = 'psa'; + if (Util::isWindows()) { + $dbName = Util::regPleskQuery('mySQLDBName'); + } else { + $dsnDbname = Util::_getDsnConfigValue('dbname'); + if ($dsnDbname) { + $dbName = $dsnDbname; + } + } + return $dbName; + } + + public static function getPleskDbLogin() + { + $dbLogin = 'admin'; + if (Util::isWindows()) { + $dbLogin = Util::regPleskQuery('PLESK_DATABASE_LOGIN'); + } else { + $dsnLogin = Util::_getDsnConfigValue('username'); + if ($dsnLogin) { + $dbLogin = $dsnLogin; + } + } + return $dbLogin; + } + + public static function getPleskDbPassword($dbPassword) + { + if (Util::isLinux()) { + $dsnPassword = Util::_getDsnConfigValue('password'); + if ($dsnPassword) { + $dbPassword = $dsnPassword; + } + } + return $dbPassword; + } + + public static function getPleskDbType() + { + $dbType = 'mysql'; + if (Util::isWindows()) { + $dbType = strtolower(Util::regPleskQuery('PLESK_DATABASE_PROVIDER_NAME')); + } + return $dbType; + } + + public static function getPleskDbHost() + { + $dbHost = 'localhost'; + if (Util::isWindows()) { + $dbProvider = strtolower(Util::regPleskQuery('PLESK_DATABASE_PROVIDER_NAME')); + if ($dbProvider == 'mysql' || $dbProvider == 'mssql') { + $dbHost = Util::regPleskQuery('MySQL_DB_HOST'); + } + } else { + $dsnHost = Util::_getDsnConfigValue('host'); + if ($dsnHost) { + $dbHost = $dsnHost; + } + } + return $dbHost; + } + + public static function getPleskDbPort() + { + $dbPort = '3306'; + if (Util::isWindows()) { + $dbPort = Util::regPleskQuery('MYSQL_PORT'); + } else { + $dsnPort = Util::_getDsnConfigValue('port'); + if ($dsnPort) { + $dbPort = $dsnPort; + } + } + return $dbPort; + } + + private static function _getDsnConfigValue($param) + { + if (Util::isWindows()) { + return null; + } + + if (is_null(self::$_dsnIni)) { + if (!is_file(self::DSN_INI_PATH_UNIX)) { + self::$_dsnIni = false; + return null; + } + self::$_dsnIni = parse_ini_file(self::DSN_INI_PATH_UNIX, true); + } + + if (!self::$_dsnIni) { + return null; + } + if (!array_key_exists('plesk', self::$_dsnIni)) { + return null; + } + if (!array_key_exists($param, self::$_dsnIni['plesk'])) { + return null; + } + return self::$_dsnIni['plesk'][$param]; + } + + public static function regPleskQuery($key, $returnResult=false) + { + $reg = 'REG QUERY "HKLM\SOFTWARE\Wow6432Node\Plesk\Psa Config\Config" /v '.$key; + $output = Util::exec($reg, $code); + + if ($code) { + $log = Log::getInstance(); + $log->info($reg); + $log->info($output); + if ($returnResult) { + return false; + } else { + fatal("Unable to get '$key' from registry"); + } + } + + if (!preg_match("/\w+\s+REG_SZ\s+(.*)/i", trim($output), $matches)) { + fatal('Unable to macth registry value by key '.$key.'. Output: ' . trim($output)); + } + + return $matches[1]; + } + + public static function regQuery($path, $key, $returnResult = false) + { + $reg = 'REG QUERY "HKLM\SOFTWARE\Wow6432Node' . $path . '" '.$key; + $output = Util::exec($reg, $code); + + if ($code) { + $log = Log::getInstance(); + $log->info($reg); + $log->info($output); + if ($returnResult) { + return false; + } else { + fatal("Unable to get '$key' from registry"); + } + } + + if (!preg_match("/\s+REG_SZ(\s+)?(.*)/i", trim($output), $matches)) { + fatal('Unable to match registry value by key '.$key.'. Output: ' . trim($output)); + } + + return $matches[2]; + } + + public static function lookupCommand($cmd, $exit = false, $path = '/bin:/usr/bin:/usr/local/bin:/usr/sbin:/sbin:/usr/local/sbin') + { + $dirs = explode(':', $path); + foreach ($dirs as $dir) { + $util = $dir . '/' . $cmd; + if (is_executable($util)) { + return $util; + } + } + if ($exit) { + fatal("{$cmd}: command not found"); + } + return false; + } + + public static function getSystemDisk() + { + $cmd = 'echo %SYSTEMROOT%'; + $output = Util::exec($cmd, $code); + return substr($output, 0, 3); + } + + public static function getSystemRoot() + { + $cmd = 'echo %SYSTEMROOT%'; + $output = Util::exec($cmd, $code); + return $output; + } + + public static function getFileVersion($file) + { + $fso = new COM("Scripting.FileSystemObject"); + $version = $fso->GetFileVersion($file); + $fso = null; + return $version; + } + + public static function isUnknownISAPIfilters() + { + if (PleskVersion::is17x_or_above()) { + return false; + } + + $log = Log::getInstance(); + + $isUnknownISAPI = false; + $knownISAPI = array ("ASP\\.Net.*", "sitepreview", "COMPRESSION", "jakarta"); + + foreach ($knownISAPI as &$value) { + $value = strtoupper($value); + } + $cmd='cscript ' . Util::getSystemDisk() . 'inetpub\AdminScripts\adsutil.vbs ENUM W3SVC/FILTERS'; + $output = Util::exec($cmd, $code); + + if ($code!=0) { + $log->info("Unable to get ISAPI filters. Error: " . $output); + return false; + } + if (!preg_match_all('/FILTERS\/(.*)]/', trim($output), $matches)) { + $log->info($output); + $log->info("Unable to get ISAPI filters from output: " . $output); + return false; + } + foreach ($matches[1] as $ISAPI) { + $valid = false; + foreach ($knownISAPI as $knownPattern) { + if (preg_match("/$knownPattern/i", $ISAPI)) { + $valid = true; + break; + } + } + if (! $valid ) { + $log->warning("Unknown ISAPI filter detected in IIS: " . $ISAPI); + $isUnknownISAPI = true; + } + } + + return $isUnknownISAPI; + } + + /** + * @return string + */ + public static function getMySQLServerVersion() + { + $credentials = Util::getDefaultClientMySQLServerCredentials(); + + if (preg_match('/AES-128-CBC/', $credentials['admin_password'])) { + Log::getInstance()->info('The administrator\'s password for the default MariaDB/MySQL server is encrypted.'); + + return ''; + } + + $mysql = new DbClientMysql( + $credentials['host'], + $credentials['admin_login'], + $credentials['admin_password'], + 'information_schema', + $credentials['port'] + ); + + if (!$mysql->hasErrors()) { + $sql = 'select version()'; + $mySQLversion = $mysql->fetchOne($sql); + if (!preg_match("/(\d{1,})\.(\d{1,})\.(\d{1,})/", trim($mySQLversion), $matches)) { + fatal('Unable to match MariaDB/MySQL server version.'); + } + + return $matches[0]; + } + + return ''; + } + + public static function getDefaultClientMySQLServerCredentials() + { + $db = PleskDb::getInstance(); + $sql = "SELECT val FROM misc WHERE param='default_server_mysql'"; + $defaultServerMysqlId = $db->fetchOne($sql); + if ($defaultServerMysqlId) { + $where = "id={$defaultServerMysqlId}"; + } else { + $where = "type='mysql' AND host='localhost'"; + } + $sql = "SELECT ds.host, ds.port, ds.admin_login, ds.admin_password FROM DatabaseServers ds WHERE {$where}"; + $clientDBServerCredentials = $db->fetchAll($sql)[0]; + if ($clientDBServerCredentials['host'] === 'localhost' && Util::isLinux()) { + $clientDBServerCredentials['admin_password'] = Util::retrieveAdminMySQLDbPassword(); + } + if (empty($clientDBServerCredentials['port'])) { + $clientDBServerCredentials['port'] = self::getPleskDbPort(); + } + + return $clientDBServerCredentials; + } + + public static function retrieveAdminMySQLDbPassword() + { + return Util::isLinux() + ? trim( Util::readfile("/etc/psa/.psa.shadow") ) + : null; + } + + public static function exec($cmd, &$code) + { + $log = Log::getInstance(); + + if (!$cmd) { + $log->info('Unable to execute a blank command. Please see ' . LOG_PATH . ' for details.'); + + $debugBacktrace = ""; + foreach (debug_backtrace() as $i => $obj) { + $debugBacktrace .= "#{$i} {$obj['file']}:{$obj['line']} {$obj['function']} ()\n"; + } + $log->debug("Unable to execute a blank command. The stack trace:\n{$debugBacktrace}"); + $code = 1; + return ''; + } + exec($cmd, $output, $code); + return trim(implode("\n", $output)); + } + + public static function readfile($file) + { + if (!is_file($file) || !is_readable($file)) { + return null; + } + $lines = file($file); + return $lines === false + ? null + : trim(implode("\n", $lines)); + } + + public static function readfileToArray($file) + { + if (!is_file($file) || !is_readable($file)) { + return null; + } + $lines = file($file); + return $lines === false + ? null + : $lines; + } + + public static function getSettingFromPsaConf($setting) + { + $file = '/etc/psa/psa.conf'; + if (!is_file($file) || !is_readable($file)) + return null; + $lines = file($file); + if ($lines === false) + return null; + foreach ($lines as $line) { + if (preg_match("/^{$setting}\s.*/", $line, $match_setting)) { + if (preg_match("/[\s].*/i", $match_setting[0], $match_value)) { + $value = trim($match_value[0]); + return $value; + } + } + } + return null; + } + + public static function getPhpIni() + { + if (Util::isLinux()) { + // Debian/Ubuntu /etc/php5/apache2/php.ini /etc/php5/conf.d/ + // SuSE /etc/php5/apache2/php.ini /etc/php5/conf.d/ + // CentOS 4/5 /etc/php.ini /etc/php.d + if (PleskOS::isRedHatLike()) { + $phpini = Util::readfileToArray('/etc/php.ini'); + } else { + $phpini = Util::readfileToArray('/etc/php5/apache2/php.ini'); + } + } + + return $phpini; + } + + public static function getUserBeanCounters() + { + if (!Util::isLinux()) { + + return false; + } + $user_beancounters = array(); + $ubRaw = Util::readfileToArray('/proc/user_beancounters'); + + if (!$ubRaw) { + + return false; + } + for ($i=2; $i<=count($ubRaw)-1; $i++) { + + if (preg_match('/^.+?:?.+?\b(\w+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)/', $ubRaw[$i], $limit_name)) { + + $user_beancounters[trim($limit_name[1])] = array( + 'held' => (int)$limit_name[2], + 'maxheld' => (int)$limit_name[3], + 'barrier' => (int)$limit_name[4], + 'limit' => (int)$limit_name[5], + 'failcnt' => (int)$limit_name[6] + ); + } + } + + return $user_beancounters; + } +} + +class PackageManager +{ + public static function buildListCmdLine($glob) + { + if (PleskOS::isRedHatLike()) { + $cmd = "rpm -qa --queryformat '%{NAME} %{VERSION}-%{RELEASE} %{ARCH}\\n'"; + } elseif (PleskOS::isDebLike()) { + $cmd = "dpkg-query --show --showformat '\${Package} \${Version} \${Architecture}\\n'"; + } else { + return false; + } + + if (!empty($glob)) { + $cmd .= " '" . $glob . "' 2>/dev/null"; + } + + return $cmd; + } + + /* + * Fetches a list of installed packages that match given criteria. + * string $glob - Glob (wildcard) pattern for coarse-grained packages selection from system package management backend. Empty $glob will fetch everything. + * string $regexp - Package name regular expression for a fine-grained filtering of the results. + * returns array of hashes with keys 'name', 'version' and 'arch', or false on error. + */ + public static function listInstalled($glob, $regexp = null) + { + $cmd = PackageManager::buildListCmdLine($glob); + if (!$cmd) { + return array(); + } + + $output = Util::exec($cmd, $code); + if ($code != 0) { + return false; + } + + $packages = array(); + $lines = explode("\n", $output); + foreach ($lines as $line) { + @list($pkgName, $pkgVersion, $pkgArch) = explode(" ", $line); + if (empty($pkgName) || empty($pkgVersion) || empty($pkgArch)) + continue; + if (!empty($regexp) && !preg_match($regexp, $pkgName)) + continue; + $packages[] = array( + 'name' => $pkgName, + 'version' => $pkgVersion, + 'arch' => $pkgArch + ); + } + + return $packages; + } + + public static function isInstalled($glob, $regexp = null) + { + $packages = PackageManager::listInstalled($glob, $regexp); + return !empty($packages); + } +} + +class Package +{ + function getManager($field, $package) + { + $redhat = 'rpm -q --queryformat \'%{' . $field . '}\n\' ' . $package; + $debian = 'dpkg-query --show --showformat=\'${' . $field . '}\n\' '. $package . ' 2> /dev/null'; + + if (PleskOS::isRedHatLike()) { + $manager = $redhat; + } elseif (PleskOS::isDebLike()) { + $manager = $debian; + } else { + return false; + } + + return $manager; + } + + /* DPKG doesn't supports ${Release} + * + */ + + function getRelease($package) + { + $manager = Package::getManager('Release', $package); + + if (!$manager) { + return false; + } + + $release = Util::exec($manager, $code); + if (!$code === 0) { + return false; + } + return $release; + } + + function getVersion($package) + { + $manager = Package::getManager('Version', $package); + + if (!$manager) { + return false; + } + + $version = Util::exec($manager, $code); + if (!$code === 0) { + return false; + } + return $version; + } + +} + +class PleskOS +{ + public static function isDebLike() + { + return is_file("/etc/debian_version"); + } + + public static function isRedHatLike() + { + return is_file("/etc/redhat-release"); + } + + public static function catEtcIssue() + { + $cmd = 'cat /etc/issue'; + $output = Util::exec($cmd, $code); + + return $output; + } + + public static function detectSystem() + { + $log = Log::getInstance('Detect system configuration'); + $log->info('OS: ' . (Util::isLinux() ? PleskOS::catEtcIssue() : 'Windows')); + $log->info('Arch: ' . Util::getArch()); + } +} + +class PleskValidator +{ + public static function validateIPv4($value) + { + $ip2long = ip2long($value); + if ($ip2long === false) { + return false; + } + + return $value == long2ip($ip2long); + } + + public static function validateIPv6($value) + { + if (strlen($value) < 3) { + return $value == '::'; + } + + if (strpos($value, '.')) { + $lastcolon = strrpos($value, ':'); + if (!($lastcolon && PleskValidator::validateIPv4(substr($value, $lastcolon + 1)))) { + return false; + } + + $value = substr($value, 0, $lastcolon) . ':0:0'; + } + + if (strpos($value, '::') === false) { + return preg_match('/\A(?:[a-f0-9]{1,4}:){7}[a-f0-9]{1,4}\z/i', $value); + } + + $colonCount = substr_count($value, ':'); + if ($colonCount < 8) { + return preg_match('/\A(?::|(?:[a-f0-9]{1,4}:)+):(?:(?:[a-f0-9]{1,4}:)*[a-f0-9]{1,4})?\z/i', $value); + } + + // special case with ending or starting double colon + if ($colonCount == 8) { + return preg_match('/\A(?:::)?(?:[a-f0-9]{1,4}:){6}[a-f0-9]{1,4}(?:::)?\z/i', $value); + } + + return false; + } +} + +class CheckRequirements +{ + function validate() + { + if (!PleskInstallation::isInstalled()) { + //:INFO: skip chking mysql extension if plesk is not installed + return; + } + + $reqExts = array(); + foreach ($reqExts as $name) { + $status = extension_loaded($name); + if (!$status) { + $this->_fail("PHP extension {$name} is not installed"); + } + } + } + + function _fail($errMsg) + { + echo '===Checking requirements===' . PHP_EOL; + echo PHP_EOL . 'Error: ' . $errMsg . PHP_EOL; + exit(1); + } +} + +class GetOpt +{ + var $_argv; + var $_adminDbPasswd; + + public function __construct() + { + $this->_argv = $_SERVER['argv']; + if (empty($this->_argv[1]) && Util::isLinux()) { + $this->_adminDbPasswd = Util::retrieveAdminMySQLDbPassword(); + } else { + $this->_adminDbPasswd = $this->_argv[1]; + } + } + + public function validate() + { + if (empty($this->_adminDbPasswd) && PleskInstallation::isInstalled()) { + echo 'Please specify Plesk database password'; + $this->_helpUsage(); + } + } + + public function getDbPasswd() + { + return $this->_adminDbPasswd; + } + + public function _helpUsage() + { + echo PHP_EOL . "Usage: {$this->_argv[0]} " . PHP_EOL; + exit(1); + } +} + +function fatal($msg) +{ + $log = Log::getInstance(); + $log->fatal($msg); + exit(1); +} + +function main() +{ + $log = Log::getInstance(); + + global $options; + //:INFO: Validate options + $options = new GetOpt(); + $options->validate(); + + //:INFO: Validate PHP requirements, need to make sure that PHP extensions are installed + $checkRequirements = new CheckRequirements(); + $checkRequirements->validate(); + + //:INFO: Validate Plesk installation + PleskInstallation::validate(); + + //:INFO: Detect system + $pleskOs = new PleskOS(); + $pleskOs->detectSystem(); + + //:INFO: Need to make sure that given db password is valid + if (PleskInstallation::isInstalled()) { + $log->step('Validating the database password'); + $pleskDb = PleskDb::getInstance(); + $log->resultOk(); + } + + //:INFO: Dump script version + $log->step('Pre-Upgrade analyzer version: ' . PRE_UPGRADE_SCRIPT_VERSION); + + //:INFO: Validate known OS specific issues with recommendation to avoid bugs in Plesk + $pleskKnownIssues = new Plesk17KnownIssues(); + $pleskKnownIssues->validate(); + + $plesk175Requirements = new Plesk175Requirements(); + $plesk175Requirements->validate(); + + $plesk178Requirements = new Plesk178Requirements(); + $plesk178Requirements->validate(); + + $plesk18Requirements = new Plesk18Requirements(); + $plesk18Requirements->validate(); + + $log->dumpStatistics(); + $log->writeJsonFile(); + + if ($log->getEmergency() > 0) { + exit(2); + } + + if ($log->getErrors() > 0 || $log->getWarnings() > 0) { + exit(1); + } +} + +if (!defined('UNIT_TEST_MODE')) { + main(); +} +// vim:set et ts=4 sts=4 sw=4: diff --git a/root/parallels/pool/PSA_18.0.74_18022/examiners/php_launcher.sh b/root/parallels/pool/PSA_18.0.74_18022/examiners/php_launcher.sh new file mode 100755 index 0000000000..70ebd0f0c6 --- /dev/null +++ b/root/parallels/pool/PSA_18.0.74_18022/examiners/php_launcher.sh @@ -0,0 +1,38 @@ +#!/bin/sh +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +die() +{ + echo $* + exit 1 +} + +[ -n "$1" ] || die "Usage: $0 php_script [args...]" + +[ "X${PLESK_INSTALLER_DEBUG}" = "X" ] || set -x +[ "X${PLESK_INSTALLER_STRICT_MODE}" = "X" ] || set -e + +php_bin= + +lookup() +{ + [ -z "$php_bin" ] || return + + local paths="$1" + local name="$2" + + for path in $paths; do + if [ -x "$path/$name" ]; then + php_bin="$path/$name" + break + fi + done +} + +lookup "/usr/local/psa/admin/bin /opt/psa/admin/bin" "php" +lookup "/usr/local/psa/bin /opt/psa/bin" "sw-engine-pleskrun" + +[ -n "$php_bin" ] || \ + die "Unable to locate the sw-engine PHP interpreter to execute the script. Make sure that Parallels Plesk Panel is installed on this server." + +exec "${php_bin}" "$@" diff --git a/root/parallels/pool/PSA_18.0.74_18022/examiners/plesk_preupgrade_checker.log b/root/parallels/pool/PSA_18.0.74_18022/examiners/plesk_preupgrade_checker.log new file mode 100644 index 0000000000..b8325f8371 --- /dev/null +++ b/root/parallels/pool/PSA_18.0.74_18022/examiners/plesk_preupgrade_checker.log @@ -0,0 +1,68 @@ + +INFO: Installed Plesk version/build: 18.0.73 Ubuntu 24.04 1800251124.20... + +INFO: STEP 0: Detect system configuration... +INFO: OS: Ubuntu 24.04.3 LTS \n \l +INFO: Arch: x86_64 + +INFO: Validating the database password... +INFO: Result: OK + +INFO: Pre-Upgrade analyzer version: 18.0.74.0... + +INFO: STEP 1: Checking for main IP address... +INFO: Result: OK + +INFO: STEP 2: Checking existence of Plesk user "root" for MariaDB/MySQL database servers... +INFO: Result: OK + +INFO: STEP 3: Checking proftpd settings... +INFO: Result: OK + +INFO: STEP 4: Checking the 'Interval' parameter in the sw-collectd configuration file... +INFO: Result: OK + +INFO: STEP 5: Checking Apache status... +INFO: Result: OK + +INFO: STEP 6: Checking Panel files for the immutable bit attribute... +INFO: Result: OK + +INFO: STEP 7: Checking the possibility to change the permissions of files in the DUMP_D directory... +INFO: Result: OK + +INFO: STEP 8: Checking consistency of the IP addresses list in the Panel database... +INFO: Result: OK + +INFO: STEP 9: Checking installed APS applications... +INFO: Result: OK + +INFO: STEP 10: Checking if apsc database tables have InnoDB engine... +INFO: Result: OK + +INFO: STEP 11: Checking for custom web server configuration templates... +INFO: Result: OK + +INFO: STEP 12: Checking for domains with mixed case names... +INFO: Result: OK + +INFO: STEP 13: Checking symbolic link /usr/local/psa on /opt/psa... +INFO: Result: OK + +INFO: STEP 14: Detecting if encrypted passwords are used... +INFO: Result: OK + +INFO: STEP 15: Checking table "servers" in database "mysql"... +INFO: The administrator's password for the default MariaDB/MySQL server is encrypted. +INFO: Result: OK + +INFO: STEP 16: Checking the availability of Plesk Panel TCP ports... +INFO: Result: OK + +INFO: STEP 17: Checking for Protected Directory Users with duplicates in login field.... + +INFO: STEP 18: Checking "domains" table with duplicates in guid field.... + +INFO: STEP 19: Checking "clients" table with duplicates in guid field.... + +INFO: STEP 20: Checking if any rules are configured in the Firewall extension... diff --git a/root/parallels/pool/PSA_18.0.74_18022/examiners/py_launcher.sh b/root/parallels/pool/PSA_18.0.74_18022/examiners/py_launcher.sh new file mode 100755 index 0000000000..96dc215391 --- /dev/null +++ b/root/parallels/pool/PSA_18.0.74_18022/examiners/py_launcher.sh @@ -0,0 +1,30 @@ +#!/bin/sh +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +die() +{ + echo "$*" + exit 1 +} + +[ -f "$1" ] || die "Usage: $0 PEX [args...]" + +[ "X${PLESK_INSTALLER_DEBUG}" = "X" ] || set -x +[ "X${PLESK_INSTALLER_STRICT_MODE}" = "X" ] || set -e + +find_python_bin() +{ + local bin + for bin in "/opt/psa/bin/py3-python" "/usr/local/psa/bin/py3-python" "/usr/libexec/platform-python" "/usr/bin/python3" "/opt/psa/bin/python" "/usr/local/psa/bin/python" "/usr/bin/python2"; do + [ -x "$bin" ] || continue + python_bin="$bin" + return 0 + done + + return 1 +} + +find_python_bin || + die "Unable to locate Python interpreter to execute the script." + +exec "$python_bin" "$@" diff --git a/root/parallels/pool/PSA_18.0.74_18022/examiners/repository_check.sh b/root/parallels/pool/PSA_18.0.74_18022/examiners/repository_check.sh new file mode 100755 index 0000000000..090f121ea1 --- /dev/null +++ b/root/parallels/pool/PSA_18.0.74_18022/examiners/repository_check.sh @@ -0,0 +1,782 @@ +#!/bin/bash +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +[ -z "$PLESK_INSTALLER_DEBUG" ] || set -x +[ -z "$PLESK_INSTALLER_STRICT_MODE" ] || set -e + +export LC_ALL=C +unset GREP_OPTIONS + +RET_SUCCESS=0 +RET_WARN=1 +RET_FATAL=2 + +is_function_defined() +{ + local fn="$1" + case "$(type $fn 2>/dev/null)" in + *function*) + return 0 + ;; + esac + return 1 +} + +# @params are tags in format "key=value" +# Report body (human readable information) is read from stdin +# and copied to stderr. +make_error_report() +{ + local report_file="${PLESK_INSTALLER_ERROR_REPORT:-}" + + local python_bin= + for bin in "/opt/psa/bin/python" "/usr/local/psa/bin/python" "/usr/bin/python2" "/opt/psa/bin/py3-python" "/usr/local/psa/bin/py3-python" "/usr/libexec/platform-python" "/usr/bin/python3"; do + if [ -x "$bin" ]; then + python_bin="$bin" + break + fi + done + + if [ -n "$report_file" -a -x "$python_bin" ]; then + "$python_bin" -c 'import sys, json +report_file = sys.argv[1] +error = sys.stdin.read() + +sys.stderr.write(error) + +data = { + "error": error, +} + +for tag in sys.argv[2:]: + k, v = tag.split("=", 1) + data[k] = v + +with open(report_file, "a") as f: + json.dump(data, f) + f.write("\n") +' "$report_file" "date=$(date --utc --iso-8601=ns)" "$@" + else + cat - >&2 + fi +} + +detect_platform() +{ + . /etc/os-release + os_name="$ID" + os_version="${VERSION_ID%%.*}" + os_arch="$(uname -m)" + if [ -e /etc/debian_version ]; then + case "$os_arch" in + x86_64) pkg_arch="amd64" ;; + aarch64) pkg_arch="arm64" ;; + esac + if [ -n "$VERSION_CODENAME" ]; then + os_codename="$VERSION_CODENAME" + else + case "$os_name$os_version" in + debian10) os_codename="buster" ;; + debian11) os_codename="bullseye" ;; + debian12) os_codename="bookworm" ;; + ubuntu18) os_codename="bionic" ;; + ubuntu20) os_codename="focal" ;; + ubuntu22) os_codename="jammy" ;; + ubuntu24) os_codename="noble" ;; + esac + fi + fi + + case "$os_name$os_version" in + rhel7|centos7|cloudlinux7|virtuozzo7) + package_manager="yum" + ;; + rhel*|centos*|cloudlinux*|almalinux*|rocky*) + package_manager="dnf" + ;; + debian*|ubuntu*) + package_manager="apt" + ;; + esac + + if [ "$os_name" = "ubuntu" -o "$os_name" = "debian" ]; then + PRODUCT_ROOT_D="/opt/psa" + else + PRODUCT_ROOT_D="/usr/local/psa" + fi +} + +has_os_impl_function() +{ + local prefix="$1" + local fn="${prefix}_${os_name}${os_version}" + is_function_defined "$fn" +} + +call_os_impl_function() +{ + local prefix="$1" + shift + local fn="${prefix}_${os_name}${os_version}" + "$fn" "$@" +} + +skip_checker_on_flag() +{ + local name="$1" + local flag="$2" + + if [ -f "$flag" ]; then + echo "$name was skipped due to flag file." >&2 + exit $RET_SUCCESS + fi +} + +skip_checker_on_env() +{ + local name="$1" + local env="$2" + + if [ -n "$env" ]; then + echo "$name was skipped due to environment variable." >&2 + exit $RET_SUCCESS + fi +} + +checker_main() +{ + local fnprefix="$1" + shift + + detect_platform + # try to execute checker only if all attributes are detected + [ -n "$os_name" -a -n "$os_version" ] || return $RET_SUCCESS + + for checker in "${fnprefix}_${os_name}${os_version}" "${fnprefix}_${os_name}" "${fnprefix}"; do + if is_function_defined "$checker"; then + local rc=$RET_SUCCESS + "$checker" "$@" || rc=$? + [ "$(( $rc & $RET_FATAL ))" = "0" ] || return $RET_FATAL + [ "$(( $rc & $RET_WARN ))" = "0" ] || return $RET_WARN + return $rc + fi + done + return $RET_SUCCESS +} + +#!/bin/sh +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +# If env variable PLESK_INSTALLER_ERROR_REPORT=path_to_file is specified then in case of error +# repository_check.sh writes single line json report into it with the following fields: +# - "stage": "repositorycheck" +# - "level": "error" +# - "errtype" is one of the following: +# * "reponotcached" - repository is not cached (mostly due to unavailability). +# * "reponotenabled" - required repository is not enabled. +# * "reponotsupported" - unsupported repository is enabled. +# * "configmanagernotinstalled" - dnf config-manager is disabled. +# - "repo": repository name. +# - "date": time of error occurance ("2020-03-24T06:59:43,127545441+0000") +# - "error": human readable error message. + +report_no_repo() +{ + local repo="$1" + + make_error_report 'stage=repositorycheck' 'level=error' 'errtype=reponotenabled' "repo=$repo" <<-EOL + Plesk installation requires '$repo' OS repository to be enabled. + Make sure it is available and enabled, then try again. + EOL +} + +report_no_repo_cache() +{ + local repo="$1" + + make_error_report 'stage=repositorycheck' 'level=error' 'errtype=reponotcached' "repo=$repo" <<-EOL + Unable to create $package_manager cache for '$repo' OS repository. + Make sure the repository is available, otherwise either disable it or fix its configuration, then try again. + EOL +} + +report_unsupported_repo() +{ + local repo="$1" + + make_error_report 'stage=repositorycheck' 'level=error' 'errtype=reponotsupported' "repo=$repo" <<-EOL + Plesk installation doesn't support '$repo' OS repository. + Make sure it is disabled, then try again. + EOL +} + +report_rh_no_config_manager() +{ + local target + case "$package_manager" in + yum) + target="yum-utils package" + ;; + dnf) + target="config-manager dnf plugin" + ;; + esac + + make_error_report 'stage=repositorycheck' 'level=error' 'errtype=configmanagernotinstalled' <<-EOL + Failed to install $target. + Make sure repositories configuration of $package_manager package manager is correct + (use '$package_manager repolist --verbose' to get its actual state), then try again. + EOL +} + +check_rh_broken_repos() +{ + local rh_enabled_repos rh_available_repos + + # 1. `yum repolist` and `dnf repolist` list all repos + # which were enabled before last cache creation + # even if cache for them was not created. + # If some repo is misconfigured and cache was created with `skip_if_unavailable=1` + # then such repo will be listed anyway despite on cache state. + # If some repo was enabled after last cache creation + # then `repolist --cacheonly` will fail. + # 2. `yum repolist --verbose` and `dnf repoinfo` list only repos + # which were successfully cached before. + # These commands fail if at least one repo is not available + # and the 'skip_if_unavailable' flag is not set. + case "$package_manager" in + yum) + rh_enabled_repos="$( + { + yum repolist enabled --cacheonly -q 2>/dev/null \ + || yum repolist enabled -q --setopt='*.skip_if_unavailable=1' + } | sed -n -e '1d' -e 's/^\*\?!\?\([^/[:space:]]\+\).*/\1/p' + )" || return $RET_FATAL + + rh_available_repos="$( + yum repolist enabled --verbose --cacheonly -q --setopt='*.skip_if_unavailable=1' \ + | sed -n -e 's/^Repo-id\s*:\s*\([^/[:space:]]\+\).*/\1/p' + )" || return $RET_FATAL + ;; + dnf) + rh_enabled_repos="$( + { + dnf repolist --enabled --cacheonly -q 2>/dev/null \ + || dnf repolist --enabled -q --setopt='*.skip_if_unavailable=1' + } | sed -n -e '1d' -e 's/^!\?\(\S\+\).*/\1/p' + )" || return $RET_FATAL + + rh_available_repos="$( \ + dnf repoinfo --enabled --cacheonly -q --setopt='*.skip_if_unavailable=1' \ + | sed -n -e 's|^Repo-id\s*:\s*\(\S\+\)\s*$|\1|p' + )" || return $RET_FATAL + ;; + esac + + local rh_enabled_repos_f="$(mktemp /tmp/plesk-installer.preupgrade_checker.XXXXXX)" + echo "$rh_enabled_repos" | sort > "$rh_enabled_repos_f" + local rh_available_repos_f="$(mktemp /tmp/plesk-installer.preupgrade_checker.XXXXXX)" + echo "$rh_available_repos" | sort > "$rh_available_repos_f" + + local repo rc=0 + for repo in $(comm -23 "$rh_enabled_repos_f" "$rh_available_repos_f"); do + report_no_repo_cache "$repo" + rc=$RET_WARN + done + + rm -f "$rh_enabled_repos_f" "$rh_available_repos_f" + + return $rc +} + +has_rh_enabled_repo() +{ + local repo="$1" + + # Try to get list of repos from cache first. + # If some repo was enabled after last cache creation + # or some repo is unavailable the query from cache will fail. + # Try to fetch actual metadata in this case. + case "$package_manager" in + yum) + # Repo-id may end with OS version and/or architecture + # if baseurl of the repo refers to $releasever and/or $basearch variables + # eg 'epel/7/x86_64', 'epel/7', 'epel/x86_64' + { + yum repolist enabled --verbose --cacheonly -q 2>/dev/null \ + || yum repolist enabled --verbose -q --setopt='*.skip_if_unavailable=1' + } | grep -E -q "^Repo-id\s*: $repo(/.+)?\s*$" + ;; + dnf) + # note: --noplugins may cause failure and empty output on RedHat + { + dnf repoinfo --enabled --cacheonly -q 2>/dev/null \ + || dnf repoinfo --enabled -q --setopt='*.skip_if_unavailable=1' + } | grep -E -q "^Repo-id\s*: $repo\s*$" + ;; + esac +} + +has_rh_config_manager() +{ + case "$package_manager" in + yum) yum-config-manager --help >/dev/null 2>&1 ;; + dnf) dnf config-manager --help >/dev/null 2>&1 ;; + esac +} + +install_rh_config_manager() +{ + case "$package_manager" in + yum) yum install --disablerepo 'PLESK_*' -q -y 'yum-utils' --setopt='*.skip_if_unavailable=1' ;; + dnf) dnf install --disablerepo 'PLESK_*' -q -y 'dnf-command(config-manager)' --setopt='*.skip_if_unavailable=1' ;; + esac +} + +check_rh_config_manager() +{ + if ! has_rh_config_manager && ! install_rh_config_manager; then + report_rh_no_config_manager + return $RET_FATAL + fi +} + +enable_rh_repo() +{ + case "$package_manager" in + yum) yum-config-manager --enable "$@" && has_rh_enabled_repo "$@" ;; + dnf) dnf config-manager --set-enabled "$@" && has_rh_enabled_repo "$@" ;; + esac +} + +enable_sm_repo() +{ + ! has_rh_enabled_repo "$@" || return 0 + subscription-manager repos --enable "$@" || return $? + # On RedHat 8 above command may return 0 on failure with "Repositories disabled by configuration." + has_rh_enabled_repo "$@" +} + +check_epel() +{ + ! enable_rh_repo "epel" || return 0 + + # try to install epel-release from centos/extras or plesk/thirdparty repo + # and then try to update it to last version shipped by epel itself + # to make package upgradable with pum + "$package_manager" install --disablerepo 'PLESK_*' -q -y 'epel-release' --setopt='*.skip_if_unavailable=1' 2>/dev/null \ + || "$package_manager" install --disablerepo='*' --enablerepo 'PLESK_18_*-thirdparty' -q -y 'epel-release' \ + || "$package_manager" install -q -y "https://dl.fedoraproject.org/pub/epel/epel-release-latest-$os_version.noarch.rpm" \ + && "$package_manager" update -q -y 'epel-release' --setopt='*.skip_if_unavailable=1' 2>/dev/null + + # Ensure any other EPEL repos have cache for subsequent check for broken repos (AL9) + local epel_repos="$( + [ "$package_manager" != "dnf" ] || { + dnf repolist --enabled --cacheonly -q 2>/dev/null || + dnf repolist --enabled -q --setopt='*.skip_if_unavailable=1' + } | sed -n -e '1d' -e 's/^!\?\(epel\S\+\).*/\1/p' + )" + for repo in $epel_repos; do + "$package_manager" makecache --repo "$repo" -q + done + + ! has_rh_enabled_repo "epel" || return 0 + + report_no_repo "epel" + return $RET_FATAL +} + +check_codeready() +{ + local repo_rhel="codeready-builder-for-rhel-$os_version-$os_arch-rpms" + local repo_rhui="codeready-builder-for-rhel-$os_version-rhui-rpms" + local repo_rhui_alt="codeready-builder-for-rhel-$os_version-$os_arch-rhui-rpms" + local repo_rhui_alt2="rhui-codeready-builder-for-rhel-$os_version-$os_arch-rhui-rpms" + + ! enable_sm_repo "$repo_rhel" || return 0 + ! enable_rh_repo "$repo_rhui" || return 0 + ! enable_rh_repo "$repo_rhui_alt" || return 0 + ! enable_rh_repo "$repo_rhui_alt2" || return 0 + + report_no_repo "$repo_rhel" + return $RET_FATAL +} + +check_optional() +{ + local repo_rhel="rhel-$os_version-server-optional-rpms" + local repo_rhui="rhel-$os_version-server-rhui-optional-rpms" + + ! enable_sm_repo "$repo_rhel" || return 0 + ! enable_rh_repo "$repo_rhui" || return 0 + + report_no_repo "$repo_rhel" + return $RET_FATAL +} + +check_repos_rhel9() +{ + check_rh_config_manager || return $? + + local rc=0 + + check_epel || rc="$(( $rc | $? ))" + check_codeready || rc="$(( $rc | $? ))" + check_rh_broken_repos || rc="$(( $rc | $? ))" + + return $rc +} + +check_repos_almalinux9() +{ + check_rh_config_manager || return $? + + local rc=0 + check_epel || rc="$(( $rc | $? ))" + check_rh_broken_repos || rc="$(( $rc | $? ))" + + # powertools is renamed to crb since AlmaLinux 9 + ! enable_rh_repo "crb" || return $rc + + report_no_repo "crb" + return $RET_FATAL +} + +check_repos_cloudlinux9() +{ + check_repos_almalinux9 "$@" +} + +check_repos_almalinux10() +{ + check_repos_almalinux9 "$@" +} + +check_repos_centos8() +{ + check_rh_config_manager || return $? + + local rc=0 + check_epel || rc="$(( $rc | $? ))" + check_rh_broken_repos || rc="$(( $rc | $? ))" + + # names of repos are lowercased since 8.3 + ! enable_rh_repo "powertools" || return $rc + ! enable_rh_repo "PowerTools" || return $rc + + report_no_repo "powertools" + return $RET_FATAL +} + +check_repos_cloudlinux8() +{ + check_rh_config_manager || return $? + + local rc=0 + check_epel || rc="$(( $rc | $? ))" + check_rh_broken_repos || rc="$(( $rc | $? ))" + + # names of repos are changed since 8.5 + ! enable_rh_repo "powertools" || return $rc + ! enable_rh_repo "cloudlinux-PowerTools" || return $rc + + report_no_repo "powertools" + return $RET_FATAL +} + +check_repos_rhel8() +{ + check_rh_config_manager || return $? + + local rc=0 + check_epel || rc="$(( $rc | $? ))" + check_rh_broken_repos || rc="$(( $rc | $? ))" + + [ "$1" = "install" ] || return $rc + + check_codeready || rc="$(( $rc | $? ))" + + return $rc +} + +check_repos_almalinux8() +{ + check_repos_centos8 "$@" +} + +check_repos_rocky8() +{ + check_repos_centos8 "$@" +} + +check_repos_rhel7() +{ + check_rh_config_manager || return $? + + local rc=0 + + check_epel || rc="$(( $rc | $? ))" + check_optional || rc="$(( $rc | $? ))" + check_rh_broken_repos || rc="$(( $rc | $? ))" + + return $rc +} + +check_repos_centos7_based() +{ + check_rh_config_manager || return $? + + local rc=0 + + check_epel || rc="$(( $rc | $? ))" + check_rh_broken_repos || rc="$(( $rc | $? ))" + + return $rc +} + +sed_escape() +{ + # Note: this is not a full implementation + echo -n "$1" | sed -e 's|\.|\\.|g' +} + +switch_eol_centos_repos() +{ + local old_mirrorlist_host="mirrorlist.centos.org" + local old_host="mirror.centos.org" + local new_host="vault.centos.org" + + grep -qFw "$old_host" /etc/yum.repos.d/CentOS-*.repo 2>/dev/null || return 0 + local backup="`mktemp -d "/tmp/yum.repos.d-$(date --rfc-3339=date)-XXXXXX"`" + ! [ -d "$backup" ] || cp -raT /etc/yum.repos.d "$backup" || : + + sed -i \ + -e "s|^\s*\(mirrorlist\b[^/]*//`sed_escape "$old_mirrorlist_host"`/.*\)$|#\1|" \ + -e "s|^#*\s*baseurl\b\([^/]*\)//`sed_escape "$old_host"`/\(.*\)$|baseurl\1//$new_host/\2|" \ + /etc/yum.repos.d/CentOS-*.repo + echo "YUM package manager repositories were backed up to '$backup' and switched from $old_host to $new_host ." >&2 +} + +check_repos_centos7() +{ + switch_eol_centos_repos + + check_repos_centos7_based "$@" +} + +check_repos_cloudlinux7() +{ + check_repos_centos7_based "$@" +} + +check_repos_virtuozzo7() +{ + check_repos_centos7_based "$@" +} + +find_apt_repo() +{ + local repo="$1" + + local dist_tag= + ! [ "$os_name" = "ubuntu" ] || dist_tag="a" + ! [ "$os_name" = "debian" ] || dist_tag="n" + + if [ -z "$_apt_cache_policy" ]; then + # extract info of each available release as a string which consists of 'tag=value' + # filter out releases with priority less or equal to 100 + _apt_cache_policy="$( + apt-cache policy \ + | grep "b=$pkg_arch" \ + | grep -Eo '([a-z]=[^,]+,?)*' \ + )" + fi + + local l="$(echo "$repo" | cut -f1 -d'/')" + local d="$(echo "$repo" | cut -f2 -d'/')" + local c="$(echo "$repo" | cut -f3 -d'/')" + + # try to find releases by distribution and component + echo "$_apt_cache_policy" \ + | grep -E "(^|,)l=$l(,|$)" \ + | grep -E "(^|,)$dist_tag=$d(,|$)" \ + | grep -E "(^|,)c=$c(,|$)" \ + | while IFS="$(printf '\n')" read rel && [ -n "$rel" ]; do + l="$(echo "$rel" | grep -Eo "(^|,)l=[^,]+" | cut -f2 -d"=")" + d="$(echo "$rel" | grep -Eo "(^|,)$dist_tag=[^,]+" | cut -f2 -d"=")" + c="$(echo "$rel" | grep -Eo "(^|,)c=[^,]+" | cut -f2 -d"=")" + echo "$l/$d/$c" + done +} + +apt_install_packages() +{ + DEBIAN_FRONTEND=noninteractive LANG=C PATH=/usr/sbin:/usr/bin:/sbin:/bin \ + apt-get -qq --assume-yes -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold -o APT::Install-Recommends=no \ + install "$@" +} + +# Takes a list of suites and disables them in APT sources. +# Multiline deb822 format is supported. +disable_apt_suites_deb822() +{ + local python3=/usr/bin/python3 + + "$python3" -c 'import aptsources.sourceslist' 2>/dev/null || + apt_install_packages python3-apt + + "$python3" -c ' +import sys + +from aptsources.sourceslist import SourcesList + + +suites_to_disable=set(sys.argv[1:]) + +sources_list = SourcesList(deb822=True) + +sources_changed = False +for src in sources_list: + if src.invalid: + continue + suites = getattr(src, "suites", ()) + if not suites: + continue + new_suites = [s for s in suites if s not in suites_to_disable] + if len(new_suites) != len(suites): + sources_changed = True + if len(new_suites) == 0: + src.disabled = True + else: + src.suites = new_suites + +if sources_changed: + sources_list.save() +' "$@" + + # Since we have changed the repositories list, we should re-read _apt_cache_policy on a next call + # of the find_apt_repo function. Hence we have to reset the value of the variable + _apt_cache_policy="" +} + +disable_apt_repo() +{ + local repos_to_disable="$(find_apt_repo "$1" | cut -d '/' -f 2,3 | sort | uniq)" + if [ -z "$repos_to_disable" ]; then + return 0 + fi + + echo "$repos_to_disable" \ + | while IFS= read -r repo_to_disable && [ -n "$repo_to_disable" ]; do + local distrib=${repo_to_disable%%/*} + local component=${repo_to_disable##*/} + find /etc/apt -name "*.list" -exec \ + sed -i -e "/^\s*#/! s/.*\s$distrib\s\+$component\b/# &/" {} + + done + + # Since we have changed the repositories list, we should re-read _apt_cache_policy on a next call + # of the find_apt_repo function. Hence we have to reset the value of the variable + _apt_cache_policy="" + + return 0 +} + +check_required_apt_repo() +{ + local repo="$1" + [ -z "$(find_apt_repo "$repo")" ] || return 0 + report_no_repo "$repo" + return $RET_FATAL +} + +check_unsupported_apt_repos_ubuntu() +{ + [ -n "$os_codename" ] || return 0 + local mode="$1" + + local repos="$( + find_apt_repo "Ubuntu/[^,]+/[^,]+" | grep -v "Ubuntu/$os_codename.*/.*" + find_apt_repo "Debian[^,]*/[^,]+/[^,]+" + )" + [ -n "$repos" ] || return 0 + + echo "$repos" | while IFS="$(printf '\n')" read repo; do + report_unsupported_repo "$repo" + done + + [ "$mode" = "install" ] || return $RET_WARN + return $RET_FATAL +} + +check_repos_ubuntu18() +{ + [ -n "$os_codename" ] || return 0 + local mode="$1" + local rc=0 + + check_required_apt_repo "Ubuntu/$os_codename/main" || rc="$(( $rc | $? ))" + check_required_apt_repo "Ubuntu/$os_codename/universe" || rc="$(( $rc | $? ))" + check_required_apt_repo "Ubuntu/$os_codename-updates/main" || rc="$(( $rc | $? ))" + check_required_apt_repo "Ubuntu/$os_codename-updates/universe" || rc="$(( $rc | $? ))" + check_unsupported_apt_repos_ubuntu "$mode" || rc="$(( $rc | $? ))" + + return $rc +} + + +check_repos_ubuntu() +{ + [ -n "$os_codename" ] || return 0 + local mode="$1" + local rc=0 + + check_required_apt_repo "Ubuntu/$os_codename/main" || rc="$(( $rc | $? ))" + check_required_apt_repo "Ubuntu/$os_codename/universe" || rc="$(( $rc | $? ))" + check_unsupported_apt_repos_ubuntu "$mode" || rc="$(( $rc | $? ))" + + return $rc +} + +check_unsupported_apt_repos_debian() +{ + [ -n "$os_codename" ] || return 0 + local mode="$1" + + local repos="$( + find_apt_repo "Debian Backports/$os_codename-backports/[^,]+" + find_apt_repo "Debian[^,]*/[^,]+/[^,]+" | grep -v "Debian.*/$os_codename.*/.*" + find_apt_repo "Ubuntu/[^,]+/[^,]+" + )" + [ -n "$repos" ] || return 0 + + echo "$repos" | while IFS="$(printf '\n')" read repo; do + report_unsupported_repo "$repo" + done + + [ "$mode" = "install" ] || return $RET_WARN + return $RET_FATAL +} + +check_repos_debian() +{ + [ -n "$os_codename" ] || return 0 + local mode="$1" + local rc=0 + + if [ "$os_name" = "debian" -a "$os_version" -ge 12 ]; then + disable_apt_suites_deb822 "$os_codename-backports" + else + disable_apt_repo "Debian Backports/$os_codename-backports/[^,]+" + fi + + check_required_apt_repo "Debian/$os_codename/main" || rc="$(( $rc | $? ))" + check_unsupported_apt_repos_debian "$mode" || rc="$(( $rc | $? ))" + + return $rc +} + +# --- + +skip_checker_on_flag "Repository check" "/tmp/plesk-installer-skip-repository-check.flag" + +checker_main 'check_repos' "$1" diff --git a/root/parallels/pool/PSA_18.0.74_18022/examiners/sh_cmd.sh b/root/parallels/pool/PSA_18.0.74_18022/examiners/sh_cmd.sh new file mode 100755 index 0000000000..ed95d0acbb --- /dev/null +++ b/root/parallels/pool/PSA_18.0.74_18022/examiners/sh_cmd.sh @@ -0,0 +1,7 @@ +#!/bin/sh +### Copyright 1999-2025. WebPros International GmbH. All rights reserved. + +[ "X${PLESK_INSTALLER_DEBUG}" = "X" ] || set -x +[ "X${PLESK_INSTALLER_STRICT_MODE}" = "X" ] || set -e + +exec "$@" diff --git a/root/parallels/pool/PSA_18.0.74_18022/plesk-18.0.74-ubt24.04-x86_64.inf3 b/root/parallels/pool/PSA_18.0.74_18022/plesk-18.0.74-ubt24.04-x86_64.inf3 new file mode 100644 index 0000000000..88d52d32e8 --- /dev/null +++ b/root/parallels/pool/PSA_18.0.74_18022/plesk-18.0.74-ubt24.04-x86_64.inf3 @@ -0,0 +1,928 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -----BEGIN PGP PUBLIC KEY BLOCK----- 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 VNnMRY8n =Hv0N -----END PGP PUBLIC KEY BLOCK----- + + + psa + + + + + + + + + + + + + + + + + + + + + + + + + + + + + mysqlgroup + l10n + proftpd + webservers + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + imapservers + + + + + + + + + + + + + + + + + + + imapservers + + + + + + + + + + + + + + mailman + spamassassin + drweb + sophos + courier + dovecot + + + + + + + + + + + + + + + + + + + + + + mailservers + + + + + + + + + + + + + + + + + + + + mailservers + + + + + + + + + + + + + + + + + + panel + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + + + + + + + + php8.3 + + + + + + + + + + + webservers + + + + + + + + + + + + + + + + + + + + + panel + + + + + + + + + + + + + panel + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + panel + + + + + + + + + + + + + + + + + + + + panel + + + + + + + panel + + + + + + + + panel + + + + + + + panel + passenger + + + + + + ruby + + + + + + + panel + + + + + + panel + + + + + + panel + passenger + + + + + + + + + + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + + + panel + + + + panel + roundcube + postfix + dovecot + mod_fcgid + proftpd + webalizer + awstats + webservers + nginx + mysqlgroup + l10n + bind + wp-toolkit + advisor + git + xovi + imunify360 + fail2ban + modsecurity + sslit + letsencrypt + repair-kit + composer + monitoring + log-browser + ssh-terminal + site-import + sitejet + ntp-timesync + php8.3 + php8.4 + mfa + configurations-troubleshooter + + + panel + roundcube + postfix + dovecot + mod_fcgid + proftpd + webalizer + awstats + webservers + nginx + mysqlgroup + l10n + bind + wp-toolkit + advisor + git + xovi + imunify360 + fail2ban + modsecurity + sslit + letsencrypt + repair-kit + composer + monitoring + log-browser + ssh-terminal + site-import + sitejet + ntp-timesync + php8.1 + php8.2 + php8.3 + php8.4 + mfa + configurations-troubleshooter + resctrl + drweb + postgresql + spamassassin + ruby + gems-pre + nodejs + pmm + psa-firewall + watchdog + passenger + phpgroup + sophos + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/pool/PSA_18.0.74_18022/release.inf3 b/root/parallels/pool/PSA_18.0.74_18022/release.inf3 new file mode 100644 index 0000000000..bcac0846cb --- /dev/null +++ b/root/parallels/pool/PSA_18.0.74_18022/release.inf3 @@ -0,0 +1,36 @@ + + + + + + + + + psa + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/pool/PSA_18.0.75_18153/release.inf3 b/root/parallels/pool/PSA_18.0.75_18153/release.inf3 new file mode 100644 index 0000000000..383bf61a46 --- /dev/null +++ b/root/parallels/pool/PSA_18.0.75_18153/release.inf3 @@ -0,0 +1,36 @@ + + + + + + + + + psa + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/pool/WPB_18.0.55_74/release.inf3 b/root/parallels/pool/WPB_18.0.55_74/release.inf3 new file mode 100644 index 0000000000..94de1181b8 --- /dev/null +++ b/root/parallels/pool/WPB_18.0.55_74/release.inf3 @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/pool/WPB_18.0.59_78/release.inf3 b/root/parallels/pool/WPB_18.0.59_78/release.inf3 new file mode 100644 index 0000000000..2ca3d25c6f --- /dev/null +++ b/root/parallels/pool/WPB_18.0.59_78/release.inf3 @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/pool/WPB_18.0.69_87/release.inf3 b/root/parallels/pool/WPB_18.0.69_87/release.inf3 new file mode 100644 index 0000000000..a3ef0137bb --- /dev/null +++ b/root/parallels/pool/WPB_18.0.69_87/release.inf3 @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/pool/WPB_18.0.69_87/sitebuilder-18.0.69-deball-all.inf3 b/root/parallels/pool/WPB_18.0.69_87/sitebuilder-18.0.69-deball-all.inf3 new file mode 100644 index 0000000000..3b4244abb6 --- /dev/null +++ b/root/parallels/pool/WPB_18.0.69_87/sitebuilder-18.0.69-deball-all.inf3 @@ -0,0 +1,28 @@ + + + -----BEGIN PGP PUBLIC KEY BLOCK----- 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 VNnMRY8n =Hv0N -----END PGP PUBLIC KEY BLOCK----- + + + pp-sitebuilder + + + + + + + + diff --git a/root/parallels/pool/WPB_18.0.69_88/release.inf3 b/root/parallels/pool/WPB_18.0.69_88/release.inf3 new file mode 100644 index 0000000000..e545568925 --- /dev/null +++ b/root/parallels/pool/WPB_18.0.69_88/release.inf3 @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/pool/WPB_18.0.69_88/sitebuilder-18.0.69-deball-all.inf3 b/root/parallels/pool/WPB_18.0.69_88/sitebuilder-18.0.69-deball-all.inf3 new file mode 100644 index 0000000000..3efbc63385 --- /dev/null +++ b/root/parallels/pool/WPB_18.0.69_88/sitebuilder-18.0.69-deball-all.inf3 @@ -0,0 +1,28 @@ + + + -----BEGIN PGP PUBLIC KEY BLOCK----- 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 VNnMRY8n =Hv0N -----END PGP PUBLIC KEY BLOCK----- + + + pp-sitebuilder + + + + + + + + diff --git a/root/parallels/pp-sitebuilder.inf3 b/root/parallels/pp-sitebuilder.inf3 new file mode 100644 index 0000000000..f51b0c544f --- /dev/null +++ b/root/parallels/pp-sitebuilder.inf3 @@ -0,0 +1,712 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/products.inf3 b/root/parallels/products.inf3 new file mode 100644 index 0000000000..3c554e76db --- /dev/null +++ b/root/parallels/products.inf3 @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/report-update b/root/parallels/report-update new file mode 100644 index 0000000000000000000000000000000000000000..fc90b069d0a1bf9a9323c8fa43452c26c8c96883 GIT binary patch literal 392468 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%0DpcitD}$ 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⁣st5Uc2AlY{(b;0{0BOGI2?m4jBuk^sGTbC zam?)8(LV-#W1b)q;c~D?3>}`)HEB>R##eMZI|$c`ST`-qO^dpHoXJBP%ff`kOTz9Z z5`n~6cJ=qEHOB=ymT3W!0zd$sot;+m9;RyeP^Id--cR6aeoUg7?GKrs@-MxGhGTFUOEkX^WOo-^E77^ zjh6_{e%j}7uuV3U?F}(`kf9Xw;Rh`p1eqryLL{X#5kujS@((sej>mAeZt0XQCA0JD zOUh%SEThU_IDn_J8UaJQTBl7w|2Mz}p2=(Bbf%604FC~VgG5C5%aO^ivn#=3qIU-K z=c%4%uvY<#+9ol@h_+3_<%ARyZ#5+ytHPak8rWVwr?OQk%li-7JiH zfjx%~MghWza#E@508HplXUTmLfGjdoQhd83^;&mX* zDv5i{y!ABt(pJ;xm+0`7_%s@Wl(Q{{f}o3`u{@(t57M)TWxH#y>0;^cqwu)aB_p;O z0dwvv)(&=UwKO>D#(R=VPx`4`(v3>-5w_Te5oDN@dqhZiXd_p` z7en#Za@xHEjf5$`5jvK#(yaulnKL_kW5eCIG%g(bw9)6O3tgEs`s~WI2@9kv)5e`+ zAWfVzML9FAZYt)?e0Y#EQ<-L@cpCXLUxY8y{X-o72!|g3Wm<;XYZtM{hDi5M!X6x8 zhx~(n-(}~PSbs&P$i9{GJV`DYyvX0gM5$vcJOT=hHS3%qfsPjcROOi;6?t|f8tjf^ckj4$d-)O?XbHb=7@lG>6bqBczA5jahidRiX=L=V4&kRno6wS32np86F3@ zmZ+G#q9=S!6s%ylm*^T#GDOoZfr=sF1gXGu_n5)Y(ln%3yJ=D~=!%2pa2Kk_d%Ky~ zT@J0|rS*~)?y{srtV9u&q%@>HRnKe9*J4RmrO272gP|B&Ty2KTa|(=B$zPrxERxl* zNC$`{#XnhXG;rsdMQ5$1bR7<+BAVv2S1DFa0s$PvgDk{xAuNrWaKKB7oRd=ay;kKQ z;~x39G#wM}L)nb!?p8P_W$6QIt#-)n!&T&(*~e>>q0LF6q$O!|`f9TQ%lxcLjP)SI z#n}Y6=NcKLDOOWL!$eUpcZ@GqBUyQMj`RCCFs2n{BG<4yP8T}oa3M*KKNJj+VCcAh zrPY*lKc|OJPO7Y_WdRlqAYljj3IkSYI@d^@jHJ`QXVxOilhI-}tSjX-j`#LXL5S%8 z&C*d}1)G%loS7khGG>lrF%Z(u@n2^&DUpZsaLkCQ^KUd|f;>bAbJm3v0!@i*rs*%U zXl!JWv%yc1bdh_(B$8>qV7gD!tl`+JjiK%k&9IDn`pOi|rgV3xPOAJyzMJQH{AV<) zvb}e`Za3c@qWSlSU7P-ljbSzGKdZj_n4hEXh9FQk$NZc!HRSo1-mMQ)ox@0KVjEOu z2Bp(nm*4(jH;4M5@op6S0=-#a@jF`|>E?N!bKTU2(Qd9gL~R}MM{#e{s&DI#Y?}0L zzCK1}ZdW~$;U0m7XuLbhI`zKpC^3wk8{4RaZNyu5(AyJu@`8EgD6t5;OAs7m16()D zc_|zsdu->02$WhJ_G)qW`CAE?%$ZRZh z21G@QaZ)|Lsv_iBs$B?uhf}dc2<(K2tyPDFtrBn{%ko0_J(dAQ1@sWN{uWpD!r?G& zb~Dl@qa96`sTg| zu0+GZF%a4t4W1&$AwoDc9)upPxzJXktM5q$5+5o^q$)#0thAVTBiC`UNP3Br|H%x1 zw*)qax(`RTdM_AOXv+DfQiN0P{Yfp3Dfc_IWY6~gAU{VsVujTVUh5-mC%pRaD>xv@ z2IAstGmGT%dp@em@yshU>FW*T~Q9D!0En!T6*Vv|Q=PmkY z<}3^Pj5UftOcn+UX&iUsj6>*<7;!AM)6A#x=1!}S>Ws%Fd1-6Bz^ALzmy{qBwY z+H)i~=2;O~BOWtc&u$uv=0;(CoZioM3sfprzKmQpnn!KJA6m}4KW!3MFpbXc?kMq~ zVd6lx``K&trgh%9Y&^qj4>m^MJo>b8rT(-L);uC>CVrInhwBA!99*BLw@ATw_A*+U zC0Mic2Dh}E?PeCF@d(TLr16NRlNxcRqkX#aY`d|#SZh{5=OZd}YOT709E_aW=l=Vn z2P}%k%a~G07K#rRV;NG|9#N4+dy|Q;x%h}xOse`t&1DBy;@#xc9gxSb*0*#jDtfTP9@>)Et($E1X@?ENjz-3q+w4UIKgxqq#w(E4(NuLlImH!KW~mh zDz2g|h%u9Iqt|pU*Hyzr)r{N4NQ~VtYmI!s`{Qgx->qTO0|pYonGE98&9n5FpG+|-d4q}I90iv55|0VJ?1=bHDl~IW>eP^f(QPOfW02c!;9-Z zNd||I58_8vSA+}M-B8_tIhDN|bL-)1cv-n3buo2AkDmH;wz$x!E*9Zxfepb!wl5Cq zzvifsjJUjyR1ZETj4HO+YKSNems%*P&`8>5IZ>DACvukY_=f_JDoRYp=KwWWjYGH; zvUu+y&Zv5SadI}%XR}aAF^xFo(`*T&6}M9KyseVcDp#}&dPtg8Ahz^UHC>oWWac_N zfq|){__0M>CIbtTgl#eFr2HKf7j>oFW+lu)!}BcBFbQ}RV+R7&j%(SZt}2*{;Z#ll z(ZY<332>TtB$CcX9mjP`&^Zs93uo7p-YO$M+XOT|_e!98)pM^TI>3moMf{kyJL2wO zXD1T!#snfH)Cp45Xer=~a)?eOKR{sMR5zS)(Y=TROmd}dEc=x6(QuY?nv%FLVK1wm z4CQU=C2dJ7=c552nI_(y?p}PF_m--nbqx%{z17rm5G11VkQ!OX8=TfM&fcn!bdYn` zxHD(vAn8~+DaDYyGe&=-^k=Yl5Zg8L^;zU*0;by>2q8@ zl-;$RU5h>FDQSab?>JJAvYV`mRgtGS>6Ju=BvwV1`J|S}4pJp2iks0TWkefWZzkI< z&#TQ5Luj&(q!enJaU}u!8ciuL(Yg+S>rwuiBBD-uxscoosmD8|+@_M!#?;#WGQ+&g zQl*F~8ewzU%e>lHV1AKQD!d1n<+)D7xms=Zc4pO$t1a`5Rkkc?UoktLnfPc6R#an6 zt)LuehApM!^!OHS(NP9i_cq{gMmA=k*9aE%FQl~BXo%3z9JP9cX>QwKpCwS5O{B(h z#vNsYsq$d5Ks>=NrNRbhj%BPXhEYVv&#>B7oSlz26_BG(!Jm{Jg+}uNVa6iDn-cse z|6MChI{8jc7dOyVI}{q|ejycl1M+GaaKh&tzBstysEekRbOaRG5x`BplDiyBruyF_ zz$7^P&Hf=6qkF%aGNMGo39m$Cl-HG5p!QS#xiw=zb|D)DU`~l$83vhA{tK5^(85D`%a`a%HYEFJBxtSfhLc`MsW{4vTAvi!Bpq+E8ER(UR*k`L z7$s6P3>o-{MAk;d?4}sw3?18~{4}>y4_wrBZDncftukVN$NOTLLNnQ{{D5;v&PMS? zk$WZ7qawnd{3fP(^_XU3ItY99q(U`Vg-X6H6^poDqL&WOs;G+=OOw@x=HNXHnaz8O zk?K#A;`9xc_lGhfgQXj=0? zxPywky-s+>vWeB2@+}U^g;`I=fd0vuo+XgV5h-)h$;BqD9ufA$=H|mmQ%*AM3O&v8 za$+{Hl-f>oWlT7YS=FO}D76P*vT9C}rYs-4F7qWI?X&fPZ{P&d3{FR-6aIzylF6y1Zad|o|8HQmG6pt94?d-qcth7iiR=pmYZes04 zT)|!1@rk^W_~1GqyZ6w`wjIUajmJTipC^{{C-!epA@HM!h1oFICOHJYNXpZ#*aBhG z3>B!9QlR!J|=a4rzi%lO)a z&qvRP!^yb-^2yuYA}OcN0b7-6Uc(^V1VTpv)lrnGQg|kTFC*p_2y(zgmP9S_<>X3$ zQL{4^M-&jS!9stuBLoMNVDkYE9$_uoG{emTs>&*M$Zs8j1bcq=D&Q$~I=WXhs$@f|mzX=Vm6T1=GvA^NAr%`r|$(b*R7WRP<$ zKqmqPF`Qj13=Oq8HhE{4x@m1+R}pz4l4E$*HJ3B)emn!2WUG_eRh+l_MN2uJ63i3M z$E2}Ih72lt2?ibW*39-;lcDu{%JvTv*(7}**~G&D_;?BI7;-p1yc(l;wSFg9Jc3`w z{7&o0eF$^xOTWGqlwH~+!s^m{oYFd)DXJ%Fa|*pVk%W+F%5;fIcXcrMF$saAqqs!1 zV?~b@K4#G$5HS%8tB8aWnWiud^zIE4fuUW6bTe}aK^z#GiymNBCl@ko5d^^5ft$s| zu5~fbL!+VP`52rWpC-ctfE!bhmnDWKy^#fjU7M$2&g$%pPs1oMVDSDu6FeJv4vuwI>O>`q{2Qv{oD786YF$9wN;|8R zo2xZ?yLFA73^9jgvZR(8ZxrNsiD-%erC`vLn8V5Vd%c`&CYefvAxf=9p=E4tg`+kZ ztC~~0>0rJj%E*ZEFzgk)D;i7XOw^;yC_$5NI_aTelkrlkwXD26*v|&IILBZ|$v2o; zDi@mc5%Cfsn&J{mm^Js(yag=dSx_hovt_(Ao=>s%RNci@Mh>+~SJM0PaNJGuk^yx8 zWmt_BvehACs|m?if|uBcEnFFZgx_mLY1HNT|8b;^#5S^Sp=*Y_ma3c*GK?8uuQqEW zYKDYOE<#q+ZVM+c%(=|)V71d?{#H@LPuO8TV$7aCZIOe82Mhby@hjTHw;L)Z&69jL zg`AmduZqO~u8!$W`cIBt<|Po#QQt;{_ky|8;BY^gy6WCD%|je)0|}rOuIBAs7!apW zo!d@SnLd-iahHqnk3H1;2){@3ap^ewnh}YcVabXXOOwN#ahcDO1wuL5dD z%?WF?zaRPaL_da01+~__s1jp7pQ7I4lzs~q8^y6&DfLQ{m9N~Yd`+p&QC=0;OmL9i z@I2+mj4OgxWIa%OK*n1gq=;v+zphV-tyH)L6o@bsk>)wmAqe&1Wl*VBG0TgX)5e>s_#w_g2 z4d?9qHaZUFcjgPZOs?#FJbDdO-@2DZ&B|w#1u&{+Lt6BMva9Q zXdFNF<*+z1e9B}i=urUzJ z+ANV98EkKVd*2|IZIua3l%;*^Oa-vA8&RHu1?-*;JG)sPag?Qf(~Q~Lk+T39 z3igS;;wALfIHS%>-FF*E<02D<4C+*A(NbIa%xs+#U)tM4e^^l9p%k0}W{dqpcvgK= zXv9(bQ^`P3G9$jw8H96&dZ@Gs+q z4ZX>Z226nv?KSLgz8To@(#I397q^66r&5bY*@RN$qlR2I8RlUp$*{eXq(|h?wgnrR zkcgP`Ss2|^g_lfLGO&YGvfb2MO_?g8^YX`Pasvj*qgSw%`J(hakSs@7x8lY5HBfNU zD{)|Yb%Q{YLZf;^KW6E1aZ$3)d_Kw4dxQbfj0m-TcU`4K7*G_tU-^Ql)I68U9R4m( z79Ks6RmS)-j5A@{ZaBOAR`}_8$6cznJ@~X--XL=o5i_9GFr@KH0?xP*%b~^sLOk&j zTx#>xW`2ID+FVpC4|+hDd_605CSmciNE^ZJ5K*h2vm%@M9PfsQ zxJ$Y44NAKlNVby0qd^lz*}cQ1*WPT>ABXHY9Ca0j>Mq3WMC{)L{|T4ksW@V}SP__) zScasHe#~p(QBFb;xmJ8Fk7`a6-?vIy%;p;{k7pmvkx6Ndfbmd8$@Y5EqxUD0mgSKJJ(BEG{1K z;u4@9rY*vHRb3pQ9xi#rL=5S7v~f!qa2IzNsg*n|==4=8t%F=Ruaai#0!Po)z%vpB zsn4fWN(x*oOPOjB+p+TS%L16>w$vn7d#@I;^PL;n|2p;|f2A!G`wzLV2AkpejST(L zC50`OkZEZhTfkI$HxqRq#FXA!xmIouNwO z%W-9l1_xWj(L&}JB0=FQ5ww~Rj#N-2vjGahgGNj!nvM}7d8;%L2{jJWuySBYtL?xy z)|R0!$e_L2M1Fj^M>I5ILLg|Zt(ciunR-HGMeKh~M_I7xzq(5E!sX2^+tqbgN2iOI zt11&UX_}Ez3TFZDYaPKIBZ__TIu{)$N@DI5@3`-kFK1XeL6C507B##Ge*>%OmowL} z9KfCF{K{RNdk8$1c^=JA&TVm+?UyRNcC~?NqFADAzKl;`{g_)gFumtAc2)Fv&z`D; zrnfPZ7$GiTdcgk2I!XBT*mY5}>(X>-SWZGt|L|&Nqw2-LMgU)sg+aX~oFIF#a^~{6 zOOMNw$1a?Afy?emd^7wc4F$#pLUBrFp0oL@)QY#*D@{I3`1!k`H9x=CMj+{gY1zbg zr~35P>&*F(2PbwJn2yh`gTAsNB z{c$YqLU9&W>mL8JPE5s%EqFVWB?mhh5w`0rXdyolt)}xp&+h3S?jDF@3DiDg>;6(0 zL{@D`qOA%Ntr%Kg=;ssawR!2^j(E+vnX}Ins|!e$N3$8tLiqoBF9(p;E_lpcUhf#P zA{j5fF2i?b&RvoU6*nGC7j~@9!=YotCh??&EtN_M#*bo@ z^Gi>pM8cp@P>2@75|C0iLbUq2l7M;rknEJWU!}*j6{ieo!i(BkZG~{U_7B^T)*~I8 zQ76IbPJwx)=a*10wzBPrDrwELTdEpn{QQ z+MOoW|M08DmLi6RW$rTB%++?>rQMX95~F6hRiT;z1NYUNjv_EwI6%o@YSHA@lQPmE z!f7}v#d=aO@FWKX-IUZP%qh0GT6L==#5&2; zCR_9laE4fV^`0M(Mq)k6JNt_N2s(N~a>VP0_3C7-QH`-mg*LG`2j>3ao|Z|I3@5^&0cTD&kEA|?PV-8f zZbkVk!osAY)!?jZ34QRI4&xXg%vYny*tbX-qf06DK77kTM{BO^S=0&gi<+OCVBwD8QUOo*o?hlh%vFfYb;n| zoR2&Bc#GJH-^I*^xz^x-fM=hzu!cz#Hte+-UQ~HEvPhuT<&66wE;ZA&dH1^X9!_6; z#XWCaaW5k~+2{0g2<#iSz7iv_)nA#tZ1`C*3SmZMh+gcf7O2}!VmtH|2pMtZR~p8& zBBedIVJwUHVg&&#@Yf3pW2smOl`oU3*t+JiY8R0#2Y^R)q40BDY4N`RGdXTH&&4;IDl<*-3RY=InW4#S>AEhqpj3(dijP=$V zWRIUefAaXH<1?h-T|9N+{DpI;&zwCK3_X7CiBl&poO$Y0ki+Tu6QA zIL4-&kpmZG>A-^fplBPIx9$NcIf5ydwK!G;anpsAow8(3b9 zNh`TITa#}ls(}^A!);$1Q!}^$SAQ_t68V`iXme)uY|4@`H9=$~5 zg11mcM1btErwx&&e#>+oWoU1{^K|H`e8?ZW0RYEJ(t(YJlY{q2ek~4&u@16&OW!AO z3rXgn8`z05d0SV5475T$noZ@Z7F%IL>)#GqPK4%e_pbesB_eIxj-A) zP?ND^i@Eac80G;0nw>k?vy{X_C?ob?xOdoBi~#{#Ryj?785Ovd zRe@VM6}UB|0=M!iaBElvZWWx7`lu)^o9?raL$MsXnG@skOf8fiQwycHtKhA16}+{B zx0}IsGIG0_Thw+lJJohGyVQ0wyVZ6xd&B`TNx!%9q&Vn)7F7S#B_g6A2fDLksyql? zxW66S0z$G2thw*Vy+ygjy*HCkfWtV^NkfKt5OE?(j-}Vqm(2Gp62YQ>D(F(B!U7o0qhG<${F;$)wUv3nj2wMd!L?%ls{?2abeNBY!-`d3x6F|48}3~+ zFsv-3u%n8?>fFLIqRk3Ym4&8@wSPMDWUIZjIH>6pA+0kz`xCVf-XJXwN*`}jt7@!G z6*@ChN(EM}6=WY>_Z{!dxkNC}mS7=9>3d>e6}JX!K=e@v)I_|009XPn8wZXB0fKeRtwg$>f0<~22Vn8X}83Ydk13w*uXHNp2#Aadx0l8HH(%Kz@B3G%! zz{!#7!Vx5H4Inu&kOr|%?2Y)@>w=^wE$_iS7_NN^E-$ZAxAeni&Bz&;a_azV>1JTV zmW@B0);4}(QFbqUi49i=f_CQ(K!YOB0H!2>o%E`|aOhQ79h!hM$Os49u-^#YFC^g| zywF^4dR$kjl@t|lKcY*yUM<+e@+V>=_R=kTMD3dT4d8S}hHBm%R=u1^)lpBgDJ$JYN}tRz)=}#@?9t^u=MUkxp5HV!GX8Kk(@l%)W@;nr7rNOl z7(~i6nh#-X)yBxm=tj;o{B4LN6FI79jA}8v=>@w>rBS)VU0Z!W#A&$I_bfByXF55# zaf5fbGd|PO8`(;y=v{8d8p#J{{_Exl$8rX?vxW_~45@XoDXp|!ZZ`WqJ&~nO%Xq?L z6A2yv5{K=E+^wrB+(&TTM!7?nwp^Thg#-79i@VrGu9TPK$$M1%g~65=2C+P>_QHV7 zU*4uJH2V)S62$@=1dPiJKj1q9Ho5qfw8A=rm-ZG|#~$Uhx%f9~6|7sWg4x-XMCNGg zi-GRe0@FR|Xyq=qW;(By&X~3eHDG~Xu1eNkz}03fNxqa3kzLcFUf(g(^3P~t(-C@) z`YpXOV2OVa32&C!*`5^Mh~fR80o}cJ0|ydF-5)1F-H`;fpML${0~RFbYYAX`gyLQr zk`Vfepl(#U(lX5j6FMfqM{MXtxxJw^^L6tuvE^ihoSYYGt%VK8?m!xh3Hav`wX@CL zW@ju3;X-pTufEgJyc*g2-Q6l-19NSkg$LEgshJ?hu6ClCod<8&+e+DCi$`nKFCNg} zPL>W(Gk@FAdP)a$<3?Y9_;8%x(twB>d!XzV~7k&zUEnqpR)EU0Y^8Sp-d6wfKnW6L4s11vs9_m9J-kC?@gwj;J`$Qm~N}( z%lU3c6Cp^o8p2&4uv!_58tjac=8RN^-3p_;BMH3m&?OSR7Vmv*9JN(CJ}!75rUGC7 zr=~Kzxv)JL|6X-N{M%~3c?_jAC@SDe3zjp6%)%C5&SBElwdGbyaxh_Bv~iDcY~ zlhVO>(~NcNs>g3oHOR>BSk*iGs=f}R%$8Q2v^{AMn`DuJhjsH*Tb$n)~K$`iRwc)tcOh>H&xeRUDwuSDs}XxiaVl< zORoM+!Wb`a>&3qJ#fd<&A2dhYVjFozs9Gq5tmqjh$^9VI!eHMMM=iM%pluCalwIsW zEytF~wP}{qPMXk)L#~5Dxz>K7+N>=AH#+;I+ppF}a2($&@)pc0#6K}}^Xk{T*U)Pr z-(P%}@eVB9Ca(Ql?wu;{2JcyA$yJhSHp`usQ}HDddN=D>5_!Edyoxd51nf%#~Vuh6KiN2dG5OGWZ+&BhC@i#?YmC@3o7It23Fhh9bYlCJTFeOP#c6v=QK zZd&hDIbRnn&R=55x#X^Kz=bU}e`Y2r06E@XO@)_pkf^|lIN3c> zX-qFUBeUVOH{Exd3Wgxf6+D#D>cmjq%G(92Gu{uy42j8urqrD%>^*EHfub{T61|Nc zzdEDEr4&t~9ei)_9J;e_4%O4__JN;F%1v1*w+9nXI4dnIkx9&_yLgcGBfTCjoLmYC zf`Yz*MIN2eaPF-j7ABFwBowJH7;zD1!}Jwm)NM`@XDnyEX_@Q>8=jJ|{kYr=Qm*O6 zs>Uu0)?E|3d-8iZ_OD(J@$xKnB9^~sXpjz9t!)W+_X?ACMQ^DXR@OCRQJKW9VnTP5 z<`^&fI?19oT8_2`Y5w6+bl=C1bir`}-2G-e|G)qN{JrctRYcOTv7U4%iQr>aXV0Jk zng*1J5gCL}6R1DWmN+IN3=r2a5VfAC2bW@~MOE{?KGed0tXN)89A`2Lkvvz!SzL;d_Y$*I{)!2qdnj!oJyPc}}jeeU< zozSSB-i0(X8IAMO{v&0;Ut>!RC#EK`>izt9Y1no4C#s5 zG~In&llPQEeo@}lfNhYxXsK?>G!c=-caL0^$qUdCTPqpHLh&w-rZHAFy59-Z@zIdc z;~w^TC1EdaLk}_w-k{~cBNfPhB+Rg}@a>_XkbaToo{uQA+=3j z0-UPbyQoF({awpXv+0bJ2FRAz*($|W*sPEnXj_RMNXcCnuh$JZ*#q1cZwcKqZ`ZxT z#wPa@k!l;{c)Zag-__6Xf=q=MyuUxAqZ3nk>W_Wsk?};2@N0w99gV53kX=wuX}g3S zjG`kep|~QMADd`DZf?mGva0jHEhNBJXGa_s^*l6DfF^|@H?s;DFIpZaO%+mbL6Q>E zWI_iXtc5mqyk6VawRtbvv=nq$$VqSVbh~mtCvwe%iCj}NotO88tW1{V8V@zO)j9^4 z6J;`bz|E32jp3WLfjk(C%o~0S-4#s0nox1TaHCZU(y;hRe6d}xz6qZaa?96c32H-- znAPQbI2wT8@dmn9VJ!cSjEb-^Ou6(289FBr)ogZdkN5xJfStJIHk>8<4Q;t)Z-kAx zf)FS*9w{WugC04u-2j#iSsCX-*BeDX>>>}_tTfAuE*2@Opl9YPhqLOkOLq0ZZ#>Spw_3iRo z6Sa?bbBH+gcVJWMZhAp(DR0&%=sJt@^-=#8eSX=z1aTviRuO)(f8KgOVO^~U1tc&xM1{vnHNZRNAmwM{+^Ri9Sz3=`I zu0LnWP28#tnMcEJt@;b)jL*xNduan&{G*r?kMNuTzqHMrse(0O+1Rd@j$_0{ZEH7E zi&5m@F-NU&I4)As%*5MFS>x8I)pDLP( z%VcDTEZ0MF5!_G+fE?;@e7T%*0TAePVu86(9^v=(o!+tj0)6wRp##dst`~4YO|ZET zBj4GT7{3UE6M9f0RhZnOYdPm0TO>ZPcDG2Kgk8!~@ua3Z(d9sG$rZv4`b^%hh`!~} z*spJER*}2Af_-hL{WbsVJ9;=IZjuc6WiqJU#S3<~UeCy;Abx*24^_sLn=hMd0s;zkNm<6J zL#hXJTzkBmayz&pR89#)SZ`z04|B}dsf+AAY>N@8J%-x+_-Hp(&tc1DJUu3J_3jV5 z)4ZsiA@)qfZ6${tDi0x950g0(u66oToPA@EC^5I~*tTukwrzXP*f!4Cwr$(CZQDHK zH@9BZt@`e-cVAa0)xUP7(?x?!n1af0dY&~2Sj)La530_#eMO4chQ54?SNwNJbIeE@)(055w5&$ zUPEj%Z&w-uJ{m(t6yS_G(V;MCeEp@E^AiSF4-G}=8N-05qv}MQnI|}Cg_W5gWH2X= zpnx>TqNHSS8nvfuCX_5HmU)u*Qpp(?4z_h$7rsYUA&6#AzT3S&eLPV zxjL>o;x_NeP-Q~}@*?P?6!ChHC<|iAS%wwG7ea?Hq>7)sKUEbLgv3 z_anp#b!xwru9O2i!MJ_I-Mk?K1raJfGR4ZHfgK-(e&j3|1a=9lc?~BQ{ZMe2ttKZ{ z`+YbH)2$0P8Wqfld1C<9s)F<}Y^Y5?x?Qlj{$>#QhEW+7s?gFm5CjyK7z7Mn`dCq>a#pkJe_04<{`87N)60Vbj^e#oUxhnv` z36I2tH2$e4t(yBsJ-HEGEizvgR5Szpj$AqMdJYB&tPM3~oP zZM}c){VqcW2{rs1a_zDbOGcV;~&!$2y})CQ944% zUR;>JvB20hu&<|Uu?!f$`XT4$&5>(?^YldpnxMQA38Bv73%WxNn_bt<^ynAL*cUWC z?XT}RliRY8A09EP7g{%8?oQXNPjX^K9_kCmT?~^bnC}NHu8hBIAy)yWnpPj5UHm%q zeWAjB40T8BOxrM{yl<(|)TQ*3M-b~X#5_-3wD=$(GC;v~jKy98rJ`anQz80=h;#{A zN78a-K=xcOPtC<*GD{>V|DeKPPV%ED=%Xc|p3RhHy(nc3G(f^v!`jH8^& z;w50c!b61K(P2g<3@Imrj=q85dc%MLD9yW;;bCiCmBU>4#M= zj>~^ki=VNvwgz4K<)QD1#gH~jEI~S4Q_eD24<1hK#Y{=rmEE~T!nhAy_g;oR*--S< z67V2DpTWK{@t*24me5EUZs57S_6`yC{n1G!Nd=_RxI1br1LtUsw>bGk+zOg3;frwV z#PrJ?Og$(CVEu&A6hd|EhOye=>EwF5IRuwyViJto=S|cwa#MG)=ic`F=Ta zDcIebOz3ur);+42~MJ&WGr&}$520BN-A|C_~?ov-lk=BQMWA{W_e z3YkCG4TPz+GIeh&opKw=x|VdBn=TcR zpfsQ@TRdC#R^IIRnwYUMUL0p%2%(k}$da)G$3*v&BIZ^9b9;|yNqZXS6?aOw2)R>_ z!rYJ*TJ4-2F6g0=GxNreZnq=C&>|;$NHDu}0U}M4)(wzp|pt)@6J3t5}DH;c=mwj>b84 zt|;n$T-5%=Q|~9sR^a(hm_HLhS&{ny_F>;4@+$A&Mun>nM}nKT<=Z}Wg?8Tj zD;EWmohSl)6@-xb%BbZ`f~;AU%Vsjp84E(|p5FW*>4cO}-$?FCl){

lqlWK9UHVX7x|1mu$(LEPq!yvY-PKH)hytH# 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


-7=t3Euo?ILfv)BFA* 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 MeX;#>9$Qkve@|AzrT_o{ literal 0 HcmV?d00001 diff --git a/root/parallels/setemplates.inf3 b/root/parallels/setemplates.inf3 new file mode 100644 index 0000000000..8b38e9106b --- /dev/null +++ b/root/parallels/setemplates.inf3 @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/root/parallels/sitebuilder.inf3 b/root/parallels/sitebuilder.inf3 new file mode 100644 index 0000000000..9da3099163 --- /dev/null +++ b/root/parallels/sitebuilder.inf3 @@ -0,0 +1,163 @@ + + + + + sitebuilder + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +