dar-backup 1.0.2__py3-none-any.whl → 1.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dar_backup/__about__.py +1 -1
- dar_backup/cleanup.py +4 -6
- dar_backup/command_runner.py +48 -2
- dar_backup/config_settings.py +0 -1
- dar_backup/dar_backup.py +92 -39
- dar_backup/dar_backup_systemd.py +1 -1
- dar_backup/demo.py +1 -2
- dar_backup/manager.py +793 -11
- dar_backup/util.py +9 -8
- {dar_backup-1.0.2.dist-info → dar_backup-1.1.0.dist-info}/METADATA +243 -25
- dar_backup-1.1.0.dist-info/RECORD +23 -0
- dar_backup/Changelog.md +0 -430
- dar_backup/README.md +0 -2105
- dar_backup-1.0.2.dist-info/RECORD +0 -25
- {dar_backup-1.0.2.dist-info → dar_backup-1.1.0.dist-info}/WHEEL +0 -0
- {dar_backup-1.0.2.dist-info → dar_backup-1.1.0.dist-info}/entry_points.txt +0 -0
- {dar_backup-1.0.2.dist-info → dar_backup-1.1.0.dist-info}/licenses/LICENSE +0 -0
dar_backup/__about__.py
CHANGED
dar_backup/cleanup.py
CHANGED
|
@@ -16,10 +16,8 @@ This script removes old DIFF and INCR archives + accompanying .par2 files accord
|
|
|
16
16
|
|
|
17
17
|
import argcomplete
|
|
18
18
|
import argparse
|
|
19
|
-
import logging
|
|
20
19
|
import os
|
|
21
20
|
import re
|
|
22
|
-
import subprocess
|
|
23
21
|
import sys
|
|
24
22
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")))
|
|
25
23
|
|
|
@@ -29,7 +27,7 @@ from inputimeout import inputimeout, TimeoutOccurred
|
|
|
29
27
|
from pathlib import Path
|
|
30
28
|
from sys import stderr
|
|
31
29
|
from time import time
|
|
32
|
-
from typing import
|
|
30
|
+
from typing import List, NamedTuple, Tuple
|
|
33
31
|
import glob
|
|
34
32
|
|
|
35
33
|
|
|
@@ -147,7 +145,7 @@ def delete_old_backups(backup_dir, age, backup_type, args, backup_definition=Non
|
|
|
147
145
|
safe_remove_file(file_path, base_dir=Path(backup_dir))
|
|
148
146
|
logger.info(f"Deleted {backup_type} backup: {file_path}")
|
|
149
147
|
archive_name = filename.split('.')[0]
|
|
150
|
-
if not
|
|
148
|
+
if archive_name not in archives_deleted:
|
|
151
149
|
logger.debug(f"Archive name: '{archive_name}' added to catalog deletion list")
|
|
152
150
|
archives_deleted[archive_name] = True
|
|
153
151
|
except Exception as e:
|
|
@@ -209,7 +207,7 @@ def delete_catalog(catalog_name: str, args: NamedTuple) -> bool:
|
|
|
209
207
|
"""
|
|
210
208
|
Call `manager.py` to delete the specified catalog in it's database
|
|
211
209
|
"""
|
|
212
|
-
command = [
|
|
210
|
+
command = ["manager", "--remove-specific-archive", catalog_name, "--config-file", args.config_file, '--log-level', 'debug', '--log-stdout']
|
|
213
211
|
logger.info(f"Deleting catalog '{catalog_name}' using config file: '{args.config_file}'")
|
|
214
212
|
try:
|
|
215
213
|
result:CommandResult = runner.run(command)
|
|
@@ -366,7 +364,7 @@ def main():
|
|
|
366
364
|
logger.error(f"Alternate archive directory does not exist: {args.alternate_archive_dir}, exiting")
|
|
367
365
|
sys.exit(1)
|
|
368
366
|
if not os.path.isdir(args.alternate_archive_dir):
|
|
369
|
-
logger.error(
|
|
367
|
+
logger.error("Alternate archive directory is not a directory, exiting")
|
|
370
368
|
sys.exit(1)
|
|
371
369
|
config_settings.backup_dir = args.alternate_archive_dir
|
|
372
370
|
|
dar_backup/command_runner.py
CHANGED
|
@@ -13,6 +13,7 @@ try:
|
|
|
13
13
|
except ImportError:
|
|
14
14
|
termios = None
|
|
15
15
|
import tempfile
|
|
16
|
+
import time
|
|
16
17
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../src")))
|
|
17
18
|
from typing import List, Optional, Union
|
|
18
19
|
from dar_backup.util import get_logger
|
|
@@ -89,12 +90,21 @@ class CommandRunner:
|
|
|
89
90
|
):
|
|
90
91
|
self.logger = logger or get_logger()
|
|
91
92
|
self.command_logger = command_logger or get_logger(command_output_logger=True)
|
|
93
|
+
if default_timeout is not None:
|
|
94
|
+
try:
|
|
95
|
+
default_timeout = int(default_timeout)
|
|
96
|
+
except (TypeError, ValueError):
|
|
97
|
+
default_timeout = 30
|
|
98
|
+
if not isinstance(default_timeout, int):
|
|
99
|
+
default_timeout = 30
|
|
92
100
|
self.default_timeout = default_timeout
|
|
93
101
|
self.default_capture_limit_bytes = default_capture_limit_bytes
|
|
94
102
|
|
|
95
103
|
if not self.logger or not self.command_logger:
|
|
96
104
|
self.logger_fallback()
|
|
97
105
|
|
|
106
|
+
if self.default_timeout is not None and self.default_timeout <= 0:
|
|
107
|
+
self.default_timeout = None
|
|
98
108
|
|
|
99
109
|
def logger_fallback(self):
|
|
100
110
|
"""
|
|
@@ -138,7 +148,10 @@ class CommandRunner:
|
|
|
138
148
|
stdin: Optional[int] = subprocess.DEVNULL
|
|
139
149
|
) -> CommandResult:
|
|
140
150
|
self._text_mode = text
|
|
141
|
-
|
|
151
|
+
if timeout is None:
|
|
152
|
+
timeout = self.default_timeout
|
|
153
|
+
if timeout is not None and timeout <= 0:
|
|
154
|
+
timeout = None
|
|
142
155
|
if capture_output_limit_bytes is None:
|
|
143
156
|
capture_output_limit_bytes = self.default_capture_limit_bytes
|
|
144
157
|
if capture_output_limit_bytes is not None and capture_output_limit_bytes < 0:
|
|
@@ -199,6 +212,7 @@ class CommandRunner:
|
|
|
199
212
|
truncated_stderr = {"value": False}
|
|
200
213
|
|
|
201
214
|
try:
|
|
215
|
+
start_time = time.monotonic()
|
|
202
216
|
use_pipes = capture_output or log_output
|
|
203
217
|
process = subprocess.Popen(
|
|
204
218
|
cmd,
|
|
@@ -209,6 +223,18 @@ class CommandRunner:
|
|
|
209
223
|
bufsize=-1,
|
|
210
224
|
cwd=cwd
|
|
211
225
|
)
|
|
226
|
+
pid = getattr(process, "pid", None)
|
|
227
|
+
if log_output:
|
|
228
|
+
self.command_logger.debug(
|
|
229
|
+
"Process started pid=%s cwd=%s",
|
|
230
|
+
pid if pid is not None else "unknown",
|
|
231
|
+
cwd or os.getcwd(),
|
|
232
|
+
)
|
|
233
|
+
self.logger.debug(
|
|
234
|
+
"Process started pid=%s cwd=%s",
|
|
235
|
+
pid if pid is not None else "unknown",
|
|
236
|
+
cwd or os.getcwd(),
|
|
237
|
+
)
|
|
212
238
|
except Exception as e:
|
|
213
239
|
stack = traceback.format_exc()
|
|
214
240
|
return CommandResult(
|
|
@@ -287,7 +313,12 @@ class CommandRunner:
|
|
|
287
313
|
process.wait(timeout=timeout)
|
|
288
314
|
except subprocess.TimeoutExpired:
|
|
289
315
|
process.kill()
|
|
290
|
-
|
|
316
|
+
duration = time.monotonic() - start_time
|
|
317
|
+
pid = getattr(process, "pid", None)
|
|
318
|
+
log_msg = (
|
|
319
|
+
f"Command timed out after {timeout} seconds: {' '.join(cmd)} "
|
|
320
|
+
f"(pid={pid if pid is not None else 'unknown'}, elapsed={duration:.2f}s):\n"
|
|
321
|
+
)
|
|
291
322
|
self.logger.error(log_msg)
|
|
292
323
|
return CommandResult(-1, ''.join(stdout_lines), log_msg.join(stderr_lines))
|
|
293
324
|
except Exception as e:
|
|
@@ -298,6 +329,21 @@ class CommandRunner:
|
|
|
298
329
|
|
|
299
330
|
for t in threads:
|
|
300
331
|
t.join()
|
|
332
|
+
duration = time.monotonic() - start_time
|
|
333
|
+
pid = getattr(process, "pid", None)
|
|
334
|
+
if log_output:
|
|
335
|
+
self.command_logger.debug(
|
|
336
|
+
"Process finished pid=%s returncode=%s elapsed=%.2fs",
|
|
337
|
+
pid if pid is not None else "unknown",
|
|
338
|
+
process.returncode,
|
|
339
|
+
duration,
|
|
340
|
+
)
|
|
341
|
+
self.logger.debug(
|
|
342
|
+
"Process finished pid=%s returncode=%s elapsed=%.2fs",
|
|
343
|
+
pid if pid is not None else "unknown",
|
|
344
|
+
process.returncode,
|
|
345
|
+
duration,
|
|
346
|
+
)
|
|
301
347
|
|
|
302
348
|
if self._text_mode:
|
|
303
349
|
stdout_combined = ''.join(stdout_lines)
|
dar_backup/config_settings.py
CHANGED
dar_backup/dar_backup.py
CHANGED
|
@@ -27,12 +27,10 @@ import xml.etree.ElementTree as ET
|
|
|
27
27
|
import tempfile
|
|
28
28
|
import threading
|
|
29
29
|
|
|
30
|
-
from argparse import ArgumentParser
|
|
31
30
|
from datetime import datetime
|
|
32
31
|
from pathlib import Path
|
|
33
32
|
from sys import exit
|
|
34
33
|
from sys import stderr
|
|
35
|
-
from sys import argv
|
|
36
34
|
from sys import version_info
|
|
37
35
|
from time import time
|
|
38
36
|
from rich.console import Console
|
|
@@ -43,6 +41,7 @@ from . import __about__ as about
|
|
|
43
41
|
from dar_backup.config_settings import ConfigSettings
|
|
44
42
|
from dar_backup.util import list_backups
|
|
45
43
|
from dar_backup.util import setup_logging
|
|
44
|
+
from dar_backup.util import derive_trace_log_path
|
|
46
45
|
from dar_backup.util import get_logger
|
|
47
46
|
from dar_backup.util import BackupError
|
|
48
47
|
from dar_backup.util import RestoreError
|
|
@@ -54,14 +53,11 @@ from dar_backup.util import get_binary_info
|
|
|
54
53
|
from dar_backup.util import print_aligned_settings
|
|
55
54
|
from dar_backup.util import backup_definition_completer, list_archive_completer
|
|
56
55
|
from dar_backup.util import show_scriptname
|
|
57
|
-
from dar_backup.util import print_debug
|
|
58
56
|
from dar_backup.util import send_discord_message
|
|
59
57
|
|
|
60
58
|
from dar_backup.command_runner import CommandRunner
|
|
61
|
-
from dar_backup.command_runner import CommandResult
|
|
62
59
|
|
|
63
60
|
|
|
64
|
-
from argcomplete.completers import FilesCompleter
|
|
65
61
|
|
|
66
62
|
logger = None
|
|
67
63
|
runner = None
|
|
@@ -126,7 +122,7 @@ def generic_backup(type: str, command: List[str], backup_file: str, backup_defin
|
|
|
126
122
|
logger.error(f"Backup command failed: {e}")
|
|
127
123
|
raise BackupError(f"Backup command failed: {e}") from e
|
|
128
124
|
except Exception as e:
|
|
129
|
-
logger.exception(
|
|
125
|
+
logger.exception("Unexpected error during backup")
|
|
130
126
|
raise BackupError(f"Unexpected error during backup: {e}") from e
|
|
131
127
|
|
|
132
128
|
|
|
@@ -164,12 +160,37 @@ def find_files_with_paths(xml_doc: str):
|
|
|
164
160
|
return files_list
|
|
165
161
|
|
|
166
162
|
|
|
163
|
+
class DoctypeStripper:
|
|
164
|
+
"""
|
|
165
|
+
File-like wrapper that strips DOCTYPE lines to prevent XXE.
|
|
166
|
+
"""
|
|
167
|
+
def __init__(self, path):
|
|
168
|
+
self.f = open(path, "r", encoding="utf-8")
|
|
169
|
+
self.buf = ""
|
|
170
|
+
def read(self, n=-1):
|
|
171
|
+
if n is None or n < 0:
|
|
172
|
+
out = []
|
|
173
|
+
for line in self.f:
|
|
174
|
+
if "<!DOCTYPE" not in line:
|
|
175
|
+
out.append(line)
|
|
176
|
+
return "".join(out)
|
|
177
|
+
while len(self.buf) < n:
|
|
178
|
+
line = self.f.readline()
|
|
179
|
+
if not line:
|
|
180
|
+
break
|
|
181
|
+
if "<!DOCTYPE" not in line:
|
|
182
|
+
self.buf += line
|
|
183
|
+
result, self.buf = self.buf[:n], self.buf[n:]
|
|
184
|
+
return result
|
|
185
|
+
|
|
186
|
+
|
|
167
187
|
def iter_files_with_paths_from_xml(xml_path: str) -> Iterator[Tuple[str, str]]:
|
|
168
188
|
"""
|
|
169
189
|
Stream file paths and sizes from a DAR XML listing to keep memory usage low.
|
|
170
190
|
"""
|
|
171
191
|
path_stack: List[str] = []
|
|
172
|
-
|
|
192
|
+
# Disable XXE by stripping DOCTYPE
|
|
193
|
+
context = ET.iterparse(DoctypeStripper(xml_path), events=("start", "end"))
|
|
173
194
|
for event, elem in context:
|
|
174
195
|
if event == "start" and elem.tag == "Directory":
|
|
175
196
|
dir_name = elem.get("name")
|
|
@@ -433,7 +454,7 @@ def verify(args: argparse.Namespace, backup_file: str, backup_definition: str, c
|
|
|
433
454
|
logger.error(f"Failure: file '{restored_file_path}' did not match the original")
|
|
434
455
|
except PermissionError:
|
|
435
456
|
result = False
|
|
436
|
-
logger.exception(
|
|
457
|
+
logger.exception("Permission error while comparing files, continuing....")
|
|
437
458
|
logger.error("Exception details:", exc_info=True)
|
|
438
459
|
except FileNotFoundError as exc:
|
|
439
460
|
result = False
|
|
@@ -1118,7 +1139,8 @@ def generate_par2_files(backup_file: str, config_settings: ConfigSettings, args,
|
|
|
1118
1139
|
def filter_darrc_file(darrc_path):
|
|
1119
1140
|
"""
|
|
1120
1141
|
Filters the .darrc file to remove lines containing the options: -vt, -vs, -vd, -vf, and -va.
|
|
1121
|
-
The filtered version is stored in a uniquely named file
|
|
1142
|
+
The filtered version is stored in a uniquely named file alongside the source .darrc
|
|
1143
|
+
(or a writable temp directory if needed).
|
|
1122
1144
|
The file permissions are set to 440.
|
|
1123
1145
|
|
|
1124
1146
|
Params:
|
|
@@ -1133,29 +1155,36 @@ def filter_darrc_file(darrc_path):
|
|
|
1133
1155
|
# Define options to filter out
|
|
1134
1156
|
options_to_remove = {"-vt", "-vs", "-vd", "-vf", "-va"}
|
|
1135
1157
|
|
|
1136
|
-
|
|
1137
|
-
|
|
1158
|
+
candidate_dirs = [
|
|
1159
|
+
os.path.dirname(os.path.abspath(darrc_path)),
|
|
1160
|
+
os.path.expanduser("~"),
|
|
1161
|
+
tempfile.gettempdir(),
|
|
1162
|
+
]
|
|
1163
|
+
last_error = None
|
|
1138
1164
|
|
|
1139
|
-
|
|
1140
|
-
|
|
1165
|
+
for candidate_dir in candidate_dirs:
|
|
1166
|
+
filtered_darrc_path = os.path.join(
|
|
1167
|
+
candidate_dir,
|
|
1168
|
+
f"filtered_darrc_{next(tempfile._get_candidate_names())}.darrc",
|
|
1169
|
+
)
|
|
1170
|
+
try:
|
|
1171
|
+
with open(darrc_path, "r") as infile, open(filtered_darrc_path, "w") as outfile:
|
|
1172
|
+
for line in infile:
|
|
1173
|
+
# Check if any unwanted option is in the line
|
|
1174
|
+
if not any(option in line for option in options_to_remove):
|
|
1175
|
+
outfile.write(line)
|
|
1141
1176
|
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
for line in infile:
|
|
1145
|
-
# Check if any unwanted option is in the line
|
|
1146
|
-
if not any(option in line for option in options_to_remove):
|
|
1147
|
-
outfile.write(line)
|
|
1148
|
-
|
|
1149
|
-
# Set file permissions to 440 (read-only for owner and group, no permissions for others)
|
|
1150
|
-
os.chmod(filtered_darrc_path, 0o440)
|
|
1177
|
+
# Set file permissions to 440 (read-only for owner and group, no permissions for others)
|
|
1178
|
+
os.chmod(filtered_darrc_path, 0o440)
|
|
1151
1179
|
|
|
1152
|
-
|
|
1180
|
+
return filtered_darrc_path
|
|
1153
1181
|
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1182
|
+
except Exception as e:
|
|
1183
|
+
last_error = e
|
|
1184
|
+
if os.path.exists(filtered_darrc_path):
|
|
1185
|
+
os.remove(filtered_darrc_path)
|
|
1186
|
+
|
|
1187
|
+
raise RuntimeError(f"Error filtering .darrc file: {last_error}")
|
|
1159
1188
|
|
|
1160
1189
|
|
|
1161
1190
|
|
|
@@ -1257,16 +1286,35 @@ def print_markdown(source: str, from_string: bool = False, pretty: bool = True):
|
|
|
1257
1286
|
|
|
1258
1287
|
|
|
1259
1288
|
|
|
1289
|
+
def _resolve_doc_path(path: Optional[str], filename: str) -> Path:
|
|
1290
|
+
if path:
|
|
1291
|
+
return Path(path)
|
|
1292
|
+
|
|
1293
|
+
candidates = [
|
|
1294
|
+
Path.cwd() / "src" / "dar_backup" / filename,
|
|
1295
|
+
Path(__file__).parent / filename,
|
|
1296
|
+
]
|
|
1297
|
+
|
|
1298
|
+
try:
|
|
1299
|
+
candidates.append(Path(__file__).resolve().parents[2] / filename)
|
|
1300
|
+
except IndexError:
|
|
1301
|
+
pass
|
|
1302
|
+
|
|
1303
|
+
for candidate in candidates:
|
|
1304
|
+
if candidate.exists():
|
|
1305
|
+
return candidate
|
|
1306
|
+
|
|
1307
|
+
return candidates[0]
|
|
1308
|
+
|
|
1309
|
+
|
|
1260
1310
|
def print_changelog(path: str = None, pretty: bool = True):
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
print_markdown(str(path), pretty=pretty)
|
|
1311
|
+
resolved_path = _resolve_doc_path(path, "Changelog.md")
|
|
1312
|
+
print_markdown(str(resolved_path), pretty=pretty)
|
|
1264
1313
|
|
|
1265
1314
|
|
|
1266
1315
|
def print_readme(path: str = None, pretty: bool = True):
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
print_markdown(str(path), pretty=pretty)
|
|
1316
|
+
resolved_path = _resolve_doc_path(path, "README.md")
|
|
1317
|
+
print_markdown(str(resolved_path), pretty=pretty)
|
|
1270
1318
|
|
|
1271
1319
|
def list_definitions(backup_d_dir: str) -> List[str]:
|
|
1272
1320
|
"""
|
|
@@ -1454,6 +1502,7 @@ def main():
|
|
|
1454
1502
|
if command_output_log == config_settings.logfile_location:
|
|
1455
1503
|
print(f"Error: logfile_location in {args.config_file} does not end at 'dar-backup.log', exiting", file=stderr)
|
|
1456
1504
|
|
|
1505
|
+
trace_log_file = derive_trace_log_path(config_settings.logfile_location)
|
|
1457
1506
|
logger = setup_logging(
|
|
1458
1507
|
config_settings.logfile_location,
|
|
1459
1508
|
command_output_log,
|
|
@@ -1461,6 +1510,7 @@ def main():
|
|
|
1461
1510
|
args.log_stdout,
|
|
1462
1511
|
logfile_max_bytes=config_settings.logfile_max_bytes,
|
|
1463
1512
|
logfile_backup_count=config_settings.logfile_backup_count,
|
|
1513
|
+
trace_log_file=trace_log_file,
|
|
1464
1514
|
trace_log_max_bytes=getattr(config_settings, "trace_log_max_bytes", 10485760),
|
|
1465
1515
|
trace_log_backup_count=getattr(config_settings, "trace_log_backup_count", 1)
|
|
1466
1516
|
)
|
|
@@ -1474,6 +1524,8 @@ def main():
|
|
|
1474
1524
|
clean_restore_test_directory(config_settings)
|
|
1475
1525
|
|
|
1476
1526
|
|
|
1527
|
+
filtered_darrc_path = None
|
|
1528
|
+
|
|
1477
1529
|
try:
|
|
1478
1530
|
if not args.darrc:
|
|
1479
1531
|
current_script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
@@ -1488,7 +1540,8 @@ def main():
|
|
|
1488
1540
|
|
|
1489
1541
|
if args.suppress_dar_msg:
|
|
1490
1542
|
logger.info("Suppressing dar messages, do not use options: -vt, -vs, -vd, -vf, -va")
|
|
1491
|
-
|
|
1543
|
+
filtered_darrc_path = filter_darrc_file(args.darrc)
|
|
1544
|
+
args.darrc = filtered_darrc_path
|
|
1492
1545
|
logger.debug(f"Filtered .darrc file: {args.darrc}")
|
|
1493
1546
|
|
|
1494
1547
|
start_msgs: List[Tuple[str, str]] = []
|
|
@@ -1522,6 +1575,7 @@ def main():
|
|
|
1522
1575
|
args.verbose and start_msgs.append(("Restore dir:", restore_dir))
|
|
1523
1576
|
|
|
1524
1577
|
args.verbose and start_msgs.append(("Logfile location:", config_settings.logfile_location))
|
|
1578
|
+
args.verbose and start_msgs.append(("Trace log:", trace_log_file))
|
|
1525
1579
|
args.verbose and start_msgs.append(("Logfile max size (bytes):", config_settings.logfile_max_bytes))
|
|
1526
1580
|
args.verbose and start_msgs.append(("Logfile backup count:", config_settings.logfile_backup_count))
|
|
1527
1581
|
|
|
@@ -1611,10 +1665,9 @@ def main():
|
|
|
1611
1665
|
end_time=int(time())
|
|
1612
1666
|
logger.info(f"END TIME: {end_time}")
|
|
1613
1667
|
# Clean up
|
|
1614
|
-
if
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
logger.debug(f"Removed filtered .darrc: {args.darrc}")
|
|
1668
|
+
if filtered_darrc_path and os.path.exists(filtered_darrc_path):
|
|
1669
|
+
os.remove(filtered_darrc_path)
|
|
1670
|
+
logger.debug(f"Removed filtered .darrc: {filtered_darrc_path}")
|
|
1618
1671
|
|
|
1619
1672
|
|
|
1620
1673
|
# Determine exit code
|
dar_backup/dar_backup_systemd.py
CHANGED
|
@@ -100,7 +100,7 @@ def write_unit_files(venv, dar_path, install=False):
|
|
|
100
100
|
|
|
101
101
|
write_unit_file(output_path, "dar-cleanup.service", generate_cleanup_service(venv, dar_path))
|
|
102
102
|
write_unit_file(output_path, "dar-cleanup.timer", CLEANUP_TIMER)
|
|
103
|
-
print(
|
|
103
|
+
print(" → Fires on: *-*-* 21:07:00")
|
|
104
104
|
|
|
105
105
|
if install:
|
|
106
106
|
for mode in FLAGS:
|
dar_backup/demo.py
CHANGED
|
@@ -19,7 +19,6 @@ User can set ROOT_DIR, DIR_TO_BACKUP and BACKUP_DIR (destination for backups) vi
|
|
|
19
19
|
|
|
20
20
|
import argparse
|
|
21
21
|
import os
|
|
22
|
-
import shutil
|
|
23
22
|
import sys
|
|
24
23
|
|
|
25
24
|
from . import __about__ as about
|
|
@@ -201,7 +200,7 @@ def main():
|
|
|
201
200
|
Path(vars_map["TEST_RESTORE_DIR"]).mkdir(parents=True, exist_ok=True)
|
|
202
201
|
Path(vars_map["CONFIG_DIR"]).mkdir(parents=True, exist_ok=True)
|
|
203
202
|
Path(vars_map["BACKUP_D_DIR"]).mkdir(parents=True, exist_ok=True)
|
|
204
|
-
print(
|
|
203
|
+
print("Directories created.")
|
|
205
204
|
|
|
206
205
|
generate_file(args, "demo_backup_def.j2", Path(vars_map["BACKUP_D_DIR"]).joinpath("demo"), vars_map, opts_dict)
|
|
207
206
|
generate_file(args, "dar-backup.conf.j2", Path(vars_map["CONFIG_DIR"]).joinpath("dar-backup.conf"), vars_map, opts_dict)
|