dar-backup 0.6.10__py3-none-any.whl → 0.6.11__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 +1 -1
- dar_backup/dar_backup.py +40 -29
- dar_backup/util.py +118 -0
- {dar_backup-0.6.10.dist-info → dar_backup-0.6.11.dist-info}/METADATA +104 -19
- {dar_backup-0.6.10.dist-info → dar_backup-0.6.11.dist-info}/RECORD +8 -8
- {dar_backup-0.6.10.dist-info → dar_backup-0.6.11.dist-info}/WHEEL +0 -0
- {dar_backup-0.6.10.dist-info → dar_backup-0.6.11.dist-info}/entry_points.txt +0 -0
- {dar_backup-0.6.10.dist-info → dar_backup-0.6.11.dist-info}/licenses/LICENSE +0 -0
dar_backup/__about__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.6.
|
|
1
|
+
__version__ = "0.6.11"
|
dar_backup/dar_backup.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
|
|
3
3
|
import argparse
|
|
4
|
-
import datetime
|
|
5
4
|
import filecmp
|
|
6
5
|
|
|
7
6
|
import os
|
|
@@ -81,30 +80,31 @@ def generic_backup(type: str, command: List[str], backup_file: str, backup_defin
|
|
|
81
80
|
raise BackupError(f"Unexpected error during backup: {e}") from e
|
|
82
81
|
|
|
83
82
|
|
|
84
|
-
|
|
85
|
-
def find_files_with_paths(
|
|
83
|
+
|
|
84
|
+
def find_files_with_paths(xml_root: ET.Element):
|
|
86
85
|
"""
|
|
87
|
-
|
|
86
|
+
Finds files within an XML element and returns a list of file paths with their sizes.
|
|
88
87
|
|
|
89
88
|
Args:
|
|
90
|
-
|
|
91
|
-
current_path (str, optional): The current path of the directory element. Defaults to "".
|
|
89
|
+
xml_root (Element): The root element of the XML.
|
|
92
90
|
|
|
93
91
|
Returns:
|
|
94
92
|
list: A list of tuples containing file paths and their sizes.
|
|
95
93
|
"""
|
|
96
|
-
logger.debug(
|
|
94
|
+
logger.debug("Generating list of tuples with file paths and sizes for File elements in dar xml output")
|
|
97
95
|
files = []
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
for
|
|
101
|
-
if
|
|
102
|
-
|
|
96
|
+
current_path = []
|
|
97
|
+
|
|
98
|
+
for elem in xml_root.iter():
|
|
99
|
+
if elem.tag == "directory":
|
|
100
|
+
current_path.append(elem.get('name'))
|
|
101
|
+
elif elem.tag == "file":
|
|
102
|
+
file_path = ("/".join(current_path + [elem.get('name')]), elem.get('size'))
|
|
103
103
|
files.append(file_path)
|
|
104
|
-
elif
|
|
105
|
-
|
|
106
|
-
return files
|
|
104
|
+
elif elem.tag == "directory" and elem.get('name') in current_path:
|
|
105
|
+
current_path.pop()
|
|
107
106
|
|
|
107
|
+
return files
|
|
108
108
|
|
|
109
109
|
|
|
110
110
|
def find_files_between_min_and_max_size(backed_up_files: list[(str, str)], config_settings: ConfigSettings):
|
|
@@ -238,20 +238,20 @@ def restore_backup(backup_name: str, config_settings: ConfigSettings, restore_di
|
|
|
238
238
|
restore_dir (str): The directory where the backup should be restored to.
|
|
239
239
|
selection (str, optional): A selection criteria to restore specific files or directories. Defaults to None.
|
|
240
240
|
"""
|
|
241
|
-
backup_file = os.path.join(config_settings.backup_dir, backup_name)
|
|
242
|
-
command = ['dar', '-x', backup_file, '-Q', '-D']
|
|
243
|
-
if restore_dir:
|
|
244
|
-
if not os.path.exists(restore_dir):
|
|
245
|
-
os.makedirs(restore_dir)
|
|
246
|
-
command.extend(['-R', restore_dir])
|
|
247
|
-
else:
|
|
248
|
-
raise RestoreError("Restore directory ('-R <dir>') not specified")
|
|
249
|
-
if selection:
|
|
250
|
-
selection_criteria = shlex.split(selection)
|
|
251
|
-
command.extend(selection_criteria)
|
|
252
|
-
command.extend(['-B', darrc, 'restore-options']) # the .darrc `restore-options` section
|
|
253
|
-
logger.info(f"Running restore command: {' '.join(map(shlex.quote, command))}")
|
|
254
241
|
try:
|
|
242
|
+
backup_file = os.path.join(config_settings.backup_dir, backup_name)
|
|
243
|
+
command = ['dar', '-x', backup_file, '-Q', '-D']
|
|
244
|
+
if restore_dir:
|
|
245
|
+
if not os.path.exists(restore_dir):
|
|
246
|
+
os.makedirs(restore_dir)
|
|
247
|
+
command.extend(['-R', restore_dir])
|
|
248
|
+
else:
|
|
249
|
+
raise RestoreError("Restore directory ('-R <dir>') not specified")
|
|
250
|
+
if selection:
|
|
251
|
+
selection_criteria = shlex.split(selection)
|
|
252
|
+
command.extend(selection_criteria)
|
|
253
|
+
command.extend(['-B', darrc, 'restore-options']) # the .darrc `restore-options` section
|
|
254
|
+
logger.info(f"Running restore command: {' '.join(map(shlex.quote, command))}")
|
|
255
255
|
process = run_command(command, config_settings.command_timeout_secs)
|
|
256
256
|
if process.returncode == 0:
|
|
257
257
|
logger.info(f"Restore completed successfully to: '{restore_dir}'")
|
|
@@ -260,6 +260,9 @@ def restore_backup(backup_name: str, config_settings: ConfigSettings, restore_di
|
|
|
260
260
|
raise RestoreError(str(process))
|
|
261
261
|
except subprocess.CalledProcessError as e:
|
|
262
262
|
raise RestoreError(f"Restore command failed: {e}") from e
|
|
263
|
+
except OSError as e:
|
|
264
|
+
logger.error(f"Failed to create restore directory: {e}")
|
|
265
|
+
raise RestoreError("Could not create restore directory")
|
|
263
266
|
except Exception as e:
|
|
264
267
|
raise RestoreError(f"Unexpected error during restore: {e}") from e
|
|
265
268
|
|
|
@@ -285,7 +288,7 @@ def get_backed_up_files(backup_name: str, backup_dir: str):
|
|
|
285
288
|
# Parse the XML data
|
|
286
289
|
root = ET.fromstring(process.stdout)
|
|
287
290
|
output = None # help gc
|
|
288
|
-
# Extract full paths and file size for all <
|
|
291
|
+
# Extract full paths and file size for all <file> elements
|
|
289
292
|
file_paths = find_files_with_paths(root)
|
|
290
293
|
root = None # help gc
|
|
291
294
|
logger.trace(str(process))
|
|
@@ -624,6 +627,14 @@ def main():
|
|
|
624
627
|
else:
|
|
625
628
|
logger.error(f"Supplied .darrc: '{args.darrc}' does not exist or is not a file")
|
|
626
629
|
|
|
630
|
+
if not args.config_file:
|
|
631
|
+
logger.error(f"Config file not specified, exiting")
|
|
632
|
+
sys.exit(1)
|
|
633
|
+
|
|
634
|
+
if not os.path.exists(args.config_file):
|
|
635
|
+
logger.error(f"Config file {args.config_file} does not exist.")
|
|
636
|
+
sys.exit(1)
|
|
637
|
+
|
|
627
638
|
|
|
628
639
|
try:
|
|
629
640
|
start_time=int(time())
|
dar_backup/util.py
CHANGED
|
@@ -14,6 +14,7 @@ import os
|
|
|
14
14
|
import re
|
|
15
15
|
import subprocess
|
|
16
16
|
import shlex
|
|
17
|
+
import shutil
|
|
17
18
|
import sys
|
|
18
19
|
import threading
|
|
19
20
|
import traceback
|
|
@@ -123,10 +124,113 @@ def _stream_reader(pipe, log_func, output_accumulator: List[str]):
|
|
|
123
124
|
log_func(stripped_line) # Log the output in real time
|
|
124
125
|
|
|
125
126
|
|
|
127
|
+
|
|
126
128
|
def run_command(command: List[str], timeout: int = 30) -> CommandResult:
|
|
127
129
|
"""
|
|
128
130
|
Executes a given command via subprocess, logs its output in real time, and returns the result.
|
|
129
131
|
|
|
132
|
+
Args:
|
|
133
|
+
command (list): The command to be executed, represented as a list of strings.
|
|
134
|
+
timeout (int): The maximum time in seconds to wait for the command to complete. Defaults to 30 seconds.
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
A CommandResult NamedTuple with the following properties:
|
|
138
|
+
- process: subprocess.CompletedProcess
|
|
139
|
+
- stdout: str: The full standard output of the command.
|
|
140
|
+
- stderr: str: The full standard error of the command.
|
|
141
|
+
- returncode: int: The return code of the command.
|
|
142
|
+
- timeout: int: The timeout value in seconds used to run the command.
|
|
143
|
+
- command: list[str]: The command executed.
|
|
144
|
+
|
|
145
|
+
Logs:
|
|
146
|
+
- Logs standard output (`stdout`) in real-time at the INFO log level.
|
|
147
|
+
- Logs standard error (`stderr`) in real-time at the ERROR log level.
|
|
148
|
+
|
|
149
|
+
Raises:
|
|
150
|
+
subprocess.TimeoutExpired: If the command execution times out (see `timeout` parameter).
|
|
151
|
+
Exception: If other exceptions occur during command execution.
|
|
152
|
+
FileNotFoundError: If the command is not found.
|
|
153
|
+
|
|
154
|
+
Notes:
|
|
155
|
+
- While the command runs, its `stdout` and `stderr` streams are logged in real-time.
|
|
156
|
+
- The returned `stdout` and `stderr` capture the complete output, even though the output is also logged.
|
|
157
|
+
- The command is forcibly terminated if it exceeds the specified timeout.
|
|
158
|
+
"""
|
|
159
|
+
stdout_lines = [] # To accumulate stdout
|
|
160
|
+
stderr_lines = [] # To accumulate stderr
|
|
161
|
+
process = None # Track the process for cleanup
|
|
162
|
+
stdout_thread = None
|
|
163
|
+
stderr_thread = None
|
|
164
|
+
|
|
165
|
+
try:
|
|
166
|
+
# Check if the command exists before executing
|
|
167
|
+
if not shutil.which(command[0]):
|
|
168
|
+
raise FileNotFoundError(f"Command not found: {command[0]}")
|
|
169
|
+
|
|
170
|
+
logger.debug(f"Running command: {command}")
|
|
171
|
+
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
|
172
|
+
|
|
173
|
+
# Start threads to read and log stdout and stderr
|
|
174
|
+
stdout_thread = threading.Thread(target=_stream_reader, args=(process.stdout, logger.info, stdout_lines))
|
|
175
|
+
stderr_thread = threading.Thread(target=_stream_reader, args=(process.stderr, logger.error, stderr_lines))
|
|
176
|
+
|
|
177
|
+
stdout_thread.start()
|
|
178
|
+
stderr_thread.start()
|
|
179
|
+
|
|
180
|
+
# Wait for process to complete or timeout
|
|
181
|
+
process.wait(timeout=timeout)
|
|
182
|
+
|
|
183
|
+
except FileNotFoundError as e:
|
|
184
|
+
logger.error(f"Command not found: {command[0]}")
|
|
185
|
+
return CommandResult(
|
|
186
|
+
process=None,
|
|
187
|
+
stdout="",
|
|
188
|
+
stderr=str(e),
|
|
189
|
+
returncode=127,
|
|
190
|
+
timeout=timeout,
|
|
191
|
+
command=command
|
|
192
|
+
)
|
|
193
|
+
except subprocess.TimeoutExpired:
|
|
194
|
+
if process:
|
|
195
|
+
process.terminate()
|
|
196
|
+
logger.error(f"Command: '{command}' timed out and was terminated.")
|
|
197
|
+
raise
|
|
198
|
+
except Exception as e:
|
|
199
|
+
logger.error(f"Error running command: {command}", exc_info=True)
|
|
200
|
+
raise
|
|
201
|
+
finally:
|
|
202
|
+
# Ensure threads are joined to clean up (only if they were started)
|
|
203
|
+
if stdout_thread and stdout_thread.is_alive():
|
|
204
|
+
stdout_thread.join()
|
|
205
|
+
if stderr_thread and stderr_thread.is_alive():
|
|
206
|
+
stderr_thread.join()
|
|
207
|
+
|
|
208
|
+
# Ensure process streams are closed
|
|
209
|
+
if process and process.stdout:
|
|
210
|
+
process.stdout.close()
|
|
211
|
+
if process and process.stderr:
|
|
212
|
+
process.stderr.close()
|
|
213
|
+
|
|
214
|
+
# Combine captured stdout and stderr lines into single strings
|
|
215
|
+
stdout = "\n".join(stdout_lines)
|
|
216
|
+
stderr = "\n".join(stderr_lines)
|
|
217
|
+
|
|
218
|
+
# Build the result object
|
|
219
|
+
result = CommandResult(
|
|
220
|
+
process=process,
|
|
221
|
+
stdout=stdout,
|
|
222
|
+
stderr=stderr,
|
|
223
|
+
returncode=process.returncode,
|
|
224
|
+
timeout=timeout,
|
|
225
|
+
command=command
|
|
226
|
+
)
|
|
227
|
+
logger.debug(f"Command result: {result}")
|
|
228
|
+
return result
|
|
229
|
+
|
|
230
|
+
def run_command2(command: List[str], timeout: int = 30) -> CommandResult:
|
|
231
|
+
"""
|
|
232
|
+
Executes a given command via subprocess, logs its output in real time, and returns the result.
|
|
233
|
+
|
|
130
234
|
Args:
|
|
131
235
|
command (list): The command to be executed, represented as a list of strings.
|
|
132
236
|
timeout (int): The maximum time in seconds to wait for the command to complete. Defaults to 30 seconds.
|
|
@@ -158,6 +262,10 @@ def run_command(command: List[str], timeout: int = 30) -> CommandResult:
|
|
|
158
262
|
process = None # Track the process for cleanup
|
|
159
263
|
|
|
160
264
|
try:
|
|
265
|
+
# Check if the command exists before executing
|
|
266
|
+
if not shutil.which(command[0]):
|
|
267
|
+
raise FileNotFoundError(f"Command not found: {command[0]}")
|
|
268
|
+
|
|
161
269
|
logger.debug(f"Running command: {command}")
|
|
162
270
|
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
|
163
271
|
|
|
@@ -171,6 +279,16 @@ def run_command(command: List[str], timeout: int = 30) -> CommandResult:
|
|
|
171
279
|
# Wait for process to complete or timeout
|
|
172
280
|
process.wait(timeout=timeout)
|
|
173
281
|
|
|
282
|
+
except FileNotFoundError as e:
|
|
283
|
+
logger.error(f"Command not found: {command[0]}")
|
|
284
|
+
return CommandResult(
|
|
285
|
+
process=None,
|
|
286
|
+
stdout="",
|
|
287
|
+
stderr=str(e),
|
|
288
|
+
returncode=127,
|
|
289
|
+
timeout=timeout,
|
|
290
|
+
command=command
|
|
291
|
+
)
|
|
174
292
|
except subprocess.TimeoutExpired:
|
|
175
293
|
if process:
|
|
176
294
|
process.terminate()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dar-backup
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.11
|
|
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
|
|
@@ -690,9 +690,9 @@ Classifier: Topic :: System :: Archiving :: Backup
|
|
|
690
690
|
Requires-Python: >=3.9
|
|
691
691
|
Description-Content-Type: text/markdown
|
|
692
692
|
|
|
693
|
-
# Full, differential or incremental backups using 'dar'
|
|
693
|
+
# Full, differential or incremental backups using 'dar'
|
|
694
694
|
|
|
695
|
-
The wonderful 'dar' [Disk Archiver]
|
|
695
|
+
The wonderful 'dar' [Disk Archiver](https://github.com/Edrusb/DAR) is used for
|
|
696
696
|
the heavy lifting, together with the par2 suite in these scripts.
|
|
697
697
|
|
|
698
698
|
## Table of Contents
|
|
@@ -705,30 +705,37 @@ Description-Content-Type: text/markdown
|
|
|
705
705
|
- [Homepage - Github](#homepage---github)
|
|
706
706
|
- [Requirements](#requirements)
|
|
707
707
|
- [Config file](#config-file)
|
|
708
|
-
- [How to run](#how-to-run)
|
|
709
|
-
- [1](#1)
|
|
710
|
-
- [2](#2)
|
|
711
|
-
- [3](#3)
|
|
712
|
-
- [4](#4)
|
|
713
|
-
- [5](#5)
|
|
714
|
-
- [6](#6)
|
|
708
|
+
- [How to install & run](#how-to-run)
|
|
709
|
+
- [1 - config file](#1---config-file)
|
|
710
|
+
- [2 - backup definitions](#2---backup-definitions)
|
|
711
|
+
- [3 - installation](#3---installation)
|
|
712
|
+
- [4 - generate catalog databases](#4---generate-catalog-databases)
|
|
713
|
+
- [5 - do FULL backups](#5---do-full-backups)
|
|
714
|
+
- [6 - deactivate venv](#6---deactivate-venv)
|
|
715
715
|
- [.darrc](#darrc)
|
|
716
716
|
- [Systemctl examples](#systemctl-examples)
|
|
717
717
|
- [Service: dar-back --incremental-backup](#service-dar-back---incremental-backup)
|
|
718
718
|
- [Timer: dar-back --incremental-backup](#timer-dar-back---incremental-backup)
|
|
719
719
|
- [List contents of an archive](#list-contents-of-an-archive)
|
|
720
|
-
- [dar file selection examples](#dar-file-selection-
|
|
720
|
+
- [dar file selection examples](#dar-file-selection-exmaples)
|
|
721
721
|
- [Select a directory](#select-a-directory)
|
|
722
722
|
- [Select file dates in the directory](#select-file-dates-in-the-directory)
|
|
723
723
|
- [Exclude .xmp files from that date](#exclude-xmp-files-from-that-date)
|
|
724
724
|
- [Restoring](#restoring)
|
|
725
725
|
- [Default location for restores](#default-location-for-restores)
|
|
726
|
-
- [--restore-dir option](
|
|
726
|
+
- [--restore-dir option](#--restore-dir-option)
|
|
727
727
|
- [A single file](#a-single-file)
|
|
728
728
|
- [A directory](#a-directory)
|
|
729
729
|
- [.NEF from a specific date](#nef-from-a-specific-date)
|
|
730
|
+
- [Restore test fails with exit code 4](#restore-test-fails-with-exit-code-4)
|
|
731
|
+
- [Restore test fails with exit code 5](#restore-test-fails-with-exit-code-5)
|
|
732
|
+
- [Par2](#par2)
|
|
733
|
+
- [Par2 to verify/repair](#par2-to-verifyrepair)
|
|
734
|
+
- [Par2 create redundancy files](#par2-create-redundancy-files)
|
|
730
735
|
- [Points of interest](#points-of-interest)
|
|
736
|
+
- [Merge FULL with DIFF, creating new FULL](#merge-full-with-diff-creating-new-full)
|
|
731
737
|
- [dar manager databases](#dar-manager-databases)
|
|
738
|
+
- [Performance tip due to par2](#performance-tip-due-to-par2)
|
|
732
739
|
- [.darrc sets -vd -vf (since v0.6.4)](#darrc-sets--vd--vf-since-v064)
|
|
733
740
|
- [Reference](#reference)
|
|
734
741
|
- [dar-backup.py](#dar-backuppy)
|
|
@@ -793,7 +800,7 @@ The default configuration is expected here: ~/.config/dar-backup/dar-backup.conf
|
|
|
793
800
|
|
|
794
801
|
## How to run
|
|
795
802
|
|
|
796
|
-
### 1
|
|
803
|
+
### 1 - config file
|
|
797
804
|
|
|
798
805
|
Config file default location is $HOME/.config/dar-backup/dar-backup.conf
|
|
799
806
|
|
|
@@ -838,7 +845,7 @@ ENABLED = True
|
|
|
838
845
|
|
|
839
846
|
````
|
|
840
847
|
|
|
841
|
-
### 2
|
|
848
|
+
### 2 - backup definitions
|
|
842
849
|
|
|
843
850
|
Put your backup definitions in the directory $BACKUP.D_DIR (defined in the config file)
|
|
844
851
|
|
|
@@ -889,7 +896,7 @@ Example of backup definition for a home directory
|
|
|
889
896
|
--cache-directory-tagging
|
|
890
897
|
````
|
|
891
898
|
|
|
892
|
-
### 3
|
|
899
|
+
### 3 - installation
|
|
893
900
|
|
|
894
901
|
Installation is currently in a venv. These commands are installed in the venv:
|
|
895
902
|
|
|
@@ -925,7 +932,7 @@ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW,
|
|
|
925
932
|
See section 15 and section 16 in the supplied "LICENSE" file.
|
|
926
933
|
````
|
|
927
934
|
|
|
928
|
-
### 4
|
|
935
|
+
### 4 - generate catalog databases
|
|
929
936
|
|
|
930
937
|
Generate the archive catalog database(s).
|
|
931
938
|
`dar-backup` expects the catalog databases to be in place, it does not automatically create them (by design)
|
|
@@ -934,7 +941,7 @@ Generate the archive catalog database(s).
|
|
|
934
941
|
manager --create-db
|
|
935
942
|
````
|
|
936
943
|
|
|
937
|
-
### 5
|
|
944
|
+
### 5 - do FULL backups
|
|
938
945
|
|
|
939
946
|
You are ready to do backups of all your backup definitions, if your backup definitions are
|
|
940
947
|
in place in BACKUP.D_DIR (see config file)
|
|
@@ -953,9 +960,9 @@ If you want a backup of a single definition, use the `-d <backup definition>` op
|
|
|
953
960
|
dar-backup --full-backup -d <your backup definition>
|
|
954
961
|
````
|
|
955
962
|
|
|
956
|
-
### 6
|
|
963
|
+
### 6 - deactivate venv
|
|
957
964
|
|
|
958
|
-
Deactivate the virtual environment
|
|
965
|
+
Deactivate the virtual environment (venv)
|
|
959
966
|
|
|
960
967
|
```` bash
|
|
961
968
|
deactivate
|
|
@@ -1311,14 +1318,92 @@ dar-backup --restore <archive_name> --selection "-X '*.xmp' -I '*2024-06-16*' -
|
|
|
1311
1318
|
deactivate
|
|
1312
1319
|
```
|
|
1313
1320
|
|
|
1321
|
+
### restore test fails with exit code 4
|
|
1322
|
+
|
|
1323
|
+
"dar" in newer versions emits a question about file ownership, which is "answered" with a "no" via the "-Q" option. That in turn leads to an error code 4.
|
|
1324
|
+
|
|
1325
|
+
Thus the dar option "--comparison-field=ignore-owner" has been placed in the supplied .darrc file (located in the virtual environment where dar-backup is installed).
|
|
1326
|
+
|
|
1327
|
+
This causes dar to restore without an error.
|
|
1328
|
+
|
|
1329
|
+
It is a good option when using dar as a non-privileged user.
|
|
1330
|
+
|
|
1331
|
+
### restore test fails with exit code 5
|
|
1332
|
+
|
|
1333
|
+
If exit code 5 is emitted on the restore test, FSA (File System specific Attributes) could be the cause.
|
|
1334
|
+
|
|
1335
|
+
That (might) occur if you backup a file stored on one type of filesystem, and restore it on another type.
|
|
1336
|
+
My home directory is on a btrfs filesystem, while /tmp (for the restore test) is on zfs.
|
|
1337
|
+
|
|
1338
|
+
The restore test can result in an exit code 5, due to the different filesystems used. In order to avoid the errors, the "option "--fsa-scope none" can be used. That will restult in FSA's not being restored.
|
|
1339
|
+
|
|
1340
|
+
If you need to use this option, un-comment it in the .darrc file (located in the virtual environment where dar-backup is installed)
|
|
1341
|
+
|
|
1342
|
+
## Par2
|
|
1343
|
+
|
|
1344
|
+
### Par2 to verify/repair
|
|
1345
|
+
|
|
1346
|
+
You can run a par2 verification on an archive like this:
|
|
1347
|
+
|
|
1348
|
+
```` bash
|
|
1349
|
+
for file in <archive>*.dar.par2; do
|
|
1350
|
+
par2 verify "$file"
|
|
1351
|
+
done
|
|
1352
|
+
````
|
|
1353
|
+
|
|
1354
|
+
if there are problems with a slice, try to repair it like this:
|
|
1355
|
+
|
|
1356
|
+
```` bash
|
|
1357
|
+
par2 repair <archive>.<slice number>.dar.par2
|
|
1358
|
+
````
|
|
1359
|
+
|
|
1360
|
+
### Par2 create redundancy files
|
|
1361
|
+
|
|
1362
|
+
If you have merged archives, you will need to create the .par2 redundency files manually.
|
|
1363
|
+
Here is an example
|
|
1364
|
+
|
|
1365
|
+
```` bash
|
|
1366
|
+
for file in <some-archive>_FULL_yyyy-mm-dd.*; do
|
|
1367
|
+
par2 c -r5 -n1 "$file"
|
|
1368
|
+
done
|
|
1369
|
+
````
|
|
1370
|
+
|
|
1371
|
+
where "c" is create, -r5 is 5% redundency and -n1 is 1 redundency file
|
|
1372
|
+
|
|
1314
1373
|
## Points of interest
|
|
1315
1374
|
|
|
1375
|
+
### Merge FULL with DIFF, creating new FULL
|
|
1376
|
+
|
|
1377
|
+
Over time, the DIFF archives become larger and larger. At some point one wishes to create a new FULL archive to do DIFF's on.
|
|
1378
|
+
One way to do that, is to let dar create a FULL archive from scratch, another is to merge a FULL archive with a DIFF, and from there do DIFF's until they once again gets too large for your taste.
|
|
1379
|
+
|
|
1380
|
+
I do backups of my homedir. Here it is shown how a FULL archive is merged with a DIFF, creating a new FULL archive.
|
|
1381
|
+
|
|
1382
|
+
```` bash
|
|
1383
|
+
dar --merge pj-homedir_FULL_2021-09-12 -A pj-homedir_FULL_2021-06-06 -@pj-homedir_DIFF_2021-08-29 -s 12G
|
|
1384
|
+
|
|
1385
|
+
# test the new FULL archive
|
|
1386
|
+
dar -t pj-homedir_FULL_2021-09-12
|
|
1387
|
+
|
|
1388
|
+
# create Par2 redundancy files
|
|
1389
|
+
for file in pj-homedir_FULL_yyyy-mm-dd.*.dar; do
|
|
1390
|
+
par2 c -r5 -n1 "$file"
|
|
1391
|
+
done
|
|
1392
|
+
|
|
1393
|
+
````
|
|
1394
|
+
|
|
1316
1395
|
### dar manager databases
|
|
1317
1396
|
|
|
1318
1397
|
`dar-backup` now saves archive catalogs in dar catalog databases.
|
|
1319
1398
|
|
|
1320
1399
|
This makes it easier to restore to a given date when having many FULL, DIFF and INCR archives.
|
|
1321
1400
|
|
|
1401
|
+
### Performance tip due to par2
|
|
1402
|
+
|
|
1403
|
+
This [dar benchmark page](https://dar.sourceforge.io/doc/benchmark.html) has an interesting note on the slice size.
|
|
1404
|
+
|
|
1405
|
+
Slice size should be smaller than available RAM, apparently a large performance hit can be avoided keeping the the par2 data in memory.
|
|
1406
|
+
|
|
1322
1407
|
### .darrc sets -vd -vf (since v0.6.4)
|
|
1323
1408
|
|
|
1324
1409
|
These .darrc settings make `dar` print the current directory being processed (-vd) and some stats after (-vf)
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
dar_backup/.darrc,sha256=-aerqivZmOsW_XBCh9IfbYTUvw0GkzDSr3Vx4GcNB1g,2113
|
|
2
|
-
dar_backup/__about__.py,sha256=
|
|
2
|
+
dar_backup/__about__.py,sha256=WAW-bfIrNx18qYeBN4lihEQibyosiKElDt_p7DfZELo,22
|
|
3
3
|
dar_backup/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
4
|
dar_backup/clean_log.py,sha256=VXKA2BMyQmaC6R08Bq9a3wP3mczdFb_moy6HkL-mnF8,5176
|
|
5
5
|
dar_backup/cleanup.py,sha256=9yEdRR84XPtEvBGc2QfwGBQl2tdTPttjetHeiSc_TsM,11419
|
|
6
6
|
dar_backup/config_settings.py,sha256=CBMUhLOOZ-x7CRdS3vBDk4TYaGqC4N1Ot8IMH-qPaI0,3617
|
|
7
|
-
dar_backup/dar_backup.py,sha256=
|
|
7
|
+
dar_backup/dar_backup.py,sha256=o4DvaVsqWHb1PxHIXfdr2qyqW6KRRGNcrR2_CRbPy1I,32942
|
|
8
8
|
dar_backup/manager.py,sha256=HDa8eYF89QFhlBRR4EWRzzmswOW00S_w8ToZ5SARO_o,21359
|
|
9
|
-
dar_backup/util.py,sha256=
|
|
10
|
-
dar_backup-0.6.
|
|
11
|
-
dar_backup-0.6.
|
|
12
|
-
dar_backup-0.6.
|
|
13
|
-
dar_backup-0.6.
|
|
14
|
-
dar_backup-0.6.
|
|
9
|
+
dar_backup/util.py,sha256=E-sEBQZY1hmdeVx5xNE22zKQ0BXDee1eI9F1-w7Fq1Q,15756
|
|
10
|
+
dar_backup-0.6.11.dist-info/METADATA,sha256=N7ICBJQEVDC01tza9lRAkmOXCIAILoDrg-mrHG926f0,71626
|
|
11
|
+
dar_backup-0.6.11.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
12
|
+
dar_backup-0.6.11.dist-info/entry_points.txt,sha256=p6c4uQLjlTIVP1Od2iorGefrVUH0IWZdFRMl63mNaRg,164
|
|
13
|
+
dar_backup-0.6.11.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
14
|
+
dar_backup-0.6.11.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|