about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--configure.ac2
-rw-r--r--doc/manual/command-ref/conf-file.xml32
-rw-r--r--doc/manual/command-ref/nix-env.xml2
-rw-r--r--doc/manual/command-ref/opt-common-syn.xml3
-rw-r--r--doc/manual/command-ref/opt-common.xml17
-rw-r--r--doc/manual/expressions/builtins.xml5
-rw-r--r--doc/manual/glossary/glossary.xml49
-rw-r--r--doc/manual/installation/prerequisites-source.xml11
-rw-r--r--doc/manual/introduction/about-nix.xml3
-rw-r--r--doc/manual/packages/garbage-collection.xml8
-rw-r--r--mk/programs.mk2
-rw-r--r--release.nix42
-rw-r--r--scripts/install-darwin-multi-user.sh831
-rw-r--r--scripts/install-multi-user.sh797
-rw-r--r--scripts/install-nix-from-closure.sh36
-rw-r--r--scripts/install-systemd-multi-user.sh154
-rw-r--r--src/build-remote/local.mk2
-rw-r--r--src/libexpr/nix-expr.pc.in2
-rw-r--r--src/libexpr/primops.cc17
-rw-r--r--src/libexpr/primops.hh8
-rw-r--r--src/libmain/nix-main.pc.in2
-rw-r--r--src/libstore/build.cc31
-rw-r--r--src/libstore/builtins/buildenv.cc87
-rw-r--r--src/libstore/download.cc10
-rw-r--r--src/libstore/globals.cc2
-rw-r--r--src/libstore/globals.hh8
-rw-r--r--src/libstore/local-store.cc12
-rw-r--r--src/libstore/local-store.hh4
-rw-r--r--src/libstore/nar-info-disk-cache.cc13
-rw-r--r--src/libstore/nix-store.pc.in4
-rwxr-xr-xsrc/nix-build/nix-build.cc2
-rw-r--r--src/nix-channel/local.mk2
-rw-r--r--src/nix-copy-closure/local.mk2
-rw-r--r--src/nix-daemon/nix-daemon.cc2
-rw-r--r--src/nix/edit.cc4
-rw-r--r--src/nix/repl.cc2
-rw-r--r--src/nix/search.cc56
-rw-r--r--tests/lang/eval-okay-regex-split.exp1
-rw-r--r--tests/nix-shell.sh9
-rw-r--r--tests/search.sh5
-rw-r--r--tests/shell.nix7
-rw-r--r--tests/shell.shebang.rb7
-rw-r--r--version2
43 files changed, 1411 insertions, 886 deletions
diff --git a/configure.ac b/configure.ac
index 7177c8914434..c41a83c97646 100644
--- a/configure.ac
+++ b/configure.ac
@@ -215,7 +215,7 @@ AC_SUBST(ENABLE_S3, [$enable_s3])
 AC_LANG_POP(C++)
 
 if test -n "$enable_s3"; then
-  declare -a aws_version_tokens=($(printf '#include <aws/core/VersionConfig.h>\nAWS_SDK_VERSION_STRING' | $CPP - | grep -v '^#.*' | sed 's/"//g' | tr '.' ' '))
+  declare -a aws_version_tokens=($(printf '#include <aws/core/VersionConfig.h>\nAWS_SDK_VERSION_STRING' | $CPP $CPPFLAGS - | grep -v '^#.*' | sed 's/"//g' | tr '.' ' '))
   AC_DEFINE_UNQUOTED([AWS_VERSION_MAJOR], ${aws_version_tokens@<:@0@:>@}, [Major version of aws-sdk-cpp.])
   AC_DEFINE_UNQUOTED([AWS_VERSION_MINOR], ${aws_version_tokens@<:@1@:>@}, [Minor version of aws-sdk-cpp.])
 fi
diff --git a/doc/manual/command-ref/conf-file.xml b/doc/manual/command-ref/conf-file.xml
index 6638bf61e454..431c0e6d3570 100644
--- a/doc/manual/command-ref/conf-file.xml
+++ b/doc/manual/command-ref/conf-file.xml
@@ -456,6 +456,36 @@ builtins.fetchurl {
 
   </varlistentry>
 
+  <varlistentry xml:id="conf-narinfo-cache-negative-ttl"><term><literal>narinfo-cache-negative-ttl</literal></term>
+
+    <listitem>
+
+      <para>The TTL in seconds for negative lookups. If a store path is
+      queried from a substituter but was not found, there will be a
+      negative lookup cached in the local disk cache database for the
+      specified duration.</para>
+
+    </listitem>
+
+  </varlistentry>
+
+  <varlistentry xml:id="conf-narinfo-cache-positive-ttl"><term><literal>narinfo-cache-positive-ttl</literal></term>
+
+    <listitem>
+
+      <para>The TTL in seconds for positive lookups. If a store path is
+      queried from a substituter, the result of the query will be cached
+      in the local disk cache database including some of the NAR
+      metadata. The default TTL is a month, setting a shorter TTL for
+      positive lookups can be useful for binary caches that have
+      frequent garbage collection, in which case having a more frequent
+      cache invalidation would prevent trying to pull the path again and
+      failing with a hash mismatch if the build isn't reproducible.
+      </para>
+
+    </listitem>
+
+  </varlistentry>
 
   <varlistentry xml:id="conf-netrc-file"><term><literal>netrc-file</literal></term>
 
@@ -511,7 +541,6 @@ password <replaceable>my-password</replaceable>
 
   </varlistentry>
 
-
   <varlistentry xml:id="conf-pre-build-hook"><term><literal>pre-build-hook</literal></term>
 
     <listitem>
@@ -788,7 +817,6 @@ password <replaceable>my-password</replaceable>
 
   </varlistentry>
 
-
 </variablelist>
 
 </para>
diff --git a/doc/manual/command-ref/nix-env.xml b/doc/manual/command-ref/nix-env.xml
index d4563ac47551..eac7739558be 100644
--- a/doc/manual/command-ref/nix-env.xml
+++ b/doc/manual/command-ref/nix-env.xml
@@ -456,7 +456,7 @@ $ nix-env -f ~/foo.nix -i '.*'</screen>
 from another profile:
 
 <screen>
-$ nix-env -i --from-profile /nix/var/nix/profiles/foo -i gcc</screen>
+$ nix-env -i --from-profile /nix/var/nix/profiles/foo gcc</screen>
 
 </para>
 
diff --git a/doc/manual/command-ref/opt-common-syn.xml b/doc/manual/command-ref/opt-common-syn.xml
index 168bef080f4f..b610b54b9620 100644
--- a/doc/manual/command-ref/opt-common-syn.xml
+++ b/doc/manual/command-ref/opt-common-syn.xml
@@ -9,6 +9,9 @@
   </group>
 </arg>
 <arg>
+  <arg choice='plain'><option>--quiet</option></arg>
+</arg>
+<arg>
   <group choice='plain'>
     <arg choice='plain'><option>--no-build-output</option></arg>
     <arg choice='plain'><option>-Q</option></arg>
diff --git a/doc/manual/command-ref/opt-common.xml b/doc/manual/command-ref/opt-common.xml
index bcb60b30125c..4c572e129445 100644
--- a/doc/manual/command-ref/opt-common.xml
+++ b/doc/manual/command-ref/opt-common.xml
@@ -75,6 +75,23 @@
 </varlistentry>
 
 
+<varlistentry><term><option>--quiet</option></term>
+
+  <listitem>
+
+  <para>Decreases the level of verbosity of diagnostic messages
+  printed on standard error.  This is the inverse option to
+  <option>-v</option> / <option>--verbose</option>.
+  </para>
+
+  <para>This option may be specified repeatedly.  See the previous
+  verbosity levels list.</para>
+
+  </listitem>
+
+</varlistentry>
+
+
 <varlistentry><term><option>--no-build-output</option> / <option>-Q</option></term>
 
   <listitem><para>By default, output written by builders to standard
diff --git a/doc/manual/expressions/builtins.xml b/doc/manual/expressions/builtins.xml
index 47b98460adf6..ac1fe7e2fafe 100644
--- a/doc/manual/expressions/builtins.xml
+++ b/doc/manual/expressions/builtins.xml
@@ -1203,7 +1203,10 @@ in foo</programlisting>
 
     This is not allowed because it would cause a cyclic dependency in
     the computation of the cryptographic hashes for
-    <varname>foo</varname> and <varname>bar</varname>.</para></listitem>
+    <varname>foo</varname> and <varname>bar</varname>.</para>
+    <para>It is also not possible to reference the result of a derivation. 
+    If you are using Nixpkgs, the <literal>writeTextFile</literal> function is able to 
+    do that.</para></listitem>
 
   </varlistentry>
 
diff --git a/doc/manual/glossary/glossary.xml b/doc/manual/glossary/glossary.xml
index e0636044cc25..4977825578f1 100644
--- a/doc/manual/glossary/glossary.xml
+++ b/doc/manual/glossary/glossary.xml
@@ -85,29 +85,48 @@
 
 <glossentry xml:id="gloss-reference"><glossterm>reference</glossterm>
 
-  <glossdef><para>A store path <varname>P</varname> is said to have a
-  reference to a store path <varname>Q</varname> if the store object
-  at <varname>P</varname> contains the path <varname>Q</varname>
-  somewhere.  This implies than an execution involving
-  <varname>P</varname> potentially needs <varname>Q</varname> to be
-  present.  The <emphasis>references</emphasis> of a store path are
-  the set of store paths to which it has a reference.</para></glossdef>
+  <glossdef>
+    <para>A store path <varname>P</varname> is said to have a
+    reference to a store path <varname>Q</varname> if the store object
+    at <varname>P</varname> contains the path <varname>Q</varname>
+    somewhere. The <emphasis>references</emphasis> of a store path are
+    the set of store paths to which it has a reference.
+    </para>
+    <para>A derivation can reference other derivations and sources
+    (but not output paths), whereas an output path only references other
+    output paths.
+    </para>
+  </glossdef>
 
 </glossentry>
 
+<glossentry xml:id="gloss-reachable"><glossterm>reachable</glossterm>
+
+  <glossdef><para>A store path <varname>Q</varname> is reachable from
+  another store path <varname>P</varname> if <varname>Q</varname> is in the
+  <link linkend="gloss-closure">closure</link> of the
+  <link linkend="gloss-reference">references</link> relation.
+  </para></glossdef>
+</glossentry>
 
 <glossentry xml:id="gloss-closure"><glossterm>closure</glossterm>
 
   <glossdef><para>The closure of a store path is the set of store
   paths that are directly or indirectly “reachable” from that store
   path; that is, it’s the closure of the path under the <link
-  linkend="gloss-reference">references</link> relation.  For instance,
-  if the store object at path <varname>P</varname> contains a
-  reference to path <varname>Q</varname>, then <varname>Q</varname> is
-  in the closure of <varname>P</varname>.  For correct deployment it
-  is necessary to deploy whole closures, since otherwise at runtime
-  files could be missing.  The command <command>nix-store
-  -qR</command> prints out closures of store paths.</para></glossdef>
+  linkend="gloss-reference">references</link> relation. For a package, the
+  closure of its derivation is equivalent to the build-time
+  dependencies, while the closure of its output path is equivalent to its
+  runtime dependencies. For correct deployment it is necessary to deploy whole
+  closures, since otherwise at runtime files could be missing. The command
+  <command>nix-store -qR</command> prints out closures of store paths.
+  </para>
+  <para>As an example, if the store object at path <varname>P</varname> contains
+  a reference to path <varname>Q</varname>, then <varname>Q</varname> is
+  in the closure of <varname>P</varname>. Further, if <varname>Q</varname>
+  references <varname>R</varname> then <varname>R</varname> is also in
+  the closure of <varname>P</varname>.
+  </para></glossdef>
 
 </glossentry>
 
@@ -147,7 +166,7 @@
   linkend="sec-profiles" />.</para>
 
   </glossdef>
-  
+
 </glossentry>
 
 
diff --git a/doc/manual/installation/prerequisites-source.xml b/doc/manual/installation/prerequisites-source.xml
index 49660c36e397..01e9688d635f 100644
--- a/doc/manual/installation/prerequisites-source.xml
+++ b/doc/manual/installation/prerequisites-source.xml
@@ -9,6 +9,9 @@
 <itemizedlist>
 
   <listitem><para>GNU Make.</para></listitem>
+  
+  <listitem><para>Bash Shell. The <literal>./configure</literal> script
+  relies on bashisms, so Bash is required.</para></listitem>
 
   <listitem><para>A version of GCC or Clang that supports C++14.</para></listitem>
 
@@ -28,6 +31,14 @@
   distribution does not provide these, you can obtain bzip2 from <link
   xlink:href="http://www.bzip.org/"/>.</para></listitem>
 
+  <listitem><para><literal>liblzma</literal>, which is provided by
+  XZ Utils. If your distribution does not provide this, you can
+  get it from <link xlink:href="https://tukaani.org/xz/"/>.</para></listitem>
+  
+  <listitem><para>cURL and its library. If your distribution does not
+  provide it, you can get it from <link
+  xlink:href="https://curl.haxx.se/"/>.</para></listitem>
+      
   <listitem><para>The SQLite embedded database library, version 3.6.19
   or higher.  If your distribution does not provide it, please install
   it from <link xlink:href="http://www.sqlite.org/" />.</para></listitem>
diff --git a/doc/manual/introduction/about-nix.xml b/doc/manual/introduction/about-nix.xml
index 83a2b6786ac0..e8c0a29753a1 100644
--- a/doc/manual/introduction/about-nix.xml
+++ b/doc/manual/introduction/about-nix.xml
@@ -60,7 +60,8 @@ This is because tools such as compilers don’t search in per-packages
 directories such as
 <filename>/nix/store/5lbfaxb722zp…-openssl-0.9.8d/include</filename>,
 so if a package builds correctly on your system, this is because you
-specified the dependency explicitly.</para>
+specified the dependency explicitly. This takes care of the build-time
+dependencies.</para>
 
 <para>Once a package is built, runtime dependencies are found by
 scanning binaries for the hash parts of Nix store paths (such as
diff --git a/doc/manual/packages/garbage-collection.xml b/doc/manual/packages/garbage-collection.xml
index 03b8e4c976c1..a1b0ef22a11e 100644
--- a/doc/manual/packages/garbage-collection.xml
+++ b/doc/manual/packages/garbage-collection.xml
@@ -52,6 +52,14 @@ garbage collector as follows:
 <screen>
 $ nix-store --gc</screen>
 
+The behaviour of the gargage collector is affected by the <literal>keep-
+derivations</literal> (default: true) and <literal>keep-outputs</literal>
+(default: false) options in the Nix configuration file. The defaults will ensure
+that all derivations that are not build-time dependencies of garbage collector roots
+will be collected but that all output paths that are not runtime dependencies
+will be collected. (This is usually what you want, but while you are developing
+it may make sense to keep outputs to ensure that rebuild times are quick.)
+
 If you are feeling uncertain, you can also first view what files would
 be deleted:
 
diff --git a/mk/programs.mk b/mk/programs.mk
index 3ac64494e3a5..2fbda12bd153 100644
--- a/mk/programs.mk
+++ b/mk/programs.mk
@@ -51,7 +51,7 @@ define build-program
   else
 
     $(DESTDIR)$$($(1)_INSTALL_PATH): $$($(1)_PATH) | $(DESTDIR)$$($(1)_INSTALL_DIR)/
-	install -t $$($(1)_INSTALL_DIR) $$<
+	install -t $(DESTDIR)$$($(1)_INSTALL_DIR) $$<
 
   endif
 
diff --git a/release.nix b/release.nix
index 91c782c51916..37deb8e7ee38 100644
--- a/release.nix
+++ b/release.nix
@@ -28,8 +28,8 @@ let
         configureFlags = "--enable-gc";
 
         postUnpack = ''
-          (cd source && find . -type f) | cut -c3- > source/.dist-files
-          cat source/.dist-files
+          (cd $sourceRoot && find . -type f) | cut -c3- > $sourceRoot/.dist-files
+          cat $sourceRoot/.dist-files
         '';
 
         preConfigure = ''
@@ -127,17 +127,39 @@ let
           substitute ${./scripts/install-nix-from-closure.sh} $TMPDIR/install \
             --subst-var-by nix ${toplevel} \
             --subst-var-by cacert ${cacert}
-          substitute ${./scripts/install-darwin-multi-user.sh} $TMPDIR/install-darwin-multi-user \
+
+          substitute ${./scripts/install-darwin-multi-user.sh} $TMPDIR/install-darwin-multi-user.sh \
+            --subst-var-by nix ${toplevel} \
+            --subst-var-by cacert ${cacert}
+          substitute ${./scripts/install-systemd-multi-user.sh} $TMPDIR/install-systemd-multi-user.sh \
+            --subst-var-by nix ${toplevel} \
+            --subst-var-by cacert ${cacert}
+          substitute ${./scripts/install-multi-user.sh} $TMPDIR/install-multi-user \
             --subst-var-by nix ${toplevel} \
             --subst-var-by cacert ${cacert}
 
           if type -p shellcheck; then
-            shellcheck -e SC1090 $TMPDIR/install
-            shellcheck -e SC1091,SC2002 $TMPDIR/install-darwin-multi-user
+            # SC1090: Don't worry about not being able to find
+            #         $nix/etc/profile.d/nix.sh
+            shellcheck --exclude SC1090 $TMPDIR/install
+            shellcheck $TMPDIR/install-darwin-multi-user.sh
+            shellcheck $TMPDIR/install-systemd-multi-user.sh
+
+            # SC1091: Don't panic about not being able to source
+            #         /etc/profile
+            # SC2002: Ignore "useless cat" "error", when loading
+            #         .reginfo, as the cat is a much cleaner
+            #         implementation, even though it is "useless"
+            # SC2116: Allow ROOT_HOME=$(echo ~root) for resolving
+            #         root's home directory
+            shellcheck --external-sources \
+              --exclude SC1091,SC2002,SC2116 $TMPDIR/install-multi-user
           fi
 
           chmod +x $TMPDIR/install
-          chmod +x $TMPDIR/install-darwin-multi-user
+          chmod +x $TMPDIR/install-darwin-multi-user.sh
+          chmod +x $TMPDIR/install-systemd-multi-user.sh
+          chmod +x $TMPDIR/install-multi-user
           dir=nix-${version}-${system}
           fn=$out/$dir.tar.bz2
           mkdir -p $out/nix-support
@@ -149,7 +171,9 @@ let
             --transform "s,$TMPDIR/install,$dir/install," \
             --transform "s,$TMPDIR/reginfo,$dir/.reginfo," \
             --transform "s,$NIX_STORE,$dir/store,S" \
-            $TMPDIR/install $TMPDIR/install-darwin-multi-user $TMPDIR/reginfo \
+            $TMPDIR/install $TMPDIR/install-darwin-multi-user.sh \
+            $TMPDIR/install-systemd-multi-user.sh \
+            $TMPDIR/install-multi-user $TMPDIR/reginfo \
             $(cat ${installerClosureInfo}/store-paths)
         '');
 
@@ -182,7 +206,6 @@ let
       };
 
 
