Compare commits

..

17 Commits
main ... lte

Author SHA1 Message Date
Daniel Barlow dc3ade7969 commentary 2024-05-14 22:27:37 +01:00
Daniel Barlow 7ece8dde5e add hacky wwan service with hardcoding all over 2024-05-14 22:19:34 +01:00
Daniel Barlow 5a19af318e ref cdc-ncm module by corretc name 9suash) 2024-05-14 21:57:28 +01:00
Daniel Barlow 6d36f0eb13 remove leftovers from arhcive example 2024-05-14 18:08:53 +01:00
Daniel Barlow 5d246e1f1a create cdc-ncm module 2024-05-14 18:07:33 +01:00
Daniel Barlow 0e2e44c6ee barebones usb_modeswitch package 2024-05-14 12:56:58 +01:00
Daniel Barlow e2c5615677 l2tp set default route via tunnel 2024-05-14 12:52:50 +01:00
Daniel Barlow ab59c852e2 exec xl2tpd
haven't fully worked out why, but without this s6 is unable to stop it.
2024-05-13 18:41:06 +01:00
Daniel Barlow 98d198960b think 2024-05-13 18:41:06 +01:00
Daniel Barlow d98f011292 add rudimentary l2tp service module 2024-05-13 18:41:06 +01:00
Daniel Barlow 991de7eb9e bordervm enable nat 2024-05-13 18:41:06 +01:00
Daniel Barlow 513c69cb5b gl-ar750 appendDTB 2024-05-13 18:41:06 +01:00
Daniel Barlow 15856d224c memorable net device names for gl-ar750
linux's view of eth1 and eth0 are opposite to that of u-boot
2024-05-13 18:41:06 +01:00
Daniel Barlow abbdda7b30 run dhcp server on bordervm
this is for testing clients that have dhcp upstream
2024-05-13 18:41:06 +01:00
Daniel Barlow e56b761ba3 rename overlaid dnsmasq package -> dnsmasqSmall
avoid clobbering the regular one
2024-05-13 18:41:06 +01:00
Daniel Barlow 15ec172739 tftp addresses 2024-05-13 18:41:06 +01:00
Daniel Barlow 6ddba0f68b think 2024-05-13 18:41:06 +01:00
1677 changed files with 1442 additions and 7848 deletions

12
NEWS
View File

@ -103,15 +103,3 @@ a bit more useful :-)
}; };
}) })
]; ];
2024-07-16
* structured parameters are available for the pppoe service
* The "wan" configuration in modules/profiles/gateway.nix has changed:
instead of passing options that are used to create a pppoe interface,
callers should create a (pppoe or other) interface and pass that as
the value of profile.gateway.wan. For the pppoe case this is now only
very slightly more verbose, and it allows using the gateway profile
with other kinds of upstream.

File diff suppressed because it is too large Load Diff

View File

@ -1,20 +0,0 @@
# This is for use with minicom, but needs you to configure it to
# use expect as its "Script program" instead of runscript. Try
# Ctrl+A O -> Filenames and paths -> D
log_user 0
log_file -a -open stderr
set f [open "result/boot.scr"]
send "version\r"
set timeout 60
while {[gets $f line] >= 0} {
puts stderr "next line $line\r"
puts stderr "waiting for prompt\r"
expect {
"ath>" {}
"BusyBox" { puts stderr "DONE"; exit 0 }
}
send "$line\r\n"
}
puts stderr "done\r\n"
close $f

View File

