about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--Makefile.config.in1
-rw-r--r--configure.ac1
-rw-r--r--doc/manual/command-ref/conf-file.xml72
-rw-r--r--doc/manual/command-ref/nix-env.xml2
-rw-r--r--doc/manual/introduction/quick-start.xml2
-rw-r--r--mk/lib.mk4
-rw-r--r--src/libmain/shared.cc2
-rw-r--r--src/libstore/gc.cc11
-rw-r--r--src/libstore/globals.cc39
-rw-r--r--src/libstore/globals.hh52
-rw-r--r--src/libstore/local.mk3
-rw-r--r--src/libstore/remote-store.cc2
-rw-r--r--src/libutil/config.cc100
-rw-r--r--src/libutil/config.hh64
-rw-r--r--src/libutil/json.cc43
-rw-r--r--src/libutil/json.hh11
-rw-r--r--src/libutil/util.cc19
-rw-r--r--src/libutil/util.hh7
-rw-r--r--src/nix-daemon/nix-daemon.cc50
-rw-r--r--src/nix/show-config.cc3
20 files changed, 312 insertions, 176 deletions
diff --git a/Makefile.config.in b/Makefile.config.in
index 53dca1fcf10a..6948dad5a60b 100644
--- a/Makefile.config.in
+++ b/Makefile.config.in
@@ -15,6 +15,7 @@ SQLITE3_LIBS = @SQLITE3_LIBS@
 bash = @bash@
 bindir = @bindir@
 bro = @bro@
+lsof = @lsof@
 datadir = @datadir@
 datarootdir = @datarootdir@
 docdir = @docdir@
diff --git a/configure.ac b/configure.ac
index 3e6a894e3b10..c7026cf954dd 100644
--- a/configure.ac
+++ b/configure.ac
@@ -128,6 +128,7 @@ NEED_PROG(xz, xz)
 AC_PATH_PROG(dot, dot)
 AC_PATH_PROG(pv, pv, pv)
 AC_PATH_PROG(bro, bro, bro)
+AC_PATH_PROG(lsof, lsof, lsof)
 
 
 NEED_PROG(cat, cat)
diff --git a/doc/manual/command-ref/conf-file.xml b/doc/manual/command-ref/conf-file.xml
index 3de9647aa4eb..616983bc7f0e 100644
--- a/doc/manual/command-ref/conf-file.xml
+++ b/doc/manual/command-ref/conf-file.xml
@@ -17,13 +17,32 @@
 
 <refsection><title>Description</title>
 
-<para>A number of persistent settings of Nix are stored in the file
-<filename><replaceable>sysconfdir</replaceable>/nix/nix.conf</filename> or
-<filename>$NIX_CONF_DIR/nix.conf</filename> if <envar>NIX_CONF_DIR</envar> is set.
-This file is a list of <literal><replaceable>name</replaceable> =
+<para>Nix reads settings from two configuration files:</para>
+
+<itemizedlist>
+
+  <listitem>
+    <para>The system-wide configuration file
+    <filename><replaceable>sysconfdir</replaceable>/nix/nix.conf</filename>
+    (i.e. <filename>/etc/nix/nix.conf</filename> on most systems), or
+    <filename>$NIX_CONF_DIR/nix.conf</filename> if
+    <envar>NIX_CONF_DIR</envar> is set.</para>
+  </listitem>
+
+  <listitem>
+    <para>The user configuration file
+    <filename>$XDG_CONFIG_HOME/nix/nix.conf</filename>, or
+    <filename>~/.config/nix/nix.conf</filename> if
+    <envar>XDG_CONFIG_HOME</envar> is not set.</para>
+  </listitem>
+
+</itemizedlist>
+
+<para>The configuration files consist of
+<literal><replaceable>name</replaceable> =
 <replaceable>value</replaceable></literal> pairs, one per line.
-Comments start with a <literal>#</literal> character.  Here is an example
-configuration file:</para>
+Comments start with a <literal>#</literal> character.  Here is an
+example configuration file:</para>
 
 <programlisting>
 gc-keep-outputs = true       # Nice for developers
@@ -31,8 +50,9 @@ gc-keep-derivations = true   # Idem
 env-keep-derivations = false
 </programlisting>
 
-<para>You can override settings using the <option>--option</option>
-flag, e.g. <literal>--option gc-keep-outputs false</literal>.</para>
+<para>You can override settings on the command line using the
+<option>--option</option> flag, e.g. <literal>--option gc-keep-outputs
+false</literal>.</para>
 
 <para>The following settings are currently available:
 
@@ -334,15 +354,16 @@ flag, e.g. <literal>--option gc-keep-outputs false</literal>.</para>
   </varlistentry>
 
 
-  <varlistentry><term><literal>binary-caches</literal></term>
+  <varlistentry><term><literal>substituters</literal></term>
 
