dar-backup 0.6.21__py3-none-any.whl → 0.7.2__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,4 +1,4 @@
1
- __version__ = "0.6.21"
1
+ __version__ = "0.7.2"
2
2
 
3
3
  __license__ = '''Licensed under GNU GENERAL PUBLIC LICENSE v3, see the supplied file "LICENSE" for details.
4
4
  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW, not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
dar_backup/clean_log.py CHANGED
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env python3
2
+ # SPDX-License-Identifier: GPL-3.0-or-later
3
+
2
4
  """
3
5
  clean-log.py source code is here: https://github.com/per2jensen/dar-backup/tree/main/v2/src/dar_backup/clean-log.py
4
6
  This script is part of dar-backup, a backup solution for Linux using dar and systemd.
@@ -135,6 +137,7 @@ def main():
135
137
  args.file = [config_settings.logfile_location]
136
138
 
137
139
  for file_path in args.file:
140
+
138
141
  if not isinstance(file_path, (str, bytes, os.PathLike)):
139
142
  print(f"Error: Invalid file path type: {file_path}")
140
143
  sys.exit(1)
dar_backup/cleanup.py CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env python3
2
+ # SPDX-License-Identifier: GPL-3.0-or-later
2
3
 
3
4
  """
4
5
  cleanup.py source code is here: https://github.com/per2jensen/dar-backup
@@ -40,6 +41,8 @@ from dar_backup.util import show_version
40
41
  from dar_backup.util import get_invocation_command_line
41
42
  from dar_backup.util import print_aligned_settings
42
43
  from dar_backup.util import backup_definition_completer, list_archive_completer
44
+ from dar_backup.util import is_safe_filename
45
+ from dar_backup.util import show_scriptname
43
46
 
44
47
  from dar_backup.command_runner import CommandRunner
45
48
  from dar_backup.command_runner import CommandResult
@@ -79,7 +82,7 @@ def delete_old_backups(backup_dir, age, backup_type, args, backup_definition=Non
79
82
  if file_date < cutoff_date:
80
83
  file_path = os.path.join(backup_dir, filename)
81
84
  try:
82
- os.remove(file_path)
85
+ is_safe_filename(file_path) and os.remove(file_path)
83
86
  logger.info(f"Deleted {backup_type} backup: {file_path}")
84
87
  archive_name = filename.split('.')[0]
85
88
  if not archive_name in archives_deleted:
@@ -108,7 +111,7 @@ def delete_archive(backup_dir, archive_name, args):
108
111
  if archive_regex.match(filename):
109
112
  file_path = os.path.join(backup_dir, filename)
110
113
  try:
111
- os.remove(file_path)
114
+ is_safe_filename(file_path) and os.remove(file_path)
112
115
  logger.info(f"Deleted archive slice: {file_path}")
113
116
  files_deleted = True
114
117
  except Exception as e:
@@ -126,7 +129,7 @@ def delete_archive(backup_dir, archive_name, args):
126
129
  if par2_regex.match(filename):
127
130
  file_path = os.path.join(backup_dir, filename)
128
131
  try:
129
- os.remove(file_path)
132
+ is_safe_filename(file_path) and os.remove(file_path)
130
133
  logger.info(f"Deleted PAR2 file: {file_path}")
131
134
  files_deleted = True
132
135
  except Exception as e:
@@ -214,13 +217,13 @@ def main():
214
217
 
215
218
  # command_output_log = os.path.join(config_settings.logfile_location.removesuffix("dar-backup.log"), "dar-backup-commands.log")
216
219
  command_output_log = config_settings.logfile_location.replace("dar-backup.log", "dar-backup-commands.log")
217
- logger = setup_logging(config_settings.logfile_location, command_output_log, args.log_level, args.log_stdout)
220
+ logger = setup_logging(config_settings.logfile_location, command_output_log, args.log_level, args.log_stdout, logfile_max_bytes=config_settings.logfile_max_bytes, logfile_backup_count=config_settings.logfile_backup_count)
218
221
  command_logger = get_logger(command_output_logger = True)
219
222
  runner = CommandRunner(logger=logger, command_logger=command_logger)
220
223
 
221
224
  start_msgs: List[Tuple[str, str]] = []
222
225
 
223
- start_msgs.append(("cleanup.py:", about.__version__))
226
+ start_msgs.append((f"{show_scriptname()}:", about.__version__))
224
227
 
225
228
  logger.info(f"START TIME: {start_time}")
226
229
  logger.debug(f"Command line: {get_invocation_command_line()}")
@@ -232,11 +235,13 @@ def main():
232
235
  start_msgs.append(("Config file:", args.config_file))
233
236
  args.verbose and start_msgs.append(("Backup dir:", config_settings.backup_dir))
234
237
  start_msgs.append(("Logfile:", config_settings.logfile_location))
238
+ args.verbose and start_msgs.append(("Logfile max size (bytes):", config_settings.logfile_max_bytes))
239
+ args.verbose and start_msgs.append(("Logfile backup count:", config_settings.logfile_backup_count))
235
240
  args.verbose and start_msgs.append(("--alternate-archive-dir:", args.alternate_archive_dir))
236
241
  args.verbose and start_msgs.append(("--cleanup-specific-archives:", args.cleanup_specific_archives))
237
242
 
238
243
  dangerous_keywords = ["--cleanup", "_FULL_"] # TODO: add more dangerous keywords
239
- print_aligned_settings(start_msgs, highlight_keywords=dangerous_keywords)
244
+ print_aligned_settings(start_msgs, highlight_keywords=dangerous_keywords, quiet=not args.verbose)
240
245
 
241
246
  # run PREREQ scripts
242
247
  requirements('PREREQ', config_settings)
@@ -260,7 +265,8 @@ def main():
260
265
  if "_FULL_" in archive_name:
261
266
  if not confirm_full_archive_deletion(archive_name, args.test_mode):
262
267
  continue
263
- logger.info(f"Deleting archive: {archive_name}")
268
+ archive_path = os.path.join(config_settings.backup_dir, archive_name.strip())
269
+ logger.info(f"Deleting archive: {archive_path}")
264
270
  delete_archive(config_settings.backup_dir, archive_name.strip(), args)
265
271
  elif args.list:
266
272
  list_backups(config_settings.backup_dir, args.backup_definition)
@@ -1,3 +1,5 @@
1
+ # SPDX-License-Identifier: GPL-3.0-or-later
2
+
1
3
  import subprocess
2
4
  import logging
3
5
  import threading
@@ -1,3 +1,5 @@
1
+ # SPDX-License-Identifier: GPL-3.0-or-later
2
+
1
3
  import configparser
2
4
  from dataclasses import dataclass, field, fields
3
5
  from os.path import expandvars, expanduser
@@ -42,6 +44,8 @@ class ConfigSettings:
42
44
  incr_age: int = field(init=False)
43
45
  error_correction_percent: int = field(init=False)
44
46
  par2_enabled: bool = field(init=False)
47
+ logfile_max_bytes: int = field(init=False)
48
+ logfile_no_count: int = field(init=False)
45
49
 
46
50
 
47
51
  OPTIONAL_CONFIG_FIELDS = [
@@ -52,6 +56,20 @@ class ConfigSettings:
52
56
  "type": str,
53
57
  "default": None,
54
58
  },
59
+ {
60
+ "section": "MISC",
61
+ "key": "LOGFILE_MAX_BYTES",
62
+ "attr": "logfile_max_bytes",
63
+ "type": int,
64
+ "default": 26214400 , # 25 MB
65
+ },
66
+ {
67
+ "section": "MISC",
68
+ "key": "LOGFILE_BACKUP_COUNT",
69
+ "attr": "logfile_backup_count",
70
+ "type": int,
71
+ "default": 5,
72
+ },
55
73
  # Add more optional fields here
56
74
  ]
57
75
 
@@ -1,6 +1,8 @@
1
+ # SPDX-License-Identifier: GPL-3.0-or-later
2
+
1
3
  # This config file is intended to demo `dar-backup`.
2
4
  #
3
- # The `installer` puts it in ~/.config/dar-backup/dar-backup.conf
5
+ # The `demo` application puts this file in ~/.config/dar-backup/dar-backup.conf
4
6
 
5
7
  [MISC]
6
8
  LOGFILE_LOCATION = ~/dar-backup/dar-backup.log
@@ -13,7 +15,7 @@ NO_FILES_VERIFICATION = 5
13
15
  COMMAND_TIMEOUT_SECS = 86400
14
16
 
15
17
  [DIRECTORIES]
16
- BACKUP_DIR = ~/dar-backup/backups
18
+ BACKUP_DIR = @@BACKUP_DIR@@
17
19
  BACKUP.D_DIR = ~/.config/dar-backup/backup.d/
18
20
  TEST_RESTORE_DIR = ~/dar-backup/restore/
19
21
  # Optional parameter
@@ -0,0 +1,64 @@
1
+
2
+ # SPDX-License-Identifier: GPL-3.0-or-later
3
+
4
+ # ------------------------------------------------------------------------
5
+ # Demo of a `dar-backup` configuration file
6
+ # This file was generated by dar-backup's `demo` program.
7
+ #
8
+ {%- if opts_dict | length > 0 %}
9
+ # Options given to the `demo` program:
10
+ {% endif %}
11
+ {%- if opts_dict.ROOT_DIR -%}
12
+ # --root-dir : {{ opts_dict.ROOT_DIR }}
13
+ {% endif %}
14
+ {%- if opts_dict.DIR_TO_BACKUP -%}
15
+ # --dir-to-backup : {{ opts_dict.DIR_TO_BACKUP }}
16
+ {% endif -%}
17
+ {%- if opts_dict.BACKUP_DIR -%}
18
+ # --backup-dir : {{ opts_dict.BACKUP_DIR }}
19
+ {% endif %}
20
+ #
21
+ # Variables used to generate this file:
22
+ # =====================================
23
+ {% for k,v in vars_map|dictsort %}# {{ k }} : {{ v }}
24
+ {% endfor -%}
25
+ # ------------------------------------------------------------------------
26
+
27
+ [MISC]
28
+ LOGFILE_LOCATION = {{ vars_map.DAR_BACKUP_DIR -}}/dar-backup.log
29
+ # optional parameters
30
+ # LOGFILE_MAX_BYTES = 26214400 # 25 MB default, change as neeeded
31
+ # LOGFILE_BACKUP_COUNT = 5 # default, change as needed
32
+
33
+ MAX_SIZE_VERIFICATION_MB = 2
34
+ MIN_SIZE_VERIFICATION_MB = 0
35
+ NO_FILES_VERIFICATION = 1
36
+ # timeout in seconds for backup, test, restore and par2 operations
37
+ # The author has such `dar` tasks running for 10-15 hours on the yearly backups, so a value of 24 hours is used.
38
+ # If a timeout is not specified when using the CommandRunner, a default timeout of 30 secs is used.
39
+ COMMAND_TIMEOUT_SECS = 86400
40
+
41
+ [DIRECTORIES]
42
+ BACKUP_DIR = {{ vars_map.BACKUP_DIR }}
43
+ BACKUP.D_DIR = {{ vars_map.BACKUP_D_DIR }}
44
+ TEST_RESTORE_DIR = {{ vars_map.TEST_RESTORE_DIR }}
45
+ # Optional parameter
46
+ # If you want to store the catalog database away from the BACKUP_DIR, use the MANAGER_DB_DIR variable.
47
+ #MANAGER_DB_DIR = /some/where/else/
48
+
49
+ [AGE]
50
+ # DIFF and INCR backups are kept for a configured number of days, then deleted by the `cleanuo`
51
+ # age settings are in days
52
+ DIFF_AGE = 100
53
+ INCR_AGE = 40
54
+
55
+ [PAR2]
56
+ ERROR_CORRECTION_PERCENT = 5
57
+ ENABLED = True
58
+
59
+ [PREREQ]
60
+ #SCRIPT_1 = <pre-script 1>
61
+
62
+ [POSTREQ]
63
+ #SCRIPT_1 = <post-script 1>
64
+
dar_backup/dar_backup.py CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env python3
2
-
2
+ # SPDX-License-Identifier: GPL-3.0-or-later
3
3
  """
4
4
  installer.py source code is here: https://github.com/per2jensen/dar-backup/tree/main/v2/src/dar_backup/installer.py
5
5
  This script is part of dar-backup, a backup solution for Linux using dar and systemd.
@@ -55,6 +55,8 @@ from dar_backup.util import get_invocation_command_line
55
55
  from dar_backup.util import get_binary_info
56
56
  from dar_backup.util import print_aligned_settings
57
57
  from dar_backup.util import backup_definition_completer, list_archive_completer
58
+ from dar_backup.util import show_scriptname
59
+ from dar_backup.util import print_debug
58
60
 
59
61
  from dar_backup.command_runner import CommandRunner
60
62
  from dar_backup.command_runner import CommandResult
@@ -92,6 +94,7 @@ def generic_backup(type: str, command: List[str], backup_file: str, backup_defin
92
94
  Returns:
93
95
  List of tuples (<msg>, <exit_code>) of errors not considered critical enough for raising an exception
94
96
  """
97
+
95
98
  result: List[tuple] = []
96
99
 
97
100
  logger.info(f"===> Starting {type} backup for {backup_definition}")
@@ -105,7 +108,6 @@ def generic_backup(type: str, command: List[str], backup_file: str, backup_defin
105
108
  stop_event = Event()
106
109
  session_marker = f"=== START BACKUP SESSION: {int(time())} ==="
107
110
  get_logger(command_output_logger=True).info(session_marker)
108
-
109
111
  progress_thread = threading.Thread(
110
112
  target=show_log_driven_bar,
111
113
  args=(log_path, stop_event, session_marker),
@@ -534,12 +536,12 @@ def perform_backup(args: argparse.Namespace, config_settings: ConfigSettings, ba
534
536
 
535
537
  # Perform backup
536
538
  backup_result = generic_backup(backup_type, command, backup_file, backup_definition_path, args.darrc, config_settings, args)
539
+
537
540
  if not isinstance(backup_result, list) or not all(isinstance(i, tuple) and len(i) == 2 for i in backup_result):
538
541
  logger.error("Unexpected return format from generic_backup")
539
542
  backup_result = [("Unexpected return format from generic_backup", 1)]
540
543
 
541
544
  results.extend(backup_result)
542
-
543
545
  logger.info("Starting verification...")
544
546
  verify_result = verify(args, backup_file, backup_definition_path, config_settings)
545
547
  if verify_result:
@@ -838,7 +840,7 @@ def main():
838
840
  if command_output_log == config_settings.logfile_location:
839
841
  print(f"Error: logfile_location in {args.config_file} does not end at 'dar-backup.log', exiting", file=stderr)
840
842
 
841
- logger = setup_logging(config_settings.logfile_location, command_output_log, args.log_level, args.log_stdout)
843
+ logger = setup_logging(config_settings.logfile_location, command_output_log, args.log_level, args.log_stdout, logfile_max_bytes=config_settings.logfile_max_bytes, logfile_backup_count=config_settings.logfile_backup_count)
842
844
  command_logger = get_logger(command_output_logger = True)
843
845
  runner = CommandRunner(logger=logger, command_logger=command_logger)
844
846
 
@@ -863,11 +865,11 @@ def main():
863
865
  start_msgs: List[Tuple[str, str]] = []
864
866
 
865
867
  start_time=int(time())
866
- start_msgs.append(('dar-backup.py:', about.__version__))
868
+ start_msgs.append((f"{show_scriptname()}:", about.__version__))
867
869
  logger.info(f"START TIME: {start_time}")
868
- logger.debug(f"Command line: {get_invocation_command_line()}")
869
- logger.debug(f"{'`Args`:\n'}{args}")
870
- logger.debug(f"{'`Config_settings`:\n'}{config_settings}")
870
+ logger.debug(f"Command line:\n{get_invocation_command_line()}")
871
+ logger.debug(f"`Args`:\n{args}")
872
+ logger.debug(f"`Config_settings`:\n{config_settings}")
871
873
  dar_properties = get_binary_info(command='dar')
872
874
  start_msgs.append(('dar path:', dar_properties['path']))
873
875
  start_msgs.append(('dar version:', dar_properties['version']))
@@ -877,9 +879,9 @@ def main():
877
879
  start_msgs.append(('Config file:', os.path.abspath(args.config_file)))
878
880
  start_msgs.append((".darrc location:", args.darrc))
879
881
 
880
- args.verbose and args.full_backup and start_msgs.append(("Type of backup:", "FULL"))
881
- args.verbose and args.differential_backup and start_msgs.append(("Type of backup:", "DIFF"))
882
- args.verbose and args.incremental_backup and start_msgs.append(("Type of backup:", "INCR"))
882
+ args.full_backup and start_msgs.append(("Type of backup:", "FULL"))
883
+ args.differential_backup and start_msgs.append(("Type of backup:", "DIFF"))
884
+ args.incremental_backup and start_msgs.append(("Type of backup:", "INCR"))
883
885
  args.verbose and args.backup_definition and start_msgs.append(("Backup definition:", args.backup_definition))
884
886
  if args.alternate_reference_archive:
885
887
  args.verbose and start_msgs.append(("Alternate ref archive:", args.alternate_reference_archive))
@@ -890,11 +892,14 @@ def main():
890
892
  args.verbose and start_msgs.append(("Restore dir:", restore_dir))
891
893
 
892
894
  args.verbose and start_msgs.append(("Logfile location:", config_settings.logfile_location))
895
+ args.verbose and start_msgs.append(("Logfile max size (bytes):", config_settings.logfile_max_bytes))
896
+ args.verbose and start_msgs.append(("Logfile backup count:", config_settings.logfile_backup_count))
897
+
893
898
  args.verbose and start_msgs.append(("PAR2 enabled:", config_settings.par2_enabled))
894
899
  args.verbose and start_msgs.append(("--do-not-compare:", args.do_not_compare))
895
900
 
896
- dangerous_keywords = ["--do-not", "alternate"] # TODO: add more dangerous keywords
897
- print_aligned_settings(start_msgs)
901
+ highlight_keywords = ["--do-not", "alternate"] # TODO: add more dangerous keywords
902
+ print_aligned_settings(start_msgs, quiet=not args.verbose, highlight_keywords=highlight_keywords)
898
903
 
899
904
  # sanity check
900
905
  if args.backup_definition and not os.path.exists(os.path.join(config_settings.backup_d_dir, args.backup_definition)):
@@ -928,6 +933,7 @@ def main():
928
933
 
929
934
  requirements('POSTREQ', config_settings)
930
935
 
936
+
931
937
  except Exception as e:
932
938
  logger.error("Exception details:", exc_info=True)
933
939
  results.append((repr(e), 1))
@@ -1,3 +1,6 @@
1
+ #!/usr/bin/env python3
2
+ # SPDX-License-Identifier: GPL-3.0-or-later
3
+
1
4
  from pathlib import Path
2
5
  import subprocess
3
6
  import argparse
dar_backup/demo.py CHANGED
@@ -1,6 +1,8 @@
1
1
  #!/usr/bin/env python3
2
+ # SPDX-License-Identifier: GPL-3.0-or-later
3
+
2
4
  """
3
- installer.py source code is here: https://github.com/per2jensen/dar-backup/tree/main/v2/src/dar_backup/installer.py
5
+ demo.py source code is here: https://github.com/per2jensen/dar-backup/tree/main/v2/src/dar_backup/demo.py
4
6
  This script is part of dar-backup, a backup solution for Linux using dar and systemd.
5
7
 
6
8
  Licensed under GNU GENERAL PUBLIC LICENSE v3, see the supplied file "LICENSE" for details.
@@ -10,7 +12,9 @@ not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10
12
  See section 15 and section 16 in the supplied "LICENSE" file
11
13
 
12
14
  This script can be used to configure dar-backup on your system.
13
- It is non-destructive and will not overwrite any existing files or directories.
15
+ It is non-destructive and will not overwrite any existing files or directories under --override is used.
16
+
17
+ User can set ROOT_DIR, DIR_TO_BACKUP and BACKUP_DIR (destination for backups) via optins to override defaults.
14
18
  """
15
19
 
16
20
  import argparse
@@ -19,117 +23,189 @@ import shutil
19
23
  import sys
20
24
 
21
25
  from . import __about__ as about
26
+ from . import util
22
27
 
28
+ from jinja2 import Environment, FileSystemLoader
23
29
  from pathlib import Path
30
+ from typing import Dict, Tuple
31
+
32
+
33
+ CONFIG_DIR = util.normalize_dir(util.expand_path("~/.config/dar-backup"))
34
+ DAR_BACKUP_DIR = util.normalize_dir(util.expand_path("~/dar-backup"))
35
+
36
+
24
37
 
25
- CONFIG_DIR = os.path.expanduser("~/.config/dar-backup")
26
- DAR_BACKUP_DIR = os.path.expanduser("~/dar-backup/")
38
+ def check_directories(args, vars_map: Dict[str,str]) -> bool:
39
+ """
40
+ Check if the directories exist and create them if they don't.
27
41
 
28
- BACKUP_DEFINITION = '''
29
- # Demo of a `dar-backup` definition file
30
- # This back definition file configures a backup of ~/.config/dar-backup
31
- # `dar-backup` puts the backups in ~/dar-backup/backups
32
- # ------------------------------------------------------------------------
42
+ Returns:
43
+ bool: True if the directories were created successfully, False otherwise.
44
+ """
45
+ result = True
46
+ for key in ("DAR_BACKUP_DIR","BACKUP_DIR","TEST_RESTORE_DIR","CONFIG_DIR","BACKUP_D_DIR"):
47
+ path = Path(vars_map[key])
48
+ if path.exists() and not args.override:
49
+ print(f"Directory '{path}' already exists")
50
+ result = False
51
+ return result
33
52
 
34
- # Switch to ordered selection mode, which means that the following options
35
- # will be considered top to bottom
36
- -am
37
53
 
38
- # Backup Root dir
39
- -R @@HOME_DIR@@
54
+ def generate_file(args, template: str, file_path: Path, vars_map: Dict[str, str], opts_dict: Dict[str, str]) -> bool:
55
+ """
56
+ Generate a file using a Jinja2 template.
40
57
 
