diff --git a/Containers/borgbackup/backupscript.sh b/Containers/borgbackup/backupscript.sh index 269c9312..26c9b42c 100644 --- a/Containers/borgbackup/backupscript.sh +++ b/Containers/borgbackup/backupscript.sh @@ -149,6 +149,54 @@ if [ "$BORG_MODE" = backup ]; then exit 1 fi + # Back up additional directories of the host + if [ "$ADDITIONAL_DIRECTORIES_BACKUP" = 'yes' ]; then + if [ -d "/docker_volumes/" ]; then + DOCKER_VOLUME_DIRS="$(find /docker_volumes -mindepth 1 -maxdepth 1 -type d)" + mapfile -t DOCKER_VOLUME_DIRS <<< "$DOCKER_VOLUME_DIRS" + for directory in "${DOCKER_VOLUME_DIRS[@]}"; do + if [ -z "$(ls -A "$directory")" ]; then + echo "$directory is empty which is not allowed." + exit 1 + fi + done + if ! borg create "${BORG_OPTS[@]}" "$BORG_BACKUP_DIRECTORY::$CURRENT_DATE-additional-docker-volumes" "/docker_volumes/"; then + echo "Deleting the failed backup archive..." + borg delete --stats --progress "$BORG_BACKUP_DIRECTORY::$CURRENT_DATE-additional-docker-volumes" + echo "Backup of additional docker-volumes failed!" + exit 1 + fi + + if ! borg prune --prefix '*_*-additional-docker-volumes' "${BORG_PRUNE_OPTS[@]}"; then + echo "Failed to prune additional docker-volumes archives!" + exit 1 + fi + fi + if [ -d "/host_mounts/" ]; then + EXCLUDED_DIRECTORIES=(home/*/.cache root/.cache var/cache lost+found run var/run dev tmp sys proc) + # Exclude borg backup cache + EXCLUDED_DIRECTORIES+=(var/lib/docker/volumes/nextcloud_aio_backup_cache/_data) + # Exclude target directory + if [ -n "$BORGBACKUP_HOST_LOCATION" ] && [ "$BORGBACKUP_HOST_LOCATION" != "nextcloud_aio_backupdir" ]; then + EXCLUDED_DIRECTORIES+=("$BORGBACKUP_HOST_LOCATION") + fi + for directory in "${EXCLUDED_DIRECTORIES[@]}" + do + EXCLUDE_DIRS+=(--exclude "/host_mounts/$directory/") + done + if ! borg create "${BORG_OPTS[@]}" "${EXCLUDED_DIRECTORIES[@]}" "$BORG_BACKUP_DIRECTORY::$CURRENT_DATE-additional-host-mounts" "/host_mounts/"; then + echo "Deleting the failed backup archive..." + borg delete --stats --progress "$BORG_BACKUP_DIRECTORY::$CURRENT_DATE-additional-host-mounts" + echo "Backup of additional host-mounts failed!" + exit 1 + fi + if ! borg prune --prefix '*_*-additional-host-mounts' "${BORG_PRUNE_OPTS[@]}"; then + echo "Failed to prune additional host-mount archives!" + exit 1 + fi + fi + fi + # Inform user get_expiration_time echo "Backup finished successfully on $END_DATE_READABLE ($DURATION_READABLE)" @@ -195,6 +243,11 @@ if [ "$BORG_MODE" = restore ]; then # Save current path BORG_LOCATION="$(jq '.borg_backup_host_location' /nextcloud_aio_volumes/nextcloud_aio_mastercontainer/data/configuration.json)" + # Save Additional Backup dirs + if [ -f "/nextcloud_aio_volumes/nextcloud_aio_mastercontainer/data/additional_backup_directories" ]; then + ADDITIONAL_BACKUP_DIRECTORIES="$(cat /nextcloud_aio_volumes/nextcloud_aio_mastercontainer/data/additional_backup_directories)" + fi + # Save current nextcloud datadir if grep -q '"nextcloud_datadir":' /nextcloud_aio_volumes/nextcloud_aio_mastercontainer/data/configuration.json; then NEXTCLOUD_DATADIR="$(jq '.nextcloud_datadir' /nextcloud_aio_volumes/nextcloud_aio_mastercontainer/data/configuration.json)" @@ -227,6 +280,13 @@ if [ "$BORG_MODE" = restore ]; then CONTENTS="$(jq ".nextcloud_datadir = $NEXTCLOUD_DATADIR" /nextcloud_aio_volumes/nextcloud_aio_mastercontainer/data/configuration.json)" echo -E "${CONTENTS}" > /nextcloud_aio_volumes/nextcloud_aio_mastercontainer/data/configuration.json + # Reset the additional backup directories + if [ -n "$ADDITIONAL_BACKUP_DIRECTORIES" ]; then + echo "$ADDITIONAL_BACKUP_DIRECTORIES" > "/nextcloud_aio_volumes/nextcloud_aio_mastercontainer/data/additional_backup_directories" + chown 33:0 "/nextcloud_aio_volumes/nextcloud_aio_mastercontainer/data/additional_backup_directories" + chmod 770 "/nextcloud_aio_volumes/nextcloud_aio_mastercontainer/data/additional_backup_directories" + fi + umount /tmp/borg # Inform user diff --git a/php/containers.json b/php/containers.json index d06299dd..c92699be 100644 --- a/php/containers.json +++ b/php/containers.json @@ -233,7 +233,9 @@ "BORG_PASSWORD=%BORGBACKUP_PASSWORD%", "BORG_MODE=%BORGBACKUP_MODE%", "SELECTED_RESTORE_TIME=%SELECTED_RESTORE_TIME%", - "BACKUP_RESTORE_PASSWORD=%BACKUP_RESTORE_PASSWORD%" + "BACKUP_RESTORE_PASSWORD=%BACKUP_RESTORE_PASSWORD%", + "ADDITIONAL_DIRECTORIES_BACKUP=%ADDITIONAL_DIRECTORIES_BACKUP%", + "BORGBACKUP_HOST_LOCATION=%BORGBACKUP_HOST_LOCATION%" ], "volumes": [ { diff --git a/php/public/index.php b/php/public/index.php index 6136f2d5..3829017a 100644 --- a/php/public/index.php +++ b/php/public/index.php @@ -104,6 +104,7 @@ $app->get('/containers', function ($request, $response, $args) use ($container) 'is_backup_section_enabled' => $configurationManager->isBackupSectionEnabled(), 'is_imaginary_enabled' => $configurationManager->isImaginaryEnabled(), 'is_fulltextsearch_enabled' => $configurationManager->isFulltextsearchEnabled(), + 'additional_backup_directories' => $configurationManager->GetAdditionalBackupDirectoriesString(), ]); })->setName('profile'); $app->get('/login', function ($request, $response, $args) use ($container) { diff --git a/php/src/Controller/ConfigurationController.php b/php/src/Controller/ConfigurationController.php index ad2697d7..a06cc6b9 100644 --- a/php/src/Controller/ConfigurationController.php +++ b/php/src/Controller/ConfigurationController.php @@ -57,6 +57,11 @@ class ConfigurationController $this->configurationManager->DeleteDailyBackupTime(); } + if (isset($request->getParsedBody()['additional_backup_directories'])) { + $additionalBackupDirectories = $request->getParsedBody()['additional_backup_directories'] ?? ''; + $this->configurationManager->SetAdditionalBackupDirectories($additionalBackupDirectories); + } + if (isset($request->getParsedBody()['delete_timezone'])) { $this->configurationManager->DeleteTimezone(); } diff --git a/php/src/Data/ConfigurationManager.php b/php/src/Data/ConfigurationManager.php index 1dec7615..826fa3ea 100644 --- a/php/src/Data/ConfigurationManager.php +++ b/php/src/Data/ConfigurationManager.php @@ -584,6 +584,45 @@ class ConfigurationManager } } + /** + * @throws InvalidSettingConfigurationException + */ + public function SetAdditionalBackupDirectories(string $additionalBackupDirectories) : void { + $additionalBackupDirectoriesArray = explode("\n", $additionalBackupDirectories); + $validDirectories = ''; + foreach($additionalBackupDirectoriesArray as $entry) { + // Trim all unwanted chars on both sites + $entry = trim($entry); + if ($entry !== "") { + if (!preg_match("#^/[0-1a-zA-Z/-_]$#", $entry) && !preg_match("#^[0-1a-zA-Z_-]$#", $entry)) { + throw new InvalidSettingConfigurationException("You entered unallowed characters! Problematic is " . $entry); + } + $validDirectories .= rtrim($entry, '/') . PHP_EOL; + } + } + + if ($validDirectories === '') { + unlink(DataConst::GetAdditionalBackupDirectoriesFile()); + } else { + file_put_contents(DataConst::GetAdditionalBackupDirectoriesFile(), $validDirectories); + } + } + + public function GetAdditionalBackupDirectoriesString() : string { + if (!file_exists(DataConst::GetAdditionalBackupDirectoriesFile())) { + return ''; + } + $additionalBackupDirectories = file_get_contents(DataConst::GetAdditionalBackupDirectoriesFile()); + return $additionalBackupDirectories; + } + + public function GetAdditionalBackupDirectoriesArray() : array { + $additionalBackupDirectories = $this->GetAdditionalBackupDirectoriesString(); + $additionalBackupDirectoriesArray = explode("\n", $additionalBackupDirectories); + $additionalBackupDirectoriesArray = array_unique($additionalBackupDirectoriesArray, SORT_REGULAR); + return $additionalBackupDirectoriesArray; + } + public function isDailyBackupRunning() : bool { if (file_exists(DataConst::GetDailyBackupBlockFile())) { return true; diff --git a/php/src/Data/DataConst.php b/php/src/Data/DataConst.php index 5ffaf403..5e671c11 100644 --- a/php/src/Data/DataConst.php +++ b/php/src/Data/DataConst.php @@ -31,6 +31,10 @@ class DataConst { return self::GetDataDirectory() . '/daily_backup_time'; } + public static function GetAdditionalBackupDirectoriesFile() : string { + return self::GetDataDirectory() . '/additional_backup_directories'; + } + public static function GetDailyBackupBlockFile() : string { return self::GetDataDirectory() . '/daily_backup_running'; } diff --git a/php/src/Docker/DockerActionManager.php b/php/src/Docker/DockerActionManager.php index cb76aac9..85c9cd21 100644 --- a/php/src/Docker/DockerActionManager.php +++ b/php/src/Docker/DockerActionManager.php @@ -314,6 +314,14 @@ class DockerActionManager $replacements[1] = $this->configurationManager->GetNextcloudUploadLimit(); } elseif ($out[1] === 'NEXTCLOUD_MAX_TIME') { $replacements[1] = $this->configurationManager->GetNextcloudMaxTime(); + } elseif ($out[1] === 'ADDITIONAL_DIRECTORIES_BACKUP') { + if ($this->configurationManager->GetAdditionalBackupDirectoriesString() !== '') { + $replacements[1] = 'yes'; + } else { + $replacements[1] = ''; + } + } elseif ($out[1] === 'BORGBACKUP_HOST_LOCATION') { + $replacements[1] = $this->configurationManager->GetBorgBackupHostLocation(); } else { $replacements[1] = $this->configurationManager->GetSecret($out[1]); } @@ -354,6 +362,22 @@ class DockerActionManager $requestBody['HostConfig']['CapAdd'] = ["SYS_ADMIN"]; $requestBody['HostConfig']['Devices'] = [["PathOnHost" => "/dev/fuse", "PathInContainer" => "/dev/fuse", "CgroupPermissions" => "rwm"]]; $requestBody['HostConfig']['SecurityOpt'] = ["apparmor:unconfined"]; + + // Additional backup directories + $mounts = []; + foreach ($this->configurationManager->GetAdditionalBackupDirectoriesArray() as $additionalBackupDirectories) { + if ($additionalBackupDirectories !== '') { + if (!str_starts_with($additionalBackupDirectories, '/')) { + $target = '/docker_volumes/'; + } else { + $target = '/host_mounts'; + } + $mounts[] = ["Type" => "bind", "Source" => $additionalBackupDirectories, "Target" => $target . $additionalBackupDirectories, "ReadOnly" => true, "BindOptions" => ["NonRecursive" => true]]; + } + } + if(count($mounts) > 0) { + $requestBody['HostConfig']['Mounts'] = $mounts; + } } $url = $this->BuildApiUrl('containers/create?name=' . $container->GetIdentifier()); diff --git a/php/templates/containers.twig b/php/templates/containers.twig index be4d3a7d..6cb573d1 100644 --- a/php/templates/containers.twig +++ b/php/templates/containers.twig @@ -371,7 +371,7 @@