diff --git a/app/classes/helpers/cryptography_helper.py b/app/classes/helpers/cryptography_helper.py index 48701874..ae0b0afb 100644 --- a/app/classes/helpers/cryptography_helper.py +++ b/app/classes/helpers/cryptography_helper.py @@ -1,3 +1,6 @@ +import base64 + + class CryptoHelper: def __init__(self, helper): self.helper = helper @@ -5,3 +8,7 @@ class CryptoHelper: def say_hello_world(self): print(self.test) + + @staticmethod + def bytes_to_b64(input_bytes: bytes) -> str: + return base64.b64encode(input_bytes).decode("UTF-8").rstrip("\n") diff --git a/app/classes/models/management.py b/app/classes/models/management.py index 9a03eb54..9d560f9b 100644 --- a/app/classes/models/management.py +++ b/app/classes/models/management.py @@ -117,6 +117,7 @@ class Backups(BaseModel): default = BooleanField(default=False) status = CharField(default='{"status": "Standby", "message": ""}') enabled = BooleanField(default=True) + backup_type = CharField(default="zip_archive") class Meta: table_name = "backups" @@ -368,6 +369,7 @@ class HelpersManagement: "after": backup.after, "default": backup.default, "enabled": backup.enabled, + "backup_type": backup.backup_type, } else: data = Backups.select().where(Backups.server_id == server_id).execute() @@ -375,13 +377,10 @@ class HelpersManagement: @staticmethod def get_default_server_backup(server_id: str) -> dict: - print(server_id) bu_query = Backups.select().where( Backups.server_id == server_id, Backups.default == True, # pylint: disable=singleton-comparison ) - for item in bu_query: - print("HI", item) backup_model = bu_query.first() if backup_model: diff --git a/app/classes/shared/backup_mgr.py b/app/classes/shared/backup_mgr.py index bd111927..79b066bd 100644 --- a/app/classes/shared/backup_mgr.py +++ b/app/classes/shared/backup_mgr.py @@ -3,6 +3,7 @@ import time import datetime import json import logging +import pathlib from zoneinfo import ZoneInfo @@ -35,11 +36,13 @@ class BackupManager: self.tz = ZoneInfo("Europe/London") def backup_starter(self, backup_config, server): - if backup_config.get("type", "zip_vault") == "zip_vault": - self.zip_vault(backup_config, server) - - def zip_vault(self, backup_config, server): + """Notify users of backup starting, and start the backup. + Args: + backup_config (_type_): _description_ + server (_type_): Server object to backup + """ + # Notify users of backup starting logger.info(f"Starting server {server.name}" f" (ID {server.server_id}) backup") server_users = PermissionsServers.get_server_user_list(server.server_id) # Alert the start of the backup to the authorized users. @@ -53,6 +56,14 @@ class BackupManager: ) time.sleep(3) + # Start the backup + if backup_config.get("backup_type", "zip_vault") == "zip_vault": + self.zip_vault(backup_config, server) + else: + self.snapshot_backup(backup_config, server) + + def zip_vault(self, backup_config, server): + # Adjust the location to include the backup ID for destination. backup_location = os.path.join( backup_config["backup_location"], backup_config["backup_id"] @@ -126,32 +137,35 @@ class BackupManager: ) time.sleep(5) except Exception as e: - logger.exception( - "Failed to create backup of server" - f" {server.name} (ID {server.server_id})" - ) - results = { - "percent": 100, - "total_files": 0, - "current_file": 0, - "backup_id": backup_config["backup_id"], - } - if len(WebSocketManager().clients) > 0: - WebSocketManager().broadcast_page_params( - "/panel/server_detail", - {"id": str(server.server_id)}, - "backup_status", - results, - ) - - HelpersManagement.update_backup_config( - backup_config["backup_id"], - {"status": json.dumps({"status": "Failed", "message": f"{e}"})}, - ) + self.fail_backup(e, backup_config, server) server.backup_server( backup_config, ) + def fail_backup(self, why: Exception, backup_config: dict, server): + logger.exception( + "Failed to create backup of server" + f" {server.name} (ID {server.server_id})" + ) + results = { + "percent": 100, + "total_files": 0, + "current_file": 0, + "backup_id": backup_config["backup_id"], + } + if len(WebSocketManager().clients) > 0: + WebSocketManager().broadcast_page_params( + "/panel/server_detail", + {"id": str(server.server_id)}, + "backup_status", + results, + ) + + HelpersManagement.update_backup_config( + backup_config["backup_id"], + {"status": json.dumps({"status": "Failed", "message": f"{why}"})}, + ) + def list_backups(self, backup_config: dict, server_id) -> list: if not backup_config: logger.info( @@ -197,3 +211,37 @@ class BackupManager: ) logger.info(f"Removing old backup '{oldfile['path']}'") os.remove(Helpers.get_os_understandable_path(oldfile_path)) + + def snapshot_backup(self, backup_config, server): + + logger.info(f"Starting snapshot style backup for {server.name}") + + # Adjust the location to include the backup ID for destination. + backup_location = os.path.join( + pathlib.Path(backup_config["backup_location"]), "snapshot_backups" + ) + try: + self.ensure_snapshot_directory_is_valid(backup_location) + except PermissionError as why: + self.fail_backup(why, backup_config, server) + + def ensure_snapshot_directory_is_valid(self, backup_path: pathlib.Path) -> bool: + backup_path.mkdir(exist_ok=True) + backup_readme_path = backup_path / "README.txt" + + if not backup_readme_path.exists(): + logger.info("Is this doing anything?") + try: + logger.info("Attempting to make snapshot storage directory.") + with open(backup_readme_path, "w", encoding="UTF-8") as f: + f.write( + "Crafty snapshot backup storage dir. Please do not touch" + "these files." + ) + + except PermissionError as why: + raise PermissionError( + f"Unable to write to snapshot backup storage path" + f": {backup_readme_path}" + ) from why + return True diff --git a/app/classes/web/routes/api/servers/server/backups/backup/index.py b/app/classes/web/routes/api/servers/server/backups/backup/index.py index 88a2b6f7..f777d724 100644 --- a/app/classes/web/routes/api/servers/server/backups/backup/index.py +++ b/app/classes/web/routes/api/servers/server/backups/backup/index.py @@ -30,6 +30,12 @@ BACKUP_PATCH_SCHEMA = { "before": {"type": "string"}, "after": {"type": "string"}, "excluded_dirs": {"type": "array"}, + "backup_type": { + "type": "string", + "enum": ["zip_vault", "snapshot"], + "error": "enumErr", + "fill": True, + }, }, "additionalProperties": False, "minProperties": 1, @@ -45,6 +51,12 @@ BASIC_BACKUP_PATCH_SCHEMA = { "before": {"type": "string"}, "after": {"type": "string"}, "excluded_dirs": {"type": "array"}, + "backup_type": { + "type": "string", + "enum": ["zip_vault", "snapshot"], + "error": "enumErr", + "fill": True, + }, }, "additionalProperties": False, "minProperties": 1, diff --git a/app/classes/web/routes/api/servers/server/backups/index.py b/app/classes/web/routes/api/servers/server/backups/index.py index a155f943..70864da9 100644 --- a/app/classes/web/routes/api/servers/server/backups/index.py +++ b/app/classes/web/routes/api/servers/server/backups/index.py @@ -19,6 +19,12 @@ backup_patch_schema = { "before": {"type": "string"}, "after": {"type": "string"}, "excluded_dirs": {"type": "array"}, + "backup_type": { + "type": "string", + "enum": ["zip_vault", "snapshot"], + "error": "enumErr", + "fill": True, + }, }, "additionalProperties": False, "minProperties": 1, @@ -34,6 +40,12 @@ basic_backup_patch_schema = { "before": {"type": "string"}, "after": {"type": "string"}, "excluded_dirs": {"type": "array"}, + "backup_type": { + "type": "string", + "enum": ["zip_vault", "snapshot"], + "error": "enumErr", + "fill": True, + }, }, "additionalProperties": False, "minProperties": 1, diff --git a/app/frontend/templates/panel/server_backup.html b/app/frontend/templates/panel/server_backup.html index 73fde1cc..98eed62f 100644 --- a/app/frontend/templates/panel/server_backup.html +++ b/app/frontend/templates/panel/server_backup.html @@ -73,8 +73,7 @@ data['lang']) }}
{{backup.backup_location}}
{{backup.max_backups}}
+{{backup.backup_type}}