-    <listitem><para>A list of URLs of binary caches, separated by
+    <listitem><para>A list of URLs of substituters, separated by
     whitespace.  The default is
     <literal>https://cache.nixos.org</literal>.</para></listitem>
 
   </varlistentry>
 
 
+  <!--
   <varlistentry><term><literal>binary-caches-files</literal></term>
 
     <listitem><para>A list of names of files that will be read to
@@ -355,28 +376,28 @@ flag, e.g. <literal>--option gc-keep-outputs false</literal>.</para>
     option to read files created by untrusted users!</para></listitem>
 
   </varlistentry>
+  -->
 
 
-  <varlistentry><term><literal>trusted-binary-caches</literal></term>
+  <varlistentry><term><literal>trusted-substituters</literal></term>
 
-    <listitem><para>A list of URLs of binary caches, separated by
+    <listitem><para>A list of URLs of substituters, separated by
     whitespace.  These are not used by default, but can be enabled by
     users of the Nix daemon by specifying <literal>--option
-    binary-caches <replaceable>urls</replaceable></literal> on the
+    substituters <replaceable>urls</replaceable></literal> on the
     command line.  Unprivileged users are only allowed to pass a
-    subset of the URLs listed in <literal>binary-caches</literal> and
-    <literal>trusted-binary-caches</literal>.</para></listitem>
+    subset of the URLs listed in <literal>substituters</literal> and
+    <literal>trusted-substituters</literal>.</para></listitem>
 
   </varlistentry>
 
 
-  <varlistentry><term><literal>extra-binary-caches</literal></term>
+  <varlistentry><term><literal>extra-substituters</literal></term>
 
     <listitem><para>Additional binary caches appended to those
-    specified in <option>binary-caches</option> and
-    <option>binary-caches-files</option>.  When used by unprivileged
-    users, untrusted binary caches (i.e. those not listed in
-    <option>trusted-binary-caches</option>) are silently
+    specified in <option>substituters</option>.  When used by
+    unprivileged users, untrusted substituters (i.e. those not listed
+    in <option>trusted-substituters</option>) are silently
     ignored.</para></listitem>
 
   </varlistentry>
@@ -403,7 +424,7 @@ flag, e.g. <literal>--option gc-keep-outputs false</literal>.</para>
   </varlistentry>
 
 
-  <varlistentry><term><literal>binary-caches-parallel-connections</literal></term>
+  <varlistentry><term><literal>http-connections</literal></term>
 
     <listitem><para>The maximum number of parallel TCP connections
     used to fetch files from binary caches and by other downloads. It
@@ -412,15 +433,6 @@ flag, e.g. <literal>--option gc-keep-outputs false</literal>.</para>
   </varlistentry>
 
 
-  <varlistentry><term><literal>verify-https-binary-caches</literal></term>
-
-    <listitem><para>Whether HTTPS binary caches are required to have a
-    certificate that can be verified. Defaults to
-    <literal>true</literal>.</para></listitem>
-
-  </varlistentry>
-
-
   <varlistentry><term><literal>netrc-file</literal></term>
 
     <listitem><para>If set to an absolute path to a <filename>netrc</filename>
diff --git a/doc/manual/command-ref/nix-env.xml b/doc/manual/command-ref/nix-env.xml
index 85f10e0760bc..8462cf8a0270 100644
--- a/doc/manual/command-ref/nix-env.xml
+++ b/doc/manual/command-ref/nix-env.xml
@@ -1136,7 +1136,7 @@ user environment elements, etc. -->
 
     <listitem><para>Print all of the meta-attributes of the
     derivation.  This option is only available with
-    <option>--xml</option>.</para></listitem>
+    <option>--xml</option> or <option>--json</option>.</para></listitem>
 
   </varlistentry>
 
diff --git a/doc/manual/introduction/quick-start.xml b/doc/manual/introduction/quick-start.xml
index 5ae9f6ad543b..aa239b7538b4 100644
--- a/doc/manual/introduction/quick-start.xml
+++ b/doc/manual/introduction/quick-start.xml
@@ -40,7 +40,7 @@ libxslt-1.1.28
 <step><para>Install some packages from the channel:
 
 <screen>
-$ nix-env -i hello <replaceable>...</replaceable> </screen>
+$ nix-env -i hello</screen>
 
 This should download pre-built packages; it should not build them
 locally (if it does, something went wrong).</para></step>
diff --git a/mk/lib.mk b/mk/lib.mk
index bb82801d3b4e..1da51d879734 100644
--- a/mk/lib.mk
+++ b/mk/lib.mk
@@ -53,8 +53,8 @@ BUILD_SHARED_LIBS ?= 1
 
 ifeq ($(BUILD_SHARED_LIBS), 1)
   ifeq (CYGWIN,$(findstring CYGWIN,$(OS)))
