dar-backup 0.6.16__py3-none-any.whl → 0.6.17__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/cleanup.py +8 -16
- dar_backup/command_runner.py +81 -0
- dar_backup/config_settings.py +7 -1
- dar_backup/dar-backup.conf +1 -1
- dar_backup/dar_backup.py +21 -18
- dar_backup/manager.py +15 -9
- dar_backup/util.py +0 -103
- {dar_backup-0.6.16.dist-info → dar_backup-0.6.17.dist-info}/METADATA +122 -68
- dar_backup-0.6.17.dist-info/RECORD +17 -0
- dar_backup-0.6.16.dist-info/RECORD +0 -16
- {dar_backup-0.6.16.dist-info → dar_backup-0.6.17.dist-info}/WHEEL +0 -0
- {dar_backup-0.6.16.dist-info → dar_backup-0.6.17.dist-info}/entry_points.txt +0 -0
- {dar_backup-0.6.16.dist-info → dar_backup-0.6.17.dist-info}/licenses/LICENSE +0 -0
dar_backup/__about__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.6.
|
|
1
|
+
__version__ = "0.6.17"
|
dar_backup/cleanup.py
CHANGED
|
@@ -29,14 +29,14 @@ from . import __about__ as about
|
|
|
29
29
|
from dar_backup.config_settings import ConfigSettings
|
|
30
30
|
from dar_backup.util import extract_error_lines
|
|
31
31
|
from dar_backup.util import list_backups
|
|
32
|
-
from dar_backup.util import run_command
|
|
33
32
|
from dar_backup.util import setup_logging
|
|
33
|
+
from dar_backup.util import get_logger
|
|
34
34
|
|
|
35
|
-
from dar_backup.
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
from dar_backup.command_runner import CommandRunner
|
|
36
|
+
from dar_backup.command_runner import CommandResult
|
|
38
37
|
|
|
39
38
|
logger = None
|
|
39
|
+
runner = None
|
|
40
40
|
|
|
41
41
|
def delete_old_backups(backup_dir, age, backup_type, args, backup_definition=None):
|
|
42
42
|
"""
|
|
@@ -134,7 +134,7 @@ def delete_catalog(catalog_name: str, args: NamedTuple) -> bool:
|
|
|
134
134
|
command = [f"manager", "--remove-specific-archive", catalog_name, "--config-file", args.config_file, '--log-level', 'debug', '--log-stdout']
|
|
135
135
|
logger.info(f"Deleting catalog '{catalog_name}' using config file: '{args.config_file}'")
|
|
136
136
|
try:
|
|
137
|
-
result:CommandResult =
|
|
137
|
+
result:CommandResult = runner.run(command)
|
|
138
138
|
if result.returncode == 0:
|
|
139
139
|
logger.info(f"Deleted catalog '{catalog_name}', using config file: '{args.config_file}'")
|
|
140
140
|
logger.debug(f"Stdout: manager.py --remove-specific-archive output:\n{result.stdout}")
|
|
@@ -159,7 +159,7 @@ See section 15 and section 16 in the supplied "LICENSE" file.''')
|
|
|
159
159
|
|
|
160
160
|
|
|
161
161
|
def main():
|
|
162
|
-
global logger
|
|
162
|
+
global logger, runner
|
|
163
163
|
|
|
164
164
|
parser = argparse.ArgumentParser(description="Cleanup old archives according to AGE configuration.")
|
|
165
165
|
parser.add_argument('-d', '--backup-definition', help="Specific backup definition to cleanup.")
|
|
@@ -188,7 +188,8 @@ def main():
|
|
|
188
188
|
# command_output_log = os.path.join(config_settings.logfile_location.removesuffix("dar-backup.log"), "dar-backup-commands.log")
|
|
189
189
|
command_output_log = config_settings.logfile_location.replace("dar-backup.log", "dar-backup-commands.log")
|
|
190
190
|
logger = setup_logging(config_settings.logfile_location, command_output_log, args.log_level, args.log_stdout)
|
|
191
|
-
|
|
191
|
+
command_logger = get_logger(command_output_logger = True)
|
|
192
|
+
runner = CommandRunner(logger=logger, command_logger=command_logger)
|
|
192
193
|
|
|
193
194
|
logger.info(f"=====================================")
|
|
194
195
|
logger.info(f"cleanup.py started, version: {about.__version__}")
|
|
@@ -283,15 +284,6 @@ def main():
|
|
|
283
284
|
end_time=int(time())
|
|
284
285
|
logger.info(f"END TIME: {end_time}")
|
|
285
286
|
|
|
286
|
-
# error_lines = extract_error_lines(config_settings.logfile_location, start_time, end_time)
|
|
287
|
-
# if len(error_lines) > 0:
|
|
288
|
-
# args.verbose and print("\033[1m\033[31mErrors\033[0m encountered")
|
|
289
|
-
# for line in error_lines:
|
|
290
|
-
# args.verbose and print(line)
|
|
291
|
-
# sys.exit(1)
|
|
292
|
-
# else:
|
|
293
|
-
# args.verbose and print("\033[1m\033[32mSUCCESS\033[0m No errors encountered")
|
|
294
|
-
# sys.exit(0)
|
|
295
287
|
|
|
296
288
|
if __name__ == "__main__":
|
|
297
289
|
main()
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import logging
|
|
3
|
+
import threading
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../src")))
|
|
7
|
+
from typing import List, Optional
|
|
8
|
+
|
|
9
|
+
class CommandResult:
|
|
10
|
+
def __init__(self, returncode: int, stdout: str, stderr: str):
|
|
11
|
+
self.returncode = returncode
|
|
12
|
+
self.stdout = stdout
|
|
13
|
+
self.stderr = stderr
|
|
14
|
+
|
|
15
|
+
def __repr__(self):
|
|
16
|
+
return f"<CommandResult returncode={self.returncode}>"
|
|
17
|
+
|
|
18
|
+
class CommandRunner:
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
logger: Optional[logging.Logger] = None,
|
|
22
|
+
command_logger: Optional[logging.Logger] = None,
|
|
23
|
+
default_timeout: int = 30
|
|
24
|
+
):
|
|
25
|
+
self.logger = logger or logging.getLogger(__name__)
|
|
26
|
+
self.command_logger = command_logger or self.logger
|
|
27
|
+
self.default_timeout = default_timeout
|
|
28
|
+
|
|
29
|
+
def run(
|
|
30
|
+
self,
|
|
31
|
+
cmd: List[str],
|
|
32
|
+
*,
|
|
33
|
+
timeout: Optional[int] = None,
|
|
34
|
+
check: bool = False,
|
|
35
|
+
capture_output: bool = True,
|
|
36
|
+
text: bool = True
|
|
37
|
+
) -> CommandResult:
|
|
38
|
+
timeout = timeout or self.default_timeout
|
|
39
|
+
self.logger.debug(f"Executing command: {' '.join(cmd)} (timeout={timeout}s)")
|
|
40
|
+
|
|
41
|
+
process = subprocess.Popen(
|
|
42
|
+
cmd,
|
|
43
|
+
stdout=subprocess.PIPE if capture_output else None,
|
|
44
|
+
stderr=subprocess.PIPE if capture_output else None,
|
|
45
|
+
text=text,
|
|
46
|
+
bufsize=1
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
stdout_lines = []
|
|
50
|
+
stderr_lines = []
|
|
51
|
+
|
|
52
|
+
def stream_output(stream, lines, level):
|
|
53
|
+
for line in iter(stream.readline, ''):
|
|
54
|
+
lines.append(line)
|
|
55
|
+
self.command_logger.log(level, line.strip())
|
|
56
|
+
stream.close()
|
|
57
|
+
|
|
58
|
+
threads = []
|
|
59
|
+
if capture_output and process.stdout:
|
|
60
|
+
t_out = threading.Thread(target=stream_output, args=(process.stdout, stdout_lines, logging.INFO))
|
|
61
|
+
t_out.start()
|
|
62
|
+
threads.append(t_out)
|
|
63
|
+
if capture_output and process.stderr:
|
|
64
|
+
t_err = threading.Thread(target=stream_output, args=(process.stderr, stderr_lines, logging.ERROR))
|
|
65
|
+
t_err.start()
|
|
66
|
+
threads.append(t_err)
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
process.wait(timeout=timeout)
|
|
70
|
+
except subprocess.TimeoutExpired:
|
|
71
|
+
process.kill()
|
|
72
|
+
self.logger.error(f"Command timed out: {' '.join(cmd)}")
|
|
73
|
+
return CommandResult(-1, ''.join(stdout_lines), ''.join(stderr_lines))
|
|
74
|
+
|
|
75
|
+
for t in threads:
|
|
76
|
+
t.join()
|
|
77
|
+
|
|
78
|
+
if check and process.returncode != 0:
|
|
79
|
+
self.logger.error(f"Command failed with exit code {process.returncode}")
|
|
80
|
+
|
|
81
|
+
return CommandResult(process.returncode, ''.join(stdout_lines), ''.join(stderr_lines))
|
dar_backup/config_settings.py
CHANGED
|
@@ -63,7 +63,13 @@ class ConfigSettings:
|
|
|
63
63
|
self.diff_age = int(self.config['AGE']['DIFF_AGE'])
|
|
64
64
|
self.incr_age = int(self.config['AGE']['INCR_AGE'])
|
|
65
65
|
self.error_correction_percent = int(self.config['PAR2']['ERROR_CORRECTION_PERCENT'])
|
|
66
|
-
|
|
66
|
+
val = self.config['PAR2']['ENABLED'].strip().lower()
|
|
67
|
+
if val in ('true', '1', 'yes'):
|
|
68
|
+
self.par2_enabled = True
|
|
69
|
+
elif val in ('false', '0', 'no'):
|
|
70
|
+
self.par2_enabled = False
|
|
71
|
+
else:
|
|
72
|
+
raise ValueError(f"Invalid boolean value for 'ENABLED' in [PAR2]: '{val}'")
|
|
67
73
|
|
|
68
74
|
# Ensure the directories exist
|
|
69
75
|
Path(self.backup_dir).mkdir(parents=True, exist_ok=True)
|
dar_backup/dar-backup.conf
CHANGED
|
@@ -9,7 +9,7 @@ MIN_SIZE_VERIFICATION_MB = 1
|
|
|
9
9
|
NO_FILES_VERIFICATION = 5
|
|
10
10
|
# timeout in seconds for backup, test, restore and par2 operations
|
|
11
11
|
# The author has such `dar` tasks running for 10-15 hours on the yearly backups, so a value of 24 hours is used.
|
|
12
|
-
# If a timeout is not specified when using the
|
|
12
|
+
# If a timeout is not specified when using the CommandRunner, a default timeout of 30 secs is used.
|
|
13
13
|
COMMAND_TIMEOUT_SECS = 86400
|
|
14
14
|
|
|
15
15
|
[DIRECTORIES]
|
dar_backup/dar_backup.py
CHANGED
|
@@ -25,14 +25,17 @@ from typing import List
|
|
|
25
25
|
from . import __about__ as about
|
|
26
26
|
from dar_backup.config_settings import ConfigSettings
|
|
27
27
|
from dar_backup.util import list_backups
|
|
28
|
-
from dar_backup.util import run_command
|
|
29
28
|
from dar_backup.util import setup_logging
|
|
30
29
|
from dar_backup.util import get_logger
|
|
31
30
|
from dar_backup.util import BackupError
|
|
32
31
|
from dar_backup.util import RestoreError
|
|
33
32
|
|
|
33
|
+
from dar_backup.command_runner import CommandRunner
|
|
34
|
+
from dar_backup.command_runner import CommandResult
|
|
35
|
+
|
|
34
36
|
|
|
35
37
|
logger = None
|
|
38
|
+
runner = None
|
|
36
39
|
|
|
37
40
|
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
41
|
"""
|
|
@@ -63,9 +66,8 @@ def generic_backup(type: str, command: List[str], backup_file: str, backup_defin
|
|
|
63
66
|
result: List[tuple] = []
|
|
64
67
|
|
|
65
68
|
logger.info(f"===> Starting {type} backup for {backup_definition}")
|
|
66
|
-
logger.info(f"Running command: {' '.join(map(shlex.quote, command))}")
|
|
67
69
|
try:
|
|
68
|
-
process =
|
|
70
|
+
process = runner.run(command, timeout = config_settings.command_timeout_secs)
|
|
69
71
|
if process.returncode == 0:
|
|
70
72
|
logger.info(f"{type} backup completed successfully.")
|
|
71
73
|
elif process.returncode == 5:
|
|
@@ -75,7 +77,7 @@ def generic_backup(type: str, command: List[str], backup_file: str, backup_defin
|
|
|
75
77
|
|
|
76
78
|
if process.returncode == 0 or process.returncode == 5:
|
|
77
79
|
add_catalog_command = ['manager', '--add-specific-archive' ,backup_file, '--config-file', args.config_file]
|
|
78
|
-
command_result =
|
|
80
|
+
command_result = runner.run(add_catalog_command, timeout = config_settings.command_timeout_secs)
|
|
79
81
|
if command_result.returncode == 0:
|
|
80
82
|
logger.info(f"Catalog for archive '{backup_file}' added successfully to its manager.")
|
|
81
83
|
else:
|
|
@@ -192,8 +194,7 @@ def verify(args: argparse.Namespace, backup_file: str, backup_definition: str, c
|
|
|
192
194
|
"""
|
|
193
195
|
result = True
|
|
194
196
|
command = ['dar', '-t', backup_file, '-Q']
|
|
195
|
-
|
|
196
|
-
process = run_command(command, config_settings.command_timeout_secs)
|
|
197
|
+
process = runner.run(command, timeout = config_settings.command_timeout_secs)
|
|
197
198
|
if process.returncode == 0:
|
|
198
199
|
logger.info("Archive integrity test passed.")
|
|
199
200
|
else:
|
|
@@ -235,7 +236,7 @@ def verify(args: argparse.Namespace, backup_file: str, backup_definition: str, c
|
|
|
235
236
|
args.verbose and logger.info(f"Restoring file: '{restored_file_path}' from backup to: '{config_settings.test_restore_dir}' for file comparing")
|
|
236
237
|
command = ['dar', '-x', backup_file, '-g', restored_file_path.lstrip("/"), '-R', config_settings.test_restore_dir, '-Q', '-B', args.darrc, 'restore-options']
|
|
237
238
|
args.verbose and logger.info(f"Running command: {' '.join(map(shlex.quote, command))}")
|
|
238
|
-
process =
|
|
239
|
+
process = runner.run(command, timeout = config_settings.command_timeout_secs)
|
|
239
240
|
if process.returncode != 0:
|
|
240
241
|
raise Exception(str(process))
|
|
241
242
|
|
|
@@ -276,7 +277,7 @@ def restore_backup(backup_name: str, config_settings: ConfigSettings, restore_di
|
|
|
276
277
|
command.extend(selection_criteria)
|
|
277
278
|
command.extend(['-B', darrc, 'restore-options']) # the .darrc `restore-options` section
|
|
278
279
|
logger.info(f"Running restore command: {' '.join(map(shlex.quote, command))}")
|
|
279
|
-
process =
|
|
280
|
+
process = runner.run(command, timeout = config_settings.command_timeout_secs)
|
|
280
281
|
if process.returncode == 0:
|
|
281
282
|
logger.info(f"Restore completed successfully to: '{restore_dir}'")
|
|
282
283
|
else:
|
|
@@ -309,7 +310,7 @@ def get_backed_up_files(backup_name: str, backup_dir: str):
|
|
|
309
310
|
try:
|
|
310
311
|
command = ['dar', '-l', backup_path, '-am', '-as', "-Txml" , '-Q']
|
|
311
312
|
logger.debug(f"Running command: {' '.join(map(shlex.quote, command))}")
|
|
312
|
-
command_result =
|
|
313
|
+
command_result = runner.run(command)
|
|
313
314
|
# Parse the XML data
|
|
314
315
|
file_paths = find_files_with_paths(command_result.stdout)
|
|
315
316
|
return file_paths
|
|
@@ -339,8 +340,7 @@ def list_contents(backup_name, backup_dir, selection=None):
|
|
|
339
340
|
if selection:
|
|
340
341
|
selection_criteria = shlex.split(selection)
|
|
341
342
|
command.extend(selection_criteria)
|
|
342
|
-
|
|
343
|
-
process = run_command(command)
|
|
343
|
+
process = runner.run(command)
|
|
344
344
|
stdout,stderr = process.stdout, process.stderr
|
|
345
345
|
if process.returncode != 0:
|
|
346
346
|
logger.error(f"Error listing contents of backup: '{backup_name}'")
|
|
@@ -512,7 +512,7 @@ def generate_par2_files(backup_file: str, config_settings: ConfigSettings, args)
|
|
|
512
512
|
|
|
513
513
|
# Run the par2 command to generate redundancy files with error correction
|
|
514
514
|
command = ['par2', 'create', f'-r{config_settings.error_correction_percent}', '-q', '-q', file_path]
|
|
515
|
-
process =
|
|
515
|
+
process = runner.run(command, timeout = config_settings.command_timeout_secs)
|
|
516
516
|
|
|
517
517
|
if process.returncode == 0:
|
|
518
518
|
logger.info(f"{counter}/{number_of_slices}: Done")
|
|
@@ -629,7 +629,7 @@ def requirements(type: str, config_setting: ConfigSettings):
|
|
|
629
629
|
config_settings (ConfigSettings): An instance of the ConfigSettings class.
|
|
630
630
|
|
|
631
631
|
Raises:
|
|
632
|
-
RuntimeError: If a subprocess
|
|
632
|
+
RuntimeError: If a subprocess returns anything but zero.
|
|
633
633
|
|
|
634
634
|
subprocess.CalledProcessError: if CalledProcessError is raised in subprocess.run(), let it bobble up.
|
|
635
635
|
"""
|
|
@@ -641,7 +641,7 @@ def requirements(type: str, config_setting: ConfigSettings):
|
|
|
641
641
|
raise RuntimeError(f"requirements: {type} not in: {allowed_types}")
|
|
642
642
|
|
|
643
643
|
|
|
644
|
-
logger.
|
|
644
|
+
logger.debug(f"Performing {type}")
|
|
645
645
|
if type in config_setting.config:
|
|
646
646
|
for key in sorted(config_setting.config[type].keys()):
|
|
647
647
|
script = config_setting.config[type][key]
|
|
@@ -658,7 +658,7 @@ def requirements(type: str, config_setting: ConfigSettings):
|
|
|
658
658
|
|
|
659
659
|
|
|
660
660
|
def main():
|
|
661
|
-
global logger
|
|
661
|
+
global logger, runner
|
|
662
662
|
results: List[(str,int)] = [] # a list op tuples (<msg>, <exit code>)
|
|
663
663
|
|
|
664
664
|
MIN_PYTHON_VERSION = (3, 9)
|
|
@@ -713,6 +713,9 @@ def main():
|
|
|
713
713
|
print(f"Error: logfile_location in {args.config_file} does not end at 'dar-backup.log', exiting", file=stderr)
|
|
714
714
|
|
|
715
715
|
logger = setup_logging(config_settings.logfile_location, command_output_log, args.log_level, args.log_stdout)
|
|
716
|
+
command_logger = get_logger(command_output_logger = True)
|
|
717
|
+
runner = CommandRunner(logger=logger, command_logger=command_logger)
|
|
718
|
+
|
|
716
719
|
|
|
717
720
|
try:
|
|
718
721
|
if not args.darrc:
|
|
@@ -741,9 +744,9 @@ def main():
|
|
|
741
744
|
file_dir = os.path.normpath(os.path.dirname(__file__))
|
|
742
745
|
args.verbose and (print(f"Script directory: {file_dir}"))
|
|
743
746
|
args.verbose and (print(f"Config file: {args.config_file}"))
|
|
744
|
-
args.verbose and args.full_backup and (print(f"Type of backup:
|
|
745
|
-
args.verbose and args.differential_backup and (print(f"Type of backup:
|
|
746
|
-
args.verbose and args.incremental_backup and (print(f"Type of backup:
|
|
747
|
+
args.verbose and args.full_backup and (print(f"Type of backup: FULL"))
|
|
748
|
+
args.verbose and args.differential_backup and (print(f"Type of backup: DIFF"))
|
|
749
|
+
args.verbose and args.incremental_backup and (print(f"Type of backup: INCR"))
|
|
747
750
|
args.verbose and args.backup_definition and (print(f"Backup definition: '{args.backup_definition}'"))
|
|
748
751
|
if args.alternate_reference_archive:
|
|
749
752
|
args.verbose and (print(f"Alternate ref archive: {args.alternate_reference_archive}"))
|
dar_backup/manager.py
CHANGED
|
@@ -29,9 +29,12 @@ import sys
|
|
|
29
29
|
|
|
30
30
|
from . import __about__ as about
|
|
31
31
|
from dar_backup.config_settings import ConfigSettings
|
|
32
|
-
from dar_backup.util import run_command
|
|
33
32
|
from dar_backup.util import setup_logging
|
|
34
33
|
from dar_backup.util import CommandResult
|
|
34
|
+
from dar_backup.util import get_logger
|
|
35
|
+
|
|
36
|
+
from dar_backup.command_runner import CommandRunner
|
|
37
|
+
from dar_backup.command_runner import CommandResult
|
|
35
38
|
|
|
36
39
|
from datetime import datetime
|
|
37
40
|
from time import time
|
|
@@ -44,6 +47,7 @@ SCRIPTDIRPATH = os.path.dirname(SCRIPTPATH)
|
|
|
44
47
|
DB_SUFFIX = ".db"
|
|
45
48
|
|
|
46
49
|
logger = None
|
|
50
|
+
runner = None
|
|
47
51
|
|
|
48
52
|
def show_more_help():
|
|
49
53
|
help_text = f"""
|
|
@@ -66,7 +70,7 @@ def create_db(backup_def: str, config_settings: ConfigSettings):
|
|
|
66
70
|
else:
|
|
67
71
|
logger.info(f'Create catalog database: "{database_path}"')
|
|
68
72
|
command = ['dar_manager', '--create' , database_path]
|
|
69
|
-
process =
|
|
73
|
+
process = runner.run(command)
|
|
70
74
|
logger.debug(f"return code from 'db created': {process.returncode}")
|
|
71
75
|
if process.returncode == 0:
|
|
72
76
|
logger.info(f'Database created: "{database_path}"')
|
|
@@ -104,7 +108,7 @@ def list_catalogs(backup_def: str, config_settings: ConfigSettings) -> NamedTupl
|
|
|
104
108
|
command=[])
|
|
105
109
|
return commandResult
|
|
106
110
|
command = ['dar_manager', '--base', database_path, '--list']
|
|
107
|
-
process =
|
|
111
|
+
process = runner.run(command)
|
|
108
112
|
stdout, stderr = process.stdout, process.stderr
|
|
109
113
|
if process.returncode != 0:
|
|
110
114
|
logger.error(f'Error listing catalogs for: "{database_path}"')
|
|
@@ -156,7 +160,7 @@ def list_archive_contents(archive: str, config_settings: ConfigSettings) -> int
|
|
|
156
160
|
logger.error(f"archive: '{archive}' not found in database: '{database_path}'")
|
|
157
161
|
return 1
|
|
158
162
|
command = ['dar_manager', '--base', database_path, '-u', f"{cat_no}"]
|
|
159
|
-
process =
|
|
163
|
+
process = runner.run(command)
|
|
160
164
|
stdout, stderr = process.stdout, process.stderr
|
|
161
165
|
if process.returncode != 0:
|
|
162
166
|
logger.error(f'Error listing catalogs for: "{database_path}"')
|
|
@@ -178,7 +182,7 @@ def list_catalog_contents(catalog_number: int, backup_def: str, config_settings:
|
|
|
178
182
|
logger.error(f'Catalog database not found: "{database_path}"')
|
|
179
183
|
return 1
|
|
180
184
|
command = ['dar_manager', '--base', database_path, '-u', f"{catalog_number}"]
|
|
181
|
-
process =
|
|
185
|
+
process = runner.run(command)
|
|
182
186
|
stdout, stderr = process.stdout, process.stderr
|
|
183
187
|
if process.returncode != 0:
|
|
184
188
|
logger.error(f'Error listing catalogs for: "{database_path}"')
|
|
@@ -199,7 +203,7 @@ def find_file(file, backup_def, config_settings):
|
|
|
199
203
|
logger.error(f'Database not found: "{database_path}"')
|
|
200
204
|
return 1
|
|
201
205
|
command = ['dar_manager', '--base', database_path, '-f', f"{file}"]
|
|
202
|
-
process =
|
|
206
|
+
process = runner.run(command)
|
|
203
207
|
stdout, stderr = process.stdout, process.stderr
|
|
204
208
|
if process.returncode != 0:
|
|
205
209
|
logger.error(f'Error finding file: {file} in: "{database_path}"')
|
|
@@ -234,7 +238,7 @@ def add_specific_archive(archive: str, config_settings: ConfigSettings, director
|
|
|
234
238
|
logger.info(f'Add "{archive_path}" to catalog: "{database}"')
|
|
235
239
|
|
|
236
240
|
command = ['dar_manager', '--base', database_path, "--add", archive_path, "-Q"]
|
|
237
|
-
process =
|
|
241
|
+
process = runner.run(command)
|
|
238
242
|
stdout, stderr = process.stdout, process.stderr
|
|
239
243
|
|
|
240
244
|
if process.returncode == 0:
|
|
@@ -338,7 +342,7 @@ def remove_specific_archive(archive: str, config_settings: ConfigSettings) -> in
|
|
|
338
342
|
cat_no:int = cat_no_for_name(archive, config_settings)
|
|
339
343
|
if cat_no >= 0:
|
|
340
344
|
command = ['dar_manager', '--base', database_path, "--delete", str(cat_no)]
|
|
341
|
-
process: CommandResult =
|
|
345
|
+
process: CommandResult = runner.run(command)
|
|
342
346
|
logger.info(f"CommandResult: {process}")
|
|
343
347
|
else:
|
|
344
348
|
logger.warning(f"archive: '{archive}' not found in it's catalog database: {database_path}")
|
|
@@ -355,7 +359,7 @@ def remove_specific_archive(archive: str, config_settings: ConfigSettings) -> in
|
|
|
355
359
|
|
|
356
360
|
|
|
357
361
|
def main():
|
|
358
|
-
global logger
|
|
362
|
+
global logger, runner
|
|
359
363
|
|
|
360
364
|
MIN_PYTHON_VERSION = (3, 9)
|
|
361
365
|
if sys.version_info < MIN_PYTHON_VERSION:
|
|
@@ -404,6 +408,8 @@ See section 15 and section 16 in the supplied "LICENSE" file.''')
|
|
|
404
408
|
# command_output_log = os.path.join(config_settings.logfile_location.removesuffix("dar-backup.log"), "dar-backup-commands.log")
|
|
405
409
|
command_output_log = config_settings.logfile_location.replace("dar-backup.log", "dar-backup-commands.log")
|
|
406
410
|
logger = setup_logging(config_settings.logfile_location, command_output_log, args.log_level, args.log_stdout)
|
|
411
|
+
command_logger = get_logger(command_output_logger = True)
|
|
412
|
+
runner = CommandRunner(logger=logger, command_logger=command_logger)
|
|
407
413
|
|
|
408
414
|
|
|
409
415
|
start_time=int(time())
|
dar_backup/util.py
CHANGED
|
@@ -97,109 +97,6 @@ def get_logger(command_output_logger: bool = False) -> logging.Logger:
|
|
|
97
97
|
return secondary_logger if command_output_logger else logger
|
|
98
98
|
|
|
99
99
|
|
|
100
|
-
|
|
101
|
-
def _stream_reader(pipe, log_funcs, output_accumulator: List[str]):
|
|
102
|
-
"""
|
|
103
|
-
Reads lines from the subprocess pipe and logs them to multiple destinations.
|
|
104
|
-
"""
|
|
105
|
-
with pipe:
|
|
106
|
-
for line in iter(pipe.readline, ''):
|
|
107
|
-
stripped_line = line.strip()
|
|
108
|
-
output_accumulator.append(stripped_line)
|
|
109
|
-
for log_func in log_funcs:
|
|
110
|
-
log_func(stripped_line) # Log the output in real-time
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
def run_command(command: List[str], timeout: int = 30, no_output_log: bool = False):
|
|
115
|
-
"""
|
|
116
|
-
Executes a command and streams output only to the secondary log unless no_log is set to True.
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
Returns:
|
|
120
|
-
A CommandResult NamedTuple with the following properties:
|
|
121
|
-
- process: subprocess.CompletedProcess
|
|
122
|
-
- stdout: str: The full standard output of the command.
|
|
123
|
-
- stderr: str: The full standard error of the command.
|
|
124
|
-
- returncode: int: The return code of the command.
|
|
125
|
-
- timeout: int: The timeout value in seconds used to run the command.
|
|
126
|
-
- command: list[str]: The command executed.
|
|
127
|
-
|
|
128
|
-
Logs:
|
|
129
|
-
- Logs standard output (`stdout`) and standard error in real-time to the
|
|
130
|
-
logger.secondary_log (that contains the command output).
|
|
131
|
-
|
|
132
|
-
Raises:
|
|
133
|
-
subprocess.TimeoutExpired: If the command execution times out (see `timeout` parameter).
|
|
134
|
-
Exception: If other exceptions occur during command execution.
|
|
135
|
-
FileNotFoundError: If the command is not found.
|
|
136
|
-
"""
|
|
137
|
-
stdout_lines, stderr_lines = [], []
|
|
138
|
-
process = None
|
|
139
|
-
stdout_thread, stderr_thread = None, None
|
|
140
|
-
|
|
141
|
-
try:
|
|
142
|
-
logger = get_logger(command_output_logger=False)
|
|
143
|
-
command_logger = get_logger(command_output_logger=True)
|
|
144
|
-
|
|
145
|
-
if not shutil.which(command[0]):
|
|
146
|
-
raise FileNotFoundError(f"Command not found: {command[0]}")
|
|
147
|
-
|
|
148
|
-
logger.debug(f"Running command: {command}")
|
|
149
|
-
command_logger.info(f"Running command: {command}")
|
|
150
|
-
|
|
151
|
-
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
|
152
|
-
|
|
153
|
-
log_funcs = [command_logger.info] if not no_output_log else []
|
|
154
|
-
err_log_funcs = [command_logger.error] if not no_output_log else []
|
|
155
|
-
|
|
156
|
-
stdout_thread = threading.Thread(target=_stream_reader, args=(process.stdout, log_funcs, stdout_lines))
|
|
157
|
-
stderr_thread = threading.Thread(target=_stream_reader, args=(process.stderr, err_log_funcs, stderr_lines))
|
|
158
|
-
|
|
159
|
-
stdout_thread.start()
|
|
160
|
-
stderr_thread.start()
|
|
161
|
-
|
|
162
|
-
process.wait(timeout=timeout)
|
|
163
|
-
|
|
164
|
-
except FileNotFoundError as e:
|
|
165
|
-
logger.error(f"Command not found: {command[0]}")
|
|
166
|
-
return CommandResult(
|
|
167
|
-
process=None,
|
|
168
|
-
stdout="",
|
|
169
|
-
stderr=str(e),
|
|
170
|
-
returncode=127,
|
|
171
|
-
timeout=timeout,
|
|
172
|
-
command=command
|
|
173
|
-
)
|
|
174
|
-
except subprocess.TimeoutExpired:
|
|
175
|
-
if process:
|
|
176
|
-
process.terminate()
|
|
177
|
-
logger.error(f"Command: '{command}' timed out and was terminated.")
|
|
178
|
-
raise
|
|
179
|
-
except Exception as e:
|
|
180
|
-
logger.error(f"Error running command: {command}", exc_info=True)
|
|
181
|
-
raise
|
|
182
|
-
finally:
|
|
183
|
-
if stdout_thread and stdout_thread.is_alive():
|
|
184
|
-
stdout_thread.join()
|
|
185
|
-
if stderr_thread and stderr_thread.is_alive():
|
|
186
|
-
stderr_thread.join()
|
|
187
|
-
if process:
|
|
188
|
-
if process.stdout:
|
|
189
|
-
process.stdout.close()
|
|
190
|
-
if process.stderr:
|
|
191
|
-
process.stderr.close()
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
# Combine captured stdout and stderr lines into single strings
|
|
195
|
-
stdout = "\n".join(stdout_lines)
|
|
196
|
-
stderr = "\n".join(stderr_lines)
|
|
197
|
-
|
|
198
|
-
#Build the result object
|
|
199
|
-
result = CommandResult(process=process, stdout=stdout, stderr=stderr, returncode=process.returncode, timeout=timeout, command=command)
|
|
200
|
-
return result
|
|
201
|
-
|
|
202
|
-
|
|
203
100
|
class BackupError(Exception):
|
|
204
101
|
"""Exception raised for errors in the backup process."""
|
|
205
102
|
pass
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dar-backup
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.17
|
|
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
|
|
@@ -744,13 +744,15 @@ This is the `Python` based **version 2** of `dar-backup`.
|
|
|
744
744
|
- [Performance tip due to par2](#performance-tip-due-to-par2)
|
|
745
745
|
- [.darrc sets -vd -vf (since v0.6.4)](#darrc-sets--vd--vf-since-v064)
|
|
746
746
|
- [Separate log file for command output](#separate-log-file-for-command-output)
|
|
747
|
+
- [Skipping cache directories](#skipping-cache-directories)
|
|
747
748
|
- [Todo](#todo)
|
|
748
749
|
- [Reference](#reference)
|
|
749
|
-
- [
|
|
750
|
-
- [
|
|
751
|
-
- [
|
|
752
|
-
- [
|
|
753
|
-
- [
|
|
750
|
+
- [Test coverage report](#test-coverage)
|
|
751
|
+
- [dar-backup](#dar-backup-options)
|
|
752
|
+
- [manager](#manager-options)
|
|
753
|
+
- [cleanup](#cleanup-options)
|
|
754
|
+
- [clean-log](#clean-log-options)
|
|
755
|
+
- [installer](#installer-options)
|
|
754
756
|
|
|
755
757
|
## My use case
|
|
756
758
|
|
|
@@ -807,15 +809,24 @@ On Ubuntu, install the requirements this way:
|
|
|
807
809
|
|
|
808
810
|
### dar-backup
|
|
809
811
|
|
|
810
|
-
`dar-backup` is built in a way that emphasizes getting backups. It loops over the backup definitions, and in the event of a failure while backing up a backup definition, dar-backup shall log an error and start working on the next backup definition.
|
|
812
|
+
`dar-backup` is built in a way that emphasizes getting backups. It loops over the [backup definitions](#backup-definition-example), and in the event of a failure while backing up a backup definition, dar-backup shall log an error and start working on the next backup definition.
|
|
811
813
|
|
|
812
814
|
There are 3 levels of backups, FULL, DIFF and INCR.
|
|
813
815
|
|
|
814
|
-
- The author does a FULL yearly backup once a year. This includes all files in all directories as defined in the backup definition(s).
|
|
815
|
-
- The author makes a DIFF once a month. The DIFF backs up new and changed files compared to the FULL backup.
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
816
|
+
- The author does a FULL yearly backup once a year. This includes all files in all directories as defined in the backup definition(s) (assuming `-d` was not given).
|
|
817
|
+
- The author makes a DIFF once a month. The DIFF backs up new and changed files **compared** to the **FULL** backup.
|
|
818
|
+
|
|
819
|
+
- No DIFF backups are taken until a FULL backup has been taken for a particular backup definition.
|
|
820
|
+
|
|
821
|
+
- The author takes an INCR backup every 3 days. An INCR backup includes new and changed files **compared** to the **DIFF** backup.
|
|
822
|
+
|
|
823
|
+
- So, a set of INCR's will contain duplicates (this might change as I become more used to use the catalog databases)
|
|
824
|
+
|
|
825
|
+
- No INCR backups are taken until a DIFF backup has been taken for a particular backup definition.
|
|
826
|
+
|
|
827
|
+
After each backup of a backup definition, `dar-backup` tests the archive and then performs a few restore operations of random files from the archive (see [dar-backup.conf](#config-file)). The restored files are compared to the originals to check if the restore went well.
|
|
828
|
+
|
|
829
|
+
`dar-backup` skips doing a backup of a backup definition if an archive is already in place. So, if you for some reason need to take a new backup on the same date, the first archive must be deleted (I recommend using [cleanup](#cleanup-1)).
|
|
819
830
|
|
|
820
831
|
### cleanup
|
|
821
832
|
|
|
@@ -823,6 +834,12 @@ The `cleanup` application deletes DIFF and INCR if the archives are older than t
|
|
|
823
834
|
|
|
824
835
|
`cleanup` will only remove FULL archives if the option `--cleanup-specific-archives` is used. It requires the user to confirm deletion of FULL archives.
|
|
825
836
|
|
|
837
|
+
### manager
|
|
838
|
+
|
|
839
|
+
`dar`has the concept of catalogs which can be exported and optionally be added to a catalog database. That database makes it much easier to restore the correct version of a backed up file if for example a target date has been set.
|
|
840
|
+
|
|
841
|
+
`dar-backup` adds archive catalogs to their databases (using the `manager` script). Should the operation fail, `dar-backup` logs an error and continue with testing and restore validation tests.
|
|
842
|
+
|
|
826
843
|
## How to run
|
|
827
844
|
|
|
828
845
|
### 1 - installation
|
|
@@ -839,7 +856,6 @@ Note:
|
|
|
839
856
|
|
|
840
857
|
The module `inputimeout` is installed into the venv and used for the confirmation input (with a 30 second timeout)
|
|
841
858
|
|
|
842
|
-
|
|
843
859
|
To install, create a venv and run pip:
|
|
844
860
|
|
|
845
861
|
```` bash
|
|
@@ -902,7 +918,7 @@ manager --create-db
|
|
|
902
918
|
### 4 - do FULL backups
|
|
903
919
|
|
|
904
920
|
Prereq:
|
|
905
|
-
Backup definitions are in place in BACKUP.D_DIR (see config file)
|
|
921
|
+
[Backup definitions](#backup-definition-example) are in place in BACKUP.D_DIR (see [config file](#config-file)).
|
|
906
922
|
|
|
907
923
|
You are ready to do backups of all your backup definitions.
|
|
908
924
|
|
|
@@ -975,7 +991,7 @@ SCRIPT_1 = df -h
|
|
|
975
991
|
|
|
976
992
|
### .darrc
|
|
977
993
|
|
|
978
|
-
The package includes a default
|
|
994
|
+
The package includes a default `darrc` file which configures `dar`.
|
|
979
995
|
|
|
980
996
|
You can override the default `.darrc` using the `--darrc` option.
|
|
981
997
|
|
|
@@ -1106,7 +1122,7 @@ compress-exclusion:
|
|
|
1106
1122
|
|
|
1107
1123
|
### Backup definition example
|
|
1108
1124
|
|
|
1109
|
-
This piece of configuration is a
|
|
1125
|
+
This piece of configuration is a [backup definition](#backup-definition-example). It is placed in the BACKUP.D_DIR (see config file description).
|
|
1110
1126
|
The name of the file is the name of the backup definition.
|
|
1111
1127
|
|
|
1112
1128
|
You can use as many backup definitions as you need.
|
|
@@ -1143,6 +1159,7 @@ You can use as many backup definitions as you need.
|
|
|
1143
1159
|
|
|
1144
1160
|
# bypass directores marked as cache directories
|
|
1145
1161
|
# http://dar.linux.free.fr/doc/Features.html
|
|
1162
|
+
# https://bford.info/cachedir/
|
|
1146
1163
|
--cache-directory-tagging
|
|
1147
1164
|
````
|
|
1148
1165
|
|
|
@@ -1367,7 +1384,7 @@ deactivate
|
|
|
1367
1384
|
|
|
1368
1385
|
"dar" in newer versions emits a question about file ownership, which is "answered" with a "no" via the "-Q" option. That in turn leads to an error code 4.
|
|
1369
1386
|
|
|
1370
|
-
Thus the dar option "--comparison-field=ignore-owner" has been placed in the supplied .darrc file (located in the virtual environment where dar-backup is installed).
|
|
1387
|
+
Thus the dar option "--comparison-field=ignore-owner" has been placed in the supplied [.darrc](#darrc) file (located in the virtual environment where dar-backup is installed).
|
|
1371
1388
|
|
|
1372
1389
|
This causes dar to restore without an error.
|
|
1373
1390
|
|
|
@@ -1382,7 +1399,7 @@ My home directory is on a btrfs filesystem, while /tmp (for the restore test) is
|
|
|
1382
1399
|
|
|
1383
1400
|
The restore test can result in an exit code 5, due to the different filesystems used. In order to avoid the errors, the "option "--fsa-scope none" can be used. That will restult in FSA's not being restored.
|
|
1384
1401
|
|
|
1385
|
-
If you need to use this option, un-comment it in the .darrc file (located in the virtual environment where dar-backup is installed)
|
|
1402
|
+
If you need to use this option, un-comment it in the [.darrc](#darrc) file (located in the virtual environment where dar-backup is installed)
|
|
1386
1403
|
|
|
1387
1404
|
## Par2
|
|
1388
1405
|
|
|
@@ -1453,7 +1470,7 @@ Slice size should be smaller than available RAM, apparently a large performance
|
|
|
1453
1470
|
|
|
1454
1471
|
### .darrc sets -vd -vf (since v0.6.4)
|
|
1455
1472
|
|
|
1456
|
-
These .darrc settings make `dar` print the current directory being processed (-vd) and some stats after (-vf)
|
|
1473
|
+
These [.darrc](#darrc) settings make `dar` print the current directory being processed (-vd) and some stats after (-vf)
|
|
1457
1474
|
|
|
1458
1475
|
This is very useful in very long running jobs to get an indication that the backup is proceeding normally.
|
|
1459
1476
|
|
|
@@ -1465,6 +1482,14 @@ In order to not clutter that log file with the output of commands being run, a n
|
|
|
1465
1482
|
|
|
1466
1483
|
The secondary log file can get quite cluttered, if you want to remove the clutter, run the `clean-log`script with the `--file` option, or simply delete it.
|
|
1467
1484
|
|
|
1485
|
+
### Skipping cache directories
|
|
1486
|
+
|
|
1487
|
+
The author uses the `--cache-directory-tagging` option in his [backup definitions](#backup-definition-example).
|
|
1488
|
+
|
|
1489
|
+
The effect is that directories with the [CACHEDIR.TAG](https://bford.info/cachedir/) file are not backed up. Those directories contain content fetched from the net, which is of an ephemeral nature and probably not what you want to back up.
|
|
1490
|
+
|
|
1491
|
+
If the option is not in the backup definition, the cache directories are backed up as any other.
|
|
1492
|
+
|
|
1468
1493
|
## Todo
|
|
1469
1494
|
|
|
1470
1495
|
- `installer` to generate, but not deploy systemd units and timers for:
|
|
@@ -1475,80 +1500,109 @@ The secondary log file can get quite cluttered, if you want to remove the clutte
|
|
|
1475
1500
|
|
|
1476
1501
|
## Reference
|
|
1477
1502
|
|
|
1478
|
-
###
|
|
1503
|
+
### test coverage
|
|
1479
1504
|
|
|
1480
|
-
|
|
1505
|
+
Running
|
|
1506
|
+
|
|
1507
|
+
```` bash
|
|
1508
|
+
pytest --cov=dar_backup tests/
|
|
1509
|
+
````
|
|
1510
|
+
|
|
1511
|
+
gives for version 0.6.17:
|
|
1512
|
+
|
|
1513
|
+
```` code
|
|
1514
|
+
---------- coverage: platform linux, python 3.12.3-final-0 -----------
|
|
1515
|
+
Name Stmts Miss Cover
|
|
1516
|
+
-------------------------------------------------------------------------------------
|
|
1517
|
+
venv/lib/python3.12/site-packages/dar_backup/__about__.py 1 0 100%
|
|
1518
|
+
venv/lib/python3.12/site-packages/dar_backup/__init__.py 0 0 100%
|
|
1519
|
+
venv/lib/python3.12/site-packages/dar_backup/clean_log.py 68 14 79%
|
|
1520
|
+
venv/lib/python3.12/site-packages/dar_backup/cleanup.py 196 53 73%
|
|
1521
|
+
venv/lib/python3.12/site-packages/dar_backup/config_settings.py 66 8 88%
|
|
1522
|
+
venv/lib/python3.12/site-packages/dar_backup/dar_backup.py 464 99 79%
|
|
1523
|
+
venv/lib/python3.12/site-packages/dar_backup/installer.py 46 46 0%
|
|
1524
|
+
venv/lib/python3.12/site-packages/dar_backup/manager.py 316 72 77%
|
|
1525
|
+
venv/lib/python3.12/site-packages/dar_backup/util.py 162 34 79%
|
|
1526
|
+
-------------------------------------------------------------------------------------
|
|
1527
|
+
TOTAL 1319 326 75%
|
|
1528
|
+
````
|
|
1529
|
+
|
|
1530
|
+
### dar-backup options
|
|
1531
|
+
|
|
1532
|
+
This script does backups, validation and restoring. It has the following options:
|
|
1481
1533
|
|
|
1482
1534
|
``` code
|
|
1483
|
-
--full-backup
|
|
1484
|
-
--differential-backup
|
|
1485
|
-
--incremental-backup
|
|
1486
|
-
--backup-definition <name>
|
|
1487
|
-
--alternate-reference-archive <file>
|
|
1488
|
-
--config-file <path>
|
|
1489
|
-
--darrc <path>
|
|
1490
|
-
--examples
|
|
1491
|
-
--list
|
|
1492
|
-
--list-contents <archive>
|
|
1493
|
-
--selection <params>
|
|
1494
|
-
--restore <archive>
|
|
1495
|
-
--restore-dir <path>
|
|
1496
|
-
--verbose
|
|
1497
|
-
--suppress-dar-msg
|
|
1498
|
-
--log-level <level>
|
|
1499
|
-
--log-stdout
|
|
1500
|
-
--do-not-compare
|
|
1501
|
-
--version
|
|
1535
|
+
-F, --full-backup Perform a full backup.
|
|
1536
|
+
-D, --differential-backup Perform a differential backup.
|
|
1537
|
+
-I, --incremental-backup Perform an incremental backup.
|
|
1538
|
+
-d, --backup-definition <name> Specify the backup definition file.
|
|
1539
|
+
--alternate-reference-archive <file> Use a different archive for DIFF/INCR backups.
|
|
1540
|
+
-c, --config-file <path> Specify the path to the configuration file.
|
|
1541
|
+
--darrc <path> Specify an optional path to .darrc.
|
|
1542
|
+
--examples Show examples of using dar-backup.py.
|
|
1543
|
+
-l, --list List available backups.
|
|
1544
|
+
--list-contents <archive> List the contents of a specified archive.
|
|
1545
|
+
--selection <params> Define file selection for listing/restoring.
|
|
1546
|
+
--restore <archive> Restore a specified archive.
|
|
1547
|
+
-r, --restore-dir <path> Directory to restore files to.
|
|
1548
|
+
--verbose Enable verbose output.
|
|
1549
|
+
--suppress-dar-msg Filter out this from the darrc: "-vt", "-vs", "-vd", "-vf", "-va"
|
|
1550
|
+
--log-level <level> `debug` or `trace`, default is `info`.
|
|
1551
|
+
--log-stdout Also print log messages to stdout.
|
|
1552
|
+
--do-not-compare Do not compare restores to file system.
|
|
1553
|
+
-v --version Show version and license information.
|
|
1502
1554
|
```
|
|
1503
1555
|
|
|
1504
|
-
### manager
|
|
1556
|
+
### manager options
|
|
1505
1557
|
|
|
1506
|
-
This script manages `dar` databases and catalogs. Available options
|
|
1558
|
+
This script manages `dar` databases and catalogs. Available options:
|
|
1507
1559
|
|
|
1508
1560
|
``` code
|
|
1509
|
-
--
|
|
1510
|
-
--
|
|
1511
|
-
--
|
|
1512
|
-
|
|
1513
|
-
--
|
|
1514
|
-
--
|
|
1515
|
-
|
|
1516
|
-
--list-
|
|
1517
|
-
--list-
|
|
1518
|
-
--
|
|
1519
|
-
--
|
|
1520
|
-
--
|
|
1561
|
+
-c, --config-file Path to dar-backup.conf
|
|
1562
|
+
--create-db Create missing databases for all backup definitions.
|
|
1563
|
+
--alternate-archive-dir <path> Use this directory instead of BACKUP_DIR in the config file.
|
|
1564
|
+
--add-dir <path> Add all archive catalogs in this directory to databases.
|
|
1565
|
+
-d, --backup-def <name> Restrict to work only on this backup definition.
|
|
1566
|
+
--add-specific-archive <archive> Add this archive to the catalog database.
|
|
1567
|
+
--remove-specific-archive <archive> Remove this archive from the catalog database.
|
|
1568
|
+
-l, --list-catalogs List catalogs in databases for all backup definitions.
|
|
1569
|
+
--list-catalog-contents <num> List contents of a catalog by catalog number.
|
|
1570
|
+
--list-archive-contents <archive> List contents of an archive’s catalog, given the archive name.
|
|
1571
|
+
--find-file <file> Search catalogs for a specific file.
|
|
1572
|
+
--verbose Enable verbose output.
|
|
1573
|
+
--log-level <level> `debug` or `trace`, default is `info`", default="info".
|
|
1521
1574
|
```
|
|
1522
1575
|
|
|
1523
|
-
### cleanup
|
|
1576
|
+
### cleanup options
|
|
1524
1577
|
|
|
1525
|
-
This script cleans up old backups and
|
|
1578
|
+
This script cleans up old backups and par2 files. Supported options:
|
|
1526
1579
|
|
|
1527
1580
|
``` code
|
|
1528
|
-
-d, --backup-definition
|
|
1529
|
-
-c, --config-file
|
|
1530
|
-
-v, --version
|
|
1531
|
-
--alternate-archive-dir
|
|
1532
|
-
--cleanup-specific-archives <archive>, ...
|
|
1533
|
-
-l,
|
|
1534
|
-
--verbose
|
|
1535
|
-
--log-level <level>
|
|
1536
|
-
--log-stdout
|
|
1581
|
+
-d, --backup-definition Backup definition to cleanup.
|
|
1582
|
+
-c, --config-file Path to 'dar-backup.conf'
|
|
1583
|
+
-v, --version Show version & license information.
|
|
1584
|
+
--alternate-archive-dir Clean up in this directory instead of the default one.
|
|
1585
|
+
--cleanup-specific-archives "<archive>, <>, ..." Comma separated list of archives to cleanup.
|
|
1586
|
+
-l, --list List available archives (filter using the -d option).
|
|
1587
|
+
--verbose Print various status messages to screen.
|
|
1588
|
+
--log-level <level> `debug` or `trace`, default is `info`", default="info".
|
|
1589
|
+
--log-stdout Print log messages to stdout.
|
|
1590
|
+
--test-mode This is used when running pytest test cases
|
|
1537
1591
|
```
|
|
1538
1592
|
|
|
1539
|
-
### clean-log
|
|
1593
|
+
### clean-log options
|
|
1540
1594
|
|
|
1541
1595
|
This script removes excessive logging output from `dar` logs, improving readability and efficiency. Available options:
|
|
1542
1596
|
|
|
1543
1597
|
``` code
|
|
1544
1598
|
-f, --file <path> Specify the log file(s) to be cleaned.
|
|
1545
|
-
-c, --config-file <path>
|
|
1599
|
+
-c, --config-file <path> Path to dar-backup.conf.
|
|
1546
1600
|
--dry-run Show which lines would be removed without modifying the file.
|
|
1547
1601
|
-v, --version Display version and licensing information.
|
|
1548
1602
|
-h, --help Displays usage info
|
|
1549
1603
|
```
|
|
1550
1604
|
|
|
1551
|
-
### installer
|
|
1605
|
+
### installer options
|
|
1552
1606
|
|
|
1553
1607
|
Sets up `dar-backup`for a user.
|
|
1554
1608
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
dar_backup/.darrc,sha256=-aerqivZmOsW_XBCh9IfbYTUvw0GkzDSr3Vx4GcNB1g,2113
|
|
2
|
+
dar_backup/__about__.py,sha256=vii4GL7MExpBC8tvQjQXAsEgfxDE9p438_97wKl4XCc,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=HA8SmwrqDGjfFg4L0CuUQ5N0YJFT6qYbCuvh5mQseF0,13148
|
|
6
|
+
dar_backup/command_runner.py,sha256=74Fsylz1NN-dn8lbdRhkL6LA1r527QJeojBlniGrPuo,2708
|
|
7
|
+
dar_backup/config_settings.py,sha256=Rh4T35-w_5tpRAViMfv3YP3GBpG4mQy7Do8cNBzYAR0,4912
|
|
8
|
+
dar_backup/dar-backup.conf,sha256=64O3bGlzqupneT2gVeaETJ1qS6-3Exet9Zto27jgwPQ,897
|
|
9
|
+
dar_backup/dar_backup.py,sha256=NHBm3zsOhCHnCVoPO0ysD3uMdIQMe62AIdr0yCQ_6BY,37952
|
|
10
|
+
dar_backup/installer.py,sha256=ehp4KSgTc8D9Edsyve5v3NY2MuDbuTFYQQPgou8woV8,4331
|
|
11
|
+
dar_backup/manager.py,sha256=4NeIVgrhIzOS8UePUCdvtswEG55ue0tXWAK7SjD3tpo,21897
|
|
12
|
+
dar_backup/util.py,sha256=6dJXFOjIIZqerbNVFxJZ6gQ4ZVAxyY-RxHcO--9bxwg,8462
|
|
13
|
+
dar_backup-0.6.17.dist-info/METADATA,sha256=-TmZ95gGD9VX1Uz0so4G8sWWX0RE6Aq8qkfKThkDFnY,79979
|
|
14
|
+
dar_backup-0.6.17.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
15
|
+
dar_backup-0.6.17.dist-info/entry_points.txt,sha256=Z7P5BUbhtJxo8_nB9qNIMay2eGDbsMKB3Fjwv3GMa4g,202
|
|
16
|
+
dar_backup-0.6.17.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
17
|
+
dar_backup-0.6.17.dist-info/RECORD,,
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
dar_backup/.darrc,sha256=-aerqivZmOsW_XBCh9IfbYTUvw0GkzDSr3Vx4GcNB1g,2113
|
|
2
|
-
dar_backup/__about__.py,sha256=iZmWvp4Ehnidf9YJv6Dpn5Sma5S84lPbQtLoYZ2OuiI,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=EoT_jTiJmy_HoWxxlWuRn21GoMnr5ioN8yM7qiNRPKE,13351
|
|
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=PI154FIXZiU36iAyLZCCxAciDeBSwoBXYQh0n5JmNEs,37895
|
|
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.16.dist-info/METADATA,sha256=xaYjZK6XAWOc_NHsnktylhdvqWLqzmo94GT1MoR6qLw,76678
|
|
13
|
-
dar_backup-0.6.16.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
14
|
-
dar_backup-0.6.16.dist-info/entry_points.txt,sha256=Z7P5BUbhtJxo8_nB9qNIMay2eGDbsMKB3Fjwv3GMa4g,202
|
|
15
|
-
dar_backup-0.6.16.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
16
|
-
dar_backup-0.6.16.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|