dar-backup 0.6.18__py3-none-any.whl → 0.6.20__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 +31 -0
- dar_backup/README.md +49 -14
- dar_backup/__about__.py +1 -1
- dar_backup/clean_log.py +14 -7
- dar_backup/cleanup.py +29 -24
- dar_backup/command_runner.py +59 -9
- dar_backup/config_settings.py +83 -53
- dar_backup/dar-backup.conf +4 -0
- dar_backup/dar_backup.py +56 -35
- dar_backup/demo.py +138 -0
- dar_backup/exceptions.py +3 -0
- dar_backup/installer.py +49 -129
- dar_backup/manager.py +214 -90
- dar_backup/util.py +345 -3
- {dar_backup-0.6.18.dist-info → dar_backup-0.6.20.dist-info}/METADATA +51 -15
- dar_backup-0.6.20.dist-info/RECORD +23 -0
- {dar_backup-0.6.18.dist-info → dar_backup-0.6.20.dist-info}/entry_points.txt +1 -1
- dar_backup-0.6.18.dist-info/RECORD +0 -21
- {dar_backup-0.6.18.dist-info → dar_backup-0.6.20.dist-info}/WHEEL +0 -0
- {dar_backup-0.6.18.dist-info → dar_backup-0.6.20.dist-info}/licenses/LICENSE +0 -0
dar_backup/manager.py
CHANGED
|
@@ -20,11 +20,14 @@
|
|
|
20
20
|
This script creates and maintains `dar` databases with catalogs.
|
|
21
21
|
"""
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
import argcomplete
|
|
24
24
|
import argparse
|
|
25
25
|
import os
|
|
26
26
|
import re
|
|
27
27
|
import sys
|
|
28
|
+
import subprocess
|
|
29
|
+
|
|
30
|
+
from inputimeout import inputimeout, TimeoutOccurred
|
|
28
31
|
|
|
29
32
|
|
|
30
33
|
from . import __about__ as about
|
|
@@ -32,13 +35,17 @@ from dar_backup.config_settings import ConfigSettings
|
|
|
32
35
|
from dar_backup.util import setup_logging
|
|
33
36
|
from dar_backup.util import CommandResult
|
|
34
37
|
from dar_backup.util import get_logger
|
|
38
|
+
from dar_backup.util import get_binary_info
|
|
39
|
+
from dar_backup.util import show_version
|
|
40
|
+
from dar_backup.util import print_aligned_settings
|
|
35
41
|
|
|
36
42
|
from dar_backup.command_runner import CommandRunner
|
|
37
43
|
from dar_backup.command_runner import CommandResult
|
|
44
|
+
from dar_backup.util import backup_definition_completer, list_archive_completer, archive_content_completer, add_specific_archive_completer
|
|
38
45
|
|
|
39
46
|
from datetime import datetime
|
|
40
47
|
from time import time
|
|
41
|
-
from typing import Dict, List, NamedTuple
|
|
48
|
+
from typing import Dict, List, NamedTuple, Tuple
|
|
42
49
|
|
|
43
50
|
# Constants
|
|
44
51
|
SCRIPTNAME = os.path.basename(__file__)
|
|
@@ -49,6 +56,15 @@ DB_SUFFIX = ".db"
|
|
|
49
56
|
logger = None
|
|
50
57
|
runner = None
|
|
51
58
|
|
|
59
|
+
|
|
60
|
+
def get_db_dir(config_settings: ConfigSettings) -> str:
|
|
61
|
+
"""
|
|
62
|
+
Return the correct directory for storing catalog databases.
|
|
63
|
+
Uses manager_db_dir if set, otherwise falls back to backup_dir.
|
|
64
|
+
"""
|
|
65
|
+
return getattr(config_settings, "manager_db_dir", None) or config_settings.backup_dir
|
|
66
|
+
|
|
67
|
+
|
|
52
68
|
def show_more_help():
|
|
53
69
|
help_text = f"""
|
|
54
70
|
NAME
|
|
@@ -57,68 +73,95 @@ NAME
|
|
|
57
73
|
print(help_text)
|
|
58
74
|
|
|
59
75
|
|
|
60
|
-
def create_db(backup_def: str, config_settings: ConfigSettings):
|
|
76
|
+
def create_db(backup_def: str, config_settings: ConfigSettings, logger, runner) -> int:
|
|
77
|
+
db_dir = get_db_dir(config_settings)
|
|
78
|
+
|
|
79
|
+
if not os.path.exists(db_dir):
|
|
80
|
+
logger.error(f"DB dir does not exist: {db_dir}")
|
|
81
|
+
return 1
|
|
82
|
+
if not os.path.isdir(db_dir):
|
|
83
|
+
logger.error(f"DB path exists but is not a directory: {db_dir}")
|
|
84
|
+
return 1
|
|
85
|
+
if not os.access(db_dir, os.W_OK):
|
|
86
|
+
logger.error(f"DB dir is not writable: {db_dir}")
|
|
87
|
+
return 1
|
|
88
|
+
|
|
61
89
|
database = f"{backup_def}{DB_SUFFIX}"
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
logger.debug(f"BACKUP_DIR: {config_settings.backup_dir}")
|
|
90
|
+
database_path = os.path.join(db_dir, database)
|
|
91
|
+
|
|
92
|
+
logger.debug(f"DB directory: {db_dir}")
|
|
66
93
|
|
|
67
94
|
if os.path.exists(database_path):
|
|
68
|
-
logger.
|
|
95
|
+
logger.info(f'"{database_path}" already exists, skipping creation')
|
|
69
96
|
return 0
|
|
70
97
|
else:
|
|
71
98
|
logger.info(f'Create catalog database: "{database_path}"')
|
|
72
|
-
command = ['dar_manager', '--create'
|
|
99
|
+
command = ['dar_manager', '--create', database_path]
|
|
73
100
|
process = runner.run(command)
|
|
74
101
|
logger.debug(f"return code from 'db created': {process.returncode}")
|
|
75
102
|
if process.returncode == 0:
|
|
76
103
|
logger.info(f'Database created: "{database_path}"')
|
|
77
104
|
else:
|
|
78
105
|
logger.error(f'Something went wrong creating the database: "{database_path}"')
|
|
79
|
-
stdout, stderr = process.stdout, process.stderr
|
|
106
|
+
stdout, stderr = process.stdout, process.stderr
|
|
80
107
|
logger.error(f"stderr: {stderr}")
|
|
81
108
|
logger.error(f"stdout: {stdout}")
|
|
82
109
|
|
|
83
110
|
return process.returncode
|
|
84
111
|
|
|
85
112
|
|
|
86
|
-
def list_catalogs(backup_def: str, config_settings: ConfigSettings) ->
|
|
113
|
+
def list_catalogs(backup_def: str, config_settings: ConfigSettings, suppress_output=False) -> CommandResult:
|
|
87
114
|
"""
|
|
115
|
+
List catalogs from the database for the given backup definition.
|
|
116
|
+
|
|
88
117
|
Returns:
|
|
89
|
-
|
|
90
|
-
- process: of type subprocess.CompletedProcess: The result of the command execution.
|
|
91
|
-
- stdout: of type str: The standard output of the command.
|
|
92
|
-
- stderr: of type str: The standard error of the command.
|
|
93
|
-
- returncode: of type int: The return code of the command.
|
|
94
|
-
- timeout: of type int: The timeout value in seconds used to run the command.
|
|
95
|
-
- command: of type list[str): The command executed.
|
|
118
|
+
A CommandResult containing the raw stdout/stderr and return code.
|
|
96
119
|
"""
|
|
97
120
|
database = f"{backup_def}{DB_SUFFIX}"
|
|
98
|
-
database_path = os.path.join(config_settings
|
|
121
|
+
database_path = os.path.join(get_db_dir(config_settings), database)
|
|
122
|
+
|
|
99
123
|
if not os.path.exists(database_path):
|
|
100
124
|
error_msg = f'Database not found: "{database_path}"'
|
|
101
125
|
logger.error(error_msg)
|
|
102
126
|
return CommandResult(1, '', error_msg)
|
|
103
127
|
|
|
104
|
-
# commandResult = CommandResult(
|
|
105
|
-
# process=None,
|
|
106
|
-
# stdout='',
|
|
107
|
-
# stderr=error_msg,
|
|
108
|
-
# returncode=1,
|
|
109
|
-
# timeout=1,
|
|
110
|
-
# command=[])
|
|
111
|
-
|
|
112
|
-
# return commandResult
|
|
113
128
|
command = ['dar_manager', '--base', database_path, '--list']
|
|
114
129
|
process = runner.run(command)
|
|
115
|
-
stdout, stderr = process.stdout, process.stderr
|
|
130
|
+
stdout, stderr = process.stdout, process.stderr
|
|
131
|
+
|
|
116
132
|
if process.returncode != 0:
|
|
117
133
|
logger.error(f'Error listing catalogs for: "{database_path}"')
|
|
118
|
-
logger.error(f"stderr: {stderr}")
|
|
134
|
+
logger.error(f"stderr: {stderr}")
|
|
119
135
|
logger.error(f"stdout: {stdout}")
|
|
120
|
-
|
|
121
|
-
|
|
136
|
+
return process
|
|
137
|
+
|
|
138
|
+
# Extract only archive basenames from stdout
|
|
139
|
+
archive_names = []
|
|
140
|
+
for line in stdout.splitlines():
|
|
141
|
+
line = line.strip()
|
|
142
|
+
if not line or "archive #" in line or "dar path" in line or "compression" in line:
|
|
143
|
+
continue
|
|
144
|
+
parts = line.split("\t")
|
|
145
|
+
if len(parts) >= 3:
|
|
146
|
+
archive_names.append(parts[2].strip())
|
|
147
|
+
|
|
148
|
+
# Sort by prefix and date
|
|
149
|
+
def extract_date(arch_name):
|
|
150
|
+
match = re.search(r"(\d{4}-\d{2}-\d{2})", arch_name)
|
|
151
|
+
if match:
|
|
152
|
+
return datetime.strptime(match.group(1), "%Y-%m-%d")
|
|
153
|
+
return datetime.min
|
|
154
|
+
|
|
155
|
+
def sort_key(name):
|
|
156
|
+
prefix = name.split("_", 1)[0]
|
|
157
|
+
return (prefix, extract_date(name))
|
|
158
|
+
|
|
159
|
+
archive_names = sorted(archive_names, key=sort_key)
|
|
160
|
+
|
|
161
|
+
if not suppress_output:
|
|
162
|
+
for name in archive_names:
|
|
163
|
+
print(name)
|
|
164
|
+
|
|
122
165
|
return process
|
|
123
166
|
|
|
124
167
|
|
|
@@ -130,47 +173,65 @@ def cat_no_for_name(archive: str, config_settings: ConfigSettings) -> int:
|
|
|
130
173
|
- the found number, if the archive catalog is present in the database
|
|
131
174
|
- "-1" if the archive is not found
|
|
132
175
|
"""
|
|
176
|
+
|
|
133
177
|
backup_def = backup_def_from_archive(archive)
|
|
134
|
-
process = list_catalogs(backup_def, config_settings)
|
|
178
|
+
process = list_catalogs(backup_def, config_settings, suppress_output=True)
|
|
135
179
|
if process.returncode != 0:
|
|
136
180
|
logger.error(f"Error listing catalogs for backup def: '{backup_def}'")
|
|
137
181
|
return -1
|
|
138
182
|
line_no = 1
|
|
139
183
|
for line in process.stdout.splitlines():
|
|
140
|
-
#print(f"{line_no}: '{line}'")
|
|
141
184
|
line_no += 1
|
|
142
185
|
search = re.search(rf".*?(\d+)\s+.*?({archive}).*", line)
|
|
143
186
|
if search:
|
|
144
|
-
#print(f"FOUND: archive: {search.group(2)}, catalog #: '{search.group(1)}'")
|
|
145
187
|
logger.info(f"Found archive: '{archive}', catalog #: '{search.group(1)}'")
|
|
146
188
|
return int(search.group(1))
|
|
147
189
|
return -1
|
|
148
190
|
|
|
149
191
|
|
|
150
|
-
|
|
151
|
-
def list_archive_contents(archive: str, config_settings: ConfigSettings) -> int :
|
|
192
|
+
def list_archive_contents(archive: str, config_settings: ConfigSettings) -> int:
|
|
152
193
|
"""
|
|
153
|
-
List the contents of a specific archive, given the archive name
|
|
194
|
+
List the contents of a specific archive, given the archive name.
|
|
195
|
+
Prints only actual file entries (lines beginning with '[ Saved ]').
|
|
196
|
+
If none are found, a notice is printed instead.
|
|
154
197
|
"""
|
|
155
198
|
backup_def = backup_def_from_archive(archive)
|
|
156
199
|
database = f"{backup_def}{DB_SUFFIX}"
|
|
157
|
-
database_path = os.path.join(config_settings
|
|
200
|
+
database_path = os.path.join(get_db_dir(config_settings), database)
|
|
201
|
+
|
|
158
202
|
if not os.path.exists(database_path):
|
|
159
203
|
logger.error(f'Database not found: "{database_path}"')
|
|
160
204
|
return 1
|
|
205
|
+
|
|
161
206
|
cat_no = cat_no_for_name(archive, config_settings)
|
|
162
207
|
if cat_no < 0:
|
|
163
208
|
logger.error(f"archive: '{archive}' not found in database: '{database_path}'")
|
|
164
209
|
return 1
|
|
210
|
+
|
|
211
|
+
|
|
165
212
|
command = ['dar_manager', '--base', database_path, '-u', f"{cat_no}"]
|
|
166
|
-
process = runner.run(command)
|
|
167
|
-
|
|
213
|
+
process = runner.run(command, timeout = 10)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
stdout = process.stdout or ""
|
|
217
|
+
stderr = process.stderr or ""
|
|
218
|
+
|
|
219
|
+
|
|
168
220
|
if process.returncode != 0:
|
|
169
221
|
logger.error(f'Error listing catalogs for: "{database_path}"')
|
|
170
|
-
logger.error(f"stderr: {stderr}")
|
|
222
|
+
logger.error(f"stderr: {stderr}")
|
|
171
223
|
logger.error(f"stdout: {stdout}")
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
combined_lines = (stdout + "\n" + stderr).splitlines()
|
|
227
|
+
file_lines = [line for line in combined_lines if line.strip().startswith("[ Saved ]")]
|
|
228
|
+
|
|
229
|
+
if file_lines:
|
|
230
|
+
for line in file_lines:
|
|
231
|
+
print(line)
|
|
172
232
|
else:
|
|
173
|
-
print(
|
|
233
|
+
print(f"[info] Archive '{archive}' is empty.")
|
|
234
|
+
|
|
174
235
|
return process.returncode
|
|
175
236
|
|
|
176
237
|
|
|
@@ -179,8 +240,9 @@ def list_catalog_contents(catalog_number: int, backup_def: str, config_settings:
|
|
|
179
240
|
"""
|
|
180
241
|
List the contents of catalog # in catalog database for given backup definition
|
|
181
242
|
"""
|
|
243
|
+
logger = get_logger()
|
|
182
244
|
database = f"{backup_def}{DB_SUFFIX}"
|
|
183
|
-
database_path = os.path.join(config_settings
|
|
245
|
+
database_path = os.path.join(get_db_dir(config_settings), database)
|
|
184
246
|
if not os.path.exists(database_path):
|
|
185
247
|
logger.error(f'Catalog database not found: "{database_path}"')
|
|
186
248
|
return 1
|
|
@@ -201,7 +263,7 @@ def find_file(file, backup_def, config_settings):
|
|
|
201
263
|
Find a specific file
|
|
202
264
|
"""
|
|
203
265
|
database = f"{backup_def}{DB_SUFFIX}"
|
|
204
|
-
database_path = os.path.join(config_settings
|
|
266
|
+
database_path = os.path.join(get_db_dir(config_settings), database)
|
|
205
267
|
if not os.path.exists(database_path):
|
|
206
268
|
logger.error(f'Database not found: "{database_path}"')
|
|
207
269
|
return 1
|
|
@@ -217,42 +279,82 @@ def find_file(file, backup_def, config_settings):
|
|
|
217
279
|
return process.returncode
|
|
218
280
|
|
|
219
281
|
|
|
220
|
-
def add_specific_archive(archive: str, config_settings: ConfigSettings, directory: str =None) -> int:
|
|
221
|
-
|
|
282
|
+
def add_specific_archive(archive: str, config_settings: ConfigSettings, directory: str = None) -> int:
|
|
283
|
+
"""
|
|
284
|
+
Adds the specified archive to its catalog database. Prompts for confirmation if it's older than existing entries.
|
|
285
|
+
|
|
286
|
+
Returns:
|
|
287
|
+
0 on success
|
|
288
|
+
1 on failure
|
|
289
|
+
"""
|
|
290
|
+
# Determine archive path
|
|
222
291
|
if not directory:
|
|
223
292
|
directory = config_settings.backup_dir
|
|
224
|
-
archive = os.path.basename(archive) #
|
|
225
|
-
archive_path = os.path.join(directory,
|
|
293
|
+
archive = os.path.basename(archive) # strip path if present
|
|
294
|
+
archive_path = os.path.join(directory, archive)
|
|
295
|
+
archive_test_path = os.path.join(directory, f'{archive}.1.dar')
|
|
226
296
|
|
|
227
|
-
archive_test_path = os.path.join(directory, f'{archive}.1.dar')
|
|
228
297
|
if not os.path.exists(archive_test_path):
|
|
229
298
|
logger.error(f'dar backup: "{archive_test_path}" not found, exiting')
|
|
230
299
|
return 1
|
|
231
|
-
|
|
232
|
-
#
|
|
300
|
+
|
|
301
|
+
# Validate backup definition
|
|
233
302
|
backup_definition = archive.split('_')[0]
|
|
234
303
|
backup_def_path = os.path.join(config_settings.backup_d_dir, backup_definition)
|
|
235
304
|
if not os.path.exists(backup_def_path):
|
|
236
305
|
logger.error(f'backup definition "{backup_definition}" not found (--add-specific-archive option probably not correct), exiting')
|
|
237
306
|
return 1
|
|
238
|
-
|
|
307
|
+
|
|
308
|
+
# Determine catalog DB path
|
|
239
309
|
database = f"{backup_definition}{DB_SUFFIX}"
|
|
240
|
-
database_path = os.path.realpath(os.path.join(config_settings
|
|
310
|
+
database_path = os.path.realpath(os.path.join(get_db_dir(config_settings), database))
|
|
311
|
+
|
|
312
|
+
# Safety check: is archive older than latest in catalog?
|
|
313
|
+
try:
|
|
314
|
+
result = subprocess.run(
|
|
315
|
+
["dar_manager", "--base", database_path, "--list"],
|
|
316
|
+
stdout=subprocess.PIPE,
|
|
317
|
+
stderr=subprocess.DEVNULL,
|
|
318
|
+
text=True,
|
|
319
|
+
check=True
|
|
320
|
+
)
|
|
321
|
+
all_lines = result.stdout.splitlines()
|
|
322
|
+
date_pattern = re.compile(r"\d{4}-\d{2}-\d{2}")
|
|
323
|
+
|
|
324
|
+
catalog_dates = [
|
|
325
|
+
datetime.strptime(date_match.group(), "%Y-%m-%d")
|
|
326
|
+
for line in all_lines
|
|
327
|
+
if (date_match := date_pattern.search(line))
|
|
328
|
+
]
|
|
329
|
+
|
|
330
|
+
if catalog_dates:
|
|
331
|
+
latest_date = max(catalog_dates)
|
|
332
|
+
archive_date_match = date_pattern.search(archive)
|
|
333
|
+
if archive_date_match:
|
|
334
|
+
archive_date = datetime.strptime(archive_date_match.group(), "%Y-%m-%d")
|
|
335
|
+
if archive_date < latest_date:
|
|
336
|
+
if not confirm_add_old_archive(archive, latest_date.strftime("%Y-%m-%d")):
|
|
337
|
+
logger.info(f"Archive {archive} skipped due to user declining to add older archive.")
|
|
338
|
+
return 1
|
|
339
|
+
|
|
340
|
+
except subprocess.CalledProcessError:
|
|
341
|
+
logger.warning("Could not determine latest catalog date for chronological check.")
|
|
342
|
+
|
|
241
343
|
logger.info(f'Add "{archive_path}" to catalog: "{database}"')
|
|
242
|
-
|
|
243
|
-
command = ['dar_manager', '--base', database_path, "--add", archive_path, "-Q"]
|
|
344
|
+
|
|
345
|
+
command = ['dar_manager', '--base', database_path, "--add", archive_path, "-Q", "--alter=ignore-order"]
|
|
244
346
|
process = runner.run(command)
|
|
245
347
|
stdout, stderr = process.stdout, process.stderr
|
|
246
348
|
|
|
247
349
|
if process.returncode == 0:
|
|
248
|
-
logger.info(f'"{archive_path}" added to
|
|
350
|
+
logger.info(f'"{archive_path}" added to its catalog')
|
|
249
351
|
elif process.returncode == 5:
|
|
250
|
-
logger.warning(f'Something did not go completely right adding "{archive_path}" to
|
|
251
|
-
else:
|
|
252
|
-
logger.error(f'something went wrong adding "{archive_path}" to
|
|
352
|
+
logger.warning(f'Something did not go completely right adding "{archive_path}" to its catalog, dar_manager error: "{process.returncode}"')
|
|
353
|
+
else:
|
|
354
|
+
logger.error(f'something went wrong adding "{archive_path}" to its catalog, dar_manager error: "{process.returncode}"')
|
|
253
355
|
logger.error(f"stderr: {stderr}")
|
|
254
356
|
logger.error(f"stdout: {stdout}")
|
|
255
|
-
|
|
357
|
+
|
|
256
358
|
return process.returncode
|
|
257
359
|
|
|
258
360
|
|
|
@@ -330,6 +432,31 @@ def backup_def_from_archive(archive: str) -> str:
|
|
|
330
432
|
return None
|
|
331
433
|
|
|
332
434
|
|
|
435
|
+
def confirm_add_old_archive(archive_name: str, latest_known_date: str, timeout_secs: int = 20) -> bool:
|
|
436
|
+
"""
|
|
437
|
+
Confirm with the user if they want to proceed with adding an archive older than the most recent in the catalog.
|
|
438
|
+
Returns True if the user confirms with "yes", False otherwise.
|
|
439
|
+
"""
|
|
440
|
+
try:
|
|
441
|
+
prompt = (
|
|
442
|
+
f"⚠️ Archive '{archive_name}' is older than the latest in the catalog ({latest_known_date}).\n"
|
|
443
|
+
f"Adding older archives may lead to inconsistent restore chains.\n"
|
|
444
|
+
f"Are you sure you want to continue? (yes/no): "
|
|
445
|
+
)
|
|
446
|
+
confirmation = inputimeout(prompt=prompt, timeout=timeout_secs)
|
|
447
|
+
|
|
448
|
+
if confirmation is None:
|
|
449
|
+
logger.info(f"No confirmation received for old archive: {archive_name}. Skipping.")
|
|
450
|
+
return False
|
|
451
|
+
return confirmation.strip().lower() == "yes"
|
|
452
|
+
|
|
453
|
+
except TimeoutOccurred:
|
|
454
|
+
logger.info(f"Timeout waiting for confirmation for old archive: {archive_name}. Skipping.")
|
|
455
|
+
return False
|
|
456
|
+
except KeyboardInterrupt:
|
|
457
|
+
logger.info(f"User interrupted confirmation for old archive: {archive_name}. Skipping.")
|
|
458
|
+
return False
|
|
459
|
+
|
|
333
460
|
|
|
334
461
|
def remove_specific_archive(archive: str, config_settings: ConfigSettings) -> int:
|
|
335
462
|
"""
|
|
@@ -341,7 +468,7 @@ def remove_specific_archive(archive: str, config_settings: ConfigSettings) -> in
|
|
|
341
468
|
|
|
342
469
|
"""
|
|
343
470
|
backup_def = backup_def_from_archive(archive)
|
|
344
|
-
database_path = os.path.join(config_settings
|
|
471
|
+
database_path = os.path.join(get_db_dir(config_settings), f"{backup_def}{DB_SUFFIX}")
|
|
345
472
|
cat_no:int = cat_no_for_name(archive, config_settings)
|
|
346
473
|
if cat_no >= 0:
|
|
347
474
|
command = ['dar_manager', '--base', database_path, "--delete", str(cat_no)]
|
|
@@ -366,12 +493,11 @@ def build_arg_parser():
|
|
|
366
493
|
parser.add_argument('--create-db', action='store_true', help='Create missing databases for all backup definitions')
|
|
367
494
|
parser.add_argument('--alternate-archive-dir', type=str, help='Use this directory instead of BACKUP_DIR in config file')
|
|
368
495
|
parser.add_argument('--add-dir', type=str, help='Add all archive catalogs in this directory to databases')
|
|
369
|
-
parser.add_argument('-d', '--backup-def', type=str, help='Restrict to work only on this backup definition')
|
|
370
|
-
parser.add_argument('--add-specific-archive', type=str, help='Add this archive to catalog database')
|
|
371
|
-
parser.add_argument('--remove-specific-archive', type=str, help='Remove this archive from catalog database')
|
|
496
|
+
parser.add_argument('-d', '--backup-def', type=str, help='Restrict to work only on this backup definition').completer = backup_definition_completer
|
|
497
|
+
parser.add_argument('--add-specific-archive', type=str, help='Add this archive to catalog database').completer = add_specific_archive_completer
|
|
498
|
+
parser.add_argument('--remove-specific-archive', type=str, help='Remove this archive from catalog database').completer = archive_content_completer
|
|
372
499
|
parser.add_argument('-l', '--list-catalogs', action='store_true', help='List catalogs in databases for all backup definitions')
|
|
373
|
-
parser.add_argument('--list-
|
|
374
|
-
parser.add_argument('--list-archive-contents', type=str, help="List contents of the archive's catalog.")
|
|
500
|
+
parser.add_argument('--list-archive-contents', type=str, help="List contents of the archive's catalog. Argument is the archive name.").completer = archive_content_completer
|
|
375
501
|
parser.add_argument('--find-file', type=str, help="List catalogs containing <path>/file. '-d <definition>' argument is also required")
|
|
376
502
|
parser.add_argument('--verbose', action='store_true', help='Be more verbose')
|
|
377
503
|
parser.add_argument('--log-level', type=str, help="`debug` or `trace`, default is `info`", default="info")
|
|
@@ -382,8 +508,6 @@ def build_arg_parser():
|
|
|
382
508
|
return parser
|
|
383
509
|
|
|
384
510
|
|
|
385
|
-
|
|
386
|
-
|
|
387
511
|
def main():
|
|
388
512
|
global logger, runner
|
|
389
513
|
|
|
@@ -394,9 +518,10 @@ def main():
|
|
|
394
518
|
return
|
|
395
519
|
|
|
396
520
|
parser = argparse.ArgumentParser(description="Creates/maintains `dar` database catalogs")
|
|
397
|
-
# [parser.add_argument(...) as before...]
|
|
398
|
-
|
|
399
521
|
parser = build_arg_parser()
|
|
522
|
+
|
|
523
|
+
argcomplete.autocomplete(parser)
|
|
524
|
+
|
|
400
525
|
args = parser.parse_args()
|
|
401
526
|
|
|
402
527
|
if args.more_help:
|
|
@@ -405,16 +530,13 @@ def main():
|
|
|
405
530
|
return
|
|
406
531
|
|
|
407
532
|
if args.version:
|
|
408
|
-
|
|
409
|
-
print(f"Source code is here: https://github.com/per2jensen/dar-backup")
|
|
410
|
-
print('''Licensed under GNU GENERAL PUBLIC LICENSE v3, see the supplied file "LICENSE" for details.
|
|
411
|
-
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW, not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
412
|
-
See section 15 and section 16 in the supplied "LICENSE" file.''')
|
|
533
|
+
show_version()
|
|
413
534
|
sys.exit(0)
|
|
414
|
-
return
|
|
415
535
|
|
|
416
536
|
args.config_file = os.path.expanduser(os.path.expandvars(args.config_file))
|
|
417
537
|
config_settings = ConfigSettings(args.config_file)
|
|
538
|
+
print(f"Config settings: {config_settings}")
|
|
539
|
+
|
|
418
540
|
if not os.path.dirname(config_settings.logfile_location):
|
|
419
541
|
print(f"Directory for log file '{config_settings.logfile_location}' does not exist, exiting")
|
|
420
542
|
sys.exit(1)
|
|
@@ -425,12 +547,23 @@ See section 15 and section 16 in the supplied "LICENSE" file.''')
|
|
|
425
547
|
command_logger = get_logger(command_output_logger=True)
|
|
426
548
|
runner = CommandRunner(logger=logger, command_logger=command_logger)
|
|
427
549
|
|
|
550
|
+
start_msgs: List[Tuple[str, str]] = []
|
|
551
|
+
|
|
428
552
|
start_time = int(time())
|
|
429
|
-
|
|
430
|
-
logger.info(f"{SCRIPTNAME} started, version: {about.__version__}")
|
|
553
|
+
start_msgs.append((f"{SCRIPTNAME}:", about.__version__))
|
|
431
554
|
logger.info(f"START TIME: {start_time}")
|
|
432
555
|
logger.debug(f"`args`:\n{args}")
|
|
433
556
|
logger.debug(f"`config_settings`:\n{config_settings}")
|
|
557
|
+
start_msgs.append(("Config file:", args.config_file))
|
|
558
|
+
args.verbose and start_msgs.append(("Backup dir:", config_settings.backup_dir))
|
|
559
|
+
start_msgs.append(("Logfile:", config_settings.logfile_location))
|
|
560
|
+
args.verbose and start_msgs.append(("--alternate-archive-dir:", args.alternate_archive_dir))
|
|
561
|
+
args.verbose and start_msgs.append(("--cleanup-specific-archives:", args.cleanup_specific_archives))
|
|
562
|
+
dar_manager_properties = get_binary_info(command='dar_manager')
|
|
563
|
+
start_msgs.append(("dar_manager:", dar_manager_properties['path']))
|
|
564
|
+
start_msgs.append(("dar_manager v.:", dar_manager_properties['version']))
|
|
565
|
+
|
|
566
|
+
print_aligned_settings(start_msgs)
|
|
434
567
|
|
|
435
568
|
# --- Sanity checks ---
|
|
436
569
|
if args.add_dir and not args.add_dir.strip():
|
|
@@ -475,11 +608,6 @@ See section 15 and section 16 in the supplied "LICENSE" file.''')
|
|
|
475
608
|
sys.exit(1)
|
|
476
609
|
return
|
|
477
610
|
|
|
478
|
-
if args.list_catalog_contents and not args.backup_def:
|
|
479
|
-
logger.error(f"--list-catalog-contents requires the --backup-def, exiting")
|
|
480
|
-
sys.exit(1)
|
|
481
|
-
return
|
|
482
|
-
|
|
483
611
|
if args.find_file and not args.backup_def:
|
|
484
612
|
logger.error(f"--find-file requires the --backup-def, exiting")
|
|
485
613
|
sys.exit(1)
|
|
@@ -496,14 +624,14 @@ See section 15 and section 16 in the supplied "LICENSE" file.''')
|
|
|
496
624
|
# --- Functional logic ---
|
|
497
625
|
if args.create_db:
|
|
498
626
|
if args.backup_def:
|
|
499
|
-
sys.exit(create_db(args.backup_def, config_settings))
|
|
627
|
+
sys.exit(create_db(args.backup_def, config_settings, logger, runner))
|
|
500
628
|
return
|
|
501
629
|
else:
|
|
502
630
|
for root, dirs, files in os.walk(config_settings.backup_d_dir):
|
|
503
631
|
for file in files:
|
|
504
632
|
current_backupdef = os.path.basename(file)
|
|
505
633
|
logger.debug(f"Create catalog db for backup definition: '{current_backupdef}'")
|
|
506
|
-
result = create_db(current_backupdef, config_settings)
|
|
634
|
+
result = create_db(current_backupdef, config_settings, logger, runner)
|
|
507
635
|
if result != 0:
|
|
508
636
|
sys.exit(result)
|
|
509
637
|
return
|
|
@@ -538,10 +666,6 @@ See section 15 and section 16 in the supplied "LICENSE" file.''')
|
|
|
538
666
|
sys.exit(result)
|
|
539
667
|
return
|
|
540
668
|
|
|
541
|
-
if args.list_catalog_contents:
|
|
542
|
-
result = list_catalog_contents(args.list_catalog_contents, args.backup_def, config_settings)
|
|
543
|
-
sys.exit(result)
|
|
544
|
-
return
|
|
545
669
|
|
|
546
670
|
if args.find_file:
|
|
547
671
|
result = find_file(args.find_file, args.backup_def, config_settings)
|