about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--doc/manual/command-ref/conf-file.xml10
-rw-r--r--doc/manual/release-notes/rl-1.11.xml7
-rw-r--r--doc/manual/release-notes/rl-1.12.xml7
-rw-r--r--local.mk3
-rw-r--r--perl/configure.ac13
-rw-r--r--perl/lib/Nix/Config.pm.in23
-rw-r--r--perl/lib/Nix/Store.xs2
-rw-r--r--release.nix3
-rw-r--r--src/boost/format/feed_args.hpp7
-rw-r--r--src/libexpr/eval.cc4
-rw-r--r--src/libexpr/local.mk2
-rw-r--r--src/libmain/common-args.cc6
-rw-r--r--src/libmain/shared.cc14
-rw-r--r--src/libstore/binary-cache-store.cc3
-rw-r--r--src/libstore/binary-cache-store.hh10
-rw-r--r--src/libstore/build.cc74
-rw-r--r--src/libstore/crypto.cc6
-rw-r--r--src/libstore/download.cc8
-rw-r--r--src/libstore/globals.cc258
-rw-r--r--src/libstore/globals.hh288
-rw-r--r--src/libstore/legacy-ssh-store.cc10
-rw-r--r--src/libstore/local-fs-store.cc9
-rw-r--r--src/libstore/local-store.cc11
-rw-r--r--src/libstore/local-store.hh6
-rw-r--r--src/libstore/remote-store.cc6
-rw-r--r--src/libstore/remote-store.hh3
-rw-r--r--src/libstore/s3-binary-cache-store.cc12
-rw-r--r--src/libstore/ssh-store.cc7
-rw-r--r--src/libstore/store-api.cc17
-rw-r--r--src/libstore/store-api.hh25
-rw-r--r--src/libutil/config.cc194
-rw-r--r--src/libutil/config.hh163
-rw-r--r--src/libutil/logging.cc9
-rw-r--r--src/libutil/logging.hh10
-rw-r--r--src/libutil/types.hh2
-rw-r--r--src/libutil/util.cc2
-rw-r--r--src/nix-daemon/nix-daemon.cc29
-rw-r--r--src/nix-store/nix-store.cc10
-rw-r--r--src/nix/show-config.cc43
-rwxr-xr-xtests/shell.shebang.sh2
-rw-r--r--tests/timeout.nix3
-rw-r--r--tests/timeout.sh5
42 files changed, 815 insertions, 511 deletions
diff --git a/doc/manual/command-ref/conf-file.xml b/doc/manual/command-ref/conf-file.xml
index 6952829e8f71..3de9647aa4eb 100644
--- a/doc/manual/command-ref/conf-file.xml
+++ b/doc/manual/command-ref/conf-file.xml
@@ -334,16 +334,6 @@ flag, e.g. <literal>--option gc-keep-outputs false</literal>.</para>
   </varlistentry>
 
 
-  <varlistentry><term><literal>use-binary-caches</literal></term>
-
-    <listitem><para>If set to <literal>true</literal> (the default),
-    Nix will check the binary caches specified by
-    <option>binary-caches</option> and related options to obtain
-    binary substitutes.</para></listitem>
-
-  </varlistentry>
-
-
   <varlistentry><term><literal>binary-caches</literal></term>
 
     <listitem><para>A list of URLs of binary caches, separated by
diff --git a/doc/manual/release-notes/rl-1.11.xml b/doc/manual/release-notes/rl-1.11.xml
index efb03d61393f..fe422dd1f893 100644
--- a/doc/manual/release-notes/rl-1.11.xml
+++ b/doc/manual/release-notes/rl-1.11.xml
@@ -122,13 +122,6 @@ $ diffoscope /nix/store/11a27shh6n2i…-zlib-1.2.8 /nix/store/11a27shh6n2i…-zl
   </listitem>
 
   <listitem>
-    <para>The Nix language now supports floating point numbers. They are
-    based on regular C++ <literal>float</literal> and compatible with
-    existing integers and number-related operations. Export and import to and
-    from JSON and XML works, too.
-  </para>
-  </listitem>
-  <listitem>
     <para>All "chroot"-containing strings got renamed to "sandbox".
       In particular, some Nix options got renamed, but the old names
       are still accepted as lower-priority aliases.
diff --git a/doc/manual/release-notes/rl-1.12.xml b/doc/manual/release-notes/rl-1.12.xml
index d6864b3f55d1..b7f45fc44a36 100644
--- a/doc/manual/release-notes/rl-1.12.xml
+++ b/doc/manual/release-notes/rl-1.12.xml
@@ -17,6 +17,13 @@
     have write access to the Nix database.</para>
   </listitem>
 
+  <listitem>
+    <para>The Nix language now supports floating point numbers. They are
+    based on regular C++ <literal>float</literal> and compatible with
+    existing integers and number-related operations. Export and import to and
+    from JSON and XML works, too.
+  </para>
+  </listitem>
 </itemizedlist>
 
 <para>This release has contributions from TBD.</para>
diff --git a/local.mk b/local.mk
index dc10e6870a87..0a225423741d 100644
--- a/local.mk
+++ b/local.mk
@@ -7,8 +7,7 @@ dist-files += configure config.h.in nix.spec perl/configure
 
 clean-files += Makefile.config
 
-GLOBAL_CXXFLAGS += -I . -I src -I src/libutil -I src/libstore -I src/libmain -I src/libexpr \
-  -Wno-unneeded-internal-declaration
+GLOBAL_CXXFLAGS += -I . -I src -I src/libutil -I src/libstore -I src/libmain -I src/libexpr
 
 $(foreach i, config.h $(call rwildcard, src/lib*, *.hh), \
   $(eval $(call install-file-in, $(i), $(includedir)/nix, 0644)))
diff --git a/perl/configure.ac b/perl/configure.ac
index d617c78535f6..7a6b28be23e8 100644
--- a/perl/configure.ac
+++ b/perl/configure.ac
@@ -52,7 +52,7 @@ PKG_CHECK_MODULES([SODIUM], [libsodium],
    have_sodium=1], [have_sodium=])
 AC_SUBST(HAVE_SODIUM, [$have_sodium])
 
