hbkit 0.4.2__tar.gz → 0.4.3__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.
Potentially problematic release.
This version of hbkit might be problematic. Click here for more details.
- {hbkit-0.4.2 → hbkit-0.4.3}/PKG-INFO +16 -5
- {hbkit-0.4.2 → hbkit-0.4.3}/README.md +15 -4
- hbkit-0.4.3/packaging/README.md +53 -0
- {hbkit-0.4.2 → hbkit-0.4.3}/pyproject.toml +1 -1
- {hbkit-0.4.2 → hbkit-0.4.3}/src/hbkit/__init__.py +1 -1
- {hbkit-0.4.2 → hbkit-0.4.3}/src/hbkit/cli.py +7 -2
- {hbkit-0.4.2 → hbkit-0.4.3}/src/hbkit/runner.py +6 -0
- {hbkit-0.4.2 → hbkit-0.4.3}/tests/test_hbkit.py +63 -0
- {hbkit-0.4.2 → hbkit-0.4.3}/.gitignore +0 -0
- {hbkit-0.4.2 → hbkit-0.4.3}/FORMAT.md +0 -0
- {hbkit-0.4.2 → hbkit-0.4.3}/LICENSE +0 -0
- {hbkit-0.4.2 → hbkit-0.4.3}/src/hbkit/archive.py +0 -0
- {hbkit-0.4.2 → hbkit-0.4.3}/src/hbkit/crypto.py +0 -0
- {hbkit-0.4.2 → hbkit-0.4.3}/src/hbkit/doctor.py +0 -0
- {hbkit-0.4.2 → hbkit-0.4.3}/src/hbkit/index.py +0 -0
- {hbkit-0.4.2 → hbkit-0.4.3}/src/hbkit/manifest.py +0 -0
- {hbkit-0.4.2 → hbkit-0.4.3}/src/hbkit/mount.py +0 -0
- {hbkit-0.4.2 → hbkit-0.4.3}/src/hbkit/tui.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hbkit
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.3
|
|
4
4
|
Summary: Recover files from Synology Hyper Backup (.hbk) archives without Synology software
|
|
5
5
|
Project-URL: Homepage, https://github.com/YordiLorenzo/hbkit
|
|
6
6
|
Project-URL: Source, https://github.com/YordiLorenzo/hbkit
|
|
@@ -40,8 +40,7 @@ a tree, and pull out what you want. Works headless on Linux and macOS, including
|
|
|
40
40
|
own Hyper Backup Explorer is awkward or unavailable.
|
|
41
41
|
|
|
42
42
|
```sh
|
|
43
|
-
brew install
|
|
44
|
-
pip install hbkit
|
|
43
|
+
brew install YordiLorenzo/tap/hbkit # macOS / Linux, pulls in liblz4 for you
|
|
45
44
|
|
|
46
45
|
hbk /Volumes/Backup doctor # can this archive be recovered?
|
|
47
46
|
hbk /Volumes/Backup doctor -p secret # encrypted? add a password
|
|
@@ -49,8 +48,20 @@ hbk-tui /Volumes/Backup # browse and select interactively
|
|
|
49
48
|
hbk /Volumes/Backup get "/Photos/*" ~/restore
|
|
50
49
|
```
|
|
51
50
|
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
### Other ways to install
|
|
52
|
+
|
|
53
|
+
```sh
|
|
54
|
+
pip install hbkit # any platform; also needs liblz4 (see below)
|
|
55
|
+
pipx install hbkit # same, kept in its own environment
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Arch users can build from [`packaging/aur/PKGBUILD`](packaging/aur/PKGBUILD); a nixpkgs
|
|
59
|
+
derivation lives in [`packaging/nix/package.nix`](packaging/nix/package.nix).
|
|
60
|
+
|
|
61
|
+
`liblz4` is a runtime requirement — chunks are raw LZ4 blocks, loaded with `dlopen`, so a
|
|
62
|
+
missing library only shows up when you extract something. The Homebrew formula installs it
|
|
63
|
+
for you. Otherwise: `brew install lz4`, `sudo apt install liblz4-1`, or
|
|
64
|
+
`sudo pacman -S lz4`. Set `HBK_LZ4` if yours lives somewhere unusual.
|
|
54
65
|
|
|
55
66
|
---
|
|
56
67
|
|
|
@@ -11,8 +11,7 @@ a tree, and pull out what you want. Works headless on Linux and macOS, including
|
|
|
11
11
|
own Hyper Backup Explorer is awkward or unavailable.
|
|
12
12
|
|
|
13
13
|
```sh
|
|
14
|
-
brew install
|
|
15
|
-
pip install hbkit
|
|
14
|
+
brew install YordiLorenzo/tap/hbkit # macOS / Linux, pulls in liblz4 for you
|
|
16
15
|
|
|
17
16
|
hbk /Volumes/Backup doctor # can this archive be recovered?
|
|
18
17
|
hbk /Volumes/Backup doctor -p secret # encrypted? add a password
|
|
@@ -20,8 +19,20 @@ hbk-tui /Volumes/Backup # browse and select interactively
|
|
|
20
19
|
hbk /Volumes/Backup get "/Photos/*" ~/restore
|
|
21
20
|
```
|
|
22
21
|
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
### Other ways to install
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
pip install hbkit # any platform; also needs liblz4 (see below)
|
|
26
|
+
pipx install hbkit # same, kept in its own environment
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Arch users can build from [`packaging/aur/PKGBUILD`](packaging/aur/PKGBUILD); a nixpkgs
|
|
30
|
+
derivation lives in [`packaging/nix/package.nix`](packaging/nix/package.nix).
|
|
31
|
+
|
|
32
|
+
`liblz4` is a runtime requirement — chunks are raw LZ4 blocks, loaded with `dlopen`, so a
|
|
33
|
+
missing library only shows up when you extract something. The Homebrew formula installs it
|
|
34
|
+
for you. Otherwise: `brew install lz4`, `sudo apt install liblz4-1`, or
|
|
35
|
+
`sudo pacman -S lz4`. Set `HBK_LZ4` if yours lives somewhere unusual.
|
|
25
36
|
|
|
26
37
|
---
|
|
27
38
|
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Packaging
|
|
2
|
+
|
|
3
|
+
Recipes for package repositories that keep their definitions outside this repo. Each one
|
|
4
|
+
pins a released sdist from PyPI, so bumping a version means changing the version string and
|
|
5
|
+
the hash, nothing else.
|
|
6
|
+
|
|
7
|
+
| Target | Files here | Lives at |
|
|
8
|
+
| ------ | ---------- | -------- |
|
|
9
|
+
| Homebrew | — | [YordiLorenzo/homebrew-tap](https://github.com/YordiLorenzo/homebrew-tap) |
|
|
10
|
+
| AUR | `aur/PKGBUILD`, `aur/.SRCINFO` | `ssh://aur@aur.archlinux.org/hbkit.git` |
|
|
11
|
+
| nixpkgs | `nix/package.nix` | `pkgs/by-name/hb/hbkit/package.nix` |
|
|
12
|
+
|
|
13
|
+
## A note on liblz4
|
|
14
|
+
|
|
15
|
+
hbkit `dlopen()`s liblz4 rather than linking it, so a package that forgets the dependency
|
|
16
|
+
still builds and installs cleanly, then fails the first time someone extracts a file. Every
|
|
17
|
+
recipe here must declare it, and should ideally set `HBK_LZ4` to an absolute path so the
|
|
18
|
+
library resolves regardless of the loader search path.
|
|
19
|
+
|
|
20
|
+
The Homebrew formula wraps both entry points with `HBK_LZ4` for exactly this reason. Arch
|
|
21
|
+
and Nix put their libraries on the default search path, so a plain dependency is enough.
|
|
22
|
+
|
|
23
|
+
## AUR
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
git clone ssh://aur@aur.archlinux.org/hbkit.git
|
|
27
|
+
cp packaging/aur/PKGBUILD packaging/aur/.SRCINFO hbkit/
|
|
28
|
+
cd hbkit && git add PKGBUILD .SRCINFO && git commit -m "hbkit 0.4.2" && git push
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
`.SRCINFO` must match `PKGBUILD`; regenerate it on Arch with `makepkg --printsrcinfo >
|
|
32
|
+
.SRCINFO`. The AUR rejects a push where the two disagree.
|
|
33
|
+
|
|
34
|
+
## nixpkgs
|
|
35
|
+
|
|
36
|
+
`nix/package.nix` goes to `pkgs/by-name/hb/hbkit/package.nix` in a nixpkgs fork. It
|
|
37
|
+
references a maintainer that must exist first, so `maintainers/maintainer-list.nix` needs:
|
|
38
|
+
|
|
39
|
+
```nix
|
|
40
|
+
yordilorenzo = {
|
|
41
|
+
email = "yordilorenzo@gmail.com";
|
|
42
|
+
github = "YordiLorenzo";
|
|
43
|
+
githubId = 7012112;
|
|
44
|
+
name = "Yordi de Kleijn";
|
|
45
|
+
};
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Build it before opening the PR:
|
|
49
|
+
|
|
50
|
+
```sh
|
|
51
|
+
nix-build -A hbkit
|
|
52
|
+
nix run -f . hbkit -- --version
|
|
53
|
+
```
|
|
@@ -87,12 +87,17 @@ def all_files(db):
|
|
|
87
87
|
""")
|
|
88
88
|
|
|
89
89
|
|
|
90
|
+
def resolve_password(password):
|
|
91
|
+
"""-p wins, then HBK_PASSWORD. None means "prompt if the archive turns out to need it"."""
|
|
92
|
+
return password or os.environ.get("HBK_PASSWORD")
|
|
93
|
+
|
|
94
|
+
|
|
90
95
|
def open_archive(archive, password=None):
|
|
91
96
|
"""Open, prompting for a password only if the archive turns out to be encrypted."""
|
|
92
97
|
try:
|
|
93
98
|
return hbk.Archive(archive, password=password)
|
|
94
99
|
except hbk.NeedPassword:
|
|
95
|
-
pw = password
|
|
100
|
+
pw = resolve_password(password)
|
|
96
101
|
if not pw:
|
|
97
102
|
if not sys.stdin.isatty():
|
|
98
103
|
sys.exit("archive is encrypted - pass --password or set HBK_PASSWORD")
|
|
@@ -226,7 +231,7 @@ def main() -> int:
|
|
|
226
231
|
|
|
227
232
|
if cmd == "doctor":
|
|
228
233
|
from . import doctor
|
|
229
|
-
r = doctor.diagnose(archive, password=password)
|
|
234
|
+
r = doctor.diagnose(archive, password=resolve_password(password))
|
|
230
235
|
print(doctor.render(r))
|
|
231
236
|
return 0 if (r.ok and not r.blockers) else 1
|
|
232
237
|
|
|
@@ -140,6 +140,12 @@ class Runner:
|
|
|
140
140
|
json.dump({"root": self.root, "dest": self.dest, "verify": self.verify,
|
|
141
141
|
"check": self.check, "strict": self.strict, "items": slice_}, fh)
|
|
142
142
|
env = dict(os.environ)
|
|
143
|
+
# sys.executable is the *unwrapped* interpreter. Where hbkit reaches
|
|
144
|
+
# sys.path through a wrapper script rather than site-packages - a Nix
|
|
145
|
+
# build, a PYTHONPATH-based distro package - that interpreter cannot
|
|
146
|
+
# import hbkit, and every worker dies before extracting a byte. Hand
|
|
147
|
+
# the parent's own path down so the child resolves what the parent did.
|
|
148
|
+
env["PYTHONPATH"] = os.pathsep.join(p for p in sys.path if p)
|
|
143
149
|
if self.password is not None: # env, not the job file - no secret on disk
|
|
144
150
|
env["HBK_PASSWORD"] = self.password
|
|
145
151
|
p = subprocess.Popen(
|
|
@@ -415,3 +415,66 @@ def test_cache_dir_prefers_new_name_but_honours_the_old_one(tmp_path, monkeypatc
|
|
|
415
415
|
assert hbki._default_cache().endswith("/.cache/hbk-recovery") # legacy honoured
|
|
416
416
|
(home / ".cache" / "hbkit").mkdir(parents=True)
|
|
417
417
|
assert hbki._default_cache().endswith("/.cache/hbkit") # new wins once present
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
def test_runner_hands_its_import_path_to_workers(tmp_path, monkeypatch):
|
|
421
|
+
"""Workers are spawned as `sys.executable -m hbkit.runner`, and sys.executable is the
|
|
422
|
+
*unwrapped* interpreter. Where hbkit reaches sys.path through a wrapper script instead
|
|
423
|
+
of site-packages - a Nix build, a PYTHONPATH-based distro package - that interpreter
|
|
424
|
+
cannot import hbkit and every worker dies before extracting a byte. Caught by packaging
|
|
425
|
+
for Nix, invisible to a pip install."""
|
|
426
|
+
import io
|
|
427
|
+
|
|
428
|
+
from hbkit import runner as hbk_runner
|
|
429
|
+
|
|
430
|
+
seen = {}
|
|
431
|
+
|
|
432
|
+
class FakePopen:
|
|
433
|
+
def __init__(self, cmd, env=None, **kw):
|
|
434
|
+
seen["cmd"], seen["env"] = cmd, env
|
|
435
|
+
self.stdout, self.stderr = io.StringIO(""), io.StringIO("")
|
|
436
|
+
|
|
437
|
+
def poll(self):
|
|
438
|
+
return 0
|
|
439
|
+
|
|
440
|
+
def wait(self):
|
|
441
|
+
return 0
|
|
442
|
+
|
|
443
|
+
monkeypatch.setattr(hbk_runner.subprocess, "Popen", FakePopen)
|
|
444
|
+
r = hbk_runner.Runner("/archive", str(tmp_path), [("/a", 0, 1, 0)], n_workers=1)
|
|
445
|
+
r.start()
|
|
446
|
+
r.cleanup()
|
|
447
|
+
|
|
448
|
+
assert seen["cmd"][1:3] == ["-m", "hbkit.runner"]
|
|
449
|
+
paths = seen["env"]["PYTHONPATH"].split(os.pathsep)
|
|
450
|
+
assert any(os.path.isdir(os.path.join(p, "hbkit")) for p in paths), \
|
|
451
|
+
f"no path in PYTHONPATH provides hbkit: {paths}"
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
@pytest.mark.parametrize(("flag", "env", "want"), [
|
|
455
|
+
(None, "env", "env"), # the bug: doctor ignored HBK_PASSWORD entirely
|
|
456
|
+
("flag", "env", "flag"), # an explicit -p still wins
|
|
457
|
+
(None, None, None), # nothing set: leave it to the prompt
|
|
458
|
+
])
|
|
459
|
+
def test_doctor_honours_the_password_environment_variable(monkeypatch, flag, env, want):
|
|
460
|
+
"""`-p` and HBK_PASSWORD are documented as equivalent, but doctor forwarded only `-p`,
|
|
461
|
+
so `HBK_PASSWORD=... hbk <archive> doctor` called a readable encrypted archive
|
|
462
|
+
unrecoverable."""
|
|
463
|
+
from hbkit import cli, doctor
|
|
464
|
+
|
|
465
|
+
seen = {}
|
|
466
|
+
|
|
467
|
+
class Result:
|
|
468
|
+
ok, blockers = True, []
|
|
469
|
+
|
|
470
|
+
monkeypatch.setattr(doctor, "diagnose",
|
|
471
|
+
lambda a, password=None: (seen.update(pw=password), Result())[1])
|
|
472
|
+
monkeypatch.setattr(doctor, "render", lambda r: "")
|
|
473
|
+
monkeypatch.delenv("HBK_PASSWORD", raising=False)
|
|
474
|
+
if env:
|
|
475
|
+
monkeypatch.setenv("HBK_PASSWORD", env)
|
|
476
|
+
|
|
477
|
+
argv = ["hbk", "/some/archive", "doctor"] + (["-p", flag] if flag else [])
|
|
478
|
+
monkeypatch.setattr(sys, "argv", argv)
|
|
479
|
+
assert cli.main() == 0
|
|
480
|
+
assert seen["pw"] == want
|
|
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
|