dar-backup 0.5.17__py3-none-any.whl → 0.6.0__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.5.17"
1
+ __version__ = "0.6.0"
@@ -14,6 +14,7 @@ class ConfigSettings:
14
14
  max_size_verification_mb (int): The maximum size for verification in megabytes.
15
15
  min_size_verification_mb (int): The minimum size for verification in megabytes.
16
16
  no_files_verification (int): The number of files for verification.
17
+ command_timeout_secs (int): The timeout in seconds for commands.
17
18
  backup_dir (str): The directory for backups.
18
19
  test_restore_dir (str): The directory for test restores.
19
20
  backup_d_dir (str): The directory for backup.d.
@@ -38,6 +39,7 @@ class ConfigSettings:
38
39
  self.max_size_verification_mb = int(self.config['MISC']['MAX_SIZE_VERIFICATION_MB'])
39
40
  self.min_size_verification_mb = int(self.config['MISC']['MIN_SIZE_VERIFICATION_MB'])
40
41
  self.no_files_verification = int(self.config['MISC']['NO_FILES_VERIFICATION'])
42
+ self.command_timeout_secs = int(self.config['MISC']['COMMAND_TIMEOUT_SECS'])
41
43
  self.backup_dir = self.config['DIRECTORIES']['BACKUP_DIR']
42
44
  self.test_restore_dir = self.config['DIRECTORIES']['TEST_RESTORE_DIR']
43
45
  self.backup_d_dir = self.config['DIRECTORIES']['BACKUP.D_DIR']
dar_backup/dar_backup.py CHANGED
@@ -33,7 +33,7 @@ from dar_backup.util import RestoreError
33
33
  logger = None
34
34
 
35
35
 
36
- def backup(backup_file: str, backup_definition: str, darrc: str):
36
+ def backup(backup_file: str, backup_definition: str, darrc: str, config_settings: ConfigSettings):
37
37
  """
38
38
  Performs a full backup using the 'dar' command.
39
39
 
@@ -63,8 +63,8 @@ def backup(backup_file: str, backup_definition: str, darrc: str):
63
63
  command = ['dar', '-c', backup_file, "-N", '-B', darrc, '-B', backup_definition, '-Q', "compress-exclusion", "verbose"]
64
64
  logger.info(f"Running command: {' '.join(map(shlex.quote, command))}")
65
65
  try:
66
- process = run_command(command)
67
- stdout, stderr = process.communicate()
66
+ process = run_command(command, config_settings.command_timeout_secs)
67
+ stdout, stderr = process.stdout, process.stderr
68
68
  if process.returncode == 0:
69
69
  logger.info("FULL backup completed successfully.")
70
70
  elif process.returncode == 5:
@@ -87,7 +87,7 @@ def backup(backup_file: str, backup_definition: str, darrc: str):
87
87
 
88
88
 
89
89
 
90
- def differential_backup(backup_file: str, backup_definition: str, base_backup_file: str, darrc: str):
90
+ def differential_backup(backup_file: str, backup_definition: str, base_backup_file: str, darrc: str, config_settings: ConfigSettings):
91
91
  """
92
92
  Creates a differential backup based on a specified base backup.
93
93
 