-    GLOBAL_CFLAGS += -U__STRICT_ANSI__
-    GLOBAL_CXXFLAGS += -U__STRICT_ANSI__
+    GLOBAL_CFLAGS += -U__STRICT_ANSI__ -D_GNU_SOURCE
+    GLOBAL_CXXFLAGS += -U__STRICT_ANSI__ -D_GNU_SOURCE
   else
     GLOBAL_CFLAGS += -fPIC
     GLOBAL_CXXFLAGS += -fPIC
diff --git a/src/libmain/shared.cc b/src/libmain/shared.cc
index 4747b9bf9b4c..d6c1c0c9cb49 100644
--- a/src/libmain/shared.cc
+++ b/src/libmain/shared.cc
@@ -259,7 +259,7 @@ int handleExceptions(const string & programName, std::function<void()> fun)
                condition is discharged before we reach printMsg()
                below, since otherwise it will throw an (uncaught)
                exception. */
-            interruptThrown = true;
+            setInterruptThrown();
             throw;
         }
     } catch (Exit & e) {
diff --git a/src/libstore/gc.cc b/src/libstore/gc.cc
index 0b03d61a789a..b6d462d2bffb 100644
--- a/src/libstore/gc.cc
+++ b/src/libstore/gc.cc
@@ -426,22 +426,27 @@ void LocalStore::findRuntimeRoots(PathSet & roots)
             throw SysError("iterating /proc");
     }
 
+#if !defined(__linux__)
     try {
-        auto lsofRegex = std::regex(R"(^n(/.*)$)");
+        printError("RUN LSOF %s", LSOF);
+        std::regex lsofRegex(R"(^n(/.*)$)");
         auto lsofLines =
-            tokenizeString<std::vector<string>>(runProgram("lsof", true, { "-n", "-w", "-F", "n" }), "\n");
+            tokenizeString<std::vector<string>>(runProgram(LSOF, true, { "-n", "-w", "-F", "n" }), "\n");
         for (const auto & line : lsofLines) {
-            auto match = std::smatch{};
+            std::smatch match;
             if (std::regex_match(line, match, lsofRegex))
                 paths.emplace(match[1]);
         }
     } catch (ExecError & e) {
         /* lsof not installed, lsof failed */
     }
+#endif
 
+#if defined(__linux__)
     readFileRoots("/proc/sys/kernel/modprobe", paths);
     readFileRoots("/proc/sys/kernel/fbsplash", paths);
     readFileRoots("/proc/sys/kernel/poweroff_cmd", paths);
+#endif
 
     for (auto & i : paths)
         if (isInStore(i)) {
diff --git a/src/libstore/globals.cc b/src/libstore/globals.cc
index bb61daa51642..953bf6aaaa0a 100644
--- a/src/libstore/globals.cc
+++ b/src/libstore/globals.cc
@@ -53,19 +53,19 @@ Settings::Settings()
 void Settings::loadConfFile()
 {
     applyConfigFile(nixConfDir + "/nix.conf");
+
+    /* We only want to send overrides to the daemon, i.e. stuff from
+       ~/.nix/nix.conf or the command line. */
+    resetOverriden();
+
+    applyConfigFile(getConfigDir() + "/nix/nix.conf");
 }
 
 void Settings::set(const string & name, const string & value)
 {
-    overrides[name] = value;
     Config::set(name, value);
 }
 
-StringMap Settings::getOverrides()
-{
-    return overrides;
-}
-
 unsigned int Settings::getDefaultCores()
 {
     return std::max(1U, std::thread::hardware_concurrency());
@@ -73,7 +73,7 @@ unsigned int Settings::getDefaultCores()
 
 const string nixVersion = PACKAGE_VERSION;
 
-template<> void Setting<SandboxMode>::set(const std::string & str)
+template<> void BaseSetting<SandboxMode>::set(const std::string & str)
 {
     if (str == "true") value = smEnabled;
     else if (str == "relaxed") value = smRelaxed;
@@ -81,7 +81,7 @@ template<> void Setting<SandboxMode>::set(const std::string & str)
     else throw UsageError("option '%s' has invalid value '%s'", name, str);
 }
 
-template<> std::string Setting<SandboxMode>::to_string()
+template<> std::string BaseSetting<SandboxMode>::to_string()
 {
     if (value == smEnabled) return "true";
     else if (value == smRelaxed) return "relaxed";
@@ -89,27 +89,16 @@ template<> std::string Setting<SandboxMode>::to_string()
     else abort();
 }
 
-template<> void Setting<unsigned int, Settings::MaxBuildJobsTag>::set(const std::string & str)
+template<> void BaseSetting<SandboxMode>::toJSON(JSONPlaceholder & out)
 {
-    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);
+    AbstractSetting::toJSON(out);
 }
 
-template<> std::string Setting<unsigned int, Settings::MaxBuildJobsTag>::to_string()
+void MaxBuildJobsSetting::set(const std::string & str)
 {
-    return std::to_string(value);
-}
-
-template<> void Setting<bool, Settings::CaseHackTag>::set(const std::string & str)
-{
-    value = parseBool(str);
-    nix::useCaseHack = true;
-}
-
-template<> std::string Setting<bool, Settings::CaseHackTag>::to_string()
-{
-    return printBool(value);
+    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);
 }
 
 }
