dar-backup 0.6.6__py3-none-any.whl → 0.6.7__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 CHANGED
@@ -1 +1 @@
1
- __version__ = "0.6.6"
1
+ __version__ = "0.6.7"
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 not files_deleted:
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-archive', type=str, help="List of archives to cleanup")
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, logging.INFO)
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-archive: {args.cleanup_specific_archive}"))
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.cleanup_specific_archive:
182
- print(f"Cleaning up specific archives: {args.cleanup_specific_archive}")
183
- archive_names = args.cleanup_specific_archive.split(',')
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
- print(f"Deleting archive: {archive_name}")
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/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
- logger.error(f'Database not found: "{database_path}"')
95
- return 1
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
@@ -278,32 +288,44 @@ def backup_def_from_archive(archive: str) -> str:
278
288
  """
279
289
  return the backup definition from archive name
280
290
  """
291
+ logger.debug(f"Get backup definition from archive: '{archive}'")
281
292
  search = re.search("(.*?)_", archive)
282
- backup_def = search.group(1)
283
- logger.debug(f"backup definition: '{backup_def}' from given archive '{archive}'")
284
- return backup_def
293
+ if search:
294
+ backup_def = search.group(1)
295
+ logger.debug(f"backup definition: '{backup_def}' from given archive '{archive}'")
296
+ return backup_def
297
+ logger.error(f"Could not find backup definition from archive name: '{archive}'")
298
+ return None
285
299
 
286
300
 
287
301
 
288
302
  def remove_specific_archive(archive: str, config_settings: ConfigSettings) -> int:
303
+ """
304
+
305
+ Returns:
306
+ - 0 if the archive was removed from it's catalog
307
+ - 1 if there was an error removing the archive
308
+ - 2 if the archive was not found in the catalog
309
+
310
+ """
289
311
  backup_def = backup_def_from_archive(archive)
290
312
  database_path = os.path.join(config_settings.backup_dir, f"{backup_def}{DB_SUFFIX}")
291
313
  cat_no = cat_no_for_name(archive, config_settings)
292
314
  if cat_no >= 0:
293
315
  command = ['dar_manager', '--base', database_path, "--delete", str(cat_no)]
294
- process = run_command(command)
316
+ process: CommandResult = run_command(command)
317
+ logger.info(f"CommandResult: {process}")
295
318
  else:
296
- logger.error(f"archive: '{archive}' not found in in't catalog database: {database_path}")
297
- return cat_no
319
+ logger.warning(f"archive: '{archive}' not found in it's catalog database: {database_path}")
320
+ return 2
298
321
 
299
322
  if process.returncode == 0:
300
323
  logger.info(f"'{archive}' removed from it's catalog")
324
+ return 0
301
325
  else:
302
326
  logger.error(process.stdout)
303
327
  logger.error(process.sterr)
304
-
305
- return process.returncode
306
-
328
+ return 1
307
329
 
308
330
 
309
331
 
@@ -323,7 +345,7 @@ def main():
323
345
  parser.add_argument('-d', '--backup-def', type=str, help='Restrict to work only on this backup definition')
324
346
  parser.add_argument('--add-specific-archive', type=str, help='Add this archive to catalog database')
325
347
  parser.add_argument('--remove-specific-archive', type=str, help='Remove this archive from catalog database')
326
- parser.add_argument('--list-catalog', action='store_true', help='List catalogs in databases for all backup definitions')
348
+ parser.add_argument('-l', '--list-catalogs', action='store_true', help='List catalogs in databases for all backup definitions')
327
349
  parser.add_argument('--list-catalog-contents', type=int, help="List contents of a catalog. Argument is the 'archive #', '-d <definition>' argument is also required")
328
350
  parser.add_argument('--find-file', type=str, help="List catalogs containing <path>/file. '-d <definition>' argument is also required")
329
351
  parser.add_argument('--verbose', action='store_true', help='Be more verbose')
@@ -431,15 +453,11 @@ See section 15 and section 16 in the supplied "LICENSE" file.''')
431
453
 
432
454
 
433
455
  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)
456
+ return remove_specific_archive(args.remove_specific_archive, config_settings)
439
457
 
440
458
 
441
459
 
442
- if args.list_catalog:
460
+ if args.list_catalogs:
443
461
  if args.backup_def:
444
462
  process = list_catalogs(args.backup_def, config_settings)
445
463
  result = process.returncode
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dar-backup
3
- Version: 0.6.6
3
+ Version: 0.6.7
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
@@ -931,8 +931,12 @@ in place in BACKUP.D_DIR (see config file)
931
931
  ````
932
932
  dar-backup --full-backup
933
933
  ````
934
+ 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.
934
935
 
935
- or a backup of a single definition. The definition's name is the filename of the definition in the `backup.d` config directory.
936
+ If you want more log messages, use the `--log-level debug` option.
937
+
938
+
939
+ 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
940
  ````
937
941
  dar-backup --full-backup -d <your backup definition>
938
942
  ````
@@ -0,0 +1,13 @@
1
+ dar_backup/.darrc,sha256=3d9opAnnZGU9XLyQpTDsLtgo6hqsvZ3JU-yMLz-7_f0,2110
2
+ dar_backup/__about__.py,sha256=0ouks3vBIBYQpXQLGG2c_DveClXKjoOTb3ZY9LRV9Xw,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=oUlGCLeYwkJKSqn1qzKqkhpoQVTp-fCWyJcpmkSnLjc,32703
7
+ dar_backup/manager.py,sha256=S66gC6m-xaMMZR3MtaB0VGYEaGPohAO0DNMSSXvimM4,19869
8
+ dar_backup/util.py,sha256=SSSJYM9lQZfubhTUBlX1xDGWmCpYEF3ePARmlY544xM,11283
9
+ dar_backup-0.6.7.dist-info/METADATA,sha256=QB3M9KzbnyRUhRBbljmYFN5rYEhvzVBB2EbOD6QWsTs,64452
10
+ dar_backup-0.6.7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
11
+ dar_backup-0.6.7.dist-info/entry_points.txt,sha256=x9vnW-JEl8mpDJC69f_XBcn0mBSkV1U0cyvFV-NAP1g,126
12
+ dar_backup-0.6.7.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
13
+ dar_backup-0.6.7.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,,