dar-backup 0.8.1__py3-none-any.whl → 0.8.3__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 CHANGED
@@ -1,6 +1,34 @@
1
1
  <!-- markdownlint-disable MD024 -->
2
2
  # dar-backup Changelog
3
3
 
4
+ ## v2-beta-0.8.3 - 2025-08-23
5
+
6
+ Github link: [v2-beta-0.8.3](https://github.com/per2jensen/dar-backup/tree/v2-beta-0.8.3/v2)
7
+
8
+ ### Added
9
+
10
+ - Dar-backup now deletes files if noted as "removed" in the archive catalog for DIFF and INCR backups.
11
+ - This ensures a restore of a FULL + DIFF + INCR matches the files in the source directories.
12
+ - Options '-wa' & '-/ Oo' added to the restore command.
13
+
14
+ ## v2-beta-0.8.2 - 2025-07-17
15
+
16
+ Github link: [v2-beta-0.8.2](https://github.com/per2jensen/dar-backup/tree/v2-beta-0.8.2/v2)
17
+
18
+ ### Added
19
+
20
+ - Security hardening: CommandRunner now performs strict command-line sanitization
21
+ - Disallows potentially dangerous characters (e.g. ;, &, |) in command arguments
22
+ - Prevents injection-style misuse when restoring specific files or invoking custom commands
23
+
24
+ - Documentation:
25
+ - New [README section](https://github.com/per2jensen/dar-backup?tab=readme-ov-file#limitations-on-file-names-with-special-characters) explains filename restrictions and safe workarounds (e.g. restoring directly with dar, if needed)
26
+ - Includes a Markdown table listing all disallowed characters
27
+
28
+ - Test suite:
29
+ - Existing test cases updated to comply with the new sanitization rules
30
+ - Additional tests ensure CommandRunner handles large binary output and edge cases cleanly
31
+
4
32
  ## v2-beta-0.8.1 - 2025-07-16
5
33
 
6
34
  Github link: [v2-beta-0.8.1](https://github.com/per2jensen/dar-backup/tree/v2-beta-0.8.1/v2)
dar_backup/README.md CHANGED
@@ -22,71 +22,90 @@ This is the `Python` based [**version 2**](https://github.com/per2jensen/dar-bac
22
22
 
23
23
  ## Table of Contents
24
24
 
25
- - [Reliable `dar` backups wrapped in Python](#dar-backup)
26
- - [My use case](#my-use-case)
27
- - [Features](#features)
28
- - [License](#license)
29
- - [Changelog version 2](https://github.com/per2jensen/dar-backup/blob/main/v2/Changelog.md)
30
- - [Quick Guide](#quick-guide)
31
- - [Status](#status)
32
- - [GPG Signing key](#gpg-signing-key)
33
- - [Breaking change in version 0.6.0](#breaking-change-in-version-060)
34
- - [Homepage - Github](#homepage---github)
35
- - [Community](#community)
36
- - [Requirements](#requirements)
37
- - [Principles](#dar-backup-principles)
38
- - [How to run](#how-to-run)
39
- - [1 - installation](#1---installation)
40
- - [2 - configuration](#2---configuration)
41
- - [3 - generate catalog databases](#3---generate-catalog-databases)
42
- - [4 - give `dar-backup` a spin](#4---give-dar-backup-a-spin)
43
- - [5 - deactivate venv](#5---deactivate-venv)
44
- - [Config](#config)
45
- - [Config file](#config-file)
46
- - [.darrc](#darrc)
47
- - [Backup definition](#backup-definition-example)
48
- - [Systemd examples](#systemctl-examples)
25
+ - [`dar-backup`](#dar-backup)
26
+ - [TL;DR](#tldr)
27
+ - [Table of Contents](#table-of-contents)
28
+ - [My use case](#my-use-case)
29
+ - [Features](#features)
30
+ - [License](#license)
31
+ - [Quick Guide](#quick-guide)
32
+ - [Status](#status)
33
+ - [GPG Signing key](#gpg-signing-key)
34
+ - [Breaking change in version 0.6.0](#breaking-change-in-version-060)
35
+ - [Homepage - Github](#homepage---github)
36
+ - [Community](#community)
37
+ - [Requirements](#requirements)
38
+ - [dar-backup principles](#dar-backup-principles)
39
+ - [dar-backup](#dar-backup-1)
40
+ - [cleanup](#cleanup)
41
+ - [manager](#manager)
42
+ - [How to run](#how-to-run)
43
+ - [1 - installation](#1---installation)
44
+ - [2 - configuration](#2---configuration)
45
+ - [3 - generate catalog databases](#3---generate-catalog-databases)
46
+ - [4 - give dar-backup a spin](#4---give-dar-backup-a-spin)
47
+ - [5 - deactivate venv](#5---deactivate-venv)
48
+ - [Config](#config)
49
+ - [Config file](#config-file)
50
+ - [.darrc](#darrc)
51
+ - [Backup definition example](#backup-definition-example)
49
52
  - [Generate systemd files](#generate-systemd-files)
50
- - [Service: dar-back --incremental-backup](#service-dar-backup---incremental-backup)
51
- - [Timer: dar-back --incremental-backup](#timer-dar-backup---incremental-backup)
52
- - [Systemd timer note](#systemd-timer-note)
53
- - [List contents of an archive](#list-contents-of-an-archive)
54
- - [dar file selection examples](#dar-file-selection-examples)
55
- - [Select a directory](#select-a-directory)
56
- - [Include and exclude some file](#select-files-with-z50-in-the-file-name-and-exclude-xmp-files)
57
- - [Restoring](#restoring)
58
- - [Default location for restores](#default-location-for-restores)
59
- - [--restore-dir option](#--restore-dir-option)
60
- - [A single file](#a-single-file)
61
- - [A directory](#a-directory)
62
- - [.NEF from a specific date](#nef-from-a-specific-date)
63
- - [Restore test fails with exit code 4](#restore-test-fails-with-exit-code-4)
64
- - [Restore test fails with exit code 5](#restore-test-fails-with-exit-code-5)
65
- - [Par2](#par2)
66
- - [Par2 to verify/repair](#par2-to-verifyrepair)
67
- - [Par2 create redundancy files](#par2-create-redundancy-files)
68
- - [Points of interest](#points-of-interest)
69
- - [Merge FULL with DIFF, creating new FULL](#merge-full-with-diff-creating-new-full)
70
- - [dar manager databases](#dar-manager-databases)
71
- - [Performance tip due to par2](#performance-tip-due-to-par2)
72
- - [.darrc sets -vd -vf (since v0.6.4)](#darrc-sets--vd--vf-since-v064)
73
- - [Separate log file for command output](#separate-log-file-for-command-output)
74
- - [Skipping cache directories](#skipping-cache-directories)
75
- - [Progress bar + current directory](#progress-bar-and-current-directory)
76
- - [Shell Autocompletion](#shell-autocompletion)
53
+ - [Systemctl examples](#systemctl-examples)
54
+ - [Service: dar-backup --incremental-backup](#service-dar-backup---incremental-backup)
55
+ - [Timer: dar-backup --incremental-backup](#timer-dar-backup---incremental-backup)
56
+ - [systemd timer note](#systemd-timer-note)
57
+ - [list contents of an archive](#list-contents-of-an-archive)
58
+ - [dar file selection examples](#dar-file-selection-examples)
59
+ - [select a directory](#select-a-directory)
60
+ - [select files with "Z50" in the file name and exclude .xmp files](#select-files-with-z50-in-the-file-name-and-exclude-xmp-files)
61
+ - [Restoring](#restoring)
62
+ - [default location for restores](#default-location-for-restores)
63
+ - [--restore-dir option](#--restore-dir-option)
64
+ - [a single file](#a-single-file)
65
+ - [a directory](#a-directory)
66
+ - [.NEF from a specific date](#nef-from-a-specific-date)
67
+ - [restore test fails with exit code 4](#restore-test-fails-with-exit-code-4)
68
+ - [restore test fails with exit code 5](#restore-test-fails-with-exit-code-5)
69
+ - [Par2](#par2)
70
+ - [Par2 to verify/repair](#par2-to-verifyrepair)
71
+ - [Par2 create redundancy files](#par2-create-redundancy-files)
72
+ - [Points of interest](#points-of-interest)
73
+ - [Limitations on File Names with Special Characters](#limitations-on-file-names-with-special-characters)
74
+ - [Why this matters](#why-this-matters)
75
+ - [Workarounds](#workarounds)
76
+ - [Restoring Files with Forbidden Characters in Their Names](#restoring-files-with-forbidden-characters-in-their-names)
77
+ - [Backups: Safe and Fully Functional](#backups-safe-and-fully-functional)
78
+ - [Restores via CLI: Limited by Sanitizer](#restores-via-cli-limited-by-sanitizer)
79
+ - [Workaround: Use `dar` Directly](#workaround-use-dar-directly)
80
+ - [Example: Manual Restore Using `dar`](#example-manual-restore-using-dar)
81
+ - [🧪 How to Locate Files with Forbidden Characters](#-how-to-locate-files-with-forbidden-characters)
82
+ - [Summary](#summary)
83
+ - [Merge FULL with DIFF, creating new FULL](#merge-full-with-diff-creating-new-full)
84
+ - [dar manager databases](#dar-manager-databases)
85
+ - [Performance tip due to par2](#performance-tip-due-to-par2)
86
+ - [.darrc sets -vd -vf (since v0.6.4)](#darrc-sets--vd--vf-since-v064)
87
+ - [Separate log file for command output](#separate-log-file-for-command-output)
88
+ - [Skipping cache directories](#skipping-cache-directories)
89
+ - [Progress bar and current directory](#progress-bar-and-current-directory)
90
+ - [Shell autocompletion](#shell-autocompletion)
91
+ - [Use it](#use-it)
92
+ - [Archive name completion (smart, context-aware)](#archive-name-completion-smart-context-aware)
93
+ - [Enabling Bash completion](#enabling-bash-completion)
94
+ - [Enable Zsh Completion](#enable-zsh-completion)
77
95
  - [Easy development setup](#easy-development-setup)
78
- - [Todo](#todo)
79
- - [Known Limitations / Edge Cases](#known-limitations--edge-cases)
80
- - [Reference](#reference)
81
- - [CLI Tools Overview](#cli-tools-overview)
82
- - [Test coverage report](#test-coverage)
83
- - [dar-backup](#dar-backup-options)
84
- - [manager](#manager-options)
85
- - [cleanup](#cleanup-options)
86
- - [clean-log](#clean-log-options)
87
- - [dar-backup-systemd](#dar-backup-systemd-options)
88
- - [Installer](#installer-options)
89
- - [demo](#demo-options)
96
+ - [Todo](#todo)
97
+ - [Known Limitations / Edge Cases](#known-limitations--edge-cases)
98
+ - [Projects these scripts benefit from](#projects-these-scripts-benefit-from)
99
+ - [Reference](#reference)
100
+ - [CLI Tools Overview](#cli-tools-overview)
101
+ - [test coverage](#test-coverage)
102
+ - [Dar-backup options](#dar-backup-options)
103
+ - [Manager Options](#manager-options)
104
+ - [Cleanup options](#cleanup-options)
105
+ - [Clean-log options](#clean-log-options)
106
+ - [Dar-backup-systemd options](#dar-backup-systemd-options)
107
+ - [Installer options](#installer-options)
108
+ - [Demo options](#demo-options)
90
109
 
91
110
  ## My use case
92
111
 
@@ -1183,6 +1202,109 @@ where "c" is create, -r5 is 5% redundency and -n1 is 1 redundency file
1183
1202
 
1184
1203
  ## Points of interest
1185
1204
 
1205
+ ### Limitations on File Names with Special Characters
1206
+
1207
+ `dar-backup` strictly validates all command-line arguments passed to its internal execution engine to protect against command injection and shell-based attacks. As part of this security measure, certain characters are disallowed in user-provided inputs — particularly those that carry special meaning in shell environments:
1208
+
1209
+ Disallowed characters include:
1210
+
1211
+ ```text
1212
+ \$ & ; | > < ` \n
1213
+ ```
1214
+
1215
+ #### Why this matters
1216
+
1217
+ When restoring specific files using the --selection argument or similar mechanisms, filenames that contain one or more of these characters (e.g., file_with_currency$.txt) cannot be safely passed as command-line arguments. As a result, attempting to restore such a file by name using the CLI will result in a validation error.
1218
+
1219
+ ✅ Backups and Restores Still Work
1220
+
1221
+ ✅ These files are still backed up and restored automatically as part of normal FULL, DIFF, or INCR operations.
1222
+
1223
+ ❌ They cannot be explicitly specified for restore using CLI options like --selection="-g path/to/file_with_currency$.txt".
1224
+
1225
+ #### Workarounds
1226
+
1227
+ If you need to restore such a file:
1228
+
1229
+ Perform a restore of the entire directory using a more general selection (e.g., --selection="-g path/to/parent-directory").
1230
+
1231
+ Manually retrieve the restored file afterward.
1232
+
1233
+ ##### Restoring Files with Forbidden Characters in Their Names
1234
+
1235
+ The DAR Backup system enforces a strict command-line argument sanitizer to improve security and prevent shell injection attacks. As a result, certain characters are **not allowed** in filenames or arguments passed to the CLI, especially during restore operations. This includes characters like:
1236
+
1237
+ | Character | Reason Blocked |
1238
+ |----------:|---------------------------------|
1239
+ | `;` | Shell command separator |
1240
+ | `&` | Background execution operator |
1241
+ | `\|` | Pipe operator |
1242
+ | `<` / `>` | Redirection operators |
1243
+ | `#` | Shell comment |
1244
+ | `` ` `` | Command substitution |
1245
+ | `"` / `'` | Quoting that may be unbalanced |
1246
+
1247
+ However, **backups of files with such names still work** — they are preserved correctly within the archive. The limitation only applies to **invoking restore commands via the CLI**, where such filenames cannot be safely passed as arguments.
1248
+
1249
+ ##### Backups: Safe and Fully Functional
1250
+
1251
+ Files with special characters in their names **are backed up without issue**. The only issue you might encounter is restoring a file and giving the file name on the command line (with forbidden characters.)
1252
+
1253
+ ##### Restores via CLI: Limited by Sanitizer
1254
+
1255
+ Attempting to restore such files via:
1256
+
1257
+ ```bash
1258
+ dar-backup restore --file "weird#name.txt"
1259
+ ```
1260
+
1261
+ ...will fail with an error like:
1262
+
1263
+ ```bash
1264
+ Unsafe argument detected: weird#name.txt
1265
+ ```
1266
+
1267
+ ---
1268
+
1269
+ ##### Workaround: Use `dar` Directly
1270
+
1271
+ You can always restore the file manually using the `dar` command-line utility itself, bypassing any CLI restrictions imposed by the backup tool.
1272
+
1273
+ ##### Example: Manual Restore Using `dar`
1274
+
1275
+ ```bash
1276
+ dar -x /path/to/backup/example -g "weird#name.txt"
1277
+ ```
1278
+
1279
+ Where:
1280
+
1281
+ - `/path/to/backup/example` is the base name of the archive (without `.dar`, `.1.dar`, etc.).
1282
+ - `"weird#name.txt"` is the exact filename with the special character(s).
1283
+
1284
+ You may need to quote the argument or escape characters depending on your shell.
1285
+
1286
+ ---
1287
+
1288
+ ##### 🧪 How to Locate Files with Forbidden Characters
1289
+
1290
+ To search for such files inside the archive:
1291
+
1292
+ ```bash
1293
+ dar -l /path/to/backup/example | grep '[#;<>|&]'
1294
+ ```
1295
+
1296
+ This will help you identify files that require manual restoration.
1297
+
1298
+ ---
1299
+
1300
+ #### Summary
1301
+
1302
+ - 🚫 Forbidden characters are blocked **only** in CLI arguments to maintain safety.
1303
+ - ✅ Files containing these characters are **still archived and restorable**.
1304
+ - 🛠 Use `dar` directly for full manual control when restoring such files.
1305
+
1306
+
1307
+
1186
1308
  ### Merge FULL with DIFF, creating new FULL
1187
1309
 
1188
1310
  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.
@@ -1516,12 +1638,18 @@ Available options:
1516
1638
  --list-contents <archive> List the contents of a specified archive.
1517
1639
  --selection <params> Define file selection for listing/restoring.
1518
1640
  --restore <archive> Restore a specified archive.
1519
- -r, --restore-dir <path> Directory to restore files to.
1641
+ -r, --restore <archive> Restore archive.
1642
+ --restore-dir Directory on which to restore
1520
1643
  --verbose Enable verbose output.
1521
1644
  --suppress-dar-msg Filter out this from the darrc: "-vt", "-vs", "-vd", "-vf", "-va"
1522
1645
  --log-level <level> `debug` or `trace`, default is `info`.
1523
1646
  --log-stdout Also print log messages to stdout.
1524
1647
  --do-not-compare Do not compare restores to file system.
1648
+ --examples Show examples of using dar-backup.
1649
+ --readme Print README.md and exit
1650
+ --readme-pretty Print README.md with Markdown styling and exit
1651
+ --changelog Print Changelog and exit
1652
+ --changelog-pretty Print Changelog with Markdown styling and exit
1525
1653
  -v, --version Show version and license information.
1526
1654
  ```
1527
1655
 
dar_backup/__about__.py CHANGED
@@ -1,4 +1,4 @@
1
- __version__ = "0.8.1"
1
+ __version__ = "0.8.3"
2
2
 
3
3
  __license__ = '''Licensed under GNU GENERAL PUBLIC LICENSE v3, see the supplied file "LICENSE" for details.
4
4
  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW, not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
@@ -5,33 +5,76 @@ import logging
5
5
  import traceback
6
6
  import threading
7
7
  import os
8
+ import re
9
+ import shlex
8
10
  import sys
9
11
  import tempfile
10
12
  sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../src")))
11
- from typing import List, Optional
13
+ from typing import List, Optional, Union
12
14
  from dar_backup.util import get_logger
13
15
 
14
16
 
17
+ def is_safe_arg(arg: str) -> bool:
18
+ """
19
+ Check if the argument is safe by rejecting dangerous shell characters.
20
+ """
21
+ return not re.search(r'[;&|><`$\n]', arg)
22
+
23
+
24
+ def sanitize_cmd(cmd: List[str]) -> List[str]:
25
+ """
26
+ Validate and sanitize a list of command-line arguments.
27
+ Ensures all elements are strings and do not contain dangerous shell characters.
28
+ Raises ValueError if any argument is unsafe.
29
+ """
30
+
31
+ if not isinstance(cmd, list):
32
+ raise ValueError("Command must be a list of strings")
33
+ for arg in cmd:
34
+ if not isinstance(arg, str):
35
+ raise ValueError(f"Invalid argument type: {arg} (must be string)")
36
+ if not is_safe_arg(arg):
37
+ raise ValueError(f"Unsafe argument detected: {arg}")
38
+ return cmd
39
+
40
+ def _safe_str(s):
41
+ if isinstance(s, bytes):
42
+ return f"<{len(s)} bytes of binary data>"
43
+ return s
44
+
45
+
15
46
  class CommandResult:
16
- def __init__(self, returncode: int, stdout: str, stderr: str, stack: str = None):
47
+ def __init__(
48
+ self,
49
+ returncode: int,
50
+ stdout: Union[str, bytes],
51
+ stderr: Union[str, bytes],
52
+ stack: Optional[str] = None,
53
+ note: Optional[str] = None
54
+ ):
17
55
  self.returncode = returncode
18
56
  self.stdout = stdout
19
57
  self.stderr = stderr
20
58
  self.stack = stack
59
+ self.note = note
60
+
61
+
21
62
 
22
63
  def __repr__(self):
23
64
  return f"<CommandResult returncode={self.returncode}\nstdout={self.stdout}\nstderr={self.stderr}\nstack={self.stack}>"
24
-
65
+
25
66
 
26
67
  def __str__(self):
27
68
  return (
28
- f"CommandResult:\n"
69
+ "CommandResult:\n"
29
70
  f" Return code: {self.returncode}\n"
30
- f" STDOUT:\n{self.stdout}\n"
31
- f" STDERR:\n{self.stderr}\n"
32
- f" Stacktrace:\n{self.stack if self.stack else '<none>'}"
71
+ f" Note: {self.note if self.note else '<none>'}\n"
72
+ f" STDOUT: {_safe_str(self.stdout)}\n"
73
+ f" STDERR: {_safe_str(self.stderr)}\n"
74
+ f" Stacktrace: {self.stack if self.stack else '<none>'}"
33
75
  )
34
76
 
77
+
35
78
  class CommandRunner:
36
79
  def __init__(
37
80
  self,
@@ -46,6 +89,7 @@ class CommandRunner:
46
89
  if not self.logger or not self.command_logger:
47
90
  self.logger_fallback()
48
91
 
92
+
49
93
  def logger_fallback(self):
50
94
  """
51
95
  Setup temporary log files
@@ -83,9 +127,31 @@ class CommandRunner:
83
127
  capture_output: bool = True,
84
128
  text: bool = True
85
129
  ) -> CommandResult:
130
+ self._text_mode = text
86
131
  timeout = timeout or self.default_timeout
87
132
 
88
- command = f"Executing command: {' '.join(cmd)} (timeout={timeout}s)"
133
+ cmd_sanitized = None
134
+
135
+ try:
136
+ cmd_sanitized = sanitize_cmd(cmd)
137
+ except ValueError as e:
138
+ stack = traceback.format_exc()
139
+ self.logger.error(f"Command sanitation failed: {e}")
140
+ return CommandResult(
141
+ returncode=-1,
142
+ note=f"Sanitizing failed: command: {' '.join(cmd)}",
143
+ stdout='',
144
+ stderr=str(e),
145
+ stack=stack,
146
+
147
+ )
148
+ finally:
149
+ cmd = cmd_sanitized
150
+
151
+ #command = f"Executing command: {' '.join(cmd)} (timeout={timeout}s)"
152
+ command = f"Executing command: {' '.join(shlex.quote(arg) for arg in cmd)} (timeout={timeout}s)"
153
+
154
+
89
155
  self.command_logger.info(command)
90
156
  self.logger.debug(command)
91
157
 
@@ -115,9 +181,13 @@ class CommandRunner:
115
181
  chunk = stream.read(1024)
116
182
  if not chunk:
117
183
  break
118
- decoded = chunk.decode('utf-8', errors='replace')
119
- lines.append(decoded)
120
- self.command_logger.log(level, decoded.strip())
184
+ if self._text_mode:
185
+ decoded = chunk.decode('utf-8', errors='replace')
186
+ lines.append(decoded)
187
+ self.command_logger.log(level, decoded.strip())
188
+ else:
189
+ lines.append(chunk)
190
+ # Avoid logging raw binary data to prevent garbled logs
121
191
  except Exception as e:
122
192
  self.logger.warning(f"stream_output decode error: {e}")
123
193
  finally:
@@ -139,29 +209,40 @@ class CommandRunner:
139
209
  process.wait(timeout=timeout)
140
210
  except subprocess.TimeoutExpired:
141
211
  process.kill()
142
- self.logger.error(f"Command timed out: {' '.join(cmd)}")
143
- return CommandResult(-1, ''.join(stdout_lines), ''.join(stderr_lines))
212
+ log_msg = f"Command timed out after {timeout} seconds: {' '.join(cmd)}:\n"
213
+ self.logger.error(log_msg)
214
+ return CommandResult(-1, ''.join(stdout_lines), log_msg.join(stderr_lines))
144
215
  except Exception as e:
145
216
  stack = traceback.format_exc()
146
- self.logger.error(f"Command execution failed: {' '.join(cmd)} with error: {e}")
147
- return CommandResult(-1, ''.join(stdout_lines), ''.join(stderr_lines), stack)
217
+ log_msg = f"Command execution failed: {' '.join(cmd)} with error: {e}\n"
218
+ self.logger.error(log_msg)
219
+ return CommandResult(-1, ''.join(stdout_lines), log_msg.join(stderr_lines), stack)
148
220
 
149
221
  for t in threads:
150
222
  t.join()
151
223
 
152
224
 
225
+
226
+ if self._text_mode:
227
+ stdout_combined = ''.join(stdout_lines)
228
+ stderr_combined = ''.join(stderr_lines)
229
+ else:
230
+ stdout_combined = b''.join(stdout_lines)
231
+ stderr_combined = b''.join(stderr_lines)
232
+
233
+
153
234
  if check and process.returncode != 0:
154
235
  self.logger.error(f"Command failed with exit code {process.returncode}")
155
236
  return CommandResult(
156
237
  process.returncode,
157
- ''.join(stdout_lines),
158
- ''.join(stderr_lines),
238
+ stdout_combined,
239
+ stderr_combined,
159
240
  stack=traceback.format_stack()
160
241
  )
161
242
 
162
243
  return CommandResult(
163
244
  process.returncode,
164
- ''.join(stdout_lines),
165
- ''.join(stderr_lines),
245
+ stdout_combined,
246
+ stderr_combined
166
247
  )
167
248
 
dar_backup/dar_backup.py CHANGED
@@ -248,7 +248,7 @@ def verify(args: argparse.Namespace, backup_file: str, backup_definition: str, c
248
248
  PermissionError: If a permission error occurs while comparing files.
249
249
  """
250
250
  result = True
251
- command = ['dar', '-t', backup_file, '-Q']
251
+ command = ['dar', '-t', backup_file, '-N', '-Q']
252
252
 
253
253
 
254
254
  log_basename = os.path. dirname(config_settings.logfile_location)
@@ -315,7 +315,7 @@ def verify(args: argparse.Namespace, backup_file: str, backup_definition: str, c
315
315
  for restored_file_path in random_files:
316
316
  try:
317
317
  args.verbose and logger.info(f"Restoring file: '{restored_file_path}' from backup to: '{config_settings.test_restore_dir}' for file comparing")
318
- command = ['dar', '-x', backup_file, '-g', restored_file_path.lstrip("/"), '-R', config_settings.test_restore_dir, '-Q', '-B', args.darrc, 'restore-options']
318
+ command = ['dar', '-x', backup_file, '-g', restored_file_path.lstrip("/"), '-R', config_settings.test_restore_dir, '--noconf', '-Q', '-B', args.darrc, 'restore-options']
319
319
  args.verbose and logger.info(f"Running command: {' '.join(map(shlex.quote, command))}")
320
320
  process = runner.run(command, timeout = config_settings.command_timeout_secs)
321
321
  if process.returncode != 0:
@@ -347,7 +347,7 @@ def restore_backup(backup_name: str, config_settings: ConfigSettings, restore_di
347
347
  results: List[tuple] = []
348
348
  try:
349
349
  backup_file = os.path.join(config_settings.backup_dir, backup_name)
350
- command = ['dar', '-x', backup_file, '-Q', '-D']
350
+ command = ['dar', '-x', backup_file, '-wa', '-/ Oo', '--noconf', '-Q', '-D']
351
351
  if restore_dir:
352
352
  if not os.path.exists(restore_dir):
353
353
  os.makedirs(restore_dir)
@@ -390,7 +390,7 @@ def get_backed_up_files(backup_name: str, backup_dir: str):
390
390
  logger.debug(f"Getting backed up files in xml from DAR archive: '{backup_name}'")
391
391
  backup_path = os.path.join(backup_dir, backup_name)
392
392
  try:
393
- command = ['dar', '-l', backup_path, '-am', '-as', "-Txml" , '-Q']
393
+ command = ['dar', '-l', backup_path, '--noconf', '-am', '-as', "-Txml" , '-Q']
394
394
  logger.debug(f"Running command: {' '.join(map(shlex.quote, command))}")
395
395
  command_result = runner.run(command)
396
396
  # Parse the XML data
@@ -418,7 +418,7 @@ def list_contents(backup_name, backup_dir, selection=None):
418
418
  backup_path = os.path.join(backup_dir, backup_name)
419
419
 
420
420
  try:
421
- command = ['dar', '-l', backup_path, '-am', '-as', '-Q']
421
+ command = ['dar', '-l', backup_path, '--noconf', '-am', '-as', '-Q']
422
422
  if selection:
423
423
  selection_criteria = shlex.split(selection)
424
424
  command.extend(selection_criteria)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dar-backup
3
- Version: 0.8.1
3
+ Version: 0.8.3
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: GPG Public Key, https://keys.openpgp.org/search?q=dar-backup@pm.me
6
6
  Project-URL: Homepage, https://github.com/per2jensen/dar-backup/tree/main/v2
@@ -740,71 +740,90 @@ This is the `Python` based [**version 2**](https://github.com/per2jensen/dar-bac
740
740
 
741
741
  ## Table of Contents
742
742
 
743
- - [Reliable `dar` backups wrapped in Python](#dar-backup)
744
- - [My use case](#my-use-case)
745
- - [Features](#features)
746
- - [License](#license)
747
- - [Changelog version 2](https://github.com/per2jensen/dar-backup/blob/main/v2/Changelog.md)
748
- - [Quick Guide](#quick-guide)
749
- - [Status](#status)
750
- - [GPG Signing key](#gpg-signing-key)
751
- - [Breaking change in version 0.6.0](#breaking-change-in-version-060)
752
- - [Homepage - Github](#homepage---github)
753
- - [Community](#community)
754
- - [Requirements](#requirements)
755
- - [Principles](#dar-backup-principles)
756
- - [How to run](#how-to-run)
757
- - [1 - installation](#1---installation)
758
- - [2 - configuration](#2---configuration)
759
- - [3 - generate catalog databases](#3---generate-catalog-databases)
760
- - [4 - give `dar-backup` a spin](#4---give-dar-backup-a-spin)
761
- - [5 - deactivate venv](#5---deactivate-venv)
762
- - [Config](#config)
763
- - [Config file](#config-file)
764
- - [.darrc](#darrc)
765
- - [Backup definition](#backup-definition-example)
766
- - [Systemd examples](#systemctl-examples)
743
+ - [`dar-backup`](#dar-backup)
744
+ - [TL;DR](#tldr)
745
+ - [Table of Contents](#table-of-contents)
746
+ - [My use case](#my-use-case)
747
+ - [Features](#features)
748
+ - [License](#license)
749
+ - [Quick Guide](#quick-guide)
750
+ - [Status](#status)
751
+ - [GPG Signing key](#gpg-signing-key)
752
+ - [Breaking change in version 0.6.0](#breaking-change-in-version-060)
753
+ - [Homepage - Github](#homepage---github)
754
+ - [Community](#community)
755
+ - [Requirements](#requirements)
756
+ - [dar-backup principles](#dar-backup-principles)
757
+ - [dar-backup](#dar-backup-1)
758
+ - [cleanup](#cleanup)
759
+ - [manager](#manager)
760
+ - [How to run](#how-to-run)
761
+ - [1 - installation](#1---installation)
762
+ - [2 - configuration](#2---configuration)
763
+ - [3 - generate catalog databases](#3---generate-catalog-databases)
764
+ - [4 - give dar-backup a spin](#4---give-dar-backup-a-spin)
765
+ - [5 - deactivate venv](#5---deactivate-venv)
766
+ - [Config](#config)
767
+ - [Config file](#config-file)
768
+ - [.darrc](#darrc)
769
+ - [Backup definition example](#backup-definition-example)
767
770
  - [Generate systemd files](#generate-systemd-files)
768
- - [Service: dar-back --incremental-backup](#service-dar-backup---incremental-backup)
769
- - [Timer: dar-back --incremental-backup](#timer-dar-backup---incremental-backup)
770
- - [Systemd timer note](#systemd-timer-note)
771
- - [List contents of an archive](#list-contents-of-an-archive)
772
- - [dar file selection examples](#dar-file-selection-examples)
773
- - [Select a directory](#select-a-directory)
774
- - [Include and exclude some file](#select-files-with-z50-in-the-file-name-and-exclude-xmp-files)
775
- - [Restoring](#restoring)
776
- - [Default location for restores](#default-location-for-restores)
777
- - [--restore-dir option](#--restore-dir-option)
778
- - [A single file](#a-single-file)
779
- - [A directory](#a-directory)
780
- - [.NEF from a specific date](#nef-from-a-specific-date)
781
- - [Restore test fails with exit code 4](#restore-test-fails-with-exit-code-4)
782
- - [Restore test fails with exit code 5](#restore-test-fails-with-exit-code-5)
783
- - [Par2](#par2)
784
- - [Par2 to verify/repair](#par2-to-verifyrepair)
785
- - [Par2 create redundancy files](#par2-create-redundancy-files)
786
- - [Points of interest](#points-of-interest)
787
- - [Merge FULL with DIFF, creating new FULL](#merge-full-with-diff-creating-new-full)
788
- - [dar manager databases](#dar-manager-databases)
789
- - [Performance tip due to par2](#performance-tip-due-to-par2)
790
- - [.darrc sets -vd -vf (since v0.6.4)](#darrc-sets--vd--vf-since-v064)
791
- - [Separate log file for command output](#separate-log-file-for-command-output)
792
- - [Skipping cache directories](#skipping-cache-directories)
793
- - [Progress bar + current directory](#progress-bar-and-current-directory)
794
- - [Shell Autocompletion](#shell-autocompletion)
771
+ - [Systemctl examples](#systemctl-examples)
772
+ - [Service: dar-backup --incremental-backup](#service-dar-backup---incremental-backup)
773
+ - [Timer: dar-backup --incremental-backup](#timer-dar-backup---incremental-backup)
774
+ - [systemd timer note](#systemd-timer-note)
775
+ - [list contents of an archive](#list-contents-of-an-archive)
776
+ - [dar file selection examples](#dar-file-selection-examples)
777
+ - [select a directory](#select-a-directory)
778
+ - [select files with "Z50" in the file name and exclude .xmp files](#select-files-with-z50-in-the-file-name-and-exclude-xmp-files)
779
+ - [Restoring](#restoring)
780
+ - [default location for restores](#default-location-for-restores)
781
+ - [--restore-dir option](#--restore-dir-option)
782
+ - [a single file](#a-single-file)
783
+ - [a directory](#a-directory)
784
+ - [.NEF from a specific date](#nef-from-a-specific-date)
785
+ - [restore test fails with exit code 4](#restore-test-fails-with-exit-code-4)
786
+ - [restore test fails with exit code 5](#restore-test-fails-with-exit-code-5)
787
+ - [Par2](#par2)
788
+ - [Par2 to verify/repair](#par2-to-verifyrepair)
789
+ - [Par2 create redundancy files](#par2-create-redundancy-files)
790
+ - [Points of interest](#points-of-interest)
791
+ - [Limitations on File Names with Special Characters](#limitations-on-file-names-with-special-characters)
792
+ - [Why this matters](#why-this-matters)
793
+ - [Workarounds](#workarounds)
794
+ - [Restoring Files with Forbidden Characters in Their Names](#restoring-files-with-forbidden-characters-in-their-names)
795
+ - [Backups: Safe and Fully Functional](#backups-safe-and-fully-functional)
796
+ - [Restores via CLI: Limited by Sanitizer](#restores-via-cli-limited-by-sanitizer)
797
+ - [Workaround: Use `dar` Directly](#workaround-use-dar-directly)
798
+ - [Example: Manual Restore Using `dar`](#example-manual-restore-using-dar)
799
+ - [🧪 How to Locate Files with Forbidden Characters](#-how-to-locate-files-with-forbidden-characters)
800
+ - [Summary](#summary)
801
+ - [Merge FULL with DIFF, creating new FULL](#merge-full-with-diff-creating-new-full)
802
+ - [dar manager databases](#dar-manager-databases)
803
+ - [Performance tip due to par2](#performance-tip-due-to-par2)
804
+ - [.darrc sets -vd -vf (since v0.6.4)](#darrc-sets--vd--vf-since-v064)
805
+ - [Separate log file for command output](#separate-log-file-for-command-output)
806
+ - [Skipping cache directories](#skipping-cache-directories)
807
+ - [Progress bar and current directory](#progress-bar-and-current-directory)
808
+ - [Shell autocompletion](#shell-autocompletion)
809
+ - [Use it](#use-it)
810
+ - [Archive name completion (smart, context-aware)](#archive-name-completion-smart-context-aware)
811
+ - [Enabling Bash completion](#enabling-bash-completion)
812
+ - [Enable Zsh Completion](#enable-zsh-completion)
795
813
  - [Easy development setup](#easy-development-setup)
796
- - [Todo](#todo)
797
- - [Known Limitations / Edge Cases](#known-limitations--edge-cases)
798
- - [Reference](#reference)
799
- - [CLI Tools Overview](#cli-tools-overview)
800
- - [Test coverage report](#test-coverage)
801
- - [dar-backup](#dar-backup-options)
802
- - [manager](#manager-options)
803
- - [cleanup](#cleanup-options)
804
- - [clean-log](#clean-log-options)
805
- - [dar-backup-systemd](#dar-backup-systemd-options)
806
- - [Installer](#installer-options)
807
- - [demo](#demo-options)
814
+ - [Todo](#todo)
815
+ - [Known Limitations / Edge Cases](#known-limitations--edge-cases)
816
+ - [Projects these scripts benefit from](#projects-these-scripts-benefit-from)
817
+ - [Reference](#reference)
818
+ - [CLI Tools Overview](#cli-tools-overview)
819
+ - [test coverage](#test-coverage)
820
+ - [Dar-backup options](#dar-backup-options)
821
+ - [Manager Options](#manager-options)
822
+ - [Cleanup options](#cleanup-options)
823
+ - [Clean-log options](#clean-log-options)
824
+ - [Dar-backup-systemd options](#dar-backup-systemd-options)
825
+ - [Installer options](#installer-options)
826
+ - [Demo options](#demo-options)
808
827
 
809
828
  ## My use case
810
829
 
@@ -1901,6 +1920,109 @@ where "c" is create, -r5 is 5% redundency and -n1 is 1 redundency file
1901
1920
 
1902
1921
  ## Points of interest
1903
1922
 
1923
+ ### Limitations on File Names with Special Characters
1924
+
1925
+ `dar-backup` strictly validates all command-line arguments passed to its internal execution engine to protect against command injection and shell-based attacks. As part of this security measure, certain characters are disallowed in user-provided inputs — particularly those that carry special meaning in shell environments:
1926
+
1927
+ Disallowed characters include:
1928
+
1929
+ ```text
1930
+ \$ & ; | > < ` \n
1931
+ ```
1932
+
1933
+ #### Why this matters
1934
+
1935
+ When restoring specific files using the --selection argument or similar mechanisms, filenames that contain one or more of these characters (e.g., file_with_currency$.txt) cannot be safely passed as command-line arguments. As a result, attempting to restore such a file by name using the CLI will result in a validation error.
1936
+
1937
+ ✅ Backups and Restores Still Work
1938
+
1939
+ ✅ These files are still backed up and restored automatically as part of normal FULL, DIFF, or INCR operations.
1940
+
1941
+ ❌ They cannot be explicitly specified for restore using CLI options like --selection="-g path/to/file_with_currency$.txt".
1942
+
1943
+ #### Workarounds
1944
+
1945
+ If you need to restore such a file:
1946
+
1947
+ Perform a restore of the entire directory using a more general selection (e.g., --selection="-g path/to/parent-directory").
1948
+
1949
+ Manually retrieve the restored file afterward.
1950
+
1951
+ ##### Restoring Files with Forbidden Characters in Their Names
1952
+
1953
+ The DAR Backup system enforces a strict command-line argument sanitizer to improve security and prevent shell injection attacks. As a result, certain characters are **not allowed** in filenames or arguments passed to the CLI, especially during restore operations. This includes characters like:
1954
+
1955
+ | Character | Reason Blocked |
1956
+ |----------:|---------------------------------|
1957
+ | `;` | Shell command separator |
1958
+ | `&` | Background execution operator |
1959
+ | `\|` | Pipe operator |
1960
+ | `<` / `>` | Redirection operators |
1961
+ | `#` | Shell comment |
1962
+ | `` ` `` | Command substitution |
1963
+ | `"` / `'` | Quoting that may be unbalanced |
1964
+
1965
+ However, **backups of files with such names still work** — they are preserved correctly within the archive. The limitation only applies to **invoking restore commands via the CLI**, where such filenames cannot be safely passed as arguments.
1966
+
1967
+ ##### Backups: Safe and Fully Functional
1968
+
1969
+ Files with special characters in their names **are backed up without issue**. The only issue you might encounter is restoring a file and giving the file name on the command line (with forbidden characters.)
1970
+
1971
+ ##### Restores via CLI: Limited by Sanitizer
1972
+
1973
+ Attempting to restore such files via:
1974
+
1975
+ ```bash
1976
+ dar-backup restore --file "weird#name.txt"
1977
+ ```
1978
+
1979
+ ...will fail with an error like:
1980
+
1981
+ ```bash
1982
+ Unsafe argument detected: weird#name.txt
1983
+ ```
1984
+
1985
+ ---
1986
+
1987
+ ##### Workaround: Use `dar` Directly
1988
+
1989
+ You can always restore the file manually using the `dar` command-line utility itself, bypassing any CLI restrictions imposed by the backup tool.
1990
+
1991
+ ##### Example: Manual Restore Using `dar`
1992
+
1993
+ ```bash
1994
+ dar -x /path/to/backup/example -g "weird#name.txt"
1995
+ ```
1996
+
1997
+ Where:
1998
+
1999
+ - `/path/to/backup/example` is the base name of the archive (without `.dar`, `.1.dar`, etc.).
2000
+ - `"weird#name.txt"` is the exact filename with the special character(s).
2001
+
2002
+ You may need to quote the argument or escape characters depending on your shell.
2003
+
2004
+ ---
2005
+
2006
+ ##### 🧪 How to Locate Files with Forbidden Characters
2007
+
2008
+ To search for such files inside the archive:
2009
+
2010
+ ```bash
2011
+ dar -l /path/to/backup/example | grep '[#;<>|&]'
2012
+ ```
2013
+
2014
+ This will help you identify files that require manual restoration.
2015
+
2016
+ ---
2017
+
2018
+ #### Summary
2019
+
2020
+ - 🚫 Forbidden characters are blocked **only** in CLI arguments to maintain safety.
2021
+ - ✅ Files containing these characters are **still archived and restorable**.
2022
+ - 🛠 Use `dar` directly for full manual control when restoring such files.
2023
+
2024
+
2025
+
1904
2026
  ### Merge FULL with DIFF, creating new FULL
1905
2027
 
1906
2028
  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.
@@ -2234,12 +2356,18 @@ Available options:
2234
2356
  --list-contents <archive> List the contents of a specified archive.
2235
2357
  --selection <params> Define file selection for listing/restoring.
2236
2358
  --restore <archive> Restore a specified archive.
2237
- -r, --restore-dir <path> Directory to restore files to.
2359
+ -r, --restore <archive> Restore archive.
2360
+ --restore-dir Directory on which to restore
2238
2361
  --verbose Enable verbose output.
2239
2362
  --suppress-dar-msg Filter out this from the darrc: "-vt", "-vs", "-vd", "-vf", "-va"
2240
2363
  --log-level <level> `debug` or `trace`, default is `info`.
2241
2364
  --log-stdout Also print log messages to stdout.
2242
2365
  --do-not-compare Do not compare restores to file system.
2366
+ --examples Show examples of using dar-backup.
2367
+ --readme Print README.md and exit
2368
+ --readme-pretty Print README.md with Markdown styling and exit
2369
+ --changelog Print Changelog and exit
2370
+ --changelog-pretty Print Changelog with Markdown styling and exit
2243
2371
  -v, --version Show version and license information.
2244
2372
  ```
2245
2373
 
@@ -1,15 +1,15 @@
1
1
  dar_backup/.darrc,sha256=-aerqivZmOsW_XBCh9IfbYTUvw0GkzDSr3Vx4GcNB1g,2113
2
- dar_backup/Changelog.md,sha256=T6w4NGFjBg2rgyTmgScKPFwdJx_M3XkEnU7aG3NQMyo,12094
3
- dar_backup/README.md,sha256=S8wpgaqa2LzXXZiQEzoOV1qjrpf9m1baYmvzGYtgEcE,59990
4
- dar_backup/__about__.py,sha256=SSWyynCpdSF2lIcROjGdr29Z-m523S9BbEc-ytO_L30,344
2
+ dar_backup/Changelog.md,sha256=bTVj2BamYKOHQtqVCf9D_YAwu_TrUR7cX3mkF4UUHRk,13383
3
+ dar_backup/README.md,sha256=NwxPE_rm54sPNlQIJIDkESzLhUy-1nW8Eu0XD3JiDCk,65675
4
+ dar_backup/__about__.py,sha256=KvK8jpeBGIXtEcfE_bSR6i55LW5IToi6U4J7zwfnIXc,344
5
5
  dar_backup/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  dar_backup/clean_log.py,sha256=pmmyPmLWbm3_3sHwJt9V_xBwUF8v015iS17ypJAGAZ4,6023
7
7
  dar_backup/cleanup.py,sha256=_ggDcpMCB1MhXStvYussp_PdGfhIFtEutT5BNrNkMSY,13297
8
- dar_backup/command_runner.py,sha256=_aaLk8j44tFNYBv9FJTpUaIFE_iIKEO1W1xDaiO5HJg,5599
8
+ dar_backup/command_runner.py,sha256=AeS8jqx2i6RX7UW6QZKvWEau3_MCGL181LvYCKFBdqg,7939
9
9
  dar_backup/config_settings.py,sha256=2UAHvatrVO4ark6lCn2Q7qBvZN6DUMK2ftlNrKpzlWc,5792
10
10
  dar_backup/dar-backup.conf,sha256=46V2zdvjj_aThFY11wWlffjmoiChYmatdf5DXCsmmao,1208
11
11
  dar_backup/dar-backup.conf.j2,sha256=z3epGo6nB_Jh3liTOp93wJO_HKUsf7qghe9cdtFH7cY,2021
12
- dar_backup/dar_backup.py,sha256=pgUQ1wZ5_yocRJJDRa4AkVyI7I8Z5WBY5vD5a4ASkmA,43001
12
+ dar_backup/dar_backup.py,sha256=wLF4W-bgTBqPBGViCpgy02qXy0ciebn5VpLY3glCQkg,43073
13
13
  dar_backup/dar_backup_systemd.py,sha256=PwAc2H2J3hQLWpnC6Ib95NZYtB2G2NDgkSblfLj1n10,3875
14
14
  dar_backup/demo.py,sha256=bxEq_nJwHuQydERPppkvhotg1fdwBX_CE33m5fX_kxw,7945
15
15
  dar_backup/demo_backup_def.j2,sha256=hQW2Glp0QGV3Kt8cwjS0mpOCdyzjVlpgbgL6LpXTKJA,1793
@@ -18,8 +18,8 @@ dar_backup/installer.py,sha256=xSXh77qquIZbUTSY3AbhERQbS7bnrPE__M_yqpszdhM,6883
18
18
  dar_backup/manager.py,sha256=d1zliFpSuWc8JhjKqLMC-xOhp5kSIcfeGkMZOVuZcM0,27143
19
19
  dar_backup/rich_progress.py,sha256=SfwFxebBl6jnDQMUQr4McknkW1yQWaJVo1Ju1OD3okA,3221
20
20
  dar_backup/util.py,sha256=iTOGsZyIdkvh9tIu7hD_IXi-9HO6GhVgqact5GGInEY,26063
21
- dar_backup-0.8.1.dist-info/METADATA,sha256=UPCRBCwXlLLjGVGZC-BVqCZXOCYa82lccd8W16I2sCg,102681
22
- dar_backup-0.8.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
23
- dar_backup-0.8.1.dist-info/entry_points.txt,sha256=pOK9M8cHeAcGIatrYzkm_1O89kPk0enyYONALYjFBx4,286
24
- dar_backup-0.8.1.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
25
- dar_backup-0.8.1.dist-info/RECORD,,
21
+ dar_backup-0.8.3.dist-info/METADATA,sha256=meBMdEXLKK7NxmJ2HpKOjEZ4WZ62imJWjPz4Z0Qkf6k,108366
22
+ dar_backup-0.8.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
23
+ dar_backup-0.8.3.dist-info/entry_points.txt,sha256=pOK9M8cHeAcGIatrYzkm_1O89kPk0enyYONALYjFBx4,286
24
+ dar_backup-0.8.3.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
25
+ dar_backup-0.8.3.dist-info/RECORD,,