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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hbkit
3
- Version: 0.4.2
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 lz4 # or: sudo apt install liblz4-1
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
- `liblz4` is a runtime requirement — chunks are raw LZ4 blocks. It is present on most
53
- systems already; set `HBK_LZ4` if yours lives somewhere unusual.
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 lz4 # or: sudo apt install liblz4-1
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
- `liblz4` is a runtime requirement — chunks are raw LZ4 blocks. It is present on most
24
- systems already; set `HBK_LZ4` if yours lives somewhere unusual.
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
+ ```
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "hbkit"
7
- version = "0.4.2"
7
+ version = "0.4.3"
8
8
  description = "Recover files from Synology Hyper Backup (.hbk) archives without Synology software"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -1,6 +1,6 @@
1
1
  """hbkit - recover files from Synology Hyper Backup (.hbk) archives without Synology software."""
2
2
 
3
- __version__ = "0.4.2"
3
+ __version__ = "0.4.3"
4
4
 
5
5
  from .archive import (Archive, NeedPassword, UnsupportedArchive, # noqa: F401
6
6
  find_archive_root, is_archive)
@@ -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 or os.environ.get("HBK_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