A set of modules to perform a fully automated installation of a customised NixOS system over the network via PXE using an NFS-mounted root file system.
The system is composed of two components: the module
modules/install-image.nix
, which creates an install
image of a NixOS system and the module
modules/installer-nfsroot.nix
, which creates a generic
installer for such an image.
An install image is a tar
archive of a (almost) complete NixOS
system created from a given nixpkgs
source tree in the form of a
NixOS channel and an arbitrary NixOS configuration. The image
contains the closure of the corresponding top-level system
configuration as well as the channel from which it was created. This
is essentially the result of an invocation of the nixos-install
command.
The installer provides the files required for a PXE-based network boot of the client system on which the install image is going to be installed. It mounts a generic root file system over NFS which contains a copy of the install image and some client-specific configuration parameters. The installer then partitions and formats the local disk, unpacks the install image onto it, performs the final activation of the NixOS configuration and reboots the client into the new system.
The install image is completely self-contained in the following sense:
no contents is transferred from the installer and all components that
are being created during the final acitvation are derived from the
install image within a chroot
environment. In particular, the NixOS
versions from which the installer and install image are derived are
completely independent.
Because the install image contains the closure of the system configuration, all components needed to activate the final configuration on the installed system should already be present and there should not be any need to fetch source packages or substitutes at that point. However, the final configuration necessarily differs from the configuration that was used to create the system closure due to hardware-dependent features that are not known ahead of the actual installation procedure.
The hardware-dependent configuration is generated by executing the
nixos-generate-config
command, which creates the file
/etc/nixos/hardware-configuration.nix
, overwriting the genereic
hardware configuration used for the creation of the install image.
The resulting differences may lead to the building of store paths
which are not part of the original system closure.
Some of those store paths can be created locally (e.g. the file system
table for the root device) but others may require fetching of sources
or subsitutes, like the derivation of the initrd
RAM disk used to
bootstrap the system due to the inclusion of kernel modules that were
not required by the original configuration.
For this reason, a number of additional store paths that are not part of the initial system closure are included in the install image. The list of these paths has been determined heuristically and is not guaranteed to be sufficient for all possible installation targets.
In any case, as long as the system being installed has network connectivity to the binary cache of the NixOS channel provided by the install image and/or the systems that provide the sources for all derivations that need to be built, the installation process will always succeed.
This section provides a walk-through of the steps needed to install a system using default settings for the creation of the installer and install image. The documentation section describes the installer and install image modules in detail.
To create an installer, clone into the nixos-pxe-installer
repository and create the file installer.nix
containing the Nix
expression for the desired configuration of the installer as described
in the section about installer examples, e.g.
{ system ? "x86_64-linux" }:
with import <nixpkgs> { inherit system; };
with lib;
let
nfsroot = (import <nixpkgs/nixos/lib/eval-config.nix> {
inherit system;
modules = [ modules/installer-nfsroot.nix ];
}).config.system.build.nfsroot;
in
with nfsroot;
[ nfsRootTarball bootLoader ]
Then execute nix-build installer.nix
in the top-level directory.
This will create two derivations containing the components of the
installer (in this example, all derivations have already been built in
a previous invocation)
$ nix-build installer.nix
/nix/store/p89a676j5gv6bmwwqy0ar67p6y8csyrd-nfsroot
/nix/store/6bxm87wczxgvm9bah6sy8gxlr5w4vpm6-grub-efi-bootloader
$ ls -l result*
lrwxrwxrwx 1 gall users 51 Sep 6 10:33 result -> /nix/store/p89a676j5gv6bmwwqy0ar67p6y8csyrd-nfsroot
lrwxrwxrwx 1 gall users 63 Sep 6 10:33 result-2 -> /nix/store/6bxm87wczxgvm9bah6sy8gxlr5w4vpm6-grub-efi-bootloader
Make a copy of the files
result/nfsroot.tar.xz
result-2/boot-loader.tar.xz
In this example, we use the following assignments
- IP subnet where the install client resides
- Subnet 192.0.2.0/24
- Gateway 192.0.2.1
- Install client
- MAC address
01:02:03:04:05:06
- Fixed IP address 192.0.2.2
- MAC address
- TFTP server address 198.51.100.1
- NFS server address 198.51.100.2
- 1st DNS server address 198.51.100.3
- 2nd DNS server address 198.51.100.4
- DNS domain
example.org
- Location of the Grub boot loader and Linux kernel on the TFTP server:
/srv/tftp/nixos
- Location of the root filesystem on the NFS server:
/srv/nixos/nfsroot
The following configuration is for a stock ISC DHCP server. It supplies the install client with its IP configuration, the information needed to find and load the Grub boot loader and Linux kernel as well as from where to mount the root file system over NFS.
option domain-name "example.org";
option domain-name-servers 198.51.100.3, 198.51.100.4;
## Generate a hostname option (#12) from a host declaration
use-host-decl-names on;
## The current grub2 networking stack does not set the DHCP magic
## cookie vendor class, i.e. it creates a pure BOOTP requests. It
## still expects a DHCP response with particular options, which is
## totally broken. This option forces the server to reply with DHCP,
## also see "dhcp-parameter-request-list" below.
always-reply-rfc1048 on;
## Only the EFI x64 client system architecture is currently supported
option arch code 93 = unsigned integer 16;
if option arch = 00:07 {
filename "nixos/bootx64.efi";
} else {
## This suppresses the "boot file name" BOOTP option
## in the reply, which causes non-EFIx64 systems to stall
filename "";
}
subnet 192.0.2.0 netmask 255.255.255.255 {
next-server 198.51.100.1;
option routers 192.0.2.1;
option tftp-server-name "198.51.100.1"; # This could also be a domain name
option root-path "192.51.100.2:/srv/nixos/nfsroot,vers=3,tcp,rsize=32768,wsize=32768,actimeo=600"; # Option 17
## Required for the borked grub2 bootp mechanism. We need to
## include all required options, since options not on this list
## are suppressed, even when the client asks for them.
option dhcp-parameter-request-list 1,3,6,12,15,17,66; # subnet mask, router, DNS server, hostname, domain, root-path, tftp-server
}
host install-client { hardware ethernet 01:02:03:04:05:06;
fixed-address 192.0.2.2; }
A TFTP server is needed to serve the boot loader and Linux kernel. In
this example, we use
tftp-hpa
and
assume that the path /srv/tftp/nixos
exists and is readable by user
nobody
.
Unpack the archive boot-loader.tar.xz
(see section
Building) to /srv/tftp/nixos
. It should
contain the following files
bootx64.efi
The EFI boot loaderbzImage
The Linux kernel fetched by GRUBgrub.cfg
The generic GRUB configurationload-kernel.cfg
A GRUB configuration file loaded fromgrub.cfg
which loadsbzImage
with the standard kernel parameters
Then start the deamon as
in.tftpd --listen --address 198.51.100.1:69 --secure /srv/tftp
If you need to, you can modify the boot loader at a later time
without going through the nix-build
process (e.g. when you don't have access to
a NixOS system).
Create the path /srv/nixos/nfsroot
and unpack nfsroot.tar.xz
(see
section Building) in it
# mkdir -p /srv/nixos/nfsroot
# cd /srv/nixos/nfsroot
# tar xaf /path-to/nfsroot.tar.xz
Export /srv/nixos/nfsroot
read-only via NFS by adding the following
line to /etc/exports
(or the equivalent on your system)
/srv/nixos/nfsroot 192.0.2.0/24(async,ro,no_subtree_check,no_root_squash)
Create the file image.nix
in the top-level directory containing the
configuration for the install image as described in the section about
install image examples, e.g.
$ cat image.nix
with import <nixpkgs> {};
let
build = (import <nixpkgs/nixos/lib/eval-config.nix> {
inherit system;
modules = [ modules/install-image.nix ];
}).config.system.build;
in
with build.installImage;
[ tarball config ]
then execute nix-build image.nix
$ nix-build image.nix
/nix/store/ypm486qd5pd0k7cnikc5dsslbi4ziky2-install-tarball-nixos-18.03.133154.f094fd6379b
/nix/store/26bakcd6si5fkkw249lb01w44yj88zly-install-config
$ ls result*
result:
nixos.tar.gz
result-2:
config
Copy the tarball result/nixos.tar.gz
to
/srv/nixos/nfsroot/installer
on the TFTP server and create a
symbolic link to it from /srv/nixos/nfsroot/installer/nixos-image
# cp /path-to/nixos.tar.gz /srv/nixos/nfsroot/installer
# ln -s ./nixos.tar.gz /srv/nixos/nfsroot/installer/nixos-image
Copy the configuration file result-2/config
to /srv/nixos/nfsroot/installer/config
.
Configure the client's EFI boot loader to perform a PXE boot on the desired interface and initiate a system boot. With all previous steps completed, the client should be installed and rebooted into the new system automatically.
The main ingredients to the creation of an install image are a copy of
a nixpkgs
source tree and a NixOS configuration directory structured
like /etc/nixos
.
The nixpkgs
source tree can either be a NixOS channel or a
checkout of the nixpkgs
Git repository, which will be transformed
into such a channel by the module. Please refer to the
appendix for details.
Pleas refer to the options declaration in module/install-image.nix
for a full description of the available options. Alternatively, you
can run the command
nix-build module-manpages.nix -A installImage && man result/share/man/man5/configuration.nix.5
in the repository to get a summary as a pseudo-manpage.
The actual image (which really isn't a disk image but a tarball) is
created by lib/make-install-image.nix
. The main ingredient is the
evaluation of the desired NixOS configuration in the context of the
supplied channel (simplified):
config = (import (channel + "/nixos/nixos/lib/eval-config.nix") {
modules = [ (nixosConfigDir + "/configuration.nix") ];
}).config;
The Nix store of the install image is populated with the closures of
config.system.build.toplevel
and channel
as well as a list of
additional packages to make the install image self-contained as
described in the introduction.
The module produces three derivations, which are available in the
attribute set config.system.build.installImage
-
channel
. The NixOS channel containing thenixpkgs
sources from theinstallImage.nixpkgs.path
option -
tarball
. Thetar
archive containing the install image -
config
. A shell-script containing configuration parameters derived from theinstallImage
module options that need to be propagated to the installer. Currently, this includes- The device on which to install the root file system of the
install client (
installImage.rootDevice
) - The character used to separate the partition number from the device
name (
installImage.partitionSeparator
, defaults to an empty string)
- The device on which to install the root file system of the
install client (
The tarball
and config
derivations each contain a single file
called nixos.tar.gz
and config
, respectively. These files need to
be copied into the installer
directory of the NFS root file system
used by the installer.
In the following examples, we assume that the current directory is a
checkout of the nixos-pxe-installer
repository.
The most basic usage is represented by the following code
$ cat default.nix
with import <nixpkgs> {};
let
installImage = (import <nixpkgs/nixos/lib/eval-config.nix> {
modules = [ modules/install-image.nix ];
}).config.system.build.installImage;
in
with installImage;
[ tarball config ]
which can be executed by nix-build
. This will build an install
image from the nixos
channel of the current system based on the
default value <nixpkgs>
of the option installImage.nixpkgs.path
,
as can be seen from the channel version
$ nixos-version
18.03.133154.f094fd6379b (Impala)
[gall@nixos:~/ALX/installer]$ nix-build image.nix
/nix/store/ypm486qd5pd0k7cnikc5dsslbi4ziky2-install-tarball-nixos-18.03.133154.f094fd6379b
/nix/store/26bakcd6si5fkkw249lb01w44yj88zly-install-config
To create a clone of the current system, one could use
with import <nixpkgs> {};
let
installImageConfig = {
installImage = {
nixosConfigDir = /etc/nixos;
};
};
installImage = (import <nixpkgs/nixos/lib/eval-config.nix> {
modules = [ modules/install-image.nix
installImageConfig ];
}).config.system.build.installImage;
in
with installImage;
[ tarball config ]
To create an install image for the current version of the NixOS
release 18.03, one would first clone into the release-18.03
branch
of the nixpkgs
repository
$ git clone -b release-18.03 https://github.com/NixOS/nixpkgs.git
Cloning into 'nixpkgs'...
remote: Counting objects: 1304345, done.
remote: Compressing objects: 100% (61/61), done.
remote: Total 1304345 (delta 21), reused 20 (delta 10), pack-reused 1304274
Receiving objects: 100% (1304345/1304345), 820.53 MiB | 2.12 MiB/s, done.
Resolving deltas: 100% (883569/883569), done.
Checking out files: 100% (14885/14885), done.
Then use
with import <nixpkgs> {};
let
installImageConfig = {
installImage = {
nixpkgs.path = builtins.path { path = ./nixpkgs; };
};
};
installImage = (import <nixpkgs/nixos/lib/eval-config.nix> {
modules = [ modules/install-image.nix
installImageConfig ];
}).config.system.build.installImage;
in
with installImage;
[ tarball config ]
to build the image. Note that nixpkgs.path
must be an object in the
Nix store. In this case, the Git repository, which resides at an
arbitrary location on the local file system, is copied to the Nix
store through the builtin function path
. Alterantively, once could
also specify the Git repository itself as the source of the nixpkgs
tree. The following Nix expression is equivalent (at the time of
writing) to the above
with import <nixpkgs> {};
let
installImageConfig = {
installImage = {
nixpkgs.path = fetchgit {
url = "https://github.com/NixOS/nixpkgs.git";
deepClone = true;
rev = "refs/heads/release-18.03";
sha256 = "1hahhmnsyhgg20mvf7a5p0y2y5lazs7jnz2lf5gw95pdx56aa6rz";
};
};
};
installImage = (import <nixpkgs/nixos/lib/eval-config.nix> {
modules = [ modules/install-image.nix
installImageConfig ];
}).config.system.build.installImage;
in
with installImage;
[ tarball config ]
The attribute deepClone = true
is required to fetch the entire
history of the repository, which is necessary to determine the version
number via git rev-list
as explained in the
appendix. Unfortunately, this takes a very long
time because fetchgit
performs a git repack
in an attempt to
create a deterministic Git repository (i.e. keep the sha256
hash
constant for each invocation of a fetchgit
of the same Git
revision). Also, it appears that it is not as deterministic as it
should be, i.e. the hash value may depend on the Git version.
To change the serial device and baud rate to use by the kernel, use the following configuration snippet in any of the examples above
installImageConfig = {
installImage = {
serial = {
unit = 1;
speed = 9600;
};
};
};
The installer currently supports only PXE-based network boots of UEFI systems. The boot sequence is as follows.
- Client PXE stack issues a DHCP "discover" request
- DHCP server responds with IP configuration,
IP address of a TFTP server (
next server
BOOTP option) from which to fetch the boot loader and the file name of the boot loader image (boot file name
BOOTP option) - Client fetches the boot loader and executes it
The boot loader used by the installer is based on the EFI variant of Grub2. It performs essentially the same DHCP request as the PXE loader did before to discover IP parameters and the address of the TFTP server.
Next, it loads the file grub.cfg
from the TFTP server in the same
directory from where the boot loader was obtained. The standard
grub.cfg
file supplied by the installer tries to load the following
files in the given order
load-kernel-<mac-address>.cfg
load-kernel-<ip-address>.cfg
load-kernel-<hostname>.cfg
load-kernel.cfg
Here, <mac-address>
is the MAC address of the interface on which the
DHCP information was obtained, <ip-address>
is the IPv4 address
configured on that interface and <hostname>
is the host name
supplied by the DHCP server (if no host name is supplied, that step is
skipped). Each file is attempted to be executed by Grub with the
configfile
command.
The only file supplied by default is load-kernel.cfg
, which loads
the Linux kernel bzImage
with a particular set of kernel parameters
as explained below.
The other cfg
files can be created on demand to customise the Linux
image and kernel parameters depending on the client being installed.
Note that control is handed back to grub.cfg
after the file has been
processed. This can lead to the undesired result that another kernel
is loaded from one of the remaining files, which will override all
previously loaded kernels. To avoid this, the custom cfg
files
should always terminate with the boot
command (load-kernel.cfg
doesn't need this because there is an implicit boot
command at the
end of grub.cfg
).
The kernel is started with the options ip=::::::dhcp:: root=/dev/nfs
to initiate yet another round of DHCP requests. On a multi-homed host,
the kernel will auto-detect all operational interfaces and issue a
DHCP request on each of them. It will only configure the interface on
which the DHCP transaction is finished first.
The DHCP request includes option #17, which asks for the address, path and parameters of an NFS-exported root file system. This option should look like
<ip-address>:<path>,vers=3,tcp,rsize=32768,wsize=32768,actimeo=600
where <ip-address>
is the literal IPv4 address of the NFS server and
<path>
is the file system path from which the root file system can
be mounted by the kernel. The file system should be exported
read-only.
Once the read-only root file system has been mounted, it is transformed into a writeable file system using unionfs/fuse and control is transferred to a shell script that performs the actual installation, which performs the following actions.
-
Source the installer configuration from the files in the given order
/installer/config-<mac-address
/installer/config-<ip-address>
/installer/config-<hostname>
/installer/config
-
Create a new partition table on the disk specified in the
rootDevice
configuration variable containing two partitions- Type FAT32, size 512MiB, used as EFI boot partition
- Type EXT4, rest of disk except 4GiB at the end (may be used as swap later on but remains unused by the installer), used as root partition
-
Create filesystems on the partitions (
vfat
andext4
, respectively) -
Label the
ext4
partition asnixos
-
Mount the root partition as
/mnt
-
Unpack the install image onto
/mnt
. The image is expected to be atar
file located at/installer/nixos-image
, which is typically a symbolic link to the filenixos.tar.gz
created byinstall-image.nix
. The image is extracted with the--auto-compress
option, i.e. the compression program is determined from the extension of the name of the install image (possibly after de-referencing symbolic links) -
The hardware-specific NixOS configuration is created by executing
nixos-generate-config --root=/mnt
. This will generate the file/mnt/etc/nixos/hardware-configuration.nix
. If/mnt/etc/nixos/configuration.nix
does not exist, it will be created as well and import./hardware-configuration.nix
automatically. If/mnt/etc/nixos/configuration.nix
has been pre-created when the install image was generated, it will not be touched bynixos-generate-config
. In this case, it is important that it is configured to import./hardware-configuration.nix
. -
The NixOS configuration of the final system is generated by executing
nix-env -p /nix/var/nix/profiles/system -f '<nixpkgs/nixos>' --set -A system
in a chroot environment on the root partition. -
The final configuration is activated by executing
/nix/var/nix/profiles/system/bin/switch-to-configuration boot
in chroot on the root partition, which will generate the boot loader and configure the EFI boot loader to boot from it. -
Reboot. The EFI boot loader will automatically prefer the fresh root partition (no need to change boot poriorities manually from the BIOS)
Pleas refer to the options declaration in module/install-image.nix
for a full description of the available options. Alternatively, you
can run the command
nix-build module-manpages.nix -A installer && man result/share/man/man5/configuration.nix.5
in the repository to get a summary as a pseudo-manpage.
In the following examples, we assume that the current directory is a
checkout of the nixos-pxe-installer
repository.
To create an installer with the default settings, put the following
Nix expression in the file default.nix
{ system ? "x86_64-linux" }:
with import <nixpkgs> { inherit system; };
with lib;
let
nfsroot = (import <nixpkgs/nixos/lib/eval-config.nix> {
inherit system;
modules = [ modules/installer-nfsroot.nix ];
}).config.system.build.nfsroot;
in
with nfsroot;
[ nfsRootTarball bootLoader ]
and execute nix-build
, which will create the NFS root file system
tarball and boot loader tarball in the Nix store paths pointed to by
the symbolic links result
and result-2
, respectively
$ nix-build installer.nix
/nix/store/p89a676j5gv6bmwwqy0ar67p6y8csyrd-nfsroot
/nix/store/6bxm87wczxgvm9bah6sy8gxlr5w4vpm6-grub-efi-bootloader
$ ls -l result*
lrwxrwxrwx 1 gall users 51 Sep 6 13:47 result -> /nix/store/p89a676j5gv6bmwwqy0ar67p6y8csyrd-nfsroot
lrwxrwxrwx 1 gall users 63 Sep 6 13:47 result-2 -> /nix/store/6bxm87wczxgvm9bah6sy8gxlr5w4vpm6-grub-efi-bootloader
$ ls result*
result:
nfsroot.tar.xz
result-2:
boot-loader.tar.xz
The following example sets the unit of the serial device used by the boot loader and the kernel to 1
{ system ? "x86_64-linux" }:
with import <nixpkgs> { inherit system; };
with lib;
let
installerConfig = {
nfsroot.serial = {
unit = 1;
};
};
nfsroot = (import <nixpkgs/nixos/lib/eval-config.nix> {
inherit system;
modules = [ modules/installer-nfsroot.nix
installerConfig ];
}).config.system.build.nfsroot;
in
with nfsroot;
[ nfsRootTarball bootLoader ]
The preferred method for generaing the boot loader is through the
bootLoader
derivation of the install-nfsroot.nix
module. Apart
from the boot loader itself (the file bootx64.efi
), it also contains
a bootable kernel, grub.cfg
and load-kernel.cfg
as described
above. The cfg
files can be edited and changed as desired.
However, the boot loader only contains the Grub modules required by
the default cfg
files. The exact command line that was used to
genereate the default boot loader is contained in the shell script
generate
, which is also part of the boot loader tarball. It looks
like this:
grub-mkimage -O x86_64-efi -o ./bootx64.efi -p '(tftp)' \
serial terminal net efinet tftp normal echo eval test linux configfile reboot
The second line lists all modules that are compiled into the boot loader. A new boot loader can be generated by modifying this script and executing it from the same directory (with the assumption that a compatible Grub2 with EFI enabled is available in the standard search path).
Caveat: the boot loader is tied to the NFS root file system through
the init=
kernel option in grub.cfg
. It is important to keep this
option unchanged and always use the NFS root file system from the same
run of nix-build
.
In the context of the install-image.nix
module, a channel is defined
as the instantiation of a NixOS
channel as
the result of performing a nix-channel --upgrade
operation. This is
a store path that contains a reference to a binary cache URL and a
copy of a nixpkgs
source tree. For example, consider a system with
the following channel named nixos
# nix-channel --list
nixos https://nixos.org/channels/nixos-18.03
Instantiation of the channel results in the creation of a new
generation of the root user's profile called channels
(the file
manifest.nix
is irrelevant in this context and is actually no longer
used by newer releases of NixOS):
# ls -l /nix/var/nix/profiles/per-user/root/channels/
total 12
lrwxrwxrwx 1 root root 88 Jan 1 1970 binary-caches -> /nix/store/z0gsgq0309r5j3ks7aqzlsibcipa0r1z-nixos-18.03.133188.8b92a4e6004/binary-caches
lrwxrwxrwx 1 root root 60 Jan 1 1970 manifest.nix -> /nix/store/45j857v08j21ybb2hb08cky2a2qvl2a6-env-manifest.nix
lrwxrwxrwx 1 root root 80 Jan 1 1970 nixos -> /nix/store/z0gsgq0309r5j3ks7aqzlsibcipa0r1z-nixos-18.03.133188.8b92a4e6004/nixos
The symbolik links binary-caches
and nixos
point to the store path
/nix/store/z0gsgq0309r...-nixos-18.03.133188.8b92a4e6004
, which
contains the URL of the binary cache associated with the channel
# cat /nix/store/z0gsgq0309r...-nixos-18.03.133188.8b92a4e6004/binary-caches/nixos
https://cache.nixos.org
and a copy of a full nixpkgs
source tree
# ls -la /nix/store/z0gsgq0309r...-nixos-18.03.133188.8b92a4e6004/nixos
total 3000
dr-xr-xr-x 8 root root 4096 Jan 1 1970 .
dr-xr-xr-x 4 root root 4096 Jan 1 1970 ..
-r--r--r-- 1 root root 1558 Jan 1 1970 COPYING
-r--r--r-- 1 root root 557 Jan 1 1970 default.nix
dr-xr-xr-x 4 root root 4096 Jan 1 1970 doc
-r--r--r-- 1 root root 677 Jan 1 1970 .editorconfig
dr-xr-xr-x 2 root root 4096 Jan 1 1970 .github
-r--r--r-- 1 root root 193 Jan 1 1970 .gitignore
-r--r--r-- 1 root root 40 Jan 1 1970 .git-revision
dr-xr-xr-x 4 root root 4096 Jan 1 1970 lib
dr-xr-xr-x 3 root root 4096 Jan 1 1970 maintainers
dr-xr-xr-x 7 root root 4096 Jan 1 1970 nixos
lrwxrwxrwx 1 root root 1 Jan 1 1970 nixpkgs -> .
dr-xr-xr-x 17 root root 4096 Jan 1 1970 pkgs
-r--r--r-- 1 root root 3002368 Jan 1 1970 programs.sqlite
-r--r--r-- 1 root root 2030 Jan 1 1970 README.md
-r--r--r-- 1 root root 20 Jan 1 1970 svn-revision
-r--r--r-- 1 root root 6 Jan 1 1970 .version
-r--r--r-- 1 root root 19 Jan 1 1970 .version-suffix
It is this store path
(/nix/store/z0gsgq0309r...-nixos-18.03.133188.8b92a4e6004
in this
example) to which we refer to as channel in the context of the
install-image.nix
module.
It is worth noting that the string nixos
in the directory names
nixos
and binary-caches/nixos
of the store path is the name by
which the channel was registered with nix-channel --add
. The
channel named nixos
plays a special role in a NixOS system, because
it provides the default Nix expression used for the configuration of
the system, e.g. by nixos-rebuild
. This is reflected in the fact
that the standard NIX_PATH
refers to that channel explicitely, e.g.
$ echo $NIX_PATH
/nix/var/nix/profiles/per-user/root/channels/nixos:nixos-config=/etc/nixos/configuration.nix:/nix/var/nix/profiles/per-user/root/channels
The install-image.nix
module takes a file system tree that holds a
copy of a nixpkgs
hierarchy and turns it into a channel. The
hierarchy can either be the nixpkgs
directory from an exsting
channel or a checkout of the NixOS/nixpkgs
Git repository.
The former is usually obtained from the channel of the local system
through the Nix path <nixpkgs>
. To construct a channel from it, one
only needs to create the directory structure described above, copy the
nixpkgs
directory inot it and add the binary-cache
given by the
installImage.binaryCacheURL
configuration option.
If the input is a Git repository, it must first be transformed into a
proper nixpkgs
tree as follows. The main difference (apart from the
Git-specific files) with respect to a nixpkgs
directory from a
channel is the absence of the file .version-suffix
and a missing
symbolic in the top-level directory.
The file .version-suffix
, together with .version
, makes up the
full version identifier of a NixOS system:
$ (cd /nix/store/z0gsgq0309r5j3ks7aqzlsibcipa0r1z-nixos-18.03.133188.8b92a4e6004/nixos && cat .version .version-suffix)
18.03.133188.8b92a4e6004
It is not part of a nixpkgs
Git repository. Instead, it is
constructed from a particular commit in one of the release
branches
by the Hydra CI system when a new release is created. The relevant
code can be found in nixos/release.nix
of nixpkgs
:
{ nixpkgs ? { outPath = ./..; revCount = 56789; shortRev = "gfedcba"; }
, stableBranch ? false
, supportedSystems ? [ "x86_64-linux" "i686-linux" ]
}:
.
.
.
version = builtins.readFile ../.version;
versionSuffix =
(if stableBranch then "." else "pre") + "${toString nixpkgs.revCount}.${nixpkgs.shortRev}";
.
.
.
channel = import lib/make-channel.nix { inherit pkgs nixpkgs version versionSuffix; };
.
.
.
When called from the Hydra build system, the dummy default values of
the variables revCount
and shortRev
(56789
and gfedcba
,
respectively), are replaced by values obtained by executing the
equivalent of the following shell code in the Git repository
revision=$(git rev-list --max-count=1 HEAD)
revCount=$(git rev-list $revision | wc -l)
shortRev=$(git rev-parse --short $revision)
Hydra also sets the variable stableBranch
to the value true
if it
is building a release from one of the stable NixOS branches
(e.g. release-18.03
or release-17.09
at the time of writing) to
make the distinction to an unstable release visible in the version
number.
The install-image.nix
module imitates this mechanism in order to
produce a NixOS channel that looks exactly as if it had been built by
the Hydra system:
- Calculate
revCount
andshortRev
from the Git repository - Create a properly versioned archive by calling
<nixpkgs/nixos/lib/make-channel.nix>
In any case, we'll end up with a tar archive containing a properly
versioned nixpkgs
hierarchy. In the final step, the channel itself
is created by evaluating the Nix expression
<nix/unpack-channel.nix>
, which creates the nixos
and
binary-caches
directories and populates them in a new store object.
This channel will eventually be installed as the initial channel of a system that gets installed from this install image.