configuration.rst (9034B)
1 Configuration 2 ############# 3 4 Liminix uses the Nix language to provide congruent configuration 5 management. This means that to change anything about the way in 6 which a Liminix system works, you make that change in 7 your :file:`configuration.nix` (or one of the other files it references), 8 and rerun :command:`nix-build` or :command:`liminix-rebuild` to action 9 the change. It is not possible (at least, without shenanigans) to make 10 changes by logging into the device and running imperative commands 11 whose effects may later be overridden: :file:`configuration.nix` 12 always describes the entire system and can be used to recreate that 13 system at any time. You can usefully keep it under version control. 14 15 If you are familiar with NixOS, you will notice some similarities 16 between NixOS and Liminix configuration, and also some 17 differences. Sometimes the differences are due to the 18 resource-constrained devices we deploy onto, sometimes due to 19 differences in the uses these devices are put to. 20 21 22 Configuration taxonomy 23 ********************** 24 25 There are many things you can specify in a configuration, but these 26 are the ones you most commonly need to change: 27 28 * which services (processes) to run 29 * what packages to install 30 * permitted users and groups 31 * Linux kernel configuration options 32 * Busybox applets 33 * filesystem layout 34 35 36 Modules 37 ******* 38 39 **Modules** are a means of abstraction which allow "bundling" 40 of configuration options related to a common purpose or theme. For 41 example, the ``dnsmasq`` module defines a template for a dnsmasq 42 service, ensures that the dnsmasq package is installed, and provides a 43 dnsmasq user and group for the service to run as. The ``ppp`` module 44 defines a service template and also enables various PPP-related kernel 45 configuration. 46 47 Not all modules are included in the configuration by default, because 48 that would mean that the kernel (and the Busybox binary providing 49 common CLI tools) was compiled with many unnecessary bells and whistles 50 and therefore be bigger than needed. (This is not purely an academic concern 51 if your device has little flash storage). Therefore, specifying a 52 service is usually a two-step process. For example, to add an NTP 53 service you first add :file:`modules/ntp` to your ``imports`` list, 54 then you create a service by calling 55 :code:`config.system.service.ntp.build { .... }` with the appropriate 56 service-dependent configuration parameters. 57 58 .. code-block:: nix 59 60 let svc = config.system.service; 61 in { 62 # ... 63 imports = [ 64 ./modules/ntp 65 # .... 66 ]; 67 config.services.ntp = svc.ntp.build { 68 pools = { "pool.ntp.org" = ["iburst"]; }; 69 makestep = { threshold = 1.0; limit = 3; }; 70 }; 71 72 Merely including the module won't define the service on its own: it 73 only creates the template in ``config.system.service.foo`` and you 74 have to create an actual service using the template. This is an 75 intentional choice to allow the creation of multiple 76 differently-configured services based on the same template - perhaps 77 e.g. when you have multiple networks (VPNs etc) in different trust 78 domains, or you want to run two SSH daemons on different ports. 79 (For the background to this, please refer to the :doc:`architecture decision record <adr/module-system>`) 80 81 .. tip:: Liminix modules should be quite familiar (but also different) 82 if you already know how to use NixOS modules. We use the 83 NixOS module infrastructure code, meaning that you should 84 recognise the syntax, the type system, the rules for 85 combining configuration values from different sources. We 86 don't use the NixOS modules themselves, because the 87 underlying system is not similar enough for them to work. 88 89 90 Services 91 ******** 92 93 We use the `s6-rc service manager <https://www.skarnet.org/software/s6-rc/overview.html>`_ to start/stop/restart services and handle 94 service dependencies. Any attribute in `config.services` will become 95 part of the default set of services that s6-rc will try to bring up on 96 boot. 97 98 For the most part, for common use cases, hopefully the services you 99 need will be defined by modules and you will only have to pass the 100 right parameters to ``build``. 101 102 Should you need to create a custom service of your own devising, use 103 the `oneshot` or `longrun` functions: 104 105 * a "longrun" service is the "normal" service concept: it has a 106 ``run`` action which describes the process to start, and it watches 107 that process to restart it if it exits. The process should not 108 attempt to daemonize or "background" itself, otherwise s6-rc will think 109 it died. Whatever it prints to standard output/standard error 110 will be logged. 111 112 .. code-block:: nix 113 114 config.services.cowsayd = pkgs.liminix.services.longrun { 115 name = "cowsayd"; 116 run = "${pkgs.cowsayd}/bin/cowsayd --port 3001 --breed hereford"; 117 # don't start this until the lan interface is ready 118 dependencies = [ config.services.lan ]; 119 } 120 121 122 * a "oneshot" service doesn't have a process attached. It consists of 123 ``up`` and ``down`` actions which are bits of shell script that 124 are run at the appropriate points in the service lifecycle 125 126 .. code-block:: nix 127 128 config.services.greenled = pkgs.liminix.services.oneshot { 129 name = "greenled"; 130 up = '' 131 echo 17 > /sys/class/gpio/export 132 echo out > /sys/class/gpio/gpio17/direction 133 echo 0 > /sys/class/gpio/gpio17/value 134 ''; 135 down = '' 136 echo 0 > /sys/class/gpio/gpio17/value 137 ''; 138 } 139 140 Services may have dependencies: as you see above in the ``cowsayd`` 141 example, it depends on some service called ``config.services.lan``, 142 meaning that it won't be started until that other service is up. 143 144 .. 145 TODO: explain service outputs 146 147 .. 148 TODO: outputs that change, and services that poll other services 149 150 Module implementation 151 ********************* 152 153 Modules in Liminix conventionally live in 154 :file:`modules/somename/default.nix`. If you want or need to 155 write your own, you may wish to refer to the 156 examples there in conjunction with reading this section. 157 158 A module is a function that accepts ``{lib, pkgs, config, ... }`` and 159 returns an attrset with keys ``imports, options config``. 160 161 * ``imports`` is a list of paths to the other modules required by this one 162 163 * ``options`` is a nested set of option declarations 164 165 * ``config`` is a nested set of option definitions 166 167 The NixOS manual section `Writing NixOS Modules 168 <https://nixos.org/manual/nixos/stable/#sec-writing-modules>`_ is a 169 quite comprehensive reference to writing NixOS modules, which is also 170 mostly applicable to Liminix except that it doesn't cover 171 service templates. 172 173 Service templates 174 ================= 175 176 To expose a service template in a module, it needs the following: 177 178 * an option declaration for ``system.service.myservicename`` with the 179 type of ``liminix.lib.types.serviceDefn`` 180 181 .. code-block:: nix 182 183 options = { 184 system.service.cowsay = mkOption { 185 type = liminix.lib.types.serviceDefn; 186 }; 187 }; 188 189 * an option definition for the same key, which specifies where to 190 import the service template from (often :file:`./service.nix`) 191 and the types of its parameters. 192 193 .. code-block:: nix 194 195 config.system.service.cowsay = liminix.callService ./service.nix { 196 address = mkOption { 197 type = types.str; 198 default = "0.0.0.0"; 199 description = "Listen on specified address"; 200 example = "127.0.0.1"; 201 }; 202 port = mkOption { 203 type = types.port; 204 default = 22; 205 description = "Listen on specified TCP port"; 206 }; 207 breed = mkOption { 208 type = types.str; 209 default = "British Friesian" 210 description = "Breed of the cow"; 211 }; 212 }; 213 214 Then you need to provide the service template itself, probably in 215 :file:`./service.nix`: 216 217 .. code-block:: nix 218 219 { 220 # any nixpkgs package can be named here 221 liminix 222 , cowsayd 223 , serviceFns 224 , lib 225 }: 226 # these are the parameters declared in the callService invocation 227 { address, port, breed} : 228 let 229 inherit (liminix.services) longrun; 230 inherit (lib.strings) escapeShellArg; 231 in longrun { 232 name = "cowsayd"; 233 run = "${cowsayd}/bin/cowsayd --address ${address} --port ${builtins.toString port} --breed ${escapeShellArg breed}"; 234 } 235 236 .. tip:: 237 238 Not relevant to module-based services specifically, but a common 239 gotcha when specifiying services is forgetting to transform "rich" 240 parameter values into text when composing a command for the shell 241 to execute. Note here that the port number, an integer, is 242 stringified with ``toString``, and the name of the breed, 243 which may contain spaces, is 244 escaped with ``escapeShellArg`` 245 246 Types 247 ===== 248 249 All of the NixOS module types are available in Liminix. These 250 Liminix-specific types also exist in ``pkgs.liminix.lib.types``: 251 252 * ``service``: an s6-rc service 253 * ``interface``: an s6-rc service which specifies a network 254 interface 255 * ``serviceDefn``: a service "template" definition 256 257 In the future it is likely that we will extend this to include other 258 useful types in the networking domain: for example; IP address, 259 network prefix or netmask, protocol family and others as we find them.