Compare commits

..

3 Commits
main ... armv7

1752 changed files with 2543 additions and 17457 deletions

1
.gitignore vendored
View File

@ -6,4 +6,3 @@ result-*
_build _build
*-secrets.nix *-secrets.nix
examples/static-leases.nix examples/static-leases.nix
/doc/hardware.rst

117
NEWS
View File

@ -1,117 +0,0 @@
A brief guide to backward-incompatible changes
that are likely to break configurations or workflows
2023-07-13
* a significant re-arrangement of modules and services, which will
probably break any configuration written before this time. For a
detailed explanation, see
https://www.liminix.org/doc/configuration.html#modules
2023-12-10
* configurations (usually) need no longer import modules from
modules/outputs because devices are expected to do this instead. This
change is because the outputs that make sense in any given context are
usually a property of the device being installed onto.
2023-12-11
* rename outputs.flashimage to outputs.mtdimage (and also diskimage to
mbrimage). This change is made in the expectation that "fooimage" is
the name of an outputs that gloms together other filesystem-like
outputs with some kind of partition table - so we might in future have
gptimage or lvmimage or ubimage.
2024-01-03
Liminix is now targeted to Nixpkgs 23.11 (not 23.05 as previously).
Upstream changes that have led to incompatible Liminix changes are:
* newer U-Boot version
* util-linux can now be built (previously depended on systemd)
2024-01-30
New port! Thanks to Arnout Engelen <arnout@bzzt.net>, Liminix
now runs on the TP-Link Archer AX23.
2024-02-12
* We now build wifi drivers (mac80211) from the same kernel source as
the running kernel, instead of using drivers from the linux-backports
project. This may be a regression on some devices that depend on
OpenWrt patches for wireless functionality: if you have a device that
used to work and now doesn't, refer to OpenWrt
package/kernel/mac80211/patches/ to see if there's something in there
that needs to be applied.
* in general, we build kernel modules (e.g. for nftables) at the same
time as the kernel itself instead of expecting to be able to build
them afterwards as though they were "out of tree". Refer to commit
b9c0d93670275e69df24902b05bf4aa4f0fcbe96 for a fuller explanation
of how this simplifies things.
2024-02-13
So that we can be more consistent about services that would like their
state to be preserved across boots (assuming a writable filesystem)
these changes have been made
* /run/service-state has been moved to /run/services/outputs
to better reflect what it's used for
* /run/services/state is either a symlink to /persist/services/state
(if there's a writeable fs on /persist) or a directory (if there
isn't)
The change will lose your ssh host key(s) unless you copy them from
the old location to the new one before rebooting into the new system
mkdir -m 02751 -p /run/services/state/dropbear
cp /persist/secrets/dropbear/* /run/services/state/dropbear
The `output`, `mkoutputs` functions defined by ${serviceFns}
have been updated for the new location.
2024-02-16
New (or at least, previously unreported) port! Liminix now runs on the
Turris Omnia and has been serving my family's internet needs for most
of this week. Thanks to NGI0 Entrust and the NLnet Foundation for
sponsoring this development (and funding the hardware)
2024-02-21
New port! Thanks to Raito Bezarius, Liminix now runs on the Zyxel NWA50AX,
an MT7621 (MIPS EL) dual radio WiFi AP.
2024-04-29
The setup for using `levitate` has changed: now it accepts an entire
config fragment, not just a list of services. Hopefully this makes it
a bit more useful :-)
defaultProfile.packages = with pkgs; [
...
(levitate.override {
config = {
services = {
inherit (config.services) dhcpc sshd watchdog;
};
defaultProfile.packages = [ mtdutils ];
users.root.openssh.authorizedKeys.keys = secrets.root.keys;
};
})
];
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.

View File

@ -18,14 +18,22 @@ outside word goes across it.
Liminix is pre-1.0. We are still finding new and better ways to do things, Liminix is pre-1.0. We are still finding new and better ways to do things,
and there is no attempt to maintain backward compatibility with the old and there is no attempt to maintain backward compatibility with the old
ways. ways. This will change when it settles down.
The [NEWS](NEWS) file (available wherever you found this README) is _In general:_ development mostly happens on the `main` branch, which is
a high-level overview of breaking changes. therefore not guaranteed to build or to work on every commit. For the
latest functioning version, see [the CI system](https://build.liminix.org/jobset/liminix/build) and pick a revision with all jobs green.
Development mostly happens on the `main` branch, which is therefore _In particular:_ as of July 2023, a significant re-arrangement of
not guaranteed to build or to work on every commit. For the latest modules and services is ongoing:
functioning version, see [the CI system](https://build.liminix.org/jobset/liminix/build) and pick a revision with all jobs green.
* if you are using out-of-tree configurations created before commit
2e50368, especially if they reference things under pkgs.liminix,
they will need updating. Look at changes to examples/rotuer.nix
for guidance
* the same is intermittently true for examples/{extensino,arhcive}.nix
where I've updated rotuer and not updated them to match.
## Documentation ## Documentation
@ -33,7 +41,7 @@ functioning version, see [the CI system](https://build.liminix.org/jobset/limini
Documentation is in the [doc](doc/) directory. You can build it Documentation is in the [doc](doc/) directory. You can build it
by running by running
nix-shell -p sphinx --run "make -C doc hardware.rst html" nix-shell -p sphinx --run "make -C doc html"
Rendered documentation corresponding to the latest commit on `main` Rendered documentation corresponding to the latest commit on `main`
is published to [https://www.liminix.org/doc/](https://www.liminix.org/doc/) is published to [https://www.liminix.org/doc/](https://www.liminix.org/doc/)

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

@ -4,10 +4,6 @@ let
inherit (lib) mkOption mkEnableOption mdDoc types optional optionals; inherit (lib) mkOption mkEnableOption mdDoc types optional optionals;
in { in {
options.bordervm = { options.bordervm = {
keys = mkOption {
type = types.listOf types.str;
default = [ ];
};
l2tp = { l2tp = {
host = mkOption { host = mkOption {
description = mdDoc '' description = mdDoc ''
@ -55,17 +51,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 = {
@ -79,62 +76,37 @@ in {
}; };
}; };
services.openssh.enable = true; services.openssh.enable = true;
services.dnsmasq = {
enable = true;
resolveLocalQueries = false;
settings = {
# domain-needed = true;
dhcp-range = [ "10.0.0.10,10.0.0.240" ];
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";
}; };
}; };
}; };
environment.systemPackages = environment.systemPackages = with pkgs; [
let wireshark-nogui = pkgs.wireshark.override { withQt = false ; }; tcpdump
in with pkgs; [ wireshark
tcpdump socat
wireshark-nogui tufted
socat iptables
tufted usbutils
iptables ];
usbutils
busybox
];
security.sudo.wheelNeedsPassword = false; security.sudo.wheelNeedsPassword = false;
networking = { networking = {
hostName = "border"; hostName = "border";
@ -143,17 +115,11 @@ in {
useDHCP = false; useDHCP = false;
ipv4.addresses = [ { address = "10.0.0.1"; prefixLength = 24;}]; ipv4.addresses = [ { address = "10.0.0.1"; prefixLength = 24;}];
}; };
nat = {
enable = true;
internalInterfaces = [ "eth1" ];
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;
}; };
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";
}; };

107
ci.nix
View File

@ -1,78 +1,55 @@
{ {
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" virt = [ "qemu" "qemu-aarch64" ];
"gl-mt300a" hw = [ "gl-ar750" "gl-mt300n-v2" "gl-mt300a" ];
"gl-mt300n-v2" };
"qemu"
"qemu-aarch64"
"qemu-armv7l"
"tp-archer-ax23"
"zyxel-nwa50ax"
];
vanilla = ./vanilla-configuration.nix; vanilla = ./vanilla-configuration.nix;
for-device = name: for-device = cfg: name:
(import liminix { (import liminix {
inherit nixpkgs borderVmConf; inherit nixpkgs borderVmConf;
device = import (liminix + "/devices/${name}"); device = import (liminix + "/devices/${name}");
liminix-config = vanilla; liminix-config = cfg;
}).outputs.default; }).outputs.default;
tests = import ./tests/ci.nix; tests = import ./tests/ci.nix;
jobs = jobs =
(genAttrs devices for-device) (genAttrs devices.hw (name: for-device ./vanilla-configuration-hw.nix name)) //
// tests (genAttrs devices.virt (name: for-device vanilla name)) //
// { tests //
buildEnv = {
(import liminix { buildEnv = (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 = pkgs.stdenv.mkDerivation {
let name = "liminix-doc";
json = nativeBuildInputs = with pkgs; [
(import liminix { gnumake sphinx
inherit nixpkgs borderVmConf; fennel luaPackages.lyaml
device = import (liminix + "/devices/qemu"); ];
liminix-config = src = ./doc;
{ ... }: buildPhase = ''
{ cat ${(import ./doc/extract-options.nix).doc} > options.json
imports = [ ./modules/all-modules.nix ]; cat options.json | fennel --correlate parse-options.fnl > modules-generated.rst
}; cp ${(import ./doc/hardware.nix)} hardware.rst
}).outputs.optionsJson; make html
in '';
pkgs.stdenv.mkDerivation { installPhase = ''
name = "liminix-doc"; mkdir -p $out/nix-support $out/share/doc/
nativeBuildInputs = with pkgs; [ cp modules.rst options.json $out
gnumake cp -a _build/html $out/share/doc/liminix
sphinx echo "file source-dist \"$out/share/doc/liminix\"" \
fennel > $out/nix-support/hydra-build-products
luaPackages.lyaml '';
];
src = ./.;
buildPhase = ''
cat ${json} | fennel --correlate doc/parse-options.fnl > doc/modules-generated.inc.rst
cat ${json} | fennel --correlate doc/parse-options-outputs.fnl > doc/outputs-generated.inc.rst
cp ${(import ./doc/hardware.nix)} doc/hardware.rst
make -C doc html
'';
installPhase = ''
mkdir -p $out/nix-support $out/share/doc/
cd doc
cp *-generated.inc.rst hardware.rst $out
ln -s ${json} $out/options.json
cp -a _build/html $out/share/doc/liminix
echo "file source-dist \"$out/share/doc/liminix\"" \
> $out/nix-support/hydra-build-products
'';
}; };
with-unstable = (import liminix { with-unstable = (import liminix {
nixpkgs = unstable; nixpkgs = unstable;

View File

@ -1,62 +1,41 @@
{ {
deviceName ? null, device
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",
}: }:
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.6" # kernel backports needs python <3
"python-2.7.18.7"
];
};
}
);
eval = pkgs.lib.evalModules {
specialArgs = {
modulesPath = builtins.toString ./modules;
}; };
});
config = (pkgs.lib.evalModules {
modules = [ modules = [
{ _module.args = { inherit pkgs; inherit (pkgs) lim; }; } { _module.args = { inherit pkgs; lib = pkgs.lib; }; }
./modules/hardware.nix ./modules/hardware.nix
./modules/base.nix ./modules/base.nix
./modules/busybox.nix ./modules/busybox.nix
./modules/hostname.nix ./modules/hostname.nix
./modules/kernel
device.module device.module
liminix-config liminix-config
./modules/s6 ./modules/s6
./modules/users.nix ./modules/users.nix
./modules/outputs.nix ./modules/outputs.nix
{
boot.imageType = imageType;
}
]; ];
}; }).config;
config = eval.config;
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
]; ];
@ -64,12 +43,6 @@ let
in { in {
outputs = config.system.outputs // { outputs = config.system.outputs // {
default = config.system.outputs.${config.hardware.defaultOutput}; default = config.system.outputs.${config.hardware.defaultOutput};
optionsJson =
let o = import ./doc/extract-options.nix {
inherit pkgs eval;
lib = pkgs.lib;
};
in pkgs.writeText "options.json" (builtins.toJSON o);
}; };
# this is just here as a convenience, so that we can get a # this is just here as a convenience, so that we can get a
@ -86,8 +59,6 @@ in {
go-l2tp go-l2tp
min-copy-closure min-copy-closure
fennelrepl fennelrepl
lzma
lua
]; ];
}; };
} }

View File

@ -6,31 +6,17 @@
This device is based on a 64 bit Mediatek MT7622 ARM platform, This device is based on a 64 bit Mediatek MT7622 ARM platform,
and is "work in progress" in Liminix. and is "work in progress" in Liminix.
.. note:: The factory flash image contains ECC errors that make it The factory flash image contains ECC errors that make it incompatible
incompatible with Liminix: you need to use the `OpenWrt with Liminix: you need to use the `OpenWrt UBI Installer <https://github.com/dangowrt/owrt-ubi-installer>`_ to rewrite the partition layout before
UBI Installer <https://github.com/dangowrt/owrt-ubi-installer>`_ to you can flash Liminix onto it (or even use it with "tftpboot",
rewrite the partition layout before you can flash if you want the wireless to work).
Liminix onto it (or even use it with
:ref:`system-outputs-tftpboot`, if you want the wireless
to work).
Hardware summary
================
- MediaTek MT7622BV (1350MHz) - MediaTek MT7622BV (1350MHz)
- 128MB NAND flash - 128MB NAND flash
- 512MB RAM - 512MB RAM
- b/g/n wireless using MediaTek MT7622BV (MT7615E driver) - b/g/n wireless using MediaTek MT7622BV (MT7615E driver)
- a/n/ac/ax wireless using MediaTek MT7915E - a/n/ac/ax wireless using MediaTek MT7915E
'';
Installation
============
Installation is currently a manual process (you need a :ref:`serial <serial>` conection and
TFTP) following the instructions at :ref:`system-outputs-ubimage`
'';
system = { system = {
crossSystem = { crossSystem = {
@ -38,7 +24,7 @@
}; };
}; };
module = {pkgs, config, lib, lim, ... }: module = {pkgs, config, lib, ... }:
let firmware = pkgs.stdenv.mkDerivation { let firmware = pkgs.stdenv.mkDerivation {
name = "wlan-firmware"; name = "wlan-firmware";
phases = ["installPhase"]; phases = ["installPhase"];
@ -48,17 +34,12 @@
''; '';
}; };
in { in {
imports = [ imports = [ ../../modules/arch/aarch64.nix ];
../../modules/arch/aarch64.nix
../../modules/outputs/tftpboot.nix
../../modules/outputs/ubifs.nix
];
config = {
kernel = { kernel = {
src = pkgs.pkgsBuildBuild.fetchurl { src = pkgs.pkgsBuildBuild.fetchurl {
name = "linux.tar.gz"; name = "linux.tar.gz";
url = "https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.15.137.tar.gz"; url = "https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.15.71.tar.gz";
hash = "sha256-PkdzUKZ0IpBiWe/RS70J76JKnBFzRblWcKlaIFNxnHQ="; hash = "sha256-yhO2cXIeIgUxkSZf/4aAsF11uxyh+UUZu6D1h92vCD8=";
}; };
extraPatchPhase = '' extraPatchPhase = ''
${pkgs.openwrt.applyPatches.mediatek} ${pkgs.openwrt.applyPatches.mediatek}
@ -73,7 +54,7 @@
MTK_INFRACFG = "y"; MTK_INFRACFG = "y";
MTK_PMIC_WRAP = "y"; MTK_PMIC_WRAP = "y";
NVMEM_MTK_EFUSE="y"; MTK_EFUSE="y";
# MTK_HSDMA="y"; # MTK_HSDMA="y";
MTK_SCPSYS="y"; MTK_SCPSYS="y";
MTK_SCPSYS_PM_DOMAINS="y"; MTK_SCPSYS_PM_DOMAINS="y";
@ -92,6 +73,7 @@
MEDIATEK_GE_PHY = "y"; MEDIATEK_GE_PHY = "y";
# MEDIATEK_MT6577_AUXADC = "y"; # MEDIATEK_MT6577_AUXADC = "y";
# MEDIATEK_WATCHDOG = "y";
NET_MEDIATEK_SOC = "y"; NET_MEDIATEK_SOC = "y";
NET_MEDIATEK_SOC_WED = "y"; NET_MEDIATEK_SOC_WED = "y";
NET_MEDIATEK_STAR_EMAC = "y"; # this enables REGMAP_MMIO NET_MEDIATEK_STAR_EMAC = "y"; # this enables REGMAP_MMIO
@ -143,22 +125,11 @@
# SERIAL_8250_NR_UARTS="3"; # SERIAL_8250_NR_UARTS="3";
# SERIAL_8250_RUNTIME_UARTS="3"; # SERIAL_8250_RUNTIME_UARTS="3";
SERIAL_OF_PLATFORM="y"; SERIAL_OF_PLATFORM="y";
# Must enble hardware watchdog drivers. Else the device reboots after several seconds
WATCHDOG = "y";
MEDIATEK_WATCHDOG = "y";
};
conditionalConfig = {
WLAN= {
MT7615E = "m";
MT7622_WMAC = "y";
MT7915E = "m";
};
}; };
}; };
boot = { boot = {
commandLine = [ "console=ttyS0,115200" ]; commandLine = [ "console=ttyS0,115200" ];
tftp.loadAddress = lim.parseInt "0x4007ff28"; tftp.loadAddress = "0x4007ff28";
imageFormat = "fit"; imageFormat = "fit";
}; };
filesystem = filesystem =
@ -175,9 +146,12 @@
hardware = hardware =
let let
openwrt = pkgs.openwrt; openwrt = pkgs.openwrt;
mac80211 = pkgs.kmodloader.override { mac80211 = pkgs.mac80211.override {
targets = ["mt7615e" "mt7915e"]; drivers = [
inherit (config.system.outputs) kernel; "mt7615e"
"mt7915e"
];
klibBuild = config.system.outputs.kernel.modulesupport;
}; };
in { in {
ubi = { ubi = {
@ -186,14 +160,14 @@
maxLEBcount = "1024"; # guessing maxLEBcount = "1024"; # guessing
}; };
defaultOutput = "ubimage"; defaultOutput = "flashimage";
# the kernel expects this to be on a 2MB boundary. U-Boot # the kernel expects this to be on a 2MB boundary. U-Boot
# (I don't know why) has a default of 0x41080000, which isn't. # (I don't know why) has a default of 0x41080000, which isn't.
# We put it at the 32MB mark so that tftpboot can put its rootfs # We put it at the 32MB mark so that tftpboot can put its rootfs
# image and DTB underneath, but maybe this is a terrible waste of # image and DTB underneath, but maybe this is a terrible waste of
# RAM unless the kernel is able to reuse it later. Oh well # RAM unless the kernel is able to reuse it later. Oh well
loadAddress = lim.parseInt "0x42000000"; loadAddress = "0x42000000";
entryPoint = lim.parseInt "0x42000000"; entryPoint = "0x42000000";
rootDevice = "ubi0:liminix"; rootDevice = "ubi0:liminix";
dts = { dts = {
src = "${openwrt.src}/target/linux/mediatek/dts/mt7622-linksys-e8450-ubi.dts"; src = "${openwrt.src}/target/linux/mediatek/dts/mt7622-linksys-e8450-ubi.dts";
@ -203,16 +177,11 @@
]; ];
}; };
# - 0x000000000000-0x000008000000 : "spi-nand0" flash.eraseBlockSize = "65536"; # this is probably wrong
# - 0x000000000000-0x000000080000 : "bl2"
# - 0x000000080000-0x0000001c0000 : "fip"
# - 0x0000001c0000-0x0000002c0000 : "factory"
# - 0x0000002c0000-0x000000300000 : "reserved"
# - 0x000000300000-0x000008000000 : "ubi"
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"; };
@ -231,6 +200,6 @@
}; };
}; };
}; };
};
}; };
} }

View File

@ -1,62 +0,0 @@
{ config, pkgs, ... }:
{
imports = [
../../modules/outputs/jffs2.nix
];
config = {
kernel = {
src = pkgs.pkgsBuildBuild.fetchurl {
name = "linux.tar.gz";
url = "https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.15.137.tar.gz";
hash = "sha256-PkdzUKZ0IpBiWe/RS70J76JKnBFzRblWcKlaIFNxnHQ=";
};
config = {
MTD = "y";
MTD_BLOCK = "y";
MTD_CMDLINE_PARTS = "y";
MTD_PHRAM = "y";
VIRTIO_MENU = "y";
PCI = "y";
VIRTIO_PCI = "y";
BLOCK = "y";
VIRTIO_BLK = "y";
VIRTIO_NET = "y";
};
conditionalConfig = {
WLAN= {
MAC80211_HWSIM = "m";
};
};
};
hardware =
let
mac80211 = pkgs.kmodloader.override {
inherit (config.system.outputs) kernel;
targets = ["mac80211_hwsim"];
};
in {
defaultOutput = "vmroot";
rootDevice = "/dev/mtdblock0";
dts.src = pkgs.lib.mkDefault null;
flash.eraseBlockSize = 65536;
networkInterfaces =
let inherit (config.system.service.network) link;
in {
wan = link.build {
devpath = "/devices/pci0000:00/0000:00:13.0/virtio0";
ifname = "wan";
};
lan = link.build {
devpath = "/devices/pci0000:00/0000:00:14.0/virtio1";
ifname = "lan";
};
wlan_24 = link.build {
ifname = "wlan0";
dependencies = [ mac80211 ];
};
};
};
};
}

View File

@ -1,3 +1,11 @@
# I like GL.iNet devices because they're relatively accessible to
# DIY users: the serial port connections have headers preinstalled
# and don't need soldering
# Mainline linux 5.19 doesn't have device-tree support for this device
# or even for the SoC, so we use the extensive OpenWrt kernel patches
{ {
system = { system = {
crossSystem = { crossSystem = {
@ -13,9 +21,6 @@
GL.iNet GL-AR750 GL.iNet GL-AR750
**************** ****************
Hardware summary
================
The GL-AR750 "Creta" travel router features: The GL-AR750 "Creta" travel router features:
- QCA9531 @650Mhz SoC - QCA9531 @650Mhz SoC
@ -25,34 +30,29 @@
- 16MB NOR Flash - 16MB NOR Flash
- supported in OpenWrt by the "ath79" SoC family - supported in OpenWrt by the "ath79" SoC family
As with many GL.iNet devices, the stock vendor firmware
is a fork of OpenWrt, meaning that the plain binary
``firmware.bin`` that Liminix builds can be flashed using the
vendor web UI and the U-Boot emergency "unbrick" routine
The GL-AR750 has two distinct sets of wifi hardware. The 2.4GHz The GL-AR750 has two distinct sets of wifi hardware. The 2.4GHz
radio is part of the QCA9531 SoC, i.e. it's on the same silicon as radio is part of the QCA9531 SoC, i.e. it's on the same silicon as
the CPU, the Ethernet, the USB etc. The device is connected to the the CPU, the Ethernet, the USB etc. The device is connected to the
host via `AHB <https://en.wikipedia.org/wiki/Advanced_Microcontroller_Bus_Architecture>`_ and it is host via AHB, the "Advanced High-Performance Bus" and it is
supported in Linux using the ath9k driver. 5GHz wifi supported in Linux using the ath9k driver. The 5GHz support, on the
is provided by a QCA9887 PCIe (PCI embedded) WLAN chip, other hand, is provided by a QCA9887 PCIe (PCI embedded) WLAN chip:
I haven't looked closely at the router innards to see if this is
actually physically a separate board that could be unplugged, but
as far as Linux is concerned it behaves as one. This is
supported by the ath10k driver. supported by the ath10k driver.
Installation
============
As with many GL.iNet devices, the stock vendor firmware
is a fork of OpenWrt, meaning that the binary created by
:ref:`system-outputs-mtdimage` can be flashed using the
vendor web UI or the U-Boot emergency "unbrick" routine.
For flashing from an existing Liminix system (we believe that) it
is necessary to first boot into a :ref:`system-outputs-kexecboot`
system, otherwise you'll be overwriting flash partitions while
they're in use - and that might not end well.
Vendor web page: https://www.gl-inet.com/products/gl-ar750/ Vendor web page: https://www.gl-inet.com/products/gl-ar750/
OpenWrt web page: https://openwrt.org/toh/gl.inet/gl-ar750 OpenWrt web page: https://openwrt.org/toh/gl.inet/gl-ar750
''; '';
module = {pkgs, config, lim, ... }: module = {pkgs, config, ... }:
let let
openwrt = pkgs.openwrt; openwrt = pkgs.openwrt;
firmwareBlobs = pkgs.pkgsBuildBuild.fetchFromGitHub { firmwareBlobs = pkgs.pkgsBuildBuild.fetchFromGitHub {
@ -71,15 +71,14 @@
cp $blobdir/board.bin $out/ath10k/QCA9887/hw1.0/ cp $blobdir/board.bin $out/ath10k/QCA9887/hw1.0/
''; '';
}; };
mac80211 = pkgs.kmodloader.override { mac80211 = pkgs.mac80211.override {
targets = ["ath9k" "ath10k_pci"]; drivers = ["ath9k" "ath10k_pci"];
inherit (config.system.outputs) kernel; klibBuild = config.system.outputs.kernel.modulesupport;
dependencies = [ ath10k_cal_data ];
}; };
ath10k_cal_data = ath10k_cal_data =
let let
offset = lim.parseInt "0x5000"; offset = 1024 * 20; # 0x5000
size = lim.parseInt "0x844"; size = 2048 + 68; # 0x844
in pkgs.liminix.services.oneshot rec { in pkgs.liminix.services.oneshot rec {
name = "ath10k_cal_data"; name = "ath10k_cal_data";
up = '' up = ''
@ -92,26 +91,24 @@
''; '';
}; };
inherit (pkgs.pseudofile) dir symlink; inherit (pkgs.pseudofile) dir symlink;
inherit (pkgs.liminix.networking) interface;
in { in {
imports = [ imports = [
../../modules/network ../../modules/network
../../modules/arch/mipseb.nix ../../modules/arch/mipseb.nix
../../modules/outputs/tftpboot.nix
../../modules/outputs/mtdimage.nix
../../modules/outputs/jffs2.nix
]; ];
programs.busybox.options = { programs.busybox.options = {
FEATURE_DD_IBS_OBS = "y"; # ath10k_cal_data needs skip_bytes,fullblock FEATURE_DD_IBS_OBS = "y"; # ath10k_cal_data needs skip_bytes,fullblock
}; };
hardware = { hardware = {
defaultOutput = "mtdimage"; defaultOutput = "flashimage";
loadAddress = lim.parseInt "0x80060000"; loadAddress = "0x80060000";
entryPoint = lim.parseInt "0x80060000"; entryPoint = "0x80060000";
flash = { flash = {
address = lim.parseInt "0x9F060000"; address = "0x9F060000";
size = lim.parseInt "0xfa0000"; size ="0xfa0000";
eraseBlockSize = 65536; eraseBlockSize = "65536";
}; };
rootDevice = "/dev/mtdblock5"; rootDevice = "/dev/mtdblock5";
dts = { dts = {
@ -124,21 +121,15 @@
networkInterfaces = networkInterfaces =
let inherit (config.system.service.network) link; let inherit (config.system.service.network) link;
in { in {
lan = link.build { lan = link.build { ifname = "eth0"; };
ifname = "lan"; wan = link.build { ifname = "eth1"; };
devpath = "/devices/platform/ahb/1a000000.eth";
};
wan = link.build {
ifname = "wan";
devpath = "/devices/platform/ahb/19000000.eth";
};
wlan = link.build { wlan = link.build {
ifname = "wlan0"; ifname = "wlan0";
dependencies = [ mac80211 ]; dependencies = [ mac80211 ];
}; };
wlan5 = link.build { wlan5 = link.build {
ifname = "wlan1"; ifname = "wlan1";
dependencies = [ ath10k_cal_data mac80211 ]; dependencies = [ mac80211 ath10k_cal_data ];
}; };
}; };
}; };
@ -153,25 +144,17 @@
}; };
}; };
boot.tftp = { boot.tftp = {
loadAddress = lim.parseInt "0x00A00000"; loadAddress = "0x00A00000";
appendDTB = true;
}; };
kernel = { kernel = {
src = pkgs.pkgsBuildBuild.fetchurl { src = pkgs.pkgsBuildBuild.fetchurl {
name = "linux.tar.gz"; name = "linux.tar.gz";
url = "https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.15.137.tar.gz"; url = "https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.15.71.tar.gz";
hash = "sha256-PkdzUKZ0IpBiWe/RS70J76JKnBFzRblWcKlaIFNxnHQ="; hash = "sha256-yhO2cXIeIgUxkSZf/4aAsF11uxyh+UUZu6D1h92vCD8=";
}; };
# Mainline linux 5.19 doesn't have device-tree support for
# this device or even for the SoC, so we use the extensive
# OpenWrt kernel patches
extraPatchPhase = '' extraPatchPhase = ''
${openwrt.applyPatches.ath79} ${openwrt.applyPatches.ath79}
sed -i.bak -e '\,include <linux/hw_random.h>,a #include <linux/gpio/driver.h>' drivers/net/wireless/ath/ath9k/ath9k.h # context reqd for next patch
patch -p1 < ${openwrt.src}/package/kernel/mac80211/patches/ath9k/552-ath9k-ahb_of.patch
''; '';
config = { config = {
ATH79 = "y"; ATH79 = "y";
PCI = "y"; PCI = "y";
@ -220,21 +203,14 @@
WATCHDOG = "y"; WATCHDOG = "y";
ATH79_WDT = "y"; # watchdog timer ATH79_WDT = "y"; # watchdog timer
# this is all copied from nixwrt ath79 config. Clearly not all
# of it is device config, some of it is wifi config or
# installation method config or ...
EARLY_PRINTK = "y"; EARLY_PRINTK = "y";
PRINTK_TIME = "y"; PRINTK_TIME = "y";
}; };
conditionalConfig = {
WLAN = {
WLAN_VENDOR_ATH = "y";
ATH_COMMON = "m";
ATH9K = "m";
ATH9K_AHB = "y";
ATH10K = "m";
ATH10K_PCI = "m";
ATH10K_DEBUG = "y";
};
};
}; };
}; };
} }

View File

@ -13,11 +13,34 @@
description = '' description = ''
GL.iNet GL-MT300A GL.iNet GL-MT300A
***************** ********************
The GL-MT300A is based on a MT7620 chipset. The GL-MT300A is based on a MT7620 chipset.
For flashing from U-Boot, the firmware partition is from The GL.iNet pocket router range makes nice cheap hardware for
playing with Liminix or similar projects. The manufacturers seem
open to the DIY market, and the devices have a reasonable amount
of RAM and are much easier to get serial connections than many
COTS routers.
Wire up the serial connection: this probably involves opening
the box, locating the serial header pins (TX, RX and GND) and
connecting a USB TTL converter - e.g. a PL2303 based device - to
it. The defunct OpenWRT wiki has a guide with some pictures. (If
you don't have a USB TTL converter to hand, other options are
available. For example, use the GPIO pins on a Raspberry Pi.)
Run a terminal emulator such as Minicom on whatever is on the
other end of the link. I use 115200 8N1 and find it also helps
to set "Line tx delay" to 1ms, "backspace sends DEL" and
"lineWrap on".
When you turn the router on you should be greeted with some
messages from U-Boot and a little bit of ASCII art, followed by
the instruction to hit SPACE to stop autoboot. Do this and you
will get a gl-mt300a> prompt.
For flashing from uboot, the firmware partition is from
0xbc050000 to 0xbcfd0000. 0xbc050000 to 0xbcfd0000.
WiFi on this device is provided by the rt2800soc module. It WiFi on this device is provided by the rt2800soc module. It
@ -25,17 +48,6 @@
- assuming we want to use the wireless - we need to build MTD - assuming we want to use the wireless - we need to build MTD
support into the kernel even if we're using TFTP root. support into the kernel even if we're using TFTP root.
Installation
============
The stock vendor firmware is a fork of OpenWrt, meaning that the
binary created by :ref:`system-outputs-mtdimage` can be flashed
using the vendor web UI or the U-Boot emergency "unbrick" routine.
For flashing from an existing Liminix system (we think) it
is necessary to first boot into a :ref:`system-outputs-kexecboot`
system, otherwise you'll be overwriting flash partitions while
they're in use - and that might not end well.
Vendor web page: https://www.gl-inet.com/products/gl-mt300a/ Vendor web page: https://www.gl-inet.com/products/gl-mt300a/
@ -43,24 +55,20 @@
''; '';
module = { pkgs, config, lib, lim, ...}: module = { pkgs, config, lib, ...}:
let let
inherit (pkgs.liminix.networking) interface;
inherit (pkgs) openwrt; inherit (pkgs) openwrt;
mac80211 = pkgs.kmodloader.override { mac80211 = pkgs.mac80211.override {
targets = ["rt2800soc"]; drivers = ["rt2800soc"];
inherit (config.system.outputs) kernel; klibBuild = config.system.outputs.kernel.modulesupport;
}; };
in { in {
imports = [ imports = [ ../../modules/arch/mipsel.nix ];
../../modules/arch/mipsel.nix
../../modules/outputs/tftpboot.nix
../../modules/outputs/mtdimage.nix
../../modules/outputs/jffs2.nix
];
hardware = { hardware = {
defaultOutput = "mtdimage"; defaultOutput = "flashimage";
loadAddress = lim.parseInt "0x80000000"; loadAddress = "0x80000000";
entryPoint = lim.parseInt "0x80000000"; entryPoint = "0x80000000";
# Creating 5 MTD partitions on "spi0.0": # Creating 5 MTD partitions on "spi0.0":
# 0x000000000000-0x000000030000 : "u-boot" # 0x000000000000-0x000000030000 : "u-boot"
@ -73,9 +81,9 @@
# 0x000000260000-0x000000f80000 : "rootfs" # 0x000000260000-0x000000f80000 : "rootfs"
flash = { flash = {
address = lim.parseInt "0xbc050000"; address = "0xbc050000";
size = lim.parseInt "0xf80000"; size ="0xf80000";
eraseBlockSize = 65536; eraseBlockSize = "65536";
}; };
rootDevice = "/dev/mtdblock5"; rootDevice = "/dev/mtdblock5";
@ -89,6 +97,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
@ -96,11 +117,13 @@
ifname = "eth0.1"; ifname = "eth0.1";
primary = eth; primary = eth;
vid = "1"; vid = "1";
dependencies = [swconfig eth];
}; };
wan = vlan.build { wan = vlan.build {
ifname = "eth0.2"; ifname = "eth0.2";
primary = eth; primary = eth;
vid = "2"; vid = "2";
dependencies = [swconfig eth];
}; };
wlan = link.build { wlan = link.build {
ifname = "wlan0"; ifname = "wlan0";
@ -109,19 +132,17 @@
}; };
}; };
boot.tftp = { boot.tftp = {
loadAddress = lim.parseInt "0x00A00000"; loadAddress = "0x00A00000";
appendDTB = true; };
};
kernel = { kernel = {
src = pkgs.fetchurl { src = pkgs.fetchurl {
name = "linux.tar.gz"; name = "linux.tar.gz";
url = "https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.15.137.tar.gz"; url = "https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.15.71.tar.gz";
hash = "sha256-PkdzUKZ0IpBiWe/RS70J76JKnBFzRblWcKlaIFNxnHQ="; hash = "sha256-yhO2cXIeIgUxkSZf/4aAsF11uxyh+UUZu6D1h92vCD8=";
}; };
extraPatchPhase = '' extraPatchPhase = ''
${openwrt.applyPatches.ramips} ${openwrt.applyPatches.ramips}
${openwrt.applyPatches.rt2x00}
''; '';
config = { config = {
@ -164,14 +185,6 @@
} // lib.optionalAttrs (config.system.service ? vlan) { } // lib.optionalAttrs (config.system.service ? vlan) {
SWCONFIG = "y"; SWCONFIG = "y";
}; };
conditionalConfig = {
WLAN = {
WLAN_VENDOR_RALINK = "y";
RT2800SOC = "m";
RT2X00 = "m";
};
};
}; };
}; };
} }

View File

@ -13,22 +13,10 @@
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 MT300A, but is
based on the MT7628 chipset instead of MT7620. It's also marginally cheaper based on MT7628 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. It's different again
v2 not v1, which is a different animal and has only half as much RAM. to the v1, which has only half the RAM.
Installation
============
The stock vendor firmware is a fork of OpenWrt, meaning that the
binary created by :ref:`system-outputs-mtdimage` can be flashed
using the vendor web UI or the U-Boot emergency "unbrick" routine.
For flashing from an existing Liminix system (we think) it
is necessary to first boot into a :ref:`system-outputs-kexecboot`
system, otherwise you'll be overwriting flash partitions while
they're in use - and that might not end well.
Vendor web page: https://www.gl-inet.com/products/gl-mt300n-v2/ Vendor web page: https://www.gl-inet.com/products/gl-mt300n-v2/
@ -36,27 +24,23 @@
''; '';
module = { pkgs, config, lib, lim, ...}: module = { pkgs, config, lib, ...}:
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;
mac80211 = pkgs.kmodloader.override { mac80211 = pkgs.mac80211.override {
targets = ["mt7603e"]; drivers = ["mt7603e"];
inherit (config.system.outputs) kernel; klibBuild = config.system.outputs.kernel.modulesupport;
}; };
wlan_firmware = pkgs.fetchurl { wlan_firmware = pkgs.fetchurl {
url = "https://github.com/openwrt/mt76/raw/f24b56f935392ca1d35fae5fd6e56ef9deda4aad/firmware/mt7628_e2.bin"; url = "https://github.com/openwrt/mt76/raw/f24b56f935392ca1d35fae5fd6e56ef9deda4aad/firmware/mt7628_e2.bin";
hash = "sha256:1dkhfznmdz6s50kwc841x3wj0h6zg6icg5g2bim9pvg66as2vmh9"; hash = "sha256:1dkhfznmdz6s50kwc841x3wj0h6zg6icg5g2bim9pvg66as2vmh9";
}; };
in { in {
imports = [ imports = [ ../../modules/arch/mipsel.nix ];
../../modules/arch/mipsel.nix
../../modules/outputs/tftpboot.nix
../../modules/outputs/mtdimage.nix
../../modules/outputs/jffs2.nix
];
filesystem = dir { filesystem = dir {
lib = dir { lib = dir {
firmware = dir { firmware = dir {
@ -65,14 +49,14 @@
}; };
}; };
hardware = { hardware = {
defaultOutput = "mtdimage"; defaultOutput = "flashimage";
loadAddress = lim.parseInt "0x80000000"; loadAddress = "0x80000000";
entryPoint = lim.parseInt "0x80000000"; entryPoint = "0x80000000";
flash = { flash = {
address = lim.parseInt "0xbc050000"; address = "0xbc050000";
size = lim.parseInt "0xfb0000"; size = "0xfb0000";
eraseBlockSize = 65536; eraseBlockSize = "65536";
}; };
rootDevice = "/dev/mtdblock5"; rootDevice = "/dev/mtdblock5";
@ -96,7 +80,7 @@
swconfig dev switch0 vlan 2 set ports '0 6t' swconfig dev switch0 vlan 2 set ports '0 6t'
swconfig dev switch0 set apply swconfig dev switch0 set apply
''; '';
down = "${pkgs.swconfig}/bin/swconfig dev switch0 set reset"; down = "swconfig dev switch0 set reset";
}; };
in rec { in rec {
eth = link.build { ifname = "eth0"; dependencies = [swconfig]; }; eth = link.build { ifname = "eth0"; dependencies = [swconfig]; };
@ -120,15 +104,14 @@
boot.tftp = { boot.tftp = {
# 20MB seems to give enough room to uncompress the kernel # 20MB seems to give enough room to uncompress the kernel
# without anything getting trodden on. 10MB was too small # without anything getting trodden on. 10MB was too small
loadAddress = lim.parseInt "0x1400000"; loadAddress = "0x1400000";
appendDTB = true;
}; };
kernel = { kernel = {
src = pkgs.fetchurl { src = pkgs.fetchurl {
name = "linux.tar.gz"; name = "linux.tar.gz";
url = "https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.15.137.tar.gz"; url = "https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.15.71.tar.gz";
hash = "sha256-PkdzUKZ0IpBiWe/RS70J76JKnBFzRblWcKlaIFNxnHQ="; hash = "sha256-yhO2cXIeIgUxkSZf/4aAsF11uxyh+UUZu6D1h92vCD8=";
}; };
extraPatchPhase = '' extraPatchPhase = ''
${openwrt.applyPatches.ramips} ${openwrt.applyPatches.ramips}
@ -185,15 +168,6 @@
RALINK_WDT = "y"; # watchdog RALINK_WDT = "y"; # watchdog
MT7621_WDT = "y"; # or it might be this one MT7621_WDT = "y"; # or it might be this one
}; };
conditionalConfig = {
WLAN = {
WLAN_VENDOR_RALINK = "y";
WLAN_VENDOR_MEDIATEK = "y";
MT7603E = "m";
};
};
}; };
}; };
} }

View File

@ -9,33 +9,34 @@
}; };
}; };
description = ''
QEMU Aarch64
************
This target produces an image for
the `QEMU "virt" platform <https://www.qemu.org/docs/master/system/arm/virt.html>`_ using a 64 bit CPU type.
ARM targets differ from MIPS in that the kernel format expected
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
documentation for the :ref:`qemu` target for more information.
'';
# this device is described by the "qemu" device # this device is described by the "qemu" device
installer = "vmroot"; description = "";
module = { config, lim, ... }: {
imports = [ module = {pkgs, config, ... }: {
../../modules/arch/aarch64.nix imports = [ ../../modules/arch/aarch64.nix ];
../families/qemu.nix
];
kernel = { kernel = {
src = pkgs.pkgsBuildBuild.fetchurl {
name = "linux.tar.gz";
url = "https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.15.71.tar.gz";
hash = "sha256-yhO2cXIeIgUxkSZf/4aAsF11uxyh+UUZu6D1h92vCD8=";
};
config = { config = {
VIRTUALIZATION = "y"; VIRTUALIZATION = "y";
PCI_HOST_GENERIC="y"; PCI_HOST_GENERIC="y";
MTD = "y";
MTD_BLOCK2MTD = "y";
MTD_BLOCK = "y";
VIRTIO_MENU = "y";
PCI = "y";
VIRTIO_PCI = "y";
BLOCK = "y";
VIRTIO_BLK = "y";
VIRTIO_NET = "y";
SERIAL_EARLYCON_ARM_SEMIHOST = "y"; # earlycon=smh
SERIAL_AMBA_PL011 = "y"; SERIAL_AMBA_PL011 = "y";
SERIAL_AMBA_PL011_CONSOLE = "y"; SERIAL_AMBA_PL011_CONSOLE = "y";
}; };
@ -43,9 +44,31 @@
boot.commandLine = [ boot.commandLine = [
"console=ttyAMA0,38400" "console=ttyAMA0,38400"
]; ];
hardware = let addr = lim.parseInt "0x40010000"; in { hardware =
loadAddress = addr; let
entryPoint = addr; mac80211 = pkgs.mac80211.override {
}; drivers = ["mac80211_hwsim"];
klibBuild = config.system.outputs.kernel.modulesupport;
};
in {
defaultOutput = "vmroot";
loadAddress = "0x0";
entryPoint = "0x0";
rootDevice = "/dev/mtdblock0";
flash.eraseBlockSize = "65536"; # c.f. pkgs/mips-vm/mips-vm.sh
networkInterfaces =
let inherit (config.system.service.network) link;
in {
wan = link.build { ifname = "eth0"; };
lan = link.build { ifname = "eth1"; };
wlan_24 = link.build {
ifname = "wlan0";
dependencies = [ mac80211 ];
};
};
};
}; };
} }

View File

@ -1,53 +0,0 @@
# This "device" generates images that can be used with the QEMU
# emulator. The default output is a directory containing separate
# kernel ("Image" format) and root filesystem (squashfs or jffs2)
# images
{
system = {
crossSystem = {
config = "armv7l-unknown-linux-musleabihf";
};
};
# this device is described by the "qemu" device
description = ''
QEMU ARM v7
***********
This target produces an image for
the `QEMU "virt" platform <https://www.qemu.org/docs/master/system/arm/virt.html>`_ using a 32 bit CPU type.
ARM targets differ from MIPS in that the kernel format expected
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
documentation for the :ref:`QEMU` (MIPS) target for more information.
'';
installer = "vmroot";
module = { config, lim, ... }: {
imports = [
../../modules/arch/arm.nix
../families/qemu.nix
];
kernel = {
config = {
PCI_HOST_GENERIC = "y";
ARCH_VIRT = "y";
VFP = "y";
NEON = "y";
AEABI = "y";
SERIAL_AMBA_PL011 = "y";
SERIAL_AMBA_PL011_CONSOLE = "y";
};
};
boot.commandLine = [
"console=ttyAMA0"
];
hardware = let addr = lim.parseInt "0x40008000"; in {
loadAddress = addr;
entryPoint = addr;
};
};
}

View File

@ -13,21 +13,24 @@
}; };
description = '' description = ''
QEMU MIPS QEMU
********* ****
This target produces an image for This is not a hardware device. This target produces an image for
QEMU, the "generic and open source machine emulator and QEMU, the "generic and open source machine emulator and
virtualizer". virtualizer".
Liminix can build QEMU for both MIPS (:code:`qemu` device) and Aarch64 (:code:`qemu-aarch64` device)
MIPS QEMU emulates a "Malta" board, which was an ATX form factor MIPS QEMU emulates a "Malta" board, which was an ATX form factor
evaluation board made by MIPS Technologies, but mostly in Liminix evaluation board made by MIPS Technologies, but mostly in Liminix
we use paravirtualized devices (Virtio) instead of emulating we use paravirtualized devices (Virtio) instead of emulating
hardware. hardware. For Aarch64 we use the QEMU "virt" board.
Building an image for QEMU results in a :file:`result/` directory Building an image for QEMU results in a :file:`result/` directory
containing ``run.sh`` ``vmlinux``, and ``rootfs`` files. To invoke containing ``run.sh`` ``vmlinux``, ``rootfs`` and possibly
the emulator, run ``run.sh``. (architecture-dependent) ``Image``. To invoke the emulator,
run ``run.sh``.
The configuration includes two emulated "hardware" ethernet The configuration includes two emulated "hardware" ethernet
devices and the kernel :code:`mac80211_hwsim` module to devices and the kernel :code:`mac80211_hwsim` module to
@ -36,41 +39,55 @@
in the Development manual. in the Development manual.
''; '';
module = { config, lib, lim, ... }: { module = {pkgs, config, ... }: {
imports = [ imports = [ ../../modules/arch/mipseb.nix ];
../../modules/arch/mipseb.nix
../families/qemu.nix
];
kernel = { kernel = {
src = pkgs.pkgsBuildBuild.fetchurl {
name = "linux.tar.gz";
url = "https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.15.71.tar.gz";
hash = "sha256-yhO2cXIeIgUxkSZf/4aAsF11uxyh+UUZu6D1h92vCD8=";
};
config = { config = {
MIPS_MALTA= "y"; MIPS_MALTA= "y";
CPU_MIPS32_R2= "y"; CPU_MIPS32_R2= "y";
POWER_RESET = "y"; MTD = "y";
POWER_RESET_SYSCON = "y"; MTD_BLOCK2MTD = "y";
MTD_BLOCK = "y";
VIRTIO_MENU = "y";
PCI = "y";
VIRTIO_PCI = "y";
BLOCK = "y";
VIRTIO_BLK = "y";
VIRTIO_NET = "y";
SERIAL_8250= "y"; SERIAL_8250= "y";
SERIAL_8250_CONSOLE= "y"; SERIAL_8250_CONSOLE= "y";
}; };
}; };
hardware = hardware =
# from arch/mips/mti-malta/Platform:load-$(CONFIG_MIPS_MALTA) += 0xffffffff80100000 let
let addr = lim.parseInt "0x80100000"; mac80211 = pkgs.mac80211.override {
in { drivers = ["mac80211_hwsim"];
loadAddress = addr; klibBuild = config.system.outputs.kernel.modulesupport;
entryPoint = addr;
# Unlike the arm qemu targets, we need a static dts when
# running u-boot-using tests, qemu dumpdtb command doesn't
# work for this board. I am not at all sure this dts is
# *correct* but it does at least boot
dts = lib.mkForce {
src = "${config.system.outputs.kernel.modulesupport}/arch/mips/boot/dts/mti/malta.dts";
includes = [
"${config.system.outputs.kernel.modulesupport}/arch/mips/boot/dts/"
];
}; };
in {
defaultOutput = "vmroot";
rootDevice = "/dev/mtdblock0";
flash.eraseBlockSize = "65536"; # c.f. pkgs/run-liminix-vm/run-liminix-vm.sh
networkInterfaces =
let inherit (config.system.service.network) link;
in {
wan = link.build { ifname = "eth0"; };
lan = link.build { ifname = "eth1"; };
wlan_24 = link.build {
ifname = "wlan0";
dependencies = [ mac80211 ];
};
};
}; };
}; };
} }

View File

@ -1,441 +0,0 @@
{
description = ''
TP-Link Archer AX23 / AX1800 Dual Band Wi-Fi 6 Router
*****************************************************
Hardware summary
================
- MediaTek MT7621 (880MHz)
- 16MB Flash
- 128MB RAM
- WLan hardware: Mediatek MT7905, MT7975
Limitations
===========
Status LEDs do not work yet.
Uploading an image via tftp doesn't work yet, because the Archer uboot
version is so old it doesn't support overriding the DTB from the mboot
command. The tftpboot module doesn't support this yet, see
https://gti.telent.net/dan/liminix/pulls/5 for the WiP.
'';
system = {
crossSystem = {
config = "mipsel-unknown-linux-musl";
gcc = {
abi = "32";
# https://openwrt.org/docs/techref/instructionset/mipsel_24kc
arch = "24kc";
};
};
};
module = {pkgs, config, lib, lim, ... }:
let firmware = pkgs.stdenv.mkDerivation {
name = "wlan-firmware";
phases = ["installPhase"];
installPhase = ''
mkdir $out
cp ${pkgs.linux-firmware}/lib/firmware/mediatek/{mt7915,mt7615,mt7622}* $out
'';
};
in {
imports = [
../../modules/arch/mipsel.nix
../../modules/outputs/tftpboot.nix
../../modules/outputs/tplink-safeloader.nix
];
config = {
kernel = {
src = pkgs.pkgsBuildBuild.fetchurl {
name = "linux.tar.gz";
url = "https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.15.137.tar.gz";
hash = "sha256-PkdzUKZ0IpBiWe/RS70J76JKnBFzRblWcKlaIFNxnHQ=";
};
extraPatchPhase = ''
${pkgs.openwrt.applyPatches.ramips}
'';
config = {
# Initially taken from openwrt's ./target/linux/ramips/mt7621/config-5.15,
# then tweaked here and there
ARCH_32BIT_OFF_T="y";
ARCH_HIBERNATION_POSSIBLE="y";
ARCH_KEEP_MEMBLOCK="y";
ARCH_MMAP_RND_BITS_MAX="15";
ARCH_MMAP_RND_COMPAT_BITS_MAX="15";
ARCH_SUSPEND_POSSIBLE="y";
AT803X_PHY="y";
BLK_MQ_PCI="y";
BOARD_SCACHE="y";
CEVT_R4K="y";
CLKSRC_MIPS_GIC="y";
CLK_MT7621="y";
CLOCKSOURCE_WATCHDOG="y";
CLONE_BACKWARDS="y";
CMDLINE_BOOL="y";
COMMON_CLK="y";
COMPAT_32BIT_TIME="y";
CPU_GENERIC_DUMP_TLB="y";
CPU_HAS_DIEI="y";
CPU_HAS_PREFETCH="y";
CPU_HAS_RIXI="y";
CPU_HAS_SYNC="y";
CPU_LITTLE_ENDIAN="y";
CPU_MIPS32="y";
CPU_MIPS32_R2="y";
CPU_MIPSR2="y";
CPU_MIPSR2_IRQ_EI="y";
CPU_MIPSR2_IRQ_VI="y";
CPU_NEEDS_NO_SMARTMIPS_OR_MICROMIPS="y";
CPU_R4K_CACHE_TLB="y";
CPU_RMAP="y";
CPU_SUPPORTS_32BIT_KERNEL="y";
CPU_SUPPORTS_HIGHMEM="y";
CPU_SUPPORTS_MSA="y";
CRC16="y";
CRYPTO_DEFLATE="y";
CRYPTO_HASH_INFO="y";
CRYPTO_LIB_BLAKE2S_GENERIC="y";
CRYPTO_LIB_POLY1305_RSIZE="2";
CRYPTO_LZO="y";
CRYPTO_ZSTD="y";
CSRC_R4K="y";
DIMLIB="y";
DMA_NONCOHERENT="y";
DTB_RT_NONE="y";
DTC="y";
EARLY_PRINTK="y";
FIXED_PHY="y";
FWNODE_MDIO="y";
FW_LOADER_PAGED_BUF="y";
GENERIC_ATOMIC64="y";
GENERIC_CLOCKEVENTS="y";
GENERIC_CMOS_UPDATE="y";
GENERIC_CPU_AUTOPROBE="y";
GENERIC_FIND_FIRST_BIT="y";
GENERIC_GETTIMEOFDAY="y";
GENERIC_IOMAP="y";
GENERIC_IRQ_CHIP="y";
GENERIC_IRQ_EFFECTIVE_AFF_MASK="y";
GENERIC_IRQ_SHOW="y";
GENERIC_LIB_ASHLDI3="y";
GENERIC_LIB_ASHRDI3="y";
GENERIC_LIB_CMPDI2="y";
GENERIC_LIB_LSHRDI3="y";
GENERIC_LIB_UCMPDI2="y";
GENERIC_PCI_IOMAP="y";
GENERIC_PHY="y";
GENERIC_PINCONF="y";
GENERIC_SCHED_CLOCK="y";
GENERIC_SMP_IDLE_THREAD="y";
GENERIC_TIME_VSYSCALL="y";
GLOB="y";
GPIOLIB_IRQCHIP="y";
GPIO_CDEV="y";
GPIO_GENERIC="y";
GPIO_MT7621="y";
GRO_CELLS="y";
HANDLE_DOMAIN_IRQ="y";
HARDWARE_WATCHPOINTS="y";
HAS_DMA="y";
HAS_IOMEM="y";
HAS_IOPORT_MAP="y";
I2C="y";
I2C_ALGOBIT="y";
I2C_BOARDINFO="y";
I2C_CHARDEV="y";
I2C_GPIO="y";
I2C_MT7621="y";
ICPLUS_PHY="y";
IRQCHIP="y";
IRQ_DOMAIN="y";
IRQ_DOMAIN_HIERARCHY="y";
IRQ_FORCED_THREADING="y";
IRQ_MIPS_CPU="y";
IRQ_WORK="y";
LIBFDT="y";
LOCK_DEBUGGING_SUPPORT="y";
LZO_COMPRESS="y";
LZO_DECOMPRESS="y";
MDIO_BUS="y";
MDIO_DEVICE="y";
MDIO_DEVRES="y";
MEDIATEK_GE_PHY="y";
MEMFD_CREATE="y";
MFD_SYSCON="y";
MIGRATION="y";
MIKROTIK="y";
MIKROTIK_RB_SYSFS="y";
MIPS="y";
MIPS_ASID_BITS="8";
MIPS_ASID_SHIFT="0";
MIPS_CLOCK_VSYSCALL="y";
MIPS_CM="y";
MIPS_CPC="y";
MIPS_CPS="y";
MIPS_CPU_SCACHE="y";
MIPS_GIC="y";
MIPS_L1_CACHE_SHIFT="5";
MIPS_LD_CAN_LINK_VDSO="y";
MIPS_MT="y";
MIPS_MT_FPAFF="y";
MIPS_MT_SMP="y";
MIPS_NR_CPU_NR_MAP="4";
MIPS_PERF_SHARED_TC_COUNTERS="y";
MIPS_SPRAM="y";
MODULES_USE_ELF_REL="y";
MTD_CMDLINE_PARTS="y";
MTD_NAND_CORE="y";
MTD_NAND_ECC="y";
MTD_NAND_ECC_SW_HAMMING="y";
MTD_NAND_MT7621="y";
MTD_NAND_MTK_BMT="y";
MTD_RAW_NAND="y";
MTD_ROUTERBOOT_PARTS="y";
MTD_SERCOMM_PARTS="y";
MTD_SPI_NOR="y";
MTD_SPLIT_FIT_FW="y";
MTD_SPLIT_MINOR_FW="y";
MTD_SPLIT_SEAMA_FW="y";
MTD_SPLIT_TPLINK_FW="y";
MTD_SPLIT_TRX_FW="y";
MTD_SPLIT_UIMAGE_FW="y";
MTD_UBI="y";
MTD_UBI_BEB_LIMIT="20";
MTD_UBI_BLOCK="y";
MTD_UBI_WL_THRESHOLD="4096";
MTD_VIRT_CONCAT="y";
NEED_DMA_MAP_STATE="y";
NET_DEVLINK="y";
NET_DSA="y";
NET_DSA_MT7530="y";
NET_DSA_MT7530_MDIO="y";
NET_DSA_TAG_MTK="y";
NET_FLOW_LIMIT="y";
NET_MEDIATEK_SOC="y";
NET_SELFTESTS="y";
NET_SWITCHDEV="y";
NET_VENDOR_MEDIATEK="y";
NO_HZ_COMMON="y";
NO_HZ_IDLE="y";
NR_CPUS="4";
NVMEM="y";
OF="y";
OF_ADDRESS="y";
OF_EARLY_FLATTREE="y";
OF_FLATTREE="y";
OF_GPIO="y";
OF_IRQ="y";
OF_KOBJ="y";
OF_MDIO="y";
PAGE_POOL="y";
PAGE_POOL_STATS="y";
PCI="y";
PCIE_MT7621="y";
PCI_DISABLE_COMMON_QUIRKS="y";
PCI_DOMAINS="y";
PCI_DOMAINS_GENERIC="y";
PCI_DRIVERS_GENERIC="y";
PCS_MTK_LYNXI="y";
PERF_USE_VMALLOC="y";
PGTABLE_LEVELS="2";
PHYLIB="y";
PHYLINK="y";
PHY_MT7621_PCI="y";
PINCTRL="y";
PINCTRL_AW9523="y";
PINCTRL_MT7621="y";
PINCTRL_RALINK="y";
PINCTRL_SX150X="y";
POWER_RESET="y";
POWER_RESET_GPIO="y";
POWER_SUPPLY="y";
PTP_1588_CLOCK_OPTIONAL="y";
QUEUED_RWLOCKS="y";
QUEUED_SPINLOCKS="y";
RALINK="y";
RATIONAL="y";
REGMAP="y";
REGMAP_I2C="y";
REGMAP_MMIO="y";
REGULATOR="y";
REGULATOR_FIXED_VOLTAGE="y";
RESET_CONTROLLER="y";
RFS_ACCEL="y";
RPS="y";
RTC_CLASS="y";
RTC_DRV_BQ32K="y";
RTC_DRV_PCF8563="y";
RTC_I2C_AND_SPI="y";
SCHED_SMT="y";
SERIAL_8250="y";
SERIAL_8250_CONSOLE="y";
SERIAL_8250_NR_UARTS="3";
SERIAL_8250_RUNTIME_UARTS="3";
SERIAL_MCTRL_GPIO="y";
SERIAL_OF_PLATFORM="y";
SGL_ALLOC="y";
SMP="y";
SMP_UP="y";
SOCK_RX_QUEUE_MAPPING="y";
SOC_BUS="y";
SOC_MT7621="y";
SPI="y";
SPI_MASTER="y";
SPI_MEM="y";
SPI_MT7621="y";
SRCU="y";
SWPHY="y";
SYNC_R4K="y";
SYSCTL_EXCEPTION_TRACE="y";
SYS_HAS_CPU_MIPS32_R1="y";
SYS_HAS_CPU_MIPS32_R2="y";
SYS_HAS_EARLY_PRINTK="y";
SYS_SUPPORTS_32BIT_KERNEL="y";
SYS_SUPPORTS_ARBIT_HZ="y";
SYS_SUPPORTS_HIGHMEM="y";
SYS_SUPPORTS_HOTPLUG_CPU="y";
SYS_SUPPORTS_LITTLE_ENDIAN="y";
SYS_SUPPORTS_MIPS16="y";
SYS_SUPPORTS_MIPS_CPS="y";
SYS_SUPPORTS_MULTITHREADING="y";
SYS_SUPPORTS_SCHED_SMT="y";
SYS_SUPPORTS_SMP="y";
SYS_SUPPORTS_ZBOOT="y";
TARGET_ISA_REV="2";
TICK_CPU_ACCOUNTING="y";
TIMER_OF="y";
TIMER_PROBE="y";
TREE_RCU="y";
TREE_SRCU="y";
UBIFS_FS="y";
USB_SUPPORT="y";
USE_OF="y";
WEAK_ORDERING="y";
XPS="y";
XXHASH="y";
ZLIB_DEFLATE="y";
ZLIB_INFLATE="y";
ZSTD_COMPRESS="y";
ZSTD_DECOMPRESS="y";
} // lib.optionalAttrs (config.system.service ? watchdog) {
RALINK_WDT = "y"; # watchdog
MT7621_WDT = "y"; # or it might be this one
};
conditionalConfig = {
WLAN = {
MT7915E = "m";
};
};
};
tplink-safeloader.board = "ARCHER-AX23-V1";
boot = {
commandLine = [ "console=ttyS0,115200" ];
tftp = {
# Should be a segment of free RAM, where the tftp artifact
# can be stored before unpacking it to the 'hardware.loadAddress'
# The 'hardware.loadAddress' is 0x80001000, which suggests the
# RAM would start at 0x8000000 and (being 128MB) go to
# to 0x8800000. Let's put it at the 100MB mark at
# 0x8000000+0x0640000=0x86400000
loadAddress = lim.parseInt "0x86400000";
};
};
filesystem =
let inherit (pkgs.pseudofile) dir symlink;
in
dir {
lib = dir {
firmware = dir {
mediatek = symlink firmware;
};
};
};
hardware =
let
openwrt = pkgs.openwrt;
mac80211 = pkgs.kmodloader.override {
targets = [
"mt7915e"
];
inherit (config.system.outputs) kernel;
};
in {
# from OEM bootlog (openwrt wiki):
# 4 cmdlinepart partitions found on MTD device raspi
# Creating 4 MTD partitions on "raspi":
# 0x000000000000-0x000000040000 : "uboot"
# 0x000000040000-0x000000440000 : "uImage"
# 0x000000440000-0x000000ff0000 : "rootfs"
# 0x000000ff0000-0x000001000000 : "ART"
# from openwrt bootlog (openwrt wiki):
# 5 fixed-partitions partitions found on MTD device spi0.0
# OF: Bad cell count for /palmbus@1e000000/spi@b00/flash@0/partitions
# OF: Bad cell count for /palmbus@1e000000/spi@b00/flash@0/partitions
# OF: Bad cell count for /palmbus@1e000000/spi@b00/flash@0/partitions
# OF: Bad cell count for /palmbus@1e000000/spi@b00/flash@0/partitions
# Creating 5 MTD partitions on "spi0.0":
# 0x000000000000-0x000000040000 : "u-boot"
# 0x000000040000-0x000000fa0000 : "firmware"
# 2 uimage-fw partitions found on MTD device firmware
# Creating 2 MTD partitions on "firmware":
# 0x000000000000-0x0000002c0000 : "kernel"
# 0x0000002c0000-0x000000f60000 : "rootfs"
# mtd: setting mtd3 (rootfs) as root device
# 1 squashfs-split partitions found on MTD device rootfs
# 0x000000640000-0x000000f60000 : "rootfs_data"
# 0x000000fa0000-0x000000fb0000 : "config"
# 0x000000fb0000-0x000000ff0000 : "tplink"
# 0x000000ff0000-0x000001000000 : "radio"
flash = {
# from the OEM bootlog 'Booting image at bc040000'
# (0x40000 from 0xbc000000)
address = lim.parseInt "0xbc040000";
# 0x000000040000-0x000000fa0000
size = lim.parseInt "0xf60000";
# TODO: find in /proc/mtd on a running system
eraseBlockSize = 65536;
};
# since this is mentioned in the partition table as well?
defaultOutput = "tplink-safeloader";
# taken from openwrt sysupgrade image:
# openwrt-23.05.2-ramips-mt7621-tplink_archer-ax23-v1-squashfs-sysupgrade.bin: u-boot legacy uImage, MIPS OpenWrt Linux-5.15.137, Linux/MIPS, OS Kernel Image (lzma), 2797386 bytes, Tue Nov 14 13:38:11 2023, Load Address: 0X80001000, Entry Point: 0X80001000, Header CRC: 0X19F74C5B, Data CRC: 0XF685563C
loadAddress = lim.parseInt "0x80001000";
entryPoint = lim.parseInt "0x80001000";
rootDevice = "/dev/mtdblock3";
dts = {
src = "${openwrt.src}/target/linux/ramips/dts/mt7621_tplink_archer-ax23-v1.dts";
includes = [
"${openwrt.src}/target/linux/ramips/dts"
"${config.system.outputs.kernel.modulesupport}/arch/arm64/boot/dts/mediatek/"
];
};
networkInterfaces =
let
inherit (config.system.service.network) link;
in rec {
lan1 = link.build { ifname = "lan1"; };
lan2 = link.build { ifname = "lan2"; };
lan3 = link.build { ifname = "lan3"; };
lan4 = link.build { ifname = "lan4"; };
wan = link.build { ifname = "wan"; };
wlan = link.build {
ifname = "wlan0";
dependencies = [ mac80211 ];
};
wlan5 = link.build {
ifname = "wlan1";
dependencies = [ mac80211 ];
};
};
};
};
};
}

View File

@ -2,149 +2,6 @@
description = '' description = ''
Turris Omnia Turris Omnia
************ ************
This is a 32 bit ARMv7 MVEBU device, which is usually shipped with
TurrisOS, an OpenWrt-based system. Rather than reformatting the
builtin storage, we install Liminix on to the existing btrfs
filesystem so that the vendor snapshot/recovery system continues
to work (and provides you an easy rollback if you decide you don't
like Liminix after all).
The install process has two stages, and is intended that you
should not need to open the device and add a serial console
(although it may be handy for visibility, and in case anything
goes wrong). First we build a minimal installation/recovery
system, then we reboot into that recovery image to prepare the
device for the full target install.
Installation using a USB stick
==============================
First, build the image for the USB stick. Review
:file:`examples/recovery.nix` in order to change the default
root password (which is ``secret``) and/or the SSH keys, then
build it with
.. code-block:: console
$ nix-build -I liminix-config=./examples/recovery.nix \
--arg device "import ./devices/turris-omnia" \
-A outputs.mbrimage -o mbrimage
$ file -L mbrimage
mbrimage: DOS/MBR boot sector; partition 1 : ID=0x83, active, start-CHS (0x0,0,5), end-CHS (0x6,130,26), startsector 4, 104602 sectors
Next, copy the image from your build machine to a USB storage
medium using :command:`dd` or your other most favoured file copying
tool, which might be a comand something like this:
.. code-block:: console
$ dd if=mbrimage of=/dev/path/to/the/usb/stick \
bs=1M conv=fdatasync status=progress
The Omnia's default boot order only checks USB after it has failed
to boot from eMMC, which is not ideal for our purpose. Unless you
have a serial cable, the easiest way to change this is by booting
to TurrisOS and logging in with ssh:
.. code-block:: console
root@turris:/# fw_printenv boot_targets
boot_targets=mmc0 nvme0 scsi0 usb0 pxe dhcp
root@turris:/# fw_setenv boot_targets usb0 mmc0
root@turris:/# fw_printenv boot_targets
boot_targets=usb0 mmc0
root@turris:/# reboot -f
It should now boot into the recovery image. It expects a network
cable to be plugged into LAN2 with something on the other end of
it that serves DHCP requests. Check your DHCP server logs for a
request from a ``liminix-recovery`` host and figure out what IP
address was assigned.
.. code-block:: console
$ ssh liminix-recovery.lan
You should get a "Busybox" banner and a root prompt. Now you can
start preparing the device to install Liminix on it. First we'll
mount the root filesystem and take a snapshot:
.. code-block:: console
# mkdir /dest && mount /dev/mmcblk0p1 /dest
# schnapps -d /dest create "pre liminix"
# schnapps -d /dest list
ERROR: not a valid btrfs filesystem: /
# | Type | Size | Date | Description
------+-----------+-------------+---------------------------+------------------------------------
1 | single | 16.00KiB | 1970-01-01 00:11:49 +0000 | pre liminix
(``not a valid btrfs filesystem: /`` is not a real error)
then we can remove all the files
.. code-block:: console
# rm -r /dest/@/*
and then it's ready to install the real Liminix system onto. On
your build system, create the Liminix configuration you wish to
install: here we'll use the ``rotuer`` example.
.. code-block:: console
build$ nix-build -I liminix-config=./examples/rotuer.nix \
--arg device "import ./devices/turris-omnia" \
-A outputs.systemConfiguration
and then use :command:`min-copy-closure` to copy it to the device.
.. code-block:: console
build$ nix-shell --run \
"min-copy-closure -r /dest/@ root@liminix-recovery.lan result"
and activate it
.. code-block:: console
build$ ssh root@liminix-recovery.lan \
"/dest/@/$(readlink result)/bin/install /dest/@"
The final steps are performed directly on the device again: add
a symlink so U-Boot can find :file:`/boot`, then restore the
default boot order and reboot into the new configuration.
.. code-block:: console
# cd /dest && ln -s @/boot .
# fw_setenv boot_targets "mmc0 nvme0 scsi0 usb0 pxe dhcp"
# cd / ; umount /dest
# reboot
Installation using a TFTP server and serial console
===================================================
If you have a :ref:`serial` console connection and a TFTP server,
and would rather use them than fiddling with USB sticks, the
:file:`examples/recovery.nix` configuration also works
using the ``tftpboot`` output. So you can do
.. code-block:: console
build$ nix-build -I liminix-config=./examples/recovery.nix \
--arg device "import ./devices/turris-omnia" \
-A outputs.tftpboot
and then paste the generated :file:`result/boot.scr` into
U-Boot, and you will end up with the same system as you would
have had after booting from USB. If you don't have a serial
console connection you could probably even get clever with
elaborate use of :command:`fw_setenv`, but that is left as
an exercise for the reader.
''; '';
system = { system = {
@ -153,257 +10,102 @@
}; };
}; };
module = {pkgs, config, lib, lim, ... }: module = {pkgs, config, lib, ... }:
let let openwrt = pkgs.openwrt; in {
inherit (pkgs.liminix.services) oneshot; imports = [ ../../modules/arch/arm.nix ];
inherit (pkgs) liminix; kernel = {
mtd_by_name_links = pkgs.liminix.services.oneshot rec { src = pkgs.pkgsBuildBuild.fetchurl {
name = "mtd_by_name_links"; name = "linux.tar.gz";
up = '' url = "https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.15.71.tar.gz";
mkdir -p /dev/mtd/by-name hash = "sha256-yhO2cXIeIgUxkSZf/4aAsF11uxyh+UUZu6D1h92vCD8=";
cd /dev/mtd/by-name
for i in /sys/class/mtd/mtd*[0-9]; do
ln -s ../../$(basename $i) $(cat $i/name)
done
'';
}; };
in { extraPatchPhase = ''
imports = [ ${pkgs.openwrt.applyPatches.mvebu}
../../modules/arch/arm.nix '';
../../modules/outputs/tftpboot.nix
../../modules/outputs/mbrimage.nix
../../modules/outputs/extlinux.nix
];
config = { config = {
services.mtd-name-links = mtd_by_name_links; PCI = "y";
kernel = { OF = "y";
src = pkgs.pkgsBuildBuild.fetchurl { MEMORY = "y"; # for MVEBU_DEVBUS
name = "linux.tar.gz"; DMADEVICES = "y"; # for MV_XOR
url = "https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.7.4.tar.gz"; CPU_V7 = "y";
hash = "sha256-wIrmL0BS63nRwWfm4nw+dRNVPUzGh9M4X7LaHzAn5tU="; ARCH_MULTIPLATFORM = "y";
}; ARCH_MVEBU = "y";
version = "6.7.4"; ARCH_MULTI_V7= "y";
config = { PCI_MVEBU = "y";
PCI = "y"; AHCI_MVEBU = "y";
OF = "y"; MACH_ARMADA_38X = "y";
MEMORY = "y"; # for MVEBU_DEVBUS SMP = "y";
DMADEVICES = "y"; # for MV_XOR NR_CPUS = "4";
CPU_V7 = "y"; VFP = "y";
ARCH_MULTIPLATFORM = "y"; NEON= "y";
ARCH_MVEBU = "y";
ARCH_MULTI_V7= "y";
PCI_MVEBU = "y";
AHCI_MVEBU = "y";
RTC_CLASS = "y"; # WARNING: unmet direct dependencies detected for ARCH_WANT_LIBATA_LEDS
RTC_DRV_ARMADA38X = "y"; # this may be useful anyway? ATA = "y";
EXPERT = "y"; # switch is DSA
ALLOW_DEV_COREDUMP = "n"; # CONFIG_NET_DSA_MV88E6060=y
# CONFIG_NET_DSA_MV88E6XXX=y
# CONFIG_NET_DSA_MV88E6XXX_GLOBAL2=y
# CONFIG_REGMAP=y
# CONFIG_REGMAP_I2C=y
# CONFIG_REGMAP_SPI=y
# CONFIG_REGMAP_MMIO=y
# dts has a compatible for this but dmesg is not PSTORE = "y";
# showing it PSTORE_RAM = "y";
EEPROM_AT24 = "y"; # atmel,24c64 PSTORE_CONSOLE = "y";
PSTORE_DEFLATE_COMPRESS = "n";
I2C = "y"; SERIAL_8250 = "y";
I2C_MUX = "y"; SERIAL_8250_CONSOLE = "y";
I2C_MUX_PCA954x = "y"; SERIAL_OF_PLATFORM="y";
SERIAL_MVEBU_UART = "y";
SERIAL_MVEBU_CONSOLE = "y";
MACH_ARMADA_38X = "y"; SERIAL_8250_DMA= "y";
SMP = "y"; SERIAL_8250_DW= "y";
# this is disabled for the moment because it relies on a SERIAL_8250_EXTENDED= "y";
# GCC plugin that requires gmp.h to build, and I can't see SERIAL_8250_MANY_PORTS= "y";
# right now how to confgure it to find gmp SERIAL_8250_SHARE_IRQ= "y";
STACKPROTECTOR_PER_TASK = "n"; OF_ADDRESS= "y";
NR_CPUS = "4"; OF_MDIO= "y";
VFP = "y";
NEON= "y";
# WARNING: unmet direct dependencies detected for ARCH_WANT_LIBATA_LEDS MVEBU_DEVBUS= "y"; # "Device Bus controller ... flash devices such as NOR, NAND, SRAM, and FPGA"
ATA = "y"; MVMDIO= "y";
MVNETA= "y";
PSTORE = "y"; MVNETA_BM= "y";
PSTORE_RAM = "y"; MVNETA_BM_ENABLE= "y";
PSTORE_CONSOLE = "y"; MVPP2= "y";
# PSTORE_DEFLATE_COMPRESS = "n"; MV_XOR= "y";
BLOCK = "y";
MMC="y";
PWRSEQ_EMMC="y"; # ???
PWRSEQ_SIMPLE="y"; # ???
MMC_BLOCK="y";
MMC_SDHCI= "y";
MMC_SDHCI_PLTFM= "y";
MMC_SDHCI_PXAV3= "y";
MMC_MVSDIO= "y";
SERIAL_8250 = "y";
SERIAL_8250_CONSOLE = "y";
SERIAL_OF_PLATFORM="y";
SERIAL_MVEBU_UART = "y";
SERIAL_MVEBU_CONSOLE = "y";
SERIAL_8250_DMA= "y";
SERIAL_8250_DW= "y";
SERIAL_8250_EXTENDED= "y";
SERIAL_8250_MANY_PORTS= "y";
SERIAL_8250_SHARE_IRQ= "y";
OF_ADDRESS= "y";
OF_MDIO= "y";
WATCHDOG = "y"; # watchdog is enabled by u-boot
ORION_WATCHDOG = "y"; # so is non-optional to keep feeding
MVEBU_DEVBUS = "y"; # "Device Bus controller ... flash devices such as NOR, NAND, SRAM, and FPGA"
MVMDIO = "y";
MVNETA = "y";
MVNETA_BM = "y";
MVNETA_BM_ENABLE = "y";
SRAM = "y"; # mmio-sram is "compatible" for bm_bppi reqd by BM
PHY_MVEBU_A38X_COMPHY = "y"; # for eth2
MARVELL_PHY = "y";
MVPP2 = "y";
MV_XOR = "y";
# there is NOR flash on this device, which is used for U-Boot
# and the rescue system (which we don't interfere with) but
# also for the U-Boot environment variables (which we might
# need to meddle with)
MTD_SPI_NOR = "y";
SPI = "y";
SPI_MASTER = "y";
SPI_ORION = "y";
NET_DSA = "y";
NET_DSA_MV88E6XXX = "y"; # depends on PTP_1588_CLOCK_OPTIONAL
};
conditionalConfig = {
USB = {
USB_XHCI_MVEBU = "y";
USB_XHCI_HCD = "y";
};
WLAN = {
WLAN_VENDOR_ATH = "y";
ATH_COMMON = "m";
ATH9K = "m";
ATH9K_PCI = "y";
ATH10K = "m";
ATH10K_PCI = "m";
ATH10K_DEBUG = "y";
};
};
};
boot = {
commandLine = [
"console=ttyS0,115200"
"pcie_aspm=off" # ath9k pci incompatible with PCIe ASPM
];
};
filesystem =
let
inherit (pkgs.pseudofile) dir symlink;
firmware = pkgs.stdenv.mkDerivation {
name = "wlan-firmware";
phases = ["installPhase"];
installPhase = ''
mkdir $out
cp -r ${pkgs.linux-firmware}/lib/firmware/ath10k/QCA988X $out
'';
};
in dir {
lib = dir {
firmware = dir {
ath10k = symlink firmware;
};
};
etc = dir {
"fw_env.config" =
let f = pkgs.writeText "fw_env.config" ''
/dev/mtd/by-name/u-boot-env 0x0 0x10000 0x10000
'';
in symlink f;
};
};
boot.tftp = {
loadAddress = lim.parseInt "0x1700000";
kernelFormat = "zimage";
compressRoot = true;
};
hardware = let
mac80211 = pkgs.kmodloader.override {
inherit (config.system.outputs) kernel;
targets = ["ath9k" "ath10k_pci"];
};
in {
defaultOutput = "mtdimage";
loadAddress = lim.parseInt "0x00800000"; # "0x00008000";
entryPoint = lim.parseInt "0x00800000"; # "0x00008000";
rootDevice = "/dev/mmcblk0p1";
dts = {
src = "${config.system.outputs.kernel.modulesupport}/arch/arm/boot/dts/marvell/armada-385-turris-omnia.dts";
includes = [
"${config.system.outputs.kernel.modulesupport}/arch/arm/boot/dts/marvell/"
];
};
flash.eraseBlockSize = 65536; # only used for tftpboot
networkInterfaces =
let
inherit (config.system.service.network) link;
in rec {
en70000 = link.build {
# in armada-38x.dtsi this is eth0.
# It's connected to port 5 of the 88E6176 switch
devpath = "/devices/platform/soc/soc:internal-regs/f1070000.ethernet";
# name is unambiguous but not very semantic
ifname = "en70000";
};
en30000 = link.build {
# in armada-38x.dtsi this is eth1
# It's connected to port 6 of the 88E6176 switch
devpath = "/devices/platform/soc/soc:internal-regs/f1030000.ethernet";
# name is unambiguous but not very semantic
ifname = "en30000";
};
# the default (from the dts? I'm guessing) behavour for
# lan ports on the switch is to attach them to
# en30000. It should be possible to do something better,
# per
# https://www.kernel.org/doc/html/latest/networking/dsa/configuration.html#affinity-of-user-ports-to-cpu-ports
# but apparently OpenWrt doesn't either so maybe it's more
# complicated than it looks.
wan = link.build {
# in armada-38x.dtsi this is eth2. It may be connected to
# an ethernet phy or to the SFP cage, depending on a gpio
devpath = "/devices/platform/soc/soc:internal-regs/f1034000.ethernet";
ifname = "wan";
};
lan0 = link.build { ifname = "lan0"; };
lan1 = link.build { ifname = "lan1"; };
lan2 = link.build { ifname = "lan2"; };
lan3 = link.build { ifname = "lan3"; };
lan4 = link.build { ifname = "lan4"; };
lan5 = link.build { ifname = "lan5"; };
lan = lan0; # maybe we should build a bridge?
wlan = link.build {
ifname = "wlan0";
dependencies = [ mac80211 ];
};
wlan5 = link.build {
ifname = "wlan1";
dependencies = [ mac80211 ];
};
};
};
}; };
}; };
boot = {
commandLine = [ "console=ttyS0,115200" ];
imageFormat = "fit";
};
hardware = {
defaultOutput = "flashimage";
loadAddress = "0x00008000";
entryPoint = "0x00008000";
rootDevice = "/dev/mtdblock0";
dts = {
src = "${config.system.outputs.kernel.modulesupport}/arch/arm/boot/dts/armada-385-turris-omnia.dts";
includes = [
# "${openwrt.src}/target/linux/mediatek/dts"
"${config.system.outputs.kernel.modulesupport}/arch/arm/boot/dts/"
];
};
networkInterfaces =
let
inherit (config.system.service.network) link;
inherit (config.system.service) bridge;
in rec {
lan = link.build { ifname = "eth0"; };
};
};
};
} }

View File

@ -1,155 +0,0 @@
#include "mt7621.dtsi"
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/input/input.h>
/ {
aliases {
label-mac-device = &gmac0;
};
};
&nand {
status = "okay";
mediatek,nmbm;
mediatek,bmt-max-ratio = <15>;
mediatek,bmt-max-reserved-blocks = <64>;
mediatek,bmt-remap-range =
<0x0 0x980000>,
<0x2980000 0x7800000>;
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
partition@0 {
label = "u-boot";
reg = <0x0 0x80000>;
read-only;
};
partition@80000 {
label = "u-boot-env";
reg = <0x80000 0x80000>;
read-only;
};
factory: partition@100000 {
label = "factory";
reg = <0x100000 0x80000>;
read-only;
};
partition@180000 {
label = "firmware_a";
reg = <0x180000 0x2800000>;
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
partition@0 {
label = "kernel_a";
reg = <0x0 0x800000>;
};
partition@400000 {
label = "ubi";
reg = <0x800000 0x2000000>;
};
};
partition@2980000 {
label = "firmware_b";
reg = <0x2980000 0x2800000>;
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
partition@0 {
label = "kernel_b";
reg = <0x0 0x800000>;
};
partition@400000 {
label = "ubi_b";
reg = <0x800000 0x2000000>;
};
};
partition@5180000 {
label = "rootfs_data";
reg = <0x5180000 0x1400000>;
};
partition@6580000 {
label = "logs";
reg = <0x6580000 0xd00000>;
};
partition@7280000 {
label = "vendor-myzyxel";
reg = <0x7280000 0x480000>;
read-only;
};
partition@7700000 {
label = "bootconfig";
reg = <0x7700000 0x80000>;
};
mrd: partition@7780000 {
label = "mrd";
reg = <0x7780000 0x80000>;
read-only;
nvmem-layout {
compatible = "fixed-layout";
#address-cells = <1>;
#size-cells = <1>;
macaddr_mrd_1fff8: macaddr@1fff8 {
reg = <0x1fff8 0x6>;
};
};
};
};
};
&pcie {
status = "okay";
};
&pcie1 {
wlan_5g: wifi@0,0 {
reg = <0x0 0 0 0 0>;
compatible = "mediatek,mt76";
mediatek,mtd-eeprom = <&factory 0x0>;
/* MAC-Address set in userspace */
};
};
&gmac0 {
nvmem-cells = <&macaddr_mrd_1fff8>;
nvmem-cell-names = "mac-address";
};
&switch0 {
ports {
port@4 {
status = "okay";
label = "lan";
};
};
};
&state_default {
gpio {
groups = "uart3";
function = "gpio";
};
};

