diff --git a/default.nix b/default.nix index 86f4dcd..1418043 100644 --- a/default.nix +++ b/default.nix @@ -1 +1,31 @@ -import ./nix-bootstrap.nix +{nixpkgs ? import {}}: + +with nixpkgs; + +let + + makeself = callPackage ./makeself.nix {}; + makedir = callPackage ./makedir.nix {}; + + makebootstrap = callPackage ./makebootstrap.nix { + inherit makedir makeself; + }; + + nix-user-chroot = callPackage ./nix-user-chroot.nix {}; + + nix-bootstrap = callPackage ./nix-bootstrap.nix { + inherit nix-user-chroot; + }; + +in { + + versionTest = makebootstrap { + name = "nix-bootstrap.sh"; + target = nix-bootstrap { + name = "nix-bootstrap"; + stage3 = "nix-env --version"; + }; + run = "/stage1.sh"; + }; + +} diff --git a/install-nix-from-closure.nix b/install-nix-from-closure.nix deleted file mode 100644 index de88348..0000000 --- a/install-nix-from-closure.nix +++ /dev/null @@ -1,22 +0,0 @@ -{ stdenv, fetchFromGitHub, nix, cacert }: - - stdenv.mkDerivation { - name = "nix-bootstrap"; - - src = fetchFromGitHub { - owner = "matthewbauer"; - repo = "nix-bootstrap"; - rev = "cc882b2cb92d8de87dad9cb890ad1745b06a9787"; - sha256 = "05w6xjg0cgz6a4szc7jd7v53bmy4zjrgph5xkgyj73g62jyq7ajf"; - }; - - propagatedBuildInputs = [ nix cacert ]; - - installPhase = '' - mkdir -p $out/ - substitute install-nix-from-closure.sh $out/install \ - --subst-var-by nix .${nix} \ - --subst-var-by cacert .${cacert} - chmod +x $out/install - ''; - } diff --git a/makebootstrap.nix b/makebootstrap.nix index 331bc9c..2d5e625 100644 --- a/makebootstrap.nix +++ b/makebootstrap.nix @@ -1,13 +1,4 @@ -{nixpkgs ? import {}}: - -with nixpkgs; - -let - - makeself = callPackage ./makeself.nix {}; - makedir = callPackage ./makedir.nix {}; - -in +{makeself, makedir}: { name, target, run }: makeself { diff --git a/makedir.nix b/makedir.nix index c1a9b90..644b856 100644 --- a/makedir.nix +++ b/makedir.nix @@ -11,7 +11,7 @@ storePaths=$(${perl}/bin/perl ${pathsFromGraph} ./closure) printRegistration=1 ${perl}/bin/perl ${pathsFromGraph} ./closure > $out/.reginfo for path in $storePaths; do - cp --parents -r $path $out/ + cp --parents -rp $path $out/ done ''; } diff --git a/nix-bootstrap.nix b/nix-bootstrap.nix index 1806acc..05f9c8c 100644 --- a/nix-bootstrap.nix +++ b/nix-bootstrap.nix @@ -1,14 +1,43 @@ -{nixpkgs ? import {}}: +{ stdenv, writeText, nix-user-chroot, nix, cacert, coreutils, bash }: -with nixpkgs; +{ name, stage3 }: let + stage1 = writeText "stage1.sh" '' + ./${nix-user-chroot}/bin/nix-user-chroot ./nix ./${bash}/bin/sh -c $(dirname $0)/stage2.sh + ''; - makebootstrap = callPackage ./makebootstrap.nix {}; - install-nix-from-closure = callPackage ./install-nix-from-closure.nix {}; + stage2 = writeText "stage2.sh" '' + unset NIX_REMOTE NIX_PROFILES NIX_USER_PROFILE_DIR NIX_OTHER_STORES NIX_PATH + export NIX_CONF_DIR=/nix/etc/nix/ + export NIX_PROFILE=/nix/var/nix/profiles/default + export SSL_CERT_FILE="${cacert}/etc/ssl/certs/ca-bundle.crt" + export PATH="${coreutils}/bin:${bash}/bin:${nix.out}/bin" -in makebootstrap { - name = "nix-bootstrap.sh"; - target = install-nix-from-closure; - run = "/install"; -} + chmod -R a-w /nix/ + chmod u+w /nix/ /nix/store/ + mkdir -p /nix/etc/nix/ /nix/var/nix/profiles $HOME /bin/ + ln -s ${bash}/bin/sh /bin/sh + + nix-store --init + nix-store --load-db < ./.reginfo + + . ${nix.out}/etc/profile.d/nix.sh + + nix-channel --add https://nixos.org/channels/nixpkgs-unstable + nix-channel --update nixpkgs + + nix-shell --pure -p ${nix.out} --run "${stage3}" + ''; + +in + + stdenv.mkDerivation { + inherit name; + buildInputs = [ nix-user-chroot nix cacert ]; + buildCommand = '' + mkdir -p $out + install -m 755 ${stage1} $out/stage1.sh + install -m 755 ${stage2} $out/stage2.sh + ''; + } diff --git a/nix-installer.nix b/nix-installer.nix new file mode 100644 index 0000000..cd03b09 --- /dev/null +++ b/nix-installer.nix @@ -0,0 +1,21 @@ +{ stdenv, fetchFromGitHub, writeText, nix, cacert }: + +let + + run-from-closure = writeText "run-from-closure" (builtins.readFile ./run-from-closure.sh); + +in + + stdenv.mkDerivation { + name = "nix-bootstrap"; + + propagatedBuildInputs = [ nix cacert ]; + + buildCommand = '' + mkdir -p $out/bin/ + substitute ${run-from-closure} $out/bin/run-from-closure \ + --subst-var-by nix ${nix.out} \ + --subst-var-by cacert ${cacert} + chmod +x $out/bin/run-from-closure + ''; + } diff --git a/nix-user-chroot.c b/nix-user-chroot.c new file mode 100644 index 0000000..73bd9b1 --- /dev/null +++ b/nix-user-chroot.c @@ -0,0 +1,144 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define err_exit(format, ...) { fprintf(stderr, format ": %s\n", ##__VA_ARGS__, strerror(errno)); exit(EXIT_FAILURE); } + +static void usage(char *pname) { + fprintf(stderr, "Usage: %s \n", pname); + + exit(EXIT_FAILURE); +} + +static void update_map(char *mapping, char *map_file) { + int fd; + + fd = open(map_file, O_WRONLY); + if (fd < 0) { + err_exit("map open"); + } + + int map_len = strlen(mapping); + if (write(fd, mapping, map_len) != map_len) { + err_exit("map write"); + } + + close(fd); +} + +static void add_path(const char* name, const char* rootdir) { + char path_buf[PATH_MAX]; + char path_buf2[PATH_MAX]; + + snprintf(path_buf, sizeof(path_buf), "/%s", name); + + struct stat statbuf; + if (stat(path_buf, &statbuf) < 0) { + fprintf(stderr, "Cannot stat %s: %s\n", path_buf, strerror(errno)); + return; + } + + snprintf(path_buf2, sizeof(path_buf2), "%s/%s", rootdir, name); + + if (S_ISDIR(statbuf.st_mode)) { + mkdir(path_buf2, statbuf.st_mode & ~S_IFMT); + if (mount(path_buf, path_buf2, "none", MS_BIND | MS_REC, NULL) < 0) { + fprintf(stderr, "Cannot bind mount %s to %s: %s\n", path_buf, path_buf2, strerror(errno)); + } + } +} + +int main(int argc, char *argv[]) { + if (argc < 3) { + usage(argv[0]); + } + + char template[] = "/tmp/nixXXXXXX"; + char *rootdir = mkdtemp(template); + if (!rootdir) { + err_exit("mkdtemp(%s)", template); + } + + char *nixdir = realpath(argv[1], NULL); + if (!nixdir) { + err_exit("realpath(%s)", argv[1]); + } + + uid_t uid = getuid(); + gid_t gid = getgid(); + + if (unshare(CLONE_NEWNS | CLONE_NEWUSER) < 0) { + err_exit("unshare()"); + } + + // bind mount all / stuff into rootdir + DIR* d = opendir("/"); + if (!d) { + err_exit("open /"); + } + + add_path("dev", rootdir); + add_path("proc", rootdir); + add_path("sys", rootdir); + add_path("run", rootdir); + add_path("tmp", rootdir); + add_path("var", rootdir); + + struct stat statbuf2; + if (stat(nixdir, &statbuf2) < 0) { + err_exit("stat(%s)", nixdir); + } + + char path_buf[PATH_MAX]; + + snprintf(path_buf, sizeof(path_buf), "%s/nix", rootdir); + mkdir(path_buf, statbuf2.st_mode & ~S_IFMT); + if (mount(nixdir, path_buf, "none", MS_BIND | MS_REC, NULL) < 0) { + err_exit("mount(%s, %s)", nixdir, path_buf); + } + + // fixes issue #1 where writing to /proc/self/gid_map fails + // see user_namespaces(7) for more documentation + int fd_setgroups = open("/proc/self/setgroups", O_WRONLY); + if (fd_setgroups > 0) { + write(fd_setgroups, "deny", 4); + } + + char map_buf[1024]; + + // map the original uid/gid in the new ns + snprintf(map_buf, sizeof(map_buf), "%d %d 1", uid, uid); + update_map(map_buf, "/proc/self/uid_map"); + + snprintf(map_buf, sizeof(map_buf), "%d %d 1", gid, gid); + update_map(map_buf, "/proc/self/gid_map"); + + char cwd[PATH_MAX]; + + if (!getcwd(cwd, PATH_MAX)) { + err_exit("getcwd()"); + } + + chdir("/"); + if (chroot(rootdir) < 0) { + err_exit("chroot(%s)", rootdir); + } + chdir(cwd); + + // execute the command + + execvp(argv[2], argv+2); + err_exit("execvp(%s)", argv[2]); +} diff --git a/nix-user-chroot.nix b/nix-user-chroot.nix new file mode 100644 index 0000000..037fcb9 --- /dev/null +++ b/nix-user-chroot.nix @@ -0,0 +1,10 @@ +{ stdenv, fetchFromGitHub }: + + stdenv.mkDerivation { + name = "nix-user-chroot"; + buildCommand = '' + cp ${./nix-user-chroot.c} nix-user-chroot.c + mkdir -p $out/bin/ + cc nix-user-chroot.c -o $out/bin/nix-user-chroot + ''; + } diff --git a/run-from-closure.sh b/run-from-closure.sh new file mode 100644 index 0000000..5066604 --- /dev/null +++ b/run-from-closure.sh @@ -0,0 +1,39 @@ +#!/bin/sh + +set -e + +self="." +nix="@nix@" +cacert="@cacert@" + +if ! [ -e $self/.reginfo ]; then + echo "$0: incomplete installer (.reginfo is missing)" >&2 + exit 1 +fi + +echo "initialising Nix database..." >&2 +if ! $nix/bin/nix-store --init; then + echo "$0: failed to initialize the Nix database" >&2 + exit 1 +fi + +if ! $nix/bin/nix-store --load-db < $self/.reginfo; then + echo "$0: unable to register valid paths" >&2 + exit 1 +fi + +. $nix/etc/profile.d/nix.sh + +if ! $nix/bin/nix-env -i "$nix"; then + echo "$0: unable to install Nix into your default profile" >&2 + exit 1 +fi + +# Install an SSL certificate bundle. +if [ -z "$SSL_CERT_FILE" -o ! -f "$SSL_CERT_FILE" ]; then + $nix/bin/nix-env -i "$cacert" + export SSL_CERT_FILE="$HOME/.nix-profile/etc/ssl/certs/ca-bundle.crt" +fi + +$nix/bin/nix-channel --add https://nixos.org/channels/nixpkgs-unstable +$nix/bin/nix-channel --update nixpkgs