dar-backup 0.6.1__py3-none-any.whl → 0.6.3__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 +102 -174
- dar_backup/manager.py +237 -24
- {dar_backup-0.6.1.dist-info → dar_backup-0.6.3.dist-info}/METADATA +1 -1
- dar_backup-0.6.3.dist-info/RECORD +13 -0
- dar_backup-0.6.1.dist-info/RECORD +0 -13
- {dar_backup-0.6.1.dist-info → dar_backup-0.6.3.dist-info}/WHEEL +0 -0
- {dar_backup-0.6.1.dist-info → dar_backup-0.6.3.dist-info}/entry_points.txt +0 -0
- {dar_backup-0.6.1.dist-info → dar_backup-0.6.3.dist-info}/licenses/LICENSE +0 -0
dar_backup/__about__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.6.
|
|
1
|
+
__version__ = "0.6.3"
|
dar_backup/dar_backup.py
CHANGED
|
@@ -17,56 +17,49 @@ from argparse import ArgumentParser
|
|
|
17
17
|
from datetime import datetime
|
|
18
18
|
from pathlib import Path
|
|
19
19
|
from time import time
|
|
20
|
+
from typing import List
|
|
20
21
|
|
|
21
22
|
from . import __about__ as about
|
|
22
23
|
from dar_backup.config_settings import ConfigSettings
|
|
23
24
|
from dar_backup.util import list_backups
|
|
24
25
|
from dar_backup.util import run_command
|
|
25
26
|
from dar_backup.util import setup_logging
|
|
26
|
-
from dar_backup.util import extract_error_lines
|
|
27
27
|
from dar_backup.util import BackupError
|
|
28
|
-
from dar_backup.util import DifferentialBackupError
|
|
29
|
-
from dar_backup.util import IncrementalBackupError
|
|
30
28
|
from dar_backup.util import RestoreError
|
|
31
29
|
|
|
32
30
|
|
|
33
31
|
logger = None
|
|
34
32
|
|
|
35
|
-
|
|
36
|
-
def backup(backup_file: str, backup_definition: str, darrc: str, config_settings: ConfigSettings):
|
|
33
|
+
def generic_backup(type: str, command: List[str], backup_file: str, backup_definition: str, darrc: str, config_settings: ConfigSettings):
|
|
37
34
|
"""
|
|
38
|
-
Performs a
|
|
35
|
+
Performs a backup using the 'dar' command.
|
|
39
36
|
|
|
40
37
|
This function initiates a full backup operation by constructing and executing a command
|
|
41
38
|
with the 'dar' utility. It checks if the backup file already exists to avoid overwriting
|
|
42
39
|
previous backups. If the backup file does not exist, it proceeds with the backup operation.
|
|
43
40
|
|
|
44
41
|
Args:
|
|
42
|
+
type (str): The type of backup (FULL, DIFF, INCR).
|
|
43
|
+
command (List[str]): The command to execute for the backup operation.
|
|
45
44
|
backup_file (str): The base name of the backup file. The actual backup will be saved
|
|
46
45
|
as '{backup_file}.1.dar'.
|
|
47
46
|
backup_definition (str): The path to the backup definition file. This file contains
|
|
48
47
|
specific instructions for the 'dar' utility, such as which
|
|
49
48
|
directories to include or exclude.
|
|
49
|
+
darrc (str): The path to the '.darrc' configuration file.
|
|
50
|
+
config_settings (ConfigSettings): An instance of the ConfigSettings class.
|
|
51
|
+
|
|
50
52
|
|
|
51
|
-
Note:
|
|
52
|
-
This function logs an error and returns early if the backup file already exists.
|
|
53
|
-
It logs the command being executed and reports upon successful completion of the backup.
|
|
54
53
|
|
|
55
54
|
Raises:
|
|
56
55
|
BackupError: If an error occurs during the backup process.
|
|
57
56
|
"""
|
|
58
|
-
|
|
59
|
-
logger.error(f"Backup file {backup_file}.1.dar already exists. Skipping backup.")
|
|
60
|
-
return
|
|
61
|
-
|
|
62
|
-
logger.info(f"===> Starting FULL backup for {backup_definition}")
|
|
63
|
-
command = ['dar', '-c', backup_file, "-N", '-B', darrc, '-B', backup_definition, '-Q', "compress-exclusion", "verbose"]
|
|
57
|
+
logger.info(f"===> Starting {type} backup for {backup_definition}")
|
|
64
58
|
logger.info(f"Running command: {' '.join(map(shlex.quote, command))}")
|
|
65
59
|
try:
|
|
66
60
|
process = run_command(command, config_settings.command_timeout_secs)
|
|
67
|
-
logger.info("Back from run_command")
|
|
68
61
|
if process.returncode == 0:
|
|
69
|
-
logger.info("
|
|
62
|
+
logger.info(f"{type} backup completed successfully.")
|
|
70
63
|
elif process.returncode == 5:
|
|
71
64
|
logger.warning("Backup completed with some files not backed up, this can happen if files are changed/deleted during the backup.")
|
|
72
65
|
else:
|
|
@@ -78,106 +71,6 @@ def backup(backup_file: str, backup_definition: str, darrc: str, config_setting
|
|
|
78
71
|
logger.exception(f"Unexpected error during backup")
|
|
79
72
|
raise BackupError(f"Unexpected error during backup: {e}") from e
|
|
80
73
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
def differential_backup(backup_file: str, backup_definition: str, base_backup_file: str, darrc: str, config_settings: ConfigSettings):
|
|
86
|
-
"""
|
|
87
|
-
Creates a differential backup based on a specified base backup.
|
|
88
|
-
|
|
89
|
-
This function performs a differential backup by comparing the current state of the data
|
|
90
|
-
against a specified base backup file. It captures only the changes made since that base
|
|
91
|
-
backup, resulting in a smaller and faster backup process compared to a full backup.
|
|
92
|
-
|
|
93
|
-
Args:
|
|
94
|
-
backup_file (str): The base name for the differential backup file. The actual backup
|
|
95
|
-
will be saved as '{backup_file}.1.dar'.
|
|
96
|
-
backup_definition (str): The path to the backup definition file. This file contains
|
|
97
|
-
specific instructions for the 'dar' utility, such as which
|
|
98
|
-
directories to include or exclude.
|
|
99
|
-
base_backup_file (str): The base name of the full backup file that serves as the
|
|
100
|
-
reference point for the differential backup.
|
|
101
|
-
|
|
102
|
-
Note:
|
|
103
|
-
This function logs an error and returns early if the differential backup file already exists.
|
|
104
|
-
It logs the command being executed and reports upon successful completion of the differential backup.
|
|
105
|
-
|
|
106
|
-
Raises:
|
|
107
|
-
DifferentialBackupError: If the differential backup command fails or encounters an unexpected error.
|
|
108
|
-
"""
|
|
109
|
-
if os.path.exists(backup_file + '.1.dar'):
|
|
110
|
-
logger.error(f"Backup file {backup_file}.1.dar already exists. Skipping backup.")
|
|
111
|
-
return
|
|
112
|
-
|
|
113
|
-
logger.info(f"===> Starting DIFF backup for {backup_definition}")
|
|
114
|
-
command = ['dar', '-c', backup_file, "-N", '-B', darrc, '-B', backup_definition, '-A', base_backup_file, '-Q', "compress-exclusion", "verbose"]
|
|
115
|
-
logger.info(f"Running command: {' '.join(map(shlex.quote, command))}")
|
|
116
|
-
try:
|
|
117
|
-
process = run_command(command)
|
|
118
|
-
if process.returncode == 0:
|
|
119
|
-
logger.info("DIFF backup completed successfully.")
|
|
120
|
-
elif process.returncode == 5:
|
|
121
|
-
logger.warning("Backup completed with some files not backed up, this can happen if files are changed/deleted during the backup.")
|
|
122
|
-
else:
|
|
123
|
-
raise Exception(str(process))
|
|
124
|
-
except subprocess.CalledProcessError as e:
|
|
125
|
-
logger.error(f"Differential backup command failed: {e}")
|
|
126
|
-
raise DifferentialBackupError(f"Differential backup command failed: {e}") from e
|
|
127
|
-
except Exception as e:
|
|
128
|
-
logger.exception(f"Unexpected error during differential backup")
|
|
129
|
-
logger.error("Exception details:", exc_info=True)
|
|
130
|
-
raise DifferentialBackupError(f"Unexpected error during differential backup: {e}") from e
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
def incremental_backup(backup_file: str, backup_definition: str, last_backup_file: str, darrc: str, config_settings: ConfigSettings):
|
|
134
|
-
"""
|
|
135
|
-
Creates an incremental backup based on the last backup file.
|
|
136
|
-
|
|
137
|
-
This function performs an incremental backup by comparing the current state of the data
|
|
138
|
-
against the last backup file, whether it's a full backup or the most recent incremental backup.
|
|
139
|
-
It captures only the changes made since that last backup, making it efficient for frequent
|
|
140
|
-
backups with minimal data changes.
|
|
141
|
-
|
|
142
|
-
Args:
|
|
143
|
-
backup_file (str): The base name for the incremental backup file. The actual backup
|
|
144
|
-
will be saved with a unique identifier to distinguish it from other backups.
|
|
145
|
-
backup_definition (str): The path to the backup definition file. This file contains
|
|
146
|
-
specific instructions for the 'dar' utility, such as which
|
|
147
|
-
directories to include or exclude.
|
|
148
|
-
last_backup_file (str): The base name of the last backup file (full or incremental) that
|
|
149
|
-
serves as the reference point for the incremental backup.
|
|
150
|
-
|
|
151
|
-
Note:
|
|
152
|
-
This function checks if the incremental backup file already exists to prevent overwriting
|
|
153
|
-
previous backups. It logs the command being executed and reports upon successful completion
|
|
154
|
-
of the incremental backup.
|
|
155
|
-
|
|
156
|
-
Raises:
|
|
157
|
-
IncrementalBackupError: If the incremental backup command fails or an unexpected error occurs.
|
|
158
|
-
"""
|
|
159
|
-
if os.path.exists(backup_file + '.1.dar'):
|
|
160
|
-
logger.error(f"Backup file {backup_file}.1.dar already exists. Skipping backup.")
|
|
161
|
-
return
|
|
162
|
-
|
|
163
|
-
logger.info(f"===> Starting INCR backup for {backup_definition}")
|
|
164
|
-
command = ['dar', '-c', backup_file, "-N", '-B', darrc, '-B', backup_definition, '-A', last_backup_file, '-Q', "compress-exclusion", "verbose"]
|
|
165
|
-
logger.info(f"Running command: {' '.join(map(shlex.quote, command))}")
|
|
166
|
-
try:
|
|
167
|
-
process = run_command(command, config_settings.command_timeout_secs)
|
|
168
|
-
if process.returncode == 0:
|
|
169
|
-
logger.info("INCR backup completed successfully.")
|
|
170
|
-
elif process.returncode == 5:
|
|
171
|
-
logger.warning("Backup completed with some files not backed up, this can happen if files are changed/deleted during the backup.")
|
|
172
|
-
else:
|
|
173
|
-
raise Exception(str(process))
|
|
174
|
-
except subprocess.CalledProcessError as e:
|
|
175
|
-
logger.error(f"Incremental backup command failed: {e}")
|
|
176
|
-
raise IncrementalBackupError(f"Incremental backup command failed: {e}") from e
|
|
177
|
-
except Exception as e:
|
|
178
|
-
logger.exception(f"Unexpected error during incremental backup")
|
|
179
|
-
raise IncrementalBackupError(f"Unexpected error during incremental backup: {e}") from e
|
|
180
|
-
|
|
181
74
|
|
|
182
75
|
# Function to recursively find <File> tags and build their full paths
|
|
183
76
|
def find_files_with_paths(element: ET, current_path=""):
|
|
@@ -331,7 +224,7 @@ def restore_backup(backup_name: str, config_settings: ConfigSettings, restore_di
|
|
|
331
224
|
Restores a backup file to a specified directory.
|
|
332
225
|
|
|
333
226
|
Args:
|
|
334
|
-
backup_name (str): The name of the backup file.
|
|
227
|
+
backup_name (str): The base name of the backup file, without the "slice number.dar"
|
|
335
228
|
backup_dir (str): The directory where the backup file is located.
|
|
336
229
|
restore_dir (str): The directory where the backup should be restored to.
|
|
337
230
|
selection (str, optional): A selection criteria to restore specific files or directories. Defaults to None.
|
|
@@ -342,16 +235,19 @@ def restore_backup(backup_name: str, config_settings: ConfigSettings, restore_di
|
|
|
342
235
|
if not os.path.exists(restore_dir):
|
|
343
236
|
os.makedirs(restore_dir)
|
|
344
237
|
command.extend(['-R', restore_dir])
|
|
238
|
+
else:
|
|
239
|
+
raise RestoreError("Restore directory ('-R <dir>') not specified")
|
|
345
240
|
if selection:
|
|
346
241
|
selection_criteria = shlex.split(selection)
|
|
347
242
|
command.extend(selection_criteria)
|
|
348
|
-
logger.info(f"Running command: {' '.join(map(shlex.quote, command))}")
|
|
243
|
+
logger.info(f"Running restore command: {' '.join(map(shlex.quote, command))}")
|
|
349
244
|
try:
|
|
350
245
|
process = run_command(command, config_settings.command_timeout_secs)
|
|
351
246
|
if process.returncode == 0:
|
|
352
247
|
logger.info(f"Restore completed successfully to: '{restore_dir}'")
|
|
353
248
|
else:
|
|
354
|
-
|
|
249
|
+
logger.error(f"Restore command failed: \n ==> stdout: {process.stdout}, \n ==> stderr: {process.stderr}")
|
|
250
|
+
raise RestoreError(str(process))
|
|
355
251
|
except subprocess.CalledProcessError as e:
|
|
356
252
|
raise RestoreError(f"Restore command failed: {e}") from e
|
|
357
253
|
except Exception as e:
|
|
@@ -405,6 +301,7 @@ def list_contents(backup_name, backup_dir, selection=None):
|
|
|
405
301
|
None
|
|
406
302
|
"""
|
|
407
303
|
backup_path = os.path.join(backup_dir, backup_name)
|
|
304
|
+
|
|
408
305
|
try:
|
|
409
306
|
command = ['dar', '-l', backup_path, '-am', '-as', '-Q']
|
|
410
307
|
if selection:
|
|
@@ -427,6 +324,32 @@ def list_contents(backup_name, backup_dir, selection=None):
|
|
|
427
324
|
|
|
428
325
|
|
|
429
326
|
|
|
327
|
+
|
|
328
|
+
def create_backup_command(backup_type: str, backup_file: str, darrc: str, backup_definition_path: str, latest_base_backup: str = None) -> List[str]:
|
|
329
|
+
"""
|
|
330
|
+
Generate the backup command for the specified backup type.
|
|
331
|
+
|
|
332
|
+
Args:
|
|
333
|
+
backup_type (str): The type of backup (FULL, DIFF, INCR).
|
|
334
|
+
backup_file (str): The backup file path. Example: /path/to/example_2021-01-01_FULL
|
|
335
|
+
darrc (str): Path to the .darrc configuration file.
|
|
336
|
+
backup_definition_path (str): Path to the backup definition file.
|
|
337
|
+
latest_base_backup (str, optional): Path to the latest base backup for DIFF or INCR types.
|
|
338
|
+
|
|
339
|
+
Returns:
|
|
340
|
+
List[str]: The constructed backup command.
|
|
341
|
+
"""
|
|
342
|
+
base_command = ['dar', '-c', backup_file, "-N", '-B', darrc, '-B', backup_definition_path, '-Q', "compress-exclusion", "verbose"]
|
|
343
|
+
|
|
344
|
+
if backup_type in ['DIFF', 'INCR']:
|
|
345
|
+
if not latest_base_backup:
|
|
346
|
+
raise ValueError(f"Base backup is required for {backup_type} backups.")
|
|
347
|
+
base_command.extend(['-A', latest_base_backup])
|
|
348
|
+
|
|
349
|
+
return base_command
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
|
|
430
353
|
def perform_backup(args: argparse.Namespace, config_settings: ConfigSettings, backup_type: str):
|
|
431
354
|
"""
|
|
432
355
|
Perform backup operation.
|
|
@@ -434,32 +357,12 @@ def perform_backup(args: argparse.Namespace, config_settings: ConfigSettings, ba
|
|
|
434
357
|
Args:
|
|
435
358
|
args: Command-line arguments.
|
|
436
359
|
config_settings: An instance of the ConfigSettings class.
|
|
437
|
-
backup_d: Directory containing backup definitions.
|
|
438
|
-
backup_dir: Directory to store backup files.
|
|
439
|
-
test_restore_dir: Directory to test restore backup files.
|
|
440
360
|
backup_type: Type of backup (FULL, DIFF, INCR).
|
|
441
|
-
min_size_verification_mb: Minimum size for verification in MB.
|
|
442
|
-
max_size_verification_mb: Maximum size for verification in MB.
|
|
443
|
-
no_files_verification: Flag indicating whether to skip file verification.
|
|
444
|
-
|
|
445
|
-
Returns:
|
|
446
|
-
None
|
|
447
|
-
|
|
448
|
-
Raises:
|
|
449
|
-
FileNotFoundError: If `backup_d` does not exist or a specified backup definition file does not exist.
|
|
450
|
-
PermissionError: If there is insufficient permission to access directories or files specified.
|
|
451
|
-
OSError: For various system-related errors, such as exhaustion of file descriptors.
|
|
452
|
-
ValueError: If there is an issue with the format string in `datetime.now().strftime`.
|
|
453
|
-
subprocess.CalledProcessError: If a subprocess invoked during the backup process exits with a non-zero status.
|
|
454
|
-
Exception: Catches any unexpected exceptions that may occur during the backup process.
|
|
455
|
-
|
|
456
|
-
Note:
|
|
457
|
-
This function assumes that any exceptions raised by the `backup` function or related subprocesses are handled
|
|
458
|
-
within those functions or propagated up to be handled by the caller of `perform_backup`.
|
|
459
361
|
"""
|
|
460
362
|
logger.debug(f"perform_backup({backup_type}) started")
|
|
461
363
|
backup_definitions = []
|
|
462
364
|
|
|
365
|
+
# Gather backup definitions
|
|
463
366
|
if args.backup_definition:
|
|
464
367
|
if '_' in args.backup_definition:
|
|
465
368
|
logger.error(f"Skipping backup definition: '{args.backup_definition}' due to '_' in name")
|
|
@@ -482,16 +385,15 @@ def perform_backup(args: argparse.Namespace, config_settings: ConfigSettings, ba
|
|
|
482
385
|
logger.error(f"Backup file {backup_file}.1.dar already exists. Skipping backup.")
|
|
483
386
|
continue
|
|
484
387
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
else:
|
|
388
|
+
latest_base_backup = None
|
|
389
|
+
if backup_type in ['DIFF', 'INCR']:
|
|
488
390
|
base_backup_type = 'FULL' if backup_type == 'DIFF' else 'DIFF'
|
|
489
|
-
|
|
391
|
+
|
|
490
392
|
if args.alternate_reference_archive:
|
|
491
|
-
latest_base_backup = os.path.join(config_settings.backup_dir, args.alternate_reference_archive)
|
|
393
|
+
latest_base_backup = os.path.join(config_settings.backup_dir, args.alternate_reference_archive)
|
|
492
394
|
logger.info(f"Using alternate reference archive: {latest_base_backup}")
|
|
493
395
|
if not os.path.exists(latest_base_backup + '.1.dar'):
|
|
494
|
-
logger.error(f"Alternate reference archive: \"{latest_base_backup}.1.dar\" does not exist,
|
|
396
|
+
logger.error(f"Alternate reference archive: \"{latest_base_backup}.1.dar\" does not exist, exiting.")
|
|
495
397
|
sys.exit(1)
|
|
496
398
|
else:
|
|
497
399
|
base_backups = sorted(
|
|
@@ -503,33 +405,36 @@ def perform_backup(args: argparse.Namespace, config_settings: ConfigSettings, ba
|
|
|
503
405
|
continue
|
|
504
406
|
latest_base_backup = os.path.join(config_settings.backup_dir, base_backups[-1].rsplit('.', 2)[0])
|
|
505
407
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
408
|
+
# Generate the backup command
|
|
409
|
+
command = create_backup_command(backup_type, backup_file, args.darrc, backup_definition_path, latest_base_backup)
|
|
410
|
+
|
|
411
|
+
# Perform backup
|
|
412
|
+
generic_backup(backup_type, command, backup_file, backup_definition_path, args.darrc, config_settings)
|
|
510
413
|
|
|
511
414
|
logger.info("Starting verification...")
|
|
512
415
|
result = verify(args, backup_file, backup_definition_path, config_settings)
|
|
513
416
|
if result:
|
|
514
417
|
logger.info("Verification completed successfully.")
|
|
515
418
|
else:
|
|
516
|
-
logger.error("Verification failed.")
|
|
419
|
+
logger.error("Verification failed.")
|
|
420
|
+
|
|
517
421
|
if config_settings.par2_enabled:
|
|
518
|
-
logger.info("Generate par2 redundancy files")
|
|
519
|
-
generate_par2_files(backup_file, config_settings
|
|
422
|
+
logger.info("Generate par2 redundancy files.")
|
|
423
|
+
generate_par2_files(backup_file, config_settings, args)
|
|
520
424
|
logger.info("par2 files completed successfully.")
|
|
521
|
-
# we want to continue with other backup definitions, thus only logging an error
|
|
522
425
|
except Exception as e:
|
|
523
|
-
logger.exception(f"Error during {backup_type} backup process, continuing
|
|
524
|
-
|
|
426
|
+
logger.exception(f"Error during {backup_type} backup process, continuing to next backup definition.")
|
|
427
|
+
|
|
428
|
+
|
|
525
429
|
|
|
526
|
-
def generate_par2_files(backup_file: str, config_settings: ConfigSettings):
|
|
430
|
+
def generate_par2_files(backup_file: str, config_settings: ConfigSettings, args):
|
|
527
431
|
"""
|
|
528
432
|
Generate PAR2 files for a given backup file in the specified backup directory.
|
|
529
433
|
|
|
530
434
|
Args:
|
|
531
435
|
backup_file (str): The name of the backup file.
|
|
532
|
-
|
|
436
|
+
config_settings: The configuration settings object.
|
|
437
|
+
args: The command-line arguments object.
|
|
533
438
|
|
|
534
439
|
Raises:
|
|
535
440
|
subprocess.CalledProcessError: If the par2 command fails to execute.
|
|
@@ -537,19 +442,39 @@ def generate_par2_files(backup_file: str, config_settings: ConfigSettings):
|
|
|
537
442
|
Returns:
|
|
538
443
|
None
|
|
539
444
|
"""
|
|
445
|
+
# Regular expression to match DAR slice files
|
|
446
|
+
dar_slice_pattern = re.compile(rf"{re.escape(os.path.basename(backup_file))}\.([0-9]+)\.dar")
|
|
447
|
+
|
|
448
|
+
# List of DAR slice files to be processed
|
|
449
|
+
dar_slices: List[str] = []
|
|
450
|
+
|
|
540
451
|
for filename in os.listdir(config_settings.backup_dir):
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
# Run the par2 command to generate redundancy files with error correction
|
|
545
|
-
command = ['par2', 'create', f'-r{config_settings.error_correction_percent}', '-q', '-q', file_path]
|
|
546
|
-
process = run_command(command, config_settings.command_timeout_secs)
|
|
547
|
-
if process.returncode != 0:
|
|
548
|
-
logger.error(f"Error generating par2 files for {file_path}")
|
|
549
|
-
raise subprocess.CalledProcessError(process.returncode, command)
|
|
550
|
-
logger.debug(f"par2 files generated for {file_path}")
|
|
452
|
+
match = dar_slice_pattern.match(filename)
|
|
453
|
+
if match:
|
|
454
|
+
dar_slices.append(filename)
|
|
551
455
|
|
|
456
|
+
# Sort the DAR slices based on the slice number
|
|
457
|
+
dar_slices.sort(key=lambda x: int(dar_slice_pattern.match(x).group(1)))
|
|
458
|
+
number_of_slices = len(dar_slices)
|
|
459
|
+
counter = 1
|
|
552
460
|
|
|
461
|
+
for slice_file in dar_slices:
|
|
462
|
+
file_path = os.path.join(config_settings.backup_dir, slice_file)
|
|
463
|
+
|
|
464
|
+
if args.verbose or args.log_level == "debug" or args.log_level == "trace":
|
|
465
|
+
logger.info(f"{counter}/{number_of_slices}: Now generating par2 files for {file_path}")
|
|
466
|
+
|
|
467
|
+
# Run the par2 command to generate redundancy files with error correction
|
|
468
|
+
command = ['par2', 'create', f'-r{config_settings.error_correction_percent}', '-q', '-q', file_path]
|
|
469
|
+
process = run_command(command, config_settings.command_timeout_secs)
|
|
470
|
+
|
|
471
|
+
if process.returncode == 0:
|
|
472
|
+
if args.verbose or args.log_level == "debug" or args.log_level == "trace":
|
|
473
|
+
logger.info(f"{counter}/{number_of_slices}: Done")
|
|
474
|
+
else:
|
|
475
|
+
logger.error(f"Error generating par2 files for {file_path}")
|
|
476
|
+
raise subprocess.CalledProcessError(process.returncode, command)
|
|
477
|
+
counter += 1
|
|
553
478
|
|
|
554
479
|
|
|
555
480
|
|
|
@@ -658,8 +583,9 @@ def main():
|
|
|
658
583
|
parser.add_argument('-l', '--list', action='store_true', help="List available archives.")
|
|
659
584
|
parser.add_argument('--list-contents', help="List the contents of the specified archive.")
|
|
660
585
|
parser.add_argument('--selection', help="dar file selection for listing/restoring specific files/directories.")
|
|
661
|
-
parser.add_argument('-r', '--restore', nargs=1, type=str, help="Restore specified archive.")
|
|
662
|
-
parser.add_argument('
|
|
586
|
+
# parser.add_argument('-r', '--restore', nargs=1, type=str, help="Restore specified archive.")
|
|
587
|
+
parser.add_argument('-r', '--restore', type=str, help="Restore specified archive.")
|
|
588
|
+
parser.add_argument('--restore-dir', type=str, help="Directory to restore files to.")
|
|
663
589
|
parser.add_argument('--verbose', action='store_true', help="Print various status messages to screen")
|
|
664
590
|
parser.add_argument('--log-level', type=str, help="`debug` or `trace`", default="info")
|
|
665
591
|
parser.add_argument('--log-stdout', action='store_true', help='also print log messages to stdout')
|
|
@@ -708,7 +634,10 @@ def main():
|
|
|
708
634
|
args.verbose and (print(f"Alternate ref archive: {args.alternate_reference_archive}"))
|
|
709
635
|
args.verbose and (print(f"Backup.d dir: {config_settings.backup_d_dir}"))
|
|
710
636
|
args.verbose and (print(f"Backup dir: {config_settings.backup_dir}"))
|
|
711
|
-
|
|
637
|
+
|
|
638
|
+
restore_dir = args.restore_dir if args.restore_dir else config_settings.test_restore_dir
|
|
639
|
+
args.verbose and (print(f"Restore dir: {restore_dir}"))
|
|
640
|
+
|
|
712
641
|
args.verbose and (print(f"Logfile location: {config_settings.logfile_location}"))
|
|
713
642
|
args.verbose and (print(f".darrc location: {args.darrc}"))
|
|
714
643
|
args.verbose and (print(f"PAR2 enabled: {config_settings.par2_enabled}"))
|
|
@@ -735,10 +664,9 @@ def main():
|
|
|
735
664
|
elif args.incremental_backup and not args.full_backup and not args.differential_backup:
|
|
736
665
|
perform_backup(args, config_settings, "INCR")
|
|
737
666
|
elif args.list_contents:
|
|
738
|
-
print(f"Listing contents of {args.list_contents}")
|
|
739
667
|
list_contents(args.list_contents, config_settings.backup_dir, args.selection)
|
|
740
668
|
elif args.restore:
|
|
741
|
-
|
|
669
|
+
logger.debug(f"Restoring {args.restore} to {restore_dir}")
|
|
742
670
|
restore_backup(args.restore, config_settings, restore_dir, args.selection)
|
|
743
671
|
else:
|
|
744
672
|
parser.print_help()
|
dar_backup/manager.py
CHANGED
|
@@ -21,8 +21,9 @@
|
|
|
21
21
|
"""
|
|
22
22
|
|
|
23
23
|
|
|
24
|
-
import os
|
|
25
24
|
import argparse
|
|
25
|
+
import os
|
|
26
|
+
import re
|
|
26
27
|
import sys
|
|
27
28
|
|
|
28
29
|
|
|
@@ -30,7 +31,9 @@ from . import __about__ as about
|
|
|
30
31
|
from dar_backup.config_settings import ConfigSettings
|
|
31
32
|
from dar_backup.util import run_command
|
|
32
33
|
from dar_backup.util import setup_logging
|
|
34
|
+
from datetime import datetime
|
|
33
35
|
from time import time
|
|
36
|
+
from typing import Dict, List, NamedTuple
|
|
34
37
|
|
|
35
38
|
# Constants
|
|
36
39
|
SCRIPTNAME = os.path.basename(__file__)
|
|
@@ -70,9 +73,20 @@ def create_db(backup_def: str, config_settings: ConfigSettings):
|
|
|
70
73
|
logger.error(f"stderr: {stderr}")
|
|
71
74
|
logger.error(f"stdout: {stdout}")
|
|
72
75
|
|
|
76
|
+
return process.returncode
|
|
73
77
|
|
|
74
78
|
|
|
75
|
-
def
|
|
79
|
+
def list_catalogs(backup_def: str, config_settings: ConfigSettings) -> NamedTuple:
|
|
80
|
+
"""
|
|
81
|
+
Returns:
|
|
82
|
+
a typing.NamedTuple of class dar-backup.util.CommandResult with the following properties:
|
|
83
|
+
- process: of type subprocess.CompletedProcess: The result of the command execution.
|
|
84
|
+
- stdout: of type str: The standard output of the command.
|
|
85
|
+
- stderr: of type str: The standard error of the command.
|
|
86
|
+
- returncode: of type int: The return code of the command.
|
|
87
|
+
- timeout: of type int: The timeout value in seconds used to run the command.
|
|
88
|
+
- command: of type list[str): The command executed.
|
|
89
|
+
"""
|
|
76
90
|
database = f"{backup_def}{DB_SUFFIX}"
|
|
77
91
|
database_path = os.path.join(config_settings.backup_dir, database)
|
|
78
92
|
if not os.path.exists(database_path):
|
|
@@ -87,27 +101,101 @@ def list_db(backup_def: str, config_settings: ConfigSettings):
|
|
|
87
101
|
logger.error(f"stdout: {stdout}")
|
|
88
102
|
else:
|
|
89
103
|
print(stdout)
|
|
90
|
-
|
|
104
|
+
return process
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def cat_no_for_name(archive: str, config_settings: ConfigSettings) -> int:
|
|
108
|
+
"""
|
|
109
|
+
Find the catalog number for the given archive name
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
- the found number, if the archive catalog is present in the database
|
|
113
|
+
- "-1" if the archive is not found
|
|
114
|
+
"""
|
|
115
|
+
backup_def = backup_def_from_archive(archive)
|
|
116
|
+
process = list_catalogs(backup_def, config_settings)
|
|
117
|
+
if process.returncode != 0:
|
|
118
|
+
logger.error(f"Error listing catalogs for backup def: '{backup_def}'")
|
|
119
|
+
return -1
|
|
120
|
+
line_no = 1
|
|
121
|
+
for line in process.stdout.splitlines():
|
|
122
|
+
#print(f"{line_no}: {line}")
|
|
123
|
+
line_no += 1
|
|
124
|
+
search = re.search(f"\s+(\d+)\s+.*?({archive}).*", line)
|
|
125
|
+
if search:
|
|
126
|
+
#print("FOUND")
|
|
127
|
+
logger.info(f"Found archive: '{archive}', catalog #: '{search.group(1)}'")
|
|
128
|
+
return int(search.group(1))
|
|
129
|
+
return -1
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def list_catalog_contents(catalog_number: int, backup_def: str, config_settings: ConfigSettings):
|
|
136
|
+
"""
|
|
137
|
+
List the contents of catalog # in catalog database for given backup definition
|
|
138
|
+
"""
|
|
139
|
+
database = f"{backup_def}{DB_SUFFIX}"
|
|
140
|
+
database_path = os.path.join(config_settings.backup_dir, database)
|
|
141
|
+
if not os.path.exists(database_path):
|
|
142
|
+
logger.error(f'Database not found: "{database_path}"')
|
|
143
|
+
return 1
|
|
144
|
+
command = ['dar_manager', '--base', database_path, '-u', f"{catalog_number}"]
|
|
145
|
+
process = run_command(command)
|
|
146
|
+
stdout, stderr = process.stdout, process.stderr
|
|
147
|
+
if process.returncode != 0:
|
|
148
|
+
logger.error(f'Error listing catalogs for: "{database_path}"')
|
|
149
|
+
logger.error(f"stderr: {stderr}")
|
|
150
|
+
logger.error(f"stdout: {stdout}")
|
|
151
|
+
else:
|
|
152
|
+
print(stdout)
|
|
153
|
+
return process.returncode
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def find_file(file, backup_def, config_settings):
|
|
157
|
+
"""
|
|
158
|
+
Find a specific file
|
|
159
|
+
"""
|
|
160
|
+
database = f"{backup_def}{DB_SUFFIX}"
|
|
161
|
+
database_path = os.path.join(config_settings.backup_dir, database)
|
|
162
|
+
if not os.path.exists(database_path):
|
|
163
|
+
logger.error(f'Database not found: "{database_path}"')
|
|
164
|
+
return 1
|
|
165
|
+
command = ['dar_manager', '--base', database_path, '-f', f"{file}"]
|
|
166
|
+
process = run_command(command)
|
|
167
|
+
stdout, stderr = process.stdout, process.stderr
|
|
168
|
+
if process.returncode != 0:
|
|
169
|
+
logger.error(f'Error finding file: {file} in: "{database_path}"')
|
|
170
|
+
logger.error(f"stderr: {stderr}")
|
|
171
|
+
logger.error(f"stdout: {stdout}")
|
|
172
|
+
else:
|
|
173
|
+
print(stdout)
|
|
174
|
+
return process.returncode
|
|
91
175
|
|
|
92
176
|
|
|
93
|
-
def add_specific_archive(archive: str, config_settings: ConfigSettings):
|
|
177
|
+
def add_specific_archive(archive: str, config_settings: ConfigSettings, directory: str =None) -> int:
|
|
94
178
|
# sanity check - does dar backup exist?
|
|
179
|
+
if not directory:
|
|
180
|
+
directory = config_settings.backup_dir
|
|
95
181
|
archive = os.path.basename(archive) # remove path if it was given
|
|
96
|
-
archive_path = os.path.join(
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
182
|
+
archive_path = os.path.join(directory, f'{archive}')
|
|
183
|
+
|
|
184
|
+
archive_test_path = os.path.join(directory, f'{archive}.1.dar')
|
|
185
|
+
if not os.path.exists(archive_test_path):
|
|
186
|
+
logger.error(f'dar backup: "{archive_test_path}" not found, exiting')
|
|
187
|
+
return 1
|
|
100
188
|
|
|
101
189
|
# sanity check - does backup definition exist?
|
|
102
190
|
backup_definition = archive.split('_')[0]
|
|
103
191
|
backup_def_path = os.path.join(config_settings.backup_d_dir, backup_definition)
|
|
104
192
|
if not os.path.exists(backup_def_path):
|
|
105
193
|
logger.error(f'backup definition "{backup_definition}" not found (--add-specific-archive option probably not correct), exiting')
|
|
106
|
-
|
|
194
|
+
return 1
|
|
107
195
|
|
|
108
196
|
database = f"{backup_definition}{DB_SUFFIX}"
|
|
109
197
|
database_path = os.path.realpath(os.path.join(config_settings.backup_dir, database))
|
|
110
|
-
logger.info(f'Add "{archive_path}" to catalog "{database}"')
|
|
198
|
+
logger.info(f'Add "{archive_path}" to catalog: "{database}"')
|
|
111
199
|
|
|
112
200
|
command = ['dar_manager', '--base', database_path, "--add", archive_path, "-ai", "-Q"]
|
|
113
201
|
process = run_command(command)
|
|
@@ -122,7 +210,99 @@ def add_specific_archive(archive: str, config_settings: ConfigSettings):
|
|
|
122
210
|
logger.error(f"stderr: {stderr}")
|
|
123
211
|
logger.error(f"stdout: {stdout}")
|
|
124
212
|
|
|
125
|
-
|
|
213
|
+
return process.returncode
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def add_directory(args: argparse.ArgumentParser, config_settings: ConfigSettings) -> None:
|
|
218
|
+
"""
|
|
219
|
+
Loop over the DAR archives in the given directory args.add_dir in increasing order by date and add them to their catalog database.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
args (argparse.ArgumentParser): The command-line arguments object containing the add_dir attribute.
|
|
223
|
+
config_settings (ConfigSettings): The configuration settings object.
|
|
224
|
+
|
|
225
|
+
This function performs the following steps:
|
|
226
|
+
1. Checks if the specified directory exists. If not, raises a RuntimeError.
|
|
227
|
+
2. Uses a regular expression to match DAR archive files with base names in the format <string>_{FULL, DIFF, INCR}_YYYY-MM-DD.
|
|
228
|
+
3. Lists the DAR archives in the specified directory and extracts their base names and dates.
|
|
229
|
+
4. Sorts the DAR archives by date.
|
|
230
|
+
5. Loops over the sorted DAR archives and adds each archive to its catalog database using the add_specific_archive function.
|
|
231
|
+
|
|
232
|
+
Example:
|
|
233
|
+
args = argparse.ArgumentParser()
|
|
234
|
+
args.add_dir = '/path/to/dar/archives'
|
|
235
|
+
config_settings = ConfigSettings()
|
|
236
|
+
add_directory(args, config_settings)
|
|
237
|
+
"""
|
|
238
|
+
if not os.path.exists(args.add_dir):
|
|
239
|
+
raise RuntimeError(f"Directory {args.add_dir} does not exist")
|
|
240
|
+
|
|
241
|
+
# Regular expression to match DAR archive files with base name and date in the format <string>_{FULL, DIFF, INCR}_YYYY-MM-DD
|
|
242
|
+
#dar_pattern = re.compile(r'^(.*?_(FULL|DIFF|INCR)_(\d{4}-\d{2}-\d{2}))\.\d+\.dar$')
|
|
243
|
+
dar_pattern = re.compile(r'^(.*?_(FULL|DIFF|INCR)_(\d{4}-\d{2}-\d{2}))\.1.dar$') # just read slice #1 of an archive
|
|
244
|
+
# List of DAR archives with their dates and base names
|
|
245
|
+
dar_archives = []
|
|
246
|
+
|
|
247
|
+
for filename in os.listdir(args.add_dir):
|
|
248
|
+
logger.debug(f"check if '{filename}' is a dar archive slice #1?")
|
|
249
|
+
match = dar_pattern.match(filename)
|
|
250
|
+
if match:
|
|
251
|
+
base_name = match.group(1)
|
|
252
|
+
date_str = match.group(3)
|
|
253
|
+
date_obj = datetime.strptime(date_str, '%Y-%m-%d')
|
|
254
|
+
dar_archives.append((date_obj, base_name))
|
|
255
|
+
logger.debug(f" -> yes: base name: {base_name}, date: {date_str}")
|
|
256
|
+
|
|
257
|
+
if not dar_archives or len(dar_archives) == 0:
|
|
258
|
+
logger.info(f"No 'dar' archives found in directory {args.add_dir}")
|
|
259
|
+
return
|
|
260
|
+
|
|
261
|
+
# Sort the DAR archives by date
|
|
262
|
+
dar_archives.sort()
|
|
263
|
+
|
|
264
|
+
# Loop over the sorted DAR archives and process them
|
|
265
|
+
result: List[Dict] = []
|
|
266
|
+
for date_obj, base_name in dar_archives:
|
|
267
|
+
logger.info(f"Adding dar archive: '{base_name}' to it's catalog database")
|
|
268
|
+
result_archive = add_specific_archive(base_name, config_settings, args.add_dir)
|
|
269
|
+
result.append({ f"{base_name}" : result_archive})
|
|
270
|
+
if result_archive != 0:
|
|
271
|
+
logger.error(f"Something went wrong added {base_name} to it's catalog")
|
|
272
|
+
|
|
273
|
+
logger.debug(f"Results adding archives found in: '{args.add_dir}': result")
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def backup_def_from_archive(archive: str) -> str:
|
|
277
|
+
"""
|
|
278
|
+
return the backup definition from archive name
|
|
279
|
+
"""
|
|
280
|
+
search = re.search("(.*?)_", archive)
|
|
281
|
+
backup_def = search.group(1)
|
|
282
|
+
logger.debug(f"backup definition: '{backup_def}' from given archive '{archive}'")
|
|
283
|
+
return backup_def
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def remove_specific_archive(archive: str, config_settings: ConfigSettings) -> int:
|
|
288
|
+
backup_def = backup_def_from_archive(archive)
|
|
289
|
+
database_path = os.path.join(config_settings.backup_dir, f"{backup_def}{DB_SUFFIX}")
|
|
290
|
+
cat_no = cat_no_for_name(archive, config_settings)
|
|
291
|
+
if cat_no > 0:
|
|
292
|
+
command = ['dar_manager', '--base', database_path, "--delete", str(cat_no)]
|
|
293
|
+
process = run_command(command)
|
|
294
|
+
else:
|
|
295
|
+
logger.error(f"archive: '{archive}' not found in in't catalog database: {database_path}")
|
|
296
|
+
return cat_no
|
|
297
|
+
|
|
298
|
+
if process.returncode == 0:
|
|
299
|
+
logger.info(f"'{archive}' removed from it's catalog")
|
|
300
|
+
else:
|
|
301
|
+
logger.error(process.stdout)
|
|
302
|
+
logger.error(process.sterr)
|
|
303
|
+
|
|
304
|
+
return process.returncode
|
|
305
|
+
|
|
126
306
|
|
|
127
307
|
|
|
128
308
|
|
|
@@ -142,7 +322,9 @@ def main():
|
|
|
142
322
|
parser.add_argument('-d', '--backup-def', type=str, help='Restrict to work only on this backup definition')
|
|
143
323
|
parser.add_argument('--add-specific-archive', type=str, help='Add this archive to catalog database')
|
|
144
324
|
parser.add_argument('--remove-specific-archive', type=str, help='Remove this archive from catalog database')
|
|
145
|
-
parser.add_argument('--list-
|
|
325
|
+
parser.add_argument('--list-catalog', action='store_true', help='List catalogs in databases for all backup definitions')
|
|
326
|
+
parser.add_argument('--list-catalog-contents', type=int, help="List contents of a catalog. Argument is the 'archive #', '-d <definition>' argument is also required")
|
|
327
|
+
parser.add_argument('--find-file', type=str, help="List catalogs containing <path>/file. '-d <definition>' argument is also required")
|
|
146
328
|
parser.add_argument('--verbose', action='store_true', help='Be more verbose')
|
|
147
329
|
parser.add_argument('--log-level', type=str, help="`debug` or `trace`, default is `info`", default="info")
|
|
148
330
|
parser.add_argument('--log-stdout', action='store_true', help='also print log messages to stdout')
|
|
@@ -196,6 +378,13 @@ See section 15 and section 16 in the supplied "LICENSE" file.''')
|
|
|
196
378
|
logger.error("you can't add and remove archives in the same operation, exiting")
|
|
197
379
|
sys.exit(1)
|
|
198
380
|
|
|
381
|
+
if args.add_dir and args.add_specific_archive:
|
|
382
|
+
logger.error("you cannot add both a directory and an archive")
|
|
383
|
+
sys.exit(1)
|
|
384
|
+
|
|
385
|
+
if args.backup_def and not args.backup_def.strip():
|
|
386
|
+
logger.error(f"No backup definition given to --backup-def")
|
|
387
|
+
|
|
199
388
|
if args.backup_def:
|
|
200
389
|
backup_def_path = os.path.join(config_settings.backup_d_dir, args.backup_def)
|
|
201
390
|
if not os.path.exists(backup_def_path):
|
|
@@ -203,6 +392,16 @@ See section 15 and section 16 in the supplied "LICENSE" file.''')
|
|
|
203
392
|
sys.exit(1)
|
|
204
393
|
|
|
205
394
|
|
|
395
|
+
if args.list_catalog_contents and not args.backup_def:
|
|
396
|
+
logger.error(f"--list-catalog-contents requires the --backup-def, exiting")
|
|
397
|
+
sys.exit(1)
|
|
398
|
+
|
|
399
|
+
if args.find_file and not args.backup_def:
|
|
400
|
+
logger.error(f"--find-file requires the --backup-def, exiting")
|
|
401
|
+
sys.exit(1)
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
|
|
206
405
|
# Modify config settings based on the arguments
|
|
207
406
|
if args.alternate_archive_dir:
|
|
208
407
|
if not os.path.exists(args.alternate_archive_dir):
|
|
@@ -213,35 +412,49 @@ See section 15 and section 16 in the supplied "LICENSE" file.''')
|
|
|
213
412
|
|
|
214
413
|
if args.create_db:
|
|
215
414
|
if args.backup_def:
|
|
216
|
-
create_db(args.backup_def, config_settings)
|
|
415
|
+
sys.exit(create_db(args.backup_def, config_settings))
|
|
217
416
|
else:
|
|
218
417
|
for root, dirs, files in os.walk(config_settings.backup_d_dir):
|
|
219
418
|
for file in files:
|
|
220
419
|
current_backupdef = os.path.basename(file)
|
|
221
|
-
|
|
222
|
-
|
|
420
|
+
logger.debug(f"Create catalog db for backup definition: '{current_backupdef}'")
|
|
421
|
+
result = create_db(current_backupdef, config_settings)
|
|
422
|
+
if result != 0:
|
|
423
|
+
sys.exit(result)
|
|
223
424
|
|
|
224
425
|
if args.add_specific_archive:
|
|
225
|
-
add_specific_archive(args.add_specific_archive, config_settings)
|
|
426
|
+
sys.exit(add_specific_archive(args.add_specific_archive, config_settings))
|
|
226
427
|
|
|
227
428
|
if args.add_dir:
|
|
228
|
-
|
|
229
|
-
|
|
429
|
+
sys.exit(add_directory(args, config_settings))
|
|
430
|
+
|
|
230
431
|
|
|
231
432
|
if args.remove_specific_archive:
|
|
232
|
-
|
|
233
|
-
|
|
433
|
+
sys.exit(remove_specific_archive(args.remove_specific_archive, config_settings))
|
|
434
|
+
|
|
234
435
|
|
|
235
436
|
|
|
236
|
-
if args.
|
|
437
|
+
if args.list_catalog:
|
|
237
438
|
if args.backup_def:
|
|
238
|
-
|
|
439
|
+
process = list_catalogs(args.backup_def, config_settings)
|
|
440
|
+
result = process.returncode
|
|
239
441
|
else:
|
|
442
|
+
result = 0
|
|
240
443
|
for root, dirs, files in os.walk(config_settings.backup_d_dir):
|
|
241
444
|
for file in files:
|
|
242
445
|
current_backupdef = os.path.basename(file)
|
|
243
|
-
|
|
244
|
-
|
|
446
|
+
if list_catalogs(current_backupdef, config_settings).returncode != 0:
|
|
447
|
+
result = 1
|
|
448
|
+
sys.exit(result)
|
|
449
|
+
|
|
450
|
+
if args.list_catalog_contents:
|
|
451
|
+
result = list_catalog_contents(args.list_catalog_contents, args.backup_def, config_settings)
|
|
452
|
+
sys.exit(result)
|
|
453
|
+
|
|
454
|
+
if args.find_file:
|
|
455
|
+
result = find_file(args.find_file, args.backup_def, config_settings)
|
|
456
|
+
sys.exit(result)
|
|
457
|
+
|
|
245
458
|
|
|
246
459
|
if __name__ == "__main__":
|
|
247
460
|
main()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dar-backup
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.3
|
|
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
|
|
6
6
|
Project-URL: Issues, https://github.com/per2jensen/dar-backup/issues
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
dar_backup/.darrc,sha256=ggex9N6eETOS6u003_QRRJMeWbveQfkT1lDBt0XpU-I,2112
|
|
2
|
+
dar_backup/__about__.py,sha256=_SsQ0ZcyZbUqlFFT370nQxs8UER9D0oW_EmCr4Q-hx4,21
|
|
3
|
+
dar_backup/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
dar_backup/cleanup.py,sha256=DgmxSUwKrLLIQuYSIY_yTRhIuOMgI6ivjlQuH4u3wX4,9057
|
|
5
|
+
dar_backup/config_settings.py,sha256=CBMUhLOOZ-x7CRdS3vBDk4TYaGqC4N1Ot8IMH-qPaI0,3617
|
|
6
|
+
dar_backup/dar_backup.py,sha256=YZoVu9NJX3_WIkQIG8EMLSK3-VWdslI0c2XKrM2Un38,32214
|
|
7
|
+
dar_backup/manager.py,sha256=Y1dQ7CRGQx5sGoGrjAO62QfJcuW_0Vod-ZGaQmHInFU,19062
|
|
8
|
+
dar_backup/util.py,sha256=6lPCFHr3MDdaLWAW9EDMZ4jdL7pt8rki-5dOXcesmP8,8955
|
|
9
|
+
dar_backup-0.6.3.dist-info/METADATA,sha256=6120hSt2mUxhn3u8PfbJweoPY4ToWFlAPHAgLozaK7Q,22496
|
|
10
|
+
dar_backup-0.6.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
11
|
+
dar_backup-0.6.3.dist-info/entry_points.txt,sha256=x9vnW-JEl8mpDJC69f_XBcn0mBSkV1U0cyvFV-NAP1g,126
|
|
12
|
+
dar_backup-0.6.3.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
13
|
+
dar_backup-0.6.3.dist-info/RECORD,,
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
dar_backup/.darrc,sha256=ggex9N6eETOS6u003_QRRJMeWbveQfkT1lDBt0XpU-I,2112
|
|
2
|
-
dar_backup/__about__.py,sha256=XvHFZM0padtrqitt9-p2enlBUGqc6vGvWNLx2iJv09g,21
|
|
3
|
-
dar_backup/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
-
dar_backup/cleanup.py,sha256=DgmxSUwKrLLIQuYSIY_yTRhIuOMgI6ivjlQuH4u3wX4,9057
|
|
5
|
-
dar_backup/config_settings.py,sha256=CBMUhLOOZ-x7CRdS3vBDk4TYaGqC4N1Ot8IMH-qPaI0,3617
|
|
6
|
-
dar_backup/dar_backup.py,sha256=xnDuqkpo8q_X1FLKID9I_s__FQozl5EIrwNHdIJAd_0,37525
|
|
7
|
-
dar_backup/manager.py,sha256=lkw1ZAIdxY7WedLPKZMnHpuq_QbjkUdcG61ooiwUYpo,10197
|
|
8
|
-
dar_backup/util.py,sha256=6lPCFHr3MDdaLWAW9EDMZ4jdL7pt8rki-5dOXcesmP8,8955
|
|
9
|
-
dar_backup-0.6.1.dist-info/METADATA,sha256=m3P_iVauaMno0fAhc4iJgO_DS9FobsqV5StQzUdU6wY,22496
|
|
10
|
-
dar_backup-0.6.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
11
|
-
dar_backup-0.6.1.dist-info/entry_points.txt,sha256=x9vnW-JEl8mpDJC69f_XBcn0mBSkV1U0cyvFV-NAP1g,126
|
|
12
|
-
dar_backup-0.6.1.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
13
|
-
dar_backup-0.6.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|