├── .envrc ├── .github ├── dependabot.yml └── workflows │ ├── nix-portable.yml │ └── update-flake-lock.yml ├── .gitignore ├── .mergify.yml ├── LICENSE ├── README.md ├── default.nix ├── flake.lock ├── flake.nix ├── proot ├── alpine.nix ├── github.nix ├── gitlab.nix ├── nixpkgs.nix └── termux.nix └── testing ├── id_ed25519 ├── id_ed25519.pub ├── nixos-iso.nix ├── qemu-efi.nix ├── test-commands.nix ├── ubuntu └── 01-netplan.yaml ├── vagrant-test.nix ├── vagrant-tests.nix ├── vagrant_insecure_key ├── vagrant_insecure_key.pub └── vm-tests.nix /.envrc: -------------------------------------------------------------------------------- 1 | use flake 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | -------------------------------------------------------------------------------- /.github/workflows/nix-portable.yml: -------------------------------------------------------------------------------- 1 | 2 | name: "build and test" 3 | on: 4 | pull_request: 5 | workflow_dispatch: 6 | push: 7 | branches: [ "ci", "main" ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | continue-on-error: true 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | system: [ x86_64-linux, aarch64-linux ] 17 | 18 | steps: 19 | 20 | - uses: actions/checkout@v4 21 | with: 22 | # Nix Flakes doesn't work on shallow clones 23 | fetch-depth: 0 24 | 25 | - uses: cachix/install-nix-action@V27 26 | with: 27 | extra_nix_config: | 28 | experimental-features = nix-command flakes 29 | extra-platforms = ${{ matrix.system }} 30 | 31 | - uses: cachix/cachix-action@v14 32 | with: 33 | name: nix-portable 34 | authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' 35 | 36 | - name: Set up QEMU 37 | uses: docker/setup-qemu-action@v2 38 | with: 39 | image: tonistiigi/binfmt@sha256:8de6f2decb92e9001d094534bf8a92880c175bd5dfb4a9d8579f26f09821cfa2 40 | platforms: all 41 | 42 | - run: 'nix build -L .#packages.${{ matrix.system }}.nix-portable' 43 | 44 | - name: Archive result 45 | uses: actions/upload-artifact@v4 46 | with: 47 | name: nix-portable-${{ matrix.system }} 48 | path: result/bin/nix-portable 49 | 50 | nix-matrix: 51 | runs-on: ubuntu-latest 52 | outputs: 53 | matrix: ${{ steps.set-matrix.outputs.matrix }} 54 | steps: 55 | - uses: actions/checkout@v4 56 | - uses: cachix/install-nix-action@v30 57 | - id: set-matrix 58 | name: Generate Nix Matrix 59 | run: | 60 | set -Eeu 61 | matrix="$(nix eval --json '.#githubActions.matrix')" 62 | echo "matrix=$matrix" >> "$GITHUB_OUTPUT" 63 | 64 | nix-build: 65 | name: ${{ matrix.name }} (${{ matrix.system }}) 66 | needs: 67 | - nix-matrix 68 | - build 69 | runs-on: ${{ matrix.os }} 70 | strategy: 71 | matrix: ${{fromJSON(needs.nix-matrix.outputs.matrix)}} 72 | steps: 73 | - uses: actions/checkout@v4 74 | - uses: cachix/install-nix-action@v30 75 | with: 76 | extra_nix_config: | 77 | experimental-features = nix-command flakes impure-derivations ca-derivations 78 | extra-platforms = aarch64-linux 79 | - uses: cachix/cachix-action@v14 80 | with: 81 | name: nix-portable 82 | authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' 83 | - name: Set up QEMU 84 | uses: docker/setup-qemu-action@v2 85 | with: 86 | image: tonistiigi/binfmt@sha256:8de6f2decb92e9001d094534bf8a92880c175bd5dfb4a9d8579f26f09821cfa2 87 | platforms: all 88 | - run: nix build -L '.#${{ matrix.attr }}' 89 | 90 | 91 | test_github: 92 | name: Test inside github action 93 | needs: build 94 | if: true 95 | runs-on: ubuntu-latest 96 | steps: 97 | 98 | - uses: actions/checkout@v4 99 | with: 100 | # Nix Flakes doesn't work on shallow clones 101 | fetch-depth: 0 102 | 103 | - uses: cachix/install-nix-action@V27 104 | with: 105 | extra_nix_config: | 106 | experimental-features = nix-command flakes 107 | 108 | - run: nix run -L .#test-local 109 | -------------------------------------------------------------------------------- /.github/workflows/update-flake-lock.yml: -------------------------------------------------------------------------------- 1 | name: update-flake-lock 2 | on: 3 | workflow_dispatch: # allows manual triggering 4 | schedule: 5 | - cron: '0 0 * * 1,4' # Run twice a week 6 | 7 | jobs: 8 | lockfile: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout repository 12 | uses: actions/checkout@v4 13 | - name: Install Nix 14 | uses: cachix/install-nix-action@V27 15 | # `bors merge` will automerge if tests succeed 16 | # Requires this github to be installed: https://app.bors.tech/ 17 | - name: Update flake.lock 18 | uses: DeterminateSystems/update-flake-lock@v23 19 | with: 20 | pr-labels: dependencies 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | test/ 2 | result* 3 | vm/ 4 | .direnv/ 5 | img 6 | QEMU_EFI.img 7 | -------------------------------------------------------------------------------- /.mergify.yml: -------------------------------------------------------------------------------- 1 | queue_rules: 2 | - name: default 3 | defaults: 4 | actions: 5 | queue: 6 | allow_merging_configuration_change: true 7 | method: rebase 8 | pull_request_rules: 9 | - name: merge using the merge queue 10 | conditions: 11 | - base=main 12 | - label~=merge-queue|auto-merge|dependencies 13 | actions: 14 | queue: 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 DavHau 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | 🪩 Use nix on any linux system, rootless and configuration free. 6 | 7 | 🔥 new: [Create software bundles](#bundle-programs) that work on any linux distribution. 8 | 9 | [💾 Downloads](https://github.com/DavHau/nix-portable/releases) 10 | 11 | --- 12 | 13 | ### Get nix-portable 14 | ```shellSession 15 | curl -L https://github.com/DavHau/nix-portable/releases/latest/download/nix-portable-$(uname -m) > ./nix-portable 16 | 17 | chmod +x ./nix-portable 18 | ``` 19 | 20 | ### Use nix via nix-portable 21 | 22 | There are two ways to run nix: 23 | 24 | #### Method 1: Pass nix command line: 25 | 26 | ```shellSession 27 | ./nix-portable nix-shell --help 28 | ``` 29 | 30 | #### Method 2: Symlink against nix-portable: 31 | 32 | To create a `nix-shell` executable, create a symlink `./nix-shell` against `./nix-portable`. 33 | 34 | ```shellSession 35 | ln -s ./nix-portable ./nix-shell 36 | ``` 37 | 38 | Then use the symlink as an executable: 39 | 40 | ```shellSession 41 | ./nix-shell --help 42 | ``` 43 | 44 | This works for any other nix native executable. 45 | 46 | ### Get and execute programs 47 | 48 | Hint: Use [search.nixos.org](https://search.nixos.org/packages) to find available programs. 49 | 50 | #### Run a program without installing it 51 | 52 | ```shellSession 53 | ./nix-portable nix run nixpkgs#htop 54 | ``` 55 | 56 | #### Create a temporary environment with multiple programs 57 | 58 | 1. Enter a temporary environment with `htop` and `vim`: 59 | 60 | ```shellSession 61 | ./nix-portable nix shell nixpkgs#{htop,vim} 62 | ``` 63 | 64 | 2. execute htop 65 | 66 | ```shellSession 67 | htop 68 | ``` 69 | 70 | ### Bundle programs 71 | nix-portable can bundle arbitrary software into a static executable that runs on [any*](#supported-platforms) linux distribution. 72 | 73 | Prerequisites: Your software is already packaged for nix. 74 | 75 | **Optional**: If you don't have nix yet, [get nix-portable](#get-nix-portable), then enter a temporary environment with nix and bash: 76 | 77 | ```shellSession 78 | ./nix-portable nix shell nixpkgs#{bashInteractive,nix} -c bash 79 | ``` 80 | 81 | Examples: 82 | 83 | #### Bundle gnu hello: 84 | 85 | 86 | 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: 87 | 88 | ```shellSession 89 | $ nix bundle --bundler github:DavHau/nix-portable -o bundle nixpkgs#hello 90 | $ cp ./bundle/bin/hello ./hello && chmod +w hello 91 | $ ./hello 92 | Hello World! 93 | ``` 94 | 95 | #### Bundle python + libraries 96 | 97 | Bundle python with arbitrary libraries as a static executable 98 | 99 | ```shellSession 100 | # create the bundle 101 | $ nix bundle --bundler github:DavHau/nix-portable -o bundle --impure --expr \ 102 | '(import {}).python3.withPackages (ps: [ ps.numpy ps.scipy ps.pandas ])' 103 | $ cp ./bundle/bin/python3 ./python3 && chmod +w ./python3 104 | 105 | # try it out 106 | $ ./python3 -c 'import numpy, scipy, pandas; print("Success !")' 107 | Success ! 108 | ``` 109 | 110 | #### Bundle whole dev environment 111 | 112 | Bundle a complex development environment including tools like compilers, linters, interpreters, etc. into a static executable. 113 | 114 | Prerequisites: 115 | - use [numtide/devshell](https://github.com/numtide/devshell) to define your devShell (`mkShell` from nixpkgs won't work because it is not executable) 116 | - expose the devShell via a flake.nix based repo on github 117 | 118 | ```shellSession 119 | $ nix bundle --bundler github:DavHau/nix-portable -o devshell github:/#devShells..default 120 | $ cp ./devshell/bin/devshell ./devshell && chmod +w ./devshell 121 | $ ./devshell 122 | 🔨 Welcome to devshell 123 | 124 | [[general commands]] 125 | [...] 126 | ``` 127 | 128 | #### Bundle compression 129 | 130 | To create smaller bundles specify `--bundler github:DavHau/nix-portable#zstd-max`. 131 | 132 | ### Supported platforms 133 | 134 | Potentially any linux system with an **x86_64** or **aarch64** CPU is supported. 135 | 136 | nix-portable is tested continuously on the following platforms: 137 | 138 | - Distros (x86_64): 139 | - Arch Linux 140 | - Fedora 141 | - Debian 142 | - NixOS 143 | - Ubuntu 144 | - Distros (aarch64): 145 | - Debian 146 | - NixOS 147 | - Other Environments: 148 | - Github Actions 149 | 150 | ### Under the hood 151 | 152 | - The nix-portable executable is a self extracting archive, caching its contents in $HOME/.nix-portable 153 | - Either nix, bubblewrap or proot is used to virtualize the /nix/store directory which actually resides in $HOME/.nix-portable/store 154 | - A default nixpkgs channel is included and the NIX_PATH variable is set accordingly. 155 | - Features `flakes` and `nix-command` are enabled out of the box. 156 | 157 | 158 | #### Virtualization 159 | 160 | To virtualize the /nix/store, nix-portable supports the following runtimes, preferred in this order: 161 | 162 | - nix (shipped via nix-portable) 163 | - bwrap (existing installation) 164 | - bwrap (shipped via nix-portable) 165 | - proot (existing installation) 166 | - proot (shipped via nix-portable) 167 | 168 | nix-portable will auto select the best runtime for your system. 169 | In case the auto selected runtime doesn't work, please open an issue. 170 | The default runtime can be overridden via [Environment Variables](#environment-variables). 171 | 172 | ### Environment Variables 173 | 174 | The following environment variables are optional and can be used to override the default behavior of nix-portable at run-time. 175 | 176 | ```txt 177 | NP_DEBUG (1 = debug msgs; 2 = 'set -x' for nix-portable) 178 | NP_GIT specify path to the git executable 179 | NP_LOCATION where to put the `.nix-portable` dir. (defaults to `$HOME`) 180 | NP_RUNTIME which runtime to use (must be one of: nix, bwrap, proot) 181 | NP_NIX specify the path to the static nix executable to use in case nix is selected as runtime 182 | NP_BWRAP specify the path to the bwrap executable to use in case bwrap is selected as runtime 183 | NP_PROOT specify the path to the proot executable to use in case proot is selected as runtime 184 | NP_RUN override the complete command to run nix 185 | (to use an unsupported runtime, or for debugging) 186 | nix will then be executed like: $NP_RUN {nix-binary} {args...} 187 | 188 | ``` 189 | 190 | ### Drawbacks / Considerations 191 | 192 | 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. 193 | 194 | If user namespaces are not available on a system, nix-portable will fall back to using proot as an alternative mechanism to virtualize /nix. 195 | Proot can introduce significant performance overhead depending on the workload. 196 | 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. 197 | 198 | 199 | ### Missing Features 200 | 201 | - managing nix profiles via `nix-env` 202 | - managing nix channels via `nix-channel` 203 | - support MacOS 204 | 205 | ### Building / Contributing 206 | 207 | To speed up builds, add the nix-portable cache: 208 | 209 | ```shellSession 210 | nix-shell -p cachix --run "cachix use nix-portable" 211 | ``` 212 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | with builtins; 2 | { 3 | bwrap, 4 | nix, 5 | proot, 6 | unzip, 7 | zip, 8 | unixtools, 9 | stdenv, 10 | buildPackages, 11 | upx, 12 | 13 | busybox ? pkgs.pkgsStatic.busybox, 14 | cacert ? pkgs.cacert, 15 | compression ? "zstd -19 -T0", 16 | gnutar ? pkgs.pkgsStatic.gnutar, 17 | lib ? pkgs.lib, 18 | perl ? pkgs.perl, 19 | pkgs ? import {}, 20 | xz ? pkgs.pkgsStatic.xz, 21 | zstd ? pkgs.pkgsStatic.zstd, 22 | nixStatic ? pkgs.pkgsStatic.nix, 23 | # hardcode executable to run. Useful when creating a bundle. 24 | bundledPackage ? null, 25 | ... 26 | }@inp: 27 | with lib; 28 | let 29 | 30 | pname = 31 | if bundledPackage == null 32 | then "nix-portable" 33 | else lib.getName bundledPackage; 34 | 35 | bundledExe = lib.getExe bundledPackage; 36 | 37 | nixpkgsSrc = pkgs.path; 38 | 39 | # TODO: git could be more minimal via: 40 | # perlSupport=false; guiSupport=false; nlsSupport=false; 41 | gitAttribute = "gitMinimal"; 42 | git = pkgs."${gitAttribute}"; 43 | 44 | maketar = targets: 45 | stdenv.mkDerivation { 46 | name = "nix-portable-store-tarball"; 47 | nativeBuildInputs = [ perl zstd ]; 48 | exportReferencesGraph = map (x: [("closure-" + baseNameOf x) x]) targets; 49 | buildCommand = '' 50 | storePaths=$(perl ${buildPackages.pathsFromGraph} ./closure-*) 51 | mkdir $out 52 | echo $storePaths > $out/index 53 | cp -r ${buildPackages.closureInfo { rootPaths = targets; }} $out/closureInfo 54 | 55 | tar -cf - \ 56 | --owner=0 --group=0 --mode=u+rw,uga+r \ 57 | --hard-dereference \ 58 | $storePaths | ${compression} > $out/tar 59 | ''; 60 | }; 61 | 62 | packStaticBin = binPath: let 63 | binName = (last (splitString "/" binPath)); in 64 | pkgs.runCommand 65 | binName 66 | { nativeBuildInputs = [ upx ]; } 67 | '' 68 | mkdir -p $out/bin 69 | upx -9 -o $out/bin/${binName} ${binPath} 70 | ''; 71 | 72 | installBin = pkg: bin: '' 73 | unzip -qqoj "\$self" ${ lib.removePrefix "/" "${pkg}/bin/${bin}"} -d \$dir/bin 74 | chmod +wx \$dir/bin/${bin}; 75 | ''; 76 | 77 | caBundleZstd = pkgs.runCommand "cacerts" {} "cat ${cacert}/etc/ssl/certs/ca-bundle.crt | ${inp.zstd}/bin/zstd -19 > $out"; 78 | 79 | bwrap = packStaticBin "${inp.bwrap}/bin/bwrap"; 80 | nixStatic = packStaticBin "${inp.nixStatic}/bin/nix"; 81 | proot = packStaticBin "${inp.proot}/bin/proot"; 82 | zstd = packStaticBin "${inp.zstd}/bin/zstd"; 83 | 84 | # the default nix store contents to extract when first used 85 | storeTar = maketar ([ cacert nix nixpkgsSrc ] ++ lib.optional (bundledPackage != null) bundledPackage); 86 | 87 | 88 | # The runtime script which unpacks the necessary files to $HOME/.nix-portable 89 | # and then executes nix via proot or bwrap 90 | # Some shell expressions will be evaluated at build time and some at run time. 91 | # Variables/expressions escaped via `\$` will be evaluated at run time 92 | runtimeScript = '' 93 | #!/usr/bin/env bash 94 | 95 | set -eo pipefail 96 | 97 | start=\$(date +%s%N) # start time in nanoseconds 98 | 99 | # dump environment on exit if debug is enabled 100 | if [ -n "\$NP_DEBUG" ] && [ "\$NP_DEBUG" -ge 1 ]; then 101 | trap "declare -p > /tmp/np_env" EXIT 102 | fi 103 | 104 | # there seem to be less issues with proot when disabling seccomp 105 | export PROOT_NO_SECCOMP=\''${PROOT_NO_SECCOMP:-1} 106 | 107 | set -e 108 | if [ -n "\$NP_DEBUG" ] && [ "\$NP_DEBUG" -ge 2 ]; then 109 | set -x 110 | fi 111 | 112 | # &3 is our error out which we either forward to &2 or to /dev/null 113 | # depending on the setting 114 | if [ -n "\$NP_DEBUG" ] && [ "\$NP_DEBUG" -ge 1 ]; then 115 | debug(){ 116 | echo \$@ || true 117 | } 118 | exec 3>&2 119 | else 120 | debug(){ 121 | true 122 | } 123 | exec 3>/dev/null 124 | fi 125 | 126 | # to reference this script's file 127 | self="\$(realpath \''${BASH_SOURCE[0]})" 128 | 129 | # fingerprint will be inserted by builder 130 | fingerprint="_FINGERPRINT_PLACEHOLDER_" 131 | 132 | # user specified location for program files and nix store 133 | [ -z "\$NP_LOCATION" ] && NP_LOCATION="\$HOME" 134 | NP_LOCATION="\$(readlink -f "\$NP_LOCATION")" 135 | dir="\$NP_LOCATION/.nix-portable" 136 | store="\$dir/nix/store" 137 | # create /nix/var/nix to prevent nix from falling back to chroot store. 138 | mkdir -p \$dir/{bin,nix/var/nix,nix/store} 139 | # sanitize the tmpbin directory 140 | rm -rf "\$dir/tmpbin" 141 | # create a directory to hold executable symlinks for overriding 142 | mkdir -p "\$dir/tmpbin" 143 | 144 | # create minimal drv file for nix to spawn a nix shell 145 | echo 'builtins.derivation {name="foo"; builder="/bin/sh"; args = ["-c" "echo hello \> \\\$out"]; system=builtins.currentSystem;}' > "\$dir/mini-drv.nix" 146 | 147 | # the fingerprint being present inside a file indicates that 148 | # this version of nix-portable has already been initialized 149 | if test -e \$dir/conf/fingerprint && [ "\$(cat \$dir/conf/fingerprint)" == "\$fingerprint" ]; then 150 | newNPVersion=false 151 | else 152 | newNPVersion=true 153 | fi 154 | 155 | # Nix portable ships its own nix.conf 156 | export NIX_CONF_DIR=\$dir/conf/ 157 | 158 | NP_CONF_SANDBOX=\''${NP_CONF_SANDBOX:-false} 159 | NP_CONF_STORE=\''${NP_CONF_STORE:-auto} 160 | 161 | 162 | recreate_nix_conf(){ 163 | mkdir -p "\$NIX_CONF_DIR" 164 | rm -f "\$NIX_CONF_DIR/nix.conf" 165 | 166 | # static config 167 | echo "build-users-group = " >> \$dir/conf/nix.conf 168 | echo "experimental-features = nix-command flakes" >> \$dir/conf/nix.conf 169 | echo "ignored-acls = security.selinux system.nfs4_acl" >> \$dir/conf/nix.conf 170 | echo "use-sqlite-wal = false" >> \$dir/conf/nix.conf 171 | echo "sandbox-paths = /bin/sh=\$dir/busybox/bin/busybox" >> \$dir/conf/nix.conf 172 | 173 | # configurable config 174 | echo "sandbox = \$NP_CONF_SANDBOX" >> \$dir/conf/nix.conf 175 | echo "store = \$NP_CONF_STORE" >> \$dir/conf/nix.conf 176 | } 177 | 178 | 179 | ### install files 180 | 181 | PATH_OLD="\$PATH" 182 | 183 | # as soon as busybox is unpacked, restrict PATH to busybox to ensure reproducibility of this script 184 | # only unpack binaries if necessary 185 | if [ "\$newNPVersion" == "false" ]; then 186 | 187 | debug "binaries already installed" 188 | export PATH="\$dir/busybox/bin" 189 | 190 | else 191 | 192 | debug "installing files" 193 | 194 | mkdir -p \$dir/emptyroot 195 | 196 | # install busybox 197 | mkdir -p \$dir/busybox/bin 198 | (base64 -d> "\$dir/busybox/bin/busybox" && chmod +x "\$dir/busybox/bin/busybox") << END 199 | $(cat ${busybox}/bin/busybox | base64) 200 | END 201 | busyBins="${toString (attrNames (filterAttrs (d: type: type == "symlink") (readDir "${inp.busybox}/bin")))}" 202 | for bin in \$busyBins; do 203 | [ ! -e "\$dir/busybox/bin/\$bin" ] && ln -s busybox "\$dir/busybox/bin/\$bin" 204 | done 205 | 206 | export PATH="\$dir/busybox/bin" 207 | 208 | # install other binaries 209 | ${installBin zstd "zstd"} 210 | ${installBin proot "proot"} 211 | ${installBin bwrap "bwrap"} 212 | ${installBin nixStatic "nix"} 213 | 214 | # install ssl cert bundle 215 | unzip -poj "\$self" ${ lib.removePrefix "/" "${caBundleZstd}"} | \$dir/bin/zstd -d > \$dir/ca-bundle.crt 216 | 217 | recreate_nix_conf 218 | fi 219 | 220 | 221 | 222 | ### setup SSL 223 | # find ssl certs or use from nixpkgs 224 | debug "figuring out ssl certs" 225 | if [ -z "\$SSL_CERT_FILE" ]; then 226 | debug "SSL_CERT_FILE not defined. trying to find certs automatically" 227 | if [ -e /etc/ssl/certs/ca-bundle.crt ]; then 228 | export SSL_CERT_FILE=\$(realpath /etc/ssl/certs/ca-bundle.crt) 229 | debug "found /etc/ssl/certs/ca-bundle.crt with real path \$SSL_CERT_FILE" 230 | elif [ -e /etc/ssl/certs/ca-certificates.crt ]; then 231 | export SSL_CERT_FILE=\$(realpath /etc/ssl/certs/ca-certificates.crt) 232 | debug "found /etc/ssl/certs/ca-certificates.crt with real path \$SSL_CERT_FILE" 233 | elif [ ! -e /etc/ssl/certs ]; then 234 | debug "/etc/ssl/certs does not exist. Will use certs from nixpkgs." 235 | export SSL_CERT_FILE=\$dir/ca-bundle.crt 236 | else 237 | debug "certs seem to reside in /etc/ssl/certs. No need to set up anything" 238 | fi 239 | fi 240 | if [ -n "\$SSL_CERT_FILE" ]; then 241 | sslBind="\$(realpath \$SSL_CERT_FILE) \$dir/ca-bundle.crt" 242 | export SSL_CERT_FILE="\$dir/ca-bundle.crt" 243 | else 244 | sslBind="/etc/ssl /etc/ssl" 245 | fi 246 | 247 | 248 | 249 | ### detecting existing git installation 250 | # we need to install git inside the wrapped environment 251 | # unless custom git executable path is specified in NP_GIT, 252 | # since the existing git might be incompatible to Nix (e.g. v1.x) 253 | if [ -n "\$NP_GIT" ]; then 254 | doInstallGit=false 255 | ln -s "\$NP_GIT" "\$dir/tmpbin/git" 256 | else 257 | doInstallGit=true 258 | fi 259 | 260 | 261 | 262 | storePathOfFile(){ 263 | file=\$(realpath \$1) 264 | sPath="\$(echo \$file | awk -F "/" 'BEGIN{OFS="/";}{print \$2,\$3,\$4}')" 265 | echo "/\$sPath" 266 | } 267 | 268 | 269 | collectBinds(){ 270 | ### gather paths to bind for proot 271 | # we cannot bind / to / without running into a lot of trouble, therefore 272 | # we need to collect all top level directories and bind them inside an empty root 273 | pathsTopLevel="\$(find / -mindepth 1 -maxdepth 1 -not -name nix -not -name dev)" 274 | 275 | 276 | toBind="" 277 | for p in \$pathsTopLevel; do 278 | if [ -e "\$p" ]; then 279 | real=\$(realpath \$p) 280 | if [ -e "\$real" ]; then 281 | if [[ "\$real" == /nix/store/* ]]; then 282 | storePath=\$(storePathOfFile \$real) 283 | toBind="\$toBind \$storePath \$storePath" 284 | else 285 | toBind="\$toBind \$real \$p" 286 | fi 287 | fi 288 | fi 289 | done 290 | 291 | 292 | # TODO: add /var/run/dbus/system_bus_socket 293 | paths="/etc/host.conf /etc/hosts /etc/hosts.equiv /etc/mtab /etc/netgroup /etc/networks /etc/passwd /etc/group /etc/nsswitch.conf /etc/resolv.conf /etc/localtime \$HOME" 294 | 295 | for p in \$paths; do 296 | if [ -e "\$p" ]; then 297 | real=\$(realpath \$p) 298 | if [ -e "\$real" ]; then 299 | if [[ "\$real" == /nix/store/* ]]; then 300 | storePath=\$(storePathOfFile \$real) 301 | toBind="\$toBind \$storePath \$storePath" 302 | else 303 | toBind="\$toBind \$real \$real" 304 | fi 305 | fi 306 | fi 307 | done 308 | 309 | # if we're on a nixos, the /bin/sh symlink will point 310 | # to a /nix/store path which doesn't exit inside the wrapped env 311 | # we fix this by binding busybox/bin to /bin 312 | if test -s /bin/sh && [[ "\$(realpath /bin/sh)" == /nix/store/* ]]; then 313 | toBind="\$toBind \$dir/busybox/bin /bin" 314 | fi 315 | } 316 | 317 | 318 | makeBindArgs(){ 319 | arg=\$1; shift 320 | sep=\$1; shift 321 | binds="" 322 | while :; do 323 | if [ -n "\$1" ]; then 324 | from="\$1"; shift 325 | to="\$1"; shift || { echo "no bind destination provided for \$from!"; exit 3; } 326 | binds="\$binds \$arg \$from\$sep\$to"; 327 | else 328 | break 329 | fi 330 | done 331 | } 332 | 333 | 334 | 335 | ### select container runtime 336 | debug "figuring out which runtime to use" 337 | [ -z "\$NP_BWRAP" ] && NP_BWRAP=\$(PATH="\$PATH_OLD:\$PATH" which bwrap 2>/dev/null) || true 338 | [ -z "\$NP_BWRAP" ] && NP_BWRAP=\$dir/bin/bwrap 339 | debug "bwrap executable: \$NP_BWRAP" 340 | # [ -z "\$NP_NIX ] && NP_NIX=\$(PATH="\$PATH_OLD:\$PATH" which nix 2>/dev/null) || true 341 | [ -z "\$NP_NIX" ] && NP_NIX=\$dir/bin/nix 342 | debug "nix executable: \$NP_NIX" 343 | [ -z "\$NP_PROOT" ] && NP_PROOT=\$(PATH="\$PATH_OLD:\$PATH" which proot 2>/dev/null) || true 344 | [ -z "\$NP_PROOT" ] && NP_PROOT=\$dir/bin/proot 345 | debug "proot executable: \$NP_PROOT" 346 | debug "testing all available runtimes..." 347 | if [ -z "\$NP_RUNTIME" ]; then 348 | # read last automatic selected runtime from disk 349 | if [ "\$newNPVersion" == "true" ]; then 350 | debug "removing cached auto selected runtime" 351 | rm -f "\$dir/conf/last_auto_runtime" 352 | fi 353 | if [ -f "\$dir/conf/last_auto_runtime" ]; then 354 | last_auto_runtime="\$(cat "\$dir/conf/last_auto_runtime")" 355 | else 356 | last_auto_runtime= 357 | fi 358 | debug "last auto selected runtime: \$last_auto_runtime" 359 | if [ "\$last_auto_runtime" != "" ]; then 360 | NP_RUNTIME="\$last_auto_runtime" 361 | # check if nix --store works 362 | elif \\ 363 | debug "testing nix --store" \\ 364 | && mkdir -p \$dir/tmp/ \\ 365 | && touch \$dir/tmp/testfile \\ 366 | && "\$NP_NIX" --store "\$dir/tmp/__store" shell -f "\$dir/mini-drv.nix" -c "\$dir/bin/nix" store add-file --store "\$dir/tmp/__store" "\$dir/tmp/testfile" >/dev/null 2>&3; then 367 | chmod -R +w \$dir/tmp/__store 368 | rm -r \$dir/tmp/__store 369 | debug "nix --store works on this system -> will use nix as runtime" 370 | NP_RUNTIME=nix 371 | # check if bwrap works properly 372 | elif \\ 373 | debug "nix --store failed -> testing bwrap" \\ 374 | && \$NP_BWRAP --bind \$dir/emptyroot / --bind \$dir/ /nix --bind \$dir/busybox/bin/busybox "\$dir/true" "\$dir/true" 2>&3 ; then 375 | debug "bwrap seems to work on this system -> will use bwrap" 376 | NP_RUNTIME=bwrap 377 | else 378 | debug "bwrap doesn't work on this system -> will use proot" 379 | NP_RUNTIME=proot 380 | fi 381 | echo -n "\$NP_RUNTIME" > "\$dir/conf/last_auto_runtime" 382 | else 383 | debug "runtime selected via NP_RUNTIME: \$NP_RUNTIME" 384 | fi 385 | debug "NP_RUNTIME: \$NP_RUNTIME" 386 | if [ "\$NP_RUNTIME" == "nix" ]; then 387 | run="\$NP_NIX shell -f \$dir/mini-drv.nix -c" 388 | export PATH="\$PATH:\$store${lib.removePrefix "/nix/store" nix}/bin" 389 | NP_CONF_STORE="\$dir" 390 | recreate_nix_conf 391 | elif [ "\$NP_RUNTIME" == "bwrap" ]; then 392 | collectBinds 393 | makeBindArgs --bind " " \$toBind \$sslBind 394 | run="\$NP_BWRAP \$BWRAP_ARGS \\ 395 | --bind \$dir/emptyroot /\\ 396 | --dev-bind /dev /dev\\ 397 | --bind \$dir/nix /nix\\ 398 | \$binds" 399 | # --bind \$dir/busybox/bin/busybox /bin/sh\\ 400 | else 401 | # proot 402 | collectBinds 403 | makeBindArgs -b ":" \$toBind \$sslBind 404 | run="\$NP_PROOT \$PROOT_ARGS\\ 405 | -r \$dir/emptyroot\\ 406 | -b /dev:/dev\\ 407 | -b \$dir/nix:/nix\\ 408 | \$binds" 409 | # -b \$dir/busybox/bin/busybox:/bin/sh\\ 410 | fi 411 | debug "base command will be: \$run" 412 | 413 | 414 | 415 | ### setup environment 416 | export NIX_PATH="\$dir/channels:nixpkgs=\$dir/channels/nixpkgs" 417 | mkdir -p \$dir/channels 418 | [ -h \$dir/channels/nixpkgs ] || ln -s ${nixpkgsSrc} \$dir/channels/nixpkgs 419 | 420 | 421 | ### install nix store 422 | # Install all the nix store paths necessary for the current nix-portable version 423 | # We only unpack missing store paths from the tar archive. 424 | index="$(cat ${storeTar}/index)" 425 | 426 | # if [ ! "\$NP_RUNTIME" == "nix" ]; then 427 | export missing=\$( 428 | for path in \$index; do 429 | if [ ! -e \$store/\$(basename \$path) ]; then 430 | echo "nix/store/\$(basename \$path)" 431 | fi 432 | done 433 | ) 434 | 435 | if [ -n "\$missing" ]; then 436 | debug "extracting missing store paths" 437 | ( 438 | mkdir -p \$dir/tmp \$store/ 439 | rm -rf \$dir/tmp/* 440 | cd \$dir/tmp 441 | unzip -qqp "\$self" ${ lib.removePrefix "/" "${storeTar}/tar"} \ 442 | | \$dir/bin/zstd -d \ 443 | | tar -x \$missing --strip-components 2 444 | mv \$dir/tmp/* \$store/ 445 | ) 446 | rm -rf \$dir/tmp 447 | fi 448 | 449 | if [ -n "\$missing" ]; then 450 | debug "registering new store paths to DB" 451 | reg="$(cat ${storeTar}/closureInfo/registration)" 452 | cmd="\$run \$store${lib.removePrefix "/nix/store" nix}/bin/nix-store --load-db" 453 | debug "running command: \$cmd" 454 | echo "\$reg" | \$cmd 455 | fi 456 | # fi 457 | 458 | 459 | ### select executable 460 | # the executable can either be selected by 461 | # - executing './nix-portable BIN_NAME', 462 | # - symlinking to nix-portable, in which case the name of the symlink selects the nix executable 463 | # Alternatively the executable can be hardcoded by specifying the argument 'executable' of nix-portable's default.nix file. 464 | executable="${if bundledPackage == null then "" else bundledExe}" 465 | if [ "\$executable" != "" ]; then 466 | bin="\$executable" 467 | debug "executable is hardcoded to: \$bin" 468 | elif [[ "\$(basename \$0)" == nix-portable* ]]; then\ 469 | if [ -z "\$1" ]; then 470 | echo "Error: please specify the nix binary to execute" 471 | echo "Alternatively symlink against \$0" 472 | exit 1 473 | elif [ "\$1" == "debug" ]; then 474 | bin="\$(which \$2)" 475 | shift; shift 476 | else 477 | bin="\$store${lib.removePrefix "/nix/store" nix}/bin/\$1" 478 | shift 479 | fi 480 | else 481 | bin="\$store${lib.removePrefix "/nix/store" nix}/bin/\$(basename \$0)" 482 | fi 483 | 484 | 485 | 486 | ### check which runtime has been used previously 487 | if [ -f "\$dir/conf/last_runtime" ]; then 488 | lastRuntime=\$(cat "\$dir/conf/last_runtime") 489 | else 490 | lastRuntime= 491 | fi 492 | 493 | 494 | 495 | ### check if nix is functional with or without sandbox 496 | # sandbox-fallback is not reliable: https://github.com/NixOS/nix/issues/4719 497 | if [ "\$newNPVersion" == "true" ] || [ "\$lastRuntime" != "\$NP_RUNTIME" ]; then 498 | nixBin="\$store${lib.removePrefix "/nix/store" nix}/bin/nix" 499 | # if [ "\$NP_RUNTIME" == "nix" ]; then 500 | # nixBin="nix" 501 | # else 502 | # fi 503 | debug "Testing if nix can build stuff without sandbox" 504 | if ! \$run "\$nixBin" build --no-link -f "\$dir/mini-drv.nix" --option sandbox false >&3 2>&3; then 505 | echo "Fatal error: nix is unable to build packages" 506 | exit 1 507 | fi 508 | 509 | debug "Testing if nix sandbox is functional" 510 | if ! \$run "\$nixBin" build --no-link -f "\$dir/mini-drv.nix" --option sandbox true >&3 2>&3; then 511 | debug "Sandbox doesn't work -> disabling sandbox" 512 | NP_CONF_SANDBOX=false 513 | recreate_nix_conf 514 | else 515 | debug "Sandboxed builds work -> enabling sandbox" 516 | NP_CONF_SANDBOX=true 517 | recreate_nix_conf 518 | fi 519 | 520 | fi 521 | 522 | 523 | ### save fingerprint and lastRuntime 524 | if [ "\$newNPVersion" == "true" ]; then 525 | echo -n "\$fingerprint" > "\$dir/conf/fingerprint" 526 | fi 527 | if [ "\$lastRuntime" != \$NP_RUNTIME ]; then 528 | echo -n \$NP_RUNTIME > "\$dir/conf/last_runtime" 529 | fi 530 | 531 | 532 | 533 | ### set PATH 534 | # restore original PATH and append busybox 535 | export PATH="\$PATH_OLD:\$dir/busybox/bin" 536 | # apply overriding executable paths in \$dir/tmpbin/ 537 | export PATH="\$dir/tmpbin:\$PATH" 538 | 539 | 540 | 541 | ### install git via nix, if git installation is not in /nix path 542 | if \$doInstallGit && [ ! -e \$store${lib.removePrefix "/nix/store" git.out} ] ; then 543 | echo "Installing git. Disable this by specifying the git executable path with 'NP_GIT'" 544 | \$run \$store${lib.removePrefix "/nix/store" nix}/bin/nix build --impure --no-link --expr " 545 | (import ${nixpkgsSrc} {}).${gitAttribute}.out 546 | " 547 | else 548 | debug "git already installed or manually specified" 549 | fi 550 | 551 | ### override the possibly existing git in the environment with the installed one 552 | # excluding the case NP_GIT is set. 553 | if \$doInstallGit; then 554 | export PATH="${git.out}/bin:\$PATH" 555 | fi 556 | 557 | 558 | ### print elapsed time 559 | end=\$(date +%s%N) # end time in nanoseconds 560 | # time elapsed in millis with two decimal places 561 | # elapsed=\$(echo "scale=2; (\$end - \$start)/1000000000" | bc) 562 | elapsed=\$(echo "scale=2; (\$end - \$start)/1000000" | bc) 563 | debug "Time to initialize nix-portable: \$elapsed millis" 564 | 565 | 566 | 567 | ### run commands 568 | [ -z "\$NP_RUN" ] && NP_RUN="\$run" 569 | if [ "\$NP_RUNTIME" == "proot" ]; then 570 | debug "running command: \$NP_RUN \$bin \$@" 571 | exec \$NP_RUN \$bin "\$@" 572 | else 573 | cmd="\$NP_RUN \$bin \$@" 574 | debug "running command: \$cmd" 575 | exec \$NP_RUN \$bin "\$@" 576 | fi 577 | exit 578 | ''; 579 | 580 | runtimeScriptEscaped = replaceStrings ["\""] ["\\\""] runtimeScript; 581 | 582 | nixPortable = pkgs.runCommand pname {nativeBuildInputs = [unixtools.xxd unzip];} '' 583 | mkdir -p $out/bin 584 | echo "${runtimeScriptEscaped}" > $out/bin/nix-portable.zip 585 | xxd $out/bin/nix-portable.zip | tail 586 | 587 | sizeA=$(printf "%08x" `stat -c "%s" $out/bin/nix-portable.zip` | tac -rs ..) 588 | echo 504b 0304 0000 0000 0000 0000 0000 0000 | xxd -r -p >> $out/bin/nix-portable.zip 589 | echo 0000 0000 0000 0000 0000 0200 0000 4242 | xxd -r -p >> $out/bin/nix-portable.zip 590 | 591 | sizeB=$(printf "%08x" `stat -c "%s" $out/bin/nix-portable.zip` | tac -rs ..) 592 | echo 504b 0102 0000 0000 0000 0000 0000 0000 | xxd -r -p >> $out/bin/nix-portable.zip 593 | echo 0000 0000 0000 0000 0000 0000 0200 0000 | xxd -r -p >> $out/bin/nix-portable.zip 594 | echo 0000 0000 0000 0000 0000 $sizeA 4242 | xxd -r -p >> $out/bin/nix-portable.zip 595 | 596 | echo 504b 0506 0000 0000 0000 0100 3000 0000 | xxd -r -p >> $out/bin/nix-portable.zip 597 | echo $sizeB 0000 0000 0000 0000 0000 0000 | xxd -r -p >> $out/bin/nix-portable.zip 598 | 599 | unzip -vl $out/bin/nix-portable.zip 600 | 601 | zip="${zip}/bin/zip -0" 602 | $zip $out/bin/nix-portable.zip ${bwrap}/bin/bwrap 603 | $zip $out/bin/nix-portable.zip ${nixStatic}/bin/nix 604 | $zip $out/bin/nix-portable.zip ${proot}/bin/proot 605 | $zip $out/bin/nix-portable.zip ${zstd}/bin/zstd 606 | $zip $out/bin/nix-portable.zip ${storeTar}/tar 607 | $zip $out/bin/nix-portable.zip ${caBundleZstd} 608 | 609 | # create fingerprint 610 | fp=$(sha256sum $out/bin/nix-portable.zip | cut -d " " -f 1) 611 | sed -i "s/_FINGERPRINT_PLACEHOLDER_/$fp/g" $out/bin/nix-portable.zip 612 | # fix broken zip header due to manual modification 613 | ${zip}/bin/zip -F $out/bin/nix-portable.zip --out $out/bin/nix-portable-fixed.zip 614 | 615 | rm $out/bin/nix-portable.zip 616 | executable=${if bundledPackage == null then "" else bundledExe} 617 | if [ "$executable" == "" ]; then 618 | target="$out/bin/nix-portable" 619 | else 620 | target="$out/bin/$(basename "$executable")" 621 | fi 622 | mv $out/bin/nix-portable-fixed.zip "$target" 623 | chmod +x "$target" 624 | ''; 625 | in 626 | nixPortable.overrideAttrs (prev: { 627 | passthru = (prev.passthru or {}) // { 628 | inherit bwrap proot; 629 | }; 630 | }) 631 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "defaultChannel": { 4 | "locked": { 5 | "lastModified": 1721562059, 6 | "narHash": "sha256-Tybxt65eyOARf285hMHIJ2uul8SULjFZbT9ZaEeUnP8=", 7 | "owner": "NixOS", 8 | "repo": "nixpkgs", 9 | "rev": "68c9ed8bbed9dfce253cc91560bf9043297ef2fe", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "id": "nixpkgs", 14 | "ref": "nixos-unstable", 15 | "type": "indirect" 16 | } 17 | }, 18 | "flake-compat": { 19 | "flake": false, 20 | "locked": { 21 | "lastModified": 1673956053, 22 | "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", 23 | "owner": "edolstra", 24 | "repo": "flake-compat", 25 | "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", 26 | "type": "github" 27 | }, 28 | "original": { 29 | "owner": "edolstra", 30 | "repo": "flake-compat", 31 | "type": "github" 32 | } 33 | }, 34 | "libgit2": { 35 | "flake": false, 36 | "locked": { 37 | "lastModified": 1697646580, 38 | "narHash": "sha256-oX4Z3S9WtJlwvj0uH9HlYcWv+x1hqp8mhXl7HsLu2f0=", 39 | "owner": "libgit2", 40 | "repo": "libgit2", 41 | "rev": "45fd9ed7ae1a9b74b957ef4f337bc3c8b3df01b5", 42 | "type": "github" 43 | }, 44 | "original": { 45 | "owner": "libgit2", 46 | "repo": "libgit2", 47 | "type": "github" 48 | } 49 | }, 50 | "nix": { 51 | "inputs": { 52 | "flake-compat": "flake-compat", 53 | "libgit2": "libgit2", 54 | "nixpkgs": "nixpkgs", 55 | "nixpkgs-regression": "nixpkgs-regression" 56 | }, 57 | "locked": { 58 | "lastModified": 1712163141, 59 | "narHash": "sha256-BSl8Jijq1A4n1ToQy0t0jDJCXhJK+w1prL8QMHS5t54=", 60 | "owner": "NixOS", 61 | "repo": "nix", 62 | "rev": "7bc4af7301df34ea4e157338ac3792c1a9ae35b7", 63 | "type": "github" 64 | }, 65 | "original": { 66 | "id": "nix", 67 | "ref": "2.20.6", 68 | "type": "indirect" 69 | } 70 | }, 71 | "nix-github-actions": { 72 | "inputs": { 73 | "nixpkgs": [ 74 | "nixpkgs" 75 | ] 76 | }, 77 | "locked": { 78 | "lastModified": 1737420293, 79 | "narHash": "sha256-F1G5ifvqTpJq7fdkT34e/Jy9VCyzd5XfJ9TO8fHhJWE=", 80 | "owner": "nix-community", 81 | "repo": "nix-github-actions", 82 | "rev": "f4158fa080ef4503c8f4c820967d946c2af31ec9", 83 | "type": "github" 84 | }, 85 | "original": { 86 | "owner": "nix-community", 87 | "repo": "nix-github-actions", 88 | "type": "github" 89 | } 90 | }, 91 | "nixpkgs": { 92 | "locked": { 93 | "lastModified": 1705033721, 94 | "narHash": "sha256-K5eJHmL1/kev6WuqyqqbS1cdNnSidIZ3jeqJ7GbrYnQ=", 95 | "owner": "NixOS", 96 | "repo": "nixpkgs", 97 | "rev": "a1982c92d8980a0114372973cbdfe0a307f1bdea", 98 | "type": "github" 99 | }, 100 | "original": { 101 | "owner": "NixOS", 102 | "ref": "nixos-23.05-small", 103 | "repo": "nixpkgs", 104 | "type": "github" 105 | } 106 | }, 107 | "nixpkgs-regression": { 108 | "locked": { 109 | "lastModified": 1643052045, 110 | "narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=", 111 | "owner": "NixOS", 112 | "repo": "nixpkgs", 113 | "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2", 114 | "type": "github" 115 | }, 116 | "original": { 117 | "owner": "NixOS", 118 | "repo": "nixpkgs", 119 | "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2", 120 | "type": "github" 121 | } 122 | }, 123 | "root": { 124 | "inputs": { 125 | "defaultChannel": "defaultChannel", 126 | "nix": "nix", 127 | "nix-github-actions": "nix-github-actions", 128 | "nixpkgs": [ 129 | "defaultChannel" 130 | ] 131 | } 132 | } 133 | }, 134 | "root": "root", 135 | "version": 7 136 | } 137 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | 4 | nixpkgs.follows = "defaultChannel"; 5 | 6 | # the nixpkgs version shipped with the nix-portable executable 7 | # TODO: find out why updating this leads to error when building pkgs.hello: 8 | # Error: checking whether build environment is sane... ls: cannot access './configure': No such file or directory 9 | defaultChannel.url = "nixpkgs/nixos-unstable"; 10 | 11 | nix.url = "nix/2.20.6"; 12 | 13 | nix-github-actions.url = "github:nix-community/nix-github-actions"; 14 | nix-github-actions.inputs.nixpkgs.follows = "nixpkgs"; 15 | }; 16 | 17 | outputs = { self, nix-github-actions, ... }@inp: 18 | with builtins; 19 | with inp.nixpkgs.lib; 20 | let 21 | 22 | lib = inp.nixpkgs.lib; 23 | 24 | supportedSystems = [ "x86_64-linux" "aarch64-linux" ]; 25 | 26 | forAllSystems = f: genAttrs supportedSystems 27 | (system: f system (import inp.nixpkgs { inherit system; })); 28 | 29 | nixPortableForSystem = { system, crossSystem ? null, } @ args: 30 | let 31 | pkgsDefaultChannel = import inp.defaultChannel { inherit system crossSystem; }; 32 | pkgs = import inp.nixpkgs { inherit system crossSystem; }; 33 | 34 | # the static proot built with nix somehow didn't work on other systems, 35 | # therefore using the proot static build from proot gitlab 36 | proot = import ./proot/alpine.nix { inherit pkgs; }; 37 | in 38 | # crashes if nixpkgs updated: error: executing 'git': No such file or directory 39 | pkgs.callPackage ./default.nix { 40 | 41 | inherit proot; 42 | 43 | pkgs = pkgsDefaultChannel; 44 | 45 | lib = inp.nixpkgs.lib; 46 | compression = "zstd -3 -T1"; 47 | 48 | nix = inp.nix.packages.${pkgs.stdenv.buildPlatform.system}.nix; 49 | nixStatic = inp.nix.packages.${pkgs.stdenv.buildPlatform.system}.nix-static; 50 | 51 | busybox = pkgs.pkgsStatic.busybox; 52 | bwrap = pkgs.pkgsStatic.bubblewrap; 53 | gnutar = pkgs.pkgsStatic.gnutar; 54 | perl = pkgs.pkgsBuildBuild.perl; 55 | xz = pkgs.pkgsStatic.xz; 56 | zstd = pkgs.pkgsStatic.zstd; 57 | }; 58 | in 59 | recursiveUpdate 60 | ({ 61 | 62 | bundlers = forAllSystems (system: pkgs: { 63 | # bundle with fast compression by default 64 | default = self.bundlers.${system}.zstd-fast; 65 | zstd-fast = drv: self.packages.${system}.nix-portable.override { 66 | bundledPackage = drv; 67 | compression = "zstd -3 -T0"; 68 | }; 69 | zstd-max = drv: self.packages.${system}.nix-portable.override { 70 | bundledPackage = drv; 71 | compression = "zstd -19 -T0"; 72 | }; 73 | }); 74 | 75 | devShell = forAllSystems (system: pkgs: 76 | pkgs.mkShell { 77 | buildInputs = with pkgs; [ 78 | bashInteractive 79 | guestfs-tools 80 | parallel 81 | proot 82 | qemu 83 | ]; 84 | } 85 | ); 86 | 87 | apps = forAllSystems (system: pkgs: { 88 | test-local.type = "app"; 89 | test-local.program = toString (pkgs.writeScript "test-local" '' 90 | #!/usr/bin/env bash 91 | set -e 92 | export NP_DEBUG=''${NP_DEBUG:-1} 93 | ${concatStringsSep "\n\n" (forEach [ "bwrap" "proot" ] (runtime: 94 | concatStringsSep "\n" (map (cmd: 95 | ''${self.packages."${system}".nix-portable}/bin/nix-portable ${cmd}'' 96 | ) (import ./testing/test-commands.nix)) 97 | ))} 98 | echo "all tests succeeded" 99 | ''); 100 | }); 101 | 102 | checks = 103 | lib.recursiveUpdate 104 | (forAllSystems (system: pkgs: 105 | pkgs.callPackages ./testing/vm-tests.nix {inherit (self.packages.${system}) nix-portable;} 106 | )) 107 | # github doesn't have KVM in aarch64 runners. -> run aarch64 vm tests on x86_64 108 | { 109 | x86_64-linux = 110 | let 111 | system = "x86_64-linux"; 112 | crossSystem = "aarch64-linux"; 113 | pkgs = import inp.nixpkgs { inherit system crossSystem; }; 114 | in 115 | lib.mapAttrs' 116 | (name: drv: {name = name + "-aarch64-linux"; value = drv;}) 117 | (pkgs.callPackages ./testing/vm-tests.nix { 118 | nix-portable = self.packages.${crossSystem}.nix-portable; 119 | pkgsNative = inp.nixpkgs.legacyPackages.${crossSystem}; 120 | }); 121 | }; 122 | 123 | packages = forAllSystems (system: pkgs: { 124 | default = self.packages.${system}.nix-portable; 125 | nix-portable = (nixPortableForSystem { inherit system; }).override { 126 | # all non x86_64-linux systems are built via emulation 127 | # -> decrease compression level to reduce CI build time 128 | compression = 129 | if system == "x86_64-linux" 130 | then "zstd -19 -T0" 131 | else "zstd -9 -T0"; 132 | }; 133 | # dev version that builds faster 134 | nix-portable-dev = self.packages.${system}.nix-portable.override { 135 | compression = "zstd -3 -T1"; 136 | }; 137 | release = pkgs.runCommand "all-nix-portable-release-files" {} '' 138 | mkdir $out 139 | cp ${self.packages.x86_64-linux.nix-portable}/bin/nix-portable $out/nix-portable-x86_64 140 | cp ${self.packages.aarch64-linux.nix-portable}/bin/nix-portable $out/nix-portable-aarch64 141 | ''; 142 | qemu-efi-aarch64 = pkgs.callPackage ./testing/qemu-efi.nix {}; 143 | }); 144 | }) 145 | { packages = (genAttrs [ "x86_64-linux" ] (system: 146 | (listToAttrs (map (crossSystem: 147 | nameValuePair "nix-portable-${crossSystem}" (nixPortableForSystem { inherit crossSystem system; } ) 148 | ) [ "aarch64-linux" ])) 149 | )); 150 | 151 | githubActions = nix-github-actions.lib.mkGithubMatrix { 152 | checks = 153 | lib.getAttrs 154 | [ "x86_64-linux" ] 155 | self.checks; 156 | }; 157 | }; 158 | } 159 | -------------------------------------------------------------------------------- /proot/alpine.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs ? import {}, 3 | ... 4 | }: let 5 | system = pkgs.system; 6 | apks = { 7 | x86_64-linux = { 8 | # original: http://dl-cdn.alpinelinux.org/alpine/edge/testing/x86_64/proot-static-5.4.0-r0.apk 9 | url = "https://web.archive.org/web/20240412082958/http://dl-cdn.alpinelinux.org/alpine/edge/testing/x86_64/proot-static-5.4.0-r0.apk"; 10 | sha256 = "sha256:0ljxc4waa5i1j7hcqli4z7hhpkvjr5k3xwq1qyhlm2lldmv9izqy"; 11 | }; 12 | aarch64-linux = { 13 | # original: http://dl-cdn.alpinelinux.org/alpine/edge/testing/aarch64/proot-static-5.4.0-r0.apk 14 | url = "https://web.archive.org/web/20240412083320/http://dl-cdn.alpinelinux.org/alpine/edge/testing/aarch64/proot-static-5.4.0-r0.apk"; 15 | sha256 = "sha256:0nl3gnbirxkhyralqx01xwg8nxanj1bgz7pkk118nv5wrf26igyd"; 16 | }; 17 | }; 18 | in 19 | pkgs.stdenv.mkDerivation { 20 | name = "proot"; 21 | src = builtins.fetchurl { 22 | url = apks.${system}.url; 23 | sha256 = apks.${system}.sha256; 24 | }; 25 | unpackPhase = '' 26 | tar -xf $src 27 | ''; 28 | installPhase = '' 29 | mkdir -p $out/bin 30 | cp ./usr/bin/proot.static $out/bin/proot 31 | ''; 32 | } 33 | -------------------------------------------------------------------------------- /proot/github.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs ? import {}, 3 | ... 4 | }: 5 | 6 | with builtins; 7 | 8 | let 9 | version = "5.3.0"; 10 | 11 | systems = { 12 | x86_64-linux = { 13 | url = "https://github.com/proot-me/proot/releases/download/v${version}/proot-v${version}-x86_64-static"; 14 | sha256 = "1nmllvdhlbdlgffq6x351p0zfgv202qfy8vhf26z0v8y435j1syi"; 15 | }; 16 | aarch64-linux = { 17 | url = "https://github.com/proot-me/proot/releases/download/v${version}/proot-v${version}-aarch64-static"; 18 | sha256 = "0icaag29a6v214am4cbdyvncjs63f02lad2qrcfmnbwch6kv247s"; 19 | }; 20 | armv7l-linux = { 21 | url = "https://github.com/proot-me/proot/releases/download/v${version}/proot-v${version}-arm-static"; 22 | sha256 = ""; 23 | }; 24 | }; 25 | in 26 | 27 | pkgs.runCommand "proot-x86_46" {} '' 28 | bin=${builtins.fetchurl { 29 | inherit (systems."${pkgs.buildPlatform.system}") url sha256; 30 | }} 31 | mkdir -p $out/bin 32 | cp $bin $out/bin/proot 33 | chmod +x $out/bin/proot 34 | '' 35 | -------------------------------------------------------------------------------- /proot/gitlab.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs ? import {}, 3 | ... 4 | }: 5 | 6 | with builtins; 7 | 8 | pkgs.runCommand "proot-x86_46" {} '' 9 | zip=${builtins.fetchurl { 10 | url = "https://gitlab.com/proot/proot/-/jobs/981080848/artifacts/download"; 11 | sha256 = "05biwh64rjs7bnxvqmb2s2sik83al84sbp34mk8z4qjcm7ddgxd0"; 12 | }} 13 | mkdir -p $out/bin 14 | ${pkgs.unzip}/bin/unzip $zip public/bin/proot 15 | mv public/bin/proot $out/bin/proot 16 | '' -------------------------------------------------------------------------------- /proot/nixpkgs.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs ? import {}, 3 | ... 4 | }: 5 | 6 | with builtins; 7 | let 8 | overlay = curr: prev: { 9 | talloc = prev.talloc.overrideAttrs (old: { 10 | wafConfigureFlags = old.wafConfigureFlags ++ [ 11 | "--disable-python" 12 | ]; 13 | }); 14 | }; 15 | overlayedPkgs = import pkgs.path { overlays = [overlay]; inherit (pkgs) system; }; 16 | static = overlayedPkgs.pkgsStatic; 17 | proot = static.proot.override { enablePython = false; }; 18 | in 19 | proot.overrideAttrs (old:{ 20 | src = pkgs.fetchFromGitHub { 21 | repo = "proot"; 22 | owner = "proot-me"; 23 | rev = "8c0ccf7db18b5d5ca2f47e1afba7897fb1bb39c0"; 24 | sha256 = "sha256-vFdUH1WrW6+MfdlW9s+9LOhk2chPxKJUjaFy01+r49Q="; 25 | }; 26 | nativeBuildInputs = with static; old.nativeBuildInputs ++ [ 27 | libarchive.dev pkg-config 28 | ]; 29 | PKG_CONFIG_PATH = [ 30 | "${static.libarchive.dev}/lib/pkgconfig" 31 | ]; 32 | }) 33 | -------------------------------------------------------------------------------- /proot/termux.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs ? import {}, 3 | ... 4 | }: 5 | 6 | with builtins; 7 | let 8 | overlay = curr: prev: { 9 | talloc = prev.talloc.overrideAttrs (old: { 10 | wafConfigureFlags = old.wafConfigureFlags ++ [ 11 | "--disable-python" 12 | ]; 13 | }); 14 | }; 15 | overlayedPkgs = import pkgs.path { overlays = [overlay]; inherit (pkgs) system; }; 16 | static = overlayedPkgs.pkgsStatic; 17 | proot = static.proot.override { enablePython = false; }; 18 | in 19 | proot.overrideAttrs (old:{ 20 | src = pkgs.fetchFromGitHub { 21 | owner = "termux"; 22 | repo = "PRoot"; 23 | rev = "3eb0f49109391537e12c6f724706c12e8b7529d7"; 24 | sha256 = "sha256-xGRMvf2OopfF8ek+jg7gZk2J17jRUVBBPog2I36Y9QU="; 25 | }; 26 | buildInputs = with static; [ talloc ]; 27 | nativeBuildInputs = with static; old.nativeBuildInputs ++ [ 28 | libarchive.dev ncurses pkg-config 29 | ]; 30 | PKG_CONFIG_PATH = [ 31 | "${static.libarchive.dev}/lib/pkgconfig" 32 | ]; 33 | }) 34 | -------------------------------------------------------------------------------- /testing/id_ed25519: -------------------------------------------------------------------------------- 1 | -----BEGIN OPENSSH PRIVATE KEY----- 2 | b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW 3 | QyNTUxOQAAACCkGe7gNv3mTzONb+Dsf0YmlCdyTNauDe5QcoHh6tCEhwAAAJg2B2NyNgdj 4 | cgAAAAtzc2gtZWQyNTUxOQAAACCkGe7gNv3mTzONb+Dsf0YmlCdyTNauDe5QcoHh6tCEhw 5 | AAAEBAQoC1JvAToiYi5dY+3r3YNVq7CMOVXWpecPCqmgPiNaQZ7uA2/eZPM41v4Ox/RiaU 6 | J3JM1q4N7lBygeHq0ISHAAAAD2dybXBmQGdybXBmLW5peAECAwQFBg== 7 | -----END OPENSSH PRIVATE KEY----- 8 | -------------------------------------------------------------------------------- /testing/id_ed25519.pub: -------------------------------------------------------------------------------- 1 | ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKQZ7uA2/eZPM41v4Ox/RiaUJ3JM1q4N7lBygeHq0ISH grmpf@grmpf-nix 2 | -------------------------------------------------------------------------------- /testing/nixos-iso.nix: -------------------------------------------------------------------------------- 1 | { config, lib, pkgs, modulesPath, ... }: 2 | with builtins; 3 | with lib; 4 | { 5 | imports = [ 6 | "${toString modulesPath}/installer/cd-dvd/iso-image.nix" 7 | ]; 8 | 9 | boot.initrd.availableKernelModules = [ 10 | "virtio_net" 11 | "virtio_pci" 12 | "virtio_mmio" 13 | "virtio_blk" 14 | "virtio_scsi" 15 | "virtio_balloon" 16 | "virtio_console" 17 | ]; 18 | 19 | boot.loader.timeout = mkOverride 49 1; 20 | 21 | fileSystems."/" = { 22 | fsType = "tmpfs"; 23 | options = [ "mode=0755" "size=2G" ]; 24 | }; 25 | 26 | # EFI booting 27 | isoImage.makeEfiBootable = true; 28 | 29 | # USB booting 30 | isoImage.makeUsbBootable = true; 31 | 32 | isoImage.squashfsCompression = "zstd -Xcompression-level 5"; 33 | 34 | users.users.vagrant.isNormalUser = true; 35 | users.users.vagrant.openssh.authorizedKeys.keyFiles = [ ./vagrant_insecure_key.pub ]; 36 | users.users.root.openssh.authorizedKeys.keyFiles = config.users.users.vagrant.openssh.authorizedKeys.keyFiles; 37 | services.openssh.enable = true; 38 | } 39 | -------------------------------------------------------------------------------- /testing/qemu-efi.nix: -------------------------------------------------------------------------------- 1 | # http://snapshots.linaro.org/components/kernel/leg-virt-tianocore-edk2-upstream/4443/QEMU-ARM/RELEASE_GCC5/QEMU_EFI.img.gz 2 | 3 | { 4 | fetchurl, 5 | runCommand, 6 | buildPackages, 7 | }: 8 | 9 | let 10 | qemu-efi-gz = fetchurl { 11 | url = "http://snapshots.linaro.org/components/kernel/leg-virt-tianocore-edk2-upstream/4801/QEMU-AARCH64/RELEASE_GCC5/QEMU_EFI.img.gz"; 12 | sha256 = "sha256-Rfio8FtcXrVslz+W6BsSV0xHvxwHLfqGhJMs2Kc3B30="; 13 | }; 14 | in 15 | 16 | runCommand "QEMU_EFI.img" {} '' 17 | cp ${qemu-efi-gz} QEMU_EFI.img.gz 18 | ${buildPackages.gzip}/bin/gunzip QEMU_EFI.img.gz 19 | mv QEMU_EFI.img $out 20 | '' 21 | -------------------------------------------------------------------------------- /testing/test-commands.nix: -------------------------------------------------------------------------------- 1 | [ 2 | # test git 3 | ''nix eval --impure --expr 'builtins.fetchGit {url="https://github.com/davhau/nix-portable"; rev="7ebf4ca972c6613983b2698ab7ecda35308e9886";}' '' 4 | # test importing and building hello works 5 | ''nix build -L --impure --expr '(import {}).hello.overrideAttrs(_:{change="_var_";})' '' 6 | # test running a program from the nix store 7 | "nix-shell -p hello --run hello" 8 | ] 9 | -------------------------------------------------------------------------------- /testing/ubuntu/01-netplan.yaml: -------------------------------------------------------------------------------- 1 | network: 2 | version: 2 3 | renderer: networkd 4 | ethernets: 5 | ens3: 6 | dhcp4: yes 7 | -------------------------------------------------------------------------------- /testing/vagrant-test.nix: -------------------------------------------------------------------------------- 1 | { 2 | qemu, 3 | openssh, 4 | lib, 5 | pkgs, 6 | pkgsBuildBuild, 7 | system, 8 | stdenv, 9 | 10 | # arguments 11 | image, 12 | testName, 13 | hostScript, 14 | guestScript ? "", 15 | }: 16 | let 17 | qemu-common = import (pkgs.path + "/nixos/lib/qemu-common.nix") { inherit lib pkgs; }; 18 | in 19 | 20 | stdenv.mkDerivation (finalAttrs: { 21 | __impure = true; 22 | name = "test-${testName}"; 23 | src = image.image; 24 | depsBuildBuild = [ 25 | qemu 26 | openssh 27 | ]; 28 | postBoot = image.postBoot or ""; 29 | dontUnpack = image.dontUnpack or false; 30 | preBuild = image.preBuild or ""; 31 | dontInstall = true; 32 | 33 | rootDisk = if finalAttrs.dontUnpack then finalAttrs.src else image.rootDisk; 34 | 35 | unpackPhase = '' 36 | tar -xf $src 37 | ''; 38 | 39 | buildPhase = '' 40 | runHook preBuild 41 | 42 | shopt -s nullglob 43 | 44 | port=$(shuf -n 1 -i 20000-30000) 45 | 46 | echo "Image is: $rootDisk" 47 | 48 | image_type=$(qemu-img info $rootDisk | sed 's/file format: \(.*\)/\1/; t; d') 49 | 50 | qemu-img create -b $rootDisk -F "$image_type" -f qcow2 ./disk.qcow2 51 | 52 | cp ${pkgs.callPackage ./qemu-efi.nix {}} ./QEMU_EFI.img 53 | chmod +w ./QEMU_EFI.img 54 | 55 | extra_qemu_opts="${image.extraQemuOpts or ""}" 56 | 57 | # Add the config disk, required by the Ubuntu images. 58 | config_drive=$(echo *configdrive.vmdk || true) 59 | if [[ -n $config_drive ]]; then 60 | extra_qemu_opts+=" -drive id=disk2,file=$config_drive,if=virtio" 61 | fi 62 | 63 | echo "Starting qemu..." 64 | ${qemu-common.qemuBinary pkgsBuildBuild.qemu}\ 65 | -m 4096 -nographic \ 66 | -smp 2 \ 67 | -drive id=disk1,file=./disk.qcow2,if=virtio \ 68 | -netdev user,id=net0,hostfwd=tcp::$port-:22 -device virtio-net-pci,netdev=net0 \ 69 | ${lib.optionalString (system == "aarch64-linux") 70 | "-cpu cortex-a53 -machine virt -drive if=pflash,format=raw,file=./QEMU_EFI.img"} \ 71 | $extra_qemu_opts & 72 | qemu_pid=$! 73 | trap "kill $qemu_pid" EXIT 74 | 75 | if ! [ -e ./vagrant_insecure_key ]; then 76 | cp ${./vagrant_insecure_key} vagrant_insecure_key 77 | fi 78 | 79 | chmod 0400 ./vagrant_insecure_key 80 | 81 | export HOME=$(realpath .) 82 | ssh_opts="-o StrictHostKeyChecking=no -o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedKeyTypes=+ssh-rsa -i ./vagrant_insecure_key" 83 | ssh="ssh -p $port -q $ssh_opts vagrant@localhost" 84 | echo "ssh command: $ssh" 85 | sshRoot="ssh -p $port -q $ssh_opts root@localhost" 86 | scp="scp -P $port $ssh_opts" 87 | 88 | echo "Waiting for SSH..." 89 | for ((i = 0; i < 120; i++)); do 90 | echo "[ssh] Trying to connect..." 91 | if $ssh -- true; then 92 | echo "[ssh] Connected!" 93 | break 94 | fi 95 | if ! kill -0 $qemu_pid; then 96 | echo "qemu died unexpectedly" 97 | exit 1 98 | fi 99 | sleep 1 100 | done 101 | 102 | if [[ -n $postBoot ]]; then 103 | echo "Running post-boot commands..." 104 | $ssh "set -ex; $postBoot" 105 | fi 106 | 107 | echo "executing host script" 108 | ${hostScript} 109 | 110 | echo "Executing script for test ${testName}..." 111 | $ssh < nixexprs/someFile 51 | tar cvf - nixexprs | bzip2 > $out/channel/nixexprs.tar.bz2 52 | ''; 53 | 54 | disableSELinux = "sudo setenforce 0"; 55 | 56 | images = { 57 | 58 | /* 59 | "ubuntu-14-04" = { 60 | image = import { 61 | url = "https://app.vagrantup.com/ubuntu/boxes/trusty64/versions/20190514.0.0/providers/virtualbox.box"; 62 | hash = "sha256-iUUXyRY8iW7DGirb0zwGgf1fRbLA7wimTJKgP7l/OQ8="; 63 | }; 64 | rootDisk = "box-disk1.vmdk"; 65 | system = "x86_64-linux"; 66 | }; 67 | */ 68 | 69 | "ubuntu-16-04" = { 70 | image = import { 71 | url = "https://app.vagrantup.com/generic/boxes/ubuntu1604/versions/4.1.12/providers/libvirt.box"; 72 | hash = "sha256-lO4oYQR2tCh5auxAYe6bPOgEqOgv3Y3GC1QM1tEEEU8="; 73 | }; 74 | rootDisk = "box.img"; 75 | system = "x86_64-linux"; 76 | }; 77 | 78 | "ubuntu-22-04" = { 79 | image = import { 80 | url = "https://app.vagrantup.com/generic/boxes/ubuntu2204/versions/4.1.12/providers/libvirt.box"; 81 | hash = "sha256-HNll0Qikw/xGIcogni5lz01vUv+R3o8xowP2EtqjuUQ="; 82 | }; 83 | rootDisk = "box.img"; 84 | system = "x86_64-linux"; 85 | }; 86 | 87 | "fedora-36" = { 88 | image = import { 89 | url = "https://app.vagrantup.com/generic/boxes/fedora36/versions/4.1.12/providers/libvirt.box"; 90 | hash = "sha256-rxPgnDnFkTDwvdqn2CV3ZUo3re9AdPtSZ9SvOHNvaks="; 91 | }; 92 | rootDisk = "box.img"; 93 | system = "x86_64-linux"; 94 | postBoot = disableSELinux; 95 | }; 96 | 97 | # Currently fails with 'error while loading shared libraries: 98 | # libsodium.so.23: cannot stat shared object: Invalid argument'. 99 | /* 100 | "rhel-6" = { 101 | image = import { 102 | url = "https://app.vagrantup.com/generic/boxes/rhel6/versions/4.1.12/providers/libvirt.box"; 103 | hash = "sha256-QwzbvRoRRGqUCQptM7X/InRWFSP2sqwRt2HaaO6zBGM="; 104 | }; 105 | rootDisk = "box.img"; 106 | system = "x86_64-linux"; 107 | }; 108 | */ 109 | 110 | "rhel-7" = { 111 | image = import { 112 | url = "https://app.vagrantup.com/generic/boxes/rhel7/versions/4.1.12/providers/libvirt.box"; 113 | hash = "sha256-b4afnqKCO9oWXgYHb9DeQ2berSwOjS27rSd9TxXDc/U="; 114 | }; 115 | rootDisk = "box.img"; 116 | system = "x86_64-linux"; 117 | }; 118 | 119 | "rhel-8" = { 120 | image = import { 121 | url = "https://app.vagrantup.com/generic/boxes/rhel8/versions/4.1.12/providers/libvirt.box"; 122 | hash = "sha256-zFOPjSputy1dPgrQRixBXmlyN88cAKjJ21VvjSWUCUY="; 123 | }; 124 | rootDisk = "box.img"; 125 | system = "x86_64-linux"; 126 | postBoot = disableSELinux; 127 | }; 128 | 129 | "rhel-9" = { 130 | image = import { 131 | url = "https://app.vagrantup.com/generic/boxes/rhel9/versions/4.1.12/providers/libvirt.box"; 132 | hash = "sha256-vL/FbB3kK1rcSaR627nWmScYGKGk4seSmAdq6N5diMg="; 133 | }; 134 | rootDisk = "box.img"; 135 | system = "x86_64-linux"; 136 | postBoot = disableSELinux; 137 | extraQemuOpts = "-cpu Westmere-v2"; 138 | }; 139 | 140 | }; 141 | 142 | makeTest = 143 | imageName: testName: 144 | let 145 | image = images.${imageName}; 146 | pkgs = nixpkgsFor.${image.system}.native; 147 | in 148 | pkgs.runCommand "installer-test-${imageName}-${testName}" 149 | { 150 | buildInputs = [ 151 | pkgs.qemu_kvm 152 | pkgs.openssh 153 | ]; 154 | image = image.image; 155 | postBoot = image.postBoot or ""; 156 | installScript = testScripts.${testName}.script; 157 | binaryTarball = binaryTarballs.${pkgs.system}; 158 | } 159 | '' 160 | shopt -s nullglob 161 | 162 | echo "Unpacking Vagrant box $image..." 163 | tar xvf $image 164 | 165 | image_type=$(qemu-img info ${image.rootDisk} | sed 's/file format: \(.*\)/\1/; t; d') 166 | 167 | qemu-img create -b ./${image.rootDisk} -F "$image_type" -f qcow2 ./disk.qcow2 168 | 169 | extra_qemu_opts="${image.extraQemuOpts or ""}" 170 | 171 | # Add the config disk, required by the Ubuntu images. 172 | config_drive=$(echo *configdrive.vmdk || true) 173 | if [[ -n $config_drive ]]; then 174 | extra_qemu_opts+=" -drive id=disk2,file=$config_drive,if=virtio" 175 | fi 176 | 177 | echo "Starting qemu..." 178 | qemu-kvm -m 4096 -nographic \ 179 | -drive id=disk1,file=./disk.qcow2,if=virtio \ 180 | -netdev user,id=net0,restrict=yes,hostfwd=tcp::20022-:22 -device virtio-net-pci,netdev=net0 \ 181 | $extra_qemu_opts & 182 | qemu_pid=$! 183 | trap "kill $qemu_pid" EXIT 184 | 185 | if ! [ -e ./vagrant_insecure_key ]; then 186 | cp ${./vagrant_insecure_key} vagrant_insecure_key 187 | fi 188 | 189 | chmod 0400 ./vagrant_insecure_key 190 | 191 | ssh_opts="-o StrictHostKeyChecking=no -o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedKeyTypes=+ssh-rsa -i ./vagrant_insecure_key" 192 | ssh="ssh -p 20022 -q $ssh_opts vagrant@localhost" 193 | 194 | echo "Waiting for SSH..." 195 | for ((i = 0; i < 120; i++)); do 196 | echo "[ssh] Trying to connect..." 197 | if $ssh -- true; then 198 | echo "[ssh] Connected!" 199 | break 200 | fi 201 | if ! kill -0 $qemu_pid; then 202 | echo "qemu died unexpectedly" 203 | exit 1 204 | fi 205 | sleep 1 206 | done 207 | 208 | if [[ -n $postBoot ]]; then 209 | echo "Running post-boot commands..." 210 | $ssh "set -ex; $postBoot" 211 | fi 212 | 213 | echo "Copying installer..." 214 | scp -P 20022 $ssh_opts $binaryTarball/nix-*.tar.xz vagrant@localhost:nix.tar.xz 215 | 216 | echo "Running installer..." 217 | $ssh "set -eux; $installScript" 218 | 219 | echo "Copying the mock channel" 220 | # `scp -r` doesn't seem to work properly on some rhel instances, so let's 221 | # use a plain tarpipe instead 222 | tar -C ${mockChannel pkgs} -c channel | ssh -p 20022 $ssh_opts vagrant@localhost tar x -f- 223 | 224 | echo "Testing Nix installation..." 225 | $ssh < \$out"]; }') 238 | [[ \$(cat \$out) = foobar ]] 239 | 240 | if pgrep nix-daemon; then 241 | MAYBESUDO="sudo" 242 | else 243 | MAYBESUDO="" 244 | fi 245 | 246 | 247 | $MAYBESUDO \$(which nix-channel) --add file://\$HOME/channel myChannel 248 | $MAYBESUDO \$(which nix-channel) --update 249 | [[ \$(nix-instantiate --eval --expr 'builtins.readFile ') = '"someContent"' ]] 250 | EOF 251 | 252 | echo "Done!" 253 | touch $out 254 | ''; 255 | 256 | in 257 | 258 | builtins.mapAttrs (imageName: image: { 259 | ${image.system} = builtins.mapAttrs (testName: test: makeTest imageName testName) testScripts; 260 | }) images 261 | -------------------------------------------------------------------------------- /testing/vagrant_insecure_key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEogIBAAKCAQEA6NF8iallvQVp22WDkTkyrtvp9eWW6A8YVr+kz4TjGYe7gHzI 3 | w+niNltGEFHzD8+v1I2YJ6oXevct1YeS0o9HZyN1Q9qgCgzUFtdOKLv6IedplqoP 4 | kcmF0aYet2PkEDo3MlTBckFXPITAMzF8dJSIFo9D8HfdOV0IAdx4O7PtixWKn5y2 5 | hMNG0zQPyUecp4pzC6kivAIhyfHilFR61RGL+GPXQ2MWZWFYbAGjyiYJnAmCP3NO 6 | Td0jMZEnDkbUvxhMmBYSdETk1rRgm+R4LOzFUGaHqHDLKLX+FIPKcF96hrucXzcW 7 | yLbIbEgE98OHlnVYCzRdK8jlqm8tehUc9c9WhQIBIwKCAQEA4iqWPJXtzZA68mKd 8 | ELs4jJsdyky+ewdZeNds5tjcnHU5zUYE25K+ffJED9qUWICcLZDc81TGWjHyAqD1 9 | Bw7XpgUwFgeUJwUlzQurAv+/ySnxiwuaGJfhFM1CaQHzfXphgVml+fZUvnJUTvzf 10 | TK2Lg6EdbUE9TarUlBf/xPfuEhMSlIE5keb/Zz3/LUlRg8yDqz5w+QWVJ4utnKnK 11 | iqwZN0mwpwU7YSyJhlT4YV1F3n4YjLswM5wJs2oqm0jssQu/BT0tyEXNDYBLEF4A 12 | sClaWuSJ2kjq7KhrrYXzagqhnSei9ODYFShJu8UWVec3Ihb5ZXlzO6vdNQ1J9Xsf 13 | 4m+2ywKBgQD6qFxx/Rv9CNN96l/4rb14HKirC2o/orApiHmHDsURs5rUKDx0f9iP 14 | cXN7S1uePXuJRK/5hsubaOCx3Owd2u9gD6Oq0CsMkE4CUSiJcYrMANtx54cGH7Rk 15 | EjFZxK8xAv1ldELEyxrFqkbE4BKd8QOt414qjvTGyAK+OLD3M2QdCQKBgQDtx8pN 16 | CAxR7yhHbIWT1AH66+XWN8bXq7l3RO/ukeaci98JfkbkxURZhtxV/HHuvUhnPLdX 17 | 3TwygPBYZFNo4pzVEhzWoTtnEtrFueKxyc3+LjZpuo+mBlQ6ORtfgkr9gBVphXZG 18 | YEzkCD3lVdl8L4cw9BVpKrJCs1c5taGjDgdInQKBgHm/fVvv96bJxc9x1tffXAcj 19 | 3OVdUN0UgXNCSaf/3A/phbeBQe9xS+3mpc4r6qvx+iy69mNBeNZ0xOitIjpjBo2+ 20 | dBEjSBwLk5q5tJqHmy/jKMJL4n9ROlx93XS+njxgibTvU6Fp9w+NOFD/HvxB3Tcz 21 | 6+jJF85D5BNAG3DBMKBjAoGBAOAxZvgsKN+JuENXsST7F89Tck2iTcQIT8g5rwWC 22 | P9Vt74yboe2kDT531w8+egz7nAmRBKNM751U/95P9t88EDacDI/Z2OwnuFQHCPDF 23 | llYOUI+SpLJ6/vURRbHSnnn8a/XG+nzedGH5JGqEJNQsz+xT2axM0/W/CRknmGaJ 24 | kda/AoGANWrLCz708y7VYgAtW2Uf1DPOIYMdvo6fxIB5i9ZfISgcJ/bbCUkFrhoH 25 | +vq/5CIWxCPp0f85R4qxxQ5ihxJ0YDQT9Jpx4TMss4PSavPaBH3RXow5Ohe+bYoQ 26 | NE5OgEXk2wVfZczCZpigBKbKZHNYcelXtTt/nP3rsCuGcM4h53s= 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /testing/vagrant_insecure_key.pub: -------------------------------------------------------------------------------- 1 | ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA6NF8iallvQVp22WDkTkyrtvp9eWW6A8YVr+kz4TjGYe7gHzIw+niNltGEFHzD8+v1I2YJ6oXevct1YeS0o9HZyN1Q9qgCgzUFtdOKLv6IedplqoPkcmF0aYet2PkEDo3MlTBckFXPITAMzF8dJSIFo9D8HfdOV0IAdx4O7PtixWKn5y2hMNG0zQPyUecp4pzC6kivAIhyfHilFR61RGL+GPXQ2MWZWFYbAGjyiYJnAmCP3NOTd0jMZEnDkbUvxhMmBYSdETk1rRgm+R4LOzFUGaHqHDLKLX+FIPKcF96hrucXzcWyLbIbEgE98OHlnVYCzRdK8jlqm8tehUc9c9WhQ== 2 | -------------------------------------------------------------------------------- /testing/vm-tests.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib, 3 | nix-portable, 4 | callPackage, 5 | system, 6 | pkgsBuildHost, 7 | runCommand, 8 | cloud-utils, 9 | writeText, 10 | 11 | # custom 12 | pkgsNative ? pkgsBuildHost, 13 | }: 14 | let 15 | inherit (builtins // lib) 16 | concatStringsSep 17 | flip 18 | forEach 19 | map 20 | mapAttrs' 21 | replaceStrings 22 | filter 23 | elem 24 | ; 25 | 26 | vagrantUrl = kind: name: version: 27 | "https://app.vagrantup.com/${kind}/boxes/${name}/versions/${version}/providers/libvirt.box"; 28 | 29 | noUserNs = [ 30 | "nix" 31 | "bwrap" 32 | ]; 33 | 34 | nixos = pkgsNative.nixos; 35 | 36 | cloudInitFile = writeText "cloud-init" '' 37 | #cloud-config 38 | users: 39 | - name: vagrant 40 | ssh_authorized_keys: 41 | - "${lib.removeSuffix "\n" (builtins.readFile ./vagrant_insecure_key.pub)}" 42 | - name: root 43 | ssh_authorized_keys: 44 | - "${lib.removeSuffix "\n" (builtins.readFile ./vagrant_insecure_key.pub)}" 45 | ''; 46 | 47 | cloudInitImg = runCommand "cloud-init-img" 48 | {nativeBuildInputs = [cloud-utils];} 49 | '' 50 | cloud-localds $out ${cloudInitFile} 51 | ''; 52 | 53 | images.aarch64-linux = { 54 | nixos = { 55 | image = (toString (nixos { 56 | imports = [ 57 | ./nixos-iso.nix 58 | ]; 59 | }).config.system.build.isoImage) + "/iso/nixos.iso"; 60 | system = "aarch64-linux"; 61 | dontUnpack = true; 62 | disabledRuntimes = ["proot"]; 63 | }; 64 | debian10 = { 65 | image = import { 66 | url = "https://cdimage.debian.org/cdimage/cloud/buster/20240703-1797/debian-10-generic-arm64-20240703-1797.qcow2"; 67 | hash = "sha256-nHYkDXWun+HthVw/kwwKPvUNi5GBiBAy9TEH3ObvvPU="; 68 | name = "debian10.qcow2"; 69 | }; 70 | dontUnpack = true; 71 | system = "aarch64-linux"; 72 | extraQemuOpts = "-drive file=cloud-init.img,format=raw,if=virtio"; 73 | preBuild = '' 74 | echo "Copying cloud-init image ${cloudInitImg} to cloud-init.img" 75 | cp ${cloudInitImg} cloud-init.img 76 | chmod +w cloud-init.img 77 | ''; 78 | disabledRuntimes = noUserNs; 79 | }; 80 | debian11 = { 81 | image = import { 82 | url = "https://cdimage.debian.org/cdimage/cloud/bullseye/20250505-2103/debian-11-genericcloud-arm64-20250505-2103.qcow2"; 83 | hash = "sha256-GKVl1WaT9Up1KhN9VjvedPcrQLNyT5+TaxfNYKTC7zE="; 84 | name = "debian11.qcow2"; 85 | }; 86 | dontUnpack = true; 87 | system = "aarch64-linux"; 88 | extraQemuOpts = "-drive file=cloud-init.img,format=raw,if=virtio"; 89 | preBuild = '' 90 | echo "Copying cloud-init image ${cloudInitImg} to cloud-init.img" 91 | cp ${cloudInitImg} cloud-init.img 92 | chmod +w cloud-init.img 93 | ''; 94 | disabledRuntimes = ["proot"]; 95 | }; 96 | debian12 = { 97 | image = import { 98 | # url = "https://cdimage.debian.org/cdimage/cloud/bookworm/20250428-2096/debian-12-nocloud-arm64-20250428-2096.qcow2"; 99 | # hash = "sha256-6Pb7PSutDjeg/Mdh6E2aXznTKhLRGzFdNlkW4Af8CFc="; 100 | url = "https://cdimage.debian.org/cdimage/cloud/bookworm/20250428-2096/debian-12-genericcloud-arm64-20250428-2096.qcow2"; 101 | hash = "sha256-exC5YUEP4KQ7MXqzgJ/Hb8bBrmbJtlaVeb4K/Lfw6vY="; 102 | name = "debian12.qcow2"; 103 | }; 104 | dontUnpack = true; 105 | system = "aarch64-linux"; 106 | extraQemuOpts = "-drive file=cloud-init.img,format=raw,if=virtio"; 107 | preBuild = '' 108 | echo "Copying cloud-init image ${cloudInitImg} to cloud-init.img" 109 | cp ${cloudInitImg} cloud-init.img 110 | chmod +w cloud-init.img 111 | ''; 112 | disabledRuntimes = ["nix" "proot"]; 113 | }; 114 | }; 115 | 116 | images.x86_64-linux = { 117 | nixos = { 118 | image = (toString (nixos { 119 | imports = [ 120 | ./nixos-iso.nix 121 | ]; 122 | }).config.system.build.isoImage) + "/iso/nixos.iso"; 123 | system = "x86_64-linux"; 124 | rootDisk = "nixos.qcow2"; 125 | dontUnpack = true; 126 | }; 127 | arch = { 128 | image = import { 129 | url = vagrantUrl "generic" "arch" "4.3.12"; 130 | hash = "sha256-LmXwLuJlVeAqPOw/KV/oHBPsyhuUCDQz0eLlWUTZ0BE="; 131 | }; 132 | rootDisk = "box.img"; 133 | system = "x86_64-linux"; 134 | postBoot = '' 135 | sudo rm -f /etc/resolv.conf 136 | sudo ln -s /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf 137 | ''; 138 | }; 139 | debian11 = { 140 | image = import { 141 | url = vagrantUrl "generic" "debian11" "4.3.12"; 142 | hash = "sha256-Sfsfo3VyfUcDhEa2Fr6OODlGoOuDLJo8YHvbyWjqJmY="; 143 | }; 144 | rootDisk = "box.img"; 145 | system = "x86_64-linux"; 146 | }; 147 | debian12 = { 148 | image = import { 149 | url = vagrantUrl "generic" "debian12" "4.3.12"; 150 | hash = "sha256-kj5NFLvyB/u0Dpeyc0kDD59UssP7gu3Yrl1KyQDTcFw="; 151 | }; 152 | rootDisk = "box.img"; 153 | system = "x86_64-linux"; 154 | }; 155 | 156 | fedora-36 = { 157 | image = import { 158 | url = vagrantUrl "generic" "fedora36" "4.1.12"; 159 | hash = "sha256-rxPgnDnFkTDwvdqn2CV3ZUo3re9AdPtSZ9SvOHNvaks="; 160 | }; 161 | rootDisk = "box.img"; 162 | system = "x86_64-linux"; 163 | }; 164 | 165 | fedora-42 = { 166 | image = import { 167 | url = vagrantUrl "cloud-image" "fedora-42" "1.1.0"; 168 | hash = "sha256-q84cWJEOFP42P3pPLOzN5IcPXYEmxieKTqX6rlwbvL8="; 169 | }; 170 | rootDisk = "box.img"; 171 | system = "x86_64-linux"; 172 | }; 173 | 174 | ubuntu-22-04 = { 175 | image = import { 176 | url = vagrantUrl "generic" "ubuntu2204" "4.1.12"; 177 | hash = "sha256-HNll0Qikw/xGIcogni5lz01vUv+R3o8xowP2EtqjuUQ="; 178 | }; 179 | rootDisk = "box.img"; 180 | system = "x86_64-linux"; 181 | }; 182 | 183 | ubuntu-24-04 = { 184 | image = import { 185 | url = vagrantUrl "cloud-image" "ubuntu-24.04" "20250502.1.0"; 186 | hash = "sha256-GBvMo4kJfWfpH9qPZSyyCgOvDxkrS8fzCZxl9omSmbs="; 187 | }; 188 | rootDisk = "box.img"; 189 | system = "x86_64-linux"; 190 | disabledRuntimes = noUserNs; 191 | }; 192 | 193 | rhel-7 = { 194 | image = import { 195 | url = vagrantUrl "generic" "rhel7" "4.1.12"; 196 | hash = "sha256-b4afnqKCO9oWXgYHb9DeQ2berSwOjS27rSd9TxXDc/U="; 197 | }; 198 | rootDisk = "box.img"; 199 | system = "x86_64-linux"; 200 | disabledRuntimes = noUserNs; 201 | }; 202 | 203 | rhel-8 = { 204 | image = import { 205 | url = vagrantUrl "generic" "rhel8" "4.1.12"; 206 | hash = "sha256-zFOPjSputy1dPgrQRixBXmlyN88cAKjJ21VvjSWUCUY="; 207 | }; 208 | rootDisk = "box.img"; 209 | system = "x86_64-linux"; 210 | }; 211 | 212 | rhel-9 = { 213 | image = import { 214 | url = vagrantUrl "generic" "rhel9" "4.1.12"; 215 | hash = "sha256-vL/FbB3kK1rcSaR627nWmScYGKGk4seSmAdq6N5diMg="; 216 | }; 217 | rootDisk = "box.img"; 218 | system = "x86_64-linux"; 219 | extraQemuOpts = "-cpu Westmere-v2"; 220 | }; 221 | }; 222 | 223 | commandsToTest = import ./test-commands.nix; 224 | runtimes = [ "nix" "bwrap" "proot" ]; 225 | announce = cmd: ''echo -e "\ntesting cmd: ${cmd}"''; 226 | escape = cmd: replaceStrings [''"''] [''\"''] cmd; 227 | mkCmd = runtime: cmd: let 228 | vars = "NP_RUNTIME=${runtime} NP_DEBUG=$NP_DEBUG NP_LOCATION=/np_tmp"; 229 | in '' 230 | ${announce (escape cmd)} 231 | $ssh "${vars} /home/vagrant/nix-portable ${escape cmd}" 232 | ''; 233 | modCommand = anyStr: forEach commandsToTest (cmd: replaceStrings [ "_var_" ] [ anyStr ] cmd); 234 | testCommands = runtime: 235 | concatStringsSep "\n" (map (mkCmd runtime) (modCommand runtime)); 236 | 237 | runtimesFor = image: 238 | filter (r: ! elem r image.disabledRuntimes or []) runtimes; 239 | 240 | makeTest = name: image: callPackage ./vagrant-test.nix { 241 | inherit image; 242 | testName = name; 243 | hostScript = '' 244 | set -x 245 | echo hello 246 | # change root password via ssh 247 | $ssh "sudo mkdir -p /root/.ssh && sudo cp -r /home/vagrant/.ssh/* /root/.ssh/" || echo "failed to copy ssh keys to root" 248 | $sshRoot mkdir -p /np_tmp 249 | $sshRoot "test -e /np_tmp/.nix-portable || mount -t tmpfs -o size=3g /bin/true /np_tmp" 250 | echo "uploading ssh key" 251 | 252 | 253 | echo "upload the nix-portable executable" 254 | $scp ${nix-portable}/bin/nix-portable vagrant@localhost:nix-portable 255 | $ssh chmod +w /home/vagrant/nix-portable 256 | 257 | echo -e "\n\nstarting to test nix-portable" 258 | # test some nix commands 259 | NP_DEBUG=''${NP_DEBUG:-1} 260 | # test if automatic runtime selection works 261 | echo "testing automatic runtime selection..." 262 | if ! $ssh "NP_DEBUG=$NP_DEBUG NP_LOCATION=/np_tmp /home/vagrant/nix-portable nix-shell -p hello --run hello"; then 263 | echo "Error: automatic runtime selection failed" 264 | exit 1 265 | fi 266 | ${concatStringsSep "\n\n" (forEach (runtimesFor image) testCommands)} 267 | ''; 268 | }; 269 | in 270 | flip mapAttrs' images.${system} or {} ( 271 | name: image: 272 | { 273 | name = "vm-test-${name}"; 274 | value = makeTest name image; 275 | } 276 | ) 277 | --------------------------------------------------------------------------------