@ -6,7 +6,7 @@ in {
options.bordervm = { options.bordervm = {
keys = mkOption { keys = mkOption {
type = types.listOf types.str; type = types.listOf types.str;
default = [ ]; default = [];
}; };
l2tp = { l2tp = {
host = mkOption { host = mkOption {
@ -55,17 +55,18 @@ in {
<nixpkgs/nixos/modules/virtualisation/qemu-vm.nix> <nixpkgs/nixos/modules/virtualisation/qemu-vm.nix>
]; ];
config = { config = {
boot.kernelParams = [ "loglevel=9" ]; boot.kernelParams = [
"loglevel=9"
];
systemd.services.pppoe = systemd.services.pppoe =
let let conf = pkgs.writeText "kpppoed.toml"
conf = pkgs.writeText "kpppoed.toml" '' ''
interface_name = "eth1" interface_name = "eth1"
services = [ "myservice" ] services = [ "myservice" ]
lns_ipaddr = "${cfg.l2tp.host}:${builtins.toString cfg.l2tp.port}" lns_ipaddr = "${cfg.l2tp.host}:${builtins.toString cfg.l2tp.port}"
ac_name = "kpppoed-1.0" ac_name = "kpppoed-1.0"
''; '';
in in {
{
wantedBy = [ "multi-user.target" ]; wantedBy = [ "multi-user.target" ];
after = [ "network-online.target" ]; after = [ "network-online.target" ];
serviceConfig = { serviceConfig = {
@ -82,43 +83,32 @@ in {
services.dnsmasq = { services.dnsmasq = {
enable = true; enable = true;
resolveLocalQueries = false; resolveLocalQueries = false;
settings = { settings = {
# domain-needed = true; # domain-needed = true;
dhcp-range = [ "10.0.0.10,10.0.0.240" ]; dhcp-range = [ "10.0.0.10,10.0.0.240" ];
interface = "eth1"; interface = "eth1";
}; };
}; };
services.nginx = {
enable = true;
user = "liminix";
virtualHosts.${config.networking.hostName} = {
root = "/home/liminix";
default = true;
};
};
systemd.services.nginx.serviceConfig.ProtectHome = "read-only";
systemd.services.sshd.wantedBy = pkgs.lib.mkForce [ "multi-user.target" ]; systemd.services.sshd.wantedBy = pkgs.lib.mkForce [ "multi-user.target" ];
virtualisation = { virtualisation = {
qemu = { qemu = {
networkingOptions = [ ]; networkingOptions = [];
options = options = [] ++
[ ] optional cfg.ethernet.pci.enable
++ optional cfg.ethernet.pci.enable "-device vfio-pci,host=${cfg.ethernet.pci.id}" "-device vfio-pci,host=${cfg.ethernet.pci.id}" ++
++ optionals cfg.ethernet.usb.enable [ optionals cfg.ethernet.usb.enable [
"-device usb-ehci,id=ehci" "-device usb-ehci,id=ehci"
"-device usb-host,bus=ehci.0,vendorid=${cfg.ethernet.usb.vendor},productid=${cfg.ethernet.usb.product}" "-device usb-host,bus=ehci.0,vendorid=${cfg.ethernet.usb.vendor},productid=${cfg.ethernet.usb.product}"
] ] ++ [
++ [
"-nographic" "-nographic"
"-serial mon:stdio" "-serial mon:stdio"
]; ];
}; };
sharedDirectories = { sharedDirectories = {
liminix = { liminix = {
securityModel = "none";
source = builtins.toString ./.; source = builtins.toString ./.;
target = "/home/liminix/liminix"; target = "/home/liminix/liminix";
}; };
@ -146,13 +136,13 @@ in {
nat = { nat = {
enable = true; enable = true;
internalInterfaces = [ "eth1" ]; internalInterfaces = [ "eth1" ];
externalInterface = "eth0"; externalInterface ="eth0";
}; };
}; };
users.users.liminix = { users.users.liminix = {
isNormalUser = true; isNormalUser = true;
uid = 1000; uid = 1000;
extraGroups = [ "wheel" ]; extraGroups = [ "wheel"];
openssh.authorizedKeys.keys = cfg.keys; openssh.authorizedKeys.keys = cfg.keys;
}; };
services.getty.autologinUser = "liminix"; services.getty.autologinUser = "liminix";

View File

@ -1,12 +1,8 @@
{ ... }: {...}:
{ {
bordervm = { bordervm = {
# ethernet.pci = { id = "01:00.0"; enable = true; }; # ethernet.pci = { id = "01:00.0"; enable = true; };
ethernet.usb = { ethernet.usb = { vendor = "0x0bda"; product = "0x8153"; enable = true; };
vendor = "0x0bda";
product = "0x8153";
enable = true;
};
l2tp = { l2tp = {
host = "l2tp.aa.net.uk"; host = "l2tp.aa.net.uk";
}; };

66
ci.nix
View File

@ -1,12 +1,12 @@
{ {
nixpkgs, nixpkgs
unstable, , unstable
liminix, , liminix
... , ... }:
}:
let let
pkgs = (import nixpkgs { }); inherit (builtins) map;
borderVmConf = ./bordervm.conf-example.nix; pkgs = (import nixpkgs {});
borderVmConf = ./bordervm.conf-example.nix;
inherit (pkgs.lib.attrsets) genAttrs; inherit (pkgs.lib.attrsets) genAttrs;
devices = [ devices = [
"gl-ar750" "gl-ar750"
@ -27,47 +27,45 @@ let
}).outputs.default; }).outputs.default;
tests = import ./tests/ci.nix; tests = import ./tests/ci.nix;
jobs = jobs =
(genAttrs devices for-device) (genAttrs devices for-device) //
// tests tests //
// { {
buildEnv = buildEnv = (import liminix {
(import liminix { inherit nixpkgs borderVmConf;
inherit nixpkgs borderVmConf; device = import (liminix + "/devices/qemu");
device = import (liminix + "/devices/qemu"); liminix-config = vanilla;
liminix-config = vanilla; }).buildEnv;
}).buildEnv;
doc = doc =
let let json =
json = (import liminix {
(import liminix { inherit nixpkgs borderVmConf;
inherit nixpkgs borderVmConf; device = import (liminix + "/devices/qemu");
device = import (liminix + "/devices/qemu"); liminix-config = {...} : {
liminix-config =
{ ... }:
{
imports = [ ./modules/all-modules.nix ]; imports = [ ./modules/all-modules.nix ];
}; };
}).outputs.optionsJson; }).outputs.optionsJson;
in installers = map (f: "system.outputs.${f}") [
pkgs.stdenv.mkDerivation { "vmroot"
"mtdimage"
"ubimage"
];
inherit (pkgs.lib) concatStringsSep;
in pkgs.stdenv.mkDerivation {
name = "liminix-doc"; name = "liminix-doc";
nativeBuildInputs = with pkgs; [ nativeBuildInputs = with pkgs; [
gnumake gnumake sphinx fennel luaPackages.lyaml
sphinx
fennel
luaPackages.lyaml
]; ];
src = ./.; src = ./.;
buildPhase = '' buildPhase = ''
cat ${json} | fennel --correlate doc/parse-options.fnl > doc/modules-generated.inc.rst cat ${json} | fennel --correlate doc/parse-options.fnl > doc/modules-generated.rst
cat ${json} | fennel --correlate doc/parse-options-outputs.fnl > doc/outputs-generated.inc.rst cat ${json} | fennel --correlate doc/parse-options-outputs.fnl > doc/outputs-generated.rst
cp ${(import ./doc/hardware.nix)} doc/hardware.rst cp ${(import ./doc/hardware.nix)} doc/hardware.rst
make -C doc html make -C doc html
''; '';
installPhase = '' installPhase = ''
mkdir -p $out/nix-support $out/share/doc/ mkdir -p $out/nix-support $out/share/doc/
cd doc cd doc
cp *-generated.inc.rst hardware.rst $out cp *-generated.rst $out
ln -s ${json} $out/options.json ln -s ${json} $out/options.json
cp -a _build/html $out/share/doc/liminix cp -a _build/html $out/share/doc/liminix
echo "file source-dist \"$out/share/doc/liminix\"" \ echo "file source-dist \"$out/share/doc/liminix\"" \

View File

@ -1,27 +1,24 @@
{ {
deviceName ? null, deviceName ? null
device ? (import ./devices/${deviceName}), , device ? (import ./devices/${deviceName} )
liminix-config ? <liminix-config>, , liminix-config ? <liminix-config>
nixpkgs ? <nixpkgs>, , nixpkgs ? <nixpkgs>
borderVmConf ? ./bordervm.conf.nix, , borderVmConf ? ./bordervm.conf.nix
imageType ? "primary", , imageType ? "primary"
}: }:
let let
overlay = import ./overlay.nix; overlay = import ./overlay.nix;
pkgs = import nixpkgs ( pkgs = import nixpkgs (device.system // {
device.system overlays = [overlay];
// { config = {
overlays = [ overlay ]; allowUnsupportedSystem = true; # mipsel
config = { permittedInsecurePackages = [
allowUnsupportedSystem = true; # mipsel "python-2.7.18.6" # kernel backports needs python <3
permittedInsecurePackages = [ "python-2.7.18.7"
"python-2.7.18.6" # kernel backports needs python <3 ];
"python-2.7.18.7" };
]; });
};
}
);
eval = pkgs.lib.evalModules { eval = pkgs.lib.evalModules {
specialArgs = { specialArgs = {
@ -49,14 +46,7 @@ let
borderVm = ((import <nixpkgs/nixos/lib/eval-config.nix>) { borderVm = ((import <nixpkgs/nixos/lib/eval-config.nix>) {
system = builtins.currentSystem; system = builtins.currentSystem;
modules = [ modules = [
{ ({ ... } : { nixpkgs.overlays = [ overlay ]; })
nixpkgs.overlays = [
(final: prev: {
go-l2tp = final.callPackage ./pkgs/go-l2tp {};
tufted = final.callPackage ./pkgs/tufted {};
})
];
}
(import ./bordervm-configuration.nix) (import ./bordervm-configuration.nix)
borderVmConf borderVmConf
]; ];

View File

@ -213,6 +213,7 @@
networkInterfaces = networkInterfaces =
let let
inherit (config.system.service.network) link; inherit (config.system.service.network) link;
inherit (config.system.service) bridge;
in rec { in rec {
wan = link.build { ifname = "wan"; }; wan = link.build { ifname = "wan"; };
lan1 = link.build { ifname = "lan1"; }; lan1 = link.build { ifname = "lan1"; };

View File

@ -23,17 +23,12 @@
VIRTIO_BLK = "y"; VIRTIO_BLK = "y";
VIRTIO_NET = "y"; VIRTIO_NET = "y";
}; };
conditionalConfig = {
WLAN= {
MAC80211_HWSIM = "m";
};
};
}; };
hardware = hardware =
let let
mac80211 = pkgs.kmodloader.override { mac80211 = pkgs.mac80211.override {
inherit (config.system.outputs) kernel; drivers = ["mac80211_hwsim"];
targets = ["mac80211_hwsim"]; klibBuild = config.system.outputs.kernel.modulesupport;
}; };
in { in {
defaultOutput = "vmroot"; defaultOutput = "vmroot";

View File

@ -92,6 +92,7 @@
''; '';
}; };
inherit (pkgs.pseudofile) dir symlink; inherit (pkgs.pseudofile) dir symlink;
inherit (pkgs.liminix.networking) interface;
in { in {
imports = [ imports = [
../../modules/network ../../modules/network
@ -126,11 +127,11 @@
in { in {
lan = link.build { lan = link.build {
ifname = "lan"; ifname = "lan";
devpath = "/devices/platform/ahb/1a000000.eth"; devpath = "/devices/platform/ahb/19000000.eth";
}; };
wan = link.build { wan = link.build {
ifname = "wan"; ifname = "wan";
devpath = "/devices/platform/ahb/19000000.eth"; devpath = "/devices/platform/ahb/1a000000.eth";
}; };
wlan = link.build { wlan = link.build {
ifname = "wlan0"; ifname = "wlan0";

View File

@ -45,6 +45,7 @@
module = { pkgs, config, lib, lim, ...}: module = { pkgs, config, lib, lim, ...}:
let let
inherit (pkgs.liminix.networking) interface;
inherit (pkgs) openwrt; inherit (pkgs) openwrt;
mac80211 = pkgs.kmodloader.override { mac80211 = pkgs.kmodloader.override {
targets = ["rt2800soc"]; targets = ["rt2800soc"];
@ -89,6 +90,19 @@
let let
inherit (config.system.service.network) link; inherit (config.system.service.network) link;
inherit (config.system.service) vlan; inherit (config.system.service) vlan;
inherit (pkgs.liminix.services) oneshot;
swconfig = oneshot {
name = "swconfig";
up = ''
PATH=${pkgs.swconfig}/bin:$PATH
swconfig dev switch0 set reset
swconfig dev switch0 set enable_vlan 1
swconfig dev switch0 vlan 1 set ports '1 2 3 4 6t'
swconfig dev switch0 vlan 2 set ports '0 6t'
swconfig dev switch0 set apply
'';
down = "${pkgs.swconfig}/bin/swconfig dev switch0 set reset";
};
in rec { in rec {
eth = link.build { ifname = "eth0"; }; eth = link.build { ifname = "eth0"; };
# lan and wan ports are both behind a switch on eth0 # lan and wan ports are both behind a switch on eth0

View File

@ -13,7 +13,7 @@
GL.iNet GL-MT300N-v2 GL.iNet GL-MT300N-v2
******************** ********************
The GL-MT300N-v2 "Mango" is is very similar to the :ref:`gl-mt300a`, but is The GL-MT300N-v2 "Mango" is is very similar to the :ref:`MT300A <GL.iNet GL-MT300A>, but is
based on the MT7628 chipset instead of MT7620. It's also marginally cheaper based on the MT7628 chipset instead of MT7620. It's also marginally cheaper
and comes in a yellow case not a blue one. Be sure your device is and comes in a yellow case not a blue one. Be sure your device is
v2 not v1, which is a different animal and has only half as much RAM. v2 not v1, which is a different animal and has only half as much RAM.
@ -38,6 +38,7 @@
module = { pkgs, config, lib, lim, ...}: module = { pkgs, config, lib, lim, ...}:
let let
inherit (pkgs.liminix.networking) interface;
inherit (pkgs.liminix.services) oneshot; inherit (pkgs.liminix.services) oneshot;
inherit (pkgs.pseudofile) dir symlink; inherit (pkgs.pseudofile) dir symlink;
inherit (pkgs) openwrt; inherit (pkgs) openwrt;

View File

@ -19,14 +19,14 @@
ARM targets differ from MIPS in that the kernel format expected ARM targets differ from MIPS in that the kernel format expected
by QEMU is an "Image" (raw binary file) rather than an ELF by QEMU is an "Image" (raw binary file) rather than an ELF
file, but this is taken care of by :command:`run.sh`. Check the file, but this is taken care of by :command:`run.sh`. Check the
documentation for the :ref:`qemu` target for more information. documentation for the :ref:`QEMU` (MIPS) target for more information.
''; '';
# this device is described by the "qemu" device # this device is described by the "qemu" device
installer = "vmroot"; installer = "vmroot";
module = { config, lim, ... }: { module = {pkgs, config, lim, ... }: {
imports = [ imports = [
../../modules/arch/aarch64.nix ../../modules/arch/aarch64.nix
../families/qemu.nix ../families/qemu.nix

View File

@ -24,7 +24,7 @@
''; '';
installer = "vmroot"; installer = "vmroot";
module = { config, lim, ... }: { module = {pkgs, config, lim, ... }: {
imports = [ imports = [
../../modules/arch/arm.nix ../../modules/arch/arm.nix
../families/qemu.nix ../families/qemu.nix

View File

@ -36,7 +36,7 @@
in the Development manual. in the Development manual.
''; '';
module = { config, lib, lim, ... }: { module = {pkgs, config, lib, lim, ... }: {
imports = [ imports = [
../../modules/arch/mipseb.nix ../../modules/arch/mipseb.nix
../families/qemu.nix ../families/qemu.nix

View File

@ -419,6 +419,7 @@
networkInterfaces = networkInterfaces =
let let
inherit (config.system.service.network) link; inherit (config.system.service.network) link;
inherit (config.system.service) bridge;
in rec { in rec {
lan1 = link.build { ifname = "lan1"; }; lan1 = link.build { ifname = "lan1"; };
lan2 = link.build { ifname = "lan2"; }; lan2 = link.build { ifname = "lan2"; };

View File

@ -155,6 +155,8 @@
module = {pkgs, config, lib, lim, ... }: module = {pkgs, config, lib, lim, ... }:
let let
openwrt = pkgs.openwrt;
inherit (lib) mkOption types;
inherit (pkgs.liminix.services) oneshot; inherit (pkgs.liminix.services) oneshot;
inherit (pkgs) liminix; inherit (pkgs) liminix;
mtd_by_name_links = pkgs.liminix.services.oneshot rec { mtd_by_name_links = pkgs.liminix.services.oneshot rec {
@ -356,6 +358,7 @@
networkInterfaces = networkInterfaces =
let let
inherit (config.system.service.network) link; inherit (config.system.service.network) link;
inherit (config.system.service) bridge;
in rec { in rec {
en70000 = link.build { en70000 = link.build {
# in armada-38x.dtsi this is eth0. # in armada-38x.dtsi this is eth0.

View File

@ -103,6 +103,8 @@
module = { pkgs, config, lib, lim, ...}: module = { pkgs, config, lib, lim, ...}:
let let
inherit (pkgs.liminix.networking) interface;
inherit (pkgs.liminix.services) oneshot;
inherit (pkgs.pseudofile) dir symlink; inherit (pkgs.pseudofile) dir symlink;
inherit (pkgs) openwrt; inherit (pkgs) openwrt;

View File

@ -4,134 +4,143 @@ System Administration
Services on a running system Services on a running system
**************************** ****************************
Liminix services are built on s6-rc, which is itself layered on s6. * add an s6-rc cheatsheet here
Services are defined at build time in your configuration (see
:ref:`configuration-services` for information) and can't be added
to/changed at runtime, but to monitor
events or diagnose problems you may need to inspect them on the
running system. Here are some of the most commonly used s6,-rc
commands:
.. list-table:: Service management quick reference
:widths: 55 45
:header-rows: 1
* - What
- How
* - List all running services
- ``s6-rc -a list``
* - List all services that are **not** running
- ``s6-rc -da list``
* - List services that ``wombat`` depends on
- ``s6-rc-db dependencies wombat``
* - ... transitively
- ``s6-rc-db all-dependencies wombat``
* - List services that depend on service ``wombat``
- ``s6-rc-db -d dependencies wombat``
* - ... transitively
- ``s6-rc-db -d all-dependencies wombat``
* - Stop service ``wombat`` and everything depending on it
- ``s6-rc -d change wombat``
* - Start service ``wombat`` (but not any services depending on it)
- ``s6-rc -u change wombat``
* - Start service ``wombat`` and all* services depending on it
- ``s6-rc-up-tree wombat``
:command:`s6-rc-up-tree` brings up a service and all services that Flashing and updating
depend on it, except for any services that depend on a "controlled" *********************
service that is not currently running. Controlled services are not
started at boot time but in response to external events (e.g. plugging
in a particular piece of hardware) so you probably don't want to be
starting them by hand if the conditions aren't there.
A service may be **up** or **down** (there are no intermediate states
like "started" or "stopping" or "dying" or "cogitating"). Some (but
not all) services have "readiness" notifications: the dependents of a
service with a readiness notification won't be started until the
service signals (by writing to a nominated file descriptor) that it's
prepared to start work. Most services defined by Liminix also have a
``timeout-up`` parameter, which means that if a service has readiness
notifications and doesn't become ready in the allotted time (defaults
20 seconds) it will be terminated and its state set to **down**.
If the process providing a service dies, it will be restarted
automatically. Liminix does not automatically set it to **down**.
(If the process providing a service dies without ever notifying Flashing from Liminix
readiness, Liminix will restart it as many times as it has to until the =====================
timeout period elapses, and then stop it and mark it down.)
Controlled services The flash procedure from an existing Liminix-system has two steps.
=================== First we reboot the device (using "kexec") into an "ephemeral"
RAM-based version of the new configuration, then when we're happy it
works we can flash the image - and if it doesn't work we can reboot
the device again and it will boot from the old image.
**Controlled** services are those which are started/stopped on demand
by a **controller** (another service) instead of being started at boot
time. For example:
* ``svc.uevent-rule.build`` creates a controlled service which is Building the RAM-based image
active when a particular hardware device (identified by uevent/sysfs ----------------------------
directory) is present.
* ``svc.round-robin.build`` creates a service controller that To create the ephemeral image, build ``outputs.kexecboot`` instead of
invokes two or more services in turn, running the next one when the ``outputs.default``. This generates a directory containing the root
process providing the previous one exits. We use this for failover filesystem image and kernel, along with an executable called `kexec`
from one network connection to a backup connection, for example. and a `boot.sh` script that runs it with appropriate arguments.
* ``svc.health-check.build`` creates a service controller that For example
runs a controlled service and periodically tests whether it is
healthy by running an external health check command or script. If the
check command repeatedly fails, the controlled service is
restarted.
The Configuration section of the manual describes controlled
services in more detail. Some operational considerations
* ``round-robin`` detects a service status by looking at its
:file:`outputs` directory, so it won't work unless the service
creates some outputs. This is considered a bug and will be
fixed in a future release
* ``health-check`` works for longruns but not for oneshots, as it
internally relies on ``s6-svc`` to restart the process
Logs
====
Logs for all services are collated into :file:`/run/uncaught-logs/current`.
The log file is rotated when it reaches a threshold size, into another
file in the same directory whose name contains a TAI64 timestamp.
Each log line is prefixed with a TAI64 timestamp and the name of the
service, if it is a longrun. If it is a oneshot, a timestamp and the
name of some other service. To convert the timestamp into a
human-readable format, use :command:`s6-tai64nlocal`.
.. code-block:: console .. code-block:: console
# ls -l /run/uncaught-logs/ nix-build -I liminix-config=./examples/arhcive.nix \
-rw-r--r-- 1 0 lock --arg device "import ./devices/gl-ar750"
-rw-r--r-- 1 0 state -A outputs.kexecboot && \
-rwxr--r-- 1 98059 @4000000000025cb629c311ac.s (tar chf - result | ssh root@the-device tar -C /run -xvf -)
-rwxr--r-- 1 98061 @40000000000260f7309c7fb4.s
-rwxr--r-- 1 98041 @40000000000265233a6cc0b6.s and then login to the device and run
-rwxr--r-- 1 98019 @400000000002695d10c06929.s
-rwxr--r-- 1 98064 @4000000000026d84189559e0.s .. code-block:: console
-rwxr--r-- 1 98055 @40000000000271ce1e031d91.s
-rwxr--r-- 1 98054 @400000000002760229733626.s cd /run/result
-rwxr--r-- 1 98104 @4000000000027a2e3b6f4e12.s sh ./boot.sh .
-rwxr--r-- 1 98023 @4000000000027e6f0ed24a6c.s
-rw-r--r-- 1 42374 current
# tail -2 /run/uncaught-logs/current
@40000000000284f130747343 wan.link.pppoe Connect: ppp0 <--> /dev/pts/0
@40000000000284f230acc669 wan.link.pppoe sent [LCP ConfReq id=0x1 <asyncmap 0x0> <magic 0x667a9594> <pcomp> <accomp>]
# tail -2 /run/uncaught-logs/current | s6-tai64nlocal
1970-01-02 21:51:45.828598156 wan.link.pppoe sent [LCP ConfReq id=0x1 <asyncmap 0x0> <magic 0x667a9594> <pcomp> <accom
p>]
1970-01-02 21:51:48.832588765 wan.link.pppoe sent [LCP ConfReq id=0x1 <asyncmap 0x0> <magic 0x667a9594> <pcomp> <accom
p>]
This will load the new kernel and map the root filesystem into a RAM
disk, then start executing the new kernel. *This is effectively a
reboot - be sure to close all open files and finish anything else
you were doing first.*
If the new system crashes or is rebooted, then the device will revert
to the old configuration it finds in flash.
Building the second (permanent) image
-------------------------------------
While running in the kexecboot system, you can build the permanent
image and copy it to the device with :command:`ssh`
.. code-block:: console
build-machine$ nix-build -I liminix-config=./examples/arhcive.nix \
--arg device "import ./devices/gl-ar750"
-A outputs.default && \
(tar chf - result | ssh root@the-device tar -C /run -xvf -)
build-machine$ tar chf - result/firmware.bin | \
ssh root@the-device tar -C /run -xvf -
Next you need to connect to the device and locate the "firmware"
partition, which you can do with a combination of :command:`dmesg`
output and the contents of :file:`/proc/mtd`
.. code-block:: console
<5>[ 0.469841] Creating 4 MTD partitions on "spi0.0":
<5>[ 0.474837] 0x000000000000-0x000000040000 : "u-boot"
<5>[ 0.480796] 0x000000040000-0x000000050000 : "u-boot-env"
<5>[ 0.487056] 0x000000050000-0x000000060000 : "art"
<5>[ 0.492753] 0x000000060000-0x000001000000 : "firmware"
# cat /proc/mtd
dev: size erasesize name
mtd0: 00040000 00001000 "u-boot"
mtd1: 00010000 00001000 "u-boot-env"
mtd2: 00010000 00001000 "art"
mtd3: 00fa0000 00001000 "firmware"
mtd4: 002a0000 00001000 "kernel"
mtd5: 00d00000 00001000 "rootfs"
Now run (in this example)
.. code-block:: console
flashcp -v firmware.bin /dev/mtd3
"I know my new image is good, can I skip the intermediate step?"
----------------------------------------------------------------
In addition to giving you a chance to see if the new image works, this
two-step process ensures that you're not copying the new image over
the top of the active root filesystem. Sometimes it works, but you
will at least need physical access to the device to power-cycle it
because it will be effectively frozen afterwards.
Flashing from the boot monitor
==============================
If you are prepared to open the device and have a TTL serial adaptor
of some kind to connect it to, you can probably use U-Boot and a TFTP
server to download and flash the image. This is quite
hardware-specific, and sometimes involves soldering: please refer
to :ref:`serial`.
Flashing from OpenWrt
=====================
.. CAUTION:: Untested! A previous version of these instructions
(without the -e flag) led to bricking the device
when flashing a jffs2 image. If you are reading
this message, nobody has yet reported on whether the
new instructions are any better.
If your device is running OpenWrt then it probably has the
:command:`mtd` command installed. After transferring the image onto the
device using e.g. :command:`ssh`, you can run it as follows:
.. code-block:: console
mtd -e -r write /tmp/firmware.bin firmware
The options to this command are for "erase before writing" and "reboot
after writing".
For more information, please see the `OpenWrt manual <https://openwrt.org/docs/guide-user/installation/sysupgrade.cli>`_ which may also contain (hardware-dependent) instructions on how to flash an image using the vendor firmware - perhaps even from a web interface.
Updating an installed system (JFFS2) Updating an installed system (JFFS2)
************************************ ************************************
@ -156,8 +165,6 @@ Note that this only copies the package to the device: it doesn't update
any profile to add it to ``$PATH`` any profile to add it to ``$PATH``
.. _rebuilding the system:
Rebuilding the system Rebuilding the system
===================== =====================
@ -189,116 +196,3 @@ Caveats
nixpkgs). nixpkgs).
* it cannot upgrade the kernel, only userland * it cannot upgrade the kernel, only userland
.. _levitate:
Reinstalling on a running system
********************************
Liminix is initially installed from a monolithic
:file:`firmware.bin` - and unless you're running a writable
filesystem, the only way to update it is to build and install a whole
new :file:`firmware.bin`. However, you probably would prefer not to
have to remove it from its installation site, unplug it from the
network and stick serial cables in it all over again.
It is not (generally) safe to install a new firmware onto the flash
partitions that the active system is running on. To address this we
have :command:`levitate`, which a way for a running Liminix system to
"soft restart" into a ramdisk running only a limited set of services,
so that the main partitions can then be safely flashed.
Configuration
=============
Levitate *needs to be configured when you create the initial system*
to specify which services/packages/etc to run in maintenance
mode. Most likely you want to configure a network interface and an ssh
for example so that you can login to reflash it.
.. code-block:: nix
defaultProfile.packages = with pkgs; [
...
(levitate.override {
config = {
services = {
inherit (config.services) dhcpc sshd watchdog;
};
defaultProfile.packages = [ mtdutils ];
users.root = config.users.root;
};
})
];
Use
===
Connect (with ssh, probably) to the running Liminix system that you
wish to upgrade.
.. code-block:: console
bash$ ssh root@the-device
Run :command:`levitate`. This takes a little while (perhaps a few
tens of seconds) to execute, and copies all config required for
maintenance mode to :file:`/run/maintenance`.
.. code-block:: console
# levitate
Reboot into maintenance mode. You will be logged out
.. code-block:: console
# reboot
Connect to the device again - note that the ssh host key will have changed.
.. code-block:: console
# ssh -o UserKnownHostsFile=/dev/null root@the-device
Check we're in maintenance mode
.. code-block:: console
# cat /etc/banner
LADIES AND GENTLEMEN WE ARE FLOATING IN SPACE
Most services are disabled. The system is operating
with a ram-based root filesystem, making it safe to
overwrite the flash devices in order to perform
upgrades and maintenance.
Don't forget to reboot when you have finished.
Perform the upgrade, using flashcp. This is an example,
your device will differ
.. code-block:: console
# cat /proc/mtd
dev: size erasesize name
mtd0: 00030000 00010000 "u-boot"
mtd1: 00010000 00010000 "u-boot-env"
mtd2: 00010000 00010000 "factory"
mtd3: 00f80000 00010000 "firmware"
mtd4: 00220000 00010000 "kernel"
mtd5: 00d60000 00010000 "rootfs"
mtd6: 00010000 00010000 "art"
# flashcp -v firmware.bin mtd:firmware
All done
.. code-block:: console
# reboot

View File

@ -7,19 +7,19 @@
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
project = 'Liminix' project = 'Liminix'
copyright = '2023-2024 Daniel Barlow' copyright = '2023, Daniel Barlow'
author = 'Daniel Barlow' author = 'Daniel Barlow'
# -- General configuration --------------------------------------------------- # -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
extensions = [ extensions = [
# 'sphinx.ext.autosectionlabel' 'sphinx.ext.autosectionlabel'
] ]
autosectionlabel_prefix_document = True autosectionlabel_prefix_document = True
templates_path = ['_templates'] templates_path = ['_templates']
exclude_patterns = ['*.inc.rst', '_build', 'Thumbs.db', '.DS_Store'] exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']

View File

@ -1,8 +1,27 @@
.. _configuration:
Configuration Configuration
############# #############
Liminix uses the Nix language to provide congruent configuration
management. This means that to change anything about the way in
which a Liminix system works, you make that change in
your :file:`configuration.nix` (or one of the other files it references),
and rerun :command:`nix-build` or :command:`liminix-rebuild` to action
the change. It is not possible (at least, without shenanigans) to make
changes by logging into the device and running imperative commands
whose effects may later be overridden: :file:`configuration.nix`
always describes the entire system and can be used to recreate that
system at any time. You can usefully keep it under version control.
If you are familiar with NixOS, you will notice some similarities
between NixOS and Liminix configuration, and also some
differences. Sometimes the differences are due to the
resource-constrained devices we deploy onto, sometimes due to
differences in the uses these devices are put to.
Configuration taxonomy
**********************
There are many things you can specify in a configuration, but these There are many things you can specify in a configuration, but these
are the ones you most commonly need to change: are the ones you most commonly need to change:
@ -67,166 +86,14 @@ domains, or you want to run two SSH daemons on different ports.
don't use the NixOS modules themselves, because the don't use the NixOS modules themselves, because the
underlying system is not similar enough for them to work. underlying system is not similar enough for them to work.
.. _configuration-services:
Services Services
******** ********
In Liminix a service is any kind of long-running task or process on We use the `s6-rc service manager <https://www.skarnet.org/software/s6-rc/overview.html>`_ to start/stop/restart services and handle
the system, that is managed (started, stopped, and monitored) by a service dependencies. Any attribute in `config.services` will become
service supervisor. A typical SOHO router might have services to part of the default set of services that s6-rc will try to bring up on
boot.
* answer DHCP and DNS requests from the LAN
* provide a wireless access point
* connect using PPPoE or L2TP to an upstream network
* start/stop the firewall
* enable/disable IP packet forwarding
* mount filesystems
(Some of these might not be considered services using other
definitions of the term: for example, this L2TP process would be a
"client" in the client/server classification; and enabling packet
forwarding doesn't require any long-lived process - just a setting to
be toggled. However, there is value in being able to use the same
abstractions for all the things to manage them and specify their
dependency relationships - so in Liminix "everything is a service")
The service supervision system enables service health monitoring,
restart of unhealthy services, and failover to "backup" services when
a primary service fails or its dependencies are unavailable. The
intention is that you have a framework in which you can specify policy
requirements like "ethernet wan dhcp-client should be restarted if it
crashes, but if it can't start because the hardware link is down, then
4G ppp service should be started instead".
Any attribute in `config.services` will become part of the default set
of services that s6-rc will try to bring up. Services are usually
started at boot time, but **controlled services** are those that are
required only in particular contexts. For example, a service to mount
a USB backup drive should run only when the drive is attached to the
system. Liminix currently implements three kinds of controlled service:
* "uevent-rule" service controllers use sysfs/uevent to identify when
particular hardware devices are present, and start/stop a controlled
service appropriately.
* the "round-robin" service controller is used for service failover:
it allows you to specify a list of services and runs each of them
in turn until it exits, then runs the next.
* the "health-check" service wraps another service, and runs a "health
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)
================================
Secrets (such as wifi passphrases, PPP username/password, SSH keys,
etc) that you provide as literal values in :file:`configuration.nix`
are processed into into config files and scripts at build time, and
eventually end up in various files in the (world-readable)
:file:`/nix/store` before being baked into a flashable image. To
change a secret - whether due to a compromise, or just as part of to a
routine key rotation - you need to rebuild the configuration and
potentially reflash the affected devices.
To avoid this, you may instead use a "secrets service", which is a
mechanism for your device to fetch secrets from a source external to
the Nix store, and create at runtime the configuration files and
scripts that start the services which require them.
Not every possible parameter to every possible service is configurable
using a secrets service. Parameters which can be configured this way
are those with the type ``liminix.lib.types.replacable``. At the time
this document was written, these include:
* ppp (pppoe and l2tp): ``username``, ``password``
* ssh: ``authorizedKeys``
* hostapd: all parameters (most likely to be useful for ``wpa_passphrase``)
To use a runtime secret for any of these parameters:
* create a secrets service to specify the source of truth for secrets
* use the :code:`outputRef` function in the service parameter to specify the secrets service and path
For example, given you had an HTTPS server hosting a JSON file with the structure
.. code-block:: json
"ssh": {
"authorizedKeys": {
"root": [ "ssh-rsa ....", "ssh-rsa ....", ... ]
"guest": [ "ssh-rsa ....", "ssh-rsa ....", ... ]
}
}
you could use a :file:`configuration.nix` fragment something like this
to make those keys visible to ssh:
.. code-block:: nix
services.secrets = svc.secrets.outboard.build {
name = "secret-service";
url = "http://10.0.0.1/secrets.json";
username = "secrets";
password = "liminix";
interval = 30; # minutes
dependencies = [ config.services.lan ];
};
services.sshd = svc.ssh.build {
authorizedKeys = outputRef config.services.secrets "ssh/authorizedKeys";
};
There are presently two implementations of a secrets service:
Outboard secrets (HTTPS)
------------------------
This service expects a URL to a JSON file containing all the secrets.
You may specify a username and password along with the URL, which are
used if the file is password-protected (HTTP Basic
authentication). Note that this is not a protection against a
malicious local user: the username and password are normal build-time
parameters so will be readable in the Nix store. This is a mitigation
against the URL being accidentally discovered due to e.g. a log file
or error message on the server leaking.
Tang secrets (encrypted local file)
-----------------------------------
Aternatively, secrets may be stored locally on the device, in a file
that has been encrypted using `Tang <https://github.com/latchset/tang>`_.
Tang is a server for binding data to network presence.
This sounds fancy, but the concept is simple. You have some data, but you only want it to be available when the system containing the data is on a certain, usually secure, network.
.. code-block:: nix
services.secrets = svc.secrets.tang.build {
name = "secret-service";
path = "/run/mnt/usbstick/secrets.json.jwe";
interval = 30; # minutes
dependencies = [ config.services.mount-usbstick ];
};
The encryption uses the
same scheme/algorithm as `Clevis <https://github.com/latchset/clevis>`_ : you may use the `Clevis instructions <https://github.com/latchset/clevis?tab=readme-ov-file#pin-tang>`_ to
encrypt the file on another host and then copy it to your Liminix
device, or you can use :command:`tangc encrypt` to encrypt 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 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 need will be defined by modules and you will only have to pass the
@ -274,101 +141,11 @@ Services may have dependencies: as you see above in the ``cowsayd``
example, it depends on some service called ``config.services.lan``, example, it depends on some service called ``config.services.lan``,
meaning that it won't be started until that other service is up. meaning that it won't be started until that other service is up.
Service outputs ..
=============== TODO: explain 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 (:code:`ppp0`, or :code:`ppp1` or
:code:`ppp7`) as an output so that a dependent service can reference
it to set up a route, or to configure firewall rules.
A service :code:`myservice` should write its outputs as files in
:file:`/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 :code:`in_outputs` shell function in the
:command:`up` or :command:`run` attributes of the service:
.. code-block:: 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
:code:`output` to print the value of an output
.. code-block:: 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 :code:`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
:file:`modules/dhcp6c/acquire-wan-address.fnl`
.. code-block:: 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 :code:`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 :code:`svc` library only reads the first line
of each output.
..
TODO: outputs that change, and services that poll other services
Module implementation Module implementation
********************* *********************
@ -415,7 +192,7 @@ To expose a service template in a module, it needs the following:
.. code-block:: nix .. code-block:: nix
config.system.service.cowsay = config.system.callService ./service.nix { config.system.service.cowsay = liminix.callService ./service.nix {
address = mkOption { address = mkOption {
type = types.str; type = types.str;
default = "0.0.0.0"; default = "0.0.0.0";

View File

@ -88,6 +88,64 @@ time with configurations for RP-PPPoE and/or Accel PPP.`
Hardware devices Hardware devices
**************** ****************
.. _serial:
U-Boot and serial shenanigans
=============================
Every device that we have so far encountered in Liminix uses `U-Boot,
the "Universal Boot Loader" <https://docs.u-boot.org/en/latest/>`_ so
it's worth knowing a bit about it. "Universal" is in this context a
bit of a misnomer, though: encountering *mainline* U-Boot is very rare
and often you'll find it is a fork from some version last updated
in 2008. Upgrading U-Boot is more or less complicated depending on the
device and is outside scope for Liminix.
To speak to U-Boot on your device you'll usually need a serial
connection to it. This is device-specific. Usually it involves
opening the box, locating the serial header pins (TX, RX and GND) and
connecting a USB TTL converter to them.
The Rolls Royce of USB/UART cables is the `FTDI cable
<https://cpc.farnell.com/ftdi/ttl-232r-rpi/cable-debug-ttl-232-usb-rpi/dp/SC12825?st=usb%20to%20uart%20cable>`_,
but there are cheaper alternatives based on the PL2303 and CP2102 chipsets. Or
get creative and use the `UART GPIO pins <https://pinout.xyz/>`_ on a Raspberry Pi. Whatever you do, make sure
that the voltages are compatible: if your device is 3.3V (this is
typical but not universal), you don't want to be sending it 5v or
(even worse) 12v.
Run a terminal emulator such as Minicom on the computer at other end
of the link. 115200 8N1 is the typical speed.
.. NOTE::
TTL serial connections typically have no form of flow control and
so don't always like having massive chunks of text pasted into
them - and U-Boot may drop characters while it's busy. So don't
necessarily expect to copy-paste large chunks of text into the
terminal emulator and have it work just like that.
If using Minicom, you may find it helps to bring up the "Termimal
settings" dialog (C^A T), then configure "Newline tx delay" to
some small but non-zero value.
When you turn the router on you should be greeted with some messages
from U-Boot, followed by the instruction to hit some key to stop
autoboot. Do this and you will get to the prompt. If you didn't see
anything, the strong likelihood is that TX and RX are the wrong way
around. If you see garbage, try a different speed.
Interesting commands to try first in U-Boot are :command:`help` and
:command:`printenv`.
To do anything useful with U-Boot you will probably need a way to get
large binary files onto the device, and the usual way to do this is by
adding a network connection and using TFTP to download them. It's
quite common that the device's U-Boot doesn't speak DHCP so it will
need a static LAN address. You might also want to keep it away from
your "real" LAN: see :ref:`bng` for some potentially useful tooling
to use it on an isolated network.
TFTP TFTP
==== ====

View File

@ -1,9 +1,11 @@
{ eval, lib, pkgs }: { eval, lib, pkgs }:
let let
inherit (lib) types;
conf = eval.config; conf = eval.config;
rootDir = builtins.toPath ./..; rootDir = builtins.toPath ./..;
stripAnyPrefixes = lib.flip (lib.fold lib.removePrefix) [ "${rootDir}/" ]; stripAnyPrefixes = lib.flip (lib.fold lib.removePrefix)
optToDoc = name: opt: { ["${rootDir}/"];
optToDoc = name: opt : {
inherit name; inherit name;
description = opt.description or null; description = opt.description or null;
default = opt.default or null; default = opt.default or null;
@ -24,6 +26,7 @@ let
let x = lib.mapAttrsToList optToDoc sd.parameters; in x; let x = lib.mapAttrsToList optToDoc sd.parameters; in x;
} }
else else
item // { declarations = map stripAnyPrefixes item.declarations; }; item // { declarations = map stripAnyPrefixes item.declarations; };
in in
builtins.map spliceServiceDefn (pkgs.lib.optionAttrSetToDocList eval.options) builtins.map spliceServiceDefn
(pkgs.lib.optionAttrSetToDocList eval.options)

View File

@ -1,22 +1,24 @@
with import <nixpkgs> { }; with import <nixpkgs> {} ;
let let
inherit (builtins) stringLength readDir filter; inherit (builtins) stringLength readDir filter;
devices = filter (n: n != "families") (lib.mapAttrsToList (n: t: n) (readDir ../devices)); devices = filter (n: n != "families")
texts = map ( (lib.mapAttrsToList (n: t: n) (readDir ../devices));
n: texts = map (n:
let let d = import ../devices/${n}/default.nix;
d = import ../devices/${n}/default.nix; d' = {
tag = ".. _${lib.strings.replaceStrings [" "] ["-"] n}:"; description = "${n}\n${substring 0 (stringLength n) "********************************"}\n";
d' = { } // d;
description = '' installer =
${n} if d ? description && d ? installer
${substring 0 (stringLength n) "********************************"} then ''
'';
} // d; The default installation route for this device is
in :ref:`system-outputs-${d.installer}`
"${tag}\n\n${d'.description}" ''
) devices; else "";
in d'.description)
devices;
in in
writeText "hwdoc" '' writeText "hwdoc" ''
Supported hardware Supported hardware

View File

@ -7,7 +7,6 @@ Liminix
intro intro
tutorial tutorial
installation
configuration configuration
admin admin
development development

View File

@ -1,211 +0,0 @@
Installation
############
Hardware devices vary wildly in their affordances for installing new
operating systems, so it should be no surprise that the Liminix
installation procedure is hardware-dependent. This section contains
generic instructions, but please refer to the documentation for your
device to find whether and how well they apply.
Building a firmware image
*************************
Liminix uses the Nix language to provide congruent configuration
management. This means that to change anything about the way in
which a Liminix system works, you make that change in
your :file:`configuration.nix` (or one of the other files it references),
and rerun :command:`nix-build` or :command:`liminix-rebuild` to action
the change. It is not possible (at least, without shenanigans) to make
changes by logging into the device and running imperative commands
whose effects may later be overridden: :file:`configuration.nix`
always describes the entire system and can be used to recreate that
system at any time. You can usefully keep it under version control.
If you are familiar with NixOS, you will notice some similarities
between NixOS and Liminix configuration, and also some
differences. Sometimes the differences are due to the
resource-constrained devices we deploy onto, sometimes due to
differences in the uses these devices are put to.
For a more full description of how to configure Liminix, see
:ref:`configuration`. Assuming for the moment that you want a typical
home wireless gateway/router, the best way to get started is to copy
:file:`examples/rotuer.nix` and edit it for your requirements.
.. code-block:: console
$ cp examples/rotuer.nix configuration.nix
$ vi configuration.nix # other editors are available
$ # adjust this next command for your hardware device
$ nix-build -I liminix-config=./configuration.nix \
--arg device "import ./devices/gl-mt300a" -A outputs.default
Usually (not always, *please check the documentation for your device*)
this will leave you with a file :file:`result/firmware.bin`
which you now need to flash to the device.
Flashing from the boot monitor (TFTP install)
*********************************************
If you are prepared to open the device and have a TTL serial adaptor
of some kind to connect it to, you can probably use U-Boot and a TFTP
server to download and flash the image.
This is quite hardware-specific and may even involve soldering - see
the documention for your device. However, it is in some ways the most
"reliable" option: if you can see what's happening (or not happening)
in early boot, the risk of "bricking" is substantially reduced and you
have options for recovering if you misstep or flash a bad image.
.. _serial:
U-Boot and serial shenanigans
=============================
Every device that we have so far encountered in Liminix uses `U-Boot,
the "Universal Boot Loader" <https://docs.u-boot.org/en/latest/>`_ so
it's worth knowing a bit about it. "Universal" is in this context a
bit of a misnomer, though: encountering *mainline* U-Boot is very rare
and often you'll find it is a fork from some version last updated
in 2008. Upgrading U-Boot is more or less complicated depending on the
device and is outside scope for Liminix.
To speak to U-Boot on your device you'll usually need a serial
connection to it. This typically involves opening the box, locating
the serial header pins (TX, RX and GND) and connecting a USB TTL
converter to them.
The Rolls Royce of USB/UART cables is the `FTDI cable
<https://cpc.farnell.com/ftdi/ttl-232r-rpi/cable-debug-ttl-232-usb-rpi/dp/SC12825?st=usb%20to%20uart%20cable>`_,
but there are cheaper alternatives based on the PL2303 and CP2102 chipsets - or you could even
get creative and use the `UART GPIO pins <https://pinout.xyz/>`_ on a Raspberry Pi. Whatever you do, make sure
that the voltages are compatible: if your device is 3.3V (this is
typical but not universal), you don't want to be sending it 5v or
(even worse) 12v.
Run a terminal emulator such as Minicom on the computer at other end
of the link. 115200 8N1 is the typical speed.
.. NOTE::
TTL serial connections often have no flow control and
so don't always like having massive chunks of text pasted into
them - and U-Boot may drop characters while it's busy. So don't
do that.
If using Minicom, you may find it helps to bring up the "Termimal
settings" dialog (C^A T), then configure "Newline tx delay" to
some small but non-zero value.
When you turn the router on you should be greeted with some messages
from U-Boot, followed by the instruction to hit some key to stop
autoboot. Do this and you will get to the prompt. If you didn't see
anything, the strong likelihood is that TX and RX are the wrong way
around. If you see garbage, try a different speed.
Interesting commands to try first in U-Boot are :command:`help` and
:command:`printenv`.
You will also need to configure a TFTP server on a network that's
accessible to the device: how you do that will vary according to which
TFTP server you're using and so is out of scope for this document.
Buildiing and installing the image
==================================
Follow the device-specific instructions for "TFTP install": usually,
the steps are
* build the `outputs.mtdimage` output
* copy :file:`result/firmware.bin` to your TFTP server
* copy/paste the commands in :file:`result/flash.scr` one at a time into the U-Boot command line
* reset the device
You should now see messages from U-Boot, then from the Linux kernel
and eventually a shell prompt.
.. NOTE:: Before you reboot, check which networks the device is
plugged into, and disconnect as necessary. If you've just
installed a DHCP server or anything similar that responds to
broadcasts, you may not want it to do that on the network
that you temporarily connected it to for installing it.
Flashing from OpenWrt
*********************
.. CAUTION:: Untested! A previous version of these instructions
(without the -e flag) led to bricking the device
when flashing a jffs2 image. If you are reading
this message, nobody has yet reported on whether the
new instructions are any better.
If your device is running OpenWrt then it probably has the
:command:`mtd` command installed. Build the `outputs.mtdimage` output
(as you would for a TFTP install) and then transfer
:file:`result/firmware.bin` onto the device using e.g.
:command:`scp`. Now flash as follows:
.. code-block:: console
mtd -e -r write /tmp/firmware.bin firmware
The options to this command are for "erase before writing" and "reboot
after writing".
For more information, please see the `OpenWrt manual <https://openwrt.org/docs/guide-user/installation/sysupgrade.cli>`_ which may also contain (hardware-dependent) instructions on how to flash an image using the vendor firmware - perhaps even from a web interface.
Flashing from Liminix
*********************
If the device is already running Liminix and has been configured with
:command:`levitate`, you can use that to safely flash your new image.
Refer to :ref:`levitate` for an explanation.
If the device is running Liminix but doesn't have :command:`levitate`
your options are more limited. You may attempt to use
:command:`flashcp` but it doesn't always work: as it copies the new
image over the top of the active root filesystem, surprise may ensue.
Consider instead using a serial connection: you may need one anyway
after trying flashcp if it corrupts the image.
flashcp (not generally recommended)
===================================
Connect to the device and locate the "firmware" partition, which you
can do with a combination of :command:`dmesg` output and the contents
of :file:`/proc/mtd`
.. code-block:: console
<5>[ 0.469841] Creating 4 MTD partitions on "spi0.0":
<5>[ 0.474837] 0x000000000000-0x000000040000 : "u-boot"
<5>[ 0.480796] 0x000000040000-0x000000050000 : "u-boot-env"
<5>[ 0.487056] 0x000000050000-0x000000060000 : "art"
<5>[ 0.492753] 0x000000060000-0x000001000000 : "firmware"
# cat /proc/mtd
dev: size erasesize name
mtd0: 00040000 00001000 "u-boot"
mtd1: 00010000 00001000 "u-boot-env"
mtd2: 00010000 00001000 "art"
mtd3: 00fa0000 00001000 "firmware"
mtd4: 002a0000 00001000 "kernel"
mtd5: 00d00000 00001000 "rootfs"
Copy :file:`result/firmware.bin` to the device and now run (in this
example)
.. code-block:: console
flashcp -v firmware.bin /dev/mtd3

View File

@ -1,4 +1,4 @@
Module options Module options
############## ##############
.. include:: modules-generated.inc.rst .. include:: modules-generated.rst

View File

@ -10,4 +10,4 @@ different artefacts, or have different ways to get that artefact
installed. The options available for a particular device are described in installed. The options available for a particular device are described in
the section for that device. the section for that device.
.. include:: outputs-generated.inc.rst .. include:: outputs-generated.rst

View File

@ -16,4 +16,4 @@
(each [_ option (ipairs (sorted-options (yaml.load (io.read "*a"))))] (each [_ option (ipairs (sorted-options (yaml.load (io.read "*a"))))]
(when (and (output? option) (not option.internal)) (when (and (output? option) (not option.internal))
(print (.. ".. _" (string.gsub option.name "%." "-") ":") "\n") (print (.. ".. _" (string.gsub option.name "%." "-") ":") "\n")
(print option.description "\n"))) (print option.description)))

View File

@ -300,7 +300,7 @@ machine, which for a test/demo system might involve a second network
device in your build system - USB ethernet adapters are cheap - or device in your build system - USB ethernet adapters are cheap - or
a bit of messing around unplugging cables.) a bit of messing around unplugging cables.)
For more information about :code:`liminix-rebuild`, see the manual section :ref:`Rebuilding the system`. For more information about :code:`liminix-rebuild`, see the manual section :ref:`admin:Rebuilding the system`.
Final thoughts Final thoughts

View File

@ -11,9 +11,9 @@
... ...
}: let }: let
secrets = import ./extneder-secrets.nix; secrets = import ./extneder-secrets.nix;
inherit (pkgs.liminix.services) oneshot longrun target; inherit (pkgs.liminix.services) oneshot longrun bundle target;
inherit (pkgs.pseudofile) dir symlink; inherit (pkgs.pseudofile) dir symlink;
inherit (pkgs) writeText serviceFns; inherit (pkgs) writeText dropbear ifwait serviceFns;
svc = config.system.service; svc = config.system.service;
in rec { in rec {
boot = { boot = {
@ -52,6 +52,7 @@ in rec {
dependencies = [ services.dhcpc ]; dependencies = [ services.dhcpc ];
name = "resolvconf"; name = "resolvconf";
up = '' up = ''
. ${serviceFns}
( in_outputs ${name} ( in_outputs ${name}
for i in $(output ${services.dhcpc} dns); do for i in $(output ${services.dhcpc} dns); do
echo "nameserver $i" > resolv.conf echo "nameserver $i" > resolv.conf
@ -92,6 +93,7 @@ in rec {
secrets_file = oneshot rec { secrets_file = oneshot rec {
name = "rsync-secrets"; name = "rsync-secrets";
up = '' up = ''
. ${serviceFns}
(in_outputs ${name} (in_outputs ${name}
echo "backup:${secrets.rsync_secret}" > secrets) echo "backup:${secrets.rsync_secret}" > secrets)
''; '';
@ -117,7 +119,7 @@ in rec {
secrets_file secrets_file
services.mount_external_disk services.mount_external_disk
config.hardware.networkInterfaces.lan config.hardware.networkInterfaces.lan
]; ] ;
}; };
users.root = { users.root = {
@ -126,22 +128,18 @@ in rec {
}; };
users.backup = { users.backup = {
uid = 500; uid=500; gid=500; gecos="Storage owner"; dir="/srv";
gid = 500; shell="/dev/null";
gecos = "Storage owner";
dir = "/srv";
shell = "/dev/null";
}; };
groups.backup = { groups.backup = {
gid = 500; gid=500; usernames = ["backup"];
usernames = [ "backup" ];
}; };
defaultProfile.packages = with pkgs; [ defaultProfile.packages = with pkgs; [
e2fsprogs e2fsprogs
mtdutils mtdutils
(levitate.override { (levitate.override {
config = { config = {
services = { services = {
inherit (config.services) dhcpc sshd watchdog; inherit (config.services) dhcpc sshd watchdog;
}; };

View File

@ -5,9 +5,9 @@
# wherever the text "EDIT" appears - please consult the tutorial # wherever the text "EDIT" appears - please consult the tutorial
# documentation for details. # documentation for details.
{ config, pkgs, ... }: { config, pkgs, lib, ... } :
let let
inherit (pkgs.liminix.services) bundle oneshot; inherit (pkgs.liminix.services) bundle oneshot longrun;
inherit (pkgs) serviceFns; inherit (pkgs) serviceFns;
# EDIT: you can pick your preferred RFC1918 address space # EDIT: you can pick your preferred RFC1918 address space
# for NATted connections, if you don't like this one. # for NATted connections, if you don't like this one.
@ -49,40 +49,31 @@ in rec {
country_code = "GB"; country_code = "GB";
wpa_passphrase = "not a real wifi password"; wpa_passphrase = "not a real wifi password";
hw_mode = "g"; hw_mode="g";
ieee80211n = 1; ieee80211n = 1;
auth_algs = 1; # 1=wpa2, 2=wep, 3=both auth_algs = 1; # 1=wpa2, 2=wep, 3=both
wpa = 2; # 1=wpa, 2=wpa2, 3=both wpa = 2; # 1=wpa, 2=wpa2, 3=both
wpa_key_mgmt = "WPA-PSK"; wpa_key_mgmt = "WPA-PSK";
wpa_pairwise = "TKIP CCMP"; # auth for wpa (may not need this?) wpa_pairwise = "TKIP CCMP"; # auth for wpa (may not need this?)
rsn_pairwise = "CCMP"; # auth for wpa2 rsn_pairwise = "CCMP"; # auth for wpa2
wmm_enabled = 1; wmm_enabled = 1;
}; };
}; };
services.int = svc.network.address.build { services.int = svc.network.address.build {
interface = svc.bridge.primary.build { ifname = "int"; }; interface = svc.bridge.primary.build { ifname = "int"; };
family = "inet"; family = "inet"; address = "${ipv4LocalNet}.1"; prefixLength = 16;
address = "${ipv4LocalNet}.1";
prefixLength = 16;
}; };
services.bridge = svc.bridge.members.build { services.bridge = svc.bridge.members.build {
primary = services.int; primary = services.int;
members = with config.hardware.networkInterfaces; [ members = with config.hardware.networkInterfaces;
wlan [ wlan lan ];
lan
];
}; };
services.ntp = svc.ntp.build { services.ntp = svc.ntp.build {
pools = { pools = { "pool.ntp.org" = ["iburst"]; };
"pool.ntp.org" = [ "iburst" ]; makestep = { threshold = 1.0; limit = 3; };
};
makestep = {
threshold = 1.0;
limit = 3;
};
}; };
services.sshd = svc.ssh.build { }; services.sshd = svc.ssh.build { };
@ -137,6 +128,7 @@ in rec {
dependencies = [ services.wan ]; dependencies = [ services.wan ];
name = "resolvconf"; name = "resolvconf";
up = '' up = ''
. ${serviceFns}
( in_outputs ${name} ( in_outputs ${name}
echo "nameserver $(output ${services.wan} ns1)" > resolv.conf echo "nameserver $(output ${services.wan} ns1)" > resolv.conf
echo "nameserver $(output ${services.wan} ns2)" >> resolv.conf echo "nameserver $(output ${services.wan} ns2)" >> resolv.conf
@ -165,7 +157,8 @@ in rec {
interface = services.wan; interface = services.wan;
}; };
services.firewall = svc.firewall.build { }; services.firewall = svc.firewall.build {
};
services.packet_forwarding = svc.network.forward.build { }; services.packet_forwarding = svc.network.forward.build { };
@ -202,5 +195,7 @@ in rec {
]; ];
}; };
defaultProfile.packages = with pkgs; [ min-collect-garbage ]; defaultProfile.packages = with pkgs; [
min-collect-garbage
];
} }

View File

@ -1,5 +1,6 @@
{ config, pkgs, ... } : { config, pkgs, lib, ... } :
let let
inherit (pkgs) serviceFns;
svc = config.system.service; svc = config.system.service;
in rec { in rec {

View File

@ -1,5 +1,6 @@
{ config, pkgs, ... } : { config, pkgs, lib, ... } :
let let
inherit (pkgs) serviceFns;
svc = config.system.service; svc = config.system.service;
in rec { in rec {

86
examples/l2tp.nix Normal file
View File

@ -0,0 +1,86 @@
{
config,
pkgs,
lib,
...
}: let
secrets = import ./extneder-secrets.nix;
rsecrets = import ./rotuer-secrets.nix;
lns = "l2tp.aaisp.net.uk";
inherit (pkgs.liminix.services) oneshot longrun bundle target;
inherit (pkgs.pseudofile) dir symlink;
inherit (pkgs) writeText dropbear ifwait serviceFns;
svc = config.system.service;
in rec {
boot = {
tftp = {
serverip = "10.0.0.1";
ipaddr = "10.0.0.8";
};
};
imports = [
../modules/cdc-ncm
../modules/network
../modules/vlan
../modules/ssh
../modules/usb.nix
../modules/watchdog
../modules/mount
../modules/ppp
];
hostname = "thing";
services.dhcpc = svc.network.dhcp.client.build {
interface = config.services.wwan;
dependencies = [ config.services.hostname ];
};
services.sshd = svc.ssh.build { };
services.resolvconf = oneshot rec {
dependencies = [ services.dhcpc ];
name = "resolvconf";
up = ''
. ${serviceFns}
( in_outputs ${name}
for i in $(output ${services.dhcpc} dns); do
echo "nameserver $i" > resolv.conf
done
)
'';
};
filesystem = dir {
etc = dir {
"resolv.conf" = symlink "${services.resolvconf}/.outputs/resolv.conf";
};
srv = dir {};
};
services.lnsroute = svc.network.route.build {
via = "$(output ${services.dhcpc} router)";
target = lns;
dependencies = [services.dhcpc];
};
services.l2tp = svc.l2tp.build {
inherit lns;
ppp-options = [
"debug" "+ipv6" "noauth"
"name" rsecrets.l2tp.name
"password" rsecrets.l2tp.password
];
dependencies = [ services.lnsroute ];
};
services.defaultroute4 = svc.network.route.build {
via = "$(output ${services.l2tp} router)";
target = "default";
dependencies = [services.l2tp];
};
users.root = {
passwd = lib.mkForce secrets.root.passwd;
openssh.authorizedKeys.keys = secrets.root.keys;
};
}

View File

@ -1,6 +1,7 @@
{ config, pkgs, ... } : { config, pkgs, ... } :
let let
inherit (pkgs.liminix.services) target; inherit (pkgs.liminix.services) oneshot longrun bundle target;
inherit (pkgs) writeText;
svc = config.system.service; svc = config.system.service;
secrets-1 = { secrets-1 = {
ssid = "Zyxel 2G (N)"; ssid = "Zyxel 2G (N)";

View File

@ -3,8 +3,8 @@ let
inherit (pkgs) serviceFns; inherit (pkgs) serviceFns;
svc = config.system.service; svc = config.system.service;
inherit (pkgs.pseudofile) dir symlink; inherit (pkgs.pseudofile) dir symlink;
inherit (pkgs.liminix.services) oneshot target; inherit (pkgs.liminix.services) oneshot longrun bundle target;
some-util-linux = pkgs.runCommand "some-util-linux" { } '' some-util-linux = pkgs.runCommand "some-util-linux" {} ''
mkdir -p $out/bin mkdir -p $out/bin
cd ${pkgs.util-linux-small}/bin cd ${pkgs.util-linux-small}/bin
cp fdisk sfdisk mkswap $out/bin cp fdisk sfdisk mkswap $out/bin
@ -53,13 +53,14 @@ in rec {
services.defaultroute4 = svc.network.route.build { services.defaultroute4 = svc.network.route.build {
via = "$(output ${services.dhcpc} router)"; via = "$(output ${services.dhcpc} router)";
target = "default"; target = "default";
dependencies = [ services.dhcpc ]; dependencies = [services.dhcpc];
}; };
services.resolvconf = oneshot rec { services.resolvconf = oneshot rec {
dependencies = [ services.dhcpc ]; dependencies = [ services.dhcpc ];
name = "resolvconf"; name = "resolvconf";
up = '' up = ''
. ${serviceFns}
( in_outputs ${name} ( in_outputs ${name}
for i in $(output ${services.dhcpc} dns); do for i in $(output ${services.dhcpc} dns); do
echo "nameserver $i" > resolv.conf echo "nameserver $i" > resolv.conf
@ -71,6 +72,7 @@ in rec {
services.growfs = let name = "growfs"; in oneshot { services.growfs = let name = "growfs"; in oneshot {
inherit name; inherit name;
up = '' up = ''
. ${serviceFns}
device=$(grep /persist /proc/1/mountinfo | cut -f9 -d' ') device=$(grep /persist /proc/1/mountinfo | cut -f9 -d' ')
${pkgs.e2fsprogs}/bin/resize2fs $device ${pkgs.e2fsprogs}/bin/resize2fs $device
''; '';

View File

@ -8,10 +8,12 @@
root = { root = {
# mkpasswd -m sha512crypt # mkpasswd -m sha512crypt
passwd = "$6$6pt0mpbgcB7kC2RJ$kSBoCYGyi1.qxt7dqmexLj1l8E6oTZJZmfGyJSsMYMW.jlsETxdgQSdv6ptOYDM7DHAwf6vLG0pz3UD31XBfC1"; passwd = "$6$6pt0mpbgcB7kC2RJ$kSBoCYGyi1.qxt7dqmexLj1l8E6oTZJZmfGyJSsMYMW.jlsETxdgQSdv6ptOYDM7DHAwf6vLG0pz3UD31XBfC1";
openssh.authorizedKeys.keys = [ ]; openssh.authorizedKeys.keys = [
];
}; };
lan = { lan = {
prefix = "10.8.0"; prefix = "10.8.0";
}; };
} }

View File

@ -1,17 +1,21 @@
# This is an example that uses the "gateway" profile to create a # This is not part of Liminix per se. This is my "scratchpad"
# "typical home wireless router" configuration suitable for a Gl.inet # configuration for the device I'm testing with.
# gl-ar750 router. It should be fairly simple to edit it for other #
# devices: mostly you will need to attend to the number of wlan and lan # Parts of it do do things that Liminix eventually needs to do, but
# interfaces # don't look in here for solutions - just for identifying the
# problems.
{ config, pkgs, lib, modulesPath, ... } : { config, pkgs, lib, modulesPath, ... } :
let let
secrets = { secrets = {
domainName = "fake.liminix.org"; domainName = "fake.liminix.org";
firewallRules = { }; firewallRules = {};
} // (import ./rotuer-secrets.nix); } // (import ./rotuer-secrets.nix);
inherit (pkgs.liminix.services) oneshot bundle;
inherit (pkgs) serviceFns;
svc = config.system.service; svc = config.system.service;
wirelessConfig = { wirelessConfig = {
country_code = "GB"; country_code = "GB";
inherit (secrets) wpa_passphrase; inherit (secrets) wpa_passphrase;
wmm_enabled = 1; wmm_enabled = 1;
@ -28,18 +32,21 @@ in rec {
imports = [ imports = [
"${modulesPath}/profiles/gateway.nix" "${modulesPath}/profiles/gateway.nix"
"${modulesPath}/schnapps"
"${modulesPath}/outputs/btrfs.nix"
"${modulesPath}/outputs/extlinux.nix"
]; ];
hostname = "rotuer"; hostname = "rotuer";
rootfsType = "btrfs";
rootOptions = "subvol=@";
boot.loader.extlinux.enable = true;
profile.gateway = { profile.gateway = {
lan = { lan = {
interfaces = with config.hardware.networkInterfaces; interfaces = with config.hardware.networkInterfaces;
[ [
# EDIT: these are the interfaces exposed by the gl.inet gl-ar750:
# if your device has more or differently named lan interfaces,
# specify them here
wlan wlan5 wlan wlan5
lan lan0 lan1 lan2 lan3 lan4
]; ];
inherit (secrets.lan) prefix; inherit (secrets.lan) prefix;
address = { address = {
@ -53,17 +60,9 @@ in rec {
}; };
}; };
wan = { wan = {
# wan interface depends on your upstream - could be dhcp, static interface = config.hardware.networkInterfaces.wan;
# ethernet, a pppoe, ppp over serial, a complicated bonded username = secrets.l2tp.name;
# failover ... who knows what else? password = secrets.l2tp.password;
interface = svc.pppoe.build {
interface = config.hardware.networkInterfaces.wan;
username = secrets.l2tp.name;
password = secrets.l2tp.password;
};
# once the wan has ipv4 connnectivity, should we run dhcp6
# client to potentially get an address range ("prefix
# delegation")
dhcp6.enable = true; dhcp6.enable = true;
}; };
firewall = { firewall = {
@ -71,19 +70,15 @@ in rec {
rules = secrets.firewallRules; rules = secrets.firewallRules;
}; };
wireless.networks = { wireless.networks = {
# EDIT: if you have more or fewer wireless radios, here is where
# you need to say so. hostapd tuning is hardware-specific and
# left as an exercise for the reader :-).
"${secrets.ssid}" = { "${secrets.ssid}" = {
interface = config.hardware.networkInterfaces.wlan; interface = config.hardware.networkInterfaces.wlan;
hw_mode = "g"; hw_mode="g";
channel = "2"; channel = "2";
ieee80211n = 1; ieee80211n = 1;
} // wirelessConfig; } // wirelessConfig;
"${secrets.ssid}5" = rec { "${secrets.ssid}5" = rec {
interface = config.hardware.networkInterfaces.wlan5; interface = config.hardware.networkInterfaces.wlan5;
hw_mode = "a"; hw_mode="a";
channel = 36; channel = 36;
ht_capab = "[HT40+]"; ht_capab = "[HT40+]";
vht_oper_chwidth = 1; vht_oper_chwidth = 1;

View File

@ -1,211 +0,0 @@
# A demonstration config for a home/soho router with PPPoE upstream
# and fallback to an L2TP tunnel over a USB WWAN device
{
config,
pkgs,
lib,
...
}: let
secrets = import ./extneder-secrets.nix;
rsecrets = import ./rotuer-secrets.nix;
# https://support.aa.net.uk/Category:Incoming_L2TP says:
# "Please use the DNS name (l2tp.aa.net.uk) instead of hardcoding an
# IP address; IP addresses can and do change. If you have to use an
# IP, use 194.4.172.12, but do check the DNS for l2tp.aa.net.uk in
# case it changes."
# but (1) we don't want to use the wwan stick's dns as our main
# resolver: it's provided by some mobile ISP and they aren't
# necessarily the best at providing unfettered services without
# deciding to do something weird; (2) it's not simple to arrange
# that xl2tpd gets a different resolver than every other process;
# (3) there's no way to specify an lns address to xl2tpd at runtime
# except by rewriting its config file. So what we will do is lookup
# the lns hostname using the mobile ISP's dns server and then refuse
# to start l2tp unless the expected lns address is one of the
# addresses returned. I think this satisfies "do check the DNS"
lns = { hostname = "l2tp.aaisp.net.uk"; address = "194.4.172.12"; };
inherit (pkgs.liminix.services) oneshot longrun target;
inherit (pkgs.liminix) outputRef;
inherit (pkgs.pseudofile) dir symlink;
inherit (pkgs) serviceFns;
svc = config.system.service;
wirelessConfig = {
country_code = "GB";
inherit (rsecrets) wpa_passphrase;
wmm_enabled = 1;
};
in rec {
boot = {
tftp = {
serverip = "10.0.0.1";
ipaddr = "10.0.0.8";
};
};
imports = [
../modules/wwan
../modules/network
../modules/ssh
../modules/usb.nix
../modules/ppp
../modules/round-robin
../modules/health-check
../modules/secrets
../modules/profiles/gateway.nix
];
hostname = "thing";
services.wan-address-for-secrets = svc.network.address.build {
interface = config.hardware.networkInterfaces.wan;
family = "inet"; address ="10.0.0.10"; prefixLength = 24;
};
services.secrets = svc.secrets.outboard.build {
name = "secret-service";
url = "http://10.0.0.1/liminix/examples/real-secrets.json";
username = "demo";
password = "demo";
interval = 5;
dependencies = [ services.wan-address-for-secrets ];
};
services.wwan = svc.wwan.huawei-e3372.build {
apn = "data.uk";
username = "user";
password = "one2one";
authType = "chap";
};
profile.gateway = {
lan = {
interfaces = with config.hardware.networkInterfaces;
[
# EDIT: these are the interfaces exposed by the gl.inet gl-ar750:
# if your device has more or differently named lan interfaces,
# specify them here
wlan wlan5
lan
];
inherit (rsecrets.lan) prefix;
address = {
family = "inet"; address ="${rsecrets.lan.prefix}.1"; prefixLength = 24;
};
dhcp = {
start = 10;
end = 240;
hosts = { } // lib.optionalAttrs (builtins.pathExists ./static-leases.nix) (import ./static-leases.nix);
localDomain = "lan";
};
};
wan =
let
secret = outputRef config.services.secrets;
username = secret "ppp/username";
password = secret "ppp/password";
in {
interface =
let
pppoe = svc.pppoe.build {
interface = config.hardware.networkInterfaces.wan;
debug = true;
inherit username password;
};
l2tp =
let
check-address = oneshot rec {
name = "check-lns-address";
up = "grep -Fx ${lns.address} $(output_path ${services.lns-address} addresses)";
dependencies = [ services.lns-address ];
};
route = svc.network.route.build {
via = "$(output ${services.bootstrap-dhcpc} router)";
target = lns.address;
dependencies = [services.bootstrap-dhcpc check-address];
};
l2tpd= svc.l2tp.build {
lns = lns.address;
inherit username password;
dependencies = [config.services.lns-address route check-address];
};
in
svc.health-check.build {
service = l2tpd;
threshold = 3;
interval = 2;
healthCheck = pkgs.writeAshScript "ping-check" {} "ping 1.1.1.1";
};
in svc.round-robin.build {
name = "wan";
services = [
pppoe
l2tp
];
};
dhcp6.enable = true;
};
wireless.networks = {
"${rsecrets.ssid}" = {
interface = config.hardware.networkInterfaces.wlan;
hw_mode = "g";
channel = "6";
ieee80211n = 1;
} // wirelessConfig // {
wpa_passphrase = outputRef config.services.secrets "wpa_passphrase";
};
"${rsecrets.ssid}5" = rec {
interface = config.hardware.networkInterfaces.wlan5;
hw_mode = "a";
channel = 36;
ht_capab = "[HT40+]";
vht_oper_chwidth = 1;
vht_oper_centr_freq_seg0_idx = channel + 6;
ieee80211n = 1;
ieee80211ac = 1;
} // wirelessConfig // {
wpa_passphrase = outputRef config.services.secrets "wpa_passphrase";
};
};
};
services.bootstrap-dhcpc = svc.network.dhcp.client.build {
interface = config.services.wwan;
dependencies = [ config.services.hostname ];
};
services.sshd = svc.ssh.build {
authorizedKeys = outputRef config.services.secrets "ssh/authorizedKeys";
};
services.lns-address = let
ns = "$(output_word ${services.bootstrap-dhcpc} dns 1)";
route-to-bootstrap-nameserver = svc.network.route.build {
via = "$(output ${services.bootstrap-dhcpc} router)";
target = ns;
dependencies = [services.bootstrap-dhcpc];
};
in oneshot rec {
name = "resolve-l2tp-server";
dependencies = [ services.bootstrap-dhcpc route-to-bootstrap-nameserver ];
up = ''
(in_outputs ${name}
DNSCACHEIP="${ns}" ${pkgs.s6-dns}/bin/s6-dnsip4 ${lns.hostname} \
> addresses
)
'';
};
users.root = rsecrets.root;
programs.busybox.options = {
FEATURE_FANCY_TAIL = "y";
};
}

View File

@ -1,19 +0,0 @@
{
"wpa_passphrase": "you bring light in",
"ssid": "liminix",
"l2tp": {
"name": "abcde@a.1",
"password": "NotMyIspPassword"
},
"root": {
"passwd": "$6$6pt0mpbgcB7kC2RJ$kSBoCYGyi1.qxt7dqmexLj1l8E6oTZJZmfGyJSsMYMW.jlsETxdgQSdv6ptOYDM7DHAwf6vLG0pz3UD31XBfC1",
"openssh": {
"authorizedKeys": {
"keys": [ ]
}
}
},
"lan": {
"prefix": "10.8.0"
}
}

View File

@ -1,5 +1,6 @@
{ config, pkgs, lim, ... } : { config, pkgs, lib, lim, ... } :
let let
inherit (pkgs) serviceFns;
svc = config.system.service; svc = config.system.service;
in rec { in rec {

View File

@ -1,4 +1,4 @@
{ lim, pkgs, config, ...}: { lib, lim, pkgs, config, ...}:
{ {
config = { config = {
kernel.config = { kernel.config = {

View File

@ -1,4 +1,4 @@
{ lim, pkgs, config, ...}: { lib, lim, pkgs, config, ...}:
{ {
config = { config = {
kernel.config = { kernel.config = {

View File

@ -1,4 +1,4 @@
{ config, lim, ...}: { lib, pkgs, config, lim, ...}:
{ {
config = { config = {
kernel.config = { kernel.config = {

View File

@ -1,4 +1,4 @@
{ pkgs, config, ...}: { lib, pkgs, config, ...}:
{ {
imports = [ ./mips.nix ]; imports = [ ./mips.nix ];
config = { config = {

View File

@ -1,4 +1,4 @@
{ config, ...}: { lib, pkgs, config, ...}:
{ {
imports = [ ./mips.nix ]; imports = [ ./mips.nix ];
config = { config = {

View File

@ -4,8 +4,10 @@
{ lib, pkgs, config, ...}: { lib, pkgs, config, ...}:
let let
inherit (lib) mkOption types; inherit (lib) mkEnableOption mkOption types isDerivation hasAttr ;
inherit (pkgs.pseudofile) dir symlink; inherit (pkgs.pseudofile) dir symlink;
inherit (pkgs.liminix.networking) address interface;
inherit (pkgs.liminix.services) bundle;
type_service = pkgs.liminix.lib.types.service; type_service = pkgs.liminix.lib.types.service;
@ -36,7 +38,7 @@ in {
''; '';
# internal = true; # probably a good case to make this internal # internal = true; # probably a good case to make this internal
}; };
rootfsType = mkOption { rootfsType = mkOption {
default = "squashfs"; default = "squashfs";
type = types.enum [ type = types.enum [
"btrfs" "btrfs"
@ -46,7 +48,7 @@ in {
"ubifs" "ubifs"
]; ];
}; };
rootOptions = mkOption { rootOptions = mkOption {
type = types.nullOr types.str; type = types.nullOr types.str;
default = null; default = null;
}; };
@ -54,29 +56,20 @@ in {
boot = { boot = {
commandLine = mkOption { commandLine = mkOption {
type = types.listOf types.nonEmptyStr; type = types.listOf types.nonEmptyStr;
default = [ ]; default = [];
description = "Kernel command line"; description = "Kernel command line";
}; };
commandLineDtbNode = mkOption { commandLineDtbNode = mkOption {
type = types.enum [ type = types.enum [ "bootargs" "bootargs-override" ];
"bootargs"
"bootargs-override"
];
default = "bootargs"; default = "bootargs";
description = "Kernel command line's devicetree node"; description = "Kernel command line's devicetree node";
}; };
imageType = mkOption { imageType = mkOption {
type = types.enum [ type = types.enum [ "primary" "secondary" ];
"primary"
"secondary"
];
default = "primary"; default = "primary";
}; };
imageFormat = mkOption { imageFormat = mkOption {
type = types.enum [ type = types.enum ["fit" "uimage"];
"fit"
"uimage"
];
default = "uimage"; default = "uimage";
}; };
tftp = { tftp = {
@ -92,7 +85,7 @@ in {
}; };
# These names match the uboot environment variables. I reserve # These names match the uboot environment variables. I reserve
# the right to change them if I think of better ones. # the right to change them if I think of better ones.
ipaddr = mkOption { ipaddr = mkOption {
type = types.str; type = types.str;
description = '' description = ''
Our IP address to use when creating scripts to Our IP address to use when creating scripts to
@ -137,11 +130,13 @@ in {
s = pkg (checkTypes parameters s = pkg (checkTypes parameters
(builtins.removeAttrs args ["dependencies"])); (builtins.removeAttrs args ["dependencies"]));
in s.overrideAttrs (o: { in s.overrideAttrs (o: {
dependencies = dependencies ++ o.dependencies; dependencies = (builtins.map (d: d.name) dependencies) ++ o.dependencies;
buildInputs = dependencies ++ o.buildInputs; buildInputs = dependencies ++ o.buildInputs;
}); });
}; };
users.root = { users.root = {
uid = 0; gid= 0; gecos = "Root of all evaluation"; uid = 0; gid= 0; gecos = "Root of all evaluation";
dir = "/home/root/"; dir = "/home/root/";

View File

@ -10,6 +10,7 @@
{ lib, pkgs, config, ...}: { lib, pkgs, config, ...}:
let let
inherit (lib) mkOption types; inherit (lib) mkOption types;
inherit (pkgs.liminix.services) oneshot;
inherit (pkgs) liminix; inherit (pkgs) liminix;
in in
{ {
@ -22,7 +23,7 @@ in
}; };
}; };
config.system.service.bridge = { config.system.service.bridge = {
primary = config.system.callService ./primary.nix { primary = liminix.callService ./primary.nix {
ifname = mkOption { ifname = mkOption {
type = types.str; type = types.str;
description = "bridge interface name to create"; description = "bridge interface name to create";

View File

@ -1,6 +1,7 @@
{ {
liminix liminix
, ifwait , ifwait
, lib
, svc , svc
}: }:
{ members, primary } : { members, primary } :
@ -8,6 +9,7 @@
let let
inherit (liminix.networking) interface; inherit (liminix.networking) interface;
inherit (liminix.services) bundle oneshot; inherit (liminix.services) bundle oneshot;
inherit (lib) mkOption types;
addif = member : addif = member :
# how do we get sight of services from here? maybe we need to # how do we get sight of services from here? maybe we need to
# implement ifwait as a regualr derivation instead of a # implement ifwait as a regualr derivation instead of a

View File

@ -1,10 +1,12 @@
{ {
liminix liminix
, ifwait
, lib , lib
}: }:
{ ifname } : { ifname } :
let let
inherit (liminix.services) oneshot; inherit (liminix.services) bundle oneshot;
inherit (lib) mkOption types;
in oneshot rec { in oneshot rec {
name = "${ifname}.link"; name = "${ifname}.link";
up = '' up = ''

View File

@ -8,7 +8,7 @@
{ lib, pkgs, config, ...}: { lib, pkgs, config, ...}:
let let
inherit (lib) mkOption types mapAttrsToList; inherit (lib) mkOption mkEnableOption types mapAttrsToList;
inherit (pkgs.pseudofile) dir symlink; inherit (pkgs.pseudofile) dir symlink;
inherit (lib.strings) toUpper; inherit (lib.strings) toUpper;
@ -85,13 +85,10 @@ in {
}; };
}; };
filesystem = dir { filesystem = dir {
bin = dir ( bin = dir ({
{ busybox = symlink "${busybox}/bin/busybox";
busybox = symlink "${busybox}/bin/busybox"; sh = symlink "${busybox}/bin/busybox";
sh = symlink "${busybox}/bin/busybox"; } // makeLinks);
}
// makeLinks
);
}; };
}; };
} }

View File

@ -0,0 +1,56 @@
{ config, pkgs, lib, ... }:
let
inherit (pkgs.liminix.services) oneshot;
svc = config.system.service;
in {
config = {
kernel.config = {
USB_NET_HUAWEI_CDC_NCM = "y";
USB_USBNET = "y";
USB_SERIAL = "y";
USB_SERIAL_OPTION = "y";
};
# https://www.0xf8.org/2017/01/flashing-a-huawei-e3372h-4g-lte-stick-from-hilink-to-stick-mode/
services.wwan = let
chat = lib.escapeShellArgs [
# Your usb modem thing might present as a tty that you run PPP
# over, or as a network device ("ndis" or "ncm"). The latter
# kind is to be preferred, at least in principle, because it's
# faster. This initialization sequence works for the Huawei
# E3372, and took much swearing: the error messages are *awful*
"" "AT"
"OK" "ATZ"
# create PDP context
"OK" "AT+CGDCONT=1,\"IP\",\"data.uk\""
# activate PDP context
"OK" "AT+CGACT=1,1"
# setup username and password per requirements of sim provider.
# (caret is special to chat, so needs escaping in AT commands)
"OK" "AT\\^AUTHDATA=1,2,\"1p\",\"one2one\",\"user\""
# start the thing (I am choosing to read this as "NDIS DialUP")
"OK" "AT\\^NDISDUP=1,1"
];
modemConfig = oneshot {
name = "modem-configure";
# this is currently only going to work if there is one
# modem only plugged in, it is plugged in already at boot,
# and nothing else is providing a USB tty.
# https://stackoverflow.com/questions/5477882/how-to-i-detect-whether-a-tty-belonging-to-a-gsm-3g-modem-is-a-data-or-control-p
up = ''
sleep 2
${pkgs.usb-modeswitch}/bin/usb_modeswitch -v 12d1 -p 14fe --huawei-new-mode
sleep 5
${pkgs.ppp}/bin/chat -s -v ${chat} 0<>/dev/ttyUSB0 1>&0
'';
down = "chat -v '' ATZ OK </dev/ttyUSB0 >&0";
};
in svc.network.link.build {
ifname = "wwan0";
dependencies = [ modemConfig ];
};
};
}

View File

@ -27,6 +27,6 @@
dir (svc.open state-directory)] dir (svc.open state-directory)]
(accumulate [addresses [] (accumulate [addresses []
v (dir:events)] v (dir:events)]
(update-prefixes lan-device addresses (or (v:output "prefix") []) system)))) (update-prefixes lan-device addresses (v:output "prefix") system))))
{ : changes : run } { : changes : run }

View File

@ -2,6 +2,7 @@
writeFennel writeFennel
, linotify , linotify
, anoia , anoia
, lua
, lualinux , lualinux
}: }:
writeFennel "acquire-delegated-prefix" { writeFennel "acquire-delegated-prefix" {

View File

@ -27,6 +27,6 @@
dir (svc.open state-directory)] dir (svc.open state-directory)]
(accumulate [addresses [] (accumulate [addresses []
v (dir:events)] v (dir:events)]
(update-addresses wan-device addresses (or (v:output "address") []) system)))) (update-addresses wan-device addresses (v:output "address") system))))
{ : update-addresses : deletions : run } { : update-addresses : deletions : run }

View File

@ -3,6 +3,7 @@
, linotify , linotify
, anoia , anoia
, lualinux , lualinux
, lua
}: }:
writeFennel "acquire-wan-address" { writeFennel "acquire-wan-address" {
packages = [ linotify anoia lualinux ]; packages = [ linotify anoia lualinux ];

View File

@ -1,10 +1,12 @@
{ {
liminix liminix
, lib
, callPackage , callPackage
}: }:
{ client, interface } : { client, interface } :
let let
inherit (liminix.services) longrun; inherit (liminix.services) longrun;
inherit (lib) mkOption types;
name = "dhcp6c.addr.${client.name}.${interface.name}"; name = "dhcp6c.addr.${client.name}.${interface.name}";
script = callPackage ./acquire-wan-address.nix { }; script = callPackage ./acquire-wan-address.nix { };
in longrun { in longrun {

View File

@ -1,11 +1,13 @@
{ {
liminix liminix
, lib
, odhcp6c , odhcp6c
, odhcp-script , odhcp-script
}: }:
{ interface } : { interface } :
let let
inherit (liminix.services) longrun; inherit (liminix.services) longrun;
inherit (lib) mkOption types;
name = "dhcp6c.${interface.name}"; name = "dhcp6c.${interface.name}";
in longrun { in longrun {
inherit name; inherit name;

View File

@ -12,6 +12,7 @@
{ lib, pkgs, config, ...}: { lib, pkgs, config, ...}:
let let
inherit (lib) mkOption types; inherit (lib) mkOption types;
inherit (pkgs.liminix.services) oneshot;
inherit (pkgs) liminix; inherit (pkgs) liminix;
in in
{ {
@ -23,13 +24,13 @@ in
}; };
}; };
config.system.service.dhcp6c = { config.system.service.dhcp6c = {
client = config.system.callService ./client.nix { client = liminix.callService ./client.nix {
interface = mkOption { interface = mkOption {
type = liminix.lib.types.interface; type = liminix.lib.types.interface;
description = "interface (usually WAN) to query for DHCP6"; description = "interface (usually WAN) to query for DHCP6";
}; };
}; };
address = config.system.callService ./address.nix { address = liminix.callService ./address.nix {
client = mkOption { client = mkOption {
type = types.anything; # liminix.lib.types.service; type = types.anything; # liminix.lib.types.service;
}; };
@ -38,7 +39,7 @@ in
description = "interface to assign the address to"; description = "interface to assign the address to";
}; };
}; };
prefix = config.system.callService ./prefix.nix { prefix = liminix.callService ./prefix.nix {
client = mkOption { client = mkOption {
type = types.anything; # liminix.lib.types.service; type = types.anything; # liminix.lib.types.service;
}; };

View File

@ -1,10 +1,12 @@
{ {
liminix liminix
, lib
, callPackage , callPackage
}: }:
{ client, interface } : { client, interface } :
let let
inherit (liminix.services) longrun; inherit (liminix.services) longrun;
inherit (lib) mkOption types;
name = "dhcp6c.prefix.${client.name}.${interface.name}"; name = "dhcp6c.prefix.${client.name}.${interface.name}";
script = callPackage ./acquire-delegated-prefix.nix { }; script = callPackage ./acquire-delegated-prefix.nix { };
in longrun { in longrun {

View File

@ -16,7 +16,7 @@ in {
}; };
}; };
config = { config = {
system.service.dnsmasq = config.system.callService ./service.nix { system.service.dnsmasq = liminix.callService ./service.nix {
user = mkOption { user = mkOption {
type = types.str; type = types.str;
default = "dnsmasq"; default = "dnsmasq";

View File

@ -1,6 +1,6 @@
{ {
liminix liminix
, dnsmasq , dnsmasqSmall
, serviceFns , serviceFns
, lib , lib
}: }:
@ -18,7 +18,7 @@ let
name = "${interface.name}.dnsmasq"; name = "${interface.name}.dnsmasq";
inherit (liminix.services) longrun; inherit (liminix.services) longrun;
inherit (lib) concatStrings concatStringsSep mapAttrsToList; inherit (lib) concatStrings concatStringsSep mapAttrsToList;
hostOpt = name : { mac, v4, v6, leasetime }: hostOpt = name : { mac, v4, v6, leasetime } @ attrs:
let v6s = concatStrings (map (a : ",[${a}]") v6); let v6s = concatStrings (map (a : ",[${a}]") v6);
in "--dhcp-host=${mac},${v4}${v6s},${name},${builtins.toString leasetime}"; in "--dhcp-host=${mac},${v4}${v6s},${name},${builtins.toString leasetime}";
in in
@ -26,7 +26,8 @@ longrun {
inherit name; inherit name;
dependencies = [ interface ]; dependencies = [ interface ];
run = '' run = ''
${dnsmasq}/bin/dnsmasq \ . ${serviceFns}
${dnsmasqSmall}/bin/dnsmasq \
--user=${user} \ --user=${user} \
--domain=${domain} \ --domain=${domain} \
--group=${group} \ --group=${group} \

View File

@ -8,6 +8,7 @@
let let
inherit (lib) mkOption types; inherit (lib) mkOption types;
inherit (pkgs) liminix; inherit (pkgs) liminix;
inherit (pkgs.liminix.services) oneshot;
kmodules = pkgs.kmodloader.override { kmodules = pkgs.kmodloader.override {
inherit (config.system.outputs) kernel; inherit (config.system.outputs) kernel;
@ -54,7 +55,7 @@ in
}; };
config = { config = {
system.service.firewall = system.service.firewall =
let svc = config.system.callService ./service.nix { let svc = liminix.callService ./service.nix {
extraRules = mkOption { extraRules = mkOption {
type = types.attrsOf types.attrs; type = types.attrsOf types.attrs;
description = "firewall ruleset"; description = "firewall ruleset";

View File

@ -7,6 +7,8 @@
{ rules, extraRules }: { rules, extraRules }:
let let
inherit (liminix.services) oneshot; inherit (liminix.services) oneshot;
inherit (liminix.lib) typeChecked;
inherit (lib) mkOption types;
script = firewallgen "firewall.nft" (lib.recursiveUpdate rules extraRules); script = firewallgen "firewall.nft" (lib.recursiveUpdate rules extraRules);
in oneshot { in oneshot {
name = "firewall"; name = "firewall";

View File

@ -5,13 +5,14 @@
## you want to run on it, and would usually be set in the "device" file: ## you want to run on it, and would usually be set in the "device" file:
## :file:`devices/manuf-model/default.nix` ## :file:`devices/manuf-model/default.nix`
{ lib, ... }:
{ lib, pkgs, config, ...}:
let let
inherit (lib) mkOption types; inherit (lib) mkEnableOption mkOption types isDerivation hasAttr ;
in in {
{
options = { options = {
boot = { }; boot = {
};
hardware = { hardware = {
dts = { dts = {
src = mkOption { src = mkOption {
@ -25,7 +26,7 @@ in
''; '';
}; };
includes = mkOption { includes = mkOption {
default = [ ]; default = [];
description = "List of directories to search for DTS includes (.dtsi files)"; description = "List of directories to search for DTS includes (.dtsi files)";
type = types.listOf types.path; type = types.listOf types.path;
}; };

View File

@ -1,43 +0,0 @@
## Health check
##
## Runs a service and a separate periodic health process. When the
## health check starts failing over a period of time, kill the service.
## (Usually that means the supervisor will restart it, but you can
## have other behaviours by e.g. combining this service with a round-robin
## for failover)
{ lib, pkgs, config, ...}:
let
inherit (lib) mkOption types;
inherit (pkgs) liminix;
# inherit (pkgs.liminix.services) longrun;
in {
options = {
system.service.health-check = mkOption {
description = "run a service while periodically checking it is healthy";
type = liminix.lib.types.serviceDefn;
};
};
config.system.service.health-check = config.system.callService ./service.nix {
service = mkOption {
type = liminix.lib.types.service;
};
interval = mkOption {
description = "interval between checks, in seconds";
type = types.int;
default = 10;
example = 10;
};
threshold = mkOption {
description = "number of consecutive failures required for the service to be kicked";
type = types.int;
example = 3;
};
healthCheck = mkOption {
description = "health check command or script. Expected to exit 0 if the service is healthy or any other exit status otherwise";
type = types.path;
};
};
config.programs.busybox.applets = ["expr"];
}

View File

@ -1,37 +0,0 @@
{
liminix, lib, lim, s6
}:
{ service, interval, threshold, healthCheck } :
let
inherit (liminix.services) oneshot longrun;
inherit (builtins) toString;
inherit (service) name;
checker = let name' = "check-${name}"; in longrun {
name = name';
run = ''
fails=0
echo waiting for /run/service/${name}
${s6}/bin/s6-svwait -U /run/service/${name} || exit
while sleep ${toString interval} ; do
${healthCheck}
if test $? -gt 0; then
fails=$(expr $fails + 1)
else
fails=0
fi
echo fails $fails/${toString threshold} for ${name}
if test "$fails" -gt "${toString threshold}" ; then
echo time to die
${s6}/bin/s6-svc -r /run/service/${name}
echo bounced
fails=0
echo waiting for /run/service/${name}
${s6}/bin/s6-svwait -U /run/service/${name}
fi
done
'';
};
in service.overrideAttrs(o: {
buildInputs = (lim.orEmpty o.buildInputs) ++ [ checker ];
dependencies = (lim.orEmpty o.dependencies) ++ [ checker ];
})

View File

@ -16,14 +16,13 @@ let
inherit (lib) mkOption types; inherit (lib) mkOption types;
inherit (pkgs) liminix; inherit (pkgs) liminix;
in { in {
imports = [ ../secrets ];
options = { options = {
system.service.hostapd = mkOption { system.service.hostapd = mkOption {
type = liminix.lib.types.serviceDefn; type = liminix.lib.types.serviceDefn;
}; };
}; };
config = { config = {
system.service.hostapd = config.system.callService ./service.nix { system.service.hostapd = liminix.callService ./service.nix {
interface = mkOption { interface = mkOption {
type = liminix.lib.types.service; type = liminix.lib.types.service;
}; };

View File

@ -1,16 +1,15 @@
{ {
liminix liminix
, svc
, hostapd , hostapd
, output-template
, writeText , writeText
, lib , lib
}: }:
{ interface, params} : { interface, params} :
let let
inherit (liminix.services) longrun; inherit (liminix.services) longrun;
inherit (lib) concatStringsSep mapAttrsToList unique ; inherit (lib) concatStringsSep mapAttrsToList;
inherit (builtins) map filter attrValues length head typeOf; inherit (liminix.lib) typeChecked;
inherit (lib) mkOption types;
# This is not a friendly interface to configuring a wireless AP: it # This is not a friendly interface to configuring a wireless AP: it
# just passes everything straight through to the hostapd config. # just passes everything straight through to the hostapd config.
@ -23,35 +22,18 @@ let
driver = "nl80211"; driver = "nl80211";
logger_syslog = "-1"; logger_syslog = "-1";
logger_syslog_level = 1; logger_syslog_level = 1;
ctrl_interface = "/run/${name}"; ctrl_interface = "/run/hostapd";
ctrl_interface_group = 0; ctrl_interface_group = 0;
}; };
attrs = defaults // params ;
literal_or_output = o: ({
string = builtins.toJSON;
int = builtins.toJSON;
lambda = (o: "output(${builtins.toJSON (o "service")}, ${builtins.toJSON (o "path")})");
}.${builtins.typeOf o}) o;
conf = conf = writeText "hostapd.conf"
(writeText "hostapd.conf.in" (concatStringsSep
((concatStringsSep "\n"
"\n" (mapAttrsToList
(mapAttrsToList (name: value: "${name}=${toString value}")
(n : v : "${n}={{ ${literal_or_output v} }}") (defaults // params)));
attrs)) + "\n")); in longrun {
service = longrun { inherit name;
inherit name; dependencies = [ interface ];
dependencies = [ interface ]; run = "${hostapd}/bin/hostapd -i $(output ${interface} ifname) -P /run/${name}.pid -S ${conf}";
run = ''
mkdir -p /run/${name}
chmod 0700 /run/${name}
${output-template}/bin/output-template '{{' '}}' < ${conf} > /run/${name}/hostapd.conf
exec ${hostapd}/bin/hostapd -i $(output ${interface} ifname) -P /run/${name}/hostapd.pid -S /run/${name}/hostapd.conf
'';
};
watch = filter (f: typeOf f == "lambda") (attrValues attrs);
in svc.secrets.subscriber.build {
inherit service watch;
action = "restart-all";
} }

View File

@ -9,7 +9,7 @@ let
in longrun { in longrun {
name = "ifwait.${interface.name}"; name = "ifwait.${interface.name}";
buildInputs = [ service ]; buildInputs = [ service ];
restart-on-upgrade = true; isTrigger = true;
run = '' run = ''
${ifwait}/bin/ifwait -s ${service.name} $(output ${interface} ifname) ${state} ${ifwait}/bin/ifwait -s ${service.name} $(output ${interface} ifname) ${state}
''; '';

View File

@ -5,9 +5,14 @@
{ lib, pkgs, config, ...}: { lib, pkgs, config, ...}:
let let
inherit (lib) mkOption types ; inherit (lib) mkEnableOption mkOption types isDerivation hasAttr ;
inherit (pkgs.pseudofile) dir symlink;
inherit (pkgs.liminix.networking) address interface;
inherit (pkgs.liminix.services) bundle;
inherit (pkgs) liminix; inherit (pkgs) liminix;
type_service = pkgs.liminix.lib.types.service;
mergeConditionals = conf : conditions : mergeConditionals = conf : conditions :
# for each key in conditions, if it is present in conf # for each key in conditions, if it is present in conf
# then merge the associated value into conf # then merge the associated value into conf

View File

@ -1,4 +1,4 @@
{ config, pkgs, ...} : { config, pkgs, lib, ...} :
let inherit (pkgs.liminix.services) oneshot longrun; let inherit (pkgs.liminix.services) oneshot longrun;
in { in {
config = { config = {
@ -11,21 +11,16 @@ in {
devout = longrun { devout = longrun {
name = "devout"; name = "devout";
notification-fd = 10; notification-fd = 10;
timeout-up = 60 * 1000; run = "${pkgs.devout}/bin/devout /run/devout.sock 4";
run = "exec ${pkgs.devout}/bin/devout /run/devout.sock 4";
dependencies = [ mdevd ];
}; };
coldplug = oneshot { coldplug = oneshot {
name = "coldplug"; name ="coldplug";
# would love to know what mdevd-coldplug/udevadm trigger does # would love to know what mdevd-coldplug/udevadm trigger does
# that this doesn't # that this doesn't
up = '' up = ''
for i in $(find /sys -name uevent); do ( echo change > $i ) ; done for i in $(find /sys -name uevent); do ( echo change > $i ) ; done
''; '';
dependencies = [ dependencies = [devout mdevd];
devout
mdevd
];
}; };
}; };
}; };

View File

@ -7,6 +7,11 @@
let let
inherit (lib) mkOption types; inherit (lib) mkOption types;
inherit (pkgs) liminix; inherit (pkgs) liminix;
mkBoolOption = description : mkOption {
type = types.bool;
inherit description;
default = true;
};
in { in {
options = { options = {
@ -14,9 +19,9 @@ in {
type = liminix.lib.types.serviceDefn; type = liminix.lib.types.serviceDefn;
}; };
}; };
imports = [ ../mdevd.nix ../uevent-rule ]; imports = [ ../mdevd.nix ];
config.system.service.mount = config.system.service.mount =
let svc = config.system.callService ./service.nix { let svc = liminix.callService ./service.nix {
partlabel = mkOption { partlabel = mkOption {
type = types.str; type = types.str;
example = "my-usb-stick"; example = "my-usb-stick";

View File

@ -1,27 +1,26 @@
{ {
liminix liminix
, uevent-watch
, lib , lib
, svc
}: }:
{ partlabel, mountpoint, options, fstype }: { partlabel, mountpoint, options, fstype }:
let let
inherit (liminix.services) oneshot; inherit (liminix.services) longrun oneshot;
device = "/dev/disk/by-partlabel/${partlabel}"; device = "/dev/disk/by-partlabel/${partlabel}";
name = "mount.${lib.strings.sanitizeDerivationName (lib.escapeURL mountpoint)}";
options_string = options_string =
if options == [] then "" else "-o ${lib.concatStringsSep "," options}"; if options == [] then "" else "-o ${lib.concatStringsSep "," options}";
controller = svc.uevent-rule.build { mount_service = oneshot {
serviceName = name; name = "mount.${lib.escapeURL mountpoint}";
symlink = device; timeout-up = 3600;
terms = { up = "mount -t ${fstype} ${options_string} ${device} ${mountpoint}";
partname = partlabel; down = "umount ${mountpoint}";
devtype = "partition";
};
}; };
in oneshot { in longrun {
inherit name; name = "watch-mount.${lib.strings.sanitizeDerivationName mountpoint}";
timeout-up = 3600; isTrigger = true;
up = "mount -t ${fstype} ${options_string} ${device} ${mountpoint}"; buildInputs = [ mount_service ];
down = "umount ${mountpoint}";
inherit controller; run = ''
${uevent-watch}/bin/uevent-watch -s ${mount_service.name} -n ${device} partname=${partlabel} devtype=partition
'';
} }

View File

@ -1,5 +1,6 @@
{ {
liminix liminix
, ifwait
, serviceFns , serviceFns
, lib , lib
}: }:
@ -11,6 +12,7 @@ let
# prefixes, or the same but different protocols # prefixes, or the same but different protocols
name = "${interface.name}.a.${address}"; name = "${interface.name}.a.${address}";
up = '' up = ''
. ${serviceFns}
dev=$(output ${interface} ifname) dev=$(output ${interface} ifname)
ip address add ${address}/${toString prefixLength} dev $dev ip address add ${address}/${toString prefixLength} dev $dev
(in_outputs ${name} (in_outputs ${name}

View File

@ -64,7 +64,7 @@ in {
services.loopback = config.hardware.networkInterfaces.lo; services.loopback = config.hardware.networkInterfaces.lo;
system.service.network = { system.service.network = {
link = config.system.callService ./link.nix { link = liminix.callService ./link.nix {
ifname = mkOption { ifname = mkOption {
type = types.str; type = types.str;
example = "eth0"; example = "eth0";
@ -89,7 +89,7 @@ in {
example = 1480; example = 1480;
}; };
}; };
address = config.system.callService ./address.nix { address = liminix.callService ./address.nix {
interface = mkOption { interface = mkOption {
type = liminix.lib.types.service; type = liminix.lib.types.service;
}; };
@ -104,7 +104,7 @@ in {
}; };
}; };
route = config.system.callService ./route.nix { route = liminix.callService ./route.nix {
interface = mkOption { interface = mkOption {
type = types.nullOr liminix.lib.types.interface; type = types.nullOr liminix.lib.types.interface;
default = null; default = null;
@ -125,7 +125,7 @@ in {
}; };
}; };
forward = config.system.callService ./forward.nix { forward = liminix.callService ./forward.nix {
enableIPv4 = mkOption { enableIPv4 = mkOption {
type = types.bool; type = types.bool;
default = true; default = true;
@ -136,7 +136,7 @@ in {
}; };
}; };
dhcp.client = config.system.callService ./dhcpc.nix { dhcp.client = liminix.callService ./dhcpc.nix {
interface = mkOption { interface = mkOption {
type = liminix.lib.types.service; type = liminix.lib.types.service;
}; };

View File

@ -17,7 +17,7 @@ let
ip address replace $ip/$mask dev $interface ip address replace $ip/$mask dev $interface
(in_outputs ${name} (in_outputs ${name}
for i in lease mask ip router siaddr dns serverid subnet opt53 interface ; do for i in lease mask ip router siaddr dns serverid subnet opt53 interface ; do
(printenv $i || true) > $i printenv $i > $i
done) done)
} }
case $action in case $action in
@ -40,7 +40,7 @@ let
''; '';
in longrun { in longrun {
inherit name; inherit name;
run = "exec /bin/udhcpc -f -i $(output ${interface} ifname) -x hostname:$(cat /proc/sys/kernel/hostname) -s ${script}"; run = "/bin/udhcpc -f -i $(output ${interface} ifname) -x hostname:$(cat /proc/sys/kernel/hostname) -s ${script}";
notification-fd = 10; notification-fd = 10;
dependencies = [ interface ]; dependencies = [ interface ];
} }

View File

@ -1,5 +1,7 @@
{ {
liminix liminix
, ifwait
, serviceFns
, lib , lib
}: }:
{ enableIPv4, enableIPv6 }: { enableIPv4, enableIPv6 }:

View File

@ -1,5 +1,7 @@
{ {
liminix liminix
, ifwait
, serviceFns
, lib , lib
}: }:
{ {
@ -9,7 +11,8 @@
# if devpath is supplied, we rename the interface at that # if devpath is supplied, we rename the interface at that
# path to have the specified name. # path to have the specified name.
let let
inherit (liminix.services) oneshot; inherit (liminix.services) longrun oneshot;
inherit (lib) concatStringsSep;
name = "${ifname}.link"; name = "${ifname}.link";
rename = if devpath != null rename = if devpath != null
then '' then ''

View File

@ -1,15 +1,15 @@
{ {
liminix liminix
, ifwait
, serviceFns
, lib , lib
}: }:
{ target, via, interface ? null, metric }: { target, via, interface ? null, metric }:
let let
inherit (liminix.services) oneshot; inherit (liminix.services) oneshot;
with_dev = if interface != null then "dev $(output ${interface} ifname)" else ""; with_dev = if interface != null then "dev $(output ${interface} ifname)" else "";
target_hash = builtins.substring 0 12 (builtins.hashString "sha256" target);
via_hash = builtins.substring 0 12 (builtins.hashString "sha256" via);
in oneshot { in oneshot {
name = "route-${target_hash}-${builtins.substring 0 12 (builtins.hashString "sha256" "${via_hash}-${if interface!=null then interface.name else ""}")}"; name = "route-${target}-${builtins.substring 0 12 (builtins.hashString "sha256" "${via}-${if interface!=null then interface.name else ""}")}";
up = '' up = ''
ip route add ${target} via ${via} metric ${toString metric} ${with_dev} ip route add ${target} via ${via} metric ${toString metric} ${with_dev}
''; '';

View File

@ -18,7 +18,7 @@ in {
}; };
}; };
config = { config = {
system.service.ntp = config.system.callService ./service.nix { system.service.ntp = liminix.callService ./service.nix {
user = mkOption { user = mkOption {
type = types.str; type = types.str;
default = "ntp"; default = "ntp";

View File

@ -1,6 +1,7 @@
{ {
liminix liminix
, chrony , chrony
, serviceFns
, lib , lib
, writeText , writeText
}: }:
@ -8,6 +9,10 @@ params:
let let
inherit (liminix.services) longrun; inherit (liminix.services) longrun;
inherit (lib) concatStringsSep mapAttrsToList; inherit (lib) concatStringsSep mapAttrsToList;
inherit (liminix.lib) typeChecked;
inherit (lib) mkOption types;
serverOpts = types.listOf types.str;
configFile = p: configFile = p:
(mapAttrsToList (name: opts: "server ${name} ${concatStringsSep "" opts}") (mapAttrsToList (name: opts: "server ${name} ${concatStringsSep "" opts}")
p.servers) p.servers)

View File

@ -1,12 +1,12 @@
{ {
config, config
pkgs, , pkgs
lib, , lib
... , ...
}: }:
let let
inherit (lib) mkOption types concatStringsSep; inherit (lib) mkOption types concatStringsSep;
inherit (pkgs) liminix writeText; inherit (pkgs) liminix callPackage writeText;
o = config.system.outputs; o = config.system.outputs;
in in
{ {
@ -22,7 +22,7 @@ in
# but only part of one. # but only part of one.
kernel = mkOption { kernel = mkOption {
type = types.package; type = types.package;
internal = true; internal = true;
description = '' description = ''
kernel kernel
****** ******
@ -42,7 +42,7 @@ in
}; };
dtb = mkOption { dtb = mkOption {
type = types.package; type = types.package;
internal = true; internal = true;
description = '' description = ''
dtb dtb
*** ***
@ -52,7 +52,7 @@ in
}; };
uimage = mkOption { uimage = mkOption {
type = types.package; type = types.package;
internal = true; internal = true;
description = '' description = ''
uimage uimage
****** ******
@ -68,7 +68,7 @@ in
}; };
manifest = mkOption { manifest = mkOption {
type = types.package; type = types.package;
internal = true; internal = true;
description = '' description = ''
Debugging aid. JSON rendition of config.filesystem, on Debugging aid. JSON rendition of config.filesystem, on
which can run "nix-store -q --tree" on it and find which can run "nix-store -q --tree" on it and find

View File

@ -5,7 +5,7 @@
, ... , ...
}: }:
let let
inherit (lib) mkIf; inherit (lib) mkIf mkOption types;
o = config.system.outputs; o = config.system.outputs;
in in
{ {

View File

@ -5,7 +5,7 @@
, ... , ...
}: }:
let let
inherit (lib) mkIf; inherit (lib) mkIf mkOption types;
o = config.system.outputs; o = config.system.outputs;
in in
{ {

View File

@ -6,7 +6,7 @@
}: }:
let let
inherit (lib) mkEnableOption mkOption mkIf types; inherit (lib) mkEnableOption mkOption mkIf types;
inherit (pkgs) runCommand; inherit (pkgs) runCommand callPackage writeText;
in in
{ {
options = { options = {

View File

@ -5,7 +5,7 @@
, ... , ...
}: }:
let let
inherit (lib) mkIf; inherit (lib) mkIf mkOption types;
o = config.system.outputs; o = config.system.outputs;
in in
{ {

View File

@ -5,16 +5,13 @@
, ... , ...
}: }:
let let
inherit (lib) mkOption types concatStringsSep; inherit (lib) mkOption mkForce types concatStringsSep;
in { in {
imports = [ ../ramdisk.nix ]; imports = [ ../ramdisk.nix ];
options.system.outputs = { options.system.outputs = {
kexecboot = mkOption { kexecboot = mkOption {
type = types.package; type = types.package;
description = '' description = ''
kexecboot
*********
Directory containing files needed for kexec booting. Directory containing files needed for kexec booting.
Can be copied onto the target device using ssh or similar Can be copied onto the target device using ssh or similar
''; '';
@ -45,7 +42,8 @@ in {
boot-sh = boot-sh =
let let
inherit (config.system.outputs) rootfs; inherit (pkgs.lib.trivial) toHexString;
inherit (config.system.outputs) rootfs kernel;
cmdline = concatStringsSep " " config.boot.commandLine; cmdline = concatStringsSep " " config.boot.commandLine;
in in
pkgs.buildPackages.runCommand "boot.sh.sh" { pkgs.buildPackages.runCommand "boot.sh.sh" {

View File

@ -5,7 +5,7 @@
, ... , ...
}: }:
let let
inherit (lib) mkOption types; inherit (lib) mkOption types concatStringsSep;
o = config.system.outputs; o = config.system.outputs;
phram_address = lib.toHexString (config.hardware.ram.startAddress + 256 * 1024 * 1024); phram_address = lib.toHexString (config.hardware.ram.startAddress + 256 * 1024 * 1024);
in { in {

View File

@ -58,6 +58,7 @@ in {
system.outputs = rec { system.outputs = rec {
tftpboot = tftpboot =
let let
inherit (pkgs.lib.trivial) toHexString;
o = config.system.outputs; o = config.system.outputs;
image = let choices = { image = let choices = {
uimage = o.uimage; uimage = o.uimage;

View File

@ -5,7 +5,7 @@
, ... , ...
}: }:
let let
inherit (lib) mkOption types; inherit (lib) mkOption types concatStringsSep;
o = config.system.outputs; o = config.system.outputs;
cfg = config.tplink-safeloader; cfg = config.tplink-safeloader;
in { in {

View File

@ -5,7 +5,7 @@
, ... , ...
}: }:
let let
inherit (lib) mkIf mkOption types; inherit (lib) mkIf mkEnableOption mkOption types concatStringsSep;
cfg = config.boot.tftp; cfg = config.boot.tftp;
instructions = pkgs.writeText "env.scr" '' instructions = pkgs.writeText "env.scr" ''
setenv serverip ${cfg.serverip} setenv serverip ${cfg.serverip}

View File

@ -5,6 +5,7 @@
, ... , ...
}: }:
let let
inherit (pkgs) liminix;
inherit (lib) mkIf mkOption types concatStringsSep optionalString; inherit (lib) mkIf mkOption types concatStringsSep optionalString;
in in
{ {

View File

@ -5,7 +5,7 @@
, ... , ...
}: }:
let let
inherit (lib) mkIf mkOption types; inherit (lib) mkIf mkEnableOption mkOption types concatStringsSep;
models = "6b e1 6f e1 ff ff ff ff ff ff"; models = "6b e1 6f e1 ff ff ff ff ff ff";
in { in {
options.system.outputs = { options.system.outputs = {

View File

@ -1,83 +0,0 @@
{ writeAshScript, liminix, svc, lib, serviceFns, output-template }:
{
command,
name,
debug
, username,
password,
lcpEcho,
ppp-options,
dependencies ? []
} :
let
inherit (lib) optional optionals escapeShellArgs concatStringsSep;
inherit (liminix.services) longrun;
inherit (builtins) toJSON toString typeOf;
ip-up = writeAshScript "ip-up" {} ''
. ${serviceFns}
(in_outputs ${name}
echo $1 > ifname
echo $2 > tty
echo $3 > speed
echo $4 > address
echo $5 > peer-address
echo $DNS1 > ns1
echo $DNS2 > ns2
)
echo >/proc/self/fd/10
'';
ip6-up = writeAshScript "ip6-up" {} ''
. ${serviceFns}
(in_outputs ${name}
echo $4 > ipv6-address
echo $5 > ipv6-peer-address
)
echo >/proc/self/fd/10
'';
literal_or_output =
let v = o: ({
string = toJSON;
int = toJSON;
lambda = (o: "output(${toJSON (o "service")}, ${toJSON (o "path")})");
}.${typeOf o}) o;
in o: "{{ ${v o} }}";
ppp-options' =
["+ipv6" "noauth"]
++ optional debug "debug"
++ optionals (username != null) ["name" (literal_or_output username)]
++ optionals (password != null) ["password" (literal_or_output password)]
++ optional lcpEcho.adaptive "lcp-echo-adaptive"
++ optionals (lcpEcho.interval != null)
["lcp-echo-interval" (toString lcpEcho.interval)]
++ optionals (lcpEcho.failure != null)
["lcp-echo-failure" (toString lcpEcho.failure)]
++ ppp-options
++ ["ip-up-script" ip-up
"ipv6-up-script" ip6-up
"ipparam" name
"nodetach"
"usepeerdns"
"nodefaultroute"
"logfd" "2"
];
service = longrun {
inherit name;
run = ''
mkdir -p /run/${name}
chmod 0700 /run/${name}
in_outputs ${name}
echo ${escapeShellArgs ppp-options'} | ${output-template}/bin/output-template '{{' '}}' > /run/${name}/ppp-options
${command}
'';
notification-fd = 10;
timeout-up = if lcpEcho.failure != null
then (10 + lcpEcho.failure * lcpEcho.interval) * 1000
else 60 * 1000;
inherit dependencies;
};
in svc.secrets.subscriber.build {
watch = lib.filter (n: typeOf n=="lambda") [ username password ];
inherit service;
}

View File

@ -1,31 +1,18 @@
## PPP ## PPP
## === ## ===
## ##
## ``ppoe`` (PPP over Ethernet) provides a service to address the case ## A PPPoE (PPP over Ethernet) configuration to address the case where
## where your Liminix device is connected to an upstream network using ## your Liminix device is connected to an upstream network using
## PPPoE. This is typical for UK broadband connections where the ## PPPoE. This is typical for UK broadband connections where the
## physical connection is made by OpenReach ("Fibre To The X") and ## physical connection is made by OpenReach ("Fibre To The X") and
## common in some other localities as well: check with your ISP if this is ## common in some other localities as well: ask your ISP if this is
## you. ## you.
##
## ``l2tp`` (Layer 2 Tunelling Protocol) provides a service that
## tunnels PPP over the Internet. This may be used by some ISPs in
## conjunction with a DHCP uplink, or other more creative forms of
## network connection
{ lib, pkgs, config, ...}: { lib, pkgs, config, ...}:
let let
inherit (lib) mkOption types; inherit (lib) mkOption types;
inherit (pkgs) liminix; inherit (pkgs) liminix;
mkStringOption =
description: mkOption {
type = types.nullOr types.str;
default = null;
inherit description;
};
in { in {
imports = [ ../secrets ];
options = { options = {
system.service.pppoe = mkOption { system.service.pppoe = mkOption {
type = liminix.lib.types.serviceDefn; type = liminix.lib.types.serviceDefn;
@ -35,89 +22,23 @@ in {
}; };
}; };
config = { config = {
system.service.pppoe = config.system.callService ./pppoe.nix { system.service.pppoe = pkgs.liminix.callService ./pppoe.nix {
interface = mkOption { interface = mkOption {
type = liminix.lib.types.service; type = liminix.lib.types.service;
description = "ethernet interface to run PPPoE over"; description = "ethernet interface to run PPPoE over";
}; };
username = mkOption {
type = types.nullOr (liminix.lib.types.replacable types.str);
default = null;
description = "username";
};
password = mkOption {
type = types.nullOr (liminix.lib.types.replacable types.str);
default = null;
description = "password";
};
lcpEcho = {
adaptive = mkOption {
description = "send LCP echo-request frames only if no traffic was received from the peer since the last echo-request was sent";
type = types.bool;
default = true;
};
interval = mkOption {
type = types.nullOr types.int;
default = 3;
description = "send an LCP echo-request frame to the peer every n seconds";
};
failure = mkOption {
type = types.nullOr types.int;
default = 3;
description = "terminate connection if n LCP echo-requests are sent without receiving a valid LCP echo-reply";
};
};
debug = mkOption {
description = "log the contents of all control packets sent or received";
default = false;
type = types.bool;
};
ppp-options = mkOption { ppp-options = mkOption {
type = types.listOf types.str; type = types.listOf types.str;
description = "options supplied on ppp command line"; description = "options supplied on ppp command line";
default = [];
}; };
}; };
system.service.l2tp = config.system.callService ./l2tp.nix { system.service.l2tp = pkgs.liminix.callService ./l2tp.nix {
lns = mkOption { lns = mkOption {
type = types.str; type = types.str;
description = "hostname or address of the L2TP network server"; description = "hostname or address of the L2TP network server";
}; };
username = mkOption {
type = types.nullOr (liminix.lib.types.replacable types.str);
default = null;
description = "username";
};
password = mkOption {
type = types.nullOr (liminix.lib.types.replacable types.str);
default = null;
description = "password";
};
lcpEcho = {
adaptive = mkOption {
description = "send LCP echo-request frames only if no traffic was received from the peer since the last echo-request was sent";
type = types.bool;
default = true;
};
interval = mkOption {
type = types.nullOr types.int;
default = 3;
description = "send an LCP echo-request frame to the peer every n seconds";
};
failure = mkOption {
type = types.nullOr types.int;
default = 3;
description = "terminate connection if n LCP echo-requests are sent without receiving a valid LCP echo-reply";
};
};
debug = mkOption {
description = "log the contents of all control packets sent or received";
default = false;
type = types.bool;
};
ppp-options = mkOption { ppp-options = mkOption {
type = types.listOf types.str; type = types.listOf types.str;
default = [];
description = "options supplied on ppp command line"; description = "options supplied on ppp command line";
}; };
}; };

View File

@ -1,40 +1,62 @@
{ {
lib, liminix
liminix, , lib
output-template, , ppp
serviceFns, , pppoe
svc, , writeAshScript
writeAshScript, , writeText
writeText, , serviceFns
xl2tpd, , xl2tpd
callPackage
} : } :
{ lns, { lns, ppp-options }:
ppp-options,
lcpEcho,
username,
password,
debug
}:
let let
inherit (liminix.services) longrun;
name = "${lns}.l2tp"; name = "${lns}.l2tp";
common = callPackage ./common.nix { inherit svc; }; ip-up = writeAshScript "ip-up" {} ''
. ${serviceFns}
(in_outputs ${name}
echo $1 > ifname
echo $2 > tty
echo $3 > speed
echo $4 > address
echo $5 > peer-address
echo $DNS1 > ns1
echo $DNS2 > ns2
)
echo >/proc/self/fd/10
'';
ip6-up = writeAshScript "ip6-up" {} ''
. ${serviceFns}
(in_outputs ${name}
echo $4 > ipv6-address
echo $5 > ipv6-peer-address
)
echo >/proc/self/fd/10
'';
ppp-options' = ppp-options ++ [
"ip-up-script" ip-up
"ipv6-up-script" ip6-up
"ipparam" name
"nodetach"
"usepeerdns"
"logfd" "2"
];
conf = writeText "xl2tpd.conf" '' conf = writeText "xl2tpd.conf" ''
[lac upstream] [lac upstream]
lns = ${lns} lns = ${lns}
require authentication = no require authentication = no
pppoptfile = /run/${name}/ppp-options pppoptfile = ${writeText "ppp-options" ppp-options'}
autodial = yes autodial = yes
redial = yes redial = yes
redial timeout = 1
max redials = 2 # this gives 1 actual retry, as xl2tpd can't count
''; '';
control = "/run/${name}/control"; control = "/run/xl2tpd/control-${name}";
in common { in
inherit name debug username password lcpEcho ppp-options; longrun {
command = '' inherit name;
run = ''
mkdir -p /run/xl2tpd
touch ${control} touch ${control}
exec ${xl2tpd}/bin/xl2tpd -D -p /run/${name}/${name}.pid -c ${conf} -C ${control} exec ${xl2tpd}/bin/xl2tpd -D -p /run/xl2tpd/${name}.pid -c ${conf} -C ${control}
''; '';
notification-fd = 10;
} }

View File

@ -1,30 +1,51 @@
{ {
lib, liminix
liminix, , lib
output-template, , ppp
ppp, , pppoe
pppoe, , writeAshScript
serviceFns, , serviceFns
svc,
writeAshScript,
callPackage
} : } :
{ interface, { interface, ppp-options }:
ppp-options,
lcpEcho,
username,
password,
debug
}:
let let
inherit (liminix.services) longrun;
name = "${interface.name}.pppoe"; name = "${interface.name}.pppoe";
common = callPackage ./common.nix { inherit svc; }; ip-up = writeAshScript "ip-up" {} ''
. ${serviceFns}
timeoutOpt = if lcpEcho.interval != null then "-T ${builtins.toString (4 * lcpEcho.interval)}" else ""; (in_outputs ${name}
in common { echo $1 > ifname
inherit name debug username password lcpEcho ppp-options; echo $2 > tty
command = '' echo $3 > speed
exec ${ppp}/bin/pppd pty "${pppoe}/bin/pppoe ${timeoutOpt} -I $(output ${interface} ifname)" file /run/${name}/ppp-options echo $4 > address
echo $5 > peer-address
echo $DNS1 > ns1
echo $DNS2 > ns2
)
echo >/proc/self/fd/10
''; '';
ip6-up = writeAshScript "ip6-up" {} ''
. ${serviceFns}
(in_outputs ${name}
echo $4 > ipv6-address
echo $5 > ipv6-peer-address
)
echo >/proc/self/fd/10
'';
ppp-options' = ppp-options ++ [
"ip-up-script" ip-up
"ipv6-up-script" ip6-up
"ipparam" name
"nodetach"
"usepeerdns"
"logfd" "2"
];
in
longrun {
inherit name;
run = ''
. ${serviceFns}
${ppp}/bin/pppd pty "${pppoe}/bin/pppoe -I $(output ${interface} ifname)" ${lib.concatStringsSep " " ppp-options'}
'';
notification-fd = 10;
dependencies = [ interface ]; dependencies = [ interface ];
} }

View File

@ -2,7 +2,7 @@
let let
svc = config.system.service; svc = config.system.service;
cfg = config.profile.gateway; cfg = config.profile.gateway;
inherit (lib) mkOption mkEnableOption mkIf types; inherit (lib) mkOption mkEnableOption mkIf mdDoc types optional optionals;
inherit (pkgs) liminix serviceFns; inherit (pkgs) liminix serviceFns;
inherit (liminix.services) bundle oneshot; inherit (liminix.services) bundle oneshot;
hostaps = hostaps =
@ -52,6 +52,8 @@ in {
wan = { wan = {
interface = mkOption { type = liminix.lib.types.interface; }; interface = mkOption { type = liminix.lib.types.interface; };
username = mkOption { type = types.str; };
password = mkOption { type = types.str; };
dhcp6.enable = mkOption { type = types.bool; }; dhcp6.enable = mkOption { type = types.bool; };
}; };
@ -84,7 +86,14 @@ in {
members = cfg.lan.interfaces; members = cfg.lan.interfaces;
}; };
services.wan = cfg.wan.interface; services.wan = svc.pppoe.build {
inherit (cfg.wan) interface;
ppp-options = [
"debug" "+ipv6" "noauth"
"name" cfg.wan.username
"password" cfg.wan.password
];
};
services.packet_forwarding = svc.network.forward.build { }; services.packet_forwarding = svc.network.forward.build { };
@ -149,6 +158,7 @@ in {
dependencies = [ config.services.wan ]; dependencies = [ config.services.wan ];
name = "resolvconf"; name = "resolvconf";
up = '' up = ''
. ${serviceFns}
( in_outputs ${name} ( in_outputs ${name}
echo "nameserver $(output ${config.services.wan} ns1)" > resolv.conf echo "nameserver $(output ${config.services.wan} ns1)" > resolv.conf
echo "nameserver $(output ${config.services.wan} ns2)" >> resolv.conf echo "nameserver $(output ${config.services.wan} ns2)" >> resolv.conf

Some files were not shown because too many files have changed in this diff Show More