dar-backup 0.6.6__py3-none-any.whl → 0.6.8__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/.darrc +6 -4
- dar_backup/__about__.py +1 -1
- dar_backup/cleanup.py +70 -24
- dar_backup/dar_backup.py +9 -9
- dar_backup/manager.py +74 -20
- {dar_backup-0.6.6.dist-info → dar_backup-0.6.8.dist-info}/METADATA +11 -3
- dar_backup-0.6.8.dist-info/RECORD +13 -0
- dar_backup-0.6.6.dist-info/RECORD +0 -13
- {dar_backup-0.6.6.dist-info → dar_backup-0.6.8.dist-info}/WHEEL +0 -0
- {dar_backup-0.6.6.dist-info → dar_backup-0.6.8.dist-info}/entry_points.txt +0 -0
- {dar_backup-0.6.6.dist-info → dar_backup-0.6.8.dist-info}/licenses/LICENSE +0 -0
dar_backup/.darrc
CHANGED
|
@@ -26,19 +26,22 @@ verbose:
|
|
|
26
26
|
# -va
|
|
27
27
|
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
restore-options:
|
|
30
30
|
# don't restore File Specific Attributes
|
|
31
31
|
#--fsa-scope none
|
|
32
32
|
|
|
33
33
|
# ignore owner, useful when used by a non-privileged user
|
|
34
34
|
--comparison-field=ignore-owner
|
|
35
35
|
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# Exclude specific file types from compression
|
|
39
|
+
compress-exclusion:
|
|
40
|
+
|
|
36
41
|
# First setting case insensitive mode on:
|
|
37
42
|
-an
|
|
38
43
|
-ag
|
|
39
44
|
|
|
40
|
-
# Exclude specific file types from compression
|
|
41
|
-
compress-exclusion:
|
|
42
45
|
-Z "*.gz"
|
|
43
46
|
-Z "*.bz2"
|
|
44
47
|
-Z "*.xz"
|
|
@@ -105,6 +108,5 @@ compress-exclusion:
|
|
|
105
108
|
-Z "*.dar"
|
|
106
109
|
|
|
107
110
|
# Now we swap back to case sensitive mode for masks which is the default
|
|
108
|
-
# mode:
|
|
109
111
|
-acase
|
|
110
112
|
|
dar_backup/__about__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.6.
|
|
1
|
+
__version__ = "0.6.8"
|
dar_backup/cleanup.py
CHANGED
|
@@ -22,17 +22,22 @@ import sys
|
|
|
22
22
|
|
|
23
23
|
from datetime import datetime, timedelta
|
|
24
24
|
from time import time
|
|
25
|
+
from typing import Dict, List, NamedTuple
|
|
25
26
|
|
|
26
27
|
from . import __about__ as about
|
|
27
28
|
from dar_backup.config_settings import ConfigSettings
|
|
28
29
|
from dar_backup.util import extract_error_lines
|
|
29
30
|
from dar_backup.util import list_backups
|
|
31
|
+
from dar_backup.util import run_command
|
|
30
32
|
from dar_backup.util import setup_logging
|
|
31
33
|
|
|
34
|
+
from dar_backup.util import CommandResult
|
|
35
|
+
|
|
36
|
+
|
|
32
37
|
|
|
33
38
|
logger = None
|
|
34
39
|
|
|
35
|
-
def delete_old_backups(backup_dir, age, backup_type, backup_definition=None):
|
|
40
|
+
def delete_old_backups(backup_dir, age, backup_type, args, backup_definition=None):
|
|
36
41
|
"""
|
|
37
42
|
Delete backups older than the specified age in days.
|
|
38
43
|
Only .dar and .par2 files are considered for deletion.
|
|
@@ -46,6 +51,8 @@ def delete_old_backups(backup_dir, age, backup_type, backup_definition=None):
|
|
|
46
51
|
now = datetime.now()
|
|
47
52
|
cutoff_date = now - timedelta(days=age)
|
|
48
53
|
|
|
54
|
+
archives_deleted = {}
|
|
55
|
+
|
|
49
56
|
for filename in sorted(os.listdir(backup_dir)):
|
|
50
57
|
if not (filename.endswith('.dar') or filename.endswith('.par2')):
|
|
51
58
|
continue
|
|
@@ -64,11 +71,18 @@ def delete_old_backups(backup_dir, age, backup_type, backup_definition=None):
|
|
|
64
71
|
try:
|
|
65
72
|
os.remove(file_path)
|
|
66
73
|
logger.info(f"Deleted {backup_type} backup: {file_path}")
|
|
74
|
+
archive_name = filename.split('.')[0]
|
|
75
|
+
if not archive_name in archives_deleted:
|
|
76
|
+
logger.debug(f"Archive name: '{archive_name}' added to catalog deletion list")
|
|
77
|
+
archives_deleted[archive_name] = True
|
|
67
78
|
except Exception as e:
|
|
68
79
|
logger.error(f"Error deleting file {file_path}: {e}")
|
|
69
80
|
|
|
81
|
+
for archive_name in archives_deleted.keys():
|
|
82
|
+
delete_catalog(archive_name, args)
|
|
83
|
+
|
|
70
84
|
|
|
71
|
-
def delete_archive(backup_dir, archive_name):
|
|
85
|
+
def delete_archive(backup_dir, archive_name, args):
|
|
72
86
|
"""
|
|
73
87
|
Delete all .dar and .par2 files in the backup directory for the given archive name.
|
|
74
88
|
|
|
@@ -90,7 +104,9 @@ def delete_archive(backup_dir, archive_name):
|
|
|
90
104
|
except Exception as e:
|
|
91
105
|
logger.error(f"Error deleting archive slice {file_path}: {e}")
|
|
92
106
|
|
|
93
|
-
if
|
|
107
|
+
if files_deleted:
|
|
108
|
+
delete_catalog(archive_name, args)
|
|
109
|
+
else:
|
|
94
110
|
logger.info("No .dar files matched the regex for deletion.")
|
|
95
111
|
|
|
96
112
|
# Delete associated .par2 files
|
|
@@ -110,6 +126,29 @@ def delete_archive(backup_dir, archive_name):
|
|
|
110
126
|
logger.info("No .par2 matched the regex for deletion.")
|
|
111
127
|
|
|
112
128
|
|
|
129
|
+
def delete_catalog(catalog_name: str, args: NamedTuple) -> bool:
|
|
130
|
+
"""
|
|
131
|
+
Call `manager.py` to delete the specified catalog in it's database
|
|
132
|
+
"""
|
|
133
|
+
command = [f"manager", "--remove-specific-archive", catalog_name, "--config-file", args.config_file, '--log-level', 'debug', '--log-stdout']
|
|
134
|
+
logger.info(f"Deleting catalog '{catalog_name}' using config file: '{args.config_file}'")
|
|
135
|
+
try:
|
|
136
|
+
result:CommandResult = run_command(command)
|
|
137
|
+
if result.returncode == 0:
|
|
138
|
+
logger.info(f"Deleted catalog '{catalog_name}', using config file: '{args.config_file}'")
|
|
139
|
+
logger.debug(f"Stdout: manager.py --remove-specific-archive output:\n{result.stdout}")
|
|
140
|
+
return True
|
|
141
|
+
elif result.returncode == 2:
|
|
142
|
+
logger.warning(f"catalog '{catalog_name}' not found in the database, skipping deletion")
|
|
143
|
+
return True
|
|
144
|
+
else:
|
|
145
|
+
logger.error(f"Error deleting catalog {catalog_name}: {result.stderr}")
|
|
146
|
+
return False
|
|
147
|
+
except Exception as e:
|
|
148
|
+
logger.error(f"Error deleting catalog {catalog_name}: {e}")
|
|
149
|
+
return False
|
|
150
|
+
|
|
151
|
+
|
|
113
152
|
def show_version():
|
|
114
153
|
script_name = os.path.basename(sys.argv[0])
|
|
115
154
|
print(f"{script_name} {about.__version__}")
|
|
@@ -118,8 +157,6 @@ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW,
|
|
|
118
157
|
See section 15 and section 16 in the supplied "LICENSE" file.''')
|
|
119
158
|
|
|
120
159
|
def main():
|
|
121
|
-
|
|
122
|
-
|
|
123
160
|
global logger
|
|
124
161
|
|
|
125
162
|
parser = argparse.ArgumentParser(description="Cleanup old archives according to AGE configuration.")
|
|
@@ -127,9 +164,11 @@ def main():
|
|
|
127
164
|
parser.add_argument('-c', '--config-file', '-c', type=str, help="Path to 'dar-backup.conf'", default='~/.config/dar-backup/dar-backup.conf')
|
|
128
165
|
parser.add_argument('-v', '--version', action='store_true', help="Show version information.")
|
|
129
166
|
parser.add_argument('--alternate-archive-dir', type=str, help="Cleanup in this directory instead of the default one.")
|
|
130
|
-
parser.add_argument('--cleanup-specific-
|
|
167
|
+
parser.add_argument('--cleanup-specific-archives', type=str, help="Commas separated list of archives to cleanup")
|
|
131
168
|
parser.add_argument('-l', '--list', action='store_true', help="List available archives.")
|
|
132
169
|
parser.add_argument('--verbose', action='store_true', help="Print various status messages to screen")
|
|
170
|
+
parser.add_argument('--log-level', type=str, help="`debug` or `trace`, default is `info`", default="info")
|
|
171
|
+
parser.add_argument('--log-stdout', action='store_true', help='also print log messages to stdout')
|
|
133
172
|
args = parser.parse_args()
|
|
134
173
|
|
|
135
174
|
args.config_file = os.path.expanduser(args.config_file)
|
|
@@ -142,7 +181,8 @@ def main():
|
|
|
142
181
|
config_settings = ConfigSettings(args.config_file)
|
|
143
182
|
|
|
144
183
|
start_time=int(time())
|
|
145
|
-
logger = setup_logging(config_settings.logfile_location,
|
|
184
|
+
logger = setup_logging(config_settings.logfile_location, args.log_level, args.log_stdout)
|
|
185
|
+
|
|
146
186
|
logger.info(f"=====================================")
|
|
147
187
|
logger.info(f"cleanup.py started, version: {about.__version__}")
|
|
148
188
|
|
|
@@ -156,7 +196,7 @@ def main():
|
|
|
156
196
|
args.verbose and (print(f"Backup dir: {config_settings.backup_dir}"))
|
|
157
197
|
args.verbose and (print(f"Logfile location: {config_settings.logfile_location}"))
|
|
158
198
|
args.verbose and (print(f"--alternate-archive-dir: {args.alternate_archive_dir}"))
|
|
159
|
-
args.verbose and (print(f"--cleanup-specific-
|
|
199
|
+
args.verbose and (print(f"--cleanup-specific-archives:{args.cleanup_specific_archives}"))
|
|
160
200
|
|
|
161
201
|
# run PREREQ scripts
|
|
162
202
|
if 'PREREQ' in config_settings.config:
|
|
@@ -175,15 +215,21 @@ def main():
|
|
|
175
215
|
|
|
176
216
|
|
|
177
217
|
if args.alternate_archive_dir:
|
|
218
|
+
if not os.path.exists(args.alternate_archive_dir):
|
|
219
|
+
logger.error(f"Alternate archive directory does not exist: {args.alternate_archive_dir}, exiting")
|
|
220
|
+
sys.exit(1)
|
|
221
|
+
if not os.path.isdir(args.alternate_archive_dir):
|
|
222
|
+
logger.error(f"Alternate archive directory is not a directory, exiting")
|
|
223
|
+
sys.exit(1)
|
|
178
224
|
config_settings.backup_dir = args.alternate_archive_dir
|
|
179
225
|
|
|
180
226
|
|
|
181
|
-
if args.
|
|
182
|
-
|
|
183
|
-
archive_names = args.
|
|
227
|
+
if args.cleanup_specific_archives:
|
|
228
|
+
logger.info(f"Cleaning up specific archives: {args.cleanup_specific_archives}")
|
|
229
|
+
archive_names = args.cleanup_specific_archives.split(',')
|
|
184
230
|
for archive_name in archive_names:
|
|
185
|
-
|
|
186
|
-
delete_archive(config_settings.backup_dir, archive_name.strip())
|
|
231
|
+
logger.info(f"Deleting archive: {archive_name}")
|
|
232
|
+
delete_archive(config_settings.backup_dir, archive_name.strip(), args)
|
|
187
233
|
elif args.list:
|
|
188
234
|
list_backups(config_settings.backup_dir, args.backup_definition)
|
|
189
235
|
else:
|
|
@@ -196,22 +242,22 @@ def main():
|
|
|
196
242
|
backup_definitions.append(file.split('.')[0])
|
|
197
243
|
|
|
198
244
|
for definition in backup_definitions:
|
|
199
|
-
delete_old_backups(config_settings.backup_dir, config_settings.diff_age, 'DIFF', definition)
|
|
200
|
-
delete_old_backups(config_settings.backup_dir, config_settings.incr_age, 'INCR', definition)
|
|
245
|
+
delete_old_backups(config_settings.backup_dir, config_settings.diff_age, 'DIFF', args, definition)
|
|
246
|
+
delete_old_backups(config_settings.backup_dir, config_settings.incr_age, 'INCR', args, definition)
|
|
201
247
|
|
|
202
248
|
|
|
203
249
|
end_time=int(time())
|
|
204
250
|
logger.info(f"END TIME: {end_time}")
|
|
205
251
|
|
|
206
|
-
error_lines = extract_error_lines(config_settings.logfile_location, start_time, end_time)
|
|
207
|
-
if len(error_lines) > 0:
|
|
208
|
-
args.verbose and print("\033[1m\033[31mErrors\033[0m encountered")
|
|
209
|
-
for line in error_lines:
|
|
210
|
-
args.verbose and print(line)
|
|
211
|
-
sys.exit(1)
|
|
212
|
-
else:
|
|
213
|
-
args.verbose and print("\033[1m\033[32mSUCCESS\033[0m No errors encountered")
|
|
214
|
-
sys.exit(0)
|
|
252
|
+
# error_lines = extract_error_lines(config_settings.logfile_location, start_time, end_time)
|
|
253
|
+
# if len(error_lines) > 0:
|
|
254
|
+
# args.verbose and print("\033[1m\033[31mErrors\033[0m encountered")
|
|
255
|
+
# for line in error_lines:
|
|
256
|
+
# args.verbose and print(line)
|
|
257
|
+
# sys.exit(1)
|
|
258
|
+
# else:
|
|
259
|
+
# args.verbose and print("\033[1m\033[32mSUCCESS\033[0m No errors encountered")
|
|
260
|
+
# sys.exit(0)
|
|
215
261
|
|
|
216
262
|
if __name__ == "__main__":
|
|
217
263
|
main()
|
dar_backup/dar_backup.py
CHANGED
|
@@ -66,7 +66,7 @@ def generic_backup(type: str, command: List[str], backup_file: str, backup_defin
|
|
|
66
66
|
raise Exception(str(process))
|
|
67
67
|
|
|
68
68
|
if process.returncode == 0 or process.returncode == 5:
|
|
69
|
-
add_catalog_command = ['manager', '--add-specific-archive' ,backup_file, '--config-file', args.config_file
|
|
69
|
+
add_catalog_command = ['manager', '--add-specific-archive' ,backup_file, '--config-file', args.config_file]
|
|
70
70
|
command_result = run_command(add_catalog_command, config_settings.command_timeout_secs)
|
|
71
71
|
if command_result.returncode == 0:
|
|
72
72
|
logger.info(f"Catalog for archive '{backup_file}' added successfully to its manager.")
|
|
@@ -209,7 +209,7 @@ def verify(args: argparse.Namespace, backup_file: str, backup_definition: str, c
|
|
|
209
209
|
for restored_file_path in random_files:
|
|
210
210
|
try:
|
|
211
211
|
logger.info(f"Restoring file: '{restored_file_path}' from backup to: '{config_settings.test_restore_dir}' for file comparing")
|
|
212
|
-
command = ['dar', '-x', backup_file, '-g', restored_file_path.lstrip("/"), '-R', config_settings.test_restore_dir, '-
|
|
212
|
+
command = ['dar', '-x', backup_file, '-g', restored_file_path.lstrip("/"), '-R', config_settings.test_restore_dir, '-Q', '-B', args.darrc, 'restore-options']
|
|
213
213
|
logger.info(f"Running command: {' '.join(map(shlex.quote, command))}")
|
|
214
214
|
process = run_command(command, config_settings.command_timeout_secs)
|
|
215
215
|
if process.returncode != 0:
|
|
@@ -228,7 +228,7 @@ def verify(args: argparse.Namespace, backup_file: str, backup_definition: str, c
|
|
|
228
228
|
|
|
229
229
|
|
|
230
230
|
|
|
231
|
-
def restore_backup(backup_name: str, config_settings: ConfigSettings, restore_dir: str, selection: str =None):
|
|
231
|
+
def restore_backup(backup_name: str, config_settings: ConfigSettings, restore_dir: str, darrc: str, selection: str =None):
|
|
232
232
|
"""
|
|
233
233
|
Restores a backup file to a specified directory.
|
|
234
234
|
|
|
@@ -239,7 +239,7 @@ def restore_backup(backup_name: str, config_settings: ConfigSettings, restore_di
|
|
|
239
239
|
selection (str, optional): A selection criteria to restore specific files or directories. Defaults to None.
|
|
240
240
|
"""
|
|
241
241
|
backup_file = os.path.join(config_settings.backup_dir, backup_name)
|
|
242
|
-
command = ['dar', '-x', backup_file, '-
|
|
242
|
+
command = ['dar', '-x', backup_file, '-Q', '-D']
|
|
243
243
|
if restore_dir:
|
|
244
244
|
if not os.path.exists(restore_dir):
|
|
245
245
|
os.makedirs(restore_dir)
|
|
@@ -249,6 +249,7 @@ def restore_backup(backup_name: str, config_settings: ConfigSettings, restore_di
|
|
|
249
249
|
if selection:
|
|
250
250
|
selection_criteria = shlex.split(selection)
|
|
251
251
|
command.extend(selection_criteria)
|
|
252
|
+
command.extend(['-B', darrc, 'restore-options']) # the .darrc `restore-options` section
|
|
252
253
|
logger.info(f"Running restore command: {' '.join(map(shlex.quote, command))}")
|
|
253
254
|
try:
|
|
254
255
|
process = run_command(command, config_settings.command_timeout_secs)
|
|
@@ -428,10 +429,9 @@ def perform_backup(args: argparse.Namespace, config_settings: ConfigSettings, ba
|
|
|
428
429
|
logger.error("Verification failed.")
|
|
429
430
|
|
|
430
431
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
logger.info("par2 files completed successfully.")
|
|
432
|
+
logger.info("Generate par2 redundancy files.")
|
|
433
|
+
generate_par2_files(backup_file, config_settings, args)
|
|
434
|
+
logger.info("par2 files completed successfully.")
|
|
435
435
|
|
|
436
436
|
|
|
437
437
|
except Exception as e:
|
|
@@ -677,7 +677,7 @@ def main():
|
|
|
677
677
|
list_contents(args.list_contents, config_settings.backup_dir, args.selection)
|
|
678
678
|
elif args.restore:
|
|
679
679
|
logger.debug(f"Restoring {args.restore} to {restore_dir}")
|
|
680
|
-
restore_backup(args.restore, config_settings, restore_dir, args.selection)
|
|
680
|
+
restore_backup(args.restore, config_settings, restore_dir, args.darrc, args.selection)
|
|
681
681
|
else:
|
|
682
682
|
parser.print_help()
|
|
683
683
|
|
dar_backup/manager.py
CHANGED
|
@@ -31,6 +31,8 @@ from . import __about__ as about
|
|
|
31
31
|
from dar_backup.config_settings import ConfigSettings
|
|
32
32
|
from dar_backup.util import run_command
|
|
33
33
|
from dar_backup.util import setup_logging
|
|
34
|
+
from dar_backup.util import CommandResult
|
|
35
|
+
|
|
34
36
|
from datetime import datetime
|
|
35
37
|
from time import time
|
|
36
38
|
from typing import Dict, List, NamedTuple
|
|
@@ -91,8 +93,16 @@ def list_catalogs(backup_def: str, config_settings: ConfigSettings) -> NamedTupl
|
|
|
91
93
|
database = f"{backup_def}{DB_SUFFIX}"
|
|
92
94
|
database_path = os.path.join(config_settings.backup_dir, database)
|
|
93
95
|
if not os.path.exists(database_path):
|
|
94
|
-
|
|
95
|
-
|
|
96
|
+
error_msg = f'Database not found: "{database_path}"'
|
|
97
|
+
logger.error(error_msg)
|
|
98
|
+
commandResult = CommandResult(
|
|
99
|
+
process=None,
|
|
100
|
+
stdout='',
|
|
101
|
+
stderr=error_msg,
|
|
102
|
+
returncode=1,
|
|
103
|
+
timeout=1,
|
|
104
|
+
command=[])
|
|
105
|
+
return commandResult
|
|
96
106
|
command = ['dar_manager', '--base', database_path, '--list']
|
|
97
107
|
process = run_command(command)
|
|
98
108
|
stdout, stderr = process.stdout, process.stderr
|
|
@@ -131,16 +141,41 @@ def cat_no_for_name(archive: str, config_settings: ConfigSettings) -> int:
|
|
|
131
141
|
|
|
132
142
|
|
|
133
143
|
|
|
144
|
+
def list_archive_contents(archive: str, config_settings: ConfigSettings) -> int :
|
|
145
|
+
"""
|
|
146
|
+
List the contents of a specific archive, given the archive name
|
|
147
|
+
"""
|
|
148
|
+
backup_def = backup_def_from_archive(archive)
|
|
149
|
+
database = f"{backup_def}{DB_SUFFIX}"
|
|
150
|
+
database_path = os.path.join(config_settings.backup_dir, database)
|
|
151
|
+
if not os.path.exists(database_path):
|
|
152
|
+
logger.error(f'Database not found: "{database_path}"')
|
|
153
|
+
return 1
|
|
154
|
+
cat_no = cat_no_for_name(archive, config_settings)
|
|
155
|
+
if cat_no < 0:
|
|
156
|
+
logger.error(f"archive: '{archive}' not found in database: '{database_path}'")
|
|
157
|
+
return 1
|
|
158
|
+
command = ['dar_manager', '--base', database_path, '-u', f"{cat_no}"]
|
|
159
|
+
process = run_command(command)
|
|
160
|
+
stdout, stderr = process.stdout, process.stderr
|
|
161
|
+
if process.returncode != 0:
|
|
162
|
+
logger.error(f'Error listing catalogs for: "{database_path}"')
|
|
163
|
+
logger.error(f"stderr: {stderr}")
|
|
164
|
+
logger.error(f"stdout: {stdout}")
|
|
165
|
+
else:
|
|
166
|
+
print(stdout)
|
|
167
|
+
return process.returncode
|
|
168
|
+
|
|
134
169
|
|
|
135
170
|
|
|
136
|
-
def list_catalog_contents(catalog_number: int, backup_def: str, config_settings: ConfigSettings):
|
|
171
|
+
def list_catalog_contents(catalog_number: int, backup_def: str, config_settings: ConfigSettings) -> int:
|
|
137
172
|
"""
|
|
138
173
|
List the contents of catalog # in catalog database for given backup definition
|
|
139
174
|
"""
|
|
140
175
|
database = f"{backup_def}{DB_SUFFIX}"
|
|
141
176
|
database_path = os.path.join(config_settings.backup_dir, database)
|
|
142
177
|
if not os.path.exists(database_path):
|
|
143
|
-
logger.error(f'
|
|
178
|
+
logger.error(f'Catalog database not found: "{database_path}"')
|
|
144
179
|
return 1
|
|
145
180
|
command = ['dar_manager', '--base', database_path, '-u', f"{catalog_number}"]
|
|
146
181
|
process = run_command(command)
|
|
@@ -278,32 +313,44 @@ def backup_def_from_archive(archive: str) -> str:
|
|
|
278
313
|
"""
|
|
279
314
|
return the backup definition from archive name
|
|
280
315
|
"""
|
|
316
|
+
logger.debug(f"Get backup definition from archive: '{archive}'")
|
|
281
317
|
search = re.search("(.*?)_", archive)
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
318
|
+
if search:
|
|
319
|
+
backup_def = search.group(1)
|
|
320
|
+
logger.debug(f"backup definition: '{backup_def}' from given archive '{archive}'")
|
|
321
|
+
return backup_def
|
|
322
|
+
logger.error(f"Could not find backup definition from archive name: '{archive}'")
|
|
323
|
+
return None
|
|
285
324
|
|
|
286
325
|
|
|
287
326
|
|
|
288
327
|
def remove_specific_archive(archive: str, config_settings: ConfigSettings) -> int:
|
|
328
|
+
"""
|
|
329
|
+
|
|
330
|
+
Returns:
|
|
331
|
+
- 0 if the archive was removed from it's catalog
|
|
332
|
+
- 1 if there was an error removing the archive
|
|
333
|
+
- 2 if the archive was not found in the catalog
|
|
334
|
+
|
|
335
|
+
"""
|
|
289
336
|
backup_def = backup_def_from_archive(archive)
|
|
290
337
|
database_path = os.path.join(config_settings.backup_dir, f"{backup_def}{DB_SUFFIX}")
|
|
291
338
|
cat_no = cat_no_for_name(archive, config_settings)
|
|
292
339
|
if cat_no >= 0:
|
|
293
340
|
command = ['dar_manager', '--base', database_path, "--delete", str(cat_no)]
|
|
294
|
-
process = run_command(command)
|
|
341
|
+
process: CommandResult = run_command(command)
|
|
342
|
+
logger.info(f"CommandResult: {process}")
|
|
295
343
|
else:
|
|
296
|
-
logger.
|
|
297
|
-
return
|
|
344
|
+
logger.warning(f"archive: '{archive}' not found in it's catalog database: {database_path}")
|
|
345
|
+
return 2
|
|
298
346
|
|
|
299
347
|
if process.returncode == 0:
|
|
300
348
|
logger.info(f"'{archive}' removed from it's catalog")
|
|
349
|
+
return 0
|
|
301
350
|
else:
|
|
302
351
|
logger.error(process.stdout)
|
|
303
352
|
logger.error(process.sterr)
|
|
304
|
-
|
|
305
|
-
return process.returncode
|
|
306
|
-
|
|
353
|
+
return 1
|
|
307
354
|
|
|
308
355
|
|
|
309
356
|
|
|
@@ -323,8 +370,9 @@ def main():
|
|
|
323
370
|
parser.add_argument('-d', '--backup-def', type=str, help='Restrict to work only on this backup definition')
|
|
324
371
|
parser.add_argument('--add-specific-archive', type=str, help='Add this archive to catalog database')
|
|
325
372
|
parser.add_argument('--remove-specific-archive', type=str, help='Remove this archive from catalog database')
|
|
326
|
-
parser.add_argument('--list-
|
|
373
|
+
parser.add_argument('-l', '--list-catalogs', action='store_true', help='List catalogs in databases for all backup definitions')
|
|
327
374
|
parser.add_argument('--list-catalog-contents', type=int, help="List contents of a catalog. Argument is the 'archive #', '-d <definition>' argument is also required")
|
|
375
|
+
parser.add_argument('--list-archive-contents', type=str, help="List contents of the archive's catalog.")
|
|
328
376
|
parser.add_argument('--find-file', type=str, help="List catalogs containing <path>/file. '-d <definition>' argument is also required")
|
|
329
377
|
parser.add_argument('--verbose', action='store_true', help='Be more verbose')
|
|
330
378
|
parser.add_argument('--log-level', type=str, help="`debug` or `trace`, default is `info`", default="info")
|
|
@@ -393,6 +441,11 @@ See section 15 and section 16 in the supplied "LICENSE" file.''')
|
|
|
393
441
|
sys.exit(1)
|
|
394
442
|
|
|
395
443
|
|
|
444
|
+
if args.list_archive_contents and not args.list_archive_contents.strip():
|
|
445
|
+
logger.error(f"--list-archive-contents <param> not given, exiting")
|
|
446
|
+
sys.exit(1)
|
|
447
|
+
|
|
448
|
+
|
|
396
449
|
if args.list_catalog_contents and not args.backup_def:
|
|
397
450
|
logger.error(f"--list-catalog-contents requires the --backup-def, exiting")
|
|
398
451
|
sys.exit(1)
|
|
@@ -431,15 +484,11 @@ See section 15 and section 16 in the supplied "LICENSE" file.''')
|
|
|
431
484
|
|
|
432
485
|
|
|
433
486
|
if args.remove_specific_archive:
|
|
434
|
-
|
|
435
|
-
if remove_specific_archive(args.remove_specific_archive, config_settings) == 0:
|
|
436
|
-
sys.exit(0)
|
|
437
|
-
else:
|
|
438
|
-
sys.exit(1)
|
|
487
|
+
return remove_specific_archive(args.remove_specific_archive, config_settings)
|
|
439
488
|
|
|
440
489
|
|
|
441
490
|
|
|
442
|
-
if args.
|
|
491
|
+
if args.list_catalogs:
|
|
443
492
|
if args.backup_def:
|
|
444
493
|
process = list_catalogs(args.backup_def, config_settings)
|
|
445
494
|
result = process.returncode
|
|
@@ -452,6 +501,11 @@ See section 15 and section 16 in the supplied "LICENSE" file.''')
|
|
|
452
501
|
result = 1
|
|
453
502
|
sys.exit(result)
|
|
454
503
|
|
|
504
|
+
|
|
505
|
+
if args.list_archive_contents:
|
|
506
|
+
result = list_archive_contents(args.list_archive_contents, config_settings)
|
|
507
|
+
sys.exit(result)
|
|
508
|
+
|
|
455
509
|
if args.list_catalog_contents:
|
|
456
510
|
result = list_catalog_contents(args.list_catalog_contents, args.backup_def, config_settings)
|
|
457
511
|
sys.exit(result)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dar-backup
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.8
|
|
4
4
|
Summary: A script to do full, differential and incremental backups using dar. Some files are restored from the backups during verification, after which par2 redundancy files are created. The script also has a cleanup feature to remove old backups and par2 files.
|
|
5
5
|
Project-URL: Homepage, https://github.com/per2jensen/dar-backup/tree/main/v2
|
|
6
6
|
Project-URL: Changelog, https://github.com/per2jensen/dar-backup/blob/main/v2/Changelog.md
|
|
@@ -681,7 +681,7 @@ License: GNU GENERAL PUBLIC LICENSE
|
|
|
681
681
|
Public License instead of this License. But first, please read
|
|
682
682
|
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
|
683
683
|
License-File: LICENSE
|
|
684
|
-
Classifier: Development Status ::
|
|
684
|
+
Classifier: Development Status :: 4 - Beta
|
|
685
685
|
Classifier: Intended Audience :: End Users/Desktop
|
|
686
686
|
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
687
687
|
Classifier: Operating System :: POSIX :: Linux
|
|
@@ -718,9 +718,13 @@ Description-Content-Type: text/markdown
|
|
|
718
718
|
These scripts are licensed under the GPLv3 license.
|
|
719
719
|
Read more here: https://www.gnu.org/licenses/gpl-3.0.en.html, or have a look at the ["LICENSE"](https://github.com/per2jensen/dar-backup/blob/main/LICENSE) file in this repository.
|
|
720
720
|
|
|
721
|
+
|
|
721
722
|
# Status
|
|
722
723
|
As of August 8, 2024 I am using the alpha versions of `dar-backup` (alpha-0.5.9 onwards) in my automated backup routine.
|
|
723
724
|
|
|
725
|
+
As of February 13, 2025, I have changed the status from alpha --> beta, as the featureset is in place and the alphas have worked well for a very long time.
|
|
726
|
+
|
|
727
|
+
|
|
724
728
|
**Breaking change in version 0.6.0**
|
|
725
729
|
|
|
726
730
|
Version 0.6.0 and forwards requires the config variable *COMMAND_TIMEOUT_SECS* in the config file.
|
|
@@ -931,8 +935,12 @@ in place in BACKUP.D_DIR (see config file)
|
|
|
931
935
|
````
|
|
932
936
|
dar-backup --full-backup
|
|
933
937
|
````
|
|
938
|
+
If you want to see dar-backup's log entries in the terminal, use the `--log-stdout` option. This is also useful if dar-backup is started by systemd.
|
|
939
|
+
|
|
940
|
+
If you want more log messages, use the `--log-level debug` option.
|
|
941
|
+
|
|
934
942
|
|
|
935
|
-
|
|
943
|
+
If you want a backup of a single definition, use the `-d <backup definition>` option. The definition's name is the filename of the definition in the `backup.d` config directory.
|
|
936
944
|
````
|
|
937
945
|
dar-backup --full-backup -d <your backup definition>
|
|
938
946
|
````
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
dar_backup/.darrc,sha256=-aerqivZmOsW_XBCh9IfbYTUvw0GkzDSr3Vx4GcNB1g,2113
|
|
2
|
+
dar_backup/__about__.py,sha256=qbWTdDuFyvScwNj95KArnOcQph9tiLZZ8rgNJYlJ4AE,21
|
|
3
|
+
dar_backup/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
dar_backup/cleanup.py,sha256=9yEdRR84XPtEvBGc2QfwGBQl2tdTPttjetHeiSc_TsM,11419
|
|
5
|
+
dar_backup/config_settings.py,sha256=CBMUhLOOZ-x7CRdS3vBDk4TYaGqC4N1Ot8IMH-qPaI0,3617
|
|
6
|
+
dar_backup/dar_backup.py,sha256=VbpyiCnoVvJuMWS7LO9wo8WIcDegCPVjb5Q7xN9J9Gg,32731
|
|
7
|
+
dar_backup/manager.py,sha256=HDa8eYF89QFhlBRR4EWRzzmswOW00S_w8ToZ5SARO_o,21359
|
|
8
|
+
dar_backup/util.py,sha256=SSSJYM9lQZfubhTUBlX1xDGWmCpYEF3ePARmlY544xM,11283
|
|
9
|
+
dar_backup-0.6.8.dist-info/METADATA,sha256=U3iy7Gzz-D6p5UePy0VLzX2lNbELs0teKBEZXr-sSqc,64610
|
|
10
|
+
dar_backup-0.6.8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
11
|
+
dar_backup-0.6.8.dist-info/entry_points.txt,sha256=x9vnW-JEl8mpDJC69f_XBcn0mBSkV1U0cyvFV-NAP1g,126
|
|
12
|
+
dar_backup-0.6.8.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
13
|
+
dar_backup-0.6.8.dist-info/RECORD,,
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
dar_backup/.darrc,sha256=3d9opAnnZGU9XLyQpTDsLtgo6hqsvZ3JU-yMLz-7_f0,2110
|
|
2
|
-
dar_backup/__about__.py,sha256=GTf8rijLTDSqTWQgxKQN312t-j2E-t3ioZB4U22DXxc,21
|
|
3
|
-
dar_backup/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
-
dar_backup/cleanup.py,sha256=DgmxSUwKrLLIQuYSIY_yTRhIuOMgI6ivjlQuH4u3wX4,9057
|
|
5
|
-
dar_backup/config_settings.py,sha256=CBMUhLOOZ-x7CRdS3vBDk4TYaGqC4N1Ot8IMH-qPaI0,3617
|
|
6
|
-
dar_backup/dar_backup.py,sha256=oUlGCLeYwkJKSqn1qzKqkhpoQVTp-fCWyJcpmkSnLjc,32703
|
|
7
|
-
dar_backup/manager.py,sha256=7bo64O4Pk5mLBp5NaID7YQ9GOuLunl5R42z_SUORW-Q,19215
|
|
8
|
-
dar_backup/util.py,sha256=SSSJYM9lQZfubhTUBlX1xDGWmCpYEF3ePARmlY544xM,11283
|
|
9
|
-
dar_backup-0.6.6.dist-info/METADATA,sha256=NklByKxLaD_ATU-XniHmM_EaOiBc5Gen93QkqVO2jfY,64184
|
|
10
|
-
dar_backup-0.6.6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
11
|
-
dar_backup-0.6.6.dist-info/entry_points.txt,sha256=x9vnW-JEl8mpDJC69f_XBcn0mBSkV1U0cyvFV-NAP1g,126
|
|
12
|
-
dar_backup-0.6.6.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
13
|
-
dar_backup-0.6.6.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|