View File

@ -1,155 +0,0 @@
#include "mt7621.dtsi"
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/input/input.h>
/ {
aliases {
label-mac-device = &gmac0;
};
};
&nand {
status = "okay";
mediatek,nmbm;
mediatek,bmt-max-ratio = <15>;
mediatek,bmt-max-reserved-blocks = <64>;
mediatek,bmt-remap-range =
<0x0 0x980000>,
<0x2980000 0x7800000>;
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
partition@0 {
label = "u-boot";
reg = <0x0 0x80000>;
read-only;
};
partition@80000 {
label = "u-boot-env";
reg = <0x80000 0x80000>;
read-only;
};
factory: partition@100000 {
label = "factory";
reg = <0x100000 0x80000>;
read-only;
};
partition@2980000 {
label = "firmware_b";
reg = <0x2980000 0x2800000>;
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
partition@0 {
label = "kernel_b";
reg = <0x0 0x800000>;
};
partition@400000 {
label = "ubi";
reg = <0x800000 0x2000000>;
};
};
partition@180000 {
label = "firmware_a";
reg = <0x180000 0x2800000>;
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
partition@0 {
label = "kernel_a";
reg = <0x0 0x800000>;
};
partition@400000 {
label = "ubi_a";
reg = <0x800000 0x2000000>;
};
};
partition@5180000 {
label = "rootfs_data";
reg = <0x5180000 0x1400000>;
};
partition@6580000 {
label = "logs";
reg = <0x6580000 0xd00000>;
};
partition@7280000 {
label = "vendor-myzyxel";
reg = <0x7280000 0x480000>;
read-only;
};
partition@7700000 {
label = "bootconfig";
reg = <0x7700000 0x80000>;
};
mrd: partition@7780000 {
label = "mrd";
reg = <0x7780000 0x80000>;
read-only;
nvmem-layout {
compatible = "fixed-layout";
#address-cells = <1>;
#size-cells = <1>;
macaddr_mrd_1fff8: macaddr@1fff8 {
reg = <0x1fff8 0x6>;
};
};
};
};
};
&pcie {
status = "okay";
};
&pcie1 {
wlan_5g: wifi@0,0 {
reg = <0x0 0 0 0 0>;
compatible = "mediatek,mt76";
mediatek,mtd-eeprom = <&factory 0x0>;
/* MAC-Address set in userspace */
};
};
&gmac0 {
nvmem-cells = <&macaddr_mrd_1fff8>;
nvmem-cell-names = "mac-address";
};
&switch0 {
ports {
port@4 {
status = "okay";
label = "lan";
};
};
};
&state_default {
gpio {
groups = "uart3";
function = "gpio";
};
};