diff --git a/src/libstore/globals.hh b/src/libstore/globals.hh
index 72863920de99..b4f44de2e65d 100644
--- a/src/libstore/globals.hh
+++ b/src/libstore/globals.hh
@@ -13,9 +13,40 @@ typedef enum { smEnabled, smRelaxed, smDisabled } SandboxMode;
 
 extern bool useCaseHack; // FIXME
 
-class Settings : public Config {
+struct CaseHackSetting : public BaseSetting<bool>
+{
+    CaseHackSetting(Config * options,
+        const std::string & name,
+        const std::string & description,
+        const std::set<std::string> & aliases = {})
+        : BaseSetting<bool>(useCaseHack, name, description, aliases)
+    {
+        options->addSetting(this);
+    }
+
+    void set(const std::string & str) override
+    {
+        BaseSetting<bool>::set(str);
+        nix::useCaseHack = true;
+    }
+};
 
-    StringMap overrides;
+struct MaxBuildJobsSetting : public BaseSetting<unsigned int>
+{
+    MaxBuildJobsSetting(Config * options,
+        unsigned int def,
+        const std::string & name,
+        const std::string & description,
+        const std::set<std::string> & aliases = {})
+        : BaseSetting<unsigned int>(def, name, description, aliases)
+    {
+        options->addSetting(this);
+    }
+
+    void set(const std::string & str) override;
+};
+
+class Settings : public Config {
 
     unsigned int getDefaultCores();
 
@@ -27,8 +58,6 @@ public:
 
     void set(const string & name, const string & value);
 
-    StringMap getOverrides();
-
     Path nixPrefix;
 
     /* The directory where we store sources and derived files. */
@@ -70,8 +99,7 @@ public:
        the log to show if a build fails. */
     size_t logLines = 10;
 
-    struct MaxBuildJobsTag { };
-    Setting<unsigned int, MaxBuildJobsTag> maxBuildJobs{this, 1, "build-max-jobs",
+    MaxBuildJobsSetting maxBuildJobs{this, 1, "build-max-jobs",
         "Maximum number of parallel build jobs. \"auto\" means use number of cores."};
 
     Setting<unsigned int> buildCores{this, getDefaultCores(), "build-cores",
@@ -216,8 +244,9 @@ public:
     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<size_t> binaryCachesParallelConnections{this, 25, "http-connections",
+        "Number of parallel HTTP connections.",
+        {"binary-caches-parallel-connections"}};
 
     Setting<bool> enableHttp2{this, true, "enable-http2",
         "Whether to enable HTTP/2 support."};
@@ -239,6 +268,10 @@ public:
         "Additional URIs of substituters.",
         {"extra-binary-caches"}};
 
+    Setting<StringSet> trustedSubstituters{this, {}, "trusted-substituters",
+        "Disabled substituters that may be enabled via the substituters option by untrusted users.",
+        {"trusted-binary-caches"}};
+
     Setting<Strings> trustedUsers{this, {"root"}, "trusted-users",
         "Which users or groups are trusted to ask the daemon to do unsafe things."};
 
@@ -267,8 +300,7 @@ public:
     Setting<bool> enableImportFromDerivation{this, true, "allow-import-from-derivation",
         "Whether the evaluator allows importing the result of a derivation."};
 
-    struct CaseHackTag { };
-    Setting<bool, CaseHackTag> useCaseHack{this, nix::useCaseHack, "use-case-hack",
+    CaseHackSetting useCaseHack{this, "use-case-hack",
         "Whether to enable a Darwin-specific hack for dealing with file name collisions."};
 
     Setting<unsigned long> connectTimeout{this, 0, "connect-timeout",
diff --git a/src/libstore/local.mk b/src/libstore/local.mk
index 9d5c04dca0c5..4da20330cf3f 100644
--- a/src/libstore/local.mk
+++ b/src/libstore/local.mk
@@ -27,7 +27,8 @@ libstore_CXXFLAGS = \
  -DNIX_CONF_DIR=\"$(sysconfdir)/nix\" \
  -DNIX_LIBEXEC_DIR=\"$(libexecdir)\" \
  -DNIX_BIN_DIR=\"$(bindir)\" \
- -DBASH_PATH="\"$(bash)\""
+ -DBASH_PATH="\"$(bash)\"" \
+ -DLSOF=\"$(lsof)\"
 
 $(d)/local-store.cc: $(d)/schema.sql.hh
 
diff --git a/src/libstore/remote-store.cc b/src/libstore/remote-store.cc
index da3c8eb8d89d..bc9ef3d47c5e 100644
--- a/src/libstore/remote-store.cc
+++ b/src/libstore/remote-store.cc
@@ -166,7 +166,7 @@ void RemoteStore::setOptions(Connection & conn)
        << settings.useSubstitutes;
 
     if (GET_PROTOCOL_MINOR(conn.daemonVersion) >= 12) {
-        StringMap overrides = settings.getOverrides();
+        auto overrides = settings.getSettings(true);
         conn.to << overrides.size();
         for (auto & i : overrides)
             conn.to << i.first << i.second;
diff --git a/src/libutil/config.cc b/src/libutil/config.cc
index e7a810cec4d2..62c6433c741b 100644
--- a/src/libutil/config.cc
+++ b/src/libutil/config.cc
@@ -1,5 +1,6 @@
 #include "config.hh"
 #include "args.hh"
+#include "json.hh"
 
 namespace nix {
 
@@ -9,6 +10,7 @@ void Config::set(const std::string & name, const std::string & value)
     if (i == _settings.end())
         throw UsageError("unknown setting '%s'", name);
     i->second.setting->set(value);
+    i->second.setting->overriden = true;
 }
 
 void Config::addSetting(AbstractSetting * setting)
@@ -22,6 +24,7 @@ void Config::addSetting(AbstractSetting * setting)
     auto i = initials.find(setting->name);
     if (i != initials.end()) {
         setting->set(i->second);
+        setting->overriden = true;
         initials.erase(i);
         set = true;
     }
@@ -34,6 +37,7 @@ void Config::addSetting(AbstractSetting * setting)
                     alias, setting->name);
             else {
                 setting->set(i->second);
+                setting->overriden = true;
                 initials.erase(i);
                 set = true;
             }
@@ -47,11 +51,11 @@ void Config::warnUnknownSettings()
         warn("unknown setting '%s'", i.first);
 }
 
-StringMap Config::getSettings()
+StringMap Config::getSettings(bool overridenOnly)
 {
     StringMap res;
     for (auto & opt : _settings)
-        if (!opt.second.isAlias)
+        if (!opt.second.isAlias && (!overridenOnly || opt.second.setting->overriden))
             res.emplace(opt.first, opt.second.setting->to_string());
     return res;
 }
@@ -94,6 +98,23 @@ void Config::applyConfigFile(const Path & path, bool fatal)
     } catch (SysError &) { }
 }
 
+void Config::resetOverriden()
+{
+    for (auto & s : _settings)
+        s.second.setting->overriden = false;
+}
+
+void Config::toJSON(JSONObject & out)
+{
+    for (auto & s : _settings)
+        if (!s.second.isAlias) {
+            JSONObject out2(out.object(s.first));
+            out2.attr("description", s.second.setting->description);
+            JSONPlaceholder out3(out2.placeholder("value"));
+            s.second.setting->toJSON(out3);
+        }
+}
+
 AbstractSetting::AbstractSetting(
     const std::string & name,
     const std::string & description,
@@ -102,83 +123,98 @@ AbstractSetting::AbstractSetting(
 {
 }
 
-template<> void Setting<std::string>::set(const std::string & str)
+void AbstractSetting::toJSON(JSONPlaceholder & out)
+{
+    out.write(to_string());
+}
+
+template<typename T>
+void BaseSetting<T>::toJSON(JSONPlaceholder & out)
+{
+    out.write(value);
+}
+
+template<> void BaseSetting<std::string>::set(const std::string & str)
 {
     value = str;
 }
 
-template<> std::string Setting<std::string>::to_string()
+template<> std::string BaseSetting<std::string>::to_string()
 {
     return value;
 }
 
-template<typename T, typename Tag>
-void Setting<T, Tag>::set(const std::string & str)
+template<typename T>
+void BaseSetting<T>::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()
+template<typename T>
+std::string BaseSetting<T>::to_string()
 {
     static_assert(std::is_integral<T>::value, "Integer required.");
     return std::to_string(value);
 }
 
-bool AbstractSetting::parseBool(const std::string & str)
+template<> void BaseSetting<bool>::set(const std::string & str)
 {
     if (str == "true" || str == "yes" || str == "1")
-        return true;
+        value = true;
     else if (str == "false" || str == "no" || str == "0")
-        return false;
+        value = false;
     else
         throw UsageError("Boolean setting '%s' has invalid value '%s'", name, str);
 }
 
-template<> void Setting<bool>::set(const std::string & str)
+template<> std::string BaseSetting<bool>::to_string()
 {
-    value = parseBool(str);
+    return value ? "true" : "false";
 }
 
-std::string AbstractSetting::printBool(bool b)
+template<> void BaseSetting<Strings>::set(const std::string & str)
 {
-    return b ? "true" : "false";
+    value = tokenizeString<Strings>(str);
 }
 
-
-template<> std::string Setting<bool>::to_string()
+template<> std::string BaseSetting<Strings>::to_string()
 {
-    return printBool(value);
+    return concatStringsSep(" ", value);
 }
 
-template<> void Setting<Strings>::set(const std::string & str)
+template<> void BaseSetting<Strings>::toJSON(JSONPlaceholder & out)
 {
-    value = tokenizeString<Strings>(str);
+    JSONList list(out.list());
+    for (auto & s : value)
+        list.elem(s);
 }
 
-template<> std::string Setting<Strings>::to_string()
+template<> void BaseSetting<StringSet>::set(const std::string & str)
 {
-    return concatStringsSep(" ", value);
+    value = tokenizeString<StringSet>(str);
 }
 
-template<> void Setting<StringSet>::set(const std::string & str)
+template<> std::string BaseSetting<StringSet>::to_string()
 {
-    value = tokenizeString<StringSet>(str);
+    return concatStringsSep(" ", value);
 }
 
-template<> std::string Setting<StringSet>::to_string()
+template<> void BaseSetting<StringSet>::toJSON(JSONPlaceholder & out)
 {
-    return concatStringsSep(" ", value);
+    JSONList list(out.list());
+    for (auto & s : value)
+        list.elem(s);
 }
 
-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>;
+template class BaseSetting<int>;
+template class BaseSetting<unsigned int>;
+template class BaseSetting<long>;
+template class BaseSetting<unsigned long>;
+template class BaseSetting<long long>;
+template class BaseSetting<unsigned long long>;
+template class BaseSetting<bool>;
 
 void PathSetting::set(const std::string & str)
 {
diff --git a/src/libutil/config.hh b/src/libutil/config.hh
index 6c8612f675c7..91962109100d 100644
--- a/src/libutil/config.hh
+++ b/src/libutil/config.hh
@@ -9,6 +9,8 @@ namespace nix {
 
 class Args;
 class AbstractSetting;
+class JSONPlaceholder;
+class JSONObject;
 
 /* A class to simplify providing configuration settings. The typical
    use is to inherit Config and add Setting<T> members:
@@ -51,9 +53,13 @@ public:
 
     void warnUnknownSettings();
 
-    StringMap getSettings();
+    StringMap getSettings(bool overridenOnly = false);
 
     void applyConfigFile(const Path & path, bool fatal = false);
+
+    void resetOverriden();
+
+    void toJSON(JSONObject & out);
 };
 
 class AbstractSetting
@@ -68,6 +74,8 @@ public:
 
     int created = 123;
 
+    bool overriden = false;
+
 protected:
 
     AbstractSetting(
@@ -78,7 +86,7 @@ protected:
     virtual ~AbstractSetting()
     {
         // Check against a gcc miscompilation causing our constructor
-        // not to run.
+        // not to run (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80431).
         assert(created == 123);
     }
 
@@ -86,15 +94,14 @@ protected:
 
     virtual std::string to_string() = 0;
 
-    bool parseBool(const std::string & str);
-    std::string printBool(bool b);
-};
+    virtual void toJSON(JSONPlaceholder & out);
 
-struct DefaultSettingTag { };
+    bool isOverriden() { return overriden; }
+};
 
 /* A setting of type T. */
-template<typename T, typename Tag = DefaultSettingTag>
-class Setting : public AbstractSetting
+template<typename T>
+class BaseSetting : public AbstractSetting
 {
 protected:
 
@@ -102,42 +109,59 @@ protected:
 
 public:
 
-    Setting(Config * options,
-        const T & def,
+    BaseSetting(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 operator =(const T & v) { assign(v); }
+    virtual void assign(const T & v) { value = v; }
 
     void set(const std::string & str) override;
 
     std::string to_string() override;
+
+    void toJSON(JSONPlaceholder & out) override;
 };
 
 template<typename T>
-std::ostream & operator <<(std::ostream & str, const Setting<T> & opt)
+std::ostream & operator <<(std::ostream & str, const BaseSetting<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; }
+bool operator ==(const T & v1, const BaseSetting<T> & v2) { return v1 == (const T &) v2; }
+
+template<typename T>
+class Setting : public BaseSetting<T>
+{
+public:
+    Setting(Config * options,
+        const T & def,
+        const std::string & name,
+        const std::string & description,
+        const std::set<std::string> & aliases = {})
+        : BaseSetting<T>(def, name, description, aliases)
+    {
+        options->addSetting(this);
+    }
+
+    void operator =(const T & v) { this->assign(v); }
+};
 
 /* A special setting for Paths. These are automatically canonicalised
    (e.g. "/foo//bar/" becomes "/foo/bar"). */
-class PathSetting : public Setting<Path>
+class PathSetting : public BaseSetting<Path>
 {
     bool allowEmpty;
 
@@ -149,15 +173,17 @@ public:
         const std::string & name,
         const std::string & description,
         const std::set<std::string> & aliases = {})
-        : Setting<Path>(options, def, name, description, aliases)
+        : BaseSetting<Path>(def, name, description, aliases)
         , allowEmpty(allowEmpty)
     {
-        set(value);
+        options->addSetting(this);
     }
 
     void set(const std::string & str) override;
 
     Path operator +(const char * p) const { return value + p; }
+
+    void operator =(const Path & v) { this->assign(v); }
 };
 
 }
diff --git a/src/libutil/json.cc b/src/libutil/json.cc
index 6023d1d4fb84..b8b8ef9c8cca 100644
--- a/src/libutil/json.cc
+++ b/src/libutil/json.cc
@@ -19,49 +19,32 @@ void toJSON(std::ostream & str, const char * start, const char * end)
     str << '"';
 }
 
-void toJSON(std::ostream & str, const std::string & s)
-{
-    toJSON(str, s.c_str(), s.c_str() + s.size());
-}
-
 void toJSON(std::ostream & str, const char * s)
 {
     if (!s) str << "null"; else toJSON(str, s, s + strlen(s));
 }
 
-void toJSON(std::ostream & str, unsigned long long n)
-{
-    str << n;
-}
-
-void toJSON(std::ostream & str, unsigned long n)
-{
-    str << n;
-}
-
-void toJSON(std::ostream & str, long n)
-{
-    str << n;
-}
+template<> void toJSON<int>(std::ostream & str, const int & n) { str << n; }
+template<> void toJSON<unsigned int>(std::ostream & str, const unsigned int & n) { str << n; }
+template<> void toJSON<long>(std::ostream & str, const long & n) { str << n; }
+template<> void toJSON<unsigned long>(std::ostream & str, const unsigned long & n) { str << n; }
+template<> void toJSON<long long>(std::ostream & str, const long long & n) { str << n; }
+template<> void toJSON<unsigned long long>(std::ostream & str, const unsigned long long & n) { str << n; }
+template<> void toJSON<float>(std::ostream & str, const float & n) { str << n; }
 
-void toJSON(std::ostream & str, unsigned int n)
+template<> void toJSON<std::string>(std::ostream & str, const std::string & s)
 {
-    str << n;
-}
-
-void toJSON(std::ostream & str, int n)
-{
-    str << n;
+    toJSON(str, s.c_str(), s.c_str() + s.size());
 }
 
-void toJSON(std::ostream & str, double f)
+template<> void toJSON<bool>(std::ostream & str, const bool & b)
 {
-    str << f;
+    str << (b ? "true" : "false");
 }
 
-void toJSON(std::ostream & str, bool b)
+template<> void toJSON<std::nullptr_t>(std::ostream & str, const std::nullptr_t & b)
 {
-    str << (b ? "true" : "false");
+    str << "null";
 }
 
 JSONWriter::JSONWriter(std::ostream & str, bool indent)
diff --git a/src/libutil/json.hh b/src/libutil/json.hh
index 03eecb732586..595e9bbe3491 100644
--- a/src/libutil/json.hh
+++ b/src/libutil/json.hh
@@ -7,15 +7,10 @@
 namespace nix {
 
 void toJSON(std::ostream & str, const char * start, const char * end);
-void toJSON(std::ostream & str, const std::string & s);
 void toJSON(std::ostream & str, const char * s);
-void toJSON(std::ostream & str, unsigned long long n);
-void toJSON(std::ostream & str, unsigned long n);
-void toJSON(std::ostream & str, long n);
-void toJSON(std::ostream & str, unsigned int n);
-void toJSON(std::ostream & str, int n);
-void toJSON(std::ostream & str, double f);
-void toJSON(std::ostream & str, bool b);
+
+template<typename T>
+void toJSON(std::ostream & str, const T & n);
 
 class JSONWriter
 {
diff --git a/src/libutil/util.cc b/src/libutil/util.cc
index 0bd51afd1a9f..88a2f752c023 100644
--- a/src/libutil/util.cc
+++ b/src/libutil/util.cc
@@ -429,6 +429,18 @@ Path getCacheDir()
 }
 
 
+Path getConfigDir()
+{
+    Path configDir = getEnv("XDG_CONFIG_HOME");
+    if (configDir.empty()) {
+        Path homeDir = getEnv("HOME");
+        if (homeDir.empty()) throw Error("$XDG_CONFIG_HOME and $HOME are not set");
+        configDir = homeDir + "/.config";
+    }
+    return configDir;
+}
+
+
 Paths createDirs(const Path & path)
 {
     Paths created;
@@ -934,7 +946,12 @@ void closeOnExec(int fd)
 
 bool _isInterrupted = false;
 
-thread_local bool interruptThrown = false;
+static thread_local bool interruptThrown = false;
+
+void setInterruptThrown()
+{
+    interruptThrown = true;
+}
 
 void _interrupted()
 {
diff --git a/src/libutil/util.hh b/src/libutil/util.hh
index 0e6941e4a8db..ae40dcd4cd2d 100644
--- a/src/libutil/util.hh
+++ b/src/libutil/util.hh
@@ -110,9 +110,12 @@ void deletePath(const Path & path, unsigned long long & bytesFreed);
 Path createTempDir(const Path & tmpRoot = "", const Path & prefix = "nix",
     bool includePid = true, bool useGlobalCounter = true, mode_t mode = 0755);
 
-/* Return the path to $XDG_CACHE_HOME/.cache. */
+/* Return $XDG_CACHE_HOME or $HOME/.cache. */
 Path getCacheDir();
 
+/* Return $XDG_CONFIG_HOME or $HOME/.config. */
+Path getConfigDir();
+
 /* Create a directory and all its parents, if necessary.  Returns the
    list of created directories, in order of creation. */
 Paths createDirs(const Path & path);
@@ -264,7 +267,7 @@ void closeOnExec(int fd);
 
 extern bool _isInterrupted;
 
-extern thread_local bool interruptThrown;
+void setInterruptThrown();
 
 void _interrupted();
 
diff --git a/src/nix-daemon/nix-daemon.cc b/src/nix-daemon/nix-daemon.cc
index 1389353bb5d8..07ad0b45b3e4 100644
--- a/src/nix-daemon/nix-daemon.cc
+++ b/src/nix-daemon/nix-daemon.cc
@@ -440,7 +440,7 @@ static void performOp(ref<LocalStore> store, bool trusted, unsigned int clientVe
         settings.keepGoing = readInt(from);
         settings.tryFallback = readInt(from);
         verbosity = (Verbosity) readInt(from);
-        settings.maxBuildJobs = readInt(from);
+        settings.maxBuildJobs.assign(readInt(from));
         settings.maxSilentTime = readInt(from);
         settings.useBuildHook = readInt(from) != 0;
         settings.verboseBuild = lvlError == (Verbosity) readInt(from);
@@ -448,20 +448,56 @@ static void performOp(ref<LocalStore> store, bool trusted, unsigned int clientVe
         readInt(from); // obsolete printBuildTrace
         settings.buildCores = readInt(from);
         settings.useSubstitutes  = readInt(from);
+
+        StringMap overrides;
         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);
-                try {
-                    if (trusted || name == "build-timeout")
-                        settings.set(name, value);
-                } catch (UsageError & e) {
-                    warn(e.what());
-                }
+                overrides.emplace(name, value);
             }
         }
+
         startWork();
+
+        for (auto & i : overrides) {
+            auto & name(i.first);
+            auto & value(i.second);
+
+            auto setSubstituters = [&](Setting<Strings> & res) {
+                if (name != res.name && res.aliases.count(name) == 0)
+                    return false;
+                StringSet trusted = settings.trustedSubstituters;
+                for (auto & s : settings.substituters.get())
+                    trusted.insert(s);
+                Strings subs;
+                auto ss = tokenizeString<Strings>(value);
+                for (auto & s : ss)
+                    if (trusted.count(s))
+                        subs.push_back(s);
+                    else
+                        warn("ignoring untrusted substituter '%s'", s);
+                res = subs;
+                return true;
+            };
+
+            try {
+                if (trusted
+                    || name == settings.buildTimeout.name
+                    || name == settings.connectTimeout.name)
+                    settings.set(name, value);
+                else if (setSubstituters(settings.substituters))
+                    ;
+                else if (setSubstituters(settings.extraSubstituters))
+                    ;
+                else
+                    debug("ignoring untrusted setting '%s'", name);
+            } catch (UsageError & e) {
+                warn(e.what());
+            }
+        }
+
         stopWork();
         break;
     }
diff --git a/src/nix/show-config.cc b/src/nix/show-config.cc
index ba39e2bb29b3..aade2adeace4 100644
--- a/src/nix/show-config.cc
+++ b/src/nix/show-config.cc
@@ -31,8 +31,7 @@ struct CmdShowConfig : Command
         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);
+            settings.toJSON(jsonObj);
         } else {
             for (auto & s : settings.getSettings())
                 std::cout << s.first + " = " + s.second + "\n";