dar-backup 0.6.21__py3-none-any.whl → 0.7.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/Changelog.md +17 -1
- dar_backup/README.md +357 -82
- dar_backup/__about__.py +1 -1
- dar_backup/clean_log.py +2 -0
- dar_backup/cleanup.py +2 -1
- dar_backup/command_runner.py +2 -0
- dar_backup/config_settings.py +2 -0
- dar_backup/dar-backup.conf +4 -2
- dar_backup/dar-backup.conf.j2 +60 -0
- dar_backup/dar_backup.py +6 -6
- dar_backup/dar_backup_systemd.py +3 -0
- dar_backup/demo.py +153 -77
- dar_backup/demo_backup_def.j2 +62 -0
- dar_backup/exceptions.py +2 -0
- dar_backup/installer.py +111 -6
- dar_backup/manager.py +3 -2
- dar_backup/rich_progress.py +3 -0
- dar_backup/util.py +24 -5
- {dar_backup-0.6.21.dist-info → dar_backup-0.7.1.dist-info}/METADATA +359 -83
- dar_backup-0.7.1.dist-info/RECORD +25 -0
- dar_backup-0.6.21.dist-info/RECORD +0 -23
- {dar_backup-0.6.21.dist-info → dar_backup-0.7.1.dist-info}/WHEEL +0 -0
- {dar_backup-0.6.21.dist-info → dar_backup-0.7.1.dist-info}/entry_points.txt +0 -0
- {dar_backup-0.6.21.dist-info → dar_backup-0.7.1.dist-info}/licenses/LICENSE +0 -0
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.
|
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
|
|
@@ -236,7 +237,7 @@ def main():
|
|
|
236
237
|
args.verbose and start_msgs.append(("--cleanup-specific-archives:", args.cleanup_specific_archives))
|
|
237
238
|
|
|
238
239
|
dangerous_keywords = ["--cleanup", "_FULL_"] # TODO: add more dangerous keywords
|
|
239
|
-
print_aligned_settings(start_msgs, highlight_keywords=dangerous_keywords)
|
|
240
|
+
print_aligned_settings(start_msgs, highlight_keywords=dangerous_keywords, quiet=not args.verbose)
|
|
240
241
|
|
|
241
242
|
# run PREREQ scripts
|
|
242
243
|
requirements('PREREQ', config_settings)
|
dar_backup/command_runner.py
CHANGED
dar_backup/config_settings.py
CHANGED
dar_backup/dar-backup.conf
CHANGED
|
@@ -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 `
|
|
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 =
|
|
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,60 @@
|
|
|
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
|
+
MAX_SIZE_VERIFICATION_MB = 2
|
|
30
|
+
MIN_SIZE_VERIFICATION_MB = 0
|
|
31
|
+
NO_FILES_VERIFICATION = 1
|
|
32
|
+
# timeout in seconds for backup, test, restore and par2 operations
|
|
33
|
+
# The author has such `dar` tasks running for 10-15 hours on the yearly backups, so a value of 24 hours is used.
|
|
34
|
+
# If a timeout is not specified when using the CommandRunner, a default timeout of 30 secs is used.
|
|
35
|
+
COMMAND_TIMEOUT_SECS = 86400
|
|
36
|
+
|
|
37
|
+
[DIRECTORIES]
|
|
38
|
+
BACKUP_DIR = {{ vars_map.BACKUP_DIR }}
|
|
39
|
+
BACKUP.D_DIR = {{ vars_map.BACKUP_D_DIR }}
|
|
40
|
+
TEST_RESTORE_DIR = {{ vars_map.TEST_RESTORE_DIR }}
|
|
41
|
+
# Optional parameter
|
|
42
|
+
# If you want to store the catalog database away from the BACKUP_DIR, use the MANAGER_DB_DIR variable.
|
|
43
|
+
#MANAGER_DB_DIR = /some/where/else/
|
|
44
|
+
|
|
45
|
+
[AGE]
|
|
46
|
+
# DIFF and INCR backups are kept for a configured number of days, then deleted by the `cleanuo`
|
|
47
|
+
# age settings are in days
|
|
48
|
+
DIFF_AGE = 100
|
|
49
|
+
INCR_AGE = 40
|
|
50
|
+
|
|
51
|
+
[PAR2]
|
|
52
|
+
ERROR_CORRECTION_PERCENT = 5
|
|
53
|
+
ENABLED = True
|
|
54
|
+
|
|
55
|
+
[PREREQ]
|
|
56
|
+
#SCRIPT_1 = <pre-script 1>
|
|
57
|
+
|
|
58
|
+
[POSTREQ]
|
|
59
|
+
#SCRIPT_1 = <post-script 1>
|
|
60
|
+
|
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.
|
|
@@ -877,9 +877,9 @@ def main():
|
|
|
877
877
|
start_msgs.append(('Config file:', os.path.abspath(args.config_file)))
|
|
878
878
|
start_msgs.append((".darrc location:", args.darrc))
|
|
879
879
|
|
|
880
|
-
args.
|
|
881
|
-
args.
|
|
882
|
-
args.
|
|
880
|
+
args.full_backup and start_msgs.append(("Type of backup:", "FULL"))
|
|
881
|
+
args.differential_backup and start_msgs.append(("Type of backup:", "DIFF"))
|
|
882
|
+
args.incremental_backup and start_msgs.append(("Type of backup:", "INCR"))
|
|
883
883
|
args.verbose and args.backup_definition and start_msgs.append(("Backup definition:", args.backup_definition))
|
|
884
884
|
if args.alternate_reference_archive:
|
|
885
885
|
args.verbose and start_msgs.append(("Alternate ref archive:", args.alternate_reference_archive))
|
|
@@ -893,8 +893,8 @@ def main():
|
|
|
893
893
|
args.verbose and start_msgs.append(("PAR2 enabled:", config_settings.par2_enabled))
|
|
894
894
|
args.verbose and start_msgs.append(("--do-not-compare:", args.do_not_compare))
|
|
895
895
|
|
|
896
|
-
|
|
897
|
-
print_aligned_settings(start_msgs)
|
|
896
|
+
highlight_keywords = ["--do-not", "alternate"] # TODO: add more dangerous keywords
|
|
897
|
+
print_aligned_settings(start_msgs, quiet=not args.verbose, highlight_keywords=highlight_keywords)
|
|
898
898
|
|
|
899
899
|
# sanity check
|
|
900
900
|
if args.backup_definition and not os.path.exists(os.path.join(config_settings.backup_d_dir, args.backup_definition)):
|
dar_backup/dar_backup_systemd.py
CHANGED
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
|
-
|
|
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
|
-
|
|
26
|
-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
39
|
-
|
|
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
|
-
|
|
42
|
-
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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()
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
|
|
2
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
3
|
+
|
|
4
|
+
# ------------------------------------------------------------------------
|
|
5
|
+
# Demo of a `dar-backup` definition 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
|
+
# Switch to ordered selection mode, which means that the following options
|
|
28
|
+
# will be considered top to bottom
|
|
29
|
+
-am
|
|
30
|
+
|
|
31
|
+
# Backup Root dir
|
|
32
|
+
{%- if vars_map.ROOT_DIR %}
|
|
33
|
+
-R {{ vars_map.ROOT_DIR }}
|
|
34
|
+
{% endif -%}
|
|
35
|
+
|
|
36
|
+
{% if vars_map.DIR_TO_BACKUP %}
|
|
37
|
+
# Directories to backup below the Root dir
|
|
38
|
+
-g {{ vars_map.DIR_TO_BACKUP }}
|
|
39
|
+
|
|
40
|
+
# This is an example of exclusion of a `.private` directory inside the
|
|
41
|
+
# directory that is backed up
|
|
42
|
+
-P {{ vars_map.DIR_TO_BACKUP }}/.private
|
|
43
|
+
{%- else %}
|
|
44
|
+
# Examples of directories to exclude below the Root dir
|
|
45
|
+
-P mnt
|
|
46
|
+
-P .cache
|
|
47
|
+
{% endif %}
|
|
48
|
+
|
|
49
|
+
# compression level
|
|
50
|
+
-z5
|
|
51
|
+
|
|
52
|
+
# no overwrite, if you rerun a backup, 'dar' halts and asks what to do
|
|
53
|
+
# as `dar-backup` gives the `-Q` option to `dar`, the net effect of `-n` and `-Q` is
|
|
54
|
+
# that `dar` will quit and not overwrite the existing backup
|
|
55
|
+
-n
|
|
56
|
+
|
|
57
|
+
# size of each slice in the archive (10G is 10 Gigabytes)
|
|
58
|
+
--slice 10G
|
|
59
|
+
|
|
60
|
+
# bypass directores marked as cache directories
|
|
61
|
+
# http://dar.linux.free.fr/doc/Features.html
|
|
62
|
+
--cache-directory-tagging
|
dar_backup/exceptions.py
CHANGED
dar_backup/installer.py
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
3
|
+
|
|
1
4
|
import argparse
|
|
2
5
|
import os
|
|
3
6
|
from . import __about__ as about
|
|
@@ -10,9 +13,93 @@ from dar_backup.manager import create_db
|
|
|
10
13
|
from dar_backup.manager import get_db_dir
|
|
11
14
|
from dar_backup.util import expand_path
|
|
12
15
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
+
def install_autocompletion():
|
|
17
|
+
"""Detect user shell, choose RC file, and idempotently append autocompletion."""
|
|
18
|
+
shell = Path(os.environ.get("SHELL", "")).name
|
|
19
|
+
home = Path.home()
|
|
20
|
+
|
|
21
|
+
# pick RC file based on shell
|
|
22
|
+
if shell == "zsh":
|
|
23
|
+
rc_file = home / ".zshrc"
|
|
24
|
+
elif shell == "bash":
|
|
25
|
+
# prefer ~/.bash_profile on macOS if present
|
|
26
|
+
rc_file = home / ".bash_profile" if (home / ".bash_profile").exists() else home / ".bashrc"
|
|
27
|
+
else:
|
|
28
|
+
rc_file = home / ".bashrc"
|
|
29
|
+
|
|
30
|
+
marker = "# >>> dar-backup autocompletion >>>"
|
|
31
|
+
end_marker = "# <<< dar-backup autocompletion <<<"
|
|
32
|
+
|
|
33
|
+
block = "\n".join([
|
|
34
|
+
marker,
|
|
35
|
+
'eval "$(register-python-argcomplete dar-backup)"',
|
|
36
|
+
'eval "$(register-python-argcomplete cleanup)"',
|
|
37
|
+
'eval "$(register-python-argcomplete manager)"',
|
|
38
|
+
"#complete -o nosort -C 'python -m argcomplete cleanup' cleanup",
|
|
39
|
+
"#complete -o nosort -C 'python -m argcomplete manager' manager",
|
|
40
|
+
end_marker,
|
|
41
|
+
]) + "\n"
|
|
42
|
+
|
|
43
|
+
# ensure RC file and parent directory exist
|
|
44
|
+
rc_file.parent.mkdir(parents=True, exist_ok=True)
|
|
45
|
+
if not rc_file.exists():
|
|
46
|
+
rc_file.touch()
|
|
47
|
+
|
|
48
|
+
content = rc_file.read_text()
|
|
49
|
+
if marker in content:
|
|
50
|
+
print(f"Autocompletion already installed in {rc_file}")
|
|
51
|
+
return
|
|
52
|
+
|
|
53
|
+
# append the autocompletion block
|
|
54
|
+
rc_file.open("a").write("\n" + block)
|
|
55
|
+
print(f"✔️ Appended autocompletion block to {rc_file}")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def uninstall_autocompletion() -> str:
|
|
60
|
+
"""Remove previously installed autocompletion block from shell RC file."""
|
|
61
|
+
shell = Path(os.environ.get("SHELL", "")).name
|
|
62
|
+
home = Path.home()
|
|
63
|
+
|
|
64
|
+
# pick RC file based on shell
|
|
65
|
+
if shell == "zsh":
|
|
66
|
+
rc_file = home / ".zshrc"
|
|
67
|
+
elif shell == "bash":
|
|
68
|
+
rc_file = home / ".bash_profile" if (home / ".bash_profile").exists() else home / ".bashrc"
|
|
69
|
+
else:
|
|
70
|
+
rc_file = home / ".bashrc"
|
|
71
|
+
|
|
72
|
+
marker = "# >>> dar-backup autocompletion >>>"
|
|
73
|
+
end_marker = "# <<< dar-backup autocompletion <<<"
|
|
74
|
+
|
|
75
|
+
if not rc_file.exists():
|
|
76
|
+
print(f"❌ RC file not found: {rc_file}")
|
|
77
|
+
return
|
|
78
|
+
|
|
79
|
+
content = rc_file.read_text()
|
|
80
|
+
if marker not in content:
|
|
81
|
+
print(f"No autocompletion block found in {rc_file}")
|
|
82
|
+
return f"No autocompletion block found in {rc_file}" # for unit test
|
|
83
|
+
|
|
84
|
+
lines = content.splitlines(keepends=True)
|
|
85
|
+
new_lines = []
|
|
86
|
+
skipping = False
|
|
87
|
+
for line in lines:
|
|
88
|
+
if marker in line:
|
|
89
|
+
skipping = True
|
|
90
|
+
continue
|
|
91
|
+
if end_marker in line and skipping:
|
|
92
|
+
skipping = False
|
|
93
|
+
continue
|
|
94
|
+
if not skipping:
|
|
95
|
+
new_lines.append(line)
|
|
96
|
+
|
|
97
|
+
rc_file.write_text(''.join(new_lines))
|
|
98
|
+
print(f"✔️ Removed autocompletion block from {rc_file}")
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def run_installer(config_file: str, create_db_flag: bool, install_ac_flag: bool):
|
|
16
103
|
"""
|
|
17
104
|
Run the installation process for dar-backup using the given config file.
|
|
18
105
|
|
|
@@ -67,16 +154,34 @@ def run_installer(config_file: str, create_db_flag: bool):
|
|
|
67
154
|
print(f"❌ Failed to create catalog: {backup_def}")
|
|
68
155
|
|
|
69
156
|
|
|
157
|
+
|
|
70
158
|
def main():
|
|
71
159
|
parser = argparse.ArgumentParser(description="dar-backup installer")
|
|
72
|
-
parser.add_argument("--config", required=
|
|
160
|
+
parser.add_argument("--config", required=False, help="Path to config file")
|
|
73
161
|
parser.add_argument("--create-db", action="store_true", help="Create catalog databases")
|
|
74
|
-
|
|
162
|
+
group = parser.add_mutually_exclusive_group()
|
|
163
|
+
group.add_argument(
|
|
164
|
+
"--install-autocompletion", action="store_true",
|
|
165
|
+
help="Append shell-completion setup to your shell RC"
|
|
166
|
+
)
|
|
167
|
+
group.add_argument(
|
|
168
|
+
"--remove-autocompletion", action="store_true",
|
|
169
|
+
help="Remove shell-completion setup from your shell RC"
|
|
170
|
+
)
|
|
171
|
+
parser.add_argument(
|
|
172
|
+
"-v", "--version", action="version",
|
|
173
|
+
version=f"%(prog)s version {about.__version__}, {about.__license__}"
|
|
75
174
|
)
|
|
76
175
|
|
|
77
176
|
args = parser.parse_args()
|
|
78
177
|
|
|
79
|
-
|
|
178
|
+
|
|
179
|
+
if args.config:
|
|
180
|
+
run_installer(args.config, args.create_db)
|
|
181
|
+
elif args.install_autocompletion:
|
|
182
|
+
install_autocompletion()
|
|
183
|
+
elif args.remove_autocompletion:
|
|
184
|
+
uninstall_autocompletion()
|
|
80
185
|
|
|
81
186
|
|
|
82
187
|
if __name__ == "__main__":
|