View File

@ -1,365 +0,0 @@
{
system = {
crossSystem = {
config = "mipsel-unknown-linux-musl";
gcc = {
abi = "32";
arch = "mips32"; # mips32r2?
};
};
};
description = ''
Zyxel NWA50AX
********************
Zyxel NWA50AX is quite close to the GL-MT300N-v2 "Mango" device, but it is based on the MT7621
chipset instead of the MT7628.
Installation
============
This device is pretty, but, due to its A/B capabilities, can be a bit hard
to use completely.
The stock vendor firmware is a downstream fork of U-Boot: <https://github.com/RaitoBezarius/uboot-nwa50ax>
with restricted boot commands. Fortunately, OpenWrt folks figured out trivial command injections,
so you can use most of the OpenWrt commands without trouble by just command injecting
atns, atna or atnf, e.g. atns "; $real_command".
From factory web UI, you can upload the result of the zyxel-nwa-fit output.
From another operating system, you need to `dumpimage -T flat_dt -p 0 $zyxel-nwa-fit -o firmware.bin`,
`flash_erase $(mtd partition of the target partition firmware or zy_firmware) 0 0`, then you complete by
`nandwrite -p $(mtd partition of the target partition firmware or zy_firmware) firmware.bin`.
How to put the firmware.bin on the machine is left to you as an exercise, e.g. SSH, TFTP, whatever.
From serial, you have two choices:
- Flash this system via U-Boot:
same reasoning as from an existing Linux system, two choices:
- ymodem the binary, perform the write manually, you can inspire yourself
from the `script` contained in the vendor firmware, those are just a FIT containing a script.
- prepare a FIT containing a script executing your commands, tftpboot this.
- boot from an existing Liminix system, e.g. TFTPBOOT image.
- boot from an OpenWrt system, i.e. follow OpenWrt steps.
Once you are in a Linux system, understand that this device has A/B boot.
OpenWrt provides you with `zyxel-bootconfig` to set/unset the image status and choice.
The kernel is booted with `bootImage=<number>` which tells you which slot are you on.
You should find yourself with 10ish MTD partitions, the most interesting ones are two:
- firmware: 40MB
- firmware_1: 40MB
In the current setup, they are split further into kernel (8MB) and ubi (32MB).
Once you are done with first installation, note that if you want to use the A/B feature,
you need to write a _secondary_ image on the slot B. There is no proper flashing code
that will set the being-updated slot to `new` and boot on it to verify if it's working.
This is a WIP.
Upgrading your system can be achieved via:
- `liminix-rebuild` for the userspace.
- `flash_erase` + `nandwrite` for the kernelspace to the other slot than the one you are booted on,
note that you can just nandwrite the mtd partition corresponding to the *kernel* and not the whole firmware.
If you soft-bricked your AP, i.e. you cannot boot anything in U-Boot, no worries, just plug the serial console,
prepare a TFTP server (via `tufted` for example), download vendor firmware, set up `atns`, `atnf`, etc. and run `atnz`.
This will reflash everything back to normal via TFTP.
If you hard-bricked your AP, i.e. U-Boot is telling you to transfer a valid bootloader via ymodem, just extract
a U-Boot from the vendor OS, send it via ymodem and use the previous operations to perform a full flash this time
of all partitions.
Note that if you erased your MRD partition, you lost your serial and MAC address. There's no way to recover the original one
except by reading the physical label on your device!
If you super-hard-bricked your AP, i.e. no output on serial console, congratulations, you reached one of the rare state
of this device. You need an external NAND flasher to repair it and write the first stage from Mediatek to continue the previous
recovery operations.
Development TODO list:
- Better support for upgrade automation w.r.t. to A/B, e.g. automagic scripts.
- Mount the logs partition, mount / as overlayfs of firmware ? rootfs and rootfs_data for extended data.
- Jitter-based entropy injection? Device can be slow to initialize its CRNG and hostapd will reject few clients at the start because of that.
- Defaults for hostapd based on MT7915 capabilities? See the example for one possible list.
- Remove primary/secondary hack and put it in preinit.
- Offer ways to reflash the *bootloader* itself to support direct boot via UBI and kernel upgrades via filesystem rewrite.
Vendor web page: https://www.zyxel.com/fr/fr/products/wireless/ax1800-wifi-6-dual-radio-nebulaflex-access-point-nwa50ax
OpenWrt web page: https://openwrt.org/inbox/toh/zyxel/nwa50ax
OpenWrt tech data: https://openwrt.org/toh/hwdata/zyxel/zyxel_nwa50ax
'';
module = { pkgs, config, lib, lim, ...}:
let
inherit (pkgs.pseudofile) dir symlink;
inherit (pkgs) openwrt;
mac80211 = pkgs.mac80211.override {
drivers = [ "mt7915e" ];
klibBuild = config.system.outputs.kernel.modulesupport;
};
# v204520220929
wlan_firmware = pkgs.fetchurl {
url = "https://github.com/openwrt/mt76/raw/1b88dd07f153b202e57fe29734806744ed006b0e/firmware/mt7915_wa.bin";
hash = "sha256-wooyefzb0i8640+lwq3vNhcBXRFCtGuo+jiL7afZaKA=";
};
wlan_firmware' = pkgs.fetchurl {
url = "https://github.com/openwrt/mt76/raw/1b88dd07f153b202e57fe29734806744ed006b0e/firmware/mt7915_wm.bin";
hash = "sha256-k62nQewRuKjBLd5R3RxU4F74YKnQx5zr6gqMMImqVQw=";
};
wlan_firmware'' = pkgs.fetchurl {
url = "https://github.com/openwrt/mt76/raw/1b88dd07f153b202e57fe29734806744ed006b0e/firmware/mt7915_rom_patch.bin";
hash = "sha256-ifriAjWzFACrxVWCANZpUaEZgB/0pdbhnTVQytx6ddg=";
};
in {
imports = [
# We include it to ensure the bridge functionality
# is available on the target kernel.
../../modules/bridge
../../modules/arch/mipsel.nix
../../modules/outputs/tftpboot.nix
../../modules/outputs/zyxel-nwa-fit.nix
../../modules/zyxel-dual-image
];
filesystem = dir {
lib = dir {
firmware = dir {
mediatek = dir {
"mt7915_wa.bin" = symlink wlan_firmware;
"mt7915_wm.bin" = symlink wlan_firmware';
"mt7915_rom_patch.bin" = symlink wlan_firmware'';
};
};
};
};
rootfsType = "ubifs";
hardware = {
# Taken from OpenWRT
# root@OpenWrt:/# ubinfo /dev/ubi0
# ubi0
# Volumes count: 2
# Logical eraseblock size: 126976 bytes, 124.0 KiB
# Total amount of logical eraseblocks: 256 (32505856 bytes, 31.0 MiB)
# Amount of available logical eraseblocks: 0 (0 bytes)
# Maximum count of volumes 128
# Count of bad physical eraseblocks: 0
# Count of reserved physical eraseblocks: 19
# Current maximum erase counter value: 2
# Minimum input/output unit size: 2048 bytes
# Character device major/minor: 250:0
# Present volumes: 0, 1
ubi = {
minIOSize = "2048";
logicalEraseBlockSize = "126976";
physicalEraseBlockSize = "128KiB";
maxLEBcount = "256";
};
# This is a FIT containing a kernel padded and
# a UBI volume rootfs.
defaultOutput = "zyxel-nwa-fit";
loadAddress = lim.parseInt "0x80001000";
entryPoint = lim.parseInt "0x80001000";
# Aligned on 2kb.
alignment = 2048;
rootDevice = "ubi:rootfs";
dts = {
# Actually, this is not what we want.
# This DTS is insufficient.
src = ./mt7621_zyxel_nwa50ax.dtsi;
includes = [
# Here's one weird trick to make `ubi` detection
# out of the box.
# We will write ubi on /dev/firmware_a:rootfs location
# and same for /dev/firmware_b:rootfs.
# How do we distinguish both?
# We can just use the DTS to point ubi at A or B.
# This, unfortunately, means that we have "two images".
# But they are really just 1 image with 2 different DTS.
# TODO: improve this hack in preinit?
(if config.boot.imageType == "primary" then "${./a_image}" else "${./b_image}")
"${openwrt.src}/target/linux/ramips/dts"
];
};
networkInterfaces =
let
inherit (config.system.service.network) link;
in {
eth = link.build { ifname = "eth0"; };
lan = link.build { ifname = "lan"; };
wlan0 = link.build {
ifname = "wlan0";
dependencies = [ mac80211 ];
};
wlan1 = link.build {
ifname = "wlan1";
dependencies = [ mac80211 ];
};
};
};
boot = {
# Critical because NWA50AX will extend your cmdline with the image number booted.
# and some bootloader version.
# You don't want to find yourself being overridden.
commandLineDtbNode = "bootargs-override";
imageFormat = "fit";
tftp = {
# 5MB is nice.
freeSpaceBytes = 5 * 1024 * 1024;
loadAddress = lim.parseInt "0x2000000";
};
};
# Dual image management service in userspace.
services.zyxel-dual-image = config.boot.zyxel-dual-image.build {
ensureActiveImage = "primary";
# TODO: use mtd names rather…
# primary and secondary are always /dev/mtd3 by virtue of the
# dtb being not too wrong…
# TODO: remove this hack.
primaryMtdPartition = "/dev/mtd3";
secondaryMtdPartition = "/dev/mtd3";
bootConfigurationMtdPartition = "/dev/mtd12";
};
# DEVICE_VENDOR := ZyXEL
# KERNEL_SIZE := 8192k
# DEVICE_PACKAGES := kmod-mt7915-firmware zyxel-bootconfig
# KERNEL := kernel-bin | lzma | fit lzma $$(KDIR)/image-$$(firstword $$(DEVICE_DTS)).dtb
# IMAGES += factory.bin ramboot-factory.bin
# IMAGE/factory.bin := append-kernel | pad-to $$(KERNEL_SIZE) | append-ubi | zyxel-nwa-fit
# IMAGE/ramboot-factory.bin := append-kernel | pad-to $$(KERNEL_SIZE) | append-ubi
kernel = {
src = pkgs.fetchurl {
name = "linux.tar.gz";
url = "https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.15.137.tar.gz";
hash = "sha256-PkdzUKZ0IpBiWe/RS70J76JKnBFzRblWcKlaIFNxnHQ=";
};
extraPatchPhase = ''
${openwrt.applyPatches.ramips}
'';
config = {
RALINK = "y";
PCI = "y";
PHY_MT7621_PCI = "y";
PCIE_MT7621 = "y";
SOC_MT7621 = "y";
CLK_MT7621 = "y";
CLOCKSOURCE_WATCHDOG = "y";
SERIAL_8250_CONSOLE = "y";
SERIAL_8250 = "y";
SERIAL_CORE_CONSOLE = "y";
SERIAL_OF_PLATFORM = "y";
SERIAL_8250_NR_UARTS = "3";
SERIAL_8250_RUNTIME_UARTS = "3";
SERIAL_MCTRL_GPIO = "y";
CONSOLE_LOGLEVEL_DEFAULT = "8";
CONSOLE_LOGLEVEL_QUIET = "4";
# MTD_UBI_BEB_LIMIT = "20";
# MTD_UBI_WL_THRESHOLD = "4096";
MTD = "y";
MTD_BLOCK = "y"; # fix undefined ref to register_mtd_blktrans_dev
MTD_RAW_NAND = "y";
MTD_NAND_MT7621 = "y";
MTD_NAND_MTK_BMT = "y"; # Bad-block Management Table
MTD_NAND_ECC_SW_HAMMING= "y";
MTD_SPI_NAND= "y";
MTD_OF_PARTS = "y";
MTD_NAND_CORE= "y";
MTD_SPLIT_FIRMWARE= "y";
MTD_SPLIT_FIT_FW= "y";
PINCTRL = "y";
PINCTRL_MT7621 = "y";
I2C = "y";
I2C_MT7621 = "y";
SPI = "y";
MTD_SPI_NOR = "y";
SPI_MT7621 = "y";
SPI_MASTER = "y";
SPI_MEM = "y";
REGULATOR = "y";
REGULATOR_FIXED_VOLTAGE = "y";
RESET_CONTROLLER = "y";
POWER_RESET = "y";
POWER_RESET_GPIO = "y";
POWER_SUPPLY = "y";
LED_TRIGGER_PHY = "y";
PCI_DISABLE_COMMON_QUIRKS = "y";
PCI_DOMAINS = "y";
PCI_DOMAINS_GENERIC = "y";
PCI_DRIVERS_GENERIC = "y";
PCS_MTK_LYNXI = "y";
SOC_BUS = "y";
NET = "y";
ETHERNET = "y";
WLAN = "y";
PHYLIB = "y";
AT803X_PHY = "y";
FIXED_PHY = "y";
GENERIC_PHY = "y";
NET_DSA = "y";
NET_DSA_MT7530 = "y";
NET_DSA_MT7530_MDIO = "y";
NET_DSA_TAG_MTK = "y";
NET_MEDIATEK_SOC = "y";
NET_SWITCHDEV = "y";
NET_VENDOR_MEDIATEK = "y";
SWPHY = "y";
GPIOLIB = "y";
GPIO_MT7621 = "y";
OF_GPIO = "y";
EARLY_PRINTK = "y";
NEW_LEDS = "y";
LEDS_TRIGGERS = "y";
LEDS_CLASS = "y"; # required by rt2x00lib
LEDS_CLASS_MULTICOLOR = "y";
LEDS_BRIGHTNESS_HW_CHANGED = "y";
PRINTK_TIME = "y";
} // lib.optionalAttrs (config.system.service ? vlan) {
SWCONFIG = "y";
} // lib.optionalAttrs (config.system.service ? watchdog) {
RALINK_WDT = "y"; # watchdog
MT7621_WDT = "y"; # or it might be this one
};
};
};
}