@@ -120,7 +120,7 @@ def differential_backup(backup_file: str, backup_definition: str, base_backup_fi
120
120
  logger.info(f"Running command: {' '.join(map(shlex.quote, command))}")
121
121
  try:
122
122
  process = run_command(command)
123
- stdout, stderr = process.communicate()
123
+ stdout, stderr = process.stdout, process.stderr
124
124
  if process.returncode == 0:
125
125
  logger.info("DIFF backup completed successfully.")
126
126
  elif process.returncode == 5:
@@ -139,7 +139,7 @@ def differential_backup(backup_file: str, backup_definition: str, base_backup_fi
139
139
  raise DifferentialBackupError(f"Unexpected error during differential backup: {e}") from e
140
140
 
141
141
 
142
- def incremental_backup(backup_file: str, backup_definition: str, last_backup_file: str, darrc: str):
142
+ def incremental_backup(backup_file: str, backup_definition: str, last_backup_file: str, darrc: str, config_settings: ConfigSettings):
143
143
  """
144
144
  Creates an incremental backup based on the last backup file.
145
145
 
@@ -173,8 +173,8 @@ def incremental_backup(backup_file: str, backup_definition: str, last_backup_fil
173
173
  command = ['dar', '-c', backup_file, "-N", '-B', darrc, '-B', backup_definition, '-A', last_backup_file, '-Q', "compress-exclusion", "verbose"]
174
174
  logger.info(f"Running command: {' '.join(map(shlex.quote, command))}")
175
175
  try:
176
- process = run_command(command)
177
- stdout, stderr = process.communicate()
176
+ process = run_command(command, config_settings.command_timeout_secs)
177
+ stdout, stderr = process.stdout, process.stderr
178
178
  if process.returncode == 0:
179
179
  logger.info("INCR backup completed successfully.")
180
180
  elif process.returncode == 5:
@@ -285,8 +285,8 @@ def verify(args: argparse.Namespace, backup_file: str, backup_definition: str, c
285
285
  result = True
286
286
  command = ['dar', '-t', backup_file, '-Q']
287
287
  logger.info(f"Running command: {' '.join(map(shlex.quote, command))}")
288
- process = run_command(command)
289
- stdout, stderr = process.communicate()
288
+ process = run_command(command, config_settings.command_timeout_secs)
289
+ stdout, stderr = process.stdout, process.stderr
290
290
  if process.returncode == 0:
291
291
  logger.info("Archive integrity test passed.")
292
292
  else:
@@ -324,8 +324,8 @@ def verify(args: argparse.Namespace, backup_file: str, backup_definition: str, c
324
324
  logger.info(f"Restoring file: '{restored_file_path}' from backup to: '{config_settings.test_restore_dir}' for file comparing")
325
325
  command = ['dar', '-x', backup_file, '-g', restored_file_path.lstrip("/"), '-R', config_settings.test_restore_dir, '-O', '-Q']
326
326
  logger.info(f"Running command: {' '.join(map(shlex.quote, command))}")
327
- process = run_command(command)
328
- stdout, stderr = process.communicate()
327
+ process = run_command(command, config_settings.command_timeout_secs)
328
+ stdout, stderr = process.stdout, process.stderr
329
329
  if process.returncode != 0:
330
330
  logger.error(f"Restore failed, dar return code: {process.returncode}.")
331
331
  raise Exception(stderr)
@@ -343,7 +343,7 @@ def verify(args: argparse.Namespace, backup_file: str, backup_definition: str, c
343
343
 
344
344
 
345
345
 
346
- def restore_backup(backup_name: str, backup_dir: str, restore_dir: str, selection: str =None):
346
+ def restore_backup(backup_name: str, config_settings: ConfigSettings, restore_dir: str, selection: str =None):
347
347
  """
348
348
  Restores a backup file to a specified directory.
349
349
 
@@ -353,7 +353,7 @@ def restore_backup(backup_name: str, backup_dir: str, restore_dir: str, selectio
353
353
  restore_dir (str): The directory where the backup should be restored to.
354
354
  selection (str, optional): A selection criteria to restore specific files or directories. Defaults to None.
355
355
  """
356
- backup_file = os.path.join(backup_dir, backup_name)
356
+ backup_file = os.path.join(config_settings.backup_dir, backup_name)
357
357
  command = ['dar', '-x', backup_file, '-O', '-Q', '-D']
358
358
  if restore_dir:
359
359
  if not os.path.exists(restore_dir):
@@ -364,13 +364,18 @@ def restore_backup(backup_name: str, backup_dir: str, restore_dir: str, selectio
364
364
  command.extend(selection_criteria)
365
365
  logger.info(f"Running command: {' '.join(map(shlex.quote, command))}")
366
366
  try:
367
- run_command(command)
367
+ process = run_command(command, config_settings.command_timeout_secs)
368
+ stdout, stderr = process.stdout, process.stderr
368
369
  except subprocess.CalledProcessError as e:
369
370
  logger.error("Exception details:", exc_info=True)
371
+ logger.error(f"stdout: {stdout}")
372
+ logger.error(f"stderr: {stderr}")
370
373
  raise RestoreError(f"Restore command failed: {e}") from e
371
374
  except Exception as e:
372
375
  logger.exception(f"Unexpected error during restore")
373
376
  logger.error("Exception details:", exc_info=True)
377
+ logger.error(f"stdout: {stdout}")
378
+ logger.error(f"stderr: {stderr}")
374
379
  raise RestoreError(f"Unexpected error during restore: {e}") from e
375
380
 
376
381
 
@@ -391,7 +396,7 @@ def get_backed_up_files(backup_name: str, backup_dir: str):
391
396
  command = ['dar', '-l', backup_path, '-am', '-as', "-Txml" , '-Q']
392
397
  logger.info(f"Running command: {' '.join(map(shlex.quote, command))}")
393
398
  process = run_command(command)
394
- stdout, stderr = process.communicate()
399
+ stdout,stderr = process.stdout, process.stderr
395
400
  # Parse the XML data
396
401
  root = ET.fromstring(stdout)
397
402
  output = None # help gc
@@ -422,7 +427,8 @@ def list_contents(backup_name, backup_dir, selection=None):
422
427
  command.extend(selection_criteria)
423
428
  logger.info(f"Running command: {' '.join(map(shlex.quote, command))}")
424
429
  process = run_command(command)
425
- stdout, stderr = process.communicate()
430
+ stdout,stderr = process.stdout, process.stderr
431
+
426
432
  for line in stdout.splitlines():
427
433
  if "[--- REMOVED ENTRY ----]" in line or "[Saved]" in line:
428
434
  print(line)
@@ -484,7 +490,7 @@ def perform_backup(args: argparse.Namespace, config_settings: ConfigSettings, ba
484
490
  continue
485
491
 
486
492
  if backup_type == 'FULL':
487
- backup(backup_file, backup_definition_path, args.darrc)
493
+ backup(backup_file, backup_definition_path, args.darrc, config_settings)
488
494
  else:
489
495
  base_backup_type = 'FULL' if backup_type == 'DIFF' else 'DIFF'
490
496
 
@@ -505,9 +511,9 @@ def perform_backup(args: argparse.Namespace, config_settings: ConfigSettings, ba
505
511
  latest_base_backup = os.path.join(config_settings.backup_dir, base_backups[-1].rsplit('.', 2)[0])
506
512
 
507
513
  if backup_type == 'DIFF':
508
- differential_backup(backup_file, backup_definition_path, latest_base_backup, args.darrc)
514
+ differential_backup(backup_file, backup_definition_path, latest_base_backup, args.darrc, config_settings)
509
515
  elif backup_type == 'INCR':
510
- incremental_backup(backup_file, backup_definition_path, latest_base_backup, args.darrc)
516
+ incremental_backup(backup_file, backup_definition_path, latest_base_backup, args.darrc, config_settings)
511
517
 
512
518
  logger.info("Starting verification...")
513
519
  result = verify(args, backup_file, backup_definition_path, config_settings)
@@ -524,7 +530,7 @@ def perform_backup(args: argparse.Namespace, config_settings: ConfigSettings, ba
524
530
  logger.exception(f"Error during {backup_type} backup process, continuing on next backup definition")
525
531
  logger.error("Exception details:", exc_info=True)
526
532
 
527
- def generate_par2_files(backup_file: str, configSettings: ConfigSettings):
533
+ def generate_par2_files(backup_file: str, config_settings: ConfigSettings):
528
534
  """
529
535
  Generate PAR2 files for a given backup file in the specified backup directory.
530
536
 
@@ -538,13 +544,13 @@ def generate_par2_files(backup_file: str, configSettings: ConfigSettings):
538
544
  Returns:
539
545
  None
540
546
  """
541
- for filename in os.listdir(configSettings.backup_dir):
547
+ for filename in os.listdir(config_settings.backup_dir):
542
548
  if os.path.basename(backup_file) in filename:
543
549
  # Construct the full path to the file
544
- file_path = os.path.join(configSettings.backup_dir, filename)
550
+ file_path = os.path.join(config_settings.backup_dir, filename)
545
551
  # Run the par2 command to generate redundancy files with error correction
546
- command = ['par2', 'create', f'-r{configSettings.error_correction_percent}', '-q', '-q', file_path]
547
- process = run_command(command)
552
+ command = ['par2', 'create', f'-r{config_settings.error_correction_percent}', '-q', '-q', file_path]
553
+ process = run_command(command, config_settings.command_timeout_secs)
548
554
  if process.returncode != 0:
549
555
  logger.error(f"Error generating par2 files for {file_path}")
550
556
  raise subprocess.CalledProcessError(process.returncode, command)
@@ -631,6 +637,9 @@ def requirements(type: str, config_setting: ConfigSettings):
631
637
  result = subprocess.run(script, shell=True, check=True)
632
638
  logger.info(f"{type} {key}: '{script}' run, return code: {result.returncode}")
633
639
  logger.info(f"{type} stdout:\n{result.stdout}")
640
+ if result.returncode != 0:
641
+ logger.error(f"{type} stderr:\n{result.stderr}")
642
+ raise RuntimeError(f"{type} {key}: '{script}' failed, return code: {result.returncode}")
634
643
  except subprocess.CalledProcessError as e:
635
644
  logger.error(f"Error executing {key}: '{script}': {e}")
636
645
  if result:
@@ -658,8 +667,8 @@ def main():
658
667
  parser.add_argument('-l', '--list', action='store_true', help="List available archives.")
659
668
  parser.add_argument('--list-contents', help="List the contents of the specified archive.")
660
669
  parser.add_argument('--selection', help="dar file selection for listing/restoring specific files/directories.")
661
- parser.add_argument('-r', '--restore', help="Restore specified archive.")
662
- parser.add_argument('--restore-dir', help="Directory to restore files to.")
670
+ parser.add_argument('-r', '--restore', nargs=1, type=str, help="Restore specified archive.")
671
+ parser.add_argument('--restore-dir', nargs=1, type=str, help="Directory to restore files to.")
663
672
  parser.add_argument('--verbose', action='store_true', help="Print various status messages to screen")
664
673
  parser.add_argument('--log-level', type=str, help="`debug` or `trace`", default="info")
665
674
  parser.add_argument('--log-stdout', action='store_true', help='also print log messages to stdout')
@@ -739,7 +748,7 @@ def main():
739
748
  list_contents(args.list_contents, config_settings.backup_dir, args.selection)
740
749
  elif args.restore:
741
750
  restore_dir = args.restore_dir if args.restore_dir else config_settings.test_restore_dir
742
- restore_backup(args.restore, config_settings.backup_dir, restore_dir, args.selection)
751
+ restore_backup(args.restore, config_settings, restore_dir, args.selection)
743
752
  else:
744
753
  parser.print_help()
745
754
 
dar_backup/manager.py CHANGED
@@ -66,7 +66,7 @@ def create_db(backup_def: str, config_settings: ConfigSettings):
66
66
  logger.info(f'Database created: "{database_path}"')
67
67
  else:
68
68
  logger.error(f'Something went wrong creating the database: "{database_path}"')
69
- stdout, stderr = process.communicate()
69
+ stdout, stderr = process.stdout, process.stderr
70
70
  logger.error(f"stderr: {stderr}")
71
71
  logger.error(f"stdout: {stdout}")
72
72
 
@@ -80,7 +80,7 @@ def list_db(backup_def: str, config_settings: ConfigSettings):
80
80
  return 1
81
81
  command = ['dar_manager', '--base', database_path, '--list']
82
82
  process = run_command(command)
83
- stdout, stderr = process.communicate()
83
+ stdout, stderr = process.stdout, process.stderr
84
84
  if process.returncode != 0:
85
85
  logger.error(f'Error listing catalogs for: "{database_path}"')
86
86
  logger.error(f"stderr: {stderr}")
@@ -111,7 +111,7 @@ def add_specific_archive(archive: str, config_settings: ConfigSettings):
111
111
 
112
112
  command = ['dar_manager', '--base', database_path, "--add", archive_path, "-ai", "-Q"]
113
113
  process = run_command(command)
114
- stdout, stderr = process.communicate()
114
+ stdout, stderr = process.stdout, process.stderr
115
115
 
116
116
  if process.returncode == 0:
117
117
  logger.info(f'"{archive_path}" added to it\'s catalog')
dar_backup/util.py CHANGED
@@ -7,7 +7,7 @@ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW,
7
7
  not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
8
8
  See section 15 and section 16 in the supplied "LICENSE" file
9
9
  """
10
-
10
+ import typing
11
11
  import locale
12
12
  import logging
13
13
  import os
@@ -18,6 +18,8 @@ import sys
18
18
  import traceback
19
19
  from datetime import datetime
20
20
 
21
+ from typing import NamedTuple
22
+
21
23
  logger=None
22
24
 
23
25
  class BackupError(Exception):
@@ -37,7 +39,9 @@ class RestoreError(Exception):
37
39
  pass
38
40
 
39
41
 
40
- def setup_logging(log_file: str, log_level: str, log_to_stdout=False) -> logging.Logger:
42
+
43
+
44
+ def setup_logging(log_file: str, log_level: str, log_to_stdout: bool=False, logger_name: str=__name__) -> logging.Logger:
41
45
  """
42
46
  log_level can be set to "debug" or "trace" for more verbose logging.
43
47
  """
@@ -64,16 +68,21 @@ def setup_logging(log_file: str, log_level: str, log_to_stdout=False) -> logging
64
68
  level_used = TRACE_LEVEL_NUM
65
69
  logger.setLevel(TRACE_LEVEL_NUM)
66
70
 
71
+ formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
72
+
73
+ file_handler = logging.FileHandler(log_file)
74
+ file_handler.setLevel(level_used)
75
+ file_handler.setFormatter(formatter)
76
+ logger.addHandler(file_handler)
77
+
67
78
  if log_to_stdout:
68
- # Create a handler for the console
69
79
  stdout_handler = logging.StreamHandler(sys.stdout)
70
80
  stdout_handler.setLevel(level_used)
71
- formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
72
81
  stdout_handler.setFormatter(formatter)
73
82
  logger.addHandler(stdout_handler)
74
83
 
75
- logging.basicConfig(filename=log_file, level=level_used,
76
- format='%(asctime)s - %(levelname)s - %(message)s')
84
+ # logging.basicConfig(filename=log_file, level=level_used,
85
+ # format='%(asctime)s - %(levelname)s - %(message)s')
77
86
 
78
87
  except Exception as e:
79
88
  print("logging not initialized, exiting.")
@@ -83,30 +92,56 @@ def setup_logging(log_file: str, log_level: str, log_to_stdout=False) -> logging
83
92
  return logger
84
93
 
85
94
 
95
+ class CommandResult(NamedTuple):
96
+ """
97
+ The reult of the run_command() function.
98
+ """
99
+ process: subprocess.CompletedProcess
100
+ stdout: str
101
+ stderr: str
102
+ returncode: int
103
+ timeout: int
104
+ command: list[str]
86
105
 
87
106
 
88
- def run_command(command: list[str]) -> subprocess.CompletedProcess:
107
+ def run_command(command: list[str], timeout: int=30) -> typing.NamedTuple:
89
108
  """
90
109
  Executes a given command via subprocess and captures its output.
91
110
 
92
111
  Args:
93
112
  command (list): The command to be executed, represented as a list of strings.
113
+ timeout (int): The maximum time in seconds to wait for the command to complete.Defaults to 30 seconds.
94
114
 
95
115
  Returns:
96
- subprocess.CompletedProcess
116
+ a typing.NamedTuple of class dar-backup.util.CommandResult with the following properties:
117
+ - process: of type subprocess.CompletedProcess: The result of the command execution.
118
+ - stdout: of type str: The standard output of the command.
119
+ - stderr: of type str: The standard error of the command.
120
+ - returncode: of type int: The return code of the command.
121
+ - timeout: of type int: The timeout value in seconds used to run the command.
122
+ - command: of type list[str): The command executed.
97
123
 
98
124
  Raises:
125
+ subprocess.TimeoutExpired: if the command execution times out (see `timeout` parameter).
99
126
  Exception: raise exceptions during command runs.
100
127
  """
128
+
101
129
  try:
102
- logger.trace(f"Running command: {command}")
130
+ logger.info(f"Running command: {command}")
103
131
  process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
104
- process.wait() # Popen() and wait() used, for potential future use of stdout and stderr while a subprocess is running.
132
+ stdout, stderr = process.communicate(timeout=30) # Wait with timeout
133
+ except subprocess.TimeoutExpired:
134
+ process.terminate()
135
+ logger.error(f"Command: '{command}' timed out and was terminated.")
105
136
  except Exception as e:
106
137
  logger.error(f"Error running command: {command}", exc_info=True)
107
138
  raise
108
-
109
- return process
139
+ finally:
140
+ if not stdout:
141
+ stdout = None
142
+ if not stderr:
143
+ stderr = None
144
+ return CommandResult(process=process, stdout=stdout, stderr=stderr, returncode=process.returncode, timeout=timeout, command=command)
110
145
 
111
146
 
112
147
  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.5.17
3
+ Version: 0.6.0
4
4
  Summary: A script to do full, differential and incremental backups using dar. Some files are restored from the backups during verification, after which par2 redundancy files are created. The script also has a cleanup feature to remove old backups and par2 files.
5
5
  Project-URL: Homepage, https://github.com/per2jensen/dar-backup
6
6
  Project-URL: Issues, https://github.com/per2jensen/dar-backup/issues
@@ -44,8 +44,11 @@ Description-Content-Type: text/markdown
44
44
  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.
45
45
 
46
46
  # Status
47
- As of August 8, 2024 I am using the alpha versions of `dar-backup` (alpha-0.5.9 onwards) in my automated backup routine
47
+ As of August 8, 2024 I am using the alpha versions of `dar-backup` (alpha-0.5.9 onwards) in my automated backup routine.
48
48
 
49
+ **Breaking change in version 0.6.0**
50
+
51
+ Version 0.6.0 and forwards requires the config variable *COMMAND_TIMEOUT_SECS* in the config file.
49
52
 
50
53
  # Homepage - Github
51
54
  This 'dar-backup' package lives at: https://github.com/per2jensen/dar-backup
@@ -79,6 +82,11 @@ MAX_SIZE_VERIFICATION_MB = 20
79
82
  MIN_SIZE_VERIFICATION_MB = 1
80
83
  NO_FILES_VERIFICATION = 5
81
84
 
85
+ # timeout in seconds for backup, test, restore and par2 operations
86
+ # The author has such `dar` tasks running for 10-15 hours on the yearly backups, so a value of 24 hours is used.
87
+ # If a timeout is not specified when using the util.run_command(), a default timeout of 30 secs is used.
88
+ COMMAND_TIMEOUT_SECS = 86400
89
+
82
90
  [DIRECTORIES]
83
91
  BACKUP_DIR = /home/user/mnt/dir/
84
92
  BACKUP.D_DIR = /home/user/.config/dar-backup/backup.d/
@@ -181,7 +189,7 @@ alias db=". ~/tmp/venv/bin/activate; dar-backup -v"
181
189
  Typing `db` at the command line gives this
182
190
  ````
183
191
  (venv) user@machine:~$ db
184
- dar-backup alpha-0.5.12
192
+ dar-backup 0.5.17
185
193
  dar-backup.py source code is here: https://github.com/per2jensen/dar-backup
186
194
  Licensed under GNU GENERAL PUBLIC LICENSE v3, see the supplied file "LICENSE" for details.
187
195
  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW, not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
@@ -190,10 +198,11 @@ See section 15 and section 16 in the supplied "LICENSE" file.
190
198
 
191
199
  `dar-backup -h` gives the usage output:
192
200
  ````
193
- (venv) user@machine:~$ dar-backup -h
194
-
195
- usage: dar-backup [-h] [-F] [-D] [-I] [-d BACKUP_DEFINITION] [-c CONFIG_FILE] [--darrc DARRC] [--examples] [-l] [--list-contents LIST_CONTENTS]
196
- [--selection SELECTION] [-r RESTORE] [--restore-dir RESTORE_DIR] [--verbose] [--log-level LOG_LEVEL] [--do-not-compare] [-v]
201
+ usage: dar-backup [-h] [-F] [-D] [-I] [-d BACKUP_DEFINITION]
202
+ [--alternate-reference-archive ALTERNATE_REFERENCE_ARCHIVE] [-c CONFIG_FILE] [--darrc DARRC]
203
+ [--examples] [-l] [--list-contents LIST_CONTENTS] [--selection SELECTION] [-r RESTORE]
204
+ [--restore-dir RESTORE_DIR] [--verbose] [--log-level LOG_LEVEL] [--log-stdout]
205
+ [--do-not-compare] [-v]
197
206
 
198
207
  Backup and verify using dar backup definitions.
199
208
 
@@ -206,6 +215,8 @@ options:
206
215
  Perform incremental backup.
207
216
  -d BACKUP_DEFINITION, --backup-definition BACKUP_DEFINITION
208
217
  Specific 'recipe' to select directories and files.
218
+ --alternate-reference-archive ALTERNATE_REFERENCE_ARCHIVE
219
+ DIFF or INCR compared to specified archive.
209
220
  -c CONFIG_FILE, --config-file CONFIG_FILE
210
221
  Path to 'dar-backup.conf'
211
222
  --darrc DARRC Optional path to .darrc
@@ -222,8 +233,10 @@ options:
222
233
  --verbose Print various status messages to screen
223
234
  --log-level LOG_LEVEL
224
235
  `debug` or `trace`
236
+ --log-stdout also print log messages to stdout
225
237
  --do-not-compare do not compare restores to file system
226
- -v, --version Show version information.
238
+ -v, --version Show version and license information.
239
+
227
240
  ````
228
241
 
229
242
  ## 4
@@ -424,7 +437,7 @@ WantedBy=timers.target
424
437
  # list contents of an archive
425
438
  ```
426
439
  . <the virtual evn>/bin/activate
427
- dar-backup --list-contents example --selection "-X '*.xmp' -I '*2024-06-16*' -g home/pj/tmp/LUT-play"
440
+ dar-backup --list-contents example_FULL_2024-06-23 --selection "-X '*.xmp' -I '*2024-06-16*' -g home/pj/tmp/LUT-play"
428
441
  deactivate
429
442
  ```
430
443
  gives
@@ -533,18 +546,43 @@ Nice :-)
533
546
 
534
547
  # Restoring
535
548
 
549
+ ## default location for restores
550
+ dar-backup will use the TEST_RESTORE_DIR location as the Root for restores, if the --restore-dir option has not been supplied.
551
+
552
+ See example below to see where files are restored to.
553
+
554
+ ## --restore-dir option
555
+ When the --restore-dir option is used for restoring, a directory must be supplied.
556
+
557
+ The directory supplied functions as the Root of the restore operation.
558
+
559
+ **Example**:
560
+
561
+ A backup has been taken using this backup definition:
562
+ ```
563
+ -R /
564
+ -g home/user/Documents
565
+ ```
566
+
567
+ When restoring and using `/tmp` for --restore-dir, the restored files can be found in `/tmp/home/user/Documents`
568
+
536
569
  ## a single file
537
570
  ```
538
571
  . <the virtual env>/bin/activate
539
- # the path/to/file is relative to the Root when the backup was taken
540
572
  dar-backup --restore <archive_name> --selection "-g path/to/file"
541
573
  deactivate
542
574
  ```
575
+ ## a directory
576
+ ```
577
+ . <the virtual env>/bin/activate
578
+ dar-backup --restore <archive_name> --selection "-g path/to/directory"
579
+ deactivate
580
+ ```
581
+
543
582
 
544
583
  ## .NEF from a specific date
545
584
  ```
546
585
  . <the virtual env>/bin/activate
547
- # the path/to/file is relative to the Root when the backup was taken
548
586
  dar-backup --restore <archive_name> --selection "-X '*.xmp' -I '*2024-06-16*' -g home/pj/tmp/LUT-play"
549
587
  deactivate
550
588
  ```
@@ -0,0 +1,13 @@
1
+ dar_backup/.darrc,sha256=ggex9N6eETOS6u003_QRRJMeWbveQfkT1lDBt0XpU-I,2112
2
+ dar_backup/__about__.py,sha256=DS49q_bFynltwBtgneHYeVXTHLg5bjAOFWvTkv-jYmY,21
3
+ dar_backup/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ dar_backup/cleanup.py,sha256=DgmxSUwKrLLIQuYSIY_yTRhIuOMgI6ivjlQuH4u3wX4,9057
5
+ dar_backup/config_settings.py,sha256=CBMUhLOOZ-x7CRdS3vBDk4TYaGqC4N1Ot8IMH-qPaI0,3617
6
+ dar_backup/dar_backup.py,sha256=Kz_Gwe9t6vHGk590D3pbqDe1ptyrhLNpQWCU8oDcqos,37870
7
+ dar_backup/manager.py,sha256=lkw1ZAIdxY7WedLPKZMnHpuq_QbjkUdcG61ooiwUYpo,10197
8
+ dar_backup/util.py,sha256=ZYalRptXvSI9laLmHOe67WOOHoHF0CS4osE9NP1MDWA,8892
9
+ dar_backup-0.6.0.dist-info/METADATA,sha256=-D3-rzNmkAMpN5SpMSeoveXuGQ1x8EncsdBxs0Lfx-s,22496
10
+ dar_backup-0.6.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
11
+ dar_backup-0.6.0.dist-info/entry_points.txt,sha256=x9vnW-JEl8mpDJC69f_XBcn0mBSkV1U0cyvFV-NAP1g,126
12
+ dar_backup-0.6.0.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
13
+ dar_backup-0.6.0.dist-info/RECORD,,
@@ -1,13 +0,0 @@
1
- dar_backup/.darrc,sha256=ggex9N6eETOS6u003_QRRJMeWbveQfkT1lDBt0XpU-I,2112
2
- dar_backup/__about__.py,sha256=JiHkNWaQtd0jZh8vy9fMhajaON_1Y2QqWuee4eqXxd4,22
3
- dar_backup/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- dar_backup/cleanup.py,sha256=DgmxSUwKrLLIQuYSIY_yTRhIuOMgI6ivjlQuH4u3wX4,9057
5
- dar_backup/config_settings.py,sha256=-PhSj0Y9lVF6YaVkkG17XT-qmnFFt-XQXBdzxhIFNkc,3455
6
- dar_backup/dar_backup.py,sha256=NKLBLA7Ma6sU72zqsRvFa0ckrrJ-iJJ_2muxtxSjTNs,36903
7
- dar_backup/manager.py,sha256=0SZbk9rekPRWwJgQwTO2Xj57v3G11kQ4i4hCSb0OZEQ,10168
8
- dar_backup/util.py,sha256=a460ISsVwc93cSuUlInCMjNNhK5D48zlopzpysver9I,7416
9
- dar_backup-0.5.17.dist-info/METADATA,sha256=JZxdM7vyX6Zn_H85hasxHaEvdZxRIE8LpLSvKHTYWPs,21132
10
- dar_backup-0.5.17.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
11
- dar_backup-0.5.17.dist-info/entry_points.txt,sha256=x9vnW-JEl8mpDJC69f_XBcn0mBSkV1U0cyvFV-NAP1g,126
12
- dar_backup-0.5.17.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
13
- dar_backup-0.5.17.dist-info/RECORD,,