diff --git a/flake.nix b/flake.nix index 2207708..1ad11b6 100644 --- a/flake.nix +++ b/flake.nix @@ -11,112 +11,104 @@ self, nixpkgs, flake-utils, + ... }: flake-utils.lib.eachDefaultSystem ( system: let pkgs = import nixpkgs { inherit system; }; + + # patched proot (relative interp + rpath) + prootPatched = (pkgs.proot.override { enablePython = false; }).overrideAttrs (prev: { + postFixup = (prev.postFixup or "") + '' + exe=$out/bin/proot + if interp="$(${pkgs.patchelf}/bin/patchelf --print-interpreter "$exe" 2>/dev/null)"; then + ${pkgs.patchelf}/bin/patchelf --set-interpreter ."$interp" "$exe" + fi + if rpath="$(${pkgs.patchelf}/bin/patchelf --print-rpath "$exe" 2>/dev/null)"; then + ${pkgs.patchelf}/bin/patchelf \ + --set-rpath "$(printf %s "$rpath" | sed 's|/nix/store/|./nix/store/|g')" "$exe" + fi + ''; + }); in { - bundlers = rec { - proot-bundler = - drv: - pkgs.stdenvNoCC.mkDerivation { - name = "myApp.run"; - nativeBuildInputs = [ - pkgs.nix - pkgs.coreutils - pkgs.findutils - pkgs.gnutar - pkgs.gzip - pkgs.patchelf + bundlers.proot-bundler = + drv: + let + ci = pkgs.closureInfo { + rootPaths = [ + drv + prootPatched ]; - # patch proot as you described - prootPatched = (pkgs.proot.override { enablePython = false; }).overrideAttrs (_: { - postFixup = '' - exe=$out/bin/proot - ${pkgs.patchelf}/bin/patchelf \ - --set-interpreter ."$(${pkgs.patchelf}/bin/patchelf --print-interpreter $exe)" \ - --set-rpath "$(${pkgs.patchelf}/bin/patchelf --print-rpath $exe | sed 's|/nix/store/|./nix/store/|g')" \ - $exe - ''; - }); - - buildCommand = '' - set -euo pipefail - # 1) Make payload dir that mirrors the bundle runtime layout - PAY="$PWD/payload" - mkdir -p "$PAY/nix/store" - - # include app + patched proot in the payload closure - app=${drv} - proot=${ - self.bundlers.${system}.selfExtracting.prootPatched or "" - } # not callable; we want the attr above - proot=${ - pkgs.proot.overrideAttrs (_: { - postFixup = "${pkgs.patchelf}/bin/patchelf --set-interpreter .\"$(${pkgs.patchelf}/bin/patchelf --print-interpreter $out/bin/proot)\" --set-rpath \"$( ${pkgs.patchelf}/bin/patchelf --print-rpath $out/bin/proot | sed 's|/nix/store/|./nix/store/|g')\" $out/bin/proot"; - }) - } - - # copy closure of app + proot into ./nix/store - paths=$( ${pkgs.nix}/bin/nix-store --query --requisites "$app" "$proot" ) - for p in $paths; do - cp -a --no-preserve=ownership "$p" "$PAY/nix/store/" - done - - # discover targets for the launcher - APP_BIN=$(find "$app/bin" -maxdepth 1 -type f -perm -111 | head -n1) - APP_REL="/nix/store/$(basename "$(dirname "$APP_BIN")")/$(basename "$APP_BIN")" - PROOT_REL="/nix/store/$(basename "$proot")/bin/proot" - - # 2) Tar.gz the payload - ( cd "$PAY" && tar -czf "$PWD/payload.tar.gz" . ) - - # 3) Assemble a self-extracting script at $out - cat > $out <<'SH' - #!/bin/sh - set -euf - # extract to temp dir - : "''${TMPDIR:=/tmp}" - EXTRACT_DIR="$(mktemp -d "''${TMPDIR%/}/nxbdl.XXXXXX")" - cleanup() { [ -n "''${KEEP_BUNDLE:-}" ] || rm -rf "$EXTRACT_DIR"; } - trap cleanup EXIT INT TERM - - # locate embedded archive (line after marker) - ARCHIVE_LINE=$(awk '/^__ARCHIVE_BELOW__/ {print NR+1; exit 0}' "$0") - tail -n +$ARCHIVE_LINE "$0" | tar -xzf - -C "$EXTRACT_DIR" - - cd "$EXTRACT_DIR" - # vars substituted by bundler at build time: - APP_REL='__APP_REL__' - PROOT_REL='__PROOT_REL__' - - # run via proot - BUNDLE_PWD="''${BUNDLE_PWD:-$PWD}" - exec ".$PROOT_REL" \ - -b ./nix:nix \ - -R / \ - -w "$BUNDLE_PWD" \ - ".$APP_REL" "$@" - - __ARCHIVE_BELOW__ - SH - - # substitute the program + proot paths into the stub - sed -i \ - -e "s|__APP_REL__|$APP_REL|g" \ - -e "s|__PROOT_REL__|$PROOT_REL|g" \ - $out - chmod +x $out - - # 4) Append the payload bytes after the marker - cat payload.tar.gz >> $out - ''; }; + # resolve these now so we can hardcode into the runner + appBinGuess = "${drv}/bin"; + prootBin = "${prootPatched}/bin/proot"; + PROOT_REL = "/nix/store/${builtins.baseNameOf (builtins.dirOf prootBin)}/bin/proot"; + in + pkgs.stdenvNoCC.mkDerivation { + name = "myApp.run"; + nativeBuildInputs = [ + pkgs.coreutils + pkgs.findutils + pkgs.gnutar + pkgs.gzip + ]; + buildCommand = '' + set -euo pipefail + PAY="$PWD/payload" + mkdir -p "$PAY/nix/store" - default = proot-bundler; - }; + # copy closure paths from closureInfo (no nix calls here) + while IFS= read -r p; do + cp -a --no-preserve=ownership "$p" "$PAY/nix/store/" + done < ${ci}/store-paths + + # pick an app binary + APP_BIN=$(find ${appBinGuess} -maxdepth 1 -type f -perm -111 | head -n1) + if [ -z "''${APP_BIN:-}" ]; then + echo "no executable found in ${appBinGuess}" >&2 + exit 1 + fi + APP_REL="/nix/store/$(basename "$(dirname "$APP_BIN")")/$(basename "$APP_BIN")" + + ( cd "$PAY" && tar -czf "$PWD/payload.tar.gz" . ) + + cat > $out <<'SH' + #!/bin/sh + set -euf + : "''${TMPDIR:=/tmp}" + EXTRACT_DIR="$(mktemp -d "''${TMPDIR%/}/nxbdl.XXXXXX")" + cleanup() { [ -n "''${KEEP_BUNDLE:-}" ] || rm -rf "$EXTRACT_DIR"; } + trap cleanup EXIT INT TERM + + ARCHIVE_LINE=$(awk '/^__ARCHIVE_BELOW__/ {print NR+1; exit 0}' "$0") + tail -n +"$ARCHIVE_LINE" "$0" | tar -xzf - -C "$EXTRACT_DIR" + + cd "$EXTRACT_DIR" + APP_REL='__APP_REL__' + PROOT_REL='__PROOT_REL__' + + BUNDLE_PWD="''${BUNDLE_PWD:-$PWD}" + exec ".${PROOT_REL}" \ + -b ./nix:nix \ + -R / \ + -w "$BUNDLE_PWD" \ + ".$APP_REL" "$@" + + __ARCHIVE_BELOW__ + SH + sed -i \ + -e "s|__APP_REL__|$APP_REL|g" \ + -e "s|__PROOT_REL__|${PROOT_REL}|g" \ + $out + chmod +x $out + cat payload.tar.gz >> $out + ''; + }; + + default = self.bundlers.${system}.proot-bundler; } ); }