View File

@ -1,56 +0,0 @@
#include "mt7621_zyxel_nwa-ax-for-ab.dtsi"
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/input/input.h>
/ {
compatible = "zyxel,nwa50ax", "mediatek,mt7621-soc";
model = "ZyXEL NWA50AX";
aliases {
led-boot = &led_system_green;
led-failsafe = &led_system_red;
led-running = &led_system_green;
led-upgrade = &led_system_red;
};
leds {
compatible = "gpio-leds";
led_system_red: system_red {
label = "red:system";
gpios = <&gpio 6 GPIO_ACTIVE_HIGH>;
};
led_system_green: system_green {
label = "green:system";
gpios = <&gpio 7 GPIO_ACTIVE_HIGH>;
};
system_blue {
label = "blue:system";
gpios = <&gpio 8 GPIO_ACTIVE_HIGH>;
};
};
keys {
compatible = "gpio-keys";
reset {
label = "reset";
gpios = <&gpio 30 GPIO_ACTIVE_LOW>;
linux,code = <KEY_RESTART>;
};
};
};
&ethernet {
pinctrl-0 = <&mdio_pins>, <&rgmii1_pins>;
};
&state_default {
gpio {
groups = "uart3", "rgmii2";
function = "gpio";
};
};

View File

@ -12,13 +12,9 @@ BUILDDIR = _build
help: help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
hardware.rst: hardware.nix
@rm -f hardware.rst || true
@cp $$(nix-build hardware.nix) hardware.rst
.PHONY: help Makefile .PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new # Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
html: Makefile %: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

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 the :ref:`development manual <tftp server>`.
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

