+ */
+ protected static function getBackupWebExtension($format)
+ {
+ switch ($format) {
+ case 'tar_gzip':
+ return '.tar.gz';
+ case 'tar_bzip2':
+ return '.tar.bz2';
+ case 'tar_xz':
+ return '.tar.xz';
+ case 'zip':
+ case 'zip_bzip2':
+ return '.zip';
+ case 'rar':
+ return '.rar';
+ }
+ if (strpos($format, "tar_7z_") === 0) {
+ return '.tar.7z';
+ }
+ return null;
+ }
+ protected static function getDefaultBackupFormat($backup_mode, $backup_type)
+ {
+ //We have a backup from old version of ISPConfig
+ switch ($backup_type) {
+ case 'mysql':
+ return 'gzip';
+ case 'web':
+ return ($backup_mode == 'userzip') ? 'zip' : 'tar_gzip';
+ }
+ return "";
+ }
+
}
?>
diff --git a/interface/web/admin/form/server_config.tform.php b/interface/web/admin/form/server_config.tform.php
index 47a48a3c279e2bf438a3efeb796debed358b1293..6b1cc3716bf37f73d450b41cda80a9f07becf504 100644
--- a/interface/web/admin/form/server_config.tform.php
+++ b/interface/web/admin/form/server_config.tform.php
@@ -207,7 +207,7 @@ $form["tabs"]['server'] = array(
'datatype' => 'VARCHAR',
'formtype' => 'SELECT',
'default' => 'userzip',
- 'value' => array('userzip' => 'backup_mode_userzip', 'rootgz' => 'backup_mode_rootgz'),
+ 'value' => array('userzip' => 'backup_mode_userzip', 'rootgz' => 'backup_mode_rootgz', 'borg' => 'backup_mode_borg_txt'),
'width' => '40',
'maxlength' => '255'
),
diff --git a/interface/web/admin/lib/lang/ar_server_config.lng b/interface/web/admin/lib/lang/ar_server_config.lng
index ac03605279d769d3b8c8d2be21bcb84b43b8e687..b5d43392c89cb786775b3f9df151ec538bda5183 100644
--- a/interface/web/admin/lib/lang/ar_server_config.lng
+++ b/interface/web/admin/lib/lang/ar_server_config.lng
@@ -176,6 +176,8 @@ $wb['website_autoalias_note_txt'] = 'Placeholders:';
$wb['backup_mode_txt'] = 'Backup mode';
$wb['backup_mode_userzip'] = 'Backup web files owned by web user as zip';
$wb['backup_mode_rootgz'] = 'Backup all files in web directory as root user';
+$wb['backup_mode_borg_txt'] = 'BorgBackup: Backup all files in vhost directory and databases into incremental repositories';
+$wb['backup_missing_utils_txt'] = 'The following backup mode can not be used because the required tools are not installed:';
$wb['realtime_blackhole_list_txt'] = 'Real-time Blackhole List';
$wb['realtime_blackhole_list_note_txt'] = '(Separate RBL\'s with commas)';
$wb['stress_adaptive_txt'] = 'Adapt to temporary load spikes';
diff --git a/interface/web/admin/lib/lang/bg_server_config.lng b/interface/web/admin/lib/lang/bg_server_config.lng
index b9d6e648cedeb6b0666b8112acb93b5a0b9fe838..398e15db8f4cef47a11d843e726589cef7a2d305 100644
--- a/interface/web/admin/lib/lang/bg_server_config.lng
+++ b/interface/web/admin/lib/lang/bg_server_config.lng
@@ -174,6 +174,8 @@ $wb['connect_userid_to_webid_start_txt'] = 'Start ID for userid/webid connect';
$wb['backup_mode_txt'] = 'Backup mode';
$wb['backup_mode_userzip'] = 'Backup web files owned by web user as zip';
$wb['backup_mode_rootgz'] = 'Backup all files in web directory as root user';
+$wb['backup_mode_borg_txt'] = 'BorgBackup: Backup all files in vhost directory and databases into incremental repositories';
+$wb['backup_missing_utils_txt'] = 'The following backup mode can not be used because the required tools are not installed:';
$wb['realtime_blackhole_list_txt'] = 'Real-time Blackhole List';
$wb['realtime_blackhole_list_note_txt'] = '(Separate RBL\'s with commas)';
$wb['stress_adaptive_txt'] = 'Adapt to temporary load spikes';
diff --git a/interface/web/admin/lib/lang/br_server_config.lng b/interface/web/admin/lib/lang/br_server_config.lng
index ac551c588cd5917247c1bce3b1eb3a4991d0affc..51828f2e158e026ab8e92a42493e5f3e17ad8421 100644
--- a/interface/web/admin/lib/lang/br_server_config.lng
+++ b/interface/web/admin/lib/lang/br_server_config.lng
@@ -114,6 +114,8 @@ $wb['fastcgi_config_syntax_txt'] = 'Sintaxe das configurações do FastCGI';
$wb['backup_mode_txt'] = 'Modo do backup';
$wb['backup_mode_userzip'] = 'Arquivos de backup com propriedade do usuário web e compactados como zip';
$wb['backup_mode_rootgz'] = 'Todos os arquivos no diretório web com proprietário root';
+$wb['backup_mode_borg_txt'] = 'BorgBackup: Faça backup de todos os arquivos no diretório vhost e bancos de dados em repositórios incrementais';
+$wb['backup_missing_utils_txt'] = 'O seguinte modo de backup não pode ser usado porque as ferramentas necessárias não estão instaladas:';
$wb['tmpdir_path_error_empty'] = 'Caminho do diretório temporário está vazio.';
$wb['tmpdir_path_error_regex'] = 'Caminho do diretório temporário é inválido.';
$wb['backup_time_txt'] = 'Hora do backup';
diff --git a/interface/web/admin/lib/lang/ca_server_config.lng b/interface/web/admin/lib/lang/ca_server_config.lng
index 25ed761836cafb2f1216212c3582bfe3c6abcd29..1e048dad50677fd5669ec145efa8d3b05e1561a3 100644
--- a/interface/web/admin/lib/lang/ca_server_config.lng
+++ b/interface/web/admin/lib/lang/ca_server_config.lng
@@ -113,6 +113,8 @@ $wb['fastcgi_config_syntax_txt'] = 'FastCGI config syntax';
$wb['backup_mode_txt'] = 'Backup mode';
$wb['backup_mode_userzip'] = 'Backup web files owned by web user as zip';
$wb['backup_mode_rootgz'] = 'Backup all files in web directory as root user';
+$wb['backup_mode_borg_txt'] = 'BorgBackup: Backup all files in vhost directory and databases into incremental repositories';
+$wb['backup_missing_utils_txt'] = 'The following backup mode can not be used because the required tools are not installed:';
$wb['server_type_txt'] = 'Server Type';
$wb['nginx_vhost_conf_dir_txt'] = 'Nginx Vhost config dir';
$wb['nginx_vhost_conf_enabled_dir_txt'] = 'Nginx Vhost config enabled dir';
diff --git a/interface/web/admin/lib/lang/cz_server_config.lng b/interface/web/admin/lib/lang/cz_server_config.lng
index 6da8dfc0bb4989c6a7e179417fdc1afe7693f1f7..9fa2a8e1d05d11b82bcdd3a919d54dfbc3a159db 100644
--- a/interface/web/admin/lib/lang/cz_server_config.lng
+++ b/interface/web/admin/lib/lang/cz_server_config.lng
@@ -162,6 +162,8 @@ $wb['website_autoalias_note_txt'] = 'Placeholders:';
$wb['backup_mode_txt'] = 'Režim zálohování';
$wb['backup_mode_userzip'] = 'Zálohování všech souborů v adresáři web jako uživatel vlastnící web adresář do souboru zip';
$wb['backup_mode_rootgz'] = 'Zálohování všech souborů v adresáři web jako uživatel root';
+$wb['backup_mode_borg_txt'] = 'BorgBackup: Zálohujte všechny soubory v adresáři vhost a databázích do přírůstkových úložišť';
+$wb['backup_missing_utils_txt'] = 'Následující režim zálohování nelze použít, protože nejsou nainstalovány požadované nástroje:';
$wb['realtime_blackhole_list_txt'] = 'Real-time Blackhole List';
$wb['realtime_blackhole_list_note_txt'] = '(jednotlivé RBL databáze oddělujte čárkou)';
$wb['stress_adaptive_txt'] = 'Adapt to temporary load spikes';
diff --git a/interface/web/admin/lib/lang/de_server_config.lng b/interface/web/admin/lib/lang/de_server_config.lng
index d0b43059c47cf52c0dea5cc5f5363007d7c5edb2..4877141a5070b4368fa504188e3a8130ee3f5a77 100644
--- a/interface/web/admin/lib/lang/de_server_config.lng
+++ b/interface/web/admin/lib/lang/de_server_config.lng
@@ -190,6 +190,8 @@ $wb['awstats_settings_txt'] = 'AWStats Einstellungen';
$wb['backup_mode_txt'] = 'Backupmodus';
$wb['backup_mode_userzip'] = 'Backup Dateien gehören dem Web Benutzer (.zip Datei)';
$wb['backup_mode_rootgz'] = 'Backup aller Dateien des Webverzeichnisses als Root Benutzer';
+$wb['backup_mode_borg_txt'] = 'BorgBackup: Maak een back-up van alle bestanden in de vhost-directory en databases in incrementele repositories';
+$wb['backup_missing_utils_txt'] = 'De volgende back-upmodus kan niet worden gebruikt omdat de vereiste tools niet zijn geïnstalleerd:';
$wb['backup_time_txt'] = 'Backupzeit';
$wb['firewall_txt'] = 'Firewall';
$wb['mailbox_quota_stats_txt'] = 'E-Mailkonto Beschränkung Statistiken';
diff --git a/interface/web/admin/lib/lang/dk_server_config.lng b/interface/web/admin/lib/lang/dk_server_config.lng
index b1ebcec391b4c26a45b1ee7edc204c1b85242560..cc923d19598986f5ef11b4cb124ff9ce2ebe8b8c 100644
--- a/interface/web/admin/lib/lang/dk_server_config.lng
+++ b/interface/web/admin/lib/lang/dk_server_config.lng
@@ -100,6 +100,8 @@ $wb['fastcgi_config_syntax_txt'] = 'FastCGI config syntax';
$wb['backup_mode_txt'] = 'Backup mode';
$wb['backup_mode_userzip'] = 'Backup webfiler ejet af web-bruger som zip';
$wb['backup_mode_rootgz'] = 'Backup alle filer i web mappe som root-bruger';
+$wb['backup_mode_borg_txt'] = 'BorgBackup: Sikkerhedskopier alle filer i vhost -bibliotek og databaser til inkrementelle lagre';
+$wb['backup_missing_utils_txt'] = 'Følgende backup -tilstand kan ikke bruges, fordi de nødvendige værktøjer ikke er installeret:';
$wb['server_type_txt'] = 'Server Type';
$wb['nginx_vhost_conf_dir_txt'] = 'Nginx Vhost config dir';
$wb['nginx_vhost_conf_enabled_dir_txt'] = 'Nginx Vhost config enabled dir';
diff --git a/interface/web/admin/lib/lang/el_server_config.lng b/interface/web/admin/lib/lang/el_server_config.lng
index b147f15e5c98954b23c73b43148a5a74362f366a..2bcda04116eacc327e56c15b320ab143ceb20531 100644
--- a/interface/web/admin/lib/lang/el_server_config.lng
+++ b/interface/web/admin/lib/lang/el_server_config.lng
@@ -176,6 +176,8 @@ $wb['website_autoalias_note_txt'] = 'Placeholders:';
$wb['backup_mode_txt'] = 'Backup mode';
$wb['backup_mode_userzip'] = 'Backup web files owned by web user as zip';
$wb['backup_mode_rootgz'] = 'Backup all files in web directory as root user';
+$wb['backup_mode_borg_txt'] = 'BorgBackup: Backup all files in vhost directory and databases into incremental repositories';
+$wb['backup_missing_utils_txt'] = 'The following backup mode can not be used because the required tools are not installed:';
$wb['realtime_blackhole_list_txt'] = 'Real-time Blackhole List';
$wb['realtime_blackhole_list_note_txt'] = '(Separate RBL\'s with commas)';
$wb['stress_adaptive_txt'] = 'Adapt to temporary load spikes';
diff --git a/interface/web/admin/lib/lang/en_server_config.lng b/interface/web/admin/lib/lang/en_server_config.lng
index 4125b2648ea4f13786b613c2ab55ac5fd11d1db1..a908cffb80462100eb5808481023a456c27018f6 100644
--- a/interface/web/admin/lib/lang/en_server_config.lng
+++ b/interface/web/admin/lib/lang/en_server_config.lng
@@ -120,6 +120,8 @@ $wb['fastcgi_config_syntax_txt'] = 'FastCGI config syntax';
$wb['backup_mode_txt'] = 'Backup mode';
$wb['backup_mode_userzip'] = 'Backup web files owned by web user as zip';
$wb['backup_mode_rootgz'] = 'Backup all files in web directory as root user';
+$wb['backup_mode_borg_txt'] = 'BorgBackup: Backup all files in vhost directory and databases into incremental repositories';
+$wb['backup_missing_utils_txt'] = 'The following backup mode can not be used because the required tools are not installed:';
$wb['tmpdir_path_error_empty'] = 'tmp-dir Path is empty.';
$wb['tmpdir_path_error_regex'] = 'Invalid tmp-dir path.';
$wb['backup_time_txt'] = 'Backup time';
diff --git a/interface/web/admin/lib/lang/es_server_config.lng b/interface/web/admin/lib/lang/es_server_config.lng
index 67e77efac87b1dd937896a43e163e5e790bd3232..b8f59c9ab39b733ad4a7cad136f0cd20c04ead5d 100644
--- a/interface/web/admin/lib/lang/es_server_config.lng
+++ b/interface/web/admin/lib/lang/es_server_config.lng
@@ -36,6 +36,8 @@ $wb['backup_dir_is_mount_txt'] = 'El directorio de copias de seguridad está mon
$wb['backup_dir_mount_cmd_txt'] = 'Comando de montaje, si el directorio de copias de seguridad no está montado';
$wb['backup_dir_txt'] = 'Directorio para respaldos';
$wb['backup_mode_rootgz'] = 'Respaldar todos los archivos en el directorio web siendo root el propietario';
+$wb['backup_mode_borg_txt'] = 'BorgBackup: Respaldar todos los archivos en el directorio vhost y las bases de datos en repositorios incrementales';
+$wb['backup_missing_utils_txt'] = 'El siguiente modo de respaldo no se puede utilizar porque las herramientas necesarias no están instaladas:';
$wb['backup_mode_txt'] = 'Modo de respaldo';
$wb['backup_mode_userzip'] = 'Respaldar archivos web siendo el usuario el propietario en formato zip';
$wb['bind_group_error_empty'] = 'El grupo para BIND está vacío.';
diff --git a/interface/web/admin/lib/lang/fi_server_config.lng b/interface/web/admin/lib/lang/fi_server_config.lng
index dac02a14b7ee502d9c91a121d7a238ffc96c6c66..591a6df75f69d998157a5d53f82cfeecc8a78207 100644
--- a/interface/web/admin/lib/lang/fi_server_config.lng
+++ b/interface/web/admin/lib/lang/fi_server_config.lng
@@ -176,6 +176,8 @@ $wb['website_autoalias_note_txt'] = 'Placeholders:';
$wb['backup_mode_txt'] = 'Backup mode';
$wb['backup_mode_userzip'] = 'Backup web files owned by web user as zip';
$wb['backup_mode_rootgz'] = 'Backup all files in web directory as root user';
+$wb['backup_mode_borg_txt'] = 'BorgBackup: Backup all files in vhost directory and databases into incremental repositories';
+$wb['backup_missing_utils_txt'] = 'The following backup mode can not be used because the required tools are not installed:';
$wb['realtime_blackhole_list_txt'] = 'Real-time Blackhole List';
$wb['realtime_blackhole_list_note_txt'] = '(Separate RBL\'s with commas)';
$wb['stress_adaptive_txt'] = 'Adapt to temporary load spikes';
diff --git a/interface/web/admin/lib/lang/fr_server_config.lng b/interface/web/admin/lib/lang/fr_server_config.lng
index 0599b8bbed10b9bd886338f09bb65205f2b4887d..692f9ab5f53154ffbec88f1080ac819439878795 100644
--- a/interface/web/admin/lib/lang/fr_server_config.lng
+++ b/interface/web/admin/lib/lang/fr_server_config.lng
@@ -164,6 +164,8 @@ $wb['website_autoalias_note_txt'] = 'Placeholders:';
$wb['backup_mode_txt'] = 'Backup mode';
$wb['backup_mode_userzip'] = 'Backup web files owned by web user as zip';
$wb['backup_mode_rootgz'] = 'Backup all files in web directory as root user';
+$wb['backup_mode_borg_txt'] = 'BorgBackup: Backup all files in vhost directory and databases into incremental repositories';
+$wb['backup_missing_utils_txt'] = 'The following backup mode can not be used because the required tools are not installed:';
$wb['realtime_blackhole_list_txt'] = 'Real-time Blackhole List';
$wb['realtime_blackhole_list_note_txt'] = '(Separate RBL’s with commas)';
$wb['stress_adaptive_txt'] = 'Adapt to temporary load spikes';
diff --git a/interface/web/admin/lib/lang/hr_server_config.lng b/interface/web/admin/lib/lang/hr_server_config.lng
index e0894ceb82916b2a2b95c0889bfca6baaed23ade..8baa83beea016974340aae2e07a1e16389e03ffd 100644
--- a/interface/web/admin/lib/lang/hr_server_config.lng
+++ b/interface/web/admin/lib/lang/hr_server_config.lng
@@ -176,6 +176,8 @@ $wb['website_autoalias_note_txt'] = 'Placeholders:';
$wb['backup_mode_txt'] = 'Backup mode';
$wb['backup_mode_userzip'] = 'Backup web files owned by web user as zip';
$wb['backup_mode_rootgz'] = 'Backup all files in web directory as root user';
+$wb['backup_mode_borg_txt'] = 'BorgBackup: Backup all files in vhost directory and databases into incremental repositories';
+$wb['backup_missing_utils_txt'] = 'The following backup mode can not be used because the required tools are not installed:';
$wb['realtime_blackhole_list_txt'] = 'Real-time Blackhole List';
$wb['realtime_blackhole_list_note_txt'] = '(Separate RBL\'s with commas)';
$wb['stress_adaptive_txt'] = 'Adapt to temporary load spikes';
diff --git a/interface/web/admin/lib/lang/hu_server_config.lng b/interface/web/admin/lib/lang/hu_server_config.lng
index 97774f9ecd77c17812d7c3e4612a935b967ca053..dde66a317c5b76f29a5066f5626328a350247987 100644
--- a/interface/web/admin/lib/lang/hu_server_config.lng
+++ b/interface/web/admin/lib/lang/hu_server_config.lng
@@ -176,6 +176,8 @@ $wb['website_autoalias_note_txt'] = 'Placeholders:';
$wb['backup_mode_txt'] = 'Backup mode';
$wb['backup_mode_userzip'] = 'Backup web files owned by web user as zip';
$wb['backup_mode_rootgz'] = 'Backup all files in web directory as root user';
+$wb['backup_mode_borg_txt'] = 'BorgBackup: Backup all files in vhost directory and databases into incremental repositories';
+$wb['backup_missing_utils_txt'] = 'The following backup mode can not be used because the required tools are not installed:';
$wb['realtime_blackhole_list_txt'] = 'Real-time Blackhole List';
$wb['realtime_blackhole_list_note_txt'] = '(Separate RBL\'s with commas)';
$wb['stress_adaptive_txt'] = 'Adapt to temporary load spikes';
diff --git a/interface/web/admin/lib/lang/id_server_config.lng b/interface/web/admin/lib/lang/id_server_config.lng
index 814e963f649938d4cf6a88cb871844a7d0186b98..0349aad4caf2c85ede07b96ff8442bc3401e22d7 100644
--- a/interface/web/admin/lib/lang/id_server_config.lng
+++ b/interface/web/admin/lib/lang/id_server_config.lng
@@ -176,6 +176,8 @@ $wb['website_autoalias_note_txt'] = 'Placeholders:';
$wb['backup_mode_txt'] = 'Backup mode';
$wb['backup_mode_userzip'] = 'Backup web files owned by web user as zip';
$wb['backup_mode_rootgz'] = 'Backup all files in web directory as root user';
+$wb['backup_mode_borg_txt'] = 'BorgBackup: Backup all files in vhost directory and databases into incremental repositories';
+$wb['backup_missing_utils_txt'] = 'The following backup mode can not be used because the required tools are not installed:';
$wb['realtime_blackhole_list_txt'] = 'Real-time Blackhole List';
$wb['realtime_blackhole_list_note_txt'] = '(Separate RBL\'s with commas)';
$wb['stress_adaptive_txt'] = 'Adapt to temporary load spikes';
diff --git a/interface/web/admin/lib/lang/it_server_config.lng b/interface/web/admin/lib/lang/it_server_config.lng
index f9e30f39374728b73ef67c587d8c84c625117e68..09529e278f8252582f00c1b1007af066fa77b6b7 100644
--- a/interface/web/admin/lib/lang/it_server_config.lng
+++ b/interface/web/admin/lib/lang/it_server_config.lng
@@ -164,6 +164,8 @@ $wb['website_autoalias_note_txt'] = 'Placeholders:';
$wb['backup_mode_txt'] = 'Modalità di Backup';
$wb['backup_mode_userzip'] = 'Backup files siti web come utente web in formato zip';
$wb['backup_mode_rootgz'] = 'Backup di tutti i files nella cartella sito come utente root';
+$wb['backup_mode_borg_txt'] = 'BorgBackup: Backup all files in vhost directory and databases into incremental repositories';
+$wb['backup_missing_utils_txt'] = 'The following backup mode can not be used because the required tools are not installed:';
$wb['realtime_blackhole_list_txt'] = 'Lista Real-Time Blackhole';
$wb['realtime_blackhole_list_note_txt'] = '(Separare RBL con le virgole)';
$wb['stress_adaptive_txt'] = 'Adapt to temporary load spikes';
diff --git a/interface/web/admin/lib/lang/ja_server_config.lng b/interface/web/admin/lib/lang/ja_server_config.lng
index 52ac44a351d9a35a9d25376c288d5225892ae266..ca5657595386c995af1663de5a6beb7621c92b7c 100644
--- a/interface/web/admin/lib/lang/ja_server_config.lng
+++ b/interface/web/admin/lib/lang/ja_server_config.lng
@@ -176,6 +176,8 @@ $wb['website_autoalias_note_txt'] = 'Placeholders:';
$wb['backup_mode_txt'] = 'Backup mode';
$wb['backup_mode_userzip'] = 'Backup web files owned by web user as zip';
$wb['backup_mode_rootgz'] = 'Backup all files in web directory as root user';
+$wb['backup_mode_borg_txt'] = 'BorgBackup: Backup all files in vhost directory and databases into incremental repositories';
+$wb['backup_missing_utils_txt'] = 'The following backup mode can not be used because the required tools are not installed:';
$wb['realtime_blackhole_list_txt'] = 'Real-time Blackhole List';
$wb['realtime_blackhole_list_note_txt'] = '(Separate RBL\'s with commas)';
$wb['stress_adaptive_txt'] = 'Adapt to temporary load spikes';
diff --git a/interface/web/admin/lib/lang/nl_server_config.lng b/interface/web/admin/lib/lang/nl_server_config.lng
index e9e412b609e1fd6acb6c4374268a3346e1898cd4..dba658411ce6a7d8fa5e03087d63b8b54c798c1c 100644
--- a/interface/web/admin/lib/lang/nl_server_config.lng
+++ b/interface/web/admin/lib/lang/nl_server_config.lng
@@ -176,6 +176,8 @@ $wb['website_autoalias_note_txt'] = 'Placeholders:';
$wb['backup_mode_txt'] = 'Backup mode';
$wb['backup_mode_userzip'] = 'Backup web files owned by web user as zip';
$wb['backup_mode_rootgz'] = 'Backup all files in web directory as root user';
+$wb['backup_mode_borg_txt'] = 'BorgBackup: Backup all files in vhost directory and databases into incremental repositories';
+$wb['backup_missing_utils_txt'] = 'The following backup mode can not be used because the required tools are not installed:';
$wb['realtime_blackhole_list_txt'] = 'Real-time Blackhole List';
$wb['realtime_blackhole_list_note_txt'] = '(Separate RBL\'s with commas)';
$wb['stress_adaptive_txt'] = 'Adapt to temporary load spikes';
diff --git a/interface/web/admin/lib/lang/pl_server_config.lng b/interface/web/admin/lib/lang/pl_server_config.lng
index f9d43d3bfd8e09a3f0e86c1dda8d219f6bbceb51..01fbf234c68d1d218db1d6d7b9e727ade850f27c 100644
--- a/interface/web/admin/lib/lang/pl_server_config.lng
+++ b/interface/web/admin/lib/lang/pl_server_config.lng
@@ -176,6 +176,8 @@ $wb['website_autoalias_note_txt'] = 'Placeholders:';
$wb['backup_mode_txt'] = 'Tryb tworzenia kopii';
$wb['backup_mode_userzip'] = 'Pliki kopii z prawami użytkownika jako zip';
$wb['backup_mode_rootgz'] = 'Twórz kopie wszystkich plików w katalogu web jako root';
+$wb['backup_mode_borg_txt'] = 'BorgBackup: Twórz kopię wszystkich plików z katalogu vhost i baz danych w repozytoriach przyrostowych';
+$wb['backup_missing_utils_txt'] = 'Nie można użyć następującego trybu kopii zapasowej, ponieważ wymagane narzędzia nie są zainstalowane:';
$wb['realtime_blackhole_list_txt'] = 'Real-time Blackhole List';
$wb['realtime_blackhole_list_note_txt'] = '(oddziel RBL-e przecinkami)';
$wb['stress_adaptive_txt'] = 'Adapt to temporary load spikes';
diff --git a/interface/web/admin/lib/lang/pt_server_config.lng b/interface/web/admin/lib/lang/pt_server_config.lng
index 468413a1deab53236c67c84e6e51018e5709fd03..0f52c114ddd1dd489e98875bb283fece637924d5 100644
--- a/interface/web/admin/lib/lang/pt_server_config.lng
+++ b/interface/web/admin/lib/lang/pt_server_config.lng
@@ -176,6 +176,8 @@ $wb['website_autoalias_note_txt'] = 'Placeholders:';
$wb['backup_mode_txt'] = 'Backup mode';
$wb['backup_mode_userzip'] = 'Backup web files owned by web user as zip';
$wb['backup_mode_rootgz'] = 'Backup all files in web directory as root user';
+$wb['backup_mode_borg_txt'] = 'BorgBackup: Backup all files in vhost directory and databases into incremental repositories';
+$wb['backup_missing_utils_txt'] = 'The following backup mode can not be used because the required tools are not installed:';
$wb['realtime_blackhole_list_txt'] = 'Real-time Blackhole List';
$wb['realtime_blackhole_list_note_txt'] = '(Separate RBL\'s with commas)';
$wb['stress_adaptive_txt'] = 'Adapt to temporary load spikes';
diff --git a/interface/web/admin/lib/lang/ro_server_config.lng b/interface/web/admin/lib/lang/ro_server_config.lng
index e20fb9ee9f5ecc75ea9ad8da2a475878bc0c9f1d..f81d0cbb5642cdb5afd1909628d09ab6bcc69afd 100644
--- a/interface/web/admin/lib/lang/ro_server_config.lng
+++ b/interface/web/admin/lib/lang/ro_server_config.lng
@@ -176,6 +176,8 @@ $wb['website_autoalias_note_txt'] = 'Placeholders:';
$wb['backup_mode_txt'] = 'Backup mode';
$wb['backup_mode_userzip'] = 'Backup web files owned by web user as zip';
$wb['backup_mode_rootgz'] = 'Backup all files in web directory as root user';
+$wb['backup_mode_borg_txt'] = 'BorgBackup: Backup all files in vhost directory and databases into incremental repositories';
+$wb['backup_missing_utils_txt'] = 'The following backup mode can not be used because the required tools are not installed:';
$wb['realtime_blackhole_list_txt'] = 'Real-time Blackhole List';
$wb['realtime_blackhole_list_note_txt'] = '(Separate RBL\'s with commas)';
$wb['stress_adaptive_txt'] = 'Adapt to temporary load spikes';
diff --git a/interface/web/admin/lib/lang/ru_server_config.lng b/interface/web/admin/lib/lang/ru_server_config.lng
index 1799b075f49fd9155b07b7cc667f8fd9cc161f9e..c134698d801bd73f08843455fa45b48be96e3c11 100644
--- a/interface/web/admin/lib/lang/ru_server_config.lng
+++ b/interface/web/admin/lib/lang/ru_server_config.lng
@@ -176,6 +176,8 @@ $wb['website_autoalias_note_txt'] = 'Заменители:';
$wb['backup_mode_txt'] = 'Режим резервного копирования';
$wb['backup_mode_userzip'] = 'Делать резервные копии web файлов принадлежащих ползователю web как архив ZIP';
$wb['backup_mode_rootgz'] = 'Делать резервные копии всех файлов в веб-каталог как корневой пользователь';
+$wb['backup_mode_borg_txt'] = 'BorgBackup: Делать резервные копии всех файлов в каталоге vhost и базах данных в инкрементные репозитории';
+$wb['backup_missing_utils_txt'] = 'Следующий режим резервного копирования нельзя использовать, поскольку не установлены необходимые инструменты:';
$wb['realtime_blackhole_list_txt'] = 'Real-time Blackhole List';
$wb['realtime_blackhole_list_note_txt'] = '(Разделяйте RBL запятыми)';
$wb['stress_adaptive_txt'] = 'Adapt to temporary load spikes';
diff --git a/interface/web/admin/lib/lang/se_server_config.lng b/interface/web/admin/lib/lang/se_server_config.lng
index fe3c2e923401b8d1a9ce1e76ef870513e5c3f30d..ada1562feed3aa9ea3d92669d9050a87be0c7fb2 100644
--- a/interface/web/admin/lib/lang/se_server_config.lng
+++ b/interface/web/admin/lib/lang/se_server_config.lng
@@ -176,6 +176,8 @@ $wb['website_autoalias_note_txt'] = 'Placeholders:';
$wb['backup_mode_txt'] = 'Backup mode';
$wb['backup_mode_userzip'] = 'Backup web files owned by web user as zip';
$wb['backup_mode_rootgz'] = 'Backup all files in web directory as root user';
+$wb['backup_mode_borg_txt'] = 'BorgBackup: Backup all files in vhost directory and databases into incremental repositories';
+$wb['backup_missing_utils_txt'] = 'The following backup mode can not be used because the required tools are not installed:';
$wb['realtime_blackhole_list_txt'] = 'Real-time Blackhole List';
$wb['realtime_blackhole_list_note_txt'] = '(Separate RBL\'s with commas)';
$wb['stress_adaptive_txt'] = 'Adapt to temporary load spikes';
diff --git a/interface/web/admin/lib/lang/sk_server_config.lng b/interface/web/admin/lib/lang/sk_server_config.lng
index bc7f9f514bdb25db6f112c4bef47c44a7817530d..39948a1c1247173affd98438841bbb61b48dd8cf 100644
--- a/interface/web/admin/lib/lang/sk_server_config.lng
+++ b/interface/web/admin/lib/lang/sk_server_config.lng
@@ -176,6 +176,8 @@ $wb['website_autoalias_note_txt'] = 'Placeholders:';
$wb['backup_mode_txt'] = 'Backup mode';
$wb['backup_mode_userzip'] = 'Backup web files owned by web user as zip';
$wb['backup_mode_rootgz'] = 'Backup all files in web directory as root user';
+$wb['backup_mode_borg_txt'] = 'BorgBackup: Backup all files in vhost directory and databases into incremental repositories';
+$wb['backup_missing_utils_txt'] = 'The following backup mode can not be used because the required tools are not installed:';
$wb['realtime_blackhole_list_txt'] = 'Real-time Blackhole List';
$wb['realtime_blackhole_list_note_txt'] = '(Separate RBL\'s with commas)';
$wb['stress_adaptive_txt'] = 'Adapt to temporary load spikes';
diff --git a/interface/web/admin/lib/lang/tr_server_config.lng b/interface/web/admin/lib/lang/tr_server_config.lng
index 0d0c84f2c73f873cb7dffb245c8deeb9e1ce2b22..be980a321b07a308dab1b49c3bd1e68d4563e6b7 100644
--- a/interface/web/admin/lib/lang/tr_server_config.lng
+++ b/interface/web/admin/lib/lang/tr_server_config.lng
@@ -114,6 +114,8 @@ $wb['fastcgi_config_syntax_txt'] = 'FastCGI Ayar Yazımı';
$wb['backup_mode_txt'] = 'Yedekleme Kipi';
$wb['backup_mode_userzip'] = 'Web kullanıcısına ait web dosyaları ZIP biçiminde yedeklensin';
$wb['backup_mode_rootgz'] = 'Web klasöründeki tüm dosyalar root kullanıcısı olarak yedeklensin';
+$wb['backup_mode_borg_txt'] = 'BorgBackup: Vhost dizinindeki ve veritabanlarındaki tüm dosyaları artımlı depolara yedekleyin';
+$wb['backup_missing_utils_txt'] = 'Gerekli araçlar kurulu olmadığı için aşağıdaki yedekleme modu kullanılamaz:';
$wb['tmpdir_path_error_empty'] = 'tmp klasörü yolu boş olamaz.';
$wb['tmpdir_path_error_regex'] = 'tmp klasörü yolu geçersiz.';
$wb['backup_time_txt'] = 'Yedekleme Zamanı';
diff --git a/interface/web/admin/server_config_edit.php b/interface/web/admin/server_config_edit.php
index 1fd1921b84e38562f1b756426c63bddf801430a3..29384ea94162e9252b13216bf090436171ac373e 100644
--- a/interface/web/admin/server_config_edit.php
+++ b/interface/web/admin/server_config_edit.php
@@ -63,7 +63,10 @@ class page_action extends tform_actions {
unset($app->tform->formDef["tabs"]["fastcgi"]);
unset($app->tform->formDef["tabs"]["vlogger"]);
}
-
+ //Check if borg is installed
+ if (!$app->system->is_installed('borg')) {
+ $app->tpl->setVar('missing_utils', 'BorgBackup');
+ }
parent::onShow();
}
diff --git a/interface/web/admin/templates/server_config_server_edit.htm b/interface/web/admin/templates/server_config_server_edit.htm
index 394bf55278833f23df18c477a3fe781242213331..1898f2bc556ef4d516abf11dd30fb22cfdc13c81 100644
--- a/interface/web/admin/templates/server_config_server_edit.htm
+++ b/interface/web/admin/templates/server_config_server_edit.htm
@@ -1,4 +1,9 @@
{tmpl_var name='server_name'}
+
+
+ {tmpl_var name='backup_missing_utils_txt'} {tmpl_var name='missing_utils'}
+
+
diff --git a/interface/web/sites/lib/lang/ar_web_backup_list.lng b/interface/web/sites/lib/lang/ar_web_backup_list.lng
index 8f0d3a7469f807d706676c8581e3ea704584baec..0f19d9339ed75bcaaf730ee4b62bacc1cc70e4f9 100644
--- a/interface/web/sites/lib/lang/ar_web_backup_list.lng
+++ b/interface/web/sites/lib/lang/ar_web_backup_list.lng
@@ -30,6 +30,7 @@ $wb['manual_backup_title_txt'] = 'Manual backup';
$wb['make_backup_web_txt'] = 'Make backup of web files';
$wb['make_backup_database_txt'] = 'Make backup of databases';
$wb['make_backup_confirm_txt'] = 'You are about to start a manual backup process. Manual backups count towards the total number of allowed backup copies: therefore if the limit will be exceeded, then oldest backups may be deleted automatically. Proceed?';
+$wb['final_size_txt'] = 'Final download size may vary depending on selected compression format.';
$wb['yes_txt'] = 'Yes';
$wb['no_txt'] = 'No';
$wb['backup_is_encrypted_txt'] = 'Encrypted';
diff --git a/interface/web/sites/lib/lang/bg_web_backup_list.lng b/interface/web/sites/lib/lang/bg_web_backup_list.lng
index 8f0d3a7469f807d706676c8581e3ea704584baec..0f19d9339ed75bcaaf730ee4b62bacc1cc70e4f9 100644
--- a/interface/web/sites/lib/lang/bg_web_backup_list.lng
+++ b/interface/web/sites/lib/lang/bg_web_backup_list.lng
@@ -30,6 +30,7 @@ $wb['manual_backup_title_txt'] = 'Manual backup';
$wb['make_backup_web_txt'] = 'Make backup of web files';
$wb['make_backup_database_txt'] = 'Make backup of databases';
$wb['make_backup_confirm_txt'] = 'You are about to start a manual backup process. Manual backups count towards the total number of allowed backup copies: therefore if the limit will be exceeded, then oldest backups may be deleted automatically. Proceed?';
+$wb['final_size_txt'] = 'Final download size may vary depending on selected compression format.';
$wb['yes_txt'] = 'Yes';
$wb['no_txt'] = 'No';
$wb['backup_is_encrypted_txt'] = 'Encrypted';
diff --git a/interface/web/sites/lib/lang/br_web_backup_list.lng b/interface/web/sites/lib/lang/br_web_backup_list.lng
index 77580e3cc62b8df480a72db72c6c210c027063c4..f718769c44599ababccf1edde36c5aef2b05cf85 100644
--- a/interface/web/sites/lib/lang/br_web_backup_list.lng
+++ b/interface/web/sites/lib/lang/br_web_backup_list.lng
@@ -30,6 +30,7 @@ $wb['manual_backup_title_txt'] = 'Backup manual';
$wb['make_backup_web_txt'] = 'Criar backup dos arquivos Web';
$wb['make_backup_database_txt'] = 'Criar backup dos bancos de dados';
$wb['make_backup_confirm_txt'] = 'Você está prestes a iniciar um processo de backup manual. Os backups manuais contam para o número total de cópias de backup permitidas: portanto, se o limite for excedido, os backups mais antigos podem ser excluídos automaticamente. Continuar?';
+$wb['final_size_txt'] = 'O tamanho final do download pode variar dependendo do formato de compressão selecionado.';
$wb['yes_txt'] = 'Sim';
$wb['no_txt'] = 'Não';
$wb['backup_is_encrypted_txt'] = 'Criptografado';
diff --git a/interface/web/sites/lib/lang/ca_web_backup_list.lng b/interface/web/sites/lib/lang/ca_web_backup_list.lng
index af33c311422dfc75d603491e7b66162a31168fb9..fbb0495a986d8d7f4588006f201443582adb7874 100644
--- a/interface/web/sites/lib/lang/ca_web_backup_list.lng
+++ b/interface/web/sites/lib/lang/ca_web_backup_list.lng
@@ -30,6 +30,7 @@ $wb['manual_backup_title_txt'] = 'Manual backup';
$wb['make_backup_web_txt'] = 'Make backup of web files';
$wb['make_backup_database_txt'] = 'Make backup of databases';
$wb['make_backup_confirm_txt'] = 'You are about to start a manual backup process. Manual backups count towards the total number of allowed backup copies: therefore if the limit will be exceeded, then oldest backups may be deleted automatically. Proceed?';
+$wb['final_size_txt'] = 'Final download size may vary depending on selected compression format.';
$wb['yes_txt'] = 'Yes';
$wb['no_txt'] = 'No';
$wb['backup_is_encrypted_txt'] = 'Encrypted';
diff --git a/interface/web/sites/lib/lang/cz_web_backup_list.lng b/interface/web/sites/lib/lang/cz_web_backup_list.lng
index e6f80dac093d38de6d210d16190bff3ae811de7b..d06d69fa2df15fa62cadb2ccbc70fc05358d5c65 100644
--- a/interface/web/sites/lib/lang/cz_web_backup_list.lng
+++ b/interface/web/sites/lib/lang/cz_web_backup_list.lng
@@ -30,6 +30,7 @@ $wb['manual_backup_title_txt'] = 'Ruční zálohování';
$wb['make_backup_web_txt'] = 'Vytvořit zálohu webových souborů';
$wb['make_backup_database_txt'] = 'Vytvořit zálohu databází';
$wb['make_backup_confirm_txt'] = 'Chystáte se zahájit proces ručního zálohování. Ruční zálohy se započítávají do celkového počtu povolených záložních kopií: proto pokud bude limit překročen, mohou být nejstarší zálohy automaticky odstraněny. Pokračovat ?';
+$wb['final_size_txt'] = 'Konečná velikost stahování se může lišit v závislosti na zvoleném kompresním formátu.';
$wb['yes_txt'] = 'Ano';
$wb['no_txt'] = 'Ne';
$wb['backup_is_encrypted_txt'] = 'Šifrované';
diff --git a/interface/web/sites/lib/lang/de_web_backup_list.lng b/interface/web/sites/lib/lang/de_web_backup_list.lng
index 790156206ad075ce1f49c51b113bada9f9eb2d5c..11ebcd0645ecc950b456a31491325a58bad18682 100644
--- a/interface/web/sites/lib/lang/de_web_backup_list.lng
+++ b/interface/web/sites/lib/lang/de_web_backup_list.lng
@@ -30,6 +30,7 @@ $wb['manual_backup_title_txt'] = 'Manual backup';
$wb['make_backup_web_txt'] = 'Make backup of web files';
$wb['make_backup_database_txt'] = 'Make backup of databases';
$wb['make_backup_confirm_txt'] = 'You are about to start a manual backup process. Manual backups count towards the total number of allowed backup copies: therefore if the limit will be exceeded, then oldest backups may be deleted automatically. Proceed?';
+$wb['final_size_txt'] = 'Final download size may vary depending on selected compression format.';
$wb['yes_txt'] = 'Yes';
$wb['no_txt'] = 'No';
$wb['backup_is_encrypted_txt'] = 'Encrypted';
diff --git a/interface/web/sites/lib/lang/dk_web_backup_list.lng b/interface/web/sites/lib/lang/dk_web_backup_list.lng
index ba5b7234f8426856ea2b70dcc40336a70b79849b..1529fa1a96d9bbef512bf9db216bfce598f601e9 100644
--- a/interface/web/sites/lib/lang/dk_web_backup_list.lng
+++ b/interface/web/sites/lib/lang/dk_web_backup_list.lng
@@ -30,6 +30,7 @@ $wb['manual_backup_title_txt'] = 'Manual backup';
$wb['make_backup_web_txt'] = 'Make backup of web files';
$wb['make_backup_database_txt'] = 'Make backup of databases';
$wb['make_backup_confirm_txt'] = 'You are about to start a manual backup process. Manual backups count towards the total number of allowed backup copies: therefore if the limit will be exceeded, then oldest backups may be deleted automatically. Proceed?';
+$wb['final_size_txt'] = 'Final download size may vary depending on selected compression format.';
$wb['yes_txt'] = 'Yes';
$wb['no_txt'] = 'No';
$wb['backup_is_encrypted_txt'] = 'Encrypted';
diff --git a/interface/web/sites/lib/lang/el_web_backup_list.lng b/interface/web/sites/lib/lang/el_web_backup_list.lng
index 8f0d3a7469f807d706676c8581e3ea704584baec..0f19d9339ed75bcaaf730ee4b62bacc1cc70e4f9 100644
--- a/interface/web/sites/lib/lang/el_web_backup_list.lng
+++ b/interface/web/sites/lib/lang/el_web_backup_list.lng
@@ -30,6 +30,7 @@ $wb['manual_backup_title_txt'] = 'Manual backup';
$wb['make_backup_web_txt'] = 'Make backup of web files';
$wb['make_backup_database_txt'] = 'Make backup of databases';
$wb['make_backup_confirm_txt'] = 'You are about to start a manual backup process. Manual backups count towards the total number of allowed backup copies: therefore if the limit will be exceeded, then oldest backups may be deleted automatically. Proceed?';
+$wb['final_size_txt'] = 'Final download size may vary depending on selected compression format.';
$wb['yes_txt'] = 'Yes';
$wb['no_txt'] = 'No';
$wb['backup_is_encrypted_txt'] = 'Encrypted';
diff --git a/interface/web/sites/lib/lang/en_web_backup_list.lng b/interface/web/sites/lib/lang/en_web_backup_list.lng
index f2cde7f216def69746314b2c67f2040e4f4044cf..571f3959b8fbf4ac53d8469d870e75ccf64d75a5 100644
--- a/interface/web/sites/lib/lang/en_web_backup_list.lng
+++ b/interface/web/sites/lib/lang/en_web_backup_list.lng
@@ -30,6 +30,7 @@ $wb['manual_backup_title_txt'] = 'Manual backup';
$wb['make_backup_web_txt'] = 'Make backup of web files';
$wb['make_backup_database_txt'] = 'Make backup of databases';
$wb['make_backup_confirm_txt'] = 'You are about to start a manual backup process. Manual backups count towards the total number of allowed backup copies: therefore if the limit will be exceeded, then oldest backups may be deleted automatically. Proceed?';
+$wb['final_size_txt'] = 'Final download size may vary depending on selected compression format.';
$wb['yes_txt'] = 'Yes';
$wb['no_txt'] = 'No';
$wb['backup_is_encrypted_txt'] = 'Encrypted';
diff --git a/interface/web/sites/lib/lang/es_web_backup_list.lng b/interface/web/sites/lib/lang/es_web_backup_list.lng
index 61a7e83cb1c2bdc63308c1aa968facd26eeab4f8..fb8a547202bc615ab70d7c69e46a193df5f03a05 100644
--- a/interface/web/sites/lib/lang/es_web_backup_list.lng
+++ b/interface/web/sites/lib/lang/es_web_backup_list.lng
@@ -30,6 +30,7 @@ $wb['manual_backup_title_txt'] = 'Manual backup';
$wb['make_backup_web_txt'] = 'Make backup of web files';
$wb['make_backup_database_txt'] = 'Make backup of databases';
$wb['make_backup_confirm_txt'] = 'You are about to start a manual backup process. Manual backups count towards the total number of allowed backup copies: therefore if the limit will be exceeded, then oldest backups may be deleted automatically. Proceed?';
+$wb['final_size_txt'] = 'Final download size may vary depending on selected compression format.';
$wb['yes_txt'] = 'Yes';
$wb['no_txt'] = 'No';
$wb['backup_is_encrypted_txt'] = 'Encrypted';
diff --git a/interface/web/sites/lib/lang/fi_web_backup_list.lng b/interface/web/sites/lib/lang/fi_web_backup_list.lng
index 8f0d3a7469f807d706676c8581e3ea704584baec..0f19d9339ed75bcaaf730ee4b62bacc1cc70e4f9 100644
--- a/interface/web/sites/lib/lang/fi_web_backup_list.lng
+++ b/interface/web/sites/lib/lang/fi_web_backup_list.lng
@@ -30,6 +30,7 @@ $wb['manual_backup_title_txt'] = 'Manual backup';
$wb['make_backup_web_txt'] = 'Make backup of web files';
$wb['make_backup_database_txt'] = 'Make backup of databases';
$wb['make_backup_confirm_txt'] = 'You are about to start a manual backup process. Manual backups count towards the total number of allowed backup copies: therefore if the limit will be exceeded, then oldest backups may be deleted automatically. Proceed?';
+$wb['final_size_txt'] = 'Final download size may vary depending on selected compression format.';
$wb['yes_txt'] = 'Yes';
$wb['no_txt'] = 'No';
$wb['backup_is_encrypted_txt'] = 'Encrypted';
diff --git a/interface/web/sites/lib/lang/fr_web_backup_list.lng b/interface/web/sites/lib/lang/fr_web_backup_list.lng
index 1c3bd84662408614ce432caa71cf7bed1766f459..49da9188a10397bd2b45f41840dcf89690ce21ef 100644
--- a/interface/web/sites/lib/lang/fr_web_backup_list.lng
+++ b/interface/web/sites/lib/lang/fr_web_backup_list.lng
@@ -30,6 +30,7 @@ $wb['manual_backup_title_txt'] = 'Manual backup';
$wb['make_backup_web_txt'] = 'Make backup of web files';
$wb['make_backup_database_txt'] = 'Make backup of databases';
$wb['make_backup_confirm_txt'] = 'You are about to start a manual backup process. Manual backups count towards the total number of allowed backup copies: therefore if the limit will be exceeded, then oldest backups may be deleted automatically. Proceed?';
+$wb['final_size_txt'] = 'Final download size may vary depending on selected compression format.';
$wb['yes_txt'] = 'Yes';
$wb['no_txt'] = 'No';
$wb['backup_is_encrypted_txt'] = 'Encrypted';
diff --git a/interface/web/sites/lib/lang/hr_web_backup_list.lng b/interface/web/sites/lib/lang/hr_web_backup_list.lng
index 8f0d3a7469f807d706676c8581e3ea704584baec..0f19d9339ed75bcaaf730ee4b62bacc1cc70e4f9 100644
--- a/interface/web/sites/lib/lang/hr_web_backup_list.lng
+++ b/interface/web/sites/lib/lang/hr_web_backup_list.lng
@@ -30,6 +30,7 @@ $wb['manual_backup_title_txt'] = 'Manual backup';
$wb['make_backup_web_txt'] = 'Make backup of web files';
$wb['make_backup_database_txt'] = 'Make backup of databases';
$wb['make_backup_confirm_txt'] = 'You are about to start a manual backup process. Manual backups count towards the total number of allowed backup copies: therefore if the limit will be exceeded, then oldest backups may be deleted automatically. Proceed?';
+$wb['final_size_txt'] = 'Final download size may vary depending on selected compression format.';
$wb['yes_txt'] = 'Yes';
$wb['no_txt'] = 'No';
$wb['backup_is_encrypted_txt'] = 'Encrypted';
diff --git a/interface/web/sites/lib/lang/hu_web_backup_list.lng b/interface/web/sites/lib/lang/hu_web_backup_list.lng
index 8f0d3a7469f807d706676c8581e3ea704584baec..0f19d9339ed75bcaaf730ee4b62bacc1cc70e4f9 100644
--- a/interface/web/sites/lib/lang/hu_web_backup_list.lng
+++ b/interface/web/sites/lib/lang/hu_web_backup_list.lng
@@ -30,6 +30,7 @@ $wb['manual_backup_title_txt'] = 'Manual backup';
$wb['make_backup_web_txt'] = 'Make backup of web files';
$wb['make_backup_database_txt'] = 'Make backup of databases';
$wb['make_backup_confirm_txt'] = 'You are about to start a manual backup process. Manual backups count towards the total number of allowed backup copies: therefore if the limit will be exceeded, then oldest backups may be deleted automatically. Proceed?';
+$wb['final_size_txt'] = 'Final download size may vary depending on selected compression format.';
$wb['yes_txt'] = 'Yes';
$wb['no_txt'] = 'No';
$wb['backup_is_encrypted_txt'] = 'Encrypted';
diff --git a/interface/web/sites/lib/lang/id_web_backup_list.lng b/interface/web/sites/lib/lang/id_web_backup_list.lng
index 8f0d3a7469f807d706676c8581e3ea704584baec..0f19d9339ed75bcaaf730ee4b62bacc1cc70e4f9 100644
--- a/interface/web/sites/lib/lang/id_web_backup_list.lng
+++ b/interface/web/sites/lib/lang/id_web_backup_list.lng
@@ -30,6 +30,7 @@ $wb['manual_backup_title_txt'] = 'Manual backup';
$wb['make_backup_web_txt'] = 'Make backup of web files';
$wb['make_backup_database_txt'] = 'Make backup of databases';
$wb['make_backup_confirm_txt'] = 'You are about to start a manual backup process. Manual backups count towards the total number of allowed backup copies: therefore if the limit will be exceeded, then oldest backups may be deleted automatically. Proceed?';
+$wb['final_size_txt'] = 'Final download size may vary depending on selected compression format.';
$wb['yes_txt'] = 'Yes';
$wb['no_txt'] = 'No';
$wb['backup_is_encrypted_txt'] = 'Encrypted';
diff --git a/interface/web/sites/lib/lang/it_web_backup_list.lng b/interface/web/sites/lib/lang/it_web_backup_list.lng
index 67cacff8645af8883809da15ceb44a396f2b9a0d..08f3b48e8422645adea5ad2f5230400af6bcc4a0 100644
--- a/interface/web/sites/lib/lang/it_web_backup_list.lng
+++ b/interface/web/sites/lib/lang/it_web_backup_list.lng
@@ -30,6 +30,7 @@ $wb['manual_backup_title_txt'] = 'Manual backup';
$wb['make_backup_web_txt'] = 'Make backup of web files';
$wb['make_backup_database_txt'] = 'Make backup of databases';
$wb['make_backup_confirm_txt'] = 'You are about to start a manual backup process. Manual backups count towards the total number of allowed backup copies: therefore if the limit will be exceeded, then oldest backups may be deleted automatically. Proceed?';
+$wb['final_size_txt'] = 'Final download size may vary depending on selected compression format.';
$wb['yes_txt'] = 'Yes';
$wb['no_txt'] = 'No';
$wb['backup_is_encrypted_txt'] = 'Encrypted';
diff --git a/interface/web/sites/lib/lang/ja_web_backup_list.lng b/interface/web/sites/lib/lang/ja_web_backup_list.lng
index 8f0d3a7469f807d706676c8581e3ea704584baec..0f19d9339ed75bcaaf730ee4b62bacc1cc70e4f9 100644
--- a/interface/web/sites/lib/lang/ja_web_backup_list.lng
+++ b/interface/web/sites/lib/lang/ja_web_backup_list.lng
@@ -30,6 +30,7 @@ $wb['manual_backup_title_txt'] = 'Manual backup';
$wb['make_backup_web_txt'] = 'Make backup of web files';
$wb['make_backup_database_txt'] = 'Make backup of databases';
$wb['make_backup_confirm_txt'] = 'You are about to start a manual backup process. Manual backups count towards the total number of allowed backup copies: therefore if the limit will be exceeded, then oldest backups may be deleted automatically. Proceed?';
+$wb['final_size_txt'] = 'Final download size may vary depending on selected compression format.';
$wb['yes_txt'] = 'Yes';
$wb['no_txt'] = 'No';
$wb['backup_is_encrypted_txt'] = 'Encrypted';
diff --git a/interface/web/sites/lib/lang/nl_web_backup_list.lng b/interface/web/sites/lib/lang/nl_web_backup_list.lng
index 8f0d3a7469f807d706676c8581e3ea704584baec..0f19d9339ed75bcaaf730ee4b62bacc1cc70e4f9 100644
--- a/interface/web/sites/lib/lang/nl_web_backup_list.lng
+++ b/interface/web/sites/lib/lang/nl_web_backup_list.lng
@@ -30,6 +30,7 @@ $wb['manual_backup_title_txt'] = 'Manual backup';
$wb['make_backup_web_txt'] = 'Make backup of web files';
$wb['make_backup_database_txt'] = 'Make backup of databases';
$wb['make_backup_confirm_txt'] = 'You are about to start a manual backup process. Manual backups count towards the total number of allowed backup copies: therefore if the limit will be exceeded, then oldest backups may be deleted automatically. Proceed?';
+$wb['final_size_txt'] = 'Final download size may vary depending on selected compression format.';
$wb['yes_txt'] = 'Yes';
$wb['no_txt'] = 'No';
$wb['backup_is_encrypted_txt'] = 'Encrypted';
diff --git a/interface/web/sites/lib/lang/pl_web_backup_list.lng b/interface/web/sites/lib/lang/pl_web_backup_list.lng
index dfdd53c25f43273ac9717e40ed16f8d91183578d..6185db50a341636a79124757d1171cd4317b11f6 100644
--- a/interface/web/sites/lib/lang/pl_web_backup_list.lng
+++ b/interface/web/sites/lib/lang/pl_web_backup_list.lng
@@ -30,6 +30,7 @@ $wb['manual_backup_title_txt'] = 'Manual backup';
$wb['make_backup_web_txt'] = 'Make backup of web files';
$wb['make_backup_database_txt'] = 'Make backup of databases';
$wb['make_backup_confirm_txt'] = 'You are about to start a manual backup process. Manual backups count towards the total number of allowed backup copies: therefore if the limit will be exceeded, then oldest backups may be deleted automatically. Proceed?';
+$wb['final_size_txt'] = 'Final download size may vary depending on selected compression format.';
$wb['yes_txt'] = 'Yes';
$wb['no_txt'] = 'No';
$wb['backup_is_encrypted_txt'] = 'Encrypted';
diff --git a/interface/web/sites/lib/lang/pt_web_backup_list.lng b/interface/web/sites/lib/lang/pt_web_backup_list.lng
index 8f0d3a7469f807d706676c8581e3ea704584baec..0f19d9339ed75bcaaf730ee4b62bacc1cc70e4f9 100644
--- a/interface/web/sites/lib/lang/pt_web_backup_list.lng
+++ b/interface/web/sites/lib/lang/pt_web_backup_list.lng
@@ -30,6 +30,7 @@ $wb['manual_backup_title_txt'] = 'Manual backup';
$wb['make_backup_web_txt'] = 'Make backup of web files';
$wb['make_backup_database_txt'] = 'Make backup of databases';
$wb['make_backup_confirm_txt'] = 'You are about to start a manual backup process. Manual backups count towards the total number of allowed backup copies: therefore if the limit will be exceeded, then oldest backups may be deleted automatically. Proceed?';
+$wb['final_size_txt'] = 'Final download size may vary depending on selected compression format.';
$wb['yes_txt'] = 'Yes';
$wb['no_txt'] = 'No';
$wb['backup_is_encrypted_txt'] = 'Encrypted';
diff --git a/interface/web/sites/lib/lang/ro_web_backup_list.lng b/interface/web/sites/lib/lang/ro_web_backup_list.lng
index 8f0d3a7469f807d706676c8581e3ea704584baec..0f19d9339ed75bcaaf730ee4b62bacc1cc70e4f9 100644
--- a/interface/web/sites/lib/lang/ro_web_backup_list.lng
+++ b/interface/web/sites/lib/lang/ro_web_backup_list.lng
@@ -30,6 +30,7 @@ $wb['manual_backup_title_txt'] = 'Manual backup';
$wb['make_backup_web_txt'] = 'Make backup of web files';
$wb['make_backup_database_txt'] = 'Make backup of databases';
$wb['make_backup_confirm_txt'] = 'You are about to start a manual backup process. Manual backups count towards the total number of allowed backup copies: therefore if the limit will be exceeded, then oldest backups may be deleted automatically. Proceed?';
+$wb['final_size_txt'] = 'Final download size may vary depending on selected compression format.';
$wb['yes_txt'] = 'Yes';
$wb['no_txt'] = 'No';
$wb['backup_is_encrypted_txt'] = 'Encrypted';
diff --git a/interface/web/sites/lib/lang/ru_web_backup_list.lng b/interface/web/sites/lib/lang/ru_web_backup_list.lng
index 2a92f2761b1d05f012f5ca78edb485bef4877eaa..3569ae7c09f816a78b24b88ab87140697656f53a 100644
--- a/interface/web/sites/lib/lang/ru_web_backup_list.lng
+++ b/interface/web/sites/lib/lang/ru_web_backup_list.lng
@@ -30,6 +30,7 @@ $wb['manual_backup_title_txt'] = 'Manual backup';
$wb['make_backup_web_txt'] = 'Make backup of web files';
$wb['make_backup_database_txt'] = 'Make backup of databases';
$wb['make_backup_confirm_txt'] = 'You are about to start a manual backup process. Manual backups count towards the total number of allowed backup copies: therefore if the limit will be exceeded, then oldest backups may be deleted automatically. Proceed?';
+$wb['final_size_txt'] = 'Final download size may vary depending on selected compression format.';
$wb['yes_txt'] = 'Yes';
$wb['no_txt'] = 'No';
$wb['backup_is_encrypted_txt'] = 'Encrypted';
diff --git a/interface/web/sites/lib/lang/se_web_backup_list.lng b/interface/web/sites/lib/lang/se_web_backup_list.lng
index 8e0167aa136ab0ed482b4864a9b3167d69cea6f5..3f210ecc4298fe1867c926d9c29301abbc48a158 100644
--- a/interface/web/sites/lib/lang/se_web_backup_list.lng
+++ b/interface/web/sites/lib/lang/se_web_backup_list.lng
@@ -30,6 +30,7 @@ $wb['manual_backup_title_txt'] = 'Manual backup';
$wb['make_backup_web_txt'] = 'Make backup of web files';
$wb['make_backup_database_txt'] = 'Make backup of databases';
$wb['make_backup_confirm_txt'] = 'You are about to start a manual backup process. Manual backups count towards the total number of allowed backup copies: therefore if the limit will be exceeded, then oldest backups may be deleted automatically. Proceed?';
+$wb['final_size_txt'] = 'Final download size may vary depending on selected compression format.';
$wb['yes_txt'] = 'Yes';
$wb['no_txt'] = 'No';
$wb['backup_is_encrypted_txt'] = 'Encrypted';
diff --git a/interface/web/sites/lib/lang/sk_web_backup_list.lng b/interface/web/sites/lib/lang/sk_web_backup_list.lng
index 8f0d3a7469f807d706676c8581e3ea704584baec..0f19d9339ed75bcaaf730ee4b62bacc1cc70e4f9 100644
--- a/interface/web/sites/lib/lang/sk_web_backup_list.lng
+++ b/interface/web/sites/lib/lang/sk_web_backup_list.lng
@@ -30,6 +30,7 @@ $wb['manual_backup_title_txt'] = 'Manual backup';
$wb['make_backup_web_txt'] = 'Make backup of web files';
$wb['make_backup_database_txt'] = 'Make backup of databases';
$wb['make_backup_confirm_txt'] = 'You are about to start a manual backup process. Manual backups count towards the total number of allowed backup copies: therefore if the limit will be exceeded, then oldest backups may be deleted automatically. Proceed?';
+$wb['final_size_txt'] = 'Final download size may vary depending on selected compression format.';
$wb['yes_txt'] = 'Yes';
$wb['no_txt'] = 'No';
$wb['backup_is_encrypted_txt'] = 'Encrypted';
diff --git a/interface/web/sites/lib/lang/tr_web_backup_list.lng b/interface/web/sites/lib/lang/tr_web_backup_list.lng
index 51bca34f2e40f292a55f5989de3b41b24c71475c..c61a5ab0180588c2c6320eb7ed3e655c894791fd 100644
--- a/interface/web/sites/lib/lang/tr_web_backup_list.lng
+++ b/interface/web/sites/lib/lang/tr_web_backup_list.lng
@@ -30,6 +30,7 @@ $wb['manual_backup_title_txt'] = 'Manual backup';
$wb['make_backup_web_txt'] = 'Make backup of web files';
$wb['make_backup_database_txt'] = 'Make backup of databases';
$wb['make_backup_confirm_txt'] = 'You are about to start a manual backup process. Manual backups count towards the total number of allowed backup copies: therefore if the limit will be exceeded, then oldest backups may be deleted automatically. Proceed?';
+$wb['final_size_txt'] = 'Final download size may vary depending on selected compression format.';
$wb['yes_txt'] = 'Yes';
$wb['no_txt'] = 'No';
$wb['backup_is_encrypted_txt'] = 'Encrypted';
diff --git a/server/lib/classes/backup.inc.php b/server/lib/classes/backup.inc.php
index 3cdf17d1fc8fe9eacacd440257589507f65b1475..2b6d21e57e3ad5501d69dc1ad83f55fdf2693c1b 100644
--- a/server/lib/classes/backup.inc.php
+++ b/server/lib/classes/backup.inc.php
@@ -32,6 +32,7 @@ EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* Class backup
* All code that makes actual backup and restore of web files and database is here.
* @author Ramil Valitov
+ * @author Jorge Muñoz (Repository addition)
* @see backup::run_backup() to run a single backup
* @see backup::run_all_backups() to run all backups
* @see backup::restoreBackupDatabase() to restore a database
@@ -93,6 +94,16 @@ class backup
}
return null;
}
+ /**
+ * Checks whatever a backup mode is for a repository
+ * @param $mode Backup mode
+ * @return bool
+ * @author Jorge Muñoz
+ */
+ protected static function backupModeIsRepos($mode)
+ {
+ return 'borg' === $mode;
+ }
/**
* Sets file ownership to $web_user for all files and folders except log, ssl and web/stats
@@ -148,6 +159,7 @@ class backup
* @return bool true if succeeded
* @see backup_plugin::mount_backup_dir()
* @author Ramil Valitov
+ * @author Jorge Muñoz
*/
public static function restoreBackupDatabase($backup_format, $password, $backup_dir, $filename, $backup_mode, $backup_type)
{
@@ -155,60 +167,116 @@ class backup
//* Load sql dump into db
include 'lib/mysql_clientdb.conf';
+ if (self::backupModeIsRepos($backup_mode)) {
- if (empty($backup_format)) {
- $backup_format = self::getDefaultBackupFormat($backup_mode, $backup_type);
- }
- $extension = self::getBackupDbExtension($backup_format);
- if (!empty($extension)) {
- //Replace dots for preg_match search
- $extension = str_replace('.', '\.', $extension);
- }
- $success = false;
- $full_filename = $backup_dir . '/' . $filename;
+ $backup_archive = $filename;
+
+ preg_match('@^(manual-)?db_(?P.+)_\d{4}-\d{2}-\d{2}_\d{2}-\d{2}$@', $backup_archive, $matches);
+ if (isset($matches['db']) && ! empty($matches['db'])) {
+ $db_name = $matches['db'];
+ $backup_repos_folder = self::getReposFolder($backup_mode, 'mysql', '_' . $db_name);
+ $backup_repos_path = $backup_dir . '/' . $backup_repos_folder;
+ $full_archive_path = $backup_repos_path . '::' . $backup_archive;
+
+ $app->log('Restoring MySQL backup from archive ' . $backup_archive . ', backup mode "' . $backup_mode . '"', LOGLEVEL_DEBUG);
+
+ $archives = self::getReposArchives($backup_mode, $backup_repos_path, $password);
+ } else {
+ $app->log('Failed to detect database name during restore of ' . $backup_archive, LOGLEVEL_ERROR);
+ $db_name = null;
+ $archives = null;
+ }
+ if (is_array($archives)) {
+ if (in_array($backup_archive, $archives)) {
+ switch ($backup_mode) {
+ case "borg":
+ $command = self::getBorgCommand('borg extract --nobsdflags', $password);
+ $command .= " --stdout ? stdin | mysql -h ? -u ? -p? ?";
+ break;
+ }
+ } else {
+ $app->log('Failed to process MySQL backup ' . $full_archive_path . ' because it does not exist', LOGLEVEL_ERROR);
+ $command = null;
+ }
+ }
+ if (!empty($command)) {
+ /** @var string $clientdb_host */
+ /** @var string $clientdb_user */
+ /** @var string $clientdb_password */
+ $app->system->exec_safe($command, $full_archive_path, $clientdb_host, $clientdb_user, $clientdb_password, $db_name);
+ $retval = $app->system->last_exec_retcode();
+ if ($retval == 0) {
+ $app->log('Restored database backup ' . $full_archive_path, LOGLEVEL_DEBUG);
+ $success = true;
+ } else {
+ $app->log('Failed to restore database backup ' . $full_archive_path . ', exit code ' . $retval, LOGLEVEL_ERROR);
+ }
+ }
+ } else {
+ if (empty($backup_format)) {
+ $backup_format = self::getDefaultBackupFormat($backup_mode, $backup_type);
+ }
+ $extension = self::getBackupDbExtension($backup_format);
+ if (!empty($extension)) {
+ //Replace dots for preg_match search
+ $extension = str_replace('.', '\.', $extension);
+ }
+ $success = false;
+ $full_filename = $backup_dir . '/' . $filename;
- $app->log('Restoring MySQL backup ' . $full_filename . ', backup format "' . $backup_format . '", backup mode "' . $backup_mode . '"', LOGLEVEL_DEBUG);
+ $app->log('Restoring MySQL backup ' . $full_filename . ', backup format "' . $backup_format . '", backup mode "' . $backup_mode . '"', LOGLEVEL_DEBUG);
- if (file_exists($full_filename) && !empty($extension)) {
preg_match('@^(manual-)?db_(?P.+)_\d{4}-\d{2}-\d{2}_\d{2}-\d{2}' . $extension . '$@', $filename, $matches);
if (!isset($matches['db']) || empty($matches['db'])) {
$app->log('Failed to detect database name during restore of ' . $full_filename, LOGLEVEL_ERROR);
- return false;
+ $db_name = null;
+ } else {
+ $db_name = $matches['db'];
}
- $db_name = $matches['db'];
- switch ($backup_format) {
- case "gzip":
- $command = "gunzip --stdout ? | mysql -h ? -u ? -p? ?";
- break;
- case "zip":
- case "zip_bzip2":
- $command = "unzip -qq -p -P " . escapeshellarg($password) . " ? | mysql -h ? -u ? -p? ?";
- break;
- case "bzip2":
- $command = "bunzip2 -q -c ? | mysql -h ? -u ? -p? ?";
- break;
- case "xz":
- $command = "unxz -q -q -c ? | mysql -h ? -u ? -p? ?";
- break;
- case "rar":
+
+ if ( ! empty($db_name)) {
+ $file_check = file_exists($full_filename) && !empty($extension);
+ if ( ! $file_check) {
+ $app->log('Archive test failed for ' . $full_filename, LOGLEVEL_WARN);
+ }
+ } else {
+ $file_check = false;
+ }
+ if ($file_check) {
+ switch ($backup_format) {
+ case "gzip":
+ $command = "gunzip --stdout ? | mysql -h ? -u ? -p? ?";
+ break;
+ case "zip":
+ case "zip_bzip2":
+ $command = "unzip -qq -p -P " . escapeshellarg($password) . " ? | mysql -h ? -u ? -p? ?";
+ break;
+ case "bzip2":
+ $command = "bunzip2 -q -c ? | mysql -h ? -u ? -p? ?";
+ break;
+ case "xz":
+ $command = "unxz -q -q -c ? | mysql -h ? -u ? -p? ?";
+ break;
+ case "rar":
+ //First, test that the archive is correct and we have a correct password
+ $options = self::getUnrarOptions($password);
+ $app->system->exec_safe("rar t " . $options . " ?", $full_filename);
+ if ($app->system->last_exec_retcode() == 0) {
+ $app->log('Archive test passed for ' . $full_filename, LOGLEVEL_DEBUG);
+ $command = "rar x " . $options. " ? | mysql -h ? -u ? -p? ?";
+ }
+ break;
+ }
+ if (strpos($backup_format, "7z_") === 0) {
+ $options = self::get7zDecompressOptions($password);
//First, test that the archive is correct and we have a correct password
- $options = self::getUnrarOptions($password);
- $app->system->exec_safe("rar t " . $options . " ?", $full_filename);
+ $app->system->exec_safe("7z t " . $options . " ?", $full_filename);
if ($app->system->last_exec_retcode() == 0) {
$app->log('Archive test passed for ' . $full_filename, LOGLEVEL_DEBUG);
- $command = "rar x " . $options. " ? | mysql -h ? -u ? -p? ?";
- }
- break;
- }
- if (strpos($backup_format, "7z_") === 0) {
- $options = self::get7zDecompressOptions($password);
- //First, test that the archive is correct and we have a correct password
- $app->system->exec_safe("7z t " . $options . " ?", $full_filename);
- if ($app->system->last_exec_retcode() == 0) {
- $app->log('Archive test passed for ' . $full_filename, LOGLEVEL_DEBUG);
- $command = "7z x " . $options . " -so ? | mysql -h ? -u ? -p? ?";
- } else
- $command = null;
+ $command = "7z x " . $options . " -so ? | mysql -h ? -u ? -p? ?";
+ } else
+ $command = null;
+ }
}
if (!empty($command)) {
/** @var string $clientdb_host */
@@ -220,13 +288,9 @@ class backup
$app->log('Restored MySQL backup ' . $full_filename, LOGLEVEL_DEBUG);
$success = true;
} else {
- $app->log('Failed to restore web backup ' . $full_filename . ', exit code ' . $retval, LOGLEVEL_ERROR);
+ $app->log('Failed to restore MySQL backup ' . $full_filename . ', exit code ' . $retval, LOGLEVEL_ERROR);
}
- } else {
- $app->log('Archive test failed for ' . $full_filename, LOGLEVEL_DEBUG);
}
- } else {
- $app->log('Failed to process MySQL backup ' . $full_filename, LOGLEVEL_ERROR);
}
unset($clientdb_host);
unset($clientdb_user);
@@ -250,118 +314,337 @@ class backup
* @return bool true if succeed
* @see backup_plugin::mount_backup_dir()
* @author Ramil Valitov
+ * @author Jorge Muñoz
*/
public static function restoreBackupWebFiles($backup_format, $password, $backup_dir, $filename, $backup_mode, $backup_type, $web_root, $web_user, $web_group)
{
global $app;
- if (empty($backup_format)) {
- $backup_format = self::getDefaultBackupFormat($backup_mode, $backup_type);
- }
- $full_filename = $backup_dir . '/' . $filename;
$result = false;
- $app->log('Restoring web backup ' . $full_filename . ', backup format "' . $backup_format . '", backup mode "' . $backup_mode . '"', LOGLEVEL_DEBUG);
+ $app->system->web_folder_protection($web_root, false);
+ if (self::backupModeIsRepos($backup_mode)) {
+ $backup_archive = $filename;
+ $backup_repos_folder = self::getReposFolder($backup_mode, 'web');
+ $backup_repos_path = $backup_dir . '/' . $backup_repos_folder;
+ $full_archive_path = $backup_repos_path . '::' . $backup_archive;
+
+ $app->log('Restoring web backup archive ' . $full_archive_path . ', backup mode "' . $backup_mode . '"', LOGLEVEL_DEBUG);
+
+ $archives = self::getReposArchives($backup_mode, $backup_repos_path, $password);
+ if (is_array($archives) && in_array($backup_archive, $archives)) {
+ $retval = 0;
+ switch ($backup_mode) {
+ case "borg":
+ $command = 'cd ? && borg extract --nobsdflags ?';
+ $app->system->exec_safe($command, $web_root, $full_archive_path);
+ $retval = $app->system->last_exec_retcode();
+ $success = ($retval == 0 || $retval == 1);
+ break;
+ }
+ if ($success) {
+ $app->log('Restored web backup ' . $full_archive_path, LOGLEVEL_DEBUG);
+ $result = true;
+ } else {
+ $app->log('Failed to restore web backup ' . $full_archive_path . ', exit code ' . $retval, LOGLEVEL_ERROR);
+ }
+ } else {
+ $app->log('Web backup archive does not exist ' . $full_archive_path, LOGLEVEL_ERROR);
+ }
- if (!empty($backup_format)) {
- $app->system->web_folder_protection($web_root, false);
- if ($backup_mode == 'userzip' || $backup_mode == 'rootgz') {
- $user_mode = $backup_mode == 'userzip';
- $filename = $user_mode ? ($web_root . '/backup/' . $filename) : $full_filename;
+ } elseif ($backup_mode == 'userzip' || $backup_mode == 'rootgz') {
- if (file_exists($full_filename) && $web_root != '' && $web_root != '/' && !stristr($full_filename, '..') && !stristr($full_filename, 'etc')) {
- if ($user_mode) {
- if (file_exists($filename)) rename($filename, $filename . '.bak');
- copy($full_filename, $filename);
- chgrp($filename, $web_group);
- }
- $user_prefix_cmd = $user_mode ? 'sudo -u ' . escapeshellarg($web_user) : '';
- $success = false;
- $retval = 0;
- switch ($backup_format) {
- case "tar_gzip":
- case "tar_bzip2":
- case "tar_xz":
- $command = $user_prefix_cmd . ' tar xf ? --directory ?';
- $app->system->exec_safe($command, $filename, $web_root);
- $retval = $app->system->last_exec_retcode();
- $success = ($retval == 0 || $retval == 2);
- break;
- case "zip":
- case "zip_bzip2":
- $command = $user_prefix_cmd . ' unzip -qq -P ' . escapeshellarg($password) . ' -o ? -d ? 2> /dev/null';
- $app->system->exec_safe($command, $filename, $web_root);
- $retval = $app->system->last_exec_retcode();
- /*
- * Exit code 50 can happen when zip fails to overwrite files that do not
- * belong to selected user, so we can consider this situation as success
- * with warnings.
- */
- $success = ($retval == 0 || $retval == 50);
- if ($success) {
- self::restoreFileOwnership($web_root, $web_user, $web_group);
- }
- break;
- case 'rar':
- $options = self::getUnRarOptions($password);
- //First, test that the archive is correct and we have a correct password
- $command = $user_prefix_cmd . " rar t " . $options . " ? ?";
- //Rar requires trailing slash
- $app->system->exec_safe($command, $filename, $web_root . '/');
- $success = ($app->system->last_exec_retcode() == 0);
- if ($success) {
- //All good, now we can extract
- $app->log('Archive test passed for ' . $full_filename, LOGLEVEL_DEBUG);
- $command = $user_prefix_cmd . " rar x " . $options . " ? ?";
- //Rar requires trailing slash
- $app->system->exec_safe($command, $filename, $web_root . '/');
- $retval = $app->system->last_exec_retcode();
- //Exit code 9 can happen when we have file permission errors, in this case some
- //files will be skipped during extraction.
- $success = ($retval == 0 || $retval == 1 || $retval == 9);
- } else {
- $app->log('Archive test failed for ' . $full_filename, LOGLEVEL_DEBUG);
- }
- break;
- }
- if (strpos($backup_format, "tar_7z_") === 0) {
- $options = self::get7zDecompressOptions($password);
+ if (empty($backup_format) || $backup_format == 'default') {
+ $backup_format = self::getDefaultBackupFormat($backup_mode, $backup_type);
+ }
+ $full_filename = $backup_dir . '/' . $filename;
+
+ $app->log('Restoring web backup ' . $full_filename . ', backup format "' . $backup_format . '", backup mode "' . $backup_mode . '"', LOGLEVEL_DEBUG);
+
+ $user_mode = $backup_mode == 'userzip';
+ $filename = $user_mode ? ($web_root . '/backup/' . $filename) : $full_filename;
+
+ if (file_exists($full_filename) && $web_root != '' && $web_root != '/' && !stristr($full_filename, '..') && !stristr($full_filename, 'etc')) {
+ if ($user_mode) {
+ if (file_exists($filename)) rename($filename, $filename . '.bak');
+ copy($full_filename, $filename);
+ chgrp($filename, $web_group);
+ }
+ $user_prefix_cmd = $user_mode ? 'sudo -u ' . escapeshellarg($web_user) : '';
+ $success = false;
+ $retval = 0;
+ switch ($backup_format) {
+ case "tar_gzip":
+ case "tar_bzip2":
+ case "tar_xz":
+ $command = $user_prefix_cmd . ' tar xf ? --directory ?';
+ $app->system->exec_safe($command, $filename, $web_root);
+ $retval = $app->system->last_exec_retcode();
+ $success = ($retval == 0 || $retval == 2);
+ break;
+ case "zip":
+ case "zip_bzip2":
+ $command = $user_prefix_cmd . ' unzip -qq -P ' . escapeshellarg($password) . ' -o ? -d ? 2> /dev/null';
+ $app->system->exec_safe($command, $filename, $web_root);
+ $retval = $app->system->last_exec_retcode();
+ /*
+ * Exit code 50 can happen when zip fails to overwrite files that do not
+ * belong to selected user, so we can consider this situation as success
+ * with warnings.
+ */
+ $success = ($retval == 0 || $retval == 50);
+ if ($success) {
+ self::restoreFileOwnership($web_root, $web_user, $web_group);
+ }
+ break;
+ case 'rar':
+ $options = self::getUnRarOptions($password);
//First, test that the archive is correct and we have a correct password
- $command = $user_prefix_cmd . " 7z t " . $options . " ?";
- $app->system->exec_safe($command, $filename);
+ $command = $user_prefix_cmd . " rar t " . $options . " ? ?";
+ //Rar requires trailing slash
+ $app->system->exec_safe($command, $filename, $web_root . '/');
$success = ($app->system->last_exec_retcode() == 0);
if ($success) {
//All good, now we can extract
$app->log('Archive test passed for ' . $full_filename, LOGLEVEL_DEBUG);
- $command = $user_prefix_cmd . " 7z x " . $options . " -so ? | tar xf - --directory ?";
- $app->system->exec_safe($command, $filename, $web_root);
+ $command = $user_prefix_cmd . " rar x " . $options . " ? ?";
+ //Rar requires trailing slash
+ $app->system->exec_safe($command, $filename, $web_root . '/');
$retval = $app->system->last_exec_retcode();
- $success = ($retval == 0 || $retval == 2);
+ //Exit code 9 can happen when we have file permission errors, in this case some
+ //files will be skipped during extraction.
+ $success = ($retval == 0 || $retval == 1 || $retval == 9);
} else {
$app->log('Archive test failed for ' . $full_filename, LOGLEVEL_DEBUG);
}
- }
- if ($user_mode) {
- unlink($filename);
- if (file_exists($filename . '.bak')) rename($filename . '.bak', $filename);
- }
+ break;
+ }
+ if (strpos($backup_format, "tar_7z_") === 0) {
+ $options = self::get7zDecompressOptions($password);
+ //First, test that the archive is correct and we have a correct password
+ $command = $user_prefix_cmd . " 7z t " . $options . " ?";
+ $app->system->exec_safe($command, $filename);
+ $success = ($app->system->last_exec_retcode() == 0);
if ($success) {
- $app->log('Restored web backup ' . $full_filename, LOGLEVEL_DEBUG);
- $result = true;
+ //All good, now we can extract
+ $app->log('Archive test passed for ' . $full_filename, LOGLEVEL_DEBUG);
+ $command = $user_prefix_cmd . " 7z x " . $options . " -so ? | tar xf - --directory ?";
+ $app->system->exec_safe($command, $filename, $web_root);
+ $retval = $app->system->last_exec_retcode();
+ $success = ($retval == 0 || $retval == 2);
} else {
- $app->log('Failed to restore web backup ' . $full_filename . ', exit code ' . $retval, LOGLEVEL_ERROR);
+ $app->log('Archive test failed for ' . $full_filename, LOGLEVEL_DEBUG);
}
}
- } else {
- $app->log('Failed to restore web backup ' . $full_filename . ', backup mode "' . $backup_mode . '" not recognized.', LOGLEVEL_DEBUG);
+ if ($user_mode) {
+ unlink($filename);
+ if (file_exists($filename . '.bak')) rename($filename . '.bak', $filename);
+ }
+ if ($success) {
+ $app->log('Restored web backup ' . $full_filename, LOGLEVEL_DEBUG);
+ $result = true;
+ } else {
+ $app->log('Failed to restore web backup ' . $full_filename . ', exit code ' . $retval, LOGLEVEL_ERROR);
+ }
}
- $app->system->web_folder_protection($web_root, true);
} else {
- $app->log('Failed to restore web backup ' . $full_filename . ', backup format not recognized.', LOGLEVEL_DEBUG);
+ $app->log('Failed to restore web backup ' . $full_filename . ', backup mode "' . $backup_mode . '" not recognized.', LOGLEVEL_DEBUG);
}
+ $app->system->web_folder_protection($web_root, true);
return $result;
}
+ /**
+ * Deletes backup copy
+ * @param string $backup_format
+ * @param string $backup_password
+ * @param string $backup_dir
+ * @param string $filename
+ * @param string $backup_mode
+ * @param string $backup_type
+ * @param int $domain_id
+ * @param bool true on success
+ * @author Jorge Muñoz
+ */
+ public static function deleteBackup($backup_format, $backup_password, $backup_dir, $filename, $backup_mode, $backup_type, $domain_id) {
+ global $app, $conf;
+ $server_id = $conf['server_id'];
+ $success = false;
+
+ if (empty($backup_format) || $backup_format == 'default') {
+ $backup_format = self::getDefaultBackupFormat($backup_mode, $backup_type);
+ }
+ if(self::backupModeIsRepos($backup_mode)) {
+ $repos_password = '';
+ $backup_archive = $filename;
+ $backup_repos_folder = self::getBackupReposFolder($backup_mode, $backup_type);
+ if ($backup_type != 'web') {
+ preg_match('@^(manual-)?db_(?P.+)_\d{4}-\d{2}-\d{2}_\d{2}-\d{2}$@', $backup_archive, $matches);
+ if (!isset($matches['db']) || empty($matches['db'])) {
+ $app->log('Failed to detect database name during delete of ' . $backup_archive, LOGLEVEL_ERROR);
+ return false;
+ }
+ $db_name = $matches['db'];
+ $backup_repos_folder .= '_' . $db_name;
+ }
+ $backup_repos_path = $backup_dir . '/' . $backup_repos_folder;
+ $archives = self::getReposArchives($backup_mode, $backup_repos_path, $repos_password);
+ if (is_array($archives) && in_array($backup_archive, $archives)) {
+ $success = self::deleteArchive($backup_mode, $backup_repos_path, $backup_archive, $repos_password);
+ } else {
+ $success = true;
+ }
+ } else {
+ if(file_exists($backup_dir.'/'.$filename) && !stristr($backup_dir.'/'.$filename, '..') && !stristr($backup_dir.'/'.$filename, 'etc')) {
+ $success = unlink($backup_dir.'/'.$filename);
+ } else {
+ $success = true;
+ }
+ }
+ if ($success) {
+ $sql = "DELETE FROM web_backup WHERE server_id = ? AND parent_domain_id = ? AND filename = ?";
+ $app->db->query($sql, $server_id, $domain_id, $filename);
+ if($app->db->dbHost != $app->dbmaster->dbHost)
+ $app->dbmaster->query($sql, $server_id, $domain_id, $filename);
+ $app->log($sql . ' - ' . json_encode([$server_id, $domain_id, $filename]), LOGLEVEL_DEBUG);
+ }
+ return $success;
+ }
+ /**
+ * Downloads the backup copy
+ * @param string $backup_format
+ * @param string $password
+ * @param string $backup_dir
+ * @param string $filename
+ * @param string $backup_mode
+ * @param string $backup_type
+ * @param array $domain web_domain record
+ * @param bool true on success
+ * @author Jorge Muñoz
+ */
+ public static function downloadBackup($backup_format, $password, $backup_dir, $filename, $backup_mode, $backup_type, $domain)
+ {
+ global $app;
+
+ $success = false;
+
+ if (self::backupModeIsRepos($backup_mode)) {
+ $backup_archive = $filename;
+ //When stored in repos, we first get target backup format to generate final download file
+ $repos_password = '';
+ $server_id = $domain['server_id'];
+ $password = $domain['backup_encrypt'] == 'y' ? trim($domain['backup_password']) : '';
+ $server_config = $app->getconf->get_server_config($server_id, 'server');
+ $backup_tmp = trim($server_config['backup_tmp']);
+
+ if ($backup_type == 'web') {
+ $backup_format = $domain['backup_format_web'];
+ if (empty($backup_format) || $backup_format == 'default') {
+ $backup_format = self::getDefaultBackupFormat($server_backup_mode, 'web');
+ }
+ $backup_repos_folder = self::getBackupReposFolder($backup_mode, 'web');
+ $extension = self::getBackupWebExtension($backup_format);
+ } else {
+ if (preg_match('@^(manual-)?db_(?P.+)_\d{4}-\d{2}-\d{2}_\d{2}-\d{2}$@', $backup_archive, $matches)) {
+ $db_name = $matches['db'];
+ $backup_format = $domain['backup_format_db'];
+ if (empty($backup_format)) {
+ $backup_format = self::getDefaultBackupFormat($server_backup_mode, $backup_type);
+ }
+ $backup_repos_folder = self::getBackupReposFolder($backup_mode, $backup_type) . '_' . $db_name;
+ $extension = self::getBackupDbExtension($backup_format);
+ } else {
+ $app->log('Failed to detect database name during download of ' . $backup_archive, LOGLEVEL_ERROR);
+ $db_name = null;
+ }
+ }
+ if ( ! empty($extension)) {
+ $filename .= $extension;
+ $backup_repos_path = $backup_dir . '/' . $backup_repos_folder;
+ $full_archive_path = $backup_repos_path . '::' . $backup_archive;
+ $archives = self::getReposArchives($backup_mode, $backup_repos_path, $repos_password);
+ } else {
+ $archives = null;
+ }
+ if (is_array($archives)) {
+ if (in_array($backup_archive, $archives)) {
+ $app->log('Extracting ' . $backup_type . ' backup from repository archive '.$full_archive_path. ' to ' . $domain['document_root'].'/backup/' . $filename, LOGLEVEL_DEBUG);
+ switch ($backup_mode) {
+ case 'borg':
+ if ($backup_type == 'mysql') {
+ if (strpos($extension, '.sql.gz') === 0 || strpos($extension, '.sql.bz2') === 0) {
+ //* .sql.gz and .sql.bz2 don't need a source file, so we can just pipe through the compression command
+ $ccmd = strpos($extension, '.sql.gz') === 0 ? 'gzip' : 'bzip2';
+ $command = self::getBorgCommand('borg extract', $repos_password) . ' --stdout ? stdin | ' . $ccmd . ' -c > ?';
+ $success = $app->system->exec_safe($command, $full_archive_path, $domain['document_root'].'/backup/'.$filename) == 0;
+ } else {
+ $tmp_extract = $backup_tmp . '/' . $backup_archive . '.sql';
+ if (file_exists($tmp_extract)) {
+ unlink($tmp_extract);
+ }
+ $command = self::getBorgCommand('borg extract', $repos_password) . ' --stdout ? stdin > ?';
+ $app->system->exec_safe($command, $full_archive_path, $tmp_extract);
+ }
+ } else {
+ if (strpos($extension, '.tar') === 0 && ($password == '' || strpos($extension, '.tar.7z') !== 0)) {
+ //* .tar.gz, .tar.bz2, etc are supported via borg export-tar, if they don't need encryption
+ $command = self::getBorgCommand('borg export-tar', $repos_password) . ' ? ?';
+ $app->system->exec_safe($command, $full_archive_path, $domain['document_root'].'/backup/'.$filename);
+ $success = $app->system->last_exec_retcode() == 0;
+ } else {
+ $tmp_extract = tempnam($backup_tmp, $backup_archive);
+ unlink($tmp_extract);
+ mkdir($tmp_extract);
+ $command = 'cd ' . $tmp_extract . ' && ' . self::getBorgCommand('borg extract --nobsdflags', $repos_password) . ' ?';
+ $app->system->exec_safe($command, $full_archive_path);
+ if ($app->system->last_exec_retcode() != 0) {
+ $app->log('Extraction of ' . $full_archive_path . ' into ' . $tmp_extract . ' failed.', LOGLEVEL_ERROR);
+ $tmp_extract = null;
+ }
+ }
+ }
+ break;
+ }
+ if ( ! empty($tmp_extract)) {
+ if (is_dir($tmp_extract)) {
+ $web_config = $app->getconf->get_server_config($server_id, 'web');
+ $http_server_user = $web_config['user'];
+ $success = self::runWebCompression($backup_format, [], 'rootgz', $tmp_extract, $domain['document_root'].'/backup/', $filename, $domain['system_user'], $domain['system_group'], $http_server_user, $backup_tmp, $password);
+ } else {
+ self::runDatabaseCompression($backup_format, dirname($tmp_extract), basename($tmp_extract), $filename, $backup_tmp, $password)
+ AND $success = rename(dirname($tmp_extract) . '/' . $filename, $domain['document_root'].'/backup/'. $filename);
+ }
+ if ($success) {
+ $app->system->exec_safe('rm -Rf ?', $tmp_extract);
+ } else {
+ $app->log('Failed to run compression of ' . $tmp_extract . ' into ' . $domain['document_root'].'/backup/' . $filename . ' failed.', LOGLEVEL_ERROR);
+ }
+ }
+ } else {
+ $app->log('Failed to find archive ' . $full_archive_path . ' for download', LOGLEVEL_ERROR);
+ }
+ }
+ if ($success) {
+ $app->log('Download of archive ' . $full_archive_path . ' into ' . $domain['document_root'].'/backup/'.$filename . ' succeeded.', LOGLEVEL_DEBUG);
+ }
+ }
+ //* Copy the backup file to the backup folder of the website
+ elseif(file_exists($backup_dir.'/'.$filename) && file_exists($domain['document_root'].'/backup/') && !stristr($backup_dir.'/'.$filename, '..') && !stristr($backup_dir.'/'.$filename, 'etc')) {
+ $success = copy($backup_dir.'/'.$filename, $domain['document_root'].'/backup/'.$filename);
+ }
+ if (file_exists($domain['document_root'].'/backup/'.$filename)) {
+ chgrp($domain['document_root'].'/backup/'.$filename, $domain['system_group']);
+ chown($domain['document_root'].'/backup/'.$filename, $domain['system_user']);
+ chmod($domain['document_root'].'/backup/'.$filename,0600);
+ $app->log('Ready '.$domain['document_root'].'/backup/'.$filename, LOGLEVEL_DEBUG);
+ return true;
+ } else {
+ $app->log('Failed download of '.$domain['document_root'].'/backup/'.$filename , LOGLEVEL_ERROR);
+ return false;
+ }
+ }
+
+
/**
* Returns a compression method, for example returns bzip2 for tar_7z_bzip2
* @param string $format
@@ -688,6 +971,139 @@ class backup
return $options;
}
+ /**
+ * Get borg command with password appended to the base command
+ * @param $command Base command to add password to
+ * @param $password Password to add
+ * @param $is_new Specify if command is for a new borg repository initialization
+ */
+ protected static function getBorgCommand($command, $password, $is_new = false)
+ {
+ if ($password) {
+ if ($is_new) {
+ return "BORG_NEW_PASSPHRASE='" . escapeshellarg($password) . "' " . $command;
+ }
+ return "BORG_PASSPHRASE='" . escapeshellarg($password) . "' " . $command;
+ }
+ return $command;
+ }
+ /**
+ * Obtains command line options for "borg create" command.
+ * @param string $compression Compression options are validated and fixed if necessary.
+ * See: https://borgbackup.readthedocs.io/en/stable/internals/data-structures.html#compression
+ * @return string
+ * @author Jorge Muñoz
+ */
+ protected static function getBorgCreateOptions($compression)
+ {
+ global $app;
+
+ //* Validate compression
+
+ $C = explode(',', $compression);
+ if (count($C) > 2) {
+ $app->log("Invalid compression option " . $C[2] . " from compression " . $compression . ".", LOGLEVEL_WARN);
+ $compression = $C[0] . ',' . $C[1];
+ $C = [$C[0], $C[1]];
+ }
+ if (count($C) > 1 && ! ctype_digit($C[1])) {
+ $app->log("Invalid compression option " . $C[1] . " from compression " . $compression . ".", LOGLEVEL_WARN);
+ $compression = $C[0];
+ $C = [$C[0]];
+ }
+
+ switch ($C[0]) {
+ case 'none':
+ case 'lz4':
+ if (count($C) > 1) {
+ $app->log("Invalid compression format " . $compression . '. Defaulting to ' . $C[0] . '.', LOGLEVEL_WARN);
+ $compression = $C[0];
+ }
+ break;
+ case 'zstd':
+ //* Check borg version
+ list(,$ver) = explode(' ', exec('borg --version'));
+ if (version_compare($ver, '1.1.4') < 0) {
+ $app->log("Current borg version " . $ver . " does not support compression format " . $compression . '. Defaulting to zlib.', LOGLEVEL_WARN);
+ $compression = 'zlib';
+ } elseif (count($C) > 1 && ($C[1] < 1 || $C[1] > 22)) {
+ $app->log("Invalid compression format " . $compression . '. Defaulting to zstd.', LOGLEVEL_WARN);
+ $compression = 'zstd';
+ }
+ break;
+ case 'zlib':
+ case 'lzma':
+ if (count($C) > 1 && ($C[1] < 0 || $C[1] > 9)) {
+ $app->log("Invalid compression format " . $compression . '. Defaulting to ' . $C[0] . '.', LOGLEVEL_WARN);
+ $compression = $C[0];
+ }
+ break;
+ default:
+ $app->log("Unsupported borg compression format " . $compression . '. Defaulting to zlib.', LOGLEVEL_WARN);
+ $compression = 'zlib';
+ }
+
+ $options = array(
+ /**
+ * -C --compression
+ */
+ '-C ' . $compression,
+ /**
+ * Excludes directories that contain CACHEDIR.TAG
+ */
+ '--exclude-caches',
+ /**
+ * specify the chunker parameters (CHUNK_MIN_EXP, CHUNK_MAX_EXP, HASH_MASK_BITS, HASH_WINDOW_SIZE).
+ * @see https://borgbackup.readthedocs.io/en/stable/internals/data-structures.html#chunker-details
+ * Default: 19,23,21,4095
+ */
+ //'--chunker-params 19,23,21,4095',
+ );
+ $options = implode(" ", $options);
+ return $options;
+ }
+
+ /**
+ * Gets a list of repository archives
+ * @param string $backup_mode
+ * @param string $repos_path absolute path to repository
+ * @param string $password repository password or empty string if none
+ * @param string $list_format Supports either 'short' or 'json'
+ * @return array
+ * @author Jorge Muñoz
+ */
+ protected static function getReposArchives($backup_mode, $repos_path, $password, $list_format = 'short')
+ {
+ global $app;
+ if ( ! is_dir($repos_path)) {
+ $app->log("Unknown path " . var_export($repos_path, TRUE)
+ . ' called from ' . (function() {
+ $dbt = debug_backtrace();
+ return $dbt[1]['file'] . ':' . $dbt[1]['line'];
+ })(), LOGLEVEL_ERROR);
+ return FALSE;
+ }
+ switch ($backup_mode) {
+ case 'borg':
+
+ $command = self::getBorgCommand('borg list', $password);
+
+ if ($list_format == 'json') {
+ $command_opts = '--json';
+ } else {
+ $command_opts = '--short';
+ }
+
+ $app->system->exec_safe($command . ' ' . $command_opts . ' ?', $repos_path);
+
+ if ($app->system->last_exec_retcode() == 0) {
+ return array_map('trim', $app->system->last_exec_out());
+ }
+ break;
+ }
+ return FALSE;
+ }
+
/**
* Clears expired backups.
* The backup directory must be mounted before calling this method.
@@ -699,36 +1115,108 @@ class backup
* @see backup_plugin::backups_garbage_collection() call this method first
* @see backup_plugin::mount_backup_dir()
* @author Ramil Valitov
+ * @author Jorge Muñoz
*/
protected static function clearBackups($server_id, $web_id, $max_backup_copies, $backup_dir, $prefix_list=null)
{
global $app;
- $files = self::get_files($backup_dir, $prefix_list);
- usort($files, function ($a, $b) use ($backup_dir) {
- $time_a = filemtime($backup_dir . '/' . $a);
- $time_b = filemtime($backup_dir . '/' . $b);
- return ($time_a > $time_b) ? -1 : 1;
- });
+ $server_config = $app->getconf->get_server_config($server_id, 'server');
+ $backup_mode = $server_config['backup_mode'];
+ //@todo obtain password from server config
+ $password = NULL;
$db_list = array($app->db);
if ($app->db->dbHost != $app->dbmaster->dbHost)
array_push($db_list, $app->dbmaster);
- //Delete old files that are beyond the limit
- for ($n = $max_backup_copies; $n < sizeof($files); $n++) {
- $filename = $files[$n];
- $full_filename = $backup_dir . '/' . $filename;
- $app->log('Backup file ' . $full_filename . ' is beyond the limit of ' . $max_backup_copies . " copies and will be deleted from disk and database", LOGLEVEL_DEBUG);
- $sql = "DELETE FROM web_backup WHERE server_id = ? AND parent_domain_id = ? AND filename = ?";
- foreach ($db_list as $db) {
- $db->query($sql, $server_id, $web_id, $filename);
+ if ($backup_mode == "userzip" || $backup_mode == "rootgz") {
+ $files = self::get_files($backup_dir, $prefix_list);
+ usort($files, function ($a, $b) use ($backup_dir) {
+ $time_a = filemtime($backup_dir . '/' . $a);
+ $time_b = filemtime($backup_dir . '/' . $b);
+ return ($time_a > $time_b) ? -1 : 1;
+ });
+
+ //Delete old files that are beyond the limit
+ for ($n = $max_backup_copies; $n < sizeof($files); $n++) {
+ $filename = $files[$n];
+ $full_filename = $backup_dir . '/' . $filename;
+ $app->log('Backup file ' . $full_filename . ' is beyond the limit of ' . $max_backup_copies . " copies and will be deleted from disk and database", LOGLEVEL_DEBUG);
+ $sql = "DELETE FROM web_backup WHERE server_id = ? AND parent_domain_id = ? AND filename = ?";
+ foreach ($db_list as $db) {
+ $db->query($sql, $server_id, $web_id, $filename);
+ }
+ @unlink($full_filename);
+ }
+ } elseif (self::backupModeIsRepos($backup_mode)) {
+ $repos_archives = self::getAllArchives($backup_dir, $backup_mode, $password);
+ usort($repos_archives, function ($a, $b) {
+ return ($a['created_at'] > $b['created_at']) ? -1 : 1;
+ });
+ //Delete old files that are beyond the limit
+ for ($n = $max_backup_copies; $n < sizeof($repos_archives); $n++) {
+ $archive = $repos_archives[$n];
+ $app->log('Backup archive ' . $archive['archive'] . ' is beyond the limit of ' . $max_backup_copies . " copies and will be deleted from disk and database", LOGLEVEL_DEBUG);
+ $sql = "DELETE FROM web_backup WHERE server_id = ? AND parent_domain_id = ? AND filename = ?";
+ foreach ($db_list as $db) {
+ $db->query($sql, $server_id, $web_id, $archive['archive']);
+ }
+ $backup_repos_path = $backup_dir . '/' . $archive['repos'];
+ self::deleteArchive($backup_mode, $backup_repos_path, $archive['archive'], $password);
}
- @unlink($full_filename);
}
return true;
}
+ protected static function getAllArchives($backup_dir, $backup_mode, $password)
+ {
+ $d = dir($backup_dir);
+ $archives = [];
+ /**
+ * $archives[] = [
+ * 'repos' => string,
+ * 'archive' => string,
+ * 'created_at' => int,
+ * ];
+ */
+ while (false !== ($entry = $d->read())) {
+ if ('.' === $entry || '..' === $entry) {
+ continue;
+ }
+ switch ($backup_mode) {
+ case 'borg':
+ $repos_path = $backup_dir . '/' . $entry;
+ if (is_dir($repos_path) && strncmp('borg_', $entry, 5) === 0) {
+ $archivesJson = json_decode(implode("", self::getReposArchives($backup_mode, $repos_path, $password, 'json')), TRUE);
+ foreach ($archivesJson['archives'] as $archive) {
+ $archives[] = [
+ 'repos' => $entry,
+ 'archive' => $archive['name'],
+ 'created_at' => strtotime($archive['time']),
+ ];
+ }
+ }
+ break;
+ }
+ }
+ return $archives;
+ }
+
+ protected static function deleteArchive($backup_mode, $backup_repos_path, $backup_archive, $password)
+ {
+ global $app;
+ $app->log("Delete Archive - repos = " . $backup_repos_path . ", archive = " . $backup_archive, LOGLEVEL_DEBUG);
+ switch ($backup_mode) {
+ case 'borg':
+ $app->system->exec_safe('borg delete ?', $backup_repos_path . '::' . $backup_archive);
+ return $app->system->last_exec_retcode() == 0;
+ default:
+ $app->log("Unknown repos type " . $backup_mode, LOGLEVEL_ERROR);
+ }
+ return FALSE;
+ }
+
/**
* Garbage collection: deletes records from database about files that do not exist and deletes untracked files and cleans up backup download directories.
* The backup directory must be mounted before calling this method.
@@ -748,7 +1236,7 @@ class backup
$args_sql_domains_with_backups = array();
$server_config = $app->getconf->get_server_config($server_id, 'server');
$backup_dir = trim($server_config['backup_dir']);
- $sql = "SELECT * FROM web_backup WHERE server_id = ?";
+ $sql = "SELECT * FROM web_backup WHERE server_id = ? AND backup_mode != 'borg'";
$sql_domains = "SELECT domain_id,document_root,system_user,system_group,backup_interval FROM web_domain WHERE server_id = ? AND (type = 'vhost' OR type = 'vhostsubdomain' OR type = 'vhostalias')";
$sql_domains_with_backups = "SELECT domain_id,document_root,system_user,system_group,backup_interval FROM web_domain WHERE domain_id in (SELECT parent_domain_id FROM web_backup WHERE server_id = ?" . ((!empty($backup_type)) ? " AND backup_type = ?" : "") . ") AND (type = 'vhost' OR type = 'vhostsubdomain' OR type = 'vhostalias')";
array_push($args_sql, $server_id);
@@ -772,7 +1260,7 @@ class backup
if ($app->db->dbHost != $app->dbmaster->dbHost)
array_push($db_list, $app->dbmaster);
- // Cleanup web_backup entries for non-existent backup files
+ // Cleanup web_backup entries for non-existent backup files
foreach ($db_list as $db) {
$backups = $app->db->queryAllRecords($sql, true, $args_sql);
foreach ($backups as $backup) {
@@ -785,7 +1273,7 @@ class backup
}
}
- // Cleanup backup files with missing web_backup entries (runs on all servers)
+ // Cleanup backup files with missing web_backup entries (runs on all servers)
$domains = $app->dbmaster->queryAllRecords($sql_domains_with_backups, true, $args_sql_domains_with_backups);
foreach ($domains as $rec) {
$domain_id = $rec['domain_id'];
@@ -795,7 +1283,7 @@ class backup
if (!empty($files)) {
// leave out server_id here, in case backup storage is shared between servers
$sql = "SELECT backup_id, filename FROM web_backup WHERE parent_domain_id = ?";
- $untracked_backup_files = array();
+ $untracked_backup_files = array();
foreach ($db_list as $db) {
$backups = $db->queryAllRecords($sql, $domain_id);
foreach ($backups as $backup) {
@@ -804,8 +1292,8 @@ class backup
}
}
}
- array_unique( $untracked_backup_files );
- foreach ($untracked_backup_files as $f) {
+ array_unique( $untracked_backup_files );
+ foreach ($untracked_backup_files as $f) {
$backup_file = $backup_dir . '/web' . $domain_id . '/' . $f;
$app->log('Backup file ' . $backup_file . ' is not contained in database, deleting this file from disk', LOGLEVEL_DEBUG);
@unlink($backup_file);
@@ -813,7 +1301,7 @@ class backup
}
}
- // This cleanup only runs on web servers
+ // This cleanup only runs on web servers
$domains = $app->db->queryAllRecords($sql_domains, true, $args_sql_domains);
foreach ($domains as $rec) {
$domain_id = $rec['domain_id'];
@@ -856,6 +1344,15 @@ class backup
}
}
+ protected static function getReposFolder($backup_mode, $backup_type, $postfix = '')
+ {
+ switch ($backup_mode) {
+ case 'borg':
+ return 'borg_' . $backup_type . $postfix;
+ }
+ return null;
+ }
+
/**
* Gets list of files in directory
* @param string $directory
@@ -922,6 +1419,48 @@ class backup
return $files;
}
+ /**
+ * Gets list of directories in directory
+ * @param string $directory
+ * @param string[]|null $prefix_list filter files that have one of the prefixes. Use null for default filtering.
+ * @return string[]
+ * @author Ramil Valitov
+ */
+ protected static function get_dirs($directory, $prefix_list = null, $endings_list = null)
+ {
+ $default_prefix_list = array(
+ 'borg',
+ );
+ if (is_null($prefix_list))
+ $prefix_list = $default_prefix_list;
+
+ if (!is_dir($directory)) {
+ return array();
+ }
+
+ $dir_handle = dir($directory);
+ $dirs = array();
+ while (false !== ($entry = $dir_handle->read())) {
+ $full_dirname = $directory . '/' . $entry;
+ if ($entry != '.' && $entry != '..' && is_dir($full_dirname)) {
+ if (!empty($prefix_list)) {
+ $add = false;
+ foreach ($prefix_list as $prefix) {
+ if (substr($entry, 0, strlen($prefix)) == $prefix) {
+ $add = true;
+ break;
+ }
+ }
+ } else
+ $add = true;
+ if ($add)
+ array_push($dirs, $entry);
+ }
+ }
+ $dir_handle->close();
+
+ return $dirs;
+ }
/**
* Generates excludes list for compressors
* @param string[] $backup_excludes
@@ -935,7 +1474,7 @@ class backup
{
$excludes = "";
foreach ($backup_excludes as $ex) {
- # pass through escapeshellarg if not already done
+ # pass through escapeshellarg if not already done
if ( preg_match( "/^'.+'$/", $ex ) ) {
$excludes .= "${arg}${pre}${ex}${post} ";
} else {
@@ -1167,6 +1706,7 @@ class backup
* @param string $backup_job type of backup job: manual or auto
* @return bool true if success
* @author Ramil Valitov
+ * @author Jorge Muñoz
* @see backup_plugin::run_backup() recommeneded to use if you need to make backups
*/
protected static function make_database_backup($web_domain, $backup_job)
@@ -1176,6 +1716,7 @@ class backup
$server_id = intval($web_domain['server_id']);
$domain_id = intval($web_domain['domain_id']);
$server_config = $app->getconf->get_server_config($server_id, 'server');
+ $backup_mode = $server_config['backup_mode'];
$backup_dir = trim($server_config['backup_dir']);
$backup_tmp = trim($server_config['backup_tmp']);
$db_backup_dir = $backup_dir . '/web' . $domain_id;
@@ -1200,67 +1741,118 @@ class backup
unset($tmp);
foreach ($records as $rec) {
- $password = ($web_domain['backup_encrypt'] == 'y') ? trim($web_domain['backup_password']) : '';
- $backup_format_db = $web_domain['backup_format_db'];
- if (empty($backup_format_db)) {
- $backup_format_db = 'gzip';
- }
- $backup_extension_db = self::getBackupDbExtension($backup_format_db);
-
- if (!empty($backup_extension_db)) {
- //* Do the mysql database backup with mysqldump
+ if (self::backupModeIsRepos($backup_mode)) {
+ //@todo get $password from server config
+ $repos_password = '';
+ //@todo get compression from server config
+ $compression = 'zlib';
$db_name = $rec['database_name'];
- $db_file_prefix = 'db_' . $db_name . '_' . date('Y-m-d_H-i');
- $db_backup_file = $db_file_prefix . '.sql';
- $db_compressed_file = ($backup_job == 'manual' ? 'manual-' : '') . $db_file_prefix . $backup_extension_db;
- $command = "mysqldump -h ? -u ? -p? -c --add-drop-table --create-options --quick --max_allowed_packet=512M " . $mysqldump_routines . " --result-file=? ?";
- /** @var string $clientdb_host */
- /** @var string $clientdb_user */
- /** @var string $clientdb_password */
- $app->system->exec_safe($command, $clientdb_host, $clientdb_user, $clientdb_password, $db_backup_dir . '/' . $db_backup_file, $db_name);
- $exit_code = $app->system->last_exec_retcode();
-
- //* Compress the backup
- if ($exit_code == 0) {
- $exit_code = self::runDatabaseCompression($backup_format_db, $db_backup_dir, $db_backup_file, $db_compressed_file, $backup_tmp, $password) ? 0 : 1;
- if ($exit_code !== 0)
- $app->log('Failed to make backup of database ' . $rec['database_name'], LOGLEVEL_ERROR);
+ $db_repos_folder = self::getBackupReposFolder($backup_mode, 'mysql') . '_' . $db_name;
+ $backup_repos_path = $db_backup_dir . '/' . $db_repos_folder;
+ $backup_format_db = '';
+ if (self::prepareRepos($backup_mode, $backup_repos_path, $repos_password)) {
+ $db_backup_archive = ($backup_job == 'manual' ? 'manual-' : '') . 'db_' . $db_name . '_' . date('Y-m-d_H-i');
+ $full_archive_path = $backup_repos_path . '::' . $db_backup_archive;
+ $dump_command = "mysqldump -h ? -u ? -p? -c --add-drop-table --create-options --quick --max_allowed_packet=512M " . $mysqldump_routines . " ?";
+ switch ($backup_mode) {
+ case 'borg':
+ $borg_cmd = self::getBorgCommand('borg create', $repos_password);
+ $borg_options = self::getBorgCreateOptions($compression);
+ $command = $dump_command . ' | ' . $borg_cmd . ' ' . $borg_options . ' ? -';
+ /** @var string $clientdb_host */
+ /** @var string $clientdb_user */
+ /** @var string $clientdb_password */
+ $app->system->exec_safe($command,
+ $clientdb_host, $clientdb_user, $clientdb_password, $db_name, #mysqldump command part
+ $full_archive_path #borg command part
+ );
+ $exit_code = $app->system->last_exec_retcode();
+ break;
+ }
+ if ($exit_code == 0) {
+ $archive_size = self::getReposArchiveSize($backup_mode, $backup_repos_path, $db_backup_archive, $repos_password);
+ if ($archive_size !== false) {
+ //* Insert web backup record in database
+ $sql = "INSERT INTO web_backup (server_id, parent_domain_id, backup_type, backup_mode, backup_format, tstamp, filename, filesize, backup_password) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
+ //* password is for `Encrypted` column informative purposes, on download password is obtained from web_domain settings
+ $password = $repos_password ? '*secret*' : '';
+ $app->db->query($sql, $server_id, $domain_id, 'mysql', $backup_mode, $backup_format_db, time(), $db_backup_archive, $archive_size, $password);
+ if ($app->db->dbHost != $app->dbmaster->dbHost)
+ $app->dbmaster->query($sql, $server_id, $domain_id, 'mysql', $backup_mode, $backup_format_db, time(), $db_backup_archive, $archive_size, $password);
+ $success = true;
+ } else {
+ $app->log('Failed to obtain backup size of ' . $full_archive_path . ' for database ' . $rec['database_name'], LOGLEVEL_ERROR);
+ return false;
+ }
+ } else {
+ rename($backup_repos_path, $new_path = $backup_repos_path . '_failed_' . uniqid());
+ $app->log('Failed to process mysql backup format ' . $backup_format_db . ' for database ' . $rec['database_name'] . ' repos renamed to ' . $new_path, LOGLEVEL_ERROR);
+ }
} else {
- $app->log('Failed to make backup of database ' . $rec['database_name'] . ', because mysqldump failed', LOGLEVEL_ERROR);
+ $app->log('Failed to initialize repository for database ' . $rec['database_name'] . ', folder ' . $backup_repos_path . ', backup mode ' . $backup_mode . '.', LOGLEVEL_ERROR);
+ }
+ } else {
+ $password = ($web_domain['backup_encrypt'] == 'y') ? trim($web_domain['backup_password']) : '';
+ $backup_format_db = $web_domain['backup_format_db'];
+ if (empty($backup_format_db)) {
+ $backup_format_db = 'gzip';
}
+ $backup_extension_db = self::getBackupDbExtension($backup_format_db);
+
+ if (!empty($backup_extension_db)) {
+ //* Do the mysql database backup with mysqldump
+ $db_name = $rec['database_name'];
+ $db_file_prefix = 'db_' . $db_name . '_' . date('Y-m-d_H-i');
+ $db_backup_file = $db_file_prefix . '.sql';
+ $db_compressed_file = ($backup_job == 'manual' ? 'manual-' : '') . $db_file_prefix . $backup_extension_db;
+ $command = "mysqldump -h ? -u ? -p? -c --add-drop-table --create-options --quick --max_allowed_packet=512M " . $mysqldump_routines . " --result-file=? ?";
+ /** @var string $clientdb_host */
+ /** @var string $clientdb_user */
+ /** @var string $clientdb_password */
+ $app->system->exec_safe($command, $clientdb_host, $clientdb_user, $clientdb_password, $db_backup_dir . '/' . $db_backup_file, $db_name);
+ $exit_code = $app->system->last_exec_retcode();
- if ($exit_code == 0) {
- if (is_file($db_backup_dir . '/' . $db_compressed_file)) {
- chmod($db_backup_dir . '/' . $db_compressed_file, 0750);
- chown($db_backup_dir . '/' . $db_compressed_file, fileowner($db_backup_dir));
- chgrp($db_backup_dir . '/' . $db_compressed_file, filegroup($db_backup_dir));
-
- //* Insert web backup record in database
- $file_size = filesize($db_backup_dir . '/' . $db_compressed_file);
- $sql = "INSERT INTO web_backup (server_id, parent_domain_id, backup_type, backup_mode, backup_format, tstamp, filename, filesize, backup_password) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
- //Making compatible with previous versions of ISPConfig:
- $sql_mode = ($backup_format_db == 'gzip') ? 'sqlgz' : ('sql' . $backup_format_db);
- $app->db->query($sql, $server_id, $domain_id, 'mysql', $sql_mode, $backup_format_db, time(), $db_compressed_file, $file_size, $password);
- if ($app->db->dbHost != $app->dbmaster->dbHost)
- $app->dbmaster->query($sql, $server_id, $domain_id, 'mysql', $sql_mode, $backup_format_db, time(), $db_compressed_file, $file_size, $password);
- $success = true;
+ //* Compress the backup
+ if ($exit_code == 0) {
+ $exit_code = self::runDatabaseCompression($backup_format_db, $db_backup_dir, $db_backup_file, $db_compressed_file, $backup_tmp, $password) ? 0 : 1;
+ if ($exit_code !== 0)
+ $app->log('Failed to make backup of database ' . $rec['database_name'], LOGLEVEL_ERROR);
+ } else {
+ $app->log('Failed to make backup of database ' . $rec['database_name'] . ', because mysqldump failed', LOGLEVEL_ERROR);
}
- } else {
- if (is_file($db_backup_dir . '/' . $db_compressed_file)) unlink($db_backup_dir . '/' . $db_compressed_file);
+
+ if ($exit_code == 0) {
+ if (is_file($db_backup_dir . '/' . $db_compressed_file)) {
+ chmod($db_backup_dir . '/' . $db_compressed_file, 0750);
+ chown($db_backup_dir . '/' . $db_compressed_file, fileowner($db_backup_dir));
+ chgrp($db_backup_dir . '/' . $db_compressed_file, filegroup($db_backup_dir));
+
+ //* Insert web backup record in database
+ $file_size = filesize($db_backup_dir . '/' . $db_compressed_file);
+ $sql = "INSERT INTO web_backup (server_id, parent_domain_id, backup_type, backup_mode, backup_format, tstamp, filename, filesize, backup_password) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
+ //Making compatible with previous versions of ISPConfig:
+ $sql_mode = ($backup_format_db == 'gzip') ? 'sqlgz' : ('sql' . $backup_format_db);
+ $app->db->query($sql, $server_id, $domain_id, 'mysql', $sql_mode, $backup_format_db, time(), $db_compressed_file, $file_size, $password);
+ if ($app->db->dbHost != $app->dbmaster->dbHost)
+ $app->dbmaster->query($sql, $server_id, $domain_id, 'mysql', $sql_mode, $backup_format_db, time(), $db_compressed_file, $file_size, $password);
+ $success = true;
+ }
+ } else {
+ if (is_file($db_backup_dir . '/' . $db_compressed_file)) unlink($db_backup_dir . '/' . $db_compressed_file);
+ }
+ //* Remove the uncompressed file
+ if (is_file($db_backup_dir . '/' . $db_backup_file)) unlink($db_backup_dir . '/' . $db_backup_file);
+ } else {
+ $app->log('Failed to process mysql backup format ' . $backup_format_db . ' for database ' . $rec['database_name'], LOGLEVEL_ERROR);
}
- //* Remove the uncompressed file
- if (is_file($db_backup_dir . '/' . $db_backup_file)) unlink($db_backup_dir . '/' . $db_backup_file);
-
- //* Remove old backups
- self::backups_garbage_collection($server_id, 'mysql', $domain_id);
- $prefix_list = array(
- "db_${db_name}_",
- "manual-db_${db_name}_",
- );
- self::clearBackups($server_id, $domain_id, intval($rec['backup_copies']), $db_backup_dir, $prefix_list);
- } else {
- $app->log('Failed to process mysql backup format ' . $backup_format_db . ' for database ' . $rec['database_name'], LOGLEVEL_ERROR);
}
+ //* Remove old backups
+ self::backups_garbage_collection($server_id, 'mysql', $domain_id);
+ $prefix_list = array(
+ "db_${db_name}_",
+ "manual-db_${db_name}_",
+ );
+ self::clearBackups($server_id, $domain_id, intval($rec['backup_copies']), $db_backup_dir, $prefix_list);
}
unset($clientdb_host);
@@ -1278,6 +1870,7 @@ class backup
* @param string $backup_job type of backup job: manual or auto
* @return bool true if success
* @author Ramil Valitov
+ * @author Jorge Muñoz
* @see backup_plugin::mount_backup_dir()
* @see backup_plugin::run_backup() recommeneded to use if you need to make backups
*/
@@ -1328,11 +1921,11 @@ class backup
self::prepare_backup_dir($server_id, $web_domain);
$web_backup_dir = $backup_dir . '/web' . $web_id;
- # default exclusions
- $backup_excludes = array(
- './backup*',
- './bin', './dev', './etc', './lib', './lib32', './lib64', './opt', './sys', './usr', './var', './proc', './run', './tmp',
- );
+ # default exclusions
+ $backup_excludes = array(
+ './backup*',
+ './bin', './dev', './etc', './lib', './lib32', './lib64', './opt', './sys', './usr', './var', './proc', './run', './tmp',
+ );
$b_excludes = explode(',', trim($web_domain['backup_excludes']));
if (is_array($b_excludes) && !empty($b_excludes)) {
@@ -1343,43 +1936,217 @@ class backup
}
}
}
+ if (self::backupModeIsRepos($backup_mode)) {
+ $backup_format_web = '';
+ $web_backup_archive = ($backup_job == 'manual' ? 'manual-' : '') . 'web' . $web_id . '_' . date('Y-m-d_H-i');
+ $backup_repos_folder = self::getBackupReposFolder($backup_mode, 'web');
+
+ $backup_repos_path = $web_backup_dir . '/' . $backup_repos_folder;
+ $full_archive_path = $backup_repos_path . '::' . $web_backup_archive;
+ /**
+ * @todo the internal borg password can't be the backup instance $password because the repos shares all backups
+ * in a period of time. Instead we'll set the backup password on backup file download.
+ */
+ $repos_password = '';
+ //@todo get this from the server config perhaps
+ $compression = 'zlib';
- $web_backup_file = ($backup_job == 'manual' ? 'manual-' : '') . 'web' . $web_id . '_' . date('Y-m-d_H-i') . $backup_extension_web;
- $full_filename = $web_backup_dir . '/' . $web_backup_file;
- if (self::runWebCompression($backup_format_web, $backup_excludes, $backup_mode, $web_path, $web_backup_dir, $web_backup_file, $web_user, $web_group, $http_server_user, $backup_tmp, $password)) {
- if (is_file($full_filename)) {
+ if ( ! self::prepareRepos($backup_mode, $backup_repos_path, $repos_password)) {
+ $app->log('Backup of web files for domain ' . $web_domain['domain'] . ' using path ' . $backup_repos_path . ' failed. Unable to prepare repository for ' . $backup_mode, LOGLEVEL_ERROR);
+ return FALSE;
+ }
+ #we wont use tar to be able to speed up things and extract specific files easily
+ #$find_user_files = 'cd ? && find . -group ? -or -user ? -print 2> /dev/null';
+ $excludes = backup::generateExcludeList($backup_excludes, '--exclude ');
+ $success = false;
+
+ $app->log('Performing web files backup of ' . $web_path . ', mode ' . $backup_mode, LOGLEVEL_DEBUG);
+ switch ($backup_mode) {
+ case 'borg':
+ $command = self::getBorgCommand('borg create', $repos_password);
+ $command_opts = self::getBorgCreateOptions($compression);
+
+ $app->system->exec_safe(
+ 'cd ? && ' . $command . ' ' . $command_opts . ' ' . $excludes . ' ? .',
+ $web_path, $backup_repos_path . '::' . $web_backup_archive
+ );
+ $success = $app->system->last_exec_retcode() == 0;
+ }
+
+ if ($success) {
$backup_username = ($global_config['backups_include_into_web_quota'] == 'y') ? $web_user : 'root';
$backup_group = ($global_config['backups_include_into_web_quota'] == 'y') ? $web_group : 'root';
- chown($full_filename, $backup_username);
- chgrp($full_filename, $backup_group);
- chmod($full_filename, 0750);
-
+
//Insert web backup record in database
- $file_size = filesize($full_filename);
+ $archive_size = self::getReposArchiveSize($backup_mode, $backup_repos_path, $web_backup_archive, $repos_password);
+ $password = $repos_password ? '*secret*' : '';
$sql = "INSERT INTO web_backup (server_id, parent_domain_id, backup_type, backup_mode, backup_format, tstamp, filename, filesize, backup_password) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
- $app->db->query($sql, $server_id, $web_id, 'web', $backup_mode, $backup_format_web, time(), $web_backup_file, $file_size, $password);
+ $backup_time = time();
+ $app->db->query($sql, $server_id, $web_id, 'web', $backup_mode, $backup_format_web, $backup_time, $web_backup_archive, $archive_size, $password);
if ($app->db->dbHost != $app->dbmaster->dbHost)
- $app->dbmaster->query($sql, $server_id, $web_id, 'web', $backup_mode, $backup_format_web, time(), $web_backup_file, $file_size, $password);
- unset($file_size);
- $app->log('Backup of web files for domain ' . $web_domain['domain'] . ' completed successfully to file ' . $full_filename, LOGLEVEL_DEBUG);
+ $app->dbmaster->query($sql, $server_id, $web_id, 'web', $backup_mode, $backup_format_web, $backup_time, $web_backup_archive, $archive_size, $password);
+ unset($archive_size);
+ $app->log('Backup of web files for domain ' . $web_domain['domain'] . ' completed successfully to archive ' . $full_archive_path, LOGLEVEL_DEBUG);
} else {
- $app->log('Backup of web files for domain ' . $web_domain['domain'] . ' reported success, but the resulting file ' . $full_filename . ' not found.', LOGLEVEL_ERROR);
+ $app->log('Backup of web files for domain ' . $web_domain['domain'] . ' using path ' . $web_path . ' failed.', LOGLEVEL_ERROR);
}
-
} else {
- if (is_file($full_filename))
- unlink($full_filename);
- $app->log('Backup of web files for domain ' . $web_domain['domain'] . ' failed using path ' . $web_path . ' failed.', LOGLEVEL_ERROR);
+ $web_backup_file = ($backup_job == 'manual' ? 'manual-' : '') . 'web' . $web_id . '_' . date('Y-m-d_H-i') . $backup_extension_web;
+ $full_filename = $web_backup_dir . '/' . $web_backup_file;
+ if (self::runWebCompression($backup_format_web, $backup_excludes, $backup_mode, $web_path, $web_backup_dir, $web_backup_file, $web_user, $web_group, $http_server_user, $backup_tmp, $password)) {
+ if (is_file($full_filename)) {
+ $backup_username = ($global_config['backups_include_into_web_quota'] == 'y') ? $web_user : 'root';
+ $backup_group = ($global_config['backups_include_into_web_quota'] == 'y') ? $web_group : 'root';
+ chown($full_filename, $backup_username);
+ chgrp($full_filename, $backup_group);
+ chmod($full_filename, 0750);
+
+ //Insert web backup record in database
+ $file_size = filesize($full_filename);
+ $sql = "INSERT INTO web_backup (server_id, parent_domain_id, backup_type, backup_mode, backup_format, tstamp, filename, filesize, backup_password) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
+ $app->db->query($sql, $server_id, $web_id, 'web', $backup_mode, $backup_format_web, time(), $web_backup_file, $file_size, $password);
+ if ($app->db->dbHost != $app->dbmaster->dbHost)
+ $app->dbmaster->query($sql, $server_id, $web_id, 'web', $backup_mode, $backup_format_web, time(), $web_backup_file, $file_size, $password);
+ unset($file_size);
+ $app->log('Backup of web files for domain ' . $web_domain['domain'] . ' completed successfully to file ' . $full_filename, LOGLEVEL_DEBUG);
+ } else {
+ $app->log('Backup of web files for domain ' . $web_domain['domain'] . ' reported success, but the resulting file ' . $full_filename . ' not found.', LOGLEVEL_ERROR);
+ }
+
+ } else {
+ if (is_file($full_filename))
+ unlink($full_filename);
+ $app->log('Backup of web files for domain ' . $web_domain['domain'] . ' failed using path ' . $web_path . ' failed.', LOGLEVEL_ERROR);
+ }
}
- $prefix_list = array(
- 'web',
- 'manual-web',
- );
+ $prefix_list = array(
+ 'web',
+ 'manual-web',
+ );
self::clearBackups($server_id, $web_id, intval($web_domain['backup_copies']), $web_backup_dir, $prefix_list);
return true;
}
+ protected static function getBackupReposFolder($backup_mode, $backup_type)
+ {
+ switch ($backup_mode) {
+ case 'borg': return 'borg_' . $backup_type;
+ }
+ return null;
+ }
+
+ /**
+ * Prepares repository for backup. Initialization, etc.
+ */
+ protected static function prepareRepos($backup_mode, $repos_path, $password)
+ {
+ global $app;
+ if (is_dir($repos_path)) {
+ self::getReposArchives($backup_mode, $repos_path, $password);
+ if ($app->system->last_exec_retcode() == 0) {
+ return true;
+ }
+ if ($app->system->last_exec_retcode() == 2 && preg_match('/passphrase supplied in.*is incorrect/', $app->system->last_exec_out()[0])) {
+ //Password was updated, so we rename folder and alert the event.
+ $repos_stat = stat($repos_path);
+ $mtime = $repos_stat['mtime'];
+ $new_repo_path = $repos_path . '_' . date('Y-m-d_H-i', $mtime);
+ rename($repos_path, $new_repo_name);
+ $app->log('Backup of web files for domain ' . $web_domain['domain'] . ' are encrypted under a different password. Original repos was moved to ' . $new_repo_name, LOGLEVEL_WARN);
+ } else {
+ return false;
+ }
+ }
+ switch ($backup_mode) {
+ case 'borg':
+ if ($password) {
+ $command = self::getBorgCommand('borg init', $password, true);
+ $app->system->exec_safe($command . ' --make-parent-dirs -e authenticated ?', $repos_path);
+ } else {
+ $app->system->exec_safe('borg init --make-parent-dirs -e none ?', $repos_path);
+ }
+ return $app->system->last_exec_retcode() == 0;
+ }
+ return false;
+ }
+
+ /**
+ * Obtains archive compressed size from specific repository.
+ * @param string $backup_mode Server backup mode.
+ * @param string $backup_repos_path Absolute path to repository.
+ * @param string $backup_archive Name of the archive to obtain size from.
+ * @param string $password Provide repository password or empty string if there is none.
+ * @author Jorge Muñoz
+ */
+ protected static function getReposArchiveSize($backup_mode, $backup_repos_path, $backup_archive, $password)
+ {
+ $info = self::getReposArchiveInfo($backup_mode, $backup_repos_path, $backup_archive, $password);
+ if ($info) {
+ return $info['compressed_size'];
+ }
+ return false;
+ }
+ /**
+ * Obtains archive information for specific repository archive.
+ * @param string $backup_mode Server backup mode.
+ * @param string $backup_repos_path Absolute path to repository.
+ * @param string $backup_archive Name of the archive to obtain size from.
+ * @param string $password Provide repository password or empty string if there is none.
+ * @return array Can contain one or more of the following keys:
+ * 'created_at': int unixtime
+ * 'created_by': string
+ * 'comment': string
+ * 'original_size': int
+ * 'compressed_size': int
+ * 'deduplicated_size': int
+ * 'num_files': int
+ * 'compression': string
+ * @author Jorge Muñoz
+ */
+ protected static function getReposArchiveInfo($backup_mode, $backup_repos_path, $backup_archive, $password)
+ {
+ global $app;
+ $info = [];
+ switch ($backup_mode) {
+ case 'borg':
+ $command = self::getBorgCommand('borg info', $password);
+ $full_archive_path = $backup_repos_path . '::' . $backup_archive;
+ $app->system->exec_safe($command . ' --json ?', $full_archive_path);
+ if ($app->system->last_exec_retcode() != 0) {
+ $app->log('Command `borg info` failed for ' . $full_archive_path . '.', LOGLEVEL_ERROR);
+ }
+ $out = implode("", $app->system->last_exec_out());
+ if ($out) {
+ $out = json_decode($out, true);
+ }
+ if (empty($out)) {
+ $app->log('No json result could be parsed from `borg info --json` command for repository ' . $full_archive_path . '.', LOGLEVEL_ERROR);
+ return false;
+ }
+ if (empty($out['archives'])) {
+ $app->log('No archive ' . $backup_archive . ' found for repository ' . $backup_repos_path . '.', LOGLEVEL_WARN);
+ return false;
+ }
+ $info['created_at'] = strtotime($out['archives'][0]['start']);
+ $info['created_by'] = $out['archives'][0]['username'];
+ $info['comment'] = $out['archives'][0]['comment'];
+ $info['original_size'] = (int)$out['archives'][0]['stats']['original_size'];
+ $info['compressed_size'] = (int)$out['archives'][0]['stats']['compressed_size'];
+ $info['deduplicated_size'] = (int)$out['archives'][0]['stats']['deduplicated_size'];
+ $info['num_files'] = (int)$out['archives'][0]['stats']['nfiles'];
+ $prev_arg = null;
+ foreach ($out['archives'][0]['command_line'] as $arg) {
+ if ($prev_arg == '-C' || $prev_arg == '--compression') {
+ $info['compression'] = $arg;
+ break;
+ }
+ $prev = $arg;
+ }
+ }
+ return $info;
+ }
+
/**
* Creates and prepares a backup dir
* @param int $server_id
@@ -1498,7 +2265,7 @@ class backup
}
}
- $sql = "SELECT DISTINCT d.*, db.server_id as `server_id` FROM web_database as db INNER JOIN web_domain as d ON (d.domain_id = db.parent_domain_id) WHERE db.server_id = ? AND db.active = 'y' AND d.backup_interval != 'none' AND d.backup_interval != ''";
+ $sql = "SELECT DISTINCT d.*, db.server_id as `server_id` FROM web_database as db INNER JOIN web_domain as d ON (d.domain_id = db.parent_domain_id) WHERE db.server_id = ? AND db.active = 'y' AND d.backup_interval != 'none' AND d.backup_interval != ''";
$databases = $app->dbmaster->queryAllRecords($sql, $server_id);
foreach ($databases as $database) {
diff --git a/server/plugins-available/backup_plugin.inc.php b/server/plugins-available/backup_plugin.inc.php
index a92165ba6d917bf32b4449d926bf180716317eec..8ba345b4c46315939be690a252485f1537997bc9 100644
--- a/server/plugins-available/backup_plugin.inc.php
+++ b/server/plugins-available/backup_plugin.inc.php
@@ -87,14 +87,7 @@ class backup_plugin {
if($backup_dir_is_ready){
//* Make backup available for download
if($action_name == 'backup_download') {
- //* Copy the backup file to the backup folder of the website
- if(file_exists($backup_dir.'/'.$backup['filename']) && file_exists($web['document_root'].'/backup/') && !stristr($backup_dir.'/'.$backup['filename'], '..') && !stristr($backup_dir.'/'.$backup['filename'], 'etc')) {
- copy($backup_dir.'/'.$backup['filename'], $web['document_root'].'/backup/'.$backup['filename']);
- chgrp($web['document_root'].'/backup/'.$backup['filename'], $web['system_group']);
- chown($web['document_root'].'/backup/'.$backup['filename'], $web['system_user']);
- chmod($web['document_root'].'/backup/'.$backup['filename'],0600);
- $app->log('cp '.$backup_dir.'/'.$backup['filename'].' '.$web['document_root'].'/backup/'.$backup['filename'], LOGLEVEL_DEBUG);
- }
+ backup::downloadBackup($backup['backup_format'], trim($backup['backup_password']), $backup_dir, $backup['filename'], $backup['backup_mode'], $backup['backup_type'], $web);
}
//* Restore a MongoDB backup
@@ -134,14 +127,7 @@ class backup_plugin {
}
if($action_name == 'backup_delete') {
- if(file_exists($backup_dir.'/'.$backup['filename']) && !stristr($backup_dir.'/'.$backup['filename'], '..') && !stristr($backup_dir.'/'.$backup['filename'], 'etc')) {
- unlink($backup_dir.'/'.$backup['filename']);
-
- $sql = "DELETE FROM web_backup WHERE server_id = ? AND parent_domain_id = ? AND filename = ?";
- $app->db->query($sql, $conf['server_id'], $backup['parent_domain_id'], $backup['filename']);
- if($app->db->dbHost != $app->dbmaster->dbHost) $app->dbmaster->query($sql, $conf['server_id'], $backup['parent_domain_id'], $backup['filename']);
- $app->log('unlink '.$backup_dir.'/'.$backup['filename'], LOGLEVEL_DEBUG);
- }
+ backup::deleteBackup($backup['backup_format'], trim($backup['backup_password']), $backup_dir, $backup['filename'], $backup['backup_mode'], $backup['backup_type'], $backup['parent_domain_id']);
}
backup::unmount_backup_dir($conf['server_id']);