dar-backup 1.0.1__py3-none-any.whl → 1.0.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dar_backup/Changelog.md +29 -0
- dar_backup/README.md +75 -15
- dar_backup/__about__.py +1 -2
- dar_backup/clean_log.py +102 -63
- dar_backup/cleanup.py +136 -102
- dar_backup/command_runner.py +75 -13
- dar_backup/config_settings.py +25 -11
- dar_backup/dar-backup.conf +7 -0
- dar_backup/dar-backup.conf.j2 +3 -1
- dar_backup/dar_backup.py +438 -64
- dar_backup/demo.py +18 -9
- dar_backup/installer.py +18 -1
- dar_backup/manager.py +295 -88
- dar_backup/util.py +119 -11
- {dar_backup-1.0.1.dist-info → dar_backup-1.0.2.dist-info}/METADATA +78 -18
- dar_backup-1.0.2.dist-info/RECORD +25 -0
- dar_backup-1.0.1.dist-info/RECORD +0 -25
- {dar_backup-1.0.1.dist-info → dar_backup-1.0.2.dist-info}/WHEEL +0 -0
- {dar_backup-1.0.1.dist-info → dar_backup-1.0.2.dist-info}/entry_points.txt +0 -0
- {dar_backup-1.0.1.dist-info → dar_backup-1.0.2.dist-info}/licenses/LICENSE +0 -0
dar_backup/demo.py
CHANGED
|
@@ -37,17 +37,22 @@ DAR_BACKUP_DIR = util.normalize_dir(util.expand_path("~/dar-backup"))
|
|
|
37
37
|
|
|
38
38
|
def check_directories(args, vars_map: Dict[str,str]) -> bool:
|
|
39
39
|
"""
|
|
40
|
-
Check if
|
|
40
|
+
Check if target paths already exist and are directories.
|
|
41
41
|
|
|
42
42
|
Returns:
|
|
43
|
-
bool: True if
|
|
43
|
+
bool: True if it is safe to proceed, False otherwise.
|
|
44
44
|
"""
|
|
45
45
|
result = True
|
|
46
46
|
for key in ("DAR_BACKUP_DIR","BACKUP_DIR","TEST_RESTORE_DIR","CONFIG_DIR","BACKUP_D_DIR"):
|
|
47
47
|
path = Path(vars_map[key])
|
|
48
|
-
if path.exists()
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
if path.exists():
|
|
49
|
+
if not path.is_dir():
|
|
50
|
+
print(f"Error: '{path}' exists and is not a directory")
|
|
51
|
+
result = False
|
|
52
|
+
continue
|
|
53
|
+
if not args.override:
|
|
54
|
+
print(f"Directory '{path}' already exists")
|
|
55
|
+
result = False
|
|
51
56
|
return result
|
|
52
57
|
|
|
53
58
|
|
|
@@ -72,12 +77,17 @@ def generate_file(args, template: str, file_path: Path, vars_map: Dict[str, str]
|
|
|
72
77
|
if rendered is None:
|
|
73
78
|
print(f"Error: Template '{template}' could not be rendered.")
|
|
74
79
|
return False
|
|
75
|
-
if
|
|
76
|
-
|
|
77
|
-
|
|
80
|
+
if file_path.exists():
|
|
81
|
+
if file_path.is_dir():
|
|
82
|
+
print(f"Error: '{file_path}' is a directory, expected a file.")
|
|
83
|
+
return False
|
|
84
|
+
if not args.override:
|
|
85
|
+
print(f"Error: File '{file_path}' already exists. Use --override to overwrite.")
|
|
86
|
+
return False
|
|
78
87
|
file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
79
88
|
file_path.write_text(rendered)
|
|
80
89
|
print(f"File generated at '{file_path}'")
|
|
90
|
+
return True
|
|
81
91
|
|
|
82
92
|
|
|
83
93
|
|
|
@@ -152,7 +162,6 @@ def main():
|
|
|
152
162
|
parser.error(
|
|
153
163
|
"Options --root-dir, --dir-to-backup must all be specified together."
|
|
154
164
|
)
|
|
155
|
-
exit(1)
|
|
156
165
|
|
|
157
166
|
args.root_dir = util.normalize_dir(util.expand_path(args.root_dir)) if args.root_dir else None
|
|
158
167
|
args.backup_dir = util.normalize_dir(util.expand_path(args.backup_dir)) if args.backup_dir else None
|
dar_backup/installer.py
CHANGED
|
@@ -43,6 +43,9 @@ def install_autocompletion():
|
|
|
43
43
|
|
|
44
44
|
# ensure RC file and parent directory exist
|
|
45
45
|
rc_file.parent.mkdir(parents=True, exist_ok=True)
|
|
46
|
+
if rc_file.exists() and rc_file.is_dir():
|
|
47
|
+
print(f"Error: RC path is a directory: {rc_file}")
|
|
48
|
+
return
|
|
46
49
|
if not rc_file.exists():
|
|
47
50
|
rc_file.touch()
|
|
48
51
|
|
|
@@ -76,11 +79,17 @@ def uninstall_autocompletion() -> str:
|
|
|
76
79
|
if not rc_file.exists():
|
|
77
80
|
print(f"❌ RC file not found: {rc_file}")
|
|
78
81
|
return
|
|
82
|
+
if rc_file.is_dir():
|
|
83
|
+
print(f"Error: RC path is a directory: {rc_file}")
|
|
84
|
+
return
|
|
79
85
|
|
|
80
86
|
content = rc_file.read_text()
|
|
81
87
|
if marker not in content:
|
|
82
88
|
print(f"No autocompletion block found in {rc_file}")
|
|
83
89
|
return f"No autocompletion block found in {rc_file}" # for unit test
|
|
90
|
+
if end_marker not in content:
|
|
91
|
+
print(f"Error: Autocompletion end marker not found in {rc_file}")
|
|
92
|
+
return f"Autocompletion end marker not found in {rc_file}"
|
|
84
93
|
|
|
85
94
|
lines = content.splitlines(keepends=True)
|
|
86
95
|
new_lines = []
|
|
@@ -128,7 +137,11 @@ def run_installer(config_file: str, create_db_flag: bool):
|
|
|
128
137
|
log_to_stdout=True,
|
|
129
138
|
)
|
|
130
139
|
command_logger = get_logger(command_output_logger=True)
|
|
131
|
-
runner = CommandRunner(
|
|
140
|
+
runner = CommandRunner(
|
|
141
|
+
logger=logger,
|
|
142
|
+
command_logger=command_logger,
|
|
143
|
+
default_capture_limit_bytes=getattr(config_settings, "command_capture_max_bytes", None)
|
|
144
|
+
)
|
|
132
145
|
|
|
133
146
|
|
|
134
147
|
# Create required directories
|
|
@@ -151,6 +164,10 @@ def run_installer(config_file: str, create_db_flag: bool):
|
|
|
151
164
|
# Optionally create databases for all backup definitions
|
|
152
165
|
if create_db_flag:
|
|
153
166
|
for file in os.listdir(config_settings.backup_d_dir):
|
|
167
|
+
file_path = os.path.join(config_settings.backup_d_dir, file)
|
|
168
|
+
if not os.path.isfile(file_path):
|
|
169
|
+
logger.info(f"Skipping non-file backup definition: {file}")
|
|
170
|
+
continue
|
|
154
171
|
backup_def = os.path.basename(file)
|
|
155
172
|
print(f"Creating catalog for: {backup_def}")
|
|
156
173
|
result = create_db(backup_def, config_settings, logger, runner)
|
dar_backup/manager.py
CHANGED
|
@@ -27,6 +27,8 @@ import os
|
|
|
27
27
|
import re
|
|
28
28
|
import sys
|
|
29
29
|
import subprocess
|
|
30
|
+
import threading
|
|
31
|
+
import shlex
|
|
30
32
|
|
|
31
33
|
from inputimeout import inputimeout, TimeoutOccurred
|
|
32
34
|
|
|
@@ -36,6 +38,7 @@ from dar_backup.config_settings import ConfigSettings
|
|
|
36
38
|
from dar_backup.util import setup_logging
|
|
37
39
|
from dar_backup.util import CommandResult
|
|
38
40
|
from dar_backup.util import get_config_file
|
|
41
|
+
from dar_backup.util import send_discord_message
|
|
39
42
|
from dar_backup.util import get_logger
|
|
40
43
|
from dar_backup.util import get_binary_info
|
|
41
44
|
from dar_backup.util import show_version
|
|
@@ -62,6 +65,25 @@ logger = None
|
|
|
62
65
|
runner = None
|
|
63
66
|
|
|
64
67
|
|
|
68
|
+
def _open_command_log(command: List[str]):
|
|
69
|
+
command_logger = get_logger(command_output_logger=True)
|
|
70
|
+
log_path = None
|
|
71
|
+
for handler in getattr(command_logger, "handlers", []):
|
|
72
|
+
if hasattr(handler, "baseFilename"):
|
|
73
|
+
log_path = handler.baseFilename
|
|
74
|
+
break
|
|
75
|
+
if not log_path:
|
|
76
|
+
return None, None
|
|
77
|
+
log_file = open(log_path, "ab")
|
|
78
|
+
header = (
|
|
79
|
+
f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - COMMAND: "
|
|
80
|
+
f"{' '.join(map(shlex.quote, command))}\n"
|
|
81
|
+
).encode("utf-8", errors="replace")
|
|
82
|
+
log_file.write(header)
|
|
83
|
+
log_file.flush()
|
|
84
|
+
return log_file, threading.Lock()
|
|
85
|
+
|
|
86
|
+
|
|
65
87
|
def get_db_dir(config_settings: ConfigSettings) -> str:
|
|
66
88
|
"""
|
|
67
89
|
Return the correct directory for storing catalog databases.
|
|
@@ -131,24 +153,106 @@ def list_catalogs(backup_def: str, config_settings: ConfigSettings, suppress_out
|
|
|
131
153
|
return CommandResult(1, '', error_msg)
|
|
132
154
|
|
|
133
155
|
command = ['dar_manager', '--base', database_path, '--list']
|
|
134
|
-
|
|
135
|
-
|
|
156
|
+
if runner is not None and not hasattr(runner, "default_capture_limit_bytes"):
|
|
157
|
+
process = runner.run(command, capture_output_limit_bytes=-1)
|
|
158
|
+
stdout, stderr = process.stdout, process.stderr
|
|
136
159
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
line
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
160
|
+
if process.returncode != 0:
|
|
161
|
+
logger.error(f'Error listing catalogs for: "{database_path}"')
|
|
162
|
+
logger.error(f"stderr: {stderr}")
|
|
163
|
+
logger.error(f"stdout: {stdout}")
|
|
164
|
+
return process
|
|
165
|
+
|
|
166
|
+
# Extract only archive basenames from stdout
|
|
167
|
+
archive_names = []
|
|
168
|
+
archive_lines = []
|
|
169
|
+
for line in stdout.splitlines():
|
|
170
|
+
line = line.strip()
|
|
171
|
+
if not line or "archive #" in line or "dar path" in line or "compression" in line:
|
|
172
|
+
continue
|
|
173
|
+
parts = line.split("\t")
|
|
174
|
+
if len(parts) >= 3:
|
|
175
|
+
archive_names.append(parts[2].strip())
|
|
176
|
+
archive_lines.append(line)
|
|
177
|
+
else:
|
|
178
|
+
stderr_lines: List[str] = []
|
|
179
|
+
stderr_bytes = 0
|
|
180
|
+
cap = getattr(config_settings, "command_capture_max_bytes", None)
|
|
181
|
+
if not isinstance(cap, int):
|
|
182
|
+
cap = None
|
|
183
|
+
log_file, log_lock = _open_command_log(command)
|
|
184
|
+
|
|
185
|
+
process = subprocess.Popen(
|
|
186
|
+
command,
|
|
187
|
+
stdout=subprocess.PIPE,
|
|
188
|
+
stderr=subprocess.PIPE,
|
|
189
|
+
text=False,
|
|
190
|
+
bufsize=0
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
def read_stderr():
|
|
194
|
+
nonlocal stderr_bytes
|
|
195
|
+
if process.stderr is None:
|
|
196
|
+
return
|
|
197
|
+
while True:
|
|
198
|
+
chunk = process.stderr.read(1024)
|
|
199
|
+
if not chunk:
|
|
200
|
+
break
|
|
201
|
+
if log_file:
|
|
202
|
+
with log_lock:
|
|
203
|
+
log_file.write(chunk)
|
|
204
|
+
log_file.flush()
|
|
205
|
+
if cap is None:
|
|
206
|
+
stderr_lines.append(chunk)
|
|
207
|
+
elif cap > 0 and stderr_bytes < cap:
|
|
208
|
+
remaining = cap - stderr_bytes
|
|
209
|
+
if len(chunk) <= remaining:
|
|
210
|
+
stderr_lines.append(chunk)
|
|
211
|
+
stderr_bytes += len(chunk)
|
|
212
|
+
else:
|
|
213
|
+
stderr_lines.append(chunk[:remaining])
|
|
214
|
+
stderr_bytes = cap
|
|
215
|
+
|
|
216
|
+
stderr_thread = threading.Thread(target=read_stderr)
|
|
217
|
+
stderr_thread.start()
|
|
218
|
+
|
|
219
|
+
archive_names = []
|
|
220
|
+
archive_lines = []
|
|
221
|
+
if process.stdout is not None:
|
|
222
|
+
buffer = b""
|
|
223
|
+
while True:
|
|
224
|
+
chunk = process.stdout.read(1024)
|
|
225
|
+
if not chunk:
|
|
226
|
+
break
|
|
227
|
+
if log_file:
|
|
228
|
+
with log_lock:
|
|
229
|
+
log_file.write(chunk)
|
|
230
|
+
buffer += chunk
|
|
231
|
+
while b"\n" in buffer:
|
|
232
|
+
line, buffer = buffer.split(b"\n", 1)
|
|
233
|
+
stripped = line.strip()
|
|
234
|
+
if not stripped:
|
|
235
|
+
continue
|
|
236
|
+
decoded = stripped.decode("utf-8", errors="replace")
|
|
237
|
+
if "archive #" in decoded or "dar path" in decoded or "compression" in decoded:
|
|
238
|
+
continue
|
|
239
|
+
parts = decoded.split("\t")
|
|
240
|
+
if len(parts) >= 3:
|
|
241
|
+
archive_names.append(parts[2].strip())
|
|
242
|
+
archive_lines.append(decoded)
|
|
243
|
+
process.stdout.close()
|
|
244
|
+
|
|
245
|
+
process.wait()
|
|
246
|
+
stderr_thread.join()
|
|
247
|
+
if log_file:
|
|
248
|
+
log_file.close()
|
|
249
|
+
|
|
250
|
+
if process.returncode != 0:
|
|
251
|
+
logger.error(f'Error listing catalogs for: "{database_path}"')
|
|
252
|
+
stderr_text = "".join(stderr_lines)
|
|
253
|
+
if stderr_text:
|
|
254
|
+
logger.error(f"stderr: {stderr_text}")
|
|
255
|
+
return CommandResult(process.returncode, "", stderr_text)
|
|
152
256
|
|
|
153
257
|
# Sort by prefix and date
|
|
154
258
|
def extract_date(arch_name):
|
|
@@ -167,7 +271,7 @@ def list_catalogs(backup_def: str, config_settings: ConfigSettings, suppress_out
|
|
|
167
271
|
for name in archive_names:
|
|
168
272
|
print(name)
|
|
169
273
|
|
|
170
|
-
return
|
|
274
|
+
return CommandResult(0, "\n".join(archive_lines), "")
|
|
171
275
|
|
|
172
276
|
|
|
173
277
|
def cat_no_for_name(archive: str, config_settings: ConfigSettings) -> int:
|
|
@@ -184,9 +288,7 @@ def cat_no_for_name(archive: str, config_settings: ConfigSettings) -> int:
|
|
|
184
288
|
if process.returncode != 0:
|
|
185
289
|
logger.error(f"Error listing catalogs for backup def: '{backup_def}'")
|
|
186
290
|
return -1
|
|
187
|
-
line_no = 1
|
|
188
291
|
for line in process.stdout.splitlines():
|
|
189
|
-
line_no += 1
|
|
190
292
|
search = re.search(rf".*?(\d+)\s+.*?({archive}).*", line)
|
|
191
293
|
if search:
|
|
192
294
|
logger.info(f"Found archive: '{archive}', catalog #: '{search.group(1)}'")
|
|
@@ -215,26 +317,104 @@ def list_archive_contents(archive: str, config_settings: ConfigSettings) -> int:
|
|
|
215
317
|
|
|
216
318
|
|
|
217
319
|
command = ['dar_manager', '--base', database_path, '-u', f"{cat_no}"]
|
|
218
|
-
|
|
320
|
+
if runner is not None and not hasattr(runner, "default_capture_limit_bytes"):
|
|
321
|
+
process = runner.run(command, timeout=10)
|
|
322
|
+
stdout = process.stdout or ""
|
|
323
|
+
stderr = process.stderr or ""
|
|
324
|
+
if process.returncode != 0:
|
|
325
|
+
logger.error(f'Error listing catalogs for: "{database_path}"')
|
|
326
|
+
logger.error(f"stderr: {stderr}")
|
|
327
|
+
logger.error(f"stdout: {stdout}")
|
|
328
|
+
return process.returncode
|
|
219
329
|
|
|
330
|
+
combined_lines = (stdout + "\n" + stderr).splitlines()
|
|
331
|
+
file_lines = [line for line in combined_lines if line.strip().startswith("[ Saved ]")]
|
|
220
332
|
|
|
221
|
-
|
|
222
|
-
|
|
333
|
+
if file_lines:
|
|
334
|
+
for line in file_lines:
|
|
335
|
+
print(line)
|
|
336
|
+
else:
|
|
337
|
+
print(f"[info] Archive '{archive}' is empty.")
|
|
338
|
+
|
|
339
|
+
return process.returncode
|
|
340
|
+
|
|
341
|
+
stderr_lines: List[str] = []
|
|
342
|
+
stderr_bytes = 0
|
|
343
|
+
cap = getattr(config_settings, "command_capture_max_bytes", None)
|
|
344
|
+
log_file, log_lock = _open_command_log(command)
|
|
345
|
+
|
|
346
|
+
process = subprocess.Popen(
|
|
347
|
+
command,
|
|
348
|
+
stdout=subprocess.PIPE,
|
|
349
|
+
stderr=subprocess.PIPE,
|
|
350
|
+
text=False,
|
|
351
|
+
bufsize=0
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
def read_stderr():
|
|
355
|
+
nonlocal stderr_bytes
|
|
356
|
+
if process.stderr is None:
|
|
357
|
+
return
|
|
358
|
+
while True:
|
|
359
|
+
chunk = process.stderr.read(1024)
|
|
360
|
+
if not chunk:
|
|
361
|
+
break
|
|
362
|
+
if log_file:
|
|
363
|
+
with log_lock:
|
|
364
|
+
log_file.write(chunk)
|
|
365
|
+
log_file.flush()
|
|
366
|
+
if cap is None:
|
|
367
|
+
stderr_lines.append(chunk)
|
|
368
|
+
elif cap > 0 and stderr_bytes < cap:
|
|
369
|
+
remaining = cap - stderr_bytes
|
|
370
|
+
if len(chunk) <= remaining:
|
|
371
|
+
stderr_lines.append(chunk)
|
|
372
|
+
stderr_bytes += len(chunk)
|
|
373
|
+
else:
|
|
374
|
+
stderr_lines.append(chunk[:remaining])
|
|
375
|
+
stderr_bytes = cap
|
|
376
|
+
|
|
377
|
+
stderr_thread = threading.Thread(target=read_stderr)
|
|
378
|
+
stderr_thread.start()
|
|
379
|
+
|
|
380
|
+
found = False
|
|
381
|
+
if process.stdout is not None:
|
|
382
|
+
buffer = b""
|
|
383
|
+
while True:
|
|
384
|
+
chunk = process.stdout.read(1024)
|
|
385
|
+
if not chunk:
|
|
386
|
+
break
|
|
387
|
+
if log_file:
|
|
388
|
+
with log_lock:
|
|
389
|
+
log_file.write(chunk)
|
|
390
|
+
buffer += chunk
|
|
391
|
+
while b"\n" in buffer:
|
|
392
|
+
line, buffer = buffer.split(b"\n", 1)
|
|
393
|
+
if line.strip().startswith(b"[ Saved ]"):
|
|
394
|
+
print(line.decode("utf-8", errors="replace"))
|
|
395
|
+
found = True
|
|
396
|
+
process.stdout.close()
|
|
223
397
|
|
|
398
|
+
try:
|
|
399
|
+
process.wait(timeout=10)
|
|
400
|
+
except subprocess.TimeoutExpired:
|
|
401
|
+
process.kill()
|
|
402
|
+
stderr_thread.join()
|
|
403
|
+
logger.error(f"Timeout listing contents of archive: '{archive}'")
|
|
404
|
+
return 1
|
|
405
|
+
|
|
406
|
+
stderr_thread.join()
|
|
407
|
+
if log_file:
|
|
408
|
+
log_file.close()
|
|
224
409
|
|
|
225
410
|
if process.returncode != 0:
|
|
226
411
|
logger.error(f'Error listing catalogs for: "{database_path}"')
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
combined_lines = (stdout + "\n" + stderr).splitlines()
|
|
232
|
-
file_lines = [line for line in combined_lines if line.strip().startswith("[ Saved ]")]
|
|
412
|
+
stderr_text = "".join(stderr_lines)
|
|
413
|
+
if stderr_text:
|
|
414
|
+
logger.error(f"stderr: {stderr_text}")
|
|
415
|
+
return process.returncode
|
|
233
416
|
|
|
234
|
-
if
|
|
235
|
-
for line in file_lines:
|
|
236
|
-
print(line)
|
|
237
|
-
else:
|
|
417
|
+
if not found:
|
|
238
418
|
print(f"[info] Archive '{archive}' is empty.")
|
|
239
419
|
|
|
240
420
|
return process.returncode
|
|
@@ -252,7 +432,7 @@ def list_catalog_contents(catalog_number: int, backup_def: str, config_settings:
|
|
|
252
432
|
logger.error(f'Catalog database not found: "{database_path}"')
|
|
253
433
|
return 1
|
|
254
434
|
command = ['dar_manager', '--base', database_path, '-u', f"{catalog_number}"]
|
|
255
|
-
process = runner.run(command)
|
|
435
|
+
process = runner.run(command, capture_output_limit_bytes=-1)
|
|
256
436
|
stdout, stderr = process.stdout, process.stderr
|
|
257
437
|
if process.returncode != 0:
|
|
258
438
|
logger.error(f'Error listing catalogs for: "{database_path}"')
|
|
@@ -273,7 +453,7 @@ def find_file(file, backup_def, config_settings):
|
|
|
273
453
|
logger.error(f'Database not found: "{database_path}"')
|
|
274
454
|
return 1
|
|
275
455
|
command = ['dar_manager', '--base', database_path, '-f', f"{file}"]
|
|
276
|
-
process = runner.run(command)
|
|
456
|
+
process = runner.run(command, capture_output_limit_bytes=-1)
|
|
277
457
|
stdout, stderr = process.stdout, process.stderr
|
|
278
458
|
if process.returncode != 0:
|
|
279
459
|
logger.error(f'Error finding file: {file} in: "{database_path}"')
|
|
@@ -544,7 +724,14 @@ def main():
|
|
|
544
724
|
raise SystemExit(127)
|
|
545
725
|
args.config_file = config_settings_path
|
|
546
726
|
|
|
547
|
-
|
|
727
|
+
try:
|
|
728
|
+
config_settings = ConfigSettings(args.config_file)
|
|
729
|
+
except Exception as exc:
|
|
730
|
+
msg = f"Config error: {exc}"
|
|
731
|
+
print(msg, file=stderr)
|
|
732
|
+
ts = datetime.now().strftime("%Y-%m-%d_%H:%M")
|
|
733
|
+
send_discord_message(f"{ts} - manager: FAILURE - {msg}")
|
|
734
|
+
sys.exit(127)
|
|
548
735
|
|
|
549
736
|
if not os.path.dirname(config_settings.logfile_location):
|
|
550
737
|
print(f"Directory for log file '{config_settings.logfile_location}' does not exist, exiting")
|
|
@@ -552,9 +739,22 @@ def main():
|
|
|
552
739
|
return
|
|
553
740
|
|
|
554
741
|
command_output_log = config_settings.logfile_location.replace("dar-backup.log", "dar-backup-commands.log")
|
|
555
|
-
logger = setup_logging(
|
|
742
|
+
logger = setup_logging(
|
|
743
|
+
config_settings.logfile_location,
|
|
744
|
+
command_output_log,
|
|
745
|
+
args.log_level,
|
|
746
|
+
args.log_stdout,
|
|
747
|
+
logfile_max_bytes=config_settings.logfile_max_bytes,
|
|
748
|
+
logfile_backup_count=config_settings.logfile_backup_count,
|
|
749
|
+
trace_log_max_bytes=getattr(config_settings, "trace_log_max_bytes", 10485760),
|
|
750
|
+
trace_log_backup_count=getattr(config_settings, "trace_log_backup_count", 1)
|
|
751
|
+
)
|
|
556
752
|
command_logger = get_logger(command_output_logger=True)
|
|
557
|
-
runner = CommandRunner(
|
|
753
|
+
runner = CommandRunner(
|
|
754
|
+
logger=logger,
|
|
755
|
+
command_logger=command_logger,
|
|
756
|
+
default_capture_limit_bytes=getattr(config_settings, "command_capture_max_bytes", None)
|
|
757
|
+
)
|
|
558
758
|
|
|
559
759
|
start_msgs: List[Tuple[str, str]] = []
|
|
560
760
|
|
|
@@ -626,63 +826,70 @@ def main():
|
|
|
626
826
|
return
|
|
627
827
|
|
|
628
828
|
# --- Modify settings ---
|
|
629
|
-
|
|
630
|
-
if
|
|
631
|
-
|
|
632
|
-
|
|
829
|
+
try:
|
|
830
|
+
if args.alternate_archive_dir:
|
|
831
|
+
if not os.path.exists(args.alternate_archive_dir):
|
|
832
|
+
logger.error(f"Alternate archive dir '{args.alternate_archive_dir}' does not exist, exiting")
|
|
833
|
+
sys.exit(1)
|
|
834
|
+
return
|
|
835
|
+
config_settings.backup_dir = args.alternate_archive_dir
|
|
836
|
+
|
|
837
|
+
# --- Functional logic ---
|
|
838
|
+
if args.create_db:
|
|
839
|
+
if args.backup_def:
|
|
840
|
+
sys.exit(create_db(args.backup_def, config_settings, logger, runner))
|
|
841
|
+
return
|
|
842
|
+
else:
|
|
843
|
+
for root, dirs, files in os.walk(config_settings.backup_d_dir):
|
|
844
|
+
for file in files:
|
|
845
|
+
current_backupdef = os.path.basename(file)
|
|
846
|
+
logger.debug(f"Create catalog db for backup definition: '{current_backupdef}'")
|
|
847
|
+
result = create_db(current_backupdef, config_settings, logger, runner)
|
|
848
|
+
if result != 0:
|
|
849
|
+
sys.exit(result)
|
|
850
|
+
return
|
|
851
|
+
|
|
852
|
+
if args.add_specific_archive:
|
|
853
|
+
sys.exit(add_specific_archive(args.add_specific_archive, config_settings))
|
|
633
854
|
return
|
|
634
|
-
config_settings.backup_dir = args.alternate_archive_dir
|
|
635
855
|
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
if args.backup_def:
|
|
639
|
-
sys.exit(create_db(args.backup_def, config_settings, logger, runner))
|
|
856
|
+
if args.add_dir:
|
|
857
|
+
sys.exit(add_directory(args, config_settings))
|
|
640
858
|
return
|
|
641
|
-
else:
|
|
642
|
-
for root, dirs, files in os.walk(config_settings.backup_d_dir):
|
|
643
|
-
for file in files:
|
|
644
|
-
current_backupdef = os.path.basename(file)
|
|
645
|
-
logger.debug(f"Create catalog db for backup definition: '{current_backupdef}'")
|
|
646
|
-
result = create_db(current_backupdef, config_settings, logger, runner)
|
|
647
|
-
if result != 0:
|
|
648
|
-
sys.exit(result)
|
|
649
|
-
return
|
|
650
|
-
|
|
651
|
-
if args.add_specific_archive:
|
|
652
|
-
sys.exit(add_specific_archive(args.add_specific_archive, config_settings))
|
|
653
|
-
return
|
|
654
859
|
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
if list_catalogs(current_backupdef, config_settings).returncode != 0:
|
|
672
|
-
result = 1
|
|
673
|
-
sys.exit(result)
|
|
674
|
-
return
|
|
860
|
+
if args.remove_specific_archive:
|
|
861
|
+
return remove_specific_archive(args.remove_specific_archive, config_settings)
|
|
862
|
+
|
|
863
|
+
if args.list_catalogs:
|
|
864
|
+
if args.backup_def:
|
|
865
|
+
process = list_catalogs(args.backup_def, config_settings)
|
|
866
|
+
result = process.returncode
|
|
867
|
+
else:
|
|
868
|
+
result = 0
|
|
869
|
+
for root, dirs, files in os.walk(config_settings.backup_d_dir):
|
|
870
|
+
for file in files:
|
|
871
|
+
current_backupdef = os.path.basename(file)
|
|
872
|
+
if list_catalogs(current_backupdef, config_settings).returncode != 0:
|
|
873
|
+
result = 1
|
|
874
|
+
sys.exit(result)
|
|
875
|
+
return
|
|
675
876
|
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
877
|
+
if args.list_archive_contents:
|
|
878
|
+
result = list_archive_contents(args.list_archive_contents, config_settings)
|
|
879
|
+
sys.exit(result)
|
|
880
|
+
return
|
|
680
881
|
|
|
681
882
|
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
883
|
+
if args.find_file:
|
|
884
|
+
result = find_file(args.find_file, args.backup_def, config_settings)
|
|
885
|
+
sys.exit(result)
|
|
886
|
+
return
|
|
887
|
+
except Exception as e:
|
|
888
|
+
msg = f"Unexpected error during manager operation: {e}"
|
|
889
|
+
logger.error(msg, exc_info=True)
|
|
890
|
+
ts = datetime.now().strftime("%Y-%m-%d_%H:%M")
|
|
891
|
+
send_discord_message(f"{ts} - manager: FAILURE - {msg}", config_settings=config_settings)
|
|
892
|
+
sys.exit(1)
|
|
686
893
|
|
|
687
894
|
|
|
688
895
|
if __name__ == "__main__":
|