dar-backup 0.6.12__py3-none-any.whl → 0.6.13.1__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 CHANGED
@@ -1 +1 @@
1
- __version__ = "0.6.12"
1
+ __version__ = "0.6.13.1"
dar_backup/cleanup.py CHANGED
@@ -181,7 +181,11 @@ def main():
181
181
  config_settings = ConfigSettings(args.config_file)
182
182
 
183
183
  start_time=int(time())
184
- logger = setup_logging(config_settings.logfile_location, args.log_level, args.log_stdout)
184
+
185
+ # command_output_log = os.path.join(config_settings.logfile_location.removesuffix("dar-backup.log"), "dar-backup-commands.log")
186
+ command_output_log = config_settings.logfile_location.replace("dar-backup.log", "dar-backup-commands.log")
187
+ logger = setup_logging(config_settings.logfile_location, command_output_log, args.log_level, args.log_stdout)
188
+
185
189
 
186
190
  logger.info(f"=====================================")
187
191
  logger.info(f"cleanup.py started, version: {about.__version__}")
dar_backup/dar_backup.py CHANGED
@@ -7,9 +7,10 @@ import os
7
7
  import random
8
8
  import re
9
9
  import shlex
10
+ import shutil
10
11
  import subprocess
11
12
  import xml.etree.ElementTree as ET
12
-
13
+ import tempfile
13
14
 
14
15
  from argparse import ArgumentParser
15
16
  from datetime import datetime
@@ -30,6 +31,7 @@ from dar_backup.util import BackupError
30
31
  from dar_backup.util import RestoreError
31
32
 
32
33
 
34
+ RESULT = True
33
35
  logger = None
34
36
 
35
37
  def generic_backup(type: str, command: List[str], backup_file: str, backup_definition: str, darrc: str, config_settings: ConfigSettings, args: argparse.Namespace):
@@ -99,12 +101,12 @@ def find_files_with_paths(xml_root: ET.Element):
99
101
  current_path = []
100
102
 
101
103
  for elem in xml_root.iter():
102
- if elem.tag == "directory":
104
+ if elem.tag == "Directory":
103
105
  current_path.append(elem.get('name'))
104
- elif elem.tag == "file":
106
+ elif elem.tag == "File":
105
107
  file_path = ("/".join(current_path + [elem.get('name')]), elem.get('size'))
106
108
  files.append(file_path)
107
- elif elem.tag == "directory" and elem.get('name') in current_path:
109
+ elif elem.tag == "Directory" and elem.get('Name') in current_path:
108
110
  current_path.pop()
109
111
 
110
112
  return files
