New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
NIX_PATH
-free nixpkgs
#30399
NIX_PATH
-free nixpkgs
#30399
Conversation
This adds a new section to the Nixpkgs manual documenting how to bootstrap and pin `nixpkgs` without the use of the `NIX_PATH` The main reasons for including this code in the documentation (as opposed to a file in the `nixpkgs` repository were): * To improve discoverability of this trick * You wouldn't be able to programmatically retrieve the code anyway ☺ * There's no obvious place to include this file in the repository
doc/bootstrap.xml
Outdated
tarball = stage1-tarball; | ||
}; | ||
|
||
stage1 = import stage1-path { inherit system; }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably set config = {};
in the nixpkgs arguments here, to prevent nondeterminism caused by ~/.config/nixpkgs/config.nix
.
doc/bootstrap.xml
Outdated
stage1 = import stage1-path { inherit system; }; | ||
}; | ||
|
||
let |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why have this be in a separate let
block, rather than putting it in the with
block above?
doc/bootstrap.xml
Outdated
# ... then this file would produce this attribute set: | ||
# | ||
# $ nix-instantiate --eval --expr 'import ./default.nix' | ||
# { 16_09 = <CODE>; 17_03 = <CODE>; 17_09 = <CODE>; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe it should be mentioned that <CODE>
is a Nix path value.
I remember hearing that Edit: I see that you're not using |
@vaibhavsagar Pretty much all of this rigmarole is because we want the semantics of |
doc/bootstrap.xml
Outdated
"$coreutils/mkdir" -p "$out" | ||
cd "$out" | ||
"$gzip" -c -d "$tarball" > temporary.tar | ||
"$tar" -xf temporary.tar --strip-components=1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
instead of doing this in two steps, we can combine it into one (n.b.: this code should be tested):
"$gzip" -d < "$tarball" | "$tar" -x --strip-components=1
this idea courtesy of @cleverca22's similar bootstrapping expression for IOHK
I hope this is not a stupid question, but what is the advantage of this code over the following?
|
@peti, what happens when you do that with |
|
another benefit of allowing it to work with an empty |
So the assumption is that (a) the user has a working installation of Nix that is, basically, capable of compiling non-trivial software and (b) the user has a broken $NIX_PATH. Is that combination of circumstances very likely to occur? I mean, I see how the scheme you've come up with would work in such a situation while others, that reference I also find some of the text misleading, i.e saying that the use of |
@peti: The strongest argument is the one that @cleverca22 gave. Our code base used to be fully of impure |
@Gabriel439, I see your point. I am totally in favor of having that technique in our toolset, i.e. it's pretty cool that it's possible to use a deterministic version of Nixpkgs without having Nixpkgs available already. I just feel like the documentation this PR offers doesn't represent the respective pros and cons very accurately. I, for one, could not see from the text why this kind of elaborate scheme might be desirable and what it achieves. |
Alright, I'll update the documentation to reflect this discussion |
I think this might be better as a wiki article. By putting it in the manual, we're elevating it to something like a best current practice, and I don't think there is a consensus on that. Also, it seems strange to me to distribute non-trivial bits of source code via the manual for users to cut & paste verbatim. BTW, @peti's example in #30399 (comment) can be changed to use |
There are two downsides to this approach:
We use something like the following: https://github.com/input-output-hk/iohk-ops/blob/develop/fetch-nixpkgs.nix All of this will be superseeded by Nix 1.12, so it would be good to know if that's coming soon before merging this. |
@domenkozar: How will this be superseded by Nix 1.12? |
@Gabriel439 From my comment above: "Once Nix 1.12 is released, fetchTarball will have a sha256 option, and most of this code will be obviated as a result." |
@edolstra: Oh, for some reason I thought the wiki was shut down in favor of the manual. I didn't realize that the Wiki was revived. Then I can move this example to the Wiki instead. I'll also incorporate the feedback to use the However, I want to point out that you have to copy and paste the bootstrap script by necessity. If there were a reproducible way to fetch the bootstrap script from the |
@edolstra: So after digging into the |
@Gabriel439 Presumably |
@taktoa: Yeah, you're correct. It will not require restricted mode if you provide the hash: So I think then pre-Nix-1.12 |
A couple other suggestions regarding this Nix script:
|
@taktoa: I like that idea. I'll refactor this to be a |
@taktoa: Here's the simpler version I've got after your feedback: # This file provides a way to fetch `nixpkgs` with an empty `NIX_PATH`. This
# comes in handy if you want to remove impure references to the `NIX_PATH` from
# your code base
#
# To use this code, save this to a `fetchNixpkgs.nix` file within your project
# and then referencing this file like so:
#
# ```nix
# let
# fetchNixpkgs = import ./fetchNixpkgs.nix;
#
# nixpkgs = fetchNixpkgs {
# rev = "76d649b59484607901f0c1b8f737d8376a904019";
#
# sha256 = "01c2f4mj4ahir0sxk9kxbymg2pki1pc9a3y6r9x6ridry75fzb8h";
# };
#
# pkgs = import nixpkgs {};
#
# in
# ...
# ```
{ rev, sha256 }:
with rec {
system = builtins.currentSystem;
tarball = import <nix/fetchurl.nix> {
url = "https://github.com/NixOS/nixpkgs/archive/${rev}.tar.gz";
inherit sha256;
};
script = builtins.toFile "nixpkgs-unpacker" ''
"$coreutils/mkdir" "$out"
cd "$out"
"$gzip" --decompress < "$tarball" | "$tar" --extract --strip-components=1
'';
builtin-paths = import <nix/config.nix>;
nixpkgs = builtins.derivation {
name = "nixpkgs-${builtins.substring 0 6 rev}";
builder = builtin-paths.shell;
args = [ script ];
inherit tarball system;
inherit (builtin-paths) tar gzip coreutils;
};
};
nixpkgs The only thing missing is suggesting the user to upgrade to a better script for Nix 1.12 and later, mainly because I'd like to first complete the better version of the script so that the suggestion can link to it |
You missed a semicolon in the comment at the top (after the closing brace before The version for Nix 1.12 would probably look like
|
It just occurred to me: since @edolstra doesn't want to backport the
and it would work on both Nix 1.11 and 1.12. EDIT: actually, in light of the fact that the |
I really like the ideas put forth in this conversation. One trick that I think is worth mentioning as well is to patch the pinned nixpkgs with a list of patches. Patching is usually less work than rebasing all those commits on some branch you have to maintain. For example this is the # This expression returns the nix/store path to our version of nixpkgs.
# It ensures that all engineers use the same revision of nixpkgs.
# It is used in the following files:
#
# * ./default.nix:
# import (import ./nixpkgs.nix) { ... }
#
# Note that this expression requires that "nixpkgs" can be found in the
# NIX_PATH. However, since this expression defines the path to nixpkgs we have a
# bootstrapping problem. To bootstrap a workstation we will create an install-cd
# which has a copy of nixpkgs in its nix/store and set its NIX_PATH to that
# store path.
#
# We also have the bootstrapping problem on hydra.lumi.guide. Here we "solve" it
# by adding nixpkgs as an input like:
#
# nixpkgs "Git checkout" https://github.com/LumiGuide/nixpkgs.git f806ddf
#
# This technique was inspired by the article:
#
# Reproducible Development Environments by Rok Garbas
# https://garbas.si/2015/reproducible-development-environments.html
let pkgs = import <nixpkgs> {};
nixpkgsVersion = pkgs.lib.importJSON ./nixpkgs-version.json;
nixpkgs = pkgs.fetchFromGitHub {
owner = "NixOS";
repo = "nixpkgs";
rev = nixpkgsVersion.rev;
sha256 = nixpkgsVersion.sha256;
};
patches = [
# elasticsearch-5.4
(pkgs.fetchpatch {
url = "https://github.com/LumiGuide/nixpkgs/commit/7ad19877d41592d45ed8723818238d1c17550644.patch";
sha256 = "15j2pgvp577ykbf6zvx57kygpfbmp8n7nwyi7pizcix4hasz46bx";
})
# Kibana-5.4, logstash-5.4 and beats-5.4
(pkgs.fetchpatch {
url = "https://github.com/LumiGuide/nixpkgs/commit/c0f9714afd7be6e045252d88c485eef9a5278129.patch";
sha256 = "089b8pb02dx390mh3g135d80hclg9lz6xwqyd5ivcbrxqchh18wd";
})
# ELK: 5.4.0 -> 5.4.2
(pkgs.fetchpatch {
url = "https://github.com/NixOS/nixpkgs/commit/946952a10891c6e6764abf546cc8cc1f3f54f668.patch";
sha256 = "15a37ydlnqvsjr3r0351h3x4zk579i62dh6qf002f7dzr2lf0rrs";
})
# ELK: 5.4.2 -> 5.5.2
(pkgs.fetchpatch {
url = "https://github.com/NixOS/nixpkgs/commit/09bde4a2cdeed27dca8a145502d405d4c986a304.patch";
sha256 = "088x4rm5gh31pncmjdvrws07nixdnbybb9ydmn35ldsyzdnalkp3";
})
# ELK: 5.5.2 -> 5.6.1
(pkgs.fetchpatch {
url = "https://github.com/NixOS/nixpkgs/commit/ea8e7ed1e32b40fc90173e35d5b8afe747d84e38.patch";
sha256 = "1js6cg6gik42j86ljmh6dfgsqpnzipkyx4jmzq5mb7qv8dh8rcqq";
})
# journalbeat: 5.1.2 -> 5.4.1
(pkgs.fetchpatch {
url = "https://github.com/NixOS/nixpkgs/commit/766cbda1f32112633f265d8ad0aad3252346dfb0.patch";
sha256 = "1mq3rhxyf15h29n9z94zb83n70lg6s7ywqrrbskqx10kv2ha4asj";
})
# journalbeat: 5.4.1 -> 5.5.0
(pkgs.fetchpatch {
url = "https://github.com/NixOS/nixpkgs/commit/4a93ce179dc0026b5c021b9fe6aa89b93527bba5.patch";
sha256 = "0hlirh46rpq8p5j29syii38yam98x79vx2h4yfxkahf7yz8mj6nw";
})
# journalbeat: 5.5.0 -> 5.6.0
(pkgs.fetchpatch {
url = "https://github.com/NixOS/nixpkgs/commit/8d07b99b96661a60e14141824bf891e10776cf85.patch";
sha256 = "0qs2a30i5p2m683a4wbmxwg7272am1ssbib0p1rmkmxprv4maik4";
})
# Fixes SSL dependency that the central server needs
(pkgs.fetchpatch {
url = "https://github.com/NixOS/nixpkgs/commit/7c0f6f4be53453e08a1d4a27cd40a46c4e920ce8.patch";
sha256 = "1r2nm3x5w9rg9772865i228rag7shigfw0da7hcwd1vh019wgk6x";
})
# Adds the postage package and NixOS module
(pkgs.fetchpatch {
url = "https://github.com/NixOS/nixpkgs/commit/943c78b10d8ed4418dbb6fb9a89e6f416af511d5.patch";
sha256 = "13vhrkihbw7nrwplxlhfvwm493h53y7yzs8j5nsxnhv70hhpiwc4";
})
# postage: 3.2.17 -> 3.2.18
(pkgs.fetchpatch {
url = "https://github.com/NixOS/nixpkgs/commit/d7f2c1f2b81a8b1c15cbe3d234cf8abc73bc5a2f.patch";
sha256 = "1limynv4aj4i3z704x14a38g5x73xakdpswf8h4amy6rlrmg3n12";
})
# Adds flockit
(pkgs.fetchpatch {
url = "https://github.com/NixOS/nixpkgs/commit/872d8fc5c9f4f8b5d31cb5a2a886e986dc0abd52.patch";
sha256 = "0r9gc97mjq1maxica8gyiikd48cy5mm2b59mgp5h6p489alndjnb";
})
# strongswan: 5.5.3 -> 5.6.0
(pkgs.fetchpatch {
url = "https://github.com/NixOS/nixpkgs/commit/a8868900ab2f063005d812a484e22c8c6f3b4364.patch";
sha256 = "0z8v45wxgwjl35ydgr4vsbbn38k80cjsaqhwgz80zv0kipxzkldl";
})
];
in pkgs.runCommand ("nixpkgs-" + nixpkgsVersion.rev) {inherit nixpkgs patches; } ''
cp -r $nixpkgs $out
chmod -R +w $out
for p in $patches ; do
echo "Applying patch $p"
patch -d $out -p1 < "$p"
done
'' |
@taktoa: Instead of an assertion, why not use an # This file provides a way to fetch `nixpkgs` with an empty `NIX_PATH`. This
# comes in handy if you want to remove impure references to the `NIX_PATH` from
# your code base
#
# To use this code, save this to a `fetchNixpkgs.nix` file within your project
# and then referencing this file like so:
#
# ```nix
# let
# fetchNixpkgs = import ./fetchNixpkgs.nix;
#
# nixpkgs = fetchNixpkgs {
# rev = "76d649b59484607901f0c1b8f737d8376a904019";
#
# sha256 = "01c2f4mj4ahir0sxk9kxbymg2pki1pc9a3y6r9x6ridry75fzb8h";
# }
#
# pkgs = import nixpkgs {};
#
# in
# ...
# ```
{ rev, sha256 }:
if 0 <= builtins.compareVersions builtins.nixVersion "1.12"
then
builtins.fetchTarball {
url = "https://github.com/NixOS/nixpkgs/archive/${rev}.tar.gz";
inherit sha256;
}
else
# `builtins.fetchTarball` only accepts a `sha256` argument in Nix version 1.12
# or later
with rec {
system = builtins.currentSystem;
tarball = import <nix/fetchurl.nix> {
url = "https://github.com/NixOS/nixpkgs/archive/${rev}.tar.gz";
inherit sha256;
};
script = builtins.toFile "nixpkgs-unpacker" ''
"$coreutils/mkdir" "$out"
cd "$out"
"$gzip" --decompress < "$tarball" | "$tar" --extract --strip-components=1
'';
builtin-paths = import <nix/config.nix>;
nixpkgs = builtins.derivation {
name = "nixpkgs-${builtins.substring 0 6 rev}";
builder = builtin-paths.shell;
args = [ script ];
inherit tarball system;
inherit (builtin-paths) tar gzip coreutils;
};
};
nixpkgs Also, it's not clear why you have the more complex version test @basvandijk: I can also to add additional documentation for how to patch |
@Gabriel439 The more complex version test is because of the following issue with the
|
@taktoa: I think it's better to be conservative about when we enable the The code is sufficiently self-describing that users can easily delete the pre-Nix-1.12 fallback case if they care enough |
@Gabriel439 From some conversation with @cleverca22 I have been informed that the strings contained in # This file provides a way to fetch `nixpkgs` with an empty `NIX_PATH`. This
# comes in handy if you want to remove impure references to the `NIX_PATH` from
# your code base
#
# To use this code, save this to a `fetchNixpkgs.nix` file within your project
# and then referencing this file like so:
#
# ```nix
# let
# fetchNixpkgs = import ./fetchNixpkgs.nix;
# nixpkgs = fetchNixpkgs {
# rev = "76d649b59484607901f0c1b8f737d8376a904019";
# sha256 = "01c2f4mj4ahir0sxk9kxbymg2pki1pc9a3y6r9x6ridry75fzb8h";
# };
# pkgs = import nixpkgs { config = {}; };
# in ...
# ```
{ rev, sha256 }:
if 0 <= builtins.compareVersions builtins.nixVersion "1.12"
then
builtins.fetchTarball {
url = "https://github.com/NixOS/nixpkgs/archive/${rev}.tar.gz";
inherit sha256;
}
else
# `builtins.fetchTarball` only accepts a `sha256` argument in Nix version or later
with rec {
system = builtins.currentSystem;
tarball = import <nix/fetchurl.nix> {
url = "https://github.com/NixOS/nixpkgs/archive/${rev}.tar.gz";
inherit sha256;
};
builtin-paths = import <nix/config.nix>;
nixpkgs = builtins.derivation {
name = "nixpkgs-${builtins.substring 0 6 rev}";
builder = builtins.storePath builtin-paths.shell;
args = [
(builtins.toFile "nixpkgs-unpacker" ''
"$coreutils/mkdir" "$out"
cd "$out"
"$gzip" --decompress < "$tarball" | "$tar" --extract --strip-components=1
'')
];
inherit tarball system;
tar = builtins.storePath builtin-paths.tar;
gzip = builtins.storePath builtin-paths.gzip;
coreutils = builtins.storePath builtin-paths.coreutils;
};
};
nixpkgs EDIT: forgot to add |
Alright, I'll use the version with |
The most minimal version I could come up with for nixcon2017 (so it fits into the slides) is: let
spec = builtins.fromJSON (builtins.readFile ./nixpkgs-src.json);
src = import <nix/fetchurl.nix> {
url = "https://github.com/${spec.owner}/${spec.repo}/archive/${spec.rev}.tar.gz";
inherit (spec) sha256;
};
nixcfg = import <nix/config.nix>;
in builtins.derivation {
system = builtins.currentSystem;
name = "${src.name}-unpacked";
builder = builtins.storePath nixcfg.shell;
inherit src;
args = [
(builtins.toFile "builder" ''
$coreutils/mkdir $out
cd $out
$gzip -d < $src | $tar -x --strip-components=1
'')
];
coreutils = builtins.storePath nixcfg.coreutils;
tar = builtins.storePath nixcfg.tar;
gzip = builtins.storePath nixcfg.gzip;
} and
I don't think conditionalizing on 1.12 is a good idea, best to just rewrite to hash usage once 1.12 is out a few weeks. |
@domenkozar not that it matters much, but there is a subtle bug in that code:
should be
I think the only reason it works without that is because of the |
Thanks! |
Alright, I just added this to the NixOS wiki: How to fetch Nixpkgs with an empty NIX PATH ... and I added it to the Nix, Nixpkgs, and Cookbook categories for discoverability |
@Gabriel439 would you also consider adding how to use it with bash scripts to set NIX_PATH if needed to pinned version? It's useful for interactive sessions.
Usage:
|
@domenkozar: Why not just: $ export NIX_PATH=nixpkgs=$(nix-build --no-out-link ./nixpkgs.nix) ... where import ./fetchNixpkgs.nix { rev = "..."; sha256 = "..."; } |
This assumes |
@domenkozar: I still don't see the benefit. Using your solution you would still need the correct path to the |
|
@cleverca22: That's still pushing the problem one level up: you need the correct path to the script that sources the Also, stepping back, why do we even need to set the |
yeah, i would rather just import fetch-nixpkgs.nix from another nix file (where relative paths work without any fuss) and then just not refer to |
@Gabriel439 because in real world you want to override for example https://github.com/NixOS/nixops/blob/master/release.nix#L7 |
My intuition here is still to keep as much logic out of Bash as possible and to move the logic into Nix |
patching nixops to accept a pkgs set would allow this to work with an empty NIX_PATH and no bash logic |
NixOS/nixops@8389ead and nixops has been patched 👍 |
For anyone coming late to this discussion and just wondering what the simplest way to achieve this with nix2 is, all you need to do is put these couple lines at the top of your nix file (no other files, overlays,
which will now work with |
This adds a new section to the Nixpkgs manual documenting how to bootstrap and
pin
nixpkgs
without the use of theNIX_PATH
The main reasons for including this code in the documentation (as opposed to
a file in the
nixpkgs
repository were):Credit to @taktoa who authored this code and Awake Security (that sponsored this work and
approved open sourcing it). I'm only upstreaming the work that was already done
Motivation for this change
The
NIX_PATH
is a common source of non-reproducibility in commercial Nix deployments.This fixes that by not only pinning
nixpkgs
but doing so in such a way that the pinningprocess does not itself depend on the
NIX_PATH
Things done
I built this on
macOS
:... and opened up the generated documentation to verify that the section
rendered correctly
cc: @peti and @basvandijk, who I mentioned this trick to earlier today