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-----
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-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-----
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
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-----
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-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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ webservers
+
+
+
+
+
+ panel
+
+
+
+
+
+ panel
+
+
+
+
+
+ panel
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ php7.4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ php8.3
+
+
+
+
+
+
+
+
+
+
+ webservers
+
+
+
+
+
+
+
+ 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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ webservers
+
+
+
+
+
+ panel
+
+
+
+
+
+ panel
+
+
+
+
+
+ panel
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ php7.4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ php8.3
+
+
+
+
+
+
+
+
+
+
+ webservers
+
+
+
+
+
+
+
+ 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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ webservers
+
+
+
+
+
+ panel
+
+
+
+
+
+ panel
+
+
+
+
+
+ panel
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ php7.4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ php8.3
+
+
+
+
+
+
+
+
+
+
+ webservers
+
+
+
+
+
+
+
+ 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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ webservers
+
+
+
+
+
+ panel
+
+
+
+
+
+ panel
+
+
+
+
+
+ panel
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ php7.4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ php8.3
+
+
+
+
+
+
+
+
+
+
+ webservers
+
+
+
+
+
+
+
+ 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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ webservers
+
+
+
+
+
+ panel
+
+
+
+
+
+ panel
+
+
+
+
+
+ panel
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ php7.4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ php8.3
+
+
+
+
+
+
+
+
+
+
+ webservers
+
+
+
+
+
+
+
+ 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 0000000000..fc90b069d0
Binary files /dev/null and b/root/parallels/report-update differ
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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+