-    rpm_fedora27i386 = makeRPM_i686 (diskImageFuns: diskImageFuns.fedora27i386) [ ];
     rpm_fedora27x86_64 = makeRPM_x86_64 (diskImageFunsFun: diskImageFunsFun.fedora27x86_64) [ ];
 
 
@@ -245,7 +268,8 @@ let
           export NIX_STATE_DIR=$TMPDIR
           nix-store --init
 
-          nix-instantiate ${nixpkgs}/nixos/release-combined.nix -A tested --dry-run
+          nix-instantiate ${nixpkgs}/nixos/release-combined.nix -A tested --dry-run \
+            --arg nixpkgs '{ outPath = ${nixpkgs}; revCount = 123; shortRev = "abcdefgh"; }'
 
           touch $out
         '';
diff --git a/scripts/install-darwin-multi-user.sh b/scripts/install-darwin-multi-user.sh
index 716b6e9bc9a3..87c4c2b0582a 100644
--- a/scripts/install-darwin-multi-user.sh
+++ b/scripts/install-darwin-multi-user.sh
@@ -1,819 +1,144 @@
-#!/bin/bash
+#!/usr/bin/env bash
 
 set -eu
 set -o pipefail
 
-# Sourced from:
-# - https://github.com/LnL7/nix-darwin/blob/8c29d0985d74b4a990238497c47a2542a5616b3c/bootstrap.sh
-# - https://gist.github.com/expipiplus1/e571ce88c608a1e83547c918591b149f/ac504c6c1b96e65505fbda437a28ce563408ecb0
-# - https://github.com/NixOS/nixos-org-configurations/blob/a122f418797713d519aadf02e677fce0dc1cb446/delft/scripts/nix-mac-installer.sh
-# - https://github.com/matthewbauer/macNixOS/blob/f6045394f9153edea417be90c216788e754feaba/install-macNixOS.sh
-# - https://gist.github.com/LnL7/9717bd6cdcb30b086fd7f2093e5f8494/86b26f852ce563e973acd30f796a9a416248c34a
-#
-# however tracking which bits came from which would be impossible.
-
-readonly ESC='\033[0m'
-readonly BOLD='\033[38;1m'
-readonly BLUE='\033[38;34m'
-readonly BLUE_UL='\033[38;4;34m'
-readonly GREEN='\033[38;32m'
-readonly GREEN_UL='\033[38;4;32m'
-readonly RED='\033[38;31m'
-readonly RED_UL='\033[38;4;31m'
-readonly YELLOW='\033[38;33m'
-readonly YELLOW_UL='\033[38;4;33m'
-
-readonly CORES=$(sysctl -n hw.ncpu)
-readonly NIX_USER_COUNT="32"
-readonly NIX_BUILD_GROUP_ID="30000"
-readonly NIX_BUILD_GROUP_NAME="nixbld"
-readonly NIX_FIRST_BUILD_UID="30001"
-# Please don't change this. We don't support it, because the
-# default shell profile that comes with Nix doesn't support it.
-readonly NIX_ROOT="/nix"
 readonly PLIST_DEST=/Library/LaunchDaemons/org.nixos.nix-daemon.plist
 
-readonly PROFILE_TARGETS=("/etc/bashrc" "/etc/zshrc")
-readonly PROFILE_BACKUP_SUFFIX=".backup-before-nix"
-readonly PROFILE_NIX_FILE="$NIX_ROOT/var/nix/profiles/default/etc/profile.d/nix-daemon.sh"
-
-readonly NIX_INSTALLED_NIX="@nix@"
-readonly NIX_INSTALLED_CACERT="@cacert@"
-readonly EXTRACTED_NIX_PATH="$(dirname "$0")"
-
-readonly ROOT_HOME="/var/root"
-
-if [ -t 0 ]; then
-    readonly IS_HEADLESS='no'
-else
-    readonly IS_HEADLESS='yes'
-fi
+dsclattr() {
+    /usr/bin/dscl . -read "$1" \
+        | awk "/$2/ { print \$2 }"
+}
 
