diff --git a/README.md b/README.md index 13aab3c..6f82815 100644 --- a/README.md +++ b/README.md @@ -2,92 +2,159 @@

-Nix as a static executable requiring no configuration, privileges, or (user) namespaces. - -For binary downloads check the [releases](https://github.com/DavHau/nix-portable/releases) page. - -### Goals: - - make it extremely simple to install nix - - make nix work in restricted environments (containers, HPC, ...) - - be able to use the official binary cache (by virtualizing the /nix/store) - - make it easy to distribute nix as a dependency of other projects - -### Tested continuously on the following systems/environments: - * Distros (x86_64): - - Arch Linux - - CentOS 7 - - Debian - - Fedora - - NixOS - - Ubuntu 22.04 - - Ubuntu 23.10 - - Ubuntu 24.04 - * Distros (aarch64): - - Debian - * Other Environments: - - Github Actions - - Docker (debian image) - -### Under the hood: - - The nix-portable executable is a self extracting archive, caching its contents in $HOME/.nix-portable - - Either nix, bubblewrap or proot is used to virtualize the /nix/store directory which actually resides in $HOME/.nix-portable/store - - A default nixpkgs channel is included and the NIX_PATH variable is set accordingly. - - Features `flakes` and `nix-command` are enabled out of the box. - - -### Drawbacks / Considerations: -If user namespaces are not available on a system, nix-portable will fall back to using proot as an alternative mechanism to virtualize /nix. -Proot can introduce significant performance overhead depending on the workload. -In that situation, it might be beneficial to use a remote builder or alternatively build the derivations on another host and sync them via a cache like cachix.org. +🪩 Use nix on any linux system, rootless and configuration free. +🔥 new: [Create software bundles](#bundle-programs) that work on any linux distribution. -### Missing Features: - - managing nix profiles via `nix-env` - - managing nix channels via `nix-channel` - - support MacOS +[💾 Downloads](https://github.com/DavHau/nix-portable/releases) +--- -### Executing nix-portable -After obtaining the binary, there are two options: -1. Specify the nix executable via cmdline argument: - ``` - ./nix-portable nix-shell ... - ``` -1. Select the nix executable via symlinking, similar to busybox: +### Get nix-portable +```shellSession +curl -L https://github.com/DavHau/nix-portable/releases/latest/download/nix-portable-$(uname -m) > ./nix-portable + +chmod +x ./nix-portable +``` + +### Use nix via nix-portable + +There are two ways to run nix: + +#### Method 1: Pass nix command line: + +```shellSession +./nix-portable nix-shell --help +``` + +#### Method 2: Symlink against nix-portable: + +To create a `nix-shell` executable, create a symlink `./nix-shell` against `./nix-portable`. + +```shellSession +ln -s ./nix-portable ./nix-shell +``` + +Then use the symlink as an executable: + +```shellSession +./nix-shell --help +``` + +This works for any other nix native executable. + +### Get and execute programs + +Hint: Use [search.nixos.org](https://search.nixos.org/packages) to find available programs. + +#### Run a program without installing it + +```shellSession +./nix-portable nix run nixpkgs#htop +``` + +#### Create a temporary environment with multiple programs + +1. Enter a temporary environment with `htop` and `vim`: + + ```shellSession + ./nix-portable nix shell nixpkgs#{htop,vim} ``` - # create a symlink from ./nix-shell to ./nix-portable - ln -s ./nix-portable ./nix-shell - # execute nix-shell - ./nix-shell + +2. execute htop + + ```shellSession + htop ``` -### Executing installed programs -All programs installed via nix-portable will only work inside the wrapped environment. -To enter the wrapped environment just use nix-shell: -``` - nix-portable nix-shell -p bash +### Bundle programs +nix-portable can bundle arbitrary software into a static executable that runs on [any*](#supported-platforms) linux distribution. + +Prerequisites: Your software is already packaged for nix. + +**Optional**: If you don't have nix yet, [get nix-portable](#get-nix-portable), then enter a temporary environment with nix and bash: + +```shellSession +./nix-portable nix shell nixpkgs#{bashInteractive,nix} -c bash ``` -... or use `nix run` to execute a program: +Examples: + +#### Bundle gnu hello: + + +Create a bundle containing [hello](https://search.nixos.org/packages?channel=unstable&from=0&size=50&sort=relevance&type=packages&query=hello) that will work on any machine: + +```shellSession +$ nix bundle --bundler github:DavHau/nix-portable -o bundle nixpkgs#hello +$ cp ./bundle/bin/hello ./hello && chmod +w hello +$ ./hello +Hello World! ``` - nix-portable nix run {flake-spec} + +#### Bundle python + libraries + +Bundle python with arbitrary libraries as a static executable + +```shellSession +# create the bundle +$ nix bundle --bundler github:DavHau/nix-portable -o bundle --impure --expr \ + '(import {}).python3.withPackages (ps: [ ps.numpy ps.scipy ps.pandas ])' +$ cp ./bundle/bin/python3 ./python3 && chmod +w ./python3 + +# try it out +$ ./python3 -c 'import numpy, scipy, pandas; print("Success !")' +Success ! ``` -### Container Runtimes -To simulate the /nix/store, nix-portable supports the following runtimes, preferred in this order: - - nix (shipped via nix-portable) - - bwrap (existing installation) - - bwrap (shipped via nix-portable) - - proot (existing installation) - - proot (shipped via nix-portable) +### Supported platforms + +Potentially any linux system with an **x86_64** or **aarch64** CPU is supported. + +nix-portable is tested continuously on the following platforms: + +- Distros (x86_64): + - Arch Linux + - CentOS 7 + - Debian + - Fedora + - NixOS + - Ubuntu 22.04 + - Ubuntu 23.10 + - Ubuntu 24.04 +- Distros (aarch64): + - Debian +- Other Environments: + - Github Actions + - Docker (debian image) + +### Under the hood + +- The nix-portable executable is a self extracting archive, caching its contents in $HOME/.nix-portable +- Either nix, bubblewrap or proot is used to virtualize the /nix/store directory which actually resides in $HOME/.nix-portable/store +- A default nixpkgs channel is included and the NIX_PATH variable is set accordingly. +- Features `flakes` and `nix-command` are enabled out of the box. + + +#### Virtualization + +To virtualize the /nix/store, nix-portable supports the following runtimes, preferred in this order: + +- nix (shipped via nix-portable) +- bwrap (existing installation) +- bwrap (shipped via nix-portable) +- proot (existing installation) +- proot (shipped via nix-portable) nix-portable will auto select the best runtime for your system. In case the auto selected runtime doesn't work, please open an issue. The default runtime can be overridden via [Environment Variables](#environment-variables). ### Environment Variables -The following environment variables are optional and can be used to override the default behavior of nix-portable -``` + +The following environment variables are optional and can be used to override the default behavior of nix-portable at run-time. + +```txt NP_DEBUG (1 = debug msgs; 2 = 'set -x' for nix-portable) NP_GIT specify path to the git executable NP_LOCATION where to put the `.nix-portable` dir. (defaults to `$HOME`) @@ -101,8 +168,25 @@ NP_RUN override the complete command to run nix ``` +### Drawbacks / Considerations + +Programs obtained outside nix-portable cannot link against or call programs obtained via nix-portable. This is because nix-portable uses a virtualized directory to store its programs which cannot be accessed by other software on the system. + +If user namespaces are not available on a system, nix-portable will fall back to using proot as an alternative mechanism to virtualize /nix. +Proot can introduce significant performance overhead depending on the workload. +In that situation, it might be beneficial to use a remote builder or alternatively build the derivations on another host and sync them via a cache like cachix.org. + + +### Missing Features + +- managing nix profiles via `nix-env` +- managing nix channels via `nix-channel` +- support MacOS + ### Building / Contributing + To speed up builds, add the nix-portable cache: -``` + +```shellSession nix-shell -p cachix --run "cachix use nix-portable" ``` diff --git a/default.nix b/default.nix index 3037dca..f9f0b8f 100644 --- a/default.nix +++ b/default.nix @@ -19,11 +19,20 @@ with builtins; nixStatic ? pkgs.pkgsStatic.nix, buildSystem ? builtins.currentSystem, + # hardcode executable to run. Useful when creating a bundle. + bundledPackage ? null, ... }@inp: with lib; let + pname = + if bundledPackage == null + then "nix-portable" + else lib.getName bundledPackage; + + bundledExe = lib.getExe bundledPackage; + nixpkgsSrc = pkgs.path; pkgsBuild = import pkgs.path { system = buildSystem; }; @@ -74,7 +83,7 @@ let zstd = packStaticBin "${inp.zstd}/bin/zstd"; # the default nix store contents to extract when first used - storeTar = maketar ([ cacert nix nixpkgsSrc ]); + storeTar = maketar ([ cacert nix nixpkgsSrc ] ++ lib.optional (bundledPackage != null) bundledPackage); # The runtime script which unpacks the necessary files to $HOME/.nix-portable @@ -433,9 +442,15 @@ let ### select executable - # the executable can either be selected by executing './nix-portable BIN_NAME', - # or by symlinking to nix-portable, in which case the name of the symlink selectes the binary - if [[ "\$(basename \$0)" == nix-portable* ]]; then + # the executable can either be selected by + # - executing './nix-portable BIN_NAME', + # - symlinking to nix-portable, in which case the name of the symlink selects the nix executable + # Alternatively the executable can be hardcoded by specifying the argument 'executable' of nix-portable's default.nix file. + executable="${if bundledPackage == null then "" else bundledExe}" + if [ "\$executable" != "" ]; then + bin="\$executable" + debug "executable is hardcoded to: \$bin" + elif [[ "\$(basename \$0)" == nix-portable* ]]; then\ if [ -z "\$1" ]; then echo "Error: please specify the nix binary to execute" echo "Alternatively symlink against \$0" @@ -541,7 +556,7 @@ let runtimeScriptEscaped = replaceStrings ["\""] ["\\\""] runtimeScript; - nixPortable = pkgs.runCommand "nix-portable" {nativeBuildInputs = [unixtools.xxd unzip];} '' + nixPortable = pkgs.runCommand pname {nativeBuildInputs = [unixtools.xxd unzip];} '' mkdir -p $out/bin echo "${runtimeScriptEscaped}" > $out/bin/nix-portable.zip xxd $out/bin/nix-portable.zip | tail @@ -575,9 +590,14 @@ let ${zip}/bin/zip -F $out/bin/nix-portable.zip --out $out/bin/nix-portable-fixed.zip rm $out/bin/nix-portable.zip - mv $out/bin/nix-portable-fixed.zip $out/bin/nix-portable - - chmod +x $out/bin/nix-portable + executable=${if bundledPackage == null then "" else bundledExe} + if [ "$executable" == "" ]; then + target="$out/bin/nix-portable" + else + target="$out/bin/$(basename "$executable")" + fi + mv $out/bin/nix-portable-fixed.zip "$target" + chmod +x "$target" ''; in nixPortable.overrideAttrs (prev: { diff --git a/flake.nix b/flake.nix index a16e842..ae510a4 100644 --- a/flake.nix +++ b/flake.nix @@ -161,6 +161,12 @@ recursiveUpdate ({ + bundlers = forAllSystems (system: pkgs: { + default = drv: self.packages.${system}.nix-portable.override { + bundledPackage = drv; + }; + }); + devShell = forAllSystems (system: pkgs: pkgs.mkShell { buildInputs = with pkgs; [