From 7bd4927b3c0cfe5d0fef24f7f8aa76cb06240850 Mon Sep 17 00:00:00 2001 From: Craig Date: Wed, 23 Apr 2025 16:21:53 +0100 Subject: [PATCH] Update readme files to reflect changes --- CLAUDE.md | 20 ++++++++++++++++++-- README.md | 12 +++++++++--- backup.py | 50 ++++++++++++++++++++++++++++---------------------- 3 files changed, 55 insertions(+), 27 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index bf4df00..72cc418 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -13,8 +13,24 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co - **Naming**: Snake_case for variables and functions, CamelCase for classes - **Error Handling**: Prefer explicit error handling with try/except blocks - **Documentation**: Use docstrings for functions -- **Config Files**: JSON format with "outputDir", "inputs", and "ignorePatterns" keys +- **Config Files**: JSON format with all required keys ## Project Structure - `/configs/`: JSON configuration files for different backup jobs -- `backup.py`: Main script for creating tar.gz archives of specified directories \ No newline at end of file +- `backup.py`: Main script for creating tar.gz or zip archives of specified directories + +## Config File Format +```json +{ + "outputDir": "~/path/to/backups/", + "inputs": [ + "~/path/to/backup/", + "~/another/path/" + ], + "ignorePatterns": [ + ".git/", + "node_modules/" + ], + "archiveType": "tar" or "zip" +} +``` \ No newline at end of file diff --git a/README.md b/README.md index 49d6a5a..1d30ac2 100644 --- a/README.md +++ b/README.md @@ -3,16 +3,22 @@ This creates a backup on the local filesystem, but does not persist it anywhere the best solution i have to to manually upload this to proton drive. ## configure -Can add new backups to this by adding another conf file. See services.json for examples and exclude patterns for more details. +Can add new backups to this by adding another conf file. See configs directory for examples. + +Each config file should include: +- `outputDir`: Where to store the backup +- `inputs`: List of directories/files to back up +- `ignorePatterns`: List of patterns to exclude +- `archiveType`: Either "tar" (default) or "zip" (faster, lower compression) ## running Can run with ./backup.py, or create a cron job. -current crontab entry is. (every sat at 0105.) +Current crontab entry is (every sat at 0105): ``` sudo crontab -e 5 1 * * 6 /home/craig/services/simple-backup/backup.py >> /var/log/simple-backup.log 2>&1 ``` -want to change this to once a week or something, just like on sunday + ## exlude patterns Uses simple "filter" in "path", so no globbing/regexing. diff --git a/backup.py b/backup.py index d1f2255..ee2118c 100755 --- a/backup.py +++ b/backup.py @@ -16,6 +16,32 @@ def tar_filter(filters, tarinfo): def should_filter_file(filters, file_path): return any([filter_name in str(file_path) for filter_name in filters]) +def create_zip_archive(archive_path: Path, inputs: list, ignore_patterns: list): + with zipfile.ZipFile(archive_path, 'w', zipfile.ZIP_DEFLATED) as zipf: + for file_or_dir in inputs: + file_or_dir = Path(file_or_dir).expanduser().resolve() + print(f"Compressing: {file_or_dir}") + + if file_or_dir.is_file(): + if not should_filter_file(ignore_patterns, file_or_dir): + zipf.write(file_or_dir, arcname=file_or_dir.name) + else: + for root, dirs, files in os.walk(file_or_dir): + root_path = Path(root) + for file in files: + file_path = root_path / file + if not should_filter_file(ignore_patterns, file_path): + relative_path = file_path.relative_to(file_or_dir.parent) + zipf.write(file_path, arcname=str(relative_path)) + +def create_tar_archive(archive_path: Path, inputs: list, ignore_patterns: list): + filter_function = partial(tar_filter, ignore_patterns) + with tarfile.open(archive_path, "w:xz") as f: + for file_or_dir in inputs: + file_or_dir = Path(file_or_dir).expanduser().resolve() + print(f"Compressing: {file_or_dir}") + f.add(file_or_dir, filter=filter_function) + def local_backup(output_dir: Path, backup_name: str, inputs: list, ignore_patterns: list, archive_type="tar"): current_time = datetime.datetime.now().isoformat() start = time.time() @@ -23,31 +49,11 @@ def local_backup(output_dir: Path, backup_name: str, inputs: list, ignore_patter if archive_type == "zip": archive_path = output_dir / f"{backup_name}-{current_time}.zip" print(archive_path) - with zipfile.ZipFile(archive_path, 'w', zipfile.ZIP_DEFLATED) as zipf: - for file_or_dir in inputs: - file_or_dir = Path(file_or_dir).expanduser().resolve() - print(f"Compressing: {file_or_dir}") - - if file_or_dir.is_file(): - if not should_filter_file(ignore_patterns, file_or_dir): - zipf.write(file_or_dir, arcname=file_or_dir.name) - else: - for root, dirs, files in os.walk(file_or_dir): - root_path = Path(root) - for file in files: - file_path = root_path / file - if not should_filter_file(ignore_patterns, file_path): - relative_path = file_path.relative_to(file_or_dir.parent) - zipf.write(file_path, arcname=str(relative_path)) + create_zip_archive(archive_path, inputs, ignore_patterns) else: archive_path = output_dir / f"{backup_name}-{current_time}.tar.gz" - filter_function = partial(tar_filter, ignore_patterns) print(archive_path) - with tarfile.open(archive_path, "w:xz") as f: - for file_or_dir in inputs: - file_or_dir = Path(file_or_dir).expanduser().resolve() - print(f"Compressing: {file_or_dir}") - f.add(file_or_dir, filter=filter_function) + create_tar_archive(archive_path, inputs, ignore_patterns) print(f"Total time: {(time.time() - start) / 60}mins")