dar-backup 0.6.15__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 CHANGED
@@ -1 +1 @@
1
- __version__ = "0.6.15"
1
+ __version__ = "0.6.17"
dar_backup/cleanup.py CHANGED
@@ -21,6 +21,7 @@ import subprocess
21
21
  import sys
22
22
 
23
23
  from datetime import datetime, timedelta
24
+ from inputimeout import inputimeout, TimeoutOccurred
24
25
  from time import time
25
26
  from typing import Dict, List, NamedTuple
26
27
 
@@ -28,14 +29,14 @@ from . import __about__ as about
28
29
  from dar_backup.config_settings import ConfigSettings
29
30
  from dar_backup.util import extract_error_lines
30
31
  from dar_backup.util import list_backups
31
- from dar_backup.util import run_command
32
32
  from dar_backup.util import setup_logging
33
+ from dar_backup.util import get_logger
33
34
 
34
- from dar_backup.util import CommandResult
35
-
36
-
35
+ from dar_backup.command_runner import CommandRunner
36
+ from dar_backup.command_runner import CommandResult
37
37
 
38
38
  logger = None
39
+ runner = None
39
40
 
40
41
  def delete_old_backups(backup_dir, age, backup_type, args, backup_definition=None):
41
42
  """
@@ -133,7 +134,7 @@ def delete_catalog(catalog_name: str, args: NamedTuple) -> bool:
133
134
  command = [f"manager", "--remove-specific-archive", catalog_name, "--config-file", args.config_file, '--log-level', 'debug', '--log-stdout']
134
135
  logger.info(f"Deleting catalog '{catalog_name}' using config file: '{args.config_file}'")
135
136
  try:
136
- result:CommandResult = run_command(command)
137
+ result:CommandResult = runner.run(command)
137
138
  if result.returncode == 0:
138
139
  logger.info(f"Deleted catalog '{catalog_name}', using config file: '{args.config_file}'")
139
140
  logger.debug(f"Stdout: manager.py --remove-specific-archive output:\n{result.stdout}")
@@ -156,19 +157,21 @@ def show_version():
156
157
  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW, not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
157
158
  See section 15 and section 16 in the supplied "LICENSE" file.''')
158
159
 
160
+
159
161
  def main():
160
- global logger
162
+ global logger, runner
161
163
 
162
164
  parser = argparse.ArgumentParser(description="Cleanup old archives according to AGE configuration.")
163
165
  parser.add_argument('-d', '--backup-definition', help="Specific backup definition to cleanup.")
164
166
  parser.add_argument('-c', '--config-file', '-c', type=str, help="Path to 'dar-backup.conf'", default='~/.config/dar-backup/dar-backup.conf')
165
167
  parser.add_argument('-v', '--version', action='store_true', help="Show version information.")
166
168
  parser.add_argument('--alternate-archive-dir', type=str, help="Cleanup in this directory instead of the default one.")
167
- parser.add_argument('--cleanup-specific-archives', type=str, help="Commas separated list of archives to cleanup")
169
+ parser.add_argument('--cleanup-specific-archives', type=str, help="Comma separated list of archives to cleanup")
168
170
  parser.add_argument('-l', '--list', action='store_true', help="List available archives.")
169
171
  parser.add_argument('--verbose', action='store_true', help="Print various status messages to screen")
170
172
  parser.add_argument('--log-level', type=str, help="`debug` or `trace`, default is `info`", default="info")
171
173
  parser.add_argument('--log-stdout', action='store_true', help='also print log messages to stdout')
174
+ parser.add_argument('--test-mode', action='store_true', help='Read envvars in order to run some pytest cases')
172
175
  args = parser.parse_args()
173
176
 
174
177
  args.config_file = os.path.expanduser(os.path.expandvars(args.config_file))
@@ -185,7 +188,8 @@ def main():
185
188
  # command_output_log = os.path.join(config_settings.logfile_location.removesuffix("dar-backup.log"), "dar-backup-commands.log")
186
189
  command_output_log = config_settings.logfile_location.replace("dar-backup.log", "dar-backup-commands.log")
187
190
  logger = setup_logging(config_settings.logfile_location, command_output_log, args.log_level, args.log_stdout)
188
-
191
+ command_logger = get_logger(command_output_logger = True)
192
+ runner = CommandRunner(logger=logger, command_logger=command_logger)
189
193
 
190
194
  logger.info(f"=====================================")
191
195
  logger.info(f"cleanup.py started, version: {about.__version__}")
@@ -232,6 +236,33 @@ def main():
232
236
  logger.info(f"Cleaning up specific archives: {args.cleanup_specific_archives}")
233
237
  archive_names = args.cleanup_specific_archives.split(',')
234
238
  for archive_name in archive_names:
239
+ if "_FULL_" in archive_name:
240
+ try:
241
+ try:
242
+ # used for pytest cases
243
+ if args.test_mode:
244
+ confirmation = os.environ.get("CLEANUP_TEST_DELETE_FULL")
245
+ if confirmation == None:
246
+ raise RuntimeError("envvar 'CLEANUP_TEST_DELETE_FULL' not set")
247
+
248
+ else:
249
+ confirmation = inputimeout(
250
+ prompt=f"Are you sure you want to delete the FULL archive '{archive_name}'? (yes/no): ",
251
+ timeout=30)
252
+ if confirmation == None:
253
+ continue
254
+ else:
255
+ confirmation = confirmation.strip().lower()
256
+ except TimeoutOccurred:
257
+ logger.info(f"Timeout waiting for confirmation for FULL archive: {archive_name}. Skipping deletion.")
258
+ continue
259
+ except KeyboardInterrupt:
260
+ logger.info(f"User interrupted confirmation for FULL archive: {archive_name}. Skipping deletion.")
261
+ continue
262
+
263
+ if confirmation != 'yes':
264
+ logger.info(f"User did not answer 'yes' to confirm deletion of FULL archive: {archive_name}. Skipping deletion.")
265
+ continue
235
266
  logger.info(f"Deleting archive: {archive_name}")
236
267
  delete_archive(config_settings.backup_dir, archive_name.strip(), args)
237
268
  elif args.list:
@@ -253,15 +284,6 @@ def main():
253
284
  end_time=int(time())
254
285
  logger.info(f"END TIME: {end_time}")
255
286
 
256
- # error_lines = extract_error_lines(config_settings.logfile_location, start_time, end_time)
257
- # if len(error_lines) > 0:
258
- # args.verbose and print("\033[1m\033[31mErrors\033[0m encountered")
259
- # for line in error_lines:
260
- # args.verbose and print(line)
261
- # sys.exit(1)
262
- # else:
263
- # args.verbose and print("\033[1m\033[32mSUCCESS\033[0m No errors encountered")
264
- # sys.exit(0)
265
287
 
266
288
  if __name__ == "__main__":
267
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))
@@ -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
- self.par2_enabled = self.config['PAR2']['ENABLED'].lower() in ('true', '1', 'yes')
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)
@@ -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 util.run_command(), a default timeout of 30 secs is used.
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 = run_command(command, config_settings.command_timeout_secs)
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 = run_command(add_catalog_command, config_settings.command_timeout_secs)
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
- logger.debug(f"Running command: {' '.join(map(shlex.quote, command))}")
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 = run_command(command, config_settings.command_timeout_secs)
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 = run_command(command, config_settings.command_timeout_secs)
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 = run_command(command)
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
- logger.info(f"Running command: {' '.join(map(shlex.quote, command))}")
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 = run_command(command, config_settings.command_timeout_secs)
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 returns anything but zero.
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.info(f"Performing {type}")
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: FULL"))
745
- args.verbose and args.differential_backup and (print(f"Type of backup: DIFF"))
746
- args.verbose and args.incremental_backup and (print(f"Type of backup: INCR"))
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}"))
@@ -797,10 +800,10 @@ def main():
797
800
  end_time=int(time())
798
801
  logger.info(f"END TIME: {end_time}")
799
802
  # Clean up
800
- if os.path.exists(args.darrc) and os.path.dirname(args.darrc) == os.path.expanduser("~"):
801
- if args.darrc.startswith("filtered_darrc_"):
802
- os.remove(args.darrc)
803
- logger.debug(f"Removed filtered .darrc: {args.darrc}")
803
+ if os.path.exists(args.darrc) and (os.path.dirname(args.darrc) == os.path.expanduser("~")):
804
+ if os.path.basename(args.darrc).startswith("filtered_darrc_"):
805
+ if os.remove(args.darrc):
806
+ logger.debug(f"Removed filtered .darrc: {args.darrc}")
804
807
 
805
808
 
806
809
  # Determine exit code
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 = run_command(command)
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 = run_command(command)
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 = run_command(command)
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 = run_command(command)
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 = run_command(command)
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 = run_command(command)
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 = run_command(command)
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.15
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
@@ -688,8 +688,10 @@ Classifier: Operating System :: POSIX :: Linux
688
688
  Classifier: Programming Language :: Python :: 3.9
689
689
  Classifier: Topic :: System :: Archiving :: Backup
690
690
  Requires-Python: >=3.9
691
+ Requires-Dist: inputimeout>=1.0.4
691
692
  Description-Content-Type: text/markdown
692
693
 
694
+ <!-- markdownlint-disable MD024 -->
693
695
  # Full, differential or incremental backups using 'dar'
694
696
 
695
697
  The wonderful 'dar' [Disk Archiver](https://github.com/Edrusb/DAR) is used for
@@ -706,17 +708,20 @@ This is the `Python` based **version 2** of `dar-backup`.
706
708
  - [Breaking change in version 0.6.0](#breaking-change-in-version-060)
707
709
  - [Homepage - Github](#homepage---github)
708
710
  - [Requirements](#requirements)
709
- - [Config file](#config-file)
711
+ - [Principles](#dar-backup-principles)
710
712
  - [How to run](#how-to-run)
711
713
  - [1 - installation](#1---installation)
712
714
  - [2 - configuration](#2---configuration)
713
715
  - [3 - generate catalog databases](#3---generate-catalog-databases)
714
716
  - [4 - do FULL backups](#4---do-full-backups)
715
717
  - [5 - deactivate venv](#5---deactivate-venv)
716
- - [.darrc](#darrc)
717
- - [Systemctl examples](#systemctl-examples)
718
- - [Service: dar-back --incremental-backup](#service-dar-back---incremental-backup)
719
- - [Timer: dar-back --incremental-backup](#timer-dar-back---incremental-backup)
718
+ - [Config](#config)
719
+ - [Config file](#config-file)
720
+ - [.darrc](#darrc)
721
+ - [Backup definition](#backup-definition-example)
722
+ - [Systemd examples](#systemctl-examples)
723
+ - [Service: dar-back --incremental-backup](#service-dar-backup---incremental-backup)
724
+ - [Timer: dar-back --incremental-backup](#timer-dar-backup---incremental-backup)
720
725
  - [List contents of an archive](#list-contents-of-an-archive)
721
726
  - [dar file selection examples](#dar-file-selection-examples)
722
727
  - [Select a directory](#select-a-directory)
@@ -739,13 +744,15 @@ This is the `Python` based **version 2** of `dar-backup`.
739
744
  - [Performance tip due to par2](#performance-tip-due-to-par2)
740
745
  - [.darrc sets -vd -vf (since v0.6.4)](#darrc-sets--vd--vf-since-v064)
741
746
  - [Separate log file for command output](#separate-log-file-for-command-output)
747
+ - [Skipping cache directories](#skipping-cache-directories)
742
748
  - [Todo](#todo)
743
749
  - [Reference](#reference)
744
- - [dar-backup](#dar-backup)
745
- - [manager](#manager)
746
- - [cleanup](#cleanup)
747
- - [clean-log](#clean-log)
748
- - [installer](#installer)
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)
749
756
 
750
757
  ## My use case
751
758
 
@@ -782,9 +789,9 @@ Version 0.6.0 and forwards requires the config variable *COMMAND_TIMEOUT_SECS* i
782
789
 
783
790
  ## Homepage - Github
784
791
 
785
- This 'dar-backup' package lives at: [Github - dar-backup](https://github.com/per2jensen/dar-backup/tree/main/v2)
792
+ 'dar-backup' package lives here: [Github - dar-backup](https://github.com/per2jensen/dar-backup/tree/main/v2)
786
793
 
787
- This python version is v2 of dar-backup, the first is made in bash.
794
+ This python version is v2 of dar-backup, v1 is made in bash.
788
795
 
789
796
  ## Requirements
790
797
 
@@ -798,9 +805,40 @@ On Ubuntu, install the requirements this way:
798
805
  sudo apt install dar par2 python3
799
806
  ````
800
807
 
801
- ## Config file
808
+ ## dar-backup principles
802
809
 
803
- The default configuration is expected here: ~/.config/dar-backup/dar-backup.conf
810
+ ### dar-backup
811
+
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.
813
+
814
+ There are 3 levels of backups, FULL, DIFF and INCR.
815
+
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)).
830
+
831
+ ### cleanup
832
+
833
+ The `cleanup` application deletes DIFF and INCR if the archives are older than the thresholds set up in the configuration file.
834
+
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.
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.
804
842
 
805
843
  ## How to run
806
844
 
@@ -814,6 +852,10 @@ Installation is currently in a venv. These commands are installed in the venv:
814
852
  - clean-log
815
853
  - installer
816
854
 
855
+ Note:
856
+
857
+ The module `inputimeout` is installed into the venv and used for the confirmation input (with a 30 second timeout)
858
+
817
859
  To install, create a venv and run pip:
818
860
 
819
861
  ```` bash
@@ -875,16 +917,18 @@ manager --create-db
875
917
 
876
918
  ### 4 - do FULL backups
877
919
 
878
- You are ready to do backups of all your backup definitions, if your backup definitions are
879
- in place in BACKUP.D_DIR (see config file)
920
+ Prereq:
921
+ [Backup definitions](#backup-definition-example) are in place in BACKUP.D_DIR (see [config file](#config-file)).
922
+
923
+ You are ready to do backups of all your backup definitions.
880
924
 
881
925
  ```` bash
882
926
  dar-backup --full-backup
883
927
  ````
884
928
 
885
- If you want to see dar-backup's log entries in the terminal, use the `--log-stdout` option. This is also useful if dar-backup is started by systemd.
929
+ If you want to see dar-backup's log entries in the terminal, use the `--log-stdout` option. This can be useful if dar-backup is started by systemd.
886
930
 
887
- If you want more log messages, use the `--log-level debug` option.
931
+ If you want more log messages, use the `--verbose` or `--log-level debug` for even more.
888
932
 
889
933
  If you want a backup of a single definition, use the `-d <backup definition>` option. The definition's name is the filename of the definition in the `backup.d` config directory.
890
934
 
@@ -900,9 +944,54 @@ Deactivate the virtual environment (venv)
900
944
  deactivate
901
945
  ````
902
946
 
903
- ## .darrc
947
+ ## Config
948
+
949
+ ### Config file
950
+
951
+ The configuration file's default location is: ~/.config/dar-backup/dar-backup.conf
952
+
953
+ If you have your config file somewhere else, use the `--config` option to point to it.
954
+
955
+ Tilde `~` and environment variables can be used in the paths for various file locations.
956
+
957
+ ```` code
958
+ [MISC]
959
+ LOGFILE_LOCATION=~/.dar-backup.log
960
+ MAX_SIZE_VERIFICATION_MB = 20
961
+ MIN_SIZE_VERIFICATION_MB = 1
962
+ NO_FILES_VERIFICATION = 5
963
+ # timeout in seconds for backup, test, restore and par2 operations
964
+ # The author has such `dar` tasks running for 10-15 hours on the yearly backups, so a value of 24 hours is used.
965
+ # If a timeout is not specified when using the util.run_command(), a default timeout of 30 secs is used.
966
+ COMMAND_TIMEOUT_SECS = 86400
967
+
968
+ [DIRECTORIES]
969
+ BACKUP_DIR = /some/where/dar-backup/backups/
970
+ BACKUP.D_DIR = /some/where/dar-backup/backup.d
971
+ TEST_RESTORE_DIR = /tmp/dar-backup/restore/
972
+
973
+ [AGE]
974
+ # age settings are in days
975
+ DIFF_AGE = 100
976
+ INCR_AGE = 40
977
+
978
+ [PAR2]
979
+ ERROR_CORRECTION_PERCENT = 5
980
+ ENABLED = True
981
+
982
+ # scripts to run before the backup to setup the environment
983
+ [PREREQ]
984
+ SCRIPT_1 = ls -l /tmp
985
+ #SCRIPT_2 = another_script.sh
986
+
987
+ [POSTREQ]
988
+ SCRIPT_1 = df -h
989
+ #SCRIPT_2 = another_script.sh
990
+ ````
991
+
992
+ ### .darrc
904
993
 
905
- The package includes a default `.darrc` file which configures `dar`.
994
+ The package includes a default `darrc` file which configures `dar`.
906
995
 
907
996
  You can override the default `.darrc` using the `--darrc` option.
908
997
 
@@ -1031,6 +1120,49 @@ compress-exclusion:
1031
1120
  -acase
1032
1121
  ````
1033
1122
 
1123
+ ### Backup definition example
1124
+
1125
+ This piece of configuration is a [backup definition](#backup-definition-example). It is placed in the BACKUP.D_DIR (see config file description).
1126
+ The name of the file is the name of the backup definition.
1127
+
1128
+ You can use as many backup definitions as you need.
1129
+
1130
+ ```` code
1131
+ # Switch to ordered selection mode, which means that the following
1132
+ # options will be considered top to bottom
1133
+ -am
1134
+
1135
+ # Backup Root Dir
1136
+ # This is the top directory, where the backups start.
1137
+ #Directories mentioned below, are relative to the Root Dir.
1138
+ -R /home/user/
1139
+
1140
+ # Directories to backup below the Root dir
1141
+ # uncomment the next line to backup only the Documents directory
1142
+ # -g Documents
1143
+
1144
+ # Directories to exclude below the Root dir
1145
+ -P mnt
1146
+ -P tmp
1147
+ -P .cache
1148
+ -P .config/Code/CachedData
1149
+
1150
+ # compression level
1151
+ -z5
1152
+
1153
+ # no overwrite, if you rerun a backup, 'dar' halts and asks what to do
1154
+ # due to the -Q option given to `dar`, the program will terminate and give en error.
1155
+ -n
1156
+
1157
+ # size of each slice in the archive
1158
+ --slice 7G
1159
+
1160
+ # bypass directores marked as cache directories
1161
+ # http://dar.linux.free.fr/doc/Features.html
1162
+ # https://bford.info/cachedir/
1163
+ --cache-directory-tagging
1164
+ ````
1165
+
1034
1166
  ## Systemctl examples
1035
1167
 
1036
1168
  I have dar-backup scheduled to run via systemd --user settings.
@@ -1051,7 +1183,7 @@ Verify your timers are set up as you want:
1051
1183
  systemctl --user list-timers
1052
1184
  ````
1053
1185
 
1054
- ## Service: dar-back --incremental-backup
1186
+ ## Service: dar-backup --incremental-backup
1055
1187
 
1056
1188
  File: dar-inc-backup.service
1057
1189
 
@@ -1067,7 +1199,7 @@ RemainAfterExit=no
1067
1199
  ExecStart=/bin/bash -c '. /home/user/programmer/dar-backup.py/venv/bin/activate && dar-backup --incremental-backup --verbose'
1068
1200
  ````
1069
1201
 
1070
- ## Timer: dar-back --incremental-backup
1202
+ ## Timer: dar-backup --incremental-backup
1071
1203
 
1072
1204
  File: dar-inc-backup.timer
1073
1205
 
@@ -1252,7 +1384,7 @@ deactivate
1252
1384
 
1253
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.
1254
1386
 
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).
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).
1256
1388
 
1257
1389
  This causes dar to restore without an error.
1258
1390
 
@@ -1267,7 +1399,7 @@ My home directory is on a btrfs filesystem, while /tmp (for the restore test) is
1267
1399
 
1268
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.
1269
1401
 
1270
- 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)
1271
1403
 
1272
1404
  ## Par2
1273
1405
 
@@ -1328,26 +1460,35 @@ done
1328
1460
 
1329
1461
  This makes it easier to restore to a given date when having many FULL, DIFF and INCR archives.
1330
1462
 
1463
+ If the manager does not add an archive to it's catalog database, `dar-backup` will log an error and continue. The important part is verify the archive is usable and continue to other backup definitions.
1464
+
1331
1465
  ### Performance tip due to par2
1332
1466
 
1333
1467
  This [dar benchmark page](https://dar.sourceforge.io/doc/benchmark.html) has an interesting note on the slice size.
1334
1468
 
1335
- Slice size should be smaller than available RAM, apparently a large performance hit can be avoided keeping the the par2 data in memory.
1469
+ Slice size should be smaller than available RAM, apparently a large performance hit can be avoided keeping the par2 data in memory.
1336
1470
 
1337
1471
  ### .darrc sets -vd -vf (since v0.6.4)
1338
1472
 
1339
- These .darrc settings make `dar` print the current directory being processed (-vd) and some stats after (-vf)
1340
- This is very useful in very long running jobs to get an indication that the backup is proceeding normally.
1341
-
1342
- if --log-stdout is used the information would be picked up by systemd and logged by journald.½
1473
+ These [.darrc](#darrc) settings make `dar` print the current directory being processed (-vd) and some stats after (-vf)
1343
1474
 
1344
- The log file can get quite cluttered, if you want the clutter to be removed, run the `clean-log`script.
1475
+ This is very useful in very long running jobs to get an indication that the backup is proceeding normally.
1345
1476
 
1346
1477
  ### Separate log file for command output
1347
1478
 
1348
1479
  Dar-backup's log file is called `dar-backup.log`.
1349
1480
 
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`.
1481
+ In order to not clutter that log file with the output of commands being run, a new secondary log file has been introduced `dar-backup-commands.log`.
1482
+
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.
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.
1351
1492
 
1352
1493
  ## Todo
1353
1494
 
@@ -1355,84 +1496,113 @@ In order to not clutter that log file with the output of commands being run, a n
1355
1496
  - FULL, DIFF and INCR backups.
1356
1497
  - cleanup.
1357
1498
 
1358
- - fix --log-stdout spams console with command output
1499
+ - Add option to dar-backup to use the `dar` option `--fsa-scope none`
1359
1500
 
1360
1501
  ## Reference
1361
1502
 
1362
- ### dar-backup
1503
+ ### test coverage
1504
+
1505
+ Running
1506
+
1507
+ ```` bash
1508
+ pytest --cov=dar_backup tests/
1509
+ ````
1363
1510
 
1364
- This script is responsible for managing the backup creation and validation process. It supports the following options:
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:
1365
1533
 
1366
1534
  ``` code
1367
- --full-backup Perform a full backup.
1368
- --differential-backup Perform a differential backup.
1369
- --incremental-backup Perform an incremental backup.
1370
- --backup-definition <name> Specify the backup definition file.
1371
- --alternate-reference-archive <file> Use a different archive for DIFF/INCR backups.
1372
- --config-file <path> Specify the path to the configuration file.
1373
- --darrc <path> Specify an optional path to .darrc.
1374
- --examples Show examples of using dar-backup.py.
1375
- --list List available backups.
1376
- --list-contents <archive> List the contents of a specified archive.
1377
- --selection <params> Define file selection for listing/restoring.
1378
- --restore <archive> Restore a specified archive.
1379
- --restore-dir <path> Directory to restore files to.
1380
- --verbose Enable verbose output.
1381
- --suppress-dar-msg Filter out this from the darrc: "-vt", "-vs", "-vd", "-vf", "-va"
1382
- --log-level <level> `debug` or `trace`, default is `info`", default="info".
1383
- --log-stdout Also print log messages to stdout.
1384
- --do-not-compare Do not compare restores to file system.
1385
- --version Show version and license information.
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.
1386
1554
  ```
1387
1555
 
1388
- ### manager
1556
+ ### manager options
1389
1557
 
1390
- This script manages `dar` databases and catalogs. Available options include:
1558
+ This script manages `dar` databases and catalogs. Available options:
1391
1559
 
1392
1560
  ``` code
1393
- --create-db Create missing databases for all backup definitions.
1394
- --alternate-archive-dir <path> Use this directory instead of BACKUP_DIR in the config file.
1395
- --add-dir <path> Add all archive catalogs in this directory to databases.
1396
- -d, --backup-def <name> Restrict to work only on this backup definition.
1397
- --add-specific-archive <archive> Add this archive to the catalog database.
1398
- --remove-specific-archive <archive> Remove this archive from the catalog database.
1399
- -l, --list-catalogs List catalogs in databases for all backup definitions.
1400
- --list-catalog-contents <num> List contents of a catalog by catalog number.
1401
- --list-archive-contents <archive> List contents of an archive’s catalog, given the archive name.
1402
- --find-file <file> Search catalogs for a specific file.
1403
- --verbose Enable verbose output.
1404
- --log-level <level> `debug` or `trace`, default is `info`", default="info".
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".
1405
1574
  ```
1406
1575
 
1407
- ### cleanup
1576
+ ### cleanup options
1408
1577
 
1409
- This script cleans up old backups and manages storage. Supported options:
1578
+ This script cleans up old backups and par2 files. Supported options:
1410
1579
 
1411
1580
  ``` code
1412
- -d, --backup-definition Backup definition to cleanup.
1413
- -c, --config-file Path to 'dar-backup.conf', default='~/.config/dar-backup/dar-backup.conf.
1414
- -v, --version Show version & license information.
1415
- --alternate-archive-dir Clean up in this directory instead of the default one.
1416
- --cleanup-specific-archives <archive>, ... Comma separated list of archives to cleanup.
1417
- -l, --list List available archives.
1418
- --verbose Print various status messages to screen.
1419
- --log-level <level> `debug` or `trace`, default is `info`", default="info".
1420
- --log-stdout Print log messages to 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
1421
1591
  ```
1422
1592
 
1423
- ### clean-log
1593
+ ### clean-log options
1424
1594
 
1425
1595
  This script removes excessive logging output from `dar` logs, improving readability and efficiency. Available options:
1426
1596
 
1427
1597
  ``` code
1428
1598
  -f, --file <path> Specify the log file(s) to be cleaned.
1429
- -c, --config-file <path> Specify the configuration file (default: ~/.config/dar-backup/dar-backup.conf).
1599
+ -c, --config-file <path> Path to dar-backup.conf.
1430
1600
  --dry-run Show which lines would be removed without modifying the file.
1431
1601
  -v, --version Display version and licensing information.
1432
1602
  -h, --help Displays usage info
1433
1603
  ```
1434
1604
 
1435
- ### installer
1605
+ ### installer options
1436
1606
 
1437
1607
  Sets up `dar-backup`for a user.
1438
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=9pegTLVQP2o3JePGrLB6muzXif64umv9ihLnS7LsH8E,22
3
- dar_backup/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- dar_backup/clean_log.py,sha256=cGhtKYnQJ2ceNQfw5XcCln_WNBasbmlfhO3kRydjDNk,5196
5
- dar_backup/cleanup.py,sha256=1g2si9-jPEL8T4OKaiGSKFGsF6rWh-Ke1-zQHE7HaPc,11703
6
- dar_backup/config_settings.py,sha256=uicCq6FnpxPFzbv7xfYSXNnQf1tfLk1Z3VIO9M71fsE,4659
7
- dar_backup/dar-backup.conf,sha256=-wXqP4vj5TS7cCfMJN1nbk-1Sqkq00Tg22ySQXynUF4,902
8
- dar_backup/dar_backup.py,sha256=TrYMYNbQ9jWabWGc7GA5p2ezprLqMUu9O0zt6CQ1QSA,37867
9
- dar_backup/installer.py,sha256=ehp4KSgTc8D9Edsyve5v3NY2MuDbuTFYQQPgou8woV8,4331
10
- dar_backup/manager.py,sha256=sQl0xdWwBgui11S9Ekg0hOSC4gt89nz_Z8Bt8IPXCDw,21640
11
- dar_backup/util.py,sha256=F6U-e-WugxCxLPVoiWsM6_YO8VrDw1wdgGvtnGnig2I,12279
12
- dar_backup-0.6.15.dist-info/METADATA,sha256=JwTNDXCElF0elUKBhwjrDUASEkpLIjgdtfEML5NvXUY,72750
13
- dar_backup-0.6.15.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
14
- dar_backup-0.6.15.dist-info/entry_points.txt,sha256=Z7P5BUbhtJxo8_nB9qNIMay2eGDbsMKB3Fjwv3GMa4g,202
15
- dar_backup-0.6.15.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
16
- dar_backup-0.6.15.dist-info/RECORD,,