NixOS Configuration

July 31, 2019 Linux 5 minutes, 10 seconds

The main config file of NixOS is /etc/nixos/configuration.nix. After each modification the changes must be propagate to the system (see NixOS Commands).

{ config, pkgs, ... }: defines a function with at least to arguments returning { option definitions} which are a set of name = value pairs.

Example for enabling apache2 service

{ config, pkgs, ... }:

{ services.httpd.enable = true;
  services.httpd.adminAddr = "alice@example.org";
  services.httpd.documentRoot = "/webroot";
}

which is equal to

{ config, pkgs, ... }:

{ services = {
    httpd = {
      enable = true;
      adminAddr = "alice@example.org";
      documentRoot = "/webroot";
    };
  };
}

Dots in option names are shorthand for a set containing another set

Options have a specified type. The most important are:

  1. Strings

    networking.hostName = "dexter";. Special characters need to be escaped with \. Multi-line strings are enclosed with double single quotes, e.g.

    networking.extraHosts =
        ''
            127.0.0.2 other-localhost
            10.0.0.1 server
        '';
  2. Booleans

    Can be true or false, e.g. networking.firewall.enable = true;

  3. Integers

    boot.kernel.sysctl."net.ipv4.tcp_keepalive_time" = 60;. Here, net.ivp4 is enclosed in quotes to prevent it from being interprated is set of sets. This is just the name of the kernel option

  4. Sets

        fileSystems."/boot" =
      { device = "/dev/sda1";
        fsType = "ext4";
        options = [ "rw" "data=ordered" "relatime" ];
      };
  5. Lists

    boot.kernelModules = [ "fuse" "kvm-intel" "coretemp" ];. Note that elements are separated by whitespaces and can be of any type, e.g. sets swapDevices = [ { device = "/dev/disk/by-label/swap"; } ];

  6. Packages

      environment.systemPackages =
      [ pkgs.thunderbird
        pkgs.emacs
      ];
    
    postgresql.package = pkgs.postgresql90;

A comprehensive list of options can be found at NixOS options.

Abstractions provide a way to reduce redundancy in your configuration.

let
  exampleOrgCommon =
    { hostName = "example.org";
      documentRoot = "/webroot";
      adminAddr = "alice@example.org";
      enableUserDir = true;
    };
in
{
  services.httpd.virtualHosts =
    [ exampleOrgCommon
      (exampleOrgCommon // {
        enableSSL = true;
        sslServerCert = "/root/ssl-example-org.crt";
        sslServerKey = "/root/ssl-example-org.key";
      })
    ];
}

This method also works on functions, here we generade a couple of virtual hosts which only differs by their hostnames:

{
  services.httpd.virtualHosts =
    let
      makeVirtualHost = name:
        { hostName = name;
          documentRoot = "/webroot";
          adminAddr = "alice@example.org";
        };
    in
      [ (makeVirtualHost "example.org")
        (makeVirtualHost "example.com")
        (makeVirtualHost "example.gov")
        (makeVirtualHost "example.nl")
      ];
}

A further improvement of this configuration is the use of map

{
  services.httpd.virtualHosts =
    let
      makeVirtualHost = ...;
    in map makeVirtualHost
      [ "example.org" "example.com" "example.gov" "example.nl" ];
}

Functions can also have more than one argument:

 let
      makeVirtualHost = { name, root }:
        { hostName = name;
          documentRoot = root;
          adminAddr = "alice@example.org";
        };
    in map makeVirtualHost

NixOS configuration can be split into different *.nix files and imported with imports = [ ./mod1.nix ./mod2.nix ]; into the main configuration. The modules have the same syntax as the main file. When necessary you can specify the ordering and the preference of settings. the config variable contains all options merged accross all config files. Access to this value is possible in your config

if config.services.xserver.enable then
      [ pkgs.firefox
        pkgs.thunderbird
      ]
    else
      [ ];

and by using the command nixos-option OPTION which prints the value to stdout.

NixOS supports two styles of package managemetn:

  1. Declarative with your configuration.nix and nixos-rebuildwhere NixOS will ensure a consistet set of binaries
  2. Ad hoc with nix-env similar to traditional package management allowing to mix packages from different Nixpkgs versions.This is the only choice for non-root users.

By adding the desired package to environment.systemPackages = [ ... ];. Executing nix-env -qaP '*' --description lists all available packages. To uninstall a package simple remove it from the file. After modifying your packages run e.g. nixos-rebuild switch

Some packages allow further configuration e.g. firefox nixpkgs.config.firefox.enableGoogleTalkPlugin = true;

Unfortunately, Nixpkgs currently lacks a way to query available configuration options.

  • Even more advanced customisation is possible!
  • When a package is not available you can build your own package and optionally patch the official repo.

Installing a package can be done with nix-env -iA nixos.thunderbird. As root the package is saved in the nix profile /nix/var/nix/profiles/default and visible in the whole system. Otherwise as user it is installed in /nix/var/nix/profiles/per-user/username/profile and only visible to that user. The -A flag specifies the package by its attribute name; without it, the package is installed by matching against its package name (e.g. thunderbird). The latter is slower because it requires matching against all available Nix packages, and is ambiguous if there are multiple matching packages.

For updating packages run nix-channel --update nixos and than nix-env -i again. Other packages in the profile are not affectet and that's a crucial difference to the declarative method where 'nixos-rebuild' updates all packages from the channel. However, you can update all packages with nix-env -u '*'.

Unistalling works with nix-env -e thunderbird and rollback with nix-env --rollback.

system.autoUpgrade.enable = true;

Like in Package Management declarative and ad hoc style is possible.

  users.extraUsers.USERNAME = {
    createHome = true;
    home = "/home/user";
    extraGroups = [ "wheel" ];
    shell = pkgs.bashInteractive;
    openssh.authorizedKeys.keys = [ "ssh-rsa ..." ];
    uid = 1000;
  };

This user has no password and is only able to login with its private ssh key. In order to use sudo -i a pasword is required. To set a password loin as root und execute passwd USERNAME.

As ad hoc style you can use standarf linux commands such as useradd, usermod, groupadd, etc.

nix.gc = {
  automatic = true;
  dates = "weekly";
  options = "--delete-older-than 30d";
};

In configuration.nix

  environment.systemPackages = with pkgs; [
        zsh
        oh-my-zsh
  ];

  programs.zsh.enable = true;
  programs.zsh.interactiveShellInit = ''
    export ZSH=${pkgs.oh-my-zsh}/share/oh-my-zsh/

    # Customize your oh-my-zsh options here
    ZSH_THEME="robbyrussell"
    plugins=(git docker)

    bindkey '\e[5~' history-beginning-search-backward
    bindkey '\e[6~' history-beginning-search-forward

    HISTFILESIZE=500000
    HISTSIZE=500000
    setopt SHARE_HISTORY
    setopt HIST_IGNORE_ALL_DUPS
    setopt HIST_IGNORE_DUPS
    setopt INC_APPEND_HISTORY
    autoload -U compinit && compinit
    unsetopt menu_complete
    setopt completealiases

    if [ -f ~/.aliases ]; then
      source ~/.aliases
    fi

    source $ZSH/oh-my-zsh.sh
  '';
  programs.zsh.promptInit = "";

  users.extraUsers.USER = {
    shell = pkgs.zsh;
  };

NixOS comes with a simple staeful firewall wich is enabled per default. You can disable it with networking.firewall.enable = false;. To allow TCP/UDP ports use networking.firewall.allowedTCPPorts = [ 80 443 ]; (respective networking.firewall.allowedUDPPorts).