diff --git a/docs/nixos-integration-test.md b/docs/nixos-integration-test.md new file mode 100644 index 0000000..2b06be5 --- /dev/null +++ b/docs/nixos-integration-test.md @@ -0,0 +1,157 @@ +# Nix integration test: `npack resolve` -> `pkgs.fetchurl` + +This records an end-to-end test of the workflow requested in +[issue #1](https://github.com/imattau/npack/issues/1): a declarative package +manager (Nix) resolving and fetching a real npack release using only +`npack resolve`'s JSON output, with Nix's own fetcher doing the SHA-256 +verification instead of npack. + +The test ran against npack's own real, signed release on Nostr/Blossom (the +same release process every `npack` version ships through), not a mock. It +used a disposable `podman` container with Nix installed; nothing from this +test persists on the host that ran it. + +## 1. Resolve the release + +```bash +npack resolve 483440dfc4efeb8776ca27413c929653ea60484ca2d0e6b97678a0622c6bf413/npack \ + --relay wss://relay.damus.io --relay wss://nos.lol +``` + +```json +{ + "publisher": "483440dfc4efeb8776ca27413c929653ea60484ca2d0e6b97678a0622c6bf413", + "name": "npack", + "version": "0.2.12", + "sha256": "0d75fd591bc45b8e21ebff4f0de05a4ae32d297dd3e6eb4eac6eabb1749d70e5", + "os": "linux", + "arch": "x86_64", + "format": "npk", + "artifact_urls": [ + "https://blossom.primal.net/0d75fd591bc45b8e21ebff4f0de05a4ae32d297dd3e6eb4eac6eabb1749d70e5" + ], + "dependencies": [], + "conflicts": [], + "runtime_requires": [ + "libgcc_s.so.1", + "libm.so.6", + "libc.so.6", + "ld-linux-x86-64.so.2" + ], + "provides": [], + "release_event_id": "76709197ff0bbe4adca14238c18bcf6c2cc16034eeed29292061038f9d29683f", + "artifact_event_id": "064938892cea36923d4d527c37c477cb6f351e0bff7c1ec03f8e3773abd03091", + "verification": { + "release_signature_valid": true, + "artifact_event_signature_valid": true, + "release_event_is_v1": true, + "publisher_trusted": true, + "revoked": false + } +} +``` + +npack did the relay discovery, trust/revocation checks, and Nostr signature +verification. Everything below uses only the `sha256` and `artifact_urls` +fields from this output; Nix never talks to a relay. + +## 2. Build a derivation from that output + +```nix +{ pkgs ? import (builtins.fetchTarball { + url = "https://github.com/NixOS/nixpkgs/archive/refs/tags/24.05.tar.gz"; + }) {} +}: + +# Built entirely from the `npack resolve` output above. npack did the +# discovery/trust/signature verification; Nix's own fetchurl does the +# SHA-256 verification and owns the store path -- the split of +# responsibilities requested in npack issue #1. +pkgs.stdenv.mkDerivation { + pname = "npack"; + version = "0.2.12"; + + src = pkgs.fetchurl { + url = "https://blossom.primal.net/0d75fd591bc45b8e21ebff4f0de05a4ae32d297dd3e6eb4eac6eabb1749d70e5"; + sha256 = "0d75fd591bc45b8e21ebff4f0de05a4ae32d297dd3e6eb4eac6eabb1749d70e5"; + }; + + nativeBuildInputs = [ pkgs.zstd pkgs.autoPatchelfHook ]; + buildInputs = [ pkgs.stdenv.cc.cc.lib pkgs.glibc ]; + + # npack's canonical artifact format: a tar archive compressed with zstd. + unpackPhase = '' + tar --use-compress-program=unzstd -xf $src + ''; + + dontBuild = true; + + installPhase = '' + mkdir -p $out/bin $out/share/man/man1 + cp bin/npack $out/bin/npack + chmod +x $out/bin/npack + if [ -f share/man/man1/npack.1 ]; then + cp share/man/man1/npack.1 $out/share/man/man1/npack.1 + fi + ''; + + # runtime_requires from the resolved release (libgcc_s.so.1, libm.so.6, + # libc.so.6, ld-linux-x86-64.so.2) are satisfied by autoPatchelfHook + # rewriting the interpreter/rpath to the Nix store instead of the host's + # /lib64 -- the CI-built binary otherwise targets a generic glibc host, + # not a NixOS one. +} +``` + +```bash +nix-build npack-from-resolve.nix -o result +``` + +Nix fetched the artifact from the resolved URL, verified its own copy of the +SHA-256 (independent of npack), unpacked the zstd-compressed tar archive, +and `autoPatchelfHook` patched the ELF interpreter and RPATH: + +``` +auto-patchelf: 0 dependencies could not be satisfied +/nix/store/1aw2l55yshxyky3zvj2l33i64p2gl9c2-npack-0.2.12 +``` + +## 3. Run the Nix-built binary + +```bash +$ result/bin/npack --version +npack 0.2.12 +``` + +The binary that Nix fetched, verified, and patched runs correctly and +reports the version `npack resolve` said it would. + +## 4. Negative control: a tampered hash is rejected + +Repeating the build with the `sha256` changed to an incorrect value: + +``` +error: hash mismatch in fixed-output derivation '/nix/store/.../.drv': + specified: sha256-////WRvEW44h6/9PDeBaSuMtKX3T5utOrG6rsXSdcOU= + got: sha256-DXX9WRvEW44h6/9PDeBaSuMtKX3T5utOrG6rsXSdcOU= +``` + +Nix refuses to build. The SHA-256 `npack resolve` reports is load-bearing, +not decorative -- if a mirror were ever compromised or a hash mistyped, the +Nix build fails closed rather than silently accepting different bytes. + +## Conclusion + +`npack resolve`'s JSON output is directly consumable by a real Nix +derivation with no changes to npack: `sha256` and `artifact_urls` are +sufficient for `pkgs.fetchurl`, and `runtime_requires` correctly predicts +what `autoPatchelfHook` needs to patch. This closes the loop on the +NixOS-friendly consumption model described in +[the roadmap](roadmap.md#declarative-and-immutable-os-integration) and +requested in issue #1, using npack's real production release rather than a +synthetic fixture. + +Remaining follow-up, per the roadmap, is validation against a full NixOS +system closure (a flake-based `nixosModules`/package entry rather than a +one-off derivation) and doing the same walk with `npack resolve --recursive` +against a package that actually declares dependencies. diff --git a/docs/roadmap.md b/docs/roadmap.md index ba2cfb1..a49a2da 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -212,10 +212,16 @@ Shipped (originally requested for NixOS integration, see array of resolved entries, so a derivation generator does not need a separate invocation per dependency. +Validated: [a real Nix derivation built from `npack resolve`'s output against +npack's own live-published release](nixos-integration-test.md), including a +negative control confirming Nix rejects a tampered SHA-256. + Remaining work: -- Real-world validation of the output shape against an actual NixOS - flake/derivation, ideally with help from the issue's reporter. +- Validation against a full NixOS system closure (a flake-based module or + package entry, not just a one-off derivation), and a package that actually + declares dependencies to exercise `npack resolve --recursive`, ideally with + help from the issue's reporter. ## Milestone order diff --git a/docs/using-npack.md b/docs/using-npack.md index 53cce5c..9ddda43 100644 --- a/docs/using-npack.md +++ b/docs/using-npack.md @@ -359,7 +359,9 @@ This is intended for declarative package managers (such as Nix) that want to own fetching, unpacking, and rollback of the artifact themselves while leaving Nostr/Blossom discovery and trust verification to npack. `--relay`, `--requirement`, `--trusted-publisher`, `--pubkey`, `--lockfile`, and -`--locked` behave the same as their `install-ref` counterparts. +`--locked` behave the same as their `install-ref` counterparts. See +[the Nix integration test](nixos-integration-test.md) for a worked example +building a real Nix derivation from this output. Pass `--recursive` to resolve the full dependency closure in one call instead of a single package. The command then walks each declared dependency the same