Compare commits
17 Commits
Author | SHA1 | Date |
---|---|---|
Daniel Barlow | dc3ade7969 | |
Daniel Barlow | 7ece8dde5e | |
Daniel Barlow | 5a19af318e | |
Daniel Barlow | 6d36f0eb13 | |
Daniel Barlow | 5d246e1f1a | |
Daniel Barlow | 0e2e44c6ee | |
Daniel Barlow | e2c5615677 | |
Daniel Barlow | ab59c852e2 | |
Daniel Barlow | 98d198960b | |
Daniel Barlow | d98f011292 | |
Daniel Barlow | 991de7eb9e | |
Daniel Barlow | 513c69cb5b | |
Daniel Barlow | 15856d224c | |
Daniel Barlow | abbdda7b30 | |
Daniel Barlow | e56b761ba3 | |
Daniel Barlow | 15ec172739 | |
Daniel Barlow | 6ddba0f68b |
12
NEWS
12
NEWS
|
@ -103,15 +103,3 @@ a bit more useful :-)
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
];
|
];
|
||||||
|
|
||||||
2024-07-16
|
|
||||||
|
|
||||||
* structured parameters are available for the pppoe service
|
|
||||||
|
|
||||||
* The "wan" configuration in modules/profiles/gateway.nix has changed:
|
|
||||||
instead of passing options that are used to create a pppoe interface,
|
|
||||||
callers should create a (pppoe or other) interface and pass that as
|
|
||||||
the value of profile.gateway.wan. For the pppoe case this is now only
|
|
||||||
very slightly more verbose, and it allows using the gateway profile
|
|
||||||
with other kinds of upstream.
|
|
||||||
|
|
||||||
|
|
1338
THOUGHTS.txt
1338
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
|
|
|
@ -6,7 +6,7 @@ in {
|
||||||
options.bordervm = {
|
options.bordervm = {
|
||||||
keys = mkOption {
|
keys = mkOption {
|
||||||
type = types.listOf types.str;
|
type = types.listOf types.str;
|
||||||
default = [ ];
|
default = [];
|
||||||
};
|
};
|
||||||
l2tp = {
|
l2tp = {
|
||||||
host = mkOption {
|
host = mkOption {
|
||||||
|
@ -55,17 +55,18 @@ in {
|
||||||
<nixpkgs/nixos/modules/virtualisation/qemu-vm.nix>
|
<nixpkgs/nixos/modules/virtualisation/qemu-vm.nix>
|
||||||
];
|
];
|
||||||
config = {
|
config = {
|
||||||
boot.kernelParams = [ "loglevel=9" ];
|
boot.kernelParams = [
|
||||||
|
"loglevel=9"
|
||||||
|
];
|
||||||
systemd.services.pppoe =
|
systemd.services.pppoe =
|
||||||
let
|
let conf = pkgs.writeText "kpppoed.toml"
|
||||||
conf = pkgs.writeText "kpppoed.toml" ''
|
''
|
||||||
interface_name = "eth1"
|
interface_name = "eth1"
|
||||||
services = [ "myservice" ]
|
services = [ "myservice" ]
|
||||||
lns_ipaddr = "${cfg.l2tp.host}:${builtins.toString cfg.l2tp.port}"
|
lns_ipaddr = "${cfg.l2tp.host}:${builtins.toString cfg.l2tp.port}"
|
||||||
ac_name = "kpppoed-1.0"
|
ac_name = "kpppoed-1.0"
|
||||||
'';
|
'';
|
||||||
in
|
in {
|
||||||
{
|
|
||||||
wantedBy = [ "multi-user.target" ];
|
wantedBy = [ "multi-user.target" ];
|
||||||
after = [ "network-online.target" ];
|
after = [ "network-online.target" ];
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
|
@ -89,36 +90,25 @@ in {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
services.nginx = {
|
|
||||||
enable = true;
|
|
||||||
user = "liminix";
|
|
||||||
virtualHosts.${config.networking.hostName} = {
|
|
||||||
root = "/home/liminix";
|
|
||||||
default = true;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
systemd.services.nginx.serviceConfig.ProtectHome = "read-only";
|
|
||||||
|
|
||||||
systemd.services.sshd.wantedBy = pkgs.lib.mkForce [ "multi-user.target" ];
|
systemd.services.sshd.wantedBy = pkgs.lib.mkForce [ "multi-user.target" ];
|
||||||
|
|
||||||
|
|
||||||
virtualisation = {
|
virtualisation = {
|
||||||
qemu = {
|
qemu = {
|
||||||
networkingOptions = [ ];
|
networkingOptions = [];
|
||||||
options =
|
options = [] ++
|
||||||
[ ]
|
optional cfg.ethernet.pci.enable
|
||||||
++ optional cfg.ethernet.pci.enable "-device vfio-pci,host=${cfg.ethernet.pci.id}"
|
"-device vfio-pci,host=${cfg.ethernet.pci.id}" ++
|
||||||
++ optionals cfg.ethernet.usb.enable [
|
optionals cfg.ethernet.usb.enable [
|
||||||
"-device usb-ehci,id=ehci"
|
"-device usb-ehci,id=ehci"
|
||||||
"-device usb-host,bus=ehci.0,vendorid=${cfg.ethernet.usb.vendor},productid=${cfg.ethernet.usb.product}"
|
"-device usb-host,bus=ehci.0,vendorid=${cfg.ethernet.usb.vendor},productid=${cfg.ethernet.usb.product}"
|
||||||
]
|
] ++ [
|
||||||
++ [
|
|
||||||
"-nographic"
|
"-nographic"
|
||||||
"-serial mon:stdio"
|
"-serial mon:stdio"
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
sharedDirectories = {
|
sharedDirectories = {
|
||||||
liminix = {
|
liminix = {
|
||||||
securityModel = "none";
|
|
||||||
source = builtins.toString ./.;
|
source = builtins.toString ./.;
|
||||||
target = "/home/liminix/liminix";
|
target = "/home/liminix/liminix";
|
||||||
};
|
};
|
||||||
|
@ -146,13 +136,13 @@ in {
|
||||||
nat = {
|
nat = {
|
||||||
enable = true;
|
enable = true;
|
||||||
internalInterfaces = [ "eth1" ];
|
internalInterfaces = [ "eth1" ];
|
||||||
externalInterface = "eth0";
|
externalInterface ="eth0";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
users.users.liminix = {
|
users.users.liminix = {
|
||||||
isNormalUser = true;
|
isNormalUser = true;
|
||||||
uid = 1000;
|
uid = 1000;
|
||||||
extraGroups = [ "wheel" ];
|
extraGroups = [ "wheel"];
|
||||||
openssh.authorizedKeys.keys = cfg.keys;
|
openssh.authorizedKeys.keys = cfg.keys;
|
||||||
};
|
};
|
||||||
services.getty.autologinUser = "liminix";
|
services.getty.autologinUser = "liminix";
|
||||||
|
|
|
@ -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";
|
||||||
};
|
};
|
||||||
|
|
48
ci.nix
48
ci.nix
|
@ -1,11 +1,11 @@
|
||||||
{
|
{
|
||||||
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 = [
|
||||||
|
@ -27,47 +27,45 @@ let
|
||||||
}).outputs.default;
|
}).outputs.default;
|
||||||
tests = import ./tests/ci.nix;
|
tests = import ./tests/ci.nix;
|
||||||
jobs =
|
jobs =
|
||||||
(genAttrs devices for-device)
|
(genAttrs devices for-device) //
|
||||||
// tests
|
tests //
|
||||||
// {
|
{
|
||||||
buildEnv =
|
buildEnv = (import liminix {
|
||||||
(import liminix {
|
|
||||||
inherit nixpkgs borderVmConf;
|
inherit nixpkgs borderVmConf;
|
||||||
device = import (liminix + "/devices/qemu");
|
device = import (liminix + "/devices/qemu");
|
||||||
liminix-config = vanilla;
|
liminix-config = vanilla;
|
||||||
}).buildEnv;
|
}).buildEnv;
|
||||||
doc =
|
doc =
|
||||||
let
|
let json =
|
||||||
json =
|
|
||||||
(import liminix {
|
(import liminix {
|
||||||
inherit nixpkgs borderVmConf;
|
inherit nixpkgs borderVmConf;
|
||||||
device = import (liminix + "/devices/qemu");
|
device = import (liminix + "/devices/qemu");
|
||||||
liminix-config =
|
liminix-config = {...} : {
|
||||||
{ ... }:
|
|
||||||
{
|
|
||||||
imports = [ ./modules/all-modules.nix ];
|
imports = [ ./modules/all-modules.nix ];
|
||||||
};
|
};
|
||||||
}).outputs.optionsJson;
|
}).outputs.optionsJson;
|
||||||
in
|
installers = map (f: "system.outputs.${f}") [
|
||||||
pkgs.stdenv.mkDerivation {
|
"vmroot"
|
||||||
|
"mtdimage"
|
||||||
|
"ubimage"
|
||||||
|
];
|
||||||
|
inherit (pkgs.lib) concatStringsSep;
|
||||||
|
in pkgs.stdenv.mkDerivation {
|
||||||
name = "liminix-doc";
|
name = "liminix-doc";
|
||||||
nativeBuildInputs = with pkgs; [
|
nativeBuildInputs = with pkgs; [
|
||||||
gnumake
|
gnumake sphinx fennel luaPackages.lyaml
|
||||||
sphinx
|
|
||||||
fennel
|
|
||||||
luaPackages.lyaml
|
|
||||||
];
|
];
|
||||||
src = ./.;
|
src = ./.;
|
||||||
buildPhase = ''
|
buildPhase = ''
|
||||||
cat ${json} | fennel --correlate doc/parse-options.fnl > doc/modules-generated.inc.rst
|
cat ${json} | fennel --correlate doc/parse-options.fnl > doc/modules-generated.rst
|
||||||
cat ${json} | fennel --correlate doc/parse-options-outputs.fnl > doc/outputs-generated.inc.rst
|
cat ${json} | fennel --correlate doc/parse-options-outputs.fnl > doc/outputs-generated.rst
|
||||||
cp ${(import ./doc/hardware.nix)} doc/hardware.rst
|
cp ${(import ./doc/hardware.nix)} doc/hardware.rst
|
||||||
make -C doc html
|
make -C doc html
|
||||||
'';
|
'';
|
||||||
installPhase = ''
|
installPhase = ''
|
||||||
mkdir -p $out/nix-support $out/share/doc/
|
mkdir -p $out/nix-support $out/share/doc/
|
||||||
cd doc
|
cd doc
|
||||||
cp *-generated.inc.rst hardware.rst $out
|
cp *-generated.rst $out
|
||||||
ln -s ${json} $out/options.json
|
ln -s ${json} $out/options.json
|
||||||
cp -a _build/html $out/share/doc/liminix
|
cp -a _build/html $out/share/doc/liminix
|
||||||
echo "file source-dist \"$out/share/doc/liminix\"" \
|
echo "file source-dist \"$out/share/doc/liminix\"" \
|
||||||
|
|
30
default.nix
30
default.nix
|
@ -1,18 +1,16 @@
|
||||||
{
|
{
|
||||||
deviceName ? null,
|
deviceName ? null
|
||||||
device ? (import ./devices/${deviceName}),
|
, device ? (import ./devices/${deviceName} )
|
||||||
liminix-config ? <liminix-config>,
|
, liminix-config ? <liminix-config>
|
||||||
nixpkgs ? <nixpkgs>,
|
, nixpkgs ? <nixpkgs>
|
||||||
borderVmConf ? ./bordervm.conf.nix,
|
, borderVmConf ? ./bordervm.conf.nix
|
||||||
imageType ? "primary",
|
, imageType ? "primary"
|
||||||
}:
|
}:
|
||||||
|
|
||||||
let
|
let
|
||||||
overlay = import ./overlay.nix;
|
overlay = import ./overlay.nix;
|
||||||
pkgs = import nixpkgs (
|
pkgs = import nixpkgs (device.system // {
|
||||||
device.system
|
overlays = [overlay];
|
||||||
// {
|
|
||||||
overlays = [ overlay ];
|
|
||||||
config = {
|
config = {
|
||||||
allowUnsupportedSystem = true; # mipsel
|
allowUnsupportedSystem = true; # mipsel
|
||||||
permittedInsecurePackages = [
|
permittedInsecurePackages = [
|
||||||
|
@ -20,8 +18,7 @@ let
|
||||||
"python-2.7.18.7"
|
"python-2.7.18.7"
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
eval = pkgs.lib.evalModules {
|
eval = pkgs.lib.evalModules {
|
||||||
specialArgs = {
|
specialArgs = {
|
||||||
|
@ -49,14 +46,7 @@ let
|
||||||
borderVm = ((import <nixpkgs/nixos/lib/eval-config.nix>) {
|
borderVm = ((import <nixpkgs/nixos/lib/eval-config.nix>) {
|
||||||
system = builtins.currentSystem;
|
system = builtins.currentSystem;
|
||||||
modules = [
|
modules = [
|
||||||
{
|
({ ... } : { nixpkgs.overlays = [ overlay ]; })
|
||||||
nixpkgs.overlays = [
|
|
||||||
(final: prev: {
|
|
||||||
go-l2tp = final.callPackage ./pkgs/go-l2tp {};
|
|
||||||
tufted = final.callPackage ./pkgs/tufted {};
|
|
||||||
})
|
|
||||||
];
|
|
||||||
}
|
|
||||||
(import ./bordervm-configuration.nix)
|
(import ./bordervm-configuration.nix)
|
||||||
borderVmConf
|
borderVmConf
|
||||||
];
|
];
|
||||||
|
|
|
@ -213,6 +213,7 @@
|
||||||
networkInterfaces =
|
networkInterfaces =
|
||||||
let
|
let
|
||||||
inherit (config.system.service.network) link;
|
inherit (config.system.service.network) link;
|
||||||
|
inherit (config.system.service) bridge;
|
||||||
in rec {
|
in rec {
|
||||||
wan = link.build { ifname = "wan"; };
|
wan = link.build { ifname = "wan"; };
|
||||||
lan1 = link.build { ifname = "lan1"; };
|
lan1 = link.build { ifname = "lan1"; };
|
||||||
|
|
|
@ -23,17 +23,12 @@
|
||||||
VIRTIO_BLK = "y";
|
VIRTIO_BLK = "y";
|
||||||
VIRTIO_NET = "y";
|
VIRTIO_NET = "y";
|
||||||
};
|
};
|
||||||
conditionalConfig = {
|
|
||||||
WLAN= {
|
|
||||||
MAC80211_HWSIM = "m";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
hardware =
|
hardware =
|
||||||
let
|
let
|
||||||
mac80211 = pkgs.kmodloader.override {
|
mac80211 = pkgs.mac80211.override {
|
||||||
inherit (config.system.outputs) kernel;
|
drivers = ["mac80211_hwsim"];
|
||||||
targets = ["mac80211_hwsim"];
|
klibBuild = config.system.outputs.kernel.modulesupport;
|
||||||
};
|
};
|
||||||
in {
|
in {
|
||||||
defaultOutput = "vmroot";
|
defaultOutput = "vmroot";
|
||||||
|
|
|
@ -92,6 +92,7 @@
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
inherit (pkgs.pseudofile) dir symlink;
|
inherit (pkgs.pseudofile) dir symlink;
|
||||||
|
inherit (pkgs.liminix.networking) interface;
|
||||||
in {
|
in {
|
||||||
imports = [
|
imports = [
|
||||||
../../modules/network
|
../../modules/network
|
||||||
|
@ -126,11 +127,11 @@
|
||||||
in {
|
in {
|
||||||
lan = link.build {
|
lan = link.build {
|
||||||
ifname = "lan";
|
ifname = "lan";
|
||||||
devpath = "/devices/platform/ahb/1a000000.eth";
|
devpath = "/devices/platform/ahb/19000000.eth";
|
||||||
};
|
};
|
||||||
wan = link.build {
|
wan = link.build {
|
||||||
ifname = "wan";
|
ifname = "wan";
|
||||||
devpath = "/devices/platform/ahb/19000000.eth";
|
devpath = "/devices/platform/ahb/1a000000.eth";
|
||||||
};
|
};
|
||||||
wlan = link.build {
|
wlan = link.build {
|
||||||
ifname = "wlan0";
|
ifname = "wlan0";
|
||||||
|
|
|
@ -45,6 +45,7 @@
|
||||||
|
|
||||||
module = { pkgs, config, lib, lim, ...}:
|
module = { pkgs, config, lib, lim, ...}:
|
||||||
let
|
let
|
||||||
|
inherit (pkgs.liminix.networking) interface;
|
||||||
inherit (pkgs) openwrt;
|
inherit (pkgs) openwrt;
|
||||||
mac80211 = pkgs.kmodloader.override {
|
mac80211 = pkgs.kmodloader.override {
|
||||||
targets = ["rt2800soc"];
|
targets = ["rt2800soc"];
|
||||||
|
@ -89,6 +90,19 @@
|
||||||
let
|
let
|
||||||
inherit (config.system.service.network) link;
|
inherit (config.system.service.network) link;
|
||||||
inherit (config.system.service) vlan;
|
inherit (config.system.service) vlan;
|
||||||
|
inherit (pkgs.liminix.services) oneshot;
|
||||||
|
swconfig = oneshot {
|
||||||
|
name = "swconfig";
|
||||||
|
up = ''
|
||||||
|
PATH=${pkgs.swconfig}/bin:$PATH
|
||||||
|
swconfig dev switch0 set reset
|
||||||
|
swconfig dev switch0 set enable_vlan 1
|
||||||
|
swconfig dev switch0 vlan 1 set ports '1 2 3 4 6t'
|
||||||
|
swconfig dev switch0 vlan 2 set ports '0 6t'
|
||||||
|
swconfig dev switch0 set apply
|
||||||
|
'';
|
||||||
|
down = "${pkgs.swconfig}/bin/swconfig dev switch0 set reset";
|
||||||
|
};
|
||||||
in rec {
|
in rec {
|
||||||
eth = link.build { ifname = "eth0"; };
|
eth = link.build { ifname = "eth0"; };
|
||||||
# lan and wan ports are both behind a switch on eth0
|
# lan and wan ports are both behind a switch on eth0
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
GL.iNet GL-MT300N-v2
|
GL.iNet GL-MT300N-v2
|
||||||
********************
|
********************
|
||||||
|
|
||||||
The GL-MT300N-v2 "Mango" is is very similar to the :ref:`gl-mt300a`, but is
|
The GL-MT300N-v2 "Mango" is is very similar to the :ref:`MT300A <GL.iNet GL-MT300A>, but is
|
||||||
based on the MT7628 chipset instead of MT7620. It's also marginally cheaper
|
based on the MT7628 chipset instead of MT7620. It's also marginally cheaper
|
||||||
and comes in a yellow case not a blue one. Be sure your device is
|
and comes in a yellow case not a blue one. Be sure your device is
|
||||||
v2 not v1, which is a different animal and has only half as much RAM.
|
v2 not v1, which is a different animal and has only half as much RAM.
|
||||||
|
@ -38,6 +38,7 @@
|
||||||
|
|
||||||
module = { pkgs, config, lib, lim, ...}:
|
module = { pkgs, config, lib, lim, ...}:
|
||||||
let
|
let
|
||||||
|
inherit (pkgs.liminix.networking) interface;
|
||||||
inherit (pkgs.liminix.services) oneshot;
|
inherit (pkgs.liminix.services) oneshot;
|
||||||
inherit (pkgs.pseudofile) dir symlink;
|
inherit (pkgs.pseudofile) dir symlink;
|
||||||
inherit (pkgs) openwrt;
|
inherit (pkgs) openwrt;
|
||||||
|
|
|
@ -19,14 +19,14 @@
|
||||||
ARM targets differ from MIPS in that the kernel format expected
|
ARM targets differ from MIPS in that the kernel format expected
|
||||||
by QEMU is an "Image" (raw binary file) rather than an ELF
|
by QEMU is an "Image" (raw binary file) rather than an ELF
|
||||||
file, but this is taken care of by :command:`run.sh`. Check the
|
file, but this is taken care of by :command:`run.sh`. Check the
|
||||||
documentation for the :ref:`qemu` target for more information.
|
documentation for the :ref:`QEMU` (MIPS) target for more information.
|
||||||
|
|
||||||
'';
|
'';
|
||||||
|
|
||||||
# this device is described by the "qemu" device
|
# this device is described by the "qemu" device
|
||||||
installer = "vmroot";
|
installer = "vmroot";
|
||||||
|
|
||||||
module = { config, lim, ... }: {
|
module = {pkgs, config, lim, ... }: {
|
||||||
imports = [
|
imports = [
|
||||||
../../modules/arch/aarch64.nix
|
../../modules/arch/aarch64.nix
|
||||||
../families/qemu.nix
|
../families/qemu.nix
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
'';
|
'';
|
||||||
installer = "vmroot";
|
installer = "vmroot";
|
||||||
|
|
||||||
module = { config, lim, ... }: {
|
module = {pkgs, config, lim, ... }: {
|
||||||
imports = [
|
imports = [
|
||||||
../../modules/arch/arm.nix
|
../../modules/arch/arm.nix
|
||||||
../families/qemu.nix
|
../families/qemu.nix
|
||||||
|
|
|
@ -36,7 +36,7 @@
|
||||||
in the Development manual.
|
in the Development manual.
|
||||||
|
|
||||||
'';
|
'';
|
||||||
module = { config, lib, lim, ... }: {
|
module = {pkgs, config, lib, lim, ... }: {
|
||||||
imports = [
|
imports = [
|
||||||
../../modules/arch/mipseb.nix
|
../../modules/arch/mipseb.nix
|
||||||
../families/qemu.nix
|
../families/qemu.nix
|
||||||
|
|
|
@ -419,6 +419,7 @@
|
||||||
networkInterfaces =
|
networkInterfaces =
|
||||||
let
|
let
|
||||||
inherit (config.system.service.network) link;
|
inherit (config.system.service.network) link;
|
||||||
|
inherit (config.system.service) bridge;
|
||||||
in rec {
|
in rec {
|
||||||
lan1 = link.build { ifname = "lan1"; };
|
lan1 = link.build { ifname = "lan1"; };
|
||||||
lan2 = link.build { ifname = "lan2"; };
|
lan2 = link.build { ifname = "lan2"; };
|
||||||
|
|
|
@ -155,6 +155,8 @@
|
||||||
|
|
||||||
module = {pkgs, config, lib, lim, ... }:
|
module = {pkgs, config, lib, lim, ... }:
|
||||||
let
|
let
|
||||||
|
openwrt = pkgs.openwrt;
|
||||||
|
inherit (lib) mkOption types;
|
||||||
inherit (pkgs.liminix.services) oneshot;
|
inherit (pkgs.liminix.services) oneshot;
|
||||||
inherit (pkgs) liminix;
|
inherit (pkgs) liminix;
|
||||||
mtd_by_name_links = pkgs.liminix.services.oneshot rec {
|
mtd_by_name_links = pkgs.liminix.services.oneshot rec {
|
||||||
|
@ -356,6 +358,7 @@
|
||||||
networkInterfaces =
|
networkInterfaces =
|
||||||
let
|
let
|
||||||
inherit (config.system.service.network) link;
|
inherit (config.system.service.network) link;
|
||||||
|
inherit (config.system.service) bridge;
|
||||||
in rec {
|
in rec {
|
||||||
en70000 = link.build {
|
en70000 = link.build {
|
||||||
# in armada-38x.dtsi this is eth0.
|
# in armada-38x.dtsi this is eth0.
|
||||||
|
|
|
@ -103,6 +103,8 @@
|
||||||
|
|
||||||
module = { pkgs, config, lib, lim, ...}:
|
module = { pkgs, config, lib, lim, ...}:
|
||||||
let
|
let
|
||||||
|
inherit (pkgs.liminix.networking) interface;
|
||||||
|
inherit (pkgs.liminix.services) oneshot;
|
||||||
inherit (pkgs.pseudofile) dir symlink;
|
inherit (pkgs.pseudofile) dir symlink;
|
||||||
inherit (pkgs) openwrt;
|
inherit (pkgs) openwrt;
|
||||||
|
|
||||||
|
|
348
doc/admin.rst
348
doc/admin.rst
|
@ -4,134 +4,143 @@ System Administration
|
||||||
Services on a running system
|
Services on a running system
|
||||||
****************************
|
****************************
|
||||||
|
|
||||||
Liminix services are built on s6-rc, which is itself layered on s6.
|
* add an s6-rc cheatsheet here
|
||||||
Services are defined at build time in your configuration (see
|
|
||||||
:ref:`configuration-services` for information) and can't be added
|
|
||||||
to/changed at runtime, but to monitor
|
|
||||||
events or diagnose problems you may need to inspect them on the
|
|
||||||
running system. Here are some of the most commonly used s6,-rc
|
|
||||||
commands:
|
|
||||||
|
|
||||||
.. list-table:: Service management quick reference
|
|
||||||
:widths: 55 45
|
|
||||||
:header-rows: 1
|
|
||||||
|
|
||||||
* - What
|
Flashing and updating
|
||||||
- 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
|
Flashing from Liminix
|
||||||
automatically. Liminix does not automatically set it to **down**.
|
=====================
|
||||||
|
|
||||||
(If the process providing a service dies without ever notifying
|
The flash procedure from an existing Liminix-system has two steps.
|
||||||
readiness, Liminix will restart it as many times as it has to until the
|
First we reboot the device (using "kexec") into an "ephemeral"
|
||||||
timeout period elapses, and then stop it and mark it down.)
|
RAM-based version of the new configuration, then when we're happy it
|
||||||
|
works we can flash the image - and if it doesn't work we can reboot
|
||||||
|
the device again and it will boot from the old image.
|
||||||
|
|
||||||
Controlled services
|
|
||||||
===================
|
|
||||||
|
|
||||||
**Controlled** services are those which are started/stopped on demand
|
Building the RAM-based image
|
||||||
by a **controller** (another service) instead of being started at boot
|
----------------------------
|
||||||
time. For example:
|
|
||||||
|
|
||||||
* ``svc.uevent-rule.build`` creates a controlled service which is
|
To create the ephemeral image, build ``outputs.kexecboot`` instead of
|
||||||
active when a particular hardware device (identified by uevent/sysfs
|
``outputs.default``. This generates a directory containing the root
|
||||||
directory) is present.
|
filesystem image and kernel, along with an executable called `kexec`
|
||||||
|
and a `boot.sh` script that runs it with appropriate arguments.
|
||||||
|
|
||||||
* ``svc.round-robin.build`` creates a service controller that
|
For example
|
||||||
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
|
.. code-block:: console
|
||||||
|
|
||||||
# ls -l /run/uncaught-logs/
|
nix-build -I liminix-config=./examples/arhcive.nix \
|
||||||
-rw-r--r-- 1 0 lock
|
--arg device "import ./devices/gl-ar750"
|
||||||
-rw-r--r-- 1 0 state
|
-A outputs.kexecboot && \
|
||||||
-rwxr--r-- 1 98059 @4000000000025cb629c311ac.s
|
(tar chf - result | ssh root@the-device tar -C /run -xvf -)
|
||||||
-rwxr--r-- 1 98061 @40000000000260f7309c7fb4.s
|
|
||||||
-rwxr--r-- 1 98041 @40000000000265233a6cc0b6.s
|
|
||||||
-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
|
and then login to the device and run
|
||||||
@40000000000284f130747343 wan.link.pppoe Connect: ppp0 <--> /dev/pts/0
|
|
||||||
@40000000000284f230acc669 wan.link.pppoe sent [LCP ConfReq id=0x1 <asyncmap 0x0> <magic 0x667a9594> <pcomp> <accomp>]
|
.. code-block:: console
|
||||||
# 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
|
cd /run/result
|
||||||
p>]
|
sh ./boot.sh .
|
||||||
1970-01-02 21:51:48.832588765 wan.link.pppoe sent [LCP ConfReq id=0x1 <asyncmap 0x0> <magic 0x667a9594> <pcomp> <accom
|
|
||||||
p>]
|
|
||||||
|
|
||||||
|
|
||||||
|
This will load the new kernel and map the root filesystem into a RAM
|
||||||
|
disk, then start executing the new kernel. *This is effectively a
|
||||||
|
reboot - be sure to close all open files and finish anything else
|
||||||
|
you were doing first.*
|
||||||
|
|
||||||
|
If the new system crashes or is rebooted, then the device will revert
|
||||||
|
to the old configuration it finds in flash.
|
||||||
|
|
||||||
|
|
||||||
|
Building the second (permanent) image
|
||||||
|
-------------------------------------
|
||||||
|
|
||||||
|
While running in the kexecboot system, you can build the permanent
|
||||||
|
image and copy it to the device with :command:`ssh`
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
build-machine$ nix-build -I liminix-config=./examples/arhcive.nix \
|
||||||
|
--arg device "import ./devices/gl-ar750"
|
||||||
|
-A outputs.default && \
|
||||||
|
(tar chf - result | ssh root@the-device tar -C /run -xvf -)
|
||||||
|
|
||||||
|
build-machine$ tar chf - result/firmware.bin | \
|
||||||
|
ssh root@the-device tar -C /run -xvf -
|
||||||
|
|
||||||
|
Next you need to connect to the device and locate the "firmware"
|
||||||
|
partition, which you can do with a combination of :command:`dmesg`
|
||||||
|
output and the contents of :file:`/proc/mtd`
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
<5>[ 0.469841] Creating 4 MTD partitions on "spi0.0":
|
||||||
|
<5>[ 0.474837] 0x000000000000-0x000000040000 : "u-boot"
|
||||||
|
<5>[ 0.480796] 0x000000040000-0x000000050000 : "u-boot-env"
|
||||||
|
<5>[ 0.487056] 0x000000050000-0x000000060000 : "art"
|
||||||
|
<5>[ 0.492753] 0x000000060000-0x000001000000 : "firmware"
|
||||||
|
|
||||||
|
# cat /proc/mtd
|
||||||
|
dev: size erasesize name
|
||||||
|
mtd0: 00040000 00001000 "u-boot"
|
||||||
|
mtd1: 00010000 00001000 "u-boot-env"
|
||||||
|
mtd2: 00010000 00001000 "art"
|
||||||
|
mtd3: 00fa0000 00001000 "firmware"
|
||||||
|
mtd4: 002a0000 00001000 "kernel"
|
||||||
|
mtd5: 00d00000 00001000 "rootfs"
|
||||||
|
|
||||||
|
Now run (in this example)
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
flashcp -v firmware.bin /dev/mtd3
|
||||||
|
|
||||||
|
|
||||||
|
"I know my new image is good, can I skip the intermediate step?"
|
||||||
|
----------------------------------------------------------------
|
||||||
|
|
||||||
|
In addition to giving you a chance to see if the new image works, this
|
||||||
|
two-step process ensures that you're not copying the new image over
|
||||||
|
the top of the active root filesystem. Sometimes it works, but you
|
||||||
|
will at least need physical access to the device to power-cycle it
|
||||||
|
because it will be effectively frozen afterwards.
|
||||||
|
|
||||||
|
|
||||||
|
Flashing from the boot monitor
|
||||||
|
==============================
|
||||||
|
|
||||||
|
If you are prepared to open the device and have a TTL serial adaptor
|
||||||
|
of some kind to connect it to, you can probably use U-Boot and a TFTP
|
||||||
|
server to download and flash the image. This is quite
|
||||||
|
hardware-specific, and sometimes involves soldering: please refer
|
||||||
|
to :ref:`serial`.
|
||||||
|
|
||||||
|
|
||||||
|
Flashing from OpenWrt
|
||||||
|
=====================
|
||||||
|
|
||||||
|
.. CAUTION:: Untested! A previous version of these instructions
|
||||||
|
(without the -e flag) led to bricking the device
|
||||||
|
when flashing a jffs2 image. If you are reading
|
||||||
|
this message, nobody has yet reported on whether the
|
||||||
|
new instructions are any better.
|
||||||
|
|
||||||
|
If your device is running OpenWrt then it probably has the
|
||||||
|
:command:`mtd` command installed. After transferring the image onto the
|
||||||
|
device using e.g. :command:`ssh`, you can run it as follows:
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
mtd -e -r write /tmp/firmware.bin firmware
|
||||||
|
|
||||||
|
The options to this command are for "erase before writing" and "reboot
|
||||||
|
after writing".
|
||||||
|
|
||||||
|
For more information, please see the `OpenWrt manual <https://openwrt.org/docs/guide-user/installation/sysupgrade.cli>`_ which may also contain (hardware-dependent) instructions on how to flash an image using the vendor firmware - perhaps even from a web interface.
|
||||||
|
|
||||||
Updating an installed system (JFFS2)
|
Updating an installed system (JFFS2)
|
||||||
************************************
|
************************************
|
||||||
|
@ -156,8 +165,6 @@ Note that this only copies the package to the device: it doesn't update
|
||||||
any profile to add it to ``$PATH``
|
any profile to add it to ``$PATH``
|
||||||
|
|
||||||
|
|
||||||
.. _rebuilding the system:
|
|
||||||
|
|
||||||
Rebuilding the system
|
Rebuilding the system
|
||||||
=====================
|
=====================
|
||||||
|
|
||||||
|
@ -189,116 +196,3 @@ Caveats
|
||||||
nixpkgs).
|
nixpkgs).
|
||||||
|
|
||||||
* it cannot upgrade the kernel, only userland
|
* it cannot upgrade the kernel, only userland
|
||||||
|
|
||||||
.. _levitate:
|
|
||||||
|
|
||||||
Reinstalling on a running system
|
|
||||||
********************************
|
|
||||||
|
|
||||||
Liminix is initially installed from a monolithic
|
|
||||||
:file:`firmware.bin` - and unless you're running a writable
|
|
||||||
filesystem, the only way to update it is to build and install a whole
|
|
||||||
new :file:`firmware.bin`. However, you probably would prefer not to
|
|
||||||
have to remove it from its installation site, unplug it from the
|
|
||||||
network and stick serial cables in it all over again.
|
|
||||||
|
|
||||||
It is not (generally) safe to install a new firmware onto the flash
|
|
||||||
partitions that the active system is running on. To address this we
|
|
||||||
have :command:`levitate`, which a way for a running Liminix system to
|
|
||||||
"soft restart" into a ramdisk running only a limited set of services,
|
|
||||||
so that the main partitions can then be safely flashed.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Configuration
|
|
||||||
=============
|
|
||||||
|
|
||||||
Levitate *needs to be configured when you create the initial system*
|
|
||||||
to specify which services/packages/etc to run in maintenance
|
|
||||||
mode. Most likely you want to configure a network interface and an ssh
|
|
||||||
for example so that you can login to reflash it.
|
|
||||||
|
|
||||||
.. code-block:: nix
|
|
||||||
|
|
||||||
defaultProfile.packages = with pkgs; [
|
|
||||||
...
|
|
||||||
(levitate.override {
|
|
||||||
config = {
|
|
||||||
services = {
|
|
||||||
inherit (config.services) dhcpc sshd watchdog;
|
|
||||||
};
|
|
||||||
defaultProfile.packages = [ mtdutils ];
|
|
||||||
users.root = config.users.root;
|
|
||||||
};
|
|
||||||
})
|
|
||||||
];
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Use
|
|
||||||
===
|
|
||||||
|
|
||||||
Connect (with ssh, probably) to the running Liminix system that you
|
|
||||||
wish to upgrade.
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
bash$ ssh root@the-device
|
|
||||||
|
|
||||||
Run :command:`levitate`. This takes a little while (perhaps a few
|
|
||||||
tens of seconds) to execute, and copies all config required for
|
|
||||||
maintenance mode to :file:`/run/maintenance`.
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
# levitate
|
|
||||||
|
|
||||||
Reboot into maintenance mode. You will be logged out
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
# reboot
|
|
||||||
|
|
||||||
Connect to the device again - note that the ssh host key will have changed.
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
# ssh -o UserKnownHostsFile=/dev/null root@the-device
|
|
||||||
|
|
||||||
Check we're in maintenance mode
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
# cat /etc/banner
|
|
||||||
|
|
||||||
LADIES AND GENTLEMEN WE ARE FLOATING IN SPACE
|
|
||||||
|
|
||||||
Most services are disabled. The system is operating
|
|
||||||
with a ram-based root filesystem, making it safe to
|
|
||||||
overwrite the flash devices in order to perform
|
|
||||||
upgrades and maintenance.
|
|
||||||
|
|
||||||
Don't forget to reboot when you have finished.
|
|
||||||
|
|
||||||
Perform the upgrade, using flashcp. This is an example,
|
|
||||||
your device will differ
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
# cat /proc/mtd
|
|
||||||
dev: size erasesize name
|
|
||||||
mtd0: 00030000 00010000 "u-boot"
|
|
||||||
mtd1: 00010000 00010000 "u-boot-env"
|
|
||||||
mtd2: 00010000 00010000 "factory"
|
|
||||||
mtd3: 00f80000 00010000 "firmware"
|
|
||||||
mtd4: 00220000 00010000 "kernel"
|
|
||||||
mtd5: 00d60000 00010000 "rootfs"
|
|
||||||
mtd6: 00010000 00010000 "art"
|
|
||||||
# flashcp -v firmware.bin mtd:firmware
|
|
||||||
|
|
||||||
All done
|
|
||||||
|
|
||||||
.. code-block:: console
|
|
||||||
|
|
||||||
# reboot
|
|
||||||
|
|
||||||
|
|
|
@ -7,19 +7,19 @@
|
||||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
|
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
|
||||||
|
|
||||||
project = 'Liminix'
|
project = 'Liminix'
|
||||||
copyright = '2023-2024 Daniel Barlow'
|
copyright = '2023, Daniel Barlow'
|
||||||
author = 'Daniel Barlow'
|
author = 'Daniel Barlow'
|
||||||
|
|
||||||
# -- General configuration ---------------------------------------------------
|
# -- General configuration ---------------------------------------------------
|
||||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
||||||
|
|
||||||
extensions = [
|
extensions = [
|
||||||
# 'sphinx.ext.autosectionlabel'
|
'sphinx.ext.autosectionlabel'
|
||||||
]
|
]
|
||||||
autosectionlabel_prefix_document = True
|
autosectionlabel_prefix_document = True
|
||||||
|
|
||||||
templates_path = ['_templates']
|
templates_path = ['_templates']
|
||||||
exclude_patterns = ['*.inc.rst', '_build', 'Thumbs.db', '.DS_Store']
|
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,27 @@
|
||||||
.. _configuration:
|
|
||||||
|
|
||||||
Configuration
|
Configuration
|
||||||
#############
|
#############
|
||||||
|
|
||||||
|
Liminix uses the Nix language to provide congruent configuration
|
||||||
|
management. This means that to change anything about the way in
|
||||||
|
which a Liminix system works, you make that change in
|
||||||
|
your :file:`configuration.nix` (or one of the other files it references),
|
||||||
|
and rerun :command:`nix-build` or :command:`liminix-rebuild` to action
|
||||||
|
the change. It is not possible (at least, without shenanigans) to make
|
||||||
|
changes by logging into the device and running imperative commands
|
||||||
|
whose effects may later be overridden: :file:`configuration.nix`
|
||||||
|
always describes the entire system and can be used to recreate that
|
||||||
|
system at any time. You can usefully keep it under version control.
|
||||||
|
|
||||||
|
If you are familiar with NixOS, you will notice some similarities
|
||||||
|
between NixOS and Liminix configuration, and also some
|
||||||
|
differences. Sometimes the differences are due to the
|
||||||
|
resource-constrained devices we deploy onto, sometimes due to
|
||||||
|
differences in the uses these devices are put to.
|
||||||
|
|
||||||
|
|
||||||
|
Configuration taxonomy
|
||||||
|
**********************
|
||||||
|
|
||||||
There are many things you can specify in a configuration, but these
|
There are many things you can specify in a configuration, but these
|
||||||
are the ones you most commonly need to change:
|
are the ones you most commonly need to change:
|
||||||
|
|
||||||
|
@ -67,166 +86,14 @@ domains, or you want to run two SSH daemons on different ports.
|
||||||
don't use the NixOS modules themselves, because the
|
don't use the NixOS modules themselves, because the
|
||||||
underlying system is not similar enough for them to work.
|
underlying system is not similar enough for them to work.
|
||||||
|
|
||||||
.. _configuration-services:
|
|
||||||
|
|
||||||
Services
|
Services
|
||||||
********
|
********
|
||||||
|
|
||||||
In Liminix a service is any kind of long-running task or process on
|
We use the `s6-rc service manager <https://www.skarnet.org/software/s6-rc/overview.html>`_ to start/stop/restart services and handle
|
||||||
the system, that is managed (started, stopped, and monitored) by a
|
service dependencies. Any attribute in `config.services` will become
|
||||||
service supervisor. A typical SOHO router might have services to
|
part of the default set of services that s6-rc will try to bring up on
|
||||||
|
boot.
|
||||||
* answer DHCP and DNS requests from the LAN
|
|
||||||
* provide a wireless access point
|
|
||||||
* connect using PPPoE or L2TP to an upstream network
|
|
||||||
* start/stop the firewall
|
|
||||||
* enable/disable IP packet forwarding
|
|
||||||
* mount filesystems
|
|
||||||
|
|
||||||
(Some of these might not be considered services using other
|
|
||||||
definitions of the term: for example, this L2TP process would be a
|
|
||||||
"client" in the client/server classification; and enabling packet
|
|
||||||
forwarding doesn't require any long-lived process - just a setting to
|
|
||||||
be toggled. However, there is value in being able to use the same
|
|
||||||
abstractions for all the things to manage them and specify their
|
|
||||||
dependency relationships - so in Liminix "everything is a service")
|
|
||||||
|
|
||||||
The service supervision system enables service health monitoring,
|
|
||||||
restart of unhealthy services, and failover to "backup" services when
|
|
||||||
a primary service fails or its dependencies are unavailable. The
|
|
||||||
intention is that you have a framework in which you can specify policy
|
|
||||||
requirements like "ethernet wan dhcp-client should be restarted if it
|
|
||||||
crashes, but if it can't start because the hardware link is down, then
|
|
||||||
4G ppp service should be started instead".
|
|
||||||
|
|
||||||
Any attribute in `config.services` will become part of the default set
|
|
||||||
of services that s6-rc will try to bring up. Services are usually
|
|
||||||
started at boot time, but **controlled services** are those that are
|
|
||||||
required only in particular contexts. For example, a service to mount
|
|
||||||
a USB backup drive should run only when the drive is attached to the
|
|
||||||
system. Liminix currently implements three kinds of controlled service:
|
|
||||||
|
|
||||||
* "uevent-rule" service controllers use sysfs/uevent to identify when
|
|
||||||
particular hardware devices are present, and start/stop a controlled
|
|
||||||
service appropriately.
|
|
||||||
|
|
||||||
* the "round-robin" service controller is used for service failover:
|
|
||||||
it allows you to specify a list of services and runs each of them
|
|
||||||
in turn until it exits, then runs the next.
|
|
||||||
|
|
||||||
* the "health-check" service wraps another service, and runs a "health
|
|
||||||
check" command at regular intervals. When the health check fails,
|
|
||||||
indicating that the wrapped service is not working, it is terminated
|
|
||||||
and allowed to restart.
|
|
||||||
|
|
||||||
Runtime secrets (external vault)
|
|
||||||
================================
|
|
||||||
|
|
||||||
Secrets (such as wifi passphrases, PPP username/password, SSH keys,
|
|
||||||
etc) that you provide as literal values in :file:`configuration.nix`
|
|
||||||
are processed into into config files and scripts at build time, and
|
|
||||||
eventually end up in various files in the (world-readable)
|
|
||||||
:file:`/nix/store` before being baked into a flashable image. To
|
|
||||||
change a secret - whether due to a compromise, or just as part of to a
|
|
||||||
routine key rotation - you need to rebuild the configuration and
|
|
||||||
potentially reflash the affected devices.
|
|
||||||
|
|
||||||
To avoid this, you may instead use a "secrets service", which is a
|
|
||||||
mechanism for your device to fetch secrets from a source external to
|
|
||||||
the Nix store, and create at runtime the configuration files and
|
|
||||||
scripts that start the services which require them.
|
|
||||||
|
|
||||||
Not every possible parameter to every possible service is configurable
|
|
||||||
using a secrets service. Parameters which can be configured this way
|
|
||||||
are those with the type ``liminix.lib.types.replacable``. At the time
|
|
||||||
this document was written, these include:
|
|
||||||
|
|
||||||
* ppp (pppoe and l2tp): ``username``, ``password``
|
|
||||||
* ssh: ``authorizedKeys``
|
|
||||||
* hostapd: all parameters (most likely to be useful for ``wpa_passphrase``)
|
|
||||||
|
|
||||||
To use a runtime secret for any of these parameters:
|
|
||||||
|
|
||||||
* create a secrets service to specify the source of truth for secrets
|
|
||||||
* use the :code:`outputRef` function in the service parameter to specify the secrets service and path
|
|
||||||
|
|
||||||
For example, given you had an HTTPS server hosting a JSON file with the structure
|
|
||||||
|
|
||||||
.. code-block:: json
|
|
||||||
|
|
||||||
"ssh": {
|
|
||||||
"authorizedKeys": {
|
|
||||||
"root": [ "ssh-rsa ....", "ssh-rsa ....", ... ]
|
|
||||||
"guest": [ "ssh-rsa ....", "ssh-rsa ....", ... ]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
you could use a :file:`configuration.nix` fragment something like this
|
|
||||||
to make those keys visible to ssh:
|
|
||||||
|
|
||||||
.. code-block:: nix
|
|
||||||
|
|
||||||
services.secrets = svc.secrets.outboard.build {
|
|
||||||
name = "secret-service";
|
|
||||||
url = "http://10.0.0.1/secrets.json";
|
|
||||||
username = "secrets";
|
|
||||||
password = "liminix";
|
|
||||||
interval = 30; # minutes
|
|
||||||
dependencies = [ config.services.lan ];
|
|
||||||
};
|
|
||||||
services.sshd = svc.ssh.build {
|
|
||||||
authorizedKeys = outputRef config.services.secrets "ssh/authorizedKeys";
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
There are presently two implementations of a secrets service:
|
|
||||||
|
|
||||||
Outboard secrets (HTTPS)
|
|
||||||
------------------------
|
|
||||||
|
|
||||||
This service expects a URL to a JSON file containing all the secrets.
|
|
||||||
|
|
||||||
You may specify a username and password along with the URL, which are
|
|
||||||
used if the file is password-protected (HTTP Basic
|
|
||||||
authentication). Note that this is not a protection against a
|
|
||||||
malicious local user: the username and password are normal build-time
|
|
||||||
parameters so will be readable in the Nix store. This is a mitigation
|
|
||||||
against the URL being accidentally discovered due to e.g. a log file
|
|
||||||
or error message on the server leaking.
|
|
||||||
|
|
||||||
|
|
||||||
Tang secrets (encrypted local file)
|
|
||||||
-----------------------------------
|
|
||||||
|
|
||||||
Aternatively, secrets may be stored locally on the device, in a file
|
|
||||||
that has been encrypted using `Tang <https://github.com/latchset/tang>`_.
|
|
||||||
|
|
||||||
Tang is a server for binding data to network presence.
|
|
||||||
|
|
||||||
This sounds fancy, but the concept is simple. You have some data, but you only want it to be available when the system containing the data is on a certain, usually secure, network.
|
|
||||||
|
|
||||||
|
|
||||||
.. code-block:: nix
|
|
||||||
|
|
||||||
services.secrets = svc.secrets.tang.build {
|
|
||||||
name = "secret-service";
|
|
||||||
path = "/run/mnt/usbstick/secrets.json.jwe";
|
|
||||||
interval = 30; # minutes
|
|
||||||
dependencies = [ config.services.mount-usbstick ];
|
|
||||||
};
|
|
||||||
|
|
||||||
The encryption uses the
|
|
||||||
same scheme/algorithm as `Clevis <https://github.com/latchset/clevis>`_ : you may use the `Clevis instructions <https://github.com/latchset/clevis?tab=readme-ov-file#pin-tang>`_ to
|
|
||||||
encrypt the file on another host and then copy it to your Liminix
|
|
||||||
device, or you can use :command:`tangc encrypt` to encrypt directly on
|
|
||||||
the device. (That latter approach may pose a chicken/egg problem if
|
|
||||||
the device needs secrets to boot up and run the services you are
|
|
||||||
relying on in order to login).
|
|
||||||
|
|
||||||
|
|
||||||
Writing services
|
|
||||||
================
|
|
||||||
|
|
||||||
For the most part, for common use cases, hopefully the services you
|
For the most part, for common use cases, hopefully the services you
|
||||||
need will be defined by modules and you will only have to pass the
|
need will be defined by modules and you will only have to pass the
|
||||||
|
@ -274,101 +141,11 @@ Services may have dependencies: as you see above in the ``cowsayd``
|
||||||
example, it depends on some service called ``config.services.lan``,
|
example, it depends on some service called ``config.services.lan``,
|
||||||
meaning that it won't be started until that other service is up.
|
meaning that it won't be started until that other service is up.
|
||||||
|
|
||||||
Service outputs
|
..
|
||||||
===============
|
TODO: explain service outputs
|
||||||
|
|
||||||
Outputs are a mechanism by which a service can provide data which may
|
|
||||||
be required by other services. For example:
|
|
||||||
|
|
||||||
* the DHCP client service can expect to receive nameserver address
|
|
||||||
information as one of the fields in the response from the DHCP
|
|
||||||
server: we provide that as an output which a dependent service for a
|
|
||||||
stub name resolver can use to configure its upstream servers.
|
|
||||||
|
|
||||||
* a service that creates a new network interface (e.g. ppp) will
|
|
||||||
provide the name of the interface (:code:`ppp0`, or :code:`ppp1` or
|
|
||||||
:code:`ppp7`) as an output so that a dependent service can reference
|
|
||||||
it to set up a route, or to configure firewall rules.
|
|
||||||
|
|
||||||
A service :code:`myservice` should write its outputs as files in
|
|
||||||
:file:`/run/services/outputs/myservice`: you can look around this
|
|
||||||
directory on a running Liminix system to see how it's used currently.
|
|
||||||
Usually we use the :code:`in_outputs` shell function in the
|
|
||||||
:command:`up` or :command:`run` attributes of the service:
|
|
||||||
|
|
||||||
.. code-block:: shell
|
|
||||||
|
|
||||||
(in_outputs ${name}
|
|
||||||
for i in lease mask ip router siaddr dns serverid subnet opt53 interface ; do
|
|
||||||
(printenv $i || true) > $i
|
|
||||||
done)
|
|
||||||
|
|
||||||
The outputs are just files, so technically you can read them using
|
|
||||||
anything that can read a file. Liminix has two "preferred"
|
|
||||||
mechanisms, though:
|
|
||||||
|
|
||||||
One-off lookups
|
|
||||||
---------------
|
|
||||||
|
|
||||||
In any context that ends up being evaluated by the shell, use
|
|
||||||
:code:`output` to print the value of an output
|
|
||||||
|
|
||||||
.. code-block:: nix
|
|
||||||
|
|
||||||
services.defaultroute4 = svc.network.route.build {
|
|
||||||
via = "$(output ${services.wan} address)";
|
|
||||||
target = "default";
|
|
||||||
dependencies = [ services.wan ];
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
Continuous updates
|
|
||||||
------------------
|
|
||||||
|
|
||||||
The downside of using shell functions in downstream service startup
|
|
||||||
scripts is that they only run when the service starts up: if a service
|
|
||||||
output *changes*, the downstream service would have to be restarted to
|
|
||||||
notice the change. Sometimes this is OK but other times the downstream
|
|
||||||
has no other need to restart, if it can only get its new data.
|
|
||||||
|
|
||||||
For this case, there is the :code:`anoia.svc` Fennel library, which
|
|
||||||
allows you to write a simple loop which is iterated over whenever a
|
|
||||||
service's outputs change. This code is from
|
|
||||||
:file:`modules/dhcp6c/acquire-wan-address.fnl`
|
|
||||||
|
|
||||||
.. code-block:: fennel
|
|
||||||
|
|
||||||
(fn update-addresses [wan-device addresses new-addresses exec]
|
|
||||||
;; run some appropriate "ip address [add|remove]" commands
|
|
||||||
)
|
|
||||||
|
|
||||||
(fn run []
|
|
||||||
(let [[state-directory wan-device] arg
|
|
||||||
dir (svc.open state-directory)]
|
|
||||||
(accumulate [addresses []
|
|
||||||
v (dir:events)]
|
|
||||||
(update-addresses wan-device addresses
|
|
||||||
(or (v:output "address") []) system))))
|
|
||||||
|
|
||||||
The :code:`output` method seen here accepts a filename (relative
|
|
||||||
to the service's output directory), or a directory name. It
|
|
||||||
returns the first line of that file, or for directories it
|
|
||||||
returns a table (Lua's key/value datastructure, similar to
|
|
||||||
a hash/dictionary) of the outputs in that directory.
|
|
||||||
|
|
||||||
|
|
||||||
Output design considerations
|
|
||||||
----------------------------
|
|
||||||
|
|
||||||
For preference, outputs should be short and simple, and not require
|
|
||||||
downstream services to do complicated parsing in order to use them.
|
|
||||||
Shell commands in Liminix are run using the Busybox shell which
|
|
||||||
doesn't have the niceties of an advanced shell like Bash let alone
|
|
||||||
those of a real programming language.
|
|
||||||
|
|
||||||
Note also that the Lua :code:`svc` library only reads the first line
|
|
||||||
of each output.
|
|
||||||
|
|
||||||
|
..
|
||||||
|
TODO: outputs that change, and services that poll other services
|
||||||
|
|
||||||
Module implementation
|
Module implementation
|
||||||
*********************
|
*********************
|
||||||
|
@ -415,7 +192,7 @@ To expose a service template in a module, it needs the following:
|
||||||
|
|
||||||
.. code-block:: nix
|
.. code-block:: nix
|
||||||
|
|
||||||
config.system.service.cowsay = config.system.callService ./service.nix {
|
config.system.service.cowsay = liminix.callService ./service.nix {
|
||||||
address = mkOption {
|
address = mkOption {
|
||||||
type = types.str;
|
type = types.str;
|
||||||
default = "0.0.0.0";
|
default = "0.0.0.0";
|
||||||
|
|
|
@ -88,6 +88,64 @@ time with configurations for RP-PPPoE and/or Accel PPP.`
|
||||||
Hardware devices
|
Hardware devices
|
||||||
****************
|
****************
|
||||||
|
|
||||||
|
.. _serial:
|
||||||
|
|
||||||
|
U-Boot and serial shenanigans
|
||||||
|
=============================
|
||||||
|
|
||||||
|
Every device that we have so far encountered in Liminix uses `U-Boot,
|
||||||
|
the "Universal Boot Loader" <https://docs.u-boot.org/en/latest/>`_ so
|
||||||
|
it's worth knowing a bit about it. "Universal" is in this context a
|
||||||
|
bit of a misnomer, though: encountering *mainline* U-Boot is very rare
|
||||||
|
and often you'll find it is a fork from some version last updated
|
||||||
|
in 2008. Upgrading U-Boot is more or less complicated depending on the
|
||||||
|
device and is outside scope for Liminix.
|
||||||
|
|
||||||
|
To speak to U-Boot on your device you'll usually need a serial
|
||||||
|
connection to it. This is device-specific. Usually it involves
|
||||||
|
opening the box, locating the serial header pins (TX, RX and GND) and
|
||||||
|
connecting a USB TTL converter to them.
|
||||||
|
|
||||||
|
The Rolls Royce of USB/UART cables is the `FTDI cable
|
||||||
|
<https://cpc.farnell.com/ftdi/ttl-232r-rpi/cable-debug-ttl-232-usb-rpi/dp/SC12825?st=usb%20to%20uart%20cable>`_,
|
||||||
|
but there are cheaper alternatives based on the PL2303 and CP2102 chipsets. Or
|
||||||
|
get creative and use the `UART GPIO pins <https://pinout.xyz/>`_ on a Raspberry Pi. Whatever you do, make sure
|
||||||
|
that the voltages are compatible: if your device is 3.3V (this is
|
||||||
|
typical but not universal), you don't want to be sending it 5v or
|
||||||
|
(even worse) 12v.
|
||||||
|
|
||||||
|
Run a terminal emulator such as Minicom on the computer at other end
|
||||||
|
of the link. 115200 8N1 is the typical speed.
|
||||||
|
|
||||||
|
.. NOTE::
|
||||||
|
|
||||||
|
TTL serial connections typically have no form of flow control and
|
||||||
|
so don't always like having massive chunks of text pasted into
|
||||||
|
them - and U-Boot may drop characters while it's busy. So don't
|
||||||
|
necessarily expect to copy-paste large chunks of text into the
|
||||||
|
terminal emulator and have it work just like that.
|
||||||
|
|
||||||
|
If using Minicom, you may find it helps to bring up the "Termimal
|
||||||
|
settings" dialog (C^A T), then configure "Newline tx delay" to
|
||||||
|
some small but non-zero value.
|
||||||
|
|
||||||
|
When you turn the router on you should be greeted with some messages
|
||||||
|
from U-Boot, followed by the instruction to hit some key to stop
|
||||||
|
autoboot. Do this and you will get to the prompt. If you didn't see
|
||||||
|
anything, the strong likelihood is that TX and RX are the wrong way
|
||||||
|
around. If you see garbage, try a different speed.
|
||||||
|
|
||||||
|
Interesting commands to try first in U-Boot are :command:`help` and
|
||||||
|
:command:`printenv`.
|
||||||
|
|
||||||
|
To do anything useful with U-Boot you will probably need a way to get
|
||||||
|
large binary files onto the device, and the usual way to do this is by
|
||||||
|
adding a network connection and using TFTP to download them. It's
|
||||||
|
quite common that the device's U-Boot doesn't speak DHCP so it will
|
||||||
|
need a static LAN address. You might also want to keep it away from
|
||||||
|
your "real" LAN: see :ref:`bng` for some potentially useful tooling
|
||||||
|
to use it on an isolated network.
|
||||||
|
|
||||||
|
|
||||||
TFTP
|
TFTP
|
||||||
====
|
====
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
{ eval, lib, pkgs }:
|
{ eval, lib, pkgs }:
|
||||||
let
|
let
|
||||||
|
inherit (lib) types;
|
||||||
conf = eval.config;
|
conf = eval.config;
|
||||||
rootDir = builtins.toPath ./..;
|
rootDir = builtins.toPath ./..;
|
||||||
stripAnyPrefixes = lib.flip (lib.fold lib.removePrefix) [ "${rootDir}/" ];
|
stripAnyPrefixes = lib.flip (lib.fold lib.removePrefix)
|
||||||
optToDoc = name: opt: {
|
["${rootDir}/"];
|
||||||
|
optToDoc = name: opt : {
|
||||||
inherit name;
|
inherit name;
|
||||||
description = opt.description or null;
|
description = opt.description or null;
|
||||||
default = opt.default or null;
|
default = opt.default or null;
|
||||||
|
@ -26,4 +28,5 @@ let
|
||||||
else
|
else
|
||||||
item // { declarations = map stripAnyPrefixes item.declarations; };
|
item // { declarations = map stripAnyPrefixes item.declarations; };
|
||||||
in
|
in
|
||||||
builtins.map spliceServiceDefn (pkgs.lib.optionAttrSetToDocList eval.options)
|
builtins.map spliceServiceDefn
|
||||||
|
(pkgs.lib.optionAttrSetToDocList eval.options)
|
||||||
|
|
|
@ -1,22 +1,24 @@
|
||||||
with import <nixpkgs> { };
|
with import <nixpkgs> {} ;
|
||||||
|
|
||||||
let
|
let
|
||||||
inherit (builtins) stringLength readDir filter;
|
inherit (builtins) stringLength readDir filter;
|
||||||
devices = filter (n: n != "families") (lib.mapAttrsToList (n: t: n) (readDir ../devices));
|
devices = filter (n: n != "families")
|
||||||
texts = map (
|
(lib.mapAttrsToList (n: t: n) (readDir ../devices));
|
||||||
n:
|
texts = map (n:
|
||||||
let
|
let d = import ../devices/${n}/default.nix;
|
||||||
d = import ../devices/${n}/default.nix;
|
|
||||||
tag = ".. _${lib.strings.replaceStrings [" "] ["-"] n}:";
|
|
||||||
d' = {
|
d' = {
|
||||||
description = ''
|
description = "${n}\n${substring 0 (stringLength n) "********************************"}\n";
|
||||||
${n}
|
|
||||||
${substring 0 (stringLength n) "********************************"}
|
|
||||||
'';
|
|
||||||
} // d;
|
} // d;
|
||||||
in
|
installer =
|
||||||
"${tag}\n\n${d'.description}"
|
if d ? description && d ? installer
|
||||||
) devices;
|
then ''
|
||||||
|
|
||||||
|
The default installation route for this device is
|
||||||
|
:ref:`system-outputs-${d.installer}`
|
||||||
|
''
|
||||||
|
else "";
|
||||||
|
in d'.description)
|
||||||
|
devices;
|
||||||
in
|
in
|
||||||
writeText "hwdoc" ''
|
writeText "hwdoc" ''
|
||||||
Supported hardware
|
Supported hardware
|
||||||
|
|
|
@ -7,7 +7,6 @@ Liminix
|
||||||
|
|
||||||
intro
|
intro
|
||||||
tutorial
|
tutorial
|
||||||
installation
|
|
||||||
configuration
|
configuration
|
||||||
admin
|
admin
|
||||||
development
|
development
|
||||||
|
|
|
@ -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 +1,4 @@
|
||||||
Module options
|
Module options
|
||||||
##############
|
##############
|
||||||
|
|
||||||
.. include:: modules-generated.inc.rst
|
.. include:: modules-generated.rst
|
||||||
|
|
|
@ -10,4 +10,4 @@ different artefacts, or have different ways to get that artefact
|
||||||
installed. The options available for a particular device are described in
|
installed. The options available for a particular device are described in
|
||||||
the section for that device.
|
the section for that device.
|
||||||
|
|
||||||
.. include:: outputs-generated.inc.rst
|
.. include:: outputs-generated.rst
|
||||||
|
|
|
@ -16,4 +16,4 @@
|
||||||
(each [_ option (ipairs (sorted-options (yaml.load (io.read "*a"))))]
|
(each [_ option (ipairs (sorted-options (yaml.load (io.read "*a"))))]
|
||||||
(when (and (output? option) (not option.internal))
|
(when (and (output? option) (not option.internal))
|
||||||
(print (.. ".. _" (string.gsub option.name "%." "-") ":") "\n")
|
(print (.. ".. _" (string.gsub option.name "%." "-") ":") "\n")
|
||||||
(print option.description "\n")))
|
(print option.description)))
|
||||||
|
|
|
@ -300,7 +300,7 @@ machine, which for a test/demo system might involve a second network
|
||||||
device in your build system - USB ethernet adapters are cheap - or
|
device in your build system - USB ethernet adapters are cheap - or
|
||||||
a bit of messing around unplugging cables.)
|
a bit of messing around unplugging cables.)
|
||||||
|
|
||||||
For more information about :code:`liminix-rebuild`, see the manual section :ref:`Rebuilding the system`.
|
For more information about :code:`liminix-rebuild`, see the manual section :ref:`admin:Rebuilding the system`.
|
||||||
|
|
||||||
|
|
||||||
Final thoughts
|
Final thoughts
|
||||||
|
|
|
@ -11,9 +11,9 @@
|
||||||
...
|
...
|
||||||
}: let
|
}: let
|
||||||
secrets = import ./extneder-secrets.nix;
|
secrets = import ./extneder-secrets.nix;
|
||||||
inherit (pkgs.liminix.services) oneshot longrun target;
|
inherit (pkgs.liminix.services) oneshot longrun bundle target;
|
||||||
inherit (pkgs.pseudofile) dir symlink;
|
inherit (pkgs.pseudofile) dir symlink;
|
||||||
inherit (pkgs) writeText serviceFns;
|
inherit (pkgs) writeText dropbear ifwait serviceFns;
|
||||||
svc = config.system.service;
|
svc = config.system.service;
|
||||||
in rec {
|
in rec {
|
||||||
boot = {
|
boot = {
|
||||||
|
@ -52,6 +52,7 @@ in rec {
|
||||||
dependencies = [ services.dhcpc ];
|
dependencies = [ services.dhcpc ];
|
||||||
name = "resolvconf";
|
name = "resolvconf";
|
||||||
up = ''
|
up = ''
|
||||||
|
. ${serviceFns}
|
||||||
( in_outputs ${name}
|
( in_outputs ${name}
|
||||||
for i in $(output ${services.dhcpc} dns); do
|
for i in $(output ${services.dhcpc} dns); do
|
||||||
echo "nameserver $i" > resolv.conf
|
echo "nameserver $i" > resolv.conf
|
||||||
|
@ -92,6 +93,7 @@ in rec {
|
||||||
secrets_file = oneshot rec {
|
secrets_file = oneshot rec {
|
||||||
name = "rsync-secrets";
|
name = "rsync-secrets";
|
||||||
up = ''
|
up = ''
|
||||||
|
. ${serviceFns}
|
||||||
(in_outputs ${name}
|
(in_outputs ${name}
|
||||||
echo "backup:${secrets.rsync_secret}" > secrets)
|
echo "backup:${secrets.rsync_secret}" > secrets)
|
||||||
'';
|
'';
|
||||||
|
@ -117,7 +119,7 @@ in rec {
|
||||||
secrets_file
|
secrets_file
|
||||||
services.mount_external_disk
|
services.mount_external_disk
|
||||||
config.hardware.networkInterfaces.lan
|
config.hardware.networkInterfaces.lan
|
||||||
];
|
] ;
|
||||||
};
|
};
|
||||||
|
|
||||||
users.root = {
|
users.root = {
|
||||||
|
@ -126,15 +128,11 @@ in rec {
|
||||||
};
|
};
|
||||||
|
|
||||||
users.backup = {
|
users.backup = {
|
||||||
uid = 500;
|
uid=500; gid=500; gecos="Storage owner"; dir="/srv";
|
||||||
gid = 500;
|
shell="/dev/null";
|
||||||
gecos = "Storage owner";
|
|
||||||
dir = "/srv";
|
|
||||||
shell = "/dev/null";
|
|
||||||
};
|
};
|
||||||
groups.backup = {
|
groups.backup = {
|
||||||
gid = 500;
|
gid=500; usernames = ["backup"];
|
||||||
usernames = [ "backup" ];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
defaultProfile.packages = with pkgs; [
|
defaultProfile.packages = with pkgs; [
|
||||||
|
|
|
@ -5,9 +5,9 @@
|
||||||
# wherever the text "EDIT" appears - please consult the tutorial
|
# wherever the text "EDIT" appears - please consult the tutorial
|
||||||
# documentation for details.
|
# documentation for details.
|
||||||
|
|
||||||
{ config, pkgs, ... }:
|
{ config, pkgs, lib, ... } :
|
||||||
let
|
let
|
||||||
inherit (pkgs.liminix.services) bundle oneshot;
|
inherit (pkgs.liminix.services) bundle oneshot longrun;
|
||||||
inherit (pkgs) serviceFns;
|
inherit (pkgs) serviceFns;
|
||||||
# EDIT: you can pick your preferred RFC1918 address space
|
# EDIT: you can pick your preferred RFC1918 address space
|
||||||
# for NATted connections, if you don't like this one.
|
# for NATted connections, if you don't like this one.
|
||||||
|
@ -49,7 +49,7 @@ in rec {
|
||||||
country_code = "GB";
|
country_code = "GB";
|
||||||
wpa_passphrase = "not a real wifi password";
|
wpa_passphrase = "not a real wifi password";
|
||||||
|
|
||||||
hw_mode = "g";
|
hw_mode="g";
|
||||||
ieee80211n = 1;
|
ieee80211n = 1;
|
||||||
auth_algs = 1; # 1=wpa2, 2=wep, 3=both
|
auth_algs = 1; # 1=wpa2, 2=wep, 3=both
|
||||||
wpa = 2; # 1=wpa, 2=wpa2, 3=both
|
wpa = 2; # 1=wpa, 2=wpa2, 3=both
|
||||||
|
@ -62,27 +62,18 @@ in rec {
|
||||||
|
|
||||||
services.int = svc.network.address.build {
|
services.int = svc.network.address.build {
|
||||||
interface = svc.bridge.primary.build { ifname = "int"; };
|
interface = svc.bridge.primary.build { ifname = "int"; };
|
||||||
family = "inet";
|
family = "inet"; address = "${ipv4LocalNet}.1"; prefixLength = 16;
|
||||||
address = "${ipv4LocalNet}.1";
|
|
||||||
prefixLength = 16;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
services.bridge = svc.bridge.members.build {
|
services.bridge = svc.bridge.members.build {
|
||||||
primary = services.int;
|
primary = services.int;
|
||||||
members = with config.hardware.networkInterfaces; [
|
members = with config.hardware.networkInterfaces;
|
||||||
wlan
|
[ wlan lan ];
|
||||||
lan
|
|
||||||
];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
services.ntp = svc.ntp.build {
|
services.ntp = svc.ntp.build {
|
||||||
pools = {
|
pools = { "pool.ntp.org" = ["iburst"]; };
|
||||||
"pool.ntp.org" = [ "iburst" ];
|
makestep = { threshold = 1.0; limit = 3; };
|
||||||
};
|
|
||||||
makestep = {
|
|
||||||
threshold = 1.0;
|
|
||||||
limit = 3;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
services.sshd = svc.ssh.build { };
|
services.sshd = svc.ssh.build { };
|
||||||
|
@ -137,6 +128,7 @@ in rec {
|
||||||
dependencies = [ services.wan ];
|
dependencies = [ services.wan ];
|
||||||
name = "resolvconf";
|
name = "resolvconf";
|
||||||
up = ''
|
up = ''
|
||||||
|
. ${serviceFns}
|
||||||
( in_outputs ${name}
|
( in_outputs ${name}
|
||||||
echo "nameserver $(output ${services.wan} ns1)" > resolv.conf
|
echo "nameserver $(output ${services.wan} ns1)" > resolv.conf
|
||||||
echo "nameserver $(output ${services.wan} ns2)" >> resolv.conf
|
echo "nameserver $(output ${services.wan} ns2)" >> resolv.conf
|
||||||
|
@ -165,7 +157,8 @@ in rec {
|
||||||
interface = services.wan;
|
interface = services.wan;
|
||||||
};
|
};
|
||||||
|
|
||||||
services.firewall = svc.firewall.build { };
|
services.firewall = svc.firewall.build {
|
||||||
|
};
|
||||||
|
|
||||||
services.packet_forwarding = svc.network.forward.build { };
|
services.packet_forwarding = svc.network.forward.build { };
|
||||||
|
|
||||||
|
@ -202,5 +195,7 @@ in rec {
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
defaultProfile.packages = with pkgs; [ min-collect-garbage ];
|
defaultProfile.packages = with pkgs; [
|
||||||
|
min-collect-garbage
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
{ config, pkgs, ... } :
|
{ config, pkgs, lib, ... } :
|
||||||
let
|
let
|
||||||
|
inherit (pkgs) serviceFns;
|
||||||
svc = config.system.service;
|
svc = config.system.service;
|
||||||
|
|
||||||
in rec {
|
in rec {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
{ config, pkgs, ... } :
|
{ config, pkgs, lib, ... } :
|
||||||
let
|
let
|
||||||
|
inherit (pkgs) serviceFns;
|
||||||
svc = config.system.service;
|
svc = config.system.service;
|
||||||
|
|
||||||
in rec {
|
in rec {
|
||||||
|
|
|
@ -0,0 +1,86 @@
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
secrets = import ./extneder-secrets.nix;
|
||||||
|
rsecrets = import ./rotuer-secrets.nix;
|
||||||
|
lns = "l2tp.aaisp.net.uk";
|
||||||
|
inherit (pkgs.liminix.services) oneshot longrun bundle target;
|
||||||
|
inherit (pkgs.pseudofile) dir symlink;
|
||||||
|
inherit (pkgs) writeText dropbear ifwait serviceFns;
|
||||||
|
svc = config.system.service;
|
||||||
|
in rec {
|
||||||
|
boot = {
|
||||||
|
tftp = {
|
||||||
|
serverip = "10.0.0.1";
|
||||||
|
ipaddr = "10.0.0.8";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
imports = [
|
||||||
|
../modules/cdc-ncm
|
||||||
|
../modules/network
|
||||||
|
../modules/vlan
|
||||||
|
../modules/ssh
|
||||||
|
../modules/usb.nix
|
||||||
|
../modules/watchdog
|
||||||
|
../modules/mount
|
||||||
|
../modules/ppp
|
||||||
|
];
|
||||||
|
hostname = "thing";
|
||||||
|
|
||||||
|
services.dhcpc = svc.network.dhcp.client.build {
|
||||||
|
interface = config.services.wwan;
|
||||||
|
dependencies = [ config.services.hostname ];
|
||||||
|
};
|
||||||
|
|
||||||
|
services.sshd = svc.ssh.build { };
|
||||||
|
|
||||||
|
services.resolvconf = oneshot rec {
|
||||||
|
dependencies = [ services.dhcpc ];
|
||||||
|
name = "resolvconf";
|
||||||
|
up = ''
|
||||||
|
. ${serviceFns}
|
||||||
|
( in_outputs ${name}
|
||||||
|
for i in $(output ${services.dhcpc} dns); do
|
||||||
|
echo "nameserver $i" > resolv.conf
|
||||||
|
done
|
||||||
|
)
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
filesystem = dir {
|
||||||
|
etc = dir {
|
||||||
|
"resolv.conf" = symlink "${services.resolvconf}/.outputs/resolv.conf";
|
||||||
|
};
|
||||||
|
srv = dir {};
|
||||||
|
};
|
||||||
|
|
||||||
|
services.lnsroute = svc.network.route.build {
|
||||||
|
via = "$(output ${services.dhcpc} router)";
|
||||||
|
target = lns;
|
||||||
|
dependencies = [services.dhcpc];
|
||||||
|
};
|
||||||
|
|
||||||
|
services.l2tp = svc.l2tp.build {
|
||||||
|
inherit lns;
|
||||||
|
ppp-options = [
|
||||||
|
"debug" "+ipv6" "noauth"
|
||||||
|
"name" rsecrets.l2tp.name
|
||||||
|
"password" rsecrets.l2tp.password
|
||||||
|
];
|
||||||
|
dependencies = [ services.lnsroute ];
|
||||||
|
};
|
||||||
|
|
||||||
|
services.defaultroute4 = svc.network.route.build {
|
||||||
|
via = "$(output ${services.l2tp} router)";
|
||||||
|
target = "default";
|
||||||
|
dependencies = [services.l2tp];
|
||||||
|
};
|
||||||
|
|
||||||
|
users.root = {
|
||||||
|
passwd = lib.mkForce secrets.root.passwd;
|
||||||
|
openssh.authorizedKeys.keys = secrets.root.keys;
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
{ config, pkgs, ... } :
|
{ config, pkgs, ... } :
|
||||||
let
|
let
|
||||||
inherit (pkgs.liminix.services) target;
|
inherit (pkgs.liminix.services) oneshot longrun bundle target;
|
||||||
|
inherit (pkgs) writeText;
|
||||||
svc = config.system.service;
|
svc = config.system.service;
|
||||||
secrets-1 = {
|
secrets-1 = {
|
||||||
ssid = "Zyxel 2G (N)";
|
ssid = "Zyxel 2G (N)";
|
||||||
|
|
|
@ -3,8 +3,8 @@ let
|
||||||
inherit (pkgs) serviceFns;
|
inherit (pkgs) serviceFns;
|
||||||
svc = config.system.service;
|
svc = config.system.service;
|
||||||
inherit (pkgs.pseudofile) dir symlink;
|
inherit (pkgs.pseudofile) dir symlink;
|
||||||
inherit (pkgs.liminix.services) oneshot target;
|
inherit (pkgs.liminix.services) oneshot longrun bundle target;
|
||||||
some-util-linux = pkgs.runCommand "some-util-linux" { } ''
|
some-util-linux = pkgs.runCommand "some-util-linux" {} ''
|
||||||
mkdir -p $out/bin
|
mkdir -p $out/bin
|
||||||
cd ${pkgs.util-linux-small}/bin
|
cd ${pkgs.util-linux-small}/bin
|
||||||
cp fdisk sfdisk mkswap $out/bin
|
cp fdisk sfdisk mkswap $out/bin
|
||||||
|
@ -53,13 +53,14 @@ in rec {
|
||||||
services.defaultroute4 = svc.network.route.build {
|
services.defaultroute4 = svc.network.route.build {
|
||||||
via = "$(output ${services.dhcpc} router)";
|
via = "$(output ${services.dhcpc} router)";
|
||||||
target = "default";
|
target = "default";
|
||||||
dependencies = [ services.dhcpc ];
|
dependencies = [services.dhcpc];
|
||||||
};
|
};
|
||||||
|
|
||||||
services.resolvconf = oneshot rec {
|
services.resolvconf = oneshot rec {
|
||||||
dependencies = [ services.dhcpc ];
|
dependencies = [ services.dhcpc ];
|
||||||
name = "resolvconf";
|
name = "resolvconf";
|
||||||
up = ''
|
up = ''
|
||||||
|
. ${serviceFns}
|
||||||
( in_outputs ${name}
|
( in_outputs ${name}
|
||||||
for i in $(output ${services.dhcpc} dns); do
|
for i in $(output ${services.dhcpc} dns); do
|
||||||
echo "nameserver $i" > resolv.conf
|
echo "nameserver $i" > resolv.conf
|
||||||
|
@ -71,6 +72,7 @@ in rec {
|
||||||
services.growfs = let name = "growfs"; in oneshot {
|
services.growfs = let name = "growfs"; in oneshot {
|
||||||
inherit name;
|
inherit name;
|
||||||
up = ''
|
up = ''
|
||||||
|
. ${serviceFns}
|
||||||
device=$(grep /persist /proc/1/mountinfo | cut -f9 -d' ')
|
device=$(grep /persist /proc/1/mountinfo | cut -f9 -d' ')
|
||||||
${pkgs.e2fsprogs}/bin/resize2fs $device
|
${pkgs.e2fsprogs}/bin/resize2fs $device
|
||||||
'';
|
'';
|
||||||
|
|
|
@ -8,10 +8,12 @@
|
||||||
root = {
|
root = {
|
||||||
# mkpasswd -m sha512crypt
|
# mkpasswd -m sha512crypt
|
||||||
passwd = "$6$6pt0mpbgcB7kC2RJ$kSBoCYGyi1.qxt7dqmexLj1l8E6oTZJZmfGyJSsMYMW.jlsETxdgQSdv6ptOYDM7DHAwf6vLG0pz3UD31XBfC1";
|
passwd = "$6$6pt0mpbgcB7kC2RJ$kSBoCYGyi1.qxt7dqmexLj1l8E6oTZJZmfGyJSsMYMW.jlsETxdgQSdv6ptOYDM7DHAwf6vLG0pz3UD31XBfC1";
|
||||||
openssh.authorizedKeys.keys = [ ];
|
openssh.authorizedKeys.keys = [
|
||||||
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
lan = {
|
lan = {
|
||||||
prefix = "10.8.0";
|
prefix = "10.8.0";
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,19 @@
|
||||||
# This is an example that uses the "gateway" profile to create a
|
# This is not part of Liminix per se. This is my "scratchpad"
|
||||||
# "typical home wireless router" configuration suitable for a Gl.inet
|
# configuration for the device I'm testing with.
|
||||||
# gl-ar750 router. It should be fairly simple to edit it for other
|
#
|
||||||
# devices: mostly you will need to attend to the number of wlan and lan
|
# Parts of it do do things that Liminix eventually needs to do, but
|
||||||
# interfaces
|
# don't look in here for solutions - just for identifying the
|
||||||
|
# problems.
|
||||||
|
|
||||||
|
|
||||||
{ config, pkgs, lib, modulesPath, ... } :
|
{ config, pkgs, lib, modulesPath, ... } :
|
||||||
let
|
let
|
||||||
secrets = {
|
secrets = {
|
||||||
domainName = "fake.liminix.org";
|
domainName = "fake.liminix.org";
|
||||||
firewallRules = { };
|
firewallRules = {};
|
||||||
} // (import ./rotuer-secrets.nix);
|
} // (import ./rotuer-secrets.nix);
|
||||||
|
inherit (pkgs.liminix.services) oneshot bundle;
|
||||||
|
inherit (pkgs) serviceFns;
|
||||||
svc = config.system.service;
|
svc = config.system.service;
|
||||||
wirelessConfig = {
|
wirelessConfig = {
|
||||||
country_code = "GB";
|
country_code = "GB";
|
||||||
|
@ -28,18 +32,21 @@ in rec {
|
||||||
|
|
||||||
imports = [
|
imports = [
|
||||||
"${modulesPath}/profiles/gateway.nix"
|
"${modulesPath}/profiles/gateway.nix"
|
||||||
|
"${modulesPath}/schnapps"
|
||||||
|
"${modulesPath}/outputs/btrfs.nix"
|
||||||
|
"${modulesPath}/outputs/extlinux.nix"
|
||||||
];
|
];
|
||||||
hostname = "rotuer";
|
hostname = "rotuer";
|
||||||
|
rootfsType = "btrfs";
|
||||||
|
rootOptions = "subvol=@";
|
||||||
|
boot.loader.extlinux.enable = true;
|
||||||
|
|
||||||
profile.gateway = {
|
profile.gateway = {
|
||||||
lan = {
|
lan = {
|
||||||
interfaces = with config.hardware.networkInterfaces;
|
interfaces = with config.hardware.networkInterfaces;
|
||||||
[
|
[
|
||||||
# EDIT: these are the interfaces exposed by the gl.inet gl-ar750:
|
|
||||||
# if your device has more or differently named lan interfaces,
|
|
||||||
# specify them here
|
|
||||||
wlan wlan5
|
wlan wlan5
|
||||||
lan
|
lan0 lan1 lan2 lan3 lan4
|
||||||
];
|
];
|
||||||
inherit (secrets.lan) prefix;
|
inherit (secrets.lan) prefix;
|
||||||
address = {
|
address = {
|
||||||
|
@ -53,17 +60,9 @@ in rec {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
wan = {
|
wan = {
|
||||||
# wan interface depends on your upstream - could be dhcp, static
|
|
||||||
# ethernet, a pppoe, ppp over serial, a complicated bonded
|
|
||||||
# failover ... who knows what else?
|
|
||||||
interface = svc.pppoe.build {
|
|
||||||
interface = config.hardware.networkInterfaces.wan;
|
interface = config.hardware.networkInterfaces.wan;
|
||||||
username = secrets.l2tp.name;
|
username = secrets.l2tp.name;
|
||||||
password = secrets.l2tp.password;
|
password = secrets.l2tp.password;
|
||||||
};
|
|
||||||
# once the wan has ipv4 connnectivity, should we run dhcp6
|
|
||||||
# client to potentially get an address range ("prefix
|
|
||||||
# delegation")
|
|
||||||
dhcp6.enable = true;
|
dhcp6.enable = true;
|
||||||
};
|
};
|
||||||
firewall = {
|
firewall = {
|
||||||
|
@ -71,19 +70,15 @@ in rec {
|
||||||
rules = secrets.firewallRules;
|
rules = secrets.firewallRules;
|
||||||
};
|
};
|
||||||
wireless.networks = {
|
wireless.networks = {
|
||||||
# EDIT: if you have more or fewer wireless radios, here is where
|
|
||||||
# you need to say so. hostapd tuning is hardware-specific and
|
|
||||||
# left as an exercise for the reader :-).
|
|
||||||
|
|
||||||
"${secrets.ssid}" = {
|
"${secrets.ssid}" = {
|
||||||
interface = config.hardware.networkInterfaces.wlan;
|
interface = config.hardware.networkInterfaces.wlan;
|
||||||
hw_mode = "g";
|
hw_mode="g";
|
||||||
channel = "2";
|
channel = "2";
|
||||||
ieee80211n = 1;
|
ieee80211n = 1;
|
||||||
} // wirelessConfig;
|
} // wirelessConfig;
|
||||||
"${secrets.ssid}5" = rec {
|
"${secrets.ssid}5" = rec {
|
||||||
interface = config.hardware.networkInterfaces.wlan5;
|
interface = config.hardware.networkInterfaces.wlan5;
|
||||||
hw_mode = "a";
|
hw_mode="a";
|
||||||
channel = 36;
|
channel = 36;
|
||||||
ht_capab = "[HT40+]";
|
ht_capab = "[HT40+]";
|
||||||
vht_oper_chwidth = 1;
|
vht_oper_chwidth = 1;
|
||||||
|
|
|
@ -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,5 +1,6 @@
|
||||||
{ config, pkgs, lim, ... } :
|
{ config, pkgs, lib, lim, ... } :
|
||||||
let
|
let
|
||||||
|
inherit (pkgs) serviceFns;
|
||||||
svc = config.system.service;
|
svc = config.system.service;
|
||||||
|
|
||||||
in rec {
|
in rec {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{ lim, pkgs, config, ...}:
|
{ lib, lim, pkgs, config, ...}:
|
||||||
{
|
{
|
||||||
config = {
|
config = {
|
||||||
kernel.config = {
|
kernel.config = {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{ lim, pkgs, config, ...}:
|
{ lib, lim, pkgs, config, ...}:
|
||||||
{
|
{
|
||||||
config = {
|
config = {
|
||||||
kernel.config = {
|
kernel.config = {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{ config, lim, ...}:
|
{ lib, pkgs, config, lim, ...}:
|
||||||
{
|
{
|
||||||
config = {
|
config = {
|
||||||
kernel.config = {
|
kernel.config = {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{ pkgs, config, ...}:
|
{ lib, pkgs, config, ...}:
|
||||||
{
|
{
|
||||||
imports = [ ./mips.nix ];
|
imports = [ ./mips.nix ];
|
||||||
config = {
|
config = {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{ config, ...}:
|
{ lib, pkgs, config, ...}:
|
||||||
{
|
{
|
||||||
imports = [ ./mips.nix ];
|
imports = [ ./mips.nix ];
|
||||||
config = {
|
config = {
|
||||||
|
|
|
@ -4,8 +4,10 @@
|
||||||
|
|
||||||
{ lib, pkgs, config, ...}:
|
{ lib, pkgs, config, ...}:
|
||||||
let
|
let
|
||||||
inherit (lib) mkOption types;
|
inherit (lib) mkEnableOption mkOption types isDerivation hasAttr ;
|
||||||
inherit (pkgs.pseudofile) dir symlink;
|
inherit (pkgs.pseudofile) dir symlink;
|
||||||
|
inherit (pkgs.liminix.networking) address interface;
|
||||||
|
inherit (pkgs.liminix.services) bundle;
|
||||||
|
|
||||||
type_service = pkgs.liminix.lib.types.service;
|
type_service = pkgs.liminix.lib.types.service;
|
||||||
|
|
||||||
|
@ -54,29 +56,20 @@ in {
|
||||||
boot = {
|
boot = {
|
||||||
commandLine = mkOption {
|
commandLine = mkOption {
|
||||||
type = types.listOf types.nonEmptyStr;
|
type = types.listOf types.nonEmptyStr;
|
||||||
default = [ ];
|
default = [];
|
||||||
description = "Kernel command line";
|
description = "Kernel command line";
|
||||||
};
|
};
|
||||||
commandLineDtbNode = mkOption {
|
commandLineDtbNode = mkOption {
|
||||||
type = types.enum [
|
type = types.enum [ "bootargs" "bootargs-override" ];
|
||||||
"bootargs"
|
|
||||||
"bootargs-override"
|
|
||||||
];
|
|
||||||
default = "bootargs";
|
default = "bootargs";
|
||||||
description = "Kernel command line's devicetree node";
|
description = "Kernel command line's devicetree node";
|
||||||
};
|
};
|
||||||
imageType = mkOption {
|
imageType = mkOption {
|
||||||
type = types.enum [
|
type = types.enum [ "primary" "secondary" ];
|
||||||
"primary"
|
|
||||||
"secondary"
|
|
||||||
];
|
|
||||||
default = "primary";
|
default = "primary";
|
||||||
};
|
};
|
||||||
imageFormat = mkOption {
|
imageFormat = mkOption {
|
||||||
type = types.enum [
|
type = types.enum ["fit" "uimage"];
|
||||||
"fit"
|
|
||||||
"uimage"
|
|
||||||
];
|
|
||||||
default = "uimage";
|
default = "uimage";
|
||||||
};
|
};
|
||||||
tftp = {
|
tftp = {
|
||||||
|
@ -137,11 +130,13 @@ in {
|
||||||
s = pkg (checkTypes parameters
|
s = pkg (checkTypes parameters
|
||||||
(builtins.removeAttrs args ["dependencies"]));
|
(builtins.removeAttrs args ["dependencies"]));
|
||||||
in s.overrideAttrs (o: {
|
in s.overrideAttrs (o: {
|
||||||
dependencies = dependencies ++ o.dependencies;
|
dependencies = (builtins.map (d: d.name) dependencies) ++ o.dependencies;
|
||||||
buildInputs = dependencies ++ o.buildInputs;
|
buildInputs = dependencies ++ o.buildInputs;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
users.root = {
|
users.root = {
|
||||||
uid = 0; gid= 0; gecos = "Root of all evaluation";
|
uid = 0; gid= 0; gecos = "Root of all evaluation";
|
||||||
dir = "/home/root/";
|
dir = "/home/root/";
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
{ lib, pkgs, config, ...}:
|
{ lib, pkgs, config, ...}:
|
||||||
let
|
let
|
||||||
inherit (lib) mkOption types;
|
inherit (lib) mkOption types;
|
||||||
|
inherit (pkgs.liminix.services) oneshot;
|
||||||
inherit (pkgs) liminix;
|
inherit (pkgs) liminix;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
|
@ -22,7 +23,7 @@ in
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
config.system.service.bridge = {
|
config.system.service.bridge = {
|
||||||
primary = config.system.callService ./primary.nix {
|
primary = liminix.callService ./primary.nix {
|
||||||
ifname = mkOption {
|
ifname = mkOption {
|
||||||
type = types.str;
|
type = types.str;
|
||||||
description = "bridge interface name to create";
|
description = "bridge interface name to create";
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
{
|
{
|
||||||
liminix
|
liminix
|
||||||
, ifwait
|
, ifwait
|
||||||
|
, lib
|
||||||
, svc
|
, svc
|
||||||
}:
|
}:
|
||||||
{ members, primary } :
|
{ members, primary } :
|
||||||
|
@ -8,6 +9,7 @@
|
||||||
let
|
let
|
||||||
inherit (liminix.networking) interface;
|
inherit (liminix.networking) interface;
|
||||||
inherit (liminix.services) bundle oneshot;
|
inherit (liminix.services) bundle oneshot;
|
||||||
|
inherit (lib) mkOption types;
|
||||||
addif = member :
|
addif = member :
|
||||||
# how do we get sight of services from here? maybe we need to
|
# how do we get sight of services from here? maybe we need to
|
||||||
# implement ifwait as a regualr derivation instead of a
|
# implement ifwait as a regualr derivation instead of a
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
{
|
{
|
||||||
liminix
|
liminix
|
||||||
|
, ifwait
|
||||||
, lib
|
, lib
|
||||||
}:
|
}:
|
||||||
{ ifname } :
|
{ ifname } :
|
||||||
let
|
let
|
||||||
inherit (liminix.services) oneshot;
|
inherit (liminix.services) bundle oneshot;
|
||||||
|
inherit (lib) mkOption types;
|
||||||
in oneshot rec {
|
in oneshot rec {
|
||||||
name = "${ifname}.link";
|
name = "${ifname}.link";
|
||||||
up = ''
|
up = ''
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
{ lib, pkgs, config, ...}:
|
{ lib, pkgs, config, ...}:
|
||||||
let
|
let
|
||||||
inherit (lib) mkOption types mapAttrsToList;
|
inherit (lib) mkOption mkEnableOption types mapAttrsToList;
|
||||||
inherit (pkgs.pseudofile) dir symlink;
|
inherit (pkgs.pseudofile) dir symlink;
|
||||||
inherit (lib.strings) toUpper;
|
inherit (lib.strings) toUpper;
|
||||||
|
|
||||||
|
@ -85,13 +85,10 @@ in {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
filesystem = dir {
|
filesystem = dir {
|
||||||
bin = dir (
|
bin = dir ({
|
||||||
{
|
|
||||||
busybox = symlink "${busybox}/bin/busybox";
|
busybox = symlink "${busybox}/bin/busybox";
|
||||||
sh = symlink "${busybox}/bin/busybox";
|
sh = symlink "${busybox}/bin/busybox";
|
||||||
}
|
} // makeLinks);
|
||||||
// makeLinks
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
{ config, pkgs, lib, ... }:
|
||||||
|
let
|
||||||
|
inherit (pkgs.liminix.services) oneshot;
|
||||||
|
svc = config.system.service;
|
||||||
|
in {
|
||||||
|
config = {
|
||||||
|
kernel.config = {
|
||||||
|
USB_NET_HUAWEI_CDC_NCM = "y";
|
||||||
|
USB_USBNET = "y";
|
||||||
|
USB_SERIAL = "y";
|
||||||
|
USB_SERIAL_OPTION = "y";
|
||||||
|
};
|
||||||
|
|
||||||
|
# https://www.0xf8.org/2017/01/flashing-a-huawei-e3372h-4g-lte-stick-from-hilink-to-stick-mode/
|
||||||
|
|
||||||
|
services.wwan = let
|
||||||
|
chat = lib.escapeShellArgs [
|
||||||
|
# Your usb modem thing might present as a tty that you run PPP
|
||||||
|
# over, or as a network device ("ndis" or "ncm"). The latter
|
||||||
|
# kind is to be preferred, at least in principle, because it's
|
||||||
|
# faster. This initialization sequence works for the Huawei
|
||||||
|
# E3372, and took much swearing: the error messages are *awful*
|
||||||
|
"" "AT"
|
||||||
|
"OK" "ATZ"
|
||||||
|
# create PDP context
|
||||||
|
"OK" "AT+CGDCONT=1,\"IP\",\"data.uk\""
|
||||||
|
# activate PDP context
|
||||||
|
"OK" "AT+CGACT=1,1"
|
||||||
|
# setup username and password per requirements of sim provider.
|
||||||
|
# (caret is special to chat, so needs escaping in AT commands)
|
||||||
|
"OK" "AT\\^AUTHDATA=1,2,\"1p\",\"one2one\",\"user\""
|
||||||
|
# start the thing (I am choosing to read this as "NDIS DialUP")
|
||||||
|
"OK" "AT\\^NDISDUP=1,1"
|
||||||
|
];
|
||||||
|
modemConfig = oneshot {
|
||||||
|
name = "modem-configure";
|
||||||
|
# this is currently only going to work if there is one
|
||||||
|
# modem only plugged in, it is plugged in already at boot,
|
||||||
|
# and nothing else is providing a USB tty.
|
||||||
|
# https://stackoverflow.com/questions/5477882/how-to-i-detect-whether-a-tty-belonging-to-a-gsm-3g-modem-is-a-data-or-control-p
|
||||||
|
up = ''
|
||||||
|
sleep 2
|
||||||
|
${pkgs.usb-modeswitch}/bin/usb_modeswitch -v 12d1 -p 14fe --huawei-new-mode
|
||||||
|
sleep 5
|
||||||
|
${pkgs.ppp}/bin/chat -s -v ${chat} 0<>/dev/ttyUSB0 1>&0
|
||||||
|
'';
|
||||||
|
down = "chat -v '' ATZ OK </dev/ttyUSB0 >&0";
|
||||||
|
};
|
||||||
|
in svc.network.link.build {
|
||||||
|
ifname = "wwan0";
|
||||||
|
dependencies = [ modemConfig ];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
|
@ -27,6 +27,6 @@
|
||||||
dir (svc.open state-directory)]
|
dir (svc.open state-directory)]
|
||||||
(accumulate [addresses []
|
(accumulate [addresses []
|
||||||
v (dir:events)]
|
v (dir:events)]
|
||||||
(update-prefixes lan-device addresses (or (v:output "prefix") []) system))))
|
(update-prefixes lan-device addresses (v:output "prefix") system))))
|
||||||
|
|
||||||
{ : changes : run }
|
{ : changes : run }
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
writeFennel
|
writeFennel
|
||||||
, linotify
|
, linotify
|
||||||
, anoia
|
, anoia
|
||||||
|
, lua
|
||||||
, lualinux
|
, lualinux
|
||||||
}:
|
}:
|
||||||
writeFennel "acquire-delegated-prefix" {
|
writeFennel "acquire-delegated-prefix" {
|
||||||
|
|
|
@ -27,6 +27,6 @@
|
||||||
dir (svc.open state-directory)]
|
dir (svc.open state-directory)]
|
||||||
(accumulate [addresses []
|
(accumulate [addresses []
|
||||||
v (dir:events)]
|
v (dir:events)]
|
||||||
(update-addresses wan-device addresses (or (v:output "address") []) system))))
|
(update-addresses wan-device addresses (v:output "address") system))))
|
||||||
|
|
||||||
{ : update-addresses : deletions : run }
|
{ : update-addresses : deletions : run }
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
, linotify
|
, linotify
|
||||||
, anoia
|
, anoia
|
||||||
, lualinux
|
, lualinux
|
||||||
|
, lua
|
||||||
}:
|
}:
|
||||||
writeFennel "acquire-wan-address" {
|
writeFennel "acquire-wan-address" {
|
||||||
packages = [ linotify anoia lualinux ];
|
packages = [ linotify anoia lualinux ];
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
{
|
{
|
||||||
liminix
|
liminix
|
||||||
|
, lib
|
||||||
, callPackage
|
, callPackage
|
||||||
}:
|
}:
|
||||||
{ client, interface } :
|
{ client, interface } :
|
||||||
let
|
let
|
||||||
inherit (liminix.services) longrun;
|
inherit (liminix.services) longrun;
|
||||||
|
inherit (lib) mkOption types;
|
||||||
name = "dhcp6c.addr.${client.name}.${interface.name}";
|
name = "dhcp6c.addr.${client.name}.${interface.name}";
|
||||||
script = callPackage ./acquire-wan-address.nix { };
|
script = callPackage ./acquire-wan-address.nix { };
|
||||||
in longrun {
|
in longrun {
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
{
|
{
|
||||||
liminix
|
liminix
|
||||||
|
, lib
|
||||||
, odhcp6c
|
, odhcp6c
|
||||||
, odhcp-script
|
, odhcp-script
|
||||||
}:
|
}:
|
||||||
{ interface } :
|
{ interface } :
|
||||||
let
|
let
|
||||||
inherit (liminix.services) longrun;
|
inherit (liminix.services) longrun;
|
||||||
|
inherit (lib) mkOption types;
|
||||||
name = "dhcp6c.${interface.name}";
|
name = "dhcp6c.${interface.name}";
|
||||||
in longrun {
|
in longrun {
|
||||||
inherit name;
|
inherit name;
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
{ lib, pkgs, config, ...}:
|
{ lib, pkgs, config, ...}:
|
||||||
let
|
let
|
||||||
inherit (lib) mkOption types;
|
inherit (lib) mkOption types;
|
||||||
|
inherit (pkgs.liminix.services) oneshot;
|
||||||
inherit (pkgs) liminix;
|
inherit (pkgs) liminix;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
|
@ -23,13 +24,13 @@ in
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
config.system.service.dhcp6c = {
|
config.system.service.dhcp6c = {
|
||||||
client = config.system.callService ./client.nix {
|
client = liminix.callService ./client.nix {
|
||||||
interface = mkOption {
|
interface = mkOption {
|
||||||
type = liminix.lib.types.interface;
|
type = liminix.lib.types.interface;
|
||||||
description = "interface (usually WAN) to query for DHCP6";
|
description = "interface (usually WAN) to query for DHCP6";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
address = config.system.callService ./address.nix {
|
address = liminix.callService ./address.nix {
|
||||||
client = mkOption {
|
client = mkOption {
|
||||||
type = types.anything; # liminix.lib.types.service;
|
type = types.anything; # liminix.lib.types.service;
|
||||||
};
|
};
|
||||||
|
@ -38,7 +39,7 @@ in
|
||||||
description = "interface to assign the address to";
|
description = "interface to assign the address to";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
prefix = config.system.callService ./prefix.nix {
|
prefix = liminix.callService ./prefix.nix {
|
||||||
client = mkOption {
|
client = mkOption {
|
||||||
type = types.anything; # liminix.lib.types.service;
|
type = types.anything; # liminix.lib.types.service;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
{
|
{
|
||||||
liminix
|
liminix
|
||||||
|
, lib
|
||||||
, callPackage
|
, callPackage
|
||||||
}:
|
}:
|
||||||
{ client, interface } :
|
{ client, interface } :
|
||||||
let
|
let
|
||||||
inherit (liminix.services) longrun;
|
inherit (liminix.services) longrun;
|
||||||
|
inherit (lib) mkOption types;
|
||||||
name = "dhcp6c.prefix.${client.name}.${interface.name}";
|
name = "dhcp6c.prefix.${client.name}.${interface.name}";
|
||||||
script = callPackage ./acquire-delegated-prefix.nix { };
|
script = callPackage ./acquire-delegated-prefix.nix { };
|
||||||
in longrun {
|
in longrun {
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
liminix
|
liminix
|
||||||
, dnsmasq
|
, dnsmasqSmall
|
||||||
, serviceFns
|
, serviceFns
|
||||||
, lib
|
, lib
|
||||||
}:
|
}:
|
||||||
|
@ -18,7 +18,7 @@ let
|
||||||
name = "${interface.name}.dnsmasq";
|
name = "${interface.name}.dnsmasq";
|
||||||
inherit (liminix.services) longrun;
|
inherit (liminix.services) longrun;
|
||||||
inherit (lib) concatStrings concatStringsSep mapAttrsToList;
|
inherit (lib) concatStrings concatStringsSep mapAttrsToList;
|
||||||
hostOpt = name : { mac, v4, v6, leasetime }:
|
hostOpt = name : { mac, v4, v6, leasetime } @ attrs:
|
||||||
let v6s = concatStrings (map (a : ",[${a}]") v6);
|
let v6s = concatStrings (map (a : ",[${a}]") v6);
|
||||||
in "--dhcp-host=${mac},${v4}${v6s},${name},${builtins.toString leasetime}";
|
in "--dhcp-host=${mac},${v4}${v6s},${name},${builtins.toString leasetime}";
|
||||||
in
|
in
|
||||||
|
@ -26,7 +26,8 @@ longrun {
|
||||||
inherit name;
|
inherit name;
|
||||||
dependencies = [ interface ];
|
dependencies = [ interface ];
|
||||||
run = ''
|
run = ''
|
||||||
${dnsmasq}/bin/dnsmasq \
|
. ${serviceFns}
|
||||||
|
${dnsmasqSmall}/bin/dnsmasq \
|
||||||
--user=${user} \
|
--user=${user} \
|
||||||
--domain=${domain} \
|
--domain=${domain} \
|
||||||
--group=${group} \
|
--group=${group} \
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
let
|
let
|
||||||
inherit (lib) mkOption types;
|
inherit (lib) mkOption types;
|
||||||
inherit (pkgs) liminix;
|
inherit (pkgs) liminix;
|
||||||
|
inherit (pkgs.liminix.services) oneshot;
|
||||||
|
|
||||||
kmodules = pkgs.kmodloader.override {
|
kmodules = pkgs.kmodloader.override {
|
||||||
inherit (config.system.outputs) kernel;
|
inherit (config.system.outputs) kernel;
|
||||||
|
@ -54,7 +55,7 @@ in
|
||||||
};
|
};
|
||||||
config = {
|
config = {
|
||||||
system.service.firewall =
|
system.service.firewall =
|
||||||
let svc = config.system.callService ./service.nix {
|
let svc = liminix.callService ./service.nix {
|
||||||
extraRules = mkOption {
|
extraRules = mkOption {
|
||||||
type = types.attrsOf types.attrs;
|
type = types.attrsOf types.attrs;
|
||||||
description = "firewall ruleset";
|
description = "firewall ruleset";
|
||||||
|
|
|
@ -7,6 +7,8 @@
|
||||||
{ rules, extraRules }:
|
{ rules, extraRules }:
|
||||||
let
|
let
|
||||||
inherit (liminix.services) oneshot;
|
inherit (liminix.services) oneshot;
|
||||||
|
inherit (liminix.lib) typeChecked;
|
||||||
|
inherit (lib) mkOption types;
|
||||||
script = firewallgen "firewall.nft" (lib.recursiveUpdate rules extraRules);
|
script = firewallgen "firewall.nft" (lib.recursiveUpdate rules extraRules);
|
||||||
in oneshot {
|
in oneshot {
|
||||||
name = "firewall";
|
name = "firewall";
|
||||||
|
|
|
@ -5,13 +5,14 @@
|
||||||
## you want to run on it, and would usually be set in the "device" file:
|
## you want to run on it, and would usually be set in the "device" file:
|
||||||
## :file:`devices/manuf-model/default.nix`
|
## :file:`devices/manuf-model/default.nix`
|
||||||
|
|
||||||
{ lib, ... }:
|
|
||||||
|
{ lib, pkgs, config, ...}:
|
||||||
let
|
let
|
||||||
inherit (lib) mkOption types;
|
inherit (lib) mkEnableOption mkOption types isDerivation hasAttr ;
|
||||||
in
|
in {
|
||||||
{
|
|
||||||
options = {
|
options = {
|
||||||
boot = { };
|
boot = {
|
||||||
|
};
|
||||||
hardware = {
|
hardware = {
|
||||||
dts = {
|
dts = {
|
||||||
src = mkOption {
|
src = mkOption {
|
||||||
|
@ -25,7 +26,7 @@ in
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
includes = mkOption {
|
includes = mkOption {
|
||||||
default = [ ];
|
default = [];
|
||||||
description = "List of directories to search for DTS includes (.dtsi files)";
|
description = "List of directories to search for DTS includes (.dtsi files)";
|
||||||
type = types.listOf types.path;
|
type = types.listOf types.path;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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";
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ let
|
||||||
in longrun {
|
in longrun {
|
||||||
name = "ifwait.${interface.name}";
|
name = "ifwait.${interface.name}";
|
||||||
buildInputs = [ service ];
|
buildInputs = [ service ];
|
||||||
restart-on-upgrade = true;
|
isTrigger = true;
|
||||||
run = ''
|
run = ''
|
||||||
${ifwait}/bin/ifwait -s ${service.name} $(output ${interface} ifname) ${state}
|
${ifwait}/bin/ifwait -s ${service.name} $(output ${interface} ifname) ${state}
|
||||||
'';
|
'';
|
||||||
|
|
|
@ -5,9 +5,14 @@
|
||||||
|
|
||||||
{ lib, pkgs, config, ...}:
|
{ lib, pkgs, config, ...}:
|
||||||
let
|
let
|
||||||
inherit (lib) mkOption types ;
|
inherit (lib) mkEnableOption mkOption types isDerivation hasAttr ;
|
||||||
|
inherit (pkgs.pseudofile) dir symlink;
|
||||||
|
inherit (pkgs.liminix.networking) address interface;
|
||||||
|
inherit (pkgs.liminix.services) bundle;
|
||||||
inherit (pkgs) liminix;
|
inherit (pkgs) liminix;
|
||||||
|
|
||||||
|
type_service = pkgs.liminix.lib.types.service;
|
||||||
|
|
||||||
mergeConditionals = conf : conditions :
|
mergeConditionals = conf : conditions :
|
||||||
# for each key in conditions, if it is present in conf
|
# for each key in conditions, if it is present in conf
|
||||||
# then merge the associated value into conf
|
# then merge the associated value into conf
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{ config, pkgs, ...} :
|
{ config, pkgs, lib, ...} :
|
||||||
let inherit (pkgs.liminix.services) oneshot longrun;
|
let inherit (pkgs.liminix.services) oneshot longrun;
|
||||||
in {
|
in {
|
||||||
config = {
|
config = {
|
||||||
|
@ -11,21 +11,16 @@ in {
|
||||||
devout = longrun {
|
devout = longrun {
|
||||||
name = "devout";
|
name = "devout";
|
||||||
notification-fd = 10;
|
notification-fd = 10;
|
||||||
timeout-up = 60 * 1000;
|
run = "${pkgs.devout}/bin/devout /run/devout.sock 4";
|
||||||
run = "exec ${pkgs.devout}/bin/devout /run/devout.sock 4";
|
|
||||||
dependencies = [ mdevd ];
|
|
||||||
};
|
};
|
||||||
coldplug = oneshot {
|
coldplug = oneshot {
|
||||||
name = "coldplug";
|
name ="coldplug";
|
||||||
# would love to know what mdevd-coldplug/udevadm trigger does
|
# would love to know what mdevd-coldplug/udevadm trigger does
|
||||||
# that this doesn't
|
# that this doesn't
|
||||||
up = ''
|
up = ''
|
||||||
for i in $(find /sys -name uevent); do ( echo change > $i ) ; done
|
for i in $(find /sys -name uevent); do ( echo change > $i ) ; done
|
||||||
'';
|
'';
|
||||||
dependencies = [
|
dependencies = [devout mdevd];
|
||||||
devout
|
|
||||||
mdevd
|
|
||||||
];
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,6 +7,11 @@
|
||||||
let
|
let
|
||||||
inherit (lib) mkOption types;
|
inherit (lib) mkOption types;
|
||||||
inherit (pkgs) liminix;
|
inherit (pkgs) liminix;
|
||||||
|
mkBoolOption = description : mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
inherit description;
|
||||||
|
default = true;
|
||||||
|
};
|
||||||
|
|
||||||
in {
|
in {
|
||||||
options = {
|
options = {
|
||||||
|
@ -14,9 +19,9 @@ in {
|
||||||
type = liminix.lib.types.serviceDefn;
|
type = liminix.lib.types.serviceDefn;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
imports = [ ../mdevd.nix ../uevent-rule ];
|
imports = [ ../mdevd.nix ];
|
||||||
config.system.service.mount =
|
config.system.service.mount =
|
||||||
let svc = config.system.callService ./service.nix {
|
let svc = liminix.callService ./service.nix {
|
||||||
partlabel = mkOption {
|
partlabel = mkOption {
|
||||||
type = types.str;
|
type = types.str;
|
||||||
example = "my-usb-stick";
|
example = "my-usb-stick";
|
||||||
|
|
|
@ -1,27 +1,26 @@
|
||||||
{
|
{
|
||||||
liminix
|
liminix
|
||||||
|
, uevent-watch
|
||||||
, lib
|
, lib
|
||||||
, svc
|
|
||||||
}:
|
}:
|
||||||
{ partlabel, mountpoint, options, fstype }:
|
{ partlabel, mountpoint, options, fstype }:
|
||||||
let
|
let
|
||||||
inherit (liminix.services) oneshot;
|
inherit (liminix.services) longrun oneshot;
|
||||||
device = "/dev/disk/by-partlabel/${partlabel}";
|
device = "/dev/disk/by-partlabel/${partlabel}";
|
||||||
name = "mount.${lib.strings.sanitizeDerivationName (lib.escapeURL mountpoint)}";
|
|
||||||
options_string =
|
options_string =
|
||||||
if options == [] then "" else "-o ${lib.concatStringsSep "," options}";
|
if options == [] then "" else "-o ${lib.concatStringsSep "," options}";
|
||||||
controller = svc.uevent-rule.build {
|
mount_service = oneshot {
|
||||||
serviceName = name;
|
name = "mount.${lib.escapeURL mountpoint}";
|
||||||
symlink = device;
|
|
||||||
terms = {
|
|
||||||
partname = partlabel;
|
|
||||||
devtype = "partition";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
in oneshot {
|
|
||||||
inherit name;
|
|
||||||
timeout-up = 3600;
|
timeout-up = 3600;
|
||||||
up = "mount -t ${fstype} ${options_string} ${device} ${mountpoint}";
|
up = "mount -t ${fstype} ${options_string} ${device} ${mountpoint}";
|
||||||
down = "umount ${mountpoint}";
|
down = "umount ${mountpoint}";
|
||||||
inherit controller;
|
};
|
||||||
|
in longrun {
|
||||||
|
name = "watch-mount.${lib.strings.sanitizeDerivationName mountpoint}";
|
||||||
|
isTrigger = true;
|
||||||
|
buildInputs = [ mount_service ];
|
||||||
|
|
||||||
|
run = ''
|
||||||
|
${uevent-watch}/bin/uevent-watch -s ${mount_service.name} -n ${device} partname=${partlabel} devtype=partition
|
||||||
|
'';
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -64,7 +64,7 @@ in {
|
||||||
services.loopback = config.hardware.networkInterfaces.lo;
|
services.loopback = config.hardware.networkInterfaces.lo;
|
||||||
|
|
||||||
system.service.network = {
|
system.service.network = {
|
||||||
link = config.system.callService ./link.nix {
|
link = liminix.callService ./link.nix {
|
||||||
ifname = mkOption {
|
ifname = mkOption {
|
||||||
type = types.str;
|
type = types.str;
|
||||||
example = "eth0";
|
example = "eth0";
|
||||||
|
@ -89,7 +89,7 @@ in {
|
||||||
example = 1480;
|
example = 1480;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
address = config.system.callService ./address.nix {
|
address = liminix.callService ./address.nix {
|
||||||
interface = mkOption {
|
interface = mkOption {
|
||||||
type = liminix.lib.types.service;
|
type = liminix.lib.types.service;
|
||||||
};
|
};
|
||||||
|
@ -104,7 +104,7 @@ in {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
route = config.system.callService ./route.nix {
|
route = liminix.callService ./route.nix {
|
||||||
interface = mkOption {
|
interface = mkOption {
|
||||||
type = types.nullOr liminix.lib.types.interface;
|
type = types.nullOr liminix.lib.types.interface;
|
||||||
default = null;
|
default = null;
|
||||||
|
@ -125,7 +125,7 @@ in {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
forward = config.system.callService ./forward.nix {
|
forward = liminix.callService ./forward.nix {
|
||||||
enableIPv4 = mkOption {
|
enableIPv4 = mkOption {
|
||||||
type = types.bool;
|
type = types.bool;
|
||||||
default = true;
|
default = true;
|
||||||
|
@ -136,7 +136,7 @@ in {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
dhcp.client = config.system.callService ./dhcpc.nix {
|
dhcp.client = liminix.callService ./dhcpc.nix {
|
||||||
interface = mkOption {
|
interface = mkOption {
|
||||||
type = liminix.lib.types.service;
|
type = liminix.lib.types.service;
|
||||||
};
|
};
|
||||||
|
|
|
@ -17,7 +17,7 @@ let
|
||||||
ip address replace $ip/$mask dev $interface
|
ip address replace $ip/$mask dev $interface
|
||||||
(in_outputs ${name}
|
(in_outputs ${name}
|
||||||
for i in lease mask ip router siaddr dns serverid subnet opt53 interface ; do
|
for i in lease mask ip router siaddr dns serverid subnet opt53 interface ; do
|
||||||
(printenv $i || true) > $i
|
printenv $i > $i
|
||||||
done)
|
done)
|
||||||
}
|
}
|
||||||
case $action in
|
case $action in
|
||||||
|
@ -40,7 +40,7 @@ let
|
||||||
'';
|
'';
|
||||||
in longrun {
|
in longrun {
|
||||||
inherit name;
|
inherit name;
|
||||||
run = "exec /bin/udhcpc -f -i $(output ${interface} ifname) -x hostname:$(cat /proc/sys/kernel/hostname) -s ${script}";
|
run = "/bin/udhcpc -f -i $(output ${interface} ifname) -x hostname:$(cat /proc/sys/kernel/hostname) -s ${script}";
|
||||||
notification-fd = 10;
|
notification-fd = 10;
|
||||||
dependencies = [ interface ];
|
dependencies = [ interface ];
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
{
|
{
|
||||||
liminix
|
liminix
|
||||||
|
, ifwait
|
||||||
|
, serviceFns
|
||||||
, lib
|
, lib
|
||||||
}:
|
}:
|
||||||
{ enableIPv4, enableIPv6 }:
|
{ enableIPv4, enableIPv6 }:
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
{
|
{
|
||||||
liminix
|
liminix
|
||||||
|
, ifwait
|
||||||
|
, serviceFns
|
||||||
, lib
|
, lib
|
||||||
}:
|
}:
|
||||||
{
|
{
|
||||||
|
@ -9,7 +11,8 @@
|
||||||
# if devpath is supplied, we rename the interface at that
|
# if devpath is supplied, we rename the interface at that
|
||||||
# path to have the specified name.
|
# path to have the specified name.
|
||||||
let
|
let
|
||||||
inherit (liminix.services) oneshot;
|
inherit (liminix.services) longrun oneshot;
|
||||||
|
inherit (lib) concatStringsSep;
|
||||||
name = "${ifname}.link";
|
name = "${ifname}.link";
|
||||||
rename = if devpath != null
|
rename = if devpath != null
|
||||||
then ''
|
then ''
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
{
|
{
|
||||||
liminix
|
liminix
|
||||||
|
, ifwait
|
||||||
|
, serviceFns
|
||||||
, lib
|
, lib
|
||||||
}:
|
}:
|
||||||
{ target, via, interface ? null, metric }:
|
{ target, via, interface ? null, metric }:
|
||||||
let
|
let
|
||||||
inherit (liminix.services) oneshot;
|
inherit (liminix.services) oneshot;
|
||||||
with_dev = if interface != null then "dev $(output ${interface} ifname)" else "";
|
with_dev = if interface != null then "dev $(output ${interface} ifname)" else "";
|
||||||
target_hash = builtins.substring 0 12 (builtins.hashString "sha256" target);
|
|
||||||
via_hash = builtins.substring 0 12 (builtins.hashString "sha256" via);
|
|
||||||
in oneshot {
|
in oneshot {
|
||||||
name = "route-${target_hash}-${builtins.substring 0 12 (builtins.hashString "sha256" "${via_hash}-${if interface!=null then interface.name else ""}")}";
|
name = "route-${target}-${builtins.substring 0 12 (builtins.hashString "sha256" "${via}-${if interface!=null then interface.name else ""}")}";
|
||||||
up = ''
|
up = ''
|
||||||
ip route add ${target} via ${via} metric ${toString metric} ${with_dev}
|
ip route add ${target} via ${via} metric ${toString metric} ${with_dev}
|
||||||
'';
|
'';
|
||||||
|
|
|
@ -18,7 +18,7 @@ in {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
config = {
|
config = {
|
||||||
system.service.ntp = config.system.callService ./service.nix {
|
system.service.ntp = liminix.callService ./service.nix {
|
||||||
user = mkOption {
|
user = mkOption {
|
||||||
type = types.str;
|
type = types.str;
|
||||||
default = "ntp";
|
default = "ntp";
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
{
|
{
|
||||||
liminix
|
liminix
|
||||||
, chrony
|
, chrony
|
||||||
|
, serviceFns
|
||||||
, lib
|
, lib
|
||||||
, writeText
|
, writeText
|
||||||
}:
|
}:
|
||||||
|
@ -8,6 +9,10 @@ params:
|
||||||
let
|
let
|
||||||
inherit (liminix.services) longrun;
|
inherit (liminix.services) longrun;
|
||||||
inherit (lib) concatStringsSep mapAttrsToList;
|
inherit (lib) concatStringsSep mapAttrsToList;
|
||||||
|
inherit (liminix.lib) typeChecked;
|
||||||
|
inherit (lib) mkOption types;
|
||||||
|
|
||||||
|
serverOpts = types.listOf types.str;
|
||||||
configFile = p:
|
configFile = p:
|
||||||
(mapAttrsToList (name: opts: "server ${name} ${concatStringsSep "" opts}")
|
(mapAttrsToList (name: opts: "server ${name} ${concatStringsSep "" opts}")
|
||||||
p.servers)
|
p.servers)
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
config,
|
config
|
||||||
pkgs,
|
, pkgs
|
||||||
lib,
|
, lib
|
||||||
...
|
, ...
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
inherit (lib) mkOption types concatStringsSep;
|
inherit (lib) mkOption types concatStringsSep;
|
||||||
inherit (pkgs) liminix writeText;
|
inherit (pkgs) liminix callPackage writeText;
|
||||||
o = config.system.outputs;
|
o = config.system.outputs;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
, ...
|
, ...
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
inherit (lib) mkIf;
|
inherit (lib) mkIf mkOption types;
|
||||||
o = config.system.outputs;
|
o = config.system.outputs;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
, ...
|
, ...
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
inherit (lib) mkIf;
|
inherit (lib) mkIf mkOption types;
|
||||||
o = config.system.outputs;
|
o = config.system.outputs;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
inherit (lib) mkEnableOption mkOption mkIf types;
|
inherit (lib) mkEnableOption mkOption mkIf types;
|
||||||
inherit (pkgs) runCommand;
|
inherit (pkgs) runCommand callPackage writeText;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
options = {
|
options = {
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
, ...
|
, ...
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
inherit (lib) mkIf;
|
inherit (lib) mkIf mkOption types;
|
||||||
o = config.system.outputs;
|
o = config.system.outputs;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
|
|
|
@ -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" {
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
, ...
|
, ...
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
inherit (lib) mkOption types;
|
inherit (lib) mkOption types concatStringsSep;
|
||||||
o = config.system.outputs;
|
o = config.system.outputs;
|
||||||
phram_address = lib.toHexString (config.hardware.ram.startAddress + 256 * 1024 * 1024);
|
phram_address = lib.toHexString (config.hardware.ram.startAddress + 256 * 1024 * 1024);
|
||||||
in {
|
in {
|
||||||
|
|
|
@ -58,6 +58,7 @@ in {
|
||||||
system.outputs = rec {
|
system.outputs = rec {
|
||||||
tftpboot =
|
tftpboot =
|
||||||
let
|
let
|
||||||
|
inherit (pkgs.lib.trivial) toHexString;
|
||||||
o = config.system.outputs;
|
o = config.system.outputs;
|
||||||
image = let choices = {
|
image = let choices = {
|
||||||
uimage = o.uimage;
|
uimage = o.uimage;
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
, ...
|
, ...
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
inherit (lib) mkOption types;
|
inherit (lib) mkOption types concatStringsSep;
|
||||||
o = config.system.outputs;
|
o = config.system.outputs;
|
||||||
cfg = config.tplink-safeloader;
|
cfg = config.tplink-safeloader;
|
||||||
in {
|
in {
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
, ...
|
, ...
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
inherit (lib) mkIf mkOption types;
|
inherit (lib) mkIf mkEnableOption mkOption types concatStringsSep;
|
||||||
cfg = config.boot.tftp;
|
cfg = config.boot.tftp;
|
||||||
instructions = pkgs.writeText "env.scr" ''
|
instructions = pkgs.writeText "env.scr" ''
|
||||||
setenv serverip ${cfg.serverip}
|
setenv serverip ${cfg.serverip}
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
, ...
|
, ...
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
|
inherit (pkgs) liminix;
|
||||||
inherit (lib) mkIf mkOption types concatStringsSep optionalString;
|
inherit (lib) mkIf mkOption types concatStringsSep optionalString;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
, ...
|
, ...
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
inherit (lib) mkIf mkOption types;
|
inherit (lib) mkIf mkEnableOption mkOption types concatStringsSep;
|
||||||
models = "6b e1 6f e1 ff ff ff ff ff ff";
|
models = "6b e1 6f e1 ff ff ff ff ff ff";
|
||||||
in {
|
in {
|
||||||
options.system.outputs = {
|
options.system.outputs = {
|
||||||
|
|
|
@ -1,83 +0,0 @@
|
||||||
{ writeAshScript, liminix, svc, lib, serviceFns, output-template }:
|
|
||||||
{
|
|
||||||
command,
|
|
||||||
name,
|
|
||||||
debug
|
|
||||||
, username,
|
|
||||||
password,
|
|
||||||
lcpEcho,
|
|
||||||
ppp-options,
|
|
||||||
dependencies ? []
|
|
||||||
} :
|
|
||||||
let
|
|
||||||
inherit (lib) optional optionals escapeShellArgs concatStringsSep;
|
|
||||||
inherit (liminix.services) longrun;
|
|
||||||
inherit (builtins) toJSON toString typeOf;
|
|
||||||
|
|
||||||
ip-up = writeAshScript "ip-up" {} ''
|
|
||||||
. ${serviceFns}
|
|
||||||
(in_outputs ${name}
|
|
||||||
echo $1 > ifname
|
|
||||||
echo $2 > tty
|
|
||||||
echo $3 > speed
|
|
||||||
echo $4 > address
|
|
||||||
echo $5 > peer-address
|
|
||||||
echo $DNS1 > ns1
|
|
||||||
echo $DNS2 > ns2
|
|
||||||
)
|
|
||||||
echo >/proc/self/fd/10
|
|
||||||
'';
|
|
||||||
ip6-up = writeAshScript "ip6-up" {} ''
|
|
||||||
. ${serviceFns}
|
|
||||||
(in_outputs ${name}
|
|
||||||
echo $4 > ipv6-address
|
|
||||||
echo $5 > ipv6-peer-address
|
|
||||||
)
|
|
||||||
echo >/proc/self/fd/10
|
|
||||||
'';
|
|
||||||
literal_or_output =
|
|
||||||
let v = o: ({
|
|
||||||
string = toJSON;
|
|
||||||
int = toJSON;
|
|
||||||
lambda = (o: "output(${toJSON (o "service")}, ${toJSON (o "path")})");
|
|
||||||
}.${typeOf o}) o;
|
|
||||||
in o: "{{ ${v o} }}";
|
|
||||||
|
|
||||||
ppp-options' =
|
|
||||||
["+ipv6" "noauth"]
|
|
||||||
++ optional debug "debug"
|
|
||||||
++ optionals (username != null) ["name" (literal_or_output username)]
|
|
||||||
++ optionals (password != null) ["password" (literal_or_output password)]
|
|
||||||
++ optional lcpEcho.adaptive "lcp-echo-adaptive"
|
|
||||||
++ optionals (lcpEcho.interval != null)
|
|
||||||
["lcp-echo-interval" (toString lcpEcho.interval)]
|
|
||||||
++ optionals (lcpEcho.failure != null)
|
|
||||||
["lcp-echo-failure" (toString lcpEcho.failure)]
|
|
||||||
++ ppp-options
|
|
||||||
++ ["ip-up-script" ip-up
|
|
||||||
"ipv6-up-script" ip6-up
|
|
||||||
"ipparam" name
|
|
||||||
"nodetach"
|
|
||||||
"usepeerdns"
|
|
||||||
"nodefaultroute"
|
|
||||||
"logfd" "2"
|
|
||||||
];
|
|
||||||
service = longrun {
|
|
||||||
inherit name;
|
|
||||||
run = ''
|
|
||||||
mkdir -p /run/${name}
|
|
||||||
chmod 0700 /run/${name}
|
|
||||||
in_outputs ${name}
|
|
||||||
echo ${escapeShellArgs ppp-options'} | ${output-template}/bin/output-template '{{' '}}' > /run/${name}/ppp-options
|
|
||||||
${command}
|
|
||||||
'';
|
|
||||||
notification-fd = 10;
|
|
||||||
timeout-up = if lcpEcho.failure != null
|
|
||||||
then (10 + lcpEcho.failure * lcpEcho.interval) * 1000
|
|
||||||
else 60 * 1000;
|
|
||||||
inherit dependencies;
|
|
||||||
};
|
|
||||||
in svc.secrets.subscriber.build {
|
|
||||||
watch = lib.filter (n: typeOf n=="lambda") [ username password ];
|
|
||||||
inherit service;
|
|
||||||
}
|
|
|
@ -1,31 +1,18 @@
|
||||||
## PPP
|
## PPP
|
||||||
## ===
|
## ===
|
||||||
##
|
##
|
||||||
## ``ppoe`` (PPP over Ethernet) provides a service to address the case
|
## A PPPoE (PPP over Ethernet) configuration to address the case where
|
||||||
## where your Liminix device is connected to an upstream network using
|
## your Liminix device is connected to an upstream network using
|
||||||
## PPPoE. This is typical for UK broadband connections where the
|
## PPPoE. This is typical for UK broadband connections where the
|
||||||
## physical connection is made by OpenReach ("Fibre To The X") and
|
## physical connection is made by OpenReach ("Fibre To The X") and
|
||||||
## common in some other localities as well: check with your ISP if this is
|
## common in some other localities as well: ask your ISP if this is
|
||||||
## you.
|
## you.
|
||||||
##
|
|
||||||
## ``l2tp`` (Layer 2 Tunelling Protocol) provides a service that
|
|
||||||
## tunnels PPP over the Internet. This may be used by some ISPs in
|
|
||||||
## conjunction with a DHCP uplink, or other more creative forms of
|
|
||||||
## network connection
|
|
||||||
|
|
||||||
|
|
||||||
{ lib, pkgs, config, ...}:
|
{ lib, pkgs, config, ...}:
|
||||||
let
|
let
|
||||||
inherit (lib) mkOption types;
|
inherit (lib) mkOption types;
|
||||||
inherit (pkgs) liminix;
|
inherit (pkgs) liminix;
|
||||||
mkStringOption =
|
|
||||||
description: mkOption {
|
|
||||||
type = types.nullOr types.str;
|
|
||||||
default = null;
|
|
||||||
inherit description;
|
|
||||||
};
|
|
||||||
in {
|
in {
|
||||||
imports = [ ../secrets ];
|
|
||||||
options = {
|
options = {
|
||||||
system.service.pppoe = mkOption {
|
system.service.pppoe = mkOption {
|
||||||
type = liminix.lib.types.serviceDefn;
|
type = liminix.lib.types.serviceDefn;
|
||||||
|
@ -35,89 +22,23 @@ in {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
config = {
|
config = {
|
||||||
system.service.pppoe = config.system.callService ./pppoe.nix {
|
system.service.pppoe = pkgs.liminix.callService ./pppoe.nix {
|
||||||
interface = mkOption {
|
interface = mkOption {
|
||||||
type = liminix.lib.types.service;
|
type = liminix.lib.types.service;
|
||||||
description = "ethernet interface to run PPPoE over";
|
description = "ethernet interface to run PPPoE over";
|
||||||
};
|
};
|
||||||
username = mkOption {
|
|
||||||
type = types.nullOr (liminix.lib.types.replacable types.str);
|
|
||||||
default = null;
|
|
||||||
description = "username";
|
|
||||||
};
|
|
||||||
password = mkOption {
|
|
||||||
type = types.nullOr (liminix.lib.types.replacable types.str);
|
|
||||||
default = null;
|
|
||||||
description = "password";
|
|
||||||
};
|
|
||||||
lcpEcho = {
|
|
||||||
adaptive = mkOption {
|
|
||||||
description = "send LCP echo-request frames only if no traffic was received from the peer since the last echo-request was sent";
|
|
||||||
type = types.bool;
|
|
||||||
default = true;
|
|
||||||
};
|
|
||||||
interval = mkOption {
|
|
||||||
type = types.nullOr types.int;
|
|
||||||
default = 3;
|
|
||||||
description = "send an LCP echo-request frame to the peer every n seconds";
|
|
||||||
};
|
|
||||||
failure = mkOption {
|
|
||||||
type = types.nullOr types.int;
|
|
||||||
default = 3;
|
|
||||||
description = "terminate connection if n LCP echo-requests are sent without receiving a valid LCP echo-reply";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
debug = mkOption {
|
|
||||||
description = "log the contents of all control packets sent or received";
|
|
||||||
default = false;
|
|
||||||
type = types.bool;
|
|
||||||
};
|
|
||||||
ppp-options = mkOption {
|
ppp-options = mkOption {
|
||||||
type = types.listOf types.str;
|
type = types.listOf types.str;
|
||||||
description = "options supplied on ppp command line";
|
description = "options supplied on ppp command line";
|
||||||
default = [];
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
system.service.l2tp = config.system.callService ./l2tp.nix {
|
system.service.l2tp = pkgs.liminix.callService ./l2tp.nix {
|
||||||
lns = mkOption {
|
lns = mkOption {
|
||||||
type = types.str;
|
type = types.str;
|
||||||
description = "hostname or address of the L2TP network server";
|
description = "hostname or address of the L2TP network server";
|
||||||
};
|
};
|
||||||
username = mkOption {
|
|
||||||
type = types.nullOr (liminix.lib.types.replacable types.str);
|
|
||||||
default = null;
|
|
||||||
description = "username";
|
|
||||||
};
|
|
||||||
password = mkOption {
|
|
||||||
type = types.nullOr (liminix.lib.types.replacable types.str);
|
|
||||||
default = null;
|
|
||||||
description = "password";
|
|
||||||
};
|
|
||||||
lcpEcho = {
|
|
||||||
adaptive = mkOption {
|
|
||||||
description = "send LCP echo-request frames only if no traffic was received from the peer since the last echo-request was sent";
|
|
||||||
type = types.bool;
|
|
||||||
default = true;
|
|
||||||
};
|
|
||||||
interval = mkOption {
|
|
||||||
type = types.nullOr types.int;
|
|
||||||
default = 3;
|
|
||||||
description = "send an LCP echo-request frame to the peer every n seconds";
|
|
||||||
};
|
|
||||||
failure = mkOption {
|
|
||||||
type = types.nullOr types.int;
|
|
||||||
default = 3;
|
|
||||||
description = "terminate connection if n LCP echo-requests are sent without receiving a valid LCP echo-reply";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
debug = mkOption {
|
|
||||||
description = "log the contents of all control packets sent or received";
|
|
||||||
default = false;
|
|
||||||
type = types.bool;
|
|
||||||
};
|
|
||||||
ppp-options = mkOption {
|
ppp-options = mkOption {
|
||||||
type = types.listOf types.str;
|
type = types.listOf types.str;
|
||||||
default = [];
|
|
||||||
description = "options supplied on ppp command line";
|
description = "options supplied on ppp command line";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,40 +1,62 @@
|
||||||
{
|
{
|
||||||
lib,
|
liminix
|
||||||
liminix,
|
, lib
|
||||||
output-template,
|
, ppp
|
||||||
serviceFns,
|
, pppoe
|
||||||
svc,
|
, writeAshScript
|
||||||
writeAshScript,
|
, writeText
|
||||||
writeText,
|
, serviceFns
|
||||||
xl2tpd,
|
, xl2tpd
|
||||||
callPackage
|
|
||||||
} :
|
} :
|
||||||
{ lns,
|
{ lns, ppp-options }:
|
||||||
ppp-options,
|
|
||||||
lcpEcho,
|
|
||||||
username,
|
|
||||||
password,
|
|
||||||
debug
|
|
||||||
}:
|
|
||||||
let
|
let
|
||||||
|
inherit (liminix.services) longrun;
|
||||||
name = "${lns}.l2tp";
|
name = "${lns}.l2tp";
|
||||||
common = callPackage ./common.nix { inherit svc; };
|
ip-up = writeAshScript "ip-up" {} ''
|
||||||
|
. ${serviceFns}
|
||||||
|
(in_outputs ${name}
|
||||||
|
echo $1 > ifname
|
||||||
|
echo $2 > tty
|
||||||
|
echo $3 > speed
|
||||||
|
echo $4 > address
|
||||||
|
echo $5 > peer-address
|
||||||
|
echo $DNS1 > ns1
|
||||||
|
echo $DNS2 > ns2
|
||||||
|
)
|
||||||
|
echo >/proc/self/fd/10
|
||||||
|
'';
|
||||||
|
ip6-up = writeAshScript "ip6-up" {} ''
|
||||||
|
. ${serviceFns}
|
||||||
|
(in_outputs ${name}
|
||||||
|
echo $4 > ipv6-address
|
||||||
|
echo $5 > ipv6-peer-address
|
||||||
|
)
|
||||||
|
echo >/proc/self/fd/10
|
||||||
|
'';
|
||||||
|
ppp-options' = ppp-options ++ [
|
||||||
|
"ip-up-script" ip-up
|
||||||
|
"ipv6-up-script" ip6-up
|
||||||
|
"ipparam" name
|
||||||
|
"nodetach"
|
||||||
|
"usepeerdns"
|
||||||
|
"logfd" "2"
|
||||||
|
];
|
||||||
conf = writeText "xl2tpd.conf" ''
|
conf = writeText "xl2tpd.conf" ''
|
||||||
[lac upstream]
|
[lac upstream]
|
||||||
lns = ${lns}
|
lns = ${lns}
|
||||||
require authentication = no
|
require authentication = no
|
||||||
pppoptfile = /run/${name}/ppp-options
|
pppoptfile = ${writeText "ppp-options" ppp-options'}
|
||||||
autodial = yes
|
autodial = yes
|
||||||
redial = yes
|
redial = yes
|
||||||
redial timeout = 1
|
|
||||||
max redials = 2 # this gives 1 actual retry, as xl2tpd can't count
|
|
||||||
'';
|
'';
|
||||||
control = "/run/${name}/control";
|
control = "/run/xl2tpd/control-${name}";
|
||||||
in common {
|
in
|
||||||
inherit name debug username password lcpEcho ppp-options;
|
longrun {
|
||||||
command = ''
|
inherit name;
|
||||||
|
run = ''
|
||||||
|
mkdir -p /run/xl2tpd
|
||||||
touch ${control}
|
touch ${control}
|
||||||
exec ${xl2tpd}/bin/xl2tpd -D -p /run/${name}/${name}.pid -c ${conf} -C ${control}
|
exec ${xl2tpd}/bin/xl2tpd -D -p /run/xl2tpd/${name}.pid -c ${conf} -C ${control}
|
||||||
'';
|
'';
|
||||||
|
notification-fd = 10;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,30 +1,51 @@
|
||||||
{
|
{
|
||||||
lib,
|
liminix
|
||||||
liminix,
|
, lib
|
||||||
output-template,
|
, ppp
|
||||||
ppp,
|
, pppoe
|
||||||
pppoe,
|
, writeAshScript
|
||||||
serviceFns,
|
, serviceFns
|
||||||
svc,
|
|
||||||
writeAshScript,
|
|
||||||
callPackage
|
|
||||||
} :
|
} :
|
||||||
{ interface,
|
{ interface, ppp-options }:
|
||||||
ppp-options,
|
|
||||||
lcpEcho,
|
|
||||||
username,
|
|
||||||
password,
|
|
||||||
debug
|
|
||||||
}:
|
|
||||||
let
|
let
|
||||||
|
inherit (liminix.services) longrun;
|
||||||
name = "${interface.name}.pppoe";
|
name = "${interface.name}.pppoe";
|
||||||
common = callPackage ./common.nix { inherit svc; };
|
ip-up = writeAshScript "ip-up" {} ''
|
||||||
|
. ${serviceFns}
|
||||||
timeoutOpt = if lcpEcho.interval != null then "-T ${builtins.toString (4 * lcpEcho.interval)}" else "";
|
(in_outputs ${name}
|
||||||
in common {
|
echo $1 > ifname
|
||||||
inherit name debug username password lcpEcho ppp-options;
|
echo $2 > tty
|
||||||
command = ''
|
echo $3 > speed
|
||||||
exec ${ppp}/bin/pppd pty "${pppoe}/bin/pppoe ${timeoutOpt} -I $(output ${interface} ifname)" file /run/${name}/ppp-options
|
echo $4 > address
|
||||||
|
echo $5 > peer-address
|
||||||
|
echo $DNS1 > ns1
|
||||||
|
echo $DNS2 > ns2
|
||||||
|
)
|
||||||
|
echo >/proc/self/fd/10
|
||||||
'';
|
'';
|
||||||
|
ip6-up = writeAshScript "ip6-up" {} ''
|
||||||
|
. ${serviceFns}
|
||||||
|
(in_outputs ${name}
|
||||||
|
echo $4 > ipv6-address
|
||||||
|
echo $5 > ipv6-peer-address
|
||||||
|
)
|
||||||
|
echo >/proc/self/fd/10
|
||||||
|
'';
|
||||||
|
ppp-options' = ppp-options ++ [
|
||||||
|
"ip-up-script" ip-up
|
||||||
|
"ipv6-up-script" ip6-up
|
||||||
|
"ipparam" name
|
||||||
|
"nodetach"
|
||||||
|
"usepeerdns"
|
||||||
|
"logfd" "2"
|
||||||
|
];
|
||||||
|
in
|
||||||
|
longrun {
|
||||||
|
inherit name;
|
||||||
|
run = ''
|
||||||
|
. ${serviceFns}
|
||||||
|
${ppp}/bin/pppd pty "${pppoe}/bin/pppoe -I $(output ${interface} ifname)" ${lib.concatStringsSep " " ppp-options'}
|
||||||
|
'';
|
||||||
|
notification-fd = 10;
|
||||||
dependencies = [ interface ];
|
dependencies = [ interface ];
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
let
|
let
|
||||||
svc = config.system.service;
|
svc = config.system.service;
|
||||||
cfg = config.profile.gateway;
|
cfg = config.profile.gateway;
|
||||||
inherit (lib) mkOption mkEnableOption mkIf types;
|
inherit (lib) mkOption mkEnableOption mkIf mdDoc types optional optionals;
|
||||||
inherit (pkgs) liminix serviceFns;
|
inherit (pkgs) liminix serviceFns;
|
||||||
inherit (liminix.services) bundle oneshot;
|
inherit (liminix.services) bundle oneshot;
|
||||||
hostaps =
|
hostaps =
|
||||||
|
@ -52,6 +52,8 @@ in {
|
||||||
|
|
||||||
wan = {
|
wan = {
|
||||||
interface = mkOption { type = liminix.lib.types.interface; };
|
interface = mkOption { type = liminix.lib.types.interface; };
|
||||||
|
username = mkOption { type = types.str; };
|
||||||
|
password = mkOption { type = types.str; };
|
||||||
dhcp6.enable = mkOption { type = types.bool; };
|
dhcp6.enable = mkOption { type = types.bool; };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -84,7 +86,14 @@ in {
|
||||||
members = cfg.lan.interfaces;
|
members = cfg.lan.interfaces;
|
||||||
};
|
};
|
||||||
|
|
||||||
services.wan = cfg.wan.interface;
|
services.wan = svc.pppoe.build {
|
||||||
|
inherit (cfg.wan) interface;
|
||||||
|
ppp-options = [
|
||||||
|
"debug" "+ipv6" "noauth"
|
||||||
|
"name" cfg.wan.username
|
||||||
|
"password" cfg.wan.password
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
services.packet_forwarding = svc.network.forward.build { };
|
services.packet_forwarding = svc.network.forward.build { };
|
||||||
|
|
||||||
|
@ -149,6 +158,7 @@ in {
|
||||||
dependencies = [ config.services.wan ];
|
dependencies = [ config.services.wan ];
|
||||||
name = "resolvconf";
|
name = "resolvconf";
|
||||||
up = ''
|
up = ''
|
||||||
|
. ${serviceFns}
|
||||||
( in_outputs ${name}
|
( in_outputs ${name}
|
||||||
echo "nameserver $(output ${config.services.wan} ns1)" > resolv.conf
|
echo "nameserver $(output ${config.services.wan} ns1)" > resolv.conf
|
||||||
echo "nameserver $(output ${config.services.wan} ns2)" >> resolv.conf
|
echo "nameserver $(output ${config.services.wan} ns2)" >> resolv.conf
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue