Compare commits
11 Commits
main
...
module-bas
Author | SHA1 | Date |
---|---|---|
Daniel Barlow | b326b685de | |
Daniel Barlow | d7209f33c8 | |
Daniel Barlow | 999a11f89c | |
Daniel Barlow | 27c9bd9707 | |
Daniel Barlow | 540d2fcf87 | |
Daniel Barlow | d1fea06959 | |
Daniel Barlow | 5e37f2b99a | |
Daniel Barlow | d83f8716ea | |
Daniel Barlow | 83c451dd8f | |
Daniel Barlow | fbec31be79 | |
Daniel Barlow | 6ae062f5e4 |
|
@ -5,5 +5,3 @@ result-*
|
||||||
*.qcow2
|
*.qcow2
|
||||||
_build
|
_build
|
||||||
*-secrets.nix
|
*-secrets.nix
|
||||||
examples/static-leases.nix
|
|
||||||
/doc/hardware.rst
|
|
||||||
|
|
117
NEWS
117
NEWS
|
@ -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.
|
|
||||||
|
|
22
README.md
22
README.md
|
@ -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/)
|
||||||
|
|
4078
THOUGHTS.txt
4078
THOUGHTS.txt
File diff suppressed because it is too large
Load Diff
20
boot.expect
20
boot.expect
|
@ -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
|
|
|
@ -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,61 +76,36 @@ 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 ; };
|
|
||||||
in with pkgs; [
|
|
||||||
tcpdump
|
tcpdump
|
||||||
wireshark-nogui
|
wireshark
|
||||||
socat
|
socat
|
||||||
tufted
|
tufted
|
||||||
iptables
|
iptables
|
||||||
usbutils
|
usbutils
|
||||||
busybox
|
|
||||||
];
|
];
|
||||||
security.sudo.wheelNeedsPassword = false;
|
security.sudo.wheelNeedsPassword = false;
|
||||||
networking = {
|
networking = {
|
||||||
|
@ -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";
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,12 +1,8 @@
|
||||||
{ ... }:
|
{...}:
|
||||||
{
|
{
|
||||||
bordervm = {
|
bordervm = {
|
||||||
# ethernet.pci = { id = "01:00.0"; enable = true; };
|
# ethernet.pci = { id = "01:00.0"; enable = true; };
|
||||||
ethernet.usb = {
|
ethernet.usb = { vendor = "0x0bda"; product = "0x8153"; enable = true; };
|
||||||
vendor = "0x0bda";
|
|
||||||
product = "0x8153";
|
|
||||||
enable = true;
|
|
||||||
};
|
|
||||||
l2tp = {
|
l2tp = {
|
||||||
host = "l2tp.aa.net.uk";
|
host = "l2tp.aa.net.uk";
|
||||||
};
|
};
|
||||||
|
|
66
ci.nix
66
ci.nix
|
@ -1,23 +1,14 @@
|
||||||
{
|
{
|
||||||
nixpkgs,
|
nixpkgs
|
||||||
unstable,
|
, unstable
|
||||||
liminix,
|
, liminix
|
||||||
...
|
, ... }:
|
||||||
}:
|
|
||||||
let
|
let
|
||||||
pkgs = (import nixpkgs { });
|
inherit (builtins) map;
|
||||||
|
pkgs = (import nixpkgs {});
|
||||||
borderVmConf = ./bordervm.conf-example.nix;
|
borderVmConf = ./bordervm.conf-example.nix;
|
||||||
inherit (pkgs.lib.attrsets) genAttrs;
|
inherit (pkgs.lib.attrsets) genAttrs;
|
||||||
devices = [
|
devices = [ "qemu" "gl-ar750" "gl-mt300n-v2" "gl-mt300a" ];
|
||||||
"gl-ar750"
|
|
||||||
"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 = name:
|
||||||
(import liminix {
|
(import liminix {
|
||||||
|
@ -27,48 +18,29 @@ let
|
||||||
}).outputs.default;
|
}).outputs.default;
|
||||||
tests = import ./tests/ci.nix;
|
tests = import ./tests/ci.nix;
|
||||||
jobs =
|
jobs =
|
||||||
(genAttrs devices for-device)
|
(genAttrs devices (name: for-device name)) //
|
||||||
// tests
|
tests //
|
||||||
// {
|
{
|
||||||
buildEnv =
|
buildEnv = (import liminix {
|
||||||
(import liminix {
|
|
||||||
inherit nixpkgs borderVmConf;
|
inherit nixpkgs borderVmConf;
|
||||||
device = import (liminix + "/devices/qemu");
|
device = import (liminix + "/devices/qemu");
|
||||||
liminix-config = vanilla;
|
liminix-config = vanilla;
|
||||||
}).buildEnv;
|
}).buildEnv;
|
||||||
doc =
|
doc = pkgs.stdenv.mkDerivation {
|
||||||
let
|
|
||||||
json =
|
|
||||||
(import liminix {
|
|
||||||
inherit nixpkgs borderVmConf;
|
|
||||||
device = import (liminix + "/devices/qemu");
|
|
||||||
liminix-config =
|
|
||||||
{ ... }:
|
|
||||||
{
|
|
||||||
imports = [ ./modules/all-modules.nix ];
|
|
||||||
};
|
|
||||||
}).outputs.optionsJson;
|
|
||||||
in
|
|
||||||
pkgs.stdenv.mkDerivation {
|
|
||||||
name = "liminix-doc";
|
name = "liminix-doc";
|
||||||
nativeBuildInputs = with pkgs; [
|
nativeBuildInputs = with pkgs; [
|
||||||
gnumake
|
gnumake sphinx
|
||||||
sphinx
|
fennel luaPackages.lyaml
|
||||||
fennel
|
|
||||||
luaPackages.lyaml
|
|
||||||
];
|
];
|
||||||
src = ./.;
|
src = ./doc;
|
||||||
buildPhase = ''
|
buildPhase = ''
|
||||||
cat ${json} | fennel --correlate doc/parse-options.fnl > doc/modules-generated.inc.rst
|
cat ${(import ./doc/extract-options.nix).doc} > options.json
|
||||||
cat ${json} | fennel --correlate doc/parse-options-outputs.fnl > doc/outputs-generated.inc.rst
|
cat options.json | fennel --correlate parse-options.fnl > modules.rst
|
||||||
cp ${(import ./doc/hardware.nix)} doc/hardware.rst
|
make html
|
||||||
make -C doc html
|
|
||||||
'';
|
'';
|
||||||
installPhase = ''
|
installPhase = ''
|
||||||
mkdir -p $out/nix-support $out/share/doc/
|
mkdir -p $out/nix-support $out/share/doc/
|
||||||
cd doc
|
cp modules.rst options.json $out
|
||||||
cp *-generated.inc.rst hardware.rst $out
|
|
||||||
ln -s ${json} $out/options.json
|
|
||||||
cp -a _build/html $out/share/doc/liminix
|
cp -a _build/html $out/share/doc/liminix
|
||||||
echo "file source-dist \"$out/share/doc/liminix\"" \
|
echo "file source-dist \"$out/share/doc/liminix\"" \
|
||||||
> $out/nix-support/hydra-build-products
|
> $out/nix-support/hydra-build-products
|
||||||
|
|
53
default.nix
53
default.nix
|
@ -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];
|
||||||
// {
|
|
||||||
overlays = [ overlay ];
|
|
||||||
config = {
|
config = {
|
||||||
allowUnsupportedSystem = true; # mipsel
|
allowUnsupportedSystem = true; # mipsel
|
||||||
permittedInsecurePackages = [
|
permittedInsecurePackages = [
|
||||||
"python-2.7.18.6" # kernel backports needs python <3
|
"python-2.7.18.6" # kernel backports needs python <3
|
||||||
"python-2.7.18.7"
|
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
eval = pkgs.lib.evalModules {
|
config = (pkgs.lib.evalModules {
|
||||||
specialArgs = {
|
|
||||||
modulesPath = builtins.toString ./modules;
|
|
||||||
};
|
|
||||||
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
|
||||||
|
@ -81,13 +54,11 @@ in {
|
||||||
tufted
|
tufted
|
||||||
routeros.routeros
|
routeros.routeros
|
||||||
routeros.ros-exec-script
|
routeros.ros-exec-script
|
||||||
run-liminix-vm
|
mips-vm
|
||||||
borderVm.build.vm
|
borderVm.build.vm
|
||||||
go-l2tp
|
go-l2tp
|
||||||
min-copy-closure
|
min-copy-closure
|
||||||
fennelrepl
|
fennelrepl
|
||||||
lzma
|
|
||||||
lua
|
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,236 +0,0 @@
|
||||||
{
|
|
||||||
description = ''
|
|
||||||
Belkin RT-3200 / Linksys E8450
|
|
||||||
******************************
|
|
||||||
|
|
||||||
This device is based on a 64 bit Mediatek MT7622 ARM platform,
|
|
||||||
and is "work in progress" in Liminix.
|
|
||||||
|
|
||||||
.. note:: The factory flash image contains ECC errors that make it
|
|
||||||
incompatible with Liminix: you need to use the `OpenWrt
|
|
||||||
UBI Installer <https://github.com/dangowrt/owrt-ubi-installer>`_ to
|
|
||||||
rewrite the partition layout before you can flash
|
|
||||||
Liminix onto it (or even use it with
|
|
||||||
:ref:`system-outputs-tftpboot`, if you want the wireless
|
|
||||||
to work).
|
|
||||||
|
|
||||||
Hardware summary
|
|
||||||
================
|
|
||||||
|
|
||||||
- MediaTek MT7622BV (1350MHz)
|
|
||||||
- 128MB NAND flash
|
|
||||||
- 512MB RAM
|
|
||||||
- b/g/n wireless using MediaTek MT7622BV (MT7615E driver)
|
|
||||||
- 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 = {
|
|
||||||
crossSystem = {
|
|
||||||
config = "aarch64-unknown-linux-musl";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
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/aarch64.nix
|
|
||||||
../../modules/outputs/tftpboot.nix
|
|
||||||
../../modules/outputs/ubifs.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.mediatek}
|
|
||||||
'';
|
|
||||||
config = {
|
|
||||||
PCI = "y";
|
|
||||||
ARCH_MEDIATEK = "y";
|
|
||||||
# ARM_MEDIATEK_CPUFREQ = "y";
|
|
||||||
|
|
||||||
# needed for "Cannot find regmap for /infracfg@10000000"
|
|
||||||
MFD_SYSCON = "y";
|
|
||||||
MTK_INFRACFG = "y";
|
|
||||||
|
|
||||||
MTK_PMIC_WRAP = "y";
|
|
||||||
NVMEM_MTK_EFUSE="y";
|
|
||||||
# MTK_HSDMA="y";
|
|
||||||
MTK_SCPSYS="y";
|
|
||||||
MTK_SCPSYS_PM_DOMAINS="y";
|
|
||||||
# MTK_THERMAL="y";
|
|
||||||
MTK_TIMER="y";
|
|
||||||
|
|
||||||
COMMON_CLK_MT7622 = "y";
|
|
||||||
COMMON_CLK_MT7622_ETHSYS = "y";
|
|
||||||
COMMON_CLK_MT7622_HIFSYS = "y";
|
|
||||||
COMMON_CLK_MT7622_AUDSYS = "y";
|
|
||||||
PM_CLK="y";
|
|
||||||
|
|
||||||
REGMAP_MMIO = "y";
|
|
||||||
CLKSRC_MMIO = "y";
|
|
||||||
REGMAP = "y";
|
|
||||||
|
|
||||||
MEDIATEK_GE_PHY = "y";
|
|
||||||
# MEDIATEK_MT6577_AUXADC = "y";
|
|
||||||
NET_MEDIATEK_SOC = "y";
|
|
||||||
NET_MEDIATEK_SOC_WED = "y";
|
|
||||||
NET_MEDIATEK_STAR_EMAC = "y"; # this enables REGMAP_MMIO
|
|
||||||
NET_VENDOR_MEDIATEK = "y";
|
|
||||||
PCIE_MEDIATEK = "y";
|
|
||||||
|
|
||||||
BLOCK = "y"; # move this to base option
|
|
||||||
|
|
||||||
SPI_MASTER = "y";
|
|
||||||
SPI = "y";
|
|
||||||
SPI_MEM="y";
|
|
||||||
SPI_MTK_NOR="y";
|
|
||||||
SPI_MTK_SNFI = "y";
|
|
||||||
|
|
||||||
MTD = "y";
|
|
||||||
MTD_BLOCK = "y";
|
|
||||||
MTD_RAW_NAND = "y";
|
|
||||||
MTD_NAND_MTK = "y";
|
|
||||||
MTD_NAND_MTK_BMT = "y"; # Bad-block Management Table
|
|
||||||
MTD_NAND_ECC_MEDIATEK= "y";
|
|
||||||
MTD_NAND_ECC_SW_HAMMING= "y";
|
|
||||||
MTD_SPI_NAND= "y";
|
|
||||||
MTD_OF_PARTS = "y";
|
|
||||||
MTD_NAND_CORE= "y";
|
|
||||||
MTD_SPI_NOR= "y";
|
|
||||||
MTD_SPLIT_FIRMWARE= "y";
|
|
||||||
MTD_SPLIT_FIT_FW= "y";
|
|
||||||
|
|
||||||
|
|
||||||
MMC = "y";
|
|
||||||
MMC_BLOCK = "y";
|
|
||||||
MMC_CQHCI = "y";
|
|
||||||
MMC_MTK = "y";
|
|
||||||
|
|
||||||
# Distributed Switch Architecture is needed
|
|
||||||
# to make the ethernet ports visible
|
|
||||||
NET_DSA="y";
|
|
||||||
NET_DSA_MT7530="y";
|
|
||||||
NET_DSA_TAG_MTK="y";
|
|
||||||
|
|
||||||
PSTORE = "y";
|
|
||||||
PSTORE_RAM = "y";
|
|
||||||
PSTORE_CONSOLE = "y";
|
|
||||||
PSTORE_DEFLATE_COMPRESS = "n";
|
|
||||||
|
|
||||||
SERIAL_8250 = "y";
|
|
||||||
SERIAL_8250_CONSOLE = "y";
|
|
||||||
SERIAL_8250_MT6577="y";
|
|
||||||
# SERIAL_8250_NR_UARTS="3";
|
|
||||||
# SERIAL_8250_RUNTIME_UARTS="3";
|
|
||||||
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 = {
|
|
||||||
commandLine = [ "console=ttyS0,115200" ];
|
|
||||||
tftp.loadAddress = lim.parseInt "0x4007ff28";
|
|
||||||
imageFormat = "fit";
|
|
||||||
};
|
|
||||||
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 = ["mt7615e" "mt7915e"];
|
|
||||||
inherit (config.system.outputs) kernel;
|
|
||||||
};
|
|
||||||
in {
|
|
||||||
ubi = {
|
|
||||||
minIOSize = "2048";
|
|
||||||
eraseBlockSize = "126976";
|
|
||||||
maxLEBcount = "1024"; # guessing
|
|
||||||
};
|
|
||||||
|
|
||||||
defaultOutput = "ubimage";
|
|
||||||
# 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.
|
|
||||||
# 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
|
|
||||||
# RAM unless the kernel is able to reuse it later. Oh well
|
|
||||||
loadAddress = lim.parseInt "0x42000000";
|
|
||||||
entryPoint = lim.parseInt "0x42000000";
|
|
||||||
rootDevice = "ubi0:liminix";
|
|
||||||
dts = {
|
|
||||||
src = "${openwrt.src}/target/linux/mediatek/dts/mt7622-linksys-e8450-ubi.dts";
|
|
||||||
includes = [
|
|
||||||
"${openwrt.src}/target/linux/mediatek/dts"
|
|
||||||
"${config.system.outputs.kernel.modulesupport}/arch/arm64/boot/dts/mediatek/"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
# - 0x000000000000-0x000008000000 : "spi-nand0"
|
|
||||||
# - 0x000000000000-0x000000080000 : "bl2"
|
|
||||||
# - 0x000000080000-0x0000001c0000 : "fip"
|
|
||||||
# - 0x0000001c0000-0x0000002c0000 : "factory"
|
|
||||||
# - 0x0000002c0000-0x000000300000 : "reserved"
|
|
||||||
# - 0x000000300000-0x000008000000 : "ubi"
|
|
||||||
|
|
||||||
networkInterfaces =
|
|
||||||
let
|
|
||||||
inherit (config.system.service.network) link;
|
|
||||||
in rec {
|
|
||||||
wan = link.build { ifname = "wan"; };
|
|
||||||
lan1 = link.build { ifname = "lan1"; };
|
|
||||||
lan2 = link.build { ifname = "lan2"; };
|
|
||||||
lan3 = link.build { ifname = "lan3"; };
|
|
||||||
lan4 = link.build { ifname = "lan4"; };
|
|
||||||
lan = lan3;
|
|
||||||
|
|
||||||
wlan = link.build {
|
|
||||||
ifname = "wlan0";
|
|
||||||
dependencies = [ mac80211 ];
|
|
||||||
};
|
|
||||||
wlan5 = link.build {
|
|
||||||
ifname = "wlan1";
|
|
||||||
dependencies = [ mac80211 ];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -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 ];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -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 = {
|
||||||
|
@ -10,49 +18,27 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
description = ''
|
description = ''
|
||||||
GL.iNet GL-AR750
|
GL.INet GL-AR750 "Creta" travel router
|
||||||
****************
|
|
||||||
|
|
||||||
Hardware summary
|
|
||||||
================
|
|
||||||
|
|
||||||
The GL-AR750 "Creta" travel router features:
|
|
||||||
|
|
||||||
- QCA9531 @650Mhz SoC
|
- QCA9531 @650Mhz SoC
|
||||||
- dual band wireless: IEEE 802.11a/b/g/n/ac
|
- dual band wireless: IEEE 802.11a/b/g/n/ac
|
||||||
- two 10/100Mbps LAN ports and one WAN
|
- two 10/100Mbps LAN ports and one WAN
|
||||||
- 128MB DDR2 RAM
|
- 128MB DDR2 RAM / 16MB NOR Flash
|
||||||
- 16MB NOR Flash
|
- "ath79" soc family
|
||||||
- supported in OpenWrt by the "ath79" SoC family
|
https://www.gl-inet.com/products/gl-ar750/
|
||||||
|
|
||||||
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 the 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/
|
|
||||||
|
|
||||||
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 +57,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 = ''
|
||||||
|
@ -90,28 +75,24 @@
|
||||||
dd if=/dev/$part of=data iflag=skip_bytes,fullblock bs=${toString size} skip=${toString offset} count=1
|
dd if=/dev/$part of=data iflag=skip_bytes,fullblock bs=${toString size} skip=${toString offset} count=1
|
||||||
)
|
)
|
||||||
'';
|
'';
|
||||||
|
down = "true";
|
||||||
};
|
};
|
||||||
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/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 = "tftpboot";
|
||||||
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 +105,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";
|
wlan_24 = link.build {
|
||||||
};
|
|
||||||
wan = link.build {
|
|
||||||
ifname = "wan";
|
|
||||||
devpath = "/devices/platform/ahb/19000000.eth";
|
|
||||||
};
|
|
||||||
wlan = link.build {
|
|
||||||
ifname = "wlan0";
|
ifname = "wlan0";
|
||||||
dependencies = [ mac80211 ];
|
dependencies = [ mac80211 ];
|
||||||
};
|
};
|
||||||
wlan5 = link.build {
|
wlan_5 = link.build {
|
||||||
ifname = "wlan1";
|
ifname = "wlan1";
|
||||||
dependencies = [ ath10k_cal_data mac80211 ];
|
dependencies = [ mac80211 ath10k_cal_data ];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -153,26 +128,21 @@
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
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 = {
|
||||||
|
MIPS_ELF_APPENDED_DTB = "y";
|
||||||
|
OF = "y";
|
||||||
|
USE_OF = "y";
|
||||||
ATH79 = "y";
|
ATH79 = "y";
|
||||||
PCI = "y";
|
PCI = "y";
|
||||||
PCI_AR724X = "y";
|
PCI_AR724X = "y";
|
||||||
|
@ -192,6 +162,7 @@
|
||||||
CONSOLE_LOGLEVEL_QUIET = "4";
|
CONSOLE_LOGLEVEL_QUIET = "4";
|
||||||
|
|
||||||
NET = "y";
|
NET = "y";
|
||||||
|
NETDEVICES = "y";
|
||||||
ETHERNET = "y";
|
ETHERNET = "y";
|
||||||
NET_VENDOR_ATHEROS = "y";
|
NET_VENDOR_ATHEROS = "y";
|
||||||
AG71XX = "y"; # ethernet (qca,qca9530-eth)
|
AG71XX = "y"; # ethernet (qca,qca9530-eth)
|
||||||
|
@ -199,6 +170,7 @@
|
||||||
AR8216_PHY = "y"; # eth1 is behind a switch
|
AR8216_PHY = "y"; # eth1 is behind a switch
|
||||||
|
|
||||||
MTD_SPI_NOR = "y";
|
MTD_SPI_NOR = "y";
|
||||||
|
MTD_SPI_NOR_USE_4K_SECTORS = "n"; # jffs2 needs min 8k erase block
|
||||||
|
|
||||||
SPI_ATH79 = "y"; # these are copied from OpenWrt.
|
SPI_ATH79 = "y"; # these are copied from OpenWrt.
|
||||||
SPI_MASTER= "y"; # At least one of them is necessary
|
SPI_MASTER= "y"; # At least one of them is necessary
|
||||||
|
@ -215,25 +187,25 @@
|
||||||
SYSFS = "y";
|
SYSFS = "y";
|
||||||
SPI = "y";
|
SPI = "y";
|
||||||
MTD = "y";
|
MTD = "y";
|
||||||
|
MTD_CMDLINE_PARTS = "y";
|
||||||
MTD_BLOCK = "y"; # fix undefined ref to register_mtd_blktrans_devs
|
MTD_BLOCK = "y"; # fix undefined ref to register_mtd_blktrans_devs
|
||||||
|
|
||||||
WATCHDOG = "y";
|
WATCHDOG = "y";
|
||||||
ATH79_WDT = "y"; # watchdog timer
|
ATH79_WDT = "y"; # watchdog timer
|
||||||
|
|
||||||
|
CPU_BIG_ENDIAN= "y";
|
||||||
|
|
||||||
|
# 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 ...
|
||||||
|
|
||||||
|
CMDLINE_PARTITION = "y";
|
||||||
EARLY_PRINTK = "y";
|
EARLY_PRINTK = "y";
|
||||||
|
|
||||||
|
PARTITION_ADVANCED = "y";
|
||||||
PRINTK_TIME = "y";
|
PRINTK_TIME = "y";
|
||||||
};
|
SQUASHFS = "y";
|
||||||
conditionalConfig = {
|
SQUASHFS_XZ = "y";
|
||||||
WLAN = {
|
|
||||||
WLAN_VENDOR_ATH = "y";
|
|
||||||
ATH_COMMON = "m";
|
|
||||||
ATH9K = "m";
|
|
||||||
ATH9K_AHB = "y";
|
|
||||||
ATH10K = "m";
|
|
||||||
ATH10K_PCI = "m";
|
|
||||||
ATH10K_DEBUG = "y";
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# GL.iNet GL-MT300A
|
# GL.INet GL-MT300A
|
||||||
|
|
||||||
{
|
{
|
||||||
system = {
|
system = {
|
||||||
|
@ -12,55 +12,25 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
description = ''
|
description = ''
|
||||||
GL.iNet GL-MT300A
|
|
||||||
*****************
|
|
||||||
|
|
||||||
The GL-MT300A is based on a MT7620 chipset.
|
|
||||||
|
|
||||||
For flashing from U-Boot, the firmware partition is from
|
|
||||||
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
|
||||||
expects firmware to be present in the "factory" MTD partition, so
|
expects firmware to be present in the "factory" MTD partition, so
|
||||||
- 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/
|
|
||||||
|
|
||||||
OpenWrt web page: https://openwrt.org/toh/gl.inet/gl-mt300a
|
|
||||||
|
|
||||||
'';
|
'';
|
||||||
|
|
||||||
module = { pkgs, config, lib, lim, ...}:
|
module = { pkgs, config, ...}:
|
||||||
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 = [
|
|
||||||
../../modules/arch/mipsel.nix
|
|
||||||
../../modules/outputs/tftpboot.nix
|
|
||||||
../../modules/outputs/mtdimage.nix
|
|
||||||
../../modules/outputs/jffs2.nix
|
|
||||||
];
|
|
||||||
hardware = {
|
hardware = {
|
||||||
defaultOutput = "mtdimage";
|
defaultOutput = "tftpboot";
|
||||||
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 +43,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";
|
||||||
|
|
||||||
|
@ -85,45 +55,46 @@
|
||||||
"${openwrt.src}/target/linux/ramips/dts"
|
"${openwrt.src}/target/linux/ramips/dts"
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
networkInterfaces =
|
networkInterfaces = rec {
|
||||||
let
|
|
||||||
inherit (config.system.service.network) link;
|
|
||||||
inherit (config.system.service) vlan;
|
|
||||||
in rec {
|
|
||||||
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
|
||||||
lan = vlan.build {
|
eth = interface { device = "eth0"; };
|
||||||
ifname = "eth0.1";
|
lan = interface {
|
||||||
primary = eth;
|
type = "vlan";
|
||||||
vid = "1";
|
device = "eth0.1";
|
||||||
|
link = "eth0";
|
||||||
|
id = "1";
|
||||||
|
dependencies = [eth];
|
||||||
};
|
};
|
||||||
wan = vlan.build {
|
wan = interface {
|
||||||
ifname = "eth0.2";
|
type = "vlan";
|
||||||
primary = eth;
|
device = "eth0.2";
|
||||||
vid = "2";
|
id = "2";
|
||||||
|
link = "eth0";
|
||||||
|
dependencies = [eth];
|
||||||
};
|
};
|
||||||
wlan = link.build {
|
wlan = interface {
|
||||||
ifname = "wlan0";
|
device = "wlan0";
|
||||||
dependencies = [ mac80211 ];
|
dependencies = [ mac80211 ];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
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 = {
|
||||||
|
MIPS_ELF_APPENDED_DTB = "y";
|
||||||
|
OF = "y";
|
||||||
|
USE_OF = "y";
|
||||||
|
|
||||||
RALINK = "y";
|
RALINK = "y";
|
||||||
PCI = "y";
|
PCI = "y";
|
||||||
|
@ -138,12 +109,12 @@
|
||||||
CONSOLE_LOGLEVEL_QUIET = "4";
|
CONSOLE_LOGLEVEL_QUIET = "4";
|
||||||
|
|
||||||
NET = "y";
|
NET = "y";
|
||||||
|
NETDEVICES = "y";
|
||||||
ETHERNET = "y";
|
ETHERNET = "y";
|
||||||
NET_VENDOR_RALINK = "y";
|
NET_VENDOR_RALINK = "y";
|
||||||
NET_RALINK_MDIO = "y";
|
NET_RALINK_MDIO = "y";
|
||||||
NET_RALINK_MDIO_MT7620 = "y";
|
NET_RALINK_MDIO_MT7620 = "y";
|
||||||
NET_RALINK_MT7620 = "y";
|
NET_RALINK_MT7620 = "y";
|
||||||
SWPHY = "y";
|
|
||||||
|
|
||||||
SPI = "y";
|
SPI = "y";
|
||||||
MTD_SPI_NOR = "y";
|
MTD_SPI_NOR = "y";
|
||||||
|
@ -152,26 +123,35 @@
|
||||||
SPI_MASTER= "y";
|
SPI_MASTER= "y";
|
||||||
SPI_MEM= "y";
|
SPI_MEM= "y";
|
||||||
|
|
||||||
|
# both the ethernet ports on this device (lan and wan)
|
||||||
|
# are behind a switch, so we need VLANs to do anything
|
||||||
|
# useful with them
|
||||||
|
|
||||||
|
VLAN_8021Q = "y";
|
||||||
|
SWCONFIG = "y";
|
||||||
|
SWPHY = "y";
|
||||||
|
|
||||||
|
BRIDGE = "y";
|
||||||
|
BRIDGE_VLAN_FILTERING = "y";
|
||||||
|
BRIDGE_IGMP_SNOOPING = "y";
|
||||||
|
|
||||||
MTD = "y";
|
MTD = "y";
|
||||||
|
MTD_CMDLINE_PARTS = "y";
|
||||||
MTD_BLOCK = "y"; # fix undefined ref to register_mtd_blktrans_devs
|
MTD_BLOCK = "y"; # fix undefined ref to register_mtd_blktrans_devs
|
||||||
|
|
||||||
|
CPU_LITTLE_ENDIAN = "y";
|
||||||
|
|
||||||
|
CMDLINE_PARTITION = "y";
|
||||||
EARLY_PRINTK = "y";
|
EARLY_PRINTK = "y";
|
||||||
|
|
||||||
NEW_LEDS = "y";
|
NEW_LEDS = "y";
|
||||||
LEDS_CLASS = "y"; # required by rt2x00lib
|
LEDS_CLASS = "y"; # required by rt2x00lib
|
||||||
|
|
||||||
|
PARTITION_ADVANCED = "y";
|
||||||
PRINTK_TIME = "y";
|
PRINTK_TIME = "y";
|
||||||
} // lib.optionalAttrs (config.system.service ? vlan) {
|
SQUASHFS = "y";
|
||||||
SWCONFIG = "y";
|
SQUASHFS_XZ = "y";
|
||||||
};
|
};
|
||||||
conditionalConfig = {
|
|
||||||
WLAN = {
|
|
||||||
WLAN_VENDOR_RALINK = "y";
|
|
||||||
RT2800SOC = "m";
|
|
||||||
RT2X00 = "m";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
# GL.INet GL-MT300N v2
|
||||||
|
|
||||||
{
|
{
|
||||||
system = {
|
system = {
|
||||||
crossSystem = {
|
crossSystem = {
|
||||||
|
@ -9,54 +11,22 @@
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
description = ''
|
module = { pkgs, config, ...}:
|
||||||
GL.iNet GL-MT300N-v2
|
|
||||||
********************
|
|
||||||
|
|
||||||
The GL-MT300N-v2 "Mango" is is very similar to the :ref:`gl-mt300a`, but is
|
|
||||||
based on the MT7628 chipset instead of MT7620. It's also marginally cheaper
|
|
||||||
and comes in a yellow case not a blue one. Be sure your device is
|
|
||||||
v2 not v1, which is a different animal and has only half as much 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/
|
|
||||||
|
|
||||||
OpenWrt web page: https://openwrt.org/toh/gl.inet/gl-mt300n_v2
|
|
||||||
|
|
||||||
'';
|
|
||||||
|
|
||||||
module = { pkgs, config, lib, lim, ...}:
|
|
||||||
let
|
let
|
||||||
|
inherit (pkgs.liminix.networking) interface;
|
||||||
inherit (pkgs.liminix.services) oneshot;
|
inherit (pkgs.liminix.services) oneshot;
|
||||||
inherit (pkgs.pseudofile) dir symlink;
|
inherit (pkgs.pseudofile) dir symlink;
|
||||||
inherit (pkgs) openwrt;
|
inherit (pkgs) openwrt;
|
||||||
|
|
||||||
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 = [
|
|
||||||
../../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 +35,14 @@
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
hardware = {
|
hardware = {
|
||||||
defaultOutput = "mtdimage";
|
defaultOutput = "tftpboot";
|
||||||
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";
|
||||||
|
|
||||||
|
@ -82,11 +52,10 @@
|
||||||
"${openwrt.src}/target/linux/ramips/dts"
|
"${openwrt.src}/target/linux/ramips/dts"
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
networkInterfaces =
|
networkInterfaces = rec {
|
||||||
let
|
# lan and wan ports are both behind a switch on eth0
|
||||||
inherit (config.system.service.network) link;
|
eth =
|
||||||
inherit (config.system.service) vlan;
|
let swconfig = oneshot {
|
||||||
swconfig = oneshot {
|
|
||||||
name = "swconfig";
|
name = "swconfig";
|
||||||
up = ''
|
up = ''
|
||||||
PATH=${pkgs.swconfig}/bin:$PATH
|
PATH=${pkgs.swconfig}/bin:$PATH
|
||||||
|
@ -96,23 +65,28 @@
|
||||||
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 interface {
|
||||||
eth = link.build { ifname = "eth0"; dependencies = [swconfig]; };
|
device = "eth0";
|
||||||
# lan and wan ports are both behind a switch on eth0
|
dependencies = [swconfig];
|
||||||
lan = vlan.build {
|
|
||||||
ifname = "eth0.1";
|
|
||||||
primary = eth;
|
|
||||||
vid = "1";
|
|
||||||
};
|
};
|
||||||
wan = vlan.build {
|
lan = interface {
|
||||||
ifname = "eth0.2";
|
type = "vlan";
|
||||||
primary = eth;
|
device = "eth0.1";
|
||||||
vid = "2";
|
link = "eth0";
|
||||||
|
id = "1";
|
||||||
|
dependencies = [eth];
|
||||||
};
|
};
|
||||||
wlan = link.build {
|
wan = interface {
|
||||||
ifname = "wlan0";
|
type = "vlan";
|
||||||
|
device = "eth0.2";
|
||||||
|
id = "2";
|
||||||
|
link = "eth0";
|
||||||
|
dependencies = [eth];
|
||||||
|
};
|
||||||
|
wlan = interface {
|
||||||
|
device = "wlan0";
|
||||||
dependencies = [ mac80211 ];
|
dependencies = [ mac80211 ];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -120,24 +94,27 @@
|
||||||
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}
|
||||||
'';
|
'';
|
||||||
config = {
|
config = {
|
||||||
|
MIPS_ELF_APPENDED_DTB = "y";
|
||||||
|
OF = "y";
|
||||||
|
USE_OF = "y";
|
||||||
|
|
||||||
RALINK = "y";
|
RALINK = "y";
|
||||||
PCI = "y";
|
PCI = "y";
|
||||||
SOC_MT7620 = "y";
|
SOC_MT7620 = "y";
|
||||||
|
CPU_LITTLE_ENDIAN= "y";
|
||||||
|
|
||||||
SERIAL_8250_CONSOLE = "y";
|
SERIAL_8250_CONSOLE = "y";
|
||||||
SERIAL_8250 = "y";
|
SERIAL_8250 = "y";
|
||||||
|
@ -148,6 +125,7 @@
|
||||||
CONSOLE_LOGLEVEL_QUIET = "4";
|
CONSOLE_LOGLEVEL_QUIET = "4";
|
||||||
|
|
||||||
MTD = "y";
|
MTD = "y";
|
||||||
|
MTD_CMDLINE_PARTS = "y";
|
||||||
MTD_BLOCK = "y"; # fix undefined ref to register_mtd_blktrans_dev
|
MTD_BLOCK = "y"; # fix undefined ref to register_mtd_blktrans_dev
|
||||||
|
|
||||||
SPI = "y";
|
SPI = "y";
|
||||||
|
@ -160,6 +138,7 @@
|
||||||
REGULATOR_FIXED_VOLTAGE = "y";
|
REGULATOR_FIXED_VOLTAGE = "y";
|
||||||
|
|
||||||
NET = "y";
|
NET = "y";
|
||||||
|
NETDEVICES = "y";
|
||||||
ETHERNET = "y";
|
ETHERNET = "y";
|
||||||
|
|
||||||
PHYLIB = "y";
|
PHYLIB = "y";
|
||||||
|
@ -169,31 +148,36 @@
|
||||||
NET_VENDOR_RALINK = "y";
|
NET_VENDOR_RALINK = "y";
|
||||||
NET_RALINK_RT3050 = "y";
|
NET_RALINK_RT3050 = "y";
|
||||||
NET_RALINK_SOC="y";
|
NET_RALINK_SOC="y";
|
||||||
|
|
||||||
|
# both the ethernet ports on this device (lan and wan)
|
||||||
|
# are behind a switch, so we need VLANs to do anything
|
||||||
|
# useful with them
|
||||||
|
|
||||||
|
VLAN_8021Q = "y";
|
||||||
|
SWCONFIG = "y";
|
||||||
SWPHY = "y";
|
SWPHY = "y";
|
||||||
|
|
||||||
|
BRIDGE = "y";
|
||||||
|
BRIDGE_VLAN_FILTERING = "y";
|
||||||
|
BRIDGE_IGMP_SNOOPING = "y";
|
||||||
|
|
||||||
|
WATCHDOG = "y";
|
||||||
|
RALINK_WDT = "y"; # watchdog
|
||||||
|
MT7621_WDT = "y"; # or it might be this one
|
||||||
|
|
||||||
GPIOLIB="y";
|
GPIOLIB="y";
|
||||||
GPIO_MT7621 = "y";
|
GPIO_MT7621 = "y";
|
||||||
|
|
||||||
PHY_RALINK_USB = "y";
|
PHY_RALINK_USB = "y";
|
||||||
|
|
||||||
|
CMDLINE_PARTITION = "y";
|
||||||
EARLY_PRINTK = "y";
|
EARLY_PRINTK = "y";
|
||||||
|
|
||||||
|
PARTITION_ADVANCED = "y";
|
||||||
PRINTK_TIME = "y";
|
PRINTK_TIME = "y";
|
||||||
} // lib.optionalAttrs (config.system.service ? vlan) {
|
SQUASHFS = "y";
|
||||||
SWCONFIG = "y";
|
SQUASHFS_XZ = "y";
|
||||||
} // lib.optionalAttrs (config.system.service ? watchdog) {
|
|
||||||
RALINK_WDT = "y"; # watchdog
|
|
||||||
MT7621_WDT = "y"; # or it might be this one
|
|
||||||
};
|
|
||||||
conditionalConfig = {
|
|
||||||
WLAN = {
|
|
||||||
WLAN_VENDOR_RALINK = "y";
|
|
||||||
WLAN_VENDOR_MEDIATEK = "y";
|
|
||||||
MT7603E = "m";
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,51 +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 = "aarch64-unknown-linux-musl";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
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
|
|
||||||
installer = "vmroot";
|
|
||||||
|
|
||||||
module = { config, lim, ... }: {
|
|
||||||
imports = [
|
|
||||||
../../modules/arch/aarch64.nix
|
|
||||||
../families/qemu.nix
|
|
||||||
];
|
|
||||||
kernel = {
|
|
||||||
config = {
|
|
||||||
VIRTUALIZATION = "y";
|
|
||||||
PCI_HOST_GENERIC="y";
|
|
||||||
|
|
||||||
SERIAL_AMBA_PL011 = "y";
|
|
||||||
SERIAL_AMBA_PL011_CONSOLE = "y";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
boot.commandLine = [
|
|
||||||
"console=ttyAMA0,38400"
|
|
||||||
];
|
|
||||||
hardware = let addr = lim.parseInt "0x40010000"; in {
|
|
||||||
loadAddress = addr;
|
|
||||||
entryPoint = addr;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -12,65 +12,59 @@
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
description = ''
|
module = {pkgs, config, ... }: {
|
||||||
QEMU MIPS
|
|
||||||
*********
|
|
||||||
|
|
||||||
This target produces an image for
|
|
||||||
QEMU, the "generic and open source machine emulator and
|
|
||||||
virtualizer".
|
|
||||||
|
|
||||||
MIPS QEMU emulates a "Malta" board, which was an ATX form factor
|
|
||||||
evaluation board made by MIPS Technologies, but mostly in Liminix
|
|
||||||
we use paravirtualized devices (Virtio) instead of emulating
|
|
||||||
hardware.
|
|
||||||
|
|
||||||
Building an image for QEMU results in a :file:`result/` directory
|
|
||||||
containing ``run.sh`` ``vmlinux``, and ``rootfs`` files. To invoke
|
|
||||||
the emulator, run ``run.sh``.
|
|
||||||
|
|
||||||
The configuration includes two emulated "hardware" ethernet
|
|
||||||
devices and the kernel :code:`mac80211_hwsim` module to
|
|
||||||
provide an emulated wlan device. To read more about how
|
|
||||||
to connect to this network, refer to :ref:`qemu-networking`
|
|
||||||
in the Development manual.
|
|
||||||
|
|
||||||
'';
|
|
||||||
module = { config, lib, lim, ... }: {
|
|
||||||
imports = [
|
|
||||||
../../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_LITTLE_ENDIAN= "n";
|
||||||
|
CPU_BIG_ENDIAN= "y";
|
||||||
CPU_MIPS32_R2= "y";
|
CPU_MIPS32_R2= "y";
|
||||||
|
|
||||||
POWER_RESET = "y";
|
MTD = "y";
|
||||||
POWER_RESET_SYSCON = "y";
|
MTD_BLOCK2MTD = "y";
|
||||||
|
MTD_BLKDEVS = "y";
|
||||||
|
MTD_BLOCK = "y";
|
||||||
|
|
||||||
|
SQUASHFS = "y";
|
||||||
|
SQUASHFS_XZ = "y";
|
||||||
|
|
||||||
|
VIRTIO_MENU = "y";
|
||||||
|
PCI = "y";
|
||||||
|
VIRTIO_PCI = "y";
|
||||||
|
BLOCK = "y";
|
||||||
|
VIRTIO_BLK = "y";
|
||||||
|
NETDEVICES = "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 {
|
||||||
|
drivers = ["mac80211_hwsim"];
|
||||||
|
klibBuild = config.system.outputs.kernel.modulesupport;
|
||||||
|
};
|
||||||
|
inherit (pkgs.liminix.networking) interface;
|
||||||
in {
|
in {
|
||||||
loadAddress = addr;
|
defaultOutput = "vmroot";
|
||||||
entryPoint = addr;
|
flash.eraseBlockSize = "65536"; # c.f. pkgs/mips-vm/mips-vm.sh
|
||||||
|
networkInterfaces = {
|
||||||
|
lan = interface { device = "eth0"; };
|
||||||
|
wan = interface { device = "eth1"; };
|
||||||
|
|
||||||
# Unlike the arm qemu targets, we need a static dts when
|
wlan_24 = interface {
|
||||||
# running u-boot-using tests, qemu dumpdtb command doesn't
|
device = "wlan0";
|
||||||
# work for this board. I am not at all sure this dts is
|
dependencies = [ mac80211 ];
|
||||||
# *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/"
|
|
||||||
];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 ];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,409 +0,0 @@
|
||||||
{
|
|
||||||
description = ''
|
|
||||||
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 = {
|
|
||||||
crossSystem = {
|
|
||||||
config = "armv7l-unknown-linux-musleabihf";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
module = {pkgs, config, lib, lim, ... }:
|
|
||||||
let
|
|
||||||
inherit (pkgs.liminix.services) oneshot;
|
|
||||||
inherit (pkgs) liminix;
|
|
||||||
mtd_by_name_links = pkgs.liminix.services.oneshot rec {
|
|
||||||
name = "mtd_by_name_links";
|
|
||||||
up = ''
|
|
||||||
mkdir -p /dev/mtd/by-name
|
|
||||||
cd /dev/mtd/by-name
|
|
||||||
for i in /sys/class/mtd/mtd*[0-9]; do
|
|
||||||
ln -s ../../$(basename $i) $(cat $i/name)
|
|
||||||
done
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
in {
|
|
||||||
imports = [
|
|
||||||
../../modules/arch/arm.nix
|
|
||||||
../../modules/outputs/tftpboot.nix
|
|
||||||
../../modules/outputs/mbrimage.nix
|
|
||||||
../../modules/outputs/extlinux.nix
|
|
||||||
];
|
|
||||||
|
|
||||||
config = {
|
|
||||||
services.mtd-name-links = mtd_by_name_links;
|
|
||||||
kernel = {
|
|
||||||
src = pkgs.pkgsBuildBuild.fetchurl {
|
|
||||||
name = "linux.tar.gz";
|
|
||||||
url = "https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.7.4.tar.gz";
|
|
||||||
hash = "sha256-wIrmL0BS63nRwWfm4nw+dRNVPUzGh9M4X7LaHzAn5tU=";
|
|
||||||
};
|
|
||||||
version = "6.7.4";
|
|
||||||
config = {
|
|
||||||
PCI = "y";
|
|
||||||
OF = "y";
|
|
||||||
MEMORY = "y"; # for MVEBU_DEVBUS
|
|
||||||
DMADEVICES = "y"; # for MV_XOR
|
|
||||||
CPU_V7 = "y";
|
|
||||||
ARCH_MULTIPLATFORM = "y";
|
|
||||||
ARCH_MVEBU = "y";
|
|
||||||
ARCH_MULTI_V7= "y";
|
|
||||||
PCI_MVEBU = "y";
|
|
||||||
AHCI_MVEBU = "y";
|
|
||||||
|
|
||||||
RTC_CLASS = "y";
|
|
||||||
RTC_DRV_ARMADA38X = "y"; # this may be useful anyway?
|
|
||||||
|
|
||||||
EXPERT = "y";
|
|
||||||
ALLOW_DEV_COREDUMP = "n";
|
|
||||||
|
|
||||||
|
|
||||||
# dts has a compatible for this but dmesg is not
|
|
||||||
# showing it
|
|
||||||
EEPROM_AT24 = "y"; # atmel,24c64
|
|
||||||
|
|
||||||
I2C = "y";
|
|
||||||
I2C_MUX = "y";
|
|
||||||
I2C_MUX_PCA954x = "y";
|
|
||||||
|
|
||||||
MACH_ARMADA_38X = "y";
|
|
||||||
SMP = "y";
|
|
||||||
# this is disabled for the moment because it relies on a
|
|
||||||
# GCC plugin that requires gmp.h to build, and I can't see
|
|
||||||
# right now how to confgure it to find gmp
|
|
||||||
STACKPROTECTOR_PER_TASK = "n";
|
|
||||||
NR_CPUS = "4";
|
|
||||||
VFP = "y";
|
|
||||||
NEON= "y";
|
|
||||||
|
|
||||||
# WARNING: unmet direct dependencies detected for ARCH_WANT_LIBATA_LEDS
|
|
||||||
ATA = "y";
|
|
||||||
|
|
||||||
PSTORE = "y";
|
|
||||||
PSTORE_RAM = "y";
|
|
||||||
PSTORE_CONSOLE = "y";
|
|
||||||
# PSTORE_DEFLATE_COMPRESS = "n";
|
|
||||||
|
|
||||||
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 ];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -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";
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -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";
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -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
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -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>;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
ðernet {
|
|
||||||
pinctrl-0 = <&mdio_pins>, <&rgmii1_pins>;
|
|
||||||
};
|
|
||||||
|
|
||||||
&state_default {
|
|
||||||
gpio {
|
|
||||||
groups = "uart3", "rgmii2";
|
|
||||||
function = "gpio";
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -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)
|
||||||
|
|
304
doc/admin.rst
304
doc/admin.rst
|
@ -1,304 +0,0 @@
|
||||||
System Administration
|
|
||||||
#####################
|
|
||||||
|
|
||||||
Services on a running system
|
|
||||||
****************************
|
|
||||||
|
|
||||||
Liminix services are built on s6-rc, which is itself layered on s6.
|
|
||||||
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
|
|
||||||
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
|
|
||||||
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
|
|
||||||
===================
|
|
||||||
|
|
||||||
**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
|
|
||||||
active when a particular hardware device (identified by uevent/sysfs
|
|
||||||
directory) is present.
|
|
||||||
|
|
||||||
* ``svc.round-robin.build`` creates a service controller that
|
|
||||||
invokes two or more services in turn, running the next one when the
|
|
||||||
process providing the previous one exits. We use this for failover
|
|
||||||
from one network connection to a backup connection, for example.
|
|
||||||
|
|
||||||
* ``svc.health-check.build`` creates a service controller that
|
|
||||||
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
|
|
||||||
|
|
||||||
# ls -l /run/uncaught-logs/
|
|
||||||
-rw-r--r-- 1 0 lock
|
|
||||||
-rw-r--r-- 1 0 state
|
|
||||||
-rwxr--r-- 1 98059 @4000000000025cb629c311ac.s
|
|
||||||
-rwxr--r-- 1 98061 @40000000000260f7309c7fb4.s
|
|
||||||
-rwxr--r-- 1 98041 @40000000000265233a6cc0b6.s
|
|
||||||
-rwxr--r-- 1 98019 @400000000002695d10c06929.s
|
|
||||||
-rwxr--r-- 1 98064 @4000000000026d84189559e0.s
|
|
||||||
-rwxr--r-- 1 98055 @40000000000271ce1e031d91.s
|
|
||||||
-rwxr--r-- 1 98054 @400000000002760229733626.s
|
|
||||||
-rwxr--r-- 1 98104 @4000000000027a2e3b6f4e12.s
|
|
||||||
-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>]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Updating an installed system (JFFS2)
|
|
||||||
************************************
|
|
||||||
|
|
||||||
|
|
||||||
Adding packages
|
|
||||||
===============
|
|
||||||
|
|
||||||
If your device is running a JFFS2 root filesystem, you can build
|
|
||||||
extra packages for it on your build system and copy them to the
|
|
||||||
device: any package in Nixpkgs or in the Liminix overlay is available
|
|
||||||
with the ``pkgs`` prefix:
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
nix-build -I liminix-config=./my-configuration.nix \
|
|
||||||
--arg device "import ./devices/mydevice" -A pkgs.tcpdump
|
|
||||||
|
|
||||||
nix-shell -p min-copy-closure root@the-device result/
|
|
||||||
|
|
||||||
Note that this only copies the package to the device: it doesn't update
|
|
||||||
any profile to add it to ``$PATH``
|
|
||||||
|
|
||||||
|
|
||||||
.. _rebuilding the system:
|
|
||||||
|
|
||||||
Rebuilding the system
|
|
||||||
=====================
|
|
||||||
|
|
||||||
:command:`liminix-rebuild` is the Liminix analogue of :command:`nixos-rebuild`, although its operation is a bit different because it expects to run on a build machine and then copy to the host device. Run it with the same ``liminix-config`` and ``device`` parameters as you would run :command:`nix-build`, and it will build any new/changed packages and then copy them to the device using SSH. For example:
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
liminix-rebuild root@the-device -I liminix-config=./examples/rotuer.nix --arg device "import ./devices/gl-ar750"
|
|
||||||
|
|
||||||
This will
|
|
||||||
|
|
||||||
* build anything that needs building
|
|
||||||
* copy new or changed packages to the device
|
|
||||||
* reboot the device
|
|
||||||
|
|
||||||
It doesn't delete old packages automatically: to do that run
|
|
||||||
:command:`min-collect-garbage`, which will delete any packages not in
|
|
||||||
the current system closure. Note that Liminix does not have the NixOS
|
|
||||||
concept of environments or generations, and there is no way back from
|
|
||||||
this except for building the previous configuration again.
|
|
||||||
|
|
||||||
|
|
||||||
Caveats
|
|
||||||
-------
|
|
||||||
|
|
||||||
* it needs there to be enough free space on the device for all the new
|
|
||||||
packages in addition to all the packages already on it - which may be
|
|
||||||
a problem if a lot of things have changed (e.g. a new version of
|
|
||||||
nixpkgs).
|
|
||||||
|
|
||||||
* 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
|
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
Architecture Decision Records
|
|
||||||
#############################
|
|
||||||
|
|
||||||
In this directory you will find descriptions of Liminix architecture
|
|
||||||
decisions.
|
|
||||||
|
|
|
@ -1,201 +0,0 @@
|
||||||
Module system
|
|
||||||
#############
|
|
||||||
|
|
||||||
**Status:** Adopted; implemented in July-September 2023
|
|
||||||
|
|
||||||
|
|
||||||
Context
|
|
||||||
*******
|
|
||||||
|
|
||||||
Liminix users need a way to assemble a full system configuration by
|
|
||||||
combining smaller, more isolated and reusable components, otherwise
|
|
||||||
systems will be unwieldy and copy-and-paste will be rife.
|
|
||||||
|
|
||||||
|
|
||||||
Alternatives
|
|
||||||
************
|
|
||||||
|
|
||||||
NixOS module system
|
|
||||||
===================
|
|
||||||
|
|
||||||
The NixOS module system addresses many of these concerns. A module is
|
|
||||||
a Nix function which accepts a ``configuration`` attrset and some
|
|
||||||
other parameters, and returns a new fragment of ``configuration``
|
|
||||||
which is merged into it. It includes a DSL describing the permitted
|
|
||||||
types of values for each key in the configuration, which is used for
|
|
||||||
checking that the supplied parameters are valid and also governs what
|
|
||||||
to do if two modules both specify a value for the same key. (Usually
|
|
||||||
they are "merged", using some type-appropriate concept of merging.)
|
|
||||||
|
|
||||||
Usually a NixOS module looks only (or mostly only) at a particular
|
|
||||||
subtree of the overall configuration which is hardcoded in the module
|
|
||||||
definition, but the configuration fragment it returns may touch any
|
|
||||||
part of the schema. For example, the factorio module refers to
|
|
||||||
``config.services.factorio``, and it returns values for keys in
|
|
||||||
``systemd.services.factorio`` and ``networking.firewall``. There is no
|
|
||||||
way to use this module to run **two** factorio services with different
|
|
||||||
config (e.g. on different ports) - the only way to make that
|
|
||||||
possible would be to extend the module definition so that it
|
|
||||||
accepts a collection of game configurations and then create
|
|
||||||
a systemd service for each.
|
|
||||||
|
|
||||||
|
|
||||||
NixWRT module system
|
|
||||||
====================
|
|
||||||
|
|
||||||
NixWRT, the (now defunct) predecessor of Liminix, used a homegrown
|
|
||||||
module system modelled on the Nixpkgs overlay pattern. Each module is
|
|
||||||
a function that accepts ``super`` and ``self`` parameters, and
|
|
||||||
using <handwaves>that fixpoint magic thing</handwaves>
|
|
||||||
is called in a chain with the configuration returned by the previous
|
|
||||||
module and the final configuration.
|
|
||||||
|
|
||||||
NixWRT modules mostly don't refer to the configuration object to
|
|
||||||
decide how to configure themselves, but accept their parameters
|
|
||||||
directly as function parameters. For example, the configuration
|
|
||||||
file for "arhcive" (a backup server) includes this text:
|
|
||||||
|
|
||||||
.. code-block:: nix
|
|
||||||
|
|
||||||
(sshd {
|
|
||||||
hostkey = secrets.sshHostKey;
|
|
||||||
authkeys = { root = lib.splitString "\n" secrets.myKeys; };
|
|
||||||
})
|
|
||||||
busybox
|
|
||||||
(usbdisk {
|
|
||||||
label = "backup-disk";
|
|
||||||
mountpoint = "/srv";
|
|
||||||
fstype = "ext4";
|
|
||||||
options = "rw";
|
|
||||||
})
|
|
||||||
|
|
||||||
This gives us flexibility that NixOS modules don't: for example, if we
|
|
||||||
want to mount two USB disks, we can simply repeat that module twice
|
|
||||||
with different parameters - and the module definition doesn't have to
|
|
||||||
handle it specially.
|
|
||||||
|
|
||||||
However, the downside of this system is that we didn't implement any
|
|
||||||
concept of "types" - there is no type information, so there is no
|
|
||||||
systematic checking that parameters are valid, and if two modules set
|
|
||||||
the same config key then the rules for merging are entirely ad hoc.
|
|
||||||
|
|
||||||
There is a further (arguable) downside, which is that the
|
|
||||||
configuration is not just data - it's now part code. While it could be
|
|
||||||
feasible (though I've never seen it done) to encode a NixOS
|
|
||||||
configuration using Yaml or XML and then manipulate it as data, this
|
|
||||||
is not even possible using the NixWRT system.
|
|
||||||
|
|
||||||
|
|
||||||
Use services for everything
|
|
||||||
===========================
|
|
||||||
|
|
||||||
The most common properties that a Liminix configuration needs to
|
|
||||||
define are:
|
|
||||||
|
|
||||||
* which services (processes) to run
|
|
||||||
* what packages to install
|
|
||||||
* permitted users and groups
|
|
||||||
* Linux kernel configuration options
|
|
||||||
* Busybox applets
|
|
||||||
* filesystem layout
|
|
||||||
|
|
||||||
Suppose we only had services?
|
|
||||||
|
|
||||||
A Liminix service is (also) a derivation, so it is able to
|
|
||||||
create any files it likes inside its own store path, and
|
|
||||||
transitively require other packages simply by referring to them.
|
|
||||||
If it needs particular kernel options it could define them
|
|
||||||
as kernel modules to be loaded on demand when the service
|
|
||||||
starts (see the nftables module for an example). However:
|
|
||||||
|
|
||||||
* there is no way for a service to add busybox modules
|
|
||||||
|
|
||||||
* it cannot create files outside of its store path, so
|
|
||||||
wouldn't be able to make e.g. :file:`/etc/something.conf`
|
|
||||||
|
|
||||||
* no way to create users/groups. We could steal the DynamicUsers idea
|
|
||||||
from systemd and make them on demand, but this starts to get a bit
|
|
||||||
more complicated.
|
|
||||||
|
|
||||||
These limitations force us to reject this option as a general
|
|
||||||
solution - though we should strive *where possible* to implement
|
|
||||||
functionality as services and to minimise the proportion of Liminix
|
|
||||||
that manipulates the global configuration.
|
|
||||||
|
|
||||||
|
|
||||||
Decision
|
|
||||||
********
|
|
||||||
|
|
||||||
"Why not both?" None of these options is sufficient alone, so we are
|
|
||||||
going to do a mixture.
|
|
||||||
|
|
||||||
We will use the NixOS module system, but instead of expecting modules
|
|
||||||
to create systemd services as instances, they will expose "service
|
|
||||||
templates": functions that accept an attrset and return an
|
|
||||||
appropriately configured service that can be assigned by the caller
|
|
||||||
to a key in ``config.services``.
|
|
||||||
|
|
||||||
We will typecheck the service template function parameters using the
|
|
||||||
same type-checking code as NixOS uses for its modules.
|
|
||||||
|
|
||||||
An example may make this clearer: to add an NTP
|
|
||||||
service you first add :file:`modules/ntp` to your ``imports`` list,
|
|
||||||
then you create a service by calling
|
|
||||||
:code:`config.system.service.ntp.build { .... }` with the appropriate
|
|
||||||
service-dependent configuration parameters.
|
|
||||||
|
|
||||||
.. code-block:: nix
|
|
||||||
|
|
||||||
let svc = config.system.service;
|
|
||||||
in {
|
|
||||||
# ...
|
|
||||||
imports = [
|
|
||||||
./modules/ntp
|
|
||||||
# ....
|
|
||||||
];
|
|
||||||
config.services.ntp = svc.ntp.build {
|
|
||||||
pools = { "pool.ntp.org" = ["iburst"]; };
|
|
||||||
makestep = { threshold = 1.0; limit = 3; };
|
|
||||||
};
|
|
||||||
|
|
||||||
Merely including the module won't define the service on its own: it
|
|
||||||
only creates the template in ``config.system.service.foo`` and you
|
|
||||||
have to create the actual service using the template.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Consequences
|
|
||||||
************
|
|
||||||
|
|
||||||
This decision has both good and bad consequences
|
|
||||||
|
|
||||||
Pro
|
|
||||||
===
|
|
||||||
|
|
||||||
* We have a workable system for reusing configuration elements in
|
|
||||||
Liminix.
|
|
||||||
|
|
||||||
* We have type checking for most imortant things, reducing the risk of
|
|
||||||
deploying an invalid configuration.
|
|
||||||
|
|
||||||
* We have a simple mechanism for creating multiple services based on
|
|
||||||
the same module, without buulding that logic into the module
|
|
||||||
definition itself. For example, we could create two SSH daemons on
|
|
||||||
different ports, or DHCP clients with different configurations on
|
|
||||||
different network devices.
|
|
||||||
|
|
||||||
* We expect to be able to automate the generation of module
|
|
||||||
documentation.
|
|
||||||
|
|
||||||
Con
|
|
||||||
===
|
|
||||||
|
|
||||||
|
|
||||||
* By departing somewhat from the NixOS conventions we increase the
|
|
||||||
amount of code we have to write/maintain ourselves - and the
|
|
||||||
learning burden on users who are already familiar with that system.
|
|
||||||
|
|
||||||
* Liminix configurations contain function calls and aren't just data,
|
|
||||||
which means we can ony realistically interpret or introspect
|
|
||||||
them with the Nix interpreter itself - we can't query them
|
|
||||||
as data with other non-Nix tools.
|
|
17
doc/conf.py
17
doc/conf.py
|
@ -7,19 +7,16 @@
|
||||||
# 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'
|
|
||||||
]
|
|
||||||
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']
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -28,11 +25,3 @@ exclude_patterns = ['*.inc.rst', '_build', 'Thumbs.db', '.DS_Store']
|
||||||
|
|
||||||
html_theme = 'alabaster'
|
html_theme = 'alabaster'
|
||||||
html_static_path = ['_static']
|
html_static_path = ['_static']
|
||||||
|
|
||||||
html_theme_options = {
|
|
||||||
'logo': '/logo.svg',
|
|
||||||
'globaltoc_collapse': 'false',
|
|
||||||
'page_width': '90%',
|
|
||||||
'body_max_width': '90%',
|
|
||||||
'description': 'A Nix-based OpenWrt-style embedded Linux system for consumer wifi routers'
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,482 +0,0 @@
|
||||||
.. _configuration:
|
|
||||||
|
|
||||||
Configuration
|
|
||||||
#############
|
|
||||||
|
|
||||||
There are many things you can specify in a configuration, but these
|
|
||||||
are the ones you most commonly need to change:
|
|
||||||
|
|
||||||
* which services (processes) to run
|
|
||||||
* what packages to install
|
|
||||||
* permitted users and groups
|
|
||||||
* Linux kernel configuration options
|
|
||||||
* Busybox applets
|
|
||||||
* filesystem layout
|
|
||||||
|
|
||||||
|
|
||||||
Modules
|
|
||||||
*******
|
|
||||||
|
|
||||||
**Modules** are a means of abstraction which allow "bundling"
|
|
||||||
of configuration options related to a common purpose or theme. For
|
|
||||||
example, the ``dnsmasq`` module defines a template for a dnsmasq
|
|
||||||
service, ensures that the dnsmasq package is installed, and provides a
|
|
||||||
dnsmasq user and group for the service to run as. The ``ppp`` module
|
|
||||||
defines a service template and also enables various PPP-related kernel
|
|
||||||
configuration.
|
|
||||||
|
|
||||||
Not all modules are included in the configuration by default, because
|
|
||||||
that would mean that the kernel (and the Busybox binary providing
|
|
||||||
common CLI tools) was compiled with many unnecessary bells and whistles
|
|
||||||
and therefore be bigger than needed. (This is not purely an academic concern
|
|
||||||
if your device has little flash storage). Therefore, specifying a
|
|
||||||
service is usually a two-step process. For example, to add an NTP
|
|
||||||
service you first add :file:`modules/ntp` to your ``imports`` list,
|
|
||||||
then you create a service by calling
|
|
||||||
:code:`config.system.service.ntp.build { .... }` with the appropriate
|
|
||||||
service-dependent configuration parameters.
|
|
||||||
|
|
||||||
.. code-block:: nix
|
|
||||||
|
|
||||||
let svc = config.system.service;
|
|
||||||
in {
|
|
||||||
# ...
|
|
||||||
imports = [
|
|
||||||
./modules/ntp
|
|
||||||
# ....
|
|
||||||
];
|
|
||||||
config.services.ntp = svc.ntp.build {
|
|
||||||
pools = { "pool.ntp.org" = ["iburst"]; };
|
|
||||||
makestep = { threshold = 1.0; limit = 3; };
|
|
||||||
};
|
|
||||||
|
|
||||||
Merely including the module won't define the service on its own: it
|
|
||||||
only creates the template in ``config.system.service.foo`` and you
|
|
||||||
have to create an actual service using the template. This is an
|
|
||||||
intentional choice to allow the creation of multiple
|
|
||||||
differently-configured services based on the same template - perhaps
|
|
||||||
e.g. when you have multiple networks (VPNs etc) in different trust
|
|
||||||
domains, or you want to run two SSH daemons on different ports.
|
|
||||||
(For the background to this, please refer to the :doc:`architecture decision record <adr/module-system>`)
|
|
||||||
|
|
||||||
.. tip:: Liminix modules should be quite familiar (but also different)
|
|
||||||
if you already know how to use NixOS modules. We use the
|
|
||||||
NixOS module infrastructure code, meaning that you should
|
|
||||||
recognise the syntax, the type system, the rules for
|
|
||||||
combining configuration values from different sources. We
|
|
||||||
don't use the NixOS modules themselves, because the
|
|
||||||
underlying system is not similar enough for them to work.
|
|
||||||
|
|
||||||
.. _configuration-services:
|
|
||||||
|
|
||||||
Services
|
|
||||||
********
|
|
||||||
|
|
||||||
In Liminix a service is any kind of long-running task or process on
|
|
||||||
the system, that is managed (started, stopped, and monitored) by a
|
|
||||||
service supervisor. A typical SOHO router might have services to
|
|
||||||
|
|
||||||
* 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
|
|
||||||
need will be defined by modules and you will only have to pass the
|
|
||||||
right parameters to ``build``.
|
|
||||||
|
|
||||||
Should you need to create a custom service of your own devising, use
|
|
||||||
the `oneshot` or `longrun` functions:
|
|
||||||
|
|
||||||
* a "longrun" service is the "normal" service concept: it has a
|
|
||||||
``run`` action which describes the process to start, and it watches
|
|
||||||
that process to restart it if it exits. The process should not
|
|
||||||
attempt to daemonize or "background" itself, otherwise s6-rc will think
|
|
||||||
it died. Whatever it prints to standard output/standard error
|
|
||||||
will be logged.
|
|
||||||
|
|
||||||
.. code-block:: nix
|
|
||||||
|
|
||||||
config.services.cowsayd = pkgs.liminix.services.longrun {
|
|
||||||
name = "cowsayd";
|
|
||||||
run = "${pkgs.cowsayd}/bin/cowsayd --port 3001 --breed hereford";
|
|
||||||
# don't start this until the lan interface is ready
|
|
||||||
dependencies = [ config.services.lan ];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
* a "oneshot" service doesn't have a process attached. It consists of
|
|
||||||
``up`` and ``down`` actions which are bits of shell script that
|
|
||||||
are run at the appropriate points in the service lifecycle
|
|
||||||
|
|
||||||
.. code-block:: nix
|
|
||||||
|
|
||||||
config.services.greenled = pkgs.liminix.services.oneshot {
|
|
||||||
name = "greenled";
|
|
||||||
up = ''
|
|
||||||
echo 17 > /sys/class/gpio/export
|
|
||||||
echo out > /sys/class/gpio/gpio17/direction
|
|
||||||
echo 0 > /sys/class/gpio/gpio17/value
|
|
||||||
'';
|
|
||||||
down = ''
|
|
||||||
echo 0 > /sys/class/gpio/gpio17/value
|
|
||||||
'';
|
|
||||||
}
|
|
||||||
|
|
||||||
Services may have dependencies: as you see above in the ``cowsayd``
|
|
||||||
example, it depends on some service called ``config.services.lan``,
|
|
||||||
meaning that it won't be started until that other service is up.
|
|
||||||
|
|
||||||
Service outputs
|
|
||||||
===============
|
|
||||||
|
|
||||||
Outputs are a mechanism by which a service can provide data which may
|
|
||||||
be required by other services. For example:
|
|
||||||
|
|
||||||
* the DHCP client service can expect to receive nameserver address
|
|
||||||
information as one of the fields in the response from the DHCP
|
|
||||||
server: we provide that as an output which a dependent service for a
|
|
||||||
stub name resolver can use to configure its upstream servers.
|
|
||||||
|
|
||||||
* a service that creates a new network interface (e.g. ppp) will
|
|
||||||
provide the name of the interface (: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.
|
|
||||||
|
|
||||||
|
|
||||||
Module implementation
|
|
||||||
*********************
|
|
||||||
|
|
||||||
Modules in Liminix conventionally live in
|
|
||||||
:file:`modules/somename/default.nix`. If you want or need to
|
|
||||||
write your own, you may wish to refer to the
|
|
||||||
examples there in conjunction with reading this section.
|
|
||||||
|
|
||||||
A module is a function that accepts ``{lib, pkgs, config, ... }`` and
|
|
||||||
returns an attrset with keys ``imports, options config``.
|
|
||||||
|
|
||||||
* ``imports`` is a list of paths to the other modules required by this one
|
|
||||||
|
|
||||||
* ``options`` is a nested set of option declarations
|
|
||||||
|
|
||||||
* ``config`` is a nested set of option definitions
|
|
||||||
|
|
||||||
The NixOS manual section `Writing NixOS Modules
|
|
||||||
<https://nixos.org/manual/nixos/stable/#sec-writing-modules>`_ is a
|
|
||||||
quite comprehensive reference to writing NixOS modules, which is also
|
|
||||||
mostly applicable to Liminix except that it doesn't cover
|
|
||||||
service templates.
|
|
||||||
|
|
||||||
Service templates
|
|
||||||
=================
|
|
||||||
|
|
||||||
To expose a service template in a module, it needs the following:
|
|
||||||
|
|
||||||
* an option declaration for ``system.service.myservicename`` with the
|
|
||||||
type of ``liminix.lib.types.serviceDefn``
|
|
||||||
|
|
||||||
.. code-block:: nix
|
|
||||||
|
|
||||||
options = {
|
|
||||||
system.service.cowsay = mkOption {
|
|
||||||
type = liminix.lib.types.serviceDefn;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
* an option definition for the same key, which specifies where to
|
|
||||||
import the service template from (often :file:`./service.nix`)
|
|
||||||
and the types of its parameters.
|
|
||||||
|
|
||||||
.. code-block:: nix
|
|
||||||
|
|
||||||
config.system.service.cowsay = config.system.callService ./service.nix {
|
|
||||||
address = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
default = "0.0.0.0";
|
|
||||||
description = "Listen on specified address";
|
|
||||||
example = "127.0.0.1";
|
|
||||||
};
|
|
||||||
port = mkOption {
|
|
||||||
type = types.port;
|
|
||||||
default = 22;
|
|
||||||
description = "Listen on specified TCP port";
|
|
||||||
};
|
|
||||||
breed = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
default = "British Friesian"
|
|
||||||
description = "Breed of the cow";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
Then you need to provide the service template itself, probably in
|
|
||||||
:file:`./service.nix`:
|
|
||||||
|
|
||||||
.. code-block:: nix
|
|
||||||
|
|
||||||
{
|
|
||||||
# any nixpkgs package can be named here
|
|
||||||
liminix
|
|
||||||
, cowsayd
|
|
||||||
, serviceFns
|
|
||||||
, lib
|
|
||||||
}:
|
|
||||||
# these are the parameters declared in the callService invocation
|
|
||||||
{ address, port, breed} :
|
|
||||||
let
|
|
||||||
inherit (liminix.services) longrun;
|
|
||||||
inherit (lib.strings) escapeShellArg;
|
|
||||||
in longrun {
|
|
||||||
name = "cowsayd";
|
|
||||||
run = "${cowsayd}/bin/cowsayd --address ${address} --port ${builtins.toString port} --breed ${escapeShellArg breed}";
|
|
||||||
}
|
|
||||||
|
|
||||||
.. tip::
|
|
||||||
|
|
||||||
Not relevant to module-based services specifically, but a common
|
|
||||||
gotcha when specifiying services is forgetting to transform "rich"
|
|
||||||
parameter values into text when composing a command for the shell
|
|
||||||
to execute. Note here that the port number, an integer, is
|
|
||||||
stringified with ``toString``, and the name of the breed,
|
|
||||||
which may contain spaces, is
|
|
||||||
escaped with ``escapeShellArg``
|
|
||||||
|
|
||||||
Types
|
|
||||||
=====
|
|
||||||
|
|
||||||
All of the NixOS module types are available in Liminix. These
|
|
||||||
Liminix-specific types also exist in ``pkgs.liminix.lib.types``:
|
|
||||||
|
|
||||||
* ``service``: an s6-rc service
|
|
||||||
* ``interface``: an s6-rc service which specifies a network
|
|
||||||
interface
|
|
||||||
* ``serviceDefn``: a service "template" definition
|
|
||||||
|
|
||||||
In the future it is likely that we will extend this to include other
|
|
||||||
useful types in the networking domain: for example; IP address,
|
|
||||||
network prefix or netmask, protocol family and others as we find them.
|
|
|
@ -1,14 +1,14 @@
|
||||||
Development
|
Developer Manual
|
||||||
###########
|
################
|
||||||
|
|
||||||
As a developer working on Liminix, or implementing a service or
|
As a developer working on Liminix, or implementing a service or
|
||||||
module, you probably want to test your changes more conveniently
|
module, you probably want to test your changes more conveniently
|
||||||
than by building and flashing a new image every time. This section
|
than by building and flashing a new image every time. This manual
|
||||||
documents various affordances for iteration and experiments.
|
documents various affordances for iteration and experiments.
|
||||||
|
|
||||||
In general, packages and tools that run on the "build" machine are
|
In general, packages and tools that run on the "build" machine are
|
||||||
available in the ``buildEnv`` derivation and can most easily
|
available in the ``buildEnv`` derivation and can most easily
|
||||||
be added to your environment by running :command:`nix-shell`.
|
be added to your environment by running :command:`nix-shell`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -27,18 +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:`mips-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 "mips-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:
|
|
||||||
|
|
||||||
Networking
|
Networking
|
||||||
==========
|
==========
|
||||||
|
@ -52,11 +53,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:`mips-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,12 +87,6 @@ time with configurations for RP-PPPoE and/or Accel PPP.`
|
||||||
Hardware devices
|
Hardware devices
|
||||||
****************
|
****************
|
||||||
|
|
||||||
|
|
||||||
TFTP
|
|
||||||
====
|
|
||||||
|
|
||||||
.. _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
|
||||||
device, but is likely to involve taking it apart to add wires to
|
device, but is likely to involve taking it apart to add wires to
|
||||||
serial console pads/headers, then using U-Boot to fetch images over
|
serial console pads/headers, then using U-Boot to fetch images over
|
||||||
|
@ -122,7 +115,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 +148,6 @@ U-Boot to transfer the kernel and filesystem over TFTP and boot the
|
||||||
kernel from RAM.
|
kernel from RAM.
|
||||||
|
|
||||||
|
|
||||||
.. _bng:
|
|
||||||
|
|
||||||
Networking
|
Networking
|
||||||
==========
|
==========
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
The Future
|
||||||
|
##########
|
||||||
|
|
||||||
|
What about NixWRT?
|
||||||
|
|
||||||
|
This is an in-progress rewrite of NixWRT, incorporating Lessons
|
||||||
|
Learned. That said, as of today it is not yet at feature parity.
|
||||||
|
|
||||||
|
Liminix will eventually provide these differentiators over NixWRT:
|
||||||
|
|
||||||
|
* a writable filesystem so that software updates or reconfiguration
|
||||||
|
(e.g. changing passwords) don't require taking the device offline to
|
||||||
|
reflash it.
|
||||||
|
|
||||||
|
* more flexible service management with dependencies, to allow
|
||||||
|
configurations such as "route through PPPoE if it is healthy, with
|
||||||
|
fallback to LTE"
|
||||||
|
|
||||||
|
* a spec for valid configuration options (a la NixOS module options)
|
||||||
|
to that we can detect errors at evaluation time instead of producing
|
||||||
|
a bad image.
|
||||||
|
|
||||||
|
* a network-based mechanism for secrets management so that changes can
|
||||||
|
be pushed from a central location to several Liminix devices at once
|
||||||
|
|
||||||
|
* send device metrics and logs to a monitoring/alerting/o11y
|
||||||
|
infrastructure
|
||||||
|
|
||||||
|
Today though, it does approximately none of these things and certainly
|
||||||
|
not on real hardware.
|
||||||
|
|
||||||
|
|
||||||
|
Articles of interest
|
||||||
|
####################
|
||||||
|
|
||||||
|
* `Build Safety of Software in 28 Popular Home Routers <https://cyber-itl.org/assets/papers/2018/build_safety_of_software_in_28_popular_home_routers.pdf>`_: "of the access
|
||||||
|
points and routers we reviewed, not a single one took full
|
||||||
|
advantage of the basic application armoring features provided by
|
||||||
|
the operating system. Indeed, only one or two models even came
|
||||||
|
close, and no brand did well consistently across all models tested"
|
||||||
|
|
||||||
|
* `A PPPoE Implementation for Linux <https://static.usenix.org/publications/library/proceedings/als00/2000papers/papers/full_papers/skoll/skoll_html/index.html>`_:
|
||||||
|
"Many DSL service providers use PPPoE for residential broadband
|
||||||
|
Internet access. This paper briefly describes the PPPoE protocol,
|
||||||
|
presents strategies for implementing it under Linux and describes in
|
||||||
|
detail a user-space implementation of a PPPoE client."
|
||||||
|
|
||||||
|
* `PPP IPV6CP vs DHCPv6 at AAISP <https://www.revk.uk/2011/01/ppp-ipv6cp-vs-dhcpv6.html>`_
|
||||||
|
|
||||||
|
|
||||||
|
* `Creating a Home IPv6 Network (James Bottomley) <https://blog.hansenpartnership.com/creating-a-home-ipv6-network/>`_
|
|
@ -1,9 +1,31 @@
|
||||||
{ 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:
|
||||||
|
(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 +41,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}
|
||||||
|
'';
|
||||||
|
}
|
||||||
|
|
|
@ -1,36 +0,0 @@
|
||||||
with import <nixpkgs> { };
|
|
||||||
|
|
||||||
let
|
|
||||||
inherit (builtins) stringLength readDir filter;
|
|
||||||
devices = filter (n: n != "families") (lib.mapAttrsToList (n: t: n) (readDir ../devices));
|
|
||||||
texts = map (
|
|
||||||
n:
|
|
||||||
let
|
|
||||||
d = import ../devices/${n}/default.nix;
|
|
||||||
tag = ".. _${lib.strings.replaceStrings [" "] ["-"] n}:";
|
|
||||||
d' = {
|
|
||||||
description = ''
|
|
||||||
${n}
|
|
||||||
${substring 0 (stringLength n) "********************************"}
|
|
||||||
'';
|
|
||||||
} // d;
|
|
||||||
in
|
|
||||||
"${tag}\n\n${d'.description}"
|
|
||||||
) devices;
|
|
||||||
in
|
|
||||||
writeText "hwdoc" ''
|
|
||||||
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}
|
|
||||||
|
|
||||||
''
|
|
|
@ -6,14 +6,9 @@ Liminix
|
||||||
:caption: Contents:
|
:caption: Contents:
|
||||||
|
|
||||||
intro
|
intro
|
||||||
tutorial
|
user
|
||||||
installation
|
developer
|
||||||
configuration
|
etc
|
||||||
admin
|
|
||||||
development
|
|
||||||
modules
|
|
||||||
hardware
|
|
||||||
outputs
|
|
||||||
|
|
||||||
|
|
||||||
Indices and tables
|
Indices and tables
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
Module options
|
|
||||||
##############
|
|
||||||
|
|
||||||
.. include:: modules-generated.inc.rst
|
|
|
@ -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
|
|
|
@ -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")))
|
|
|
@ -2,17 +2,13 @@
|
||||||
|
|
||||||
(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]
|
||||||
|
(if (= (pathname:sub 1 1) "/")
|
||||||
(let [pathname (if (string.match pathname ".nix$")
|
(let [pathname (if (string.match pathname ".nix$")
|
||||||
pathname
|
pathname
|
||||||
(.. pathname "/default.nix"))]
|
(.. pathname "/default.nix"))]
|
||||||
|
@ -20,7 +16,7 @@
|
||||||
(accumulate [lines nil
|
(accumulate [lines nil
|
||||||
l (f:lines)
|
l (f:lines)
|
||||||
:until (not (= (string.sub l 1 2) "##"))]
|
:until (not (= (string.sub l 1 2) "##"))]
|
||||||
(.. (or lines "") (string.gsub l "^## *" "") "\n")))))
|
(.. (or lines "") (string.gsub l "^## *" "") "\n"))))))
|
||||||
|
|
||||||
(fn relative-pathname [pathname]
|
(fn relative-pathname [pathname]
|
||||||
(let [pathname
|
(let [pathname
|
||||||
|
|
324
doc/tutorial.rst
324
doc/tutorial.rst
|
@ -1,324 +0,0 @@
|
||||||
Tutorial
|
|
||||||
########
|
|
||||||
|
|
||||||
Liminix is very configurable, which can make it initially quite
|
|
||||||
daunting - especially if you're learning Nix or Linux or networking
|
|
||||||
concepts at the same time. In this section we build some "worked
|
|
||||||
example" Liminix images to introduce the concepts. If you follow the
|
|
||||||
examples exactly, they should work. If you change things as you go
|
|
||||||
along, they may work differently or not at all, but the experience
|
|
||||||
should be educational either way.
|
|
||||||
|
|
||||||
|
|
||||||
Requirements
|
|
||||||
************
|
|
||||||
|
|
||||||
You will need a reasonably powerful computer running Nix. Target
|
|
||||||
devices for Liminix are unlikely to have the CPU power and disk space
|
|
||||||
to be able to build it in situ, so the build process is based around
|
|
||||||
"cross-compilation" from another computer. The build machine can be
|
|
||||||
any reasonably powerful desktop/laptop/server PC running NixOS.
|
|
||||||
Standalone Nixpkgs installations on other Linux distributions - or on
|
|
||||||
MacOS, or even in a Docker container - also ought to work but are
|
|
||||||
untested.
|
|
||||||
|
|
||||||
|
|
||||||
Running in Qemu
|
|
||||||
***************
|
|
||||||
|
|
||||||
You can try out Liminix without even having a router to play with.
|
|
||||||
Clone the Liminix git repository and change into its directory
|
|
||||||
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
git clone https://gti.telent.net/dan/liminix
|
|
||||||
cd liminix
|
|
||||||
|
|
||||||
Now build Liminix
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
nix-build -I liminix-config=./examples/hello-from-qemu.nix \
|
|
||||||
--arg device "import ./devices/qemu" -A outputs.default
|
|
||||||
|
|
||||||
In this command ``liminix-config`` points to the desired software
|
|
||||||
configuration (e.g. services, users, filesystem, secrets) and
|
|
||||||
``device`` describes the hardware (or emulated hardware) to run it on.
|
|
||||||
``outputs.default`` tells Liminix that we want the default image
|
|
||||||
output for flashing to the device: for the Qemu "hardware" it's an
|
|
||||||
alias for ``outputs.vmbuild``, which creates a directory containing a
|
|
||||||
root filesystem image and a kernel.
|
|
||||||
|
|
||||||
.. tip:: The first time you run this it may take several hours,
|
|
||||||
because it builds all of the dependencies including a full
|
|
||||||
MIPS gcc and library toolchain. Once those intermediate build
|
|
||||||
products are in the nix store, subsequent builds will be much
|
|
||||||
faster - practically instant, if nothing has changed.
|
|
||||||
|
|
||||||
Now you can try it:
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
./result/run.sh
|
|
||||||
|
|
||||||
This starts the Qemu emulator with a bunch of useful options, to run
|
|
||||||
the Liminix configuration you just built. It connects the emulated
|
|
||||||
device's serial console and the `QEMU monitor
|
|
||||||
<https://www.qemu.org/docs/master/system/monitor.html>`_ to
|
|
||||||
stdin/stdout.
|
|
||||||
|
|
||||||
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
|
|
||||||
the filesystem, see what processes are running, view log messages (in
|
|
||||||
:file:/run/uncaught-logs.current), etc. To kill the emulator, press ^P
|
|
||||||
(Control P) then c to enter the "QEMU Monitor", then type ``quit`` at
|
|
||||||
the ``(qemu)`` prompt.
|
|
||||||
|
|
||||||
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
|
|
||||||
open up a second terminal on your build machine. We're going to run
|
|
||||||
another virtual machine attached to the virtual network, which will
|
|
||||||
request an IP address from our Liminix system and give you a shell you
|
|
||||||
can run ssh from.
|
|
||||||
|
|
||||||
We use `System Rescue <https://www.system-rescue.org/>`_ in tty
|
|
||||||
mode (no graphical output) for this example, but if you have some
|
|
||||||
other favourite Linux Live CD ISO - or, for that matter, any other OS
|
|
||||||
image that QEMU can boot - adjust the command to suit.
|
|
||||||
|
|
||||||
Download the System Rescue ISO:
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
curl https://fastly-cdn.system-rescue.org/releases/10.01/systemrescue-10.01-amd64.iso -O
|
|
||||||
|
|
||||||
and run it
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
nix-shell -p qemu --run " \
|
|
||||||
qemu-system-x86_64 \
|
|
||||||
-echr 16 \
|
|
||||||
-m 1024 \
|
|
||||||
-cdrom systemrescue-10.01-amd64.iso \
|
|
||||||
-netdev socket,mcast=230.0.0.1:1235,localaddr=127.0.0.1,id=lan \
|
|
||||||
-device virtio-net,disable-legacy=on,disable-modern=off,netdev=lan,mac=ba:ad:3d:ea:21:01 \
|
|
||||||
-display none -serial mon:stdio"
|
|
||||||
|
|
||||||
System Rescue displays a boot menu at which you should select the
|
|
||||||
"serial console" option, then after a few moments it boots to a root
|
|
||||||
prompt. You can now try things out:
|
|
||||||
|
|
||||||
* run :command:`ip a` and see that it's been allocated an IP address in the range 10.3.0.0/16.
|
|
||||||
|
|
||||||
* run :command:`ping 10.3.0.1` to see that the Liminix VM responds
|
|
||||||
|
|
||||||
* run :command:`ssh root@10.3.0.1` to try logging into it.
|
|
||||||
|
|
||||||
Congratulations! You have installed your first Liminix system - albeit
|
|
||||||
it has no practical use and it's not even real. The next step is to try
|
|
||||||
running it on hardware.
|
|
||||||
|
|
||||||
Installing on hardware
|
|
||||||
**********************
|
|
||||||
|
|
||||||
For the next example, we're going to install onto an actual hardware
|
|
||||||
device. These steps have been tested using a GL.iNet GL-MT300A, which
|
|
||||||
has been chosen for the purpose because it's cheap and easy to
|
|
||||||
unbrick if necessary.
|
|
||||||
|
|
||||||
.. warning:: There is always a risk of rendering your device
|
|
||||||
unbootable by flashing it with an image that doesn't
|
|
||||||
work. The GL-MT300A 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. Using some other
|
|
||||||
Liminix-supported MIPS hardware device also *ought* to
|
|
||||||
work here, but you accept the slightly greater bricking
|
|
||||||
risk if it doesn't.
|
|
||||||
|
|
||||||
See :doc:`hardware` for device support status.
|
|
||||||
|
|
||||||
You may want to read and inwardly digest the Develoment Manual section
|
|
||||||
:ref:`serial` when you start working with Liminix on real hardware. You
|
|
||||||
won't *need* serial access for this example, assuming it works, but it
|
|
||||||
allows you
|
|
||||||
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.
|
|
||||||
|
|
||||||
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
|
|
||||||
server into your working LAN because it will compete with the real
|
|
||||||
DHCP service. So we're going to use a different configuration with a
|
|
||||||
DHCP client: this is :file:`examples/hello-from-mt300.nix`
|
|
||||||
|
|
||||||
It's instructive to compare the two configurations:
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
diff -u examples/hello-from-qemu.nix examples/hello-from-mt300.nix
|
|
||||||
|
|
||||||
You'll see a new ``boot.tftp`` stanza which you can ignore,
|
|
||||||
``services.dns`` has been removed, and the static IP address allocation
|
|
||||||
has been replaced by a ``dhcp.client`` service.
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
nix-build -I liminix-config=./examples/hello-from-mt300.nix \
|
|
||||||
--arg device "import ./devices/gl-mt300a" -A outputs.default
|
|
||||||
|
|
||||||
.. tip:: The first time you run this it may take several hours.
|
|
||||||
Again? Yes, even if you ran the previous example. Qemu is
|
|
||||||
set up as a big-endian system whereas the MediaTek SoC
|
|
||||||
on this device is little-endian - so it requires building
|
|
||||||
all of the dependencies including an entirely different
|
|
||||||
MIPS gcc and library toolchain to the other one.
|
|
||||||
|
|
||||||
This time in :file:`result/` you will see a bunch of files. Most of
|
|
||||||
them you can ignore for the moment, but :file:`result/firmware.bin` is
|
|
||||||
the firmware image you can flash.
|
|
||||||
|
|
||||||
|
|
||||||
Flashing
|
|
||||||
========
|
|
||||||
|
|
||||||
Again, there are a number of different ways you could do this: using
|
|
||||||
TFTP with a serial cable, through the stock firmware's web UI, or
|
|
||||||
using the `vendor's "debrick" process
|
|
||||||
<https://docs.gl-inet.com/router/en/3/tutorials/debrick/>`_. The last
|
|
||||||
of these options has a lot to recommend it for a first attempt:
|
|
||||||
|
|
||||||
* it works no matter what firmware is currently installed
|
|
||||||
|
|
||||||
* it doesn't require plugging a router into the same network as your
|
|
||||||
build system and potentially messing up your actual upstream
|
|
||||||
|
|
||||||
* no need to open the device and add cables
|
|
||||||
|
|
||||||
You can read detailed instructions on the vendor site, but the short version is:
|
|
||||||
|
|
||||||
1. turn the device off
|
|
||||||
2. connect it by ethernet cable to a computer
|
|
||||||
3. configure the computer to have static ip address 192.168.1.10
|
|
||||||
4. while holding down the Reset button, turn the device on
|
|
||||||
5. after about five seconds you can release the Reset button
|
|
||||||
6. visit http://192.168.1.1/ using a web browser on the connected computer
|
|
||||||
7. click on "Browse" and choose :file:`result/firmware.bin`
|
|
||||||
8. click on "Update firmware"
|
|
||||||
9. wait a minute or so while it updates.
|
|
||||||
|
|
||||||
There's no feedback from the web interface when the flashing is
|
|
||||||
finished, but what should happen is that the router reboots and
|
|
||||||
starts running Liminix. Now you need to figure out what address it got
|
|
||||||
from DHCP - e.g. by checking the DHCP server logs, or maybe by pinging
|
|
||||||
``hello.lan`` or something. Once you've found it on the
|
|
||||||
network you can ping it and ssh to it just like you did the Qemu
|
|
||||||
example, but this time for real.
|
|
||||||
|
|
||||||
.. warning:: Do not leave the default root password in place on any
|
|
||||||
device exposed to the internet! Although it has no
|
|
||||||
writable storage and no default route, a motivated attacker
|
|
||||||
with some imagination could probably still do something
|
|
||||||
awful using it.
|
|
||||||
|
|
||||||
Congratulations Part II! You have installed your first Liminix system on actual hardware - albeit that it *still* has no practical use.
|
|
||||||
|
|
||||||
Exercise for the reader: change the default password by editing
|
|
||||||
:file:`examples/hello-from-mt300.nix`, and then create and upload a
|
|
||||||
new image that has it set to something less hopeless.
|
|
||||||
|
|
||||||
Routing
|
|
||||||
*******
|
|
||||||
|
|
||||||
The third example :file:`examples/demo.nix` is a fully-functional home
|
|
||||||
"WiFi router" - although you will have to edit it a bit before it will
|
|
||||||
actually work for you. Copy :file:`examples/demo.nix` to
|
|
||||||
:file:`my-router.nix` (or other name of your choice) and open it in
|
|
||||||
your favourite text editor. Everywhere that the text :code:`EDIT`
|
|
||||||
appears is either a place you probably want to change or a place you
|
|
||||||
almost certainly need to change.
|
|
||||||
|
|
||||||
There's a lot going on in this configuration:
|
|
||||||
|
|
||||||
* it provides a wireless access point using the :code:`hostapd`
|
|
||||||
service: in this stanza you can change the ssid, the channel,
|
|
||||||
the passphrase etc.
|
|
||||||
|
|
||||||
* the wireless lan and wired lan are bridged together with the
|
|
||||||
:code:`bridge` service, so that your wired and wireless clients appear
|
|
||||||
to be on the same network.
|
|
||||||
|
|
||||||
.. tip:: If you were using a hardware device that provides both 2.4GHz
|
|
||||||
and 5GHz wifi, you'd probably find that it has two wireless
|
|
||||||
devices (often called wlan0 and wlan1). In Liminix we handle
|
|
||||||
this by running two :code:`hostapd` services, and adding
|
|
||||||
both of them to the network bridge along with the wired lan.
|
|
||||||
(You can see an example in :file:`examples/rotuer.nix`)
|
|
||||||
|
|
||||||
* we use the combination DNS and DHCP daemon provided by the
|
|
||||||
:code:`dnsmasq` service, which you can configure
|
|
||||||
|
|
||||||
* the upstream network is "PPP over Ethernet", provided by the
|
|
||||||
:code:`pppoe` service. Assuming that your ISP uses this standard,
|
|
||||||
they will have provided you with a PPP username and password
|
|
||||||
(sometimes this will be listed as "PAP" or "CHAP") which you can edit
|
|
||||||
into the configuration
|
|
||||||
|
|
||||||
* this example supports the new [#ipv6]_ Internet Protocol v6
|
|
||||||
as well as traditional IPv4. Configuring IPv6 seems to
|
|
||||||
vary from one ISP to the next: this example expects them
|
|
||||||
to be providing IP address allocation and "prefix delegation"
|
|
||||||
using DHCP6.
|
|
||||||
|
|
||||||
Build it using the same method as the previous example
|
|
||||||
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
nix-build -I liminix-config=./my-router.nix \
|
|
||||||
--arg device "import ./devices/gl-mt300a" -A outputs.default
|
|
||||||
|
|
||||||
and then you can flash it to the device.
|
|
||||||
|
|
||||||
|
|
||||||
Bonus: in-place updates
|
|
||||||
=======================
|
|
||||||
|
|
||||||
This configuration uses a writable filesystem (see the line
|
|
||||||
:code:`rootfsType = "jffs2"`), which means that once you've flashed it
|
|
||||||
for the first time, you can make further updates over SSH onto the
|
|
||||||
running router. To try this, make a small change (I'd suggest changing
|
|
||||||
the hostname) and then run
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
nix-shell --run "liminix-rebuild root@address-of-the-device -I liminix-config=./my-router.nix --arg device "import ./devices/gl-ar750""
|
|
||||||
|
|
||||||
(This requires the device to be network-accessible from your build
|
|
||||||
machine, which for a test/demo system might involve a second network
|
|
||||||
device in your build system - USB ethernet adapters are cheap - or
|
|
||||||
a bit of messing around unplugging cables.)
|
|
||||||
|
|
||||||
For more information about :code:`liminix-rebuild`, see the manual section :ref:`Rebuilding the system`.
|
|
||||||
|
|
||||||
|
|
||||||
Final thoughts
|
|
||||||
**************
|
|
||||||
|
|
||||||
* These are demonstration configs for pedagogical purposes. If you'd
|
|
||||||
like to see some more realistic uses of Liminix,
|
|
||||||
:file:`examples/rotuer,arhcive,extneder.nix` are based on some
|
|
||||||
actual real hosts in my home network.
|
|
||||||
|
|
||||||
* The technique used here for flashing was chosen mostly because it
|
|
||||||
doesn't need much infrastructure/tooling, but it is a bit of a faff
|
|
||||||
(requires physical access, vendor specific). There are slicker ways
|
|
||||||
to do it that need a bit more setup - we'll talk about that later as
|
|
||||||
well.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. rubric:: Footnotes
|
|
||||||
|
|
||||||
.. [#ipv6] `RFC1883 Internet Protocol, Version 6 <https://datatracker.ietf.org/doc/html/rfc1883>`_ was published in 1995, so only "new" when Bill Clinton was US President
|
|
|
@ -0,0 +1,347 @@
|
||||||
|
User Manual
|
||||||
|
###########
|
||||||
|
|
||||||
|
This manual is an early work in progress, not least because Liminix is
|
||||||
|
not yet really ready for users who are not also developers. Your
|
||||||
|
feedback to improve it is very welcome.
|
||||||
|
|
||||||
|
Installation
|
||||||
|
************
|
||||||
|
|
||||||
|
The Liminix installation process is not quite like installing NixOS on
|
||||||
|
a real computer, but some NixOS experience will nevertheless be
|
||||||
|
helpful in understanding it. The steps are as follows:
|
||||||
|
|
||||||
|
* Decide whether you want the device to be updatable in-place (there
|
||||||
|
are advantages and disadvantages), or if you are happy to generate
|
||||||
|
and flash a new image whenever changes are required.
|
||||||
|
|
||||||
|
* Create a :file:`configuration.nix` describing the system you want
|
||||||
|
|
||||||
|
* Build an image
|
||||||
|
|
||||||
|
* Flash it to the device
|
||||||
|
|
||||||
|
Supported devices
|
||||||
|
=================
|
||||||
|
|
||||||
|
For a list of devices that Liminix (present or previous versions)
|
||||||
|
has run on, refer to `devices/ in the source repo <https://gti.telent.net/dan/liminix/src/branch/main/devices>`_. For devices that _currently_ build,
|
||||||
|
cross-reference it with `the CI status <https://build.liminix.org/jobset/liminix/build#tabs-jobs>`_. Everything that builds is (usually) expected
|
||||||
|
to run, so if you end up with an image that builds but doesn't
|
||||||
|
boot, please report it as a bug.
|
||||||
|
|
||||||
|
As of June 2023 the device list is a little thin. Adding devices based
|
||||||
|
on the Atheros or Mediatek (Ralink) platform should be quite
|
||||||
|
straightforward if you have some C/Linux kernel experience and are
|
||||||
|
prepared to open it up and attach serial wires: please refer to the
|
||||||
|
Developer Manual.
|
||||||
|
|
||||||
|
|
||||||
|
Choosing a flavour (read-only or updatable)
|
||||||
|
===========================================
|
||||||
|
|
||||||
|
Liminix installations come in two "flavours"- read-only or in-place
|
||||||
|
updatable:
|
||||||
|
|
||||||
|
* a read-only installation can't be updated once it is flashed to your
|
||||||
|
device, and so must be reinstalled in its entirety every time you
|
||||||
|
want to change it. It uses the ``squashfs`` filesystem which has
|
||||||
|
very good compression ratios and so you can pack quite a lot of
|
||||||
|
useful stuff onto your device. This is good if you don't expect
|
||||||
|
to change it often.
|
||||||
|
|
||||||
|
* an updatable installation has a writable filesystem so that you can
|
||||||
|
update configuration, upgrade packages and install new packages over
|
||||||
|
the network after installation. This uses the `jffs2
|
||||||
|
<http://www.linux-mtd.infradead.org/doc/jffs2.html>`_ filesystem:
|
||||||
|
although it does compress the data, the need to support writes means
|
||||||
|
that it can't pack quite as small as squashfs, so you will not have
|
||||||
|
as much space to play with.
|
||||||
|
|
||||||
|
Updatability caveats
|
||||||
|
~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
At the time of writing this manual the read-only squashfs support is
|
||||||
|
much more mature. Consider also that it may not be possible to perform
|
||||||
|
"larger" updates in-place even if you do opt for updatability. If you
|
||||||
|
have (for example) an 11MB system on a 16MB device, you won't be able
|
||||||
|
to do an in-place update of something fundamental like the C library
|
||||||
|
(libc), as this will temporarily require 22MB to install all the
|
||||||
|
packages needing the new library before the packages using the old
|
||||||
|
library can be removed. A writable system will be more useful for
|
||||||
|
smaller updates such as installing a new package (perhaps you
|
||||||
|
temporarily need tcpdump to diagnose a network problem) or for
|
||||||
|
changing configuration files.
|
||||||
|
|
||||||
|
Note also that the kernel is not part of the filesystem so cannot be
|
||||||
|
updated this way. Kernel changes require a full reflash.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Creating configuration.nix
|
||||||
|
==========================
|
||||||
|
|
||||||
|
|
||||||
|
You need to create a :file:`configuration.nix` that describes your
|
||||||
|
device and the services that you want to run on it. The best way to
|
||||||
|
get started is by reading one of the examples such as
|
||||||
|
:file:`examples/rotuer.nix` and modifying it to your needs.
|
||||||
|
|
||||||
|
:file:`configuration.nix` conventionally describes the packages, services,
|
||||||
|
user accounts etc of the device. It does not describe the hardware
|
||||||
|
itself, which is specified separately in the build command (as you
|
||||||
|
will see below).
|
||||||
|
|
||||||
|
Most of the functionality of a Liminix system is driven by *services*
|
||||||
|
which are declared by *modules*: thus, to add for example an NTP service
|
||||||
|
you first add :file:`modules/ntp` to your ``imports`` list, then
|
||||||
|
you create a service by calling :code:`config.system.service.ntp.build { .... }`
|
||||||
|
with the appropriate service-dependent configuration parameters.
|
||||||
|
|
||||||
|
.. code-block:: nix
|
||||||
|
|
||||||
|
let svc = config.system.service;
|
||||||
|
in {
|
||||||
|
# ...
|
||||||
|
imports = [
|
||||||
|
./modules/ntp
|
||||||
|
# ....
|
||||||
|
];
|
||||||
|
config.services.ntp = svc.ntp.build {
|
||||||
|
pools = { "pool.ntp.org" = ["iburst"]; };
|
||||||
|
makestep = { threshold = 1.0; limit = 3; };
|
||||||
|
};
|
||||||
|
|
||||||
|
A :ref:`full list of module options <module-options>` is provided
|
||||||
|
later in this manual.
|
||||||
|
|
||||||
|
You *most likely* want to include the ``standard`` module unless you
|
||||||
|
have a quite unusual use case for a very minimal system, in which case
|
||||||
|
you will understand what it does and what happens if you leave it out.
|
||||||
|
|
||||||
|
.. code-block:: nix
|
||||||
|
|
||||||
|
imports = [
|
||||||
|
./modules/standard.nix
|
||||||
|
]
|
||||||
|
configuration.rootfsType = "jffs2"; # or "squashfs"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Building
|
||||||
|
========
|
||||||
|
|
||||||
|
Build Liminix using the :file:`default.nix` in the project toplevel
|
||||||
|
directory, passing it arguments for configuration and hardware. For
|
||||||
|
example:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
nix-build -I liminix-config=./tests/smoke/configuration.nix \
|
||||||
|
--arg device "import ./devices/qemu" -A outputs.default
|
||||||
|
|
||||||
|
In this command ``<liminix-config>`` points to your
|
||||||
|
:file:`configuration.nix`, ``device`` is the file for your hardware device
|
||||||
|
definition, and ``outputs.default`` will generate some kind of
|
||||||
|
Liminix image output appropriate to that device.
|
||||||
|
|
||||||
|
For the qemu device in this example, ``outputs.default`` is an alias
|
||||||
|
for ``outputs.vmbuild``, which creates a directory containing a
|
||||||
|
squashfs root image and a kernel. You can use the :command:`mips-vm` command to
|
||||||
|
run this.
|
||||||
|
|
||||||
|
For the currently supported hardware devices, ``outputs.default``
|
||||||
|
creates a directory containing a file called ``firmware.bin``. This
|
||||||
|
is a raw image file that can be written directly to the firmware flash
|
||||||
|
partition.
|
||||||
|
|
||||||
|
|
||||||
|
Flashing
|
||||||
|
========
|
||||||
|
|
||||||
|
|
||||||
|
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 flash it using U-Boot.
|
||||||
|
This is quite hardware-specific, and sometimes involves soldering:
|
||||||
|
please refer to the Developer Manual.
|
||||||
|
|
||||||
|
|
||||||
|
Flashing from an existing Liminix system with :command:`flashcp`
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The flash procedure from an existing Liminix-system is two-step.
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Building the RAM-based image
|
||||||
|
............................
|
||||||
|
|
||||||
|
To create the ephemeral image, build ``outputs.kexecboot`` instead of
|
||||||
|
``outputs.default``. This generates a directory containing the root
|
||||||
|
filesystem image and kernel, along with an executable called `kexec`
|
||||||
|
and a `boot.sh` script that runs it with appropriate arguments.
|
||||||
|
|
||||||
|
For example
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
nix-build --show-trace -I liminix-config=./examples/arhcive.nix \
|
||||||
|
--arg device "import ./devices/gl-ar750"
|
||||||
|
-A outputs.kexecboot && \
|
||||||
|
(tar chf - result | ssh root@the-device tar -C /run -xvf -)
|
||||||
|
|
||||||
|
and then login to the device and run
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
cd /run/result
|
||||||
|
sh ./boot.sh .
|
||||||
|
|
||||||
|
|
||||||
|
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 copy the permanent
|
||||||
|
image to the device with :command:`ssh`
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
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 intemediate 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. It might work, or it might
|
||||||
|
crash in surprising ways.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Flashing from OpenWrt (not currently advised!)
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. CAUTION:: At your own risk! This will (at least in some
|
||||||
|
circumstances) lead to bricking the device: we think this
|
||||||
|
flash method is currently incompatible with use of a
|
||||||
|
writeable (jffs2) filesystem.
|
||||||
|
|
||||||
|
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 -r write /tmp/firmware.bin firmware
|
||||||
|
|
||||||
|
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)
|
||||||
|
************************************
|
||||||
|
|
||||||
|
Adding packages
|
||||||
|
===============
|
||||||
|
|
||||||
|
|
||||||
|
If your device is running a JFFS2 root filesystem, you can build
|
||||||
|
extra packages for it on your build system and copy them to the
|
||||||
|
device: any package in Nixpkgs or in the Liminix overlay is available
|
||||||
|
with the ``pkgs`` prefix:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
nix-build -I liminix-config=./my-configuration.nix \
|
||||||
|
--arg device "import ./devices/mydevice" -A pkgs.tcpdump
|
||||||
|
|
||||||
|
nix-shell -p min-copy-closure root@the-device result/
|
||||||
|
|
||||||
|
Note that this only copies the package to the device: it doesn't update
|
||||||
|
any profile to add it to ``$PATH``
|
||||||
|
|
||||||
|
|
||||||
|
Rebuilding the system
|
||||||
|
=====================
|
||||||
|
|
||||||
|
:command:`liminix-rebuild` is the Liminix analogue of :command:`nixos-rebuild`, although its operation is a bit different because it expects to run on a build machine and then copy to the host device. Run it with the same ``liminix-config`` and ``device`` parameters as you would run :command:`nix-build`, and it will build any new/changed packages and then copy them to the device using SSH. For example:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
liminix-rebuild root@the-device -I liminix-config=./examples/rotuer.nix --arg device "import ./devices/gl-ar750"
|
||||||
|
|
||||||
|
This will
|
||||||
|
|
||||||
|
* build anything that needs building
|
||||||
|
* copy new or changed packages to the device
|
||||||
|
* reboot the device
|
||||||
|
|
||||||
|
It doesn't delete old packages automatically: to do that run
|
||||||
|
:command:`min-collect-garbage`, which will delete any packages not in
|
||||||
|
the current system closure. Note that Liminix does not have the NixOS
|
||||||
|
concept of environments or generations, and there is no way back from
|
||||||
|
this except for building the previous configuration again.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Caveats
|
||||||
|
~~~~~~~
|
||||||
|
|
||||||
|
* it needs there to be enough free space on the device for all the new
|
||||||
|
packages in addition to all the packages already on it - which may be
|
||||||
|
a problem if a lot of things have changed (e.g. a new version of
|
||||||
|
nixpkgs).
|
||||||
|
|
||||||
|
* it cannot upgrade the kernel, only userland
|
||||||
|
|
||||||
|
Configuration options
|
||||||
|
*********************
|
||||||
|
|
||||||
|
.. _module-options:
|
||||||
|
|
||||||
|
.. include:: modules.rst
|
|
@ -0,0 +1,59 @@
|
||||||
|
(local { : merge : split : file-exists? : system } (require :anoia))
|
||||||
|
(local svc (require :anoia.svc))
|
||||||
|
|
||||||
|
(fn parse-prefix [str]
|
||||||
|
(fn parse-extra [s]
|
||||||
|
(let [out {}]
|
||||||
|
(each [name val (string.gmatch s ",(.-)=([^,]+)")]
|
||||||
|
(tset out name val))
|
||||||
|
out))
|
||||||
|
(let [(prefix len preferred valid extra)
|
||||||
|
(string.match str "(.-)::/(%d+),(%d+),(%d+)(.*)$")]
|
||||||
|
(merge {: prefix : len : preferred : valid} (parse-extra extra))))
|
||||||
|
|
||||||
|
|
||||||
|
;; Format: <prefix>/<length>,preferred,valid[,excluded=<excluded-prefix>/<length>][,class=<prefix class #>]
|
||||||
|
|
||||||
|
;;(parse-prefix "2001:8b0:de3a:40dc::/64,7198,7198")
|
||||||
|
;;(parse-prefix "2001:8b0:de3a:1001::/64,7198,7188,excluded=1/2,thi=10")
|
||||||
|
|
||||||
|
|
||||||
|
(local bound-states
|
||||||
|
{ :bound true
|
||||||
|
:rebound true
|
||||||
|
:informed true
|
||||||
|
:updated true
|
||||||
|
:ra-updated true
|
||||||
|
})
|
||||||
|
|
||||||
|
; (local { : view } (require :fennel))
|
||||||
|
|
||||||
|
(fn changes [old-prefixes new-prefixes]
|
||||||
|
(let [added {}
|
||||||
|
deleted {}
|
||||||
|
old-set (collect [_ v (ipairs old-prefixes)] (values v true))
|
||||||
|
new-set (collect [_ v (ipairs new-prefixes)] (values v true))]
|
||||||
|
(each [_ prefix (ipairs new-prefixes)]
|
||||||
|
(if (not (. old-set prefix))
|
||||||
|
(table.insert added (parse-prefix prefix))))
|
||||||
|
(each [_ prefix (ipairs old-prefixes)]
|
||||||
|
(if (not (. new-set prefix))
|
||||||
|
(table.insert deleted (parse-prefix prefix))))
|
||||||
|
(values added deleted)))
|
||||||
|
|
||||||
|
(let [[state-directory lan-device] arg
|
||||||
|
dir (svc.open state-directory)]
|
||||||
|
(var prefixes [])
|
||||||
|
(while true
|
||||||
|
(while (not (dir:ready?)) (dir:wait))
|
||||||
|
(if (. bound-states (dir:output "state"))
|
||||||
|
(let [new-prefixes (split " " (dir:output "/prefixes"))
|
||||||
|
(added deleted) (changes prefixes new-prefixes)]
|
||||||
|
(each [_ p (ipairs added)]
|
||||||
|
(system
|
||||||
|
(.. "ip address add " p.prefix "::1/" p.len " dev " lan-device)))
|
||||||
|
(each [_ p (ipairs deleted)]
|
||||||
|
(system
|
||||||
|
(.. "ip address del " p.prefix "::1/" p.len " dev " lan-device)))
|
||||||
|
(set prefixes new-prefixes)))
|
||||||
|
(dir:wait)))
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
writeFennelScript
|
||||||
|
, linotify
|
||||||
|
, anoia
|
||||||
|
}:
|
||||||
|
writeFennelScript "acquire-delegated-prefix"
|
||||||
|
[ linotify anoia ]
|
||||||
|
./acquire-delegated-prefix.fnl
|
|
@ -0,0 +1,60 @@
|
||||||
|
(local { : merge : split : file-exists? : system } (require :anoia))
|
||||||
|
(local svc (require :anoia.svc))
|
||||||
|
|
||||||
|
;; structurally this is remarkably similar to
|
||||||
|
;; acquire-lan-prefix.fnl. maybe they should be merged: if not then
|
||||||
|
;; we could at least extract some common code
|
||||||
|
|
||||||
|
;; (alternatively we could move all the parsing code into the thing in
|
||||||
|
;; the odhcp service that *writes* this stuff)
|
||||||
|
|
||||||
|
; (parse-address "2001:8b0:1111:1111:0:ffff:51bb:4cf2/128,3600,7200")
|
||||||
|
|
||||||
|
|
||||||
|
(fn parse-address [str]
|
||||||
|
(fn parse-extra [s]
|
||||||
|
(let [out {}]
|
||||||
|
(each [name val (string.gmatch s ",(.-)=([^,]+)")]
|
||||||
|
(tset out name val))
|
||||||
|
out))
|
||||||
|
(let [(address len preferred valid extra)
|
||||||
|
(string.match str "(.-)/(%d+),(%d+),(%d+)(.*)$")]
|
||||||
|
(merge {: address : len : preferred : valid} (parse-extra extra))))
|
||||||
|
|
||||||
|
(local bound-states
|
||||||
|
{ :bound true
|
||||||
|
:rebound true
|
||||||
|
:informed true
|
||||||
|
:updated true
|
||||||
|
:ra-updated true
|
||||||
|
})
|
||||||
|
|
||||||
|
(fn changes [old-addresses new-addresses]
|
||||||
|
(let [added {}
|
||||||
|
deleted {}
|
||||||
|
old-set (collect [_ v (ipairs old-addresses)] (values v true))
|
||||||
|
new-set (collect [_ v (ipairs new-addresses)] (values v true))]
|
||||||
|
(each [_ address (ipairs new-addresses)]
|
||||||
|
(if (not (. old-set address))
|
||||||
|
(table.insert added (parse-address address))))
|
||||||
|
(each [_ address (ipairs old-addresses)]
|
||||||
|
(if (not (. new-set address))
|
||||||
|
(table.insert deleted (parse-address address))))
|
||||||
|
(values added deleted)))
|
||||||
|
|
||||||
|
(let [[state-directory wan-device] arg
|
||||||
|
dir (svc.open state-directory)]
|
||||||
|
(var addresses [])
|
||||||
|
(while true
|
||||||
|
(while (not (dir:ready?)) (dir:wait))
|
||||||
|
(if (. bound-states (dir:output "state"))
|
||||||
|
(let [new-addresses (split " " (dir:output "/addresses"))
|
||||||
|
(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)))
|
||||||
|
(set addresses new-addresses)))
|
||||||
|
(dir:wait)))
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
writeFennelScript
|
||||||
|
, linotify
|
||||||
|
, anoia
|
||||||
|
}:
|
||||||
|
writeFennelScript "acquire-wan-address"
|
||||||
|
[ linotify anoia ]
|
||||||
|
./acquire-wan-address.fnl
|
|
@ -11,29 +11,54 @@
|
||||||
...
|
...
|
||||||
}: let
|
}: let
|
||||||
secrets = import ./extneder-secrets.nix;
|
secrets = import ./extneder-secrets.nix;
|
||||||
inherit (pkgs.liminix.services) oneshot longrun target;
|
inherit
|
||||||
|
(pkgs.liminix.networking)
|
||||||
|
route
|
||||||
|
;
|
||||||
|
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/ssh
|
|
||||||
../modules/usb.nix
|
|
||||||
../modules/watchdog
|
|
||||||
../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";
|
||||||
|
PARTITION_ADVANCED = "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;
|
||||||
|
@ -42,22 +67,65 @@ in rec {
|
||||||
dependencies = [ config.services.hostname ];
|
dependencies = [ config.services.hostname ];
|
||||||
};
|
};
|
||||||
|
|
||||||
services.sshd = svc.ssh.build { };
|
services.sshd = longrun {
|
||||||
|
name = "sshd";
|
||||||
services.watchdog = svc.watchdog.build {
|
run = ''
|
||||||
watched = with config.services ; [ sshd dhcpc ];
|
mkdir -p /run/dropbear
|
||||||
|
${dropbear}/bin/dropbear -E -P /run/dropbear.pid -R -F
|
||||||
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
services.watchdog =
|
||||||
|
let
|
||||||
|
watched = with config.services ; [ sshd dhcpc ];
|
||||||
|
spinupGrace = 60;
|
||||||
|
script = pkgs.writeAshScript "gaspode" {
|
||||||
|
runtimeInputs = [ pkgs.s6 ];
|
||||||
|
} ''
|
||||||
|
deadline=$(expr $(date +%s) + ${toString spinupGrace})
|
||||||
|
services=$@
|
||||||
|
echo started feeding the dog
|
||||||
|
exec 3> ''${WATCHDOG-/dev/watchdog}
|
||||||
|
|
||||||
|
healthy(){
|
||||||
|
test $(date +%s) -le $deadline && return 0
|
||||||
|
|
||||||
|
for i in $services; do
|
||||||
|
if test "$(s6-svstat -o up /run/service/$i)" != "true" ; then
|
||||||
|
echo "service $i is down"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
while healthy ;do
|
||||||
|
sleep 10
|
||||||
|
echo >&3
|
||||||
|
done
|
||||||
|
echo "stopped feeding the dog"
|
||||||
|
sleep 6000 # don't want s6-rc to restart
|
||||||
|
'';
|
||||||
|
in longrun {
|
||||||
|
name = "watchdog";
|
||||||
|
run =
|
||||||
|
"${script} ${lib.concatStringsSep " " (builtins.map (s: s.name) watched)}";
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
services.resolvconf = oneshot rec {
|
services.resolvconf = oneshot rec {
|
||||||
dependencies = [ services.dhcpc ];
|
dependencies = [ services.dhcpc ];
|
||||||
name = "resolvconf";
|
name = "resolvconf";
|
||||||
up = ''
|
up = ''
|
||||||
|
. ${serviceFns}
|
||||||
( in_outputs ${name}
|
( in_outputs ${name}
|
||||||
for i in $(output ${services.dhcpc} dns); do
|
for i in $(output ${services.dhcpc} dns); do
|
||||||
echo "nameserver $i" > resolv.conf
|
echo "nameserver $i" > resolv.conf
|
||||||
done
|
done
|
||||||
)
|
)
|
||||||
'';
|
'';
|
||||||
|
down = ''
|
||||||
|
rm -rf /run/service-state/${name}/
|
||||||
|
'';
|
||||||
};
|
};
|
||||||
filesystem = dir {
|
filesystem = dir {
|
||||||
etc = dir {
|
etc = dir {
|
||||||
|
@ -66,25 +134,35 @@ in rec {
|
||||||
srv = dir {};
|
srv = dir {};
|
||||||
};
|
};
|
||||||
|
|
||||||
services.defaultroute4 = svc.network.route.build {
|
services.defaultroute4 = route {
|
||||||
|
name = "defaultroute";
|
||||||
via = "$(output ${services.dhcpc} router)";
|
via = "$(output ${services.dhcpc} router)";
|
||||||
target = "default";
|
target = "default";
|
||||||
dependencies = [services.dhcpc];
|
dependencies = [services.dhcpc];
|
||||||
};
|
};
|
||||||
|
|
||||||
programs.busybox = {
|
programs.busybox = {
|
||||||
applets = ["lsusb" "tar"];
|
applets = ["blkid" "lsusb" "findfs" "tar"];
|
||||||
options = {
|
options = {
|
||||||
FEATURE_LS_TIMESTAMPS = "y";
|
FEATURE_LS_TIMESTAMPS = "y";
|
||||||
FEATURE_LS_SORTFILES = "y";
|
FEATURE_LS_SORTFILES = "y";
|
||||||
|
FEATURE_BLKID_TYPE = "y";
|
||||||
|
FEATURE_MOUNT_FLAGS = "y";
|
||||||
|
FEATURE_MOUNT_LABEL = "y";
|
||||||
FEATURE_VOLUMEID_EXT = "y";
|
FEATURE_VOLUMEID_EXT = "y";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
services.mount_external_disk = svc.mount.build {
|
services.mount_external_disk = oneshot {
|
||||||
partlabel = "backup-disk";
|
name = "mount_external_disk";
|
||||||
mountpoint = "/srv";
|
up = ''
|
||||||
fstype = "ext4";
|
while ! findfs LABEL=backup-disk; do
|
||||||
|
echo waiting for backup-disk
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
mount -t ext4 LABEL=backup-disk /srv
|
||||||
|
'';
|
||||||
|
down = "umount /srv";
|
||||||
};
|
};
|
||||||
|
|
||||||
services.rsync =
|
services.rsync =
|
||||||
|
@ -92,6 +170,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 +196,37 @@ in rec {
|
||||||
secrets_file
|
secrets_file
|
||||||
services.mount_external_disk
|
services.mount_external_disk
|
||||||
config.hardware.networkInterfaces.lan
|
config.hardware.networkInterfaces.lan
|
||||||
|
] ;
|
||||||
|
};
|
||||||
|
|
||||||
|
services.default = target {
|
||||||
|
name = "default";
|
||||||
|
contents =
|
||||||
|
let links = config.hardware.networkInterfaces;
|
||||||
|
in with config.services; [
|
||||||
|
links.lo
|
||||||
|
defaultroute4
|
||||||
|
resolvconf
|
||||||
|
sshd
|
||||||
|
rsync
|
||||||
|
watchdog
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
})
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,206 +0,0 @@
|
||||||
# This is an example configuration for a "typical" small office/home
|
|
||||||
# router and wifi access point.
|
|
||||||
|
|
||||||
# You need to copy it to another filename and change the configuration
|
|
||||||
# wherever the text "EDIT" appears - please consult the tutorial
|
|
||||||
# documentation for details.
|
|
||||||
|
|
||||||
{ config, pkgs, ... }:
|
|
||||||
let
|
|
||||||
inherit (pkgs.liminix.services) bundle oneshot;
|
|
||||||
inherit (pkgs) serviceFns;
|
|
||||||
# EDIT: you can pick your preferred RFC1918 address space
|
|
||||||
# for NATted connections, if you don't like this one.
|
|
||||||
ipv4LocalNet = "10.8.0";
|
|
||||||
svc = config.system.service;
|
|
||||||
|
|
||||||
in rec {
|
|
||||||
boot = {
|
|
||||||
tftp = {
|
|
||||||
freeSpaceBytes = 3 * 1024 * 1024;
|
|
||||||
serverip = "10.0.0.1";
|
|
||||||
ipaddr = "10.0.0.8";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
imports = [
|
|
||||||
../modules/bridge
|
|
||||||
../modules/dhcp6c
|
|
||||||
../modules/dnsmasq
|
|
||||||
../modules/firewall
|
|
||||||
../modules/hostapd
|
|
||||||
../modules/network
|
|
||||||
../modules/ntp
|
|
||||||
../modules/ppp
|
|
||||||
../modules/ssh
|
|
||||||
../modules/vlan
|
|
||||||
../modules/wlan.nix
|
|
||||||
];
|
|
||||||
rootfsType = "jffs2";
|
|
||||||
hostname = "the-internet"; # EDIT
|
|
||||||
|
|
||||||
services.hostap = svc.hostapd.build {
|
|
||||||
interface = config.hardware.networkInterfaces.wlan;
|
|
||||||
# EDIT: you will want to change the obvious things
|
|
||||||
# here to values of your choice
|
|
||||||
params = {
|
|
||||||
ssid = "the-internet";
|
|
||||||
channel = "1";
|
|
||||||
country_code = "GB";
|
|
||||||
wpa_passphrase = "not a real wifi password";
|
|
||||||
|
|
||||||
hw_mode = "g";
|
|
||||||
ieee80211n = 1;
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
services.int = svc.network.address.build {
|
|
||||||
interface = svc.bridge.primary.build { ifname = "int"; };
|
|
||||||
family = "inet";
|
|
||||||
address = "${ipv4LocalNet}.1";
|
|
||||||
prefixLength = 16;
|
|
||||||
};
|
|
||||||
|
|
||||||
services.bridge = svc.bridge.members.build {
|
|
||||||
primary = services.int;
|
|
||||||
members = with config.hardware.networkInterfaces; [
|
|
||||||
wlan
|
|
||||||
lan
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
services.ntp = svc.ntp.build {
|
|
||||||
pools = {
|
|
||||||
"pool.ntp.org" = [ "iburst" ];
|
|
||||||
};
|
|
||||||
makestep = {
|
|
||||||
threshold = 1.0;
|
|
||||||
limit = 3;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
services.sshd = svc.ssh.build { };
|
|
||||||
|
|
||||||
users.root = {
|
|
||||||
# EDIT: choose a root password and then use
|
|
||||||
# "mkpasswd -m sha512crypt" to determine the hash.
|
|
||||||
# It should start wirh $6$.
|
|
||||||
passwd = "$6$6HG7WALLQQY1LQDE$428cnouMJ7wVmyK9.dF1uWs7t0z9ztgp3MHvN5bbeo0M4Kqg/u2ThjoSHIjCEJQlnVpDOaEKcOjXAlIClHWN21";
|
|
||||||
openssh.authorizedKeys.keys = [
|
|
||||||
# EDIT: you can add your ssh pubkey here
|
|
||||||
# "ssh-rsa AAAAB3NzaC1....H6hKd user@example.com";
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
services.dns =
|
|
||||||
let interface = services.int;
|
|
||||||
in svc.dnsmasq.build {
|
|
||||||
resolvconf = services.resolvconf;
|
|
||||||
inherit interface;
|
|
||||||
ranges = [
|
|
||||||
"${ipv4LocalNet}.10,${ipv4LocalNet}.249"
|
|
||||||
# EDIT: ... maybe. In this example we use "ra-stateless",
|
|
||||||
# meaning dnsmasq 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.
|
|
||||||
# If you didn't understand the preceding sentence then
|
|
||||||
# the default is _probably_ fine, but if you need
|
|
||||||
# a DHCP-only IPv6 network or some other different
|
|
||||||
# configuration, this is the place to change it.
|
|
||||||
"::,constructor:$(output ${interface} ifname),ra-stateless"
|
|
||||||
];
|
|
||||||
# EDIT: choose a domain name for the DNS names issued for your
|
|
||||||
# DHCP-issued hosts
|
|
||||||
domain = "lan.example.com";
|
|
||||||
};
|
|
||||||
|
|
||||||
services.wan = svc.pppoe.build {
|
|
||||||
interface = config.hardware.networkInterfaces.wan;
|
|
||||||
ppp-options = [
|
|
||||||
"debug" "+ipv6" "noauth"
|
|
||||||
# EDIT: change the strings "chap-username"
|
|
||||||
# and "chap-secret" to match the username/password
|
|
||||||
# provided by your ISP for PPP logins
|
|
||||||
"name" "chap-username"
|
|
||||||
"password" "chap-secret"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
services.resolvconf = oneshot rec {
|
|
||||||
dependencies = [ services.wan ];
|
|
||||||
name = "resolvconf";
|
|
||||||
up = ''
|
|
||||||
( 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 { };
|
|
||||||
|
|
||||||
services.packet_forwarding = svc.network.forward.build { };
|
|
||||||
|
|
||||||
# We expect the ISP uses DHCP6 to issue IPv6 addresses. There is a
|
|
||||||
# service to request address information in the form of a DHCP
|
|
||||||
# lease, and two dependent services that listen for updates to the
|
|
||||||
# DHCP address information and update the addresses of the WAN and
|
|
||||||
# LAN interfaces respectively.
|
|
||||||
|
|
||||||
services.dhcp6c =
|
|
||||||
let client = svc.dhcp6c.client.build {
|
|
||||||
interface = services.wan;
|
|
||||||
};
|
|
||||||
in bundle {
|
|
||||||
name = "dhcp6c";
|
|
||||||
contents = [
|
|
||||||
(svc.dhcp6c.prefix.build {
|
|
||||||
# if your ISP provides you a real IPv6 prefix for your local
|
|
||||||
# network (usually a /64 or /48 or something in between the
|
|
||||||
# two), this service subscribes to that "prefix delegation"
|
|
||||||
# information, and uses it to assign an address to the LAN
|
|
||||||
# device. dnsmasq will notice this address and use it to
|
|
||||||
# form the addresses it hands out to devices on the lan
|
|
||||||
inherit client;
|
|
||||||
interface = services.int;
|
|
||||||
})
|
|
||||||
(svc.dhcp6c.address.build {
|
|
||||||
# if your ISP provides you a regular global IPv6 address,
|
|
||||||
# this service subscribes to that information and assigns
|
|
||||||
# the address to the WAN device.
|
|
||||||
inherit client;
|
|
||||||
interface = services.wan;
|
|
||||||
})
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
defaultProfile.packages = with pkgs; [ min-collect-garbage ];
|
|
||||||
}
|
|
|
@ -8,10 +8,18 @@
|
||||||
config,
|
config,
|
||||||
pkgs,
|
pkgs,
|
||||||
lib,
|
lib,
|
||||||
modulesPath,
|
|
||||||
...
|
...
|
||||||
}: let
|
}: let
|
||||||
secrets = import ./extneder-secrets.nix;
|
secrets = import ./extneder-secrets.nix;
|
||||||
|
inherit
|
||||||
|
(pkgs.liminix.networking)
|
||||||
|
address
|
||||||
|
interface
|
||||||
|
route
|
||||||
|
;
|
||||||
|
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 +30,135 @@ in rec {
|
||||||
};
|
};
|
||||||
|
|
||||||
imports = [
|
imports = [
|
||||||
"${modulesPath}/profiles/wap.nix"
|
../modules/wlan.nix
|
||||||
"${modulesPath}/vlan"
|
../modules/network
|
||||||
"${modulesPath}/ssh"
|
../modules/hostapd
|
||||||
|
../modules/bridge
|
||||||
|
../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} = {
|
|
||||||
|
IP6_NF_IPTABLES = "y"; # do we still need these
|
||||||
|
IP_NF_IPTABLES = "y"; # if using nftables directly
|
||||||
|
|
||||||
|
# these are copied from rotuer and need review
|
||||||
|
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;
|
interface = config.hardware.networkInterfaces.wlan;
|
||||||
inherit (secrets) channel wpa_passphrase;
|
params = {
|
||||||
country_code = "GB";
|
country_code = "GB";
|
||||||
hw_mode = "g";
|
hw_mode = "g";
|
||||||
wmm_enabled = 1;
|
wmm_enabled = 1;
|
||||||
ieee80211n = 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.sshd = svc.ssh.build {};
|
services.int = interface {
|
||||||
users.root.passwd = lib.mkForce secrets.root.passwd;
|
type = "bridge";
|
||||||
|
device = "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 = longrun {
|
||||||
|
name = "sshd";
|
||||||
|
run = ''
|
||||||
|
mkdir -p /run/dropbear
|
||||||
|
${dropbear}/bin/dropbear -E -P /run/dropbear.pid -R -F
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
|
'';
|
||||||
|
down = ''
|
||||||
|
rm -rf /run/service-state/${name}/
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
filesystem = dir {
|
||||||
|
etc = dir {
|
||||||
|
"resolv.conf" = symlink "${services.resolvconf}/.outputs/resolv.conf";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
services.defaultroute4 = route {
|
||||||
|
name = "defaultroute";
|
||||||
|
via = "$(output ${services.dhcpc} router)";
|
||||||
|
target = "default";
|
||||||
|
dependencies = [services.dhcpc];
|
||||||
|
};
|
||||||
|
|
||||||
|
services.default = target {
|
||||||
|
name = "default";
|
||||||
|
contents =
|
||||||
|
let links = config.hardware.networkInterfaces;
|
||||||
|
in with config.services; [
|
||||||
|
links.lo links.eth links.wlan
|
||||||
|
int
|
||||||
|
bridge
|
||||||
|
hostap
|
||||||
|
defaultroute4
|
||||||
|
resolvconf
|
||||||
|
sshd
|
||||||
|
];
|
||||||
|
};
|
||||||
|
users.root.passwd = lib.mkForce secrets.root_password;
|
||||||
defaultProfile.packages = with pkgs; [nftables strace tcpdump swconfig];
|
defaultProfile.packages = with pkgs; [nftables strace tcpdump swconfig];
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,41 +0,0 @@
|
||||||
{ config, pkgs, ... } :
|
|
||||||
let
|
|
||||||
svc = config.system.service;
|
|
||||||
|
|
||||||
in rec {
|
|
||||||
imports = [
|
|
||||||
../modules/network
|
|
||||||
../modules/ssh
|
|
||||||
../modules/vlan
|
|
||||||
];
|
|
||||||
|
|
||||||
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 = "192.0.2.115"; # my address
|
|
||||||
serverip = "192.0.2.5"; # build machine or other tftp server
|
|
||||||
};
|
|
||||||
|
|
||||||
hostname = "hello";
|
|
||||||
|
|
||||||
services.dhcpc = svc.network.dhcp.client.build {
|
|
||||||
interface = config.hardware.networkInterfaces.lan;
|
|
||||||
|
|
||||||
# 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 { };
|
|
||||||
|
|
||||||
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
|
|
||||||
];
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
{ config, pkgs, ... } :
|
|
||||||
let
|
|
||||||
svc = config.system.service;
|
|
||||||
|
|
||||||
in rec {
|
|
||||||
imports = [
|
|
||||||
../modules/network
|
|
||||||
../modules/dnsmasq
|
|
||||||
../modules/ssh
|
|
||||||
];
|
|
||||||
|
|
||||||
hostname = "hello";
|
|
||||||
|
|
||||||
# configure the internal network (LAN) with an address
|
|
||||||
services.int = svc.network.address.build {
|
|
||||||
interface = config.hardware.networkInterfaces.lan;
|
|
||||||
family = "inet"; address ="10.3.0.1"; prefixLength = 16;
|
|
||||||
};
|
|
||||||
|
|
||||||
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";
|
|
||||||
};
|
|
||||||
|
|
||||||
services.dns =
|
|
||||||
let interface = services.int;
|
|
||||||
in svc.dnsmasq.build {
|
|
||||||
inherit interface;
|
|
||||||
ranges = [
|
|
||||||
"10.3.0.10,10.3.0.240"
|
|
||||||
"::,constructor:$(output ${interface} ifname),ra-stateless"
|
|
||||||
];
|
|
||||||
|
|
||||||
domain = "example.org";
|
|
||||||
};
|
|
||||||
|
|
||||||
defaultProfile.packages = with pkgs; [
|
|
||||||
figlet
|
|
||||||
];
|
|
||||||
}
|
|
|
@ -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 ];
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
];
|
|
||||||
}
|
|
|
@ -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).
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,34 @@
|
||||||
# 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.networking)
|
||||||
firewallRules = { };
|
address
|
||||||
} // (import ./rotuer-secrets.nix);
|
interface
|
||||||
|
route;
|
||||||
|
inherit (pkgs.liminix.services) oneshot longrun bundle target;
|
||||||
|
inherit (pkgs)
|
||||||
|
dropbear
|
||||||
|
ifwait
|
||||||
|
writeText
|
||||||
|
writeFennelScript
|
||||||
|
serviceFns;
|
||||||
svc = config.system.service;
|
svc = config.system.service;
|
||||||
wirelessConfig = {
|
wirelessConfig = {
|
||||||
country_code = "GB";
|
country_code = "GB";
|
||||||
inherit (secrets) wpa_passphrase;
|
inherit (secrets) wpa_passphrase;
|
||||||
|
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 +42,52 @@ in rec {
|
||||||
};
|
};
|
||||||
|
|
||||||
imports = [
|
imports = [
|
||||||
"${modulesPath}/profiles/gateway.nix"
|
../modules/wlan.nix
|
||||||
|
../modules/standard.nix
|
||||||
|
../modules/network
|
||||||
|
../modules/ppp
|
||||||
|
../modules/dnsmasq
|
||||||
|
../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_24;
|
||||||
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,
|
|
||||||
# specify them here
|
|
||||||
wlan wlan5
|
|
||||||
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}" = {
|
|
||||||
interface = config.hardware.networkInterfaces.wlan;
|
|
||||||
hw_mode = "g";
|
|
||||||
channel = "2";
|
channel = "2";
|
||||||
ieee80211n = 1;
|
ieee80211n = 1;
|
||||||
} // wirelessConfig;
|
} // wirelessConfig;
|
||||||
"${secrets.ssid}5" = rec {
|
};
|
||||||
interface = config.hardware.networkInterfaces.wlan5;
|
|
||||||
hw_mode = "a";
|
services.hostap5 = svc.hostapd.build {
|
||||||
|
interface = config.hardware.networkInterfaces.wlan_5;
|
||||||
|
params = rec {
|
||||||
|
ssid = "liminix_5";
|
||||||
|
hw_mode="a";
|
||||||
channel = 36;
|
channel = 36;
|
||||||
ht_capab = "[HT40+]";
|
ht_capab = "[HT40+]";
|
||||||
vht_oper_chwidth = 1;
|
vht_oper_chwidth = 1;
|
||||||
vht_oper_centr_freq_seg0_idx = channel + 6;
|
vht_oper_centr_freq_seg0_idx = channel + 6;
|
||||||
ieee80211n = 1;
|
|
||||||
ieee80211ac = 1;
|
ieee80211ac = 1;
|
||||||
} // wirelessConfig;
|
} // wirelessConfig;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
services.int = svc.network.address.build {
|
||||||
|
interface = svc.bridge.primary.build { ifname = "int"; };# services.int;
|
||||||
|
family = "inet"; address ="10.8.0.1"; prefixLength = 16;
|
||||||
|
};
|
||||||
|
|
||||||
|
services.bridge = svc.bridge.members.build {
|
||||||
|
primary = services.int;
|
||||||
|
members = with config.hardware.networkInterfaces;
|
||||||
|
[ wlan_24 wlan_5 lan ];
|
||||||
};
|
};
|
||||||
|
|
||||||
services.ntp = svc.ntp.build {
|
services.ntp = svc.ntp.build {
|
||||||
|
@ -103,20 +99,118 @@ 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"
|
||||||
|
"::,constructor:$(output ${interface} ifname),ra-stateless"
|
||||||
|
];
|
||||||
|
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
|
||||||
|
)
|
||||||
|
'';
|
||||||
|
down = ''
|
||||||
|
rm -rf /run/service-state/${name}/
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
filesystem =
|
||||||
|
let inherit (pkgs.pseudofile) dir symlink;
|
||||||
|
in dir {
|
||||||
|
etc = dir {
|
||||||
|
"resolv.conf" = symlink "${services.resolvconf}/.outputs/resolv.conf";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
services.defaultroute4 = route {
|
||||||
|
name = "defaultroute4";
|
||||||
|
via = "$(output ${services.wan} address)";
|
||||||
|
target = "default";
|
||||||
|
dependencies = [ services.wan ];
|
||||||
|
};
|
||||||
|
|
||||||
|
services.defaultroute6 = route {
|
||||||
|
name = "defaultroute6";
|
||||||
|
via = "$(output ${services.wan} ipv6-peer-address)";
|
||||||
|
target = "default";
|
||||||
|
dev = "$(output ${services.wan} ifname)";
|
||||||
|
dependencies = [ services.wan ];
|
||||||
|
};
|
||||||
|
|
||||||
|
services.firewall = svc.firewall.build {
|
||||||
|
ruleset = import ./rotuer-firewall.nix;
|
||||||
|
};
|
||||||
|
|
||||||
|
services.packet_forwarding =
|
||||||
|
let
|
||||||
|
ip4 = "/proc/sys/net/ipv4/conf/all/forwarding";
|
||||||
|
ip6 = "/proc/sys/net/ipv6/conf/all/forwarding";
|
||||||
|
in oneshot {
|
||||||
|
name = "let-the-ip-flow";
|
||||||
|
up = ''
|
||||||
|
echo 1 > ${ip4}
|
||||||
|
echo 1 > ${ip6}
|
||||||
|
'';
|
||||||
|
down = ''
|
||||||
|
echo 0 > ${ip4};
|
||||||
|
echo 0 > ${ip6};
|
||||||
|
'';
|
||||||
|
dependencies = [ services.firewall ];
|
||||||
|
};
|
||||||
|
|
||||||
|
services.dhcp6 =
|
||||||
|
let
|
||||||
|
name = "dhcp6c.wan";
|
||||||
|
in longrun {
|
||||||
|
inherit name;
|
||||||
|
notification-fd = 10;
|
||||||
|
run = ''
|
||||||
|
export SERVICE_STATE=/run/service-state/${name}
|
||||||
|
${pkgs.odhcp6c}/bin/odhcp6c -s ${pkgs.odhcp-script} -e -v -p /run/${name}.pid -P 48 $(output ${services.wan} ifname)
|
||||||
|
)
|
||||||
|
'';
|
||||||
|
dependencies = [ services.wan ];
|
||||||
|
};
|
||||||
|
|
||||||
|
services.acquire-lan-prefix =
|
||||||
|
let script = pkgs.callPackage ./acquire-delegated-prefix.nix { };
|
||||||
|
in longrun {
|
||||||
|
name = "acquire-lan-prefix";
|
||||||
|
run = "${script} /run/service-state/dhcp6c.wan $(output ${services.int} ifname)";
|
||||||
|
dependencies = [ services.dhcp6 ];
|
||||||
|
};
|
||||||
|
|
||||||
|
services.acquire-wan-address =
|
||||||
|
let script = pkgs.callPackage ./acquire-wan-address.nix { };
|
||||||
|
in longrun {
|
||||||
|
name = "acquire-wan-address";
|
||||||
|
run = "${script} /run/service-state/dhcp6c.wan $(output ${services.wan} ifname)";
|
||||||
|
dependencies = [ services.dhcp6 ];
|
||||||
|
};
|
||||||
|
|
||||||
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";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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";
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
|
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
];
|
|
||||||
}
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
{
|
||||||
|
lzma
|
||||||
|
, stdenv
|
||||||
|
, ubootTools
|
||||||
|
, dtc
|
||||||
|
} :
|
||||||
|
let
|
||||||
|
objcopy = "${stdenv.cc.bintools.targetPrefix}objcopy";
|
||||||
|
in {
|
||||||
|
kernel
|
||||||
|
, commandLine
|
||||||
|
, entryPoint
|
||||||
|
, extraName ? "" # e.g. socFamily
|
||||||
|
, loadAddress
|
||||||
|
, dtb ? null
|
||||||
|
} :
|
||||||
|
stdenv.mkDerivation {
|
||||||
|
name = "kernel.image";
|
||||||
|
phases = [
|
||||||
|
"preparePhase"
|
||||||
|
(if dtb != null then "dtbPhase" else ":")
|
||||||
|
"buildPhase"
|
||||||
|
"installPhase" ];
|
||||||
|
nativeBuildInputs = [
|
||||||
|
lzma
|
||||||
|
dtc
|
||||||
|
stdenv.cc
|
||||||
|
ubootTools
|
||||||
|
];
|
||||||
|
preparePhase = ''
|
||||||
|
cp ${kernel} vmlinux.elf; chmod +w vmlinux.elf
|
||||||
|
'';
|
||||||
|
dtbPhase = ''
|
||||||
|
dtc -I dtb -O dts -o tmp.dts ${dtb}
|
||||||
|
echo '/{ chosen { bootargs = ${builtins.toJSON commandLine}; }; };' >> tmp.dts
|
||||||
|
dtc -I dts -O dtb -o tmp.dtb tmp.dts
|
||||||
|
${objcopy} --update-section .appended_dtb=tmp.dtb vmlinux.elf || ${objcopy} --add-section .appended_dtb=${dtb} vmlinux.elf
|
||||||
|
'';
|
||||||
|
|
||||||
|
buildPhase = ''
|
||||||
|
${objcopy} -O binary -R .reginfo -R .notes -R .note -R .comment -R .mdebug -R .note.gnu.build-id -S vmlinux.elf vmlinux.bin
|
||||||
|
rm -f vmlinux.bin.lzma ; lzma -k -z vmlinux.bin
|
||||||
|
mkimage -A mips -O linux -T kernel -C lzma -a ${loadAddress} -e ${entryPoint} -n 'MIPS Liminix Linux ${extraName}' -d vmlinux.bin.lzma kernel.uimage
|
||||||
|
'';
|
||||||
|
installPhase = ''
|
||||||
|
cp kernel.uimage $out
|
||||||
|
'';
|
||||||
|
}
|
|
@ -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
|
|
||||||
];
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
{ lim, pkgs, config, ...}:
|
|
||||||
{
|
|
||||||
config = {
|
|
||||||
kernel.config = {
|
|
||||||
CPU_LITTLE_ENDIAN= "y";
|
|
||||||
CPU_BIG_ENDIAN= "n";
|
|
||||||
# CMDLINE_FROM_BOOTLOADER availability is conditional
|
|
||||||
# on CMDLINE being set to something non-empty
|
|
||||||
CMDLINE="\"empty=false\"";
|
|
||||||
CMDLINE_FROM_BOOTLOADER = "y";
|
|
||||||
|
|
||||||
OF = "y";
|
|
||||||
# USE_OF = "y";
|
|
||||||
};
|
|
||||||
hardware.ram.startAddress = lim.parseInt "0x40000000";
|
|
||||||
system.outputs.u-boot = pkgs.ubootQemuAarch64;
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
{ config, lim, ...}:
|
|
||||||
{
|
|
||||||
config = {
|
|
||||||
kernel.config = {
|
|
||||||
MIPS_ELF_APPENDED_DTB = "y";
|
|
||||||
MIPS_BOOTLOADER_CMDLINE_REQUIRE_COOKIE = "y";
|
|
||||||
MIPS_BOOTLOADER_CMDLINE_COOKIE = "\"liminix\"";
|
|
||||||
MIPS_CMDLINE_DTB_EXTEND = "y";
|
|
||||||
|
|
||||||
OF = "y";
|
|
||||||
USE_OF = "y";
|
|
||||||
};
|
|
||||||
hardware.ram.startAddress = lim.parseInt "0x80000000";
|
|
||||||
boot.commandLine = [
|
|
||||||
"console=ttyS0,115200" # true of all mips we've yet encountered
|
|
||||||
];
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
{ pkgs, config, ...}:
|
|
||||||
{
|
|
||||||
imports = [ ./mips.nix ];
|
|
||||||
config = {
|
|
||||||
kernel.config = {
|
|
||||||
CPU_BIG_ENDIAN = "y";
|
|
||||||
};
|
|
||||||
system.outputs.u-boot = pkgs.ubootQemuMips;
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
{ config, ...}:
|
|
||||||
{
|
|
||||||
imports = [ ./mips.nix ];
|
|
||||||
config = {
|
|
||||||
kernel.config = {
|
|
||||||
CPU_LITTLE_ENDIAN = "y";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -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 = ''
|
||||||
|
@ -38,50 +39,17 @@ in {
|
||||||
};
|
};
|
||||||
rootfsType = mkOption {
|
rootfsType = mkOption {
|
||||||
default = "squashfs";
|
default = "squashfs";
|
||||||
type = types.enum [
|
type = types.enum ["squashfs" "jffs2"];
|
||||||
"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 {
|
|
||||||
type = types.enum [
|
|
||||||
"fit"
|
|
||||||
"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,
|
||||||
|
@ -112,36 +80,24 @@ in {
|
||||||
defaultProfile.packages = with pkgs;
|
defaultProfile.packages = with pkgs;
|
||||||
[ s6 s6-init-bin execline s6-linux-init s6-rc ];
|
[ s6 s6-init-bin execline s6-linux-init s6-rc ];
|
||||||
|
|
||||||
|
hardware.networkInterfaces = {
|
||||||
|
lo =
|
||||||
|
let iface = interface { type = "loopback"; device = "lo";};
|
||||||
|
in bundle {
|
||||||
|
name = "loopback";
|
||||||
|
contents = [
|
||||||
|
(address iface { family = "inet4"; address ="127.0.0.1"; prefixLength = 8;})
|
||||||
|
(address iface { family = "inet6"; address ="::1"; prefixLength = 128;})
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
boot.commandLine = [
|
boot.commandLine = [
|
||||||
"panic=10 oops=panic init=/bin/init loglevel=8"
|
"console=ttyS0,115200 panic=10 oops=panic init=/bin/init loglevel=8"
|
||||||
"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 +137,6 @@ in {
|
||||||
proc = dir {};
|
proc = dir {};
|
||||||
run = dir {};
|
run = dir {};
|
||||||
sys = dir {};
|
sys = dir {};
|
||||||
tmp = dir {};
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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";
|
||||||
|
@ -40,13 +39,5 @@ in
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
config.kernel.config = {
|
config.kernel.config.BRIDGE = "y";
|
||||||
BRIDGE = "y";
|
|
||||||
BRIDGE_IGMP_SNOOPING = "y";
|
|
||||||
} // lib.optionalAttrs (config.system.service ? vlan) {
|
|
||||||
# depends on bridge _and_ vlan. I would like there to be
|
|
||||||
# a better way to test for the existence of vlan config:
|
|
||||||
# maybe the module should set an `enabled` attribute?
|
|
||||||
BRIDGE_VLAN_FILTERING = "y";
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
# servicedefinition
|
|
||||||
svc.ifwait.build {
|
|
||||||
state = "running";
|
|
||||||
interface = member;
|
|
||||||
dependencies = [ primary member ];
|
|
||||||
service = oneshot {
|
|
||||||
name = "${primary.name}.member.${member.name}";
|
name = "${primary.name}.member.${member.name}";
|
||||||
up = ''
|
up = ''
|
||||||
ip link set dev $(output ${member} ifname) master $(output ${primary} ifname)
|
dev=$(output ${member} ifname)
|
||||||
|
${ifwait}/bin/ifwait $dev running && ip link set dev $dev master $(output ${primary} ifname)
|
||||||
'';
|
'';
|
||||||
down = "ip link set dev $(output ${member} ifname) nomaster";
|
down = "ip link set dev $(output ${member} ifname) nomaster";
|
||||||
};
|
dependencies = [ primary member ];
|
||||||
};
|
};
|
||||||
in bundle {
|
in bundle {
|
||||||
name = "${primary.name}.members";
|
name = "${primary.name}.members";
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
{
|
{
|
||||||
liminix
|
liminix
|
||||||
|
, ifwait
|
||||||
, lib
|
, lib
|
||||||
}:
|
}:
|
||||||
{ ifname } :
|
{ ifname } :
|
||||||
let
|
let
|
||||||
inherit (liminix.services) oneshot;
|
inherit (liminix.networking) interface;
|
||||||
|
inherit (liminix.services) bundle oneshot;
|
||||||
|
inherit (lib) mkOption types;
|
||||||
in oneshot rec {
|
in oneshot rec {
|
||||||
name = "${ifname}.link";
|
name = "${ifname}.link";
|
||||||
up = ''
|
up = ''
|
||||||
|
|
|
@ -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
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,32 +0,0 @@
|
||||||
(local { : system } (require :anoia))
|
|
||||||
(local svc (require :anoia.svc))
|
|
||||||
|
|
||||||
(fn deletions [old-addresses new-addresses]
|
|
||||||
(let [deleted {}]
|
|
||||||
(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-prefixes [wan-device addresses new-addresses exec]
|
|
||||||
(each [_ p (ipairs (deletions addresses new-addresses))]
|
|
||||||
(exec
|
|
||||||
(.. "ip address del " p.address "1/" p.len " dev " wan-device)))
|
|
||||||
(each [_ p (pairs new-addresses)]
|
|
||||||
(exec
|
|
||||||
(.. "ip address change " p.address "1/" p.len
|
|
||||||
" dev " wan-device
|
|
||||||
" valid_lft " p.valid
|
|
||||||
" preferred_lft " p.preferred
|
|
||||||
)))
|
|
||||||
new-addresses)
|
|
||||||
|
|
||||||
(fn run []
|
|
||||||
(let [[state-directory lan-device] arg
|
|
||||||
dir (svc.open state-directory)]
|
|
||||||
(accumulate [addresses []
|
|
||||||
v (dir:events)]
|
|
||||||
(update-prefixes lan-device addresses (or (v:output "prefix") []) system))))
|
|
||||||
|
|
||||||
{ : changes : run }
|
|
|
@ -1,10 +0,0 @@
|
||||||
{
|
|
||||||
writeFennel
|
|
||||||
, linotify
|
|
||||||
, anoia
|
|
||||||
, lualinux
|
|
||||||
}:
|
|
||||||
writeFennel "acquire-delegated-prefix" {
|
|
||||||
packages = [ linotify anoia lualinux ];
|
|
||||||
mainFunction = "run";
|
|
||||||
} ./acquire-delegated-prefix.fnl
|
|
|
@ -1,124 +0,0 @@
|
||||||
(local subject (require :acquire-wan-address))
|
|
||||||
(import-macros { : expect= } :anoia.assert)
|
|
||||||
(local { : merge : dup } (require :anoia))
|
|
||||||
|
|
||||||
;; nix-shell --run "cd modules/dhcp6c && fennelrepl acquire-wan-address-test.fnl"
|
|
||||||
|
|
||||||
(local a1
|
|
||||||
{
|
|
||||||
"2001-ab-cd-ef" {
|
|
||||||
:address "2001:ab:cd:ef"
|
|
||||||
:len "64"
|
|
||||||
:preferred "3600"
|
|
||||||
:valid "7200"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
(local a156
|
|
||||||
{
|
|
||||||
"2001-ab-cd-ef" {
|
|
||||||
:address "2001:ab:cd:ef"
|
|
||||||
:len "56"
|
|
||||||
:preferred "3600"
|
|
||||||
:valid "7200"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
(local a2
|
|
||||||
{
|
|
||||||
"2001-0-1-2-3" {
|
|
||||||
:address "2001:0:1:2:3"
|
|
||||||
:len "64"
|
|
||||||
:preferred "3600"
|
|
||||||
:valid "7200"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
(local a21
|
|
||||||
{
|
|
||||||
"2001-0-1-2-3" {
|
|
||||||
:address "2001:0:1:2:3"
|
|
||||||
:len "64"
|
|
||||||
:preferred "1800"
|
|
||||||
:valid "5400"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
(fn first-address []
|
|
||||||
(let [deleted
|
|
||||||
(subject.deletions
|
|
||||||
{ }
|
|
||||||
a1
|
|
||||||
)]
|
|
||||||
(expect= deleted [])))
|
|
||||||
|
|
||||||
(fn second-address []
|
|
||||||
(let [del
|
|
||||||
(subject.deletions
|
|
||||||
a1
|
|
||||||
(merge (dup a1) a2)
|
|
||||||
)]
|
|
||||||
(expect= del [])))
|
|
||||||
|
|
||||||
(fn old-address-is-deleted []
|
|
||||||
(let [del
|
|
||||||
(subject.deletions
|
|
||||||
(merge (dup a1) a2)
|
|
||||||
a1
|
|
||||||
)]
|
|
||||||
(expect= (. del 1) (. a2 "2001-0-1-2-3"))
|
|
||||||
))
|
|
||||||
|
|
||||||
(fn changed-lifetime-not-deleted []
|
|
||||||
(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)
|
|
||||||
(second-address)
|
|
||||||
(old-address-is-deleted)
|
|
||||||
(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")
|
|
|
@ -1,32 +0,0 @@
|
||||||
(local { : system } (require :anoia))
|
|
||||||
(local svc (require :anoia.svc))
|
|
||||||
|
|
||||||
(fn deletions [old-addresses new-addresses]
|
|
||||||
(let [deleted {}]
|
|
||||||
(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]
|
|
||||||
(each [_ p (ipairs (deletions addresses new-addresses))]
|
|
||||||
(exec
|
|
||||||
(.. "ip address del " p.address "/" p.len " dev " wan-device)))
|
|
||||||
(each [_ p (pairs new-addresses)]
|
|
||||||
(exec
|
|
||||||
(.. "ip address change " p.address "/" p.len
|
|
||||||
" dev " wan-device
|
|
||||||
" valid_lft " p.valid
|
|
||||||
" preferred_lft " p.preferred
|
|
||||||
)))
|
|
||||||
new-addresses)
|
|
||||||
|
|
||||||
(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))))
|
|
||||||
|
|
||||||
{ : update-addresses : deletions : run }
|
|
|
@ -1,10 +0,0 @@
|
||||||
{
|
|
||||||
writeFennel
|
|
||||||
, linotify
|
|
||||||
, anoia
|
|
||||||
, lualinux
|
|
||||||
}:
|
|
||||||
writeFennel "acquire-wan-address" {
|
|
||||||
packages = [ linotify anoia lualinux ];
|
|
||||||
mainFunction = "run";
|
|
||||||
} ./acquire-wan-address.fnl
|
|
|
@ -1,14 +0,0 @@
|
||||||
{
|
|
||||||
liminix
|
|
||||||
, callPackage
|
|
||||||
}:
|
|
||||||
{ client, interface } :
|
|
||||||
let
|
|
||||||
inherit (liminix.services) longrun;
|
|
||||||
name = "dhcp6c.addr.${client.name}.${interface.name}";
|
|
||||||
script = callPackage ./acquire-wan-address.nix { };
|
|
||||||
in longrun {
|
|
||||||
inherit name;
|
|
||||||
run = "${script} $SERVICE_OUTPUTS/${client.name} $(output ${interface} ifname)";
|
|
||||||
dependencies = [ client interface ];
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
{
|
|
||||||
liminix
|
|
||||||
, odhcp6c
|
|
||||||
, odhcp-script
|
|
||||||
}:
|
|
||||||
{ interface } :
|
|
||||||
let
|
|
||||||
inherit (liminix.services) longrun;
|
|
||||||
name = "dhcp6c.${interface.name}";
|
|
||||||
in longrun {
|
|
||||||
inherit name;
|
|
||||||
notification-fd = 10;
|
|
||||||
run = ''
|
|
||||||
export SERVICE_STATE=$SERVICE_OUTPUTS/${name}
|
|
||||||
${odhcp6c}/bin/odhcp6c -s ${odhcp-script} -e -v -p /run/${name}.pid -P0 $(output ${interface} ifname)
|
|
||||||
)
|
|
||||||
'';
|
|
||||||
dependencies = [ interface ];
|
|
||||||
}
|
|
|
@ -1,51 +0,0 @@
|
||||||
## DHCP6 client module
|
|
||||||
## ===================
|
|
||||||
##
|
|
||||||
## This is for use if you have an IPv6-capable upstream that provides
|
|
||||||
## address information and/or prefix delegation using DHCP6. It
|
|
||||||
## provides a service to request address information in the form of a
|
|
||||||
## DHCP lease, and two dependent services that listen for updates
|
|
||||||
## to the DHCP address information and can be used to update
|
|
||||||
## addresses of network interfaces that you want to assign those
|
|
||||||
## prefixes to
|
|
||||||
|
|
||||||
{ lib, pkgs, config, ...}:
|
|
||||||
let
|
|
||||||
inherit (lib) mkOption types;
|
|
||||||
inherit (pkgs) liminix;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
options = {
|
|
||||||
system.service.dhcp6c = {
|
|
||||||
client = mkOption { type = liminix.lib.types.serviceDefn; };
|
|
||||||
prefix = mkOption { type = liminix.lib.types.serviceDefn; };
|
|
||||||
address = mkOption { type = liminix.lib.types.serviceDefn; };
|
|
||||||
};
|
|
||||||
};
|
|
||||||
config.system.service.dhcp6c = {
|
|
||||||
client = config.system.callService ./client.nix {
|
|
||||||
interface = mkOption {
|
|
||||||
type = liminix.lib.types.interface;
|
|
||||||
description = "interface (usually WAN) to query for DHCP6";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
address = config.system.callService ./address.nix {
|
|
||||||
client = mkOption {
|
|
||||||
type = types.anything; # liminix.lib.types.service;
|
|
||||||
};
|
|
||||||
interface = mkOption {
|
|
||||||
type = liminix.lib.types.interface;
|
|
||||||
description = "interface to assign the address to";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
prefix = config.system.callService ./prefix.nix {
|
|
||||||
client = mkOption {
|
|
||||||
type = types.anything; # liminix.lib.types.service;
|
|
||||||
};
|
|
||||||
interface = mkOption {
|
|
||||||
type = liminix.lib.types.interface;
|
|
||||||
description = "interface to assign <prefix>::1 to";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
{
|
|
||||||
liminix
|
|
||||||
, callPackage
|
|
||||||
}:
|
|
||||||
{ client, interface } :
|
|
||||||
let
|
|
||||||
inherit (liminix.services) longrun;
|
|
||||||
name = "dhcp6c.prefix.${client.name}.${interface.name}";
|
|
||||||
script = callPackage ./acquire-delegated-prefix.nix { };
|
|
||||||
in longrun {
|
|
||||||
inherit name;
|
|
||||||
run = "${script} $SERVICE_OUTPUTS/${client.name} $(output ${interface} ifname)";
|
|
||||||
dependencies = [ client interface ];
|
|
||||||
}
|
|
|
@ -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";
|
||||||
|
@ -42,38 +42,6 @@ in {
|
||||||
ranges = mkOption {
|
ranges = mkOption {
|
||||||
type = types.listOf types.str;
|
type = types.listOf types.str;
|
||||||
};
|
};
|
||||||
hosts = mkOption {
|
|
||||||
default = {};
|
|
||||||
type = types.attrsOf (types.submodule {
|
|
||||||
options = {
|
|
||||||
mac = mkOption {
|
|
||||||
description = ''
|
|
||||||
MAC or other hardware address to match on. For Ethernet
|
|
||||||
this is a 48 bit address represented as colon-separated
|
|
||||||
hex bytes, or "id:clientid" to match a presented
|
|
||||||
client id (IPv6 DUID)
|
|
||||||
'';
|
|
||||||
type = types.str;
|
|
||||||
example = "01:20:31:4a:50";
|
|
||||||
};
|
|
||||||
v4 = mkOption {
|
|
||||||
description = "IPv4 address to assign to this client";
|
|
||||||
example = "192.0.2.1";
|
|
||||||
type = types.str;
|
|
||||||
};
|
|
||||||
v6 = mkOption {
|
|
||||||
type = types.listOf types.str;
|
|
||||||
description = "IPv6 addresses or interface-ids to assign to this client";
|
|
||||||
default = [];
|
|
||||||
example = [ "fe80::42:1eff:fefd:b341" "::1234"];
|
|
||||||
};
|
|
||||||
leasetime = mkOption {
|
|
||||||
type = types.int;
|
|
||||||
default = 86400;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
});
|
|
||||||
};
|
|
||||||
domain = mkOption {
|
domain = mkOption {
|
||||||
# this can be given multiple times so probably should be
|
# this can be given multiple times so probably should be
|
||||||
# domains plural and list of string
|
# domains plural and list of string
|
||||||
|
|
|
@ -10,22 +10,19 @@
|
||||||
, domain
|
, domain
|
||||||
, group
|
, group
|
||||||
, ranges
|
, ranges
|
||||||
, hosts
|
|
||||||
, upstreams
|
, upstreams
|
||||||
, resolvconf
|
, resolvconf
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
name = "${interface.name}.dnsmasq";
|
name = "${interface.name}.dnsmasq";
|
||||||
inherit (liminix.services) longrun;
|
inherit (liminix.services) longrun;
|
||||||
inherit (lib) concatStrings concatStringsSep mapAttrsToList;
|
inherit (lib) concatStringsSep;
|
||||||
hostOpt = name : { mac, v4, v6, leasetime }:
|
|
||||||
let v6s = concatStrings (map (a : ",[${a}]") v6);
|
|
||||||
in "--dhcp-host=${mac},${v4}${v6s},${name},${builtins.toString leasetime}";
|
|
||||||
in
|
in
|
||||||
longrun {
|
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} \
|
||||||
|
@ -36,15 +33,13 @@ longrun {
|
||||||
--keep-in-foreground \
|
--keep-in-foreground \
|
||||||
--dhcp-authoritative \
|
--dhcp-authoritative \
|
||||||
${if resolvconf != null then "--resolv-file=$(output_path ${resolvconf} resolv.conf)" else "--no-resolv"} \
|
${if resolvconf != null then "--resolv-file=$(output_path ${resolvconf} resolv.conf)" else "--no-resolv"} \
|
||||||
${lib.concatStringsSep " " (mapAttrsToList hostOpt hosts)} \
|
|
||||||
--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 \
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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";
|
|
||||||
IP_NF_TARGET_MASQUERADE = "m";
|
|
||||||
|
|
||||||
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_NAT_MASQUERADE = "y";
|
||||||
NF_TABLES = "m";
|
NF_TABLES= "y";
|
||||||
NF_TABLES_INET = "y";
|
NF_TABLES_INET = "y";
|
||||||
NF_TABLES_IPV4 = "y";
|
NF_TABLES_IPV4 = "y";
|
||||||
NF_TABLES_IPV6 = "y";
|
NF_TABLES_IPV6 = "y";
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
{
|
||||||
|
config
|
||||||
|
, pkgs
|
||||||
|
, lib
|
||||||
|
, ...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
inherit (lib) mkOption types concatStringsSep;
|
||||||
|
inherit (config.boot) tftp;
|
||||||
|
in {
|
||||||
|
options.system.outputs = {
|
||||||
|
firmware = mkOption {
|
||||||
|
type = types.package;
|
||||||
|
internal = true; # component of flashimage
|
||||||
|
description = ''
|
||||||
|
Binary image (combining kernel, FDT, rootfs, initramfs
|
||||||
|
if needed, etc) for the target device.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
flash-scr = mkOption {
|
||||||
|
type = types.package;
|
||||||
|
internal = true; # component of flashimage
|
||||||
|
description = ''
|
||||||
|
Copy-pastable U-Boot commands to TFTP download the
|
||||||
|
image and write it to flash
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
flashimage = mkOption {
|
||||||
|
type = types.package;
|
||||||
|
description = ''
|
||||||
|
Flashable image for the target device, and the script to
|
||||||
|
install it
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = {
|
||||||
|
kernel = {
|
||||||
|
config = {
|
||||||
|
MTD_SPLIT_UIMAGE_FW = "y";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
programs.busybox.applets = [
|
||||||
|
"flashcp"
|
||||||
|
];
|
||||||
|
|
||||||
|
system.outputs = {
|
||||||
|
firmware =
|
||||||
|
let o = config.system.outputs; in
|
||||||
|
pkgs.runCommand "firmware" {} ''
|
||||||
|
dd if=${o.uimage} of=$out bs=128k conv=sync
|
||||||
|
dd if=${o.rootfs} of=$out bs=128k conv=sync,nocreat,notrunc oflag=append
|
||||||
|
'';
|
||||||
|
flashimage =
|
||||||
|
let o = config.system.outputs; in
|
||||||
|
# could use trivial-builders.linkFarmFromDrvs here?
|
||||||
|
pkgs.runCommand "flashimage" {} ''
|
||||||
|
mkdir $out
|
||||||
|
cd $out
|
||||||
|
ln -s ${o.firmware} firmware.bin
|
||||||
|
ln -s ${o.rootfs} rootfs
|
||||||
|
ln -s ${o.kernel} vmlinux
|
||||||
|
ln -s ${o.manifest} manifest
|
||||||
|
ln -s ${o.kernel.headers} build
|
||||||
|
ln -s ${o.uimage} uimage
|
||||||
|
ln -s ${o.dtb} dtb
|
||||||
|
ln -s ${o.flash-scr} flash.scr
|
||||||
|
'';
|
||||||
|
|
||||||
|
flash-scr =
|
||||||
|
let
|
||||||
|
inherit (pkgs.lib.trivial) toHexString;
|
||||||
|
inherit (config.hardware) flash;
|
||||||
|
in
|
||||||
|
pkgs.buildPackages.runCommand "" {} ''
|
||||||
|
imageSize=$(stat -L -c %s ${config.system.outputs.firmware})
|
||||||
|
cat > $out << EOF
|
||||||
|
setenv serverip ${tftp.serverip}
|
||||||
|
setenv ipaddr ${tftp.ipaddr}
|
||||||
|
tftp 0x$(printf %x ${tftp.loadAddress}) result/firmware.bin
|
||||||
|
erase 0x$(printf %x ${flash.address}) +${flash.size}
|
||||||
|
cp.b 0x$(printf %x ${tftp.loadAddress}) 0x$(printf %x ${flash.address}) \''${filesize}
|
||||||
|
echo command line was ${builtins.toJSON (concatStringsSep " " config.boot.commandLine)}
|
||||||
|
EOF
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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"];
|
|
||||||
}
|
|
|
@ -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 ];
|
|
||||||
})
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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
|
||||||
(n : v : "${n}={{ ${literal_or_output v} }}")
|
(name: value: "${name}=${toString value}")
|
||||||
attrs)) + "\n"));
|
(defaults // params)));
|
||||||
service = longrun {
|
in longrun {
|
||||||
inherit name;
|
inherit name;
|
||||||
dependencies = [ interface ];
|
dependencies = [ interface ];
|
||||||
run = ''
|
run = "${hostapd}/bin/hostapd -i $(output ${interface} ifname) -P /run/${name}.pid -S ${conf}";
|
||||||
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";
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -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}
|
|
||||||
'';
|
|
||||||
}
|
|
|
@ -6,31 +6,21 @@
|
||||||
}:
|
}:
|
||||||
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 real root
|
Initramfs image capable of mounting the jffs2 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 {
|
||||||
kernel.config = {
|
kernel.config = {
|
||||||
|
@ -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;
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
|
@ -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
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
|
@ -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;
|
||||||
|
@ -31,7 +24,7 @@ in {
|
||||||
extraPatchPhase = mkOption {
|
extraPatchPhase = mkOption {
|
||||||
default = "true";
|
default = "true";
|
||||||
type = types.lines;
|
type = types.lines;
|
||||||
};
|
} ;
|
||||||
config = mkOption {
|
config = mkOption {
|
||||||
description = ''
|
description = ''
|
||||||
Kernel config options, as listed in Kconfig* files in the
|
Kernel config options, as listed in Kconfig* files in the
|
||||||
|
@ -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";
|
||||||
|
@ -96,6 +56,10 @@ in {
|
||||||
MODULE_SIG = if modular then "y" else "n";
|
MODULE_SIG = if modular then "y" else "n";
|
||||||
DEBUG_FS = "y";
|
DEBUG_FS = "y";
|
||||||
|
|
||||||
|
MIPS_BOOTLOADER_CMDLINE_REQUIRE_COOKIE = "y";
|
||||||
|
MIPS_BOOTLOADER_CMDLINE_COOKIE = "\"liminix\"";
|
||||||
|
MIPS_CMDLINE_DTB_EXTEND = "y";
|
||||||
|
|
||||||
# basic networking protocols
|
# basic networking protocols
|
||||||
NET = "y";
|
NET = "y";
|
||||||
UNIX = "y";
|
UNIX = "y";
|
||||||
|
@ -104,8 +68,6 @@ in {
|
||||||
PACKET = "y"; # for ppp, tcpdump ...
|
PACKET = "y"; # for ppp, tcpdump ...
|
||||||
SYSVIPC= "y";
|
SYSVIPC= "y";
|
||||||
|
|
||||||
NETDEVICES = "y"; # even PPP needs this
|
|
||||||
|
|
||||||
# disabling this option causes the kernel to use an "empty"
|
# disabling this option causes the kernel to use an "empty"
|
||||||
# initramfs instead: it has a /dev/console node and not much
|
# initramfs instead: it has a /dev/console node and not much
|
||||||
# else. Note that pid 1 is started *before* the root
|
# else. Note that pid 1 is started *before* the root
|
|
@ -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" {
|
|
@ -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
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,59 +0,0 @@
|
||||||
## Mount
|
|
||||||
##
|
|
||||||
## Mount filesystems
|
|
||||||
|
|
||||||
|
|
||||||
{ lib, pkgs, config, ...}:
|
|
||||||
let
|
|
||||||
inherit (lib) mkOption types;
|
|
||||||
inherit (pkgs) liminix;
|
|
||||||
|
|
||||||
in {
|
|
||||||
options = {
|
|
||||||
system.service.mount = mkOption {
|
|
||||||
type = liminix.lib.types.serviceDefn;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
imports = [ ../mdevd.nix ../uevent-rule ];
|
|
||||||
config.system.service.mount =
|
|
||||||
let svc = config.system.callService ./service.nix {
|
|
||||||
partlabel = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
example = "my-usb-stick";
|
|
||||||
};
|
|
||||||
mountpoint = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
example = "/mnt/media";
|
|
||||||
};
|
|
||||||
options = mkOption {
|
|
||||||
type = types.listOf types.str;
|
|
||||||
default = [];
|
|
||||||
example = ["noatime" "ro" "sync"];
|
|
||||||
};
|
|
||||||
fstype = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
default = "auto";
|
|
||||||
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 = {
|
|
||||||
applets = ["blkid" "findfs"];
|
|
||||||
options = {
|
|
||||||
FEATURE_BLKID_TYPE = "y";
|
|
||||||
FEATURE_MOUNT_FLAGS = "y";
|
|
||||||
FEATURE_MOUNT_LABEL = "y";
|
|
||||||
FEATURE_VOLUMEID_EXT = "y";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
{
|
|
||||||
liminix
|
|
||||||
, lib
|
|
||||||
, svc
|
|
||||||
}:
|
|
||||||
{ partlabel, mountpoint, options, fstype }:
|
|
||||||
let
|
|
||||||
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 {
|
|
||||||
inherit name;
|
|
||||||
timeout-up = 3600;
|
|
||||||
up = "mount -t ${fstype} ${options_string} ${device} ${mountpoint}";
|
|
||||||
down = "umount ${mountpoint}";
|
|
||||||
inherit controller;
|
|
||||||
}
|
|
|
@ -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}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue