diff --git a/doc/configuration.adoc b/doc/configuration.adoc index 28a7a37..219d892 100644 --- a/doc/configuration.adoc +++ b/doc/configuration.adoc @@ -112,7 +112,7 @@ check" command at regular intervals. When the health check fails, indicating that the wrapped service is not working, it is terminated and allowed to restart. -==== Runtime secrets (external vault) +=== Runtime secrets (external vault) Secrets (such as wifi passphrases, PPP username/password, SSH keys, etc) that you provide as literal values in `+configuration.nix+` are @@ -221,250 +221,3 @@ directly on the device. (That latter approach may pose a chicken/egg problem if the device needs secrets to boot up and run the services you are relying on in order to login). -==== Writing services - -For the most part, for common use cases, hopefully the services you need -will be defined by modules and you will only have to pass the right -parameters to `+build+`. - -Should you need to create a custom service of your own devising, use the -[.title-ref]#oneshot# or [.title-ref]#longrun# functions: - -* a "longrun" service is the "normal" service concept: it has a `+run+` -action which describes the process to start, and it watches that process -to restart it if it exits. The process should not attempt to daemonize -or "background" itself, otherwise s6-rc will think it died. Whatever it -prints to standard output/standard error will be logged. - -[source,nix] ----- -config.services.cowsayd = pkgs.liminix.services.longrun { - name = "cowsayd"; - run = "${pkgs.cowsayd}/bin/cowsayd --port 3001 --breed hereford"; - # don't start this until the lan interface is ready - dependencies = [ config.services.lan ]; -} ----- - -* a "oneshot" service doesn't have a process attached. It consists of -`+up+` and `+down+` actions which are bits of shell script that are run -at the appropriate points in the service lifecycle - -[source,nix] ----- -config.services.greenled = pkgs.liminix.services.oneshot { - name = "greenled"; - up = '' -echo 17 > /sys/class/gpio/export -echo out > /sys/class/gpio/gpio17/direction -echo 0 > /sys/class/gpio/gpio17/value - ''; - down = '' -echo 0 > /sys/class/gpio/gpio17/value - ''; -} ----- - -Services may have dependencies: as you see above in the `+cowsayd+` -example, it depends on some service called `+config.services.lan+`, -meaning that it won't be started until that other service is up. - -==== Service outputs - -Outputs are a mechanism by which a service can provide data which may be -required by other services. For example: - -* the DHCP client service can expect to receive nameserver address -information as one of the fields in the response from the DHCP server: -we provide that as an output which a dependent service for a stub name -resolver can use to configure its upstream servers. -* a service that creates a new network interface (e.g. ppp) will provide -the name of the interface (`+ppp0+`, or `+ppp1+` or `+ppp7+`) as an -output so that a dependent service can reference it to set up a route, -or to configure firewall rules. - -A service `+myservice+` should write its outputs as files in -`+/run/services/outputs/myservice+`: you can look around this directory -on a running Liminix system to see how it's used currently. Usually we -use the `+in_outputs+` shell function in the `+up+` or `+run+` -attributes of the service: - -[source,shell] ----- -(in_outputs ${name} - for i in lease mask ip router siaddr dns serverid subnet opt53 interface ; do - (printenv $i || true) > $i - done) ----- - -The outputs are just files, so technically you can read them using -anything that can read a file. Liminix has two "preferred" mechanisms, -though: - -===== One-off lookups - -In any context that ends up being evaluated by the shell, use `+output+` -to print the value of an output - -[source,nix] ----- -services.defaultroute4 = svc.network.route.build { - via = "$(output ${services.wan} address)"; - target = "default"; - dependencies = [ services.wan ]; -}; ----- - -===== Continuous updates - -The downside of using shell functions in downstream service startup -scripts is that they only run when the service starts up: if a service -output _changes_, the downstream service would have to be restarted to -notice the change. Sometimes this is OK but other times the downstream -has no other need to restart, if it can only get its new data. - -For this case, there is the `+anoia.svc+` Fennel library, which allows -you to write a simple loop which is iterated over whenever a service's -outputs change. This code is from -`+modules/dhcp6c/acquire-wan-address.fnl+` - -[source,fennel] ----- -(fn update-addresses [wan-device addresses new-addresses exec] - ;; run some appropriate "ip address [add|remove]" commands - ) - -(fn run [] - (let [[state-directory wan-device] arg - dir (svc.open state-directory)] -(accumulate [addresses [] - v (dir:events)] - (update-addresses wan-device addresses - (or (v:output "address") []) system)))) ----- - -The `+output+` method seen here accepts a filename (relative to the -service's output directory), or a directory name. It returns the first -line of that file, or for directories it returns a table (Lua's -key/value datastructure, similar to a hash/dictionary) of the outputs in -that directory. - -===== Output design considerations - -For preference, outputs should be short and simple, and not require -downstream services to do complicated parsing in order to use them. -Shell commands in Liminix are run using the Busybox shell which doesn't -have the niceties of an advanced shell like Bash let alone those of a -real programming language. - -Note also that the Lua `+svc+` library only reads the first line of each -output. - -=== Module implementation - -Modules in Liminix conventionally live in -`+modules/somename/default.nix+`. If you want or need to write your own, -you may wish to refer to the examples there in conjunction with reading -this section. - -A module is a function that accepts `+{lib, pkgs, config, ... }+` and -returns an attrset with keys `+imports, options config+`. - -* `+imports+` is a list of paths to the other modules required by this -one -* `+options+` is a nested set of option declarations -* `+config+` is a nested set of option definitions - -The NixOS manual section -https://nixos.org/manual/nixos/stable/#sec-writing-modules[Writing NixOS -Modules] is a quite comprehensive reference to writing NixOS modules, -which is also mostly applicable to Liminix except that it doesn't cover -service templates. - -==== Service templates - -To expose a service template in a module, it needs the following: - -* an option declaration for `+system.service.myservicename+` with the -type of `+liminix.lib.types.serviceDefn+` - -[source,nix] ----- -options = { - system.service.cowsay = mkOption { -type = liminix.lib.types.serviceDefn; - }; -}; ----- - -* an option definition for the same key, which specifies where to import -the service template from (often `+./service.nix+`) and the types of its -parameters. - -[source,nix] ----- -config.system.service.cowsay = config.system.callService ./service.nix { - address = mkOption { -type = types.str; -default = "0.0.0.0"; -description = "Listen on specified address"; -example = "127.0.0.1"; - }; - port = mkOption { -type = types.port; -default = 22; -description = "Listen on specified TCP port"; - }; - breed = mkOption { -type = types.str; -default = "British Friesian" -description = "Breed of the cow"; - }; -}; ----- - -Then you need to provide the service template itself, probably in -`+./service.nix+`: - -[source,nix] ----- -{ - # any nixpkgs package can be named here - liminix -, cowsayd -, serviceFns -, lib -}: -# these are the parameters declared in the callService invocation -{ address, port, breed} : -let - inherit (liminix.services) longrun; - inherit (lib.strings) escapeShellArg; -in longrun { - name = "cowsayd"; - run = "${cowsayd}/bin/cowsayd --address ${address} --port ${builtins.toString port} --breed ${escapeShellArg breed}"; -} ----- - -[TIP] -==== -Not relevant to module-based services specifically, but a common gotcha -when specifiying services is forgetting to transform "rich" parameter -values into text when composing a command for the shell to execute. Note -here that the port number, an integer, is stringified with `+toString+`, -and the name of the breed, which may contain spaces, is escaped with -`+escapeShellArg+` -==== - -==== Types - -All of the NixOS module types are available in Liminix. These -Liminix-specific types also exist in `+pkgs.liminix.lib.types+`: - -* `+service+`: an s6-rc service -* `+interface+`: an s6-rc service which specifies a network interface -* `+serviceDefn+`: a service "template" definition - -In the future it is likely that we will extend this to include other -useful types in the networking domain: for example; IP address, network -prefix or netmask, protocol family and others as we find them. diff --git a/doc/development.adoc b/doc/development.adoc index 5c5ad3b..3d8cd77 100644 --- a/doc/development.adoc +++ b/doc/development.adoc @@ -1,124 +1,287 @@ = For Developers -== Contributing +In any Nix-based system the line between "configuration" +and "development" is less of a line and more of a continuum. +This section covers some topics further towards the latter end. -Patches welcome! Also bug reports, documentation improvements, -experience reports/case studies etc etc all equally as welcome. +== Writing modules and services -* if you have an obvious bug fix, new package, documentation - improvement or other uncontroversial small patch, send it straight - in. +It helps here to know NixOS! Liminix uses the NixOS module +infrastructure code, meaning that everything that has been written for +NixOS about the syntax, the type system, and the rules for combining +configuration values from different sources is just as applicable +here. -* if you have a large new feature or design change in mind, please - please _get in touch_ to talk about it before you commit time to - implementing it. Perhaps it isn't what we were expecting, almost - certainly we will have ideas or advice on what it should do or how - it should be done. +=== Services -Liminix development is not tied to Github or any other particular -forge. How to send changes: +For the most part, for common use cases, we hope that Liminix modules +provide service templates for all the services you will need, and you +will only have to pass the right parameters to `+build+`. -1. Push your Liminix repo with your changes to a git repository -somewhere on the Internet that I can clone from. It can be on Codeberg -or Gitlab or Sourcehut or Forgejo or Gitea or Github or a bare repo in -your own personal web space or any kind of hosting you like. +But if you're reading this then our hopes are in vain. To create a +custom service of your own devising, use the [.title-ref]#oneshot# or +[.title-ref]#longrun# functions: -2. Email devel@liminix.org with the URL of the repo and the branch -name. And/or put it on the IRC channel if you prefer, as long as you -make sure someone who can merge it has seen your message. +* a "longrun" service is the "normal" service concept: it has a `+run+` +action which describes the process to start, and it watches that process +to restart it if it exits. The process should not attempt to daemonize +or "background" itself, otherwise s6-rc will think it died. Whatever it +prints to standard output/standard error will be logged. -If that's not an option, I’m also happy for you to send your changes -direct to the list itself, as an incremental git bundle or using git -format-patch. We'll work it out somehow. +[source,nix] +---- +config.services.cowsayd = pkgs.liminix.services.longrun { + name = "cowsayd"; + run = "${pkgs.cowsayd}/bin/cowsayd --port 3001 --breed hereford"; + # don't start this until the lan interface is ready + dependencies = [ config.services.lan ]; +} +---- -The main development repo for Liminix is hosted at -<https://gti.telent.net/dan/liminix>, with a read-only mirror at -<https://github.com/telent/liminix>. If you're happy to use Github -then you can fork from the latter to make your changes, but please use -the mailing list one of the approved routes to tell me about your changes because I -don't regularly check PRs. +* a "oneshot" service doesn't have a process attached. It consists of +`+up+` and `+down+` actions which are bits of shell script that are run +at the appropriate points in the service lifecycle -Remember that the <<_code_of_conduct>> applies to all Liminix spaces, -and anyone who violates it may be sanctioned or expelled from these -spaces at the discretion of the project leadership. +[source,nix] +---- +config.services.greenled = pkgs.liminix.services.oneshot { + name = "greenled"; + up = '' + echo 17 > /sys/class/gpio/export + echo out > /sys/class/gpio/gpio17/direction + echo 0 > /sys/class/gpio/gpio17/value + ''; + down = '' + echo 0 > /sys/class/gpio/gpio17/value + ''; +} +---- -=== Nix language style +Services may have dependencies: as you see above in the `+cowsayd+` +example, it depends on some service called `+config.services.lan+`, +meaning that it won't be started until that other service is up. -This section describes some Nix language style points that we attempt to -adhere to in this repo. Some are more aspirational than actual. +===== Service outputs -* indentation and style is according to `nixfmt-rfc-style` -* favour `+callPackage+` over raw `+import+` for calling derivations or -any function that may generate one - any code that might need `+pkgs+` -or parts of it. -* prefer `+let inherit (quark) up down strange charm+` over -`+with quark+`, in any context where the scope is more than a single -expression or there is more than one reference to `+up+`, `+down+` etc. -`+with pkgs; [ foo bar baz]+` is OK, -`+with lib; stdenv.mkDerivation { ... }+` is usually not. -* `+<liminix>+` is defined only when running tests, so don't refer to it -in "application" code -* the parameters to a derivation are sorted alphabetically, except for -`+lib+`, `+stdenv+` and maybe other non-package "special cases" -* where a `+let+` form defines multiple names, put a newline after the -token `+let+`, and indent each name two characters -* to decide whether some code should be a package or a module? Packages -are self-contained - they live in `+/nix/store/eeeeeee-name+` and don't -directly change system behaviour by their presence or absense. modules -can add to `+/etc+` or `+/bin+` or other global state, create services, -all that side-effecty stuff. Generally it should be a package unless it -can't be. +Outputs are a mechanism by which a service can provide data which may be +required by other services. For example: -=== Copyright +* the DHCP client service can expect to receive nameserver address +information as one of the fields in the response from the DHCP server: +we provide that as an output which a dependent service for a stub name +resolver can use to configure its upstream servers. +* a service that creates a new network interface (e.g. ppp) will provide +the name of the interface (`+ppp0+`, or `+ppp1+` or `+ppp7+`) as an +output so that a dependent service can reference it to set up a route, +or to configure firewall rules. -The Nix code in Liminix is MIT-licenced (same as Nixpkgs), but the code -it combines from other places (e.g. Linux, OpenWrt) may have a variety -of licences. Copyright assignment is not expected: -just like when submitting to the Linux kernel you retain the copyright -on the code you contribute. +A service `+myservice+` should write its outputs as files in +`+/run/services/outputs/myservice+`: you can look around this directory +on a running Liminix system to see how it's used currently. Usually we +use the `+in_outputs+` shell function in the `+up+` or `+run+` +attributes of the service: -=== Automated builds +[source,shell] +---- +(in_outputs ${name} + for i in lease mask ip router siaddr dns serverid subnet opt53 interface ; do + (printenv $i || true) > $i + done) +---- -Automated builds are run on each push to the main branch. This tests -that (among other things) +The outputs are just files, so technically you can read them using +anything that can read a file. Liminix has two "preferred" mechanisms, +though: -* every device image builds -* the build for the “qemu” target is executed with a fake network upstream to test - * PPPoE and DHCP service - * hostap (wireless gateway) +===== One-off lookups -You can view the build output at https://build.liminix.org . The tests -are defined in ci.nix. +In any context that ends up being evaluated by the shell, use `+output+` +to print the value of an output -Unfortunately there's no (easy) way I can make _my_ CI infrastructure -run _your_ code, other than merging it. But see <<_running_tests>> -for how to exercise the same code locally on your machine. +[source,nix] +---- +services.defaultroute4 = svc.network.route.build { + via = "$(output ${services.wan} address)"; + target = "default"; + dependencies = [ services.wan ]; +}; +---- + +===== Continuous updates + +The downside of using shell functions in downstream service startup +scripts is that they only run when the service starts up: if a service +output _changes_, the downstream service would have to be restarted to +notice the change. Sometimes this is OK but other times the downstream +has no other need to restart, if it can only get its new data. + +For this case, there is the `+anoia.svc+` Fennel library, which allows +you to write a simple loop which is iterated over whenever a service's +outputs change. This code is from +`+modules/dhcp6c/acquire-wan-address.fnl+` + +[source,fennel] +---- +(fn update-addresses [wan-device addresses new-addresses exec] + ;; run some appropriate "ip address [add|remove]" commands + ) + +(fn run [] + (let [[state-directory wan-device] arg + dir (svc.open state-directory)] + (accumulate [addresses [] + v (dir:events)] + (update-addresses wan-device addresses + (or (v:output "address") []) system)))) +---- + +The `+output+` method seen here accepts a filename (relative to the +service's output directory), or a directory name. It returns the first +line of that file, or for directories it returns a table (Lua's +key/value datastructure, similar to a hash/dictionary) of the outputs in +that directory. + +===== Design considerations for outputs + +For preference, outputs should be short and simple, and not require +downstream services to do complicated parsing in order to use them. +Shell commands in Liminix are run using the Busybox shell which doesn't +have the niceties evel of an advanced shell like Bash, let alone those of a +real programming language. + +Note also that the Lua `+svc+` library only reads the first line of each +output. -== Development tools +=== Modules -In this section we describe some tools to make the edit/build/run -development cycle less painful than flashing a new image on a hardware -device every time. +Modules in Liminix conventionally live in +`+modules/somename/default.nix+`. If you want or need to write your own, +you may wish to refer to the examples there in conjunction with reading +this section. + +A module is a function that accepts `+{lib, pkgs, config, ... }+` and +returns an attrset with keys `+imports, options, config+`. + +* `+imports+` is a list of paths to the other modules required by this +one +* `+options+` is a nested set of option declarations +* `+config+` is a nested set of option definitions + +The NixOS manual section +https://nixos.org/manual/nixos/stable/#sec-writing-modules[Writing NixOS +Modules] is a quite comprehensive reference to writing NixOS modules, +which is also mostly applicable to Liminix except that it doesn't cover +service templates. + + +==== Service templates + +Although you can define services "ad hoc" using `longrun` or `oneshot` +<<_writing_services,as above>>, this approach has limitations if +you're writing code intended for wider use. Services in the +modules bundled with Liminix are implemented following a pattern we +call "service templates": functions that accept a _type-checked_ +attrset and return an appropriately configured service that can be +assigned by the caller to a key in ``config.services``. + +To expose a service template in a module, it needs the following: + +* an option declaration for `+system.service.myservicename+` with the +type of `+liminix.lib.types.serviceDefn+` + +[source,nix] +---- +options = { + system.service.cowsay = mkOption { + type = liminix.lib.types.serviceDefn; + }; +}; +---- + +* an option definition for the same key, which specifies where to import +the service template from (often `+./service.nix+`) and the types of its +parameters. + +[source,nix] +---- +config.system.service.cowsay = config.system.callService ./service.nix { + address = mkOption { + type = types.str; + default = "0.0.0.0"; + description = "Listen on specified address"; + example = "127.0.0.1"; + }; + port = mkOption { + type = types.port; + default = 22; + description = "Listen on specified TCP port"; + }; + breed = mkOption { + type = types.str; + default = "British Friesian" + description = "Breed of the cow"; + }; +}; +---- + +Then you need to provide the service template itself, probably in +`+./service.nix+`: + +[source,nix] +---- +{ + # any nixpkgs package can be named here + liminix +, cowsayd +, serviceFns +, lib +}: +# these are the parameters declared in the callService invocation +{ address, port, breed} : +let + inherit (liminix.services) longrun; + inherit (lib.strings) escapeShellArg; +in longrun { + name = "cowsayd"; + run = "${cowsayd}/bin/cowsayd --address ${address} --port ${builtins.toString port} --breed ${escapeShellArg breed}"; +} +---- + +TIP: Not relevant to module-based services specifically, but a common gotcha +when specifiying services is forgetting to transform "rich" parameter +values into text when composing a command for the shell to execute. Note +here that the port number, an integer, is stringified with `+toString+`, +and the name of the breed, which may contain spaces, is escaped with +`+escapeShellArg+` + +=== Types + +All of the NixOS module types are available in Liminix. These +Liminix-specific types also exist in `+pkgs.liminix.lib.types+`: + +* `+service+`: an s6-rc service +* `+interface+`: an s6-rc service which specifies a network interface +* `+serviceDefn+`: a service "template" definition + +In the future it is likely that we will extend this to include other +useful types in the networking domain: for example; IP address, network +prefix or netmask, protocol family and others as we find them. -// FIXME if this is still true we should fix it -In general, packages and tools that run on the "build" machine are -available in the `+buildEnv+` derivation and can most easily be added to -your environment by running `+nix-shell+`. === Emulated devices -Liminix has a number of emulated device descriptions which generate -images suitable for running on your build machine using the free +Unless your changes depend on particular hardware devices, you may +want to test your new/changed module with one of the emulated +"devices" which runn on your build machine using the free http://www.qemu.org[QEMU machine emulator]. They are * `qemu`(MIPS) * `qemu-armv7l`(32 bit ARM) * `qemu-aarch64` (64 bit ARM) -This is useful for developing userland without needing to keep -flashing or messing with U-Boot: it also enables testing against -emulated network peers using +This means you don't need to keep flashing or messing with U-Boot: it +also enables testing against emulated network peers using https://wiki.qemu.org/Documentation/Networking#Socket[QEMU socket networking], which may be preferable to letting Liminix loose on your actual LAN. To build, @@ -143,7 +306,7 @@ connect-vm+` to connect to either of these sockets, and ^O to disconnect. [[qemu-networking]] -==== Networking +===== Networking VMs can network with each other using QEMU socket networking. We observe these conventions, so that we can run multiple emulated instances and @@ -158,7 +321,7 @@ Any VM started by a `+run.sh+` script is connected to "lan" and "access". The emulated upstream (see below) runs PPPoE and is connected to "access" and "world". -==== Upstream connection +===== Upstream connection In pkgs/routeros there is a derivation to install and configure https://mikrotik.com/software[Mikrotik RouterOS] as a PPPoE access @@ -186,9 +349,11 @@ own responsibility if you use this to ensure you're compliant with the terms of Mikrotik's licencing. It may be supplemented or replaced in time with configurations for RP-PPPoE and/or Accel PPP.# -=== Hardware devices +== Hardware hacking/porting to new device -==== TFTP +Coming soon + +=== TFTP [[tftpserver]] How you get your image onto hardware will vary according to the device, @@ -238,7 +403,7 @@ to copy-paste the whole of `+boot.scr+` into a terminal emulator and have it work just like that. You may need to paste each line one at a time, or even retype it. -==== Running from RAM +=== Running from RAM For a faster edit-compile-test cycle, you can build a TFTP-bootable image which boots directly from RAM (using phram) instead of needing @@ -256,7 +421,7 @@ transfer the kernel and filesystem over TFTP and boot the kernel from RAM. [[bng]] -==== Networking +=== Networking You probably don't want to be testing a device that might serve DHCP, DNS and routing protocols on the same LAN as you (or your colleagues, @@ -322,6 +487,99 @@ NOTE: If you make changes to the bordervm configuration after executing `+run-border-vm+`, you need to remove the `+border.qcow2+` disk image file otherwise the changes won't get picked up. +== Contributing + +Patches welcome! Also bug reports, documentation improvements, +experience reports/case studies etc etc all equally as welcome. + +* if you have an obvious bug fix, new package, documentation + improvement or other uncontroversial small patch, send it straight + in. + +* if you have a large new feature or design change in mind, please + please _get in touch_ to talk about it before you commit time to + implementing it. Perhaps it isn't what we were expecting, almost + certainly we will have ideas or advice on what it should do or how + it should be done. + +Liminix development is not tied to Github or any other particular +forge. How to send changes: + +1. Push your Liminix repo with your changes to a git repository +somewhere on the Internet that I can clone from. It can be on Codeberg +or Gitlab or Sourcehut or Forgejo or Gitea or Github or a bare repo in +your own personal web space or any kind of hosting you like. + +2. Email devel@liminix.org with the URL of the repo and the branch +name, and we will take a look. + +If that's not an option, I’m also happy for you to send your changes +direct to the list itself, as an incremental git bundle or using git +format-patch. We'll work it out somehow. + +The main development repo for Liminix is hosted at +<https://gti.telent.net/dan/liminix>, with a read-only mirror at +<https://github.com/telent/liminix>. If you're happy to use Github +then you can fork from the latter to make your changes, but please use +the mailing list one of the approved routes to tell me about your changes because I don't regularly go there to check PRs. + +Remember that the <<_code_of_conduct>> applies to all Liminix spaces, +and anyone who violates it may be sanctioned or expelled from these +spaces at the discretion of the project leadership. + +=== Nix language style + +This section describes some Nix language style points that we attempt to +adhere to in this repo. Some are more aspirational than actual. + +* indentation and style is according to `nixfmt-rfc-style` +* favour `+callPackage+` over raw `+import+` for calling derivations or +any function that may generate one - any code that might need `+pkgs+` +or parts of it. +* prefer `+let inherit (quark) up down strange charm+` over +`+with quark+`, in any context where the scope is more than a single +expression or there is more than one reference to `+up+`, `+down+` etc. +`+with pkgs; [ foo bar baz]+` is OK, +`+with lib; stdenv.mkDerivation { ... }+` is usually not. +* `+<liminix>+` is defined only when running tests, so don't refer to it +in "application" code +* the parameters to a derivation are sorted alphabetically, except for +`+lib+`, `+stdenv+` and maybe other non-package "special cases" +* where a `+let+` form defines multiple names, put a newline after the +token `+let+`, and indent each name two characters +* to decide whether some code should be a package or a module? Packages +are self-contained - they live in `+/nix/store/eeeeeee-name+` and don't +directly change system behaviour by their presence or absense. modules +can add to `+/etc+` or `+/bin+` or other global state, create services, +all that side-effecty stuff. Generally it should be a package unless it +can't be. + +=== Copyright + +The Nix code in Liminix is MIT-licenced (same as Nixpkgs), but the code +it combines from other places (e.g. Linux, OpenWrt) may have a variety +of licences. Copyright assignment is not expected: +just like when submitting to the Linux kernel you retain the copyright +on the code you contribute. + +=== Automated builds + +Automated builds are run on each push to the main branch. This tests +that (among other things) + +* every device image builds +* the build for the “qemu” target is executed with a fake network upstream to test + * PPPoE and DHCP service + * hostap (wireless gateway) + +You can view the build output at https://build.liminix.org . The tests +are defined in ci.nix. + +Unfortunately there's no (easy) way I can make _my_ CI infrastructure +run _your_ code, other than merging it. But see <<_running_tests>> +for how to exercise the same code locally on your machine. + + == Running tests You can run all of the tests by evaluating `+ci.nix+`, which is the @@ -333,12 +591,6 @@ nix-build -I liminix=`pwd` ci.nix -A pppoe # run one job nix-build -I liminix=`pwd` ci.nix -A all # run all jobs ---- -== Porting to new hardware - -// FIXME add this - -TBD - == Troubleshooting === Diagnosing unexpectedly large images