-# Check for the required Perl dependencies (DBI, DBD::SQLite and WWW::Curl).
+# Check for the required Perl dependencies (DBI and DBD::SQLite).
 perlFlags="-I$perllibdir"
 
 AC_ARG_WITH(dbi, AC_HELP_STRING([--with-dbi=PATH],
@@ -63,10 +63,6 @@ AC_ARG_WITH(dbd-sqlite, AC_HELP_STRING([--with-dbd-sqlite=PATH],
   [prefix of the Perl DBD::SQLite library]),
   perlFlags="$perlFlags -I$withval")
 
-AC_ARG_WITH(www-curl, AC_HELP_STRING([--with-www-curl=PATH],
-  [prefix of the Perl WWW::Curl library]),
-  perlFlags="$perlFlags -I$withval")
-
 AC_MSG_CHECKING([whether DBD::SQLite works])
 if ! $perl $perlFlags -e 'use DBI; use DBD::SQLite;' 2>&5; then
     AC_MSG_RESULT(no)
@@ -74,13 +70,6 @@ if ! $perl $perlFlags -e 'use DBI; use DBD::SQLite;' 2>&5; then
 fi
 AC_MSG_RESULT(yes)
 
-AC_MSG_CHECKING([whether WWW::Curl works])
-if ! $perl $perlFlags -e 'use WWW::Curl;' 2>&5; then
-    AC_MSG_RESULT(no)
-    AC_MSG_FAILURE([The Perl module WWW::Curl is missing.])
-fi
-AC_MSG_RESULT(yes)
-
 AC_SUBST(perlFlags)
 
 PKG_CHECK_MODULES([NIX], [nix-store])
diff --git a/perl/lib/Nix/Config.pm.in b/perl/lib/Nix/Config.pm.in
index f494e34a5e7b..4bdee7fd89f9 100644
--- a/perl/lib/Nix/Config.pm.in
+++ b/perl/lib/Nix/Config.pm.in
@@ -20,22 +20,15 @@ $useBindings = 1;
 %config = ();
 
 sub readConfig {
-    if (defined $ENV{'_NIX_OPTIONS'}) {
-        foreach my $s (split '\n', $ENV{'_NIX_OPTIONS'}) {
-            my ($n, $v) = split '=', $s, 2;
-            $config{$n} = $v;
-        }
-    } else {
-        my $config = "$confDir/nix.conf";
-        return unless -f $config;
-
-        open CONFIG, "<$config" or die "cannot open ‘$config’";
-        while (<CONFIG>) {
-            /^\s*([\w\-\.]+)\s*=\s*(.*)$/ or next;
-            $config{$1} = $2;
-        }
-        close CONFIG;
+    my $config = "$confDir/nix.conf";
+    return unless -f $config;
+
+    open CONFIG, "<$config" or die "cannot open ‘$config’";
+    while (<CONFIG>) {
+        /^\s*([\w\-\.]+)\s*=\s*(.*)$/ or next;
+        $config{$1} = $2;
     }
+    close CONFIG;
 }
 
 return 1;
diff --git a/perl/lib/Nix/Store.xs b/perl/lib/Nix/Store.xs
index f613e3df329a..1920942a4c03 100644
--- a/perl/lib/Nix/Store.xs
+++ b/perl/lib/Nix/Store.xs
@@ -25,9 +25,7 @@ static ref<Store> store()
     static std::shared_ptr<Store> _store;
     if (!_store) {
         try {
-            logger = makeDefaultLogger();
             settings.loadConfFile();
-            settings.update();
             settings.lockCPU = false;
             _store = openStore();
         } catch (Error & e) {
diff --git a/release.nix b/release.nix
index 8727c2520b1e..294af54cd61b 100644
--- a/release.nix
+++ b/release.nix
@@ -7,7 +7,7 @@ let
 
   pkgs = import <nixpkgs> {};
 
-  systems = [ "x86_64-linux" "i686-linux" "x86_64-darwin" ];
+  systems = [ "x86_64-linux" "i686-linux" "x86_64-darwin" "aarch64-linux" ];
 
 
   jobs = rec {
@@ -116,7 +116,6 @@ let
         configureFlags = ''
           --with-dbi=${perlPackages.DBI}/${pkgs.perl.libPrefix}
           --with-dbd-sqlite=${perlPackages.DBDSQLite}/${pkgs.perl.libPrefix}
-          --with-www-curl=${perlPackages.WWWCurl}/${pkgs.perl.libPrefix}
         '';
 
         enableParallelBuilding = true;
diff --git a/src/boost/format/feed_args.hpp b/src/boost/format/feed_args.hpp
index 3d0b47b4a12e..cdd57fdf2bf1 100644
--- a/src/boost/format/feed_args.hpp
+++ b/src/boost/format/feed_args.hpp
@@ -41,6 +41,13 @@ namespace  {
                 std::streamsize w, 
                 const char c, 
                 std::ios::fmtflags f, 
+                bool center)
+    __attribute__ ((unused));
+
+  void do_pad( std::string & s, 
+                std::streamsize w, 
+                const char c, 
+                std::ios::fmtflags f, 
                 bool center) 
     // applies centered / left / right  padding  to the string s.
     // Effects : string s is padded.
diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc
index d418ab4e43aa..5e1ae63c4824 100644
--- a/src/libexpr/eval.cc
+++ b/src/libexpr/eval.cc
@@ -202,7 +202,7 @@ void initGC()
 
     GC_INIT();
 
-    GC_oom_fn = oomHandler;
+    GC_set_oom_fn(oomHandler);
 
     /* Set the initial heap size to something fairly big (25% of
        physical RAM, up to a maximum of 384 MiB) so that in most cases
@@ -299,7 +299,7 @@ EvalState::EvalState(const Strings & _searchPath, ref<Store> store)
 {
     countCalls = getEnv("NIX_COUNT_CALLS", "0") != "0";
 
-    restricted = settings.get("restrict-eval", false);
+    restricted = settings.restrictEval;
 
     assert(gcInitialised);
 
diff --git a/src/libexpr/local.mk b/src/libexpr/local.mk
index 620050a13b05..daa3258f0d3c 100644
--- a/src/libexpr/local.mk
+++ b/src/libexpr/local.mk
@@ -6,8 +6,6 @@ libexpr_DIR := $(d)
 
 libexpr_SOURCES := $(wildcard $(d)/*.cc) $(wildcard $(d)/primops/*.cc) $(d)/lexer-tab.cc $(d)/parser-tab.cc
 
-libexpr_CXXFLAGS := -Wno-deprecated-register
-
 libexpr_LIBS = libutil libstore libformat
 
 libexpr_LDFLAGS =
diff --git a/src/libmain/common-args.cc b/src/libmain/common-args.cc
index 98693d78a7f4..9a7a893138de 100644
--- a/src/libmain/common-args.cc
+++ b/src/libmain/common-args.cc
@@ -22,7 +22,11 @@ MixCommonArgs::MixCommonArgs(const string & programName)
         [](Strings ss) {
             auto name = ss.front(); ss.pop_front();
             auto value = ss.front();
-            settings.set(name, value);
+            try {
+                settings.set(name, value);
+            } catch (UsageError & e) {
+                warn(e.what());
+            }
         });
 }
 
diff --git a/src/libmain/shared.cc b/src/libmain/shared.cc
index a720afd6cdd4..4747b9bf9b4c 100644
--- a/src/libmain/shared.cc
+++ b/src/libmain/shared.cc
@@ -106,8 +106,6 @@ void initNix()
     std::cerr.rdbuf()->pubsetbuf(buf, sizeof(buf));
 #endif
 
-    logger = makeDefaultLogger();
-
     /* Initialise OpenSSL locking. */
     opensslLocks = std::vector<std::mutex>(CRYPTO_num_locks());
     CRYPTO_set_locking_callback(opensslLockCallback);
@@ -140,9 +138,6 @@ void initNix()
     struct timeval tv;
     gettimeofday(&tv, 0);
     srandom(tv.tv_usec);
-
-    if (char *pack = getenv("_NIX_OPTIONS"))
-        settings.unpack(pack);
 }
 
 
@@ -158,13 +153,13 @@ struct LegacyArgs : public MixCommonArgs
             &settings.verboseBuild, false);
 
         mkFlag('K', "keep-failed", "keep temporary directories of failed builds",
-            &settings.keepFailed);
+            &(bool&) settings.keepFailed);
 
         mkFlag('k', "keep-going", "keep going after a build fails",
-            &settings.keepGoing);
+            &(bool&) settings.keepGoing);
 
         mkFlag(0, "fallback", "build from source if substitution fails", []() {
-            settings.set("build-fallback", "true");
+            settings.tryFallback = true;
         });
 
         mkFlag1('j', "max-jobs", "jobs", "maximum number of parallel builds", [=](std::string s) {
@@ -186,7 +181,7 @@ struct LegacyArgs : public MixCommonArgs
             &settings.readOnlyMode);
 
         mkFlag(0, "no-build-hook", "disable use of the build hook mechanism",
-            &settings.useBuildHook, false);
+            &(bool&) settings.useBuildHook, false);
 
         mkFlag(0, "show-trace", "show Nix expression stack trace in evaluation errors",
             &settings.showTrace);
@@ -220,7 +215,6 @@ void parseCmdLine(int argc, char * * argv,
     std::function<bool(Strings::iterator & arg, const Strings::iterator & end)> parseArg)
 {
     LegacyArgs(baseNameOf(argv[0]), parseArg).parseCmdline(argvToStrings(argc, argv));
-    settings.update();
 }
 
 
diff --git a/src/libstore/binary-cache-store.cc b/src/libstore/binary-cache-store.cc
index 25ad0d75b70a..b536c6c00044 100644
--- a/src/libstore/binary-cache-store.cc
+++ b/src/libstore/binary-cache-store.cc
@@ -79,10 +79,7 @@ struct BinaryCacheStoreAccessor : public FSAccessor
 
 BinaryCacheStore::BinaryCacheStore(const Params & params)
     : Store(params)
-    , compression(get(params, "compression", "xz"))
-    , writeNARListing(get(params, "write-nar-listing", "0") == "1")
 {
-    auto secretKeyFile = get(params, "secret-key", "");
     if (secretKeyFile != "")
         secretKey = std::unique_ptr<SecretKey>(new SecretKey(readFile(secretKeyFile)));
 
diff --git a/src/libstore/binary-cache-store.hh b/src/libstore/binary-cache-store.hh
index d42b1abd2455..5c2d0acfdbb7 100644
--- a/src/libstore/binary-cache-store.hh
+++ b/src/libstore/binary-cache-store.hh
@@ -13,13 +13,15 @@ struct NarInfo;
 
 class BinaryCacheStore : public Store
 {
-private:
+public:
 
-    std::unique_ptr<SecretKey> secretKey;
+    const Setting<std::string> compression{this, "xz", "compression", "NAR compression method ('xz', 'bzip2', or 'none')"};
+    const Setting<bool> writeNARListing{this, false, "write-nar-listing", "whether to write a JSON file listing the files in each NAR"};
+    const Setting<Path> secretKeyFile{this, "", "secret-key", "path to secret key used to sign the binary cache"};
 
-    std::string compression;
+private:
 
-    bool writeNARListing;
+    std::unique_ptr<SecretKey> secretKey;
 
 protected:
 
diff --git a/src/libstore/build.cc b/src/libstore/build.cc
index 968e291129f3..9bf1ab5aa581 100644
--- a/src/libstore/build.cc
+++ b/src/libstore/build.cc
@@ -34,13 +34,6 @@
 #include <pwd.h>
 #include <grp.h>
 
-/* chroot-like behavior from Apple's sandbox */
-#if __APPLE__
-    #define DEFAULT_ALLOWED_IMPURE_PREFIXES "/System/Library /usr/lib /dev /bin/sh"
-#else
-    #define DEFAULT_ALLOWED_IMPURE_PREFIXES ""
-#endif
-
 /* Includes required for chroot support. */
 #if __linux__
 #include <sys/socket.h>
@@ -469,7 +462,7 @@ UserLock::UserLock()
     assert(settings.buildUsersGroup != "");
 
     /* Get the members of the build-users-group. */
-    struct group * gr = getgrnam(settings.buildUsersGroup.c_str());
+    struct group * gr = getgrnam(settings.buildUsersGroup.get().c_str());
     if (!gr)
         throw Error(format("the group ‘%1%’ specified in ‘build-users-group’ does not exist")
             % settings.buildUsersGroup);
@@ -1279,7 +1272,7 @@ void DerivationGoal::inputsRealised()
 
     /* Don't repeat fixed-output derivations since they're already
        verified by their output hash.*/
-    nrRounds = fixedOutput ? 1 : settings.get("build-repeat", 0) + 1;
+    nrRounds = fixedOutput ? 1 : settings.buildRepeat + 1;
 
     /* Okay, try to build.  Note that here we don't wait for a build
        slot to become available, since we don't need one if there is a
@@ -1697,12 +1690,7 @@ void DerivationGoal::startBuilder()
 
     /* Are we doing a chroot build? */
     {
-        string x = settings.get("build-use-sandbox",
-            /* deprecated alias */
-            settings.get("build-use-chroot", string("false")));
-        if (x != "true" && x != "false" && x != "relaxed")
-            throw Error("option ‘build-use-sandbox’ must be set to one of ‘true’, ‘false’ or ‘relaxed’");
-        if (x == "true") {
+        if (settings.sandboxMode == smEnabled) {
             if (get(drv->env, "__noChroot") == "1")
                 throw Error(format("derivation ‘%1%’ has ‘__noChroot’ set, "
                     "but that's not allowed when ‘build-use-sandbox’ is ‘true’") % drvPath);
@@ -1713,9 +1701,9 @@ void DerivationGoal::startBuilder()
 #endif
             useChroot = true;
         }
-        else if (x == "false")
+        else if (settings.sandboxMode == smDisabled)
             useChroot = false;
-        else if (x == "relaxed")
+        else if (settings.sandboxMode == smRelaxed)
             useChroot = !fixedOutput && get(drv->env, "__noChroot") != "1";
     }
 
@@ -1756,21 +1744,10 @@ void DerivationGoal::startBuilder()
 
     if (useChroot) {
 
-        string defaultChrootDirs;
-#if __linux__
-        if (worker.store.isInStore(BASH_PATH))
-            defaultChrootDirs = "/bin/sh=" BASH_PATH;
-#endif
-
         /* Allow a user-configurable set of directories from the
            host file system. */
-        PathSet dirs = tokenizeString<StringSet>(
-            settings.get("build-sandbox-paths",
-                /* deprecated alias with lower priority */
-                settings.get("build-chroot-dirs", defaultChrootDirs)));
-        PathSet dirs2 = tokenizeString<StringSet>(
-            settings.get("build-extra-chroot-dirs",
-                settings.get("build-extra-sandbox-paths", string(""))));
+        PathSet dirs = settings.sandboxPaths;
+        PathSet dirs2 = settings.extraSandboxPaths;
         dirs.insert(dirs2.begin(), dirs2.end());
 
         dirsInChroot.clear();
@@ -1802,8 +1779,7 @@ void DerivationGoal::startBuilder()
         for (auto & i : closure)
             dirsInChroot[i] = i;
 
-        string allowed = settings.get("allowed-impure-host-deps", string(DEFAULT_ALLOWED_IMPURE_PREFIXES));
-        PathSet allowedPaths = tokenizeString<StringSet>(allowed);
+        PathSet allowedPaths = settings.allowedImpureHostPrefixes;
 
         /* This works like the above, except on a per-derivation level */
         Strings impurePaths = tokenizeString<Strings>(get(drv->env, "__impureHostDeps"));
@@ -1823,7 +1799,7 @@ void DerivationGoal::startBuilder()
                 }
             }
             if (!found)
-                throw Error(format("derivation ‘%1%’ requested impure path ‘%2%’, but it was not in allowed-impure-host-deps (‘%3%’)") % drvPath % i % allowed);
+                throw Error(format("derivation ‘%1%’ requested impure path ‘%2%’, but it was not in allowed-impure-host-deps") % drvPath % i);
 
             dirsInChroot[i] = i;
         }
@@ -2444,7 +2420,7 @@ void DerivationGoal::runChild()
             /* Mount a new tmpfs on /dev/shm to ensure that whatever
                the builder puts in /dev/shm is cleaned up automatically. */
             if (pathExists("/dev/shm") && mount("none", (chrootRootDir + "/dev/shm").c_str(), "tmpfs", 0,
-                    fmt("size=%s", settings.get("sandbox-dev-shm-size", std::string("50%"))).c_str()) == -1)
+                    fmt("size=%s", settings.sandboxShmSize).c_str()) == -1)
                 throw SysError("mounting /dev/shm");
 
             /* Mount a new devpts on /dev/pts.  Note that this
@@ -2602,7 +2578,7 @@ void DerivationGoal::runChild()
             sandboxProfile += "(version 1)\n";
 
             /* Violations will go to the syslog if you set this. Unfortunately the destination does not appear to be configurable */
-            if (settings.get("darwin-log-sandbox-violations", false)) {
+            if (settings.darwinLogSandboxViolations) {
                 sandboxProfile += "(deny default)\n";
             } else {
                 sandboxProfile += "(deny default (with no-log))\n";
@@ -2749,7 +2725,7 @@ void DerivationGoal::registerOutputs()
     InodesSeen inodesSeen;
 
     Path checkSuffix = ".check";
-    bool runDiffHook = settings.get("run-diff-hook", false);
+    bool runDiffHook = settings.runDiffHook;
     bool keepPreviousRound = settings.keepFailed || runDiffHook;
 
     /* Check whether the output paths were created, and grep each
@@ -2990,7 +2966,7 @@ void DerivationGoal::registerOutputs()
                     ? fmt("output ‘%1%’ of ‘%2%’ differs from ‘%3%’ from previous round", i->path, drvPath, prev)
                     : fmt("output ‘%1%’ of ‘%2%’ differs from previous round", i->path, drvPath);
 
-                auto diffHook = settings.get("diff-hook", std::string(""));
+                auto diffHook = settings.diffHook;
                 if (prevExists && diffHook != "" && runDiffHook) {
                     try {
                         auto diff = runProgram(diffHook, true, {prev, i->path});
@@ -3001,7 +2977,7 @@ void DerivationGoal::registerOutputs()
                     }
                 }
 
-                if (settings.get("enforce-determinism", true))
+                if (settings.enforceDeterminism)
                     throw NotDeterministic(msg);
 
                 printError(msg);
@@ -3051,13 +3027,11 @@ Path DerivationGoal::openLogFile()
     string baseName = baseNameOf(drvPath);
 
     /* Create a log file. */
-    Path dir = (format("%1%/%2%/%3%/") % worker.store.logDir % worker.store.drvsLogDir % string(baseName, 0, 2)).str();
+    Path dir = fmt("%s/%s/%s/", worker.store.logDir, worker.store.drvsLogDir, string(baseName, 0, 2));
     createDirs(dir);
 
-    Path logFileName = (format("%1%/%2%%3%")
-        % dir
-        % string(baseName, 2)
-        % (settings.compressLog ? ".bz2" : "")).str();
+    Path logFileName = fmt("%s/%s%s", dir, string(baseName, 2),
+        settings.compressLog ? ".bz2" : "");
 
     fdLogFile = open(logFileName.c_str(), O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, 0666);
     if (!fdLogFile) throw SysError(format("creating log file ‘%1%’") % logFileName);
@@ -3402,10 +3376,10 @@ void SubstitutionGoal::tryToRun()
     trace("trying to run");
 
     /* Make sure that we are allowed to start a build.  Note that even
-       is maxBuildJobs == 0 (no local builds allowed), we still allow
+       if maxBuildJobs == 0 (no local builds allowed), we still allow
        a substituter to run.  This is because substitutions cannot be
        distributed to another machine via the build hook. */
-    if (worker.getNrLocalBuilds() >= (settings.maxBuildJobs == 0 ? 1 : settings.maxBuildJobs)) {
+    if (worker.getNrLocalBuilds() >= std::min(1U, (unsigned int) settings.maxBuildJobs)) {
         worker.waitForBuildSlot(shared_from_this());
         return;
     }
@@ -3686,7 +3660,7 @@ void Worker::run(const Goals & _topGoals)
         if (!children.empty() || !waitingForAWhile.empty())
             waitForInput();
         else {
-            if (awake.empty() && settings.maxBuildJobs == 0) throw Error(
+            if (awake.empty() && 0 == settings.maxBuildJobs) throw Error(
                 "unable to start any build; either increase ‘--max-jobs’ "
                 "or enable distributed builds");
             assert(!awake.empty());
@@ -3723,9 +3697,9 @@ void Worker::waitForInput()
     auto nearest = steady_time_point::max(); // nearest deadline
     for (auto & i : children) {
         if (!i.respectTimeouts) continue;
-        if (settings.maxSilentTime != 0)
+        if (0 != settings.maxSilentTime)
             nearest = std::min(nearest, i.lastOutput + std::chrono::seconds(settings.maxSilentTime));
-        if (settings.buildTimeout != 0)
+        if (0 != settings.buildTimeout)
             nearest = std::min(nearest, i.timeStarted + std::chrono::seconds(settings.buildTimeout));
     }
     if (nearest != steady_time_point::max()) {
@@ -3803,7 +3777,7 @@ void Worker::waitForInput()
         }
 
         if (goal->getExitCode() == Goal::ecBusy &&
-            settings.maxSilentTime != 0 &&
+            0 != settings.maxSilentTime &&
             j->respectTimeouts &&
             after - j->lastOutput >= std::chrono::seconds(settings.maxSilentTime))
         {
@@ -3814,7 +3788,7 @@ void Worker::waitForInput()
         }
 
         else if (goal->getExitCode() == Goal::ecBusy &&
-            settings.buildTimeout != 0 &&
+            0 != settings.buildTimeout &&
             j->respectTimeouts &&
             after - j->timeStarted >= std::chrono::seconds(settings.buildTimeout))
         {
diff --git a/src/libstore/crypto.cc b/src/libstore/crypto.cc
index 0fc86a1fe921..f56a6adab9c9 100644
--- a/src/libstore/crypto.cc
+++ b/src/libstore/crypto.cc
@@ -105,14 +105,12 @@ PublicKeys getDefaultPublicKeys()
 
     // FIXME: filter duplicates
 
-    for (auto s : settings.get("binary-cache-public-keys",
-            Strings{"cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="}))
-    {
+    for (auto s : settings.binaryCachePublicKeys.get()) {
         PublicKey key(s);
         publicKeys.emplace(key.name, key);
     }
 
-    for (auto secretKeyFile : settings.get("secret-key-files", Strings())) {
+    for (auto secretKeyFile : settings.secretKeyFiles.get()) {
         try {
             SecretKey secretKey(readFile(secretKeyFile));
             publicKeys.emplace(secretKey.name, secretKey.toPublicKey());
diff --git a/src/libstore/download.cc b/src/libstore/download.cc
index f8f578695033..42fd05bd80d7 100644
--- a/src/libstore/download.cc
+++ b/src/libstore/download.cc
@@ -251,7 +251,7 @@ struct CurlDownloader : public Downloader
 
             /* If no file exist in the specified path, curl continues to work
                anyway as if netrc support was disabled. */
-            curl_easy_setopt(req, CURLOPT_NETRC_FILE, settings.netrcFile.c_str());
+            curl_easy_setopt(req, CURLOPT_NETRC_FILE, settings.netrcFile.get().c_str());
             curl_easy_setopt(req, CURLOPT_NETRC, CURL_NETRC_OPTIONAL);
 
             result.data = std::make_shared<std::string>();
@@ -369,9 +369,9 @@ struct CurlDownloader : public Downloader
         curl_multi_setopt(curlm, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX);
         #endif
         curl_multi_setopt(curlm, CURLMOPT_MAX_TOTAL_CONNECTIONS,
-            settings.get("binary-caches-parallel-connections", 25));
+            settings.binaryCachesParallelConnections.get());
 
-        enableHttp2 = settings.get("enable-http2", true);
+        enableHttp2 = settings.enableHttp2;
 
         wakeupPipe.create();
         fcntl(wakeupPipe.readSide.get(), F_SETFL, O_NONBLOCK);
@@ -611,7 +611,7 @@ Path Downloader::downloadCached(ref<Store> store, const string & url_, bool unpa
 
     string expectedETag;
 
-    int ttl = settings.get("tarball-ttl", 60 * 60);
+    int ttl = settings.tarballTtl;
     bool skip = false;
 
     if (pathExists(fileLink) && pathExists(dataFile)) {
diff --git a/src/libstore/globals.cc b/src/libstore/globals.cc
index 8c900be77b8f..bb61daa51642 100644
--- a/src/libstore/globals.cc
+++ b/src/libstore/globals.cc
@@ -1,6 +1,7 @@
 #include "globals.hh"
 #include "util.hh"
 #include "archive.hh"
+#include "args.hh"
 
 #include <algorithm>
 #include <map>
@@ -17,255 +18,98 @@ namespace nix {
    must be deleted and recreated on startup.) */
 #define DEFAULT_SOCKET_PATH "/daemon-socket/socket"
 
+/* chroot-like behavior from Apple's sandbox */
+#if __APPLE__
+    #define DEFAULT_ALLOWED_IMPURE_PREFIXES "/System/Library /usr/lib /dev /bin/sh"
+#else
+    #define DEFAULT_ALLOWED_IMPURE_PREFIXES ""
+#endif
 
 Settings settings;
 
-
 Settings::Settings()
+    : Config({})
+    , nixPrefix(NIX_PREFIX)
+    , nixStore(canonPath(getEnv("NIX_STORE_DIR", getEnv("NIX_STORE", NIX_STORE_DIR))))
+    , nixDataDir(canonPath(getEnv("NIX_DATA_DIR", NIX_DATA_DIR)))
+    , nixLogDir(canonPath(getEnv("NIX_LOG_DIR", NIX_LOG_DIR)))
+    , nixStateDir(canonPath(getEnv("NIX_STATE_DIR", NIX_STATE_DIR)))
+    , nixConfDir(canonPath(getEnv("NIX_CONF_DIR", NIX_CONF_DIR)))
+    , nixLibexecDir(canonPath(getEnv("NIX_LIBEXEC_DIR", NIX_LIBEXEC_DIR)))
+    , nixBinDir(canonPath(getEnv("NIX_BIN_DIR", NIX_BIN_DIR)))
+    , nixDaemonSocketFile(canonPath(nixStateDir + DEFAULT_SOCKET_PATH))
 {
-    nixPrefix = NIX_PREFIX;
-    nixStore = canonPath(getEnv("NIX_STORE_DIR", getEnv("NIX_STORE", NIX_STORE_DIR)));
-    nixDataDir = canonPath(getEnv("NIX_DATA_DIR", NIX_DATA_DIR));
-    nixLogDir = canonPath(getEnv("NIX_LOG_DIR", NIX_LOG_DIR));
-    nixStateDir = canonPath(getEnv("NIX_STATE_DIR", NIX_STATE_DIR));
-    nixConfDir = canonPath(getEnv("NIX_CONF_DIR", NIX_CONF_DIR));
-    nixLibexecDir = canonPath(getEnv("NIX_LIBEXEC_DIR", NIX_LIBEXEC_DIR));
-    nixBinDir = canonPath(getEnv("NIX_BIN_DIR", NIX_BIN_DIR));
-    nixDaemonSocketFile = canonPath(nixStateDir + DEFAULT_SOCKET_PATH);
-
-    // should be set with the other config options, but depends on nixLibexecDir
-#ifdef __APPLE__
-    preBuildHook = nixLibexecDir + "/nix/resolve-system-dependencies";
-#endif
-
-    keepFailed = false;
-    keepGoing = false;
-    tryFallback = false;
-    maxBuildJobs = 1;
-    buildCores = std::max(1U, std::thread::hardware_concurrency());
-    readOnlyMode = false;
-    thisSystem = SYSTEM;
-    maxSilentTime = 0;
-    buildTimeout = 0;
-    useBuildHook = true;
-    reservedSize = 8 * 1024 * 1024;
-    fsyncMetadata = true;
-    useSQLiteWAL = true;
-    syncBeforeRegistering = false;
-    useSubstitutes = true;
     buildUsersGroup = getuid() == 0 ? "nixbld" : "";
-    useSshSubstituter = true;
-    impersonateLinux26 = false;
-    keepLog = true;
-    compressLog = true;
-    maxLogSize = 0;
-    pollInterval = 5;
-    checkRootReachability = false;
-    gcKeepOutputs = false;
-    gcKeepDerivations = true;
-    autoOptimiseStore = false;
-    envKeepDerivations = false;
     lockCPU = getEnv("NIX_AFFINITY_HACK", "1") == "1";
-    showTrace = false;
-    enableNativeCode = false;
-    netrcFile = fmt("%s/%s", nixConfDir, "netrc");
     caFile = getEnv("NIX_SSL_CERT_FILE", getEnv("SSL_CERT_FILE", "/etc/ssl/certs/ca-certificates.crt"));
-    enableImportFromDerivation = true;
-}
 
+#if __linux__
+    sandboxPaths = tokenizeString<StringSet>("/bin/sh=" BASH_PATH);
+#endif
+
+    allowedImpureHostPrefixes = tokenizeString<StringSet>(DEFAULT_ALLOWED_IMPURE_PREFIXES);
+}
 
 void Settings::loadConfFile()
 {
-    Path settingsFile = (format("%1%/%2%") % nixConfDir % "nix.conf").str();
-    if (!pathExists(settingsFile)) return;
-    string contents = readFile(settingsFile);
-
-    unsigned int pos = 0;
-
-    while (pos < contents.size()) {
-        string line;
-        while (pos < contents.size() && contents[pos] != '\n')
-            line += contents[pos++];
-        pos++;
-
-        string::size_type hash = line.find('#');
-        if (hash != string::npos)
-            line = string(line, 0, hash);
-
-        vector<string> tokens = tokenizeString<vector<string> >(line);
-        if (tokens.empty()) continue;
-
-        if (tokens.size() < 2 || tokens[1] != "=")
-            throw Error(format("illegal configuration line ‘%1%’ in ‘%2%’") % line % settingsFile);
-
-        string name = tokens[0];
-
-        vector<string>::iterator i = tokens.begin();
-        advance(i, 2);
-        settings[name] = concatStringsSep(" ", Strings(i, tokens.end())); // FIXME: slow
-    };
+    applyConfigFile(nixConfDir + "/nix.conf");
 }
 
-
 void Settings::set(const string & name, const string & value)
 {
-    settings[name] = value;
     overrides[name] = value;
+    Config::set(name, value);
 }
 
-
-string Settings::get(const string & name, const string & def)
-{
-    auto i = settings.find(name);
-    if (i == settings.end()) return def;
-    return i->second;
-}
-
-
-Strings Settings::get(const string & name, const Strings & def)
-{
-    auto i = settings.find(name);
-    if (i == settings.end()) return def;
-    return tokenizeString<Strings>(i->second);
-}
-
-
-bool Settings::get(const string & name, bool def)
+StringMap Settings::getOverrides()
 {
-    bool res = def;
-    _get(res, name);
-    return res;
+    return overrides;
 }
 
-
-int Settings::get(const string & name, int def)
+unsigned int Settings::getDefaultCores()
 {
-    int res = def;
-    _get(res, name);
-    return res;
-}
-
-
-void Settings::update()
-{
-    _get(tryFallback, "build-fallback");
-
-    auto s = get("build-max-jobs", std::string("1"));
-    if (s == "auto")
-        maxBuildJobs = std::max(1U, std::thread::hardware_concurrency());
-    else
-        if (!string2Int(s, maxBuildJobs))
-            throw Error("configuration setting ‘build-max-jobs’ should be ‘auto’ or an integer");
-
-    _get(buildCores, "build-cores");
-    _get(thisSystem, "system");
-    _get(maxSilentTime, "build-max-silent-time");
-    _get(buildTimeout, "build-timeout");
-    _get(reservedSize, "gc-reserved-space");
-    _get(fsyncMetadata, "fsync-metadata");
-    _get(useSQLiteWAL, "use-sqlite-wal");
-    _get(syncBeforeRegistering, "sync-before-registering");
-    _get(useSubstitutes, "build-use-substitutes");
-    _get(buildUsersGroup, "build-users-group");
-    _get(impersonateLinux26, "build-impersonate-linux-26");
-    _get(keepLog, "build-keep-log");
-    _get(compressLog, "build-compress-log");
-    _get(maxLogSize, "build-max-log-size");
-    _get(pollInterval, "build-poll-interval");
-    _get(checkRootReachability, "gc-check-reachability");
-    _get(gcKeepOutputs, "gc-keep-outputs");
-    _get(gcKeepDerivations, "gc-keep-derivations");
-    _get(autoOptimiseStore, "auto-optimise-store");
-    _get(envKeepDerivations, "env-keep-derivations");
-    _get(sshSubstituterHosts, "ssh-substituter-hosts");
-    _get(useSshSubstituter, "use-ssh-substituter");
-    _get(enableNativeCode, "allow-unsafe-native-code-during-evaluation");
-    _get(useCaseHack, "use-case-hack");
-    _get(preBuildHook, "pre-build-hook");
-    _get(keepGoing, "keep-going");
-    _get(keepFailed, "keep-failed");
-    _get(netrcFile, "netrc-file");
-    _get(enableImportFromDerivation, "allow-import-from-derivation");
+    return std::max(1U, std::thread::hardware_concurrency());
 }
 
+const string nixVersion = PACKAGE_VERSION;
 
-void Settings::_get(string & res, const string & name)
+template<> void Setting<SandboxMode>::set(const std::string & str)
 {
-    SettingsMap::iterator i = settings.find(name);
-    if (i == settings.end()) return;
-    res = i->second;
+    if (str == "true") value = smEnabled;
+    else if (str == "relaxed") value = smRelaxed;
+    else if (str == "false") value = smDisabled;
+    else throw UsageError("option '%s' has invalid value '%s'", name, str);
 }
 
-
-void Settings::_get(bool & res, const string & name)
+template<> std::string Setting<SandboxMode>::to_string()
 {
-    SettingsMap::iterator i = settings.find(name);
-    if (i == settings.end()) return;
-    if (i->second == "true") res = true;
-    else if (i->second == "false") res = false;
-    else throw Error(format("configuration option ‘%1%’ should be either ‘true’ or ‘false’, not ‘%2%’")
-        % name % i->second);
+    if (value == smEnabled) return "true";
+    else if (value == smRelaxed) return "relaxed";
+    else if (value == smDisabled) return "false";
+    else abort();
 }
 
-
-void Settings::_get(StringSet & res, const string & name)
+template<> void Setting<unsigned int, Settings::MaxBuildJobsTag>::set(const std::string & str)
 {
-    SettingsMap::iterator i = settings.find(name);
-    if (i == settings.end()) return;
-    res.clear();
-    Strings ss = tokenizeString<Strings>(i->second);
-    res.insert(ss.begin(), ss.end());
+    if (str == "auto") value = std::max(1U, std::thread::hardware_concurrency());
+    else if (!string2Int(str, value))
+        throw UsageError("configuration setting ‘%s’ should be ‘auto’ or an integer", name);
 }
 
-void Settings::_get(Strings & res, const string & name)
+template<> std::string Setting<unsigned int, Settings::MaxBuildJobsTag>::to_string()
 {
-    SettingsMap::iterator i = settings.find(name);
-    if (i == settings.end()) return;
-    res = tokenizeString<Strings>(i->second);
+    return std::to_string(value);
 }
 
-
-template<class N> void Settings::_get(N & res, const string & name)
+template<> void Setting<bool, Settings::CaseHackTag>::set(const std::string & str)
 {
-    SettingsMap::iterator i = settings.find(name);
-    if (i == settings.end()) return;
-    if (!string2Int(i->second, res))
-        throw Error(format("configuration setting ‘%1%’ should have an integer value") % name);
+    value = parseBool(str);
+    nix::useCaseHack = true;
 }
 
-
-string Settings::pack()
+template<> std::string Setting<bool, Settings::CaseHackTag>::to_string()
 {
-    string s;
-    for (auto & i : settings) {
-        if (i.first.find('\n') != string::npos ||
-            i.first.find('=') != string::npos ||
-            i.second.find('\n') != string::npos)
-            throw Error("illegal option name/value");
-        s += i.first; s += '='; s += i.second; s += '\n';
-    }
-    return s;
+    return printBool(value);
 }
 
-
-void Settings::unpack(const string & pack) {
-    Strings lines = tokenizeString<Strings>(pack, "\n");
-    for (auto & i : lines) {
-        string::size_type eq = i.find('=');
-        if (eq == string::npos)
-            throw Error("illegal option name/value");
-        set(i.substr(0, eq), i.substr(eq + 1));
-    }
-}
-
-
-Settings::SettingsMap Settings::getOverrides()
-{
-    return overrides;
-}
-
-
-const string nixVersion = PACKAGE_VERSION;
-
-
 }
diff --git a/src/libstore/globals.hh b/src/libstore/globals.hh
index ccec300f776e..f3a6038cafae 100644
--- a/src/libstore/globals.hh
+++ b/src/libstore/globals.hh
@@ -1,7 +1,7 @@
 #pragma once
 
 #include "types.hh"
-#include "logging.hh"
+#include "config.hh"
 
 #include <map>
 #include <sys/types.h>
@@ -9,40 +9,33 @@
 
 namespace nix {
 
+typedef enum { smEnabled, smRelaxed, smDisabled } SandboxMode;
 
-struct Settings {
+extern bool useCaseHack; // FIXME
 
-    typedef std::map<string, string> SettingsMap;
+class Settings : public Config {
 
-    Settings();
-
-    void loadConfFile();
-
-    void set(const string & name, const string & value);
+    StringMap overrides;
 
-    string get(const string & name, const string & def);
+    unsigned int getDefaultCores();
 
-    Strings get(const string & name, const Strings & def);
+public:
 
-    bool get(const string & name, bool def);
-
-    int get(const string & name, int def);
+    Settings();
 
-    void update();
+    void loadConfFile();
 
-    string pack();
+    void set(const string & name, const string & value);
 
-    void unpack(const string & pack);
+    StringMap getOverrides();
 
-    SettingsMap getOverrides();
+    Path nixPrefix;
 
     /* The directory where we store sources and derived files. */
     Path nixStore;
 
     Path nixDataDir; /* !!! fix */
 
-    Path nixPrefix;
-
     /* The directory where we log various operations. */
     Path nixLogDir;
 
@@ -61,17 +54,14 @@ struct Settings {
     /* File name of the socket the daemon listens to.  */
     Path nixDaemonSocketFile;
 
-    /* Whether to keep temporary directories of failed builds. */
-    bool keepFailed;
+    Setting<bool> keepFailed{this, false, "keep-failed",
+        "Whether to keep temporary directories of failed builds."};
 
-    /* Whether to keep building subgoals when a sibling (another
-       subgoal of the same goal) fails. */
-    bool keepGoing;
+    Setting<bool> keepGoing{this, false, "keep-going",
+        "Whether to keep building derivations when another build fails."};
 
-    /* Whether, if we cannot realise the known closure corresponding
-       to a derivation, we should try to normalise the derivation
-       instead. */
-    bool tryFallback;
+    Setting<bool> tryFallback{this, false, "build-fallback",
+        "Whether to fall back to building when substitution fails."};
 
     /* Whether to show build log output in real time. */
     bool verboseBuild = true;
@@ -80,132 +70,206 @@ struct Settings {
        the log to show if a build fails. */
     size_t logLines = 10;
 
-    /* Maximum number of parallel build jobs.  0 means unlimited. */
-    unsigned int maxBuildJobs;
+    struct MaxBuildJobsTag { };
+    Setting<unsigned int, MaxBuildJobsTag> maxBuildJobs{this, 1, "build-max-jobs",
+        "Maximum number of parallel build jobs. \"auto\" means use number of cores."};
 
-    /* Number of CPU cores to utilize in parallel within a build,
-       i.e. by passing this number to Make via '-j'. 0 means that the
-       number of actual CPU cores on the local host ought to be
-       auto-detected. */
-    unsigned int buildCores;
+    Setting<unsigned int> buildCores{this, getDefaultCores(), "build-cores",
+        "Number of CPU cores to utilize in parallel within a build, "
+        "i.e. by passing this number to Make via '-j'. 0 means that the "
+        "number of actual CPU cores on the local host ought to be "
+        "auto-detected."};
 
     /* Read-only mode.  Don't copy stuff to the store, don't change
        the database. */
-    bool readOnlyMode;
-
-    /* The canonical system name, as returned by config.guess. */
-    string thisSystem;
+    bool readOnlyMode = false;
 
-    /* The maximum time in seconds that a builer can go without
-       producing any output on stdout/stderr before it is killed.  0
-       means infinity. */
-    time_t maxSilentTime;
+    Setting<std::string> thisSystem{this, SYSTEM, "system",
+        "The canonical Nix system name."};
 
-    /* The maximum duration in seconds that a builder can run.  0
-       means infinity.  */
-    time_t buildTimeout;
+    Setting<time_t> maxSilentTime{this, 0, "build-max-silent-time",
+        "The maximum time in seconds that a builer can go without "
+        "producing any output on stdout/stderr before it is killed. "
+        "0 means infinity."};
 
-    /* Whether to use build hooks (for distributed builds).  Sometimes
-       users want to disable this from the command-line. */
-    bool useBuildHook;
+    Setting<time_t> buildTimeout{this, 0, "build-timeout",
+        "The maximum duration in seconds that a builder can run. "
+        "0 means infinity."};
 
-    /* Amount of reserved space for the garbage collector
-       (/nix/var/nix/db/reserved). */
-    off_t reservedSize;
+    Setting<bool> useBuildHook{this, true, "remote-builds",
+        "Whether to use build hooks (for distributed builds)."};
 
-    /* Whether SQLite should use fsync. */
-    bool fsyncMetadata;
+    Setting<off_t> reservedSize{this, 8 * 1024 * 1024, "gc-reserved-space",
+        "Amount of reserved disk space for the garbage collector."};
 
-    /* Whether SQLite should use WAL mode. */
-    bool useSQLiteWAL;
+    Setting<bool> fsyncMetadata{this, true, "fsync-metadata",
+        "Whether SQLite should use fsync()."};
 
-    /* Whether to call sync() before registering a path as valid. */
-    bool syncBeforeRegistering;
+    Setting<bool> useSQLiteWAL{this, true, "use-sqlite-wal",
+        "Whether SQLite should use WAL mode."};
 
-    /* Whether to use substitutes. */
-    bool useSubstitutes;
+    Setting<bool> syncBeforeRegistering{this, false, "sync-before-registering",
+        "Whether to call sync() before registering a path as valid."};
 
-    /* The Unix group that contains the build users. */
-    string buildUsersGroup;
+    Setting<bool> useSubstitutes{this, true, "build-use-substitutes",
+        "Whether to use substitutes."};
 
-    /* Set of ssh connection strings for the ssh substituter */
-    Strings sshSubstituterHosts;
+    Setting<std::string> buildUsersGroup{this, "", "build-users-group",
+        "The Unix group that contains the build users."};
 
-    /* Whether to use the ssh substituter at all */
-    bool useSshSubstituter;
+    Setting<bool> impersonateLinux26{this, false, "build-impersonate-linux-26",
+        "Whether to impersonate a Linux 2.6 machine on newer kernels."};
 
-    /* Whether to impersonate a Linux 2.6 machine on newer kernels. */
-    bool impersonateLinux26;
+    Setting<bool> keepLog{this, true, "build-keep-log",
+        "Whether to store build logs."};
 
-    /* Whether to store build logs. */
-    bool keepLog;
+    Setting<bool> compressLog{this, true, "build-compress-log",
+        "Whether to compress logs."};
 
-    /* Whether to compress logs. */
-    bool compressLog;
-
-    /* Maximum number of bytes a builder can write to stdout/stderr
-       before being killed (0 means no limit). */
-    unsigned long maxLogSize;
+    Setting<unsigned long> maxLogSize{this, 0, "build-max-log-size",
+        "Maximum number of bytes a builder can write to stdout/stderr "
+        "before being killed (0 means no limit)."};
 
     /* When build-repeat > 0 and verboseBuild == true, whether to
        print repeated builds (i.e. builds other than the first one) to
        stderr. Hack to prevent Hydra logs from being polluted. */
     bool printRepeatedBuilds = true;
 
-    /* How often (in seconds) to poll for locks. */
-    unsigned int pollInterval;
+    Setting<unsigned int> pollInterval{this, 5, "build-poll-interval",
+        "How often (in seconds) to poll for locks."};
 
-    /* Whether to check if new GC roots can in fact be found by the
-       garbage collector. */
-    bool checkRootReachability;
+    Setting<bool> checkRootReachability{this, false, "gc-check-reachability",
+        "Whether to check if new GC roots can in fact be found by the "
+        "garbage collector."};
 
-    /* Whether the garbage collector should keep outputs of live
-       derivations. */
-    bool gcKeepOutputs;
+    Setting<bool> gcKeepOutputs{this, false, "gc-keep-outputs",
+        "Whether the garbage collector should keep outputs of live derivations."};
 
-    /* Whether the garbage collector should keep derivers of live
-       paths. */
-    bool gcKeepDerivations;
+    Setting<bool> gcKeepDerivations{this, true, "gc-keep-derivations",
+        "Whether the garbage collector should keep derivers of live paths."};
 
-    /* Whether to automatically replace files with identical contents
-       with hard links. */
-    bool autoOptimiseStore;
+    Setting<bool> autoOptimiseStore{this, false, "auto-optimise-store",
+        "Whether to automatically replace files with identical contents with hard links."};
 
-    /* Whether to add derivations as a dependency of user environments
-       (to prevent them from being GCed). */
-    bool envKeepDerivations;
+    Setting<bool> envKeepDerivations{this, false, "env-keep-derivations",
+        "Whether to add derivations as a dependency of user environments "
+        "(to prevent them from being GCed)."};
 
     /* Whether to lock the Nix client and worker to the same CPU. */
     bool lockCPU;
 
     /* Whether to show a stack trace if Nix evaluation fails. */
-    bool showTrace;
+    bool showTrace = false;
+
+    Setting<bool> enableNativeCode{this, false, "allow-unsafe-native-code-during-evaluation",
+        "Whether builtin functions that allow executing native code should be enabled."};
+
+    Setting<SandboxMode> sandboxMode{this, smDisabled, "build-use-sandbox",
+        "Whether to enable sandboxed builds. Can be \"true\", \"false\" or \"relaxed\".",
+        {"build-use-chroot"}};
+
+    Setting<PathSet> sandboxPaths{this, {}, "build-sandbox-paths",
+        "The paths to make available inside the build sandbox.",
+        {"build-chroot-dirs"}};
+
+    Setting<PathSet> extraSandboxPaths{this, {}, "build-extra-sandbox-paths",
+        "Additional paths to make available inside the build sandbox.",
+        {"build-extra-chroot-dirs"}};
+
+    Setting<bool> restrictEval{this, false, "restrict-eval",
+        "Whether to restrict file system access to paths in $NIX_PATH, "
+        "and to disallow fetching files from the network."};
 
-    /* Whether native-code enabling primops should be enabled */
-    bool enableNativeCode;
+    Setting<size_t> buildRepeat{this, 0, "build-repeat",
+        "The number of times to repeat a build in order to verify determinism."};
 
-    /* The hook to run just before a build to set derivation-specific
-       build settings */
-    Path preBuildHook;
+#if __linux__
+    Setting<std::string> sandboxShmSize{this, "50%", "sandbox-dev-shm-size",
+        "The size of /dev/shm in the build sandbox."};
+#endif
 
-    /* Path to the netrc file used to obtain usernames/passwords for
-       downloads. */
-    Path netrcFile;
+    Setting<PathSet> allowedImpureHostPrefixes{this, {}, "allowed-impure-host-deps",
+        "Which prefixes to allow derivations to ask for access to (primarily for Darwin)."};
+
+#if __APPLE__
+    Setting<bool> darwinLogSandboxViolations{this, false, "darwin-log-sandbox-violations",
+        "Whether to log Darwin sandbox access violations to the system log."};
+#endif
+
+    Setting<bool> runDiffHook{this, false, "run-diff-hook",
+        "Whether to run the program specified by the diff-hook setting "
+        "repeated builds produce a different result. Typically used to "
+        "plug in diffoscope."};
+
+    PathSetting diffHook{this, true, "", "diff-hook",
+        "A program that prints out the differences between the two paths "
+        "specified on its command line."};
+
+    Setting<bool> enforceDeterminism{this, true, "enforce-determinism",
+        "Whether to fail if repeated builds produce different output."};
+
+    Setting<Strings> binaryCachePublicKeys{this,
+        {"cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="},
+        "binary-cache-public-keys",
+        "Trusted public keys for secure substitution."};
+
+    Setting<Strings> secretKeyFiles{this, {}, "secret-key-files",
+        "Secret keys with which to sign local builds."};
+
+    Setting<size_t> binaryCachesParallelConnections{this, 25, "binary-caches-parallel-connections",
+        "Number of parallel connections to binary caches."};
+
+    Setting<bool> enableHttp2{this, true, "enable-http2",
+        "Whether to enable HTTP/2 support."};
+
+    Setting<unsigned int> tarballTtl{this, 60 * 60, "tarball-ttl",
+        "How soon to expire files fetched by builtins.fetchTarball and builtins.fetchurl."};
+
+    Setting<std::string> signedBinaryCaches{this, "*", "signed-binary-caches",
+        "Obsolete."};
+
+    Setting<Strings> substituters{this,
+        nixStore == "/nix/store" ? Strings{"https://cache.nixos.org/"} : Strings(),
+        "substituters",
+        "The URIs of substituters (such as https://cache.nixos.org/).",
+        {"binary-caches"}};
+
+    // FIXME: provide a way to add to option values.
+    Setting<Strings> extraSubstituters{this, {}, "extra-substituters",
+        "Additional URIs of substituters.",
+        {"extra-binary-caches"}};
+
+    Setting<Strings> trustedUsers{this, {"root"}, "trusted-users",
+        "Which users or groups are trusted to ask the daemon to do unsafe things."};
+
+    /* ?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."};
+
+    Setting<bool> printMissing{this, true, "print-missing",
+        "Whether to print what paths need to be built or downloaded."};
+
+    Setting<std::string> preBuildHook{this,
+#if __APPLE__
+        nixLibexecDir + "/nix/resolve-system-dependencies",
+#else
+        "",
+#endif
+        "pre-build-hook",
+        "A program to run just before a build to set derivation-specific build settings."};
+
+    Setting<std::string> netrcFile{this, fmt("%s/%s", nixConfDir, "netrc"), "netrc-file",
+        "Path to the netrc file used to obtain usernames/passwords for downloads."};
 
     /* Path to the SSL CA file used */
     Path caFile;
 
-    /* Whether we allow import-from-derivation */
-    bool enableImportFromDerivation;
-
-private:
-    SettingsMap settings, overrides;
+    Setting<bool> enableImportFromDerivation{this, true, "allow-import-from-derivation",
+        "Whether the evaluator allows importing the result of a derivation."};
 
-    void _get(string & res, const string & name);
-    void _get(bool & res, const string & name);
-    void _get(StringSet & res, const string & name);
-    void _get(Strings & res, const string & name);
-    template<class N> void _get(N & res, const string & name);
+    struct CaseHackTag { };
+    Setting<bool, CaseHackTag> useCaseHack{this, nix::useCaseHack, "use-case-hack",
+        "Whether to enable a Darwin-specific hack for dealing with file name collisions."};
 };
 
 
diff --git a/src/libstore/legacy-ssh-store.cc b/src/libstore/legacy-ssh-store.cc
index 0e838846c794..befc560bfcec 100644
--- a/src/libstore/legacy-ssh-store.cc
+++ b/src/libstore/legacy-ssh-store.cc
@@ -12,6 +12,10 @@ static std::string uriScheme = "ssh://";
 
 struct LegacySSHStore : public Store
 {
+    const Setting<int> maxConnections{this, 1, "max-connections", "maximum number of concurrent SSH connections"};
+    const Setting<Path> sshKey{this, "", "ssh-key", "path to an SSH private key"};
+    const Setting<bool> compress{this, false, "compress", "whether to compress the connection"};
+
     struct Connection
     {
         std::unique_ptr<SSHMaster::Connection> sshConn;
@@ -29,16 +33,16 @@ struct LegacySSHStore : public Store
         : Store(params)
         , host(host)
         , connections(make_ref<Pool<Connection>>(
-            std::max(1, std::stoi(get(params, "max-connections", "1"))),
+            std::max(1, (int) maxConnections),
             [this]() { return openConnection(); },
             [](const ref<Connection> & r) { return true; }
             ))
         , master(
             host,
-            get(params, "ssh-key", ""),
+            sshKey,
             // Use SSH master only if using more than 1 connection.
             connections->capacity() > 1,
-            get(params, "compress", "") == "true")
+            compress)
     {
     }
 
diff --git a/src/libstore/local-fs-store.cc b/src/libstore/local-fs-store.cc
index 57e1b8a09fe6..bf247903c9db 100644
--- a/src/libstore/local-fs-store.cc
+++ b/src/libstore/local-fs-store.cc
@@ -9,9 +9,6 @@ namespace nix {
 
 LocalFSStore::LocalFSStore(const Params & params)
     : Store(params)
-    , rootDir(get(params, "root"))
-    , stateDir(canonPath(get(params, "state", rootDir != "" ? rootDir + "/nix/var/nix" : settings.nixStateDir)))
-    , logDir(canonPath(get(params, "log", rootDir != "" ? rootDir + "/nix/var/log/nix" : settings.nixLogDir)))
 {
 }
 
@@ -88,6 +85,8 @@ void LocalFSStore::narFromPath(const Path & path, Sink & sink)
 
 const string LocalFSStore::drvsLogDir = "drvs";
 
+
+
 std::shared_ptr<std::string> LocalFSStore::getBuildLog(const Path & path_)
 {
     auto path(path_);
@@ -110,8 +109,8 @@ std::shared_ptr<std::string> LocalFSStore::getBuildLog(const Path & path_)
 
         Path logPath =
             j == 0
-            ? (format("%1%/%2%/%3%/%4%") % logDir % drvsLogDir % string(baseName, 0, 2) % string(baseName, 2)).str()
-            : (format("%1%/%2%/%3%") % logDir % drvsLogDir % baseName).str();
+            ? fmt("%s/%s/%s/%s", logDir, drvsLogDir, string(baseName, 0, 2), string(baseName, 2))
+            : fmt("%s/%s/%s", logDir, drvsLogDir, baseName);
         Path logBz2Path = logPath + ".bz2";
 
         if (pathExists(logPath))
diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc
index 8610841d7229..5a98454ab38e 100644
--- a/src/libstore/local-store.cc
+++ b/src/libstore/local-store.cc
@@ -38,13 +38,14 @@ namespace nix {
 LocalStore::LocalStore(const Params & params)
     : Store(params)
     , LocalFSStore(params)
-    , realStoreDir(get(params, "real", rootDir != "" ? rootDir + "/nix/store" : storeDir))
+    , realStoreDir_{this, false, rootDir != "" ? rootDir + "/nix/store" : storeDir, "real",
+        "physical path to the Nix store"}
+    , realStoreDir(realStoreDir_)
     , dbDir(stateDir + "/db")
     , linksDir(realStoreDir + "/.links")
     , reservedPath(dbDir + "/reserved")
     , schemaPath(dbDir + "/schema")
     , trashDir(realStoreDir + "/trash")
-    , requireSigs(trim(settings.get("signed-binary-caches", std::string("*"))) != "") // FIXME: rename option
     , publicKeys(getDefaultPublicKeys())
 {
     auto state(_state.lock());
@@ -74,7 +75,7 @@ LocalStore::LocalStore(const Params & params)
 
         mode_t perm = 01775;
 
-        struct group * gr = getgrnam(settings.buildUsersGroup.c_str());
+        struct group * gr = getgrnam(settings.buildUsersGroup.get().c_str());
         if (!gr)
             printError(format("warning: the group ‘%1%’ specified in ‘build-users-group’ does not exist")
                 % settings.buildUsersGroup);
@@ -1332,9 +1333,9 @@ void LocalStore::signPathInfo(ValidPathInfo & info)
 {
     // FIXME: keep secret keys in memory.
 
-    auto secretKeyFiles = settings.get("secret-key-files", Strings());
+    auto secretKeyFiles = settings.secretKeyFiles;
 
-    for (auto & secretKeyFile : secretKeyFiles) {
+    for (auto & secretKeyFile : secretKeyFiles.get()) {
         SecretKey secretKey(readFile(secretKeyFile));
         info.sign(secretKey);
     }
diff --git a/src/libstore/local-store.hh b/src/libstore/local-store.hh
index 750da0c142d3..f2c40e96464b 100644
--- a/src/libstore/local-store.hh
+++ b/src/libstore/local-store.hh
@@ -67,6 +67,8 @@ private:
 
 public:
 
+    PathSetting realStoreDir_;
+
     const Path realStoreDir;
     const Path dbDir;
     const Path linksDir;
@@ -76,7 +78,9 @@ public:
 
 private:
 
-    bool requireSigs;
+    Setting<bool> requireSigs{(Store*) this,
+        settings.signedBinaryCaches != "", // FIXME
+        "require-sigs", "whether store paths should have a trusted signature on import"};
 
     PublicKeys publicKeys;
 
diff --git a/src/libstore/remote-store.cc b/src/libstore/remote-store.cc
index c9c590787450..da3c8eb8d89d 100644
--- a/src/libstore/remote-store.cc
+++ b/src/libstore/remote-store.cc
@@ -43,7 +43,7 @@ template Paths readStorePaths(Store & store, Source & from);
 RemoteStore::RemoteStore(const Params & params)
     : Store(params)
     , connections(make_ref<Pool<Connection>>(
-            std::max(1, std::stoi(get(params, "max-connections", "1"))),
+            std::max(1, (int) maxConnections),
             [this]() { return openConnectionWrapper(); },
             [](const ref<Connection> & r) { return r->to.good() && r->from.good(); }
             ))
@@ -166,9 +166,7 @@ void RemoteStore::setOptions(Connection & conn)
        << settings.useSubstitutes;
 
     if (GET_PROTOCOL_MINOR(conn.daemonVersion) >= 12) {
-        Settings::SettingsMap overrides = settings.getOverrides();
-        if (overrides["ssh-auth-sock"] == "")
-            overrides["ssh-auth-sock"] = getEnv("SSH_AUTH_SOCK");
+        StringMap overrides = settings.getOverrides();
         conn.to << overrides.size();
         for (auto & i : overrides)
             conn.to << i.first << i.second;
diff --git a/src/libstore/remote-store.hh b/src/libstore/remote-store.hh
index db8da7eaa8c5..479cf3a7909d 100644
--- a/src/libstore/remote-store.hh
+++ b/src/libstore/remote-store.hh
@@ -22,6 +22,9 @@ class RemoteStore : public virtual Store
 {
 public:
 
+    const Setting<int> maxConnections{(Store*) this, 1,
+            "max-connections", "maximum number of concurrent connections to the Nix daemon"};
+
     RemoteStore(const Params & params);
 
     /* Implementations of abstract store API methods. */
diff --git a/src/libstore/s3-binary-cache-store.cc b/src/libstore/s3-binary-cache-store.cc
index 3053f908c4e2..245455296013 100644
--- a/src/libstore/s3-binary-cache-store.cc
+++ b/src/libstore/s3-binary-cache-store.cc
@@ -125,22 +125,22 @@ S3Helper::DownloadResult S3Helper::getObject(
 
 struct S3BinaryCacheStoreImpl : public S3BinaryCacheStore
 {
+    const Setting<std::string> region{this, Aws::Region::US_EAST_1, "region", {"aws-region"}};
+    const Setting<std::string> narinfoCompression{this, "", "narinfo-compression", "compression method for .narinfo files"};
+    const Setting<std::string> lsCompression{this, "", "ls-compression", "compression method for .ls files"};
+    const Setting<std::string> logCompression{this, "", "log-compression", "compression method for log/* files"};
+
     std::string bucketName;
 
     Stats stats;
 
     S3Helper s3Helper;
 
-    std::string narinfoCompression, lsCompression, logCompression;
-
     S3BinaryCacheStoreImpl(
         const Params & params, const std::string & bucketName)
         : S3BinaryCacheStore(params)
         , bucketName(bucketName)
-        , s3Helper(get(params, "aws-region", Aws::Region::US_EAST_1))
-        , narinfoCompression(get(params, "narinfo-compression", ""))
-        , lsCompression(get(params, "ls-compression", ""))
-        , logCompression(get(params, "log-compression", ""))
+        , s3Helper(region)
     {
         diskCache = getNarInfoDiskCache();
     }
diff --git a/src/libstore/ssh-store.cc b/src/libstore/ssh-store.cc
index 2a81a8b1ebe5..bb536fadfd51 100644
--- a/src/libstore/ssh-store.cc
+++ b/src/libstore/ssh-store.cc
@@ -14,16 +14,19 @@ class SSHStore : public RemoteStore
 {
 public:
 
+    const Setting<Path> sshKey{(Store*) this, "", "ssh-key", "path to an SSH private key"};
+    const Setting<bool> compress{(Store*) this, false, "compress", "whether to compress the connection"};
+
     SSHStore(const std::string & host, const Params & params)
         : Store(params)
         , RemoteStore(params)
         , host(host)
         , master(
             host,
-            get(params, "ssh-key", ""),
+            sshKey,
             // Use SSH master only if using more than 1 connection.
             connections->capacity() > 1,
-            get(params, "compress", "") == "true")
+            compress)
     {
     }
 
diff --git a/src/libstore/store-api.cc b/src/libstore/store-api.cc
index 53c802044ea6..835bbb90e0bb 100644
--- a/src/libstore/store-api.cc
+++ b/src/libstore/store-api.cc
@@ -241,8 +241,8 @@ Path Store::computeStorePathForText(const string & name, const string & s,
 
 
 Store::Store(const Params & params)
-    : storeDir(get(params, "store", settings.nixStore))
-    , state({std::stoi(get(params, "path-info-cache-size", "65536"))})
+    : Config(params)
+    , state({(size_t) pathInfoCacheSize})
 {
 }
 
@@ -718,7 +718,10 @@ ref<Store> openStore(const std::string & uri, const Store::Params & params)
 {
     for (auto fun : *RegisterStoreImplementation::implementations) {
         auto store = fun(uri, params);
-        if (store) return ref<Store>(store);
+        if (store) {
+            store->warnUnknownSettings();
+            return ref<Store>(store);
+        }
     }
 
     throw Error(format("don't know how to open Nix store ‘%s’") % uri);
@@ -779,14 +782,10 @@ std::list<ref<Store>> getDefaultSubstituters()
         state->stores.push_back(openStore(uri));
     };
 
-    Strings defaultSubstituters;
-    if (settings.nixStore == "/nix/store")
-        defaultSubstituters.push_back("https://cache.nixos.org/");
-
-    for (auto uri : settings.get("substituters", settings.get("binary-caches", defaultSubstituters)))
+    for (auto uri : settings.substituters.get())
         addStore(uri);
 
-    for (auto uri : settings.get("extra-binary-caches", Strings()))
+    for (auto uri : settings.extraSubstituters.get())
         addStore(uri);
 
     state->done = true;
diff --git a/src/libstore/store-api.hh b/src/libstore/store-api.hh
index c0a52145af53..067309c9e956 100644
--- a/src/libstore/store-api.hh
+++ b/src/libstore/store-api.hh
@@ -6,6 +6,7 @@
 #include "lru-cache.hh"
 #include "sync.hh"
 #include "globals.hh"
+#include "config.hh"
 
 #include <atomic>
 #include <limits>
@@ -224,13 +225,17 @@ struct BuildResult
 };
 
 
-class Store : public std::enable_shared_from_this<Store>
+class Store : public std::enable_shared_from_this<Store>, public Config
 {
 public:
 
     typedef std::map<std::string, std::string> Params;
 
-    const Path storeDir;
+    const PathSetting storeDir_{this, false, settings.nixStore,
+        "store", "path to the Nix store"};
+    const Path storeDir = storeDir_;
+
+    const Setting<int> pathInfoCacheSize{this, 65536, "path-info-cache-size", "size of the in-memory store path information cache"};
 
 protected:
 
@@ -585,9 +590,19 @@ protected:
 class LocalFSStore : public virtual Store
 {
 public:
-    const Path rootDir;
-    const Path stateDir;
-    const Path logDir;
+
+    // FIXME: the (Store*) cast works around a bug in gcc that causes
+    // it to emit the call to the Option constructor. Clang works fine
+    // either way.
+    const PathSetting rootDir{(Store*) this, true, "",
+        "root", "directory prefixed to all other paths"};
+    const PathSetting stateDir{(Store*) this, false,
+        rootDir != "" ? rootDir + "/nix/var/nix" : settings.nixStateDir,
+        "state", "directory where Nix will store state"};
+    const PathSetting logDir{(Store*) this, false,
+        rootDir != "" ? rootDir + "/nix/var/log/nix" : settings.nixLogDir,
+        "log", "directory where Nix will store state"};
+
     const static string drvsLogDir;
 
     LocalFSStore(const Params & params);
diff --git a/src/libutil/config.cc b/src/libutil/config.cc
new file mode 100644
index 000000000000..e7a810cec4d2
--- /dev/null
+++ b/src/libutil/config.cc
@@ -0,0 +1,194 @@
+#include "config.hh"
+#include "args.hh"
+
+namespace nix {
+
+void Config::set(const std::string & name, const std::string & value)
+{
+    auto i = _settings.find(name);
+    if (i == _settings.end())
+        throw UsageError("unknown setting '%s'", name);
+    i->second.setting->set(value);
+}
+
+void Config::addSetting(AbstractSetting * setting)
+{
+    _settings.emplace(setting->name, Config::SettingData{false, setting});
+    for (auto & alias : setting->aliases)
+        _settings.emplace(alias, Config::SettingData{true, setting});
+
+    bool set = false;
+
+    auto i = initials.find(setting->name);
+    if (i != initials.end()) {
+        setting->set(i->second);
+        initials.erase(i);
+        set = true;
+    }
+
+    for (auto & alias : setting->aliases) {
+        auto i = initials.find(alias);
+        if (i != initials.end()) {
+            if (set)
+                warn("setting '%s' is set, but it's an alias of '%s' which is also set",
+                    alias, setting->name);
+            else {
+                setting->set(i->second);
+                initials.erase(i);
+                set = true;
+            }
+        }
+    }
+}
+
+void Config::warnUnknownSettings()
+{
+    for (auto & i : initials)
+        warn("unknown setting '%s'", i.first);
+}
+
+StringMap Config::getSettings()
+{
+    StringMap res;
+    for (auto & opt : _settings)
+        if (!opt.second.isAlias)
+            res.emplace(opt.first, opt.second.setting->to_string());
+    return res;
+}
+
+void Config::applyConfigFile(const Path & path, bool fatal)
+{
+    try {
+        string contents = readFile(path);
+
+        unsigned int pos = 0;
+
+        while (pos < contents.size()) {
+            string line;
+            while (pos < contents.size() && contents[pos] != '\n')
+                line += contents[pos++];
+            pos++;
+
+            string::size_type hash = line.find('#');
+            if (hash != string::npos)
+                line = string(line, 0, hash);
+
+            vector<string> tokens = tokenizeString<vector<string> >(line);
+            if (tokens.empty()) continue;
+
+            if (tokens.size() < 2 || tokens[1] != "=")
+                throw UsageError("illegal configuration line ‘%1%’ in ‘%2%’", line, path);
+
+            string name = tokens[0];
+
+            vector<string>::iterator i = tokens.begin();
+            advance(i, 2);
+
+            try {
+                set(name, concatStringsSep(" ", Strings(i, tokens.end()))); // FIXME: slow
+            } catch (UsageError & e) {
+                if (fatal) throw;
+                warn("in configuration file '%s': %s", path, e.what());
+            }
+        };
+    } catch (SysError &) { }
+}
+
+AbstractSetting::AbstractSetting(
+    const std::string & name,
+    const std::string & description,
+    const std::set<std::string> & aliases)
+    : name(name), description(description), aliases(aliases)
+{
+}
+
+template<> void Setting<std::string>::set(const std::string & str)
+{
+    value = str;
+}
+
+template<> std::string Setting<std::string>::to_string()
+{
+    return value;
+}
+
+template<typename T, typename Tag>
+void Setting<T, Tag>::set(const std::string & str)
+{
+    static_assert(std::is_integral<T>::value, "Integer required.");
+    if (!string2Int(str, value))
+        throw UsageError("setting '%s' has invalid value '%s'", name, str);
+}
+
+template<typename T, typename Tag>
+std::string Setting<T, Tag>::to_string()
+{
+    static_assert(std::is_integral<T>::value, "Integer required.");
+    return std::to_string(value);
+}
+
+bool AbstractSetting::parseBool(const std::string & str)
+{
+    if (str == "true" || str == "yes" || str == "1")
+        return true;
+    else if (str == "false" || str == "no" || str == "0")
+        return false;
+    else
+        throw UsageError("Boolean setting '%s' has invalid value '%s'", name, str);
+}
+
+template<> void Setting<bool>::set(const std::string & str)
+{
+    value = parseBool(str);
+}
+
+std::string AbstractSetting::printBool(bool b)
+{
+    return b ? "true" : "false";
+}
+
+
+template<> std::string Setting<bool>::to_string()
+{
+    return printBool(value);
+}
+
+template<> void Setting<Strings>::set(const std::string & str)
+{
+    value = tokenizeString<Strings>(str);
+}
+
+template<> std::string Setting<Strings>::to_string()
+{
+    return concatStringsSep(" ", value);
+}
+
+template<> void Setting<StringSet>::set(const std::string & str)
+{
+    value = tokenizeString<StringSet>(str);
+}
+
+template<> std::string Setting<StringSet>::to_string()
+{
+    return concatStringsSep(" ", value);
+}
+
+template class Setting<int>;
+template class Setting<unsigned int>;
+template class Setting<long>;
+template class Setting<unsigned long>;
+template class Setting<long long>;
+template class Setting<unsigned long long>;
+
+void PathSetting::set(const std::string & str)
+{
+    if (str == "") {
+        if (allowEmpty)
+            value = "";
+        else
+            throw UsageError("setting '%s' cannot be empty", name);
+    } else
+        value = canonPath(str);
+}
+
+}
diff --git a/src/libutil/config.hh b/src/libutil/config.hh
new file mode 100644
index 000000000000..6c8612f675c7
--- /dev/null
+++ b/src/libutil/config.hh
@@ -0,0 +1,163 @@
+#include <map>
+#include <set>
+
+#include "types.hh"
+
+#pragma once
+
+namespace nix {
+
+class Args;
+class AbstractSetting;
+
+/* A class to simplify providing configuration settings. The typical
+   use is to inherit Config and add Setting<T> members:
+
+   class MyClass : private Config
+   {
+     Setting<int> foo{this, 123, "foo", "the number of foos to use"};
+     Setting<std::string> bar{this, "blabla", "bar", "the name of the bar"};
+
+     MyClass() : Config(readConfigFile("/etc/my-app.conf"))
+     {
+       std::cout << foo << "\n"; // will print 123 unless overriden
+     }
+   };
+*/
+
+class Config
+{
+    friend class AbstractSetting;
+
+    struct SettingData
+    {
+        bool isAlias = false;
+        AbstractSetting * setting;
+    };
+
+    std::map<std::string, SettingData> _settings;
+
+    StringMap initials;
+
+public:
+
+    Config(const StringMap & initials)
+        : initials(initials)
+    { }
+
+    void set(const std::string & name, const std::string & value);
+
+    void addSetting(AbstractSetting * setting);
+
+    void warnUnknownSettings();
+
+    StringMap getSettings();
+
+    void applyConfigFile(const Path & path, bool fatal = false);
+};
+
+class AbstractSetting
+{
+    friend class Config;
+
+public:
+
+    const std::string name;
+    const std::string description;
+    const std::set<std::string> aliases;
+
+    int created = 123;
+
+protected:
+
+    AbstractSetting(
+        const std::string & name,
+        const std::string & description,
+        const std::set<std::string> & aliases);
+
+    virtual ~AbstractSetting()
+    {
+        // Check against a gcc miscompilation causing our constructor
+        // not to run.
+        assert(created == 123);
+    }
+
+    virtual void set(const std::string & value) = 0;
+
+    virtual std::string to_string() = 0;
+
+    bool parseBool(const std::string & str);
+    std::string printBool(bool b);
+};
+
+struct DefaultSettingTag { };
+
+/* A setting of type T. */
+template<typename T, typename Tag = DefaultSettingTag>
+class Setting : public AbstractSetting
+{
+protected:
+
+    T value;
+
+public:
+
+    Setting(Config * options,
+        const T & def,
+        const std::string & name,
+        const std::string & description,
+        const std::set<std::string> & aliases = {})
+        : AbstractSetting(name, description, aliases)
+        , value(def)
+    {
+        options->addSetting(this);
+    }
+
+    operator const T &() const { return value; }
+    operator T &() { return value; }
+    const T & get() const { return value; }
+    bool operator ==(const T & v2) const { return value == v2; }
+    bool operator !=(const T & v2) const { return value != v2; }
+    void operator =(const T & v) { value = v; }
+
+    void set(const std::string & str) override;
+
+    std::string to_string() override;
+};
+
+template<typename T>
+std::ostream & operator <<(std::ostream & str, const Setting<T> & opt)
+{
+    str << (const T &) opt;
+    return str;
+}
+
+template<typename T>
+bool operator ==(const T & v1, const Setting<T> & v2) { return v1 == (const T &) v2; }
+
+/* A special setting for Paths. These are automatically canonicalised
+   (e.g. "/foo//bar/" becomes "/foo/bar"). */
+class PathSetting : public Setting<Path>
+{
+    bool allowEmpty;
+
+public:
+
+    PathSetting(Config * options,
+        bool allowEmpty,
+        const Path & def,
+        const std::string & name,
+        const std::string & description,
+        const std::set<std::string> & aliases = {})
+        : Setting<Path>(options, def, name, description, aliases)
+        , allowEmpty(allowEmpty)
+    {
+        set(value);
+    }
+
+    void set(const std::string & str) override;
+
+    Path operator +(const char * p) const { return value + p; }
+};
+
+}
diff --git a/src/libutil/logging.cc b/src/libutil/logging.cc
index d9e8d22d7685..afcc2ec58543 100644
--- a/src/libutil/logging.cc
+++ b/src/libutil/logging.cc
@@ -3,7 +3,12 @@
 
 namespace nix {
 
-Logger * logger = 0;
+Logger * logger = makeDefaultLogger();
+
+void Logger::warn(const std::string & msg)
+{
+    log(lvlInfo, ANSI_RED "warning:" ANSI_NORMAL " " + msg);
+}
 
 class SimpleLogger : public Logger
 {
@@ -52,7 +57,7 @@ Verbosity verbosity = lvlInfo;
 void warnOnce(bool & haveWarned, const FormatOrString & fs)
 {
     if (!haveWarned) {
-        printError(format("warning: %1%") % fs.s);
+        warn(fs.s);
         haveWarned = true;
     }
 }
diff --git a/src/libutil/logging.hh b/src/libutil/logging.hh
index 3f83664794f7..81aebccdca45 100644
--- a/src/libutil/logging.hh
+++ b/src/libutil/logging.hh
@@ -30,6 +30,8 @@ public:
         log(lvlInfo, fs);
     }
 
+    virtual void warn(const std::string & msg);
+
     virtual void setExpected(const std::string & label, uint64_t value = 1) { }
     virtual void setProgress(const std::string & label, uint64_t value = 1) { }
     virtual void incExpected(const std::string & label, uint64_t value = 1) { }
@@ -82,6 +84,14 @@ extern Verbosity verbosity; /* suppress msgs > this */
 #define debug(args...) printMsg(lvlDebug, args)
 #define vomit(args...) printMsg(lvlVomit, args)
 
+template<typename... Args>
+inline void warn(const std::string & fs, Args... args)
+{
+    boost::format f(fs);
+    formatHelper(f, args...);
+    logger->warn(f.str());
+}
+
 void warnOnce(bool & haveWarned, const FormatOrString & fs);
 
 void writeToStderr(const string & s);
diff --git a/src/libutil/types.hh b/src/libutil/types.hh
index 97d79af9b5d6..1429c238513b 100644
--- a/src/libutil/types.hh
+++ b/src/libutil/types.hh
@@ -7,6 +7,7 @@
 #include <list>
 #include <set>
 #include <memory>
+#include <map>
 
 #include <boost/format.hpp>
 
@@ -141,6 +142,7 @@ private:
 
 typedef list<string> Strings;
 typedef set<string> StringSet;
+typedef std::map<std::string, std::string> StringMap;
 
 
 /* Paths are just strings. */
diff --git a/src/libutil/util.cc b/src/libutil/util.cc
index a640a64c724e..0bd51afd1a9f 100644
--- a/src/libutil/util.cc
+++ b/src/libutil/util.cc
@@ -96,6 +96,8 @@ Path absPath(Path path, Path dir)
 
 Path canonPath(const Path & path, bool resolveSymlinks)
 {
+    assert(path != "");
+
     string s;
 
     if (path[0] != '/')
diff --git a/src/nix-daemon/nix-daemon.cc b/src/nix-daemon/nix-daemon.cc
index 8786e2561b9c..1389353bb5d8 100644
--- a/src/nix-daemon/nix-daemon.cc
+++ b/src/nix-daemon/nix-daemon.cc
@@ -436,30 +436,31 @@ static void performOp(ref<LocalStore> store, bool trusted, unsigned int clientVe
     }
 
     case wopSetOptions: {
-        from >> settings.keepFailed;
-        from >> settings.keepGoing;
-        settings.set("build-fallback", readInt(from) ? "true" : "false");
+        settings.keepFailed = readInt(from);
+        settings.keepGoing = readInt(from);
+        settings.tryFallback = readInt(from);
         verbosity = (Verbosity) readInt(from);
-        settings.set("build-max-jobs", std::to_string(readInt(from)));
-        settings.set("build-max-silent-time", std::to_string(readInt(from)));
+        settings.maxBuildJobs = readInt(from);
+        settings.maxSilentTime = readInt(from);
         settings.useBuildHook = readInt(from) != 0;
         settings.verboseBuild = lvlError == (Verbosity) readInt(from);
         readInt(from); // obsolete logType
         readInt(from); // obsolete printBuildTrace
-        settings.set("build-cores", std::to_string(readInt(from)));
-        settings.set("build-use-substitutes", readInt(from) ? "true" : "false");
+        settings.buildCores = readInt(from);
+        settings.useSubstitutes  = readInt(from);
         if (GET_PROTOCOL_MINOR(clientVersion) >= 12) {
             unsigned int n = readInt(from);
             for (unsigned int i = 0; i < n; i++) {
                 string name = readString(from);
                 string value = readString(from);
-                if (name == "build-timeout" || name == "use-ssh-substituter")
-                    settings.set(name, value);
-                else
-                    settings.set(trusted ? name : "untrusted-" + name, value);
+                try {
+                    if (trusted || name == "build-timeout")
+                        settings.set(name, value);
+                } catch (UsageError & e) {
+                    warn(e.what());
+                }
             }
         }
-        settings.update();
         startWork();
         stopWork();
         break;
@@ -878,8 +879,8 @@ static void daemonLoop(char * * argv)
             struct group * gr = peer.gidKnown ? getgrgid(peer.gid) : 0;
             string group = gr ? gr->gr_name : std::to_string(peer.gid);
 
-            Strings trustedUsers = settings.get("trusted-users", Strings({"root"}));
-            Strings allowedUsers = settings.get("allowed-users", Strings({"*"}));
+            Strings trustedUsers = settings.trustedUsers;
+            Strings allowedUsers = settings.allowedUsers;
 
             if (matchUser(user, group, trustedUsers))
                 trusted = true;
diff --git a/src/nix-store/nix-store.cc b/src/nix-store/nix-store.cc
index 3dc167191c83..9131b74dfb41 100644
--- a/src/nix-store/nix-store.cc
+++ b/src/nix-store/nix-store.cc
@@ -145,7 +145,7 @@ static void opRealise(Strings opFlags, Strings opArgs)
         unknown = PathSet();
     }
 
-    if (settings.get("print-missing", true))
+    if (settings.printMissing)
         printMissing(ref<Store>(store), willBuild, willSubstitute, unknown, downloadSize, narSize);
 
     if (dryRun) return;
@@ -795,11 +795,11 @@ static void opServe(Strings opFlags, Strings opArgs)
         settings.maxSilentTime = readInt(in);
         settings.buildTimeout = readInt(in);
         if (GET_PROTOCOL_MINOR(clientVersion) >= 2)
-            in >> settings.maxLogSize;
+            settings.maxLogSize = readNum<unsigned long>(in);
         if (GET_PROTOCOL_MINOR(clientVersion) >= 3) {
-            settings.set("build-repeat", std::to_string(readInt(in)));
-            settings.set("enforce-determinism", readInt(in) != 0 ? "true" : "false");
-            settings.set("run-diff-hook", "true");
+            settings.buildRepeat = readInt(in);
+            settings.enforceDeterminism = readInt(in);
+            settings.runDiffHook = readInt(in);
         }
         settings.printRepeatedBuilds = false;
     };
diff --git a/src/nix/show-config.cc b/src/nix/show-config.cc
new file mode 100644
index 000000000000..ba39e2bb29b3
--- /dev/null
+++ b/src/nix/show-config.cc
@@ -0,0 +1,43 @@
+#include "command.hh"
+#include "common-args.hh"
+#include "installables.hh"
+#include "shared.hh"
+#include "store-api.hh"
+#include "json.hh"
+
+using namespace nix;
+
+struct CmdShowConfig : Command
+{
+    bool json = false;
+
+    CmdShowConfig()
+    {
+        mkFlag(0, "json", "produce JSON output", &json);
+    }
+
+    std::string name() override
+    {
+        return "show-config";
+    }
+
+    std::string description() override
+    {
+        return "show the Nix configuration";
+    }
+
+    void run() override
+    {
+        if (json) {
+            // FIXME: use appropriate JSON types (bool, ints, etc).
+            JSONObject jsonObj(std::cout, true);
+            for (auto & s : settings.getSettings())
+                jsonObj.attr(s.first, s.second);
+        } else {
+            for (auto & s : settings.getSettings())
+                std::cout << s.first + " = " + s.second + "\n";
+        }
+    }
+};
+
+static RegisterCommand r1(make_ref<CmdShowConfig>());
diff --git a/tests/shell.shebang.sh b/tests/shell.shebang.sh
index 3dadd591572d..c9a83aaf83dc 100755
--- a/tests/shell.shebang.sh
+++ b/tests/shell.shebang.sh
@@ -1,4 +1,4 @@
 #! @ENV_PROG@ nix-shell
-#! nix-shell -I nixpkgs=shell.nix --option use-binary-caches false
+#! nix-shell -I nixpkgs=shell.nix --option build-use-substitutes false
 #! nix-shell --pure -i bash -p foo bar
 echo "$(foo) $(bar) $@"
diff --git a/tests/timeout.nix b/tests/timeout.nix
index 540fba934ff6..e18d717eff1f 100644
--- a/tests/timeout.nix
+++ b/tests/timeout.nix
@@ -5,6 +5,7 @@ with import ./config.nix;
   infiniteLoop = mkDerivation {
     name = "timeout";
     buildCommand = ''
+      touch $out
       echo "‘timeout’ builder entering an infinite loop"
       while true ; do echo -n .; done
     '';
@@ -13,6 +14,7 @@ with import ./config.nix;
   silent = mkDerivation {
     name = "silent";
     buildCommand = ''
+      touch $out
       sleep 60
     '';
   };
@@ -20,6 +22,7 @@ with import ./config.nix;
   closeLog = mkDerivation {
     name = "silent";
     buildCommand = ''
+      touch $out
       exec > /dev/null 2>&1
       sleep 1000000000
     '';
diff --git a/tests/timeout.sh b/tests/timeout.sh
index ce1ae7d674a1..77b227e89ba5 100644
--- a/tests/timeout.sh
+++ b/tests/timeout.sh
@@ -29,3 +29,8 @@ if nix-build timeout.nix -A closeLog; then
     echo "build should have failed"
     exit 1
 fi
+
+if nix build -f timeout.nix silent --option build-max-silent-time 2; then
+    echo "build should have failed"
+    exit 1
+fi