dar-backup 0.6.11__py3-none-any.whl → 0.6.13__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.11"
1
+ __version__ = "0.6.13"
dar_backup/clean_log.py CHANGED
@@ -122,7 +122,7 @@ def main():
122
122
 
123
123
  args = parser.parse_args()
124
124
 
125
- config_settings = ConfigSettings(os.path.expanduser(args.config_file))
125
+ config_settings = ConfigSettings(os.path.expanduser(os.path.expandvars(args.config_file)))
126
126
 
127
127
  if not args.file:
128
128
  args.file = [config_settings.logfile_location]
dar_backup/cleanup.py CHANGED
@@ -171,7 +171,7 @@ def main():
171
171
  parser.add_argument('--log-stdout', action='store_true', help='also print log messages to stdout')
172
172
  args = parser.parse_args()
173
173
 
174
- args.config_file = os.path.expanduser(args.config_file)
174
+ args.config_file = os.path.expanduser(os.path.expandvars(args.config_file))
175
175
 
176
176
 
177
177
  if args.version:
@@ -181,7 +181,11 @@ def main():
181
181
  config_settings = ConfigSettings(args.config_file)
182
182
 
183
183
  start_time=int(time())
184
- logger = setup_logging(config_settings.logfile_location, args.log_level, args.log_stdout)
184
+
185
+ # command_output_log = os.path.join(config_settings.logfile_location.removesuffix("dar-backup.log"), "dar-backup-commands.log")
186
+ command_output_log = config_settings.logfile_location.replace("dar-backup.log", "dar-backup-commands.log")
187
+ logger = setup_logging(config_settings.logfile_location, command_output_log, args.log_level, args.log_stdout)
188
+
185
189
 
186
190
  logger.info(f"=====================================")
187
191
  logger.info(f"cleanup.py started, version: {about.__version__}")
@@ -1,8 +1,11 @@
1
- from dataclasses import dataclass, field
1
+
2
2
  import configparser
3
- from pathlib import Path
4
- import sys
5
3
  import logging
4
+ import sys
5
+
6
+ from dataclasses import dataclass, field, fields
7
+ from os.path import expandvars, expanduser
8
+ from pathlib import Path
6
9
 
7
10
  @dataclass
8
11
  class ConfigSettings:
@@ -20,21 +23,35 @@ class ConfigSettings:
20
23
  backup_d_dir (str): The directory for backup.d.
21
24
  diff_age (int): The age for differential backups before deletion.
22
25
  incr_age (int): The age for incremental backups before deletion.
26
+ error_correction_percent (int): The error correction percentage for PAR2.
27
+ par2_enabled (bool): Whether PAR2 is enabled.
23
28
  """
24
29
 
25
- def __init__(self, config_file: str):
30
+ config_file: str
31
+ logfile_location: str = field(init=False)
32
+ max_size_verification_mb: int = field(init=False)
33
+ min_size_verification_mb: int = field(init=False)
34
+ no_files_verification: int = field(init=False)
35
+ command_timeout_secs: int = field(init=False)
36
+ backup_dir: str = field(init=False)
37
+ test_restore_dir: str = field(init=False)
38
+ backup_d_dir: str = field(init=False)
39
+ diff_age: int = field(init=False)
40
+ incr_age: int = field(init=False)
41
+ error_correction_percent: int = field(init=False)
42
+ par2_enabled: bool = field(init=False)
43
+
44
+ def __post_init__(self):
26
45
  """
