dar-backup 0.8.0__py3-none-any.whl → 0.8.2__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 +26 -0
- dar_backup/README.md +1 -1
- dar_backup/__about__.py +1 -1
- dar_backup/command_runner.py +132 -19
- dar_backup/dar_backup.py +5 -5
- {dar_backup-0.8.0.dist-info → dar_backup-0.8.2.dist-info}/METADATA +2 -2
- {dar_backup-0.8.0.dist-info → dar_backup-0.8.2.dist-info}/RECORD +10 -10
- {dar_backup-0.8.0.dist-info → dar_backup-0.8.2.dist-info}/WHEEL +0 -0
- {dar_backup-0.8.0.dist-info → dar_backup-0.8.2.dist-info}/entry_points.txt +0 -0
- {dar_backup-0.8.0.dist-info → dar_backup-0.8.2.dist-info}/licenses/LICENSE +0 -0
dar_backup/Changelog.md
CHANGED
|
@@ -1,6 +1,32 @@
|
|
|
1
1
|
<!-- markdownlint-disable MD024 -->
|
|
2
2
|
# dar-backup Changelog
|
|
3
3
|
|
|
4
|
+
## v2-beta-0.8.2 - 2025-07-17
|
|
5
|
+
|
|
6
|
+
Github link: [v2-beta-0.8.2](https://github.com/per2jensen/dar-backup/tree/v2-beta-0.8.2/v2)
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- Security hardening: CommandRunner now performs strict command-line sanitization
|
|
11
|
+
- Disallows potentially dangerous characters (e.g. ;, &, |) in command arguments
|
|
12
|
+
- Prevents injection-style misuse when restoring specific files or invoking custom commands
|
|
13
|
+
|
|
14
|
+
- Documentation:
|
|
15
|
+
- 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)
|
|
16
|
+
- Includes a Markdown table listing all disallowed characters
|
|
17
|
+
|
|
18
|
+
- Test suite:
|
|
19
|
+
- Existing test cases updated to comply with the new sanitization rules
|
|
20
|
+
- Additional tests ensure CommandRunner handles large binary output and edge cases cleanly
|
|
21
|
+
|
|
22
|
+
## v2-beta-0.8.1 - 2025-07-16
|
|
23
|
+
|
|
24
|
+
Github link: [v2-beta-0.8.1](https://github.com/per2jensen/dar-backup/tree/v2-beta-0.8.1/v2)
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
|
|
28
|
+
- FIX: runner now logs an error and fills more data into the returned CommandResult.
|
|
29
|
+
|
|
4
30
|
## v2-beta-0.8.0 - 2025-06-13
|
|
5
31
|
|
|
6
32
|
Github link: [v2-beta-0.8.0](https://github.com/per2jensen/dar-backup/tree/v2-beta-0.8.0/v2)
|
dar_backup/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
[](https://pypi.org/project/dar-backup/)
|
|
10
10
|
[](https://pypi.org/project/dar-backup/)
|
|
11
11
|
[](https://github.com/per2jensen/dar-backup/blob/main/v2/doc/weekly_clones.png)
|
|
12
|
-
[](https://github.com/per2jensen/dar-backup/blob/main/v2/doc/weekly_clones.png)
|
|
12
|
+
[](https://github.com/per2jensen/dar-backup/blob/main/v2/doc/weekly_clones.png) <sub>🎯 Stats powered by [ClonePulse](https://github.com/per2jensen/clonepulse)</sub>
|
|
13
13
|
|
|
14
14
|
The wonderful 'dar' [Disk Archiver](https://github.com/Edrusb/DAR) is used for
|
|
15
15
|
the heavy lifting, together with the [parchive](https://github.com/Parchive/par2cmdline) suite in these scripts.
|
dar_backup/__about__.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
__version__ = "0.8.
|
|
1
|
+
__version__ = "0.8.2"
|
|
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.
|
dar_backup/command_runner.py
CHANGED
|
@@ -2,23 +2,77 @@
|
|
|
2
2
|
|
|
3
3
|
import subprocess
|
|
4
4
|
import logging
|
|
5
|
+
import traceback
|
|
5
6
|
import threading
|
|
6
7
|
import os
|
|
8
|
+
import re
|
|
9
|
+
import shlex
|
|
7
10
|
import sys
|
|
8
11
|
import tempfile
|
|
9
12
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../src")))
|
|
10
|
-
from typing import List, Optional
|
|
13
|
+
from typing import List, Optional, Union
|
|
11
14
|
from dar_backup.util import get_logger
|
|
12
15
|
|
|
13
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
|
+
|
|
14
46
|
class CommandResult:
|
|
15
|
-
def __init__(
|
|
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
|
+
):
|
|
16
55
|
self.returncode = returncode
|
|
17
56
|
self.stdout = stdout
|
|
18
57
|
self.stderr = stderr
|
|
58
|
+
self.stack = stack
|
|
59
|
+
self.note = note
|
|
60
|
+
|
|
61
|
+
|
|
19
62
|
|
|
20
63
|
def __repr__(self):
|
|
21
|
-
return f"<CommandResult returncode={self.returncode}>"
|
|
64
|
+
return f"<CommandResult returncode={self.returncode}\nstdout={self.stdout}\nstderr={self.stderr}\nstack={self.stack}>"
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def __str__(self):
|
|
68
|
+
return (
|
|
69
|
+
"CommandResult:\n"
|
|
70
|
+
f" Return code: {self.returncode}\n"
|
|
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>'}"
|
|
75
|
+
)
|
|
22
76
|
|
|
23
77
|
|
|
24
78
|
class CommandRunner:
|
|
@@ -35,6 +89,7 @@ class CommandRunner:
|
|
|
35
89
|
if not self.logger or not self.command_logger:
|
|
36
90
|
self.logger_fallback()
|
|
37
91
|
|
|
92
|
+
|
|
38
93
|
def logger_fallback(self):
|
|
39
94
|
"""
|
|
40
95
|
Setup temporary log files
|
|
@@ -72,24 +127,53 @@ class CommandRunner:
|
|
|
72
127
|
capture_output: bool = True,
|
|
73
128
|
text: bool = True
|
|
74
129
|
) -> CommandResult:
|
|
130
|
+
self._text_mode = text
|
|
75
131
|
timeout = timeout or self.default_timeout
|
|
76
132
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
+
|
|
155
|
+
self.command_logger.info(command)
|
|
156
|
+
self.logger.debug(command)
|
|
89
157
|
|
|
90
158
|
stdout_lines = []
|
|
91
159
|
stderr_lines = []
|
|
92
160
|
|
|
161
|
+
try:
|
|
162
|
+
process = subprocess.Popen(
|
|
163
|
+
cmd,
|
|
164
|
+
stdout=subprocess.PIPE if capture_output else None,
|
|
165
|
+
stderr=subprocess.PIPE if capture_output else None,
|
|
166
|
+
text=False,
|
|
167
|
+
bufsize=-1
|
|
168
|
+
)
|
|
169
|
+
except Exception as e:
|
|
170
|
+
stack = traceback.format_exc()
|
|
171
|
+
return CommandResult(
|
|
172
|
+
returncode=-1,
|
|
173
|
+
stdout='',
|
|
174
|
+
stderr=str(e),
|
|
175
|
+
stack=stack
|
|
176
|
+
)
|
|
93
177
|
|
|
94
178
|
def stream_output(stream, lines, level):
|
|
95
179
|
try:
|
|
@@ -97,9 +181,13 @@ class CommandRunner:
|
|
|
97
181
|
chunk = stream.read(1024)
|
|
98
182
|
if not chunk:
|
|
99
183
|
break
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
|
103
191
|
except Exception as e:
|
|
104
192
|
self.logger.warning(f"stream_output decode error: {e}")
|
|
105
193
|
finally:
|
|
@@ -123,11 +211,36 @@ class CommandRunner:
|
|
|
123
211
|
process.kill()
|
|
124
212
|
self.logger.error(f"Command timed out: {' '.join(cmd)}")
|
|
125
213
|
return CommandResult(-1, ''.join(stdout_lines), ''.join(stderr_lines))
|
|
214
|
+
except Exception as e:
|
|
215
|
+
stack = traceback.format_exc()
|
|
216
|
+
self.logger.error(f"Command execution failed: {' '.join(cmd)} with error: {e}")
|
|
217
|
+
return CommandResult(-1, ''.join(stdout_lines), ''.join(stderr_lines), stack)
|
|
126
218
|
|
|
127
219
|
for t in threads:
|
|
128
220
|
t.join()
|
|
129
221
|
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
if self._text_mode:
|
|
225
|
+
stdout_combined = ''.join(stdout_lines)
|
|
226
|
+
stderr_combined = ''.join(stderr_lines)
|
|
227
|
+
else:
|
|
228
|
+
stdout_combined = b''.join(stdout_lines)
|
|
229
|
+
stderr_combined = b''.join(stderr_lines)
|
|
230
|
+
|
|
231
|
+
|
|
130
232
|
if check and process.returncode != 0:
|
|
131
233
|
self.logger.error(f"Command failed with exit code {process.returncode}")
|
|
234
|
+
return CommandResult(
|
|
235
|
+
process.returncode,
|
|
236
|
+
stdout_combined,
|
|
237
|
+
stderr_combined,
|
|
238
|
+
stack=traceback.format_stack()
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
return CommandResult(
|
|
242
|
+
process.returncode,
|
|
243
|
+
stdout_combined,
|
|
244
|
+
stderr_combined
|
|
245
|
+
)
|
|
132
246
|
|
|
133
|
-
return CommandResult(process.returncode, ''.join(stdout_lines), ''.join(stderr_lines))
|
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, '--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.
|
|
3
|
+
Version: 0.8.2
|
|
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
|
|
@@ -727,7 +727,7 @@ Description-Content-Type: text/markdown
|
|
|
727
727
|
[](https://pypi.org/project/dar-backup/)
|
|
728
728
|
[](https://pypi.org/project/dar-backup/)
|
|
729
729
|
[](https://github.com/per2jensen/dar-backup/blob/main/v2/doc/weekly_clones.png)
|
|
730
|
-
[](https://github.com/per2jensen/dar-backup/blob/main/v2/doc/weekly_clones.png)
|
|
730
|
+
[](https://github.com/per2jensen/dar-backup/blob/main/v2/doc/weekly_clones.png) <sub>🎯 Stats powered by [ClonePulse](https://github.com/per2jensen/clonepulse)</sub>
|
|
731
731
|
|
|
732
732
|
The wonderful 'dar' [Disk Archiver](https://github.com/Edrusb/DAR) is used for
|
|
733
733
|
the heavy lifting, together with the [parchive](https://github.com/Parchive/par2cmdline) suite in these scripts.
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
dar_backup/.darrc,sha256=-aerqivZmOsW_XBCh9IfbYTUvw0GkzDSr3Vx4GcNB1g,2113
|
|
2
|
-
dar_backup/Changelog.md,sha256=
|
|
3
|
-
dar_backup/README.md,sha256=
|
|
4
|
-
dar_backup/__about__.py,sha256=
|
|
2
|
+
dar_backup/Changelog.md,sha256=kJHH12ETI46nzZURvhKZvZR4RndnY8KP22ORfN5u3iA,12988
|
|
3
|
+
dar_backup/README.md,sha256=S8wpgaqa2LzXXZiQEzoOV1qjrpf9m1baYmvzGYtgEcE,59990
|
|
4
|
+
dar_backup/__about__.py,sha256=HtwR6RuPdVHxYDraJvSQ0J9gRO3qkMMealFqBS1CfGc,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=
|
|
8
|
+
dar_backup/command_runner.py,sha256=cwthuNU4N1vzAjri0uh2x32vmCLu1B2S3OxGExpCGRE,7840
|
|
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=
|
|
12
|
+
dar_backup/dar_backup.py,sha256=JW1k0LuQhf_y2f1K1pIu1-PmKw4bGSrCB_MBoPvFWuM,43057
|
|
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.
|
|
22
|
-
dar_backup-0.8.
|
|
23
|
-
dar_backup-0.8.
|
|
24
|
-
dar_backup-0.8.
|
|
25
|
-
dar_backup-0.8.
|
|
21
|
+
dar_backup-0.8.2.dist-info/METADATA,sha256=YgHdGcT3ZcaCUYxtRwaArlHj4xelOH0DVumw2bgGwTk,102681
|
|
22
|
+
dar_backup-0.8.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
23
|
+
dar_backup-0.8.2.dist-info/entry_points.txt,sha256=pOK9M8cHeAcGIatrYzkm_1O89kPk0enyYONALYjFBx4,286
|
|
24
|
+
dar_backup-0.8.2.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
25
|
+
dar_backup-0.8.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|