Development This chapter has some random notes on hacking on NixOS.
Extending NixOS A unique syntax is used to express all system, hardware, computer and service configurations. This syntax helps for reading and writing new configuration files. It is coming with some extra strategies defined in NixPkgs which are used to merge and evaluate all configuration files. A configuration file is the same as your own computer configuration. Usual configuration file {config, modulesPath, pkgs, ...}: let inherit (pkgs.lib) mkOption mkIf mkThenElse types; cfg = config.services.locate; locatedb = "/var/cache/locatedb"; logfile = "/var/log/updatedb"; cmd = "root updatedb --localuser=nobody --output=${locatedb} > ${logfile}"; in { imports = [ (modulesPath + /services/scheduling/cron.nix) ]; options = { services.locate = { enable = mkOption { default = false; example = true; type = types.bool; description = '' If enabled, NixOS will periodically update the database of files used by the locate command. ''; }; period = mkOption { default = "15 02 * * *"; type = types.uniq type.string; description = '' This option defines (in the format used by cron) when the locate database is updated. The default is to update at 02:15 (at night) every day. ''; }; }; }; config = mkIf cfg.enable { services.cron = { systemCronJobs = mkThenElse { thenPart = "${cfg.period} root ${cmd}"; elsePart = ""; }; }; }; } shows the configuration file for the locate service which uses cron to update the database at some dates which can be defined by the user. This nix expression is coming from upstart-jobs/cron/locate.nix. It shows a simple example of a service that can be either distributed on many computer that are using the same configuration or to shared with the community. This file is divided in two with the interface and the implementation. Both the interface and the implementation declare a configuration set. This line declares the arguments of the configuration file. You can omit this line if there is no reference to pkgs and config inside the configuration file. The argument pkgs refers to NixPkgs and allow you to access all attributes contained inside it. In this example pkgs is used to retrieve common functions to ease the writing of configuration files like mkOption, mkIf and mkThenElse. The argument config corresponds to the whole NixOS configuration. This is a set which is build by merging all configuration files imported to set up the system. Thus all options declared are contained inside this variable. In this example config is used to retrieve the status of the enable flag. The important point of this argument is that it contains either the result of the merge of different settings or the default value, therefore you cannot assume that is always false because it may have been defined in another configuration file. This line is used to import a functions that are useful for writing this configuration file. The variable options is a configuration set which is only used to declare options with the function mkOption imported from pkgs/lib/default.nix. Options may contained any attribute but only the following have a special meaning: default, example, description, merge and apply. The merge attribute is used to merge all values defined in all configuration files and this function return a value which has the same type as the default value. If the merge function is not defined, then a default function (pkgs.lib.mergeDefaultOption) is used to merge values. The merge attribute is a function which expect two arguments: the location of the option and the list of values which have to be merged. The apply attribute is a function used to process the option. Thus the value return by would be the result of the apply function called with either the default value or the result of the merge function. This line is a common trick used to reduce the amount of writing. In this case cfg is just a sugar over This line is used to declare a special IF statement. If you had put a usual IF statement here, with the same condition, then you will get an infinite loop. The reason is that your condition ask for the value of the option but in order to get this value you have to evaluate all configuration sets including the configuration set contained inside your file. To remove this extra complexity, mkIf has been introduced to get rid of possible infinite loop and to factor your writing. The attribute imports contains a list of other module location. These modules should provide option declarations for the current module in order to be evaluated safely. When a dependence on a NixOS module has to be made, then you should use the argument modulesPath to prefix the location of your NixOS repository. The attribute config, which should not be confused with the argument of the same name, contains a set of option definitions defined by this module. When there is neither imports nor options, then this attribute set can be return without any enclosing. This feature allow you to write your configuration.nix as a module which does not contains any option declarations. As mkIf does not need any then part or else part, then you can specify one on each option definition with the function mkThenElse. To avoid a lot of mkThenElse with empty else part, a sugar has been added to infer the corresponding empty-value of your option when the function mkThenElse is not used. If your then part and else part are identical, then you should use the function mkAlways to ignore the condition. If you need to add another condition, then you can add mkIf to on the appropriate location. Thus the then part will only be used if all conditions declared with mkIf are satisfied.
Building specific parts of NixOS $ nix-build /etc/nixos/nixos -A attr where attr is an attribute in /etc/nixos/nixos/default.nix. Attributes of interest include: config The computer configuration generated from the NIXOS_CONFIG environment variable (default is /etc/nixos/configuration.nix) with the NixOS default set of modules. system The derivation which build your computer system. It is built by the command nixos-rebuild build vm The derivation which build your computer system inside a virtual machine. It is built by the command nixos-rebuild build-vm Most parts of NixOS can be build through the config attribute set. This attribute set allows you to have a view of the merged option definitions and all its derivations. Important derivations are store inside the option and can be listed with the command nix-instantiate --xml --eval-only /etc/nixos/nixos -A config.system.build
Building your own NixOS CD Building a NixOS CD is as easy as configuring your own computer. The idea is to use another module which will replace your configuration.nix to configure the system that would be install on the CD. Default CD/DVD configurations are available inside nixos/modules/installer/cd-dvd. To build them you have to set NIXOS_CONFIG before running nix-build to build the ISO. $ export NIXOS_CONFIG=/etc/nixos/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix $ nix-build /etc/nixos/nixos -A config.system.build.isoImage Before burning your CD/DVD, you can check the content of the image by mounting anywhere like suggested by the following command: $ mount -o loop -t iso9660 ./result/iso/cd.iso /mnt/iso
Testing the installer Building, burning, and booting from an installation CD is rather tedious, so here is a quick way to see if the installer works properly: $ export NIXOS_CONFIG=/etc/nixos/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix $ nix-build /etc/nixos/nixos -A config.system.build.nixosInstall $ dd if=/dev/zero of=diskimage seek=2G count=0 bs=1 $ yes | mke2fs -j diskimage $ mount -o loop diskimage /mnt $ ./result/bin/nixos-install
Testing the <literal>initrd</literal> A quick way to test whether the kernel and the initial ramdisk boot correctly is to use QEMU’s and options: $ nix-build /etc/nixos/nixos -A config.system.build.initialRamdisk -o initrd $ nix-build /etc/nixos/nixos -A config.system.build.kernel -o kernel $ qemu-system-x86_64 -kernel ./kernel/bzImage -initrd ./initrd/initrd -hda /dev/null
Whole-system testing using virtual machines Complete NixOS GNU/Linux systems can be tested in virtual machines (VMs). This makes it possible to test a system upgrade or configuration change before rebooting into it, using the nixos-rebuild build-vm or nixos-rebuild build-vm-with-bootloader command. The tests/ directory in the NixOS source tree contains several whole-system unit tests. These tests can be runNixOS tests can be run both from NixOS and from a non-NixOS GNU/Linux distribution, provided the Nix package manager is installed. from the NixOS source tree as follows: $ nix-build tests/ -A nfs.test This performs an automated test of the NFS client and server functionality in the Linux kernel, including file locking semantics (e.g., whether locks are maintained across server crashes). It will first build or download all the dependencies of the test (e.g., all packages needed to run a NixOS VM). The test is defined in tests/nfs.nix. If the test succeeds, nix-build will place a symlink ./result in the current directory pointing at the location in the Nix store of the test results (e.g., screenshots, test reports, and so on). In particular, a pretty-printed log of the test is written to log.html, which can be viewed using a web browser like this: $ icecat result/log.html It is also possible to run the test environment interactively, allowing you to experiment with the VMs. For example: $ nix-build tests/ -A nfs.driver $ ./result/bin/nixos-run-vms The script nixos-run-vms starts the three virtual machines defined in the NFS test using QEMU/KVM. The root file system of the VMs is created on the fly and kept across VM restarts in ./hostname.qcow2. Finally, the test itself can be run interactively. This is particularly useful when developing or debugging a test: $ nix-build tests/ -A nfs.driver $ ./result/bin/nixos-test-driver starting VDE switch for network 1 > Perl statements can now be typed in to start or manipulate the VMs: > startAll; (the VMs start booting) > $server->waitForJob("nfs-kernel-nfsd"); > $client1->succeed("flock -x /data/lock -c 'sleep 100000' &"); > $client2->fail("flock -n -s /data/lock true"); > $client1->shutdown; (this releases client1's lock) > $client2->succeed("flock -n -s /data/lock true"); The function testScript executes the entire test script and drops you back into the test driver command line upon its completion. This allows you to inspect the state of the VMs after the test (e.g. to debug the test script). This and other tests are continuously run on the Hydra instance at nixos.org, which allows developers to be notified of any regressions introduced by a NixOS or Nixpkgs change.