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