From b6d2235e09636e18e768d14aa03b823fd3c48529 Mon Sep 17 00:00:00 2001 From: Cyber-Syntax <115875369+Cyber-Syntax@users.noreply.github.com> Date: Mon, 13 Jan 2025 12:34:40 +0300 Subject: [PATCH 1/2] refactor!: move backup and ignore paths to config.json BREAKING CHANGES: Replaced the use of external .txt files for storing directories and ignore paths with direct management in the `config.json`. --- config_files_example/config.json | 23 +++-- example-dirs_to_backup.txt | 4 - ignore.txt | 4 - main.py | 5 + src/backup_manager.py | 164 +++++++++++++++++++++++-------- 5 files changed, 143 insertions(+), 57 deletions(-) delete mode 100644 example-dirs_to_backup.txt delete mode 100644 ignore.txt diff --git a/config_files_example/config.json b/config_files_example/config.json index 0b48df9..0856184 100644 --- a/config_files_example/config.json +++ b/config_files_example/config.json @@ -1,7 +1,18 @@ { - "backup_folder": "~/Documents/backup-for-cloud/", - "keep_backup": 1, - "keep_enc_backup": 1, - "dirs_file_path": "dirs_to_backup.txt", - "ignore_file_path": "ignore.txt" -} \ No newline at end of file + "backup_folder": "~/Documents/backup-for-cloud/", + "keep_backup": 1, + "keep_enc_backup": 1, + "dirs_to_backup": [ + "~/Documents/", + "~/dotfiles/", + "~/bare/", + "~/Pictures/", + "~/Music/" + ], + "ignore_list": [ + "~/Documents/appimages", + "~/Documents/backup-for-cloud", + "~/Documents/linuxISO" + ] +} + diff --git a/example-dirs_to_backup.txt b/example-dirs_to_backup.txt deleted file mode 100644 index 545ffc2..0000000 --- a/example-dirs_to_backup.txt +++ /dev/null @@ -1,4 +0,0 @@ -# Change to your own directories and delete this lines. -# Also don't forget to change this .txt file name to dirs_to_backup.txt -~/Documents/ -~/Documents/books diff --git a/ignore.txt b/ignore.txt deleted file mode 100644 index 3b560f3..0000000 --- a/ignore.txt +++ /dev/null @@ -1,4 +0,0 @@ -~/Documents/appimages -~/Documents/screenloyout -~/Documents/backup-for-cloud -~/Documents/linuxISO diff --git a/main.py b/main.py index 79000d2..65c3a1b 100644 --- a/main.py +++ b/main.py @@ -16,10 +16,15 @@ def main(): backup_manager = BackupManager() if not os.path.isfile(backup_manager.config_file_path): + logging.info("Configuration file not found. Creating a new one.") backup_manager.ask_inputs() backup_manager.save_credentials() else: backup_manager.load_credentials() + # Check if directories to backup are configured + if not backup_manager.dirs_to_backup: + logging.warning("No directories found in configuration. Setting up now.") + backup_manager.configure_directories() # Create a loop that will run until the user enters 6 to exit while True: diff --git a/src/backup_manager.py b/src/backup_manager.py index 9b837a2..066cfa3 100644 --- a/src/backup_manager.py +++ b/src/backup_manager.py @@ -23,32 +23,25 @@ class BackupManager: backup_folder: str = field(default_factory=lambda: "~/Documents/backup-for-cloud/") keep_backup: int = field(default_factory=lambda: 1) keep_enc_backup: int = field(default_factory=lambda: 1) - dirs_file_path: str = field(default_factory=lambda: "dirs_to_backup.txt") - ignore_file_path: str = field(default_factory=lambda: "ignore.txt") + dirs_to_backup: List[str] = field(default_factory=list) + ignore_list: List[str] = field(default_factory=list) config_file_path: str = field(init=False) - - current_date: str = datetime.datetime.now().strftime("%d-%m-%Y") backup_file_path: str = field(init=False) decrypt_file_path: str = field(init=False) - # size calculator - dirs_to_backup: List[str] = field(init=False) - ignore_list: List[str] = field(init=False) + current_date: str = datetime.datetime.now().strftime("%d-%m-%Y") def __post_init__(self): self.backup_file_path = os.path.expanduser( f"{self.backup_folder}/{self.current_date}.tar.xz" ) - # self.backup_folder = os.path.expanduser(self.backup_folder) - - # TODO: save backup folder without expanduser self.config_file_path = os.path.join( os.path.expanduser(self.backup_folder), "config_files", "config.json" ) # encryption self.decrypt_file_path = os.path.join(self.backup_folder, "decrypted.tar.xz") - # size calculator - self.dirs_to_backup = self.read_dirs_to_backup() - self.ignore_list = self.read_ignore_list() + # # size calculator + # self.dirs_to_backup = [] + # self.ignore_list = [] def ask_inputs(self): while True: @@ -75,6 +68,102 @@ def ask_inputs(self): except ValueError: print("Invalid input, please enter a valid integer.") + print("\nNow let's configure the directories for backup.") + self.configure_directories() + + def configure_directories(self): + """Interactive method to add directories to backup and ignore lists.""" + print("=== Configure Directories to Backup ===") + while True: + print("\nCurrent directories to backup:") + for idx, directory in enumerate(self.dirs_to_backup, start=1): + print(f"{idx}. {directory}") + print("\nOptions:") + print("1. Add directories (separate multiple entries with commas)") + print("2. Remove a directory") + print("3. View ignore list") + print("4. Modify ignore list") + print("5. Finish and save") + + choice = input("Choose an option (1-5): ").strip() + if choice == "1": + dirs_input = input( + "Enter directories to add (separate multiple entries with commas): " + ).strip() + new_dirs = [d.strip() for d in dirs_input.split(",") if d.strip()] + # Avoid duplicates and invalid entries + added_dirs = [d for d in new_dirs if d not in self.dirs_to_backup] + self.dirs_to_backup.extend(added_dirs) + print(f"Added directories: {', '.join(added_dirs)}") + elif choice == "2": + print("Select a directory to remove:") + for idx, directory in enumerate(self.dirs_to_backup, start=1): + print(f"{idx}. {directory}") + try: + idx_to_remove = int(input("Enter the number to remove: ").strip()) + if 0 < idx_to_remove <= len(self.dirs_to_backup): + removed_dir = self.dirs_to_backup.pop(idx_to_remove - 1) + print(f"Removed: {removed_dir}") + else: + print("Invalid selection.") + except ValueError: + print("Invalid input.") + elif choice == "3": + print("Current ignore list:") + for idx, path in enumerate(self.ignore_list, start=1): + print(f"{idx}. {path}") + elif choice == "4": + self.modify_ignore_list() + elif choice == "5": + self.save_credentials() + print("Configuration saved.") + break + else: + print("Invalid choice. Please try again.") + + def modify_ignore_list(self): + """Interactive method to add or remove paths from the ignore list.""" + print("=== Modify Ignore List ===") + while True: + print("\nCurrent ignore list:") + for idx, path in enumerate(self.ignore_list, start=1): + print(f"{idx}. {path}") + + print("\nOptions:") + print("1. Add ignore paths (separate multiple entries with commas)") + print("2. Remove an ignore path") + print("3. Finish and save") + + choice = input("Choose an option (1-3): ").strip() + if choice == "1": + ignore_input = input( + "Enter paths to ignore (separate multiple entries with commas): " + ).strip() + new_ignores = [p.strip() for p in ignore_input.split(",") if p.strip()] + # Avoid duplicates and invalid entries + added_ignores = [p for p in new_ignores if p not in self.ignore_list] + self.ignore_list.extend(added_ignores) + print(f"Added ignore paths: {', '.join(added_ignores)}") + elif choice == "2": + print("Select an ignore path to remove:") + for idx, path in enumerate(self.ignore_list, start=1): + print(f"{idx}. {path}") + try: + idx_to_remove = int(input("Enter the number to remove: ").strip()) + if 0 < idx_to_remove <= len(self.ignore_list): + removed_path = self.ignore_list.pop(idx_to_remove - 1) + print(f"Removed from ignore list: {removed_path}") + else: + print("Invalid selection.") + except ValueError: + print("Invalid input.") + elif choice == "3": + self.save_credentials() + print("Ignore list updated and saved.") + break + else: + print("Invalid choice. Please try again.") + def save_credentials(self): """Save the credentials to a file in json format from response""" @@ -86,11 +175,11 @@ def save_credentials(self): "backup_folder": self.backup_folder, "keep_backup": self.keep_backup, "keep_enc_backup": self.keep_enc_backup, - "dirs_file_path": self.dirs_file_path, - "ignore_file_path": self.ignore_file_path, + "dirs_to_backup": self.dirs_to_backup, + "ignore_list": self.ignore_list, } - with open(self.config_file_path, "w") as config_file: + with open(self.config_file_path, "w", encoding="utf-8") as config_file: json.dump(config, config_file, indent=4) print(f"Configuration file created at {self.config_file_path}") @@ -109,15 +198,15 @@ def load_credentials(self): self.backup_folder = config.get("backup_folder", self.backup_folder) self.keep_backup = config.get("keep_backup", self.keep_backup) self.keep_enc_backup = config.get("keep_enc_backup", self.keep_enc_backup) - self.dirs_file_path = config.get("dirs_file_path", self.dirs_file_path) - self.ignore_file_path = config.get( - "ignore_file_path", self.ignore_file_path - ) + self.dirs_to_backup = config.get("dirs_to_backup", self.dirs_to_backup) + self.ignore_list = config.get("ignore_list", self.ignore_list) print(f"Configuration loaded from {self.config_file_path}") print(f"Backup folder set to {self.backup_folder}") print(f"Keep backup: {self.keep_backup}") print(f"Keep encrypted backup: {self.keep_enc_backup}") + print(f"Directories to backup: {self.dirs_to_backup}") + print(f"Ignore list: {self.ignore_list}") else: print(f"Configuration file {self.config_file_path} not found.") @@ -128,22 +217,13 @@ def check_backup_exist(self) -> bool: def backup_directories(self) -> bool: """Backup the directories listed in dirs_to_backup.txt to a compressed file""" - # Read in the directories to backup from the file - dirs_to_backup: List[str] = [] - with open(self.dirs_file_path, "r", encoding="utf-8") as file: - for line in file: - directory = line.strip() - if directory: - dirs_to_backup.append(directory) + if not self.dirs_to_backup: + print( + "No directories to backup. Create directories on the config.json. Look example config.json." + ) + sys.exit() - # Read and expand paths in the ignore file - ignore_paths = [] - if os.path.isfile(self.ignore_file_path): - with open(self.ignore_file_path, "r", encoding="utf-8") as file: - for line in file: - ignore_path = line.strip() - if ignore_path: - ignore_paths.append(os.path.expanduser(ignore_path)) + ignore_paths = [os.path.expanduser(path) for path in self.ignore_list] # Generate the exclude options for the tar command exclude_options = " ".join([f"--exclude={path}" for path in ignore_paths]) @@ -152,13 +232,11 @@ def backup_directories(self) -> bool: filesystem_option = "--one-file-system" # Expand the user's home directory for each directory to backup - dir_paths = [os.path.expanduser(path) for path in dirs_to_backup] + dir_paths = [os.path.expanduser(path) for path in self.dirs_to_backup] # Calculate the total size using SizeCalculator - size_calculator = BackupManager(self.dirs_file_path, self.ignore_file_path) - total_size_bytes = size_calculator.calculate_total_backup_size( - dirs_to_backup, size_calculator.ignore_list - ) + # size_calculator = BackupManager(self.dirs_to_backup, self.ignore_list) + total_size_bytes = self.calculate_total_backup_size(dir_paths, ignore_paths) # Convert the total size to MB and GiB total_size_mb = total_size_bytes / (1024 * 1024) @@ -409,7 +487,7 @@ def delete_old_backups(self): def read_dirs_to_backup(self) -> List[str]: """Read the directories and files listed in dirs_to_backup.txt""" dirs_to_backup = [] - with open(self.dirs_file_path, "r", encoding="utf-8") as file: + with open(self.dirs_to_backup, "r", encoding="utf-8") as file: for line in file: directory = line.strip() if directory: @@ -419,8 +497,8 @@ def read_dirs_to_backup(self) -> List[str]: def read_ignore_list(self) -> List[str]: """Read the directories and files listed in ignore.txt""" ignore_list = [] - if os.path.isfile(self.ignore_file_path): - with open(self.ignore_file_path, "r", encoding="utf-8") as file: + if os.path.isfile(self.ignore_list): + with open(self.ignore_list, "r", encoding="utf-8") as file: for line in file: ignore_path = line.strip() if ignore_path: From 887701d8d99f6b1ab8859d90e839c05af16f297e Mon Sep 17 00:00:00 2001 From: Cyber-Syntax <115875369+Cyber-Syntax@users.noreply.github.com> Date: Mon, 13 Jan 2025 12:56:51 +0300 Subject: [PATCH 2/2] docs: update readme.md --- README.md | 18 ++++++++++++++---- README.tr.md | 16 ++++++++++++---- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 89a01e8..d7854ee 100644 --- a/README.md +++ b/README.md @@ -50,17 +50,27 @@ source .venv/bin/activate ``` -5. You need do before start: +5. Config File Handling: + To customize your backup settings, you can use a `config.json` file. This file allows you to specify: - - You need to change example directories on the **example-dirs_to_backup.txt** and rename it to **dirs_to_backup.txt**. +- Backup folder location, Directories to back up, Directories to ignore, Number of tar.xz and tar.xz.enc files to keep + **Creating a Config File:** + You have two options: -6. Start the script: +1. **Run the script and follow the on-screen instructions**. This will guide you through creating a `config.json` file. +2. **Use an example config file (Optional)**: + + - Copy the example configuration from `config_files_example/config.json` + - Paste it into your `backup_folder/config_files/config.json` location (e.g `~/Documents/backup-for-cloud/config_files/config.json) + - Modify it as needed + +3. Start the script: ```bash python3 main.py ``` -7. Follow the on-screen instructions. +4. Follow the on-screen instructions. --- diff --git a/README.tr.md b/README.tr.md index 1796148..fffa254 100644 --- a/README.tr.md +++ b/README.tr.md @@ -50,17 +50,25 @@ source .venv/bin/activate ``` -5. Başlamadan önce yapmanız gerekenler: +5. Yapılandırma dosyası ayarlama: + Yedeklemeye ait ayarlarınızı özelleştirmek için, bir `config.json` dosyasını kullanabilirsiniz. Bu dosya size şunları belirtmenizi sağlar: - - **example-dirs_to_backup.txt** dosyasındaki örnek **dizinleri** değiştirmelisiniz ve adını **dirs_to_backup.txt** olarak değiştirmelisiniz. +- Yedekleme klasörünün konumu, Geri yüklenecek dizinler, İlgisiz bırakılacak dizinler, Saklanacak tar.xz ve tar.xz.enc dosyalarının sayısı + **Ayar Dosyası Oluşturma:** + İki seçeneğiniz vardır: -6. Script'i başlatın: +1. **Senaryoyu çalıştır ve ekrandaki talimatları takip et**. Bu size bir `config.json` dosyası oluşturmayı kılavuzlayacaktır. +2. **Örnek Ayar Dosyasını Kullan (Opsiyonel)**: + - Örnek konfigürasyonunuzu `config_files_example/config.json` konumundan kopyalayın + - Bu dosyayı `backup_folder/config_files/config.json` konumuna yapıştırın (örn. `~/Documents/backup-for-cloud/config_files/config.json`) + - Gereksinim duyduğunuz kadarını değiştirin +3. Script'i başlatın: ```bash python3 main.py ``` -7. Ekrandaki talimatları izleyin. +4. Ekrandaki talimatları izleyin. ---