@ -27,16 +27,19 @@ To build it,
nix-build -I liminix-config=path/to/your/configuration.nix --arg device "import ./devices/qemu" -A outputs.default nix-build -I liminix-config=path/to/your/configuration.nix --arg device "import ./devices/qemu" -A outputs.default
This creates a :file:`result/` directory containing a :file:`vmlinux` In a ``buildEnv`` nix-shell, you can use the :command:`run-liminix-vm` command
and a :file:`rootfs`, and also a shell script :file:`run.sh` which to run Qemu with appropriate options. It connects the Liminix
invokes QEMU to run that kernel with that filesystem. It connects the Liminix
serial console and the `QEMU monitor <https://www.qemu.org/docs/master/system/monitor.html>`_ to stdin/stdout. Use ^P (not ^A) to switch to the monitor. serial console and the `QEMU monitor <https://www.qemu.org/docs/master/system/monitor.html>`_ to stdin/stdout. Use ^P (not ^A) to switch to the monitor.
.. code-block:: console
nix-shell --run "run-liminix-vm result/vmlinux result/squashfs"
If you run with ``--background /path/to/some/directory`` as the first If you run with ``--background /path/to/some/directory`` as the first
parameter, it will fork into the background and open Unix sockets in parameter, it will fork into the background and open Unix sockets in
that directory for console and monitor. Use :command:`nix-shell --run that directory for console and monitor. Use :command:`connect-vm`
connect-vm` to connect to either of these sockets, and ^O to (also in the ``buildEnv`` environment) to connect to either of these
disconnect. sockets, and ^O to disconnect.
.. _qemu-networking: .. _qemu-networking:
@ -52,11 +55,9 @@ the right way:
* multicast 230.0.0.1:1235 : lan * multicast 230.0.0.1:1235 : lan
* multicast 230.0.0.1:1236 : world (the internet) * multicast 230.0.0.1:1236 : world (the internet)
Any VM started by a :command:`run.sh` script is connected to "lan" and A VM started with :command:`run-liminix-vm` is connected to "lan" and "access", and
"access", and the emulated border network gateway (see below) runs the emulated border network gateway (see below) runs PPPoE and is
PPPoE and is connected to "access" and "world". connected to "access" and "world".
.. _border-network-gateway:
Border Network Gateway Border Network Gateway
---------------------- ----------------------
@ -88,10 +89,6 @@ time with configurations for RP-PPPoE and/or Accel PPP.`
Hardware devices Hardware devices
**************** ****************
TFTP
====
.. _tftp server: .. _tftp server:
How you get your image onto hardware will vary according to the How you get your image onto hardware will vary according to the
@ -122,7 +119,7 @@ Now add the device and server IP addresses to your configuration:
}; };
and then build the derivation for ``outputs.default`` or and then build the derivation for ``outputs.default`` or
``outputs.mtdimage`` (for which it will be an alias on any device ``outputs.flashimage`` (for which it will be an alias on any device
where this is applicable). You should find it has created where this is applicable). You should find it has created
* :file:`result/firmware.bin` which is the file you are going to flash * :file:`result/firmware.bin` which is the file you are going to flash
@ -155,8 +152,6 @@ U-Boot to transfer the kernel and filesystem over TFTP and boot the
kernel from RAM. kernel from RAM.
.. _bng:
Networking Networking
========== ==========

View File

@ -1,9 +1,32 @@
{ eval, lib, pkgs }:
let let
overlay = import ../overlay.nix;
pkgs = import <nixpkgs> ( {
overlays = [overlay];
config = {
allowUnsupportedSystem = true; # mipsel
permittedInsecurePackages = [
"python-2.7.18.6" # kernel backports needs python <3
];
};
});
inherit (pkgs) lib;
inherit (lib) types;
modulenames =
builtins.attrNames
(lib.filterAttrsRecursive
(n: t:
(n != "arch") &&
((t=="directory") ||
((t=="regular") && ((builtins.match ".*\\.nix$" n) != null))))
(builtins.readDir ../modules));
modulefiles = builtins.map (n: builtins.toPath "${../modules}/${n}") modulenames;
eval = (lib.evalModules {
modules = [
{ _module.args = { inherit pkgs; lib = pkgs.lib; }; }
] ++ modulefiles;
});
conf = eval.config; conf = eval.config;
rootDir = builtins.toPath ./..; optToDoc = name: opt : {
stripAnyPrefixes = lib.flip (lib.fold lib.removePrefix) [ "${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;
@ -19,11 +42,16 @@ let
then then
let sd = lib.attrByPath item.loc ["not found"] conf; let sd = lib.attrByPath item.loc ["not found"] conf;
in item // { in item // {
declarations = map stripAnyPrefixes item.declarations;
parameters = parameters =
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;
in o = builtins.map spliceServiceDefn
builtins.map spliceServiceDefn (pkgs.lib.optionAttrSetToDocList eval.options) (pkgs.lib.optionAttrSetToDocList eval.options);
in {
doc = pkgs.writeText "options.yaml" ''
# ${./..}
${builtins.toJSON o}
'';
}

View File

@ -1,36 +1,18 @@
with import <nixpkgs> { }; with import <nixpkgs> {} ;
let let
inherit (builtins) stringLength readDir filter; devices =
devices = filter (n: n != "families") (lib.mapAttrsToList (n: t: n) (readDir ../devices)); builtins.readDir ../devices;
texts = map ( texts = lib.mapAttrsToList (n: t:
n: let d = import ../devices/${n}/default.nix;
let d' = { description = "no description for ${n}"; } // d;
d = import ../devices/${n}/default.nix; in d'.description )
tag = ".. _${lib.strings.replaceStrings [" "] ["-"] n}:"; devices;
d' = {
description = ''
${n}
${substring 0 (stringLength n) "********************************"}
'';
} // d;
in
"${tag}\n\n${d'.description}"
) devices;
in in
writeText "hwdoc" '' writeText "hwdoc" ''
Supported hardware Supported hardware
################## ##################
For development, the `GL.iNet GL-MT300A <https://www.gl-inet.com/products/gl-mt300a/>`_
is an attractive choice as it has a builtin "debrick" procedure in the
boot monitor and is also comparatively simple to
attach serial cables to (soldering not required), so it
is lower-risk than some devices.
For a more powerful device, something with an ath10k would be the safe bet,
or the Linksys E8450 which seems popular in the openwrt community.
${lib.concatStringsSep "\n\n" texts} ${lib.concatStringsSep "\n\n" texts}
'' ''

View File

@ -7,13 +7,11 @@ Liminix
intro intro
tutorial tutorial
installation
configuration configuration
admin admin
development development
modules modules
hardware hardware
outputs
Indices and tables Indices and tables

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

@ -1,13 +0,0 @@
Outputs
#######
Liminix *outputs* are artefacts that can be installed somehow on a
target device, or "installers" which run on the target device to
perform the installation.
There are different outputs because different target devices need
different artefacts, or have different ways to get that artefact
installed. The options available for a particular device are described in
the section for that device.
.. include:: outputs-generated.inc.rst

View File

@ -1,19 +0,0 @@
(local yaml (require :lyaml))
;; (local { : view } (require :fennel))
(fn output? [option]
(match option.loc
["system" "outputs" & _] true
_ false))
(fn sorted-options [options]
(table.sort
options
(fn [a b] (< a.name b.name)))
options)
(each [_ option (ipairs (sorted-options (yaml.load (io.read "*a"))))]
(when (and (output? option) (not option.internal))
(print (.. ".. _" (string.gsub option.name "%." "-") ":") "\n")
(print option.description "\n")))

View File

@ -2,25 +2,21 @@
(local { : view } (require :fennel)) (local { : view } (require :fennel))
(fn basename [str ext]
(-> str
(string.gsub "(.*/)(.*)" "%2")
(string.gsub (.. ext "$") "")))
(fn headline [name] (fn headline [name]
(let [title (assert (basename name ".nix")) (let [(_ _ basename) (string.find name ".*/([^/].*).nix")
len (title:len)] len (basename:len)]
(.. title "\n" (string.rep "=" len)))) (.. basename "\n" (string.rep "=" len))))
(fn read-preamble [pathname] (fn read-preamble [pathname]
(let [pathname (if (string.match pathname ".nix$") (if (= (pathname:sub 1 1) "/")
pathname (let [pathname (if (string.match pathname ".nix$")
(.. pathname "/default.nix"))] pathname
(with-open [f (assert (io.open pathname :r))] (.. pathname "/default.nix"))]
(accumulate [lines nil (with-open [f (assert (io.open pathname :r))]
l (f:lines) (accumulate [lines nil
:until (not (= (string.sub l 1 2) "##"))] l (f:lines)
(.. (or lines "") (string.gsub l "^## *" "") "\n"))))) :until (not (= (string.sub l 1 2) "##"))]
(.. (or lines "") (string.gsub l "^## *" "") "\n"))))))
(fn relative-pathname [pathname] (fn relative-pathname [pathname]
(let [pathname (let [pathname

View File

@ -69,11 +69,10 @@ device's serial console and the `QEMU monitor
stdin/stdout. stdin/stdout.
You should now see Linux boot messages and after a few seconds be You should now see Linux boot messages and after a few seconds be
presented with a root shell prompt. You can run commands to look at presented with a login prompt. You can login on the console as
the filesystem, see what processes are running, view log messages (in ``root`` (password is "secret") and poke around to see what processes are
:file:/run/uncaught-logs.current), etc. To kill the emulator, press ^P running. To kill the emulator, press ^P (Control P) then c to enter the
(Control P) then c to enter the "QEMU Monitor", then type ``quit`` at "QEMU Monitor", then type ``quit`` at the ``(qemu)`` prompt.
the ``(qemu)`` prompt.
To see that it's running network services we need to connect to its To see that it's running network services we need to connect to its
emulated network. Start the machine again, if you had stopped it, and emulated network. Start the machine again, if you had stopped it, and
@ -138,14 +137,20 @@ unbrick if necessary.
work here, but you accept the slightly greater bricking work here, but you accept the slightly greater bricking
risk if it doesn't. risk if it doesn't.
See :doc:`hardware` for device support status. You may want to acquire a `USB TTL serial cable
<https://cpc.farnell.com/ftdi/ttl-232r-rpi/cable-debug-ttl-232-usb-rpi/dp/SC12825?st=usb%20to%20uart%20cable>`_
You may want to read and inwardly digest the Develoment Manual section when you start working with Liminix on real hardware. You
:ref:`serial` when you start working with Liminix on real hardware. You won't *need* it for this example, assuming it works, but it
won't *need* serial access for this example, assuming it works, but it
allows you allows you
to see the boot monitor and kernel messages, and to login directly to to see the boot monitor and kernel messages, and to login directly to
the device if for some reason it doesn't bring its network up. the device if for some reason it doesn't bring its network up. You have options
here: the FTDI-based cables are the Rolls Royce of serial cables,
whereas the ones based on PL2303 and CP2102 chipsets are cheaper but
also fussier - or you could even get creative and use e.g. a
`Raspberry Pi <https://pinout.xyz/#>`_ or other SBC with a UART and
TX/RX/GND header pins. Make sure that the voltages are compatible:
this is a 3.3v device and you don't want to be sending it 5v or (even
worse) 12v.
Now we can build Liminix. Although we could use the same example Now we can build Liminix. Although we could use the same example
configuration as we did for Qemu, you might not want to plug a DHCP configuration as we did for Qemu, you might not want to plug a DHCP
@ -300,7 +305,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,29 +11,53 @@
... ...
}: 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 = {
tftp = { tftp = {
serverip = "10.0.0.1"; serverip = "192.168.8.148";
ipaddr = "10.0.0.8"; ipaddr = "192.168.8.251";
}; };
}; };
imports = [ imports = [
../modules/standard.nix
../modules/wlan.nix ../modules/wlan.nix
../modules/network ../modules/network
../modules/vlan ../modules/vlan
../modules/ssh ../modules/ssh
../modules/usb.nix
../modules/watchdog ../modules/watchdog
../modules/mount ../modules/mount
]; ];
hostname = "arhcive"; hostname = "arhcive";
kernel = {
config = {
USB = "y";
USB_EHCI_HCD = "y";
USB_EHCI_HCD_PLATFORM = "y";
USB_OHCI_HCD = "y";
USB_OHCI_HCD_PLATFORM = "y";
USB_SUPPORT = "y";
USB_COMMON = "y";
USB_STORAGE = "y";
USB_STORAGE_DEBUG = "n";
USB_UAS = "y";
USB_ANNOUNCE_NEW_DEVICES = "y";
SCSI = "y";
BLK_DEV_SD = "y";
USB_PRINTER = "y";
MSDOS_PARTITION = "y";
EFI_PARTITION = "y";
EXT4_FS = "y";
EXT4_USE_FOR_EXT2 = "y";
FS_ENCRYPTION = "y";
};
};
services.dhcpc = services.dhcpc =
let iface = config.hardware.networkInterfaces.lan; let iface = config.hardware.networkInterfaces.lan;
@ -52,6 +76,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
@ -82,7 +107,7 @@ in rec {
}; };
services.mount_external_disk = svc.mount.build { services.mount_external_disk = svc.mount.build {
partlabel = "backup-disk"; device = "LABEL=backup-disk";
mountpoint = "/srv"; mountpoint = "/srv";
fstype = "ext4"; fstype = "ext4";
}; };
@ -92,6 +117,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,37 +143,23 @@ 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 = {
passwd = lib.mkForce secrets.root.passwd; passwd = lib.mkForce secrets.root_password;
openssh.authorizedKeys.keys = secrets.root.keys; # openssh.authorizedKeys.keys = [
# (builtins.readFile "/home/dan/.ssh/id_rsa.pub")
# ];
}; };
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 strace tcpdump ];
e2fsprogs
mtdutils
(levitate.override {
config = {
services = {
inherit (config.services) dhcpc sshd watchdog;
};
defaultProfile.packages = [ mtdutils ];
users.root.openssh.authorizedKeys.keys = secrets.root.keys;
};
})
];
} }

View File

@ -35,7 +35,6 @@ in {
(drop "icmpv6 type destination-unreachable ct state invalid,untracked") (drop "icmpv6 type destination-unreachable ct state invalid,untracked")
]; ];
}; };
forward-ip6 = { forward-ip6 = {
type = "filter"; type = "filter";
family = "ip6"; family = "ip6";
@ -96,23 +95,19 @@ in {
# recognised (outbound-initiated) flow # recognised (outbound-initiated) flow
(accept "oifname \"int\" iifname \"ppp0\" ct state established,related") (accept "oifname \"int\" iifname \"ppp0\" ct state established,related")
(accept "iifname \"int\" oifname \"ppp0\" ") (accept "iifname \"int\" oifname \"ppp0\" ")
"log prefix \"DENIED CHAIN=forward-ip6 \""
]; ];
}; };
input-lan = {
input-ip6-lan = {
type = "filter"; type = "filter";
family = "ip6"; family = "ip6";
rules = [ rules = [
(accept "udp dport 547") # dhcp, could restrict to daddr ff02::1:2 (accept "udp dport 547") # dhcp, could restrict to daddr ff02::1:2
(accept "udp dport 53") # dns
(accept "tcp dport 22") (accept "tcp dport 22")
]; ];
}; };
input-ip6-wan = { input-wan = {
type = "filter"; type = "filter";
family = "ip6"; family = "ip6";
@ -128,8 +123,8 @@ in {
hook = "input"; hook = "input";
rules = [ rules = [
(accept "meta l4proto icmpv6") (accept "meta l4proto icmpv6")
"iifname int jump input-ip6-lan" "iifname int jump input-lan"
"iifname ppp0 jump input-ip6-wan" "iifname ppp0 jump input-wan"
(if allow-incoming (if allow-incoming
then accept "oifname \"int\" iifname \"ppp0\"" then accept "oifname \"int\" iifname \"ppp0\""
else "oifname \"int\" iifname \"ppp0\" jump incoming-allowed-ip6" else "oifname \"int\" iifname \"ppp0\" jump incoming-allowed-ip6"
@ -137,7 +132,6 @@ in {
# how does this even make sense in an input chain? # how does this even make sense in an input chain?
(accept "oifname \"int\" iifname \"ppp0\" ct state established,related") (accept "oifname \"int\" iifname \"ppp0\" ct state established,related")
(accept "iifname \"int\" oifname \"ppp0\" ") (accept "iifname \"int\" oifname \"ppp0\" ")
"log prefix \"DENIED CHAIN=input-ip6 \""
]; ];
}; };
@ -160,7 +154,6 @@ in {
"oifname \"ppp0\" masquerade" "oifname \"ppp0\" masquerade"
]; ];
}; };
nat-rx = { nat-rx = {
type = "nat"; type = "nat";
hook = "prerouting"; hook = "prerouting";
@ -174,71 +167,4 @@ in {
# packet replies. " # packet replies. "
]; ];
}; };
# these chains are for rules that have to be present for things to
# basically work at all: for example, the router won't issue DHCP
# unless it's allowed to receive DHCP requests. For "site policy"
# rules you may prefer to use incoming-allowed-ip[46] instead
input-ip4-lan = {
type = "filter";
family = "ip";
rules = [
(accept "udp dport 67") # dhcp
(accept "udp dport 53") # dns
(accept "tcp dport 22") # ssh
];
};
input-ip4-wan = {
type = "filter";
family = "ip";
rules = [
(accept "udp sport 53")
];
};
input-ip4 = {
type = "filter";
family = "ip";
policy = "drop";
hook = "input";
rules = [
"iifname lo accept"
"icmp type { echo-request, echo-reply } accept"
"iifname int jump input-ip4-lan"
"iifname ppp0 jump input-ip4-wan"
"oifname \"int\" iifname \"ppp0\" jump incoming-allowed-ip4"
"ct state established,related accept"
"log prefix \"DENIED CHAIN=input-ip4 \""
];
};
forward-ip4 = {
type = "filter";
family = "ip";
policy = "drop";
hook = "forward";
rules = [
"iifname \"int\" accept"
"ct state established,related accept"
"oifname \"int\" iifname \"ppp0\" jump incoming-allowed-ip4"
"log prefix \"DENIED CHAIN=forward-ip4 \""
];
};
incoming-allowed-ip4 = {
type = "filter";
family = "ip";
rules = [
# This is where you put permitted incoming connections. If
# you're using NAT and want to forward a port from outside to
# devices on the LAN, then you need a DNAT rule in nat-rx chain
# *and* to accept the packet in this chain (specifying the
# internal (RFC1918) address).
];
};
} }

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.
@ -33,6 +33,7 @@ in rec {
../modules/ntp ../modules/ntp
../modules/ppp ../modules/ppp
../modules/ssh ../modules/ssh
../modules/standard.nix
../modules/vlan ../modules/vlan
../modules/wlan.nix ../modules/wlan.nix
]; ];
@ -49,40 +50,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 +129,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 +158,9 @@ in rec {
interface = services.wan; interface = services.wan;
}; };
services.firewall = svc.firewall.build { }; services.firewall = svc.firewall.build {
ruleset = import ./demo-firewall.nix;
};
services.packet_forwarding = svc.network.forward.build { }; services.packet_forwarding = svc.network.forward.build { };
@ -202,5 +197,7 @@ in rec {
]; ];
}; };
defaultProfile.packages = with pkgs; [ min-collect-garbage ]; defaultProfile.packages = with pkgs; [
min-collect-garbage
];
} }

View File

