developer.rst (12347B)
1 Developer Manual 2 ################ 3 4 As a developer working on Liminix, or implementing a service or 5 module, you probably want to test your changes more conveniently 6 than by building and flashing a new image every time. This manual 7 documents various affordances for iteration and experiments. 8 9 In general, packages and tools that run on the "build" machine are 10 available in the ``buildEnv`` derivation and can most easily 11 be added to your environment by running :command:`nix-shell` 12 13 14 15 Emulated devices 16 **************** 17 18 Liminix has a ``qemu`` device, which generates images suitable for 19 running on your build machine using the free `QEMU machine emulator <http://www.qemu.org>`_. 20 This is useful for developing userland without needing to keep 21 flashing or messing with U-Boot: it also enables testing against 22 emulated network peers using `QEMU socket networking <https://wiki.qemu.org/Documentation/Networking#Socket>`_, 23 which may be preferable to letting Liminix loose on your actual LAN. 24 To build it, 25 26 .. code-block:: console 27 28 nix-build -I liminix-config=path/to/your/configuration.nix --arg device "import ./devices/qemu" -A outputs.default 29 30 In a ``buildEnv`` nix-shell, you can use the :command:`mips-vm` command 31 to run Qemu with appropriate options. It connects the Liminix 32 serial console and the `QEMU monitor <https://www.qemu.org/docs/master/system/monitor.html>`_ to stdin/stdout. Use ^P (not ^A) to switch to the monitor. 33 34 .. code-block:: console 35 36 nix-shell --run "mips-vm result/vmlinux result/squashfs" 37 38 If you run with ``--background /path/to/some/directory`` as the first 39 parameter, it will fork into the background and open Unix sockets in 40 that directory for console and monitor. Use :command:`connect-vm` 41 (also in the ``buildEnv`` environment) to connect to either of these 42 sockets, and ^O to disconnect. 43 44 Networking 45 ========== 46 47 VMs can network with each other using QEMU 48 socket networking. We observe these conventions, so that we can run 49 multiple emulated instances and have them wired up to each other in 50 the right way: 51 52 * multicast 230.0.0.1:1234 : access (interconnect between router and "isp") 53 * multicast 230.0.0.1:1235 : lan 54 * multicast 230.0.0.1:1236 : world (the internet) 55 56 A VM started with :command:`mips-vm` is connected to "lan" and "access", and 57 the emulated border network gateway (see below) runs PPPoE and is 58 connected to "access" and "world". 59 60 Border Network Gateway 61 ---------------------- 62 63 In pkgs/routeros there is a derivation to install and configure 64 `Mikrotik RouterOS <https://mikrotik.com/software>`_ as a PPPoE access 65 concentrator connected to the ``access`` and ``world`` networks, so that 66 Liminix PPPoE client support can be tested without actual hardware. 67 68 This is made available as the :command:`routeros` command in 69 ``buildEnv``, so you can do something like:: 70 71 mkdir ros-sockets 72 nix-shell 73 nix-shell$ routeros ros-sockets 74 nix-shell$ connect-vm ./ros-sockets/console 75 76 to start it and connect to it. Note that by default it runs in the 77 background. It is connected to "access" and "world" virtual networks 78 and runs a PPPoE service on "access" - so a Liminix VM with a 79 PPPOE client can connect to it and thus reach the virtual internet. 80 [ check, but pretty sure this is not the actual internet ] 81 82 `Liminix does not provide RouterOS licences and it is your own 83 responsibility if you use this to ensure you're compliant with the 84 terms of Mikrotik's licencing. It may be supplemented or replaced in 85 time with configurations for RP-PPPoE and/or Accel PPP.` 86 87 Hardware devices 88 **************** 89 90 How you get your image onto hardware will vary according to the 91 device, but is likely to involve taking it apart to add wires to 92 serial console pads/headers, then using U-Boot to fetch images over 93 TFTP. The OpenWrt documentation has a `good explanation <https://openwrt.org/docs/techref/hardware/port.serial>`_ of what you may expect to find on 94 the device. 95 96 There is a rudimentary TFTP server bundled with the system which runs 97 from the command line, has an allowlist for client connections, and 98 follows symlinks, so you can have your device download images direct 99 from the :file:`./result` directory without exposing :file:`/nix/store/` to the 100 internet or mucking about copying files to :file:`/tftproot`. If the 101 permitted device is to be given the IP address 192.168.8.251 you might 102 do something like this: 103 104 .. code-block:: console 105 106 nix-shell --run "tufted -a 192.168.8.251 result" 107 108 Now add the device and server IP addresses to your configuration: 109 110 .. code-block:: nix 111 112 boot.tftp = { 113 serverip = "192.168.8.111"; 114 ipaddr = "192.168.8.251"; 115 }; 116 117 and then build the derivation for ``outputs.default`` or 118 ``outputs.flashimage`` (for which it will be an alias on any device 119 where this is applicable). You should find it has created 120 121 * :file:`result/firmware.bin` which is the file you are going to flash 122 * :file:`result/flash.scr` which is a set of instructions to U-Boot to 123 download the image and write it to flash after erasing the appropriate 124 flash partition. 125 126 .. NOTE:: 127 128 TTL serial connections typically have no form of flow control and 129 so don't always like having massive chunks of text pasted into 130 them - and U-Boot may drop characters while it's busy. So don't 131 necessarily expect to copy-paste the whole of :file:`boot.scr` into 132 a terminal emulator and have it work just like that. You may need 133 to paste each line one at a time, or even retype it. 134 135 136 For a faster edit-compile-test cycle, you can build a TFTP-bootable 137 image instead of flashing. In your device configuration add 138 139 .. code-block:: nix 140 141 imports = [ 142 ./modules/tftpboot.nix 143 ]; 144 145 and then build ``outputs.tftpboot``. This creates a file in 146 ``result/`` called ``boot.scr``, which you can copy and paste into 147 U-Boot to transfer the kernel and filesystem over TFTP and boot the 148 kernel from RAM. 149 150 151 Networking 152 ========== 153 154 You probably don't want to be testing a device that might serve DHCP, 155 DNS and routing protocols on the same LAN as you (or your colleagues, 156 employees, or family) are using for anything else, because it will 157 interfere. You also might want to test the device against an 158 "upstream" connection without having to unplug your regular home 159 router from the internet so you can borrow the cable/fibre/DSL. 160 161 ``bordervm`` is included for this purpose. You will need 162 163 * a Linux machine with a spare (PCI or USB) ethernet device which you can dedicate to Liminix 164 165 * an L2TP service such as https://www.aa.net.uk/broadband/l2tp-service/ 166 167 You need to "hide" the Ethernet device from the host - for PCI this 168 means configuring it for VFIO passthru; for USB you need to unload the 169 module(s) it uses. I have this segment in configuration.nix which you 170 may be able to adapt: 171 172 .. code-block:: nix 173 174 boot = { 175 kernelParams = [ "intel_iommu=on" ]; 176 kernelModules = [ 177 "kvm-intel" "vfio_virqfd" "vfio_pci" "vfio_iommu_type1" "vfio" 178 ]; 179 180 postBootCommands = '' 181 # modprobe -i vfio-pci 182 # echo vfio-pci > /sys/bus/pci/devices/0000:01:00.0/driver_override 183 ''; 184 blacklistedKernelModules = [ 185 "r8153_ecm" "cdc_ether" 186 ]; 187 }; 188 services.udev.extraRules = '' 189 SUBSYSTEM=="usb", ATTRS{idVendor}=="0bda", ATTRS{idProduct}=="8153", OWNER="dan" 190 ''; 191 192 Then 193 you can execute :command:`run-border-vm` in a ``buildEnv`` shell, 194 which starts up QEMU using the NixOS configuration in 195 :file:`bordervm-configuration.nix`. 196 197 In this VM 198 199 * your Liminix checkout is mounted under :file:`/home/liminix/liminix` 200 201 * TFTP is listening on the ethernet device and serving 202 :file:`/home/liminix/liminix`. The server IP address is 10.0.0.1 203 204 * a PPPOE-L2TP relay is running on the same ethernet card. When the 205 connected Liminix device makes PPPoE requests, the relay spawns 206 L2TPv2 Access Concentrator sessions to your specified L2TP LNS. 207 Note that authentication is expected at the PPP layer not the L2TP 208 layer, so the PAP/CHAP credentials provided by your L2TP service can 209 be configured into your test device - bordervm doesn't need to know 210 about them. 211 212 To configure bordervm, you need a file called :file:`bordervm.conf.nix` 213 which you can create by copying and appropriately editing :file:`bordervm.conf-example.nix` 214 215 .. note:: 216 217 If you make changes to the bordervm configuration after executing 218 :command:`run-border-vm`, you need to remove the :file:`border.qcow2` disk 219 image file otherwise the changes won't get picked up. 220 221 222 Running tests 223 ************* 224 225 You can run all of the tests by evaluating :file:`ci.nix`, which is the 226 input I use in Hydra. Note that it expects Nixpkgs stable `and` unstable 227 as inputs, because it builds the qemu device against both. 228 229 .. code-block:: console 230 231 nix-build --argstr liminix `pwd` --arg nixpkgs "<nixpkgs>" \ 232 --argstr unstable `pwd`/../unstable-nixpkgs/ ci.nix 233 234 To run a single named test, use the ``-A`` flag. For example, ``-A pppoe`` 235 236 237 238 239 Troubleshooting 240 *************** 241 242 Diagnosing unexpectedly large images 243 ==================================== 244 245 Sometimes you can add a package and it causes the image size to balloon 246 because it has dependencies on other things you didn't know about. Build the 247 ``outputs.manifest`` attribute, which is a JSON representation of the 248 filesystem, and you can run :command:`nix-store --query` on it. 249 250 .. code-block:: console 251 252 nix-build -I liminix-config=path/to/your/configuration.nix \ 253 --arg device "import ./devices/qemu" -A outputs.manifest \ 254 -o manifest 255 nix-store -q --tree manifest 256 257 258 Contributing 259 ************ 260 261 Contributions are welcome, though in these early days there may be a 262 bit of back and forth involved before patches are merged: 263 Please get in touch somehow `before` you invest a lot of time into a 264 code contribution I haven't asked for. Just so I know it's expected 265 and you're not wasting time doing something I won't accept or have 266 already started on. 267 268 269 Nix language style 270 ================== 271 272 This section describes some Nix language style points that we 273 attempt to adhere to in this repo. 274 275 * favour ``callPackage`` over raw ``import`` for calling derivations 276 or any function that may generate one - any code that might need 277 ``pkgs`` or parts of it. 278 279 * prefer ``let inherit (quark) up down strange charm`` over 280 ``with quark``, in any context where the scope is more than a single 281 expression or there is more than one reference to ``up``, ``down`` 282 etc. ``with pkgs; [ foo bar baz]`` is OK, 283 ``with lib; stdenv.mkDerivation { ... }`` is usually not. 284 285 * ``<liminix>`` is defined only when running tests, so don't refer to it 286 in "application" code 287 288 * the parameters to a derivation are sorted alphabetically, except for 289 ``lib``, ``stdenv`` and maybe other non-package "special cases" 290 291 * indentation is whatever emacs nix-mode says it is. 292 293 * where a ``let`` form defines multiple names, put a newline after the 294 token ``let``, and indent each name two characters 295 296 * to decide whether some code should be a package or a module? 297 Packages are self-contained - they live in ``/nix/store/eeeeeee-name`` 298 and don't directly change system behaviour by their presence or 299 absense. modules can add to 300 ``/etc`` or ``/bin`` or other global state, create services, all that 301 side-effecty stuff. Generally it should be a package unless it 302 can't be. 303 304 305 306 Copyright 307 ========= 308 309 The Nix code in Liminix is MIT-licenced (same as Nixpkgs), but the 310 code it combines from other places (e.g. Linux, OpenWrt) may have a 311 variety of licences. I have no intention of asking for copyright 312 assignment: just like when submitting to the Linux kernel you retain 313 the copyright on the code you contribute. 314 315 316 Code of Conduct 317 =============== 318 319 Please govern yourself in Liminix project venues according to the 320 `Code of Conduct <https://gti.telent.net/dan/liminix/src/commit/7bcf6b15c3fdddafeda13f65b3cd4a422dc52cd3/CODE-OF-CONDUCT.md>`_ 321 322 323 Where to send patches 324 ===================== 325 326 327 Liminix' primary repo is https://gti.telent.net/dan/liminix but you 328 can't send code there directly because it doesn't have open registrations. 329 330 * There's a `mirror on Github <https://github.com/telent/liminix>`_ for 331 convenience and visibility: you can open PRs against that 332 333 * or, you can send me your patch by email using `git send-email <https://git-send-email.io/>`_ 334 335 * or in the future, some day, we will have federated Gitea using 336 ActivityPub.