|
4 | 4 | import shlex |
5 | 5 | import subprocess |
6 | 6 | import sys |
| 7 | +import tempfile |
7 | 8 | import textwrap |
8 | 9 | from pathlib import Path |
9 | 10 |
|
10 | 11 | from .rsnapshots import get_snapshots |
11 | 12 | from .rsynchl import get_rsyncsnapshots |
12 | 13 | from .rsync_tmbackup import get_tmbackup_snapshots |
| 14 | +from .borg import get_borg_archives |
13 | 15 |
|
14 | 16 | log = logging.getLogger(__name__) |
15 | 17 |
|
16 | 18 |
|
17 | 19 | def borg_import(args, archive_name, path, timestamp=None): |
18 | | - borg_cmdline = ['borg', 'create'] |
| 20 | + borg_cmdline = ['borg', 'create', '--numeric-ids'] |
19 | 21 | if timestamp: |
20 | 22 | borg_cmdline += '--timestamp', timestamp.isoformat() |
21 | 23 | if args.create_options: |
@@ -282,6 +284,67 @@ def import_rsync_tmbackup(self, args): |
282 | 284 | import_journal.unlink() |
283 | 285 |
|
284 | 286 |
|
| 287 | +class borgImporter(Importer): |
| 288 | + name = 'borg' |
| 289 | + description = 'import archives from another Borg repository' |
| 290 | + epilog = """ |
| 291 | + Imports archives from an existing Borg repository into a new one. |
| 292 | +
|
| 293 | + This is useful when a Borg repository needs to be rebuilt and all archives |
| 294 | + transferred from the old repository to a new one. |
| 295 | +
|
| 296 | + The importer extracts each archive from the source repository to a temporary |
| 297 | + directory and then creates a new archive with the same name and timestamp in |
| 298 | + the destination repository. |
| 299 | +
|
| 300 | + By default, archive names are preserved. Use --prefix to add a prefix to |
| 301 | + the imported archive names. |
| 302 | + """ |
| 303 | + |
| 304 | + def populate_parser(self, parser): |
| 305 | + parser.add_argument('source_repository', metavar='SOURCE_REPOSITORY', |
| 306 | + help='Source Borg repository (must be a valid Borg repository spec)') |
| 307 | + parser.add_argument('repository', metavar='DESTINATION_REPOSITORY', |
| 308 | + help='Destination Borg repository (must be a valid Borg repository spec)') |
| 309 | + parser.set_defaults(function=self.import_borg) |
| 310 | + |
| 311 | + def import_borg(self, args): |
| 312 | + existing_archives = list_borg_archives(args) |
| 313 | + |
| 314 | + for archive in get_borg_archives(args.source_repository): |
| 315 | + name = archive['name'] |
| 316 | + timestamp = archive['timestamp'].replace(microsecond=0) |
| 317 | + archive_name = args.prefix + name |
| 318 | + |
| 319 | + if archive_name in existing_archives: |
| 320 | + print('Skipping (already exists in repository):', name) |
| 321 | + continue |
| 322 | + |
| 323 | + print('Importing {} (timestamp {}) '.format(name, timestamp), end='') |
| 324 | + if archive_name != name: |
| 325 | + print('as', archive_name) |
| 326 | + else: |
| 327 | + print() |
| 328 | + |
| 329 | + # Create a temporary directory for extraction |
| 330 | + with tempfile.TemporaryDirectory() as extract_path: |
| 331 | + try: |
| 332 | + # Extract the archive from the source repository |
| 333 | + extract_cmdline = ['borg', 'extract', '--numeric-owner'] |
| 334 | + extract_cmdline.append(args.source_repository + '::' + name) |
| 335 | + |
| 336 | + print(' Extracting archive to temporary directory...') |
| 337 | + subprocess.check_call(extract_cmdline, cwd=extract_path) |
| 338 | + |
| 339 | + # Create a new archive in the destination repository |
| 340 | + borg_import(args, archive_name, extract_path, timestamp=timestamp) |
| 341 | + |
| 342 | + except subprocess.CalledProcessError as cpe: |
| 343 | + print('Error during import of {}: {}'.format(name, cpe)) |
| 344 | + if cpe.returncode != 1: # Borg returns 1 for warnings |
| 345 | + raise |
| 346 | + |
| 347 | + |
285 | 348 | def build_parser(): |
286 | 349 | common_parser = argparse.ArgumentParser(add_help=False) |
287 | 350 | common_group = common_parser.add_argument_group('Common options') |
|
0 commit comments