Development This chapter has some random notes on hacking on NixOS.
Extending NixOS NixOS is based on a modular system for declarative configuration. This system combines multiple modules to produce one configuration. One of the module which compose your computer configuration is /etc/nixos/configuration.nix. Other modules are available under NixOS modules directory A module is a file which handles one specific part of the configuration. This part of the configuration could correspond to an hardware, a service, network settings, or preferences. A module configuration does not have to handle everything from scratch, it can base its configuration on other configurations provided by other modules. Thus a module can define options to setup its configuration, and it can also declare options to be fed by other modules. A module is a file which contains a Nix expression. This expression should be either an expression which gets evaluated into an attribute set or a function which returns an attribute set. When the expression is a function, it should expect only one argument which is an attribute set containing an attribute named config and another attribute named pkgs. The config attribute contains the result of the merge of all modules. This attribute is evaluated lazily, such as any Nix expression. For more details on how options are merged, see the details in . The pkgs attribute contains nixpkgs attribute set of packages. This attribute is necessary for declaring options. Usual module content {config, pkgs, ...}: { imports = [ ]; options = { }; config = { }; } Illustrates a module skeleton. This line makes the current Nix expression a function. This line can be omitted if there is no reference to pkgs and config inside the module. This list is used to enumerate path to other modules which are declaring options used by the current module. In NixOS, default modules are listed in the file modules/module-list.nix. The default modules don't need to be added in the import list. This attribute set contains an attribute set of option declaration. This attribute set contains an attribute set of option definitions. If the module does not have any imported modules or any option declarations, then this attribute set can be used in place of its parent attribute set. This is a common case for simple modules such as /etc/nixos/configuration.nix. A module defines a configuration which would be interpreted by other modules. To define a configuration, a module needs to provide option definitions. An option definition is a simple attribute assignment. Option definitions are made in a declarative manner. Without properties, options will always be defined with the same value. To introduce more flexibility in the system, option definitions are guarded by properties. Properties are means to introduce conditional values inside option definitions. This conditional values can be distinguished in two categories. The condition which are local to the current configuration and conditions which are dependent on others configurations. Local properties are mkIf, mkAlways and mkAssert. Global properties are mkOverride, mkDefault and mkOrder. mkIf is used to remove the option definitions which are below it if the condition is evaluated to false. mkAssert expects the condition to be evaluated to true otherwise it raises an error message. mkAlways is used to ignore all the mkIf and mkAssert which have been made previously. mkAlways and mkAssert are often used together to set an option value and to ensure that it has not been masked by another one. mkOverride is used to mask previous definitions if the current value has a lower mask number. The mask value is 100 (default) for any option definition which does not use this property. Thus, mkDefault is just a short-cut with a higher mask (1000) than the default mask value. This means that a module can set an option definition as a preference, and still let another module defining it with a different value without using any property. mkOrder is used to sort definitions based on the rank number. The rank number will sort all options definitions before giving the sorted list of option definition to the merge function defined in the option declaration. A lower rank will move the definition to the beginning and a higher rank will move the option toward the end. The default rank is 100. A module may declare options which are used by other module to change the configuration provided by the current module. Changes to the option definitions are made with properties which are using values extracted from the result of the merge of all modules (the config argument). The config argument reproduce the same hierarchy of all options declared in all modules. For each option, the result of the option is available, it is either the default value or the merge of all definitions of the option. Options are declared with the function pkgs.lib.mkOption. This function expects an attribute set which at least provides a description. A default value, an example, a type, a merge function and a post-process function can be added. Types are used to provide a merge strategy for options and to ensure the type of each option definitions. They are defined in pkgs.lib.types. The merge function expects a list of option definitions and merge them to obtain one result of the same type. The post-process function (named apply) takes the result of the merge or of the default value, and produce an output which could have a different type than the type expected by the option. Locate Module Example {config, pkgs, ...}: with pkgs.lib; let cfg = config.services.locate; locatedb = "/var/cache/locatedb"; logfile = "/var/log/updatedb"; cmd =''root updatedb --localuser=nobody --output=${locatedb} > ${logfile}''; mkCheck = x: mkIf cfg.enable ( mkAssert config.services.cron.enable '' The cron daemon is not enabled, required by services.locate.enable. '' x ) in { imports = [ /etc/nixos/nixos/modules/services/scheduling/cron.nix ]; options = { services.locate = { enable = mkOption { default = false; example = true; type = with types; bool; description = '' If enabled, NixOS will periodically update the database of files used by the locate command. ''; }; period = mkOption { default = "15 02 * * *"; type = with types; uniq 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 = mkCheck { services.cron = { enable = mkAlways cfg.enable; systemCronJobs = "${cfg.period} root ${cmd}"; }; }; } illustrates a module which handles the regular update of the database which index all files on the file system. This modules has option definitions to rely on the cron service to run the command at predefined dates. In addition, this modules provides option declarations to enable the indexing and to use different period of time to run the indexing. Properties are used to prevent ambiguous definitions of option (enable locate service and disable cron services) and to ensure that no options would be defined if the locate service is not enabled.
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. The actual Nix programming interface to VM testing is in NixOS, under lib/testing.nix. This file defines a function which takes an attribute set containing a nixpkgs attribute (the path to a Nixpkgs checkout), and a system attribute (the system type). It returns an attribute set containing several utility functions, among which the main entry point is makeTest. The makeTest function takes a function similar to that found in tests/nfs.nix (discussed above). It returns an attribute set containing (among others): test A derivation containing the test log as an HTML file, as seen above, suitable for presentation in the Hydra continuous build system. report A derivation containing a code coverage report, with meta-data suitable for Hydra. driver A derivation containing scripts to run the VM test or interact with the VM network interactively, as seen above.