@@ -192,10 +194,10 @@ def verify(args: argparse.Namespace, backup_file: str, backup_definition: str, c
192
194
  logger.info(f"No files between {config_settings.min_size_verification_mb}MB and {config_settings.max_size_verification_mb}MB for verification, skipping")
193
195
  return result
194
196
 
197
+ # find Root path in backup definition
195
198
  with open(backup_definition, 'r') as f:
196
199
  backup_definition_content = f.readlines()
197
200
  logger.debug(f"Backup definition: '{backup_definition}', content:\n{backup_definition_content}")
198
- # Initialize a variable to hold the path after "-R"
199
201
  root_path = None
200
202
  for line in backup_definition_content:
201
203
  line = line.strip()
@@ -203,7 +205,10 @@ def verify(args: argparse.Namespace, backup_file: str, backup_definition: str, c
203
205
  root_path = line.split("-R", 1)[1].strip()
204
206
  break
205
207
  if root_path is None:
206
- logger.warning("No Root (-R) path specified in the backup definition file.")
208
+ msg = f"No Root (-R) path found in the backup definition file: '{backup_definition}', restore verification skipped"
209
+ raise BackupError(msg)
210
+
211
+
207
212
 
208
213
  no_files_verification = config_settings.no_files_verification
209
214
  if len(files) < config_settings.no_files_verification:
@@ -438,9 +443,10 @@ def perform_backup(args: argparse.Namespace, config_settings: ConfigSettings, ba
438
443
  logger.info("Generate par2 redundancy files.")
439
444
  generate_par2_files(backup_file, config_settings, args)
440
445
  logger.info("par2 files completed successfully.")
441
-
442
446
 
443
447
  except Exception as e:
448
+ global RESULT
449
+ RESULT = False
444
450
  logger.exception(f"Error during {backup_type} backup process, continuing to next backup definition.")
445
451
 
446
452
 
@@ -494,6 +500,44 @@ def generate_par2_files(backup_file: str, config_settings: ConfigSettings, args)
494
500
 
495
501
 
496
502
 
503
+ def filter_darrc_file(darrc_path):
504
+ """
505
+ Filters the .darrc file to remove lines containing the options: -vt, -vs, -vd, -vf, and -va.
506
+ The filtered version is stored in a uniquely named file in the home directory of the user running the script.
507
+ The file permissions are set to 440.
508
+
509
+ :param darrc_path: Path to the original .darrc file.
510
+ :return: Path to the filtered .darrc file.
511
+ """
512
+ # Define options to filter out
513
+ options_to_remove = {"-vt", "-vs", "-vd", "-vf", "-va"}
514
+
515
+ # Get the user's home directory
516
+ home_dir = os.path.expanduser("~")
517
+
518
+ # Create a unique file name in the home directory
519
+ filtered_darrc_path = os.path.join(home_dir, f"filtered_darrc_{next(tempfile._get_candidate_names())}.darrc")
520
+
521
+ try:
522
+ with open(darrc_path, "r") as infile, open(filtered_darrc_path, "w") as outfile:
523
+ for line in infile:
524
+ # Check if any unwanted option is in the line
525
+ if not any(option in line for option in options_to_remove):
526
+ outfile.write(line)
527
+
528
+ # Set file permissions to 440 (read-only for owner and group, no permissions for others)
529
+ os.chmod(filtered_darrc_path, 0o440)
530
+
531
+ return filtered_darrc_path
532
+
533
+ except Exception as e:
534
+ # If anything goes wrong, clean up the temp file if it was created
535
+ if os.path.exists(filtered_darrc_path):
536
+ os.remove(filtered_darrc_path)
537
+ raise RuntimeError(f"Error filtering .darrc file: {e}")
538
+
539
+
540
+
497
541
  def show_version():
498
542
  script_name = os.path.basename(argv[0])
499
543
  print(f"{script_name} {about.__version__}")
@@ -580,14 +624,14 @@ def requirements(type: str, config_setting: ConfigSettings):
580
624
 
581
625
 
582
626
  def main():
583
- global logger
627
+ global logger, RESULT
584
628
 
585
629
  MIN_PYTHON_VERSION = (3, 9)
586
630
  if version_info < MIN_PYTHON_VERSION:
587
631
  stderr.write(f"Error: This script requires Python {'.'.join(map(str, MIN_PYTHON_VERSION))} or higher.\n")
588
632
  exit(1)
589
633
 
590
- parser = argparse.ArgumentParser(description="Backup and verify using dar backup definitions.")
634
+ parser = argparse.ArgumentParser(description="Backup, verify & redundancy using dar and par2.")
591
635
  parser.add_argument('-F', '--full-backup', action='store_true', help="Perform a full backup.")
592
636
  parser.add_argument('-D', '--differential-backup', action='store_true', help="Perform differential backup.")
593
637
  parser.add_argument('-I', '--incremental-backup', action='store_true', help="Perform incremental backup.")
@@ -603,6 +647,7 @@ def main():
603
647
  parser.add_argument('-r', '--restore', type=str, help="Restore specified archive.")
604
648
  parser.add_argument('--restore-dir', type=str, help="Directory to restore files to.")
605
649
  parser.add_argument('--verbose', action='store_true', help="Print various status messages to screen")
650
+ parser.add_argument('--suppress-dar-msg', action='store_true', help="cancel dar options in .darrc: -vt, -vs, -vd, -vf and -va")
606
651
  parser.add_argument('--log-level', type=str, help="`debug` or `trace`", default="info")
607
652
  parser.add_argument('--log-stdout', action='store_true', help='also print log messages to stdout')
608
653
  parser.add_argument('--do-not-compare', action='store_true', help="do not compare restores to file system")
@@ -628,23 +673,29 @@ def main():
628
673
  args.config_file = config_settings_path
629
674
  config_settings = ConfigSettings(args.config_file)
630
675
 
676
+ command_output_log = config_settings.logfile_location.replace("dar-backup.log", "dar-backup-commands.log")
677
+ if command_output_log == config_settings.logfile_location:
678
+ print(f"Error: logfile_location in {args.config_file} does not end at 'dar-backup.log', exiting", file=stderr)
631
679
 
632
- logger = setup_logging(config_settings.logfile_location, args.log_level, args.log_stdout)
633
-
634
- if not args.darrc:
635
- current_script_dir = os.path.dirname(os.path.abspath(__file__))
636
- args.darrc = os.path.join(current_script_dir, ".darrc")
680
+ logger = setup_logging(config_settings.logfile_location, command_output_log, args.log_level, args.log_stdout)
637
681
 
638
- darrc_file = os.path.expanduser(os.path.expandvars(args.darrc))
639
- if os.path.exists(darrc_file) and os.path.isfile(darrc_file):
640
- logger.debug(f"Using .darrc: {args.darrc}")
641
- else:
642
- logger.error(f"Supplied .darrc: '{args.darrc}' does not exist or is not a file, exiting", file=stderr)
643
- exit(127)
682
+ try:
683
+ if not args.darrc:
684
+ current_script_dir = os.path.dirname(os.path.abspath(__file__))
685
+ args.darrc = os.path.join(current_script_dir, ".darrc")
644
686
 
687
+ darrc_file = os.path.expanduser(os.path.expandvars(args.darrc))
688
+ if os.path.exists(darrc_file) and os.path.isfile(darrc_file):
689
+ logger.debug(f"Using .darrc: {args.darrc}")
690
+ else:
691
+ logger.error(f"Supplied .darrc: '{args.darrc}' does not exist or is not a file, exiting", file=stderr)
692
+ exit(127)
645
693
 
694
+ if args.suppress_dar_msg:
695
+ logger.info("Suppressing dar messages: -vt, -vs, -vd, -vf and -va")
696
+ args.darrc = filter_darrc_file(args.darrc)
697
+ logger.debug(f"Filtered .darrc file saved at: {args.darrc}")
646
698
 
647
- try:
648
699
  start_time=int(time())
649
700
  logger.info(f"=====================================")
650
701
  logger.info(f"dar-backup.py started, version: {about.__version__}")
@@ -672,7 +723,6 @@ def main():
672
723
  args.verbose and (print(f"PAR2 enabled: {config_settings.par2_enabled}"))
673
724
  args.verbose and (print(f"--do-not-compare: {args.do_not_compare}"))
674
725
 
675
-
676
726
  # sanity check
677
727
  if args.backup_definition and not os.path.exists(os.path.join(config_settings.backup_d_dir, args.backup_definition)):
678
728
  logger.error(f"Backup definition: '{args.backup_definition}' does not exist, exiting")
@@ -702,19 +752,24 @@ def main():
702
752
 
703
753
  requirements('POSTREQ', config_settings)
704
754
 
705
- args.verbose and print("\033[1m\033[32mSUCCESS\033[0m No errors encountered")
706
- exit(0)
707
755
  except Exception as e:
708
756
  logger.exception("An error occurred")
709
757
  logger.error("Exception details:", exc_info=True)
710
758
  args.verbose and print("\033[1m\033[31mErrors\033[0m encountered")
711
- exit(1)
759
+ RESULT = False
712
760
  finally:
713
761
  end_time=int(time())
714
762
  logger.info(f"END TIME: {end_time}")
715
-
716
- # error_lines = extract_error_lines(config_settings.logfile_location, start_time, end_time)
763
+ # Clean up
764
+ if args.suppress_dar_msg and os.path.exists(args.darrc) and os.path.dirname(args.darrc) == os.path.expanduser("~"):
765
+ if args.darrc.startswith("filtered_darrc_"):
766
+ os.remove(args.darrc)
767
+ logger.info(f"Removed filtered .darrc: {args.darrc}")
717
768
 
718
769
 
770
+ if RESULT:
771
+ exit(0)
772
+ else:
773
+ exit(1)
719
774
  if __name__ == "__main__":
720
775
  main()
dar_backup/installer.py CHANGED
@@ -73,13 +73,6 @@ def main():
73
73
  help="Deploy a simple config file, use ~/dar-backup/ for log file, archives and restore tests."
74
74
  )
75
75
 
76
- parser.add_argument(
77
- "--dry-run",
78
- action="store_true",
79
- help="Show which lines would be removed without modifying the file."
80
- )
81
-
82
-
83
76
  parser.add_argument(
84
77
  "-v", "--version",
85
78
  action="version",
dar_backup/manager.py CHANGED
@@ -335,7 +335,7 @@ def remove_specific_archive(archive: str, config_settings: ConfigSettings) -> in
335
335
  """
336
336
  backup_def = backup_def_from_archive(archive)
337
337
  database_path = os.path.join(config_settings.backup_dir, f"{backup_def}{DB_SUFFIX}")
338
- cat_no = cat_no_for_name(archive, config_settings)
338
+ cat_no:int = cat_no_for_name(archive, config_settings)
339
339
  if cat_no >= 0:
340
340
  command = ['dar_manager', '--base', database_path, "--delete", str(cat_no)]
341
341
  process: CommandResult = run_command(command)
@@ -400,7 +400,11 @@ See section 15 and section 16 in the supplied "LICENSE" file.''')
400
400
  if not os.path.dirname(config_settings.logfile_location):
401
401
  print(f"Directory for log file '{config_settings.logfile_location}' does not exist, exiting")
402
402
  sys.exit(1)
403
- logger = setup_logging(config_settings.logfile_location, args.log_level, args.log_stdout)
403
+
404
+ # command_output_log = os.path.join(config_settings.logfile_location.removesuffix("dar-backup.log"), "dar-backup-commands.log")
405
+ command_output_log = config_settings.logfile_location.replace("dar-backup.log", "dar-backup-commands.log")
406
+ logger = setup_logging(config_settings.logfile_location, command_output_log, args.log_level, args.log_stdout)
407
+
404
408
 
405
409
  start_time=int(time())
406
410
  logger.info(f"=====================================")
dar_backup/util.py CHANGED
@@ -23,115 +23,98 @@ from datetime import datetime
23
23
  from typing import NamedTuple, List
24
24
 
25
25
  logger=None
26
+ secondary_logger=None
26
27
 
27
- class BackupError(Exception):
28
- """Exception raised for errors in the backup process."""
29
- pass
30
-
31
- class DifferentialBackupError(BackupError):
32
- """Exception raised for errors in the differential backup process."""
33
- pass
34
-
35
- class IncrementalBackupError(BackupError):
36
- """Exception raised for errors in the incremental backup process."""
37
- pass
38
-
39
- class RestoreError(Exception):
40
- """Exception raised for errors in the restore process."""
41
- pass
42
28
 
29
+ def setup_logging(log_file: str, command_output_log_file: str, log_level: str = "info", log_to_stdout: bool = False) -> logging.Logger:
30
+ """
31
+ Sets up logging for the main program and a separate secondary logfile for command outputs.
43
32
 
33
+ Args:
34
+ log_file (str): The path to the main log file.
35
+ command_output_log_file (str): The path to the secondary log file for command outputs.
36
+ log_level (str): The log level to use. Can be "info", "debug", or "trace". Defaults to "info".
37
+ log_to_stdout (bool): If True, log messages will be printed to the console. Defaults to False.
44
38
 
39
+ Returns:
40
+ None
45
41
 
46
- def setup_logging(log_file: str, log_level: str, log_to_stdout: bool=False, logger_name: str=__name__) -> logging.Logger:
42
+ Raises:
43
+ Exception: If an error occurs during logging initialization
47
44
  """
48
- log_level can be set to "debug" or "trace" for more verbose logging.
49
- """
50
- global logger
45
+ global logger, secondary_logger
51
46
  try:
52
47
  TRACE_LEVEL_NUM = 5
53
48
  logging.addLevelName(TRACE_LEVEL_NUM, "TRACE")
54
49
 
55
50
  def trace(self, message, *args, **kws):
56
51
  if self.isEnabledFor(TRACE_LEVEL_NUM):
57
- self.log(TRACE_LEVEL_NUM, message, args, **kws)
52
+ self.log(TRACE_LEVEL_NUM, message, *args, **kws)
58
53
 
59
54
  logging.Logger.trace = trace
60
55
 
61
- # Create a custom logger
62
- logger = logging.getLogger(__name__)
63
-
64
- level_used = logging.INFO
65
- logger.setLevel(logging.INFO)
66
- if log_level == "debug":
67
- level_used = logging.DEBUG
68
- logger.setLevel(logging.DEBUG)
69
- elif log_level == "trace":
70
- level_used = TRACE_LEVEL_NUM
71
- logger.setLevel(TRACE_LEVEL_NUM)
72
-
73
- formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
74
-
56
+ # Setup main logger
57
+ logger = logging.getLogger("main_logger")
58
+ logger.setLevel(logging.DEBUG if log_level == "debug" else TRACE_LEVEL_NUM if log_level == "trace" else logging.INFO)
59
+ formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
75
60
  file_handler = logging.FileHandler(log_file)
76
- file_handler.setLevel(level_used)
77
61
  file_handler.setFormatter(formatter)
78
62
  logger.addHandler(file_handler)
79
63
 
64
+ # Setup secondary logger for command outputs
65
+ secondary_logger = logging.getLogger("command_output_logger")
66
+ secondary_logger.setLevel(logging.DEBUG if log_level == "debug" else TRACE_LEVEL_NUM if log_level == "trace" else logging.INFO)
67
+ sec_file_handler = logging.FileHandler(command_output_log_file)
68
+ sec_file_handler.setFormatter(formatter)
69
+ secondary_logger.addHandler(sec_file_handler)
70
+
80
71
  if log_to_stdout:
81
72
  stdout_handler = logging.StreamHandler(sys.stdout)
82
- stdout_handler.setLevel(level_used)
83
73
  stdout_handler.setFormatter(formatter)
84
74
  logger.addHandler(stdout_handler)
75
+ secondary_logger.addHandler(stdout_handler)
85
76
 
77
+ return logger
86
78
  except Exception as e:
87
- print("logging not initialized, exiting.")
79
+ print("Logging initialization failed.")
88
80
  traceback.print_exc()
89
81
  sys.exit(1)
90
82
 
91
- return logger
92
83
 
93
84
 
94
- class CommandResult(NamedTuple):
85
+ def get_logger(command_output_logger: bool = False) -> logging.Logger:
95
86
  """
96
- The reult of the run_command() function.
87
+ Returns a logger
88
+
89
+ Args:
90
+ use_secondary (bool): If True, returns the secondary logger. Defaults to False.
91
+
92
+ Returns:
93
+ logger to dar-backup.log or the logger for command output.
97
94
  """
98
- process: subprocess.CompletedProcess
99
- stdout: str
100
- stderr: str
101
- returncode: int
102
- timeout: int
103
- command: list[str]
95
+ global logger, secondary_logger
104
96
 
105
- def __str__(self):
106
- #return f"CommandResult: [Return Code: '{self.returncode}', \nCommand: '{' '.join(map(shlex.quote, self.command))}', \nStdout:\n'{self.stdout}', \nStderr:\n'{self.stderr}', \nTimeout: '{self.timeout}']"
107
- return f"CommandResult: [Return Code: '{self.returncode}', \nCommand: '{' '.join(map(shlex.quote, self.command))}']"
97
+ return secondary_logger if command_output_logger else logger
108
98
 
109
99
 
110
100
 
111
- def _stream_reader(pipe, log_func, output_accumulator: List[str]):
101
+ def _stream_reader(pipe, log_funcs, output_accumulator: List[str]):
112
102
  """
113
- Reads lines from the subprocess pipe, logs them, and accumulates them.
114
-
115
- Args:
116
- pipe: The pipe to read from (stdout or stderr).
117
- log_func: The logging function to use (e.g., logger.info, logger.error).
118
- output_accumulator: A list to store the lines read from the pipe.
103
+ Reads lines from the subprocess pipe and logs them to multiple destinations.
119
104
  """
120
105
  with pipe:
121
106
  for line in iter(pipe.readline, ''):
122
107
  stripped_line = line.strip()
123
- output_accumulator.append(stripped_line) # Accumulate the output
124
- log_func(stripped_line) # Log the output in real time
108
+ output_accumulator.append(stripped_line)
109
+ for log_func in log_funcs:
110
+ log_func(stripped_line) # Log the output in real-time
125
111
 
126
112
 
127
113
 
128
- def run_command(command: List[str], timeout: int = 30) -> CommandResult:
114
+ def run_command(command: List[str], timeout: int = 30, no_output_log: bool = False):
129
115
  """
130
- Executes a given command via subprocess, logs its output in real time, and returns the result.
116
+ Executes a command and streams output only to the secondary log unless no_log is set to True.
131
117
 
132
- Args:
133
- command (list): The command to be executed, represented as a list of strings.
134
- timeout (int): The maximum time in seconds to wait for the command to complete. Defaults to 30 seconds.
135
118
 
136
119
  Returns:
137
120
  A CommandResult NamedTuple with the following properties:
@@ -143,41 +126,39 @@ def run_command(command: List[str], timeout: int = 30) -> CommandResult:
143
126
  - command: list[str]: The command executed.
144
127
 
145
128
  Logs:
146
- - Logs standard output (`stdout`) in real-time at the INFO log level.
147
- - Logs standard error (`stderr`) in real-time at the ERROR log level.
148
-
129
+ - Logs standard output (`stdout`) and standard error in real-time to the
130
+ logger.secondary_log (that contains the command output).
131
+
149
132
  Raises:
150
133
  subprocess.TimeoutExpired: If the command execution times out (see `timeout` parameter).
151
134
  Exception: If other exceptions occur during command execution.
152
135
  FileNotFoundError: If the command is not found.
153
-
154
- Notes:
155
- - While the command runs, its `stdout` and `stderr` streams are logged in real-time.
156
- - The returned `stdout` and `stderr` capture the complete output, even though the output is also logged.
157
- - The command is forcibly terminated if it exceeds the specified timeout.
158
136
  """
159
- stdout_lines = [] # To accumulate stdout
160
- stderr_lines = [] # To accumulate stderr
161
- process = None # Track the process for cleanup
162
- stdout_thread = None
163
- stderr_thread = None
137
+ stdout_lines, stderr_lines = [], []
138
+ process = None
139
+ stdout_thread, stderr_thread = None, None
164
140
 
165
141
  try:
166
- # Check if the command exists before executing
142
+ logger = get_logger(command_output_logger=False)
143
+ command_logger = get_logger(command_output_logger=True)
144
+
167
145
  if not shutil.which(command[0]):
168
146
  raise FileNotFoundError(f"Command not found: {command[0]}")
169
-
147
+
170
148
  logger.debug(f"Running command: {command}")
149
+ command_logger.info(f"Running command: {command}")
150
+
171
151
  process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
172
152
 
173
- # Start threads to read and log stdout and stderr
174
- stdout_thread = threading.Thread(target=_stream_reader, args=(process.stdout, logger.info, stdout_lines))
175
- stderr_thread = threading.Thread(target=_stream_reader, args=(process.stderr, logger.error, stderr_lines))
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))
176
158
 
177
159
  stdout_thread.start()
178
160
  stderr_thread.start()
179
161
 
180
- # Wait for process to complete or timeout
181
162
  process.wait(timeout=timeout)
182
163
 
183
164
  except FileNotFoundError as e:
@@ -199,132 +180,60 @@ def run_command(command: List[str], timeout: int = 30) -> CommandResult:
199
180
  logger.error(f"Error running command: {command}", exc_info=True)
200
181
  raise
201
182
  finally:
202
- # Ensure threads are joined to clean up (only if they were started)
203
183
  if stdout_thread and stdout_thread.is_alive():
204
184
  stdout_thread.join()
205
185
  if stderr_thread and stderr_thread.is_alive():
206
186
  stderr_thread.join()
187
+ if process:
188
+ if process.stdout:
189
+ process.stdout.close()
190
+ if process.stderr:
191
+ process.stderr.close()
207
192
 
208
- # Ensure process streams are closed
209
- if process and process.stdout:
210
- process.stdout.close()
211
- if process and process.stderr:
212
- process.stderr.close()
213
-
193
+
214
194
  # Combine captured stdout and stderr lines into single strings
215
195
  stdout = "\n".join(stdout_lines)
216
196
  stderr = "\n".join(stderr_lines)
217
197
 
218
- # Build the result object
219
- result = CommandResult(
220
- process=process,
221
- stdout=stdout,
222
- stderr=stderr,
223
- returncode=process.returncode,
224
- timeout=timeout,
225
- command=command
226
- )
227
- logger.debug(f"Command result: {result}")
198
+ #Build the result object
199
+ result = CommandResult(process=process, stdout=stdout, stderr=stderr, returncode=process.returncode, timeout=timeout, command=command)
228
200
  return result
229
201
 
230
- def run_command2(command: List[str], timeout: int = 30) -> CommandResult:
231
- """
232
- Executes a given command via subprocess, logs its output in real time, and returns the result.
233
-
234
- Args:
235
- command (list): The command to be executed, represented as a list of strings.
236
- timeout (int): The maximum time in seconds to wait for the command to complete. Defaults to 30 seconds.
237
-
238
- Returns:
239
- A CommandResult NamedTuple with the following properties:
240
- - process: subprocess.CompletedProcess
241
- - stdout: str: The full standard output of the command.
242
- - stderr: str: The full standard error of the command.
243
- - returncode: int: The return code of the command.
244
- - timeout: int: The timeout value in seconds used to run the command.
245
- - command: list[str]: The command executed.
246
-
247
- Logs:
248
- - Logs standard output (`stdout`) in real-time at the INFO log level.
249
- - Logs standard error (`stderr`) in real-time at the ERROR log level.
250
202
 
251
- Raises:
252
- subprocess.TimeoutExpired: If the command execution times out (see `timeout` parameter).
253
- Exception: If other exceptions occur during command execution.
203
+ class BackupError(Exception):
204
+ """Exception raised for errors in the backup process."""
205
+ pass
254
206
 
255
- Notes:
256
- - While the command runs, its `stdout` and `stderr` streams are logged in real-time.
257
- - The returned `stdout` and `stderr` capture the complete output, even though the output is also logged.
258
- - The command is forcibly terminated if it exceeds the specified timeout.
259
- """
260
- stdout_lines = [] # To accumulate stdout
261
- stderr_lines = [] # To accumulate stderr
262
- process = None # Track the process for cleanup
207
+ class DifferentialBackupError(BackupError):
208
+ """Exception raised for errors in the differential backup process."""
209
+ pass
263
210
 
264
- try:
265
- # Check if the command exists before executing
266
- if not shutil.which(command[0]):
267
- raise FileNotFoundError(f"Command not found: {command[0]}")
211
+ class IncrementalBackupError(BackupError):
212
+ """Exception raised for errors in the incremental backup process."""
213
+ pass
268
214
 
269
- logger.debug(f"Running command: {command}")
270
- process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
215
+ class RestoreError(Exception):
216
+ """Exception raised for errors in the restore process."""
217
+ pass
271
218
 
272
- # Start threads to read and log stdout and stderr
273
- stdout_thread = threading.Thread(target=_stream_reader, args=(process.stdout, logger.info, stdout_lines))
274
- stderr_thread = threading.Thread(target=_stream_reader, args=(process.stderr, logger.error, stderr_lines))
275
-
276
- stdout_thread.start()
277
- stderr_thread.start()
278
219
 
279
- # Wait for process to complete or timeout
280
- process.wait(timeout=timeout)
281
220
 
282
- except FileNotFoundError as e:
283
- logger.error(f"Command not found: {command[0]}")
284
- return CommandResult(
285
- process=None,
286
- stdout="",
287
- stderr=str(e),
288
- returncode=127,
289
- timeout=timeout,
290
- command=command
291
- )
292
- except subprocess.TimeoutExpired:
293
- if process:
294
- process.terminate()
295
- logger.error(f"Command: '{command}' timed out and was terminated.")
296
- raise
297
- except Exception as e:
298
- logger.error(f"Error running command: {command}", exc_info=True)
299
- raise
300
- finally:
301
- # Ensure threads are joined to clean up
302
- if stdout_thread.is_alive():
303
- stdout_thread.join()
304
- if stderr_thread.is_alive():
305
- stderr_thread.join()
221
+ class CommandResult(NamedTuple):
222
+ """
223
+ The reult of the run_command() function.
224
+ """
225
+ process: subprocess.CompletedProcess
226
+ stdout: str
227
+ stderr: str
228
+ returncode: int
229
+ timeout: int
230
+ command: list[str]
306
231
 
307
- # Ensure process streams are closed
308
- if process and process.stdout:
309
- process.stdout.close()
310
- if process and process.stderr:
311
- process.stderr.close()
232
+ def __str__(self):
233
+ #return f"CommandResult: [Return Code: '{self.returncode}', \nCommand: '{' '.join(map(shlex.quote, self.command))}', \nStdout:\n'{self.stdout}', \nStderr:\n'{self.stderr}', \nTimeout: '{self.timeout}']"
234
+ return f"CommandResult: [Return Code: '{self.returncode}', \nCommand: '{' '.join(map(shlex.quote, self.command))}']"
312
235
 
313
- # Combine captured stdout and stderr lines into single strings
314
- stdout = "\n".join(stdout_lines)
315
- stderr = "\n".join(stderr_lines)
316
236
 
317
- # Build the result object
318
- result = CommandResult(
319
- process=process,
320
- stdout=stdout,
321
- stderr=stderr,
322
- returncode=process.returncode,
323
- timeout=timeout,
324
- command=command
325
- )
326
- logger.debug(f"Command result: {result}")
327
- return result
328
237
 
329
238
 
330
239
  def extract_error_lines(log_file_path: str, start_time: str, end_time: str):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dar-backup
3
- Version: 0.6.12
3
+ Version: 0.6.13.1
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
@@ -692,8 +692,10 @@ Description-Content-Type: text/markdown
692
692
 
693
693
  # Full, differential or incremental backups using 'dar'
694
694
 
695
- The wonderful 'dar' [Disk Archiver](https://github.com/Edrusb/DAR) is used for
696
- the heavy lifting, together with the par2 suite in these scripts.
695
+ The wonderful 'dar' [Disk Archiver](https://github.com/Edrusb/DAR) is used for
696
+ the heavy lifting, together with the par2 suite in these scripts.
697
+
698
+ This is the `Python` based **version 2** of `dar-backup`.
697
699
 
698
700
  ## Table of Contents
699
701
 
@@ -736,17 +738,20 @@ Description-Content-Type: text/markdown
736
738
  - [dar manager databases](#dar-manager-databases)
737
739
  - [Performance tip due to par2](#performance-tip-due-to-par2)
738
740
  - [.darrc sets -vd -vf (since v0.6.4)](#darrc-sets--vd--vf-since-v064)
741
+ - [Separate log file for command output](#separate-log-file-for-command-output)
742
+ - [Todo](#todo)
739
743
  - [Reference](#reference)
740
- - [dar-backup.py](#dar-backuppy)
741
- - [manager.py](#managerpy)
742
- - [cleanup.py](#cleanuppy)
743
- - [clean-log.py](#clean-logpy)
744
-
744
+ - [dar-backup](#dar-backup)
745
+ - [manager](#manager)
746
+ - [cleanup](#cleanup)
747
+ - [clean-log](#clean-log)
748
+ - [installer](#installer)
749
+
745
750
  ## My use case
746
751
 
747
752
  I have cloud storage mounted on a directory within my home dir. The filesystem is [FUSE based](https://www.kernel.org/doc/html/latest/filesystems/fuse.html), which gives it a few special features
748
753
 
749
- - a non-privileged user (me :-)) can perform a mount
754
+ - a non-privileged user can perform a mount
750
755
  - a privileged user cannot look into the filesystem --> a backup script running as root is not suitable
751
756
 
752
757
  I needed the following:
@@ -763,7 +768,7 @@ I have cloud storage mounted on a directory within my home dir. The filesystem i
763
768
  ## License
764
769
 
765
770
  These scripts are licensed under the GPLv3 license.
766
- Read more here: https://www.gnu.org/licenses/gpl-3.0.en.html, or have a look at the ["LICENSE"](https://github.com/per2jensen/dar-backup/blob/main/LICENSE) file in this repository.
771
+ Read more here: [GNU CPL 3.0](https://www.gnu.org/licenses/gpl-3.0.en.html), or have a look at the ["LICENSE"](https://github.com/per2jensen/dar-backup/blob/main/LICENSE) file in this repository.
767
772
 
768
773
  ## Status
769
774
 
@@ -777,7 +782,7 @@ Version 0.6.0 and forwards requires the config variable *COMMAND_TIMEOUT_SECS* i
777
782
 
778
783
  ## Homepage - Github
779
784
 
780
- This 'dar-backup' package lives at: https://github.com/per2jensen/dar-backup/tree/main/v2
785
+ This 'dar-backup' package lives at: [Github - dar-backup](https://github.com/per2jensen/dar-backup/tree/main/v2)
781
786
 
782
787
  This python version is v2 of dar-backup, the first is made in bash.
783
788
 
@@ -785,7 +790,7 @@ This python version is v2 of dar-backup, the first is made in bash.
785
790
 
786
791
  - dar
787
792
  - par2
788
- - python3 :-)
793
+ - python3
789
794
 
790
795
  On Ubuntu, install the requirements this way:
791
796
 
@@ -852,7 +857,7 @@ The output is
852
857
  Directories created: `/home/user/dar-backup/` and `/home/user/.config/dar-backup`
853
858
  Config file deployed to /home/user/.config/dar-backup/dar-backup.conf
854
859
  Default backup definition deployed to /home/user/.config/dar-backup/backup.d/default
855
- 1. Now run `manager --create` to create the catalog database.
860
+ 1. Now run `manager --create-db` to create the catalog database.
856
861
  2. Then you can run `dar-backup --full-backup` to create a backup.
857
862
  3. List backups with `dar-backup --list`
858
863
  4. List contents of a backup with `dar-backup --list-contents <backup-name>`
@@ -865,7 +870,7 @@ Generate the archive catalog database(s).
865
870
  `dar-backup` expects the catalog databases to be in place, it does not automatically create them (by design)
866
871
 
867
872
  ```` bash
868
- manager --create
873
+ manager --create-db
869
874
  ````
870
875
 
871
876
  ### 4 - do FULL backups
@@ -1091,14 +1096,14 @@ gives
1091
1096
  ``` code
1092
1097
  [Data ][D][ EA ][FSA][Compr][S]| Permission | User | Group | Size | Date | filename
1093
1098
  --------------------------------+------------+-------+-------+---------+-------------------------------+------------
1094
- [Saved][-] [-L-][ 0%][ ] drwxr-xr-x root root 113 Mio Sat May 11 16:16:48 2024 home
1095
- [Saved][-] [-L-][ 0%][ ] drwxrwxr-x pj pj 113 Mio Sun Jun 23 10:46:30 2024 home/pj
1096
- [Saved][-] [-L-][ 0%][ ] drwxrwxr-x pj pj 113 Mio Sun Jun 23 09:17:42 2024 home/pj/tmp
1097
- [Saved][-] [-L-][ 1%][ ] drwxrwxr-x pj pj 50 Mio Wed Jun 19 20:52:13 2024 home/pj/tmp/LUT-play
1098
- [Saved][ ] [-L-][ 0%][X] -rw-rw-r-- pj pj 49 Mio Sun Jun 16 12:52:22 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15.NEF
1099
+ [Saved][-] [-L-][ 0%][ ] drwxr-xr-x root root 113 Mio Sat May 11 16:16:48 2024 home
1100
+ [Saved][-] [-L-][ 0%][ ] drwxrwxr-x pj pj 113 Mio Sun Jun 23 10:46:30 2024 home/pj
1101
+ [Saved][-] [-L-][ 0%][ ] drwxrwxr-x pj pj 113 Mio Sun Jun 23 09:17:42 2024 home/pj/tmp
1102
+ [Saved][-] [-L-][ 1%][ ] drwxrwxr-x pj pj 50 Mio Wed Jun 19 20:52:13 2024 home/pj/tmp/LUT-play
1103
+ [Saved][ ] [-L-][ 0%][X] -rw-rw-r-- pj pj 49 Mio Sun Jun 16 12:52:22 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15.NEF
1099
1104
  ```
1100
1105
 
1101
- ## dar file selection exmaples
1106
+ ## dar file selection examples
1102
1107
 
1103
1108
  ### select a directory
1104
1109
 
@@ -1111,33 +1116,33 @@ gives
1111
1116
  ```` code
1112
1117
  [Data ][D][ EA ][FSA][Compr][S]| Permission | User | Group | Size | Date | filename
1113
1118
  --------------------------------+------------+-------+-------+---------+-------------------------------+------------
1114
- [Saved][-] [-L-][ 0%][ ] drwxr-xr-x root root 113 Mio Sat May 11 16:16:48 2024 home
1115
- [Saved][-] [-L-][ 0%][ ] drwxrwxr-x pj pj 113 Mio Sun Jun 23 10:46:30 2024 home/pj
1116
- [Saved][-] [-L-][ 0%][ ] drwxrwxr-x pj pj 113 Mio Sun Jun 23 09:17:42 2024 home/pj/tmp
1117
- [Saved][-] [-L-][ 1%][ ] drwxrwxr-x pj pj 50 Mio Wed Jun 19 20:52:13 2024 home/pj/tmp/LUT-play
1118
- [Saved][ ] [-L-][ 0%][X] -rw-rw-r-- pj pj 49 Mio Sun Jun 16 12:52:22 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15.NEF
1119
- [Saved][ ] [-L-][ 95%][ ] -rw-rw-r-- pj pj 48 kio Sat Jun 22 21:51:24 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15.NEF.xmp
1120
- [Saved][ ] [-L-][ 95%][ ] -rw-rw-r-- pj pj 50 kio Sat Jun 22 21:51:25 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_01.NEF.xmp
1121
- [Saved][ ] [-L-][ 95%][ ] -rw-rw-r-- pj pj 51 kio Sat Jun 22 21:51:26 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_02.NEF.xmp
1122
- [Saved][ ] [-L-][ 95%][ ] -rw-rw-r-- pj pj 51 kio Sat Jun 22 21:51:27 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_03.NEF.xmp
1123
- [Saved][ ] [-L-][ 95%][ ] -rw-rw-r-- pj pj 51 kio Sat Jun 22 21:51:27 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_04.NEF.xmp
1124
- [Saved][ ] [-L-][ 97%][ ] -rw-rw-r-- pj pj 77 kio Sat Jun 22 21:50:16 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_05.NEF.xmp
1125
- [Saved][ ] [-L-][ 95%][ ] -rw-rw-r-- pj pj 52 kio Sat Jun 22 21:49:37 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_06.NEF.xmp
1126
- [Saved][ ] [-L-][ 92%][ ] -rw-rw-r-- pj pj 24 kio Sat Jun 22 21:50:47 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_07.NEF.xmp
1127
- [Saved][ ] [-L-][ 92%][ ] -rw-rw-r-- pj pj 24 kio Sat Jun 22 21:51:12 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_08.NEF.xmp
1128
- [Saved][ ] [-L-][ 92%][ ] -rw-rw-r-- pj pj 24 kio Sat Jun 22 21:51:12 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_09.NEF.xmp
1129
- [Saved][ ] [-L-][ 92%][ ] -rw-rw-r-- pj pj 24 kio Sat Jun 22 21:50:39 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_10.NEF.xmp
1130
- [Saved][ ] [-L-][ 92%][ ] -rw-rw-r-- pj pj 24 kio Sat Jun 22 21:50:36 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_11.NEF.xmp
1131
- [Saved][ ] [-L-][ 92%][ ] -rw-rw-r-- pj pj 24 kio Sat Jun 22 21:50:35 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_12.NEF.xmp
1132
- [Saved][ ] [-L-][ 88%][ ] -rw-rw-r-- pj pj 15 kio Sat Jun 22 21:51:11 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_13.NEF.xmp
1133
- [Saved][ ] [-L-][ 96%][ ] -rw-rw-r-- pj pj 84 kio Sat Jun 22 21:51:09 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_14.NEF.xmp
1134
- [Saved][ ] [-L-][ 96%][ ] -rw-rw-r-- pj pj 90 kio Sat Jun 22 21:51:04 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_15.NEF.xmp
1135
- [Saved][ ] [-L-][ 92%][ ] -rw-rw-r-- pj pj 24 kio Sat Jun 22 21:51:15 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_16.NEF.xmp
1136
- [Saved][ ] [-L-][ 92%][ ] -rw-rw-r-- pj pj 24 kio Sat Jun 22 21:50:48 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_17.NEF.xmp
1137
- [Saved][ ] [-L-][ 92%][ ] -rw-rw-r-- pj pj 24 kio Sat Jun 22 21:50:19 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_18.NEF.xmp
1119
+ [Saved][-] [-L-][ 0%][ ] drwxr-xr-x root root 113 Mio Sat May 11 16:16:48 2024 home
1120
+ [Saved][-] [-L-][ 0%][ ] drwxrwxr-x pj pj 113 Mio Sun Jun 23 10:46:30 2024 home/pj
1121
+ [Saved][-] [-L-][ 0%][ ] drwxrwxr-x pj pj 113 Mio Sun Jun 23 09:17:42 2024 home/pj/tmp
1122
+ [Saved][-] [-L-][ 1%][ ] drwxrwxr-x pj pj 50 Mio Wed Jun 19 20:52:13 2024 home/pj/tmp/LUT-play
1123
+ [Saved][ ] [-L-][ 0%][X] -rw-rw-r-- pj pj 49 Mio Sun Jun 16 12:52:22 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15.NEF
1124
+ [Saved][ ] [-L-][ 95%][ ] -rw-rw-r-- pj pj 48 kio Sat Jun 22 21:51:24 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15.NEF.xmp
1125
+ [Saved][ ] [-L-][ 95%][ ] -rw-rw-r-- pj pj 50 kio Sat Jun 22 21:51:25 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_01.NEF.xmp
1126
+ [Saved][ ] [-L-][ 95%][ ] -rw-rw-r-- pj pj 51 kio Sat Jun 22 21:51:26 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_02.NEF.xmp
1127
+ [Saved][ ] [-L-][ 95%][ ] -rw-rw-r-- pj pj 51 kio Sat Jun 22 21:51:27 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_03.NEF.xmp
1128
+ [Saved][ ] [-L-][ 95%][ ] -rw-rw-r-- pj pj 51 kio Sat Jun 22 21:51:27 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_04.NEF.xmp
1129
+ [Saved][ ] [-L-][ 97%][ ] -rw-rw-r-- pj pj 77 kio Sat Jun 22 21:50:16 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_05.NEF.xmp
1130
+ [Saved][ ] [-L-][ 95%][ ] -rw-rw-r-- pj pj 52 kio Sat Jun 22 21:49:37 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_06.NEF.xmp
1131
+ [Saved][ ] [-L-][ 92%][ ] -rw-rw-r-- pj pj 24 kio Sat Jun 22 21:50:47 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_07.NEF.xmp
1132
+ [Saved][ ] [-L-][ 92%][ ] -rw-rw-r-- pj pj 24 kio Sat Jun 22 21:51:12 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_08.NEF.xmp
1133
+ [Saved][ ] [-L-][ 92%][ ] -rw-rw-r-- pj pj 24 kio Sat Jun 22 21:51:12 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_09.NEF.xmp
1134
+ [Saved][ ] [-L-][ 92%][ ] -rw-rw-r-- pj pj 24 kio Sat Jun 22 21:50:39 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_10.NEF.xmp
1135
+ [Saved][ ] [-L-][ 92%][ ] -rw-rw-r-- pj pj 24 kio Sat Jun 22 21:50:36 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_11.NEF.xmp
1136
+ [Saved][ ] [-L-][ 92%][ ] -rw-rw-r-- pj pj 24 kio Sat Jun 22 21:50:35 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_12.NEF.xmp
1137
+ [Saved][ ] [-L-][ 88%][ ] -rw-rw-r-- pj pj 15 kio Sat Jun 22 21:51:11 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_13.NEF.xmp
1138
+ [Saved][ ] [-L-][ 96%][ ] -rw-rw-r-- pj pj 84 kio Sat Jun 22 21:51:09 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_14.NEF.xmp
1139
+ [Saved][ ] [-L-][ 96%][ ] -rw-rw-r-- pj pj 90 kio Sat Jun 22 21:51:04 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_15.NEF.xmp
1140
+ [Saved][ ] [-L-][ 92%][ ] -rw-rw-r-- pj pj 24 kio Sat Jun 22 21:51:15 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_16.NEF.xmp
1141
+ [Saved][ ] [-L-][ 92%][ ] -rw-rw-r-- pj pj 24 kio Sat Jun 22 21:50:48 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_17.NEF.xmp
1142
+ [Saved][ ] [-L-][ 92%][ ] -rw-rw-r-- pj pj 24 kio Sat Jun 22 21:50:19 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_18.NEF.xmp
1138
1143
  ````
1139
1144
 
1140
- ### select file dates in the directory:
1145
+ ### select file dates in the directory
1141
1146
 
1142
1147
  ``` bash
1143
1148
  dar -l /tmp/example_FULL_2024-06-23 -I '*2024-06-16*' -g home/pj/tmp/LUT-play
@@ -1148,30 +1153,30 @@ gives
1148
1153
  ``` code
1149
1154
  [Data ][D][ EA ][FSA][Compr][S]| Permission | User | Group | Size | Date | filename
1150
1155
  --------------------------------+------------+-------+-------+---------+-------------------------------+------------
1151
- [Saved][-] [-L-][ 0%][ ] drwxr-xr-x root root 113 Mio Sat May 11 16:16:48 2024 home
1152
- [Saved][-] [-L-][ 0%][ ] drwxrwxr-x pj pj 113 Mio Sun Jun 23 10:46:30 2024 home/pj
1153
- [Saved][-] [-L-][ 0%][ ] drwxrwxr-x pj pj 113 Mio Sun Jun 23 09:17:42 2024 home/pj/tmp
1154
- [Saved][-] [-L-][ 1%][ ] drwxrwxr-x pj pj 50 Mio Wed Jun 19 20:52:13 2024 home/pj/tmp/LUT-play
1155
- [Saved][ ] [-L-][ 0%][X] -rw-rw-r-- pj pj 49 Mio Sun Jun 16 12:52:22 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15.NEF
1156
- [Saved][ ] [-L-][ 95%][ ] -rw-rw-r-- pj pj 48 kio Sat Jun 22 21:51:24 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15.NEF.xmp
1157
- [Saved][ ] [-L-][ 95%][ ] -rw-rw-r-- pj pj 50 kio Sat Jun 22 21:51:25 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_01.NEF.xmp
1158
- [Saved][ ] [-L-][ 95%][ ] -rw-rw-r-- pj pj 51 kio Sat Jun 22 21:51:26 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_02.NEF.xmp
1159
- [Saved][ ] [-L-][ 95%][ ] -rw-rw-r-- pj pj 51 kio Sat Jun 22 21:51:27 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_03.NEF.xmp
1160
- [Saved][ ] [-L-][ 95%][ ] -rw-rw-r-- pj pj 51 kio Sat Jun 22 21:51:27 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_04.NEF.xmp
1161
- [Saved][ ] [-L-][ 97%][ ] -rw-rw-r-- pj pj 77 kio Sat Jun 22 21:50:16 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_05.NEF.xmp
1162
- [Saved][ ] [-L-][ 95%][ ] -rw-rw-r-- pj pj 52 kio Sat Jun 22 21:49:37 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_06.NEF.xmp
1163
- [Saved][ ] [-L-][ 92%][ ] -rw-rw-r-- pj pj 24 kio Sat Jun 22 21:50:47 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_07.NEF.xmp
1164
- [Saved][ ] [-L-][ 92%][ ] -rw-rw-r-- pj pj 24 kio Sat Jun 22 21:51:12 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_08.NEF.xmp
1165
- [Saved][ ] [-L-][ 92%][ ] -rw-rw-r-- pj pj 24 kio Sat Jun 22 21:51:12 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_09.NEF.xmp
1166
- [Saved][ ] [-L-][ 92%][ ] -rw-rw-r-- pj pj 24 kio Sat Jun 22 21:50:39 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_10.NEF.xmp
1167
- [Saved][ ] [-L-][ 92%][ ] -rw-rw-r-- pj pj 24 kio Sat Jun 22 21:50:36 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_11.NEF.xmp
1168
- [Saved][ ] [-L-][ 92%][ ] -rw-rw-r-- pj pj 24 kio Sat Jun 22 21:50:35 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_12.NEF.xmp
1169
- [Saved][ ] [-L-][ 88%][ ] -rw-rw-r-- pj pj 15 kio Sat Jun 22 21:51:11 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_13.NEF.xmp
1170
- [Saved][ ] [-L-][ 96%][ ] -rw-rw-r-- pj pj 84 kio Sat Jun 22 21:51:09 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_14.NEF.xmp
1171
- [Saved][ ] [-L-][ 96%][ ] -rw-rw-r-- pj pj 90 kio Sat Jun 22 21:51:04 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_15.NEF.xmp
1172
- [Saved][ ] [-L-][ 92%][ ] -rw-rw-r-- pj pj 24 kio Sat Jun 22 21:51:15 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_16.NEF.xmp
1173
- [Saved][ ] [-L-][ 92%][ ] -rw-rw-r-- pj pj 24 kio Sat Jun 22 21:50:48 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_17.NEF.xmp
1174
- [Saved][ ] [-L-][ 92%][ ] -rw-rw-r-- pj pj 24 kio Sat Jun 22 21:50:19 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_18.NEF.xmp
1156
+ [Saved][-] [-L-][ 0%][ ] drwxr-xr-x root root 113 Mio Sat May 11 16:16:48 2024 home
1157
+ [Saved][-] [-L-][ 0%][ ] drwxrwxr-x pj pj 113 Mio Sun Jun 23 10:46:30 2024 home/pj
1158
+ [Saved][-] [-L-][ 0%][ ] drwxrwxr-x pj pj 113 Mio Sun Jun 23 09:17:42 2024 home/pj/tmp
1159
+ [Saved][-] [-L-][ 1%][ ] drwxrwxr-x pj pj 50 Mio Sed Jun 19 20:52:13 2024 home/pj/tmp/LUT-play
1160
+ [Saved][ ] [-L-][ 0%][X] -rw-rw-r-- pj pj 49 Mio Sun Jun 16 12:52:22 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15.NEF
1161
+ [Saved][ ] [-L-][ 95%][ ] -rw-rw-r-- pj pj 48 kio Sat Jun 22 21:51:24 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15.NEF.xmp
1162
+ [Saved][ ] [-L-][ 95%][ ] -rw-rw-r-- pj pj 50 kio Sat Jun 22 21:51:25 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_01.NEF.xmp
1163
+ [Saved][ ] [-L-][ 95%][ ] -rw-rw-r-- pj pj 51 kio Sat Jun 22 21:51:26 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_02.NEF.xmp
1164
+ [Saved][ ] [-L-][ 95%][ ] -rw-rw-r-- pj pj 51 kio Sat Jun 22 21:51:27 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_03.NEF.xmp
1165
+ [Saved][ ] [-L-][ 95%][ ] -rw-rw-r-- pj pj 51 kio Sat Jun 22 21:51:27 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_04.NEF.xmp
1166
+ [Saved][ ] [-L-][ 97%][ ] -rw-rw-r-- pj pj 77 kio Sat Jun 22 21:50:16 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_05.NEF.xmp
1167
+ [Saved][ ] [-L-][ 95%][ ] -rw-rw-r-- pj pj 52 kio Sat Jun 22 21:49:37 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_06.NEF.xmp
1168
+ [Saved][ ] [-L-][ 92%][ ] -rw-rw-r-- pj pj 24 kio Sat Jun 22 21:50:47 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_07.NEF.xmp
1169
+ [Saved][ ] [-L-][ 92%][ ] -rw-rw-r-- pj pj 24 kio Sat Jun 22 21:51:12 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_08.NEF.xmp
1170
+ [Saved][ ] [-L-][ 92%][ ] -rw-rw-r-- pj pj 24 kio Sat Jun 22 21:51:12 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_09.NEF.xmp
1171
+ [Saved][ ] [-L-][ 92%][ ] -rw-rw-r-- pj pj 24 kio Sat Jun 22 21:50:39 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_10.NEF.xmp
1172
+ [Saved][ ] [-L-][ 92%][ ] -rw-rw-r-- pj pj 24 kio Sat Jun 22 21:50:36 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_11.NEF.xmp
1173
+ [Saved][ ] [-L-][ 92%][ ] -rw-rw-r-- pj pj 24 kio Sat Jun 22 21:50:35 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_12.NEF.xmp
1174
+ [Saved][ ] [-L-][ 88%][ ] -rw-rw-r-- pj pj 15 kio Sat Jun 22 21:51:11 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_13.NEF.xmp
1175
+ [Saved][ ] [-L-][ 96%][ ] -rw-rw-r-- pj pj 84 kio Sat Jun 22 21:51:09 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_14.NEF.xmp
1176
+ [Saved][ ] [-L-][ 96%][ ] -rw-rw-r-- pj pj 90 kio Sat Jun 22 21:51:04 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_15.NEF.xmp
1177
+ [Saved][ ] [-L-][ 92%][ ] -rw-rw-r-- pj pj 24 kio Sat Jun 22 21:51:15 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_16.NEF.xmp
1178
+ [Saved][ ] [-L-][ 92%][ ] -rw-rw-r-- pj pj 24 kio Sat Jun 22 21:50:48 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_17.NEF.xmp
1179
+ [Saved][ ] [-L-][ 92%][ ] -rw-rw-r-- pj pj 24 kio Sat Jun 22 21:50:19 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15_18.NEF.xmp
1175
1180
  ```
1176
1181
 
1177
1182
  ### exclude .xmp files from that date
@@ -1183,17 +1188,15 @@ dar -l /tmp/example_FULL_2024-06-23 -X '*.xmp' -I '*2024-06-16*' -g home/pj/tmp/
1183
1188
 
1184
1189
  gives
1185
1190
 
1186
- ``` code
1191
+ ```` code
1187
1192
  [Data ][D][ EA ][FSA][Compr][S]| Permission | User | Group | Size | Date | filename
1188
1193
  --------------------------------+------------+-------+-------+---------+-------------------------------+------------
1189
- [Saved][-] [-L-][ 0%][ ] drwxr-xr-x root root 113 Mio Sat May 11 16:16:48 2024 home
1190
- [Saved][-] [-L-][ 0%][ ] drwxrwxr-x pj pj 113 Mio Sun Jun 23 10:46:30 2024 home/pj
1191
- [Saved][-] [-L-][ 0%][ ] drwxrwxr-x pj pj 113 Mio Sun Jun 23 09:17:42 2024 home/pj/tmp
1192
- [Saved][-] [-L-][ 1%][ ] drwxrwxr-x pj pj 50 Mio Wed Jun 19 20:52:13 2024 home/pj/tmp/LUT-play
1193
- [Saved][ ] [-L-][ 0%][X] -rw-rw-r-- pj pj 49 Mio Sun Jun 16 12:52:22 2024 home/pj/tmp/LUT-play/2024-06-16_12:52:22,15.NEF
1194
- ```
1195
-
1196
- Nice :-)
1194
+ [Saved][-] [-L-][ 0%][ ] drwxr-xr-x root root 113 Mio Sat May 11 16:16:48 2024 home
1195
+ [Saved][-] [-L-][ 0%][ ] drwxrwxr-x pj pj 113 Mio Sun Jun 23 10:46:30 2024 ome/pj
1196
+ [Saved][-] [-L-][ 0%][ ] drwxrwxr-x pj pj 113 Mio Sun Jun 23 09:17:42 2024 ome/pj/tmp
1197
+ [Saved][-] [-L-][ 1%][ ] drwxrwxr-x pj pj 50 Mio Wed Jun 19 20:52:13 2024 ` ome/pj/tmp/LUT-play
1198
+ [Saved][ ] [-L-][ 0%][X] -rw-rw-r-- pj pj 49 Mio Sun Jun 16 12:52:22 2024 ` home/pj/tmp/LUT-play/2024-06-16_12:52:22,15.NEF
1199
+ ````
1197
1200
 
1198
1201
  ## Restoring
1199
1202
 
@@ -1249,7 +1252,7 @@ deactivate
1249
1252
 
1250
1253
  "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.
1251
1254
 
1252
- 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).
1255
+ 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).
1253
1256
 
1254
1257
  This causes dar to restore without an error.
1255
1258
 
@@ -1340,66 +1343,82 @@ if --log-stdout is used the information would be picked up by systemd and logged
1340
1343
 
1341
1344
  The log file can get quite cluttered, if you want the clutter to be removed, run the `clean-log`script.
1342
1345
 
1346
+ ### Separate log file for command output
1347
+
1348
+ Dar-backup's log file is called `dar-backup.log`.
1349
+
1350
+ In order to not clutter that log file with the output of commands being run, a new log file has been introduced `dar-backup-commands.log`.
1351
+
1352
+ ## Todo
1353
+
1354
+ - `installer` to generate, but not deploy systemd units and timers for:
1355
+ - FULL, DIFF and INCR backups.
1356
+ - cleanup.
1357
+
1343
1358
  ## Reference
1344
1359
 
1345
- ### dar-backup.py
1360
+ ### dar-backup
1346
1361
 
1347
1362
  This script is responsible for managing the backup creation and validation process. It supports the following options:
1348
1363
 
1349
1364
  ``` code
1350
- --full-backup Perform a full backup.
1351
- --differential-backup Perform a differential backup.
1352
- --incremental-backup Perform an incremental backup.
1353
- --backup-definition <name> Specify the backup definition file.
1365
+ --full-backup Perform a full backup.
1366
+ --differential-backup Perform a differential backup.
1367
+ --incremental-backup Perform an incremental backup.
1368
+ --backup-definition <name> Specify the backup definition file.
1354
1369
  --alternate-reference-archive <file> Use a different archive for DIFF/INCR backups.
1355
- --config-file <path> Specify the path to the configuration file.
1356
- --darrc <path> Specify an optional path to .darrc.
1357
- --examples Show examples of using dar-backup.py.
1358
- --list List available backups.
1359
- --list-contents <archive> List the contents of a specified archive.
1360
- --selection <params> Define file selection for listing/restoring.
1361
- --restore <archive> Restore a specified archive.
1362
- --restore-dir <path> Directory to restore files to.
1363
- --verbose Enable verbose output.
1364
- --log-level <level> Set log level (debug, trace, etc.).
1365
- --log-stdout Also print log messages to stdout.
1366
- --do-not-compare Do not compare restores to file system.
1367
- --version Show version and license information.
1370
+ --config-file <path> Specify the path to the configuration file.
1371
+ --darrc <path> Specify an optional path to .darrc.
1372
+ --examples Show examples of using dar-backup.py.
1373
+ --list List available backups.
1374
+ --list-contents <archive> List the contents of a specified archive.
1375
+ --selection <params> Define file selection for listing/restoring.
1376
+ --restore <archive> Restore a specified archive.
1377
+ --restore-dir <path> Directory to restore files to.
1378
+ --verbose Enable verbose output.
1379
+ --suppress-dar-msg Filter out this from the darrc: "-vt", "-vs", "-vd", "-vf", "-va"
1380
+ --log-level <level> `debug` or `trace`, default is `info`", default="info".
1381
+ --log-stdout Also print log messages to stdout.
1382
+ --do-not-compare Do not compare restores to file system.
1383
+ --version Show version and license information.
1368
1384
  ```
1369
1385
 
1370
- ### manager.py
1386
+ ### manager
1371
1387
 
1372
1388
  This script manages `dar` databases and catalogs. Available options include:
1373
1389
 
1374
1390
  ``` code
1375
- --create-db Create missing databases for all backup definitions.
1376
- --alternate-archive-dir <path> Use this directory instead of BACKUP_DIR in the config file.
1377
- --add-dir <path> Add all archive catalogs in this directory to databases.
1378
- -d, --backup-def <name> Restrict to work only on this backup definition.
1379
- --add-specific-archive <file> Add this archive to the catalog database.
1380
- --remove-specific-archive <file> Remove this archive from the catalog database.
1381
- -l, --list-catalogs List catalogs in databases for all backup definitions.
1382
- --list-catalog-contents <num> List contents of a catalog by archive number.
1383
- --list-archive-contents <file> List contents of an archive’s catalog.
1384
- --find-file <file> Search catalogs for a specific file.
1385
- --verbose Enable verbose output.
1386
- --log-level <level> Set log level.
1391
+ --create-db Create missing databases for all backup definitions.
1392
+ --alternate-archive-dir <path> Use this directory instead of BACKUP_DIR in the config file.
1393
+ --add-dir <path> Add all archive catalogs in this directory to databases.
1394
+ -d, --backup-def <name> Restrict to work only on this backup definition.
1395
+ --add-specific-archive <archive> Add this archive to the catalog database.
1396
+ --remove-specific-archive <archive> Remove this archive from the catalog database.
1397
+ -l, --list-catalogs List catalogs in databases for all backup definitions.
1398
+ --list-catalog-contents <num> List contents of a catalog by catalog number.
1399
+ --list-archive-contents <archive> List contents of an archive’s catalog, given the archive name.
1400
+ --find-file <file> Search catalogs for a specific file.
1401
+ --verbose Enable verbose output.
1402
+ --log-level <level> `debug` or `trace`, default is `info`", default="info".
1387
1403
  ```
1388
1404
 
1389
- ### cleanup.py
1405
+ ### cleanup
1390
1406
 
1391
1407
  This script cleans up old backups and manages storage. Supported options:
1392
1408
 
1393
1409
  ``` code
1394
- --prune-older-than <days> Remove backups older than the specified number of days.
1395
- --keep-last <num> Retain only the last <num> backups.
1396
- --dry-run Show what would be deleted without actually deleting.
1397
- --backup-dir <path> Specify the backup directory.
1398
- --verbose Enable verbose output.
1399
- --help Show this help message and exit.
1410
+ -d, --backup-definition Backup definition to cleanup.
1411
+ -c, --config-file Path to 'dar-backup.conf', default='~/.config/dar-backup/dar-backup.conf.
1412
+ -v, --version Show version & license information.
1413
+ --alternate-archive-dir Clean up in this directory instead of the default one.
1414
+ --cleanup-specific-archives <archive>, ... Comma separated list of archives to cleanup.
1415
+ -l, --list List available archives.
1416
+ --verbose Print various status messages to screen.
1417
+ --log-level <level> `debug` or `trace`, default is `info`", default="info".
1418
+ --log-stdout Print log messages to stdout.
1400
1419
  ```
1401
1420
 
1402
- ### clean-log.py
1421
+ ### clean-log
1403
1422
 
1404
1423
  This script removes excessive logging output from `dar` logs, improving readability and efficiency. Available options:
1405
1424
 
@@ -1408,4 +1427,30 @@ This script removes excessive logging output from `dar` logs, improving readabil
1408
1427
  -c, --config-file <path> Specify the configuration file (default: ~/.config/dar-backup/dar-backup.conf).
1409
1428
  --dry-run Show which lines would be removed without modifying the file.
1410
1429
  -v, --version Display version and licensing information.
1430
+ -h, --help Displays usage info
1431
+ ```
1432
+
1433
+ ### installer
1434
+
1435
+ Sets up `dar-backup`for a user.
1436
+
1437
+ It is non-destructive and stops if directories are already in place.
1438
+
1439
+ Create directories:
1440
+
1441
+ - ~/.config/dar-backup/
1442
+ - ~/.config/dar-backup/backup.d/
1443
+ - ~/dar-backup/
1444
+ - ~/dar-backup/backups
1445
+ - ~/dar-backup/restore
1446
+
1447
+ Sets up demo config files:
1448
+
1449
+ - ~/.config/dar-backup/dar-backup.conf
1450
+ - ~/.config/dar-backup/backup.d/default
1451
+
1452
+ ``` code
1453
+ -i, --install Sets up `dar-backup`.
1454
+ -v, --version Display version and licensing information.
1455
+ -h, --help Displays usage info
1411
1456
  ```
@@ -0,0 +1,16 @@
1
+ dar_backup/.darrc,sha256=-aerqivZmOsW_XBCh9IfbYTUvw0GkzDSr3Vx4GcNB1g,2113
2
+ dar_backup/__about__.py,sha256=WhY38gJ9InHtiokbumhF5t6Q_JtwytMg5oigYIZ6CZ0,24
3
+ dar_backup/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ dar_backup/clean_log.py,sha256=cGhtKYnQJ2ceNQfw5XcCln_WNBasbmlfhO3kRydjDNk,5196
5
+ dar_backup/cleanup.py,sha256=1g2si9-jPEL8T4OKaiGSKFGsF6rWh-Ke1-zQHE7HaPc,11703
6
+ dar_backup/config_settings.py,sha256=uicCq6FnpxPFzbv7xfYSXNnQf1tfLk1Z3VIO9M71fsE,4659
7
+ dar_backup/dar-backup.conf,sha256=-wXqP4vj5TS7cCfMJN1nbk-1Sqkq00Tg22ySQXynUF4,902
8
+ dar_backup/dar_backup.py,sha256=yJjDqyPBWsjsMZeMbEgLzLt0N8R2y-YZGdfxvZjm8gs,35723
9
+ dar_backup/installer.py,sha256=ehp4KSgTc8D9Edsyve5v3NY2MuDbuTFYQQPgou8woV8,4331
10
+ dar_backup/manager.py,sha256=VBeZEIETDL_Lxhmo-T47xSQAxbOPGU-ZLI7GFXJx-Go,21647
11
+ dar_backup/util.py,sha256=lfgfxC_C6x3I8f9vzyxpQE7P-7rm6tTEr3P-2uWlVtQ,12278
12
+ dar_backup-0.6.13.1.dist-info/METADATA,sha256=zWP8JXewEWB5x-dbTjnDBJ8PBFiFdwBPtkptaSP9Fg4,72698
13
+ dar_backup-0.6.13.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
14
+ dar_backup-0.6.13.1.dist-info/entry_points.txt,sha256=Z7P5BUbhtJxo8_nB9qNIMay2eGDbsMKB3Fjwv3GMa4g,202
15
+ dar_backup-0.6.13.1.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
16
+ dar_backup-0.6.13.1.dist-info/RECORD,,
@@ -1,16 +0,0 @@
1
- dar_backup/.darrc,sha256=-aerqivZmOsW_XBCh9IfbYTUvw0GkzDSr3Vx4GcNB1g,2113
2
- dar_backup/__about__.py,sha256=AY38r3HUSyMqkCPP-vaHASQmjQF5-PRm11j_QYtx28w,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=NaZMrTdtYv4uJSw3jwDQWc5F5jMfIdfIQdrcPGAVcnM,11439
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=Cye8gS0E0mNKaUzcjqsdsuTyyeZYCspRMBIdcGbsEik,33171
9
- dar_backup/installer.py,sha256=0TgC_O-T7Y3sLn_NIQ9lBYt8GJqLZzxPqkmbjElfgkM,4491
10
- dar_backup/manager.py,sha256=MkrB0AL0MefE_cUAvdzQb_0bzvqsPmYi5s0M3EPw-z8,21379
11
- dar_backup/util.py,sha256=E-sEBQZY1hmdeVx5xNE22zKQ0BXDee1eI9F1-w7Fq1Q,15756
12
- dar_backup-0.6.12.dist-info/METADATA,sha256=YEbPNJr_ntP03BVKSACo2MU-Qko_9quyzmuqrhryBz0,69768
13
- dar_backup-0.6.12.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
14
- dar_backup-0.6.12.dist-info/entry_points.txt,sha256=Z7P5BUbhtJxo8_nB9qNIMay2eGDbsMKB3Fjwv3GMa4g,202
15
- dar_backup-0.6.12.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
16
- dar_backup-0.6.12.dist-info/RECORD,,