@ -8,10 +8,12 @@
config, config,
pkgs, pkgs,
lib, lib,
modulesPath,
... ...
}: let }: let
secrets = import ./extneder-secrets.nix; secrets = import ./extneder-secrets.nix;
inherit (pkgs.liminix.services) oneshot longrun bundle target;
inherit (pkgs.pseudofile) dir symlink;
inherit (pkgs) dropbear ifwait serviceFns;
svc = config.system.service; svc = config.system.service;
in rec { in rec {
boot = { boot = {
@ -22,32 +24,114 @@ in rec {
}; };
imports = [ imports = [
"${modulesPath}/profiles/wap.nix" ../modules/wlan.nix
"${modulesPath}/vlan" ../modules/vlan
"${modulesPath}/ssh" ../modules/network
../modules/hostapd
../modules/bridge
../modules/ssh
../modules/standard.nix
]; ];
hostname = "extneder"; hostname = "extneder";
profile.wap = { kernel = {
interfaces = with config.hardware.networkInterfaces; [ config = {
lan
wlan
];
wireless = { NETFILTER_XT_MATCH_CONNTRACK = "y";
networks.${secrets.ssid} = {
interface = config.hardware.networkInterfaces.wlan; IP6_NF_IPTABLES = "y"; # do we still need these
inherit (secrets) channel wpa_passphrase; IP_NF_IPTABLES = "y"; # if using nftables directly
country_code = "GB";
hw_mode = "g"; # these are copied from rotuer and need review.
wmm_enabled = 1; # we're not running a firewall, so why do we need
ieee80211n = 1; # nftables config?
}; IP_NF_NAT = "y";
IP_NF_TARGET_MASQUERADE = "y";
NETFILTER = "y";
NETFILTER_ADVANCED = "y";
NETFILTER_XTABLES = "y";
NFT_COMPAT = "y";
NFT_CT = "y";
NFT_LOG = "y";
NFT_MASQ = "y";
NFT_NAT = "y";
NFT_REJECT = "y";
NFT_REJECT_INET = "y";
NF_CONNTRACK = "y";
NF_NAT = "y";
NF_NAT_MASQUERADE = "y";
NF_TABLES = "y";
NF_TABLES_INET = "y";
NF_TABLES_IPV4 = "y";
NF_TABLES_IPV6 = "y";
}; };
}; };
services.hostap = svc.hostapd.build {
interface = config.hardware.networkInterfaces.wlan;
params = {
country_code = "GB";
hw_mode = "g";
wmm_enabled = 1;
ieee80211n = 1;
inherit (secrets) ssid channel wpa_passphrase;
auth_algs = 1; # 1=wpa2, 2=wep, 3=both
wpa = 2; # 1=wpa, 2=wpa2, 3=both
wpa_key_mgmt = "WPA-PSK";
wpa_pairwise = "TKIP CCMP"; # auth for wpa (may not need this?)
rsn_pairwise = "CCMP"; # auth for wpa2
};
};
services.int = svc.bridge.primary.build {
ifname = "int";
};
services.dhcpc = svc.network.dhcp.client.build {
interface = services.int;
dependencies = [ config.services.hostname ];
};
services.bridge = svc.bridge.members.build {
primary = services.int;
members = with config.hardware.networkInterfaces; [
lan
wlan
];
};
services.sshd = svc.ssh.build {}; services.sshd = svc.ssh.build {};
users.root.passwd = lib.mkForce secrets.root.passwd;
services.resolvconf = oneshot rec {
dependencies = [ services.dhcpc ];
name = "resolvconf";
# CHECK: https://udhcp.busybox.net/README.udhcpc says
# 'A list of DNS server' but doesn't say what separates the
# list members. Assuming it's a space or other IFS character
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";
};
};
services.defaultroute4 = svc.network.route.build {
via = "$(output ${services.dhcpc} router)";
target = "default";
dependencies = [services.dhcpc];
};
users.root.passwd = lib.mkForce secrets.root_password;
defaultProfile.packages = with pkgs; [nftables strace tcpdump swconfig]; defaultProfile.packages = with pkgs; [nftables strace tcpdump swconfig];
} }

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 {
@ -7,6 +8,7 @@ in rec {
../modules/network ../modules/network
../modules/ssh ../modules/ssh
../modules/vlan ../modules/vlan
../modules/flashimage.nix
]; ];
boot.tftp = { boot.tftp = {

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,119 +0,0 @@
{ config, pkgs, ... } :
let
inherit (pkgs.liminix.services) target;
svc = config.system.service;
secrets-1 = {
ssid = "Zyxel 2G (N)";
wpa_passphrase = "diamond dogs";
};
secrets-2 = {
ssid = "Zyxel 5G (AX)";
wpa_passphrase = "diamond dogs";
};
baseParams = {
country_code = "FR";
hw_mode = "g";
channel = 6;
wmm_enabled = 1;
ieee80211n = 1;
ht_capab = "[LDPC][GF][HT40-][HT40+][SHORT-GI-40][MAX-AMSDU-7935][TX-STBC]";
auth_algs = 1;
wpa = 2;
wpa_key_mgmt = "WPA-PSK";
wpa_pairwise = "TKIP CCMP";
rsn_pairwise = "CCMP";
};
modernParams = {
hw_mode = "a";
he_su_beamformer = 1;
he_su_beamformee = 1;
he_mu_beamformer = 1;
preamble = 1;
# Allow radar detection.
ieee80211d = 1;
ieee80211h = 1;
ieee80211ac = 1;
ieee80211ax = 1;
vht_capab = "[MAX-MPDU-7991][SU-BEAMFORMEE][SU-BEAMFORMER][RXLDPC][SHORT-GI-80][MAX-A-MPDU-LEN-EXP3][RX-ANTENNA-PATTERN][TX-ANTENNA-PATTERN][TX-STBC-2BY1][RX-STBC-1][MU-BEAMFORMER]";
vht_oper_chwidth = 1;
he_oper_chwidth = 1;
channel = 36;
vht_oper_centr_freq_seg0_idx = 42;
he_oper_centr_freq_seg0_idx = 42;
require_vht = 1;
};
mkWifiSta = params: interface: secrets: svc.hostapd.build {
inherit interface;
params = params // {
inherit (secrets) ssid wpa_passphrase;
};
};
in rec {
imports = [
../modules/wlan.nix
../modules/network
../modules/hostapd
../modules/ssh
../modules/ntp
../modules/vlan
../modules/bridge
];
hostname = "zyxel";
users.root = {
# EDIT: choose a root password and then use
# "mkpasswd -m sha512crypt" to determine the hash.
# It should start wirh $6$.
passwd = "$y$j9T$f8GhLiqYmr3lc58eKhgyD0$z7P/7S9u.kq/cANZExxhS98bze/6i7aBxU6tbl7RMi.";
openssh.authorizedKeys.keys = [
# EDIT: you can add your ssh pubkey here
# "ssh-rsa AAAAB3NzaC1....H6hKd user@example.com";
];
};
services.int = svc.bridge.primary.build {
ifname = "int";
};
services.bridge = svc.bridge.members.build {
primary = services.int;
members = with config.hardware.networkInterfaces; [
lan
wlan0
wlan1
];
};
services.dhcpv4 =
let iface = services.int;
in svc.network.dhcp.client.build { interface = iface; };
services.defaultroute4 = svc.network.route.build {
via = "$(output ${services.dhcpv4} address)";
target = "default";
dependencies = [ services.dhcpv4 ];
};
services.packet_forwarding = svc.network.forward.build { };
services.sshd = svc.ssh.build {
allowRoot = true;
};
services.ntp = config.system.service.ntp.build {
pools = { "pool.ntp.org" = ["iburst"] ; };
};
boot.tftp = {
serverip = "192.0.2.10";
ipaddr = "192.0.2.12";
};
# wlan0 is the 2.4GHz interface.
services.hostap-1 = mkWifiSta baseParams config.hardware.networkInterfaces.wlan0 secrets-1;
# wlan1 is the 5GHz interface, e.g. AX capable.
services.hostap-2 = mkWifiSta (baseParams // modernParams) config.hardware.networkInterfaces.wlan1 secrets-2;
defaultProfile.packages = with pkgs; [ zyxel-bootconfig iw min-collect-garbage mtdutils ];
}

View File

@ -1,114 +0,0 @@
{ config, pkgs, lib, ... } :
let
inherit (pkgs) serviceFns;
svc = config.system.service;
inherit (pkgs.pseudofile) dir symlink;
inherit (pkgs.liminix.services) oneshot target;
some-util-linux = pkgs.runCommand "some-util-linux" { } ''
mkdir -p $out/bin
cd ${pkgs.util-linux-small}/bin
cp fdisk sfdisk mkswap $out/bin
'';
in rec {
imports = [
../modules/network
../modules/ssh
../modules/usb.nix
../modules/schnapps
../modules/outputs/mtdimage.nix
../modules/outputs/mbrimage.nix
../modules/outputs/tftpboot.nix
../modules/outputs/ubifs.nix
../modules/outputs/ubimage.nix
../modules/outputs/jffs2.nix
../modules/outputs/ext4fs.nix
../modules/outputs/extlinux.nix
];
kernel.config = {
BTRFS_FS = "y";
};
boot.tftp = {
ipaddr = "10.0.0.8"; # my address
serverip = "10.0.0.1"; # build machine or other tftp server
freeSpaceBytes = 1024 * 1024 * 4;
};
boot.loader.extlinux.enable = true;
hostname = "liminix-recovery";
services.dhcpc = svc.network.dhcp.client.build {
interface = config.hardware.networkInterfaces.lan2;
# don't start DHCP until the hostname is configured,
# so it can identify itself to the DHCP server
dependencies = [ config.services.hostname ];
};
services.sshd = svc.ssh.build {
dependencies = [ config.services.growfs ];
};
services.defaultroute4 = svc.network.route.build {
via = "$(output ${services.dhcpc} router)";
target = "default";
dependencies = [ services.dhcpc ];
};
services.resolvconf = oneshot rec {
dependencies = [ services.dhcpc ];
name = "resolvconf";
up = ''
( in_outputs ${name}
for i in $(output ${services.dhcpc} dns); do
echo "nameserver $i" > resolv.conf
done
)
'';
};
services.growfs = let name = "growfs"; in oneshot {
inherit name;
up = ''
device=$(grep /persist /proc/1/mountinfo | cut -f9 -d' ')
${pkgs.e2fsprogs}/bin/resize2fs $device
'';
};
filesystem = dir {
etc = dir {
"resolv.conf" = symlink "${services.resolvconf}/.outputs/resolv.conf";
};
mnt = dir {};
};
rootfsType = "ext4";
# sda is most likely correct for the boot-from-USB case. For tftp
# it's overridden by the boot.scr anyway, so maybe it all works out
hardware.rootDevice = lib.mkForce "/dev/sda1";
users.root = {
# the password is "secret". Use mkpasswd -m sha512crypt to
# create this hashed password string
passwd = "$6$y7WZ5hM6l5nriLmo$5AJlmzQZ6WA.7uBC7S8L4o19ESR28Dg25v64/vDvvCN01Ms9QoHeGByj8lGlJ4/b.dbwR9Hq2KXurSnLigt1W1";
openssh.authorizedKeys.keys =
let fromBuild =
(builtins.readFile
((builtins.toPath (builtins.getEnv "HOME")) + "/.ssh/authorized_keys")
);
in lib.splitString "\n" fromBuild;
};
defaultProfile.packages = with pkgs; [
e2fsprogs # ext4
btrfs-progs
mtdutils # mtd, jffs2, ubifs
dtc # you never know when you might need device tree stuff
some-util-linux
libubootenv # fw_{set,print}env
pciutils
];
}

View File

@ -1,6 +1,5 @@
{ rec {
wpa_passphrase = "you bring light in"; wpa_passphrase = "you bring light in";
ssid = "liminix";
l2tp = { l2tp = {
name = "abcde@a.1"; name = "abcde@a.1";
password = "NotMyIspPassword"; password = "NotMyIspPassword";
@ -8,10 +7,8 @@
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 = {
prefix = "10.8.0";
}; };
root_password = root.passwd;
} }

View File

@ -1,19 +1,25 @@
# 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, ... } :
let let
secrets = { secrets = import ./rotuer-secrets.nix;
domainName = "fake.liminix.org"; inherit (pkgs.liminix.services) oneshot longrun bundle;
firewallRules = { }; inherit (pkgs) serviceFns;
} // (import ./rotuer-secrets.nix);
svc = config.system.service; svc = config.system.service;
wirelessConfig = { wirelessConfig = {
country_code = "GB"; country_code = "GB";
inherit (secrets) wpa_passphrase; inherit (secrets) wpa_passphrase;
auth_algs = 1; # 1=wpa2, 2=wep, 3=both
wpa = 2; # 1=wpa, 2=wpa2, 3=both
wpa_key_mgmt = "WPA-PSK";
wpa_pairwise = "TKIP CCMP"; # auth for wpa (may not need this?)
rsn_pairwise = "CCMP"; # auth for wpa2
wmm_enabled = 1; wmm_enabled = 1;
}; };
@ -27,71 +33,55 @@ in rec {
}; };
imports = [ imports = [
"${modulesPath}/profiles/gateway.nix" ../modules/wlan.nix
../modules/standard.nix
../modules/network
../modules/ppp
../modules/dnsmasq
../modules/dhcp6c
../modules/firewall
../modules/hostapd
../modules/bridge
../modules/ntp
../modules/ssh
]; ];
rootfsType = "jffs2";
hostname = "rotuer"; hostname = "rotuer";
profile.gateway = { services.hostap = svc.hostapd.build {
lan = { interface = config.hardware.networkInterfaces.wlan;
interfaces = with config.hardware.networkInterfaces; params = {
[ ssid = "liminix";
# EDIT: these are the interfaces exposed by the gl.inet gl-ar750: hw_mode="g";
# if your device has more or differently named lan interfaces, channel = "2";
# specify them here ieee80211n = 1;
wlan wlan5 } // wirelessConfig;
lan };
];
inherit (secrets.lan) prefix;
address = {
family = "inet"; address ="${secrets.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 = {
# wan interface depends on your upstream - could be dhcp, static
# ethernet, a pppoe, ppp over serial, a complicated bonded
# failover ... who knows what else?
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;
};
firewall = {
enable = true;
rules = secrets.firewallRules;
};
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}" = { services.hostap5 = svc.hostapd.build {
interface = config.hardware.networkInterfaces.wlan; interface = config.hardware.networkInterfaces.wlan5;
hw_mode = "g"; params = rec {
channel = "2"; ssid = "liminix_5";
ieee80211n = 1; hw_mode="a";
} // wirelessConfig; channel = 36;
"${secrets.ssid}5" = rec { ht_capab = "[HT40+]";
interface = config.hardware.networkInterfaces.wlan5; vht_oper_chwidth = 1;
hw_mode = "a"; vht_oper_centr_freq_seg0_idx = channel + 6;
channel = 36; ieee80211ac = 1;
ht_capab = "[HT40+]"; } // wirelessConfig;
vht_oper_chwidth = 1; };
vht_oper_centr_freq_seg0_idx = channel + 6;
ieee80211n = 1; services.int = svc.network.address.build {
ieee80211ac = 1; interface = svc.bridge.primary.build { ifname = "int"; };
} // wirelessConfig; family = "inet"; address ="10.8.0.1"; prefixLength = 16;
}; };
services.bridge = svc.bridge.members.build {
primary = services.int;
members = with config.hardware.networkInterfaces;
[ wlan
wlan5
lan ];
}; };
services.ntp = svc.ntp.build { services.ntp = svc.ntp.build {
@ -103,20 +93,94 @@ in rec {
users.root = secrets.root; users.root = secrets.root;
services.dns =
let interface = services.int;
in svc.dnsmasq.build {
resolvconf = services.resolvconf;
inherit interface;
ranges = [
"10.8.0.10,10.8.0.240"
# ra-stateless: sends router advertisements with the O and A
# bits set, and provides a stateless DHCP service. The client
# will use a SLAAC address, and use DHCP for other
# configuration information.
"::,constructor:$(output ${interface} ifname),ra-stateless"
];
# You can add static addresses for the DHCP server here. I'm
# not putting my actual MAC addresses in a public git repo ...
hosts = { } // lib.optionalAttrs (builtins.pathExists ./static-leases.nix) (import ./static-leases.nix);
domain = "fake.liminix.org";
};
services.wan = svc.pppoe.build {
interface = config.hardware.networkInterfaces.wan;
ppp-options = [
"debug" "+ipv6" "noauth"
"name" secrets.l2tp.name
"password" secrets.l2tp.password
];
};
services.resolvconf = oneshot rec {
dependencies = [ services.wan ];
name = "resolvconf";
up = ''
. ${serviceFns}
( in_outputs ${name}
echo "nameserver $(output ${services.wan} ns1)" > resolv.conf
echo "nameserver $(output ${services.wan} ns2)" >> resolv.conf
chmod 0444 resolv.conf
)
'';
};
filesystem =
let inherit (pkgs.pseudofile) dir symlink;
in dir {
etc = dir {
"resolv.conf" = symlink "${services.resolvconf}/.outputs/resolv.conf";
};
};
services.defaultroute4 = svc.network.route.build {
via = "$(output ${services.wan} address)";
target = "default";
dependencies = [ services.wan ];
};
services.defaultroute6 = svc.network.route.build {
via = "$(output ${services.wan} ipv6-peer-address)";
target = "default";
interface = services.wan;
};
services.firewall = svc.firewall.build {
ruleset = import ./demo-firewall.nix;
};
services.packet_forwarding = svc.network.forward.build { };
services.dhcp6c =
let client = svc.dhcp6c.client.build {
interface = services.wan;
};
in bundle {
name = "dhcp6c";
contents = [
(svc.dhcp6c.prefix.build {
inherit client;
interface = services.int;
})
(svc.dhcp6c.address.build {
inherit client;
interface = services.wan;
})
];
};
defaultProfile.packages = with pkgs; [ defaultProfile.packages = with pkgs; [
min-collect-garbage min-collect-garbage
nftables
strace
tcpdump
s6
]; ];
programs.busybox = {
applets = [
"fdisk" "sfdisk"
];
options = {
FEATURE_FANCY_TAIL = "y";
};
};
} }

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,108 +0,0 @@
{ config, pkgs, lim, ... } :
let
svc = config.system.service;
in rec {
imports = [
../modules/network
../modules/ssh
../modules/vlan
../modules/wlan.nix
../modules/hostapd
../modules/bridge
../modules/ext4fs.nix
../modules/tftpboot.nix
];
rootfsType = "ext4";
boot.tftp = {
# IP addresses to use in the boot monitor when flashing/ booting
# over TFTP. If you are flashing using the stock firmware's Web UI
# then these dummy values are fine
ipaddr = "10.0.0.8"; # my address
serverip = "10.0.0.1"; # build machine or other tftp server
loadAddress = lim.parseInt "0x40000800";
};
hostname = "omnia";
services.hostap =
let secrets = {
ssid = "not-the-internet";
channel = 4;
wpa_passphrase = "diamond dogs";
};
in svc.hostapd.build {
interface = config.hardware.networkInterfaces.wlan;
params = {
country_code = "GB";
hw_mode = "g";
wmm_enabled = 1;
ieee80211n = 1;
inherit (secrets) ssid channel wpa_passphrase;
auth_algs = 1; # 1=wpa2, 2=wep, 3=both
wpa = 2; # 1=wpa, 2=wpa2, 3=both
wpa_key_mgmt = "WPA-PSK";
wpa_pairwise = "TKIP CCMP"; # auth for wpa (may not need this?)
rsn_pairwise = "CCMP"; # auth for wpa2
};
};
services.hostap5 =
let secrets = {
ssid = "not-the-internet";
channel = 36;
wpa_passphrase = "diamond dogs";
};
in svc.hostapd.build {
interface = config.hardware.networkInterfaces.wlan5;
params = {
country_code = "GB";
hw_mode = "a";
ht_capab = "[HT40+]";
vht_oper_chwidth = 1;
vht_oper_centr_freq_seg0_idx = secrets.channel + 6;
ieee80211ac = 1;
wmm_enabled = 1;
inherit (secrets) ssid channel wpa_passphrase;
auth_algs = 1; # 1=wpa2, 2=wep, 3=both
wpa = 2; # 1=wpa, 2=wpa2, 3=both
wpa_key_mgmt = "WPA-PSK";
wpa_pairwise = "TKIP CCMP"; # auth for wpa (may not need this?)
rsn_pairwise = "CCMP"; # auth for wpa2
};
};
services.int = svc.bridge.primary.build {
ifname = "int";
};
services.dhcpc = svc.network.dhcp.client.build {
interface = services.int;
dependencies = [ config.services.hostname ];
};
services.bridge = svc.bridge.members.build {
primary = services.int;
members = with config.hardware.networkInterfaces; [
lan
wlan
];
};
services.sshd = svc.ssh.build { };
users.root = {
# the password is "secret". Use mkpasswd -m sha512crypt to
# create this hashed password string
passwd = "$6$y7WZ5hM6l5nriLmo$5AJlmzQZ6WA.7uBC7S8L4o19ESR28Dg25v64/vDvvCN01Ms9QoHeGByj8lGlJ4/b.dbwR9Hq2KXurSnLigt1W1";
};
defaultProfile.packages = with pkgs; [
figlet pciutils
];
}

View File

@ -1,40 +0,0 @@
# Import all of the modules, used in the documentation generator. Not
# currently expected to work in an actual configuration, but it would
# be nice if it did.
{
imports = [
./base.nix
./bridge
./busybox.nix
./dhcp6c
./dnsmasq
./firewall
./hardware.nix
./hostapd
./hostname.nix
./kernel
./mdevd.nix
./mount
./network
./ntp
./outputs.nix
./outputs/ext4fs.nix
./outputs/initramfs.nix
./outputs/jffs2.nix
./outputs/kexecboot.nix
./outputs/mtdimage.nix
./outputs/tftpboot.nix
./outputs/ubifs.nix
./outputs/ubimage.nix
./outputs/vmroot.nix
./ppp
./ramdisk.nix
./squashfs.nix
./ssh
./users.nix
./vlan
./watchdog
./wlan.nix
];
}

View File

@ -1,4 +1,4 @@
{ lim, pkgs, config, ...}: { lib, pkgs, config, ...}:
{ {
config = { config = {
kernel.config = { kernel.config = {
@ -12,7 +12,5 @@
OF = "y"; OF = "y";
# USE_OF = "y"; # USE_OF = "y";
}; };
hardware.ram.startAddress = lim.parseInt "0x40000000";
system.outputs.u-boot = pkgs.ubootQemuAarch64;
}; };
} }

View File

@ -1,11 +0,0 @@
{ lim, pkgs, config, ...}:
{
config = {
kernel.config = {
OF = "y";
};
kernel.makeTargets = ["arch/arm/boot/zImage"];
hardware.ram.startAddress = lim.parseInt "0x40000000";
system.outputs.u-boot = pkgs.ubootQemuArm;
};
}

View File

@ -1,4 +1,4 @@
{ config, lim, ...}: { lib, pkgs, config, ...}:
{ {
config = { config = {
kernel.config = { kernel.config = {
@ -10,7 +10,6 @@
OF = "y"; OF = "y";
USE_OF = "y"; USE_OF = "y";
}; };
hardware.ram.startAddress = lim.parseInt "0x80000000";
boot.commandLine = [ boot.commandLine = [
"console=ttyS0,115200" # true of all mips we've yet encountered "console=ttyS0,115200" # true of all mips we've yet encountered
]; ];

View File

@ -1,10 +1,9 @@
{ pkgs, config, ...}: { lib, pkgs, config, ...}:
{ {
imports = [ ./mips.nix ]; imports = [ ./mips.nix ];
config = { config = {
kernel.config = { kernel.config = {
CPU_BIG_ENDIAN = "y"; CPU_BIG_ENDIAN = "y";
}; };
system.outputs.u-boot = pkgs.ubootQemuMips;
}; };
} }

View File

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

View File

@ -4,12 +4,17 @@
{ 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;
in { in {
imports = [
./kernel.nix # kernel is a separate module for doc purposes
];
options = { options = {
defaultProfile = { defaultProfile = {
packages = mkOption { packages = mkOption {
@ -24,10 +29,6 @@ in {
services = mkOption { services = mkOption {
type = types.attrsOf type_service; type = types.attrsOf type_service;
}; };
system.callService = mkOption {
type = types.functionTo (types.functionTo types.anything);
};
filesystem = mkOption { filesystem = mkOption {
type = types.anything; type = types.anything;
description = '' description = ''
@ -36,52 +37,23 @@ 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 ["squashfs" "jffs2" "ubifs"];
"btrfs"
"ext4"
"jffs2"
"squashfs"
"ubifs"
];
}; };
rootOptions = mkOption {
type = types.nullOr types.str;
default = null;
};
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 {
type = types.enum [
"bootargs"
"bootargs-override"
];
default = "bootargs";
description = "Kernel command line's devicetree node";
};
imageType = mkOption {
type = types.enum [
"primary"
"secondary"
];
default = "primary";
};
imageFormat = mkOption { imageFormat = mkOption {
type = types.enum [ type = types.enum ["fit" "uimage"];
"fit"
"uimage"
];
default = "uimage"; default = "uimage";
}; };
tftp = { tftp = {
loadAddress = mkOption { loadAddress = mkOption {
type = types.ints.unsigned; type = types.str;
description = '' description = ''
RAM address at which to load data when transferring via RAM address at which to load data when transferring via
TFTP. This is not the address of the flash storage, TFTP. This is not the address of the flash storage,
@ -92,7 +64,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
@ -117,31 +89,7 @@ in {
"root=${config.hardware.rootDevice}" "root=${config.hardware.rootDevice}"
"rootfstype=${config.rootfsType}" "rootfstype=${config.rootfsType}"
"fw_devlink=off" "fw_devlink=off"
] ++ lib.optional (config.rootOptions != null) "rootflags=${config.rootOptions}"; ];
system.callService = path : parameters :
let
typeChecked = caller: type: value:
let
inherit (lib) types mergeDefinitions;
defs = [{ file = caller; inherit value; }];
type' = types.submodule { options = type; };
in (mergeDefinitions [] type' defs).mergedValue;
cp = lib.callPackageWith(pkgs // { svc = config.system.service; });
pkg = cp path {};
checkTypes = t : p : typeChecked (builtins.toString path) t p;
in {
inherit parameters;
build = { dependencies ? [], ... } @ args :
let
s = pkg (checkTypes parameters
(builtins.removeAttrs args ["dependencies"]));
in s.overrideAttrs (o: {
dependencies = dependencies ++ o.dependencies;
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/";
@ -181,7 +129,6 @@ in {
proc = dir {}; proc = dir {};
run = dir {}; run = dir {};
sys = dir {}; sys = dir {};
tmp = dir {};
}; };
}; };
} }

View File

@ -10,11 +10,10 @@
{ 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
{ {
imports = [ ../ifwait ];
options = { options = {
system.service.bridge = { system.service.bridge = {
primary = mkOption { type = liminix.lib.types.serviceDefn; }; primary = mkOption { type = liminix.lib.types.serviceDefn; };
@ -22,13 +21,13 @@ 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";
}; };
}; };
members = config.system.callService ./members.nix { members = liminix.callService ./members.nix {
primary = mkOption { primary = mkOption {
type = liminix.lib.types.interface; type = liminix.lib.types.interface;
description = "primary bridge interface"; description = "primary bridge interface";
@ -48,5 +47,5 @@ in
# a better way to test for the existence of vlan config: # a better way to test for the existence of vlan config:
# maybe the module should set an `enabled` attribute? # maybe the module should set an `enabled` attribute?
BRIDGE_VLAN_FILTERING = "y"; BRIDGE_VLAN_FILTERING = "y";
}; };
} }

View File

@ -1,28 +1,23 @@
{ {
liminix liminix
, ifwait , ifwait
, svc , lib
}: }:
{ members, primary } : { members, primary } :
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 oneshot {
# implement ifwait as a regualr derivation instead of a name = "${primary.name}.member.${member.name}";
# servicedefinition up = ''
svc.ifwait.build { dev=$(output ${member} ifname)
state = "running"; ${ifwait}/bin/ifwait $dev running && ip link set dev $dev master $(output ${primary} ifname)
interface = member; '';
down = "ip link set dev $(output ${member} ifname) nomaster";
dependencies = [ primary member ]; dependencies = [ primary member ];
service = oneshot {
name = "${primary.name}.member.${member.name}";
up = ''
ip link set dev $(output ${member} ifname) master $(output ${primary} ifname)
'';
down = "ip link set dev $(output ${member} ifname) nomaster";
};
}; };
in bundle { in bundle {
name = "${primary.name}.members"; name = "${primary.name}.members";

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;
@ -32,21 +32,23 @@ let
(a: symlink "${busybox}/bin/busybox"); (a: symlink "${busybox}/bin/busybox");
minimalApplets = [ minimalApplets = [
# this is probably less minimal than it could be # this is probably less minimal than it could be
"arch" "ash" "base64" "basename" "bc" "brctl" "bunzip2" "bzcat" "bzip2" "arch" "ash" "base64" "basename" "bc" "brctl" "bunzip2" "bzcat"
"cal" "cat" "chattr" "chgrp" "chmod" "chown" "chpst" "chroot" "clear" "cmp" "bzip2" "cal" "cat" "chattr" "chgrp" "chmod" "chown" "chpst"
"comm" "cp" "cpio" "cut" "date" "dhcprelay" "dd" "df" "dirname" "dmesg" "chroot" "clear" "cmp" "comm" "cp" "cpio" "cut" "date" "dd" "df"
"du" "echo" "egrep" "env" "expand" "expr" "false" "fdisk" "fgrep" "find" "dirname" "dmesg" "du" "echo" "egrep" "env" "expand" "expr"
"free" "fuser" "grep" "gunzip" "gzip" "head" "hexdump" "hostname" "hwclock" "false" "fdisk" "fgrep" "find" "free" "fuser" "grep" "gunzip"
"ifconfig" "ip" "ipaddr" "iplink" "ipneigh" "iproute" "iprule" "kill" "gzip" "head" "hexdump" "hostname" "hwclock" "ifconfig" "ip"
"killall" "killall5" "less" "ln" "ls" "lsattr" "lsof" "md5sum" "mkdir" "ipaddr" "iplink" "ipneigh" "iproute" "iprule" "kill" "killall"
"mknod" "mktemp" "mount" "mv" "nc" "netstat" "nohup" "od" "pgrep" "pidof" "killall5" "less" "ln" "ls" "lsattr" "lsof" "md5sum" "mkdir"
"ping" "ping6" "pkill" "pmap" "printenv" "printf" "ps" "pwd" "readlink" "mknod" "mktemp" "mount" "mv" "nc" "netstat" "nohup" "od" "pgrep"
"realpath" "reset" "rm" "rmdir" "route" "sed" "seq" "setsid" "sha1sum" "pidof" "ping" "ping6" "pkill" "pmap" "printenv" "printf" "ps"
"sha256sum" "sha512sum" "sleep" "sort" "stat" "strings" "stty" "su" "sum" "pwd" "readlink" "realpath" "reset" "rm" "rmdir" "route" "sed"
"swapoff" "swapon" "sync" "tail" "tee" "test" "time" "touch" "tr" "seq" "setsid" "sha1sum" "sha256sum" "sha512sum" "sleep" "sort"
"traceroute" "traceroute6" "true" "truncate" "tty" "udhcpc" "umount" "stat" "strings" "stty" "su" "sum" "swapoff" "swapon" "sync"
"uname" "unexpand" "uniq" "unlink" "unlzma" "unxz" "unzip" "uptime" "watch" "tail" "tee" "test" "time" "touch" "tr" "traceroute" "traceroute6"
"wc" "whoami" "xargs" "xxd" "xz" "xzcat" "yes" "zcat" "true" "truncate" "tty" "udhcpc" "umount" "uname"
"unexpand" "uniq" "unlink" "unlzma" "unxz" "unzip" "uptime"
"watch" "wc" "whoami" "xargs" "xxd" "xz" "xzcat" "yes" "zcat"
]; ];
in { in {
options = { options = {
@ -85,13 +87,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

@ -1,32 +1,31 @@
(local { : system } (require :anoia)) (local { : system } (require :anoia))
(local svc (require :anoia.svc)) (local svc (require :anoia.svc))
(fn deletions [old-addresses new-addresses] (fn changes [old-addresses new-addresses]
(let [deleted {}] (let [added {}
deleted {}]
(each [n address (pairs new-addresses)]
(if (not (. old-addresses n))
(table.insert added address)))
(each [n address (pairs old-addresses)] (each [n address (pairs old-addresses)]
(let [now (. new-addresses n)] (if (not (. new-addresses n))
(if (or (not now) (not (= now.len address.len))) (table.insert deleted address)))
(table.insert deleted address)))) (values added deleted)))
deleted))
(fn update-prefixes [wan-device addresses new-addresses exec] (fn update-prefixes [device prefixes new-prefixes]
(each [_ p (ipairs (deletions addresses new-addresses))] (let [(added deleted) (changes prefixes new-prefixes)]
(exec (each [_ p (ipairs added)]
(.. "ip address del " p.address "1/" p.len " dev " wan-device))) (system
(each [_ p (pairs new-addresses)] (.. "ip address add " p.address "1/" p.len " dev " device)))
(exec (each [_ p (ipairs deleted)]
(.. "ip address change " p.address "1/" p.len (system
" dev " wan-device (.. "ip address del " p.address "1/" p.len " dev " device)))))
" valid_lft " p.valid
" preferred_lft " p.preferred
)))
new-addresses)
(fn run [] (fn run []
(let [[state-directory lan-device] arg (let [[state-directory lan-device] arg
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")))))
{ : changes : run } { : changes : run }

View File

@ -2,9 +2,9 @@
writeFennel writeFennel
, linotify , linotify
, anoia , anoia
, lualinux , lua
}: }:
writeFennel "acquire-delegated-prefix" { writeFennel "acquire-delegated-prefix" {
packages = [ linotify anoia lualinux ]; packages = [ linotify anoia lua.pkgs.luafilesystem ];
mainFunction = "run"; mainFunction = "run";
} ./acquire-delegated-prefix.fnl } ./acquire-delegated-prefix.fnl

View File

@ -1,124 +1,68 @@
(local subject (require :acquire-wan-address)) (local subject (require :acquire-wan-address))
(import-macros { : expect= } :anoia.assert) (local { : view } (require :fennel))
(local { : merge : dup } (require :anoia)) (local { : merge : dup } (require :anoia))
;; nix-shell --run "cd modules/dhcp6c && fennelrepl acquire-wan-address-test.fnl"
(local a1 (local a1
{ {
"2001-ab-cd-ef" { "2001-ab-cd-ef_hjgKHGhKJH" {
:address "2001:ab:cd:ef" :address "2001:ab:cd:ef"
:len "64" :len "64"
:preferred "3600" :preferred "200"
:valid "7200" :valid "200"
} }
}
)
(local a156
{
"2001-ab-cd-ef" {
:address "2001:ab:cd:ef"
:len "56"
:preferred "3600"
:valid "7200"
}
} }
) )
(local a2 (local a2
{ {
"2001-0-1-2-3" { "2001-0-1-2-3_aNteBnb" {
:address "2001:0:1:2:3" :address "2001:0:1:2:3"
:len "64" :len "64"
:preferred "3600" :preferred "200"
:valid "7200" :valid "200"
} }
} }
) )
(local a21 (macro expect [assertion]
{ (let [msg (.. "expectation failed: " (view assertion))]
"2001-0-1-2-3" { `(when (not ,assertion)
:address "2001:0:1:2:3" (assert false ,msg))))
:len "64"
:preferred "1800"
:valid "5400"
}
}
)
(fn first-address [] (fn first-address []
(let [deleted (let [(add del)
(subject.deletions (subject.changes
{ } { }
a1 a1
)] )]
(expect= deleted []))) (expect (= (# del) 0))
(expect (= (# add) 1))
(let [[first] add]
(expect (= first.address "2001:ab:cd:ef")))))
(fn second-address [] (fn second-address []
(let [del (let [(add del)
(subject.deletions (subject.changes
a1 a1
(merge (dup a1) a2) (merge (dup a1) a2)
)] )]
(expect= del []))) (expect (= (# del) 0))
(expect (= (# add) 1))
(let [[first] add] (expect (= first.address "2001:0:1:2:3")))))
(fn old-address-is-deleted [] (fn less-address []1
(let [del (let [(add del)
(subject.deletions (subject.changes
(merge (dup a1) a2) (merge (dup a1) a2)
a1 a1
)] )]
(expect= (. del 1) (. a2 "2001-0-1-2-3")) (expect (= (# add) 0))
)) (expect (= (# del) 1))
(fn changed-lifetime-not-deleted [] (let [[first] del] (expect (= first.address "2001:0:1:2:3")))))
(let [del
(subject.deletions
(merge (dup a1) a2)
(merge (dup a1) a21)
)]
;; when an address lifetime changes, "ip address change"
;; will update that so it need not (should not) be deleted
(expect= del [])))
(fn changed-prefix-is-deleted []
(let [del
(subject.deletions a1 a156)]
;; when an address prefix changes, "ip address change"
;; ignores that cjhange, so we have to remove the
;; address before reinstating it
(expect= del [(. a1 "2001-ab-cd-ef")])))
(first-address) (first-address)
(second-address) (second-address)
(old-address-is-deleted) (less-address)
(changed-lifetime-not-deleted)
(changed-prefix-is-deleted)
(let [cmds []]
(subject.update-addresses
"ppp0" a1 (merge (dup a1) a2)
(fn [a] (table.insert cmds a)))
(expect=
(doto cmds table.sort)
[
;; order of changes is unimportant
"ip address change 2001:0:1:2:3/64 dev ppp0 valid_lft 7200 preferred_lft 3600"
"ip address change 2001:ab:cd:ef/64 dev ppp0 valid_lft 7200 preferred_lft 3600"
]))
(let [cmds []]
(subject.update-addresses
"ppp0" (merge (dup a1) a2) a1
(fn [a] (table.insert cmds a)))
(expect=
cmds
[
;; deletes are executed before changes
"ip address del 2001:0:1:2:3/64 dev ppp0"
"ip address change 2001:ab:cd:ef/64 dev ppp0 valid_lft 7200 preferred_lft 3600"
]))
(print "OK")

View File

@ -1,32 +1,35 @@
(local { : system } (require :anoia)) (local { : system } (require :anoia))
(local svc (require :anoia.svc)) (local svc (require :anoia.svc))
(fn deletions [old-addresses new-addresses] ;; acquire-delegated-prefix has very similar code: we'd like to move
(let [deleted {}] ;; this to anoia.svc when we see what the general form would look like
(each [n address (pairs old-addresses)]
(let [now (. new-addresses n)]
(if (or (not now) (not (= now.len address.len)))
(table.insert deleted address))))
deleted))
(fn update-addresses [wan-device addresses new-addresses exec] (fn changes [old-addresses new-addresses]
(each [_ p (ipairs (deletions addresses new-addresses))] (let [added {}
(exec deleted {}]
(.. "ip address del " p.address "/" p.len " dev " wan-device))) (each [n address (pairs new-addresses)]
(each [_ p (pairs new-addresses)] (if (not (. old-addresses n))
(exec (table.insert added address)))
(.. "ip address change " p.address "/" p.len (each [n address (pairs old-addresses)]
" dev " wan-device (if (not (. new-addresses n))
" valid_lft " p.valid (table.insert deleted address)))
" preferred_lft " p.preferred (values added deleted)))
)))
new-addresses) (fn update-addresses [wan-device addresses new-addresses]
(let [(added deleted) (changes addresses new-addresses)]
(each [_ p (ipairs added)]
(system
(.. "ip address add " p.address "/" p.len " dev " wan-device)))
(each [_ p (ipairs deleted)]
(system
(.. "ip address del " p.address "/" p.len " dev " wan-device)))
new-addresses))
(fn run [] (fn run []
(let [[state-directory wan-device] arg (let [[state-directory wan-device] arg
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")))))
{ : update-addresses : deletions : run } { : update-addresses : changes : run }

View File

@ -2,9 +2,9 @@
writeFennel writeFennel
, linotify , linotify
, anoia , anoia
, lualinux , lua
}: }:
writeFennel "acquire-wan-address" { writeFennel "acquire-wan-address" {
packages = [ linotify anoia lualinux ]; packages = [ linotify anoia lua.pkgs.luafilesystem ];
mainFunction = "run"; mainFunction = "run";
} ./acquire-wan-address.fnl } ./acquire-wan-address.fnl

View File

@ -1,14 +1,16 @@
{ {
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 {
inherit name; inherit name;
run = "${script} $SERVICE_OUTPUTS/${client.name} $(output ${interface} ifname)"; run = "${script} /run/service-state/${client.name} $(output ${interface} ifname)";
dependencies = [ client interface ]; dependencies = [ client interface ];
} }

View File

@ -1,17 +1,19 @@
{ {
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;
notification-fd = 10; notification-fd = 10;
run = '' run = ''
export SERVICE_STATE=$SERVICE_OUTPUTS/${name} export SERVICE_STATE=/run/service-state/${name}
${odhcp6c}/bin/odhcp6c -s ${odhcp-script} -e -v -p /run/${name}.pid -P0 $(output ${interface} ifname) ${odhcp6c}/bin/odhcp6c -s ${odhcp-script} -e -v -p /run/${name}.pid -P0 $(output ${interface} ifname)
) )
''; '';

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,14 +1,16 @@
{ {
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 {
inherit name; inherit name;
run = "${script} $SERVICE_OUTPUTS/${client.name} $(output ${interface} ifname)"; run = "${script} /run/service-state/${client.name} $(output ${interface} ifname)";
dependencies = [ client interface ]; dependencies = [ client interface ];
} }

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

@ -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,6 +26,7 @@ longrun {
inherit name; inherit name;
dependencies = [ interface ]; dependencies = [ interface ];
run = '' run = ''
. ${serviceFns}
${dnsmasq}/bin/dnsmasq \ ${dnsmasq}/bin/dnsmasq \
--user=${user} \ --user=${user} \
--domain=${domain} \ --domain=${domain} \
@ -40,11 +41,10 @@ longrun {
--no-hosts \ --no-hosts \
--log-dhcp \ --log-dhcp \
--enable-ra \ --enable-ra \
--log-debug \
--log-queries \
--log-facility=- \ --log-facility=- \
--dhcp-leasefile=$(mkstate ${name})/leases \ --dhcp-leasefile=/run/${name}.leases \
--pid-file=/run/${name}.pid --pid-file=/run/${name}.pid
''; '';
# --log-debug \
# --log-queries \
} }

View File

@ -8,42 +8,36 @@
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 { kconf = isModule :
inherit (config.system.outputs) kernel; # setting isModule false is utterly untested and mostly
# unimplemented: I say this to preempt any "how on earth is this
# even supposed to work?" questions
let yes = if isModule then "m" else "y";
in {
NFT_FIB_IPV4 = yes;
NFT_FIB_IPV6 = yes;
NF_TABLES = yes;
NF_CT_PROTO_DCCP = "y";
NF_CT_PROTO_SCTP = "y";
NF_CT_PROTO_UDPLITE = "y";
# NF_CONNTRACK_FTP = yes;
NFT_CT = yes;
};
kmodules = pkgs.kernel-modules.override {
kernelSrc = config.system.outputs.kernel.src;
modulesupport = config.system.outputs.kernel.modulesupport;
targets = [ targets = [
"nft_fib_ipv4" "nft_fib_ipv4"
"nft_fib_ipv6" "nft_fib_ipv6"
"nf_log_syslog"
"ip6_tables"
"ip_tables"
"iptable_nat"
"nf_conntrack"
"nf_defrag_ipv4"
"nf_defrag_ipv6"
"nf_log_syslog"
"nf_nat"
"nf_reject_ipv4"
"nf_reject_ipv6"
"nf_tables"
"nft_chain_nat"
"nft_ct"
"nft_fib"
"nft_fib_ipv4"
"nft_fib_ipv6"
"nft_log"
"nft_masq"
"nft_nat"
"nft_reject"
"nft_reject_inet"
"nft_reject_ipv4"
"nft_reject_ipv6"
"x_tables"
"xt_MASQUERADE"
"xt_nat"
"xt_tcpudp"
]; ];
kconfig = kconf true;
};
loadModules = oneshot {
name = "firewall-modules";
up = "sh ${kmodules}/load.sh";
down = "sh ${kmodules}/unload.sh";
}; };
in in
{ {
@ -54,57 +48,45 @@ in
}; };
config = { config = {
system.service.firewall = system.service.firewall =
let svc = config.system.callService ./service.nix { let svc = liminix.callService ./service.nix {
extraRules = mkOption { ruleset = mkOption {
type = types.attrsOf types.attrs;
description = "firewall ruleset";
default = {};
};
rules = mkOption {
type = types.attrsOf types.attrs; # we could usefully tighten this a bit :-) type = types.attrsOf types.attrs; # we could usefully tighten this a bit :-)
default = import ./default-rules.nix;
description = "firewall ruleset"; description = "firewall ruleset";
}; };
}; };
in svc // { in svc // {
build = args : build = args : (svc.build args) // {
let args' = args // { dependencies = [ loadModules ] ++ (svc.dependencies or []);
dependencies = (args.dependencies or []) ++ [kmodules]; };
};
in svc.build args' ;
}; };
programs.busybox.applets = [
"insmod" "rmmod" # For historical reasons the kernel config is split between
]; # monolithic options and modules. TODO: go through this list
# and see what can be moved into the "kconf" definiton above
kernel.config = { kernel.config = {
NETFILTER_XT_MATCH_CONNTRACK = "y";
IP6_NF_IPTABLES= "y";
IP_NF_IPTABLES= "y";
IP_NF_NAT = "y";
IP_NF_TARGET_MASQUERADE = "y";
NETFILTER = "y"; NETFILTER = "y";
NETFILTER_ADVANCED = "y"; NETFILTER_ADVANCED = "y";
NETFILTER_NETLINK = "m"; NETFILTER_XTABLES = "y";
NF_CONNTRACK = "m";
NETLINK_DIAG = "y"; NFT_COMPAT = "y";
NFT_CT = "y";
NFT_LOG = "y";
NFT_MASQ = "y";
NFT_NAT = "y";
NFT_REJECT = "y";
NFT_REJECT_INET = "y";
IP6_NF_IPTABLES= "m"; NF_CONNTRACK = "y";
IP_NF_IPTABLES = "m"; NF_NAT = "y";
IP_NF_NAT = "m"; NF_NAT_MASQUERADE = "y";
IP_NF_TARGET_MASQUERADE = "m"; NF_TABLES= "y";
NFT_CT = "m";
NFT_FIB_IPV4 = "m";
NFT_FIB_IPV6 = "m";
NFT_LOG = "m";
NFT_MASQ = "m";
NFT_NAT = "m";
NFT_REJECT = "m";
NFT_REJECT_INET = "m";
NF_CT_PROTO_DCCP = "y";
NF_CT_PROTO_SCTP = "y";
NF_CT_PROTO_UDPLITE = "y";
NF_LOG_SYSLOG = "m";
NF_NAT = "m";
NF_NAT_MASQUERADE = "y";
NF_TABLES = "m";
NF_TABLES_INET = "y"; NF_TABLES_INET = "y";
NF_TABLES_IPV4 = "y"; NF_TABLES_IPV4 = "y";
NF_TABLES_IPV6 = "y"; NF_TABLES_IPV6 = "y";

View File

@ -4,10 +4,12 @@
, firewallgen , firewallgen
, nftables , nftables
}: }:
{ rules, extraRules }: { ruleset }:
let let
inherit (liminix.services) oneshot; inherit (liminix.services) oneshot;
script = firewallgen "firewall.nft" (lib.recursiveUpdate rules extraRules); inherit (liminix.lib) typeChecked;
inherit (lib) mkOption types;
script = firewallgen "firewall.nft" ruleset;
in oneshot { in oneshot {
name = "firewall"; name = "firewall";
up = script; up = script;

View File

@ -11,7 +11,7 @@ in {
options.system.outputs = { options.system.outputs = {
firmware = mkOption { firmware = mkOption {
type = types.package; type = types.package;
internal = true; # component of mtdimage internal = true; # component of flashimage
description = '' description = ''
Binary image (combining kernel, FDT, rootfs, initramfs Binary image (combining kernel, FDT, rootfs, initramfs
if needed, etc) for the target device. if needed, etc) for the target device.
@ -19,40 +19,17 @@ in {
}; };
flash-scr = mkOption { flash-scr = mkOption {
type = types.package; type = types.package;
internal = true; # component of mtdimage internal = true; # component of flashimage
description = '' description = ''
Copy-pastable U-Boot commands to TFTP download the Copy-pastable U-Boot commands to TFTP download the
image and write it to flash image and write it to flash
''; '';
}; };
mtdimage = mkOption { flashimage = mkOption {
type = types.package; type = types.package;
description = '' description = ''
mtdimage Flashable image for the target device, and the script to
********** install it
This creates an image called :file:`firmware.bin` suitable for
squashfs or jffs2 systems. It can be flashed from U-Boot (if
you have a serial console connection), or on some devices from
the vendor firmware, or from a Liminix kexecboot system.
If you are flashing from U-Boot, the file
:file:`flash.scr` is a sequence of commands
which you can paste at the U-Boot prompt to
to transfer the firmware file from a TFTP server and
write it to flash. **Please read the script before
running it: flash operations carry the potential to
brick your device**
.. 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
the whole of :file:`flash.scr` into a terminal emulator and
have it work just like that. You may need to paste each
line one at a time, or even retype it.
''; '';
}; };
}; };
@ -75,15 +52,15 @@ in {
firmware = firmware =
let let
o = config.system.outputs; o = config.system.outputs;
bs = toString config.hardware.flash.eraseBlockSize; bs = config.hardware.flash.eraseBlockSize;
in pkgs.runCommand "firmware" {} '' in pkgs.runCommand "firmware" {} ''
dd if=${o.uimage} of=$out bs=${bs} conv=sync dd if=${o.uimage} of=$out bs=${bs} conv=sync
dd if=${o.rootfs} of=$out bs=${bs} conv=sync,nocreat,notrunc oflag=append dd if=${o.rootfs} of=$out bs=${bs} conv=sync,nocreat,notrunc oflag=append
''; '';
mtdimage = flashimage =
let o = config.system.outputs; in let o = config.system.outputs; in
# could use trivial-builders.linkFarmFromDrvs here? # could use trivial-builders.linkFarmFromDrvs here?
pkgs.runCommand "mtdimage" {} '' pkgs.runCommand "flashimage" {} ''
mkdir $out mkdir $out
cd $out cd $out
ln -s ${o.firmware} firmware.bin ln -s ${o.firmware} firmware.bin
@ -106,9 +83,9 @@ in {
cat > $out << EOF cat > $out << EOF
setenv serverip ${tftp.serverip} setenv serverip ${tftp.serverip}
setenv ipaddr ${tftp.ipaddr} setenv ipaddr ${tftp.ipaddr}
tftp 0x${toHexString tftp.loadAddress} result/firmware.bin tftp 0x$(printf %x ${tftp.loadAddress}) result/firmware.bin
erase 0x${toHexString flash.address} +0x${toHexString flash.size} erase 0x$(printf %x ${flash.address}) +${flash.size}
cp.b 0x${toHexString tftp.loadAddress} 0x${toHexString flash.address} \''${filesize} cp.b 0x$(printf %x ${tftp.loadAddress}) 0x$(printf %x ${flash.address}) \''${filesize}
echo command line was ${builtins.toJSON (concatStringsSep " " config.boot.commandLine)} echo command line was ${builtins.toJSON (concatStringsSep " " config.boot.commandLine)}
EOF EOF
''; '';

View File

@ -5,41 +5,30 @@
## 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 {
type = types.nullOr types.path; type = types.path;
description = '' description = "Path to the device tree source (usually from OpenWrt)";
If the device requires an external device tree to be loaded
alongside the kernel, this is the path to the device tree source
(we usually get these from OpenWrt). This value may be null if the
platform creates the device tree - currently this is the case
only for QEMU.
'';
}; };
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;
}; };
}; };
defaultOutput = mkOption { defaultOutput = mkOption {
description = "\"Default\" output: what gets built for this device when outputs.default is requested. Typically this is \"mtdimage\" or \"vmroot\""; description = "\"Default\" output: what gets built for this device when outputs.default is requested. Typically this is \"flashimage\" or \"vmroot\"";
type = types.nonEmptyStr; type = types.nonEmptyStr;
}; };
ram = {
startAddress = mkOption {
type = types.int;
};
};
flash = { flash = {
# start address and size of whichever partition (often # start address and size of whichever partition (often
# called "firmware") we're going to overwrite with our # called "firmware") we're going to overwrite with our
@ -53,20 +42,19 @@ in
kernel uimage and root fs. Usually not the entire flash, as kernel uimage and root fs. Usually not the entire flash, as
we don't want to clobber the bootloader, calibration data etc we don't want to clobber the bootloader, calibration data etc
''; '';
type = types.ints.unsigned; type = types.str;
}; };
size = mkOption { size = mkOption {
type = types.ints.unsigned; type = types.str;
description = "Size in bytes of the firmware partition"; description = "Size in bytes of the firmware partition";
}; };
eraseBlockSize = mkOption { eraseBlockSize = mkOption {
description = "Flash erase block size in bytes"; description = "Flash erase block size in bytes";
type = types.ints.unsigned; type = types.str;
}; };
}; };
loadAddress = mkOption { type = types.ints.unsigned; default = null; }; loadAddress = mkOption { default = null; };
entryPoint = mkOption { type = types.ints.unsigned; }; entryPoint = mkOption { };
alignment = mkOption { type = types.nullOr types.ints.unsigned; default = null; description = "Alignment passed to `mkimage` for FIT"; };
radios = mkOption { radios = mkOption {
description = '' description = ''
Kernel modules (from mac80211 package) required for the Kernel modules (from mac80211 package) required for the
@ -76,11 +64,7 @@ in
default = []; default = [];
example = ["ath9k" "ath10k"]; example = ["ath9k" "ath10k"];
}; };
rootDevice = mkOption { rootDevice = mkOption { };
description = "Full path to preferred root device";
type = types.str;
example = "/dev/mtdblock3";
};
networkInterfaces = mkOption { networkInterfaces = mkOption {
type = types.attrsOf types.anything; type = types.attrsOf types.anything;
}; };

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

@ -1,18 +0,0 @@
{ config, pkgs, lib, ... } :
let
inherit (pkgs) liminix;
inherit (lib) mkOption types;
in {
options.system.service.ifwait =
mkOption { type = liminix.lib.types.serviceDefn; };
config.system.service.ifwait = config.system.callService ./ifwait.nix {
state = mkOption { type = types.str; };
interface = mkOption {
type = liminix.lib.types.interface;
};
service = mkOption {
type = liminix.lib.types.service;
};
};
}

View File

@ -1,16 +0,0 @@
{ ifwait, liminix } :
{
state
, interface
, service
}:
let
inherit (liminix.services) longrun;
in longrun {
name = "ifwait.${interface.name}";
buildInputs = [ service ];
restart-on-upgrade = true;
run = ''
${ifwait}/bin/ifwait -s ${service.name} $(output ${interface} ifname) ${state}
'';
}

View File

@ -6,30 +6,20 @@
}: }:
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 = {
boot.initramfs = { boot.initramfs = {
enable = mkEnableOption "initramfs"; enable = mkEnableOption "initramfs";
}; };
system.outputs = { system.outputs.initramfs = mkOption {
initramfs = mkOption { type = types.package;
type = types.package; internal = true;
internal = true; description = ''
description = '' Initramfs image capable of mounting the jffs2 root
Initramfs image capable of mounting the real root filesystem
filesystem '';
'';
};
systemConfiguration = mkOption {
type = types.package;
description = ''
pkgs.systemconfig for the configured filesystem,
contains 'activate' and 'init' commands
'';
internal = true;
};
}; };
}; };
config = mkIf config.boot.initramfs.enable { config = mkIf config.boot.initramfs.enable {
@ -47,14 +37,18 @@ in
dir /proc 0755 0 0 dir /proc 0755 0 0
dir /dev 0755 0 0 dir /dev 0755 0 0
nod /dev/console 0600 0 0 c 5 1 nod /dev/console 0600 0 0 c 5 1
nod /dev/mtdblock0 0600 0 0 b 31 0
nod /dev/mtdblock1 0600 0 0 b 31 1
nod /dev/mtdblock2 0600 0 0 b 31 2
nod /dev/mtdblock3 0600 0 0 b 31 3
nod /dev/mtdblock4 0600 0 0 b 31 4
nod /dev/mtdblock5 0600 0 0 b 31 5
dir /target 0755 0 0 dir /target 0755 0 0
dir /target/persist 0755 0 0 dir /target/persist 0755 0 0
dir /target/nix 0755 0 0 dir /target/nix 0755 0 0
file /init ${pkgs.preinit}/bin/preinit 0755 0 0 file /init ${pkgs.preinit}/bin/preinit 0755 0 0
SPECIALS SPECIALS
''; '';
systemConfiguration =
pkgs.systemconfig config.filesystem.contents;
}; };
}; };
} }

54
modules/jffs2.nix Normal file
View File

@ -0,0 +1,54 @@
{
config
, pkgs
, lib
, ...
}:
let
inherit (lib) mkIf mkOption types;
in
{
imports = [
./initramfs.nix
];
options.system.outputs = {
systemConfiguration = mkOption {
type = types.package;
description = ''
pkgs.systemconfig for the configured filesystem,
contains 'activate' and 'init' commands
'';
internal = true;
};
};
config = mkIf (config.rootfsType == "jffs2") {
kernel.config = {
JFFS2_FS = "y";
JFFS2_LZO = "y";
JFFS2_RTIME = "y";
JFFS2_COMPRESSION_OPTIONS = "y";
JFFS2_ZLIB = "y";
JFFS2_CMODE_SIZE = "y";
};
boot.initramfs.enable = true;
system.outputs = rec {
systemConfiguration =
pkgs.systemconfig config.filesystem.contents;
rootfs =
let
inherit (pkgs.pkgsBuildBuild) runCommand mtdutils;
endian = if pkgs.stdenv.isBigEndian
then "--big-endian" else "--little-endian";
in runCommand "make-jffs2" {
depsBuildBuild = [ mtdutils ];
} ''
mkdir -p $TMPDIR/empty/nix/store/ $TMPDIR/empty/secrets
cp ${systemConfiguration}/bin/activate $TMPDIR/empty/activate
ln -s ${pkgs.s6-init-bin}/bin/init $TMPDIR/empty/init
grafts=$(sed < ${systemConfiguration}/etc/nix-store-paths 's/^\(.*\)$/--graft \1:\1/g')
mkfs.jffs2 --compression-mode=size ${endian} -e ${config.hardware.flash.eraseBlockSize} --enable-compressor=lzo --pad --root $TMPDIR/empty --output $out $grafts --squash --faketime
'';
};
};
}

View File

@ -5,24 +5,17 @@
{ lib, pkgs, config, ...}: { lib, pkgs, config, ...}:
let let
inherit (lib) mkOption types ; inherit (lib) mkEnableOption mkOption types isDerivation hasAttr ;
inherit (pkgs) liminix; inherit (pkgs.pseudofile) dir symlink;
inherit (pkgs.liminix.networking) address interface;
inherit (pkgs.liminix.services) bundle;
type_service = pkgs.liminix.lib.types.service;
mergeConditionals = conf : conditions :
# for each key in conditions, if it is present in conf
# then merge the associated value into conf
lib.foldlAttrs
(acc: name: value:
if (conf ? ${name}) && (conf.${name} != "n")
then acc // value
else acc)
conf
conditions;
in { in {
options = { options = {
kernel = { kernel = {
src = mkOption { type = types.path; } ; src = mkOption { type = types.package; } ;
version = mkOption { type = types.str; default = "5.15.137";} ;
modular = mkOption { modular = mkOption {
type = types.bool; type = types.bool;
default = true; default = true;
@ -48,44 +41,11 @@ in {
}; };
''; '';
}; };
conditionalConfig = mkOption {
description = ''
Kernel config options that should only be applied when
some other option is present.
'';
type = types.attrsOf (types.attrsOf types.nonEmptyStr);
default = {};
example = {
USB = {
USB_XHCI_MVEBU = "y";
USB_XHCI_HCD = "y";
};
};
};
makeTargets = mkOption {
type = types.listOf types.str;
};
}; };
}; };
config = { config = {
system.outputs =
let
mergedConfig = mergeConditionals
config.kernel.config
config.kernel.conditionalConfig;
k = liminix.builders.kernel.override {
config = mergedConfig;
inherit (config.kernel) version src extraPatchPhase;
targets = config.kernel.makeTargets;
};
in {
kernel = k.vmlinux;
zimage = k.zImage;
};
kernel = rec { kernel = rec {
modular = true; # disabling this is not yet supported modular = true; # disabling this is not yet supported
makeTargets = ["vmlinux"];
config = { config = {
IKCONFIG = "y"; IKCONFIG = "y";
IKCONFIG_PROC = "y"; IKCONFIG_PROC = "y";

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

@ -1,32 +0,0 @@
{ config, pkgs, ...} :
let inherit (pkgs.liminix.services) oneshot longrun;
in {
config = {
services = rec {
mdevd = longrun {
name = "mdevd";
notification-fd = 3;
run = "${pkgs.mdevd}/bin/mdevd -D 3 -b 200000 -O4";
};
devout = longrun {
name = "devout";
notification-fd = 10;
timeout-up = 60 * 1000;
run = "exec ${pkgs.devout}/bin/devout /run/devout.sock 4";
dependencies = [ mdevd ];
};
coldplug = oneshot {
name = "coldplug";
# would love to know what mdevd-coldplug/udevadm trigger does
# that this doesn't
up = ''
for i in $(find /sys -name uevent); do ( echo change > $i ) ; done
'';
dependencies = [
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,39 +19,28 @@ in {
type = liminix.lib.types.serviceDefn; type = liminix.lib.types.serviceDefn;
}; };
}; };
imports = [ ../mdevd.nix ../uevent-rule ]; config.system.service = {
config.system.service.mount = mount = liminix.callService ./service.nix {
let svc = config.system.callService ./service.nix { device = mkOption {
partlabel = mkOption { type = types.str;
type = types.str; example = "/dev/sda1";
example = "my-usb-stick"; };
}; mountpoint = mkOption {
mountpoint = mkOption { type = types.str;
type = types.str; example = "/mnt/media";
example = "/mnt/media"; };
}; options = mkOption {
options = mkOption { type = types.listOf types.str;
type = types.listOf types.str; default = [];
default = []; example = ["noatime" "ro" "sync"];
example = ["noatime" "ro" "sync"]; };
}; fstype = mkOption {
fstype = mkOption { type = types.str;
type = types.str; default = "auto";
default = "auto"; example = "vfat";
example = "vfat"; };
};
};
in svc // {
build = args:
let args' = args // {
dependencies = (args.dependencies or []) ++ [
config.services.mdevd
config.services.devout
];
};
in svc.build args' ;
}; };
};
config.programs.busybox = { config.programs.busybox = {
applets = ["blkid" "findfs"]; applets = ["blkid" "findfs"];
options = { options = {

View File

@ -1,27 +1,18 @@
{ {
liminix liminix
, lib , lib
, svc
}: }:
{ partlabel, mountpoint, options, fstype }: { device, mountpoint, options, fstype }:
let let
inherit (liminix.services) oneshot; inherit (liminix.services) oneshot;
device = "/dev/disk/by-partlabel/${partlabel}";
name = "mount.${lib.strings.sanitizeDerivationName (lib.escapeURL mountpoint)}";
options_string =
if options == [] then "" else "-o ${lib.concatStringsSep "," options}";
controller = svc.uevent-rule.build {
serviceName = name;
symlink = device;
terms = {
partname = partlabel;
devtype = "partition";
};
};
in oneshot { in oneshot {
inherit name; name = "mount.${lib.escapeURL mountpoint}";
timeout-up = 3600; up = ''
up = "mount -t ${fstype} ${options_string} ${device} ${mountpoint}"; while ! findfs ${device}; do
echo waiting for device ${device}
sleep 1
done
mount -t ${fstype} -o ${lib.concatStringsSep "," options} ${device} ${mountpoint}
'';
down = "umount ${mountpoint}"; down = "umount ${mountpoint}";
inherit controller;
} }

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,32 +64,18 @@ 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";
description = ''
Device name as used by the kernel (as seen in "ip link"
or "ifconfig" output). If devpath is also specified, the
device will be renamed to the name provided.
'';
}; };
devpath = mkOption {
type = types.nullOr types.str;
default = null;
example = "/devices/platform/soc/soc:internal-regs/f1070000.ethernet";
description = ''
Path to the sysfs node of the device. If you provide this
and the ifname option, the device will be renamed to the
name given by ifname.
''; };
# other "ip link add" options could go here as well # other "ip link add" options could go here as well
mtu = mkOption { mtu = mkOption {
type = types.nullOr types.int; type = types.nullOr types.int;
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 +90,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 +111,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 +122,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,27 +1,16 @@
{ {
liminix liminix
, ifwait
, serviceFns
, lib , lib
}: }:
{ {ifname, mtu} :
ifname
, devpath ? null
, mtu} :
# if devpath is supplied, we rename the interface at that
# 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 up = liminix.networking.ifup name ifname;
then ''
oldname=$(cd /sys${devpath} && cd net/ && echo *)
ip link set ''${oldname} name ${ifname}
''
else "";
in oneshot { in oneshot {
inherit name; inherit name up;
up = ''
${rename}
${liminix.networking.ifup name ifname}
'';
down = "ip link set down dev ${ifname}"; down = "ip link set down dev ${ifname}";
} }

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,142 +1,110 @@
{ {
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; arch = let s = pkgs.stdenv; in
if s.isAarch64
then "aarch64"
else if s.isMips
then "mips"
else throw "can't determine arch";
in in
{ {
imports = [ imports = [
./squashfs.nix ./squashfs.nix
./outputs/vmroot.nix
./outputs/extlinux.nix
]; ];
options = { options = {
system.outputs = { system.outputs = {
# the convention here is to mark an output as "internal" if
# it's not a complete system (kernel plus userland, or installer)
# but only part of one.
kernel = mkOption { kernel = mkOption {
type = types.package; type = types.package;
internal = true;
description = '' description = ''
kernel
******
Kernel vmlinux file (usually ELF) Kernel vmlinux file (usually ELF)
''; '';
}; };
zimage = mkOption {
type = types.package;
internal = true;
description = ''
zimage
******
Kernel in compressed self-extracting package
'';
};
dtb = mkOption { dtb = mkOption {
type = types.package; type = types.package;
internal = true;
description = '' description = ''
dtb
***
Compiled device tree (FDT) for the target device Compiled device tree (FDT) for the target device
''; '';
}; };
uimage = mkOption { uimage = mkOption {
type = types.package; type = types.package;
internal = true;
description = '' description = ''
uimage
******
Combined kernel and FDT in uImage (U-Boot compatible) format Combined kernel and FDT in uImage (U-Boot compatible) format
''; '';
}; };
tplink-safeloader = mkOption { vmroot = mkOption {
type = types.package;
};
u-boot = mkOption {
type = types.package; type = types.package;
description = ''
Directory containing separate kernel and rootfs image for
use with qemu (see run-liminix-vm)
'';
}; };
manifest = mkOption { manifest = mkOption {
type = types.package; type = types.package;
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
out what's in the image, which is nice if it's unexpectedly huge out what's in the image, which is nice if it's unexpectedly huge
''; '';
}; };
rootdir = mkOption {
type = types.package;
internal = true;
description = ''
directory of files to package into root filesystem
'';
};
bootablerootdir = mkOption {
type = types.package;
internal = true;
description = ''
directory of files to package into root filesystem, including
a kernel and appropriate associated gubbins for the
selected bootloader
'';
};
rootfs = mkOption { rootfs = mkOption {
type = types.package; type = types.package;
internal = true;
description = '' description = ''
root filesystem (squashfs or jffs2) image root filesystem (squashfs or jffs2) image
''; '';
internal = true;
}; };
}; };
}; };
config = { config = {
system.outputs = rec { system.outputs = rec {
# tftpd = pkgs.buildPackages.tufted;
kernel = liminix.builders.kernel.override {
inherit (config.kernel) config src extraPatchPhase;
};
dtb = liminix.builders.dtb { dtb = liminix.builders.dtb {
inherit (config.boot) commandLine; inherit (config.boot) commandLine;
dts = config.hardware.dts.src; dts = config.hardware.dts.src;
includes = config.hardware.dts.includes ++ [ includes = config.hardware.dts.includes ++ [
"${o.kernel.headers}/include" "${kernel.headers}/include"
]; ];
}; };
uimage = liminix.builders.uimage { uimage = liminix.builders.uimage {
commandLine = concatStringsSep " " config.boot.commandLine; commandLine = concatStringsSep " " config.boot.commandLine;
inherit (config.boot) commandLineDtbNode; inherit (config.hardware) loadAddress entryPoint;
inherit (config.hardware) loadAddress entryPoint alignment;
inherit (config.boot) imageFormat; inherit (config.boot) imageFormat;
inherit (o) kernel dtb; inherit kernel;
inherit dtb;
}; };
rootdir = # could use trivial-builders.linkFarmFromDrvs here?
vmroot =
let let
inherit (pkgs.pkgsBuildBuild) runCommand; cmdline = builtins.toJSON (concatStringsSep " " config.boot.commandLine);
in runCommand "mktree" { } '' makeBootableImage = pkgs.runCommandCC "objcopy" {}
mkdir -p $out/nix/store/ $out/secrets $out/boot (if pkgs.stdenv.isAarch64
cp ${o.systemConfiguration}/bin/activate $out/activate then "${pkgs.stdenv.cc.targetPrefix}objcopy -O binary -S ${kernel} $out"
ln -s ${pkgs.s6-init-bin}/bin/init $out/init else "cp ${kernel} $out");
mkdir -p $out/nix/store in pkgs.runCommandCC "vmroot" {} ''
for path in $(cat ${o.systemConfiguration}/etc/nix-store-paths) ; do mkdir $out
(cd $out && cp -a $path .$path) cd $out
done ln -s ${config.system.outputs.rootfs} rootfs
''; ln -s ${kernel} vmlinux
bootablerootdir = ln -s ${manifest} manifest
let inherit (pkgs.pkgsBuildBuild) runCommand; ln -s ${kernel.headers} build
in runCommand "add-slash-boot" { } '' echo ${cmdline} > commandline
cp -a ${o.rootdir} $out cat > run.sh << EOF
${if config.boot.loader.extlinux.enable #!${pkgs.runtimeShell}
then "(cd $out && chmod -R +w . && rmdir boot && cp -a ${o.extlinux} boot)" CMDLINE=${cmdline} ${pkgs.pkgsBuildBuild.run-liminix-vm}/bin/run-liminix-vm --arch ${arch} \$* ${makeBootableImage} ${config.system.outputs.rootfs}
else "" EOF
} chmod +x run.sh
''; '';
manifest = writeText "manifest.json" (builtins.toJSON config.filesystem.contents); manifest = writeText "manifest.json" (builtins.toJSON config.filesystem.contents);
}; };
}; };

View File

@ -1,37 +0,0 @@
{
config
, pkgs
, lib
, ...
}:
let
inherit (lib) mkIf;
o = config.system.outputs;
in
{
imports = [
./initramfs.nix
];
config = mkIf (config.rootfsType == "btrfs") {
kernel.config = {
BTRFS_FS = "y";
};
boot.initramfs.enable = true;
system.outputs = {
rootfs =
let
inherit (pkgs.pkgsBuildBuild) runCommand e2fsprogs;
in runCommand "mkfs.btrfs" {
depsBuildBuild = [ e2fsprogs ];
} ''
tree=${o.bootablerootdir}
size=$(du -s --apparent-size --block-size 1024 $tree |cut -f1)
# add 25% for filesystem overhead
size=$(( 5 * $size / 4))
dd if=/dev/zero of=$out bs=1024 count=$size
echo "not implemented" ; exit 1
# mke2fs -t ext4 -j -d $tree $out
'';
};
};
}

View File

@ -1,38 +0,0 @@
{
config
, pkgs
, lib
, ...
}:
let
inherit (lib) mkIf;
o = config.system.outputs;
in
{
imports = [
./initramfs.nix
];
config = mkIf (config.rootfsType == "ext4") {
kernel.config = {
EXT4_FS = "y";
EXT4_USE_FOR_EXT2 = "y";
FS_ENCRYPTION = "y";
};
boot.initramfs.enable = true;
system.outputs = {
rootfs =
let
inherit (pkgs.pkgsBuildBuild) runCommand e2fsprogs;
in runCommand "mkfs.ext4" {
depsBuildBuild = [ e2fsprogs ];
} ''
tree=${o.bootablerootdir}
size=$(du -s --apparent-size --block-size 1024 $tree |cut -f1)
# add 25% for filesystem overhead
size=$(( 5 * $size / 4))
dd if=/dev/zero of=$out bs=1024 count=$size
mke2fs -t ext4 -j -d $tree $out
'';
};
};
}

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