41
- # Directories to backup below the Root dir
42
- -g .config/dar-backup
58
+ Args:
59
+ args: Command line arguments.
60
+ template (str): The name of the template file.
61
+ file_path (Path): The path where the generated file will be saved.
62
+ vars_map (Dict[str, str]): A dictionary containing variables for the template.
63
+ opts_dict (Dict[str, str]): A dictionary containing options given by user.
43
64
 
44
- # Examples of directories to exclude below the Root dir
45
- -P mnt
46
- -P .private
47
- -P .cache
65
+ Returns:
66
+ bool: True if the file was generated successfully, False otherwise.
67
+ """
68
+ current_script_dir = os.path.dirname(os.path.abspath(__file__))
69
+ env = Environment(loader=FileSystemLoader(current_script_dir))
70
+ tpl = env.get_template(template)
71
+ rendered = tpl.render(vars_map = vars_map, opts_dict = opts_dict)
72
+ if rendered is None:
73
+ print(f"Error: Template '{template}' could not be rendered.")
74
+ return False
75
+ if os.path.exists(file_path) and not args.override:
76
+ print(f"Error: File '{file_path}' already exists. Use --override to overwrite.")
77
+ return False
78
+ file_path.parent.mkdir(parents=True, exist_ok=True)
79
+ file_path.write_text(rendered)
80
+ print(f"File generated at '{file_path}'")
48
81
 
49
- # compression level
50
- -z5
51
82
 
52
- # no overwrite, if you rerun a backup, 'dar' halts and asks what to do
53
- -n
54
-
55
- # size of each slice in the archive
56
- --slice 10G
57
83
 
58
- # bypass directores marked as cache directories
59
- # http://dar.linux.free.fr/doc/Features.html
60
- --cache-directory-tagging
61
- '''
84
+ def setup_dicts(args, vars_map: Dict[str, str]) -> Tuple[Dict[str, str], Dict[str, str]]:
85
+ """
86
+ Override various entries in the dictionaries for jinja templating with user input.
87
+
88
+ Returns:
89
+ Tuple[Dict[str, str], Dict[str, str]]: A tuple containing the vars_map and opts_dict dictionaries.
90
+ """
91
+ opts_dict = {}
92
+ if args.root_dir:
93
+ opts_dict["ROOT_DIR"] = args.root_dir
94
+ if args.dir_to_backup:
95
+ opts_dict["DIR_TO_BACKUP"] = args.dir_to_backup
96
+ if args.backup_dir:
97
+ opts_dict["BACKUP_DIR"] = args.backup_dir
98
+
99
+ for key, value in opts_dict.items():
100
+ vars_map[key] = value
101
+
102
+ return vars_map, opts_dict
62
103
 
63
104
 
64
105
  def main():
65
106
  parser = argparse.ArgumentParser(
66
- description="Set up `dar-backup` on your system.",
107
+ description="Set up demo configuration for `dar-backup` on your system.",
67
108
  )
68
109
  parser.add_argument(
69
110
  "-i", "--install",
70
111
  action="store_true",
71
- help="Deploy a simple config file, use ~/dar-backup/ for log file, archives and restore tests."
112
+ help="Deploy demo config files and directories. Will not overwrite existing files or directories unless --override is used."
113
+ )
114
+ req = parser.add_argument_group(
115
+ 'These options must be used together'
116
+ )
117
+ req.add_argument(
118
+ "--root-dir",
119
+ type=str,
120
+ help="Specify the root directory for the backup."
121
+ )
122
+ req.add_argument(
123
+ "--dir-to-backup",
124
+ type=str,
125
+ help="Directory to backup, relative to the root directory."
126
+ )
127
+ parser.add_argument(
128
+ "--backup-dir",
129
+ type=str,
130
+ help="Directory where backups and redundancy files are put"
131
+ )
132
+ parser.add_argument(
133
+ "--override",
134
+ action="store_true",
135
+ help="By default, the script will not overwrite existing files or directories. Use this option to override this behavior."
72
136
  )
73
137
  parser.add_argument(
74
138
  "-v", "--version",
75
139
  action="version",
76
140
  version=f"%(prog)s version {about.__version__}, {about.__license__}"
77
141
  )
142
+ parser.add_argument(
143
+ "-g", "--generate",
144
+ action="store_true",
145
+ help="Generate config files and put them in /tmp/."
146
+ )
78
147
 
79
148
  args = parser.parse_args()
80
149
 
81
- if args.install:
82
- errors = []
83
- if os.path.exists(CONFIG_DIR):
84
- errors.append(f"Config directory '{CONFIG_DIR}' already exists.")
85
- if os.path.exists(DAR_BACKUP_DIR):
86
- errors.append(f"Directory '{DAR_BACKUP_DIR}' already exists.")
87
-
88
- if errors:
89
- for error in errors:
90
- print(f"Error: {error}")
150
+ group = [args.root_dir, args.dir_to_backup]
151
+ if any(group) and not all(group):
152
+ parser.error(
153
+ "Options --root-dir, --dir-to-backup must all be specified together."
154
+ )
155
+ exit(1)
156
+
157
+ args.root_dir = util.normalize_dir(util.expand_path(args.root_dir)) if args.root_dir else None
158
+ args.backup_dir = util.normalize_dir(util.expand_path(args.backup_dir)) if args.backup_dir else None
159
+ args.dir_to_backup = util.normalize_dir(util.expand_path(args.dir_to_backup)) if args.dir_to_backup else None
160
+
161
+
162
+
163
+ vars_map = {
164
+ # dar-backup.conf variables
165
+ "CONFIG_DIR" : CONFIG_DIR,
166
+ "DAR_BACKUP_DIR" : DAR_BACKUP_DIR,
167
+ "BACKUP_DIR" : os.path.join(DAR_BACKUP_DIR, "backups"),
168
+ "BACKUP_D_DIR" : os.path.join(CONFIG_DIR, "backup.d"),
169
+ "TEST_RESTORE_DIR" : os.path.join(DAR_BACKUP_DIR, "restore"),
170
+ # backup definition variables
171
+ "ROOT_DIR" : util.normalize_dir(util.expand_path("$HOME")),
172
+ "DIR_TO_BACKUP" : ".config/dar-backup",
173
+ }
174
+
175
+
176
+ vars_map, opts_dict = setup_dicts(args, vars_map)
177
+
178
+ if args.generate:
179
+ print("Generating backup definition file...")
180
+ vars_map["DAR_BACKUP_DIR"] = "/tmp"
181
+ args.override = True
182
+ generate_file(args, "demo_backup_def.j2", Path("/tmp/dar-backup/backup.d/demo"), vars_map, opts_dict)
183
+ vars_map["CONFIG_DIR"] = "/tmp"
184
+ generate_file(args, "dar-backup.conf.j2", Path("/tmp/dar-backup.conf"), vars_map, opts_dict)
185
+ elif args.install:
186
+ if not check_directories(args, vars_map):
187
+ print("Error: One or more directories already exist.\nSpecify non-existent directories or use --override to overwrite.")
91
188
  sys.exit(1)
92
189
 
93
- try:
94
- os.makedirs(DAR_BACKUP_DIR, exist_ok=False)
95
- os.makedirs(os.path.join(DAR_BACKUP_DIR, "backups"), exist_ok=False)
96
- os.makedirs(os.path.join(DAR_BACKUP_DIR, "restore"), exist_ok=False)
97
- os.makedirs(CONFIG_DIR, exist_ok=False)
98
- os.makedirs(os.path.join(CONFIG_DIR, "backup.d"), exist_ok=False)
99
- print(f"Directories created: `{DAR_BACKUP_DIR}` and `{CONFIG_DIR}`")
100
-
101
- script_dir = Path(__file__).parent
102
- source_file = script_dir / "dar-backup.conf"
103
- destination_file = Path(CONFIG_DIR) / "dar-backup.conf"
104
-
105
- try:
106
- shutil.copy2(source_file, destination_file)
107
- print(f"Config file deployed to {destination_file}")
108
- except Exception as e:
109
- print(f"Error: Could not copy config file: {e}")
110
- sys.exit(1)
111
-
112
-
113
- backup_definition = BACKUP_DEFINITION.replace("@@HOME_DIR@@", os.path.expanduser("~"))
114
-
115
- try:
116
- with open(os.path.join(CONFIG_DIR, "backup.d", "default"), "w") as f:
117
- f.write(backup_definition)
118
- print(f"Default backup definition file deployed to {os.path.join(CONFIG_DIR, 'backup.d', 'default')}")
119
- except Exception as e:
120
- print(f"Error: Could not write default backup definition: {e}")
121
- sys.exit(1)
122
- except Exception as e:
123
- print(f"Installation failed: {e}")
124
- sys.exit(1)
190
+ Path(vars_map["DAR_BACKUP_DIR"]).mkdir(parents=True, exist_ok=True)
191
+ Path(vars_map["BACKUP_DIR"]).mkdir(parents=True, exist_ok=True)
192
+ Path(vars_map["TEST_RESTORE_DIR"]).mkdir(parents=True, exist_ok=True)
193
+ Path(vars_map["CONFIG_DIR"]).mkdir(parents=True, exist_ok=True)
194
+ Path(vars_map["BACKUP_D_DIR"]).mkdir(parents=True, exist_ok=True)
195
+ print(f"Directories created.")
196
+
197
+ generate_file(args, "demo_backup_def.j2", Path(vars_map["BACKUP_D_DIR"]).joinpath("demo"), vars_map, opts_dict)
198
+ generate_file(args, "dar-backup.conf.j2", Path(vars_map["CONFIG_DIR"]).joinpath("dar-backup.conf"), vars_map, opts_dict)
125
199
 
126
- print("1. Now run `manager --create` to create the catalog database.")
200
+ print("1. Now run `manager --create-db` to create the catalog database.")
127
201
  print("2. Then you can run `dar-backup --full-backup` to create a backup.")
128
202
  print("3. List backups with `dar-backup --list`")
129
203
  print("4. List contents of a backup with `dar-backup --list-contents <backup-name>`")
204
+ else:
205
+ parser.print_help()
206
+ sys.exit(1)
130
207
 
131
208
  sys.exit(0)
132
209
 
133
-
134
210
  if __name__ == "__main__":
135
211
  main()