commit 7535298a7429cb10c9110802a0817acf16deb773 Author: Marc Michalsky Date: Mon Nov 23 16:27:19 2020 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1ceb8e5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +venv/ +.idea/ +settings.yaml \ No newline at end of file diff --git a/Models/Backup.py b/Models/Backup.py new file mode 100644 index 0000000..e69de29 diff --git a/Models/Container.py b/Models/Container.py new file mode 100644 index 0000000..87dee5a --- /dev/null +++ b/Models/Container.py @@ -0,0 +1,91 @@ +import datetime +import os +import stat +import tarfile +from Models.NextcloudBackupException import NextcloudBackupException + + +class Container: + + def __init__(self, name, password, backup_folder, compose_file_path) -> None: + self.__datetime = datetime.datetime.now().strftime("%Y-%m-%d_%H:%M:%S") + self.name = name + self.__password = password + self.backup_folder = backup_folder + self.compose_file_path = compose_file_path + self.__dump_file = self.name + '_' + self.__datetime + '.sql' + self.__dump_file_path = os.path.join(self.backup_folder, self.__dump_file) + self.__tar_file = self.name + '_' + self.__datetime + 'tar.gz' + self.__tar_file_path = os.path.join(self.backup_folder, self.__tar_file) + + def __str__(self) -> str: + return F"name: {self.name}\npassword: {self.__password}\ncompose_file_path: {self.compose_file_path}" + + # Create backup folder if it does not yet exist + def create_backup_dir(self): + if not os.path.isfile(self.backup_folder): + try: + os.makedirs(self.backup_folder) + except OSError as e: + raise OSError(e) + return True + else: + return False + + # Dump database + def __dump_db(self) -> bool: + try: + os.system(F"docker exec {self.name} mysqldump --password={self.__password} --all-databases > " + F"{self.__dump_file}") + return os.path.isfile(self.__dump_file) + except Exception as e: + raise Exception(e) + + # Tar config and settings folder within container and copy it into backup folder + def __export_config(self) -> bool: + try: + os.system(F"docker exec {self.name} tar -czf config_settings.tar config settings") + os.system(F"docker cp {self.name}:/var/www/html/config_settings.tar {self.__tar_file_path}") + os.system(F"docker exec {self.name} rm config_settings.tar") + return os.path.isfile(self.__tar_file_path) + except Exception as e: + raise NextcloudBackupException(e) + + # Tar database with config and settings + def __tar_db(self) -> bool: + try: + with tarfile.open(self.__tar_file_path, 'w:gz') as tarball: + tarball.add(self.__dump_file_path, arcname=self.__dump_file) + return tarball.gettarinfo(self.__dump_file).isfile() + except Exception as e: + raise NextcloudBackupException(e) + + # Set secure file permissions + def __set_file_permissions(self) -> bool: + try: + os.chmod(self.__tar_file_path, stat.S_IREAD) + return oct(os.stat(self.__tar_file_path).st_mode)[-3:] == 600 + except Exception as e: + raise NextcloudBackupException(e) + + # Create backup + def create_backup(self) -> dict: + try: + return {"database dump": self.__dump_db(), + "export config": self.__export_config(), + "include db in backup": self.__tar_db(), + "set secure backup file permissions": self.__set_file_permissions()} + except NextcloudBackupException as e: + raise NextcloudBackupException(e) + + @staticmethod + def deserialize_containers(data: dict) -> list: + containers = [] + for name, values in data['nextcloud_containers'].items(): + containers.append(Container( + name, + values['mysql_password'], + values['backup_folder'], + values['compose_file_path']) + ) + return containers diff --git a/Models/NextcloudBackupException.py b/Models/NextcloudBackupException.py new file mode 100644 index 0000000..8e22ba4 --- /dev/null +++ b/Models/NextcloudBackupException.py @@ -0,0 +1,8 @@ + +class NextcloudBackupException(Exception): + def __init__(self, message): + self.message = message + + + def __write_to_log(self): + pass diff --git a/Models/__pycache__/Backup.cpython-38.pyc b/Models/__pycache__/Backup.cpython-38.pyc new file mode 100644 index 0000000..1bc444d Binary files /dev/null and b/Models/__pycache__/Backup.cpython-38.pyc differ diff --git a/README.md b/README.md new file mode 100644 index 0000000..91dd2de --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +nextcloud_docker_scripts diff --git a/backup.py b/backup.py new file mode 100644 index 0000000..417eb52 --- /dev/null +++ b/backup.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 + +import datetime +import os +import sys +import yaml +from colorama import Fore, Style +import Models.Backup +from Models.Container import Container +from Models.NextcloudBackupException import NextcloudBackupException + + +def backup(container_names=None): + global backup_successfull + with open(r"./settings.yaml") as file: + # Load settings + settings_list = yaml.full_load(file) + log_file = settings_list['paths']['log_file'] + containers = Container.deserialize_containers(settings_list) + + if type(container_names) is list: + containers_tmp = [] + for container_name in container_names: + if container_name is str and container_name in containers: + containers_tmp.append(containers[container_name]) + else: + print(F"{Fore.RED}Cannot find configuration for {container_name} in settings.yaml{Style.RESET_ALL}") + containers = containers_tmp + + # Loop through Nextcloud container instances + container: Container + for container in containers: + # Create backup folder if it does not yet exist + if container.create_backup_dir(): + try: + print(F"{Fore.GREEN}Backup folder created under: {container.backup_folder}{Style.RESET_ALL}") + except OSError as e: + sys.exit(F"{Fore.RED}Could not create backup folder: {e.strerror}{Style.RESET_ALL}") + + print(F"Starting backup for {container.name}:") + try: + backup_successfull = True + for key, status in container.create_backup(): + if status is True: + print(F"{Fore.GREEN}{key}: success{Style.RESET_ALL}") + else: + print(F"{Fore.RED}{key}: failed{Style.RESET_ALL}") + backup_successfull = False + except NextcloudBackupException as e: + print(F"{Fore.RED}Backup for {container.name} failed!/n{e.message}{Style.RESET_ALL}") + print("---------------------------------") + if backup_successfull is True: + print(F"{Fore.GREEN}Backup for {container.name} was successful{Style.RESET_ALL}") + else: + print(F"{Fore.RED}Backup for {container.name} failed{Style.RESET_ALL}") + + + +if __name__ == '__main__': + backup(sys.argv) + diff --git a/example_settings.yml b/example_settings.yml new file mode 100644 index 0000000..45698c9 --- /dev/null +++ b/example_settings.yml @@ -0,0 +1,17 @@ +nextcloud_containers: + + instance1: # name like Nextcloud container instance + mysql_password: "password" + backup_folder: "/full/path/to/directory/" + compose_file_path: "/full/path/to/directory/" + + instance2: # name like Nextcloud container instance + mysql_password: "password" + backup_folder: "/full/path/to/directory/" + compose_file_path: "/full/path/to/directory/" + +log: + text: true + json: false + log_file: "/full/path/to/file.log" + diff --git a/main.py b/main.py new file mode 100644 index 0000000..8f91060 --- /dev/null +++ b/main.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python3 +import sys + +# check for settings permissions + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..bd7afdc --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +yaml +colorama \ No newline at end of file diff --git a/restore.py b/restore.py new file mode 100644 index 0000000..5f7ce86 --- /dev/null +++ b/restore.py @@ -0,0 +1 @@ +#!/usr/bin/env python3 \ No newline at end of file diff --git a/upgrade.py b/upgrade.py new file mode 100644 index 0000000..5f7ce86 --- /dev/null +++ b/upgrade.py @@ -0,0 +1 @@ +#!/usr/bin/env python3 \ No newline at end of file