dar-backup 0.8.0__tar.gz → 0.8.2__tar.gz
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-0.8.0/src/dar_backup → dar_backup-0.8.2}/Changelog.md +26 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/PKG-INFO +2 -2
- {dar_backup-0.8.0 → dar_backup-0.8.2}/README.md +1 -1
- {dar_backup-0.8.0 → dar_backup-0.8.2}/doc/badges/badge_clones.json +1 -1
- {dar_backup-0.8.0 → dar_backup-0.8.2}/doc/clones.json +195 -25
- dar_backup-0.8.2/doc/weekly_clones.png +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2/src/dar_backup}/Changelog.md +26 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/src/dar_backup/README.md +1 -1
- {dar_backup-0.8.0 → dar_backup-0.8.2}/src/dar_backup/__about__.py +1 -1
- dar_backup-0.8.2/src/dar_backup/command_runner.py +246 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/src/dar_backup/dar_backup.py +5 -5
- dar_backup-0.8.0/doc/weekly_clones.png +0 -0
- dar_backup-0.8.0/src/dar_backup/command_runner.py +0 -133
- {dar_backup-0.8.0 → dar_backup-0.8.2}/.gitignore +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/doc/badges/README.md +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/doc/badges/milestone_1000.txt +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/doc/badges/milestone_500.txt +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/doc/badges/milestone_badge.json +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/doc/dev.md +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/doc/doc.md +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/packages/deb/README.md +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/pyproject.toml +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/src/dar_backup/.darrc +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/src/dar_backup/__init__.py +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/src/dar_backup/clean_log.py +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/src/dar_backup/cleanup.py +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/src/dar_backup/config_settings.py +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/src/dar_backup/dar-backup.conf +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/src/dar_backup/dar-backup.conf.j2 +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/src/dar_backup/dar_backup_systemd.py +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/src/dar_backup/demo.py +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/src/dar_backup/demo_backup_def.j2 +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/src/dar_backup/exceptions.py +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/src/dar_backup/installer.py +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/src/dar_backup/manager.py +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/src/dar_backup/rich_progress.py +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/src/dar_backup/util.py +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/SecretStorage-3.3.3.dist-info/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/anyio-4.9.0.dist-info/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/black-25.1.0.dist-info/licenses/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/blib2to3/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/build-1.2.2.post1.dist-info/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/certifi-2025.4.26.dist-info/licenses/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/cffi-1.17.1.dist-info/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/charset_normalizer-3.4.2.dist-info/licenses/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/contourpy-1.3.2.dist-info/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/cryptography-45.0.3.dist-info/licenses/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/cycler-0.12.1.dist-info/LICENSE +0 -0
- {dar_backup-0.8.0/venv/lib/python3.12/site-packages/dar_backup-0.8.0.dist-info → dar_backup-0.8.2/venv/lib/python3.12/site-packages/dar_backup-0.8.2.dist-info}/licenses/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/filelock-3.18.0.dist-info/licenses/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/fonttools-4.58.2.dist-info/licenses/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/hyperlink-21.0.0.dist-info/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/id-1.5.0.dist-info/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/iniconfig-2.1.0.dist-info/licenses/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/jaraco.classes-3.4.0.dist-info/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/jaraco.context-6.0.1.dist-info/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/jaraco.functools-4.1.0.dist-info/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/jeepney-0.9.0.dist-info/licenses/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/keyring-25.6.0.dist-info/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/kiwisolver-1.4.8.dist-info/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/markdown_it_py-3.0.0.dist-info/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/matplotlib-3.10.3.dist-info/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/mdurl-0.1.2.dist-info/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/more_itertools-10.7.0.dist-info/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/mypy_extensions-1.1.0.dist-info/licenses/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/nh3-0.2.21.dist-info/licenses/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/numpy/ma/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/packaging-25.0.dist-info/licenses/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/pandas-2.3.0.dist-info/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/pathspec-0.12.1.dist-info/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/pexpect-4.9.0.dist-info/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/pillow-11.2.1.dist-info/licenses/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/platformdirs-4.3.8.dist-info/licenses/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/pluggy-1.6.0.dist-info/licenses/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/psutil-7.0.0.dist-info/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/ptyprocess-0.7.0.dist-info/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/pycparser-2.22.dist-info/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/pygments-2.19.1.dist-info/licenses/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/pyparsing-3.2.3.dist-info/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/pyproject_hooks-1.2.0.dist-info/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/pytest-8.4.0.dist-info/licenses/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/pytest_cov-6.1.1.dist-info/licenses/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/pytest_timeout-2.4.0.dist-info/licenses/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/python_dateutil-2.9.0.post0.dist-info/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/readme_renderer-44.0.dist-info/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/requests-2.32.3.dist-info/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/requests_toolbelt-1.0.0.dist-info/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/rfc3986-2.0.0.dist-info/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/rich-14.0.0.dist-info/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/shellingham-1.5.4.dist-info/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/six-1.17.0.dist-info/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/sniffio-1.3.1.dist-info/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/tomli_w-1.2.0.dist-info/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/tomlkit-0.13.2.dist-info/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/trove_classifiers-2025.5.9.12.dist-info/licenses/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/twine-6.1.0.dist-info/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/typing_extensions-4.14.0.dist-info/licenses/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/tzdata-2025.2.dist-info/licenses/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/virtualenv-20.31.2.dist-info/licenses/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/wheel/vendored/packaging/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/zipp-3.22.0.dist-info/licenses/LICENSE +0 -0
- {dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/zstandard-0.23.0.dist-info/LICENSE +0 -0
|
@@ -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)
|
|
@@ -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.
|
|
@@ -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.
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
"label": "Daily max: 124"
|
|
14
14
|
}
|
|
15
15
|
],
|
|
16
|
-
"total_clones":
|
|
17
|
-
"unique_clones":
|
|
16
|
+
"total_clones": 1475,
|
|
17
|
+
"unique_clones": 838,
|
|
18
18
|
"daily": [
|
|
19
19
|
{
|
|
20
20
|
"timestamp": "2025-05-04T00:00:00Z",
|
|
@@ -153,68 +153,238 @@
|
|
|
153
153
|
},
|
|
154
154
|
{
|
|
155
155
|
"timestamp": "2025-05-31T00:00:00Z",
|
|
156
|
-
"count":
|
|
157
|
-
"uniques":
|
|
156
|
+
"count": 8,
|
|
157
|
+
"uniques": 5
|
|
158
158
|
},
|
|
159
159
|
{
|
|
160
160
|
"timestamp": "2025-06-01T00:00:00Z",
|
|
161
|
-
"count":
|
|
162
|
-
"uniques":
|
|
161
|
+
"count": 25,
|
|
162
|
+
"uniques": 15
|
|
163
163
|
},
|
|
164
164
|
{
|
|
165
165
|
"timestamp": "2025-06-02T00:00:00Z",
|
|
166
|
-
"count":
|
|
167
|
-
"uniques":
|
|
166
|
+
"count": 15,
|
|
167
|
+
"uniques": 7
|
|
168
168
|
},
|
|
169
169
|
{
|
|
170
170
|
"timestamp": "2025-06-03T00:00:00Z",
|
|
171
|
-
"count":
|
|
172
|
-
"uniques":
|
|
171
|
+
"count": 57,
|
|
172
|
+
"uniques": 31
|
|
173
173
|
},
|
|
174
174
|
{
|
|
175
175
|
"timestamp": "2025-06-04T00:00:00Z",
|
|
176
|
-
"count":
|
|
177
|
-
"uniques":
|
|
176
|
+
"count": 35,
|
|
177
|
+
"uniques": 22
|
|
178
178
|
},
|
|
179
179
|
{
|
|
180
180
|
"timestamp": "2025-06-05T00:00:00Z",
|
|
181
|
-
"count":
|
|
182
|
-
"uniques":
|
|
181
|
+
"count": 15,
|
|
182
|
+
"uniques": 12
|
|
183
183
|
},
|
|
184
184
|
{
|
|
185
185
|
"timestamp": "2025-06-06T00:00:00Z",
|
|
186
|
-
"count":
|
|
187
|
-
"uniques":
|
|
186
|
+
"count": 33,
|
|
187
|
+
"uniques": 18
|
|
188
188
|
},
|
|
189
189
|
{
|
|
190
190
|
"timestamp": "2025-06-07T00:00:00Z",
|
|
191
|
-
"count":
|
|
192
|
-
"uniques":
|
|
191
|
+
"count": 49,
|
|
192
|
+
"uniques": 20
|
|
193
193
|
},
|
|
194
194
|
{
|
|
195
195
|
"timestamp": "2025-06-08T00:00:00Z",
|
|
196
|
-
"count":
|
|
197
|
-
"uniques":
|
|
196
|
+
"count": 33,
|
|
197
|
+
"uniques": 19
|
|
198
198
|
},
|
|
199
199
|
{
|
|
200
200
|
"timestamp": "2025-06-09T00:00:00Z",
|
|
201
|
-
"count":
|
|
202
|
-
"uniques":
|
|
201
|
+
"count": 18,
|
|
202
|
+
"uniques": 12
|
|
203
203
|
},
|
|
204
204
|
{
|
|
205
205
|
"timestamp": "2025-06-10T00:00:00Z",
|
|
206
|
-
"count":
|
|
207
|
-
"uniques":
|
|
206
|
+
"count": 26,
|
|
207
|
+
"uniques": 16
|
|
208
208
|
},
|
|
209
209
|
{
|
|
210
210
|
"timestamp": "2025-06-11T00:00:00Z",
|
|
211
|
+
"count": 9,
|
|
212
|
+
"uniques": 6
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
"timestamp": "2025-06-12T00:00:00Z",
|
|
216
|
+
"count": 10,
|
|
217
|
+
"uniques": 8
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
"timestamp": "2025-06-13T00:00:00Z",
|
|
221
|
+
"count": 48,
|
|
222
|
+
"uniques": 18
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
"timestamp": "2025-06-14T00:00:00Z",
|
|
226
|
+
"count": 11,
|
|
227
|
+
"uniques": 9
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
"timestamp": "2025-06-15T00:00:00Z",
|
|
231
|
+
"count": 10,
|
|
232
|
+
"uniques": 8
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
"timestamp": "2025-06-16T00:00:00Z",
|
|
236
|
+
"count": 18,
|
|
237
|
+
"uniques": 11
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
"timestamp": "2025-06-17T00:00:00Z",
|
|
241
|
+
"count": 11,
|
|
242
|
+
"uniques": 9
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
"timestamp": "2025-06-18T00:00:00Z",
|
|
246
|
+
"count": 9,
|
|
247
|
+
"uniques": 7
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
"timestamp": "2025-06-19T00:00:00Z",
|
|
251
|
+
"count": 10,
|
|
252
|
+
"uniques": 8
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
"timestamp": "2025-06-20T00:00:00Z",
|
|
256
|
+
"count": 25,
|
|
257
|
+
"uniques": 18
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
"timestamp": "2025-06-21T00:00:00Z",
|
|
261
|
+
"count": 8,
|
|
262
|
+
"uniques": 6
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
"timestamp": "2025-06-22T00:00:00Z",
|
|
266
|
+
"count": 8,
|
|
267
|
+
"uniques": 6
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
"timestamp": "2025-06-23T00:00:00Z",
|
|
271
|
+
"count": 19,
|
|
272
|
+
"uniques": 14
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
"timestamp": "2025-06-24T00:00:00Z",
|
|
276
|
+
"count": 8,
|
|
277
|
+
"uniques": 6
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
"timestamp": "2025-06-25T00:00:00Z",
|
|
281
|
+
"count": 11,
|
|
282
|
+
"uniques": 8
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
"timestamp": "2025-06-26T00:00:00Z",
|
|
286
|
+
"count": 6,
|
|
287
|
+
"uniques": 5
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
"timestamp": "2025-06-27T00:00:00Z",
|
|
291
|
+
"count": 11,
|
|
292
|
+
"uniques": 7
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
"timestamp": "2025-06-28T00:00:00Z",
|
|
296
|
+
"count": 12,
|
|
297
|
+
"uniques": 9
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
"timestamp": "2025-06-29T00:00:00Z",
|
|
301
|
+
"count": 10,
|
|
302
|
+
"uniques": 7
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
"timestamp": "2025-06-30T00:00:00Z",
|
|
211
306
|
"count": 20,
|
|
212
307
|
"uniques": 13
|
|
213
308
|
},
|
|
214
309
|
{
|
|
215
|
-
"timestamp": "2025-
|
|
310
|
+
"timestamp": "2025-07-01T00:00:00Z",
|
|
311
|
+
"count": 12,
|
|
312
|
+
"uniques": 10
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
"timestamp": "2025-07-02T00:00:00Z",
|
|
316
|
+
"count": 12,
|
|
317
|
+
"uniques": 10
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
"timestamp": "2025-07-03T00:00:00Z",
|
|
321
|
+
"count": 12,
|
|
322
|
+
"uniques": 10
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
"timestamp": "2025-07-04T00:00:00Z",
|
|
216
326
|
"count": 18,
|
|
327
|
+
"uniques": 9
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
"timestamp": "2025-07-05T00:00:00Z",
|
|
331
|
+
"count": 21,
|
|
332
|
+
"uniques": 14
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
"timestamp": "2025-07-06T00:00:00Z",
|
|
336
|
+
"count": 20,
|
|
337
|
+
"uniques": 15
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
"timestamp": "2025-07-07T00:00:00Z",
|
|
341
|
+
"count": 26,
|
|
342
|
+
"uniques": 17
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
"timestamp": "2025-07-08T00:00:00Z",
|
|
346
|
+
"count": 14,
|
|
347
|
+
"uniques": 9
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
"timestamp": "2025-07-09T00:00:00Z",
|
|
351
|
+
"count": 19,
|
|
352
|
+
"uniques": 13
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
"timestamp": "2025-07-10T00:00:00Z",
|
|
356
|
+
"count": 18,
|
|
357
|
+
"uniques": 13
|
|
358
|
+
},
|
|
359
|
+
{
|
|
360
|
+
"timestamp": "2025-07-11T00:00:00Z",
|
|
361
|
+
"count": 22,
|
|
362
|
+
"uniques": 13
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
"timestamp": "2025-07-12T00:00:00Z",
|
|
366
|
+
"count": 19,
|
|
217
367
|
"uniques": 12
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
"timestamp": "2025-07-13T00:00:00Z",
|
|
371
|
+
"count": 20,
|
|
372
|
+
"uniques": 13
|
|
373
|
+
},
|
|
374
|
+
{
|
|
375
|
+
"timestamp": "2025-07-14T00:00:00Z",
|
|
376
|
+
"count": 38,
|
|
377
|
+
"uniques": 21
|
|
378
|
+
},
|
|
379
|
+
{
|
|
380
|
+
"timestamp": "2025-07-15T00:00:00Z",
|
|
381
|
+
"count": 18,
|
|
382
|
+
"uniques": 12
|
|
383
|
+
},
|
|
384
|
+
{
|
|
385
|
+
"timestamp": "2025-07-16T00:00:00Z",
|
|
386
|
+
"count": 30,
|
|
387
|
+
"uniques": 18
|
|
218
388
|
}
|
|
219
389
|
]
|
|
220
390
|
}
|
|
Binary file
|
|
@@ -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)
|
|
@@ -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.
|
|
@@ -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.
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
2
|
+
|
|
3
|
+
import subprocess
|
|
4
|
+
import logging
|
|
5
|
+
import traceback
|
|
6
|
+
import threading
|
|
7
|
+
import os
|
|
8
|
+
import re
|
|
9
|
+
import shlex
|
|
10
|
+
import sys
|
|
11
|
+
import tempfile
|
|
12
|
+
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../src")))
|
|
13
|
+
from typing import List, Optional, Union
|
|
14
|
+
from dar_backup.util import get_logger
|
|
15
|
+
|
|
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
|
+
|
|
46
|
+
class CommandResult:
|
|
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
|
+
):
|
|
55
|
+
self.returncode = returncode
|
|
56
|
+
self.stdout = stdout
|
|
57
|
+
self.stderr = stderr
|
|
58
|
+
self.stack = stack
|
|
59
|
+
self.note = note
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def __repr__(self):
|
|
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
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class CommandRunner:
|
|
79
|
+
def __init__(
|
|
80
|
+
self,
|
|
81
|
+
logger: Optional[logging.Logger] = None,
|
|
82
|
+
command_logger: Optional[logging.Logger] = None,
|
|
83
|
+
default_timeout: int = 30
|
|
84
|
+
):
|
|
85
|
+
self.logger = logger or get_logger()
|
|
86
|
+
self.command_logger = command_logger or get_logger(command_output_logger=True)
|
|
87
|
+
self.default_timeout = default_timeout
|
|
88
|
+
|
|
89
|
+
if not self.logger or not self.command_logger:
|
|
90
|
+
self.logger_fallback()
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def logger_fallback(self):
|
|
94
|
+
"""
|
|
95
|
+
Setup temporary log files
|
|
96
|
+
"""
|
|
97
|
+
main_log = tempfile.NamedTemporaryFile(delete=False)
|
|
98
|
+
command_log = tempfile.NamedTemporaryFile(delete=False)
|
|
99
|
+
|
|
100
|
+
logger = logging.getLogger("command_runner_fallback_main_logger")
|
|
101
|
+
command_logger = logging.getLogger("command_runner_fallback_command_logger")
|
|
102
|
+
logger.setLevel(logging.DEBUG)
|
|
103
|
+
command_logger.setLevel(logging.DEBUG)
|
|
104
|
+
|
|
105
|
+
main_handler = logging.FileHandler(main_log.name)
|
|
106
|
+
command_handler = logging.FileHandler(command_log.name)
|
|
107
|
+
|
|
108
|
+
logger.addHandler(main_handler)
|
|
109
|
+
command_logger.addHandler(command_handler)
|
|
110
|
+
|
|
111
|
+
self.logger = logger
|
|
112
|
+
self.command_logger = command_logger
|
|
113
|
+
self.default_timeout = 30
|
|
114
|
+
self.logger.info("CommandRunner initialized with fallback loggers")
|
|
115
|
+
self.command_logger.info("CommandRunner initialized with fallback loggers")
|
|
116
|
+
|
|
117
|
+
print(f"[WARN] Using fallback loggers:\n Main log: {main_log.name}\n Command log: {command_log.name}", file=sys.stderr)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def run(
|
|
122
|
+
self,
|
|
123
|
+
cmd: List[str],
|
|
124
|
+
*,
|
|
125
|
+
timeout: Optional[int] = None,
|
|
126
|
+
check: bool = False,
|
|
127
|
+
capture_output: bool = True,
|
|
128
|
+
text: bool = True
|
|
129
|
+
) -> CommandResult:
|
|
130
|
+
self._text_mode = text
|
|
131
|
+
timeout = timeout or self.default_timeout
|
|
132
|
+
|
|
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)
|
|
157
|
+
|
|
158
|
+
stdout_lines = []
|
|
159
|
+
stderr_lines = []
|
|
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
|
+
)
|
|
177
|
+
|
|
178
|
+
def stream_output(stream, lines, level):
|
|
179
|
+
try:
|
|
180
|
+
while True:
|
|
181
|
+
chunk = stream.read(1024)
|
|
182
|
+
if not chunk:
|
|
183
|
+
break
|
|
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
|
|
191
|
+
except Exception as e:
|
|
192
|
+
self.logger.warning(f"stream_output decode error: {e}")
|
|
193
|
+
finally:
|
|
194
|
+
stream.close()
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
threads = []
|
|
199
|
+
if capture_output and process.stdout:
|
|
200
|
+
t_out = threading.Thread(target=stream_output, args=(process.stdout, stdout_lines, logging.INFO))
|
|
201
|
+
t_out.start()
|
|
202
|
+
threads.append(t_out)
|
|
203
|
+
if capture_output and process.stderr:
|
|
204
|
+
t_err = threading.Thread(target=stream_output, args=(process.stderr, stderr_lines, logging.ERROR))
|
|
205
|
+
t_err.start()
|
|
206
|
+
threads.append(t_err)
|
|
207
|
+
|
|
208
|
+
try:
|
|
209
|
+
process.wait(timeout=timeout)
|
|
210
|
+
except subprocess.TimeoutExpired:
|
|
211
|
+
process.kill()
|
|
212
|
+
self.logger.error(f"Command timed out: {' '.join(cmd)}")
|
|
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)
|
|
218
|
+
|
|
219
|
+
for t in threads:
|
|
220
|
+
t.join()
|
|
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
|
+
|
|
232
|
+
if check and process.returncode != 0:
|
|
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
|
+
)
|
|
246
|
+
|
|
@@ -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)
|
|
Binary file
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
2
|
-
|
|
3
|
-
import subprocess
|
|
4
|
-
import logging
|
|
5
|
-
import threading
|
|
6
|
-
import os
|
|
7
|
-
import sys
|
|
8
|
-
import tempfile
|
|
9
|
-
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../src")))
|
|
10
|
-
from typing import List, Optional
|
|
11
|
-
from dar_backup.util import get_logger
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class CommandResult:
|
|
15
|
-
def __init__(self, returncode: int, stdout: str, stderr: str):
|
|
16
|
-
self.returncode = returncode
|
|
17
|
-
self.stdout = stdout
|
|
18
|
-
self.stderr = stderr
|
|
19
|
-
|
|
20
|
-
def __repr__(self):
|
|
21
|
-
return f"<CommandResult returncode={self.returncode}>"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class CommandRunner:
|
|
25
|
-
def __init__(
|
|
26
|
-
self,
|
|
27
|
-
logger: Optional[logging.Logger] = None,
|
|
28
|
-
command_logger: Optional[logging.Logger] = None,
|
|
29
|
-
default_timeout: int = 30
|
|
30
|
-
):
|
|
31
|
-
self.logger = logger or get_logger()
|
|
32
|
-
self.command_logger = command_logger or get_logger(command_output_logger=True)
|
|
33
|
-
self.default_timeout = default_timeout
|
|
34
|
-
|
|
35
|
-
if not self.logger or not self.command_logger:
|
|
36
|
-
self.logger_fallback()
|
|
37
|
-
|
|
38
|
-
def logger_fallback(self):
|
|
39
|
-
"""
|
|
40
|
-
Setup temporary log files
|
|
41
|
-
"""
|
|
42
|
-
main_log = tempfile.NamedTemporaryFile(delete=False)
|
|
43
|
-
command_log = tempfile.NamedTemporaryFile(delete=False)
|
|
44
|
-
|
|
45
|
-
logger = logging.getLogger("command_runner_fallback_main_logger")
|
|
46
|
-
command_logger = logging.getLogger("command_runner_fallback_command_logger")
|
|
47
|
-
logger.setLevel(logging.DEBUG)
|
|
48
|
-
command_logger.setLevel(logging.DEBUG)
|
|
49
|
-
|
|
50
|
-
main_handler = logging.FileHandler(main_log.name)
|
|
51
|
-
command_handler = logging.FileHandler(command_log.name)
|
|
52
|
-
|
|
53
|
-
logger.addHandler(main_handler)
|
|
54
|
-
command_logger.addHandler(command_handler)
|
|
55
|
-
|
|
56
|
-
self.logger = logger
|
|
57
|
-
self.command_logger = command_logger
|
|
58
|
-
self.default_timeout = 30
|
|
59
|
-
self.logger.info("CommandRunner initialized with fallback loggers")
|
|
60
|
-
self.command_logger.info("CommandRunner initialized with fallback loggers")
|
|
61
|
-
|
|
62
|
-
print(f"[WARN] Using fallback loggers:\n Main log: {main_log.name}\n Command log: {command_log.name}", file=sys.stderr)
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
def run(
|
|
67
|
-
self,
|
|
68
|
-
cmd: List[str],
|
|
69
|
-
*,
|
|
70
|
-
timeout: Optional[int] = None,
|
|
71
|
-
check: bool = False,
|
|
72
|
-
capture_output: bool = True,
|
|
73
|
-
text: bool = True
|
|
74
|
-
) -> CommandResult:
|
|
75
|
-
timeout = timeout or self.default_timeout
|
|
76
|
-
|
|
77
|
-
#log the command to be executed
|
|
78
|
-
command = f"Executing command: {' '.join(cmd)} (timeout={timeout}s)"
|
|
79
|
-
self.command_logger.info(command) # log to command logger
|
|
80
|
-
self.logger.debug(command) # log to main logger if "--log-level debug"
|
|
81
|
-
|
|
82
|
-
process = subprocess.Popen(
|
|
83
|
-
cmd,
|
|
84
|
-
stdout=subprocess.PIPE if capture_output else None,
|
|
85
|
-
stderr=subprocess.PIPE if capture_output else None,
|
|
86
|
-
text=False,
|
|
87
|
-
bufsize=-1
|
|
88
|
-
)
|
|
89
|
-
|
|
90
|
-
stdout_lines = []
|
|
91
|
-
stderr_lines = []
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
def stream_output(stream, lines, level):
|
|
95
|
-
try:
|
|
96
|
-
while True:
|
|
97
|
-
chunk = stream.read(1024)
|
|
98
|
-
if not chunk:
|
|
99
|
-
break
|
|
100
|
-
decoded = chunk.decode('utf-8', errors='replace')
|
|
101
|
-
lines.append(decoded)
|
|
102
|
-
self.command_logger.log(level, decoded.strip())
|
|
103
|
-
except Exception as e:
|
|
104
|
-
self.logger.warning(f"stream_output decode error: {e}")
|
|
105
|
-
finally:
|
|
106
|
-
stream.close()
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
threads = []
|
|
111
|
-
if capture_output and process.stdout:
|
|
112
|
-
t_out = threading.Thread(target=stream_output, args=(process.stdout, stdout_lines, logging.INFO))
|
|
113
|
-
t_out.start()
|
|
114
|
-
threads.append(t_out)
|
|
115
|
-
if capture_output and process.stderr:
|
|
116
|
-
t_err = threading.Thread(target=stream_output, args=(process.stderr, stderr_lines, logging.ERROR))
|
|
117
|
-
t_err.start()
|
|
118
|
-
threads.append(t_err)
|
|
119
|
-
|
|
120
|
-
try:
|
|
121
|
-
process.wait(timeout=timeout)
|
|
122
|
-
except subprocess.TimeoutExpired:
|
|
123
|
-
process.kill()
|
|
124
|
-
self.logger.error(f"Command timed out: {' '.join(cmd)}")
|
|
125
|
-
return CommandResult(-1, ''.join(stdout_lines), ''.join(stderr_lines))
|
|
126
|
-
|
|
127
|
-
for t in threads:
|
|
128
|
-
t.join()
|
|
129
|
-
|
|
130
|
-
if check and process.returncode != 0:
|
|
131
|
-
self.logger.error(f"Command failed with exit code {process.returncode}")
|
|
132
|
-
|
|
133
|
-
return CommandResult(process.returncode, ''.join(stdout_lines), ''.join(stderr_lines))
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/id-1.5.0.dist-info/LICENSE
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dar_backup-0.8.0 → dar_backup-0.8.2}/venv/lib/python3.12/site-packages/six-1.17.0.dist-info/LICENSE
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|