work on backup storages

Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
This commit is contained in:
Michael Kaufmann
2023-07-27 11:08:27 +02:00
parent d1043b4645
commit c52d9bbd03
17 changed files with 623 additions and 143 deletions

View File

@@ -30,7 +30,7 @@ return [
'icon' => 'fa-solid fa-sliders',
'advanced_mode' => true,
'fields' => [
'system_backup_enabled' => [
'backup_enabled' => [
'label' => lng('serversettings.backup_enabled'),
'settinggroup' => 'backup',
'varname' => 'enabled',
@@ -40,74 +40,39 @@ return [
'overview_option' => true,
'cronmodule' => 'froxlor/backup'
],
'system_backup_type' => [
'label' => lng('serversettings.backup_type'),
'backup_default_storage' => [
'label' => lng('serversettings.backup_default_storage'),
'settinggroup' => 'backup',
'varname' => 'type',
'varname' => 'default_storage',
'type' => 'select',
'default' => 'Local',
'select_var' => [
'Local' => lng('serversettings.local'),
'SFTP' => lng('serversettings.sftp'),
'FTPS' => lng('serversettings.ftps'),
'S3' => lng('serversettings.s3'),
'default' => '1',
'option_options_method' => [
'\\Froxlor\\Backup\\Backup',
'getBackupStorages'
],
'save_method' => 'storeSettingField',
'overview_option' => true,
'save_method' => 'storeSettingField'
],
'system_backup_region' => [
'label' => lng('serversettings.backup_region'),
'backup_default_retention' => [
'label' => lng('serversettings.backup_default_retention'),
'settinggroup' => 'backup',
'varname' => 'region',
'type' => 'text',
'default' => 'eu-central-1',
'varname' => 'default_retention',
'type' => 'number',
'default' => 3,
'min' => 0,
'save_method' => 'storeSettingField',
],
'system_backup_bucket' => [
'label' => lng('serversettings.backup_bucket'),
'backup_default_customer_access' => [
'label' => lng('serversettings.backup_default_customer_access'),
'settinggroup' => 'backup',
'varname' => 'bucket',
'type' => 'text',
'default' => '',
'varname' => 'default_customer_access',
'type' => 'checkbox',
'default' => true,
'save_method' => 'storeSettingField',
],
'system_backup_destination_path' => [
'label' => lng('serversettings.backup_destination_path'),
'backup_default_pgp_public_key' => [
'label' => lng('serversettings.backup_default_pgp_public_key'),
'settinggroup' => 'backup',
'varname' => 'destination_path',
'type' => 'text',
'string_type' => 'confdir',
'default' => '/srv/backups/',
'save_method' => 'storeSettingField',
],
'system_backup_hostname' => [
'label' => lng('serversettings.backup_hostname'),
'settinggroup' => 'backup',
'varname' => 'hostname',
'type' => 'text',
'default' => '',
'save_method' => 'storeSettingField',
],
'system_backup_username' => [
'label' => lng('serversettings.backup_username'),
'settinggroup' => 'backup',
'varname' => 'username',
'type' => 'text',
'default' => '',
'save_method' => 'storeSettingField',
],
'system_backup_password' => [
'label' => lng('serversettings.backup_password'),
'settinggroup' => 'backup',
'varname' => 'password',
'type' => 'password',
'default' => '',
'save_method' => 'storeSettingField',
],
'system_backup_pgp_public_key' => [
'label' => lng('serversettings.backup_pgp_public_key'),
'settinggroup' => 'backup',
'varname' => 'pgp_public_key',
'varname' => 'default_pgp_public_key',
'type' => 'textarea',
'default' => '',
'save_method' => 'storeSettingField',
@@ -116,15 +81,6 @@ return [
'checkPgpPublicKeySetting'
],
],
'system_backup_retention' => [
'label' => lng('serversettings.backup_retention'),
'settinggroup' => 'backup',
'varname' => 'retention',
'type' => 'number',
'default' => 3,
'min' => 0,
'save_method' => 'storeSettingField',
],
]
]
]

View File

@@ -236,7 +236,7 @@ if (($page == 'customers' || $page == 'overview') && $userinfo['customers'] != '
$result_json = BackupStorages::getLocal($userinfo)->listing();
$result_decoded = json_decode($result_json, true)['data']['list'];
foreach ($result_decoded as $storagedata) {
$backup_storages[$storagedata['id']] = $storagedata['description'] . ' (' . $storagedata['type'] . ')';
$backup_storages[$storagedata['id']] = "[" . $storagedata['type'] . "] " . html_entity_decode($storagedata['description']);
}
} catch (Exception $e) {
/* just none */
@@ -335,7 +335,7 @@ if (($page == 'customers' || $page == 'overview') && $userinfo['customers'] != '
$result_json = BackupStorages::getLocal($userinfo)->listing();
$result_decoded = json_decode($result_json, true)['data']['list'];
foreach ($result_decoded as $storagedata) {
$backup_storages[$storagedata['id']] = $storagedata['description'] . ' (' . $storagedata['type'] . ')';
$backup_storages[$storagedata['id']] = "[" . $storagedata['type'] . "] " . html_entity_decode($storagedata['description']);
}
} catch (Exception $e) {
/* just none */

View File

@@ -46,6 +46,7 @@
"ext-fileinfo": "*",
"ext-gmp": "*",
"ext-gd": "*",
"ext-ftp": "*",
"phpmailer/phpmailer": "~6.0",
"monolog/monolog": "^1.24",
"robthree/twofactorauth": "^1.6",

131
composer.lock generated
View File

@@ -561,16 +561,16 @@
},
{
"name": "symfony/console",
"version": "v5.4.22",
"version": "v5.4.24",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "3cd51fd2e6c461ca678f84d419461281bd87a0a8"
"reference": "560fc3ed7a43e6d30ea94a07d77f9a60b8ed0fb8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/3cd51fd2e6c461ca678f84d419461281bd87a0a8",
"reference": "3cd51fd2e6c461ca678f84d419461281bd87a0a8",
"url": "https://api.github.com/repos/symfony/console/zipball/560fc3ed7a43e6d30ea94a07d77f9a60b8ed0fb8",
"reference": "560fc3ed7a43e6d30ea94a07d77f9a60b8ed0fb8",
"shasum": ""
},
"require": {
@@ -640,7 +640,7 @@
"terminal"
],
"support": {
"source": "https://github.com/symfony/console/tree/v5.4.22"
"source": "https://github.com/symfony/console/tree/v5.4.24"
},
"funding": [
{
@@ -656,7 +656,7 @@
"type": "tidelift"
}
],
"time": "2023-03-25T09:27:28+00:00"
"time": "2023-05-26T05:13:16+00:00"
},
{
"name": "symfony/deprecation-contracts",
@@ -1547,16 +1547,16 @@
},
{
"name": "twig/twig",
"version": "v3.5.1",
"version": "v3.7.0",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
"reference": "a6e0510cc793912b451fd40ab983a1d28f611c15"
"reference": "5cf942bbab3df42afa918caeba947f1b690af64b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/a6e0510cc793912b451fd40ab983a1d28f611c15",
"reference": "a6e0510cc793912b451fd40ab983a1d28f611c15",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/5cf942bbab3df42afa918caeba947f1b690af64b",
"reference": "5cf942bbab3df42afa918caeba947f1b690af64b",
"shasum": ""
},
"require": {
@@ -1565,15 +1565,10 @@
"symfony/polyfill-mbstring": "^1.3"
},
"require-dev": {
"psr/container": "^1.0",
"psr/container": "^1.0|^2.0",
"symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.5-dev"
}
},
"autoload": {
"psr-4": {
"Twig\\": "src/"
@@ -1607,7 +1602,7 @@
],
"support": {
"issues": "https://github.com/twigphp/Twig/issues",
"source": "https://github.com/twigphp/Twig/tree/v3.5.1"
"source": "https://github.com/twigphp/Twig/tree/v3.7.0"
},
"funding": [
{
@@ -1619,20 +1614,20 @@
"type": "tidelift"
}
],
"time": "2023-02-08T07:49:20+00:00"
"time": "2023-07-26T07:16:09+00:00"
},
{
"name": "voku/anti-xss",
"version": "4.1.41",
"version": "4.1.42",
"source": {
"type": "git",
"url": "https://github.com/voku/anti-xss.git",
"reference": "55a403436494e44a2547a8d42de68e6cad4bca1d"
"reference": "bca1f8607e55a3c5077483615cd93bd8f11bd675"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/voku/anti-xss/zipball/55a403436494e44a2547a8d42de68e6cad4bca1d",
"reference": "55a403436494e44a2547a8d42de68e6cad4bca1d",
"url": "https://api.github.com/repos/voku/anti-xss/zipball/bca1f8607e55a3c5077483615cd93bd8f11bd675",
"reference": "bca1f8607e55a3c5077483615cd93bd8f11bd675",
"shasum": ""
},
"require": {
@@ -1678,7 +1673,7 @@
],
"support": {
"issues": "https://github.com/voku/anti-xss/issues",
"source": "https://github.com/voku/anti-xss/tree/4.1.41"
"source": "https://github.com/voku/anti-xss/tree/4.1.42"
},
"funding": [
{
@@ -1702,7 +1697,7 @@
"type": "tidelift"
}
],
"time": "2023-02-12T15:56:55+00:00"
"time": "2023-07-03T14:40:46+00:00"
},
{
"name": "voku/portable-ascii",
@@ -2151,16 +2146,16 @@
},
{
"name": "nikic/php-parser",
"version": "v4.15.4",
"version": "v4.16.0",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
"reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290"
"reference": "19526a33fb561ef417e822e85f08a00db4059c17"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/6bb5176bc4af8bcb7d926f88718db9b96a2d4290",
"reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/19526a33fb561ef417e822e85f08a00db4059c17",
"reference": "19526a33fb561ef417e822e85f08a00db4059c17",
"shasum": ""
},
"require": {
@@ -2201,22 +2196,22 @@
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
"source": "https://github.com/nikic/PHP-Parser/tree/v4.15.4"
"source": "https://github.com/nikic/PHP-Parser/tree/v4.16.0"
},
"time": "2023-03-05T19:49:14+00:00"
"time": "2023-06-25T14:52:30+00:00"
},
{
"name": "pdepend/pdepend",
"version": "2.13.0",
"version": "2.14.0",
"source": {
"type": "git",
"url": "https://github.com/pdepend/pdepend.git",
"reference": "31be7cd4f305f3f7b52af99c1cb13fc938d1cfad"
"reference": "1121d4b04af06e33e9659bac3a6741b91cab1de1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pdepend/pdepend/zipball/31be7cd4f305f3f7b52af99c1cb13fc938d1cfad",
"reference": "31be7cd4f305f3f7b52af99c1cb13fc938d1cfad",
"url": "https://api.github.com/repos/pdepend/pdepend/zipball/1121d4b04af06e33e9659bac3a6741b91cab1de1",
"reference": "1121d4b04af06e33e9659bac3a6741b91cab1de1",
"shasum": ""
},
"require": {
@@ -2250,9 +2245,15 @@
"BSD-3-Clause"
],
"description": "Official version of pdepend to be handled with Composer",
"keywords": [
"PHP Depend",
"PHP_Depend",
"dev",
"pdepend"
],
"support": {
"issues": "https://github.com/pdepend/pdepend/issues",
"source": "https://github.com/pdepend/pdepend/tree/2.13.0"
"source": "https://github.com/pdepend/pdepend/tree/2.14.0"
},
"funding": [
{
@@ -2260,7 +2261,7 @@
"type": "tidelift"
}
],
"time": "2023-02-28T20:56:15+00:00"
"time": "2023-05-26T13:15:18+00:00"
},
{
"name": "phar-io/manifest",
@@ -2582,16 +2583,16 @@
},
{
"name": "phpstan/phpstan",
"version": "1.10.14",
"version": "1.10.26",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
"reference": "d232901b09e67538e5c86a724be841bea5768a7c"
"reference": "5d660cbb7e1b89253a47147ae44044f49832351f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/d232901b09e67538e5c86a724be841bea5768a7c",
"reference": "d232901b09e67538e5c86a724be841bea5768a7c",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/5d660cbb7e1b89253a47147ae44044f49832351f",
"reference": "5d660cbb7e1b89253a47147ae44044f49832351f",
"shasum": ""
},
"require": {
@@ -2640,7 +2641,7 @@
"type": "tidelift"
}
],
"time": "2023-04-19T13:47:27+00:00"
"time": "2023-07-19T12:44:37+00:00"
},
{
"name": "phpunit/php-code-coverage",
@@ -2962,16 +2963,16 @@
},
{
"name": "phpunit/phpunit",
"version": "9.6.7",
"version": "9.6.10",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "c993f0d3b0489ffc42ee2fe0bd645af1538a63b2"
"reference": "a6d351645c3fe5a30f5e86be6577d946af65a328"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c993f0d3b0489ffc42ee2fe0bd645af1538a63b2",
"reference": "c993f0d3b0489ffc42ee2fe0bd645af1538a63b2",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a6d351645c3fe5a30f5e86be6577d946af65a328",
"reference": "a6d351645c3fe5a30f5e86be6577d946af65a328",
"shasum": ""
},
"require": {
@@ -3045,7 +3046,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.7"
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.10"
},
"funding": [
{
@@ -3061,7 +3062,7 @@
"type": "tidelift"
}
],
"time": "2023-04-14T08:58:40+00:00"
"time": "2023-07-10T04:04:23+00:00"
},
{
"name": "sebastian/cli-parser",
@@ -3363,16 +3364,16 @@
},
{
"name": "sebastian/diff",
"version": "4.0.4",
"version": "4.0.5",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/diff.git",
"reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d"
"reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d",
"reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d",
"url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/74be17022044ebaaecfdf0c5cd504fc9cd5a7131",
"reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131",
"shasum": ""
},
"require": {
@@ -3417,7 +3418,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/diff/issues",
"source": "https://github.com/sebastianbergmann/diff/tree/4.0.4"
"source": "https://github.com/sebastianbergmann/diff/tree/4.0.5"
},
"funding": [
{
@@ -3425,7 +3426,7 @@
"type": "github"
}
],
"time": "2020-10-26T13:10:38+00:00"
"time": "2023-05-07T05:35:17+00:00"
},
{
"name": "sebastian/environment",
@@ -4227,16 +4228,16 @@
},
{
"name": "symfony/dependency-injection",
"version": "v5.4.22",
"version": "v5.4.25",
"source": {
"type": "git",
"url": "https://github.com/symfony/dependency-injection.git",
"reference": "e1b7c1432efb4ad1dd89d62906187271e2601ed9"
"reference": "f0410c30a6c86bbce6c719c2b5cfc343362b982e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/dependency-injection/zipball/e1b7c1432efb4ad1dd89d62906187271e2601ed9",
"reference": "e1b7c1432efb4ad1dd89d62906187271e2601ed9",
"url": "https://api.github.com/repos/symfony/dependency-injection/zipball/f0410c30a6c86bbce6c719c2b5cfc343362b982e",
"reference": "f0410c30a6c86bbce6c719c2b5cfc343362b982e",
"shasum": ""
},
"require": {
@@ -4296,7 +4297,7 @@
"description": "Allows you to standardize and centralize the way objects are constructed in your application",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/dependency-injection/tree/v5.4.22"
"source": "https://github.com/symfony/dependency-injection/tree/v5.4.25"
},
"funding": [
{
@@ -4312,20 +4313,20 @@
"type": "tidelift"
}
],
"time": "2023-03-10T10:02:45+00:00"
"time": "2023-06-24T09:45:28+00:00"
},
{
"name": "symfony/filesystem",
"version": "v5.4.21",
"version": "v5.4.25",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
"reference": "e75960b1bbfd2b8c9e483e0d74811d555ca3de9f"
"reference": "0ce3a62c9579a53358d3a7eb6b3dfb79789a6364"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/e75960b1bbfd2b8c9e483e0d74811d555ca3de9f",
"reference": "e75960b1bbfd2b8c9e483e0d74811d555ca3de9f",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/0ce3a62c9579a53358d3a7eb6b3dfb79789a6364",
"reference": "0ce3a62c9579a53358d3a7eb6b3dfb79789a6364",
"shasum": ""
},
"require": {
@@ -4360,7 +4361,7 @@
"description": "Provides basic utilities for the filesystem",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/filesystem/tree/v5.4.21"
"source": "https://github.com/symfony/filesystem/tree/v5.4.25"
},
"funding": [
{
@@ -4376,7 +4377,7 @@
"type": "tidelift"
}
],
"time": "2023-02-14T08:03:56+00:00"
"time": "2023-05-31T13:04:02+00:00"
},
{
"name": "symfony/polyfill-php81",

View File

@@ -705,6 +705,8 @@ opcache.validate_timestamps'),
('backup', 'enabled', 0),
('backup', 'default_storage', '1'),
('backup', 'default_customer_access', '1'),
('backup', 'default_pgp_public_key', ''),
('backup', 'default_retention', '3'),
('api', 'enabled', '0'),
('api', 'customer_default', '1'),
('2fa', 'enabled', '1'),
@@ -1079,8 +1081,8 @@ CREATE TABLE `panel_backup_storages` (
`destination_path` varchar(255) NOT NULL,
`hostname` varchar(255) NULL,
`username` varchar(255) NULL,
`password` varchar(255) NULL,
`pgp_public_key` varchar(255) NULL,
`password` text,
`pgp_public_key` text,
`retention` int(3) NOT NULL DEFAULT 3,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;

View File

@@ -73,8 +73,8 @@ if (Froxlor::isDatabaseVersion('202304260')) {
`destination_path` varchar(255) NOT NULL,
`hostname` varchar(255) NULL,
`username` varchar(255) NULL,
`password` varchar(255) NULL,
`pgp_public_key` varchar(255) NULL,
`password` text,
`pgp_public_key` text,
`retention` int(3) NOT NULL DEFAULT 3,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;";
@@ -105,6 +105,8 @@ if (Froxlor::isDatabaseVersion('202304260')) {
Settings::AddNew('backup.enabled', 0);
Settings::AddNew('backup.default_storage', 1);
Settings::AddNew('backup.default_customer_access', 1);
Settings::AddNew('backup.default_pgp_public_key', '');
Settings::AddNew('backup.default_retention', 3);
Update::lastStepStatus(0);
Update::showUpdateStep("Adjusting cronjobs");

View File

@@ -73,8 +73,8 @@ class BackupStorages extends ApiCommand implements ResourceEntity
$this->logger()->logAction(FroxlorLogger::ADM_ACTION, LOG_INFO, "[API] list backup storages");
$query_fields = [];
$result_stmt = Database::prepare("
SELECT * FROM `" . TABLE_PANEL_BACKUP_STORAGES . "`
");
SELECT * FROM `" . TABLE_PANEL_BACKUP_STORAGES . "` ". $this->getSearchWhere($query_fields) . $this->getOrderBy() . $this->getLimit()
);
Database::pexecute($result_stmt, $query_fields, true, true);
$result = [];
while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) {

View File

@@ -95,7 +95,8 @@ class Backups extends ApiCommand implements ResourceEntity
FROM `" . TABLE_PANEL_BACKUPS . "` `b`
LEFT JOIN `" . TABLE_PANEL_ADMINS . "` `a` USING(`adminid`)
WHERE `b`.`customerid` IN (" . implode(', ', $customer_ids) . ")
");
" . $this->getSearchWhere($query_fields, true) . $this->getOrderBy() . $this->getLimit()
);
Database::pexecute($result_stmt, $query_fields, true, true);
$result = [];
while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) {

View File

@@ -0,0 +1,53 @@
<?php
/**
* This file is part of the Froxlor project.
* Copyright (c) 2010 the Froxlor Team (see authors).
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can also view it online at
* https://files.froxlor.org/misc/COPYING.txt
*
* @copyright the authors
* @author Froxlor team <team@froxlor.org>
* @license https://files.froxlor.org/misc/COPYING.txt GPLv2
*/
namespace Froxlor\Backup;
use Froxlor\Database\Database;
use PDO;
class Backup
{
/**
* returns an array of existing backup-storages
* in our database for the settings-array
*
* @return array
*/
public static function getBackupStorages(): array
{
$storages_array = [
0 => lng('backup.storage_none')
];
// get all storages
$result_stmt = Database::query("SELECT id, type, description FROM `" . TABLE_PANEL_BACKUP_STORAGES . "` ORDER BY type, description");
while ($row = $result_stmt->fetch(PDO::FETCH_ASSOC)) {
if (!isset($storages_array[$row['id']]) && !in_array($row['id'], $storages_array)) {
$storages_array[$row['id']] = "[" . $row['type'] . "] " . html_entity_decode($row['description']);
}
}
return $storages_array;
}
}

View File

@@ -0,0 +1,98 @@
<?php
namespace Froxlor\Backup\Storages;
use Exception;
use Froxlor\FileDir;
class Ftp extends Storage
{
private $ftp_conn = null;
/**
* @return bool
* @throws Exception
*/
public function init(): bool
{
$hostname = $this->sData['storage']['hostname'] ?? '';
$username = $this->sData['storage']['username'] ?? '';
$password = $this->sData['storage']['password'] ?? '';
if (!empty($hostname) && !empty($username) && !empty($password)) {
$tmp = explode(":", $hostname);
$hostname = $tmp[0];
$port = $tmp[1] ?? 21;
$this->ftp_conn = ftp_connect($hostname, $port);
if ($this->ftp_conn === false) {
throw new Exception('Unable to connect to ftp-server "' . $hostname . ':' . $port . '"');
}
if (!ftp_login($this->ftp_conn, $username, $password)) {
throw new Exception('Unable to login to ftp-server "' . $hostname . ':' . $port . '"');
}
return $this->changeToCorrectDirectory();
}
throw new Exception('Empty hostname for FTP backup storage');
}
/**
* @param string $filename
* @param string $tmp_source_directory
* @return bool
* @throws Exception
*/
protected function putFile(string $filename, string $tmp_source_directory): bool
{
$source = FileDir::makeCorrectFile($tmp_source_directory . "/" . $filename);
$target = basename($filename);
if (file_exists($source) && !file_exists($target)) {
return ftp_put($this->ftp_conn, $target, $source, FTP_BINARY);
}
return false;
}
/**
* @param string $filename
* @return bool
* @throws Exception
*/
protected function rmFile(string $filename): bool
{
$target = basename($filename);
if (ftp_size($this->ftp_conn, $target) >= 0) {
return ftp_delete($this->ftp_conn, $target);
}
return true;
}
/**
* @return bool
*/
public function shutdown(): bool
{
return ftp_close($this->ftp_conn);
}
/**
* @return bool
* @throws Exception
*/
private function changeToCorrectDirectory(): bool
{
$dirs = explode("/", $this->getDestinationDirectory());
array_shift($dirs);
if (count($dirs) > 0 && !empty($dirs[0])) {
foreach ($dirs as $dir) {
if (empty($dir)) {
continue;
}
if (!@ftp_chdir($this->ftp_conn, $dir)) {
ftp_mkdir($this->ftp_conn, $dir);
ftp_chmod($this->ftp_conn, 0700, $dir);
ftp_chdir($this->ftp_conn, $dir);
}
}
return true;
}
return ftp_chdir($this->ftp_conn, "/");
}
}

View File

@@ -0,0 +1,60 @@
<?php
namespace Froxlor\Backup\Storages;
use Exception;
use Froxlor\FileDir;
class Local extends Storage
{
/**
* @throws Exception
*/
public function init(): bool
{
// create destination_path
if (!file_exists($this->getDestinationDirectory())) {
return mkdir($this->getDestinationDirectory(), 0700, true);
}
return true;
}
/**
* @param string $filename
* @param string $tmp_source_directory
* @return bool
* @throws Exception
*/
protected function putFile(string $filename, string $tmp_source_directory): bool
{
$source = FileDir::makeCorrectFile($tmp_source_directory . "/" . $filename);
$target = FileDir::makeCorrectFile($this->getDestinationDirectory() . "/" . $filename);
if (file_exists($source) && !file_exists($target)) {
return rename($source, $target);
}
return false;
}
/**
* @param string $filename
* @return bool
* @throws Exception
*/
protected function rmFile(string $filename): bool
{
$target = FileDir::makeCorrectFile($this->getDestinationDirectory() . "/" . $filename);
if (file_exists($target)) {
return @unlink($target);
}
return true;
}
/**
* @return bool
*/
public function shutdown(): bool
{
return true;
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace Froxlor\Backup\Storages;
class Rsync extends Storage
{
/**
* @return bool
*/
public function init(): bool
{
// TODO: Implement init() method.
}
/**
* @param string $filename
* @param string $tmp_source_directory
* @return bool
*/
protected function putFile(string $filename, string $tmp_source_directory): bool
{
// TODO: Implement putFiles() method.
}
/**
* @param string $filename
* @return bool
*/
protected function rmFile(string $filename): bool
{
// TODO: Implement removeOld() method.
}
/**
* @return bool
*/
public function shutdown(): bool
{
return true;
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace Froxlor\Backup\Storages;
class S3 extends Storage
{
/**
* @return bool
*/
public function init(): bool
{
// TODO: Implement init() method.
}
/**
* @param string $filename
* @param string $tmp_source_directory
* @return bool
*/
protected function putFile(string $filename, string $tmp_source_directory): bool
{
// TODO: Implement putFiles() method.
}
/**
* @param string $filename
* @return bool
*/
protected function rmFile(string $filename): bool
{
// TODO: Implement removeOld() method.
}
/**
* @return bool
*/
public function shutdown(): bool
{
return true;
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace Froxlor\Backup\Storages;
class Sftp extends Storage
{
/**
* @return bool
*/
public function init(): bool
{
// TODO: Implement init() method.
}
/**
* @param string $filename
* @param string $tmp_source_directory
* @return bool
*/
protected function putFile(string $filename, string $tmp_source_directory): bool
{
// TODO: Implement putFiles() method.
}
/**
* @param string $filename
* @return bool
*/
protected function rmFile(string $filename): bool
{
// TODO: Implement removeOld() method.
}
/**
* @return bool
*/
public function shutdown(): bool
{
return true;
}
}

View File

@@ -0,0 +1,155 @@
<?php
namespace Froxlor\Backup\Storages;
use Exception;
use Froxlor\Database\Database;
use Froxlor\FileDir;
abstract class Storage
{
private string $tmpDirectory;
protected array $sData;
protected array $filesToStore;
public function __construct(array $storage_data)
{
$this->sData = $storage_data;
$this->tmpDirectory = sys_get_temp_dir();
}
/**
* Validate sData, open connection to target storage, etc.
*
* @return bool
*/
abstract public function init(): bool;
/**
* Disconnect / clean up connection if needed
*
* @return bool
*/
abstract public function shutdown(): bool;
/**
* prepare files to back up (e.g. create archive or similar) and fill $filesToStore
*
* @return void
*/
public function prepareFiles(): void
{
$this->filesToStore = [];
// create archive of web, mail and database data
// create json-info-file
}
/**
* @param string $filename
* @param string $tmp_source_directory
* @return bool
*/
abstract protected function putFile(string $filename, string $tmp_source_directory): bool;
/**
* @param string $filename
* @return bool
*/
abstract protected function rmFile(string $filename): bool;
/**
* @return bool
* @throws Exception
*/
public function removeOld(): bool
{
// retention in days
$retention = $this->sData['storage']['retention'] ?? 3;
// keep date
$keepDate = new \DateTime();
$keepDate->setTime(0, 0, 0, 1);
// subtract retention days
$keepDate->sub(new \DateInterval('P' . $retention . 'D'));
// select target backups to remove for this storage-id and customer
$sel_stmt = Database::prepare("
SELECT * FROM `" . TABLE_PANEL_BACKUPS . "`
WHERE `created_at` < :keepdate
AND `storage_id` = :sid
AND `customerid` = :cid
");
Database::pexecute($sel_stmt, [
'keepdate' => $keepDate->format('U'),
'sid' => $this->sData['backup'],
'cid' => $this->sData['customerid']
]);
while ($oldBackup = $sel_stmt->fetch(\PDO::FETCH_ASSOC)) {
$this->rmFile($oldBackup['filename']);
}
}
/**
* @return string
* @throws Exception
*/
public function getDestinationDirectory(): string
{
return FileDir::makeCorrectDir($this->sData['storage']['destination_path'] ?? "/");
}
/**
* Create backup-archive/file from $filesToStore and call putFile()
*
* @return bool
* @throws Exception
*/
public function createFromFiles(): bool
{
if (empty($this->filesToStore)) {
return false;
}
$filename = FileDir::makeCorrectFile("/backup-" . $this->sData['loginname'] . "-" . date('c') . ".tar.gz");
// @todo create archive $filename from $filesToStore
// determine filesize (use stat locally here b/c files are possibly large and php's filesize() can't handle them)
$sizeCheckFile = FileDir::makeCorrectFile($this->tmpDirectory . "/" . $filename);
$fileSizeOutput = FileDir::safe_exec('/usr/bin/stat -c "%s" ' . escapeshellarg($sizeCheckFile));
$fileSize = (int)array_shift($fileSizeOutput);
// add entry to database and upload/store file
$this->addEntry($filename, $fileSize);
return $this->putFile($filename, $this->tmpDirectory);
}
/**
* @param string $filename
* @param int $fileSize
* @return void
* @throws Exception
*/
private function addEntry(string $filename, int $fileSize): void
{
$ins_stmt = Database::prepare("
INSERT INTO `" . TABLE_PANEL_BACKUPS . "` SET
`adminid` = :adminid,
`customerid` = :customerid,
`loginname` = :loginname,
`size` = :size,
`storage_id` = :sid,
`filename` = :filename,
`created_at` = UNIX_TIMESTAMP()
");
Database::pexecute($ins_stmt, [
'adminid' => $this->sData['adminid'],
'customerid' => $this->sData['customerid'],
'loginname' => $this->sData['loginname'],
'size' => $fileSize,
'sid' => $this->sData['backup'],
'filename' => $filename
]);
}
}

View File

@@ -0,0 +1,12 @@
<?php
namespace Froxlor\Backup\Storages;
class StorageFactory
{
public static function fromType(string $type, array $storage_data): Storage
{
$type = "\\Froxlor\\Backup\\Storages\\" . ucfirst($type);
return new $type($storage_data);
}
}

View File

@@ -25,6 +25,8 @@
namespace Froxlor\Cron\Backup;
use Exception;
use Froxlor\Backup\Storages\StorageFactory;
use Froxlor\Cron\Forkable;
use Froxlor\Cron\FroxlorCron;
use Froxlor\Database\Database;
@@ -38,7 +40,7 @@ class BackupCron extends FroxlorCron
public static function run()
{
if(!Settings::Get('backup.enabled')) {
if (!Settings::Get('backup.enabled')) {
FroxlorLogger::getInstanceOf()->logAction(FroxlorLogger::CRON_ACTION, LOG_DEBUG, 'BackupCron: disabled - exiting');
return -1;
}
@@ -71,11 +73,22 @@ class BackupCron extends FroxlorCron
self::runFork([self::class, 'handle'], $customers);
}
/**
* @throws Exception
*/
private static function handle(array $userdata)
{
echo "BackupCron: started - creating customer backup for user " . $userdata['loginname'] . "\n";
echo json_encode($userdata['storage']) . "\n";
$backupStorage = StorageFactory::fromType($userdata['storage']['type'], $userdata);
// initialize storage
$backupStorage->init();
// do what is required to obtain files/archives and move/upload
$backupStorage->prepareFiles();
// upload/move to target
$backupStorage->createFromFiles();
// remove by retention
$backupStorage->removeOld();
echo "BackupCron: finished - creating customer backup for user " . $userdata['loginname'] . "\n";
}