27
- Initializes the ConfigSettings instance by reading the specified configuration file.
28
-
29
- Args:
30
- config_file (str): The path to the configuration file.
46
+ Initializes the ConfigSettings instance by reading the specified configuration file
47
+ and expands environment variables for all string fields.
31
48
  """
32
- if config_file is None:
49
+ if self.config_file is None:
33
50
  raise ValueError("`config_file` must be specified.")
34
51
 
35
52
  self.config = configparser.ConfigParser()
36
53
  try:
37
- self.config.read(config_file)
54
+ self.config.read(self.config_file)
38
55
  self.logfile_location = self.config['MISC']['LOGFILE_LOCATION']
39
56
  self.max_size_verification_mb = int(self.config['MISC']['MAX_SIZE_VERIFICATION_MB'])
40
57
  self.min_size_verification_mb = int(self.config['MISC']['MIN_SIZE_VERIFICATION_MB'])
@@ -46,12 +63,18 @@ class ConfigSettings:
46
63
  self.diff_age = int(self.config['AGE']['DIFF_AGE'])
47
64
  self.incr_age = int(self.config['AGE']['INCR_AGE'])
48
65
  self.error_correction_percent = int(self.config['PAR2']['ERROR_CORRECTION_PERCENT'])
49
- self.par2_enabled = bool(self.config['PAR2']['ENABLED'])
66
+ self.par2_enabled = self.config['PAR2']['ENABLED'].lower() in ('true', '1', 'yes')
67
+
50
68
  # Ensure the directories exist
51
69
  Path(self.backup_dir).mkdir(parents=True, exist_ok=True)
52
70
  Path(self.test_restore_dir).mkdir(parents=True, exist_ok=True)
53
71
  Path(self.backup_d_dir).mkdir(parents=True, exist_ok=True)
54
72
 
73
+ # Expand environment variables for all string fields
74
+ for field in fields(self):
75
+ if isinstance(getattr(self, field.name), str):
76
+ setattr(self, field.name, expanduser(expandvars(getattr(self, field.name))))
77
+
55
78
  except FileNotFoundError as e:
56
79
  logging.error(f"Configuration file not found: {self.config_file}")
57
80
  logging.error(f"Error details: {e}")
@@ -0,0 +1,33 @@
1
+ # This config file is intended to demo `dar-backup`.
2
+ #
3
+ # The `installer` puts it in ~/.config/dar-backup/dar-backup.conf
4
+
5
+ [MISC]
6
+ LOGFILE_LOCATION = ~/dar-backup/dar-backup.log
7
+ MAX_SIZE_VERIFICATION_MB = 20
8
+ MIN_SIZE_VERIFICATION_MB = 1
9
+ NO_FILES_VERIFICATION = 5
10
+ # timeout in seconds for backup, test, restore and par2 operations
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.
13
+ COMMAND_TIMEOUT_SECS = 86400
14
+
15
+ [DIRECTORIES]
16
+ BACKUP_DIR = ~/dar-backup/backups
17
+ BACKUP.D_DIR = ~/.config/dar-backup/backup.d/
18
+ TEST_RESTORE_DIR = ~/dar-backup/restore/
19
+
20
+ [AGE]
21
+ # age settings are in days
22
+ DIFF_AGE = 100
23
+ INCR_AGE = 40
24
+
25
+ [PAR2]
26
+ ERROR_CORRECTION_PERCENT = 5
27
+ ENABLED = True
28
+
29
+ [PREREQ]
30
+ #SCRIPT_1 = <pre-script 1>
31
+
32
+ [POSTREQ]
33
+ #SCRIPT_1 = <post-script 1>
dar_backup/dar_backup.py CHANGED
@@ -7,14 +7,18 @@ import os
7
7
  import random
8
8
  import re
9
9
  import shlex
10
+ import shutil
10
11
  import subprocess
11
- import sys
12
12
  import xml.etree.ElementTree as ET
13
-
13
+ import tempfile
14
14
 
15
15
  from argparse import ArgumentParser
16
16
  from datetime import datetime
17
17
  from pathlib import Path
18
+ from sys import exit
19
+ from sys import stderr
20
+ from sys import argv
21
+ from sys import version_info
18
22
  from time import time
19
23
  from typing import List
20
24
 
@@ -27,6 +31,7 @@ from dar_backup.util import BackupError
27
31
  from dar_backup.util import RestoreError
28
32
 
29
33
 
34
+ RESULT = True
30
35
  logger = None
31
36
 
32
37
  def generic_backup(type: str, command: List[str], backup_file: str, backup_definition: str, darrc: str, config_settings: ConfigSettings, args: argparse.Namespace):
@@ -96,12 +101,12 @@ def find_files_with_paths(xml_root: ET.Element):
96
101
  current_path = []
97
102
 
98
103
  for elem in xml_root.iter():
99
- if elem.tag == "directory":
104
+ if elem.tag == "Directory":
100
105
  current_path.append(elem.get('name'))
101
- elif elem.tag == "file":
106
+ elif elem.tag == "File":
102
107
  file_path = ("/".join(current_path + [elem.get('name')]), elem.get('size'))
103
108
  files.append(file_path)
104
- elif elem.tag == "directory" and elem.get('name') in current_path:
109
+ elif elem.tag == "Directory" and elem.get('Name') in current_path:
105
110
  current_path.pop()
106
111
 
107
112
  return files
@@ -189,10 +194,10 @@ def verify(args: argparse.Namespace, backup_file: str, backup_definition: str, c
189
194
  logger.info(f"No files between {config_settings.min_size_verification_mb}MB and {config_settings.max_size_verification_mb}MB for verification, skipping")
190
195
  return result
191
196
 
197
+ # find Root path in backup definition
192
198
  with open(backup_definition, 'r') as f:
193
199
  backup_definition_content = f.readlines()
194
200
  logger.debug(f"Backup definition: '{backup_definition}', content:\n{backup_definition_content}")
195
- # Initialize a variable to hold the path after "-R"
196
201
  root_path = None
197
202
  for line in backup_definition_content:
198
203
  line = line.strip()
@@ -200,7 +205,10 @@ def verify(args: argparse.Namespace, backup_file: str, backup_definition: str, c
200
205
  root_path = line.split("-R", 1)[1].strip()
201
206
  break
202
207
  if root_path is None:
203
- logger.warning("No Root (-R) path specified in the backup definition file.")
208
+ msg = f"No Root (-R) path found in the backup definition file: '{backup_definition}', restore verification skipped"
209
+ raise BackupError(msg)
210
+
211
+
204
212
 
205
213
  no_files_verification = config_settings.no_files_verification
206
214
  if len(files) < config_settings.no_files_verification:
@@ -407,7 +415,7 @@ def perform_backup(args: argparse.Namespace, config_settings: ConfigSettings, ba
407
415
  logger.info(f"Using alternate reference archive: {latest_base_backup}")
408
416
  if not os.path.exists(latest_base_backup + '.1.dar'):
409
417
  logger.error(f"Alternate reference archive: \"{latest_base_backup}.1.dar\" does not exist, exiting.")
410
- sys.exit(1)
418
+ exit(1)
411
419
  else:
412
420
  base_backups = sorted(
413
421
  [f for f in os.listdir(config_settings.backup_dir) if f.startswith(f"{backup_definition}_{base_backup_type}_") and f.endswith('.1.dar')],
@@ -435,9 +443,10 @@ def perform_backup(args: argparse.Namespace, config_settings: ConfigSettings, ba
435
443
  logger.info("Generate par2 redundancy files.")
436
444
  generate_par2_files(backup_file, config_settings, args)
437
445
  logger.info("par2 files completed successfully.")
438
-
439
446
 
440
447
  except Exception as e:
448
+ global RESULT
449
+ RESULT = False
441
450
  logger.exception(f"Error during {backup_type} backup process, continuing to next backup definition.")
442
451
 
443
452
 
@@ -491,8 +500,46 @@ def generate_par2_files(backup_file: str, config_settings: ConfigSettings, args)
491
500
 
492
501
 
493
502
 
503
+ def filter_darrc_file(darrc_path):
504
+ """
505
+ Filters the .darrc file to remove lines containing the options: -vt, -vs, -vd, -vf, and -va.
506
+ The filtered version is stored in a uniquely named file in the home directory of the user running the script.
507
+ The file permissions are set to 440.
508
+
509
+ :param darrc_path: Path to the original .darrc file.
510
+ :return: Path to the filtered .darrc file.
511
+ """
512
+ # Define options to filter out
513
+ options_to_remove = {"-vt", "-vs", "-vd", "-vf", "-va"}
514
+
515
+ # Get the user's home directory
516
+ home_dir = os.path.expanduser("~")
517
+
518
+ # Create a unique file name in the home directory
519
+ filtered_darrc_path = os.path.join(home_dir, f"filtered_darrc_{next(tempfile._get_candidate_names())}.darrc")
520
+
521
+ try:
522
+ with open(darrc_path, "r") as infile, open(filtered_darrc_path, "w") as outfile:
523
+ for line in infile:
524
+ # Check if any unwanted option is in the line
525
+ if not any(option in line for option in options_to_remove):
526
+ outfile.write(line)
527
+
528
+ # Set file permissions to 440 (read-only for owner and group, no permissions for others)
529
+ os.chmod(filtered_darrc_path, 0o440)
530
+
531
+ return filtered_darrc_path
532
+
533
+ except Exception as e:
534
+ # If anything goes wrong, clean up the temp file if it was created
535
+ if os.path.exists(filtered_darrc_path):
536
+ os.remove(filtered_darrc_path)
537
+ raise RuntimeError(f"Error filtering .darrc file: {e}")
538
+
539
+
540
+
494
541
  def show_version():
495
- script_name = os.path.basename(sys.argv[0])
542
+ script_name = os.path.basename(argv[0])
496
543
  print(f"{script_name} {about.__version__}")
497
544
  print(f"dar-backup.py source code is here: https://github.com/per2jensen/dar-backup")
498
545
  print('''Licensed under GNU GENERAL PUBLIC LICENSE v3, see the supplied file "LICENSE" for details.
@@ -577,14 +624,14 @@ def requirements(type: str, config_setting: ConfigSettings):
577
624
 
578
625
 
579
626
  def main():
580
- global logger
627
+ global logger, RESULT
581
628
 
582
629
  MIN_PYTHON_VERSION = (3, 9)
583
- if sys.version_info < MIN_PYTHON_VERSION:
584
- sys.stderr.write(f"Error: This script requires Python {'.'.join(map(str, MIN_PYTHON_VERSION))} or higher.\n")
585
- sys.exit(1)
630
+ if version_info < MIN_PYTHON_VERSION:
631
+ stderr.write(f"Error: This script requires Python {'.'.join(map(str, MIN_PYTHON_VERSION))} or higher.\n")
632
+ exit(1)
586
633
 
587
- parser = argparse.ArgumentParser(description="Backup and verify using dar backup definitions.")
634
+ parser = argparse.ArgumentParser(description="Backup, verify & redundancy using dar and par2.")
588
635
  parser.add_argument('-F', '--full-backup', action='store_true', help="Perform a full backup.")
589
636
  parser.add_argument('-D', '--differential-backup', action='store_true', help="Perform differential backup.")
590
637
  parser.add_argument('-I', '--incremental-backup', action='store_true', help="Perform incremental backup.")
@@ -600,43 +647,55 @@ def main():
600
647
  parser.add_argument('-r', '--restore', type=str, help="Restore specified archive.")
601
648
  parser.add_argument('--restore-dir', type=str, help="Directory to restore files to.")
602
649
  parser.add_argument('--verbose', action='store_true', help="Print various status messages to screen")
650
+ parser.add_argument('--suppress-dar-msg', action='store_true', help="cancel dar options in .darrc: -vt, -vs, -vd, -vf and -va")
603
651
  parser.add_argument('--log-level', type=str, help="`debug` or `trace`", default="info")
604
652
  parser.add_argument('--log-stdout', action='store_true', help='also print log messages to stdout')
605
653
  parser.add_argument('--do-not-compare', action='store_true', help="do not compare restores to file system")
606
654
  parser.add_argument('-v', '--version', action='store_true', help="Show version and license information.")
607
655
  args = parser.parse_args()
608
656
 
609
- args.config_file = os.path.expanduser(args.config_file)
610
- config_settings = ConfigSettings(args.config_file)
611
-
612
657
  if args.version:
613
658
  show_version()
614
- sys.exit(0)
659
+ exit(0)
615
660
  elif args.examples:
616
661
  show_examples()
617
- sys.exit(0)
618
-
619
- logger = setup_logging(config_settings.logfile_location, args.log_level, args.log_stdout)
620
-
621
- if not args.darrc:
622
- current_script_dir = os.path.dirname(os.path.abspath(__file__))
623
- args.darrc = os.path.join(current_script_dir, ".darrc")
624
-
625
- if os.path.exists(args.darrc) and os.path.isfile(args.darrc):
626
- logger.debug(f"Using .darrc: {args.darrc}")
627
- else:
628
- logger.error(f"Supplied .darrc: '{args.darrc}' does not exist or is not a file")
662
+ exit(0)
629
663
 
630
664
  if not args.config_file:
631
- logger.error(f"Config file not specified, exiting")
632
- sys.exit(1)
665
+ print(f"Config file not specified, exiting", file=stderr)
666
+ exit(1)
633
667
 
634
- if not os.path.exists(args.config_file):
635
- logger.error(f"Config file {args.config_file} does not exist.")
636
- sys.exit(1)
668
+ config_settings_path = os.path.expanduser(os.path.expandvars(args.config_file))
669
+ if not os.path.exists(config_settings_path):
670
+ print(f"Config file {args.config_file} does not exist.", file=stderr)
671
+ exit(127)
672
+
673
+ args.config_file = config_settings_path
674
+ config_settings = ConfigSettings(args.config_file)
637
675
 
676
+ command_output_log = config_settings.logfile_location.replace("dar-backup.log", "dar-backup-commands.log")
677
+ if command_output_log == config_settings.logfile_location:
678
+ print(f"Error: logfile_location in {args.config_file} does not end at 'dar-backup.log', exiting", file=stderr)
679
+
680
+ logger = setup_logging(config_settings.logfile_location, command_output_log, args.log_level, args.log_stdout)
638
681
 
639
682
  try:
683
+ if not args.darrc:
684
+ current_script_dir = os.path.dirname(os.path.abspath(__file__))
685
+ args.darrc = os.path.join(current_script_dir, ".darrc")
686
+
687
+ darrc_file = os.path.expanduser(os.path.expandvars(args.darrc))
688
+ if os.path.exists(darrc_file) and os.path.isfile(darrc_file):
689
+ logger.debug(f"Using .darrc: {args.darrc}")
690
+ else:
691
+ logger.error(f"Supplied .darrc: '{args.darrc}' does not exist or is not a file, exiting", file=stderr)
692
+ exit(127)
693
+
694
+ if args.suppress_dar_msg:
695
+ logger.info("Suppressing dar messages: -vt, -vs, -vd, -vf and -va")
696
+ args.darrc = filter_darrc_file(args.darrc)
697
+ logger.debug(f"Filtered .darrc file saved at: {args.darrc}")
698
+
640
699
  start_time=int(time())
641
700
  logger.info(f"=====================================")
642
701
  logger.info(f"dar-backup.py started, version: {about.__version__}")
@@ -664,14 +723,13 @@ def main():
664
723
  args.verbose and (print(f"PAR2 enabled: {config_settings.par2_enabled}"))
665
724
  args.verbose and (print(f"--do-not-compare: {args.do_not_compare}"))
666
725
 
667
-
668
726
  # sanity check
669
727
  if args.backup_definition and not os.path.exists(os.path.join(config_settings.backup_d_dir, args.backup_definition)):
670
728
  logger.error(f"Backup definition: '{args.backup_definition}' does not exist, exiting")
671
- sys.exit(1)
729
+ exit(127)
672
730
  if args.backup_definition and '_' in args.backup_definition:
673
731
  logger.error(f"Backup definition: '{args.backup_definition}' contains '_', exiting")
674
- sys.exit(1)
732
+ exit(1)
675
733
 
676
734
 
677
735
  requirements('PREREQ', config_settings)
@@ -694,19 +752,24 @@ def main():
694
752
 
695
753
  requirements('POSTREQ', config_settings)
696
754
 
697
- args.verbose and print("\033[1m\033[32mSUCCESS\033[0m No errors encountered")
698
- sys.exit(0)
699
755
  except Exception as e:
700
756
  logger.exception("An error occurred")
701
757
  logger.error("Exception details:", exc_info=True)
702
758
  args.verbose and print("\033[1m\033[31mErrors\033[0m encountered")
703
- sys.exit(1)
759
+ RESULT = False
704
760
  finally:
705
761
  end_time=int(time())
706
762
  logger.info(f"END TIME: {end_time}")
707
-
708
- # error_lines = extract_error_lines(config_settings.logfile_location, start_time, end_time)
763
+ # Clean up
764
+ if args.suppress_dar_msg and os.path.exists(args.darrc) and os.path.dirname(args.darrc) == os.path.expanduser("~"):
765
+ if args.darrc.startswith("filtered_darrc_"):
766
+ os.remove(args.darrc)
767
+ logger.info(f"Removed filtered .darrc: {args.darrc}")
709
768
 
710
769
 
770
+ if RESULT:
771
+ exit(0)
772
+ else:
773
+ exit(1)
711
774
  if __name__ == "__main__":
712
775
  main()
@@ -0,0 +1,122 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ installer.py source code is here: https://github.com/per2jensen/dar-backup/tree/main/v2/src/dar_backup/installer.py
4
+ This script is part of dar-backup, a backup solution for Linux using dar and systemd.
5
+
6
+ Licensed under GNU GENERAL PUBLIC LICENSE v3, see the supplied file "LICENSE" for details.
7
+
8
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW,
9
+ not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10
+ See section 15 and section 16 in the supplied "LICENSE" file
11
+
12
+ 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.
14
+ """
15
+
16
+ import argparse
17
+ import os
18
+ import shutil
19
+ import sys
20
+
21
+ from . import __about__ as about
22
+ from pathlib import Path
23
+
24
+ LICENSE = '''Licensed under GNU GENERAL PUBLIC LICENSE v3, see the supplied file "LICENSE" for details.
25
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW, not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
26
+ See section 15 and section 16 in the supplied "LICENSE" file.'''
27
+
28
+ CONFIG_DIR = os.path.expanduser("~/.config/dar-backup")
29
+ DAR_BACKUP_DIR = os.path.expanduser("~/dar-backup/")
30
+
31
+ BACKUP_DEFINITION = '''
32
+ # Demo of a `dar-backup` definition file
33
+ # This back definition file configures a backup of ~/.config/dar-backup
34
+ # `dar-backup` puts the backups in ~/dar-backup/backups
35
+ # ------------------------------------------------------------------------
36
+
37
+ # Switch to ordered selection mode, which means that the following options
38
+ # will be considered top to bottom
39
+ -am
40
+
41
+ # Backup Root dir
42
+ -R @@HOME_DIR@@
43
+
44
+ # Directories to backup below the Root dir
45
+ -g .config/dar-backup
46
+
47
+ # Examples of directories to exclude below the Root dir
48
+ -P mnt
49
+ -P .private
50
+ -P .cache
51
+
52
+ # compression level
53
+ -z5
54
+
55
+ # no overwrite, if you rerun a backup, 'dar' halts and asks what to do
56
+ -n
57
+
58
+ # size of each slice in the archive
59
+ --slice 10G
60
+
61
+ # bypass directores marked as cache directories
62
+ # http://dar.linux.free.fr/doc/Features.html
63
+ --cache-directory-tagging
64
+ '''
65
+
66
+ def main():
67
+ parser = argparse.ArgumentParser(
68
+ description="Set up `dar-backup` on your system.",
69
+ )
70
+ parser.add_argument(
71
+ "-i", "--install",
72
+ action="store_true",
73
+ help="Deploy a simple config file, use ~/dar-backup/ for log file, archives and restore tests."
74
+ )
75
+
76
+ parser.add_argument(
77
+ "-v", "--version",
78
+ action="version",
79
+ version=f"%(prog)s version {about.__version__}, {LICENSE}"
80
+ )
81
+
82
+ args = parser.parse_args()
83
+
84
+ if args.install:
85
+ errors = []
86
+ if os.path.exists(CONFIG_DIR):
87
+ errors.append(f"Config directory '{CONFIG_DIR}' already exists.")
88
+
89
+ if os.path.exists(DAR_BACKUP_DIR):
90
+ errors.append(f"Directory '{DAR_BACKUP_DIR}' already exists.")
91
+
92
+ if len(errors) > 0:
93
+ for error in errors:
94
+ print(f"Error: {error}")
95
+ sys.exit(1)
96
+
97
+ os.makedirs(DAR_BACKUP_DIR, exist_ok=False)
98
+ os.makedirs(os.path.join(DAR_BACKUP_DIR, "backups"), exist_ok=False)
99
+ os.makedirs(os.path.join(DAR_BACKUP_DIR, "restore"), exist_ok=False)
100
+ os.makedirs(CONFIG_DIR, exist_ok=False)
101
+ os.makedirs(os.path.join(CONFIG_DIR, "backup.d"), exist_ok=False)
102
+ print(f"Directories created: `{DAR_BACKUP_DIR}` and `{CONFIG_DIR}`")
103
+
104
+ script_dir = Path(__file__).parent
105
+ source_file = script_dir / "dar-backup.conf"
106
+ destination_file = Path(CONFIG_DIR) / "dar-backup.conf"
107
+ shutil.copy(source_file, destination_file)
108
+ print(f"Config file deployed to {destination_file}")
109
+
110
+
111
+ backup_definition = BACKUP_DEFINITION.replace("@@HOME_DIR@@", os.path.expanduser("~"))
112
+ with open(os.path.join(CONFIG_DIR, "backup.d", "default"), "w") as f:
113
+ f.write(backup_definition)
114
+ print(f"Default backup definition file deployed to {os.path.join(CONFIG_DIR, 'backup.d', 'default')}")
115
+ print("1. Now run `manager --create` to create the catalog database.")
116
+ print("2. Then you can run `dar-backup --full-backup` to create a backup.")
117
+ print("3. List backups with `dar-backup --list`")
118
+ print("4. List contents of a backup with `dar-backup --list-contents <backup-name>`")
119
+
120
+
121
+ if __name__ == "__main__":
122
+ main()
dar_backup/manager.py CHANGED
@@ -335,7 +335,7 @@ def remove_specific_archive(archive: str, config_settings: ConfigSettings) -> in
335
335
  """
336
336
  backup_def = backup_def_from_archive(archive)
337
337
  database_path = os.path.join(config_settings.backup_dir, f"{backup_def}{DB_SUFFIX}")
338
- cat_no = cat_no_for_name(archive, config_settings)
338
+ cat_no:int = cat_no_for_name(archive, config_settings)
339
339
  if cat_no >= 0:
340
340
  command = ['dar_manager', '--base', database_path, "--delete", str(cat_no)]
341
341
  process: CommandResult = run_command(command)
@@ -395,12 +395,16 @@ See section 15 and section 16 in the supplied "LICENSE" file.''')
395
395
  sys.exit(0)
396
396
 
397
397
  # setup logging
398
- args.config_file = os.path.expanduser(args.config_file)
398
+ args.config_file = os.path.expanduser(os.path.expandvars(args.config_file))
399
399
  config_settings = ConfigSettings(args.config_file)
400
400
  if not os.path.dirname(config_settings.logfile_location):
401
401
  print(f"Directory for log file '{config_settings.logfile_location}' does not exist, exiting")
402
402
  sys.exit(1)
403
- logger = setup_logging(config_settings.logfile_location, args.log_level, args.log_stdout)
403
+
404
+ # command_output_log = os.path.join(config_settings.logfile_location.removesuffix("dar-backup.log"), "dar-backup-commands.log")
405
+ command_output_log = config_settings.logfile_location.replace("dar-backup.log", "dar-backup-commands.log")
406
+ logger = setup_logging(config_settings.logfile_location, command_output_log, args.log_level, args.log_stdout)
407
+
404
408
 
405
409
  start_time=int(time())
406
410
  logger.info(f"=====================================")