-headless() {
-    if [ "$IS_HEADLESS" = "yes" ]; then
-        return 0
-    else
-        return 1
+poly_validate_assumptions() {
+    if [ "$(uname -s)" != "Darwin" ]; then
+        failure "This script is for use with macOS!"
     fi
 }
 
-contactme() {
-    echo "We'd love to help if you need it."
-    echo ""
-    echo "If you can, open an issue at https://github.com/nixos/nix/issues"
-    echo ""
-    echo "Or feel free to contact the team,"
-    echo " - on IRC #nixos on irc.freenode.net"
-    echo " - on twitter @nixos_org"
+poly_service_installed_check() {
+    [ -e "$PLIST_DEST" ]
 }
 
-uninstall_directions() {
-    subheader "Uninstalling nix:"
-    local step=0
-
-    if [ -e "$PLIST_DEST" ]; then
-        step=$((step + 1))
+poly_service_uninstall_directions() {
         cat <<EOF
-$step. Delete $PLIST_DEST
+$1. Delete $PLIST_DEST
 
   sudo launchctl unload $PLIST_DEST
   sudo rm $PLIST_DEST
 
 EOF
-    fi
-
-    for profile_target in "${PROFILE_TARGETS[@]}"; do
-        if [ -e "$profile_target" ] && [ -e "$profile_target$PROFILE_BACKUP_SUFFIX" ]; then
-            step=$((step + 1))
-            cat <<EOF
-$step. Restore $profile_target$PROFILE_BACKUP_SUFFIX back to $profile_target
-
-  sudo mv $profile_target$PROFILE_BACKUP_SUFFIX $profile_target
-
-(after this one, you may need to re-open any terminals that were
-opened while it existed.)
-
-EOF
-        fi
-    done
+}
 
-    step=$((step + 1))
+poly_service_setup_note() {
     cat <<EOF
-$step. Delete the files Nix added to your system:
-
-  sudo rm -rf /etc/nix $NIX_ROOT $ROOT_HOME/.nix-profile $ROOT_HOME/.nix-defexpr $ROOT_HOME/.nix-channels $HOME/.nix-profile $HOME/.nix-defexpr $HOME/.nix-channels
-
-and that is it.
+ - load and start a LaunchDaemon (at $PLIST_DEST) for nix-daemon
 
 EOF
-
-}
-
-nix_user_for_core() {
-    printf "nixbld%d" "$1"
-}
-
-nix_uid_for_core() {
-    echo $((NIX_FIRST_BUILD_UID + $1 - 1))
 }
 
-dsclattr() {
-    /usr/bin/dscl . -read "$1" \
-        | awk "/$2/ { print \$2 }"
-}
-
-_textout() {
-    echo -en "$1"
-    shift
-    if [ "$*" = "" ]; then
-        cat
-    else
-        echo "$@"
-    fi
-    echo -en "$ESC"
-}
-
-header() {
-    follow="---------------------------------------------------------"
-    header=$(echo "---- $* $follow$follow$follow" | head -c 80)
-    echo ""
-    _textout "$BLUE" "$header"
-}
-
-warningheader() {
-    follow="---------------------------------------------------------"
-    header=$(echo "---- $* $follow$follow$follow" | head -c 80)
-    echo ""
-    _textout "$RED" "$header"
-}
-
-subheader() {
-    echo ""
-    _textout "$BLUE_UL" "$*"
-}
-
-row() {
-    printf "$BOLD%s$ESC:\\t%s\\n" "$1" "$2"
-}
-
-task() {
-    echo ""
-    ok "~~> $1"
-}
-
-bold() {
-    echo "$BOLD$*$ESC"
-}
-
-ok() {
-    _textout "$GREEN" "$@"
-}
-
-warning() {
-    warningheader "warning!"
-    cat
-    echo ""
-}
-
-failure() {
-    header "oh no!"
-    _textout "$RED" "$@"
-    echo ""
-    _textout "$RED" "$(contactme)"
-    trap finish_cleanup EXIT
-    exit 1
-}
-
-ui_confirm() {
-    _textout "$GREEN$GREEN_UL" "$1"
-
-    if headless; then
-        echo "No TTY, assuming you would say yes :)"
-        return 0
-    fi
-
-    local prompt="[y/n] "
-    echo -n "$prompt"
-    while read -r y; do
-        if [ "$y" = "y" ]; then
-            echo ""
-            return 0
-        elif [ "$y" = "n" ]; then
-            echo ""
-            return 1
-        else
-            _textout "$RED" "Sorry, I didn't understand. I can only understand answers of y or n"
-            echo -n "$prompt"
-        fi
-    done
-    echo ""
-    return 1
-}
-
-__sudo() {
-    local expl="$1"
-    local cmd="$2"
-    shift
-    header "sudo execution"
-
-    echo "I am executing:"
-    echo ""
-    printf "    $ sudo %s\\n" "$cmd"
-    echo ""
-    echo "$expl"
-    echo ""
-
-    return 0
-}
+poly_configure_nix_daemon_service() {
+    _sudo "to set up the nix-daemon as a LaunchDaemon" \
+          ln -sfn "/nix/var/nix/profiles/default$PLIST_DEST" "$PLIST_DEST"
 
-_sudo() {
-    local expl="$1"
-    shift
-    if ! headless; then
-        __sudo "$expl" "$*"
-    fi
-    sudo "$@"
-}
+    _sudo "to load the LaunchDaemon plist for nix-daemon" \
+          launchctl load /Library/LaunchDaemons/org.nixos.nix-daemon.plist
 
+    _sudo "to start the nix-daemon" \
+          launchctl start org.nixos.nix-daemon
 
-readonly SCRATCH=$(mktemp -d -t tmp.XXXXXXXXXX)
-function finish_cleanup {
-    rm -rf "$SCRATCH"
 }
 
-function finish_fail {
-    finish_cleanup
-
-    failure <<EOF
-Jeeze, something went wrong. If you can take all the output and open
-an issue, we'd love to fix the problem so nobody else has this issue.
-
-:(
-EOF
+poly_group_exists() {
+    /usr/bin/dscl . -read "/Groups/$1" > /dev/null 2>&1
 }
-trap finish_fail EXIT
-
-function finish_success {
-    finish_cleanup
 
-    ok "Alright! We're done!"
-    cat <<EOF
-
-Before Nix will work in your existing shells, you'll need to close
-them and open them again. Other than that, you should be ready to go.
-
-Try it! Open a new terminal, and type:
-
-  $ nix-shell -p nix-info --run "nix-info -m"
-
-Thank you for using this installer. If you have any feedback, don't
-hesitate:
-
-$(contactme)
-EOF
+poly_group_id_get() {
+    dsclattr "/Groups/$1" "PrimaryGroupID"
 }
 
-
-validate_starting_assumptions() {
-    if [ "$(uname -s)" != "Darwin" ]; then
-        failure "This script is for use with macOS!"
-    fi
-
-    if [ $EUID -eq 0 ]; then
-        failure <<EOF
-Please do not run this script with root privileges. We will call sudo
-when we need to.
-EOF
-    fi
-
-    if type nix-env 2> /dev/null >&2; then
-        failure <<EOF
-Nix already appears to be installed, and this tool assumes it is
-_not_ yet installed.
-
-$(uninstall_directions)
-EOF
-    fi
-
-    if [ "${NIX_REMOTE:-}" != "" ]; then
-        failure <<EOF
-For some reason, \$NIX_REMOTE is set. It really should not be set
-before this installer runs, and it hints that Nix is currently
-installed. Please delete the old Nix installation and start again.
-
-Note: You might need to close your shell window and open a new shell
-to clear the variable.
-EOF
-    fi
-
-    if echo "${SSL_CERT_FILE:-}" | grep -qE "(nix/var/nix|nix-profile)"; then
-        failure <<EOF
-It looks like \$SSL_CERT_FILE is set to a path that used to be part of
-the old Nix installation. Please unset that variable and try again:
-
-  $ unset SSL_CERT_FILE
-
-EOF
-    fi
-
-    for file in ~/.bash_profile ~/.bash_login ~/.profile ~/.zshenv ~/.zprofile ~/.zshrc ~/.zlogin; do
-        if [ -f "$file" ]; then
-            if grep -l "^[^#].*.nix-profile" "$file"; then
-                failure <<EOF
-I found a reference to a ".nix-profile" in $file.
-This has a high chance of breaking a new nix installation. It was most
-likely put there by a previous Nix installer.
-
-Please remove this reference and try running this again. You should
-also look for similar references in:
-
- - ~/.bash_profile
- - ~/.bash_login
- - ~/.profile
-
-or other shell init files that you may have.
-
-$(uninstall_directions)
-EOF
-            fi
-        fi
-    done
-
-    if [ -d /nix ]; then
-        failure <<EOF
-There are some relics of a previous installation of Nix at /nix, and
-this scripts assumes Nix is _not_ yet installed. Please delete the old
-Nix installation and start again.
-
-$(uninstall_directions)
-EOF
-    fi
-
-    if [ -d /etc/nix ]; then
-        failure <<EOF
-There are some relics of a previous installation of Nix at /etc/nix, and
-this scripts assumes Nix is _not_ yet installed. Please delete the old
-Nix installation and start again.
-
-$(uninstall_directions)
-EOF
-    fi
-
-    for profile_target in "${PROFILE_TARGETS[@]}"; do
-        if [ -e "$profile_target$PROFILE_BACKUP_SUFFIX" ]; then
-        failure <<EOF
-When this script runs, it backs up the current $profile_target to
-$profile_target$PROFILE_BACKUP_SUFFIX. This backup file already exists, though.
-
-Please follow these instructions to clean up the old backup file:
-
-1. Copy $profile_target and $profile_target$PROFILE_BACKUP_SUFFIX to another place, just
-in case.
-
-2. Take care to make sure that $profile_target$PROFILE_BACKUP_SUFFIX doesn't look like
-it has anything nix-related in it. If it does, something is probably
-quite wrong. Please open an issue or get in touch immediately.
-
-3. Take care to make sure that $profile_target doesn't look like it has
-anything nix-related in it. If it does, and $profile_target _did not_,
-run:
-
-  $ /usr/bin/sudo /bin/mv $profile_target$PROFILE_BACKUP_SUFFIX $profile_target
-
-and try again.
-EOF
-        fi
-
-        if grep -qi "nix" "$profile_target"; then
-            failure <<EOF
-It looks like $profile_target already has some Nix configuration in
-there. There should be no reason to run this again. If you're having
-trouble, please open an issue.
-EOF
-        fi
-    done
-
-    danger_paths=("$ROOT_HOME/.nix-defexpr" "$ROOT_HOME/.nix-channels" "$ROOT_HOME/.nix-profile")
-    for danger_path in "${danger_paths[@]}"; do
-        if _sudo "making sure that $danger_path doesn't exist" \
-           test -e "$danger_path"; then
-            failure <<EOF
-I found a file at $danger_path, which is a relic of a previous
-installation. You must first delete this file before continuing.
-
-$(uninstall_directions)
-EOF
-        fi
-    done
+poly_create_build_group() {
+    _sudo "Create the Nix build group, $NIX_BUILD_GROUP_NAME" \
+          /usr/sbin/dseditgroup -o create \
+          -r "Nix build group for nix-daemon" \
+          -i "$NIX_BUILD_GROUP_ID" \
+          "$NIX_BUILD_GROUP_NAME" >&2
 }
 
-setup_report() {
-    header "hardware report"
-    row "           Cores" "$CORES"
-
-    header "Nix config report"
-    row "        Temp Dir" "$SCRATCH"
-    row "        Nix Root" "$NIX_ROOT"
-    row "     Build Users" "$NIX_USER_COUNT"
-    row "  Build Group ID" "$NIX_BUILD_GROUP_ID"
-    row "Build Group Name" "$NIX_BUILD_GROUP_NAME"
-    if [ "${ALLOW_PREEXISTING_INSTALLATION:-}" != "" ]; then
-        row "Preexisting Install" "Allowed"
-    fi
-
-    subheader "build users:"
-
-    row "    Username" "UID"
-    for i in $(seq 1 "$NIX_USER_COUNT"); do
-        row "     $(nix_user_for_core "$i")" "$(nix_uid_for_core "$i")"
-    done
-    echo ""
+poly_user_exists() {
+    /usr/bin/dscl . -read "/Users/$1" > /dev/null 2>&1
 }
 
-create_build_group() {
-    local primary_group_id
-
-    task "Setting up the build group $NIX_BUILD_GROUP_NAME"
-    if ! /usr/bin/dscl . -read "/Groups/$NIX_BUILD_GROUP_NAME" > /dev/null 2>&1; then
-        _sudo "Create the Nix build group, $NIX_BUILD_GROUP_NAME" \
-              /usr/sbin/dseditgroup -o create \
-              -r "Nix build group for nix-daemon" \
-              -i "$NIX_BUILD_GROUP_ID" \
-              "$NIX_BUILD_GROUP_NAME" >&2
-        row "            Created" "Yes"
-    else
-        primary_group_id=$(dsclattr "/Groups/$NIX_BUILD_GROUP_NAME" "PrimaryGroupID")
-        if [ "$primary_group_id" -ne "$NIX_BUILD_GROUP_ID" ]; then
-            failure <<EOF
-It seems the build group $NIX_BUILD_GROUP_NAME already exists, but
-with the UID $primary_group_id. This script can't really handle
-that right now, so I'm going to give up.
-
-You can fix this by editing this script and changing the
-NIX_BUILD_GROUP_ID variable near the top to from $NIX_BUILD_GROUP_ID
-to $primary_group_id and re-run.
-EOF
-        else
-            row "            Exists" "Yes"
-        fi
-    fi
+poly_user_id_get() {
+    dsclattr "/Users/$1" "UniqueID"
 }
 
-create_build_user_for_core() {
-    local coreid
-    local username
-    local uid
-
-    coreid="$1"
-    username=$(nix_user_for_core "$coreid")
-    uid=$(nix_uid_for_core "$coreid")
-    dsclpath="/Users/$username"
-
-    task "Setting up the build user $username"
-
-    if ! /usr/bin/dscl . -read "$dsclpath" > /dev/null 2>&1; then
-        _sudo "Creating the Nix build user, $username" \
-              /usr/bin/dscl . create "$dsclpath" \
-              UniqueID "${uid}"
-        row "           Created" "Yes"
-    else
-        actual_uid=$(dsclattr "$dsclpath" "UniqueID")
-        if [ "$actual_uid" -ne "$uid" ]; then
-            failure <<EOF
-It seems the build user $username already exists, but with the UID
-with the UID $actual_uid. This script can't really handle that right
-now, so I'm going to give up.
-
-If you already created the users and you know they start from
-$actual_uid and go up from there, you can edit this script and change
-NIX_FIRST_BUILD_UID near the top of the file to $actual_uid and try
-again.
-EOF
-        else
-            row "            Exists" "Yes"
-        fi
-    fi
-
-    if [ "$(dsclattr "$dsclpath" "IsHidden")" = "1" ]; then
-        row "          IsHidden" "Yes"
-    else
-        _sudo "in order to make $username a hidden user" \
-              /usr/bin/dscl . -create "$dsclpath" "IsHidden" "1"
-        row "          IsHidden" "Yes"
-    fi
-
-    if [ "$(dsclattr "$dsclpath" "NFSHomeDirectory")" = "/var/empty" ]; then
-        row "          NFSHomeDirectory" "/var/empty"
-    else
-        _sudo "in order to give $username a safe home directory" \
-              /usr/bin/dscl . -create "$dsclpath" "NFSHomeDirectory" "/var/empty"
-        row "          NFSHomeDirectory" "/var/empty"
-    fi
-
-    if [ "$(dsclattr "$dsclpath" "RealName")" = "Nix build user $coreid" ]; then
-        row "          RealName" "Nix build user $coreid"
-    else
-        _sudo "in order to give $username a useful name" \
-              /usr/bin/dscl . -create "$dsclpath" "RealName" "Nix build user $coreid"
-        row "          RealName" "Nix build user $coreid"
-    fi
-
-    if [ "$(dsclattr "$dsclpath" "UserShell")" = "/sbin/nologin" ]; then
-        row "   Logins Disabled" "Yes"
-    else
-        _sudo "in order to prevent $username from logging in" \
-              /usr/bin/dscl . -create "$dsclpath" "UserShell" "/sbin/nologin"
-        row "   Logins Disabled" "Yes"
-    fi
-
-    if dseditgroup -o checkmember -m "$username" "$NIX_BUILD_GROUP_NAME" > /dev/null 2>&1 ; then
-        row "  Member of $NIX_BUILD_GROUP_NAME" "Yes"
-    else
-        _sudo "Add $username to the $NIX_BUILD_GROUP_NAME group"\
-            /usr/sbin/dseditgroup -o edit -t user \
-            -a "$username" "$NIX_BUILD_GROUP_NAME"
-        row "  Member of $NIX_BUILD_GROUP_NAME" "Yes"
-    fi
-
-    if [ "$(dsclattr "$dsclpath" "PrimaryGroupID")" = "$NIX_BUILD_GROUP_ID" ]; then
-        row "    PrimaryGroupID" "$NIX_BUILD_GROUP_ID"
-    else
-        _sudo "to let the nix daemon use this user for builds (this might seem redundant, but there are two concepts of group membership)" \
-              /usr/bin/dscl . -create "$dsclpath" "PrimaryGroupID" "$NIX_BUILD_GROUP_ID"
-        row "    PrimaryGroupID" "$NIX_BUILD_GROUP_ID"
-
-    fi
+poly_user_hidden_get() {
+    dsclattr "/Users/$1" "IsHidden"
 }
 
-create_build_users() {
-    for i in $(seq 1 "$NIX_USER_COUNT"); do
-        create_build_user_for_core "$i"
-    done
+poly_user_hidden_set() {
+    _sudo "in order to make $1 a hidden user" \
+          /usr/bin/dscl . -create "/Users/$1" "IsHidden" "1"
 }
 
-create_directories() {
-    _sudo "to make the basic directory structure of Nix (part 1)" \
-          mkdir -pv -m 0755 /nix /nix/var /nix/var/log /nix/var/log/nix /nix/var/log/nix/drvs /nix/var/nix{,/db,/gcroots,/profiles,/temproots,/userpool}
-
-    _sudo "to make the basic directory structure of Nix (part 2)" \
-          mkdir -pv -m 1777 /nix/var/nix/{gcroots,profiles}/per-user
-
-    _sudo "to make the basic directory structure of Nix (part 3)" \
-          mkdir -pv -m 1775 /nix/store
-
-    _sudo "to make the basic directory structure of Nix (part 4)" \
-          chgrp "$NIX_BUILD_GROUP_NAME" /nix/store
-
-    _sudo "to set up the root user's profile (part 1)" \
-          mkdir -pv -m 0755 /nix/var/nix/profiles/per-user/root
-
-    _sudo "to set up the root user's profile (part 2)" \
-          mkdir -pv -m 0700 "$ROOT_HOME/.nix-defexpr"
-
-    _sudo "to place the default nix daemon configuration (part 1)" \
-          mkdir -pv -m 0555 /etc/nix
+poly_user_home_get() {
+    dsclattr "/Users/$1" "NFSHomeDirectory"
 }
 
-place_channel_configuration() {
-    echo "https://nixos.org/channels/nixpkgs-unstable nixpkgs" > "$SCRATCH/.nix-channels"
-    _sudo "to set up the default system channel (part 1)" \
-          install -m 0664 "$SCRATCH/.nix-channels" "$ROOT_HOME/.nix-channels"
+poly_user_home_set() {
+    _sudo "in order to give $1 a safe home directory" \
+          /usr/bin/dscl . -create "/Users/$1" "NFSHomeDirectory" "$2"
 }
 
-welcome_to_nix() {
-    ok "Welcome to the Multi-User Nix Installation"
-
-    cat <<EOF
-
-This installation tool will set up your computer with the Nix package
-manager. This will happen in a few stages:
-
-1. Make sure your computer doesn't already have Nix. If it does, I
-   will show you instructions on how to clean up your old one.
-
-2. Show you what we are going to install and where. Then we will ask
-   if you are ready to continue.
-
-3. Create the system users and groups that the Nix daemon uses to run
-   builds.
-
-4. Perform the basic installation of the Nix files daemon.
-
-5. Configure your shell to import special Nix Profile files, so you
-   can use Nix.
-
-6. Start the Nix daemon.
-
-EOF
-
-    if ui_confirm "Would you like to see a more detailed list of what we will do?"; then
-        cat <<EOF
-
-We will:
-
- - make sure your computer doesn't already have Nix files
-   (if it does, I  will tell you how to clean them up.)
- - create local users (see the list above for the users we'll make)
- - create a local group ($NIX_BUILD_GROUP_NAME)
- - install Nix in to $NIX_ROOT
- - create a configuration file in /etc/nix
- - set up the "default profile" by creating some Nix-related files in
-   $ROOT_HOME
-EOF
-        for profile_target in "${PROFILE_TARGETS[@]}"; do
-            if [ -e "$profile_target" ]; then
-                cat <<EOF
- - back up $profile_target to $profile_target$PROFILE_BACKUP_SUFFIX
- - update $profile_target to include some Nix configuration
-EOF
-            fi
-        done
-        cat <<EOF
- - load and start a LaunchDaemon (at $PLIST_DEST) for nix-daemon
-
-EOF
-        if ! ui_confirm "Ready to continue?"; then
-            failure <<EOF
-Okay, maybe you would like to talk to the team.
-EOF
-        fi
-    fi
+poly_user_note_get() {
+    dsclattr "/Users/$1" "RealName"
 }
 
-chat_about_sudo() {
-    header "let's talk about sudo"
-
-    if headless; then
-        cat <<EOF
-This script is going to call sudo a lot. Normally, it would show you
-exactly what commands it is running and why. However, the script is
-run in a headless fashion, like this:
-
-  $ curl https://nixos.org/nix/install | sh
-
-or maybe in a CI pipeline. Because of that, we're going to skip the
-verbose output in the interest of brevity.
-
-If you would like to
-see the output, try like this:
-
-  $ curl -o install-nix https://nixos.org/nix/install
-  $ sh ./install-nix
-
-EOF
-        return 0
-    fi
-
-    cat <<EOF
-This script is going to call sudo a lot. Every time we do, it'll
-output exactly what it'll do, and why.
-
-Just like this:
-EOF
-
-    __sudo "to demonstrate how our sudo prompts look" \
-           echo "this is a sudo prompt"
-
-    cat <<EOF
-
-This might look scary, but everything can be undone by running just a
-few commands. We used to ask you to confirm each time sudo ran, but it
-was too many times. Instead, I'll just ask you this one time:
-
-EOF
-    if ui_confirm "Can we use sudo?"; then
-        ok "Yay! Thanks! Let's get going!"
-    else
-        failure <<EOF
-That is okay, but we can't install.
-EOF
-    fi
+poly_user_note_set() {
+    _sudo "in order to give $username a useful note" \
+          /usr/bin/dscl . -create "/Users/$1" "RealName" "$2"
 }
 
-install_from_extracted_nix() {
-    (
-        cd "$EXTRACTED_NIX_PATH"
-
-        _sudo "to copy the basic Nix files to the new store at $NIX_ROOT/store" \
-              rsync -rlpt ./store/* "$NIX_ROOT/store/"
-
-        if [ -d "$NIX_INSTALLED_NIX" ]; then
-            echo "      Alright! We have our first nix at $NIX_INSTALLED_NIX"
-        else
-            failure <<EOF
-Something went wrong, and I didn't find Nix installed at
-$NIX_INSTALLED_NIX.
-EOF
-        fi
-
-        _sudo "to initialize the Nix Database" \
-              $NIX_INSTALLED_NIX/bin/nix-store --init
-
-        cat ./.reginfo \
-            | _sudo "to load data for the first time in to the Nix Database" \
-                   "$NIX_INSTALLED_NIX/bin/nix-store" --load-db
-
-        echo "      Just finished getting the nix database ready."
-    )
+poly_user_shell_get() {
+    dsclattr "/Users/$1" "UserShell"
 }
 
-shell_source_lines() {
-    cat <<EOF
-
-# Nix
-if [ -e '$PROFILE_NIX_FILE' ]; then
-  . '$PROFILE_NIX_FILE'
-fi
-# End Nix
-
-EOF
+poly_user_shell_set() {
+    _sudo "in order to give $1 a safe home directory" \
+          /usr/bin/dscl . -create "/Users/$1" "UserShell" "$2"
 }
-configure_shell_profile() {
-    for profile_target in "${PROFILE_TARGETS[@]}"; do
-        if [ -e "$profile_target" ]; then
-            _sudo "to back up your current $profile_target to $profile_target$PROFILE_BACKUP_SUFFIX" \
-                  cp "$profile_target" "$profile_target$PROFILE_BACKUP_SUFFIX"
-
-            shell_source_lines \
-                | _sudo "extend your $profile_target with nix-daemon settings" \
-                        tee -a "$profile_target"
-        fi
-    done
 
+poly_user_in_group_check() {
+    username=$1
+    group=$2
+    dseditgroup -o checkmember -m "$username" "$group" > /dev/null 2>&1
 }
 
-setup_default_profile() {
-    _sudo "to installing a bootstrapping Nix in to the default Profile" \
-          HOME=$ROOT_HOME "$NIX_INSTALLED_NIX/bin/nix-env" -i "$NIX_INSTALLED_NIX"
-
-    _sudo "to installing a bootstrapping SSL certificate just for Nix in to the default Profile" \
-          HOME=$ROOT_HOME "$NIX_INSTALLED_NIX/bin/nix-env" -i "$NIX_INSTALLED_CACERT"
+poly_user_in_group_set() {
+    username=$1
+    group=$2
 
-    _sudo "to update the default channel in the default profile" \
-          HOME=$ROOT_HOME NIX_SSL_CERT_FILE=/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt "$NIX_INSTALLED_NIX/bin/nix-channel" --update nixpkgs
+    _sudo "Add $username to the $group group"\
+          /usr/sbin/dseditgroup -o edit -t user \
+          -a "$username" "$group"
 }
 
-
-place_nix_configuration() {
-    cat <<EOF > "$SCRATCH/nix.conf"
-build-users-group = $NIX_BUILD_GROUP_NAME
-
-max-jobs = $NIX_USER_COUNT
-cores = 1
-sandbox = false
-EOF
-    _sudo "to place the default nix daemon configuration (part 2)" \
-          install -m 0664 "$SCRATCH/nix.conf" /etc/nix/nix.conf
+poly_user_primary_group_get() {
+    dsclattr "/Users/$1" "PrimaryGroupID"
 }
 
-configure_nix_daemon_plist() {
-    _sudo "to set up the nix-daemon as a LaunchDaemon" \
-          ln -sfn "/nix/var/nix/profiles/default$PLIST_DEST" "$PLIST_DEST"
-
-    _sudo "to load the LaunchDaemon plist for nix-daemon" \
-          launchctl load /Library/LaunchDaemons/org.nixos.nix-daemon.plist
-
-    _sudo "to start the nix-daemon" \
-          launchctl start org.nixos.nix-daemon
-
+poly_user_primary_group_set() {
+    _sudo "to let the nix daemon use this user for builds (this might seem redundant, but there are two concepts of group membership)" \
+          /usr/bin/dscl . -create "/Users/$1" "PrimaryGroupID" "$2"
 }
 
+poly_create_build_user() {
+    username=$1
+    uid=$2
+    builder_num=$3
 
-main() {
-    welcome_to_nix
-    chat_about_sudo
-
-    if [ "${ALLOW_PREEXISTING_INSTALLATION:-}" = "" ]; then
-        validate_starting_assumptions
-    fi
-
-    setup_report
-
-    if ! ui_confirm "Ready to continue?"; then
-        ok "Alright, no changes have been made :)"
-        contactme
-        trap finish_cleanup EXIT
-        exit 1
-    fi
-
-    create_build_group
-    create_build_users
-    create_directories
-    place_channel_configuration
-    install_from_extracted_nix
-
-    configure_shell_profile
-
-    set +eu
-    . /etc/profile
-    set -eu
-
-    setup_default_profile
-    place_nix_configuration
-    configure_nix_daemon_plist
-
-    trap finish_success EXIT
+    _sudo "Creating the Nix build user (#$builder_num), $username" \
+          /usr/bin/dscl . create "/Users/$username" \
+          UniqueID "${uid}"
 }
-
-
-main
diff --git a/scripts/install-multi-user.sh b/scripts/install-multi-user.sh
new file mode 100644
index 000000000000..5f6542355e0c
--- /dev/null
+++ b/scripts/install-multi-user.sh
@@ -0,0 +1,797 @@
+#!/usr/bin/env bash
+
+set -eu
+set -o pipefail
+
+# Sourced from:
+# - https://github.com/LnL7/nix-darwin/blob/8c29d0985d74b4a990238497c47a2542a5616b3c/bootstrap.sh
+# - https://gist.github.com/expipiplus1/e571ce88c608a1e83547c918591b149f/ac504c6c1b96e65505fbda437a28ce563408ecb0
+# - https://github.com/NixOS/nixos-org-configurations/blob/a122f418797713d519aadf02e677fce0dc1cb446/delft/scripts/nix-mac-installer.sh
+# - https://github.com/matthewbauer/macNixOS/blob/f6045394f9153edea417be90c216788e754feaba/install-macNixOS.sh
+# - https://gist.github.com/LnL7/9717bd6cdcb30b086fd7f2093e5f8494/86b26f852ce563e973acd30f796a9a416248c34a
+#
+# however tracking which bits came from which would be impossible.
+
+readonly ESC='\033[0m'
+readonly BOLD='\033[38;1m'
+readonly BLUE='\033[38;34m'
+readonly BLUE_UL='\033[38;4;34m'
+readonly GREEN='\033[38;32m'
+readonly GREEN_UL='\033[38;4;32m'
+readonly RED='\033[38;31m'
+readonly RED_UL='\033[38;4;31m'
+readonly YELLOW='\033[38;33m'
+readonly YELLOW_UL='\033[38;4;33m'
+
+readonly NIX_USER_COUNT="32"
+readonly NIX_BUILD_GROUP_ID="30000"
+readonly NIX_BUILD_GROUP_NAME="nixbld"
+readonly NIX_FIRST_BUILD_UID="30001"
+# Please don't change this. We don't support it, because the
+# default shell profile that comes with Nix doesn't support it.
+readonly NIX_ROOT="/nix"
+
+readonly PROFILE_TARGETS=("/etc/bashrc" "/etc/profile.d/nix.sh" "/etc/zshrc")
+readonly PROFILE_BACKUP_SUFFIX=".backup-before-nix"
+readonly PROFILE_NIX_FILE="$NIX_ROOT/var/nix/profiles/default/etc/profile.d/nix-daemon.sh"
+
+readonly NIX_INSTALLED_NIX="@nix@"
+readonly NIX_INSTALLED_CACERT="@cacert@"
+readonly EXTRACTED_NIX_PATH="$(dirname "$0")"
+
+readonly ROOT_HOME=$(echo ~root)
+
+if [ -t 0 ]; then
+    readonly IS_HEADLESS='no'
+else
+    readonly IS_HEADLESS='yes'
+fi
+
+headless() {
+    if [ "$IS_HEADLESS" = "yes" ]; then
+        return 0
+    else
+        return 1
+    fi
+}
+
+contactme() {
+    echo "We'd love to help if you need it."
+    echo ""
+    echo "If you can, open an issue at https://github.com/nixos/nix/issues"
+    echo ""
+    echo "Or feel free to contact the team,"
+    echo " - on IRC #nixos on irc.freenode.net"
+    echo " - on twitter @nixos_org"
+}
+
+uninstall_directions() {
+    subheader "Uninstalling nix:"
+    local step=0
+
+    if poly_service_installed_check; then
+        step=$((step + 1))
+        poly_service_uninstall_directions "$step"
+    fi
+
+    for profile_target in "${PROFILE_TARGETS[@]}"; do
+        if [ -e "$profile_target" ] && [ -e "$profile_target$PROFILE_BACKUP_SUFFIX" ]; then
+            step=$((step + 1))
+            cat <<EOF
+$step. Restore $profile_target$PROFILE_BACKUP_SUFFIX back to $profile_target
+
+  sudo mv $profile_target$PROFILE_BACKUP_SUFFIX $profile_target
+
+(after this one, you may need to re-open any terminals that were
+opened while it existed.)
+
+EOF
+        fi
+    done
+
+    step=$((step + 1))
+    cat <<EOF
+$step. Delete the files Nix added to your system:
+
+  sudo rm -rf /etc/nix $NIX_ROOT $ROOT_HOME/.nix-profile $ROOT_HOME/.nix-defexpr $ROOT_HOME/.nix-channels $HOME/.nix-profile $HOME/.nix-defexpr $HOME/.nix-channels
+
+and that is it.
+
+EOF
+
+}
+
+nix_user_for_core() {
+    printf "nixbld%d" "$1"
+}
+
+nix_uid_for_core() {
+    echo $((NIX_FIRST_BUILD_UID + $1 - 1))
+}
+
+_textout() {
+    echo -en "$1"
+    shift
+    if [ "$*" = "" ]; then
+        cat
+    else
+        echo "$@"
+    fi
+    echo -en "$ESC"
+}
+
+header() {
+    follow="---------------------------------------------------------"
+    header=$(echo "---- $* $follow$follow$follow" | head -c 80)
+    echo ""
+    _textout "$BLUE" "$header"
+}
+
+warningheader() {
+    follow="---------------------------------------------------------"
+    header=$(echo "---- $* $follow$follow$follow" | head -c 80)
+    echo ""
+    _textout "$RED" "$header"
+}
+
+subheader() {
+    echo ""
+    _textout "$BLUE_UL" "$*"
+}
+
+row() {
+    printf "$BOLD%s$ESC:\\t%s\\n" "$1" "$2"
+}
+
+task() {
+    echo ""
+    ok "~~> $1"
+}
+
+bold() {
+    echo "$BOLD$*$ESC"
+}
+
+ok() {
+    _textout "$GREEN" "$@"
+}
+
+warning() {
+    warningheader "warning!"
+    cat
+    echo ""
+}
+
+failure() {
+    header "oh no!"
+    _textout "$RED" "$@"
+    echo ""
+    _textout "$RED" "$(contactme)"
+    trap finish_cleanup EXIT
+    exit 1
+}
+
+ui_confirm() {
+    _textout "$GREEN$GREEN_UL" "$1"
+
+    if headless; then
+        echo "No TTY, assuming you would say yes :)"
+        return 0
+    fi
+
+    local prompt="[y/n] "
+    echo -n "$prompt"
+    while read -r y; do
+        if [ "$y" = "y" ]; then
+            echo ""
+            return 0
+        elif [ "$y" = "n" ]; then
+            echo ""
+            return 1
+        else
+            _textout "$RED" "Sorry, I didn't understand. I can only understand answers of y or n"
+            echo -n "$prompt"
+        fi
+    done
+    echo ""
+    return 1
+}
+
+__sudo() {
+    local expl="$1"
+    local cmd="$2"
+    shift
+    header "sudo execution"
+
+    echo "I am executing:"
+    echo ""
+    printf "    $ sudo %s\\n" "$cmd"
+    echo ""
+    echo "$expl"
+    echo ""
+
+    return 0
+}
+
+_sudo() {
+    local expl="$1"
+    shift
+    if ! headless; then
+        __sudo "$expl" "$*"
+    fi
+    sudo "$@"
+}
+
+
+readonly SCRATCH=$(mktemp -d -t tmp.XXXXXXXXXX)
+function finish_cleanup {
+    rm -rf "$SCRATCH"
+}
+
+function finish_fail {
+    finish_cleanup
+
+    failure <<EOF
+Jeeze, something went wrong. If you can take all the output and open
+an issue, we'd love to fix the problem so nobody else has this issue.
+
+:(
+EOF
+}
+trap finish_fail EXIT
+
+function finish_success {
+    finish_cleanup
+
+    ok "Alright! We're done!"
+    cat <<EOF
+
+Before Nix will work in your existing shells, you'll need to close
+them and open them again. Other than that, you should be ready to go.
+
+Try it! Open a new terminal, and type:
+
+  $ nix-shell -p nix-info --run "nix-info -m"
+
+Thank you for using this installer. If you have any feedback, don't
+hesitate:
+
+$(contactme)
+EOF
+}
+
+
+validate_starting_assumptions() {
+    poly_validate_assumptions
+
+    if [ $EUID -eq 0 ]; then
+        failure <<EOF
+Please do not run this script with root privileges. We will call sudo
+when we need to.
+EOF
+    fi
+
+    if type nix-env 2> /dev/null >&2; then
+        failure <<EOF
+Nix already appears to be installed, and this tool assumes it is
+_not_ yet installed.
+
+$(uninstall_directions)
+EOF
+    fi
+
+    if [ "${NIX_REMOTE:-}" != "" ]; then
+        failure <<EOF
+For some reason, \$NIX_REMOTE is set. It really should not be set
+before this installer runs, and it hints that Nix is currently
+installed. Please delete the old Nix installation and start again.
+
+Note: You might need to close your shell window and open a new shell
+to clear the variable.
+EOF
+    fi
+
+    if echo "${SSL_CERT_FILE:-}" | grep -qE "(nix/var/nix|nix-profile)"; then
+        failure <<EOF
+It looks like \$SSL_CERT_FILE is set to a path that used to be part of
+the old Nix installation. Please unset that variable and try again:
+
+  $ unset SSL_CERT_FILE
+
+EOF
+    fi
+
+    for file in ~/.bash_profile ~/.bash_login ~/.profile ~/.zshenv ~/.zprofile ~/.zshrc ~/.zlogin; do
+        if [ -f "$file" ]; then
+            if grep -l "^[^#].*.nix-profile" "$file"; then
+                failure <<EOF
+I found a reference to a ".nix-profile" in $file.
+This has a high chance of breaking a new nix installation. It was most
+likely put there by a previous Nix installer.
+
+Please remove this reference and try running this again. You should
+also look for similar references in:
+
+ - ~/.bash_profile
+ - ~/.bash_login
+ - ~/.profile
+
+or other shell init files that you may have.
+
+$(uninstall_directions)
+EOF
+            fi
+        fi
+    done
+
+    if [ -d /nix ]; then
+        failure <<EOF
+There are some relics of a previous installation of Nix at /nix, and
+this scripts assumes Nix is _not_ yet installed. Please delete the old
+Nix installation and start again.
+
+$(uninstall_directions)
+EOF
+    fi
+
+    if [ -d /etc/nix ]; then
+        failure <<EOF
+There are some relics of a previous installation of Nix at /etc/nix, and
+this scripts assumes Nix is _not_ yet installed. Please delete the old
+Nix installation and start again.
+
+$(uninstall_directions)
+EOF
+    fi
+
+    for profile_target in "${PROFILE_TARGETS[@]}"; do
+        if [ -e "$profile_target$PROFILE_BACKUP_SUFFIX" ]; then
+        failure <<EOF
+When this script runs, it backs up the current $profile_target to
+$profile_target$PROFILE_BACKUP_SUFFIX. This backup file already exists, though.
+
+Please follow these instructions to clean up the old backup file:
+
+1. Copy $profile_target and $profile_target$PROFILE_BACKUP_SUFFIX to another place, just
+in case.
+
+2. Take care to make sure that $profile_target$PROFILE_BACKUP_SUFFIX doesn't look like
+it has anything nix-related in it. If it does, something is probably
+quite wrong. Please open an issue or get in touch immediately.
+
+3. Take care to make sure that $profile_target doesn't look like it has
+anything nix-related in it. If it does, and $profile_target _did not_,
+run:
+
+  $ /usr/bin/sudo /bin/mv $profile_target$PROFILE_BACKUP_SUFFIX $profile_target
+
+and try again.
+EOF
+        fi
+
+        if [ -e "$profile_target" ] && grep -qi "nix" "$profile_target"; then
+            failure <<EOF
+It looks like $profile_target already has some Nix configuration in
+there. There should be no reason to run this again. If you're having
+trouble, please open an issue.
+EOF
+        fi
+    done
+
+    danger_paths=("$ROOT_HOME/.nix-defexpr" "$ROOT_HOME/.nix-channels" "$ROOT_HOME/.nix-profile")
+    for danger_path in "${danger_paths[@]}"; do
+        if _sudo "making sure that $danger_path doesn't exist" \
+           test -e "$danger_path"; then
+            failure <<EOF
+I found a file at $danger_path, which is a relic of a previous
+installation. You must first delete this file before continuing.
+
+$(uninstall_directions)
+EOF
+        fi
+    done
+}
+
+setup_report() {
+    header "Nix config report"
+    row "        Temp Dir" "$SCRATCH"
+    row "        Nix Root" "$NIX_ROOT"
+    row "     Build Users" "$NIX_USER_COUNT"
+    row "  Build Group ID" "$NIX_BUILD_GROUP_ID"
+    row "Build Group Name" "$NIX_BUILD_GROUP_NAME"
+    if [ "${ALLOW_PREEXISTING_INSTALLATION:-}" != "" ]; then
+        row "Preexisting Install" "Allowed"
+    fi
+
+    subheader "build users:"
+
+    row "    Username" "UID"
+    for i in $(seq 1 "$NIX_USER_COUNT"); do
+        row "     $(nix_user_for_core "$i")" "$(nix_uid_for_core "$i")"
+    done
+    echo ""
+}
+
+create_build_group() {
+    local primary_group_id
+
+    task "Setting up the build group $NIX_BUILD_GROUP_NAME"
+    if ! poly_group_exists "$NIX_BUILD_GROUP_NAME"; then
+        poly_create_build_group
+        row "            Created" "Yes"
+    else
+        primary_group_id=$(poly_group_id_get "$NIX_BUILD_GROUP_NAME")
+        if [ "$primary_group_id" -ne "$NIX_BUILD_GROUP_ID" ]; then
+            failure <<EOF
+It seems the build group $NIX_BUILD_GROUP_NAME already exists, but
+with the UID $primary_group_id. This script can't really handle
+that right now, so I'm going to give up.
+
+You can fix this by editing this script and changing the
+NIX_BUILD_GROUP_ID variable near the top to from $NIX_BUILD_GROUP_ID
+to $primary_group_id and re-run.
+EOF
+        else
+            row "            Exists" "Yes"
+        fi
+    fi
+}
+
+create_build_user_for_core() {
+    local coreid
+    local username
+    local uid
+
+    coreid="$1"
+    username=$(nix_user_for_core "$coreid")
+    uid=$(nix_uid_for_core "$coreid")
+
+    task "Setting up the build user $username"
+
+    if ! poly_user_exists "$username"; then
+        poly_create_build_user "$username" "$uid" "$coreid"
+        row "           Created" "Yes"
+    else
+        actual_uid=$(poly_user_id_get "$username")
+        if [ "$actual_uid" != "$uid" ]; then
+            failure <<EOF
+It seems the build user $username already exists, but with the UID
+with the UID '$actual_uid'. This script can't really handle that right
+now, so I'm going to give up.
+
+If you already created the users and you know they start from
+$actual_uid and go up from there, you can edit this script and change
+NIX_FIRST_BUILD_UID near the top of the file to $actual_uid and try
+again.
+EOF
+        else
+            row "            Exists" "Yes"
+        fi
+    fi
+
+    if [ "$(poly_user_hidden_get "$username")" = "1" ]; then
+        row "            Hidden" "Yes"
+    else
+        poly_user_hidden_set "$username"
+        row "            Hidden" "Yes"
+    fi
+
+    if [ "$(poly_user_home_get "$username")" = "/var/empty" ]; then
+        row "    Home Directory" "/var/empty"
+    else
+        poly_user_home_set "$username" "/var/empty"
+        row "    Home Directory" "/var/empty"
+    fi
+
+    # We use grep instead of an equality check because it is difficult
+    # to extract _just_ the user's note, instead it is prefixed with
+    # some plist junk. This was causing the user note to always be set,
+    # even if there was no reason for it.
+    if ! poly_user_note_get "$username" | grep -q "Nix build user $coreid"; then
+        row "              Note" "Nix build user $coreid"
+    else
+        poly_user_note_set "$username" "Nix build user $coreid"
+        row "              Note" "Nix build user $coreid"
+    fi
+
+    if [ "$(poly_user_shell_get "$username")" = "/sbin/nologin" ]; then
+        row "   Logins Disabled" "Yes"
+    else
+        poly_user_shell_set "$username" "/sbin/nologin"
+        row "   Logins Disabled" "Yes"
+    fi
+
+    if poly_user_in_group_check "$username" "$NIX_BUILD_GROUP_NAME"; then
+        row "  Member of $NIX_BUILD_GROUP_NAME" "Yes"
+    else
+        poly_user_in_group_set "$username" "$NIX_BUILD_GROUP_NAME"
+        row "  Member of $NIX_BUILD_GROUP_NAME" "Yes"
+    fi
+
+    if [ "$(poly_user_primary_group_get "$username")" = "$NIX_BUILD_GROUP_ID" ]; then
+        row "    PrimaryGroupID" "$NIX_BUILD_GROUP_ID"
+    else
+        poly_user_primary_group_set "$username" "$NIX_BUILD_GROUP_ID"
+        row "    PrimaryGroupID" "$NIX_BUILD_GROUP_ID"
+    fi
+}
+
+create_build_users() {
+    for i in $(seq 1 "$NIX_USER_COUNT"); do
+        create_build_user_for_core "$i"
+    done
+}
+
+create_directories() {
+    _sudo "to make the basic directory structure of Nix (part 1)" \
+          mkdir -pv -m 0755 /nix /nix/var /nix/var/log /nix/var/log/nix /nix/var/log/nix/drvs /nix/var/nix{,/db,/gcroots,/profiles,/temproots,/userpool}
+
+    _sudo "to make the basic directory structure of Nix (part 2)" \
+          mkdir -pv -m 1777 /nix/var/nix/{gcroots,profiles}/per-user
+
+    _sudo "to make the basic directory structure of Nix (part 3)" \
+          mkdir -pv -m 1775 /nix/store
+
+    _sudo "to make the basic directory structure of Nix (part 4)" \
+          chgrp "$NIX_BUILD_GROUP_NAME" /nix/store
+
+    _sudo "to set up the root user's profile (part 1)" \
+          mkdir -pv -m 0755 /nix/var/nix/profiles/per-user/root
+
+    _sudo "to set up the root user's profile (part 2)" \
+          mkdir -pv -m 0700 "$ROOT_HOME/.nix-defexpr"
+
+    _sudo "to place the default nix daemon configuration (part 1)" \
+          mkdir -pv -m 0555 /etc/nix
+}
+
+place_channel_configuration() {
+    echo "https://nixos.org/channels/nixpkgs-unstable nixpkgs" > "$SCRATCH/.nix-channels"
+    _sudo "to set up the default system channel (part 1)" \
+          install -m 0664 "$SCRATCH/.nix-channels" "$ROOT_HOME/.nix-channels"
+}
+
+welcome_to_nix() {
+    ok "Welcome to the Multi-User Nix Installation"
+
+    cat <<EOF
+
+This installation tool will set up your computer with the Nix package
+manager. This will happen in a few stages:
+
+1. Make sure your computer doesn't already have Nix. If it does, I
+   will show you instructions on how to clean up your old one.
+
+2. Show you what we are going to install and where. Then we will ask
+   if you are ready to continue.
+
+3. Create the system users and groups that the Nix daemon uses to run
+   builds.
+
+4. Perform the basic installation of the Nix files daemon.
+
+5. Configure your shell to import special Nix Profile files, so you
+   can use Nix.
+
+6. Start the Nix daemon.
+
+EOF
+
+    if ui_confirm "Would you like to see a more detailed list of what we will do?"; then
+        cat <<EOF
+
+We will:
+
+ - make sure your computer doesn't already have Nix files
+   (if it does, I  will tell you how to clean them up.)
+ - create local users (see the list above for the users we'll make)
+ - create a local group ($NIX_BUILD_GROUP_NAME)
+ - install Nix in to $NIX_ROOT
+ - create a configuration file in /etc/nix
+ - set up the "default profile" by creating some Nix-related files in
+   $ROOT_HOME
+EOF
+        for profile_target in "${PROFILE_TARGETS[@]}"; do
+            if [ -e "$profile_target" ]; then
+                cat <<EOF
+ - back up $profile_target to $profile_target$PROFILE_BACKUP_SUFFIX
+ - update $profile_target to include some Nix configuration
+EOF
+            fi
+        done
+        poly_service_setup_note
+        if ! ui_confirm "Ready to continue?"; then
+            failure <<EOF
+Okay, maybe you would like to talk to the team.
+EOF
+        fi
+    fi
+}
+
+chat_about_sudo() {
+    header "let's talk about sudo"
+
+    if headless; then
+        cat <<EOF
+This script is going to call sudo a lot. Normally, it would show you
+exactly what commands it is running and why. However, the script is
+run in a headless fashion, like this:
+
+  $ curl https://nixos.org/nix/install | sh
+
+or maybe in a CI pipeline. Because of that, we're going to skip the
+verbose output in the interest of brevity.
+
+If you would like to
+see the output, try like this:
+
+  $ curl -o install-nix https://nixos.org/nix/install
+  $ sh ./install-nix
+
+EOF
+        return 0
+    fi
+
+    cat <<EOF
+This script is going to call sudo a lot. Every time we do, it'll
+output exactly what it'll do, and why.
+
+Just like this:
+EOF
+
+    __sudo "to demonstrate how our sudo prompts look" \
+           echo "this is a sudo prompt"
+
+    cat <<EOF
+
+This might look scary, but everything can be undone by running just a
+few commands. We used to ask you to confirm each time sudo ran, but it
+was too many times. Instead, I'll just ask you this one time:
+
+EOF
+    if ui_confirm "Can we use sudo?"; then
+        ok "Yay! Thanks! Let's get going!"
+    else
+        failure <<EOF
+That is okay, but we can't install.
+EOF
+    fi
+}
+
+install_from_extracted_nix() {
+    (
+        cd "$EXTRACTED_NIX_PATH"
+
+        _sudo "to copy the basic Nix files to the new store at $NIX_ROOT/store" \
+              rsync -rlpt ./store/* "$NIX_ROOT/store/"
+
+        if [ -d "$NIX_INSTALLED_NIX" ]; then
+            echo "      Alright! We have our first nix at $NIX_INSTALLED_NIX"
+        else
+            failure <<EOF
+Something went wrong, and I didn't find Nix installed at
+$NIX_INSTALLED_NIX.
+EOF
+        fi
+
+        _sudo "to initialize the Nix Database" \
+              $NIX_INSTALLED_NIX/bin/nix-store --init
+
+        cat ./.reginfo \
+            | _sudo "to load data for the first time in to the Nix Database" \
+                   "$NIX_INSTALLED_NIX/bin/nix-store" --load-db
+
+        echo "      Just finished getting the nix database ready."
+    )
+}
+
+shell_source_lines() {
+    cat <<EOF
+
+# Nix
+if [ -e '$PROFILE_NIX_FILE' ]; then
+  . '$PROFILE_NIX_FILE'
+fi
+# End Nix
+
+EOF
+}
+
+configure_shell_profile() {
+    # If there is an /etc/profile.d directory, we want to ensure there
+    # is a nix.sh within it, so we can use the following loop to add
+    # the source lines to it. Note that I'm _not_ adding the source
+    # lines here, because we want to be using the regular machinery.
+    #
+    # If we go around that machinery, it becomes more complicated and
+    # adds complications to the uninstall instruction generator and
+    # old instruction sniffer as well.
+    if [ -d /etc/profile.d ]; then
+        _sudo "create a stub /etc/profile.d/nix.sh which will be updated" \
+              touch /etc/profile.d/nix.sh
+    fi
+
+    for profile_target in "${PROFILE_TARGETS[@]}"; do
+        if [ -e "$profile_target" ]; then
+            _sudo "to back up your current $profile_target to $profile_target$PROFILE_BACKUP_SUFFIX" \
+                  cp "$profile_target" "$profile_target$PROFILE_BACKUP_SUFFIX"
+
+            shell_source_lines \
+                | _sudo "extend your $profile_target with nix-daemon settings" \
+                        tee -a "$profile_target"
+        fi
+    done
+}
+
+setup_default_profile() {
+    _sudo "to installing a bootstrapping Nix in to the default Profile" \
+          HOME="$ROOT_HOME" "$NIX_INSTALLED_NIX/bin/nix-env" -i "$NIX_INSTALLED_NIX"
+
+    _sudo "to installing a bootstrapping SSL certificate just for Nix in to the default Profile" \
+          HOME="$ROOT_HOME" "$NIX_INSTALLED_NIX/bin/nix-env" -i "$NIX_INSTALLED_CACERT"
+
+    _sudo "to update the default channel in the default profile" \
+          HOME="$ROOT_HOME" NIX_SSL_CERT_FILE=/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt "$NIX_INSTALLED_NIX/bin/nix-channel" --update nixpkgs
+}
+
+
+place_nix_configuration() {
+    cat <<EOF > "$SCRATCH/nix.conf"
+build-users-group = $NIX_BUILD_GROUP_NAME
+
+max-jobs = $NIX_USER_COUNT
+cores = 1
+sandbox = false
+EOF
+    _sudo "to place the default nix daemon configuration (part 2)" \
+          install -m 0664 "$SCRATCH/nix.conf" /etc/nix/nix.conf
+}
+
+main() {
+    if [ "$(uname -s)" = "Darwin" ]; then
+        # shellcheck source=./install-darwin-multi-user.sh
+        . "$EXTRACTED_NIX_PATH/install-darwin-multi-user.sh"
+    elif [ "$(uname -s)" = "Linux" ] && [ -e /run/systemd/system ]; then
+        # shellcheck source=./install-systemd-multi-user.sh
+        . "$EXTRACTED_NIX_PATH/install-systemd-multi-user.sh"
+    else
+        failure "Sorry, I don't know what to do on $(uname)"
+    fi
+
+    welcome_to_nix
+    chat_about_sudo
+
+    if [ "${ALLOW_PREEXISTING_INSTALLATION:-}" = "" ]; then
+        validate_starting_assumptions
+    fi
+
+    setup_report
+
+    if ! ui_confirm "Ready to continue?"; then
+        ok "Alright, no changes have been made :)"
+        contactme
+        trap finish_cleanup EXIT
+        exit 1
+    fi
+
+    create_build_group
+    create_build_users
+    create_directories
+    place_channel_configuration
+    install_from_extracted_nix
+
+    configure_shell_profile
+
+    set +eu
+    . /etc/profile
+    set -eu
+
+    setup_default_profile
+    place_nix_configuration
+    poly_configure_nix_daemon_service
+
+    trap finish_success EXIT
+}
+
+
+main
diff --git a/scripts/install-nix-from-closure.sh b/scripts/install-nix-from-closure.sh
index 3e5676f419ba..cd71d7947d77 100644
--- a/scripts/install-nix-from-closure.sh
+++ b/scripts/install-nix-from-closure.sh
@@ -28,9 +28,41 @@ if [ "$(uname -s)" = "Darwin" ]; then
         echo "$0: macOS $(sw_vers -productVersion) is not supported, upgrade to 10.10 or higher"
         exit 1
     fi
+fi
+
+# Determine if we should punt to the single-user installer or not
+if [ "$(uname -s)" = "Darwin" ]; then
+    INSTALL_MODE=daemon
+elif [ "$(uname -s)" = "Linux" ] && [ -e /run/systemd/system ]; then
+    INSTALL_MODE=daemon
+else
+    INSTALL_MODE=no-daemon
+fi
+
+# Trivially handle the --daemon / --no-daemon options
+if [ "x${1:-}" = "x--no-daemon" ]; then
+    INSTALL_MODE=no-daemon
+elif [ "x${1:-}" = "x--daemon" ]; then
+    INSTALL_MODE=daemon
+elif [ "x${1:-}" != "x" ]; then
+    (
+        echo "Nix Installer [--daemon|--no-daemon]"
+        echo ""
+        echo " --daemon:    Force the installer to use the Daemon"
+        echo "              based installer, even though it may not"
+        echo "              work."
+        echo ""
+        echo " --no-daemon: Force a no-daemon, single-user"
+        echo "              installation even when the preferred"
+        echo "              method is with the daemon."
+        echo ""
+    ) >&2
+    exit
+fi
 
-    printf '\e[1;31mSwitching to the Multi-User Darwin Installer\e[0m\n'
-    exec "$self/install-darwin-multi-user"
+if [ "$INSTALL_MODE" = "daemon" ]; then
+    printf '\e[1;31mSwitching to the Daemon-based Installer\e[0m\n'
+    exec "$self/install-multi-user"
     exit 0
 fi
 
diff --git a/scripts/install-systemd-multi-user.sh b/scripts/install-systemd-multi-user.sh
new file mode 100644
index 000000000000..04bc539a1099
--- /dev/null
+++ b/scripts/install-systemd-multi-user.sh
@@ -0,0 +1,154 @@
+#!/usr/bin/env bash
+
+set -eu
+set -o pipefail
+
+readonly SERVICE_SRC=/lib/systemd/system/nix-daemon.service
+readonly SERVICE_DEST=/etc/systemd/system/nix-daemon.service
+
+readonly SOCKET_SRC=/lib/systemd/system/nix-daemon.socket
+readonly SOCKET_DEST=/etc/systemd/system/nix-daemon.socket
+
+poly_validate_assumptions() {
+    if [ "$(uname -s)" != "Linux" ]; then
+        failure "This script is for use with Linux!"
+    fi
+}
+
+poly_service_installed_check() {
+    [ "$(systemctl is-enabled nix-daemon.service)" = "linked" ] \
+        || [ "$(systemctl is-enabled nix-daemon.socket)" = "enabled" ]
+}
+
+poly_service_uninstall_directions() {
+        cat <<EOF
+$1. Delete the systemd service and socket units
+
+  sudo systemctl stop nix-daemon.socket
+  sudo systemctl stop nix-daemon.service
+  sudo systemctl disable nix-daemon.socket
+  sudo systemctl disable nix-daemon.service
+  sudo systemctl daemon-reload
+EOF
+}
+
+poly_service_setup_note() {
+    cat <<EOF
+ - load and start a service (at $SERVICE_DEST
+   and $SOCKET_DEST) for nix-daemon
+
+EOF
+}
+
+poly_configure_nix_daemon_service() {
+    _sudo "to set up the nix-daemon service" \
+          systemctl link "/nix/var/nix/profiles/default$SERVICE_SRC"
+
+    _sudo "to set up the nix-daemon socket service" \
+          systemctl enable "/nix/var/nix/profiles/default$SOCKET_SRC"
+
+    _sudo "to load the systemd unit for nix-daemon" \
+          systemctl daemon-reload
+
+    _sudo "to start the nix-daemon.socket" \
+          systemctl start nix-daemon.socket
+
+    _sudo "to start the nix-daemon.service" \
+          systemctl start nix-daemon.service
+
+}
+
+poly_group_exists() {
+    getent group "$1" > /dev/null 2>&1
+}
+
+poly_group_id_get() {
+    getent group "$1" | cut -d: -f3
+}
+
+poly_create_build_group() {
+    _sudo "Create the Nix build group, $NIX_BUILD_GROUP_NAME" \
+          groupadd -g "$NIX_BUILD_GROUP_ID" --system \
+          "$NIX_BUILD_GROUP_NAME" >&2
+}
+
+poly_user_exists() {
+    getent passwd "$1" > /dev/null 2>&1
+}
+
+poly_user_id_get() {
+    getent passwd "$1" | cut -d: -f3
+}
+
+poly_user_hidden_get() {
+    echo "1"
+}
+
+poly_user_hidden_set() {
+    true
+}
+
+poly_user_home_get() {
+    getent passwd "$1" | cut -d: -f6
+}
+
+poly_user_home_set() {
+    _sudo "in order to give $1 a safe home directory" \
+          usermod --home "$2" "$1"
+}
+
+poly_user_note_get() {
+    getent passwd "$1" | cut -d: -f5
+}
+
+poly_user_note_set() {
+    _sudo "in order to give $1 a useful comment" \
+          usermod --comment "$2" "$1"
+}
+
+poly_user_shell_get() {
+    getent passwd "$1" | cut -d: -f7
+}
+
+poly_user_shell_set() {
+    _sudo "in order to prevent $1 from logging in" \
+          usermod --shell "$2" "$1"
+}
+
+poly_user_in_group_check() {
+    groups "$1" | grep -q "$2" > /dev/null 2>&1
+}
+
+poly_user_in_group_set() {
+    _sudo "Add $1 to the $2 group"\
+          usermod --append --groups "$2" "$1"
+}
+
+poly_user_primary_group_get() {
+    getent passwd "$1" | cut -d: -f4
+}
+
+poly_user_primary_group_set() {
+    _sudo "to let the nix daemon use this user for builds (this might seem redundant, but there are two concepts of group membership)" \
+          usermod --gid "$2" "$1"
+
+}
+
+poly_create_build_user() {
+    username=$1
+    uid=$2
+    builder_num=$3
+
+    _sudo "Creating the Nix build user, $username" \
+          useradd \
+          --home-dir /var/empty \
+          --comment "Nix build user $builder_num" \
+          --gid "$NIX_BUILD_GROUP_ID" \
+          --groups "$NIX_BUILD_GROUP_NAME" \
+          --no-user-group \
+          --system \
+          --shell /sbin/nologin \
+          --uid "$uid" \
+          --password "!" \
+          "$username"
+}
diff --git a/src/build-remote/local.mk b/src/build-remote/local.mk
index 64368a43ff73..50b0409d1886 100644
--- a/src/build-remote/local.mk
+++ b/src/build-remote/local.mk
@@ -4,6 +4,6 @@ build-remote_DIR := $(d)
 
 build-remote_INSTALL_DIR := $(libexecdir)/nix
 
-build-remote_LIBS = libmain libutil libformat libstore
+build-remote_LIBS = libmain libformat libstore libutil
 
 build-remote_SOURCES := $(d)/build-remote.cc
diff --git a/src/libexpr/nix-expr.pc.in b/src/libexpr/nix-expr.pc.in
index 21b6c38dd133..79f3e2f4506e 100644
--- a/src/libexpr/nix-expr.pc.in
+++ b/src/libexpr/nix-expr.pc.in
@@ -7,4 +7,4 @@ Description: Nix Package Manager
 Version: @PACKAGE_VERSION@
 Requires: nix-store bdw-gc
 Libs: -L${libdir} -lnixexpr
-Cflags: -I${includedir}/nix
+Cflags: -I${includedir}/nix -std=c++14
diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc
index c88f677da085..57dc7bd1279d 100644
--- a/src/libexpr/primops.cc
+++ b/src/libexpr/primops.cc
@@ -155,7 +155,7 @@ static void prim_scopedImport(EvalState & state, const Pos & pos, Value * * args
 extern "C" typedef void (*ValueInitializer)(EvalState & state, Value & v);
 
 /* Load a ValueInitializer from a DSO and return whatever it initializes */
-static void prim_importNative(EvalState & state, const Pos & pos, Value * * args, Value & v)
+void prim_importNative(EvalState & state, const Pos & pos, Value * * args, Value & v)
 {
     PathSet context;
     Path path = state.coerceToPath(pos, *args[0], context);
@@ -193,7 +193,7 @@ static void prim_importNative(EvalState & state, const Pos & pos, Value * * args
 
 
 /* Execute a program and parse its output */
-static void prim_exec(EvalState & state, const Pos & pos, Value * * args, Value & v)
+void prim_exec(EvalState & state, const Pos & pos, Value * * args, Value & v)
 {
     state.forceList(*args[0], pos);
     auto elems = args[0]->listElems();
@@ -271,7 +271,18 @@ static void prim_isNull(EvalState & state, const Pos & pos, Value * * args, Valu
 static void prim_isFunction(EvalState & state, const Pos & pos, Value * * args, Value & v)
 {
     state.forceValue(*args[0]);
-    mkBool(v, args[0]->type == tLambda);
+    bool res;
+    switch (args[0]->type) {
+        case tLambda:
+        case tPrimOp:
+        case tPrimOpApp:
+            res = true;
+            break;
+        default:
+            res = false;
+            break;
+    }
+    mkBool(v, res);
 }
 
 
diff --git a/src/libexpr/primops.hh b/src/libexpr/primops.hh
index 31bf3f84f6c7..c790b30f6d0b 100644
--- a/src/libexpr/primops.hh
+++ b/src/libexpr/primops.hh
@@ -15,4 +15,12 @@ struct RegisterPrimOp
     RegisterPrimOp(std::string name, size_t arity, PrimOpFun fun);
 };
 
+/* These primops are disabled without enableNativeCode, but plugins
+   may wish to use them in limited contexts without globally enabling
+   them. */
+/* Load a ValueInitializer from a DSO and return whatever it initializes */
+void prim_importNative(EvalState & state, const Pos & pos, Value * * args, Value & v);
+/* Execute a program and parse its output */
+void prim_exec(EvalState & state, const Pos & pos, Value * * args, Value & v);
+
 }
diff --git a/src/libmain/nix-main.pc.in b/src/libmain/nix-main.pc.in
index de1bdf706f72..38bc85c484eb 100644
--- a/src/libmain/nix-main.pc.in
+++ b/src/libmain/nix-main.pc.in
@@ -6,4 +6,4 @@ Name: Nix
 Description: Nix Package Manager
 Version: @PACKAGE_VERSION@
 Libs: -L${libdir} -lnixmain
-Cflags: -I${includedir}/nix
+Cflags: -I${includedir}/nix -std=c++14
diff --git a/src/libstore/build.cc b/src/libstore/build.cc
index 73139d6d551a..416c775a35d2 100644
--- a/src/libstore/build.cc
+++ b/src/libstore/build.cc
@@ -964,6 +964,8 @@ private:
     }
 
     void done(BuildResult::Status status, const string & msg = "");
+
+    PathSet exportReferences(PathSet storePaths);
 };
 
 
@@ -1730,22 +1732,23 @@ int childEntry(void * arg)
 }
 
 
-PathSet exportReferences(Store & store, PathSet storePaths)
+PathSet DerivationGoal::exportReferences(PathSet storePaths)
 {
     PathSet paths;
 
     for (auto storePath : storePaths) {
 
         /* Check that the store path is valid. */
-        if (!store.isInStore(storePath))
+        if (!worker.store.isInStore(storePath))
             throw BuildError(format("'exportReferencesGraph' contains a non-store path '%1%'")
                 % storePath);
-        storePath = store.toStorePath(storePath);
-        if (!store.isValidPath(storePath))
-            throw BuildError(format("'exportReferencesGraph' contains an invalid path '%1%'")
-                % storePath);
 
-        store.computeFSClosure(storePath, paths);
+        storePath = worker.store.toStorePath(storePath);
+
+        if (!inputPaths.count(storePath))
+            throw BuildError("cannot export references of path '%s' because it is not in the input closure of the derivation", storePath);
+
+        worker.store.computeFSClosure(storePath, paths);
     }
 
     /* If there are derivations in the graph, then include their
@@ -1756,9 +1759,9 @@ PathSet exportReferences(Store & store, PathSet storePaths)
 
     for (auto & j : paths2) {
         if (isDerivation(j)) {
-            Derivation drv = store.derivationFromPath(j);
+            Derivation drv = worker.store.derivationFromPath(j);
             for (auto & k : drv.outputs)
-                store.computeFSClosure(k.second.path, paths);
+                worker.store.computeFSClosure(k.second.path, paths);
         }
     }
 
@@ -1882,7 +1885,7 @@ void DerivationGoal::startBuilder()
             /* Write closure info to <fileName>. */
             writeFile(tmpDir + "/" + fileName,
                 worker.store.makeValidityRegistration(
-                    exportReferences(worker.store, {storePath}), false, false));
+                    exportReferences({storePath}), false, false));
         }
     }
 
@@ -2384,7 +2387,7 @@ void DerivationGoal::writeStructuredAttrs()
                     for (auto & p : *i)
                         storePaths.insert(p.get<std::string>());
                     worker.store.pathInfoToJSON(jsonRoot,
-                        exportReferences(worker.store, storePaths), false, true);
+                        exportReferences(storePaths), false, true);
                 }
                 json[i.key()] = nlohmann::json::parse(str.str()); // urgh
             }
@@ -2696,8 +2699,8 @@ void DerivationGoal::runChild()
                 } else {
                     if (errno != EINVAL)
                         throw SysError("mounting /dev/pts");
-                    doBind("/dev/pts", "/dev/pts");
-                    doBind("/dev/ptmx", "/dev/ptmx");
+                    doBind("/dev/pts", chrootRootDir + "/dev/pts");
+                    doBind("/dev/ptmx", chrootRootDir + "/dev/ptmx");
                 }
             }
 
@@ -3687,7 +3690,7 @@ void SubstitutionGoal::tryNext()
        only after we've downloaded the path. */
     if (worker.store.requireSigs
         && !sub->isTrusted
-        && !info->checkSignatures(worker.store, worker.store.publicKeys))
+        && !info->checkSignatures(worker.store, worker.store.getPublicKeys()))
     {
         printError("warning: substituter '%s' does not have a valid signature for path '%s'",
             sub->getUri(), storePath);
diff --git a/src/libstore/builtins/buildenv.cc b/src/libstore/builtins/buildenv.cc
index 938d02c35a02..74e706664694 100644
--- a/src/libstore/builtins/buildenv.cc
+++ b/src/libstore/builtins/buildenv.cc
@@ -9,14 +9,6 @@ namespace nix {
 
 typedef std::map<Path,int> Priorities;
 
-static bool isDirectory(const Path & path)
-{
-    struct stat st;
-    if (stat(path.c_str(), &st) == -1)
-        throw SysError(format("getting status of '%1%'") % path);
-    return S_ISDIR(st.st_mode);
-}
-
 // FIXME: change into local variables.
 
 static Priorities priorities;
@@ -26,14 +18,37 @@ static unsigned long symlinks;
 /* For each activated package, create symlinks */
 static void createLinks(const Path & srcDir, const Path & dstDir, int priority)
 {
-    auto srcFiles = readDirectory(srcDir);
+    DirEntries srcFiles;
+
+    try {
+        srcFiles = readDirectory(srcDir);
+    } catch (SysError & e) {
+        if (e.errNo == ENOTDIR) {
+            printError("warning: not including '%s' in the user environment because it's not a directory", srcDir);
+            return;
+        }
+        throw;
+    }
+
     for (const auto & ent : srcFiles) {
         if (ent.name[0] == '.')
             /* not matched by glob */
             continue;
-        const auto & srcFile = srcDir + "/" + ent.name;
+        auto srcFile = srcDir + "/" + ent.name;
         auto dstFile = dstDir + "/" + ent.name;
 
+        struct stat srcSt;
+        try {
+            if (stat(srcFile.c_str(), &srcSt) == -1)
+                throw SysError("getting status of '%1%'", srcFile);
+        } catch (SysError & e) {
+            if (e.errNo == ENOENT || e.errNo == ENOTDIR) {
+                printError("warning: skipping dangling symlink '%s'", dstFile);
+                continue;
+            }
+            throw;
+        }
+
         /* The files below are special-cased to that they don't show up
          * in user profiles, either because they are useless, or
          * because they would cauase pointless collisions (e.g., each
@@ -44,9 +59,10 @@ static void createLinks(const Path & srcDir, const Path & dstDir, int priority)
             hasSuffix(srcFile, "/nix-support") ||
             hasSuffix(srcFile, "/perllocal.pod") ||
             hasSuffix(srcFile, "/info/dir") ||
-            hasSuffix(srcFile, "/log")) {
+            hasSuffix(srcFile, "/log"))
             continue;
-        } else if (isDirectory(srcFile)) {
+
+        else if (S_ISDIR(srcSt.st_mode)) {
             struct stat dstSt;
             auto res = lstat(dstFile.c_str(), &dstSt);
             if (res == 0) {
@@ -54,10 +70,9 @@ static void createLinks(const Path & srcDir, const Path & dstDir, int priority)
                     createLinks(srcFile, dstFile, priority);
                     continue;
                 } else if (S_ISLNK(dstSt.st_mode)) {
-                    auto target = readLink(dstFile);
-                    if (!isDirectory(target))
-                        throw Error(format("collision between '%1%' and non-directory '%2%'")
-                            % srcFile % target);
+                    auto target = canonPath(dstFile, true);
+                    if (!S_ISDIR(lstat(target).st_mode))
+                        throw Error("collision between '%1%' and non-directory '%2%'", srcFile, target);
                     if (unlink(dstFile.c_str()) == -1)
                         throw SysError(format("unlinking '%1%'") % dstFile);
                     if (mkdir(dstFile.c_str(), 0755) == -1)
@@ -68,28 +83,31 @@ static void createLinks(const Path & srcDir, const Path & dstDir, int priority)
                 }
             } else if (errno != ENOENT)
                 throw SysError(format("getting status of '%1%'") % dstFile);
-        } else {
+        }
+
+        else {
             struct stat dstSt;
             auto res = lstat(dstFile.c_str(), &dstSt);
             if (res == 0) {
                 if (S_ISLNK(dstSt.st_mode)) {
-                    auto target = readLink(dstFile);
                     auto prevPriority = priorities[dstFile];
                     if (prevPriority == priority)
-                        throw Error(format(
+                        throw Error(
                                 "packages '%1%' and '%2%' have the same priority %3%; "
                                 "use 'nix-env --set-flag priority NUMBER INSTALLED_PKGNAME' "
                                 "to change the priority of one of the conflicting packages"
-                                " (0 being the highest priority)"
-                                ) % srcFile % target % priority);
+                                " (0 being the highest priority)",
+                                srcFile, readLink(dstFile), priority);
                     if (prevPriority < priority)
                         continue;
                     if (unlink(dstFile.c_str()) == -1)
                         throw SysError(format("unlinking '%1%'") % dstFile);
-                }
+                } else if (S_ISDIR(dstSt.st_mode))
+                    throw Error("collision between non-directory '%1%' and directory '%2%'", srcFile, dstFile);
             } else if (errno != ENOENT)
                 throw SysError(format("getting status of '%1%'") % dstFile);
         }
+
         createSymlink(srcFile, dstFile);
         priorities[dstFile] = priority;
         symlinks++;
@@ -105,24 +123,18 @@ static Path out;
 
 static void addPkg(const Path & pkgDir, int priority)
 {
-    if (done.find(pkgDir) != done.end())
-        return;
+    if (done.count(pkgDir)) return;
     done.insert(pkgDir);
     createLinks(pkgDir, out, priority);
-    auto propagatedFN = pkgDir + "/nix-support/propagated-user-env-packages";
-    std::string propagated;
-    {
-        AutoCloseFD fd = open(propagatedFN.c_str(), O_RDONLY | O_CLOEXEC);
-        if (!fd) {
-            if (errno == ENOENT)
-                return;
-            throw SysError(format("opening '%1%'") % propagatedFN);
-        }
-        propagated = readFile(fd.get());
+
+    try {
+        for (const auto & p : tokenizeString<std::vector<string>>(
+                readFile(pkgDir + "/nix-support/propagated-user-env-packages"), " \n"))
+            if (!done.count(p))
+                postponed.insert(p);
+    } catch (SysError & e) {
+        if (e.errNo != ENOENT && e.errNo != ENOTDIR) throw;
     }
-    for (const auto & p : tokenizeString<std::vector<string>>(propagated, " \n"))
-        if (done.find(p) == done.end())
-            postponed.insert(p);
 }
 
 struct Package {
@@ -190,4 +202,3 @@ void builtinBuildenv(const BasicDerivation & drv)
 }
 
 }
-
diff --git a/src/libstore/download.cc b/src/libstore/download.cc
index 4d7f5690192d..18f9094f82e0 100644
--- a/src/libstore/download.cc
+++ b/src/libstore/download.cc
@@ -173,7 +173,11 @@ struct CurlDownloader : public Downloader
 
         int progressCallback(double dltotal, double dlnow)
         {
-            act.progress(dlnow, dltotal);
+            try {
+              act.progress(dlnow, dltotal);
+            } catch (nix::Interrupted &) {
+              assert(_isInterrupted);
+            }
             return _isInterrupted;
         }
 
@@ -730,8 +734,8 @@ Path Downloader::downloadCached(ref<Store> store, const string & url_, bool unpa
         Hash gotHash = unpack
             ? hashPath(expectedHash.type, store->toRealPath(storePath)).first
             : hashFile(expectedHash.type, store->toRealPath(storePath));
-        throw nix::Error("hash mismatch in file downloaded from '%s': expected hash '%s', got '%s'",
-            url, expectedHash.to_string(), gotHash.to_string());
+        throw nix::Error("hash mismatch in file downloaded from '%s': got hash '%s' instead of the expected hash '%s'",
+            url, gotHash.to_string(), expectedHash.to_string());
     }
 
     return store->toRealPath(storePath);
diff --git a/src/libstore/globals.cc b/src/libstore/globals.cc
index f46e8326235f..544566e0b573 100644
--- a/src/libstore/globals.cc
+++ b/src/libstore/globals.cc
@@ -159,7 +159,7 @@ void initPlugins()
             void *handle =
                 dlopen(file.c_str(), RTLD_LAZY | RTLD_LOCAL);
             if (!handle)
-                throw Error("could not dynamically open plugin file '%s%': %s%", file, dlerror());
+                throw Error("could not dynamically open plugin file '%s': %s", file, dlerror());
         }
     }
     /* We handle settings registrations here, since plugins can add settings */
diff --git a/src/libstore/globals.hh b/src/libstore/globals.hh
index 117404ec14c8..7430bbedbe44 100644
--- a/src/libstore/globals.hh
+++ b/src/libstore/globals.hh
@@ -313,6 +313,14 @@ public:
     Setting<Strings> trustedUsers{this, {"root"}, "trusted-users",
         "Which users or groups are trusted to ask the daemon to do unsafe things."};
 
+    Setting<unsigned int> ttlNegativeNarInfoCache{this, 3600, "narinfo-cache-negative-ttl",
+        "The TTL in seconds for negative lookups in the disk cache i.e binary cache lookups that "
+        "return an invalid path result"};
+
+    Setting<unsigned int> ttlPositiveNarInfoCache{this, 30 * 24 * 3600, "narinfo-cache-positive-ttl",
+        "The TTL in seconds for positive lookups in the disk cache i.e binary cache lookups that "
+        "return a valid path result."};
+
     /* ?Who we trust to use the daemon in safe ways */
     Setting<Strings> allowedUsers{this, {"*"}, "allowed-users",
         "Which users or groups are allowed to connect to the daemon."};
diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc
index acc0002acee1..b63584f28a30 100644
--- a/src/libstore/local-store.cc
+++ b/src/libstore/local-store.cc
@@ -53,7 +53,6 @@ LocalStore::LocalStore(const Params & params)
     , trashDir(realStoreDir + "/trash")
     , tempRootsDir(stateDir + "/temproots")
     , fnTempRoots(fmt("%s/%d", tempRootsDir, getpid()))
-    , publicKeys(getDefaultPublicKeys())
 {
     auto state(_state.lock());
 
@@ -964,12 +963,21 @@ void LocalStore::invalidatePath(State & state, const Path & path)
 }
 
 
+const PublicKeys & LocalStore::getPublicKeys()
+{
+    auto state(_state.lock());
+    if (!state->publicKeys)
+        state->publicKeys = std::make_unique<PublicKeys>(getDefaultPublicKeys());
+    return *state->publicKeys;
+}
+
+
 void LocalStore::addToStore(const ValidPathInfo & info, Source & source,
     RepairFlag repair, CheckSigsFlag checkSigs, std::shared_ptr<FSAccessor> accessor)
 {
     assert(info.narHash);
 
-    if (requireSigs && checkSigs && !info.checkSignatures(*this, publicKeys))
+    if (requireSigs && checkSigs && !info.checkSignatures(*this, getPublicKeys()))
         throw Error("cannot add path '%s' because it lacks a valid signature", info.path);
 
     addTempRoot(info.path);
diff --git a/src/libstore/local-store.hh b/src/libstore/local-store.hh
index 0d6c176595c8..1209a06356f7 100644
--- a/src/libstore/local-store.hh
+++ b/src/libstore/local-store.hh
@@ -77,6 +77,8 @@ private:
            minFree but not much below availAfterGC, then there is no
            point in starting a new GC. */
         uint64_t availAfterGC = std::numeric_limits<uint64_t>::max();
+
+        std::unique_ptr<PublicKeys> publicKeys;
     };
 
     Sync<State, std::recursive_mutex> _state;
@@ -100,7 +102,7 @@ private:
         settings.requireSigs,
         "require-sigs", "whether store paths should have a trusted signature on import"};
 
-    PublicKeys publicKeys;
+    const PublicKeys & getPublicKeys();
 
 public:
 
diff --git a/src/libstore/nar-info-disk-cache.cc b/src/libstore/nar-info-disk-cache.cc
index 3c52303f0ea4..35403e5df56f 100644
--- a/src/libstore/nar-info-disk-cache.cc
+++ b/src/libstore/nar-info-disk-cache.cc
@@ -1,6 +1,7 @@
 #include "nar-info-disk-cache.hh"
 #include "sync.hh"
 #include "sqlite.hh"
+#include "globals.hh"
 
 #include <sqlite3.h>
 
@@ -47,10 +48,6 @@ class NarInfoDiskCacheImpl : public NarInfoDiskCache
 {
 public:
 
-    /* How long negative and positive lookups are valid. */
-    const int ttlNegative = 3600;
-    const int ttlPositive = 30 * 24 * 3600;
-
     /* How often to purge expired entries from the cache. */
     const int purgeInterval = 24 * 3600;
 
@@ -116,8 +113,8 @@ public:
                 SQLiteStmt(state->db,
                     "delete from NARs where ((present = 0 and timestamp < ?) or (present = 1 and timestamp < ?))")
                     .use()
-                    (now - ttlNegative)
-                    (now - ttlPositive)
+                    (now - settings.ttlNegativeNarInfoCache)
+                    (now - settings.ttlPositiveNarInfoCache)
                     .exec();
 
                 debug("deleted %d entries from the NAR info disk cache", sqlite3_changes(state->db));
@@ -186,8 +183,8 @@ public:
             auto queryNAR(state->queryNAR.use()
                 (cache.id)
                 (hashPart)
-                (now - ttlNegative)
-                (now - ttlPositive));
+                (now - settings.ttlNegativeNarInfoCache)
+                (now - settings.ttlPositiveNarInfoCache));
 
             if (!queryNAR.next())
                 return {oUnknown, 0};
diff --git a/src/libstore/nix-store.pc.in b/src/libstore/nix-store.pc.in
index 3f1a2d83d2f2..5cf22faadcbe 100644
--- a/src/libstore/nix-store.pc.in
+++ b/src/libstore/nix-store.pc.in
@@ -5,5 +5,5 @@ includedir=@includedir@
 Name: Nix
 Description: Nix Package Manager
 Version: @PACKAGE_VERSION@
-Libs: -L${libdir} -lnixstore -lnixutil -lnixformat
-Cflags: -I${includedir}/nix
+Libs: -L${libdir} -lnixstore -lnixutil
+Cflags: -I${includedir}/nix -std=c++14
diff --git a/src/nix-build/nix-build.cc b/src/nix-build/nix-build.cc
index cf628519c6e7..a63b3e07ae77 100755
--- a/src/nix-build/nix-build.cc
+++ b/src/nix-build/nix-build.cc
@@ -212,7 +212,7 @@ void mainWrapped(int argc, char * * argv)
                 // read the shebang to understand which packages to read from. Since
                 // this is handled via nix-shell -p, we wrap our ruby script execution
                 // in ruby -e 'load' which ignores the shebangs.
-                envCommand = (format("exec %1% %2% -e 'load(\"%3%\") -- %4%") % execArgs % interpreter % script % joined.str()).str();
+                envCommand = (format("exec %1% %2% -e 'load(\"%3%\")' -- %4%") % execArgs % interpreter % script % joined.str()).str();
             } else {
                 envCommand = (format("exec %1% %2% %3% %4%") % execArgs % interpreter % script % joined.str()).str();
             }
diff --git a/src/nix-channel/local.mk b/src/nix-channel/local.mk
index 49fc105c6f79..c14e8c359ca0 100644
--- a/src/nix-channel/local.mk
+++ b/src/nix-channel/local.mk
@@ -2,6 +2,6 @@ programs += nix-channel
 
 nix-channel_DIR := $(d)
 
-nix-channel_LIBS = libmain libutil libformat libstore
+nix-channel_LIBS = libmain libformat libstore libutil
 
 nix-channel_SOURCES := $(d)/nix-channel.cc
diff --git a/src/nix-copy-closure/local.mk b/src/nix-copy-closure/local.mk
index 42bb34dd8201..5018ab975b44 100644
--- a/src/nix-copy-closure/local.mk
+++ b/src/nix-copy-closure/local.mk
@@ -2,6 +2,6 @@ programs += nix-copy-closure
 
 nix-copy-closure_DIR := $(d)
 
-nix-copy-closure_LIBS = libmain libutil libformat libstore
+nix-copy-closure_LIBS = libmain libformat libstore libutil
 
 nix-copy-closure_SOURCES := $(d)/nix-copy-closure.cc
diff --git a/src/nix-daemon/nix-daemon.cc b/src/nix-daemon/nix-daemon.cc
index f28a52b73ff9..35603af7082a 100644
--- a/src/nix-daemon/nix-daemon.cc
+++ b/src/nix-daemon/nix-daemon.cc
@@ -1035,7 +1035,7 @@ static void daemonLoop(char * * argv)
             }, options);
 
         } catch (Interrupted & e) {
-            throw;
+            return;
         } catch (Error & e) {
             printError(format("error processing connection: %1%") % e.msg());
         }
diff --git a/src/nix/edit.cc b/src/nix/edit.cc
index 7eaa86e2f914..c9671f76d0fa 100644
--- a/src/nix/edit.cc
+++ b/src/nix/edit.cc
@@ -61,7 +61,7 @@ struct CmdEdit : InstallableCommand
 
         auto editor = getEnv("EDITOR", "cat");
 
-        Strings args{editor};
+        auto args = tokenizeString<Strings>(editor);
 
         if (editor.find("emacs") != std::string::npos ||
             editor.find("nano") != std::string::npos ||
@@ -72,7 +72,7 @@ struct CmdEdit : InstallableCommand
 
         stopProgressBar();
 
-        execvp(editor.c_str(), stringsToCharPtrs(args).data());
+        execvp(args.front().c_str(), stringsToCharPtrs(args).data());
 
         throw SysError("cannot run editor '%s'", editor);
     }
diff --git a/src/nix/repl.cc b/src/nix/repl.cc
index 9216209173d9..f84774a53367 100644
--- a/src/nix/repl.cc
+++ b/src/nix/repl.cc
@@ -189,6 +189,7 @@ bool NixRepl::getLine(string & input, const std::string &prompt)
     if (!s) {
       switch (auto type = linenoiseKeyType()) {
         case 1: // ctrl-C
+          input = "";
           return true;
         case 2: // ctrl-D
           return false;
@@ -197,6 +198,7 @@ bool NixRepl::getLine(string & input, const std::string &prompt)
       }
     }
     input += s;
+    input += '\n';
     return true;
 }
 
diff --git a/src/nix/search.cc b/src/nix/search.cc
index 5ccf1b7cf529..539676698086 100644
--- a/src/nix/search.cc
+++ b/src/nix/search.cc
@@ -25,14 +25,14 @@ std::string hilite(const std::string & s, const std::smatch & m)
 
 struct CmdSearch : SourceExprCommand, MixJSON
 {
-    std::string re;
+    std::vector<std::string> res;
 
     bool writeCache = true;
     bool useCache = true;
 
     CmdSearch()
     {
-        expectArg("regex", &re, true);
+        expectArgs("regex", &res);
 
         mkFlag()
             .longName("update-cache")
@@ -68,9 +68,13 @@ struct CmdSearch : SourceExprCommand, MixJSON
                 "nix search blender"
             },
             Example{
-                "To search for Firefox and Chromium:",
+                "To search for Firefox or Chromium:",
                 "nix search 'firefox|chromium'"
             },
+            Example{
+                "To search for git and frontend or gui:",
+                "nix search git 'frontend|gui'"
+            },
         };
     }
 
@@ -81,9 +85,16 @@ struct CmdSearch : SourceExprCommand, MixJSON
         // Empty search string should match all packages
         // Use "^" here instead of ".*" due to differences in resulting highlighting
         // (see #1893 -- libc++ claims empty search string is not in POSIX grammar)
-        if (re.empty()) re = "^";
+        if (res.empty()) {
+            res.push_back("^");
+        }
 
-        std::regex regex(re, std::regex::extended | std::regex::icase);
+        std::vector<std::regex> regexes;
+        regexes.reserve(res.size());
+
+        for (auto &re : res) {
+            regexes.push_back(std::regex(re, std::regex::extended | std::regex::icase));
+        }
 
         auto state = getEvalState();
 
@@ -102,6 +113,7 @@ struct CmdSearch : SourceExprCommand, MixJSON
             debug("at attribute '%s'", attrPath);
 
             try {
+                uint found = 0;
 
                 state->forceValue(*v);
 
@@ -115,25 +127,33 @@ struct CmdSearch : SourceExprCommand, MixJSON
                 if (state->isDerivation(*v)) {
 
                     DrvInfo drv(*state, attrPath, v->attrs);
+                    std::string description;
+                    std::smatch attrPathMatch;
+                    std::smatch descriptionMatch;
+                    std::smatch nameMatch;
+                    std::string name;
 
                     DrvName parsed(drv.queryName());
 
-                    std::smatch attrPathMatch;
-                    std::regex_search(attrPath, attrPathMatch, regex);
+                    for (auto &regex : regexes) {
+                        std::regex_search(attrPath, attrPathMatch, regex);
 
-                    auto name = parsed.name;
-                    std::smatch nameMatch;
-                    std::regex_search(name, nameMatch, regex);
+                        name = parsed.name;
+                        std::regex_search(name, nameMatch, regex);
 
-                    std::string description = drv.queryMetaString("description");
-                    std::replace(description.begin(), description.end(), '\n', ' ');
-                    std::smatch descriptionMatch;
-                    std::regex_search(description, descriptionMatch, regex);
+                        description = drv.queryMetaString("description");
+                        std::replace(description.begin(), description.end(), '\n', ' ');
+                        std::regex_search(description, descriptionMatch, regex);
+
+                        if (!attrPathMatch.empty()
+                            || !nameMatch.empty()
+                            || !descriptionMatch.empty())
+                        {
+                            found++;
+                        }
+                    }
 
-                    if (!attrPathMatch.empty()
-                        || !nameMatch.empty()
-                        || !descriptionMatch.empty())
-                    {
+                    if (found == res.size()) {
                         if (json) {
 
                             auto jsonElem = jsonOut->object(attrPath);
diff --git a/tests/lang/eval-okay-regex-split.exp b/tests/lang/eval-okay-regex-split.exp
new file mode 100644
index 000000000000..27ba77ddaf61
--- /dev/null
+++ b/tests/lang/eval-okay-regex-split.exp
@@ -0,0 +1 @@
+true
diff --git a/tests/nix-shell.sh b/tests/nix-shell.sh
index 063e97ce2c75..d25c456cedfb 100644
--- a/tests/nix-shell.sh
+++ b/tests/nix-shell.sh
@@ -39,3 +39,12 @@ chmod a+rx $TEST_ROOT/shell.shebang.sh
 
 output=$($TEST_ROOT/shell.shebang.sh abc def)
 [ "$output" = "foo bar abc def" ]
+
+# Test nix-shell shebang mode for ruby
+# This uses a fake interpreter that returns the arguments passed
+# This, in turn, verifies the `rc` script is valid and the `load()` script (given using `-e`) is as expected.
+sed -e "s|@SHELL_PROG@|$(type -p nix-shell)|" shell.shebang.rb > $TEST_ROOT/shell.shebang.rb
+chmod a+rx $TEST_ROOT/shell.shebang.rb
+
+output=$($TEST_ROOT/shell.shebang.rb abc ruby)
+[ "$output" = '-e load("'"$TEST_ROOT"'/shell.shebang.rb") -- abc ruby' ]
diff --git a/tests/search.sh b/tests/search.sh
index d83427247d3e..0b26a125120f 100644
--- a/tests/search.sh
+++ b/tests/search.sh
@@ -29,6 +29,11 @@ clearCache
 # Check search that matches nothing
 (( $(nix search nosuchpackageexists | wc -l) == 0 ))
 
+# Search for multiple arguments
+(( $(nix search hello empty | wc -l) == 5 ))
+
+# Multiple arguments will not exist
+(( $(nix search hello broken | wc -l) == 0 ))
 
 ## Search expressions
 
diff --git a/tests/shell.nix b/tests/shell.nix
index 5845d36fc161..eb39f9039a88 100644
--- a/tests/shell.nix
+++ b/tests/shell.nix
@@ -45,5 +45,12 @@ let pkgs = rec {
 
   bash = shell;
 
+  # ruby "interpreter" that outputs "$@"
+  ruby = runCommand "ruby" {} ''
+    mkdir -p $out/bin
+    echo 'printf -- "$*"' > $out/bin/ruby
+    chmod a+rx $out/bin/ruby
+  '';
+
   inherit pkgs;
 }; in pkgs
diff --git a/tests/shell.shebang.rb b/tests/shell.shebang.rb
new file mode 100644
index 000000000000..ea67eb09c1c6
--- /dev/null
+++ b/tests/shell.shebang.rb
@@ -0,0 +1,7 @@
+#! @SHELL_PROG@
+#! ruby
+#! nix-shell -I nixpkgs=shell.nix --no-substitute
+#! nix-shell --pure -p ruby -i ruby
+
+# Contents doesn't matter.
+abort("This shouldn't be executed.")
diff --git a/version b/version
index 415b19fc3623..42f7d2336ea8 100644
--- a/version
+++ b/version
@@ -1 +1 @@
-2.0
\ No newline at end of file
+2.1
\ No newline at end of file