diff --git a/actions/admin/settings/230.backup.php b/actions/admin/settings/230.backup.php index 63ebdbc0..6d2a1769 100644 --- a/actions/admin/settings/230.backup.php +++ b/actions/admin/settings/230.backup.php @@ -81,6 +81,15 @@ return [ 'checkPgpPublicKeySetting' ], ], + 'backup_backup_tmp_dir' => [ + 'label' => lng('serversettings.backup_tmp_dir'), + 'settinggroup' => 'backup', + 'varname' => 'backup_tmp_dir', + 'type' => 'text', + 'string_type' => 'dir', + 'default' => '/var/customers/backup/', + 'save_method' => 'storeSettingField' + ] ] ] ] diff --git a/admin_backups.php b/admin_backups.php index 0004da5b..d073cfd8 100644 --- a/admin_backups.php +++ b/admin_backups.php @@ -55,13 +55,13 @@ if (($page == 'backups' || $page == 'overview')) { 'actions_links' => [ [ 'href' => $linker->getLink(['section' => 'backups', 'page' => $page, 'action' => 'restore']), - 'label' => lng('admin.backups_restore'), + 'label' => lng('backup.backups_restore'), 'icon' => 'fa-solid fa-file-import', 'class' => 'btn-outline-secondary' ], [ 'href' => $linker->getLink(['section' => 'backups', 'page' => 'storages']), - 'label' => lng('admin.backup_storages'), + 'label' => lng('backup.backup_storages'), 'icon' => 'fa-solid fa-hard-drive', 'class' => 'btn-outline-secondary', 'visible' => $userinfo['change_serversettings'] == '1' @@ -94,12 +94,12 @@ if (($page == 'backups' || $page == 'overview')) { 'actions_links' => [ [ 'href' => $linker->getLink(['section' => 'backups', 'page' => 'backups']), - 'label' => lng('admin.backups'), + 'label' => lng('backup.backups'), 'icon' => 'fa-solid fa-reply' ], [ 'href' => $linker->getLink(['section' => 'backups', 'page' => $page, 'action' => 'add']), - 'label' => lng('admin.backup_storage_add') + 'label' => lng('backup.backup_storage.add') ] ] ]); diff --git a/composer.json b/composer.json index e005badd..6d590322 100644 --- a/composer.json +++ b/composer.json @@ -47,6 +47,7 @@ "ext-gmp": "*", "ext-gd": "*", "ext-ftp": "*", + "ext-gnupg": "*", "phpmailer/phpmailer": "~6.0", "monolog/monolog": "^1.24", "robthree/twofactorauth": "^1.6", @@ -57,7 +58,8 @@ "symfony/console": "^5.4", "pear/net_dns2": "^1.5", "amnuts/opcache-gui": "^3.4", - "aws/aws-sdk-php": "^3.280" + "aws/aws-sdk-php": "^3.280", + "phpseclib/phpseclib": "~3.0" }, "require-dev": { "phpunit/phpunit": "^9", diff --git a/composer.lock b/composer.lock index b1c79df5..016aa532 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b3c285b81c9729ba8e2bac6f3b586e9c", + "content-hash": "c64c2e8e5669531310620aa423ad2ecd", "packages": [ { "name": "amnuts/opcache-gui", @@ -800,6 +800,123 @@ }, "time": "2023-08-25T10:54:48+00:00" }, + { + "name": "paragonie/constant_time_encoding", + "version": "v2.6.3", + "source": { + "type": "git", + "url": "https://github.com/paragonie/constant_time_encoding.git", + "reference": "58c3f47f650c94ec05a151692652a868995d2938" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/58c3f47f650c94ec05a151692652a868995d2938", + "reference": "58c3f47f650c94ec05a151692652a868995d2938", + "shasum": "" + }, + "require": { + "php": "^7|^8" + }, + "require-dev": { + "phpunit/phpunit": "^6|^7|^8|^9", + "vimeo/psalm": "^1|^2|^3|^4" + }, + "type": "library", + "autoload": { + "psr-4": { + "ParagonIE\\ConstantTime\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com", + "role": "Maintainer" + }, + { + "name": "Steve 'Sc00bz' Thomas", + "email": "steve@tobtu.com", + "homepage": "https://www.tobtu.com", + "role": "Original Developer" + } + ], + "description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)", + "keywords": [ + "base16", + "base32", + "base32_decode", + "base32_encode", + "base64", + "base64_decode", + "base64_encode", + "bin2hex", + "encoding", + "hex", + "hex2bin", + "rfc4648" + ], + "support": { + "email": "info@paragonie.com", + "issues": "https://github.com/paragonie/constant_time_encoding/issues", + "source": "https://github.com/paragonie/constant_time_encoding" + }, + "time": "2022-06-14T06:56:20+00:00" + }, + { + "name": "paragonie/random_compat", + "version": "v9.99.100", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a", + "shasum": "" + }, + "require": { + "php": ">= 7" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*", + "vimeo/psalm": "^1" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "polyfill", + "pseudorandom", + "random" + ], + "support": { + "email": "info@paragonie.com", + "issues": "https://github.com/paragonie/random_compat/issues", + "source": "https://github.com/paragonie/random_compat" + }, + "time": "2020-10-15T08:29:30+00:00" + }, { "name": "pear/net_dns2", "version": "v1.5.3", @@ -931,6 +1048,116 @@ ], "time": "2023-08-29T08:26:30+00:00" }, + { + "name": "phpseclib/phpseclib", + "version": "3.0.21", + "source": { + "type": "git", + "url": "https://github.com/phpseclib/phpseclib.git", + "reference": "4580645d3fc05c189024eb3b834c6c1e4f0f30a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/4580645d3fc05c189024eb3b834c6c1e4f0f30a1", + "reference": "4580645d3fc05c189024eb3b834c6c1e4f0f30a1", + "shasum": "" + }, + "require": { + "paragonie/constant_time_encoding": "^1|^2", + "paragonie/random_compat": "^1.4|^2.0|^9.99.99", + "php": ">=5.6.1" + }, + "require-dev": { + "phpunit/phpunit": "*" + }, + "suggest": { + "ext-dom": "Install the DOM extension to load XML formatted public keys.", + "ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.", + "ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.", + "ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.", + "ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations." + }, + "type": "library", + "autoload": { + "files": [ + "phpseclib/bootstrap.php" + ], + "psr-4": { + "phpseclib3\\": "phpseclib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jim Wigginton", + "email": "terrafrost@php.net", + "role": "Lead Developer" + }, + { + "name": "Patrick Monnerat", + "email": "pm@datasphere.ch", + "role": "Developer" + }, + { + "name": "Andreas Fischer", + "email": "bantu@phpbb.com", + "role": "Developer" + }, + { + "name": "Hans-Jürgen Petrich", + "email": "petrich@tronic-media.com", + "role": "Developer" + }, + { + "name": "Graham Campbell", + "email": "graham@alt-three.com", + "role": "Developer" + } + ], + "description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.", + "homepage": "http://phpseclib.sourceforge.net", + "keywords": [ + "BigInteger", + "aes", + "asn.1", + "asn1", + "blowfish", + "crypto", + "cryptography", + "encryption", + "rsa", + "security", + "sftp", + "signature", + "signing", + "ssh", + "twofish", + "x.509", + "x509" + ], + "support": { + "issues": "https://github.com/phpseclib/phpseclib/issues", + "source": "https://github.com/phpseclib/phpseclib/tree/3.0.21" + }, + "funding": [ + { + "url": "https://github.com/terrafrost", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpseclib", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpseclib/phpseclib", + "type": "tidelift" + } + ], + "time": "2023-07-09T15:24:48+00:00" + }, { "name": "psr/container", "version": "1.1.2", diff --git a/install/froxlor.sql.php b/install/froxlor.sql.php index ad268f7d..29181ea5 100644 --- a/install/froxlor.sql.php +++ b/install/froxlor.sql.php @@ -698,7 +698,7 @@ opcache.validate_timestamps'), ('system', 'distribution', ''), ('system', 'update_channel', 'stable'), ('system', 'updatecheck_data', ''), - ('system', 'update_notify_last', '2.0.20'), + ('system', 'update_notify_last', '2.1.0-dev1'), ('system', 'traffictool', 'goaccess'), ('system', 'req_limit_per_interval', 60), ('system', 'req_limit_interval', 60), @@ -751,7 +751,7 @@ opcache.validate_timestamps'), ('panel', 'logo_overridetheme', '0'), ('panel', 'logo_overridecustom', '0'), ('panel', 'settings_mode', '0'), - ('panel', 'version', '2.0.20'), + ('panel', 'version', '2.1.0-dev1'), ('panel', 'db_version', '202305240'); diff --git a/lib/Froxlor/Api/Commands/BackupStorages.php b/lib/Froxlor/Api/Commands/BackupStorages.php index 2952162e..2f9a6e0b 100644 --- a/lib/Froxlor/Api/Commands/BackupStorages.php +++ b/lib/Froxlor/Api/Commands/BackupStorages.php @@ -35,6 +35,9 @@ use Froxlor\Settings; use Froxlor\UI\Response; use Froxlor\Validate\Validate; use PDO; +use phpseclib3\Crypt\Common\PublicKey; +use phpseclib3\Crypt\PublicKeyLoader; +use phpseclib3\Exception\NoKeyLoadedException; /** * @since 2.1.0 @@ -73,7 +76,7 @@ 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 . "` ". $this->getSearchWhere($query_fields) . $this->getOrderBy() . $this->getLimit() + SELECT * FROM `" . TABLE_PANEL_BACKUP_STORAGES . "` " . $this->getSearchWhere($query_fields) . $this->getOrderBy() . $this->getLimit() ); Database::pexecute($result_stmt, $query_fields, true, true); $result = []; @@ -133,7 +136,7 @@ class BackupStorages extends ApiCommand implements ResourceEntity * @param string $pgp_public_key * optional, pgp public key for backup storage * @param string $retention - * optional, retention for backup storage (default 3) + * optional, retention for backup storage (default {backup.default_retention}) * * @access admin * @return string json-encoded array @@ -177,10 +180,36 @@ class BackupStorages extends ApiCommand implements ResourceEntity $username = $this->getParam('username', $optional_flags['username']); $password = $this->getParam('password', $optional_flags['password']); $pgp_public_key = $this->getParam('pgp_public_key', true, null); - $retention = $this->getParam('retention', true, 3); + $retention = $this->getParam('retention', true, Settings::Get('backup.default_retention')); // validation $destination_path = FileDir::makeCorrectDir(Validate::validate($destination_path, 'destination_path', Validate::REGEX_DIR, '', [], true)); + if ($type != 'local') { + if (!Validate::validateUrl($hostname)) { + Response::standardError('invalidhostname', '', true); + } + // check whether password is an ssh public key + $pwd_is_ssh_key = false; + try { + $key = PublicKeyLoader::loadPublicKey($password); + if ($key instanceof PublicKey) { + $pwd_is_ssh_key = true; + } + } catch (NoKeyLoadedException $e) { + /* nothing to do */ + } + if (!$pwd_is_ssh_key) { + // normal password + $password = Validate::validate($password, 'password', '', '', [], true); + } + } + if ($type == 's3') { + $region = Validate::validate($region, 'region', '', '', [], true); + $bucket = Validate::validate($bucket, 'bucket', '/(?!(^xn--|.+-s3alias$))^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$/', '', [], true); + } + if ($retention <= 0) { + $retention = Settings::Get('backup.default_retention'); + } // TODO: add more validation // pgp public key validation @@ -303,7 +332,7 @@ class BackupStorages extends ApiCommand implements ResourceEntity * @param string $pgp_public_key * optional, pgp public key for backup storage * @param string $retention - * optional, retention for backup storage (default 3) + * optional, retention for backup storage (default {backup.default_retention}) * * @access admin * @return string json-encoded array @@ -342,7 +371,23 @@ class BackupStorages extends ApiCommand implements ResourceEntity if (empty($username)) { throw new Exception("Field 'username' cannot be empty", 406); } - $password = Validate::validate($password, 'password', '', '', [], true); + // password change + if (!empty($password)) { + // check whether password is an ssh public key + $pwd_is_ssh_key = false; + try { + $key = PublicKeyLoader::loadPublicKey($password); + if ($key instanceof PublicKey) { + $pwd_is_ssh_key = true; + } + } catch (NoKeyLoadedException $e) { + /* nothing to do */ + } + if (!$pwd_is_ssh_key) { + // normal password + $password = Validate::validate($password, 'password', '', '', [], true); + } + } } if ($type == 's3') { if (empty($region)) { @@ -355,6 +400,16 @@ class BackupStorages extends ApiCommand implements ResourceEntity // validation $destination_path = FileDir::makeCorrectDir(Validate::validate($destination_path, 'destination_path', Validate::REGEX_DIR, '', [], true)); + if ($type != 'local' && !Validate::validateUrl($hostname)) { + Response::standardError('invalidhostname', '', true); + } + if ($type == 's3') { + $region = Validate::validate($region, 'region', '', '', [], true); + $bucket = Validate::validate($bucket, 'bucket', '/(?!(^xn--|.+-s3alias$))^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$/', '', [], true); + } + if ($retention <= 0) { + $retention = Settings::Get('backup.default_retention'); + } // TODO: add more validation // pgp public key validation diff --git a/lib/Froxlor/Backup/Storages/S3.php b/lib/Froxlor/Backup/Storages/S3.php index fa7c8f3e..ea75731c 100644 --- a/lib/Froxlor/Backup/Storages/S3.php +++ b/lib/Froxlor/Backup/Storages/S3.php @@ -35,13 +35,16 @@ class S3 extends Storage * @param string $filename * @param string $tmp_source_directory * @return string + * @throws \Exception */ protected function putFile(string $filename, string $tmp_source_directory): string { + $source = FileDir::makeCorrectFile($tmp_source_directory . "/" . $filename); + $target = FileDir::makeCorrectFile($this->getDestinationDirectory() . "/" . $filename); $this->s3_client->putObject([ 'Bucket' => $this->sData['storage']['bucket'], - 'Key' => $filename, - 'SourceFile' => FileDir::makeCorrectFile($tmp_source_directory . '/' . $filename), + 'Key' => $target, + 'SourceFile' => $source, ]); } diff --git a/lib/Froxlor/Backup/Storages/Sftp.php b/lib/Froxlor/Backup/Storages/Sftp.php index eff8a1af..1e417d52 100644 --- a/lib/Froxlor/Backup/Storages/Sftp.php +++ b/lib/Froxlor/Backup/Storages/Sftp.php @@ -2,15 +2,34 @@ namespace Froxlor\Backup\Storages; +use Exception; +use Froxlor\FileDir; +use phpseclib3\Net\SFTP as secSFTP; + class Sftp extends Storage { + private secSFTP $sftp_client; /** * @return bool */ public function init(): bool { - // TODO: Implement init() method. + $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] ?? 22; + $this->sftp_client = new secSFTP($hostname, $port); + if ($this->sftp_client->isConnected()) { + // @todo login by either user/passwd or user/ssh-key + return true; + } + return false; + } + throw new Exception('Empty hostname for FTP backup storage'); } /** @@ -20,19 +39,27 @@ class Sftp extends Storage * @param string $filename * @param string $tmp_source_directory * @return string + * @throws Exception */ protected function putFile(string $filename, string $tmp_source_directory): string { - return ""; + $source = FileDir::makeCorrectFile($tmp_source_directory . "/" . $filename); + $target = FileDir::makeCorrectFile($this->getDestinationDirectory() . "/" . $filename); + $this->sftp_client->put($target, $source, secSFTP::SOURCE_LOCAL_FILE); } /** * @param string $filename * @return bool + * @throws Exception */ protected function rmFile(string $filename): bool { - // TODO: Implement removeOld() method. + $target = FileDir::makeCorrectFile($this->getDestinationDirectory() . "/" . $filename); + if ($this->sftp_client->file_exists($target)) { + return $this->sftp_client->delete($target); + } + return false; } /** @@ -40,6 +67,7 @@ class Sftp extends Storage */ public function shutdown(): bool { + $this->sftp_client->disconnect(); return true; } } diff --git a/lib/formfields/admin/backup_storages/formfield.backup_storage_add.php b/lib/formfields/admin/backup_storages/formfield.backup_storage_add.php index 7ea3bf5b..983f90a4 100644 --- a/lib/formfields/admin/backup_storages/formfield.backup_storage_add.php +++ b/lib/formfields/admin/backup_storages/formfield.backup_storage_add.php @@ -27,12 +27,12 @@ use Froxlor\Settings; return [ 'backup_storage_add' => [ - 'title' => lng('backups.backup_storage_add'), + 'title' => lng('backup.backup_storage.add'), 'image' => 'fa-solid fa-file-archive', 'self_overview' => ['section' => 'backups', 'page' => 'storages'], 'sections' => [ 'section_a' => [ - 'title' => lng('backup.backup_storage_create'), + 'title' => lng('backup.backup_storage.create'), 'fields' => [ 'description' => [ 'label' => lng('backup.backup_storage.description'), @@ -63,7 +63,8 @@ return [ ], 'destination_path' => [ 'label' => lng('backup.backup_storage.destination_path'), - 'type' => 'text' + 'type' => 'text', + 'mandatory' => true, ], 'hostname' => [ 'label' => lng('backup.backup_storage.hostname'), @@ -75,7 +76,7 @@ return [ ], 'password' => [ 'label' => lng('backup.backup_storage.password'), - 'type' => 'password', + 'type' => 'textarea', 'autocomplete' => 'off', ], 'pgp_public_key' => [ @@ -87,6 +88,7 @@ return [ 'label' => lng('backup.backup_storage.retention'), 'type' => 'number', 'min' => 0, + 'value' => Settings::Get('backup.default_retention') ] ] ] diff --git a/lib/formfields/admin/backup_storages/formfield.backup_storage_edit.php b/lib/formfields/admin/backup_storages/formfield.backup_storage_edit.php index 0ed8b6fa..8aaaa698 100644 --- a/lib/formfields/admin/backup_storages/formfield.backup_storage_edit.php +++ b/lib/formfields/admin/backup_storages/formfield.backup_storage_edit.php @@ -25,12 +25,12 @@ return [ 'backup_storage_edit' => [ - 'title' => lng('backups.backup_storage_edit'), + 'title' => lng('backup.backup_storage.edit'), 'image' => 'fa-solid fa-file-archive', 'self_overview' => ['section' => 'backups', 'page' => 'storages'], 'sections' => [ 'section_a' => [ - 'title' => lng('backup.backup_storage_edit'), + 'title' => lng('backup.backup_storage.edit'), 'fields' => [ 'description' => [ 'label' => lng('backup.backup_storage.description'), @@ -64,7 +64,8 @@ return [ 'destination_path' => [ 'label' => lng('backup.backup_storage.destination_path'), 'type' => 'text', - 'value' => $result['destination_path'] + 'value' => $result['destination_path'], + 'mandatory' => true, ], 'hostname' => [ 'label' => lng('backup.backup_storage.hostname'), @@ -77,8 +78,9 @@ return [ 'value' => $result['username'] ], 'password' => [ - 'label' => lng('backup.backup_storage.password') . ' (' . lng('panel.emptyfornochanges') . ')', - 'type' => 'password', + 'label' => lng('backup.backup_storage.password.title'), + 'desc' => lng('backup.backup_storage.password.description') . '
(' . lng('panel.emptyfornochanges') . ')', + 'type' => 'textarea', 'autocomplete' => 'off' ], 'pgp_public_key' => [ diff --git a/lib/tablelisting/admin/tablelisting.backup_storages.php b/lib/tablelisting/admin/tablelisting.backup_storages.php index c1c32bd0..83b6506d 100644 --- a/lib/tablelisting/admin/tablelisting.backup_storages.php +++ b/lib/tablelisting/admin/tablelisting.backup_storages.php @@ -34,7 +34,7 @@ use Froxlor\UI\Listing; return [ 'backup_storages_list' => [ - 'title' => lng('backup.backup_storages.list'), + 'title' => lng('backup.backup_storage.list'), 'icon' => 'fa-solid fa-file-archive', 'self_overview' => ['section' => 'backups', 'page' => 'storages'], 'default_sorting' => ['description' => 'asc'], @@ -45,42 +45,42 @@ return [ 'sortable' => true, ], 'description' => [ - 'label' => lng('description'), + 'label' => lng('backup.backup_storage.description'), 'field' => 'description', 'sortable' => true, ], 'type' => [ - 'label' => lng('type'), + 'label' => lng('backup.backup_storage.type'), 'field' => 'type', 'sortable' => true, ], 'region' => [ - 'label' => lng('region'), + 'label' => lng('backup.backup_storage.region'), 'field' => 'region', 'sortable' => true, ], 'bucket' => [ - 'label' => lng('bucket'), + 'label' => lng('backup.backup_storage.bucket'), 'field' => 'bucket', 'sortable' => true, ], 'destination_path' => [ - 'label' => lng('destination_path'), + 'label' => lng('backup.backup_storage.destination_path.title'), 'field' => 'destination_path', 'sortable' => true, ], 'hostname' => [ - 'label' => lng('hostname'), + 'label' => lng('backup.backup_storage.hostname'), 'field' => 'hostname', 'sortable' => true, ], 'username' => [ - 'label' => lng('username'), + 'label' => lng('backup.backup_storage.username'), 'field' => 'username', 'sortable' => true, ], 'retention' => [ - 'label' => lng('retention'), + 'label' => lng('backup.backup_storage.retention'), 'field' => 'retention', 'sortable' => true, ], diff --git a/lng/de.lng.php b/lng/de.lng.php index 269837c1..3abeec91 100644 --- a/lng/de.lng.php +++ b/lng/de.lng.php @@ -2296,4 +2296,39 @@ Vielen Dank, Ihr Administrator', 'config_note' => 'Damit froxlor mit dem Backend vernünftig kommunizieren kann, musst du dieses noch konfigurieren.', 'config_now' => 'Jetzt konfigurieren' ], + 'backup' => [ + 'backup' => 'Backup', + 'backups' => 'Backups', + 'backups_restore' => 'Wiederherstellen', + 'backup_storages' => 'Speichermedien verwalten', + 'size' => 'Größe', + 'created_at' => 'Erstellt', + 'backup_storage' => [ + 'add' => 'Speichermedium hinzufügen', + 'create' => 'Neues Speichermedium erstellen', + 'edit' => 'Speichermedien bearbeiten', + 'list' => 'Speichermedien', + 'description' => 'Beschreibung', + 'type' => 'Typ', + 'type_local' => 'Lokales Dateisystem', + 'type_ftp' => 'FTP Server', + 'type_sftp' => 'SFTP auf entfernten Server', + 'type_rsync' => 'Rsync zu entfernten Server', + 'type_s3' => 'S3 Server', + 'region' => 'S3 Region', + 'bucket' => 'S3 Bucket', + 'destination_path' => [ + 'title' => 'Ziel-Pfad', + 'description' => 'Absoluter Pfad, wenn Speicher-Typ ist "lokal", sonst relativ zum Heimatverzeichnisses des Benutzers', + ], + 'hostname' => 'Ziel Hostname', + 'username' => 'Benutzername', + 'password' => [ + 'title' => 'Passwort oder SSH Public-key', + 'description' => 'Es kann entweder ein Passwort oder ein vollständiger SSH Public-Key angegeben werden' + ], + 'pgp_public_key' => 'PGP Public-key', + 'retention' => 'Backup-Aufbewahrung in Tagen', + ], + ], ]; diff --git a/lng/en.lng.php b/lng/en.lng.php index 263759f6..38224a84 100644 --- a/lng/en.lng.php +++ b/lng/en.lng.php @@ -2432,7 +2432,37 @@ Yours sincerely, your administrator', ], 'backup' => [ 'backup' => 'Backup', + 'backups' => 'Backups', + 'backups_restore' => 'Restore backups', + 'backup_storages' => 'Configure storages', 'size' => 'Size', 'created_at' => 'Created at', + 'backup_storage' => [ + 'add' => 'Add backup storage', + 'create' => 'Create new backup storage', + 'edit' => 'Edit backup storage', + 'list' => 'Backup storages', + 'description' => 'Storage description', + 'type' => 'Storage type', + 'type_local' => 'local filesystem', + 'type_ftp' => 'FTP server', + 'type_sftp' => 'SFTP to remote server', + 'type_rsync' => 'Rsync to remote server', + 'type_s3' => 'S3 server', + 'region' => 'S3 region', + 'bucket' => 'S3 bucket', + 'destination_path' => [ + 'title' => 'Destination path', + 'description' => 'Absolute path if storage type is "local", else relative to home-directory of given user', + ], + 'hostname' => 'Target hostname', + 'username' => 'Username', + 'password' => [ + 'title' => 'Password or ssh public-key', + 'description' => 'Can be either a password or a complete SSH public key' + ], + 'pgp_public_key' => 'PGP public key', + 'retention' => 'Backup retention in days', + ], ], ];