From 8662f0980aa3abc8e56bf44e846d5d416437a54e Mon Sep 17 00:00:00 2001 From: DavHau Date: Thu, 11 Apr 2024 00:26:19 +0700 Subject: [PATCH 1/2] new runtime: nix --store --- default.nix | 152 ++++++++++++++++++++++++++++++++++------------------ flake.lock | 38 ++++++------- flake.nix | 14 ++--- 3 files changed, 126 insertions(+), 78 deletions(-) diff --git a/default.nix b/default.nix index eb419f3..bc132a1 100644 --- a/default.nix +++ b/default.nix @@ -16,6 +16,8 @@ with builtins; pkgs ? import {}, xz ? pkgs.pkgsStatic.xz, zstd ? pkgs.pkgsStatic.zstd, + nixRev ? "master", + nixStatic ? pkgs.pkgsStatic.nix, buildSystem ? builtins.currentSystem, ... @@ -68,6 +70,7 @@ let caBundleZstd = pkgs.runCommand "cacerts" {} "cat ${cacert}/etc/ssl/certs/ca-bundle.crt | ${inp.zstd}/bin/zstd -19 > $out"; bwrap = packStaticBin "${inp.bwrap}/bin/bwrap"; + nixStatic = packStaticBin "${inp.nixStatic}/bin/nix"; proot = packStaticBin "${inp.proot}/bin/proot"; zstd = packStaticBin "${inp.zstd}/bin/zstd"; @@ -82,6 +85,13 @@ let runtimeScript = '' #!/usr/bin/env bash + set -eo pipefail + + # dump environment on exit if debug is enabled + if [ -n "\$NP_DEBUG" ] && [ "\$NP_DEBUG" -ge 1 ]; then + trap "declare -p > /tmp/np_env" EXIT + fi + # there seem to be less issues with proot when disabling seccomp export PROOT_NO_SECCOMP=\''${PROOT_NO_SECCOMP:-1} @@ -114,8 +124,9 @@ let [ -z "\$NP_LOCATION" ] && NP_LOCATION="\$HOME" NP_LOCATION="\$(readlink -f "\$NP_LOCATION")" dir="\$NP_LOCATION/.nix-portable" + store="\$dir/nix/store" # create /nix/var/nix to prevent nix from falling back to chroot store. - mkdir -p \$dir/{bin,var/nix/var} + mkdir -p \$dir/{bin,nix/var/nix,nix/store} # santize the tmpbin directory rm -rf "\$dir/tmpbin" # create a directory to hold executable symlinks for overriding @@ -132,20 +143,24 @@ let # Nix portable ships its own nix.conf export NIX_CONF_DIR=\$dir/conf/ + NP_CONF_SANDBOX=\''${NP_CONF_SANDBOX:-false} + NP_CONF_STORE=\''${NP_CONF_STORE:-auto} - create_nix_conf(){ - sandbox=\$1 - mkdir -p \$dir/conf/ - rm -f \$dir/conf/nix.conf + recreate_nix_conf(){ + mkdir -p "\$NIX_CONF_DIR" + rm -f "\$NIX_CONF_DIR/nix.conf" - echo "build-users-group = " > \$dir/conf/nix.conf + # static config + echo "build-users-group = " >> \$dir/conf/nix.conf echo "experimental-features = nix-command flakes" >> \$dir/conf/nix.conf echo "ignored-acls = security.selinux system.nfs4_acl" >> \$dir/conf/nix.conf echo "use-sqlite-wal = false" >> \$dir/conf/nix.conf echo "sandbox-paths = /bin/sh=\$dir/busybox/bin/busybox" >> \$dir/conf/nix.conf - echo "sandbox = \$sandbox" >> \$dir/conf/nix.conf + # configurable config + echo "sandbox = \$NP_CONF_SANDBOX" >> \$dir/conf/nix.conf + echo "store = \$NP_CONF_STORE" >> \$dir/conf/nix.conf } @@ -182,12 +197,12 @@ let ${installBin zstd "zstd"} ${installBin proot "proot"} ${installBin bwrap "bwrap"} + ${installBin nixStatic "nix"} # install ssl cert bundle unzip -poj "\$self" ${ lib.removePrefix "/" "${caBundleZstd}"} | \$dir/bin/zstd -d > \$dir/ca-bundle.crt - create_nix_conf false - + recreate_nix_conf fi @@ -310,12 +325,25 @@ let [ -z "\$NP_BWRAP" ] && NP_BWRAP=\$(PATH="\$PATH_OLD:\$PATH" which bwrap 2>/dev/null) || true [ -z "\$NP_BWRAP" ] && NP_BWRAP=\$dir/bin/bwrap debug "bwrap executable: \$NP_BWRAP" + # [ -z "\$NP_NIX ] && NP_NIX=\$(PATH="\$PATH_OLD:\$PATH" which nix 2>/dev/null) || true + [ -z "\$NP_NIX" ] && NP_NIX=\$dir/bin/nix + debug "nix executable: \$NP_NIX" [ -z "\$NP_PROOT" ] && NP_PROOT=\$(PATH="\$PATH_OLD:\$PATH" which proot 2>/dev/null) || true [ -z "\$NP_PROOT" ] && NP_PROOT=\$dir/bin/proot debug "proot executable: \$NP_PROOT" + debug "testing all available runtimes..." if [ -z "\$NP_RUNTIME" ]; then + # check if nix --store works + mkdir -p \$dir/tmp/ + touch \$dir/tmp/testfile + debug "testing nix --store" + if "\$NP_NIX" store add-file --store $dir/tmp/__store \$dir/tmp/testfile >/dev/null 2>&3; then + chmod -R +w $dir/tmp/__store + rm -r $dir/tmp/__store + debug "nix --store works on this system -> will use nix as runtime" + NP_RUNTIME=nix # check if bwrap works properly - if \$NP_BWRAP --bind \$dir/emptyroot / --bind \$dir/ /nix --bind \$dir/busybox/bin/busybox "\$dir/true" "\$dir/true" 2>&3 ; then + elif \$NP_BWRAP --bind \$dir/emptyroot / --bind \$dir/ /nix --bind \$dir/busybox/bin/busybox "\$dir/true" "\$dir/true" 2>&3 ; then debug "bwrap seems to work on this system -> will use bwrap" NP_RUNTIME=bwrap else @@ -325,13 +353,18 @@ let else debug "runtime selected via NP_RUNTIME: \$NP_RUNTIME" fi - if [ "\$NP_RUNTIME" == "bwrap" ]; then + debug "NP_RUNTIME: \$NP_RUNTIME" + if [ "\$NP_RUNTIME" == "nix" ]; then + run="\$NP_NIX shell nix/${nixRev}#nix -c" + NP_CONF_STORE="\$dir" + recreate_nix_conf + elif [ "\$NP_RUNTIME" == "bwrap" ]; then collectBinds makeBindArgs --bind " " \$toBind \$sslBind run="\$NP_BWRAP \$BWRAP_ARGS \\ --bind \$dir/emptyroot /\\ --dev-bind /dev /dev\\ - --bind \$dir/ /nix\\ + --bind \$dir/nix /nix\\ \$binds" # --bind \$dir/busybox/bin/busybox /bin/sh\\ else @@ -341,7 +374,7 @@ let run="\$NP_PROOT \$PROOT_ARGS\\ -r \$dir/emptyroot\\ -b /dev:/dev\\ - -b \$dir:/nix\\ + -b \$dir/nix:/nix\\ \$binds" # -b \$dir/busybox/bin/busybox:/bin/sh\\ fi @@ -361,36 +394,37 @@ let # xz must be in PATH index="$(cat ${storeTar}/index)" - export missing=\$( - for path in \$index; do - if [ ! -e \$dir/store/\$(basename \$path) ]; then - echo "nix/store/\$(basename \$path)" - fi - done - ) - - if [ -n "\$missing" ]; then - debug "extracting missing store paths" - ( - mkdir -p \$dir/tmp \$dir/store/ - rm -rf \$dir/tmp/* - cd \$dir/tmp - unzip -qqp "\$self" ${ lib.removePrefix "/" "${storeTar}/tar"} \ - | \$dir/bin/zstd -d \ - | tar -x \$missing --strip-components 2 - mv \$dir/tmp/* \$dir/store/ + # if [ ! "\$NP_RUNTIME" == "nix" ]; then + export missing=\$( + for path in \$index; do + if [ ! -e \$store/\$(basename \$path) ]; then + echo "nix/store/\$(basename \$path)" + fi + done ) - rm -rf \$dir/tmp - fi - if [ -n "\$missing" ]; then - debug "registering new store paths to DB" - reg="$(cat ${storeTar}/closureInfo/registration)" - cmd="\$run \$dir/store${lib.removePrefix "/nix/store" nix}/bin/nix-store --load-db" - debug "running command: \$cmd" - echo "\$reg" | \$cmd - fi + if [ -n "\$missing" ]; then + debug "extracting missing store paths" + ( + mkdir -p \$dir/tmp \$store/ + rm -rf \$dir/tmp/* + cd \$dir/tmp + unzip -qqp "\$self" ${ lib.removePrefix "/" "${storeTar}/tar"} \ + | \$dir/bin/zstd -d \ + | tar -x \$missing --strip-components 2 + mv \$dir/tmp/* \$store/ + ) + rm -rf \$dir/tmp + fi + if [ -n "\$missing" ]; then + debug "registering new store paths to DB" + reg="$(cat ${storeTar}/closureInfo/registration)" + cmd="\$run \$store${lib.removePrefix "/nix/store" nix}/bin/nix-store --load-db" + debug "running command: \$cmd" + # echo "\$reg" | \$cmd + fi + # fi ### select executable @@ -405,37 +439,47 @@ let bin="\$(which \$2)" shift; shift else - bin="\$dir/store${lib.removePrefix "/nix/store" nix}/bin/\$1" + bin="\$store${lib.removePrefix "/nix/store" nix}/bin/\$1" shift fi else - bin="\$dir/store${lib.removePrefix "/nix/store" nix}/bin/\$(basename \$0)" + bin="\$store${lib.removePrefix "/nix/store" nix}/bin/\$(basename \$0)" fi ### check which runtime has been used previously - lastRuntime=\$(cat "\$dir/conf/last_runtime" 2>&3) || true + if [ -f "\$dir/conf/last_runtime" ]; then + lastRuntime=\$(cat "\$dir/conf/last_runtime") + else + lastRuntime= + fi - ### check if nix is funtional with or without sandbox + ### check if nix is functional with or without sandbox # sandbox-fallback is not reliable: https://github.com/NixOS/nix/issues/4719 if [ "\$newNPVersion" == "true" ] || [ "\$lastRuntime" != "\$NP_RUNTIME" ]; then - nixBin="\$dir/store${lib.removePrefix "/nix/store" nix}/bin/nix-build" + nixBin="\$store${lib.removePrefix "/nix/store" nix}/bin/nix" + # if [ "\$NP_RUNTIME" == "nix" ]; then + # nixBin="nix" + # else + # fi debug "Testing if nix can build stuff without sandbox" - if ! \$run "\$nixBin" -E "(import {}).runCommand \\"test\\" {} \\"echo \$(date) > \\\$out\\"" --option sandbox false >&3 2>&3; then + if ! \$run "\$nixBin" build --no-link --impure --expr "(import {}).runCommand \\"test\\" {} \\"echo \$(date) > \\\$out\\"" --option sandbox false >&3 2>&3; then echo "Fatal error: nix is unable to build packages" exit 1 fi - debug "Testing if nix sandox is functional" - if ! \$run "\$nixBin" -E "(import {}).runCommand \\"test\\" {} \\"echo \$(date) > \\\$out\\"" --option sandbox true >&3 2>&3; then + debug "Testing if nix sandbox is functional" + if ! \$run "\$nixBin" build --no-link --impure --expr "(import {}).runCommand \\"test\\" {} \\"echo \$(date) > \\\$out\\"" --option sandbox true >&3 2>&3; then debug "Sandbox doesn't work -> disabling sandbox" - create_nix_conf false + NP_CONF_SANDBOX=false + recreate_nix_conf else debug "Sandboxed builds work -> enabling sandbox" - create_nix_conf true + NP_CONF_SANDBOX=true + recreate_nix_conf fi fi @@ -460,9 +504,9 @@ let ### install git via nix, if git installation is not in /nix path - if \$doInstallGit && [ ! -e \$dir/store${lib.removePrefix "/nix/store" git.out} ] ; then + if \$doInstallGit && [ ! -e \$store${lib.removePrefix "/nix/store" git.out} ] ; then echo "Installing git. Disable this by specifying the git executable path with 'NP_GIT'" - \$run \$dir/store${lib.removePrefix "/nix/store" nix}/bin/nix build --impure --no-link --expr " + \$run \$store${lib.removePrefix "/nix/store" nix}/bin/nix build --impure --no-link --expr " (import ${nixpkgsSrc} {}).${gitAttribute}.out " else @@ -487,6 +531,7 @@ let debug "running command: \$cmd" exec \$NP_RUN \$bin "\$@" fi + exit ''; runtimeScriptEscaped = replaceStrings ["\""] ["\\\""] runtimeScript; @@ -511,8 +556,9 @@ let unzip -vl $out/bin/nix-portable.zip zip="${zip}/bin/zip -0" - $zip $out/bin/nix-portable.zip ${proot}/bin/proot $zip $out/bin/nix-portable.zip ${bwrap}/bin/bwrap + $zip $out/bin/nix-portable.zip ${nixStatic}/bin/nix + $zip $out/bin/nix-portable.zip ${proot}/bin/proot $zip $out/bin/nix-portable.zip ${zstd}/bin/zstd $zip $out/bin/nix-portable.zip ${storeTar}/tar $zip $out/bin/nix-portable.zip ${caBundleZstd} diff --git a/flake.lock b/flake.lock index d420e4f..b313bd0 100644 --- a/flake.lock +++ b/flake.lock @@ -31,55 +31,55 @@ "type": "github" } }, - "lowdown-src": { + "libgit2": { "flake": false, "locked": { - "lastModified": 1633514407, - "narHash": "sha256-Dw32tiMjdK9t3ETl5fzGrutQTzh2rufgZV4A/BbxuD4=", - "owner": "kristapsdz", - "repo": "lowdown", - "rev": "d2c2b44ff6c27b936ec27358a2653caaef8f73b8", + "lastModified": 1697646580, + "narHash": "sha256-oX4Z3S9WtJlwvj0uH9HlYcWv+x1hqp8mhXl7HsLu2f0=", + "owner": "libgit2", + "repo": "libgit2", + "rev": "45fd9ed7ae1a9b74b957ef4f337bc3c8b3df01b5", "type": "github" }, "original": { - "owner": "kristapsdz", - "repo": "lowdown", + "owner": "libgit2", + "repo": "libgit2", "type": "github" } }, "nix": { "inputs": { "flake-compat": "flake-compat", - "lowdown-src": "lowdown-src", + "libgit2": "libgit2", "nixpkgs": "nixpkgs", "nixpkgs-regression": "nixpkgs-regression" }, "locked": { - "lastModified": 1695206941, - "narHash": "sha256-ggISAuGpTkKp6JXt1BbcLtLDYOPECWqrVnPVgQEFHYc=", + "lastModified": 1712163141, + "narHash": "sha256-BSl8Jijq1A4n1ToQy0t0jDJCXhJK+w1prL8QMHS5t54=", "owner": "NixOS", "repo": "nix", - "rev": "44fb1192185cdd03343da7faa08a1c605f773419", + "rev": "7bc4af7301df34ea4e157338ac3792c1a9ae35b7", "type": "github" }, "original": { "id": "nix", - "ref": "2.18.0", + "ref": "2.20.6", "type": "indirect" } }, "nixpkgs": { "locked": { - "lastModified": 1695124524, - "narHash": "sha256-trXDytVCqf3KryQQQrHOZKUabu1/lB8/ndOAuZKQrOE=", - "owner": "edolstra", + "lastModified": 1705033721, + "narHash": "sha256-K5eJHmL1/kev6WuqyqqbS1cdNnSidIZ3jeqJ7GbrYnQ=", + "owner": "NixOS", "repo": "nixpkgs", - "rev": "a3d30b525535e3158221abc1a957ce798ab159fe", + "rev": "a1982c92d8980a0114372973cbdfe0a307f1bdea", "type": "github" }, "original": { - "owner": "edolstra", - "ref": "fix-aws-sdk-cpp", + "owner": "NixOS", + "ref": "nixos-23.05-small", "repo": "nixpkgs", "type": "github" } diff --git a/flake.nix b/flake.nix index a119b6c..91ae900 100644 --- a/flake.nix +++ b/flake.nix @@ -8,7 +8,7 @@ # Error: checking whether build environment is sane... ls: cannot access './configure': No such file or directory defaultChannel.url = "nixpkgs/nixos-unstable"; - nix.url = "nix/2.18.0"; + nix.url = "nix/2.20.6"; }; outputs = { self, ... }@inp: @@ -54,14 +54,14 @@ url = "https://cloud.centos.org/altarch/7/images/CentOS-7-x86_64-GenericCloud-2009.qcow2c"; sha256 = "09wqzlhb858qm548ak4jj4adchxn7rgf5fq778hrc52rjqym393v"; # user namespaces are disabled on centos 7 - excludeRuntimes = [ "bwrap" ]; + excludeRuntimes = [ "nix" "bwrap" ]; }; debian = { system = "x86_64-linux"; url = "https://cdimage.debian.org/cdimage/openstack/archive/10.9.0/debian-10.9.0-openstack-amd64.qcow2"; sha256 = "0mf9k3pgzighibly1sy3cjq7c761r3akp8mlgd878lwf006vqrky"; # permissions for user namespaces not enabled by default - excludeRuntimes = [ "bwrap" ]; + excludeRuntimes = [ "nix" "bwrap" ]; }; fedora = { system = "x86_64-linux"; @@ -114,7 +114,7 @@ url = "https://cdimage.debian.org/cdimage/openstack/archive/10.9.0/debian-10.9.0-openstack-arm64.qcow2"; sha256 = "0mz868j1k8jwhgg9a21dv7dr4rsy1bhklbqqw3qig06acy0vg8yi"; # permissions for user namespaces not enabled by default - excludeRuntimes = [ "bwrap" ]; + excludeRuntimes = [ "nix" "bwrap" ]; }; }; @@ -148,7 +148,9 @@ lib = inp.nixpkgs.lib; compression = "zstd -3 -T1"; - nix = inp.nix.packages."${system}".nix; + nix = inp.nix.packages.${system}.nix; + nixRev = inp.nix.rev; + nixStatic = inp.nix.packages.${system}.nix-static; busybox = pkgs.pkgsStatic.busybox; bwrap = pkgs.pkgsStatic.bubblewrap; @@ -190,7 +192,7 @@ makeQemuPipelines = mode: mapAttrs' (os: img: let debug = mode == "debug"; suffix = if mode == "normal" then "" else "-${mode}"; - runtimes = filter (runtime: ! elem runtime (testImages."${os}".excludeRuntimes or []) ) [ "bwrap" "proot" ]; + runtimes = filter (runtime: ! elem runtime (testImages."${os}".excludeRuntimes or []) ) [ "nix" "bwrap" "proot" ]; img = if testImages."${os}" ? img then testImages."${os}".img else fetchurl { inherit (testImages."${os}") url sha256 ;}; From fa340dce418842cbc816e8088ef646b8c89fa4a1 Mon Sep 17 00:00:00 2001 From: DavHau Date: Thu, 11 Apr 2024 17:12:59 +0700 Subject: [PATCH 2/2] update readme for new runtime nix --- README.md | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 0539037..9d44421 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,8 @@ For binary downloads check the [releases](https://github.com/DavHau/nix-portable ### 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 simulating the /nix/store) - - make it easy to distribute nix (via other package managers) + - 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 on the following systems/environments: * Distros (x86_64): @@ -27,14 +27,14 @@ For binary downloads check the [releases](https://github.com/DavHau/nix-portable ### Under the hood: - The nix-portable executable is a self extracting archive, caching its contents in $HOME/.nix-portable - - Either bubblewrap or proot is used to simulate the /nix/store directory which actually resides in $HOME/.nix-portable/store + - 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 instead of bubblewrap. -Proot's virtualization can have a significant performance overhead depending on the workload. +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. @@ -71,25 +71,27 @@ To enter the wrapped environment just use nix-shell: ``` ### Container Runtimes -To simulate the /nix/store and a few other directories, nix-portable supports the following 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) -bwrap is preferred over proot and existing installations are preferred over the nix-portable included binaries. -nix-portable will try to figure out which runtime is best for your system. -In case the automatically selected runtime doesn't work, use the follwing environment variables to specify the runtime, but please also open an issue, so we can improve the automatic selection. +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 behaviour of nix-portable +The following environment variables are optional and can be used to override the default behavior of nix-portable ``` 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`) -NP_RUNTIME which runtime to use (must be 'bwrap' or 'proot') -NP_BWRAP specify the path to the bwrap executable to use -NP_PROOT specify the path to the proot executable to use +NP_RUNTIME which runtime to use (must be one of: nix, bwrap, proot) +NP_NIX specify the path to the static nix executable to use in case nix is selected as runtime +NP_BWRAP specify the path to the bwrap executable to use in case bwrap is selected as runtime +NP_PROOT specify the path to the proot executable to use in case proot is selected as runtime NP_RUN override the complete command to run nix (to use an unsupported runtime, or for debugging) nix will then be executed like: $NP_RUN {nix-binary} {args...}