diff options
82 files changed, 1455 insertions, 660 deletions
diff --git a/.gitignore b/.gitignore index e420fbf0144f..94067256b8bc 100644 --- a/.gitignore +++ b/.gitignore @@ -112,6 +112,8 @@ Makefile.config *.a *.o *.so +*.dll +*.exe *.dep *~ *.pc diff --git a/Makefile b/Makefile index 08e4012f99b2..d8d4a7cc5768 100644 --- a/Makefile +++ b/Makefile @@ -25,7 +25,7 @@ makefiles = \ GLOBAL_CXXFLAGS += -std=c++0x -g -Wall -include Makefile.config +-include Makefile.config OPTIMIZE = 1 diff --git a/Makefile.config.in b/Makefile.config.in index 797b99f80504..29ccc1b146f1 100644 --- a/Makefile.config.in +++ b/Makefile.config.in @@ -4,9 +4,11 @@ CFLAGS = @CFLAGS@ CXX = @CXX@ CXXFLAGS = @CXXFLAGS@ HAVE_OPENSSL = @HAVE_OPENSSL@ +HAVE_SODIUM = @HAVE_SODIUM@ OPENSSL_LIBS = @OPENSSL_LIBS@ PACKAGE_NAME = @PACKAGE_NAME@ PACKAGE_VERSION = @PACKAGE_VERSION@ +SODIUM_LIBS = @SODIUM_LIBS@ bash = @bash@ bindir = @bindir@ bsddiff_compat_include = @bsddiff_compat_include@ diff --git a/configure.ac b/configure.ac index 622cf1e2045d..756b2f22729c 100644 --- a/configure.ac +++ b/configure.ac @@ -87,6 +87,7 @@ AC_CHECK_HEADERS([sys/mount.h], [], [], # include <sys/param.h> # endif ]) +AC_CHECK_HEADERS([sys/syscall.h]) # Check for lutimes, optionally used for changing the mtime of @@ -205,6 +206,14 @@ AC_CHECK_HEADERS([bzlib.h], [true], PKG_CHECK_MODULES([SQLITE3], [sqlite3 >= 3.6.19], [CXXFLAGS="$SQLITE3_CFLAGS $CXXFLAGS"]) +# Look for libsodium, an optional dependency. +PKG_CHECK_MODULES([SODIUM], [libsodium], + [AC_DEFINE([HAVE_SODIUM], [1], [Whether to use libsodium for cryptography.]) + CXXFLAGS="$SODIUM_CFLAGS $CXXFLAGS" + have_sodium=1], [have_sodium=]) +AC_SUBST(HAVE_SODIUM, [$have_sodium]) + + # Whether to use the Boehm garbage collector. AC_ARG_ENABLE(gc, AC_HELP_STRING([--enable-gc], [enable garbage collection in the Nix expression evaluator (requires Boehm GC) [default=no]]), diff --git a/doc/manual/advanced-topics/advanced-topics.xml b/doc/manual/advanced-topics/advanced-topics.xml new file mode 100644 index 000000000000..338aa6f3a238 --- /dev/null +++ b/doc/manual/advanced-topics/advanced-topics.xml @@ -0,0 +1,10 @@ +<part xmlns="http://docbook.org/ns/docbook" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:xi="http://www.w3.org/2001/XInclude" + version="5.0"> + +<title>Advanced Topics</title> + +<xi:include href="distributed-builds.xml" /> + +</part> diff --git a/doc/manual/builds/enabling-builds.xml b/doc/manual/advanced-topics/distributed-builds.xml index 4b45812ee918..70f396f81cdb 100644 --- a/doc/manual/builds/enabling-builds.xml +++ b/doc/manual/advanced-topics/distributed-builds.xml @@ -2,9 +2,18 @@ xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xi="http://www.w3.org/2001/XInclude" version="5.0" - xml:id="ch-enabling-builds"> + xml:id='chap-distributed-builds'> -<title>Enabling Distributed Builds</title> +<title>Distributed Builds</title> + +<para>Nix supports distributed builds, where a local Nix installation can +forward Nix builds to other machines over the network. This allows +multiple builds to be performed in parallel (thus improving +performance) and allows Nix to perform multi-platform builds in a +semi-transparent way. For instance, if you perform a build for a +<literal>powerpc-darwin</literal> on an <literal>i686-linux</literal> +machine, Nix can automatically forward the build to a +<literal>powerpc-darwin</literal> machine, if available.</para> <para>You can enable distributed builds by setting the environment variable <envar>NIX_BUILD_HOOK</envar> to point to a program that Nix @@ -41,7 +50,7 @@ example configuration is shown in <xref linkend='ex-remote-systems' bits of information: <orderedlist> - + <listitem><para>The name of the remote machine, with optionally the user under which the remote build should be performed. This is actually passed as an argument to <command>ssh</command>, so it can @@ -73,9 +82,9 @@ bits of information: <filename>build-remote.pl</filename> will only perform the derivation on a machine that has the specified features. For instance, the attribute - + <programlisting> -requiredSystemFeatures = [ "kvm" ]; +requiredSystemFeatures = [ "kvm" ]; </programlisting> will cause the build to be performed on a machine that has the @@ -103,4 +112,4 @@ running, they should use the same <envar>NIX_CURRENT_LOAD</envar> file. Maybe in the future <filename>build-remote.pl</filename> will look at the actual remote load.</para> -</chapter> \ No newline at end of file +</chapter> diff --git a/doc/manual/builds/build-farm.xml b/doc/manual/builds/build-farm.xml deleted file mode 100644 index e0e9f10f1173..000000000000 --- a/doc/manual/builds/build-farm.xml +++ /dev/null @@ -1,22 +0,0 @@ -<part xmlns="http://docbook.org/ns/docbook" - xmlns:xlink="http://www.w3.org/1999/xlink" - xmlns:xi="http://www.w3.org/2001/XInclude" - version="5.0" - xml:id='chap-distributed-builds'> - -<title>Distributed Builds</title> - -<partintro> -<para>Nix supports distributed builds, where a local Nix installation can -forward Nix builds to other machines over the network. This allows -multiple builds to be performed in parallel (thus improving -performance) and allows Nix to perform multi-platform builds in a -semi-transparent way. For instance, if you perform a build for a -<literal>powerpc-darwin</literal> on an <literal>i686-linux</literal> -machine, Nix can automatically forward the build to a -<literal>powerpc-darwin</literal> machine, if available.</para> -</partintro> - -<xi:include href="enabling-builds.xml" /> - -</part> diff --git a/doc/manual/command-ref/conf-file.xml b/doc/manual/command-ref/conf-file.xml index 053f4d43cb0c..1728abfd9c59 100644 --- a/doc/manual/command-ref/conf-file.xml +++ b/doc/manual/command-ref/conf-file.xml @@ -243,7 +243,8 @@ flag, e.g. <literal>--option gc-keep-outputs false</literal>.</para> users” feature</link> to perform the actual builds under different users than root). Currently, chroot builds only work on Linux because Nix uses “bind mounts” to make the Nix store and other - directories available inside the chroot.</para> + directories available inside the chroot. Kernel version 3.13 or later + is needed.</para> </listitem> diff --git a/doc/manual/command-ref/nix-channel.xml b/doc/manual/command-ref/nix-channel.xml index 195b209f9523..a6f4a27203ac 100644 --- a/doc/manual/command-ref/nix-channel.xml +++ b/doc/manual/command-ref/nix-channel.xml @@ -21,7 +21,7 @@ <command>nix-channel</command> <group choice='req'> <arg choice='plain'><option>--add</option> <replaceable>url</replaceable> <arg choice='opt'><replaceable>name</replaceable></arg></arg> - <arg choice='plain'><option>--remove</option> <replaceable>url</replaceable></arg> + <arg choice='plain'><option>--remove</option> <replaceable>name</replaceable></arg> <arg choice='plain'><option>--list</option></arg> <arg choice='plain'><option>--update</option> <arg rep='repeat'><replaceable>names</replaceable></arg></arg> <arg choice='plain'><option>--rollback</option> <arg choice='opt'><replaceable>generation</replaceable></arg></arg> @@ -33,8 +33,8 @@ <para>A Nix channel is mechanism that allows you to automatically stay up-to-date with a set of pre-built Nix expressions. A Nix channel is -just a URL that points to a place containing a set of Nix expressions -and a <command>nix-push</command> manifest. <phrase +just a URL that points to a place containing both a set of Nix +expressions and a pointer to a binary cache. <phrase condition="manual">See also <xref linkend="sec-channels" />.</phrase></para> @@ -99,13 +99,6 @@ an update.</para> <para>The list of subscribed channels is stored in <filename>~/.nix-channels</filename>.</para> -<para>A channel consists of two elements: a bzipped Tar archive -containing the Nix expressions, and a manifest created by -<command>nix-push</command>. These must be stored under -<literal><replaceable>url</replaceable>/nixexprs.tar.bz2</literal> and -<literal><replaceable>url</replaceable>/MANIFEST</literal>, -respectively.</para> - </refsection> <refsection><title>Examples</title> @@ -163,4 +156,49 @@ $ nix-instantiate --eval -E '(import <nixpkgs> {}).lib.nixpkgsVersion' </refsection> +<refsection><title>Channel format</title> + +<para>A channel URL should point to a directory containing the +following files:</para> + +<variablelist> + + <varlistentry><term><filename>nixexprs.tar.xz</filename></term> + + <listitem><para>A tarball containing Nix expressions and files + referenced by them (such as build scripts and patches). At + top-level, the tarball should contain a single directory. That + directory must contain a file <filename>default.nix</filename> + that serves as the channel’s “entry point”.</para></listitem> + + </varlistentry> + + <varlistentry><term><filename>binary-cache-url</filename></term> + + <listitem><para>A file containing the URL to a binary cache (such + as <uri>https://cache.nixos.org</uri>. Nix will automatically + check this cache for pre-built binaries, if the user has + sufficient rights to add binary caches. For instance, in a + multi-user Nix setup, the binary caches provided by the channels + of the root user are used automatically, but caches corresponding + to the channels of non-root users are ignored. Binary caches can + be created and maintained using + <command>nix-push</command>.</para></listitem> + + </varlistentry> + + <varlistentry><term><filename>MANIFEST.bz2</filename></term> + + <listitem><para>(Deprecated in favour of binary caches.) A + manifest as created by <command>nix-push</command>. Only used if + <filename>binary-cache-url</filename> is not present or if the + <filename>nix.conf</filename> option + <option>force-manifest</option> is set.</para></listitem> + + </varlistentry> + +</variablelist> + +</refsection> + </refentry> diff --git a/doc/manual/command-ref/nix-install-package.xml b/doc/manual/command-ref/nix-install-package.xml index 7d5cd996e44a..f7802a95d55e 100644 --- a/doc/manual/command-ref/nix-install-package.xml +++ b/doc/manual/command-ref/nix-install-package.xml @@ -3,7 +3,7 @@ xmlns:xi="http://www.w3.org/2001/XInclude" version="5.0" xml:id="sec-nix-install-package"> - + <refmeta> <refentrytitle>nix-install-package</refentrytitle> <manvolnum>1</manvolnum> @@ -47,8 +47,7 @@ <para>The command <command>nix-install-package</command> interactively installs a Nix Package file (<filename>*.nixpkg</filename>), which is a small file that contains a store path to be installed along with the -URL of a <link linkend="sec-nix-push"><command>nix-push</command> -manifest</link>. The Nix Package file is either +URL of a binary cache. The Nix Package file is either <replaceable>file</replaceable>, or automatically downloaded from <replaceable>url</replaceable> if the <option>--url</option> switch is used.</para> @@ -76,7 +75,7 @@ to restart itself with <command>xterm</command>, <refsection><title>Options</title> <variablelist> - + <varlistentry><term><option>--non-interactive</option></term> <listitem><para>Do not open a new terminal window and do not ask @@ -139,14 +138,14 @@ The elements are as follows: <variablelist> <varlistentry><term><literal>NIXPKG1</literal></term> - + <listitem><para>The version of the Nix Package file.</para></listitem> </varlistentry> <varlistentry><term><replaceable>manifestURL</replaceable></term> - + <listitem><para>The manifest to be pulled by <command>nix-pull</command>. The manifest must contain <replaceable>outPath</replaceable>.</para></listitem> @@ -154,21 +153,21 @@ The elements are as follows: </varlistentry> <varlistentry><term><replaceable>name</replaceable></term> - + <listitem><para>The symbolic name and version of the package.</para></listitem> </varlistentry> <varlistentry><term><replaceable>system</replaceable></term> - + <listitem><para>The platform identifier of the platform for which this binary package is intended.</para></listitem> </varlistentry> <varlistentry><term><replaceable>drvPath</replaceable></term> - + <listitem><para>The path in the Nix store of the derivation from which <replaceable>outPath</replaceable> was built. Not currently used.</para></listitem> @@ -176,17 +175,21 @@ The elements are as follows: </varlistentry> <varlistentry><term><replaceable>outPath</replaceable></term> - - <listitem><para>The path in the Nix store of the package. After - <command>nix-install-package</command> has obtained the manifest - from <replaceable>manifestURL</replaceable>, it performs a - <literal>nix-env -i</literal> <replaceable>outPath</replaceable> - to install the binary package.</para></listitem> + + <listitem><para>The path in the Nix store of the + package.</para></listitem> + + </varlistentry> + + <varlistentry><term><replaceable>binaryCacheURL</replaceable></term> + + <listitem><para>The URL of a binary cache containing the closure + of <replaceable>outPath</replaceable>.</para></listitem> </varlistentry> </variablelist> - + </para> <para>An example follows: diff --git a/doc/manual/command-ref/nix-pull.xml b/doc/manual/command-ref/nix-pull.xml index 598c651b5091..eb471677b63f 100644 --- a/doc/manual/command-ref/nix-pull.xml +++ b/doc/manual/command-ref/nix-pull.xml @@ -13,7 +13,7 @@ <refnamediv> <refname>nix-pull</refname> - <refpurpose>pull substitutes from a network cache</refpurpose> + <refpurpose>register availability of pre-built binaries (deprecated)</refpurpose> </refnamediv> <refsynopsisdiv> @@ -26,6 +26,9 @@ <refsection><title>Description</title> +<note><para>This command and the use of manifests is deprecated. It is +better to use binary caches.</para></note> + <para>The command <command>nix-pull</command> obtains a list of pre-built store paths from the URL <replaceable>url</replaceable>, and for each of these store paths, registers a substitute derivation that @@ -43,7 +46,7 @@ with the files created by <replaceable>nix-push</replaceable>.</para> <refsection><title>Examples</title> <screen> -$ nix-pull http://nix.cs.uu.nl/dist/nix/nixpkgs-0.5pre753/MANIFEST</screen> +$ nix-pull https://nixos.org/releases/nixpkgs/nixpkgs-15.05pre54468.69858d7/MANIFEST</screen> </refsection> diff --git a/doc/manual/command-ref/nix-shell.xml b/doc/manual/command-ref/nix-shell.xml index 1f03117636c9..c1b172b70380 100644 --- a/doc/manual/command-ref/nix-shell.xml +++ b/doc/manual/command-ref/nix-shell.xml @@ -29,6 +29,7 @@ <replaceable>attrPath</replaceable> </arg> <arg><option>--command</option> <replaceable>cmd</replaceable></arg> + <arg><option>--run</option> <replaceable>cmd</replaceable></arg> <arg><option>--exclude</option> <replaceable>regexp</replaceable></arg> <arg><option>--pure</option></arg> <group choice='req'> @@ -92,11 +93,24 @@ also <xref linkend="sec-common-options" />.</phrase></para> <varlistentry><term><option>--command</option> <replaceable>cmd</replaceable></term> <listitem><para>In the environment of the derivation, run the - shell command <replaceable>cmd</replaceable> instead of starting - an interactive shell. However, if you end the shell command with - <literal>return</literal>, you still get an interactive shell. - This can be useful for doing any additional - initialisation.</para></listitem> + shell command <replaceable>cmd</replaceable>. This command is + executed in an interactive shell. (Use <option>--run</option> to + use a non-interactive shell instead.) However, a call to + <literal>exit</literal> is implicitly added to the command, so the + shell will exit after running the command. To prevent this, add + <literal>return</literal> at the end; e.g. <literal>--command + "echo Hello; return"</literal> will print <literal>Hello</literal> + and then drop you into the interactive shell. This can be useful + for doing any additional initialisation.</para></listitem> + + </varlistentry> + + <varlistentry><term><option>--run</option> <replaceable>cmd</replaceable></term> + + <listitem><para>Like <option>--command</option>, but executes the + command in a non-interactive shell. This means (among other + things) that if you hit Ctrl-C while the command is running, the + shell exits.</para></listitem> </varlistentry> diff --git a/doc/manual/command-ref/utilities.xml b/doc/manual/command-ref/utilities.xml index d5650fd38f43..be2fe6e2d235 100644 --- a/doc/manual/command-ref/utilities.xml +++ b/doc/manual/command-ref/utilities.xml @@ -13,7 +13,9 @@ work with Nix.</para> <xi:include href="nix-collect-garbage.xml" /> <xi:include href="nix-copy-closure.xml" /> <xi:include href="nix-daemon.xml" /> +<!-- <xi:include href="nix-generate-patches.xml" /> +--> <xi:include href="nix-hash.xml" /> <xi:include href="nix-install-package.xml" /> <xi:include href="nix-instantiate.xml" /> @@ -21,4 +23,4 @@ work with Nix.</para> <xi:include href="nix-pull.xml" /> <xi:include href="nix-push.xml" /> -</chapter> \ No newline at end of file +</chapter> diff --git a/doc/manual/expressions/advanced-attributes.xml b/doc/manual/expressions/advanced-attributes.xml index f8b84b98cb82..83ad6eefc8b1 100644 --- a/doc/manual/expressions/advanced-attributes.xml +++ b/doc/manual/expressions/advanced-attributes.xml @@ -40,7 +40,7 @@ allowedReferences = []; recursively. For example, <programlisting> -allowedReferences = [ foobar ]; +allowedRequisites = [ foobar ]; </programlisting> enforces that the output of a derivation cannot have any other @@ -90,6 +90,33 @@ derivation { </varlistentry> + <varlistentry><term><varname>impureEnvVars</varname></term> + + <listitem><para>This attribute allows you to specify a list of + environment variables that should be passed from the environment + of the calling user to the builder. Usually, the environment is + cleared completely when the builder is executed, but with this + attribute you can allow specific environment variables to be + passed unmodified. For example, <function>fetchurl</function> in + Nixpkgs has the line + +<programlisting> +impureEnvVars = [ "http_proxy" "https_proxy" <replaceable>...</replaceable> ]; +</programlisting> + + to make it use the proxy server configuration specified by the + user in the environment variables <envar>http_proxy</envar> and + friends.</para> + + <para>This attribute is only allowed in <link + linkend="fixed-output-drvs">fixed-output derivations</link>, where + impurities such as these are okay since (the hash of) the output + is known in advance. It is ignored for all other + derivations.</para></listitem> + + </varlistentry> + + <varlistentry xml:id="fixed-output-drvs"> <term><varname>outputHash</varname></term> <term><varname>outputHashAlgo</varname></term> @@ -215,29 +242,29 @@ stdenv.mkDerivation { </varlistentry> - <varlistentry><term><varname>impureEnvVars</varname></term> - - <listitem><para>This attribute allows you to specify a list of - environment variables that should be passed from the environment - of the calling user to the builder. Usually, the environment is - cleared completely when the builder is executed, but with this - attribute you can allow specific environment variables to be - passed unmodified. For example, <function>fetchurl</function> in - Nixpkgs has the line - -<programlisting> -impureEnvVars = [ "http_proxy" "https_proxy" <replaceable>...</replaceable> ]; -</programlisting> - - to make it use the proxy server configuration specified by the - user in the environment variables <envar>http_proxy</envar> and - friends.</para> - - <para>This attribute is only allowed in <link - linkend="fixed-output-drvs">fixed-output derivations</link>, where - impurities such as these are okay since (the hash of) the output - is known in advance. It is ignored for all other - derivations.</para></listitem> + <varlistentry><term><varname>passAsFile</varname></term> + + <listitem><para>A list of names of attributes that should be + passed via files rather than environment variables. For example, + if you have + + <programlisting> +passAsFile = ["big"]; +big = "a very long string"; + </programlisting> + + then when the builder runs, the environment variable + <envar>bigPath</envar> will contain the absolute path to a + temporary file containing <literal>a very long + string</literal>. That is, for any attribute + <replaceable>x</replaceable> listed in + <varname>passAsFile</varname>, Nix will pass an environment + variable <envar><replaceable>x</replaceable>Path</envar> holding + the path of the file containing the value of attribute + <replaceable>x</replaceable>. This is useful when you need to pass + large strings to a builder, since most operating systems impose a + limit on the size of the environment (typically, a few hundred + kilobyte).</para></listitem> </varlistentry> diff --git a/doc/manual/expressions/custom-builder.xml b/doc/manual/expressions/custom-builder.xml deleted file mode 100644 index c26deac40f4a..000000000000 --- a/doc/manual/expressions/custom-builder.xml +++ /dev/null @@ -1,26 +0,0 @@ -<section xmlns="http://docbook.org/ns/docbook" - xmlns:xlink="http://www.w3.org/1999/xlink" - xmlns:xi="http://www.w3.org/2001/XInclude" - version="5.0" - xml:id="sec-custom-builder"> - -<title>Customizing the Generic Builder</title> - -<para>The operation of the generic builder can be modified in many -places by setting certain variables. These <emphasis>hook -variables</emphasis> are typically set to the name of some shell -function defined by you. For instance, to perform some additional -steps after <command>make install</command> you would set the -<varname>postInstall</varname> variable: - -<programlisting> -postInstall=myPostInstall - -myPostInstall() { - mkdir $out/share/extra - cp extrafiles/* $out/share/extra -}</programlisting> - -</para> - -</section> \ No newline at end of file diff --git a/doc/manual/expressions/debug-build.xml b/doc/manual/expressions/debug-build.xml index 508cb2c1930e..0c1f4e6719b2 100644 --- a/doc/manual/expressions/debug-build.xml +++ b/doc/manual/expressions/debug-build.xml @@ -6,13 +6,14 @@ <title>Debugging Build Failures</title> -<para>At the beginning of each phase, the set of all shell variables -is written to the file <filename>env-vars</filename> at the top-level -build directory. This is useful for debugging: it allows you to -recreate the environment in which a build was performed. For -instance, if a build fails, then assuming you used the -<option>-K</option> flag, you can go to the output directory and -<quote>switch</quote> to the environment of the builder: +<para>At the beginning of each phase of the build (such as unpacking, +building or installing), the set of all shell variables is written to +the file <filename>env-vars</filename> at the top-level build +directory. This is useful for debugging: it allows you to recreate +the environment in which a build was performed. For instance, if a +build fails, then assuming you used the <option>-K</option> flag, you +can go to the output directory and <quote>switch</quote> to the +environment of the builder: <screen> $ nix-build -K ./foo.nix @@ -30,4 +31,4 @@ $ make </para> -</section> \ No newline at end of file +</section> diff --git a/doc/manual/expressions/derivations.xml b/doc/manual/expressions/derivations.xml index b57c33f4e3a9..90e2786faaab 100644 --- a/doc/manual/expressions/derivations.xml +++ b/doc/manual/expressions/derivations.xml @@ -110,12 +110,12 @@ buildInputs = [ pkg pkg.headers ]; </itemizedlist> -<para>The function <function>mkDerivation</function> in the standard -environment is a wrapper around <function>derivation</function> that -adds a default value for <varname>system</varname> and always uses -Bash as the builder, to which the supplied builder is passed as a -command-line argument. See <xref linkend='sec-standard-environment' -/>.</para> +<para>The function <function>mkDerivation</function> in the Nixpkgs +standard environment is a wrapper around +<function>derivation</function> that adds a default value for +<varname>system</varname> and always uses Bash as the builder, to +which the supplied builder is passed as a command-line argument. See +the Nixpkgs manual for details.</para> <para>The builder is executed as follows: @@ -208,4 +208,4 @@ command-line argument. See <xref linkend='sec-standard-environment' <xi:include href="advanced-attributes.xml" /> -</section> \ No newline at end of file +</section> diff --git a/doc/manual/expressions/generic-builder.xml b/doc/manual/expressions/generic-builder.xml index f8567a042d47..db7ff405d8b1 100644 --- a/doc/manual/expressions/generic-builder.xml +++ b/doc/manual/expressions/generic-builder.xml @@ -71,7 +71,7 @@ genericBuild <co xml:id='ex-hello-builder2-co-3' /></programlisting> generic builder is smart enough to figure out whether to unpack the sources using <command>gzip</command>, <command>bzip2</command>, etc. It can be customised in many ways; - see <xref linkend='sec-standard-environment' />.</para> + see the Nixpkgs manual for details.</para> </callout> @@ -95,4 +95,4 @@ In fact, <varname>mkDerivation</varname> provides a default builder that looks exactly like that, so it is actually possible to omit the builder for Hello entirely.</para> -</section> \ No newline at end of file +</section> diff --git a/doc/manual/expressions/language-values.xml b/doc/manual/expressions/language-values.xml index c3514e58f0de..0bf6632d6e3a 100644 --- a/doc/manual/expressions/language-values.xml +++ b/doc/manual/expressions/language-values.xml @@ -155,7 +155,14 @@ stdenv.mkDerivation { expression that contained it. For instance, if a Nix expression in <filename>/foo/bar/bla.nix</filename> refers to <filename>../xyzzy/fnord.nix</filename>, the absolute path is - <filename>/foo/xyzzy/fnord.nix</filename>.</para></listitem> + <filename>/foo/xyzzy/fnord.nix</filename>.</para> + + <para>If the first component of a path is a <literal>~</literal>, + it is interpreted as if the rest of the path were relative to the + user's home directory. e.g. <filename>~/foo</filename> would be + equivalent to <filename>/home/edolstra/foo</filename> for a user + whose home directory is <filename>/home/edolstra</filename>. + </para></listitem> <listitem><para><emphasis>Booleans</emphasis> with values <literal>true</literal> and diff --git a/doc/manual/expressions/simple-building-testing.xml b/doc/manual/expressions/simple-building-testing.xml index cc90409b5e93..e0dd98b7e67e 100644 --- a/doc/manual/expressions/simple-building-testing.xml +++ b/doc/manual/expressions/simple-building-testing.xml @@ -83,4 +83,6 @@ Just pass the option <link linkend='opt-max-jobs'><option>-j in parallel, or set. Typically this should be the number of CPUs.</para> -</section> \ No newline at end of file +<xi:include href="debug-build.xml" /> + +</section> diff --git a/doc/manual/expressions/simple-expression.xml b/doc/manual/expressions/simple-expression.xml index a8eb96f5a8e2..29fd872eea19 100644 --- a/doc/manual/expressions/simple-expression.xml +++ b/doc/manual/expressions/simple-expression.xml @@ -4,7 +4,7 @@ version="5.0" xml:id="ch-simple-expression"> -<title>Simple Nix Expression Use-Case</title> +<title>A Simple Nix Expression</title> <para>This section shows how to add and test the <link xlink:href='http://www.gnu.org/software/hello/hello.html'>GNU Hello @@ -44,4 +44,4 @@ need to do three things: <xi:include href="simple-building-testing.xml" /> <xi:include href="generic-builder.xml" /> -</chapter> \ No newline at end of file +</chapter> diff --git a/doc/manual/expressions/standard-env.xml b/doc/manual/expressions/standard-env.xml deleted file mode 100644 index 2571f43fccba..000000000000 --- a/doc/manual/expressions/standard-env.xml +++ /dev/null @@ -1,60 +0,0 @@ -<chapter xmlns="http://docbook.org/ns/docbook" - xmlns:xlink="http://www.w3.org/1999/xlink" - xmlns:xi="http://www.w3.org/2001/XInclude" - version="5.0" - xml:id='sec-standard-environment'> - -<title>The Standard Environment</title> - - -<para>The standard environment is used by passing it as an input -called <envar>stdenv</envar> to the derivation, and then doing - -<programlisting> -source $stdenv/setup</programlisting> - -at the top of the builder.</para> - -<para>Apart from adding the aforementioned commands to the -<envar>PATH</envar>, <filename>setup</filename> also does the -following: - -<itemizedlist> - - <listitem><para>All input packages specified in the - <envar>buildInputs</envar> environment variable have their - <filename>/bin</filename> subdirectory added to <envar>PATH</envar>, - their <filename>/include</filename> subdirectory added to the C/C++ - header file search path, and their <filename>/lib</filename> - subdirectory added to the linker search path. This can be extended. - For instance, when the <command>pkgconfig</command> package is - used, the subdirectory <filename>/lib/pkgconfig</filename> of each - input is added to the <envar>PKG_CONFIG_PATH</envar> environment - variable.</para></listitem> - - <listitem><para>The environment variable - <envar>NIX_CFLAGS_STRIP</envar> is set so that the compiler strips - debug information from object files. This can be disabled by - setting <envar>NIX_STRIP_DEBUG</envar> to - <literal>0</literal>.</para></listitem> - -</itemizedlist> - -</para> - -<para>The <filename>setup</filename> script also exports a function -called <function>genericBuild</function> that knows how to build -typical Autoconf-style packages. It can be customised to perform -builds for any type of package. It is advisable to use -<function>genericBuild</function> since it provides facilities that -are almost always useful such as unpacking of sources, patching of -sources, nested logging, etc.</para> - -<para>The definitive, up-to-date documentation of the generic builder -is the source itself, which resides in -<filename>pkgs/stdenv/generic/setup.sh</filename>.</para> - -<xi:include href="custom-builder.xml" /> -<xi:include href="debug-build.xml" /> - -</chapter> \ No newline at end of file diff --git a/doc/manual/expressions/writing-nix-expressions.xml b/doc/manual/expressions/writing-nix-expressions.xml index 6b797c200992..6646dddf0842 100644 --- a/doc/manual/expressions/writing-nix-expressions.xml +++ b/doc/manual/expressions/writing-nix-expressions.xml @@ -22,6 +22,5 @@ manual</link>.</para></note> <xi:include href="simple-expression.xml" /> <xi:include href="expression-language.xml" /> -<xi:include href="standard-env.xml" /> </part> diff --git a/doc/manual/installation/multi-user.xml b/doc/manual/installation/multi-user.xml index 312f5966452d..49c4f723597e 100644 --- a/doc/manual/installation/multi-user.xml +++ b/doc/manual/installation/multi-user.xml @@ -23,11 +23,11 @@ daemon</emphasis> running under the owner of the Nix store/database that performs the operation.</para> <note><para>Multi-user mode has one important limitation: only -<systemitem class="username">root</systemitem> can run <command -linkend="sec-nix-pull">nix-pull</command> to register the availability -of pre-built binaries. However, those registrations are shared by all -users, so they still get the benefit from <command>nix-pull</command>s -done by <systemitem class="username">root</systemitem>.</para></note> +<systemitem class="username">root</systemitem> and a set of trusted +users specified in <filename>nix.conf</filename> can specify arbitrary +binary caches. So while unprivileged users may install packages from +arbitrary Nix expressions, they may not get pre-built +binaries.</para></note> <simplesect> @@ -52,6 +52,34 @@ This creates 10 build users. There can never be more concurrent builds than the number of build users, so you may want to increase this if you expect to do many builds at the same time.</para> +<para>On Mac OS X, you can create the required group and users by +running the following script: + +<programlisting> +#! /bin/bash -e + +dseditgroup -o create nixbld -q + +gid=$(dscl . -read /Groups/nixbld | awk '($1 == "PrimaryGroupID:") {print $2 }') + +echo "created nixbld group with gid $gid" + +for i in $(seq 1 10); do + user=/Users/nixbld$i + uid="$((30000 + $i))" + dscl . create $user + dscl . create $user RealName "Nix build user $i" + dscl . create $user PrimaryGroupID "$gid" + dscl . create $user UserShell /usr/bin/false + dscl . create $user NFSHomeDirectory /var/empty + dscl . create $user UniqueID "$uid" + dseditgroup -o edit -a nixbld$i -t user nixbld + echo "created nixbld$i user with uid $uid" +done +</programlisting> + +</para> + </simplesect> diff --git a/doc/manual/local.mk b/doc/manual/local.mk index 8511e83599cd..3d7e7fed9631 100644 --- a/doc/manual/local.mk +++ b/doc/manual/local.mk @@ -7,7 +7,8 @@ XSLTPROC = $(xsltproc) --nonet $(xmlflags) \ --param admon.style \'\' \ --param callout.graphics.extension \'.gif\' \ --param contrib.inline.enabled 0 \ - --stringparam generate.toc "book toc" + --stringparam generate.toc "book toc" \ + --param keep.relative.image.uris 0 docbookxsl = http://docbook.sourceforge.net/release/xsl-ns/1.78.1 docbookrng = http://docbook.org/xml/5.0/rng/docbook.rng @@ -39,7 +40,7 @@ dist-files += $(d)/manual.xmli $(d)/version.txt $(d)/manual.is-valid man-pages := $(foreach n, \ nix-env.1 nix-build.1 nix-shell.1 nix-store.1 nix-instantiate.1 \ nix-collect-garbage.1 nix-push.1 nix-pull.1 \ - nix-prefetch-url.1 nix-channel.1 nix-generate-patches.1 \ + nix-prefetch-url.1 nix-channel.1 \ nix-install-package.1 nix-hash.1 nix-copy-closure.1 \ nix.conf.5 nix-daemon.8, \ $(d)/$(n)) diff --git a/doc/manual/manual.xml b/doc/manual/manual.xml index 5c46f303a11c..61205d916993 100644 --- a/doc/manual/manual.xml +++ b/doc/manual/manual.xml @@ -4,10 +4,8 @@ version="5.0"> <info> - <title>Nix Package Manager Guide</title> - - <edition>Version <xi:include href="version.txt" parse="text" /></edition> + <subtitle>Version <xi:include href="version.txt" parse="text" /></subtitle> <author> <personname> @@ -41,7 +39,7 @@ <xi:include href="installation/installation.xml" /> <xi:include href="packages/package-management.xml" /> <xi:include href="expressions/writing-nix-expressions.xml" /> - <xi:include href="builds/build-farm.xml" /> + <xi:include href="advanced-topics/advanced-topics.xml" /> <xi:include href="command-ref/command-ref.xml" /> <xi:include href="troubleshooting/troubleshooting.xml" /> <xi:include href="glossary/glossary.xml" /> diff --git a/doc/manual/packages/basic-package-mgmt.xml b/doc/manual/packages/basic-package-mgmt.xml index 69c955c1dd11..e8d1419da093 100644 --- a/doc/manual/packages/basic-package-mgmt.xml +++ b/doc/manual/packages/basic-package-mgmt.xml @@ -28,40 +28,71 @@ Nix expressions called the Nix Package collection that contains packages ranging from basic development stuff such as GCC and Glibc, to end-user applications like Mozilla Firefox. (Nix is however not tied to the Nix Package collection; you could write your own Nix -expressions based on it, or completely new ones.) You can download -the latest version from <link -xlink:href='http://nixos.org/nixpkgs/download.html' />.</para> +expressions based on it, or completely new ones.)</para> + +<para>You can manually download the latest version of Nixpkgs from +<link xlink:href='http://nixos.org/nixpkgs/download.html'/>. However, +it’s much more convenient to use the Nixpkgs +<emphasis>channel</emphasis>, since it makes it easy to stay up to +date with new versions of Nixpkgs. (Channels are described in more +detail in <xref linkend="sec-channels"/>.) Nixpkgs is automatically +added to your list of “subscribed” channels when you install +Nix. If this is not the case for some reason, you can add it as +follows: -<para>Assuming that you have downloaded and unpacked a release of Nix -Packages, you can view the set of available packages in the release: +<screen> +$ nix-channel --add https://nixos.org/channels/nixpkgs-unstable +$ nix-channel --update +</screen> + +</para> + +<note><para>On NixOS, you’re automatically subscribed to a NixOS +channel corresponding to your NixOS major release +(e.g. <uri>http://nixos.org/channels/nixos-14.12</uri>). A NixOS +channel is identical to the Nixpkgs channel, except that it contains +only Linux binaries and is updated only if a set of regression tests +succeed.</para></note> + +<para>You can view the set of available packages in Nixpkgs: <screen> -$ nix-env -qaf nixpkgs-<replaceable>version</replaceable> '*' -ant-blackdown-1.4.2 +$ nix-env -qa aterm-2.2 bash-3.0 binutils-2.15 bison-1.875d blackdown-1.4.2 bzip2-1.0.2 -...</screen> - -where <literal>nixpkgs-<replaceable>version</replaceable></literal> is -where you’ve unpacked the release. The flag <option>-q</option> -specifies a query operation; <option>-a</option> means that you want -to show the “available” (i.e., installable) packages, as opposed to -the installed packages; and <option>-f</option> -<filename>nixpkgs-<replaceable>version</replaceable></filename> -specifies the source of the packages. The argument -<literal>'*'</literal> shows all installable packages. (The quotes are -necessary to prevent shell expansion.) You can also select specific -packages by name: +…</screen> + +The flag <option>-q</option> specifies a query operation, and +<option>-a</option> means that you want to show the “available” (i.e., +installable) packages, as opposed to the installed packages. If you +downloaded Nixpkgs yourself, or if you checked it out from GitHub, +then you need to pass the path to your Nixpkgs tree using the +<option>-f</option> flag: + +<screen> +$ nix-env -qaf <replaceable>/path/to/nixpkgs</replaceable> +</screen> + +where <replaceable>/path/to/nixpkgs</replaceable> is where you’ve +unpacked or checked out Nixpkgs.</para> + +<para>You can select specific packages by name: + +<screen> +$ nix-env -qa firefox +firefox-34.0.5 +firefox-with-plugins-34.0.5 +</screen> + +and using regular expressions: <screen> -$ nix-env -qaf nixpkgs-<replaceable>version</replaceable> gcc -gcc-3.4.6 -gcc-4.0.3 -gcc-4.1.1</screen> +$ nix-env -qa 'firefox.*' +</screen> </para> @@ -70,12 +101,12 @@ available packages, i.e., whether they are installed into the user environment and/or present in the system: <screen> -$ nix-env -qasf nixpkgs-<replaceable>version</replaceable> '*' -... +$ nix-env -qas +… -PS bash-3.0 --S binutils-2.15 IPS bison-1.875d -...</screen> +…</screen> The first character (<literal>I</literal>) indicates whether the package is installed in your current user environment. The second @@ -88,40 +119,33 @@ just means that Nix knows that it can fetch a pre-built package from somewhere (typically a network server) instead of building it locally.</para> -<para>So now that we have a set of Nix expressions we can build the -packages contained in them. This is done using <literal>nix-env --i</literal>. For instance, +<para>You can install a package using <literal>nix-env -i</literal>. +For instance, <screen> -$ nix-env -f nixpkgs-<replaceable>version</replaceable> -i subversion</screen> +$ nix-env -i subversion</screen> will install the package called <literal>subversion</literal> (which is, of course, the <link xlink:href='http://subversion.tigris.org/'>Subversion version management system</link>).</para> -<para>When you do this for the first time, Nix will start building -Subversion and all its dependencies. This will take quite a while — -typically an hour or two on modern machines. Fortunately, there is a -faster way (so do a Ctrl-C on that install operation!): you just need -to tell Nix that pre-built binaries of all those packages are -available somewhere. This is done using the -<command>nix-pull</command> command, which must be supplied with a URL -containing a <emphasis>manifest</emphasis> describing what binaries -are available. This URL should correspond to the Nix Packages release -that you’re using. For instance, if you obtained a release from <link -xlink:href='http://nixos.org/releases/nixpkgs/nixpkgs-0.12pre11712-4lrp7j8x' -/>, then you should do: - -<screen> -$ nix-pull http://nixos.org/releases/nixpkgs/nixpkgs-0.12pre11712-4lrp7j8x/MANIFEST</screen> - -If you then issue the installation command, it should start -downloading binaries from <systemitem -class='fqdomainname'>nixos.org</systemitem>, instead of building -them from source. This might still take a while since all -dependencies must be downloaded, but on a reasonably fast connection -such as a DSL line it’s on the order of a few minutes.</para> +<note><para>When you ask Nix to install a package, it will first try +to get it in pre-compiled form from a <emphasis>binary +cache</emphasis>. By default, Nix will use the binary cache +<uri>https://cache.nixos.org</uri>; it contains binaries for most +packages in Nixpkgs. Only if no binary is available in the binary +cache, Nix will build the package from source. So if <literal>nix-env +-i subversion</literal> results in Nix building stuff from source, +then either the package is not built for your platform by the Nixpkgs +build servers, or your version of Nixpkgs is too old or too new. For +instance, if you have a very recent checkout of Nixpkgs, then the +Nixpkgs build servers may not have had a chance to build everything +and upload the resulting binaries to +<uri>https://cache.nixos.org</uri>. The Nixpkgs channel is only +updated after all binaries have been uploaded to the cache, so if you +stick to the Nixpkgs channel (rather than using a Git checkout of the +Nixpkgs tree), you will get binaries for most packages.</para></note> <para>Naturally, packages can also be uninstalled: @@ -134,7 +158,7 @@ $ nix-env -e subversion</screen> release of Nix Packages, you can do: <screen> -$ nix-env -f nixpkgs-<replaceable>version</replaceable> -u subversion</screen> +$ nix-env -u subversion</screen> This will <emphasis>only</emphasis> upgrade Subversion if there is a “newer” version in the new set of Nix expressions, as @@ -149,17 +173,17 @@ whatever version is already installed.</para> versions: <screen> -$ nix-env -f nixpkgs-<replaceable>version</replaceable> -u '*'</screen> +$ nix-env -u</screen> </para> <para>Sometimes it’s useful to be able to ask what <command>nix-env</command> would do, without actually doing it. For instance, to find out what packages would be upgraded by -<literal>nix-env -u '*'</literal>, you can do +<literal>nix-env -u</literal>, you can do <screen> -$ nix-env ... -u '*' --dry-run +$ nix-env -u --dry-run (dry run; not doing anything) upgrading `libxslt-1.1.0' to `libxslt-1.1.10' upgrading `graphviz-1.10' to `graphviz-1.12' @@ -167,4 +191,4 @@ upgrading `coreutils-5.0' to `coreutils-5.2.1'</screen> </para> -</chapter> \ No newline at end of file +</chapter> diff --git a/doc/manual/packages/binary-cache-substituter.xml b/doc/manual/packages/binary-cache-substituter.xml new file mode 100644 index 000000000000..c6ceb9c80610 --- /dev/null +++ b/doc/manual/packages/binary-cache-substituter.xml @@ -0,0 +1,70 @@ +<section xmlns="http://docbook.org/ns/docbook" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:xi="http://www.w3.org/2001/XInclude" + version="5.0" + xml:id="ssec-binary-cache-substituter"> + +<title>Serving a Nix store via HTTP</title> + +<para>You can easily share the Nix store of a machine via HTTP. This +allows other machines to fetch store paths from that machine to speed +up installations. It uses the same <emphasis>binary cache</emphasis> +mechanism that Nix usually uses to fetch pre-built binaries from +<uri>https://cache.nixos.org</uri>.</para> + +<para>The daemon that handles binary cache requests via HTTP, +<command>nix-serve</command>, is not part of the Nix distribution, but +you can install it from Nixpkgs: + +<screen> +$ nix-env -i nix-serve +</screen> + +You can then start the server, listening for HTTP connections on +whatever port you like: + +<screen> +$ nix-serve -p 8080 +</screen> + +To check whether it works, try the following on the client: + +<screen> +$ curl http://avalon:8080/nix-cache-info +</screen> + +which should print something like: + +<screen> +StoreDir: /nix/store +WantMassQuery: 1 +Priority: 30 +</screen> + +</para> + +<para>On the client side, you can tell Nix to use your binary cache +using <option>--option extra-binary-caches</option>, e.g.: + +<screen> +$ nix-env -i firefox --option extra-binary-caches http://avalon:8080/ +</screen> + +The option <option>extra-binary-caches</option> tells Nix to use this +binary cache in addition to your default caches, such as +<uri>https://cache.nixos.org</uri>. Thus, for any path in the closure +of Firefox, Nix will first check if the path is available on the +server <literal>avalon</literal> or another binary caches. If not, it +will fall back to building from source.</para> + +<para>You can also tell Nix to always use your binary cache by adding +a line to the <filename linkend="sec-conf-file">nix.conf</filename> +configuration file like this: + +<programlisting> +binary-caches = http://avalon:8080/ https://cache.nixos.org/ +</programlisting> + +</para> + +</section> diff --git a/doc/manual/packages/channels.xml b/doc/manual/packages/channels.xml index 094e11fe3b15..15c119fcb1f9 100644 --- a/doc/manual/packages/channels.xml +++ b/doc/manual/packages/channels.xml @@ -8,10 +8,9 @@ <para>If you want to stay up to date with a set of packages, it’s not very convenient to manually download the latest set of Nix expressions -for those packages, use <command>nix-pull</command> to register -pre-built binaries (if available), and upgrade using -<command>nix-env</command>. Fortunately, there’s a better way: -<emphasis>Nix channels</emphasis>.</para> +for those packages and upgrade using <command>nix-env</command>. +Fortunately, there’s a better way: <emphasis>Nix +channels</emphasis>.</para> <para>A Nix channel is just a URL that points to a place that contains a set of Nix expressions and a manifest. Using the command <link @@ -23,35 +22,36 @@ URL.</para> <command>nix-channel --add</command>, e.g., <screen> -$ nix-channel --add http://nixos.org/channels/nixpkgs-unstable</screen> +$ nix-channel --add https://nixos.org/channels/nixpkgs-unstable</screen> subscribes you to a channel that always contains that latest version -of the Nix Packages collection. (Instead of -<literal>nixpkgs-unstable</literal> you could also subscribe to -<literal>nixpkgs-stable</literal>, which should have a higher level of -stability, but right now is just outdated.) Subscribing really just -means that the URL is added to the file -<filename>~/.nix-channels</filename>. Right now there is no command -to “unsubscribe”; you should just edit that file manually -and delete the offending URL.</para> +of the Nix Packages collection. (Subscribing really just means that +the URL is added to the file <filename>~/.nix-channels</filename>, +where it is read by subsequent calls to <command>nix-channel +--update</command>.) You can “unsubscribe” using <command>nix-channel +--remove</command>: + +<screen> +$ nix-channel --remove nixpkgs +</screen> +</para> <para>To obtain the latest Nix expressions available in a channel, do <screen> $ nix-channel --update</screen> -This downloads the Nix expressions in every channel (downloaded from -<literal><replaceable>url</replaceable>/nixexprs.tar.bz2</literal>) -and registers any available pre-built binaries in every channel -(by <command>nix-pull</command>ing -<literal><replaceable>url</replaceable>/MANIFEST</literal>). It also -makes the union of each channel’s Nix expressions the default for -<command>nix-env</command> operations. Consequently, you can then say +This downloads and unpacks the Nix expressions in every channel +(downloaded from <literal><replaceable>url</replaceable>/nixexprs.tar.bz2</literal>). +It also makes the union of each channel’s Nix expressions available by +default to <command>nix-env</command> operations (via the symlink +<filename>~/.nix-defexpr/channels</filename>). Consequently, you can +then say <screen> -$ nix-env -u '*'</screen> +$ nix-env -u</screen> to upgrade all packages in your profile to the latest versions available in the subscribed channels.</para> -</chapter> \ No newline at end of file +</chapter> diff --git a/doc/manual/packages/copy-closure.xml b/doc/manual/packages/copy-closure.xml new file mode 100644 index 000000000000..012030e3eb67 --- /dev/null +++ b/doc/manual/packages/copy-closure.xml @@ -0,0 +1,50 @@ +<section xmlns="http://docbook.org/ns/docbook" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:xi="http://www.w3.org/2001/XInclude" + version="5.0" + xml:id="ssec-copy-closure"> + +<title>Copying Closures Via SSH</title> + +<para>The command <command +linkend="sec-nix-copy-closure">nix-copy-closure</command> copies a Nix +store path along with all its dependencies to or from another machine +via the SSH protocol. It doesn’t copy store paths that are already +present on the target machine. For example, the following command +copies Firefox with all its dependencies: + +<screen> +$ nix-copy-closure --to alice@itchy.example.org $(type -p firefox)</screen> + +See <xref linkend='sec-nix-copy-closure' /> for details.</para> + +<para>With <command linkend='refsec-nix-store-export'>nix-store +--export</command> and <command +linkend='refsec-nix-store-import'>nix-store --import</command> you can +write the closure of a store path (that is, the path and all its +dependencies) to a file, and then unpack that file into another Nix +store. For example, + +<screen> +$ nix-store --export $(nix-store -qR $(type -p firefox)) > firefox.closure</screen> + +writes the closure of Firefox to a file. You can then copy this file +to another machine and install the closure: + +<screen> +$ nix-store --import < firefox.closure</screen> + +Any store paths in the closure that are already present in the target +store are ignored. It is also possible to pipe the export into +another command, e.g. to copy and install a closure directly to/on +another machine: + +<screen> +$ nix-store --export $(nix-store -qR $(type -p firefox)) | bzip2 | \ + ssh alice@itchy.example.org "bunzip2 | nix-store --import"</screen> + +However, <command>nix-copy-closure</command> is generally more +efficient because it only copies paths that are not already present in +the target Nix store.</para> + +</section> diff --git a/doc/manual/packages/garbage-collection.xml b/doc/manual/packages/garbage-collection.xml index ae28c485f076..03b8e4c976c1 100644 --- a/doc/manual/packages/garbage-collection.xml +++ b/doc/manual/packages/garbage-collection.xml @@ -37,7 +37,14 @@ generations, e.g., <screen> $ nix-env --delete-generations 10 11 14</screen> -</para> +To delete all generations older than a specified number of days +(except the current generation), use the <literal>d</literal> +suffix. For example, + +<screen> +$ nix-env --delete-generations 14d</screen> + +deletes all generations older than two weeks.</para> <para>After removing appropriate old generations you can run the garbage collector as follows: @@ -67,4 +74,4 @@ is a quick and easy way to clean up your system.</para> <xi:include href="garbage-collector-roots.xml" /> -</chapter> \ No newline at end of file +</chapter> diff --git a/doc/manual/packages/profiles.xml b/doc/manual/packages/profiles.xml index ad5e92aeb64b..4d10319abe1c 100644 --- a/doc/manual/packages/profiles.xml +++ b/doc/manual/packages/profiles.xml @@ -30,7 +30,7 @@ store.</para> <figure xml:id='fig-user-environments'><title>User environments</title> <mediaobject> <imageobject> - <imagedata fileref='figures/user-environments.png' format='PNG' /> + <imagedata fileref='../figures/user-environments.png' format='PNG' /> </imageobject> </mediaobject> </figure> @@ -73,9 +73,9 @@ generated based on the current one. For instance, generation 43 was created from generation 42 when we did <screen> -$ nix-env -i subversion mozilla</screen> +$ nix-env -i subversion firefox</screen> -on a set of Nix expressions that contained Mozilla and a new version +on a set of Nix expressions that contained Firefox and a new version of Subversion.</para> <para>Generations are grouped together into @@ -120,8 +120,7 @@ can also see all available generations: <screen> $ nix-env --list-generations</screen></para> -<para>Actually, there is another level of indirection not shown in the -figure above. You generally wouldn’t have +<para>You generally wouldn’t have <filename>/nix/var/nix/profiles/<replaceable>some-profile</replaceable>/bin</filename> in your <envar>PATH</envar>. Rather, there is a symlink <filename>~/.nix-profile</filename> that points to your current @@ -156,4 +155,4 @@ $ nix-env -p /nix/var/nix/profiles/other-profile -i subversion</screen> This will <emphasis>not</emphasis> change the <command>~/.nix-profile</command> symlink.</para> -</chapter> \ No newline at end of file +</chapter> diff --git a/doc/manual/packages/sharing-packages.xml b/doc/manual/packages/sharing-packages.xml index 8fab15f7ef2d..8465c182ee72 100644 --- a/doc/manual/packages/sharing-packages.xml +++ b/doc/manual/packages/sharing-packages.xml @@ -12,46 +12,8 @@ another machine already has some or all of those packages or their dependencies. In that case there are mechanisms to quickly copy packages between machines.</para> -<para>The command <command -linkend="sec-nix-copy-closure">nix-copy-closure</command> copies a Nix -store path along with all its dependencies to or from another machine -via the SSH protocol. It doesn’t copy store paths that are already -present on the target machine. For example, the following command -copies Firefox with all its dependencies: - -<screen> -$ nix-copy-closure --to alice@itchy.example.org $(type -p firefox)</screen> - -See <xref linkend='sec-nix-copy-closure' /> for details.</para> - -<para>With <command linkend='refsec-nix-store-export'>nix-store ---export</command> and <command -linkend='refsec-nix-store-import'>nix-store --import</command> you can -write the closure of a store path (that is, the path and all its -dependencies) to a file, and then unpack that file into another Nix -store. For example, - -<screen> -$ nix-store --export $(nix-store -qR $(type -p firefox)) > firefox.closure</screen> - -writes the closure of Firefox to a file. You can then copy this file -to another machine and install the closure: - -<screen> -$ nix-store --import < firefox.closure</screen> - -Any store paths in the closure that are already present in the target -store are ignored. It is also possible to pipe the export into -another command, e.g. to copy and install a closure directly to/on -another machine: - -<screen> -$ nix-store --export $(nix-store -qR $(type -p firefox)) | bzip2 | \ - ssh alice@itchy.example.org "bunzip2 | nix-store --import"</screen> - -But note that <command>nix-copy-closure</command> is generally more -efficient in this example because it only copies paths that are not -already present in the target Nix store.</para> - +<xi:include href="binary-cache-substituter.xml" /> +<xi:include href="copy-closure.xml" /> +<xi:include href="ssh-substituter.xml" /> </chapter> diff --git a/doc/manual/packages/ssh-substituter.xml b/doc/manual/packages/ssh-substituter.xml new file mode 100644 index 000000000000..f24f354c4c39 --- /dev/null +++ b/doc/manual/packages/ssh-substituter.xml @@ -0,0 +1,73 @@ +<section xmlns="http://docbook.org/ns/docbook" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:xi="http://www.w3.org/2001/XInclude" + version="5.0" + xml:id="ssec-ssh-substituter"> + +<title>Serving a Nix store via SSH</title> + +<para>You can tell Nix to automatically fetch needed binaries from a +remote Nix store via SSH. For example, the following installs Firefox, +automatically fetching any store paths in Firefox’s closure if they +are available on the server <literal>avalon</literal>: + +<screen> +$ nix-env -i firefox --option ssh-substituter-hosts alice@avalon +</screen> + +This works similar to the binary cache substituter that Nix usually +uses, only using SSH instead of HTTP: if a store path +<literal>P</literal> is needed, Nix will first check if it’s available +in the Nix store on <literal>avalon</literal>. If not, it will fall +back to using the binary cache substituter, and then to building from +source.</para> + +<note><para>The SSH substituter currently does not allow you to enter +an SSH passphrase interactively. Therefore, you should use +<command>ssh-add</command> to load the decrypted private key into +<command>ssh-agent</command>.</para></note> + +<para>You can also copy the closure of some store path, without +installing it into your profile, e.g. + +<screen> +$ nix-store -r /nix/store/m85bxg…-firefox-34.0.5 --option ssh-substituter-hosts alice@avalon +</screen> + +This is essentially equivalent to doing + +<screen> +$ nix-copy-closure --from alice@avalon /nix/store/m85bxg…-firefox-34.0.5 +</screen> + +</para> + +<para>You can use SSH’s <emphasis>forced command</emphasis> feature to +set up a restricted user account for SSH substituter access, allowing +read-only access to the local Nix store, but nothing more. For +example, add the following lines to <filename>sshd_config</filename> +to restrict the user <literal>nix-ssh</literal>: + +<programlisting> +Match User nix-ssh + AllowAgentForwarding no + AllowTcpForwarding no + PermitTTY no + PermitTunnel no + X11Forwarding no + ForceCommand nix-store --serve +Match All +</programlisting> + +On NixOS, you can accomplish the same by adding the following to your +<filename>configuration.nix</filename>: + +<programlisting> +nix.sshServe.enable = true; +nix.sshServe.keys = [ "ssh-dss AAAAB3NzaC1k... bob@example.org" ]; +</programlisting> + +where the latter line lists the public keys of users that are allowed +to connect.</para> + +</section> diff --git a/doc/manual/release-notes/rl-1.8.xml b/doc/manual/release-notes/rl-1.8.xml index b812915a95cd..e551ee06055f 100644 --- a/doc/manual/release-notes/rl-1.8.xml +++ b/doc/manual/release-notes/rl-1.8.xml @@ -4,7 +4,7 @@ version="5.0" xml:id="ssec-relnotes-1.8"> -<title>Release 1.8 (December 9, 2014)</title> +<title>Release 1.8 (December 14, 2014)</title> <itemizedlist> @@ -113,4 +113,11 @@ $ nix-store -l $(which xterm) </itemizedlist> +<para>This release has contributions from Adam Szkoda, Aristid +Breitkreuz, Bob van der Linden, Charles Strahan, darealshinji, Eelco +Dolstra, Gergely Risko, Joel Taylor, Ludovic Courtès, Marko Durkovic, +Mikey Ariel, Paul Colomiets, Ricardo M. Correia, Ricky Elrod, Robert +Helgesson, Rob Vermaas, Russell O'Connor, Shea Levy, Shell Turner, +Sönke Hahn, Steve Purcell, Vladimír Čunát and Wout Mertens.</para> + </section> diff --git a/doc/manual/style.css b/doc/manual/style.css index f805aeab076c..53fd9d5709c3 100644 --- a/doc/manual/style.css +++ b/doc/manual/style.css @@ -23,6 +23,11 @@ h1 /* title */ font-size: 200%; } +div.part h1 +{ + font-size: 240%; +} + h2 /* chapters, appendices, subtitle */ { font-size: 180%; @@ -30,7 +35,7 @@ h2 /* chapters, appendices, subtitle */ div.part { - margin-top: 2em; + margin-top: 4em; } /* Extra space between chapters, appendices. */ @@ -61,6 +66,12 @@ div.appendix h3 margin-top: 1.5em; } +div.refentry\.separator +{ + margin-top: 2.5em; + margin-bottom: 2em; +} + div.refnamediv h2, div.refsynopsisdiv h2, div.refsection h2 /* refentry parts */ { margin-top: 1.4em; diff --git a/doc/manual/troubleshooting/links-nix-store.xml b/doc/manual/troubleshooting/links-nix-store.xml index c81477bd4f27..c768889567d0 100644 --- a/doc/manual/troubleshooting/links-nix-store.xml +++ b/doc/manual/troubleshooting/links-nix-store.xml @@ -20,7 +20,7 @@ in <filename>/nix/store</filename>, as can be seen using <command>ls -l</command>: <screen> -$ ls -l /nix/store +$ ls -ld /nix/store drwxrwxrwt 32000 nix nix 4620288 Sep 8 15:08 store</screen> The <literal>ext2</literal> file system is limited to an inode link @@ -35,9 +35,9 @@ machines).</para> the <option>--max-links</option> option.</para> <para>Real solution: put the Nix store on a file system that supports -more than 32,000 subdirectories per directory, such as ReiserFS. -(This doesn’t solve the <literal>st_nlink</literal> limit, but -ReiserFS lies to the kernel by reporting a link count of 1 if it -exceeds the limit.)</para> +more than 32,000 subdirectories per directory, such as ext4. (This +doesn’t solve the <literal>st_nlink</literal> limit, but ext4 lies to +the kernel by reporting a link count of 1 if it exceeds the +limit.)</para> </section> diff --git a/mk/functions.mk b/mk/functions.mk index 45d917399391..c48775db8c3b 100644 --- a/mk/functions.mk +++ b/mk/functions.mk @@ -10,5 +10,5 @@ filename-to-dep = $(dir $1).$(notdir $1).dep # empty string if not found. find-program = $(shell for i in $$(IFS=: ; echo $$PATH); do p=$$i/$(strip $1); if [ -e $$p ]; then echo $$p; break; fi; done) -# Remove trailing slash. +# Ensure that the given string ends in a single slash. add-trailing-slash = $(patsubst %/,%,$(1))/ diff --git a/mk/jars.mk b/mk/jars.mk index 99470f37435b..c8513e664ed5 100644 --- a/mk/jars.mk +++ b/mk/jars.mk @@ -1,4 +1,5 @@ define build-jar + $(1)_NAME ?= $(1) _d := $$(strip $$($(1)_DIR)) @@ -7,14 +8,20 @@ define build-jar $(1)_TMPDIR := $$(_d)/.$$($(1)_NAME).jar.tmp - $$($(1)_PATH): $$($(1)_SOURCES) + _jars := $$(foreach jar, $$($(1)_JARS), $$($$(jar)_PATH)) + + $$($(1)_PATH): $$($(1)_SOURCES) $$(_jars) $$($(1)_EXTRA_DEPS)| $$($(1)_ORDER_AFTER) @rm -rf $$($(1)_TMPDIR) @mkdir -p $$($(1)_TMPDIR) - $$(trace-javac) javac $(GLOBAL_JAVACFLAGS) $$($(1)_JAVACFLAGS) -d $$($(1)_TMPDIR) $$($(1)_SOURCES) - $$(trace-jar) jar cf $$($(1)_PATH) -C $$($(1)_TMPDIR) . + $$(trace-javac) javac $(GLOBAL_JAVACFLAGS) $$($(1)_JAVACFLAGS) -d $$($(1)_TMPDIR) \ + $$(foreach fn, $$($(1)_SOURCES), '$$(fn)') \ + -cp "$$(subst $$(space),,$$(foreach jar,$$($(1)_JARS),$$($$(jar)_PATH):))$$$$CLASSPATH" + @echo -e '$$(subst $$(newline),\n,$$($(1)_MANIFEST))' > $$($(1)_PATH).manifest + $$(trace-jar) jar cfm $$($(1)_PATH) $$($(1)_PATH).manifest -C $$($(1)_TMPDIR) . + @rm $$($(1)_PATH).manifest @rm -rf $$($(1)_TMPDIR) - $(1)_INSTALL_DIR ?= $$(libdir)/java + $(1)_INSTALL_DIR ?= $$(jardir) $(1)_INSTALL_PATH := $$($(1)_INSTALL_DIR)/$$($(1)_NAME).jar diff --git a/mk/lib.mk b/mk/lib.mk index 56e162d5007c..4ad5c636c8d4 100644 --- a/mk/lib.mk +++ b/mk/lib.mk @@ -14,23 +14,51 @@ dist-files := OS = $(shell uname -s) +# Hack to define a literal space. +space := +space += + + +# Hack to define a literal newline. +define newline + + +endef + + # Default installation paths. prefix ?= /usr/local libdir ?= $(prefix)/lib bindir ?= $(prefix)/bin libexecdir ?= $(prefix)/libexec datadir ?= $(prefix)/share +jardir ?= $(datadir)/java localstatedir ?= $(prefix)/var sysconfdir ?= $(prefix)/etc mandir ?= $(prefix)/share/man +# Initialise support for build directories. +builddir ?= + +ifdef builddir + buildprefix = $(builddir)/ +else + buildprefix = +endif + + # Pass -fPIC if we're building dynamic libraries. BUILD_SHARED_LIBS ?= 1 ifeq ($(BUILD_SHARED_LIBS), 1) - GLOBAL_CFLAGS += -fPIC - GLOBAL_CXXFLAGS += -fPIC + ifeq (CYGWIN,$(findstring CYGWIN,$(OS))) + GLOBAL_CFLAGS += -U__STRICT_ANSI__ + GLOBAL_CXXFLAGS += -U__STRICT_ANSI__ + else + GLOBAL_CFLAGS += -fPIC + GLOBAL_CXXFLAGS += -fPIC + endif ifneq ($(OS), Darwin) ifneq ($(OS), SunOS) GLOBAL_LDFLAGS += -Wl,--no-copy-dt-needed-entries @@ -39,7 +67,6 @@ ifeq ($(BUILD_SHARED_LIBS), 1) SET_RPATH_TO_LIBS ?= 1 endif - # Pass -g if we want debug info. BUILD_DEBUG ?= 1 diff --git a/mk/libraries.mk b/mk/libraries.mk index 3b91c699e65a..3cd7a53107bd 100644 --- a/mk/libraries.mk +++ b/mk/libraries.mk @@ -3,7 +3,11 @@ libs-list := ifeq ($(OS), Darwin) SO_EXT = dylib else - SO_EXT = so + ifeq (CYGWIN,$(findstring CYGWIN,$(OS))) + SO_EXT = dll + else + SO_EXT = so + endif endif # Build a library with symbolic name $(1). The library is defined by @@ -45,12 +49,16 @@ endif # built, otherwise a static library. define build-library $(1)_NAME ?= $(1) - _d := $$(strip $$($(1)_DIR)) + _d := $(buildprefix)$$(strip $$($(1)_DIR)) _srcs := $$(sort $$(foreach src, $$($(1)_SOURCES), $$(src))) - $(1)_OBJS := $$(addsuffix .o, $$(basename $$(_srcs))) + $(1)_OBJS := $$(addprefix $(buildprefix), $$(addsuffix .o, $$(basename $$(_srcs)))) _libs := $$(foreach lib, $$($(1)_LIBS), $$($$(lib)_PATH)) - $(1)_INSTALL_DIR ?= $$(libdir) + ifeq (CYGWIN,$(findstring CYGWIN,$(OS))) + $(1)_INSTALL_DIR ?= $$(bindir) + else + $(1)_INSTALL_DIR ?= $$(libdir) + endif $(1)_LDFLAGS_USE := $(1)_LDFLAGS_USE_INSTALLED := @@ -65,7 +73,9 @@ define build-library endif else ifneq ($(OS), Darwin) - $(1)_LDFLAGS += -Wl,-z,defs + ifneq (CYGWIN,$(findstring CYGWIN,$(OS))) + $(1)_LDFLAGS += -Wl,-z,defs + endif endif endif @@ -76,9 +86,12 @@ define build-library $(1)_PATH := $$(_d)/$$($(1)_NAME).$(SO_EXT) $$($(1)_PATH): $$($(1)_OBJS) $$(_libs) | $$(_d)/ - $$(trace-ld) $(CXX) -o $$@ -shared $$(GLOBAL_LDFLAGS) $$($(1)_OBJS) $$($(1)_LDFLAGS) $$($(1)_LDFLAGS_PROPAGATED) $$(foreach lib, $$($(1)_LIBS), $$($$(lib)_LDFLAGS_USE)) + $$(trace-ld) $(CXX) -o $$(abspath $$@) -shared $$(GLOBAL_LDFLAGS) $$($(1)_OBJS) $$($(1)_LDFLAGS) $$($(1)_LDFLAGS_PROPAGATED) $$(foreach lib, $$($(1)_LIBS), $$($$(lib)_LDFLAGS_USE)) $$($(1)_LDFLAGS_UNINSTALLED) - $(1)_LDFLAGS_USE += -L$$(_d) -Wl,-rpath,$$(abspath $$(_d)) -l$$(patsubst lib%,%,$$(strip $$($(1)_NAME))) + ifneq ($(OS), Darwin) + $(1)_LDFLAGS_USE += -Wl,-rpath,$$(abspath $$(_d)) + endif + $(1)_LDFLAGS_USE += -L$$(_d) -l$$(patsubst lib%,%,$$(strip $$($(1)_NAME))) $(1)_INSTALL_PATH := $(DESTDIR)$$($(1)_INSTALL_DIR)/$$($(1)_NAME).$(SO_EXT) @@ -90,10 +103,12 @@ define build-library $$(trace-ld) $(CXX) -o $$@ -shared $$(GLOBAL_LDFLAGS) $$($(1)_OBJS) $$($(1)_LDFLAGS) $$($(1)_LDFLAGS_PROPAGATED) $$(foreach lib, $$($(1)_LIBS), $$($$(lib)_LDFLAGS_USE_INSTALLED)) $(1)_LDFLAGS_USE_INSTALLED += -L$$(DESTDIR)$$($(1)_INSTALL_DIR) -l$$(patsubst lib%,%,$$(strip $$($(1)_NAME))) - ifeq ($(SET_RPATH_TO_LIBS), 1) - $(1)_LDFLAGS_USE_INSTALLED += -Wl,-rpath,$$($(1)_INSTALL_DIR) - else - $(1)_LDFLAGS_USE_INSTALLED += -Wl,-rpath-link,$$($(1)_INSTALL_DIR) + ifneq ($(OS), Darwin) + ifeq ($(SET_RPATH_TO_LIBS), 1) + $(1)_LDFLAGS_USE_INSTALLED += -Wl,-rpath,$$($(1)_INSTALL_DIR) + else + $(1)_LDFLAGS_USE_INSTALLED += -Wl,-rpath-link,$$($(1)_INSTALL_DIR) + endif endif ifdef $(1)_FORCE_INSTALL diff --git a/mk/patterns.mk b/mk/patterns.mk index 6b2cfd017050..3219d9629fbf 100644 --- a/mk/patterns.mk +++ b/mk/patterns.mk @@ -1,8 +1,11 @@ -%.o: %.cc +$(buildprefix)%.o: %.cc + @mkdir -p "$(dir $@)" $(trace-cxx) $(CXX) -o $@ -c $< $(GLOBAL_CXXFLAGS) $(GLOBAL_CXXFLAGS_PCH) $(CXXFLAGS) $($@_CXXFLAGS) -MMD -MF $(call filename-to-dep, $@) -MP -%.o: %.cpp +$(buildprefix)%.o: %.cpp + @mkdir -p "$(dir $@)" $(trace-cxx) $(CXX) -o $@ -c $< $(GLOBAL_CXXFLAGS) $(GLOBAL_CXXFLAGS_PCH) $(CXXFLAGS) $($@_CXXFLAGS) -MMD -MF $(call filename-to-dep, $@) -MP -%.o: %.c +$(buildprefix)%.o: %.c + @mkdir -p "$(dir $@)" $(trace-cc) $(CC) -o $@ -c $< $(GLOBAL_CFLAGS) $(CFLAGS) $($@_CFLAGS) -MMD -MF $(call filename-to-dep, $@) -MP diff --git a/mk/programs.mk b/mk/programs.mk index 72afdf95251b..3ac64494e3a5 100644 --- a/mk/programs.mk +++ b/mk/programs.mk @@ -23,9 +23,9 @@ programs-list := # - $(1)_INSTALL_DIR: the directory where the program will be # installed; defaults to $(bindir). define build-program - _d := $$($(1)_DIR) + _d := $(buildprefix)$$($(1)_DIR) _srcs := $$(sort $$(foreach src, $$($(1)_SOURCES), $$(src))) - $(1)_OBJS := $$(addsuffix .o, $$(basename $$(_srcs))) + $(1)_OBJS := $$(addprefix $(buildprefix), $$(addsuffix .o, $$(basename $$(_srcs)))) _libs := $$(foreach lib, $$($(1)_LIBS), $$($$(lib)_PATH)) $(1)_PATH := $$(_d)/$(1) diff --git a/mk/templates.mk b/mk/templates.mk index ab99168bb7a5..c7ac7afbff27 100644 --- a/mk/templates.mk +++ b/mk/templates.mk @@ -8,8 +8,12 @@ define instantiate-template endef +ifneq ($(MAKECMDGOALS), clean) + %.h: %.h.in $(trace-gen) rm -f $@ && ./config.status --quiet --header=$@ %: %.in $(trace-gen) rm -f $@ && ./config.status --quiet --file=$@ + +endif diff --git a/perl/lib/Nix/Config.pm.in b/perl/lib/Nix/Config.pm.in index bc51310e5aff..388acd2e61c0 100644 --- a/perl/lib/Nix/Config.pm.in +++ b/perl/lib/Nix/Config.pm.in @@ -1,5 +1,7 @@ package Nix::Config; +use MIME::Base64; + $version = "@PACKAGE_VERSION@"; $binDir = $ENV{"NIX_BIN_DIR"} || "@bindir@"; @@ -19,24 +21,31 @@ $useBindings = "@perlbindings@" eq "yes"; %config = (); +%binaryCachePublicKeys = (); + sub readConfig { if (defined $ENV{'_NIX_OPTIONS'}) { foreach my $s (split '\n', $ENV{'_NIX_OPTIONS'}) { my ($n, $v) = split '=', $s, 2; $config{$n} = $v; } - return; + } 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; + foreach my $s (split(/ /, $config{"binary-cache-public-keys"} // "")) { + my ($keyName, $publicKey) = split ":", $s; + next unless defined $keyName && defined $publicKey; + $binaryCachePublicKeys{$keyName} = decode_base64($publicKey); } - close CONFIG; } return 1; diff --git a/perl/lib/Nix/Crypto.pm b/perl/lib/Nix/Crypto.pm deleted file mode 100644 index 0286e88d3d28..000000000000 --- a/perl/lib/Nix/Crypto.pm +++ /dev/null @@ -1,42 +0,0 @@ -package Nix::Crypto; - -use strict; -use MIME::Base64; -use Nix::Store; -use Nix::Config; -use IPC::Open2; - -our @ISA = qw(Exporter); -our @EXPORT = qw(signString isValidSignature); - -sub signString { - my ($privateKeyFile, $s) = @_; - my $hash = hashString("sha256", 0, $s); - my ($from, $to); - my $pid = open2($from, $to, $Nix::Config::openssl, "rsautl", "-sign", "-inkey", $privateKeyFile); - print $to $hash; - close $to; - local $/ = undef; - my $sig = <$from>; - close $from; - waitpid($pid, 0); - die "$0: OpenSSL returned exit code $? while signing hash\n" if $? != 0; - my $sig64 = encode_base64($sig, ""); - return $sig64; -} - -sub isValidSignature { - my ($publicKeyFile, $sig64, $s) = @_; - my ($from, $to); - my $pid = open2($from, $to, $Nix::Config::openssl, "rsautl", "-verify", "-inkey", $publicKeyFile, "-pubin"); - print $to decode_base64($sig64); - close $to; - my $decoded = <$from>; - close $from; - waitpid($pid, 0); - return 0 if $? != 0; - my $hash = hashString("sha256", 0, $s); - return $decoded eq $hash; -} - -1; diff --git a/perl/lib/Nix/Manifest.pm b/perl/lib/Nix/Manifest.pm index 9b7e89fa42fb..93c9c261ddc9 100644 --- a/perl/lib/Nix/Manifest.pm +++ b/perl/lib/Nix/Manifest.pm @@ -8,11 +8,12 @@ use Cwd; use File::stat; use File::Path; use Fcntl ':flock'; +use MIME::Base64; use Nix::Config; -use Nix::Crypto; +use Nix::Store; our @ISA = qw(Exporter); -our @EXPORT = qw(readManifest writeManifest updateManifestDB addPatch deleteOldManifests parseNARInfo); +our @EXPORT = qw(readManifest writeManifest updateManifestDB addPatch deleteOldManifests parseNARInfo fingerprintPath); sub addNAR { @@ -376,7 +377,6 @@ EOF } - # Delete all old manifests downloaded from a given URL. sub deleteOldManifests { my ($url, $curUrlFile) = @_; @@ -394,12 +394,26 @@ sub deleteOldManifests { } +# Return a fingerprint of a store path to be used in binary cache +# signatures. It contains the store path, the SHA-256 hash of the +# contents of the path, and the references. +sub fingerprintPath { + my ($storePath, $narHash, $narSize, $references) = @_; + die if substr($storePath, 0, length($Nix::Config::storeDir)) ne $Nix::Config::storeDir; + die if substr($narHash, 0, 7) ne "sha256:"; + die if length($narHash) != 59; + foreach my $ref (@{$references}) { + die if substr($ref, 0, length($Nix::Config::storeDir)) ne $Nix::Config::storeDir; + } + return "1;" . $storePath . ";" . $narHash . ";" . $narSize . ";" . join(",", @{$references}); +} + + # Parse a NAR info file. sub parseNARInfo { my ($storePath, $content, $requireValidSig, $location) = @_; my ($storePath2, $url, $fileHash, $fileSize, $narHash, $narSize, $deriver, $system, $sig); - my $signedData = ""; my $compression = "bzip2"; my @refs; @@ -415,8 +429,7 @@ sub parseNARInfo { elsif ($1 eq "References") { @refs = split / /, $2; } elsif ($1 eq "Deriver") { $deriver = $2; } elsif ($1 eq "System") { $system = $2; } - elsif ($1 eq "Signature") { $sig = $2; last; } - $signedData .= "$line\n"; + elsif ($1 eq "Sig") { $sig = $2; } } return undef if $storePath ne $storePath2 || !defined $url || !defined $narHash; @@ -434,30 +447,30 @@ sub parseNARInfo { }; if ($requireValidSig) { + # FIXME: might be useful to support multiple signatures per .narinfo. + if (!defined $sig) { warn "NAR info file ‘$location’ lacks a signature; ignoring\n"; return undef; } - my ($sigVersion, $keyName, $sig64) = split ";", $sig; - $sigVersion //= 0; - if ($sigVersion != 1) { - warn "NAR info file ‘$location’ has unsupported version $sigVersion; ignoring\n"; - return undef; - } + my ($keyName, $sig64) = split ":", $sig; return undef unless defined $keyName && defined $sig64; - my $publicKeyFile = $Nix::Config::config{"binary-cache-public-key-$keyName"}; - if (!defined $publicKeyFile) { + + my $publicKey = $Nix::Config::binaryCachePublicKeys{$keyName}; + if (!defined $publicKey) { warn "NAR info file ‘$location’ is signed by unknown key ‘$keyName’; ignoring\n"; return undef; } - if (! -f $publicKeyFile) { - die "binary cache public key file ‘$publicKeyFile’ does not exist\n"; - return undef; - } - if (!isValidSignature($publicKeyFile, $sig64, $signedData)) { - warn "NAR info file ‘$location’ has an invalid signature; ignoring\n"; + + my $fingerprint = fingerprintPath( + $storePath, $narHash, $narSize, + [ map { "$Nix::Config::storeDir/$_" } @refs ]); + + if (!checkSignature($publicKey, decode_base64($sig64), $fingerprint)) { + warn "NAR info file ‘$location’ has an incorrect signature; ignoring\n"; return undef; } + $res->{signedBy} = $keyName; } diff --git a/perl/lib/Nix/Store.pm b/perl/lib/Nix/Store.pm index 89cfaefa5fd4..233a432ee085 100644 --- a/perl/lib/Nix/Store.pm +++ b/perl/lib/Nix/Store.pm @@ -17,6 +17,7 @@ our @EXPORT = qw( queryPathFromHashPart topoSortPaths computeFSClosure followLinksToStorePath exportPaths importPaths hashPath hashFile hashString + signString checkSignature addToStore makeFixedOutputPath derivationFromPath ); diff --git a/perl/lib/Nix/Store.xs b/perl/lib/Nix/Store.xs index ff90616d3766..4c550cdb752c 100644 --- a/perl/lib/Nix/Store.xs +++ b/perl/lib/Nix/Store.xs @@ -11,6 +11,10 @@ #include <misc.hh> #include <util.hh> +#if HAVE_SODIUM +#include <sodium.h> +#endif + using namespace nix; @@ -223,6 +227,52 @@ SV * hashString(char * algo, int base32, char * s) } +SV * signString(SV * secretKey_, char * msg) + PPCODE: + try { +#if HAVE_SODIUM + STRLEN secretKeyLen; + unsigned char * secretKey = (unsigned char *) SvPV(secretKey_, secretKeyLen); + if (secretKeyLen != crypto_sign_SECRETKEYBYTES) + throw Error("secret key is not valid"); + + unsigned char sig[crypto_sign_BYTES]; + unsigned long long sigLen; + crypto_sign_detached(sig, &sigLen, (unsigned char *) msg, strlen(msg), secretKey); + XPUSHs(sv_2mortal(newSVpv((char *) sig, sigLen))); +#else + throw Error("Nix was not compiled with libsodium, required for signed binary cache support"); +#endif + } catch (Error & e) { + croak(e.what()); + } + + +int checkSignature(SV * publicKey_, SV * sig_, char * msg) + CODE: + try { +#if HAVE_SODIUM + STRLEN publicKeyLen; + unsigned char * publicKey = (unsigned char *) SvPV(publicKey_, publicKeyLen); + if (publicKeyLen != crypto_sign_PUBLICKEYBYTES) + throw Error("public key is not valid"); + + STRLEN sigLen; + unsigned char * sig = (unsigned char *) SvPV(sig_, sigLen); + if (sigLen != crypto_sign_BYTES) + throw Error("signature is not valid"); + + RETVAL = crypto_sign_verify_detached(sig, (unsigned char *) msg, strlen(msg), publicKey) == 0; +#else + throw Error("Nix was not compiled with libsodium, required for signed binary cache support"); +#endif + } catch (Error & e) { + croak(e.what()); + } + OUTPUT: + RETVAL + + SV * addToStore(char * srcPath, int recursive, char * algo) PPCODE: try { diff --git a/perl/local.mk b/perl/local.mk index 8ff60e9ce3c7..132676f53341 100644 --- a/perl/local.mk +++ b/perl/local.mk @@ -5,8 +5,7 @@ nix_perl_sources := \ $(d)/lib/Nix/SSH.pm \ $(d)/lib/Nix/CopyClosure.pm \ $(d)/lib/Nix/Config.pm.in \ - $(d)/lib/Nix/Utils.pm \ - $(d)/lib/Nix/Crypto.pm + $(d)/lib/Nix/Utils.pm nix_perl_modules := $(nix_perl_sources:.in=) @@ -23,12 +22,20 @@ ifeq ($(perlbindings), yes) Store_SOURCES := $(Store_DIR)/Store.cc - Store_LIBS = libstore libutil - Store_CXXFLAGS = \ -I$(shell $(perl) -e 'use Config; print $$Config{archlibexp};')/CORE \ -D_FILE_OFFSET_BITS=64 -Wno-unused-variable -Wno-literal-suffix -Wno-reserved-user-defined-literal + Store_LIBS = libstore libutil + + Store_LDFLAGS := $(SODIUM_LIBS) + + ifeq (CYGWIN,$(findstring CYGWIN,$(OS))) + archlib = $(shell perl -E 'use Config; print $$Config{archlib};') + libperl = $(shell perl -E 'use Config; print $$Config{libperl};') + Store_LDFLAGS += $(shell find ${archlib} -name ${libperl}) + endif + Store_ALLOW_UNDEFINED = 1 Store_FORCE_INSTALL = 1 diff --git a/release.nix b/release.nix index a08cf7a96c81..c5f700d8e776 100644 --- a/release.nix +++ b/release.nix @@ -24,7 +24,7 @@ let buildInputs = [ curl bison flex perl libxml2 libxslt bzip2 - tetex dblatex nukeReferences pkgconfig sqlite + tetex dblatex nukeReferences pkgconfig sqlite libsodium docbook5 docbook5_xsl ] ++ lib.optional (!lib.inNixShell) git; @@ -80,7 +80,9 @@ let name = "nix"; src = tarball; - buildInputs = [ curl perl bzip2 openssl pkgconfig sqlite boehmgc ]; + buildInputs = + [ curl perl bzip2 openssl pkgconfig sqlite boehmgc ] + ++ lib.optional stdenv.isLinux libsodium; configureFlags = '' --disable-init-state @@ -181,8 +183,14 @@ let }; - rpm_fedora20i386 = makeRPM_i686 (diskImageFuns: diskImageFuns.fedora20i386); - rpm_fedora20x86_64 = makeRPM_x86_64 (diskImageFunsFun: diskImageFunsFun.fedora20x86_64); + rpm_fedora18i386 = makeRPM_i686 (diskImageFuns: diskImageFuns.fedora18i386) []; + rpm_fedora18x86_64 = makeRPM_x86_64 (diskImageFunsFun: diskImageFunsFun.fedora18x86_64) []; + rpm_fedora19i386 = makeRPM_i686 (diskImageFuns: diskImageFuns.fedora19i386) []; + rpm_fedora19x86_64 = makeRPM_x86_64 (diskImageFunsFun: diskImageFunsFun.fedora19x86_64) []; + rpm_fedora20i386 = makeRPM_i686 (diskImageFuns: diskImageFuns.fedora20i386) []; + rpm_fedora20x86_64 = makeRPM_x86_64 (diskImageFunsFun: diskImageFunsFun.fedora20x86_64) []; + rpm_fedora21i386 = makeRPM_i686 (diskImageFuns: diskImageFuns.fedora21i386) [ "libsodium-devel" ]; + rpm_fedora21x86_64 = makeRPM_x86_64 (diskImageFunsFun: diskImageFunsFun.fedora21x86_64) [ "libsodium-devel" ]; deb_debian7i386 = makeDeb_i686 (diskImageFuns: diskImageFuns.debian7i386); @@ -264,7 +272,7 @@ let makeRPM_x86_64 = makeRPM "x86_64-linux"; makeRPM = - system: diskImageFun: + system: diskImageFun: extraPackages: with import <nixpkgs> { inherit system; }; @@ -272,7 +280,9 @@ let name = "nix-rpm"; src = jobs.tarball; diskImage = (diskImageFun vmTools.diskImageFuns) - { extraPackages = [ "perl-DBD-SQLite" "perl-devel" "sqlite" "sqlite-devel" "bzip2-devel" "emacs" "perl-WWW-Curl" ]; }; + { extraPackages = + [ "perl-DBD-SQLite" "perl-devel" "sqlite" "sqlite-devel" "bzip2-devel" "emacs" "perl-WWW-Curl" ] + ++ extraPackages; }; memSize = 1024; meta.schedulingPriority = 50; postRPMInstall = "cd /tmp/rpmout/BUILD/nix-* && make installcheck"; diff --git a/scripts/copy-from-other-stores.pl.in b/scripts/copy-from-other-stores.pl.in index cf36bae9e803..8ce5a9d4049f 100755 --- a/scripts/copy-from-other-stores.pl.in +++ b/scripts/copy-from-other-stores.pl.in @@ -9,6 +9,7 @@ my $binDir = $ENV{"NIX_BIN_DIR"} || "@bindir@"; STDOUT->autoflush(1); +binmode STDERR, ":encoding(utf8)"; my @remoteStoresAll = split ':', ($ENV{"NIX_OTHER_STORES"} or ""); diff --git a/scripts/download-from-binary-cache.pl.in b/scripts/download-from-binary-cache.pl.in index 6285a0503682..bb63eafca522 100644 --- a/scripts/download-from-binary-cache.pl.in +++ b/scripts/download-from-binary-cache.pl.in @@ -47,11 +47,17 @@ $caBundle = "/etc/ssl/certs/ca-certificates.crt" if !$caBundle && -f "/etc/ssl/c my $userName = getpwuid($<) || $ENV{"USER"} or die "cannot figure out user name"; +my $userAgent = "Nix/$Nix::Config::version"; + sub isTrue { my ($x) = @_; return $x eq "true" || $x eq "1"; } +# FIXME: this should be cache URLs required to have valid signatures, +# or "*" to require signatures on all binary caches. +# FIXME: should binary caches using a key in +# ‘binary-cache-public-keys’ be trusted by default? my $requireSignedBinaryCaches = ($Nix::Config::config{"signed-binary-caches"} // "0") ne "0"; my $curlConnectTimeout = int( @@ -75,7 +81,7 @@ sub addRequest { $curl->setopt(CURLOPT_FOLLOWLOCATION, 1); $curl->setopt(CURLOPT_CAINFO, $caBundle) if defined $caBundle; $curl->setopt(CURLOPT_SSL_VERIFYPEER, 0) unless isTrue($Nix::Config::config{"verify-https-binary-caches"} // "1"); - $curl->setopt(CURLOPT_USERAGENT, "Nix/$Nix::Config::version"); + $curl->setopt(CURLOPT_USERAGENT, $userAgent); $curl->setopt(CURLOPT_NOBODY, 1) if $head; $curl->setopt(CURLOPT_FAILONERROR, 1); $curl->setopt(CURLOPT_CONNECTTIMEOUT, $curlConnectTimeout); @@ -555,7 +561,7 @@ sub downloadBinary { die if $requireSignedBinaryCaches && !defined $info->{signedBy}; print STDERR "\n*** Downloading ‘$url’ ", ($requireSignedBinaryCaches ? "(signed by ‘$info->{signedBy}’) " : ""), "to ‘$storePath’...\n"; checkURL $url; - if (system("$Nix::Config::curl --fail --location --insecure --connect-timeout $curlConnectTimeout '$url' $decompressor | $Nix::Config::binDir/nix-store --restore $destPath") != 0) { + if (system("$Nix::Config::curl --fail --location --insecure --connect-timeout $curlConnectTimeout -A '$userAgent' '$url' $decompressor | $Nix::Config::binDir/nix-store --restore $destPath") != 0) { warn "download of ‘$url’ failed" . ($! ? ": $!" : "") . "\n"; next; } diff --git a/scripts/nix-build.in b/scripts/nix-build.in index 9127d90b24dd..19de6feb6080 100755 --- a/scripts/nix-build.in +++ b/scripts/nix-build.in @@ -5,6 +5,8 @@ use strict; use Nix::Config; use Nix::Store; use Nix::Utils; +use File::Basename; +use Cwd; binmode STDERR, ":encoding(utf8)"; @@ -14,6 +16,7 @@ my $runEnv = $0 =~ /nix-shell$/; my $pure = 0; my $fromArgs = 0; my $packages = 0; +my $interactive = 1; my @instArgs = (); my @buildArgs = (); @@ -25,6 +28,9 @@ my @envExclude = (); my $myName = $runEnv ? "nix-shell" : "nix-build"; +my $inShebang = 0; +my $script; +my @savedArgs; my $tmpDir = mkTempDir($myName); @@ -35,6 +41,30 @@ my $drvLink = "$tmpDir/derivation"; $SIG{'INT'} = sub { exit 1 }; +# Heuristic to see if we're invoked as a shebang script, namely, if we +# have a single argument, it's the name of an executable file, and it +# starts with "#!". +if ($runEnv && $ARGV[0] !~ /nix-shell/) { + $script = $ARGV[0]; + if (-f $script && -x $script) { + open SCRIPT, "<$script" or die "$0: cannot open ‘$script’: $!\n"; + my $first = <SCRIPT>; + if ($first =~ /^\#\!/) { + $inShebang = 1; + @savedArgs = @ARGV; shift @savedArgs; + @ARGV = (); + while (<SCRIPT>) { + chomp; + if (/^\#\!\s*nix-shell (.*)$/) { + @ARGV = split / /, $1; + } + } + } + close SCRIPT; + } +} + + for (my $n = 0; $n < scalar @ARGV; $n++) { my $arg = $ARGV[$n]; @@ -131,10 +161,11 @@ for (my $n = 0; $n < scalar @ARGV; $n++) { $runEnv = 1; } - elsif ($arg eq "--command") { + elsif ($arg eq "--command" || $arg eq "--run") { $n++; die "$0: ‘$arg’ requires an argument\n" unless $n < scalar @ARGV; - $envCommand = "$ARGV[$n]\nexit $!"; + $envCommand = "$ARGV[$n]\nexit"; + $interactive = 0 if $arg eq "--run"; } elsif ($arg eq "--exclude") { @@ -155,10 +186,32 @@ for (my $n = 0; $n < scalar @ARGV; $n++) { $packages = 1; } + elsif ($inShebang && $arg eq "-i") { + $n++; + die "$0: ‘$arg’ requires an argument\n" unless $n < scalar @ARGV; + my $interpreter = $ARGV[$n]; + # Überhack to support Perl. Perl examines the shebang and + # executes it unless it contains the string "perl" or "indir", + # or (undocumented) argv[0] does not contain "perl". Exploit + # the latter by doing "exec -a". + my $execArgs = $interpreter =~ /perl/ ? "-a PERL" : ""; + sub shellEscape { + my $s = $_; + $s =~ s/'/'\\''/g; + return "'" . $s . "'"; + } + $envCommand = "exec $execArgs $interpreter $script ${\(join ' ', (map shellEscape, @savedArgs))}"; + } + elsif (substr($arg, 0, 1) eq "-") { push @buildArgs, $arg; } + elsif ($arg eq "-Q" || $arg eq "--no-build-output") { + push @buildArgs, $arg; + push @instArgs, $arg; + } + else { push @exprs, $arg; } @@ -182,6 +235,11 @@ foreach my $expr (@exprs) { # Instantiate. my @drvPaths; if ($expr !~ /^\/.*\.drv$/) { + # If we're in a #! script, interpret filenames relative to the + # script. + $expr = dirname(Cwd::abs_path($script)) . "/" . $expr + if $inShebang && !$packages && $expr !~ /^\//; + # !!! would prefer the perl 5.8.0 pipe open feature here. my $pid = open(DRVPATHS, "-|") || exec "$Nix::Config::binDir/nix-instantiate", "--add-root", $drvLink, "--indirect", @instArgs, $expr; while (<DRVPATHS>) {chomp; push @drvPaths, $_;} @@ -232,17 +290,20 @@ foreach my $expr (@exprs) { ($pure ? '' : 'p=$PATH; ' ) . 'dontAddDisableDepTrack=1; ' . '[ -e $stdenv/setup ] && source $stdenv/setup; ' . - 'if [ "$(type -t runHook)" = function ]; then runHook shellHook; fi; ' . ($pure ? '' : 'PATH=$PATH:$p; unset p; ') . 'set +e; ' . '[ -n "$PS1" ] && PS1="\n\[\033[1;32m\][nix-shell:\w]$\[\033[0m\] "; ' . + 'if [ "$(type -t runHook)" = function ]; then runHook shellHook; fi; ' . 'unset NIX_ENFORCE_PURITY; ' . 'unset NIX_INDENT_MAKE; ' . 'shopt -u nullglob; ' . 'unset TZ; ' . (defined $ENV{'TZ'} ? "export TZ='${ENV{'TZ'}}'; " : '') . $envCommand); $ENV{BASH_ENV} = $rcfile; - exec($ENV{NIX_BUILD_SHELL} // "bash", "--rcfile", $rcfile); + my @args = ($ENV{NIX_BUILD_SHELL} // "bash"); + push @args, "--rcfile" if $interactive; + push @args, $rcfile; + exec @args; die; } diff --git a/scripts/nix-install-package.in b/scripts/nix-install-package.in index c109652d1fca..b442c708b1a2 100755 --- a/scripts/nix-install-package.in +++ b/scripts/nix-install-package.in @@ -71,7 +71,7 @@ sub barf { my $pkgFile = $source; if ($fromURL) { $pkgFile = "$tmpDir/tmp.nixpkg"; - system("@curl@", "--silent", $source, "-o", $pkgFile) == 0 + system("@curl@", "-L", "--silent", $source, "-o", $pkgFile) == 0 or barf "curl failed: $?"; } diff --git a/scripts/nix-push.in b/scripts/nix-push.in index c6d187704bc7..d5d3bc1e7e79 100755 --- a/scripts/nix-push.in +++ b/scripts/nix-push.in @@ -6,11 +6,11 @@ use File::Basename; use File::Path qw(mkpath); use File::stat; use File::Copy; +use MIME::Base64; use Nix::Config; use Nix::Store; use Nix::Manifest; use Nix::Utils; -use Nix::Crypto; binmode STDERR, ":encoding(utf8)"; @@ -27,8 +27,7 @@ my $writeManifest = 0; my $manifestPath; my $archivesURL; my $link = 0; -my $privateKeyFile; -my $keyName; +my $secretKeyFile; my @roots; for (my $n = 0; $n < scalar @ARGV; $n++) { @@ -61,14 +60,10 @@ for (my $n = 0; $n < scalar @ARGV; $n++) { $archivesURL = $ARGV[$n]; } elsif ($arg eq "--link") { $link = 1; - } elsif ($arg eq "--key") { + } elsif ($arg eq "--key-file") { $n++; die "$0: ‘$arg’ requires an argument\n" unless $n < scalar @ARGV; - $privateKeyFile = $ARGV[$n]; - } elsif ($arg eq "--key-name") { - $n++; - die "$0: ‘$arg’ requires an argument\n" unless $n < scalar @ARGV; - $keyName = $ARGV[$n]; + $secretKeyFile = $ARGV[$n]; } elsif (substr($arg, 0, 1) eq "-") { die "$0: unknown flag ‘$arg’\n"; } else { @@ -110,7 +105,7 @@ my %narFiles; foreach my $storePath (@storePaths) { my $pathHash = substr(basename($storePath), 0, 32); my $narInfoFile = "$destDir/$pathHash.narinfo"; - if (-e $narInfoFile) { + if (!$force && -e $narInfoFile) { my $narInfo = parseNARInfo($storePath, readFile($narInfoFile), 0, $narInfoFile) or die "cannot read ‘$narInfoFile’\n"; my $narFile = "$destDir/$narInfo->{url}"; if (-e $narFile) { @@ -257,9 +252,14 @@ for (my $n = 0; $n < scalar @storePaths2; $n++) { } } - if (defined $privateKeyFile && defined $keyName) { - my $sig = signString($privateKeyFile, $info); - $info .= "Signature: 1;$keyName;$sig\n"; + if (defined $secretKeyFile) { + my $s = readFile $secretKeyFile; + chomp $s; + my ($keyName, $secretKey) = split ":", $s; + die "invalid secret key file ‘$secretKeyFile’\n" unless defined $keyName && defined $secretKey; + my $fingerprint = fingerprintPath($storePath, $narHash, $narSize, $refs); + my $sig = encode_base64(signString(decode_base64($secretKey), $fingerprint), ""); + $info .= "Sig: $keyName:$sig\n"; } my $pathHash = substr(basename($storePath), 0, 32); diff --git a/src/download-via-ssh/download-via-ssh.cc b/src/download-via-ssh/download-via-ssh.cc index b64455eb1724..f71cf56507b8 100644 --- a/src/download-via-ssh/download-via-ssh.cc +++ b/src/download-via-ssh/download-via-ssh.cc @@ -9,6 +9,7 @@ #include "store-api.hh" #include <iostream> +#include <cstdlib> #include <unistd.h> using namespace nix; diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 298f6a3a60e3..95b56e84d89a 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -1174,7 +1174,8 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v) else if (firstType == tPath) { if (!context.empty()) throwEvalError("a string that refers to a store path cannot be appended to a path, at %1%", pos); - mkPath(v, s.str().c_str()); + auto path = canonPath(s.str()); + mkPath(v, path.c_str()); } else mkString(v, s.str(), context); } diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index 78942927fd24..f7415fb78dfd 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -39,10 +39,10 @@ public: typedef uint32_t size_t; private: - size_t size_, capacity; + size_t size_, capacity_; Attr attrs[0]; - Bindings(uint32_t capacity) : size_(0), capacity(capacity) { } + Bindings(size_t capacity) : size_(0), capacity_(capacity) { } Bindings(const Bindings & bindings) = delete; public: @@ -54,7 +54,7 @@ public: void push_back(const Attr & attr) { - assert(size_ < capacity); + assert(size_ < capacity_); attrs[size_++] = attr; } @@ -76,6 +76,8 @@ public: void sort(); + size_t capacity() { return capacity_; } + friend class EvalState; }; @@ -169,7 +171,7 @@ public: /* Look up a file in the search path. */ Path findFile(const string & path); - Path findFile(SearchPath & searchPath, const string & path); + Path findFile(SearchPath & searchPath, const string & path, const Pos & pos = noPos); /* Evaluate an expression to normal form, storing the result in value `v'. */ diff --git a/src/libexpr/lexer.l b/src/libexpr/lexer.l index 82520ee7a59a..7051909008d1 100644 --- a/src/libexpr/lexer.l +++ b/src/libexpr/lexer.l @@ -80,6 +80,7 @@ static Expr * unescapeStr(SymbolTable & symbols, const char * s) ID [a-zA-Z\_][a-zA-Z0-9\_\'\-]* INT [0-9]+ PATH [a-zA-Z0-9\.\_\-\+]*(\/[a-zA-Z0-9\.\_\-\+]+)+ +HPATH \~(\/[a-zA-Z0-9\.\_\-\+]+)+ SPATH \<[a-zA-Z0-9\.\_\-\+]+(\/[a-zA-Z0-9\.\_\-\+]+)*\> URI [a-zA-Z][a-zA-Z0-9\+\-\.]*\:[a-zA-Z0-9\%\/\?\:\@\&\=\+\$\,\-\_\.\!\~\*\']+ @@ -159,6 +160,7 @@ or { return OR_KW; } <IND_STRING>. return yytext[0]; /* just in case: shouldn't be reached */ {PATH} { yylval->path = strdup(yytext); return PATH; } +{HPATH} { yylval->path = strdup(yytext); return HPATH; } {SPATH} { yylval->path = strdup(yytext); return SPATH; } {URI} { yylval->uri = strdup(yytext); return URI; } diff --git a/src/libexpr/nixexpr.cc b/src/libexpr/nixexpr.cc index 6945e4daed80..50997e096fd1 100644 --- a/src/libexpr/nixexpr.cc +++ b/src/libexpr/nixexpr.cc @@ -191,7 +191,7 @@ std::ostream & operator << (std::ostream & str, const Pos & pos) if (!pos) str << "undefined position"; else - str << (format(ANSI_BOLD "%1%" ANSI_NORMAL ":%2%:%3%") % pos.file % pos.line % pos.column).str(); + str << (format(ANSI_BOLD "%1%" ANSI_NORMAL ":%2%:%3%") % (string) pos.file % pos.line % pos.column).str(); return str; } diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y index dcb270b862a3..d70d29be8ba7 100644 --- a/src/libexpr/parser.y +++ b/src/libexpr/parser.y @@ -53,10 +53,6 @@ namespace nix { #include "parser-tab.hh" #include "lexer-tab.hh" -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - YY_DECL; using namespace nix; @@ -272,7 +268,7 @@ void yyerror(YYLTYPE * loc, yyscan_t scanner, ParseData * data, const char * err %token <id> ID ATTRPATH %token <e> STR IND_STR %token <n> INT -%token <path> PATH SPATH +%token <path> PATH HPATH SPATH %token <uri> URI %token IF THEN ELSE ASSERT WITH LET IN REC INHERIT EQ NEQ AND OR IMPL OR_KW %token DOLLAR_CURLY /* == ${ */ @@ -290,7 +286,6 @@ void yyerror(YYLTYPE * loc, yyscan_t scanner, ParseData * data, const char * err %left '*' '/' %right CONCAT %nonassoc '?' -%nonassoc '~' %nonassoc NEGATE %% @@ -380,6 +375,7 @@ expr_simple $$ = stripIndentation(CUR_POS, data->symbols, *$2); } | PATH { $$ = new ExprPath(absPath($1, data->basePath)); } + | HPATH { $$ = new ExprPath(getEnv("HOME", "") + string{$1 + 1}); } | SPATH { string path($1 + 1, strlen($1) - 2); $$ = new ExprApp(CUR_POS, @@ -630,7 +626,7 @@ Path EvalState::findFile(const string & path) } -Path EvalState::findFile(SearchPath & searchPath, const string & path) +Path EvalState::findFile(SearchPath & searchPath, const string & path, const Pos & pos) { foreach (SearchPath::iterator, i, searchPath) { Path res; @@ -645,7 +641,11 @@ Path EvalState::findFile(SearchPath & searchPath, const string & path) } if (pathExists(res)) return canonPath(res); } - throw ThrownError(format("file ‘%1%’ was not found in the Nix search path (add it using $NIX_PATH or -I)") % path); + format f = format( + "file ‘%1%’ was not found in the Nix search path (add it using $NIX_PATH or -I)" + + string(pos ? ", at %2%" : "")); + f.exceptions(boost::io::all_error_bits ^ boost::io::too_many_args_bit); + throw ThrownError(f % path % pos); } diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index cd7b287e29c3..a4efd397ec7a 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -187,7 +187,7 @@ static void prim_typeOf(EvalState & state, const Pos & pos, Value * * args, Valu case tPrimOpApp: t = "lambda"; break; - case tExternal: + case tExternal: t = args[0]->external->typeOf(); break; default: abort(); @@ -710,7 +710,7 @@ static void prim_pathExists(EvalState & state, const Pos & pos, Value * * args, static void prim_baseNameOf(EvalState & state, const Pos & pos, Value * * args, Value & v) { PathSet context; - mkString(v, baseNameOf(state.coerceToString(pos, *args[0], context)), context); + mkString(v, baseNameOf(state.coerceToString(pos, *args[0], context, false, false)), context); } @@ -775,7 +775,7 @@ static void prim_findFile(EvalState & state, const Pos & pos, Value * * args, Va % path % e.path % pos); } - mkPath(v, state.findFile(searchPath, path).c_str()); + mkPath(v, state.findFile(searchPath, path, pos).c_str()); } /* Read a directory (without . or ..) */ @@ -796,7 +796,7 @@ static void prim_readDir(EvalState & state, const Pos & pos, Value * * args, Val for (auto & ent : entries) { Value * ent_val = state.allocAttr(v, state.symbols.create(ent.name)); if (ent.type == DT_UNKNOWN) - ent.type = getFileType(path); + ent.type = getFileType(path + "/" + ent.name); mkStringNoCopy(*ent_val, ent.type == DT_REG ? "regular" : ent.type == DT_DIR ? "directory" : diff --git a/src/libmain/shared.cc b/src/libmain/shared.cc index 7b50bfa9bb32..c83e997b2307 100644 --- a/src/libmain/shared.cc +++ b/src/libmain/shared.cc @@ -11,6 +11,7 @@ #include <exception> #include <algorithm> +#include <cstdlib> #include <sys/time.h> #include <sys/stat.h> #include <unistd.h> @@ -292,8 +293,9 @@ int handleExceptions(const string & programName, std::function<void()> fun) RunPager::RunPager() { if (!isatty(STDOUT_FILENO)) return; - string pager = getEnv("PAGER", "default"); - if (pager == "" || pager == "cat") return; + char * pager = getenv("NIX_PAGER"); + if (!pager) pager = getenv("PAGER"); + if (pager && ((string) pager == "" || (string) pager == "cat")) return; /* Ignore SIGINT. The pager will handle it (and we'll get SIGPIPE). */ @@ -313,8 +315,8 @@ RunPager::RunPager() throw SysError("dupping stdin"); if (!getenv("LESS")) setenv("LESS", "FRSXMK", 1); - if (pager != "default") - execl("/bin/sh", "sh", "-c", pager.c_str(), NULL); + if (pager) + execl("/bin/sh", "sh", "-c", pager, NULL); execlp("pager", "pager", NULL); execlp("less", "less", NULL); execlp("more", "more", NULL); diff --git a/src/libstore/build.cc b/src/libstore/build.cc index 08f44b392b00..e64bd3fef587 100644 --- a/src/libstore/build.cc +++ b/src/libstore/build.cc @@ -38,6 +38,9 @@ #if HAVE_SYS_MOUNT_H #include <sys/mount.h> #endif +#if HAVE_SYS_SYSCALL_H +#include <sys/syscall.h> +#endif #if HAVE_SCHED_H #include <sched.h> #endif @@ -48,7 +51,16 @@ #include <linux/fs.h> #endif -#define CHROOT_ENABLED HAVE_CHROOT && HAVE_UNSHARE && HAVE_SYS_MOUNT_H && defined(MS_BIND) && defined(MS_PRIVATE) && defined(CLONE_NEWNS) +#define CHROOT_ENABLED HAVE_CHROOT && HAVE_UNSHARE && HAVE_SYS_MOUNT_H && defined(MS_BIND) && defined(MS_PRIVATE) && defined(CLONE_NEWNS) && defined(SYS_pivot_root) + +/* chroot-like behavior from Apple's sandbox */ +#if __APPLE__ + #define SANDBOX_ENABLED 1 + #define DEFAULT_ALLOWED_IMPURE_PREFIXES "/System/Library/Frameworks /usr/lib /dev /bin/sh" +#else + #define SANDBOX_ENABLED 0 + #define DEFAULT_ALLOWED_IMPURE_PREFIXES "/bin" "/usr/bin" +#endif #if CHROOT_ENABLED #include <sys/socket.h> @@ -1586,6 +1598,13 @@ void chmod_(const Path & path, mode_t mode) } +int childEntry(void * arg) +{ + ((DerivationGoal *) arg)->runChild(); + return 1; +} + + void DerivationGoal::startBuilder() { startNest(nest, lvlInfo, format( @@ -1627,14 +1646,26 @@ void DerivationGoal::startBuilder() /* The maximum number of cores to utilize for parallel building. */ env["NIX_BUILD_CORES"] = (format("%d") % settings.buildCores).str(); - /* Add all bindings specified in the derivation. */ - foreach (StringPairs::iterator, i, drv.env) - env[i->first] = i->second; - /* Create a temporary directory where the build will take place. */ tmpDir = createTempDir("", "nix-build-" + storePathToName(drvPath), false, false, 0700); + /* Add all bindings specified in the derivation via the + environments, except those listed in the passAsFile + attribute. Those are passed as file names pointing to + temporary files containing the contents. */ + StringSet passAsFile = tokenizeString<StringSet>(get(drv.env, "passAsFile")); + int fileNr = 0; + for (auto & i : drv.env) { + if (passAsFile.find(i.first) == passAsFile.end()) { + env[i.first] = i.second; + } else { + Path p = tmpDir + "/.attr-" + int2String(fileNr++); + writeFile(p, i.second); + env[i.first + "Path"] = p; + } + } + /* For convenience, set an environment pointing to the top build directory. */ env["NIX_BUILD_TOP"] = tmpDir; @@ -1729,21 +1760,6 @@ void DerivationGoal::startBuilder() /* Change ownership of the temporary build directory. */ if (chown(tmpDir.c_str(), buildUser.getUID(), buildUser.getGID()) == -1) throw SysError(format("cannot change ownership of ‘%1%’") % tmpDir); - - /* Check that the Nix store has the appropriate permissions, - i.e., owned by root and mode 1775 (sticky bit on so that - the builder can create its output but not mess with the - outputs of other processes). */ - struct stat st; - if (stat(settings.nixStore.c_str(), &st) == -1) - throw SysError(format("cannot stat ‘%1%’") % settings.nixStore); - if (!(st.st_mode & S_ISVTX) || - ((st.st_mode & S_IRWXG) != S_IRWXG) || - (st.st_gid != buildUser.getGID())) - throw Error(format( - "builder does not have write permission to ‘%2%’; " - "try ‘chgrp %1% %2%; chmod 1775 %2%’") - % buildUser.getGID() % settings.nixStore); } @@ -1760,6 +1776,47 @@ void DerivationGoal::startBuilder() if (get(drv.env, "__noChroot") == "1") useChroot = false; if (useChroot) { + /* Allow a user-configurable set of directories from the + host file system. */ + PathSet dirs = tokenizeString<StringSet>(settings.get("build-chroot-dirs", string(DEFAULT_CHROOT_DIRS))); + PathSet dirs2 = tokenizeString<StringSet>(settings.get("build-extra-chroot-dirs", string(""))); + dirs.insert(dirs2.begin(), dirs2.end()); + + for (auto & i : dirs) { + size_t p = i.find('='); + if (p == string::npos) + dirsInChroot[i] = i; + else + dirsInChroot[string(i, 0, p)] = string(i, p + 1); + } + dirsInChroot[tmpDir] = tmpDir; + + string allowed = settings.get("allowed-impure-host-deps", string(DEFAULT_ALLOWED_IMPURE_PREFIXES)); + PathSet allowedPaths = tokenizeString<StringSet>(allowed); + + /* This works like the above, except on a per-derivation level */ + Strings impurePaths = tokenizeString<Strings>(get(drv.env, "__impureHostDeps")); + + for (auto & i : impurePaths) { + bool found = false; + /* Note: we're not resolving symlinks here to prevent + giving a non-root user info about inaccessible + files. */ + Path canonI = canonPath(i); + /* If only we had a trie to do this more efficiently :) luckily, these are generally going to be pretty small */ + for (auto & a : allowedPaths) { + Path canonA = canonPath(a); + if (canonI == canonA || isInDir(canonI, canonA)) { + found = true; + break; + } + } + if (!found) + throw Error(format("derivation '%1%' requested impure path ‘%2%’, but it was not in allowed-impure-host-deps (‘%3%’)") % drvPath % i % allowed); + + dirsInChroot[i] = i; + } + #if CHROOT_ENABLED /* Create a temporary directory in which we set up the chroot environment using bind-mounts. We put it in the Nix store @@ -1801,20 +1858,6 @@ void DerivationGoal::startBuilder() /* Create /etc/hosts with localhost entry. */ writeFile(chrootRootDir + "/etc/hosts", "127.0.0.1 localhost\n"); - /* Bind-mount a user-configurable set of directories from the - host file system. */ - PathSet dirs = tokenizeString<StringSet>(settings.get("build-chroot-dirs", string(DEFAULT_CHROOT_DIRS))); - PathSet dirs2 = tokenizeString<StringSet>(settings.get("build-extra-chroot-dirs", string(""))); - dirs.insert(dirs2.begin(), dirs2.end()); - for (auto & i : dirs) { - size_t p = i.find('='); - if (p == string::npos) - dirsInChroot[i] = i; - else - dirsInChroot[string(i, 0, p)] = string(i, p + 1); - } - dirsInChroot[tmpDir] = tmpDir; - /* Make the closure of the inputs available in the chroot, rather than the whole Nix store. This prevents any access to undeclared dependencies. Directories are bind-mounted, @@ -1858,6 +1901,9 @@ void DerivationGoal::startBuilder() foreach (DerivationOutputs::iterator, i, drv.outputs) dirsInChroot.erase(i->second.path); +#elif SANDBOX_ENABLED + /* We don't really have any parent prep work to do (yet?) + All work happens in the child, instead. */ #else throw Error("chroot builds are not supported on this platform"); #endif @@ -1902,11 +1948,66 @@ void DerivationGoal::startBuilder() builderOut.create(); /* Fork a child to build the package. */ - ProcessOptions options; - options.allowVfork = !buildUser.enabled(); - pid = startProcess([&]() { - runChild(); - }, options); +#if CHROOT_ENABLED + if (useChroot) { + /* Set up private namespaces for the build: + + - The PID namespace causes the build to start as PID 1. + Processes outside of the chroot are not visible to those + on the inside, but processes inside the chroot are + visible from the outside (though with different PIDs). + + - The private mount namespace ensures that all the bind + mounts we do will only show up in this process and its + children, and will disappear automatically when we're + done. + + - The private network namespace ensures that the builder + cannot talk to the outside world (or vice versa). It + only has a private loopback interface. + + - The IPC namespace prevents the builder from communicating + with outside processes using SysV IPC mechanisms (shared + memory, message queues, semaphores). It also ensures + that all IPC objects are destroyed when the builder + exits. + + - The UTS namespace ensures that builders see a hostname of + localhost rather than the actual hostname. + + We use a helper process to do the clone() to work around + clone() being broken in multi-threaded programs due to + at-fork handlers not being run. Note that we use + CLONE_PARENT to ensure that the real builder is parented to + us. + */ + Pid helper = startProcess([&]() { + char stack[32 * 1024]; + pid_t child = clone(childEntry, stack + sizeof(stack) - 8, + CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWNET | CLONE_NEWIPC | CLONE_NEWUTS | CLONE_PARENT | SIGCHLD, this); + if (child == -1) { + if (errno == EINVAL) + throw SysError("cloning builder process (Linux chroot builds require 3.13 or later)"); + else + throw SysError("cloning builder process"); + } + writeFull(builderOut.writeSide, int2String(child) + "\n"); + _exit(0); + }); + if (helper.wait(true) != 0) + throw Error("unable to start build process"); + pid_t tmp; + if (!string2Int<pid_t>(readLine(builderOut.readSide), tmp)) abort(); + pid = tmp; + } else +#endif + { + ProcessOptions options; + options.allowVfork = !buildUser.enabled(); + pid = startProcess([&]() { + runChild(); + }, options); + } /* parent */ pid.setSeparatePG(true); @@ -1922,7 +2023,6 @@ void DerivationGoal::startBuilder() printMsg(lvlError, format("@ build-started %1% - %2% %3%") % drvPath % drv.platform % logFile); } - } @@ -1938,30 +2038,6 @@ void DerivationGoal::runChild() #if CHROOT_ENABLED if (useChroot) { - /* Set up private namespaces for the build: - - - The private mount namespace ensures that all the bind - mounts we do will only show up in this process and - its children, and will disappear automatically when - we're done. - - - The private network namespace ensures that the - builder cannot talk to the outside world (or vice - versa). It only has a private loopback interface. - - - The IPC namespace prevents the builder from - communicating with outside processes using SysV IPC - mechanisms (shared memory, message queues, - semaphores). It also ensures that all IPC objects - are destroyed when the builder exits. - - - The UTS namespace ensures that builders see a - hostname of localhost rather than the actual - hostname. - */ - if (unshare(CLONE_NEWNS | CLONE_NEWNET | CLONE_NEWIPC | CLONE_NEWUTS) == -1) - throw SysError("setting up private namespaces"); - /* Initialise the loopback interface. */ AutoCloseFD fd(socket(PF_INET, SOCK_DGRAM, IPPROTO_IP)); if (fd == -1) throw SysError("cannot open IP socket"); @@ -1998,6 +2074,11 @@ void DerivationGoal::runChild() throw SysError(format("unable to make filesystem ‘%1%’ private") % fs); } + /* Bind-mount chroot directory to itself, to treat it as a + different filesystem from /, as needed for pivot_root. */ + if (mount(chrootRootDir.c_str(), chrootRootDir.c_str(), 0, MS_BIND, 0) == -1) + throw SysError(format("unable to bind mount ‘%1%’") % chrootRootDir); + /* Set up a nearly empty /dev, unless the user asked to bind-mount the host /dev. */ if (dirsInChroot.find("/dev") == dirsInChroot.end()) { @@ -2069,13 +2150,26 @@ void DerivationGoal::runChild() chmod_(chrootRootDir + "/dev/pts/ptmx", 0666); } - /* Do the chroot(). Below we do a chdir() to the - temporary build directory to make sure the current - directory is in the chroot. (Actually the order - doesn't matter, since due to the bind mount tmpDir and - tmpRootDit/tmpDir are the same directories.) */ - if (chroot(chrootRootDir.c_str()) == -1) + /* Do the chroot(). */ + if (chdir(chrootRootDir.c_str()) == -1) + throw SysError(format("cannot change directory to ‘%1%’") % chrootRootDir); + + if (mkdir("real-root", 0) == -1) + throw SysError("cannot create real-root directory"); + +#define pivot_root(new_root, put_old) (syscall(SYS_pivot_root, new_root, put_old)) + if (pivot_root(".", "real-root") == -1) + throw SysError(format("cannot pivot old root directory onto ‘%1%’") % (chrootRootDir + "/real-root")); +#undef pivot_root + + if (chroot(".") == -1) throw SysError(format("cannot change root directory to ‘%1%’") % chrootRootDir); + + if (umount2("real-root", MNT_DETACH) == -1) + throw SysError("cannot unmount real root filesystem"); + + if (rmdir("real-root") == -1) + throw SysError("cannot remove real-root directory"); } #endif @@ -2139,8 +2233,123 @@ void DerivationGoal::runChild() /* Fill in the arguments. */ Strings args; - string builderBasename = baseNameOf(drv.builder); - args.push_back(builderBasename); + + const char *builder = "invalid"; + + string sandboxProfile; + if (useChroot && SANDBOX_ENABLED) { + /* Lots and lots and lots of file functions freak out if they can't stat their full ancestry */ + PathSet ancestry; + + /* We build the ancestry before adding all inputPaths to the store because we know they'll + all have the same parents (the store), and there might be lots of inputs. This isn't + particularly efficient... I doubt it'll be a bottleneck in practice */ + for (auto & i : dirsInChroot) { + Path cur = i.first; + while (cur.compare("/") != 0) { + cur = dirOf(cur); + ancestry.insert(cur); + } + } + + /* And we want the store in there regardless of how empty dirsInChroot. We include the innermost + path component this time, since it's typically /nix/store and we care about that. */ + Path cur = settings.nixStore; + while (cur.compare("/") != 0) { + ancestry.insert(cur); + cur = dirOf(cur); + } + + /* Add all our input paths to the chroot */ + for (auto & i : inputPaths) + dirsInChroot[i] = i; + + + /* TODO: we should factor out the policy cleanly, so we don't have to repeat the constants every time... */ + 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)) { + sandboxProfile += "(deny default)\n"; + } else { + sandboxProfile += "(deny default (with no-log))\n"; + } + + sandboxProfile += "(allow file-read* file-write-data (literal \"/dev/null\"))\n"; + + sandboxProfile += "(allow file-read-metadata\n" + "\t(literal \"/var\")\n" + "\t(literal \"/tmp\")\n" + "\t(literal \"/etc\")\n" + "\t(literal \"/etc/nix\")\n" + "\t(literal \"/etc/nix/nix.conf\"))\n"; + + /* The tmpDir in scope points at the temporary build directory for our derivation. Some packages try different mechanisms + to find temporary directories, so we want to open up a broader place for them to dump their files, if needed. */ + Path globalTmpDir = canonPath(getEnv("TMPDIR", "/tmp"), true); + + /* They don't like trailing slashes on subpath directives */ + if (globalTmpDir.back() == '/') globalTmpDir.pop_back(); + + /* This is where our temp folders are and where most of the building will happen, so we want rwx on it. */ + sandboxProfile += (format("(allow file-read* file-write* process-exec (subpath \"%1%\") (subpath \"/private/tmp\"))\n") % globalTmpDir).str(); + + sandboxProfile += "(allow process-fork)\n"; + sandboxProfile += "(allow sysctl-read)\n"; + sandboxProfile += "(allow signal (target same-sandbox))\n"; + + /* Enables getpwuid (used by git and others) */ + sandboxProfile += "(allow mach-lookup (global-name \"com.apple.system.notification_center\") (global-name \"com.apple.system.opendirectoryd.libinfo\"))\n"; + + + /* Our rwx outputs */ + sandboxProfile += "(allow file-read* file-write* process-exec\n"; + for (auto & i : missingPaths) { + sandboxProfile += (format("\t(subpath \"%1%\")\n") % i.c_str()).str(); + } + sandboxProfile += ")\n"; + + /* Our inputs (transitive dependencies and any impurities computed above) + Note that the sandbox profile allows file-write* even though it isn't seemingly necessary. First of all, nix's standard user permissioning + mechanism still prevents builders from writing to input directories, so no security/purity is lost. The reason we allow file-write* is that + denying it means the `access` syscall will return EPERM instead of EACCESS, which confuses a few programs that assume (understandably, since + it appears to be a violation of the POSIX spec) that `access` won't do that, and don't deal with it nicely if it does. The most notable of + these is the entire GHC Haskell ecosystem. */ + sandboxProfile += "(allow file-read* file-write* process-exec\n"; + for (auto & i : dirsInChroot) { + if (i.first != i.second) + throw SysError(format("can't map '%1%' to '%2%': mismatched impure paths not supported on darwin")); + + string path = i.first; + struct stat st; + if (lstat(path.c_str(), &st)) + throw SysError(format("getting attributes of path ‘%1%’") % path); + if (S_ISDIR(st.st_mode)) + sandboxProfile += (format("\t(subpath \"%1%\")\n") % path).str(); + else + sandboxProfile += (format("\t(literal \"%1%\")\n") % path).str(); + } + sandboxProfile += ")\n"; + + /* Our ancestry. N.B: this uses literal on folders, instead of subpath. Without that, + you open up the entire filesystem because you end up with (subpath "/") */ + sandboxProfile += "(allow file-read-metadata\n"; + for (auto & i : ancestry) { + sandboxProfile += (format("\t(literal \"%1%\")\n") % i.c_str()).str(); + } + sandboxProfile += ")\n"; + + builder = "/usr/bin/sandbox-exec"; + args.push_back("sandbox-exec"); + args.push_back("-p"); + args.push_back(sandboxProfile); + args.push_back(drv.builder); + } else { + builder = drv.builder.c_str(); + string builderBasename = baseNameOf(drv.builder); + args.push_back(builderBasename); + } + foreach (Strings::iterator, i, drv.args) args.push_back(rewriteHashes(*i, rewritesToTmp)); auto argArr = stringsToCharPtrs(args); @@ -2150,8 +2359,14 @@ void DerivationGoal::runChild() /* Indicate that we managed to set up the build environment. */ writeFull(STDERR_FILENO, "\n"); + /* This needs to be after that fateful '\n', and I didn't want to duplicate code */ + if (useChroot && SANDBOX_ENABLED) { + printMsg(lvlDebug, "Generated sandbox profile:"); + printMsg(lvlDebug, sandboxProfile); + } + /* Execute the program. This should not return. */ - execve(drv.builder.c_str(), (char * *) &argArr[0], (char * *) &envArr[0]); + execve(builder, (char * *) &argArr[0], (char * *) &envArr[0]); throw SysError(format("executing ‘%1%’") % drv.builder); diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc index 3ad80bc4e6f4..bc792baf296b 100644 --- a/src/libstore/local-store.cc +++ b/src/libstore/local-store.cc @@ -256,20 +256,23 @@ LocalStore::LocalStore(bool reserveSpace) if (chmod(perUserDir.c_str(), 01777) == -1) throw SysError(format("could not set permissions on ‘%1%’ to 1777") % perUserDir); + mode_t perm = 01735; + struct group * gr = getgrnam(settings.buildUsersGroup.c_str()); if (!gr) - throw Error(format("the group ‘%1%’ specified in ‘build-users-group’ does not exist") + printMsg(lvlError, format("warning: the group ‘%1%’ specified in ‘build-users-group’ does not exist") % settings.buildUsersGroup); - - struct stat st; - if (stat(settings.nixStore.c_str(), &st)) - throw SysError(format("getting attributes of path ‘%1%’") % settings.nixStore); - - if (st.st_uid != 0 || st.st_gid != gr->gr_gid || (st.st_mode & ~S_IFMT) != 01775) { - if (chown(settings.nixStore.c_str(), 0, gr->gr_gid) == -1) - throw SysError(format("changing ownership of path ‘%1%’") % settings.nixStore); - if (chmod(settings.nixStore.c_str(), 01775) == -1) - throw SysError(format("changing permissions on path ‘%1%’") % settings.nixStore); + else { + struct stat st; + if (stat(settings.nixStore.c_str(), &st)) + throw SysError(format("getting attributes of path ‘%1%’") % settings.nixStore); + + if (st.st_uid != 0 || st.st_gid != gr->gr_gid || (st.st_mode & ~S_IFMT) != perm) { + if (chown(settings.nixStore.c_str(), 0, gr->gr_gid) == -1) + throw SysError(format("changing ownership of path ‘%1%’") % settings.nixStore); + if (chmod(settings.nixStore.c_str(), perm) == -1) + throw SysError(format("changing permissions on path ‘%1%’") % settings.nixStore); + } } } diff --git a/src/libstore/optimise-store.cc b/src/libstore/optimise-store.cc index dd18d66fa008..55c252b9b2e3 100644 --- a/src/libstore/optimise-store.cc +++ b/src/libstore/optimise-store.cc @@ -4,6 +4,7 @@ #include "local-store.hh" #include "globals.hh" +#include <cstdlib> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> diff --git a/src/libstore/remote-store.cc b/src/libstore/remote-store.cc index acc895409eba..d08913246321 100644 --- a/src/libstore/remote-store.cc +++ b/src/libstore/remote-store.cc @@ -10,6 +10,7 @@ #include <sys/stat.h> #include <sys/socket.h> #include <sys/un.h> +#include <errno.h> #include <fcntl.h> #include <iostream> diff --git a/src/libutil/hash.cc b/src/libutil/hash.cc index 965f3ed47701..a83ba0a81817 100644 --- a/src/libutil/hash.cc +++ b/src/libutil/hash.cc @@ -84,7 +84,7 @@ string printHash(const Hash & hash) return string(buf, hash.hashSize * 2); } - + Hash parseHash(HashType ht, const string & s) { Hash hash(ht); @@ -92,7 +92,7 @@ Hash parseHash(HashType ht, const string & s) throw Error(format("invalid hash ‘%1%’") % s); for (unsigned int i = 0; i < hash.hashSize; i++) { string s2(s, i * 2, 2); - if (!isxdigit(s2[0]) || !isxdigit(s2[1])) + if (!isxdigit(s2[0]) || !isxdigit(s2[1])) throw Error(format("invalid hash ‘%1%’") % s); std::istringstream str(s2); int n; @@ -103,24 +103,6 @@ Hash parseHash(HashType ht, const string & s) } -static unsigned char divMod(unsigned char * bytes, unsigned char y) -{ - unsigned int borrow = 0; - - int pos = Hash::maxHashSize - 1; - while (pos >= 0 && !bytes[pos]) --pos; - - for ( ; pos >= 0; --pos) { - unsigned int s = bytes[pos] + (borrow << 8); - unsigned int d = s / y; - borrow = s % y; - bytes[pos] = d; - } - - return borrow; -} - - unsigned int hashLength32(const Hash & hash) { return (hash.hashSize * 8 - 1) / 5 + 1; @@ -136,19 +118,19 @@ string printHash32(const Hash & hash) Hash hash2(hash); unsigned int len = hashLength32(hash); - const char * chars = base32Chars.data(); - - string s(len, '0'); - - int pos = len - 1; - while (pos >= 0) { - unsigned char digit = divMod(hash2.hash, 32); - s[pos--] = chars[digit]; + string s; + s.reserve(len); + + for (int n = len - 1; n >= 0; n--) { + unsigned int b = n * 5; + unsigned int i = b / 8; + unsigned int j = b % 8; + unsigned char c = + (hash.hash[i] >> j) + | (i >= hash.hashSize - 1 ? 0 : hash.hash[i + 1] << (8 - j)); + s.push_back(base32Chars[c & 0x1f]); } - for (unsigned int i = 0; i < hash2.maxHashSize; ++i) - assert(hash2.hash[i] == 0); - return s; } @@ -159,51 +141,24 @@ string printHash16or32(const Hash & hash) } -static bool mul(unsigned char * bytes, unsigned char y, int maxSize) -{ - unsigned char carry = 0; - - for (int pos = 0; pos < maxSize; ++pos) { - unsigned int m = bytes[pos] * y + carry; - bytes[pos] = m & 0xff; - carry = m >> 8; - } - - return carry; -} - - -static bool add(unsigned char * bytes, unsigned char y, int maxSize) -{ - unsigned char carry = y; - - for (int pos = 0; pos < maxSize; ++pos) { - unsigned int m = bytes[pos] + carry; - bytes[pos] = m & 0xff; - carry = m >> 8; - if (carry == 0) break; - } - - return carry; -} - - Hash parseHash32(HashType ht, const string & s) { Hash hash(ht); + unsigned int len = hashLength32(ht); + assert(s.size() == len); - const char * chars = base32Chars.data(); - - for (unsigned int i = 0; i < s.length(); ++i) { - char c = s[i]; + for (unsigned int n = 0; n < len; ++n) { + char c = s[len - n - 1]; unsigned char digit; for (digit = 0; digit < base32Chars.size(); ++digit) /* !!! slow */ - if (chars[digit] == c) break; + if (base32Chars[digit] == c) break; if (digit >= 32) throw Error(format("invalid base-32 hash ‘%1%’") % s); - if (mul(hash.hash, 32, hash.hashSize) || - add(hash.hash, digit, hash.hashSize)) - throw Error(format("base-32 hash ‘%1%’ is too large") % s); + unsigned int b = n * 5; + unsigned int i = b / 8; + unsigned int j = b % 8; + hash.hash[i] |= digit << j; + if (i < hash.hashSize - 1) hash.hash[i + 1] |= digit >> (8 - j); } return hash; @@ -299,7 +254,7 @@ Hash hashFile(HashType ht, const Path & path) if (n == -1) throw SysError(format("reading file ‘%1%’") % path); update(ht, ctx, buf, n); } - + finish(ht, ctx, hash.hash); return hash; } @@ -311,7 +266,7 @@ HashSink::HashSink(HashType ht) : ht(ht) bytes = 0; start(ht, *ctx); } - + HashSink::~HashSink() { bufPos = 0; @@ -369,7 +324,7 @@ HashType parseHashType(const string & s) else return htUnknown; } - + string printHashType(HashType ht) { if (ht == htMD5) return "md5"; @@ -378,5 +333,5 @@ string printHashType(HashType ht) else throw Error("cannot print unknown hash type"); } - + } diff --git a/src/libutil/monitor-fd.hh b/src/libutil/monitor-fd.hh index 72d23fb6934c..6f01ccd91a43 100644 --- a/src/libutil/monitor-fd.hh +++ b/src/libutil/monitor-fd.hh @@ -3,6 +3,7 @@ #include <thread> #include <atomic> +#include <cstdlib> #include <poll.h> #include <sys/types.h> #include <unistd.h> diff --git a/src/libutil/util.cc b/src/libutil/util.cc index dcdb438e03b2..be0a9bf317d1 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -193,8 +193,12 @@ Path readLink(const Path & path) if (!S_ISLNK(st.st_mode)) throw Error(format("‘%1%’ is not a symlink") % path); char buf[st.st_size]; - if (readlink(path.c_str(), buf, st.st_size) != st.st_size) + ssize_t rlsize = readlink(path.c_str(), buf, st.st_size); + if (rlsize == -1) throw SysError(format("reading symbolic link ‘%1%’") % path); + else if (rlsize > st.st_size) + throw Error(format("symbolic link ‘%1%’ size overflow %2% > %3%") + % path % rlsize % st.st_size); return string(buf, st.st_size); } @@ -921,18 +925,24 @@ std::vector<const char *> stringsToCharPtrs(const Strings & ss) } -string runProgram(Path program, bool searchPath, const Strings & args) +string runProgram(Path program, bool searchPath, const Strings & args, + const string & input) { checkInterrupt(); /* Create a pipe. */ - Pipe pipe; - pipe.create(); + Pipe stdout, stdin; + stdout.create(); + if (!input.empty()) stdin.create(); /* Fork. */ Pid pid = startProcess([&]() { - if (dup2(pipe.writeSide, STDOUT_FILENO) == -1) + if (dup2(stdout.writeSide, STDOUT_FILENO) == -1) throw SysError("dupping stdout"); + if (!input.empty()) { + if (dup2(stdin.readSide, STDIN_FILENO) == -1) + throw SysError("dupping stdin"); + } Strings args_(args); args_.push_front(program); @@ -946,9 +956,16 @@ string runProgram(Path program, bool searchPath, const Strings & args) throw SysError(format("executing ‘%1%’") % program); }); - pipe.writeSide.close(); + stdout.writeSide.close(); + + /* FIXME: This can deadlock if the input is too long. */ + if (!input.empty()) { + stdin.readSide.close(); + writeFull(stdin.writeSide, input); + stdin.writeSide.close(); + } - string result = drainFD(pipe.readSide); + string result = drainFD(stdout.readSide); /* Wait for the child to finish. */ int status = pid.wait(true); @@ -1191,4 +1208,63 @@ string filterANSIEscapes(const string & s, bool nixOnly) } +static char base64Chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + +string base64Encode(const string & s) +{ + string res; + int data = 0, nbits = 0; + + for (char c : s) { + data = data << 8 | (unsigned char) c; + nbits += 8; + while (nbits >= 6) { + nbits -= 6; + res.push_back(base64Chars[data >> nbits & 0x3f]); + } + } + + if (nbits) res.push_back(base64Chars[data << (6 - nbits) & 0x3f]); + while (res.size() % 4) res.push_back('='); + + return res; +} + + +string base64Decode(const string & s) +{ + bool init = false; + char decode[256]; + if (!init) { + // FIXME: not thread-safe. + memset(decode, -1, sizeof(decode)); + for (int i = 0; i < 64; i++) + decode[(int) base64Chars[i]] = i; + init = true; + } + + string res; + unsigned int d = 0, bits = 0; + + for (char c : s) { + if (c == '=') break; + if (c == '\n') continue; + + char digit = decode[(unsigned char) c]; + if (digit == -1) + throw Error("invalid character in Base64 string"); + + bits += 6; + d = d << 6 | digit; + if (bits >= 8) { + res.push_back(d >> (bits - 8) & 0xff); + bits -= 8; + } + } + + return res; +} + + } diff --git a/src/libutil/util.hh b/src/libutil/util.hh index 186ee71f45d0..20330fb7699e 100644 --- a/src/libutil/util.hh +++ b/src/libutil/util.hh @@ -285,7 +285,7 @@ pid_t startProcess(std::function<void()> fun, const ProcessOptions & options = P /* Run a program and return its stdout in a string (i.e., like the shell backtick operator). */ string runProgram(Path program, bool searchPath = false, - const Strings & args = Strings()); + const Strings & args = Strings(), const string & input = ""); MakeError(ExecError, Error) @@ -398,4 +398,9 @@ void ignoreException(); string filterANSIEscapes(const string & s, bool nixOnly = false); +/* Base64 encoding/decoding. */ +string base64Encode(const string & s); +string base64Decode(const string & s); + + } diff --git a/src/nix-env/nix-env.cc b/src/nix-env/nix-env.cc index f5e8ee08c42f..f3c8d3ba8953 100644 --- a/src/nix-env/nix-env.cc +++ b/src/nix-env/nix-env.cc @@ -132,6 +132,8 @@ static void getAllExprs(EvalState & state, Value & vArg(*state.allocValue()); state.getBuiltin("import", vFun); mkString(vArg, path2); + if (v.attrs->size() == v.attrs->capacity()) + throw Error(format("too many Nix expressions in directory ‘%1%’") % path); mkApp(*state.allocAttr(v, state.symbols.create(attrName)), vFun, vArg); } else if (S_ISDIR(st.st_mode)) @@ -160,7 +162,7 @@ static void loadSourceExpr(EvalState & state, const Path & path, Value & v) ~/.nix-defexpr directory that includes some system-wide directory). */ if (S_ISDIR(st.st_mode)) { - state.mkAttrs(v, 16); + state.mkAttrs(v, 1024); state.mkList(*state.allocAttr(v, state.symbols.create("_combineChannels")), 0); StringSet attrs; getAllExprs(state, path, attrs, v); diff --git a/src/nix-store/local.mk b/src/nix-store/local.mk index b887fe03389b..84ff15b241f3 100644 --- a/src/nix-store/local.mk +++ b/src/nix-store/local.mk @@ -6,6 +6,6 @@ nix-store_SOURCES := $(wildcard $(d)/*.cc) nix-store_LIBS = libmain libstore libutil libformat -nix-store_LDFLAGS = -lbz2 -pthread +nix-store_LDFLAGS = -lbz2 -pthread $(SODIUM_LIBS) nix-store_CXXFLAGS = -DCURL=\"$(curl)\" diff --git a/src/nix-store/nix-store.cc b/src/nix-store/nix-store.cc index 87bc8c379de5..7ce5f63c2d2f 100644 --- a/src/nix-store/nix-store.cc +++ b/src/nix-store/nix-store.cc @@ -20,6 +20,10 @@ #include <bzlib.h> +#if HAVE_SODIUM +#include <sodium.h> +#endif + using namespace nix; using std::cin; @@ -1006,6 +1010,34 @@ static void opServe(Strings opFlags, Strings opArgs) } +static void opGenerateBinaryCacheKey(Strings opFlags, Strings opArgs) +{ + foreach (Strings::iterator, i, opFlags) + throw UsageError(format("unknown flag ‘%1%’") % *i); + + if (opArgs.size() != 3) throw UsageError("three arguments expected"); + auto i = opArgs.begin(); + string keyName = *i++; + string secretKeyFile = *i++; + string publicKeyFile = *i++; + +#if HAVE_SODIUM + sodium_init(); + + unsigned char pk[crypto_sign_PUBLICKEYBYTES]; + unsigned char sk[crypto_sign_SECRETKEYBYTES]; + if (crypto_sign_keypair(pk, sk) != 0) + throw Error("key generation failed"); + + writeFile(publicKeyFile, keyName + ":" + base64Encode(string((char *) pk, crypto_sign_PUBLICKEYBYTES))); + umask(0077); + writeFile(secretKeyFile, keyName + ":" + base64Encode(string((char *) sk, crypto_sign_SECRETKEYBYTES))); +#else + throw Error("Nix was not compiled with libsodium, required for signed binary cache support"); +#endif +} + + /* Scan the arguments; find the operation, set global flags, put all other flags in a list, and put all other arguments in another list. */ @@ -1072,14 +1104,16 @@ int main(int argc, char * * argv) op = opQueryFailedPaths; else if (*arg == "--clear-failed-paths") op = opClearFailedPaths; + else if (*arg == "--serve") + op = opServe; + else if (*arg == "--generate-binary-cache-key") + op = opGenerateBinaryCacheKey; else if (*arg == "--add-root") gcRoot = absPath(getArg(*arg, arg, end)); else if (*arg == "--indirect") indirectRoot = true; else if (*arg == "--no-output") noOutput = true; - else if (*arg == "--serve") - op = opServe; else if (*arg != "" && arg->at(0) == '-') { opFlags.push_back(*arg); if (*arg == "--max-freed" || *arg == "--max-links" || *arg == "--max-atime") /* !!! hack */ diff --git a/tests/binary-cache.sh b/tests/binary-cache.sh index 6f0c36f6300f..c72d2defa5d0 100644 --- a/tests/binary-cache.sh +++ b/tests/binary-cache.sh @@ -87,3 +87,55 @@ rm $(grep -l "StorePath:.*dependencies-input-2" $cacheDir/*.narinfo) nix-build --option binary-caches "file://$cacheDir" dependencies.nix -o $TEST_ROOT/result 2>&1 | tee $TEST_ROOT/log grep -q "Downloading" $TEST_ROOT/log + + +if [ -n "$HAVE_SODIUM" ]; then + +# Create a signed binary cache. +clearCache + +declare -a res=($(nix-store --generate-binary-cache-key test.nixos.org-1 $TEST_ROOT/sk1 $TEST_ROOT/pk1 )) +publicKey="$(cat $TEST_ROOT/pk1)" + +res=($(nix-store --generate-binary-cache-key test.nixos.org-1 $TEST_ROOT/sk2 $TEST_ROOT/pk2)) +badKey="$(cat $TEST_ROOT/pk2)" + +res=($(nix-store --generate-binary-cache-key foo.nixos.org-1 $TEST_ROOT/sk3 $TEST_ROOT/pk3)) +otherKey="$(cat $TEST_ROOT/pk3)" + +nix-push --dest $cacheDir --key-file $TEST_ROOT/sk1 $outPath + + +# Downloading should fail if we don't provide a key. +clearStore + +rm -f $NIX_STATE_DIR/binary-cache* + +(! nix-store -r $outPath --option binary-caches "file://$cacheDir" --option signed-binary-caches '*' ) + + +# And it should fail if we provide an incorrect key. +clearStore + +rm -f $NIX_STATE_DIR/binary-cache* + +(! nix-store -r $outPath --option binary-caches "file://$cacheDir" --option signed-binary-caches '*' --option binary-cache-public-keys "$badKey") + + +# It should succeed if we provide the correct key. +nix-store -r $outPath --option binary-caches "file://$cacheDir" --option signed-binary-caches '*' --option binary-cache-public-keys "$otherKey $publicKey" + + +# It should fail if we corrupt the .narinfo. +clearStore + +for i in $cacheDir/*.narinfo; do + grep -v References $i > $i.tmp + mv $i.tmp $i +done + +rm -f $NIX_STATE_DIR/binary-cache* + +(! nix-store -r $outPath --option binary-caches "file://$cacheDir" --option signed-binary-caches '*' --option binary-cache-public-keys "$publicKey") + +fi # HAVE_LIBSODIUM diff --git a/tests/common.sh.in b/tests/common.sh.in index 8c265d1a8de0..eb9798a27b45 100644 --- a/tests/common.sh.in +++ b/tests/common.sh.in @@ -25,6 +25,7 @@ export dot=@dot@ export xmllint="@xmllint@" export SHELL="@bash@" export PAGER=cat +export HAVE_SODIUM="@HAVE_SODIUM@" export version=@PACKAGE_VERSION@ export system=@system@ diff --git a/tests/lang/eval-okay-context.nix b/tests/lang/eval-okay-context.nix index 8cd8f2e131d8..7b9531cfe9e1 100644 --- a/tests/lang/eval-okay-context.nix +++ b/tests/lang/eval-okay-context.nix @@ -1,4 +1,4 @@ -let s = "foo ${builtins.substring 33 100 (baseNameOf ./eval-okay-context.nix)} bar"; +let s = "foo ${builtins.substring 33 100 (baseNameOf "${./eval-okay-context.nix}")} bar"; in if s != "foo eval-okay-context.nix bar" then abort "context not discarded" diff --git a/tests/local.mk b/tests/local.mk index 69a227495d94..7a24fadcb8b9 100644 --- a/tests/local.mk +++ b/tests/local.mk @@ -11,7 +11,7 @@ nix_tests = \ binary-patching.sh timeout.sh secure-drv-outputs.sh nix-channel.sh \ multiple-outputs.sh import-derivation.sh fetchurl.sh optimise-store.sh \ binary-cache.sh nix-profile.sh repair.sh dump-db.sh case-hack.sh \ - check-reqs.sh + check-reqs.sh pass-as-file.sh # parallel.sh install-tests += $(foreach x, $(nix_tests), tests/$(x)) diff --git a/tests/pass-as-file.sh b/tests/pass-as-file.sh new file mode 100644 index 000000000000..3dfe10baa235 --- /dev/null +++ b/tests/pass-as-file.sh @@ -0,0 +1,17 @@ +source common.sh + +clearStore + +outPath=$(nix-build --no-out-link -E " +with import ./config.nix; + +mkDerivation { + name = \"pass-as-file\"; + passAsFile = [ \"foo\" ]; + foo = [ \"xyzzy\" ]; + builder = builtins.toFile \"builder.sh\" '' + [ \"\$(cat \$fooPath)\" = xyzzy ] + touch \$out + ''; +} +") diff --git a/tests/remote-builds.nix b/tests/remote-builds.nix index 0f16026a428a..34276e7d6981 100644 --- a/tests/remote-builds.nix +++ b/tests/remote-builds.nix @@ -14,6 +14,7 @@ let { services.openssh.enable = true; virtualisation.writableStore = true; nix.package = nix; + nix.useChroot = true; }; # Trivial Nix expression to build remotely. diff --git a/version b/version index 468437494697..872765e5f285 100644 --- a/version +++ b/version @@ -1 +1 @@ -1.8 \ No newline at end of file +1.9 \ No newline at end of file |