diff --git a/app/classes/controllers/management_controller.py b/app/classes/controllers/management_controller.py index d256c52a..6efe492a 100644 --- a/app/classes/controllers/management_controller.py +++ b/app/classes/controllers/management_controller.py @@ -104,5 +104,17 @@ class Management_Controller: return management_helper.get_backup_config(server_id) @staticmethod - def set_backup_config(server_id: int, backup_path: str = None, max_backups: int = None): - return management_helper.set_backup_config(server_id, backup_path, max_backups) + def set_backup_config(server_id: int, backup_path: str = None, max_backups: int = None, excluded_dirs: list = None): + return management_helper.set_backup_config(server_id, backup_path, max_backups, excluded_dirs) + + @staticmethod + def get_excluded_backup_dirs(server_id: int): + return management_helper.get_excluded_backup_dirs(server_id) + + @staticmethod + def add_excluded_backup_dir(server_id: int, dir_to_add: str): + management_helper.add_excluded_backup_dir(server_id, dir_to_add) + + @staticmethod + def del_excluded_backup_dir(server_id: int, dir_to_del: str): + management_helper.del_excluded_backup_dir(server_id, dir_to_del) diff --git a/app/classes/models/management.py b/app/classes/models/management.py index b652ff11..e197add6 100644 --- a/app/classes/models/management.py +++ b/app/classes/models/management.py @@ -125,7 +125,7 @@ class Schedules(Model): # Backups Class #************************************************************************************************ class Backups(Model): - directories = CharField(null=True) + excluded_dirs = CharField(null=True) max_backups = IntegerField() server_id = ForeignKeyField(Servers, backref='backups_server') class Meta: @@ -311,34 +311,37 @@ class helpers_management: row = Backups.select().where(Backups.server_id == server_id).join(Servers)[0] conf = { "backup_path": row.server_id.backup_path, - "directories": row.directories, + "excluded_dirs": row.excluded_dirs, "max_backups": row.max_backups, "server_id": row.server_id.server_id } except IndexError: conf = { "backup_path": None, - "directories": None, + "excluded_dirs": None, "max_backups": 0, "server_id": server_id } return conf @staticmethod - def set_backup_config(server_id: int, backup_path: str = None, max_backups: int = None): + def set_backup_config(server_id: int, backup_path: str = None, max_backups: int = None, excluded_dirs: list = None): logger.debug(f"Updating server {server_id} backup config with {locals()}") if Backups.select().where(Backups.server_id == server_id).count() != 0: new_row = False conf = {} else: conf = { - "directories": None, + "excluded_dirs": None, "max_backups": 0, "server_id": server_id } new_row = True if max_backups is not None: conf['max_backups'] = max_backups + if excluded_dirs is not None: + dirs_to_exclude = ",".join(excluded_dirs) + conf['excluded_dirs'] = dirs_to_exclude if not new_row: with database.atomic(): if backup_path is not None: @@ -355,5 +358,34 @@ class helpers_management: Backups.create(**conf) logger.debug("Creating new backup record.") + @staticmethod + def get_excluded_backup_dirs(server_id: int): + excluded_dirs = helpers_management.get_backup_config(server_id)['excluded_dirs'] + if excluded_dirs is not None: + dir_list = excluded_dirs.split(",") + else: + dir_list = [] + return dir_list + + @staticmethod + def add_excluded_backup_dir(server_id: int, dir_to_add: str): + dir_list = management_helper.get_excluded_backup_dirs() + if dir_to_add not in dir_list: + dir_list.append(dir_to_add) + excluded_dirs = ",".join(dir_list) + management_helper.set_backup_config(server_id=server_id, excluded_dirs=excluded_dirs) + else: + logger.debug(f"Not adding {dir_to_add} to excluded directories - already in the excluded directory list for server ID {server_id}") + + @staticmethod + def del_excluded_backup_dir(server_id: int, dir_to_del: str): + dir_list = management_helper.get_excluded_backup_dirs() + if dir_to_del in dir_list: + dir_list.remove(dir_to_del) + excluded_dirs = ",".join(dir_list) + management_helper.set_backup_config(server_id=server_id, excluded_dirs=excluded_dirs) + else: + logger.debug(f"Not removing {dir_to_del} from excluded directories - not in the excluded directory list for server ID {server_id}") + management_helper = helpers_management() diff --git a/app/classes/shared/helpers.py b/app/classes/shared/helpers.py index a4566df5..f21963d0 100644 --- a/app/classes/shared/helpers.py +++ b/app/classes/shared/helpers.py @@ -773,6 +773,12 @@ class Helpers: websocket_helper.broadcast_user(user_id, 'send_temp_path',{ 'path': tempDir }) + @staticmethod + def backup_select(path, user_id): + if user_id: + websocket_helper.broadcast_user(user_id, 'send_temp_path',{ + 'path': path + }) @staticmethod def unzip_backup_archive(backup_path, zip_name): diff --git a/app/classes/shared/server.py b/app/classes/shared/server.py index 0e84472d..7b902296 100644 --- a/app/classes/shared/server.py +++ b/app/classes/shared/server.py @@ -8,6 +8,7 @@ import logging.config import shutil import subprocess import html +import tempfile from apscheduler.schedulers.background import BackgroundScheduler #TZLocal is set as a hidden import on win pipeline from tzlocal import get_localzone @@ -580,14 +581,36 @@ class Server: backup_filename = f"{self.settings['backup_path']}/{datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}" logger.info(f"Creating backup of server '{self.settings['server_name']}'" + f" (ID#{self.server_id}, path={self.server_path}) at '{backup_filename}'") - shutil.make_archive(helper.get_os_understandable_path(backup_filename), 'zip', self.server_path) + + tempDir = tempfile.mkdtemp() + # pylint: disable=unexpected-keyword-arg + shutil.copytree(self.server_path, tempDir, dirs_exist_ok=True) + excluded_dirs = management_helper.get_excluded_backup_dirs(self.server_id) + server_dir = helper.get_os_understandable_path(self.settings['path']) + + for my_dir in excluded_dirs: + # Take the full path of the excluded dir and replace the server path with the temp path + # This is so that we're only deleting excluded dirs from the temp path and not the server path + excluded_dir = helper.get_os_understandable_path(my_dir).replace(server_dir, helper.get_os_understandable_path(tempDir)) + # Next, check to see if it is a directory + if os.path.isdir(excluded_dir): + # If it is a directory, recursively delete the entire directory from the backup + shutil.rmtree(excluded_dir) + else: + # If not, just remove the file + os.remove(excluded_dir) + + shutil.make_archive(helper.get_os_understandable_path(backup_filename), 'zip', tempDir) + while len(self.list_backups()) > conf["max_backups"] and conf["max_backups"] > 0: backup_list = self.list_backups() oldfile = backup_list[0] oldfile_path = f"{conf['backup_path']}/{oldfile['path']}" logger.info(f"Removing old backup '{oldfile['path']}'") os.remove(helper.get_os_understandable_path(oldfile_path)) + self.is_backingup = False + shutil.rmtree(tempDir) logger.info(f"Backup of server: {self.name} completed") return except: diff --git a/app/classes/web/ajax_handler.py b/app/classes/web/ajax_handler.py index e847cc61..6e8f4a85 100644 --- a/app/classes/web/ajax_handler.py +++ b/app/classes/web/ajax_handler.py @@ -99,6 +99,141 @@ class AjaxHandler(BaseHandler): helper.generate_zip_dir(path)) self.finish() + elif page == "get_backup_tree": + server_id = self.get_argument('id', None) + folder = self.get_argument('path', None) + + output = "" + + file_list = os.listdir(folder) + file_list = sorted(file_list, key=str.casefold) + output += \ + f"""