dar-backup 0.6.13.1__py3-none-any.whl → 0.6.15__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/__about__.py +1 -1
- dar_backup/dar_backup.py +136 -82
- dar_backup/manager.py +1 -1
- dar_backup/util.py +1 -1
- {dar_backup-0.6.13.1.dist-info → dar_backup-0.6.15.dist-info}/METADATA +3 -1
- dar_backup-0.6.15.dist-info/RECORD +16 -0
- dar_backup-0.6.13.1.dist-info/RECORD +0 -16
- {dar_backup-0.6.13.1.dist-info → dar_backup-0.6.15.dist-info}/WHEEL +0 -0
- {dar_backup-0.6.13.1.dist-info → dar_backup-0.6.15.dist-info}/entry_points.txt +0 -0
- {dar_backup-0.6.13.1.dist-info → dar_backup-0.6.15.dist-info}/licenses/LICENSE +0 -0
dar_backup/__about__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.6.
|
|
1
|
+
__version__ = "0.6.15"
|
dar_backup/dar_backup.py
CHANGED
|
@@ -27,14 +27,14 @@ from dar_backup.config_settings import ConfigSettings
|
|
|
27
27
|
from dar_backup.util import list_backups
|
|
28
28
|
from dar_backup.util import run_command
|
|
29
29
|
from dar_backup.util import setup_logging
|
|
30
|
+
from dar_backup.util import get_logger
|
|
30
31
|
from dar_backup.util import BackupError
|
|
31
32
|
from dar_backup.util import RestoreError
|
|
32
33
|
|
|
33
34
|
|
|
34
|
-
RESULT = True
|
|
35
35
|
logger = None
|
|
36
36
|
|
|
37
|
-
def generic_backup(type: str, command: List[str], backup_file: str, backup_definition: str, darrc: str, config_settings: ConfigSettings, args: argparse.Namespace):
|
|
37
|
+
def generic_backup(type: str, command: List[str], backup_file: str, backup_definition: str, darrc: str, config_settings: ConfigSettings, args: argparse.Namespace) -> List[str]:
|
|
38
38
|
"""
|
|
39
39
|
Performs a backup using the 'dar' command.
|
|
40
40
|
|
|
@@ -54,10 +54,14 @@ def generic_backup(type: str, command: List[str], backup_file: str, backup_defin
|
|
|
54
54
|
config_settings (ConfigSettings): An instance of the ConfigSettings class.
|
|
55
55
|
|
|
56
56
|
|
|
57
|
-
|
|
58
57
|
Raises:
|
|
59
|
-
BackupError: If an error occurs during the backup process.
|
|
58
|
+
BackupError: If an error leading to a bad backup occurs during the backup process.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
List of tuples (<msg>, <exit_code>) of errors not considered critical enough for raising an exception
|
|
60
62
|
"""
|
|
63
|
+
result: List[tuple] = []
|
|
64
|
+
|
|
61
65
|
logger.info(f"===> Starting {type} backup for {backup_definition}")
|
|
62
66
|
logger.info(f"Running command: {' '.join(map(shlex.quote, command))}")
|
|
63
67
|
try:
|
|
@@ -75,7 +79,11 @@ def generic_backup(type: str, command: List[str], backup_file: str, backup_defin
|
|
|
75
79
|
if command_result.returncode == 0:
|
|
76
80
|
logger.info(f"Catalog for archive '{backup_file}' added successfully to its manager.")
|
|
77
81
|
else:
|
|
78
|
-
|
|
82
|
+
msg = f"Catalog for archive '{backup_file}' not added."
|
|
83
|
+
logger.error(msg)
|
|
84
|
+
result.append((msg, 1))
|
|
85
|
+
|
|
86
|
+
return result
|
|
79
87
|
|
|
80
88
|
except subprocess.CalledProcessError as e:
|
|
81
89
|
logger.error(f"Backup command failed: {e}")
|
|
@@ -83,33 +91,40 @@ def generic_backup(type: str, command: List[str], backup_file: str, backup_defin
|
|
|
83
91
|
except Exception as e:
|
|
84
92
|
logger.exception(f"Unexpected error during backup")
|
|
85
93
|
raise BackupError(f"Unexpected error during backup: {e}") from e
|
|
86
|
-
|
|
94
|
+
|
|
87
95
|
|
|
88
96
|
|
|
89
|
-
def find_files_with_paths(
|
|
97
|
+
def find_files_with_paths(xml_doc: str):
|
|
90
98
|
"""
|
|
91
|
-
Finds files within an XML element and returns a list of file
|
|
99
|
+
Finds files within an XML element and returns a list of tuples (file path, size).
|
|
92
100
|
|
|
93
101
|
Args:
|
|
94
|
-
xml_root
|
|
102
|
+
xml_root: str The XML generated by dar -l <archive> -Txml.
|
|
95
103
|
|
|
96
104
|
Returns:
|
|
97
|
-
list: A list of tuples
|
|
105
|
+
list: A list of tuples (file path, size).
|
|
98
106
|
"""
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
107
|
+
#get_logger().debug("Generating list of tuples with file paths and sizes for File elements in dar xml output")
|
|
108
|
+
xml_doc = re.sub(r'<!DOCTYPE[^>]*>', '', xml_doc)
|
|
109
|
+
root = ET.fromstring(xml_doc)
|
|
102
110
|
|
|
103
|
-
|
|
104
|
-
if elem.tag == "Directory":
|
|
105
|
-
current_path.append(elem.get('name'))
|
|
106
|
-
elif elem.tag == "File":
|
|
107
|
-
file_path = ("/".join(current_path + [elem.get('name')]), elem.get('size'))
|
|
108
|
-
files.append(file_path)
|
|
109
|
-
elif elem.tag == "Directory" and elem.get('Name') in current_path:
|
|
110
|
-
current_path.pop()
|
|
111
|
+
files_list = []
|
|
111
112
|
|
|
112
|
-
|
|
113
|
+
def iterate_dir(element, current_path=""):
|
|
114
|
+
for child in element:
|
|
115
|
+
if child.tag == 'Directory':
|
|
116
|
+
dir_name = child.get('name')
|
|
117
|
+
new_path = f"{current_path}/{dir_name}" if current_path else dir_name
|
|
118
|
+
iterate_dir(child, new_path)
|
|
119
|
+
|
|
120
|
+
elif child.tag == 'File':
|
|
121
|
+
file_name = child.get('name')
|
|
122
|
+
file_size = child.get('size')
|
|
123
|
+
file_path = f"{current_path}/{file_name}" if current_path else file_name
|
|
124
|
+
files_list.append((file_path, file_size))
|
|
125
|
+
|
|
126
|
+
iterate_dir(root)
|
|
127
|
+
return files_list
|
|
113
128
|
|
|
114
129
|
|
|
115
130
|
def find_files_between_min_and_max_size(backed_up_files: list[(str, str)], config_settings: ConfigSettings):
|
|
@@ -177,7 +192,7 @@ def verify(args: argparse.Namespace, backup_file: str, backup_definition: str, c
|
|
|
177
192
|
"""
|
|
178
193
|
result = True
|
|
179
194
|
command = ['dar', '-t', backup_file, '-Q']
|
|
180
|
-
logger.
|
|
195
|
+
logger.debug(f"Running command: {' '.join(map(shlex.quote, command))}")
|
|
181
196
|
process = run_command(command, config_settings.command_timeout_secs)
|
|
182
197
|
if process.returncode == 0:
|
|
183
198
|
logger.info("Archive integrity test passed.")
|
|
@@ -201,8 +216,9 @@ def verify(args: argparse.Namespace, backup_file: str, backup_definition: str, c
|
|
|
201
216
|
root_path = None
|
|
202
217
|
for line in backup_definition_content:
|
|
203
218
|
line = line.strip()
|
|
204
|
-
|
|
205
|
-
|
|
219
|
+
match = re.match(r'^\s*-R\s+(.*)', line)
|
|
220
|
+
if match:
|
|
221
|
+
root_path = match.group(1).strip()
|
|
206
222
|
break
|
|
207
223
|
if root_path is None:
|
|
208
224
|
msg = f"No Root (-R) path found in the backup definition file: '{backup_definition}', restore verification skipped"
|
|
@@ -216,18 +232,17 @@ def verify(args: argparse.Namespace, backup_file: str, backup_definition: str, c
|
|
|
216
232
|
random_files = random.sample(files, no_files_verification)
|
|
217
233
|
for restored_file_path in random_files:
|
|
218
234
|
try:
|
|
219
|
-
logger.info(f"Restoring file: '{restored_file_path}' from backup to: '{config_settings.test_restore_dir}' for file comparing")
|
|
235
|
+
args.verbose and logger.info(f"Restoring file: '{restored_file_path}' from backup to: '{config_settings.test_restore_dir}' for file comparing")
|
|
220
236
|
command = ['dar', '-x', backup_file, '-g', restored_file_path.lstrip("/"), '-R', config_settings.test_restore_dir, '-Q', '-B', args.darrc, 'restore-options']
|
|
221
|
-
logger.info(f"Running command: {' '.join(map(shlex.quote, command))}")
|
|
237
|
+
args.verbose and logger.info(f"Running command: {' '.join(map(shlex.quote, command))}")
|
|
222
238
|
process = run_command(command, config_settings.command_timeout_secs)
|
|
223
239
|
if process.returncode != 0:
|
|
224
240
|
raise Exception(str(process))
|
|
225
241
|
|
|
226
242
|
if filecmp.cmp(os.path.join(config_settings.test_restore_dir, restored_file_path.lstrip("/")), os.path.join(root_path, restored_file_path.lstrip("/")), shallow=False):
|
|
227
|
-
logger.info(f"Success: file '{restored_file_path}' matches the original")
|
|
243
|
+
args.verbose and logger.info(f"Success: file '{restored_file_path}' matches the original")
|
|
228
244
|
else:
|
|
229
|
-
|
|
230
|
-
result = False
|
|
245
|
+
raise BackupError(f"Failure: file '{restored_file_path}' did not match the original")
|
|
231
246
|
except PermissionError:
|
|
232
247
|
result = False
|
|
233
248
|
logger.exception(f"Permission error while comparing files, continuing....")
|
|
@@ -246,6 +261,7 @@ def restore_backup(backup_name: str, config_settings: ConfigSettings, restore_di
|
|
|
246
261
|
restore_dir (str): The directory where the backup should be restored to.
|
|
247
262
|
selection (str, optional): A selection criteria to restore specific files or directories. Defaults to None.
|
|
248
263
|
"""
|
|
264
|
+
results: List[tuple] = []
|
|
249
265
|
try:
|
|
250
266
|
backup_file = os.path.join(config_settings.backup_dir, backup_name)
|
|
251
267
|
command = ['dar', '-x', backup_file, '-Q', '-D']
|
|
@@ -274,6 +290,7 @@ def restore_backup(backup_name: str, config_settings: ConfigSettings, restore_di
|
|
|
274
290
|
except Exception as e:
|
|
275
291
|
raise RestoreError(f"Unexpected error during restore: {e}") from e
|
|
276
292
|
|
|
293
|
+
return results
|
|
277
294
|
|
|
278
295
|
|
|
279
296
|
def get_backed_up_files(backup_name: str, backup_dir: str):
|
|
@@ -287,20 +304,14 @@ def get_backed_up_files(backup_name: str, backup_dir: str):
|
|
|
287
304
|
Returns:
|
|
288
305
|
list: A list of file paths for all backed up files in the DAR archive.
|
|
289
306
|
"""
|
|
290
|
-
logger.debug(f"Getting backed up files from DAR archive
|
|
307
|
+
logger.debug(f"Getting backed up files in xml from DAR archive: '{backup_name}'")
|
|
291
308
|
backup_path = os.path.join(backup_dir, backup_name)
|
|
292
309
|
try:
|
|
293
310
|
command = ['dar', '-l', backup_path, '-am', '-as', "-Txml" , '-Q']
|
|
294
|
-
logger.
|
|
295
|
-
|
|
311
|
+
logger.debug(f"Running command: {' '.join(map(shlex.quote, command))}")
|
|
312
|
+
command_result = run_command(command)
|
|
296
313
|
# Parse the XML data
|
|
297
|
-
|
|
298
|
-
output = None # help gc
|
|
299
|
-
# Extract full paths and file size for all <file> elements
|
|
300
|
-
file_paths = find_files_with_paths(root)
|
|
301
|
-
root = None # help gc
|
|
302
|
-
logger.trace(str(process))
|
|
303
|
-
logger.trace(file_paths)
|
|
314
|
+
file_paths = find_files_with_paths(command_result.stdout)
|
|
304
315
|
return file_paths
|
|
305
316
|
except subprocess.CalledProcessError as e:
|
|
306
317
|
logger.error(f"Error listing backed up files from DAR archive: '{backup_name}'")
|
|
@@ -371,7 +382,7 @@ def create_backup_command(backup_type: str, backup_file: str, darrc: str, backup
|
|
|
371
382
|
|
|
372
383
|
|
|
373
384
|
|
|
374
|
-
def perform_backup(args: argparse.Namespace, config_settings: ConfigSettings, backup_type: str):
|
|
385
|
+
def perform_backup(args: argparse.Namespace, config_settings: ConfigSettings, backup_type: str) -> List[str]:
|
|
375
386
|
"""
|
|
376
387
|
Perform backup operation.
|
|
377
388
|
|
|
@@ -379,21 +390,27 @@ def perform_backup(args: argparse.Namespace, config_settings: ConfigSettings, ba
|
|
|
379
390
|
args: Command-line arguments.
|
|
380
391
|
config_settings: An instance of the ConfigSettings class.
|
|
381
392
|
backup_type: Type of backup (FULL, DIFF, INCR).
|
|
393
|
+
|
|
394
|
+
Returns:
|
|
395
|
+
List[tuples] - each tuple consists of (<str message>, <exit code>)
|
|
382
396
|
"""
|
|
383
|
-
logger.debug(f"perform_backup({backup_type}) started")
|
|
384
397
|
backup_definitions = []
|
|
398
|
+
results: List[tuple] = []
|
|
385
399
|
|
|
386
400
|
# Gather backup definitions
|
|
387
401
|
if args.backup_definition:
|
|
388
402
|
if '_' in args.backup_definition:
|
|
389
|
-
|
|
390
|
-
|
|
403
|
+
msg = f"Skipping backup definition: '{args.backup_definition}' due to '_' in name"
|
|
404
|
+
logger.error(msg)
|
|
405
|
+
return results.append((msg, 1))
|
|
391
406
|
backup_definitions.append((os.path.basename(args.backup_definition).split('.')[0], os.path.join(config_settings.backup_d_dir, args.backup_definition)))
|
|
392
407
|
else:
|
|
393
408
|
for root, _, files in os.walk(config_settings.backup_d_dir):
|
|
394
409
|
for file in files:
|
|
395
410
|
if '_' in file:
|
|
396
|
-
|
|
411
|
+
msg = f"Skipping backup definition: '{file} due to '_' in: name"
|
|
412
|
+
logger.error(msg)
|
|
413
|
+
results.append((msg, 1))
|
|
397
414
|
continue
|
|
398
415
|
backup_definitions.append((file.split('.')[0], os.path.join(root, file)))
|
|
399
416
|
|
|
@@ -403,7 +420,9 @@ def perform_backup(args: argparse.Namespace, config_settings: ConfigSettings, ba
|
|
|
403
420
|
backup_file = os.path.join(config_settings.backup_dir, f"{backup_definition}_{backup_type}_{date}")
|
|
404
421
|
|
|
405
422
|
if os.path.exists(backup_file + '.1.dar'):
|
|
406
|
-
|
|
423
|
+
msg = f"Backup file {backup_file}.1.dar already exists. Skipping backup [1]."
|
|
424
|
+
logger.error(msg)
|
|
425
|
+
results.append((msg, 1))
|
|
407
426
|
continue
|
|
408
427
|
|
|
409
428
|
latest_base_backup = None
|
|
@@ -414,15 +433,18 @@ def perform_backup(args: argparse.Namespace, config_settings: ConfigSettings, ba
|
|
|
414
433
|
latest_base_backup = os.path.join(config_settings.backup_dir, args.alternate_reference_archive)
|
|
415
434
|
logger.info(f"Using alternate reference archive: {latest_base_backup}")
|
|
416
435
|
if not os.path.exists(latest_base_backup + '.1.dar'):
|
|
417
|
-
|
|
418
|
-
|
|
436
|
+
msg = f"Alternate reference archive: \"{latest_base_backup}.1.dar\" does not exist, exiting..."
|
|
437
|
+
logger.error(msg)
|
|
438
|
+
results.append((msg, 1))
|
|
439
|
+
return results
|
|
419
440
|
else:
|
|
420
441
|
base_backups = sorted(
|
|
421
442
|
[f for f in os.listdir(config_settings.backup_dir) if f.startswith(f"{backup_definition}_{base_backup_type}_") and f.endswith('.1.dar')],
|
|
422
443
|
key=lambda x: datetime.strptime(x.split('_')[-1].split('.')[0], '%Y-%m-%d')
|
|
423
444
|
)
|
|
424
445
|
if not base_backups:
|
|
425
|
-
|
|
446
|
+
msg = f"No {base_backup_type} backup found for {backup_definition}. Skipping {backup_type} backup."
|
|
447
|
+
results.append((msg, 1))
|
|
426
448
|
continue
|
|
427
449
|
latest_base_backup = os.path.join(config_settings.backup_dir, base_backups[-1].rsplit('.', 2)[0])
|
|
428
450
|
|
|
@@ -430,26 +452,27 @@ def perform_backup(args: argparse.Namespace, config_settings: ConfigSettings, ba
|
|
|
430
452
|
command = create_backup_command(backup_type, backup_file, args.darrc, backup_definition_path, latest_base_backup)
|
|
431
453
|
|
|
432
454
|
# Perform backup
|
|
433
|
-
generic_backup(backup_type, command, backup_file, backup_definition_path, args.darrc, config_settings, args)
|
|
455
|
+
backup_result = generic_backup(backup_type, command, backup_file, backup_definition_path, args.darrc, config_settings, args)
|
|
456
|
+
results.extend(backup_result)
|
|
434
457
|
|
|
435
458
|
logger.info("Starting verification...")
|
|
436
459
|
verify_result = verify(args, backup_file, backup_definition_path, config_settings)
|
|
437
460
|
if verify_result:
|
|
438
461
|
logger.info("Verification completed successfully.")
|
|
439
462
|
else:
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
463
|
+
msg = f"Verification of '{backup_file}' failed."
|
|
464
|
+
logger.error(msg)
|
|
465
|
+
results.append((msg, 1))
|
|
443
466
|
logger.info("Generate par2 redundancy files.")
|
|
444
467
|
generate_par2_files(backup_file, config_settings, args)
|
|
445
468
|
logger.info("par2 files completed successfully.")
|
|
446
469
|
|
|
447
470
|
except Exception as e:
|
|
448
|
-
|
|
449
|
-
RESULT = False
|
|
471
|
+
results.append((repr(e), 1))
|
|
450
472
|
logger.exception(f"Error during {backup_type} backup process, continuing to next backup definition.")
|
|
451
473
|
|
|
452
|
-
|
|
474
|
+
logger.trace(f"perform_backup() results[]: {results}")
|
|
475
|
+
return results
|
|
453
476
|
|
|
454
477
|
def generate_par2_files(backup_file: str, config_settings: ConfigSettings, args):
|
|
455
478
|
"""
|
|
@@ -506,8 +529,14 @@ def filter_darrc_file(darrc_path):
|
|
|
506
529
|
The filtered version is stored in a uniquely named file in the home directory of the user running the script.
|
|
507
530
|
The file permissions are set to 440.
|
|
508
531
|
|
|
509
|
-
:
|
|
510
|
-
|
|
532
|
+
Params:
|
|
533
|
+
darrc_path: Path to the original .darrc file.
|
|
534
|
+
|
|
535
|
+
Raises:
|
|
536
|
+
RuntimeError if something went wrong
|
|
537
|
+
|
|
538
|
+
Returns:
|
|
539
|
+
Path to the filtered .darrc file.
|
|
511
540
|
"""
|
|
512
541
|
# Define options to filter out
|
|
513
542
|
options_to_remove = {"-vt", "-vs", "-vd", "-vf", "-va"}
|
|
@@ -593,18 +622,23 @@ INCR back of a single backup definition in backup.d
|
|
|
593
622
|
|
|
594
623
|
def requirements(type: str, config_setting: ConfigSettings):
|
|
595
624
|
"""
|
|
596
|
-
Perform PREREQ or POSTREQ
|
|
625
|
+
Perform PREREQ or POSTREQ requirements.
|
|
597
626
|
|
|
598
627
|
Args:
|
|
599
628
|
type (str): The type of prereq (PREREQ, POSTREQ).
|
|
600
629
|
config_settings (ConfigSettings): An instance of the ConfigSettings class.
|
|
601
630
|
|
|
602
631
|
Raises:
|
|
603
|
-
RuntimeError: If a subprocess
|
|
632
|
+
RuntimeError: If a subprocess returns anything but zero.
|
|
633
|
+
|
|
634
|
+
subprocess.CalledProcessError: if CalledProcessError is raised in subprocess.run(), let it bobble up.
|
|
604
635
|
"""
|
|
605
|
-
if
|
|
606
|
-
|
|
607
|
-
|
|
636
|
+
if type is None or config_setting is None:
|
|
637
|
+
raise RuntimeError(f"requirements: 'type' or config_setting is None")
|
|
638
|
+
|
|
639
|
+
allowed_types = ['PREREQ', 'POSTREQ']
|
|
640
|
+
if type not in allowed_types:
|
|
641
|
+
raise RuntimeError(f"requirements: {type} not in: {allowed_types}")
|
|
608
642
|
|
|
609
643
|
|
|
610
644
|
logger.info(f"Performing {type}")
|
|
@@ -613,8 +647,8 @@ def requirements(type: str, config_setting: ConfigSettings):
|
|
|
613
647
|
script = config_setting.config[type][key]
|
|
614
648
|
try:
|
|
615
649
|
result = subprocess.run(script, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, shell=True, check=True)
|
|
616
|
-
logger.
|
|
617
|
-
logger.
|
|
650
|
+
logger.debug(f"{type} {key}: '{script}' run, return code: {result.returncode}")
|
|
651
|
+
logger.debug(f"{type} stdout:\n{result.stdout}")
|
|
618
652
|
if result.returncode != 0:
|
|
619
653
|
logger.error(f"{type} stderr:\n{result.stderr}")
|
|
620
654
|
raise RuntimeError(f"{type} {key}: '{script}' failed, return code: {result.returncode}")
|
|
@@ -624,7 +658,8 @@ def requirements(type: str, config_setting: ConfigSettings):
|
|
|
624
658
|
|
|
625
659
|
|
|
626
660
|
def main():
|
|
627
|
-
global logger
|
|
661
|
+
global logger
|
|
662
|
+
results: List[(str,int)] = [] # a list op tuples (<msg>, <exit code>)
|
|
628
663
|
|
|
629
664
|
MIN_PYTHON_VERSION = (3, 9)
|
|
630
665
|
if version_info < MIN_PYTHON_VERSION:
|
|
@@ -692,9 +727,9 @@ def main():
|
|
|
692
727
|
exit(127)
|
|
693
728
|
|
|
694
729
|
if args.suppress_dar_msg:
|
|
695
|
-
logger.info("Suppressing dar messages: -vt, -vs, -vd, -vf
|
|
730
|
+
logger.info("Suppressing dar messages, do not use options: -vt, -vs, -vd, -vf, -va")
|
|
696
731
|
args.darrc = filter_darrc_file(args.darrc)
|
|
697
|
-
logger.debug(f"Filtered .darrc file
|
|
732
|
+
logger.debug(f"Filtered .darrc file: {args.darrc}")
|
|
698
733
|
|
|
699
734
|
start_time=int(time())
|
|
700
735
|
logger.info(f"=====================================")
|
|
@@ -737,39 +772,58 @@ def main():
|
|
|
737
772
|
if args.list:
|
|
738
773
|
list_backups(config_settings.backup_dir, args.backup_definition)
|
|
739
774
|
elif args.full_backup and not args.differential_backup and not args.incremental_backup:
|
|
740
|
-
perform_backup(args, config_settings, "FULL")
|
|
775
|
+
results.extend(perform_backup(args, config_settings, "FULL"))
|
|
741
776
|
elif args.differential_backup and not args.full_backup and not args.incremental_backup:
|
|
742
|
-
perform_backup(args, config_settings, "DIFF")
|
|
777
|
+
results.extend(perform_backup(args, config_settings, "DIFF"))
|
|
743
778
|
elif args.incremental_backup and not args.full_backup and not args.differential_backup:
|
|
744
|
-
perform_backup(args, config_settings, "INCR")
|
|
779
|
+
results.extend(perform_backup(args, config_settings, "INCR"))
|
|
780
|
+
logger.debug(f"results from perform_backup(): {results}")
|
|
745
781
|
elif args.list_contents:
|
|
746
782
|
list_contents(args.list_contents, config_settings.backup_dir, args.selection)
|
|
747
783
|
elif args.restore:
|
|
748
784
|
logger.debug(f"Restoring {args.restore} to {restore_dir}")
|
|
749
|
-
restore_backup(args.restore, config_settings, restore_dir, args.darrc, args.selection)
|
|
785
|
+
results.extend(restore_backup(args.restore, config_settings, restore_dir, args.darrc, args.selection))
|
|
750
786
|
else:
|
|
751
787
|
parser.print_help()
|
|
752
788
|
|
|
789
|
+
logger.debug(f"results[]: {results}")
|
|
790
|
+
|
|
753
791
|
requirements('POSTREQ', config_settings)
|
|
754
792
|
|
|
755
793
|
except Exception as e:
|
|
756
|
-
logger.exception("An error occurred")
|
|
757
794
|
logger.error("Exception details:", exc_info=True)
|
|
758
|
-
|
|
759
|
-
RESULT = False
|
|
795
|
+
results.append((repr(e), 1))
|
|
760
796
|
finally:
|
|
761
797
|
end_time=int(time())
|
|
762
798
|
logger.info(f"END TIME: {end_time}")
|
|
763
799
|
# Clean up
|
|
764
|
-
if
|
|
800
|
+
if os.path.exists(args.darrc) and os.path.dirname(args.darrc) == os.path.expanduser("~"):
|
|
765
801
|
if args.darrc.startswith("filtered_darrc_"):
|
|
766
802
|
os.remove(args.darrc)
|
|
767
|
-
logger.
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
803
|
+
logger.debug(f"Removed filtered .darrc: {args.darrc}")
|
|
804
|
+
|
|
805
|
+
|
|
806
|
+
# Determine exit code
|
|
807
|
+
error = False
|
|
808
|
+
logger.debug(f"results[]: {results}")
|
|
809
|
+
if results:
|
|
810
|
+
i = 0
|
|
811
|
+
for result in results:
|
|
812
|
+
if isinstance(result, tuple) and len(result) == 2:
|
|
813
|
+
msg, exit_code = result
|
|
814
|
+
logger.debug(f"exit code: {exit_code}, msg: {msg}")
|
|
815
|
+
if exit_code > 0:
|
|
816
|
+
error = True
|
|
817
|
+
args.verbose and print(msg)
|
|
818
|
+
else:
|
|
819
|
+
logger.error(f"not correct result type: {result}, which must be a tuple (<msg>, <exit_code>)")
|
|
820
|
+
i=i+1
|
|
821
|
+
if error:
|
|
822
|
+
args.verbose and print("\033[1m\033[31mErrors\033[0m encountered")
|
|
773
823
|
exit(1)
|
|
824
|
+
else:
|
|
825
|
+
args.verbose and print("\033[1m\033[32mSuccess\033[0m all backups completed")
|
|
826
|
+
exit(0)
|
|
827
|
+
|
|
774
828
|
if __name__ == "__main__":
|
|
775
829
|
main()
|
dar_backup/manager.py
CHANGED
|
@@ -233,7 +233,7 @@ def add_specific_archive(archive: str, config_settings: ConfigSettings, director
|
|
|
233
233
|
database_path = os.path.realpath(os.path.join(config_settings.backup_dir, database))
|
|
234
234
|
logger.info(f'Add "{archive_path}" to catalog: "{database}"')
|
|
235
235
|
|
|
236
|
-
command = ['dar_manager', '--base', database_path, "--add", archive_path, "-
|
|
236
|
+
command = ['dar_manager', '--base', database_path, "--add", archive_path, "-Q"]
|
|
237
237
|
process = run_command(command)
|
|
238
238
|
stdout, stderr = process.stdout, process.stderr
|
|
239
239
|
|
dar_backup/util.py
CHANGED
|
@@ -72,7 +72,7 @@ def setup_logging(log_file: str, command_output_log_file: str, log_level: str =
|
|
|
72
72
|
stdout_handler = logging.StreamHandler(sys.stdout)
|
|
73
73
|
stdout_handler.setFormatter(formatter)
|
|
74
74
|
logger.addHandler(stdout_handler)
|
|
75
|
-
secondary_logger.addHandler(stdout_handler)
|
|
75
|
+
#secondary_logger.addHandler(stdout_handler)
|
|
76
76
|
|
|
77
77
|
return logger
|
|
78
78
|
except Exception as e:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dar-backup
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.15
|
|
4
4
|
Summary: A script to do full, differential and incremental backups using dar. Some files are restored from the backups during verification, after which par2 redundancy files are created. The script also has a cleanup feature to remove old backups and par2 files.
|
|
5
5
|
Project-URL: Homepage, https://github.com/per2jensen/dar-backup/tree/main/v2
|
|
6
6
|
Project-URL: Changelog, https://github.com/per2jensen/dar-backup/blob/main/v2/Changelog.md
|
|
@@ -1355,6 +1355,8 @@ In order to not clutter that log file with the output of commands being run, a n
|
|
|
1355
1355
|
- FULL, DIFF and INCR backups.
|
|
1356
1356
|
- cleanup.
|
|
1357
1357
|
|
|
1358
|
+
- fix --log-stdout spams console with command output
|
|
1359
|
+
|
|
1358
1360
|
## Reference
|
|
1359
1361
|
|
|
1360
1362
|
### dar-backup
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
dar_backup/.darrc,sha256=-aerqivZmOsW_XBCh9IfbYTUvw0GkzDSr3Vx4GcNB1g,2113
|
|
2
|
+
dar_backup/__about__.py,sha256=9pegTLVQP2o3JePGrLB6muzXif64umv9ihLnS7LsH8E,22
|
|
3
|
+
dar_backup/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
dar_backup/clean_log.py,sha256=cGhtKYnQJ2ceNQfw5XcCln_WNBasbmlfhO3kRydjDNk,5196
|
|
5
|
+
dar_backup/cleanup.py,sha256=1g2si9-jPEL8T4OKaiGSKFGsF6rWh-Ke1-zQHE7HaPc,11703
|
|
6
|
+
dar_backup/config_settings.py,sha256=uicCq6FnpxPFzbv7xfYSXNnQf1tfLk1Z3VIO9M71fsE,4659
|
|
7
|
+
dar_backup/dar-backup.conf,sha256=-wXqP4vj5TS7cCfMJN1nbk-1Sqkq00Tg22ySQXynUF4,902
|
|
8
|
+
dar_backup/dar_backup.py,sha256=TrYMYNbQ9jWabWGc7GA5p2ezprLqMUu9O0zt6CQ1QSA,37867
|
|
9
|
+
dar_backup/installer.py,sha256=ehp4KSgTc8D9Edsyve5v3NY2MuDbuTFYQQPgou8woV8,4331
|
|
10
|
+
dar_backup/manager.py,sha256=sQl0xdWwBgui11S9Ekg0hOSC4gt89nz_Z8Bt8IPXCDw,21640
|
|
11
|
+
dar_backup/util.py,sha256=F6U-e-WugxCxLPVoiWsM6_YO8VrDw1wdgGvtnGnig2I,12279
|
|
12
|
+
dar_backup-0.6.15.dist-info/METADATA,sha256=JwTNDXCElF0elUKBhwjrDUASEkpLIjgdtfEML5NvXUY,72750
|
|
13
|
+
dar_backup-0.6.15.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
14
|
+
dar_backup-0.6.15.dist-info/entry_points.txt,sha256=Z7P5BUbhtJxo8_nB9qNIMay2eGDbsMKB3Fjwv3GMa4g,202
|
|
15
|
+
dar_backup-0.6.15.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
16
|
+
dar_backup-0.6.15.dist-info/RECORD,,
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
dar_backup/.darrc,sha256=-aerqivZmOsW_XBCh9IfbYTUvw0GkzDSr3Vx4GcNB1g,2113
|
|
2
|
-
dar_backup/__about__.py,sha256=WhY38gJ9InHtiokbumhF5t6Q_JtwytMg5oigYIZ6CZ0,24
|
|
3
|
-
dar_backup/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
-
dar_backup/clean_log.py,sha256=cGhtKYnQJ2ceNQfw5XcCln_WNBasbmlfhO3kRydjDNk,5196
|
|
5
|
-
dar_backup/cleanup.py,sha256=1g2si9-jPEL8T4OKaiGSKFGsF6rWh-Ke1-zQHE7HaPc,11703
|
|
6
|
-
dar_backup/config_settings.py,sha256=uicCq6FnpxPFzbv7xfYSXNnQf1tfLk1Z3VIO9M71fsE,4659
|
|
7
|
-
dar_backup/dar-backup.conf,sha256=-wXqP4vj5TS7cCfMJN1nbk-1Sqkq00Tg22ySQXynUF4,902
|
|
8
|
-
dar_backup/dar_backup.py,sha256=yJjDqyPBWsjsMZeMbEgLzLt0N8R2y-YZGdfxvZjm8gs,35723
|
|
9
|
-
dar_backup/installer.py,sha256=ehp4KSgTc8D9Edsyve5v3NY2MuDbuTFYQQPgou8woV8,4331
|
|
10
|
-
dar_backup/manager.py,sha256=VBeZEIETDL_Lxhmo-T47xSQAxbOPGU-ZLI7GFXJx-Go,21647
|
|
11
|
-
dar_backup/util.py,sha256=lfgfxC_C6x3I8f9vzyxpQE7P-7rm6tTEr3P-2uWlVtQ,12278
|
|
12
|
-
dar_backup-0.6.13.1.dist-info/METADATA,sha256=zWP8JXewEWB5x-dbTjnDBJ8PBFiFdwBPtkptaSP9Fg4,72698
|
|
13
|
-
dar_backup-0.6.13.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
14
|
-
dar_backup-0.6.13.1.dist-info/entry_points.txt,sha256=Z7P5BUbhtJxo8_nB9qNIMay2eGDbsMKB3Fjwv3GMa4g,202
|
|
15
|
-
dar_backup-0.6.13.1.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
16
|
-
dar_backup-0.6.13.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|