dar-backup 0.5.8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
dar_backup/.darrc ADDED
@@ -0,0 +1,108 @@
1
+ # Default configuration file for dar
2
+ # Place this file in the user's home directory as .darrc or specify it with the -B option
3
+
4
+ extract:
5
+ # don't restore File Specific Attributes
6
+ #--fsa-scope none
7
+
8
+ # ignore owner, useful when used by a non-privileged user
9
+ --comparison-field=ignore-owner
10
+
11
+ # First setting case insensitive mode on:
12
+ -an
13
+ -ag
14
+
15
+
16
+ # Exclude specific file types from compression
17
+ compress-exclusion:
18
+ -Z *.gz
19
+ -Z *.bz2
20
+ -Z *.xz
21
+ -Z *.zip
22
+ -Z *.rar
23
+ -Z *.7z
24
+ -Z *.tar
25
+ -Z *.tgz
26
+ -Z *.tbz2
27
+ -Z *.txz
28
+ # Exclude common image file types from compression
29
+ -Z *.jpg
30
+ -Z *.jpeg
31
+ -Z *.png
32
+ -Z *.gif
33
+ -Z *.bmp
34
+ -Z *.tiff
35
+ -Z *.svg
36
+ # Exclude common movie file types from compression
37
+ -Z *.mp4
38
+ -Z *.avi
39
+ -Z *.mkv
40
+ -Z *.mov
41
+ -Z *.wmv
42
+ -Z *.flv
43
+ -Z *.mpeg
44
+ -Z *.mpg
45
+
46
+ # These are zip files. Not all are compressed, but considering that they can
47
+ # get quite large it is probably more prudent to leave this uncommented.
48
+ -Z "*.pk3"
49
+ -Z "*.zip"
50
+ # You can get better compression on these files, but then you should be
51
+ # de/recompressing with an actual program, not dar.
52
+ -Z "*.lz4"
53
+ -Z "*.zoo"
54
+
55
+ # Other, in alphabetical order.
56
+ -Z "*.Po"
57
+ -Z "*.aar"
58
+ -Z "*.bx"
59
+ -Z "*.chm"
60
+ -Z "*.doc"
61
+ -Z "*.epub"
62
+ -Z "*.f3d"
63
+ -Z "*.gpg"
64
+ -Z "*.htmlz"
65
+ -Z "*.iix"
66
+ -Z "*.iso"
67
+ -Z "*.jin"
68
+ -Z "*.ods"
69
+ -Z "*.odt"
70
+ -Z "*.ser"
71
+ -Z "*.svgz"
72
+ -Z "*.swx"
73
+ -Z "*.sxi"
74
+ -Z "*.whl"
75
+ -Z "*.wings"
76
+
77
+
78
+ # Dar archives (may be compressed).
79
+ -Z "*.dar"
80
+
81
+ # Now we swap back to case sensitive mode for masks which is the default
82
+ # mode:
83
+ -acase
84
+
85
+
86
+ ##############################################################
87
+ # target: verbose
88
+ # remove comments belov for dar being more verbose
89
+ verbose:
90
+
91
+ # -vt show files teated due to filtering inclusion or no filtering at all
92
+ # -vt
93
+
94
+ # -vs show skipped files du to exclusion
95
+ # -vs
96
+
97
+ # -vd show diretory currently being processed
98
+ # -vd
99
+
100
+ # -vm show detailed messages, not related to files and directories
101
+ # -vm
102
+
103
+ # -vf show summary of each treated directory, including average compression
104
+ # -vf
105
+
106
+ # -va equivalent to "-vm -vs -vt"
107
+ # -va
108
+
dar_backup/cleanup.py ADDED
@@ -0,0 +1,197 @@
1
+ #!/usr/bin/env python3
2
+
3
+ """
4
+ cleanup.py source code is here: https://github.com/per2jensen/dar-backup
5
+
6
+ Licensed under GNU GENERAL PUBLIC LICENSE v3, see the supplied file "LICENSE" for details.
7
+
8
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW,
9
+ not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10
+ See section 15 and section 16 in the supplied "LICENSE" file
11
+
12
+ This script removes old DIFF and INCR archives + accompanying .par2 files according to the
13
+ [AGE] settings in the configuration file.
14
+ """
15
+
16
+
17
+ import argparse
18
+ import logging
19
+ import os
20
+ import re
21
+ import sys
22
+
23
+ from datetime import datetime, timedelta
24
+ from time import time
25
+
26
+ from dar_backup.config_settings import ConfigSettings
27
+ from dar_backup.util import extract_error_lines
28
+ from dar_backup.util import list_backups
29
+ from dar_backup.util import setup_logging
30
+
31
+ VERSION = "aplha-0.5"
32
+
33
+ logger = None
34
+
35
+ def delete_old_backups(backup_dir, age, backup_type, backup_definition=None):
36
+ """
37
+ Delete backups older than the specified age in days.
38
+ Only .dar and .par2 files are considered for deletion.
39
+ """
40
+ logger.info(f"Deleting {backup_type} backups older than {age} days in {backup_dir} for backup definition: {backup_definition}")
41
+
42
+ if backup_type not in ['DIFF', 'INCR']:
43
+ logger.error(f"Invalid backup type: {backup_type}")
44
+ return
45
+
46
+ now = datetime.now()
47
+ cutoff_date = now - timedelta(days=age)
48
+
49
+ for filename in sorted(os.listdir(backup_dir)):
50
+ if not (filename.endswith('.dar') or filename.endswith('.par2')):
51
+ continue
52
+ if backup_definition and not filename.startswith(backup_definition):
53
+ continue
54
+ if backup_type in filename:
55
+ try:
56
+ date_str = filename.split(f"_{backup_type}_")[1].split('.')[0]
57
+ file_date = datetime.strptime(date_str, '%Y-%m-%d')
58
+ except Exception as e:
59
+ logger.error(f"Error parsing date from filename {filename}: {e}")
60
+ raise
61
+
62
+ if file_date < cutoff_date:
63
+ file_path = os.path.join(backup_dir, filename)
64
+ try:
65
+ os.remove(file_path)
66
+ logger.info(f"Deleted {backup_type} backup: {file_path}")
67
+ except Exception as e:
68
+ logger.error(f"Error deleting file {file_path}: {e}")
69
+
70
+
71
+ def delete_archives(backup_dir, archive_name):
72
+ """
73
+ Delete all .dar and .par2 files in the backup directory for the given archive name.
74
+
75
+ This function will delete any type of archive, including FULL.
76
+ """
77
+ logger.info(f"Deleting all .dar and .par2 files for archive: `{archive_name}`")
78
+ # Regex to match the archive files according to the naming convention
79
+ archive_regex = re.compile(rf"^{re.escape(archive_name)}\.[0-9]+\.dar$")
80
+
81
+ # Delete the specified .dar files according to the naming convention
82
+ files_deleted = False
83
+ for filename in sorted(os.listdir(backup_dir)):
84
+ if archive_regex.match(filename):
85
+ file_path = os.path.join(backup_dir, filename)
86
+ try:
87
+ os.remove(file_path)
88
+ logger.info(f"Deleted archive slice: {file_path}")
89
+ files_deleted = True
90
+ except Exception as e:
91
+ logger.error(f"Error deleting archive slice {file_path}: {e}")
92
+
93
+ if not files_deleted:
94
+ logger.info("No .dar files matched the regex for deletion.")
95
+
96
+ # Delete associated .par2 files
97
+ par2_regex = re.compile(rf"^{re.escape(archive_name)}\.[0-9]+\.dar.*\.par2$")
98
+ files_deleted = False
99
+ for filename in sorted(os.listdir(backup_dir)):
100
+ if par2_regex.match(filename):
101
+ file_path = os.path.join(backup_dir, filename)
102
+ try:
103
+ os.remove(file_path)
104
+ logger.info(f"Deleted PAR2 file: {file_path}")
105
+ files_deleted = True
106
+ except Exception as e:
107
+ logger.error(f"Error deleting PAR2 file {file_path}: {e}")
108
+
109
+ if not files_deleted:
110
+ logger.info("No .par2 matched the regex for deletion.")
111
+
112
+
113
+ def show_version():
114
+ script_name = os.path.basename(sys.argv[0])
115
+ print(f"{script_name} {VERSION}")
116
+ print('''Licensed under GNU GENERAL PUBLIC LICENSE v3, see the supplied file "LICENSE" for details.
117
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW, not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
118
+ See section 15 and section 16 in the supplied "LICENSE" file.''')
119
+
120
+ def main():
121
+
122
+
123
+ global logger
124
+
125
+ parser = argparse.ArgumentParser(description="Cleanup old backup files.")
126
+ parser.add_argument('--backup-definition', '-d', help="Specific backup definition to clean.")
127
+ parser.add_argument('--config-file', '-c', type=str, help="Path to 'dar-backup.conf'", default='~/.config/dar-backup/dar-backup.conf')
128
+ parser.add_argument('--version', '-v', action='store_true', help="Show version information.")
129
+ parser.add_argument('--alternate-archive-dir', type=str, help="Cleanup in this directory instead of the default one.")
130
+ parser.add_argument('--cleanup-specific-archive', type=str, help="Force delete all .dar and .par2 files in the backup directory for given archive name")
131
+ parser.add_argument('--list', action='store_true', help="List available archives.")
132
+ parser.add_argument('--verbose', action='store_true', help="Print various status messages to screen")
133
+ args = parser.parse_args()
134
+
135
+ args.config_file = os.path.expanduser(args.config_file)
136
+
137
+
138
+ if args.version:
139
+ show_version()
140
+ sys.exit(0)
141
+
142
+ config_settings = ConfigSettings(args.config_file)
143
+
144
+ start_time=int(time())
145
+ logger = setup_logging(config_settings.logfile_location, logging.INFO)
146
+ logger.info(f"=====================================")
147
+ logger.info(f"cleanup.py started, version: {VERSION}")
148
+ logger.info(f"START TIME: {start_time}")
149
+ logger.debug(f"`args`:\n{args}")
150
+ logger.debug(f"`config_settings`:\n{config_settings}")
151
+
152
+ current_dir = os.path.normpath(os.path.dirname(__file__))
153
+ args.verbose and (print(f"Current directory: {current_dir}"))
154
+ args.verbose and (print(f"Config file: {args.config_file}"))
155
+ args.verbose and (print(f"Backup dir: {config_settings.backup_dir}"))
156
+ args.verbose and (print(f"Logfile location: {config_settings.logfile_location}"))
157
+ args.verbose and (print(f"--alternate-archive-dir: {args.alternate_archive_dir}"))
158
+ args.verbose and (print(f"--cleanup-specific-archive: {args.cleanup_specific_archive}"))
159
+
160
+
161
+ if args.alternate_archive_dir:
162
+ config_settings.backup_dir = args.alternate_archive_dir
163
+
164
+ if args.cleanup_specific_archive:
165
+ delete_archives(config_settings.backup_dir, args.cleanup_specific_archive)
166
+ sys.exit(0)
167
+ elif args.list:
168
+ list_backups(config_settings.backup_dir, args.backup_definition)
169
+ else:
170
+ backup_definitions = []
171
+ if args.backup_definition:
172
+ backup_definitions.append(args.backup_definition)
173
+ else:
174
+ for root, _, files in os.walk(config_settings.backup_d_dir):
175
+ for file in files:
176
+ backup_definitions.append(file.split('.')[0])
177
+
178
+ for definition in backup_definitions:
179
+ delete_old_backups(config_settings.backup_dir, config_settings.diff_age, 'DIFF', definition)
180
+ delete_old_backups(config_settings.backup_dir, config_settings.incr_age, 'INCR', definition)
181
+
182
+
183
+ end_time=int(time())
184
+ logger.info(f"END TIME: {end_time}")
185
+
186
+ error_lines = extract_error_lines(config_settings.logfile_location, start_time, end_time)
187
+ if len(error_lines) > 0:
188
+ args.verbose and print("\033[1m\033[31mErrors\033[0m encountered")
189
+ for line in error_lines:
190
+ print(line)
191
+ sys.exit(1)
192
+ else:
193
+ args.verbose and print("\033[1m\033[32mSUCCESS\033[0m No errors encountered")
194
+ sys.exit(0)
195
+
196
+ if __name__ == "__main__":
197
+ main()
@@ -0,0 +1,65 @@
1
+ from dataclasses import dataclass, field
2
+ import configparser
3
+ from pathlib import Path
4
+ import sys
5
+ import logging
6
+
7
+ @dataclass
8
+ class ConfigSettings:
9
+ """
10
+ A dataclass for holding configuration settings, initialized from a configuration file.
11
+
12
+ Attributes:
13
+ logfile_location (str): The location of the log file.
14
+ max_size_verification_mb (int): The maximum size for verification in megabytes.
15
+ min_size_verification_mb (int): The minimum size for verification in megabytes.
16
+ no_files_verification (int): The number of files for verification.
17
+ backup_dir (str): The directory for backups.
18
+ test_restore_dir (str): The directory for test restores.
19
+ backup_d_dir (str): The directory for backup.d.
20
+ diff_age (int): The age for differential backups before deletion.
21
+ incr_age (int): The age for incremental backups before deletion.
22
+ """
23
+
24
+ def __init__(self, config_file: str):
25
+ """
26
+ Initializes the ConfigSettings instance by reading the specified configuration file.
27
+
28
+ Args:
29
+ config_file (str): The path to the configuration file.
30
+ """
31
+ self.config = configparser.ConfigParser()
32
+ try:
33
+ self.config.read(config_file)
34
+ self.logfile_location = self.config['MISC']['LOGFILE_LOCATION']
35
+ self.max_size_verification_mb = int(self.config['MISC']['MAX_SIZE_VERIFICATION_MB'])
36
+ self.min_size_verification_mb = int(self.config['MISC']['MIN_SIZE_VERIFICATION_MB'])
37
+ self.no_files_verification = int(self.config['MISC']['NO_FILES_VERIFICATION'])
38
+ self.backup_dir = self.config['DIRECTORIES']['BACKUP_DIR']
39
+ self.test_restore_dir = self.config['DIRECTORIES']['TEST_RESTORE_DIR']
40
+ self.backup_d_dir = self.config['DIRECTORIES']['BACKUP.D_DIR']
41
+ self.diff_age = int(self.config['AGE']['DIFF_AGE'])
42
+ self.incr_age = int(self.config['AGE']['INCR_AGE'])
43
+ self.error_correction_percent = int(self.config['PAR2']['ERROR_CORRECTION_PERCENT'])
44
+
45
+ # Ensure the directories exist
46
+ Path(self.backup_dir).mkdir(parents=True, exist_ok=True)
47
+ Path(self.test_restore_dir).mkdir(parents=True, exist_ok=True)
48
+ Path(self.backup_d_dir).mkdir(parents=True, exist_ok=True)
49
+
50
+ except FileNotFoundError as e:
51
+ logging.error(f"Configuration file not found: {config_file}")
52
+ logging.error(f"Error details: {e}")
53
+ sys.exit("Error: Configuration file not found.")
54
+ except PermissionError as e:
55
+ logging.error(f"Permission error while reading config file {config_file}")
56
+ logging.error(f"Error details: {e}")
57
+ sys.exit("Error: Permission error while reading config file.")
58
+ except KeyError as e:
59
+ logging.error(f"Missing mandatory configuration key: {e}")
60
+ logging.error(f"Error details: {e}")
61
+ sys.exit(f"Error: Missing mandatory configuration key: {e}.")
62
+ except Exception as e:
63
+ logging.exception(f"Unexpected error reading config file {config_file}: {e}")
64
+ logging.error(f"Error details: {e}")
65
+ sys.exit(f"Unexpected error reading config file: {e}.")