├── .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 |
--------------------------------------------------------------------------------