Compare commits
No commits in common. "main" and "main" have entirely different histories.
33
NEWS
33
NEWS
|
@ -83,35 +83,4 @@ sponsoring this development (and funding the hardware)
|
|||
2024-02-21
|
||||
|
||||
New port! Thanks to Raito Bezarius, Liminix now runs on the Zyxel NWA50AX,
|
||||
an MT7621 (MIPS EL) dual radio WiFi AP.
|
||||
|
||||
2024-04-29
|
||||
|
||||
The setup for using `levitate` has changed: now it accepts an entire
|
||||
config fragment, not just a list of services. Hopefully this makes it
|
||||
a bit more useful :-)
|
||||
|
||||
defaultProfile.packages = with pkgs; [
|
||||
...
|
||||
(levitate.override {
|
||||
config = {
|
||||
services = {
|
||||
inherit (config.services) dhcpc sshd watchdog;
|
||||
};
|
||||
defaultProfile.packages = [ mtdutils ];
|
||||
users.root.openssh.authorizedKeys.keys = secrets.root.keys;
|
||||
};
|
||||
})
|
||||
];
|
||||
|
||||
2024-07-16
|
||||
|
||||
* structured parameters are available for the pppoe service
|
||||
|
||||
* The "wan" configuration in modules/profiles/gateway.nix has changed:
|
||||
instead of passing options that are used to create a pppoe interface,
|
||||
callers should create a (pppoe or other) interface and pass that as
|
||||
the value of profile.gateway.wan. For the pppoe case this is now only
|
||||
very slightly more verbose, and it allows using the gateway profile
|
||||
with other kinds of upstream.
|
||||
|
||||
an MT7621 (MIPS EL) dual radio WiFi AP.
|
2387
THOUGHTS.txt
2387
THOUGHTS.txt
File diff suppressed because it is too large
Load Diff
20
boot.expect
20
boot.expect
|
@ -1,20 +0,0 @@
|
|||
# This is for use with minicom, but needs you to configure it to
|
||||
# use expect as its "Script program" instead of runscript. Try
|
||||
# Ctrl+A O -> Filenames and paths -> D
|
||||
|
||||
log_user 0
|
||||
log_file -a -open stderr
|
||||
set f [open "result/boot.scr"]
|
||||
send "version\r"
|
||||
set timeout 60
|
||||
while {[gets $f line] >= 0} {
|
||||
puts stderr "next line $line\r"
|
||||
puts stderr "waiting for prompt\r"
|
||||
expect {
|
||||
"ath>" {}
|
||||
"BusyBox" { puts stderr "DONE"; exit 0 }
|
||||
}
|
||||
send "$line\r\n"
|
||||
}
|
||||
puts stderr "done\r\n"
|
||||
close $f
|
|
@ -4,10 +4,6 @@ let
|
|||
inherit (lib) mkOption mkEnableOption mdDoc types optional optionals;
|
||||
in {
|
||||
options.bordervm = {
|
||||
keys = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ ];
|
||||
};
|
||||
l2tp = {
|
||||
host = mkOption {
|
||||
description = mdDoc ''
|
||||
|
@ -55,17 +51,18 @@ in {
|
|||
<nixpkgs/nixos/modules/virtualisation/qemu-vm.nix>
|
||||
];
|
||||
config = {
|
||||
boot.kernelParams = [ "loglevel=9" ];
|
||||
boot.kernelParams = [
|
||||
"loglevel=9"
|
||||
];
|
||||
systemd.services.pppoe =
|
||||
let
|
||||
conf = pkgs.writeText "kpppoed.toml" ''
|
||||
interface_name = "eth1"
|
||||
services = [ "myservice" ]
|
||||
lns_ipaddr = "${cfg.l2tp.host}:${builtins.toString cfg.l2tp.port}"
|
||||
ac_name = "kpppoed-1.0"
|
||||
'';
|
||||
in
|
||||
{
|
||||
let conf = pkgs.writeText "kpppoed.toml"
|
||||
''
|
||||
interface_name = "eth1"
|
||||
services = [ "myservice" ]
|
||||
lns_ipaddr = "${cfg.l2tp.host}:${builtins.toString cfg.l2tp.port}"
|
||||
ac_name = "kpppoed-1.0"
|
||||
'';
|
||||
in {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network-online.target" ];
|
||||
serviceConfig = {
|
||||
|
@ -79,63 +76,29 @@ in {
|
|||
};
|
||||
};
|
||||
services.openssh.enable = true;
|
||||
services.dnsmasq = {
|
||||
enable = true;
|
||||
resolveLocalQueries = false;
|
||||
settings = {
|
||||
# domain-needed = true;
|
||||
dhcp-range = [ "10.0.0.10,10.0.0.240" ];
|
||||
interface = "eth1";
|
||||
};
|
||||
};
|
||||
|
||||
services.nginx = {
|
||||
enable = true;
|
||||
user = "liminix";
|
||||
virtualHosts.${config.networking.hostName} = {
|
||||
root = "/home/liminix";
|
||||
default = true;
|
||||
};
|
||||
};
|
||||
systemd.services.nginx.serviceConfig.ProtectHome = "read-only";
|
||||
|
||||
systemd.services.sshd.wantedBy = pkgs.lib.mkForce [ "multi-user.target" ];
|
||||
|
||||
virtualisation = {
|
||||
forwardPorts = [ {
|
||||
from = "host";
|
||||
host.port = 7654;
|
||||
# guest.address = "10.0.2.15";
|
||||
guest.port =7654;
|
||||
} ];
|
||||
qemu = {
|
||||
networkingOptions = [ ];
|
||||
options =
|
||||
[ ]
|
||||
++ optional cfg.ethernet.pci.enable "-device vfio-pci,host=${cfg.ethernet.pci.id}"
|
||||
++ optionals cfg.ethernet.usb.enable [
|
||||
networkingOptions = [];
|
||||
options = [] ++
|
||||
optional cfg.ethernet.pci.enable
|
||||
"-device vfio-pci,host=${cfg.ethernet.pci.id}" ++
|
||||
optionals cfg.ethernet.usb.enable [
|
||||
"-device usb-ehci,id=ehci"
|
||||
"-device usb-host,bus=ehci.0,vendorid=${cfg.ethernet.usb.vendor},productid=${cfg.ethernet.usb.product}"
|
||||
]
|
||||
++ [
|
||||
] ++ [
|
||||
"-nographic"
|
||||
"-serial mon:stdio"
|
||||
];
|
||||
};
|
||||
sharedDirectories = {
|
||||
liminix = {
|
||||
securityModel = "none";
|
||||
source = builtins.toString ./.;
|
||||
target = "/home/liminix/liminix";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
services.tang = {
|
||||
enable = true;
|
||||
ipAddressAllow = [ "10.0.0.0/24" "0.0.0.0/0" ];
|
||||
};
|
||||
|
||||
environment.systemPackages =
|
||||
let wireshark-nogui = pkgs.wireshark.override { withQt = false ; };
|
||||
in with pkgs; [
|
||||
|
@ -145,8 +108,6 @@ in {
|
|||
tufted
|
||||
iptables
|
||||
usbutils
|
||||
busybox
|
||||
clevis
|
||||
];
|
||||
security.sudo.wheelNeedsPassword = false;
|
||||
networking = {
|
||||
|
@ -156,17 +117,11 @@ in {
|
|||
useDHCP = false;
|
||||
ipv4.addresses = [ { address = "10.0.0.1"; prefixLength = 24;}];
|
||||
};
|
||||
nat = {
|
||||
enable = true;
|
||||
internalInterfaces = [ "eth1" ];
|
||||
externalInterface = "eth0";
|
||||
};
|
||||
};
|
||||
users.users.liminix = {
|
||||
isNormalUser = true;
|
||||
uid = 1000;
|
||||
extraGroups = [ "wheel" ];
|
||||
openssh.authorizedKeys.keys = cfg.keys;
|
||||
extraGroups = [ "wheel"];
|
||||
};
|
||||
services.getty.autologinUser = "liminix";
|
||||
};
|
||||
|
|
|
@ -1,12 +1,8 @@
|
|||
{ ... }:
|
||||
{...}:
|
||||
{
|
||||
bordervm = {
|
||||
# ethernet.pci = { id = "01:00.0"; enable = true; };
|
||||
ethernet.usb = {
|
||||
vendor = "0x0bda";
|
||||
product = "0x8153";
|
||||
enable = true;
|
||||
};
|
||||
ethernet.usb = { vendor = "0x0bda"; product = "0x8153"; enable = true; };
|
||||
l2tp = {
|
||||
host = "l2tp.aa.net.uk";
|
||||
};
|
||||
|
|
79
ci.nix
79
ci.nix
|
@ -1,7 +1,12 @@
|
|||
{
|
||||
nixpkgs
|
||||
, unstable
|
||||
, liminix
|
||||
, ... }:
|
||||
let
|
||||
pkgs = import <nixpkgs> { };
|
||||
liminix = <liminix>;
|
||||
borderVmConf = ./bordervm.conf-example.nix;
|
||||
inherit (builtins) map;
|
||||
pkgs = (import nixpkgs {});
|
||||
borderVmConf = ./bordervm.conf-example.nix;
|
||||
inherit (pkgs.lib.attrsets) genAttrs;
|
||||
devices = [
|
||||
"gl-ar750"
|
||||
|
@ -16,64 +21,62 @@ let
|
|||
vanilla = ./vanilla-configuration.nix;
|
||||
for-device = name:
|
||||
(import liminix {
|
||||
inherit borderVmConf;
|
||||
inherit nixpkgs borderVmConf;
|
||||
device = import (liminix + "/devices/${name}");
|
||||
liminix-config = vanilla;
|
||||
}).outputs.default;
|
||||
tests = import ./tests/ci.nix;
|
||||
jobs =
|
||||
(genAttrs devices for-device)
|
||||
// tests
|
||||
// {
|
||||
buildEnv =
|
||||
(import liminix {
|
||||
inherit borderVmConf;
|
||||
device = import (liminix + "/devices/qemu");
|
||||
liminix-config = vanilla;
|
||||
}).buildEnv;
|
||||
(genAttrs devices for-device) //
|
||||
tests //
|
||||
{
|
||||
buildEnv = (import liminix {
|
||||
inherit nixpkgs borderVmConf;
|
||||
device = import (liminix + "/devices/qemu");
|
||||
liminix-config = vanilla;
|
||||
}).buildEnv;
|
||||
doc =
|
||||
let
|
||||
json =
|
||||
(import liminix {
|
||||
inherit borderVmConf;
|
||||
device = import (liminix + "/devices/qemu");
|
||||
liminix-config =
|
||||
{ ... }:
|
||||
{
|
||||
let json =
|
||||
(import liminix {
|
||||
inherit nixpkgs borderVmConf;
|
||||
device = import (liminix + "/devices/qemu");
|
||||
liminix-config = {...} : {
|
||||
imports = [ ./modules/all-modules.nix ];
|
||||
};
|
||||
}).outputs.optionsJson;
|
||||
in
|
||||
pkgs.stdenv.mkDerivation {
|
||||
}).outputs.optionsJson;
|
||||
installers = map (f: "system.outputs.${f}") [
|
||||
"vmroot"
|
||||
"mtdimage"
|
||||
"ubimage"
|
||||
];
|
||||
inherit (pkgs.lib) concatStringsSep;
|
||||
in pkgs.stdenv.mkDerivation {
|
||||
name = "liminix-doc";
|
||||
nativeBuildInputs = with pkgs; [
|
||||
gnumake
|
||||
sphinx
|
||||
fennel
|
||||
luaPackages.lyaml
|
||||
gnumake sphinx fennel luaPackages.lyaml
|
||||
];
|
||||
src = ./.;
|
||||
buildPhase = ''
|
||||
cat ${json} | fennel --correlate doc/parse-options.fnl > doc/modules-generated.inc.rst
|
||||
cat ${json} | fennel --correlate doc/parse-options-outputs.fnl > doc/outputs-generated.inc.rst
|
||||
cat ${json} | fennel --correlate doc/parse-options.fnl > doc/modules-generated.rst
|
||||
cat ${json} | fennel --correlate doc/parse-options-outputs.fnl > doc/outputs-generated.rst
|
||||
cp ${(import ./doc/hardware.nix)} doc/hardware.rst
|
||||
make -C doc html
|
||||
'';
|
||||
installPhase = ''
|
||||
mkdir -p $out/nix-support $out/share/doc/
|
||||
cd doc
|
||||
cp *-generated.inc.rst hardware.rst $out
|
||||
cp *-generated.rst $out
|
||||
ln -s ${json} $out/options.json
|
||||
cp -a _build/html $out/share/doc/liminix
|
||||
echo "file source-dist \"$out/share/doc/liminix\"" \
|
||||
> $out/nix-support/hydra-build-products
|
||||
'';
|
||||
};
|
||||
with-unstable = (import liminix {
|
||||
nixpkgs = unstable;
|
||||
inherit borderVmConf;
|
||||
device = import (liminix + "/devices/qemu");
|
||||
liminix-config = vanilla;
|
||||
}).outputs.default;
|
||||
};
|
||||
in jobs //
|
||||
{
|
||||
all = pkgs.mkShell {
|
||||
name = "all tests";
|
||||
contents = pkgs.lib.collect pkgs.lib.isDerivation jobs;
|
||||
};
|
||||
}
|
||||
in jobs
|
||||
|
|
47
default.nix
47
default.nix
|
@ -1,38 +1,31 @@
|
|||
{
|
||||
deviceName ? null,
|
||||
device ? (import ./devices/${deviceName}),
|
||||
liminix-config ? <liminix-config>,
|
||||
borderVmConf ? ./bordervm.conf.nix,
|
||||
imageType ? "primary",
|
||||
device
|
||||
, liminix-config ? <liminix-config>
|
||||
, nixpkgs ? <nixpkgs>
|
||||
, borderVmConf ? ./bordervm.conf.nix
|
||||
, imageType ? "primary"
|
||||
}:
|
||||
|
||||
let
|
||||
overlay = import ./overlay.nix;
|
||||
pkgs = import <nixpkgs> (
|
||||
device.system
|
||||
// {
|
||||
overlays = [ overlay ];
|
||||
config = {
|
||||
allowUnsupportedSystem = true; # mipsel
|
||||
permittedInsecurePackages = [
|
||||
"python-2.7.18.6" # kernel backports needs python <3
|
||||
"python-2.7.18.7"
|
||||
];
|
||||
};
|
||||
}
|
||||
);
|
||||
pkgs = import nixpkgs (device.system // {
|
||||
overlays = [overlay];
|
||||
config = {
|
||||
allowUnsupportedSystem = true; # mipsel
|
||||
permittedInsecurePackages = [
|
||||
"python-2.7.18.6" # kernel backports needs python <3
|
||||
"python-2.7.18.7"
|
||||
];
|
||||
};
|
||||
});
|
||||
|
||||
eval = pkgs.lib.evalModules {
|
||||
specialArgs = {
|
||||
modulesPath = builtins.toString ./modules;
|
||||
};
|
||||
modules = [
|
||||
{ _module.args = { inherit pkgs; inherit (pkgs) lim; }; }
|
||||
./modules/hardware.nix
|
||||
./modules/base.nix
|
||||
./modules/busybox.nix
|
||||
./modules/hostname.nix
|
||||
./modules/kernel
|
||||
device.module
|
||||
liminix-config
|
||||
./modules/s6
|
||||
|
@ -48,14 +41,7 @@ let
|
|||
borderVm = ((import <nixpkgs/nixos/lib/eval-config.nix>) {
|
||||
system = builtins.currentSystem;
|
||||
modules = [
|
||||
{
|
||||
nixpkgs.overlays = [
|
||||
(final: prev: {
|
||||
go-l2tp = final.callPackage ./pkgs/go-l2tp {};
|
||||
tufted = final.callPackage ./pkgs/tufted {};
|
||||
})
|
||||
];
|
||||
}
|
||||
({ ... } : { nixpkgs.overlays = [ overlay ]; })
|
||||
(import ./bordervm-configuration.nix)
|
||||
borderVmConf
|
||||
];
|
||||
|
@ -86,7 +72,6 @@ in {
|
|||
min-copy-closure
|
||||
fennelrepl
|
||||
lzma
|
||||
lua
|
||||
];
|
||||
};
|
||||
}
|
||||
|
|
|
@ -73,7 +73,7 @@
|
|||
MTK_INFRACFG = "y";
|
||||
|
||||
MTK_PMIC_WRAP = "y";
|
||||
NVMEM_MTK_EFUSE="y";
|
||||
MTK_EFUSE="y";
|
||||
# MTK_HSDMA="y";
|
||||
MTK_SCPSYS="y";
|
||||
MTK_SCPSYS_PM_DOMAINS="y";
|
||||
|
@ -92,6 +92,7 @@
|
|||
|
||||
MEDIATEK_GE_PHY = "y";
|
||||
# MEDIATEK_MT6577_AUXADC = "y";
|
||||
# MEDIATEK_WATCHDOG = "y";
|
||||
NET_MEDIATEK_SOC = "y";
|
||||
NET_MEDIATEK_SOC_WED = "y";
|
||||
NET_MEDIATEK_STAR_EMAC = "y"; # this enables REGMAP_MMIO
|
||||
|
@ -213,6 +214,7 @@
|
|||
networkInterfaces =
|
||||
let
|
||||
inherit (config.system.service.network) link;
|
||||
inherit (config.system.service) bridge;
|
||||
in rec {
|
||||
wan = link.build { ifname = "wan"; };
|
||||
lan1 = link.build { ifname = "lan1"; };
|
||||
|
|
|
@ -23,17 +23,12 @@
|
|||
VIRTIO_BLK = "y";
|
||||
VIRTIO_NET = "y";
|
||||
};
|
||||
conditionalConfig = {
|
||||
WLAN= {
|
||||
MAC80211_HWSIM = "m";
|
||||
};
|
||||
};
|
||||
};
|
||||
hardware =
|
||||
let
|
||||
mac80211 = pkgs.kmodloader.override {
|
||||
inherit (config.system.outputs) kernel;
|
||||
targets = ["mac80211_hwsim"];
|
||||
mac80211 = pkgs.mac80211.override {
|
||||
drivers = ["mac80211_hwsim"];
|
||||
klibBuild = config.system.outputs.kernel.modulesupport;
|
||||
};
|
||||
in {
|
||||
defaultOutput = "vmroot";
|
||||
|
|
|
@ -92,6 +92,7 @@
|
|||
'';
|
||||
};
|
||||
inherit (pkgs.pseudofile) dir symlink;
|
||||
inherit (pkgs.liminix.networking) interface;
|
||||
in {
|
||||
imports = [
|
||||
../../modules/network
|
||||
|
@ -124,14 +125,8 @@
|
|||
networkInterfaces =
|
||||
let inherit (config.system.service.network) link;
|
||||
in {
|
||||
lan = link.build {
|
||||
ifname = "lan";
|
||||
devpath = "/devices/platform/ahb/1a000000.eth";
|
||||
};
|
||||
wan = link.build {
|
||||
ifname = "wan";
|
||||
devpath = "/devices/platform/ahb/19000000.eth";
|
||||
};
|
||||
lan = link.build { ifname = "eth0"; };
|
||||
wan = link.build { ifname = "eth1"; };
|
||||
wlan = link.build {
|
||||
ifname = "wlan0";
|
||||
dependencies = [ mac80211 ];
|
||||
|
@ -154,7 +149,6 @@
|
|||
};
|
||||
boot.tftp = {
|
||||
loadAddress = lim.parseInt "0x00A00000";
|
||||
appendDTB = true;
|
||||
};
|
||||
kernel = {
|
||||
src = pkgs.pkgsBuildBuild.fetchurl {
|
||||
|
|
|
@ -45,6 +45,7 @@
|
|||
|
||||
module = { pkgs, config, lib, lim, ...}:
|
||||
let
|
||||
inherit (pkgs.liminix.networking) interface;
|
||||
inherit (pkgs) openwrt;
|
||||
mac80211 = pkgs.kmodloader.override {
|
||||
targets = ["rt2800soc"];
|
||||
|
@ -89,6 +90,19 @@
|
|||
let
|
||||
inherit (config.system.service.network) link;
|
||||
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 {
|
||||
eth = link.build { ifname = "eth0"; };
|
||||
# lan and wan ports are both behind a switch on eth0
|
||||
|
@ -96,11 +110,13 @@
|
|||
ifname = "eth0.1";
|
||||
primary = eth;
|
||||
vid = "1";
|
||||
dependencies = [swconfig eth];
|
||||
};
|
||||
wan = vlan.build {
|
||||
ifname = "eth0.2";
|
||||
primary = eth;
|
||||
vid = "2";
|
||||
dependencies = [swconfig eth];
|
||||
};
|
||||
wlan = link.build {
|
||||
ifname = "wlan0";
|
||||
|
@ -110,8 +126,7 @@
|
|||
};
|
||||
boot.tftp = {
|
||||
loadAddress = lim.parseInt "0x00A00000";
|
||||
appendDTB = true;
|
||||
};
|
||||
};
|
||||
|
||||
kernel = {
|
||||
src = pkgs.fetchurl {
|
||||
|
@ -121,7 +136,6 @@
|
|||
};
|
||||
extraPatchPhase = ''
|
||||
${openwrt.applyPatches.ramips}
|
||||
${openwrt.applyPatches.rt2x00}
|
||||
'';
|
||||
config = {
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
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
|
||||
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.
|
||||
|
@ -38,6 +38,7 @@
|
|||
|
||||
module = { pkgs, config, lib, lim, ...}:
|
||||
let
|
||||
inherit (pkgs.liminix.networking) interface;
|
||||
inherit (pkgs.liminix.services) oneshot;
|
||||
inherit (pkgs.pseudofile) dir symlink;
|
||||
inherit (pkgs) openwrt;
|
||||
|
@ -96,7 +97,7 @@
|
|||
swconfig dev switch0 vlan 2 set ports '0 6t'
|
||||
swconfig dev switch0 set apply
|
||||
'';
|
||||
down = "${pkgs.swconfig}/bin/swconfig dev switch0 set reset";
|
||||
down = "swconfig dev switch0 set reset";
|
||||
};
|
||||
in rec {
|
||||
eth = link.build { ifname = "eth0"; dependencies = [swconfig]; };
|
||||
|
@ -121,7 +122,6 @@
|
|||
# 20MB seems to give enough room to uncompress the kernel
|
||||
# without anything getting trodden on. 10MB was too small
|
||||
loadAddress = lim.parseInt "0x1400000";
|
||||
appendDTB = true;
|
||||
};
|
||||
|
||||
kernel = {
|
||||
|
|
|
@ -19,14 +19,14 @@
|
|||
ARM targets differ from MIPS in that the kernel format expected
|
||||
by QEMU is an "Image" (raw binary file) rather than an ELF
|
||||
file, but this is taken care of by :command:`run.sh`. Check the
|
||||
documentation for the :ref:`qemu` target for more information.
|
||||
documentation for the :ref:`QEMU` (MIPS) target for more information.
|
||||
|
||||
'';
|
||||
|
||||
# this device is described by the "qemu" device
|
||||
installer = "vmroot";
|
||||
|
||||
module = { config, lim, ... }: {
|
||||
module = {pkgs, config, lim, ... }: {
|
||||
imports = [
|
||||
../../modules/arch/aarch64.nix
|
||||
../families/qemu.nix
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
'';
|
||||
installer = "vmroot";
|
||||
|
||||
module = { config, lim, ... }: {
|
||||
module = {pkgs, config, lim, ... }: {
|
||||
imports = [
|
||||
../../modules/arch/arm.nix
|
||||
../families/qemu.nix
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
in the Development manual.
|
||||
|
||||
'';
|
||||
module = { config, lib, lim, ... }: {
|
||||
module = {pkgs, config, lib, lim, ... }: {
|
||||
imports = [
|
||||
../../modules/arch/mipseb.nix
|
||||
../families/qemu.nix
|
||||
|
|
|
@ -419,6 +419,7 @@
|
|||
networkInterfaces =
|
||||
let
|
||||
inherit (config.system.service.network) link;
|
||||
inherit (config.system.service) bridge;
|
||||
in rec {
|
||||
lan1 = link.build { ifname = "lan1"; };
|
||||
lan2 = link.build { ifname = "lan2"; };
|
||||
|
|
|
@ -155,6 +155,8 @@
|
|||
|
||||
module = {pkgs, config, lib, lim, ... }:
|
||||
let
|
||||
openwrt = pkgs.openwrt;
|
||||
inherit (lib) mkOption types;
|
||||
inherit (pkgs.liminix.services) oneshot;
|
||||
inherit (pkgs) liminix;
|
||||
mtd_by_name_links = pkgs.liminix.services.oneshot rec {
|
||||
|
@ -356,6 +358,7 @@
|
|||
networkInterfaces =
|
||||
let
|
||||
inherit (config.system.service.network) link;
|
||||
inherit (config.system.service) bridge;
|
||||
in rec {
|
||||
en70000 = link.build {
|
||||
# in armada-38x.dtsi this is eth0.
|
||||
|
|
|
@ -103,6 +103,8 @@
|
|||
|
||||
module = { pkgs, config, lib, lim, ...}:
|
||||
let
|
||||
inherit (pkgs.liminix.networking) interface;
|
||||
inherit (pkgs.liminix.services) oneshot;
|
||||
inherit (pkgs.pseudofile) dir symlink;
|
||||
inherit (pkgs) openwrt;
|
||||
|
||||
|
|
350
doc/admin.rst
350
doc/admin.rst
|
@ -4,134 +4,143 @@ System Administration
|
|||
Services on a running system
|
||||
****************************
|
||||
|
||||
Liminix services are built on s6-rc, which is itself layered on s6.
|
||||
Services are defined at build time in your configuration (see
|
||||
:ref:`configuration-services` for information) and can't be added
|
||||
to/changed at runtime, but to monitor
|
||||
events or diagnose problems you may need to inspect them on the
|
||||
running system. Here are some of the most commonly used s6,-rc
|
||||
commands:
|
||||
* add an s6-rc cheatsheet here
|
||||
|
||||
.. list-table:: Service management quick reference
|
||||
:widths: 55 45
|
||||
:header-rows: 1
|
||||
|
||||
* - What
|
||||
- How
|
||||
* - List all running services
|
||||
- ``s6-rc -a list``
|
||||
* - List all services that are **not** running
|
||||
- ``s6-rc -da list``
|
||||
* - List services that ``wombat`` depends on
|
||||
- ``s6-rc-db dependencies wombat``
|
||||
* - ... transitively
|
||||
- ``s6-rc-db all-dependencies wombat``
|
||||
* - List services that depend on service ``wombat``
|
||||
- ``s6-rc-db -d dependencies wombat``
|
||||
* - ... transitively
|
||||
- ``s6-rc-db -d all-dependencies wombat``
|
||||
* - Stop service ``wombat`` and everything depending on it
|
||||
- ``s6-rc -d change wombat``
|
||||
* - Start service ``wombat`` (but not any services depending on it)
|
||||
- ``s6-rc -u change wombat``
|
||||
* - Start service ``wombat`` and all* services depending on it
|
||||
- ``s6-rc-up-tree wombat``
|
||||
|
||||
:command:`s6-rc-up-tree` brings up a service and all services that
|
||||
depend on it, except for any services that depend on a "controlled"
|
||||
service that is not currently running. Controlled services are not
|
||||
started at boot time but in response to external events (e.g. plugging
|
||||
in a particular piece of hardware) so you probably don't want to be
|
||||
starting them by hand if the conditions aren't there.
|
||||
Flashing and updating
|
||||
*********************
|
||||
|
||||
A service may be **up** or **down** (there are no intermediate states
|
||||
like "started" or "stopping" or "dying" or "cogitating"). Some (but
|
||||
not all) services have "readiness" notifications: the dependents of a
|
||||
service with a readiness notification won't be started until the
|
||||
service signals (by writing to a nominated file descriptor) that it's
|
||||
prepared to start work. Most services defined by Liminix also have a
|
||||
``timeout-up`` parameter, which means that if a service has readiness
|
||||
notifications and doesn't become ready in the allotted time (defaults
|
||||
20 seconds) it will be terminated and its state set to **down**.
|
||||
|
||||
If the process providing a service dies, it will be restarted
|
||||
automatically. Liminix does not automatically set it to **down**.
|
||||
|
||||
(If the process providing a service dies without ever notifying
|
||||
readiness, Liminix will restart it as many times as it has to until the
|
||||
timeout period elapses, and then stop it and mark it down.)
|
||||
Flashing from Liminix
|
||||
=====================
|
||||
|
||||
Controlled services
|
||||
===================
|
||||
The flash procedure from an existing Liminix-system has two steps.
|
||||
First we reboot the device (using "kexec") into an "ephemeral"
|
||||
RAM-based version of the new configuration, then when we're happy it
|
||||
works we can flash the image - and if it doesn't work we can reboot
|
||||
the device again and it will boot from the old image.
|
||||
|
||||
**Controlled** services are those which are started/stopped on demand
|
||||
by a **controller** (another service) instead of being started at boot
|
||||
time. For example:
|
||||
|
||||
* ``svc.uevent-rule.build`` creates a controlled service which is
|
||||
active when a particular hardware device (identified by uevent/sysfs
|
||||
directory) is present.
|
||||
Building the RAM-based image
|
||||
----------------------------
|
||||
|
||||
* ``svc.round-robin.build`` creates a service controller that
|
||||
invokes two or more services in turn, running the next one when the
|
||||
process providing the previous one exits. We use this for failover
|
||||
from one network connection to a backup connection, for example.
|
||||
To create the ephemeral image, build ``outputs.kexecboot`` instead of
|
||||
``outputs.default``. This generates a directory containing the root
|
||||
filesystem image and kernel, along with an executable called `kexec`
|
||||
and a `boot.sh` script that runs it with appropriate arguments.
|
||||
|
||||
* ``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/log/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`.
|
||||
For example
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
# ls -l /run/log/
|
||||
-rw-r--r-- 1 0 lock
|
||||
-rw-r--r-- 1 0 state
|
||||
-rwxr--r-- 1 98059 @4000000000025cb629c311ac.s
|
||||
-rwxr--r-- 1 98061 @40000000000260f7309c7fb4.s
|
||||
-rwxr--r-- 1 98041 @40000000000265233a6cc0b6.s
|
||||
-rwxr--r-- 1 98019 @400000000002695d10c06929.s
|
||||
-rwxr--r-- 1 98064 @4000000000026d84189559e0.s
|
||||
-rwxr--r-- 1 98055 @40000000000271ce1e031d91.s
|
||||
-rwxr--r-- 1 98054 @400000000002760229733626.s
|
||||
-rwxr--r-- 1 98104 @4000000000027a2e3b6f4e12.s
|
||||
-rwxr--r-- 1 98023 @4000000000027e6f0ed24a6c.s
|
||||
-rw-r--r-- 1 42374 current
|
||||
|
||||
# tail -2 /run/log/current
|
||||
@40000000000284f130747343 wan.link.pppoe Connect: ppp0 <--> /dev/pts/0
|
||||
@40000000000284f230acc669 wan.link.pppoe sent [LCP ConfReq id=0x1 <asyncmap 0x0> <magic 0x667a9594> <pcomp> <accomp>]
|
||||
# tail -2 /run/log/current | s6-tai64nlocal
|
||||
1970-01-02 21:51:45.828598156 wan.link.pppoe sent [LCP ConfReq id=0x1 <asyncmap 0x0> <magic 0x667a9594> <pcomp> <accom
|
||||
p>]
|
||||
1970-01-02 21:51:48.832588765 wan.link.pppoe sent [LCP ConfReq id=0x1 <asyncmap 0x0> <magic 0x667a9594> <pcomp> <accom
|
||||
p>]
|
||||
nix-build -I liminix-config=./examples/arhcive.nix \
|
||||
--arg device "import ./devices/gl-ar750"
|
||||
-A outputs.kexecboot && \
|
||||
(tar chf - result | ssh root@the-device tar -C /run -xvf -)
|
||||
|
||||
and then login to the device and run
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
cd /run/result
|
||||
sh ./boot.sh .
|
||||
|
||||
|
||||
This will load the new kernel and map the root filesystem into a RAM
|
||||
disk, then start executing the new kernel. *This is effectively a
|
||||
reboot - be sure to close all open files and finish anything else
|
||||
you were doing first.*
|
||||
|
||||
If the new system crashes or is rebooted, then the device will revert
|
||||
to the old configuration it finds in flash.
|
||||
|
||||
|
||||
Building the second (permanent) image
|
||||
-------------------------------------
|
||||
|
||||
While running in the kexecboot system, you can 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)
|
||||
************************************
|
||||
|
@ -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``
|
||||
|
||||
|
||||
.. _rebuilding the system:
|
||||
|
||||
Rebuilding the system
|
||||
=====================
|
||||
|
||||
|
@ -189,116 +196,3 @@ Caveats
|
|||
nixpkgs).
|
||||
|
||||
* it cannot upgrade the kernel, only userland
|
||||
|
||||
.. _levitate:
|
||||
|
||||
Reinstalling on a running system
|
||||
********************************
|
||||
|
||||
Liminix is initially installed from a monolithic
|
||||
:file:`firmware.bin` - and unless you're running a writable
|
||||
filesystem, the only way to update it is to build and install a whole
|
||||
new :file:`firmware.bin`. However, you probably would prefer not to
|
||||
have to remove it from its installation site, unplug it from the
|
||||
network and stick serial cables in it all over again.
|
||||
|
||||
It is not (generally) safe to install a new firmware onto the flash
|
||||
partitions that the active system is running on. To address this we
|
||||
have :command:`levitate`, which a way for a running Liminix system to
|
||||
"soft restart" into a ramdisk running only a limited set of services,
|
||||
so that the main partitions can then be safely flashed.
|
||||
|
||||
|
||||
|
||||
Configuration
|
||||
=============
|
||||
|
||||
Levitate *needs to be configured when you create the initial system*
|
||||
to specify which services/packages/etc to run in maintenance
|
||||
mode. Most likely you want to configure a network interface and an ssh
|
||||
for example so that you can login to reflash it.
|
||||
|
||||
.. code-block:: nix
|
||||
|
||||
defaultProfile.packages = with pkgs; [
|
||||
...
|
||||
(levitate.override {
|
||||
config = {
|
||||
services = {
|
||||
inherit (config.services) dhcpc sshd watchdog;
|
||||
};
|
||||
defaultProfile.packages = [ mtdutils ];
|
||||
users.root = config.users.root;
|
||||
};
|
||||
})
|
||||
];
|
||||
|
||||
|
||||
|
||||
Use
|
||||
===
|
||||
|
||||
Connect (with ssh, probably) to the running Liminix system that you
|
||||
wish to upgrade.
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
bash$ ssh root@the-device
|
||||
|
||||
Run :command:`levitate`. This takes a little while (perhaps a few
|
||||
tens of seconds) to execute, and copies all config required for
|
||||
maintenance mode to :file:`/run/maintenance`.
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
# levitate
|
||||
|
||||
Reboot into maintenance mode. You will be logged out
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
# reboot
|
||||
|
||||
Connect to the device again - note that the ssh host key will have changed.
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
# ssh -o UserKnownHostsFile=/dev/null root@the-device
|
||||
|
||||
Check we're in maintenance mode
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
# cat /etc/banner
|
||||
|
||||
LADIES AND GENTLEMEN WE ARE FLOATING IN SPACE
|
||||
|
||||
Most services are disabled. The system is operating
|
||||
with a ram-based root filesystem, making it safe to
|
||||
overwrite the flash devices in order to perform
|
||||
upgrades and maintenance.
|
||||
|
||||
Don't forget to reboot when you have finished.
|
||||
|
||||
Perform the upgrade, using flashcp. This is an example,
|
||||
your device will differ
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
# cat /proc/mtd
|
||||
dev: size erasesize name
|
||||
mtd0: 00030000 00010000 "u-boot"
|
||||
mtd1: 00010000 00010000 "u-boot-env"
|
||||
mtd2: 00010000 00010000 "factory"
|
||||
mtd3: 00f80000 00010000 "firmware"
|
||||
mtd4: 00220000 00010000 "kernel"
|
||||
mtd5: 00d60000 00010000 "rootfs"
|
||||
mtd6: 00010000 00010000 "art"
|
||||
# flashcp -v firmware.bin mtd:firmware
|
||||
|
||||
All done
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
# reboot
|
||||
|
||||
|
|
|
@ -7,19 +7,19 @@
|
|||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
|
||||
|
||||
project = 'Liminix'
|
||||
copyright = '2023-2024 Daniel Barlow'
|
||||
copyright = '2023, Daniel Barlow'
|
||||
author = 'Daniel Barlow'
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
||||
|
||||
extensions = [
|
||||
# 'sphinx.ext.autosectionlabel'
|
||||
'sphinx.ext.autosectionlabel'
|
||||
]
|
||||
autosectionlabel_prefix_document = True
|
||||
|
||||
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
|
||||
#############
|
||||
|
||||
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
|
||||
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
|
||||
underlying system is not similar enough for them to work.
|
||||
|
||||
.. _configuration-services:
|
||||
|
||||
Services
|
||||
********
|
||||
|
||||
In Liminix a service is any kind of long-running task or process on
|
||||
the system, that is managed (started, stopped, and monitored) by a
|
||||
service supervisor. A typical SOHO router might have services to
|
||||
|
||||
* answer DHCP and DNS requests from the LAN
|
||||
* provide a wireless access point
|
||||
* connect using PPPoE or L2TP to an upstream network
|
||||
* start/stop the firewall
|
||||
* enable/disable IP packet forwarding
|
||||
* mount filesystems
|
||||
|
||||
(Some of these might not be considered services using other
|
||||
definitions of the term: for example, this L2TP process would be a
|
||||
"client" in the client/server classification; and enabling packet
|
||||
forwarding doesn't require any long-lived process - just a setting to
|
||||
be toggled. However, there is value in being able to use the same
|
||||
abstractions for all the things to manage them and specify their
|
||||
dependency relationships - so in Liminix "everything is a service")
|
||||
|
||||
The service supervision system enables service health monitoring,
|
||||
restart of unhealthy services, and failover to "backup" services when
|
||||
a primary service fails or its dependencies are unavailable. The
|
||||
intention is that you have a framework in which you can specify policy
|
||||
requirements like "ethernet wan dhcp-client should be restarted if it
|
||||
crashes, but if it can't start because the hardware link is down, then
|
||||
4G ppp service should be started instead".
|
||||
|
||||
Any attribute in `config.services` will become part of the default set
|
||||
of services that s6-rc will try to bring up. Services are usually
|
||||
started at boot time, but **controlled services** are those that are
|
||||
required only in particular contexts. For example, a service to mount
|
||||
a USB backup drive should run only when the drive is attached to the
|
||||
system. Liminix currently implements three kinds of controlled service:
|
||||
|
||||
* "uevent-rule" service controllers use sysfs/uevent to identify when
|
||||
particular hardware devices are present, and start/stop a controlled
|
||||
service appropriately.
|
||||
|
||||
* the "round-robin" service controller is used for service failover:
|
||||
it allows you to specify a list of services and runs each of them
|
||||
in turn until it exits, then runs the next.
|
||||
|
||||
* the "health-check" service wraps another service, and runs a "health
|
||||
check" command at regular intervals. When the health check fails,
|
||||
indicating that the wrapped service is not working, it is terminated
|
||||
and allowed to restart.
|
||||
|
||||
Runtime secrets (external vault)
|
||||
================================
|
||||
|
||||
Secrets (such as wifi passphrases, PPP username/password, SSH keys,
|
||||
etc) that you provide as literal values in :file:`configuration.nix`
|
||||
are processed into into config files and scripts at build time, and
|
||||
eventually end up in various files in the (world-readable)
|
||||
:file:`/nix/store` before being baked into a flashable image. To
|
||||
change a secret - whether due to a compromise, or just as part of to a
|
||||
routine key rotation - you need to rebuild the configuration and
|
||||
potentially reflash the affected devices.
|
||||
|
||||
To avoid this, you may instead use a "secrets service", which is a
|
||||
mechanism for your device to fetch secrets from a source external to
|
||||
the Nix store, and create at runtime the configuration files and
|
||||
scripts that start the services which require them.
|
||||
|
||||
Not every possible parameter to every possible service is configurable
|
||||
using a secrets service. Parameters which can be configured this way
|
||||
are those with the type ``liminix.lib.types.replacable``. At the time
|
||||
this document was written, these include:
|
||||
|
||||
* ppp (pppoe and l2tp): ``username``, ``password``
|
||||
* ssh: ``authorizedKeys``
|
||||
* hostapd: all parameters (most likely to be useful for ``wpa_passphrase``)
|
||||
|
||||
To use a runtime secret for any of these parameters:
|
||||
|
||||
* create a secrets service to specify the source of truth for secrets
|
||||
* use the :code:`outputRef` function in the service parameter to specify the secrets service and path
|
||||
|
||||
For example, given you had an HTTPS server hosting a JSON file with the structure
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
"ssh": {
|
||||
"authorizedKeys": {
|
||||
"root": [ "ssh-rsa ....", "ssh-rsa ....", ... ]
|
||||
"guest": [ "ssh-rsa ....", "ssh-rsa ....", ... ]
|
||||
}
|
||||
}
|
||||
|
||||
you could use a :file:`configuration.nix` fragment something like this
|
||||
to make those keys visible to ssh:
|
||||
|
||||
.. code-block:: nix
|
||||
|
||||
services.secrets = svc.secrets.outboard.build {
|
||||
name = "secret-service";
|
||||
url = "http://10.0.0.1/secrets.json";
|
||||
username = "secrets";
|
||||
password = "liminix";
|
||||
interval = 30; # minutes
|
||||
dependencies = [ config.services.lan ];
|
||||
};
|
||||
services.sshd = svc.ssh.build {
|
||||
authorizedKeys = outputRef config.services.secrets "ssh/authorizedKeys";
|
||||
};
|
||||
|
||||
|
||||
|
||||
There are presently two implementations of a secrets service:
|
||||
|
||||
Outboard secrets (HTTPS)
|
||||
------------------------
|
||||
|
||||
This service expects a URL to a JSON file containing all the secrets.
|
||||
|
||||
You may specify a username and password along with the URL, which are
|
||||
used if the file is password-protected (HTTP Basic
|
||||
authentication). Note that this is not a protection against a
|
||||
malicious local user: the username and password are normal build-time
|
||||
parameters so will be readable in the Nix store. This is a mitigation
|
||||
against the URL being accidentally discovered due to e.g. a log file
|
||||
or error message on the server leaking.
|
||||
|
||||
|
||||
Tang secrets (encrypted local file)
|
||||
-----------------------------------
|
||||
|
||||
Aternatively, secrets may be stored locally on the device, in a file
|
||||
that has been encrypted using `Tang <https://github.com/latchset/tang>`_.
|
||||
|
||||
Tang is a server for binding data to network presence.
|
||||
|
||||
This sounds fancy, but the concept is simple. You have some data, but you only want it to be available when the system containing the data is on a certain, usually secure, network.
|
||||
|
||||
|
||||
.. code-block:: nix
|
||||
|
||||
services.secrets = svc.secrets.tang.build {
|
||||
name = "secret-service";
|
||||
path = "/run/mnt/usbstick/secrets.json.jwe";
|
||||
interval = 30; # minutes
|
||||
dependencies = [ config.services.mount-usbstick ];
|
||||
};
|
||||
|
||||
The encryption uses the
|
||||
same scheme/algorithm as `Clevis <https://github.com/latchset/clevis>`_ : you may use the `Clevis instructions <https://github.com/latchset/clevis?tab=readme-ov-file#pin-tang>`_ to
|
||||
encrypt the file on another host and then copy it to your Liminix
|
||||
device, or you can use :command:`tangc encrypt` to encrypt directly on
|
||||
the device. (That latter approach may pose a chicken/egg problem if
|
||||
the device needs secrets to boot up and run the services you are
|
||||
relying on in order to login).
|
||||
|
||||
|
||||
Writing services
|
||||
================
|
||||
We use the `s6-rc service manager <https://www.skarnet.org/software/s6-rc/overview.html>`_ to start/stop/restart services and handle
|
||||
service dependencies. Any attribute in `config.services` will become
|
||||
part of the default set of services that s6-rc will try to bring up on
|
||||
boot.
|
||||
|
||||
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
|
||||
|
@ -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``,
|
||||
meaning that it won't be started until that other service is up.
|
||||
|
||||
Service outputs
|
||||
===============
|
||||
|
||||
Outputs are a mechanism by which a service can provide data which may
|
||||
be required by other services. For example:
|
||||
|
||||
* the DHCP client service can expect to receive nameserver address
|
||||
information as one of the fields in the response from the DHCP
|
||||
server: we provide that as an output which a dependent service for a
|
||||
stub name resolver can use to configure its upstream servers.
|
||||
|
||||
* a service that creates a new network interface (e.g. ppp) will
|
||||
provide the name of the interface (:code:`ppp0`, or :code:`ppp1` or
|
||||
:code:`ppp7`) as an output so that a dependent service can reference
|
||||
it to set up a route, or to configure firewall rules.
|
||||
|
||||
A service :code:`myservice` should write its outputs as files in
|
||||
:file:`/run/services/outputs/myservice`: you can look around this
|
||||
directory on a running Liminix system to see how it's used currently.
|
||||
Usually we use the :code:`in_outputs` shell function in the
|
||||
:command:`up` or :command:`run` attributes of the service:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
(in_outputs ${name}
|
||||
for i in lease mask ip router siaddr dns serverid subnet opt53 interface ; do
|
||||
(printenv $i || true) > $i
|
||||
done)
|
||||
|
||||
The outputs are just files, so technically you can read them using
|
||||
anything that can read a file. Liminix has two "preferred"
|
||||
mechanisms, though:
|
||||
|
||||
One-off lookups
|
||||
---------------
|
||||
|
||||
In any context that ends up being evaluated by the shell, use
|
||||
:code:`output` to print the value of an output
|
||||
|
||||
.. code-block:: nix
|
||||
|
||||
services.defaultroute4 = svc.network.route.build {
|
||||
via = "$(output ${services.wan} address)";
|
||||
target = "default";
|
||||
dependencies = [ services.wan ];
|
||||
};
|
||||
|
||||
|
||||
Continuous updates
|
||||
------------------
|
||||
|
||||
The downside of using shell functions in downstream service startup
|
||||
scripts is that they only run when the service starts up: if a service
|
||||
output *changes*, the downstream service would have to be restarted to
|
||||
notice the change. Sometimes this is OK but other times the downstream
|
||||
has no other need to restart, if it can only get its new data.
|
||||
|
||||
For this case, there is the :code:`anoia.svc` Fennel library, which
|
||||
allows you to write a simple loop which is iterated over whenever a
|
||||
service's outputs change. This code is from
|
||||
:file:`modules/dhcp6c/acquire-wan-address.fnl`
|
||||
|
||||
.. code-block:: fennel
|
||||
|
||||
(fn update-addresses [wan-device addresses new-addresses exec]
|
||||
;; run some appropriate "ip address [add|remove]" commands
|
||||
)
|
||||
|
||||
(fn run []
|
||||
(let [[state-directory wan-device] arg
|
||||
dir (svc.open state-directory)]
|
||||
(accumulate [addresses []
|
||||
v (dir:events)]
|
||||
(update-addresses wan-device addresses
|
||||
(or (v:output "address") []) system))))
|
||||
|
||||
The :code:`output` method seen here accepts a filename (relative
|
||||
to the service's output directory), or a directory name. It
|
||||
returns the first line of that file, or for directories it
|
||||
returns a table (Lua's key/value datastructure, similar to
|
||||
a hash/dictionary) of the outputs in that directory.
|
||||
|
||||
|
||||
Output design considerations
|
||||
----------------------------
|
||||
|
||||
For preference, outputs should be short and simple, and not require
|
||||
downstream services to do complicated parsing in order to use them.
|
||||
Shell commands in Liminix are run using the Busybox shell which
|
||||
doesn't have the niceties of an advanced shell like Bash let alone
|
||||
those of a real programming language.
|
||||
|
||||
Note also that the Lua :code:`svc` library only reads the first line
|
||||
of each output.
|
||||
..
|
||||
TODO: explain service outputs
|
||||
|
||||
..
|
||||
TODO: outputs that change, and services that poll other services
|
||||
|
||||
Module implementation
|
||||
*********************
|
||||
|
@ -415,7 +192,7 @@ To expose a service template in a module, it needs the following:
|
|||
|
||||
.. code-block:: nix
|
||||
|
||||
config.system.service.cowsay = config.system.callService ./service.nix {
|
||||
config.system.service.cowsay = liminix.callService ./service.nix {
|
||||
address = mkOption {
|
||||
type = types.str;
|
||||
default = "0.0.0.0";
|
||||
|
|
|
@ -88,6 +88,64 @@ time with configurations for RP-PPPoE and/or Accel PPP.`
|
|||
Hardware devices
|
||||