Compare commits

...

25 Commits

Author SHA1 Message Date
Daniel Barlow 67080627a1 fix wireless network names 2024-03-20 18:27:10 +00:00
Daniel Barlow 269c9cd916 firewall -> profile 2024-03-20 18:18:34 +00:00
Daniel Barlow 95ebddb661 move default routes to gateway profile 2024-03-20 18:13:33 +00:00
Daniel Barlow a66510c9e6 deep thoughts 2024-03-20 18:13:09 +00:00
Daniel Barlow 75aa01513c move dnsmasq -> gateway profile 2024-03-18 21:44:54 +00:00
Daniel Barlow d3fd6f5c0a move dhcp6 client to profile 2024-03-18 21:26:13 +00:00
Daniel Barlow 90302f0944 delete commented code 2024-03-18 21:18:48 +00:00
Daniel Barlow 00bf3446f1 add pppoe to gateway profile 2024-03-18 20:58:34 +00:00
Daniel Barlow 0d8abbc314 extneder: delete kernel firewall config
we don't need nftables for a bridge
2024-03-18 20:13:22 +00:00
Daniel Barlow 4c3883a5e7 remove sshd from wap profile
It's not actually part of providing a wireless access point.
2024-03-18 20:08:24 +00:00
Daniel Barlow 2ecaa3bef8 delete commented code 2024-03-18 20:02:24 +00:00
Daniel Barlow 5dd595a209 configure hostapds in gateway profile 2024-03-18 19:52:37 +00:00
Daniel Barlow 0b61c0b318 create gateway profile by extracting from rotuer example 2024-03-18 00:05:43 +00:00
Daniel Barlow 78efd6dfcf expose modulesPath to ease out-of-tree configuration.nix 2024-03-17 19:03:44 +00:00
Daniel Barlow 77421a5d3b ifwait block if s6-rc lock is held
otherwise it doesn't trigger the service if something else is
slow to start
2024-03-16 23:09:41 +00:00
Daniel Barlow 74f4577521 support timeouts (default 30s) for starting s6-rc services 2024-03-16 23:08:59 +00:00
Daniel Barlow 3ec5245fa4 implement ifwait trigger service and use in bridge
should we convert all ifwait uses to this trigger too? seems
reasonable
2024-03-16 20:41:13 +00:00
Daniel Barlow c223e5cea2 allow buildInputs param to s6 service
this is in preparation for trigger services that need to
close over the triggered service without adding it to
s6-rc dependencies
2024-03-16 20:25:29 +00:00
Daniel Barlow 921be2f3c8 add config.system.callService
this is like pkgs.callService except that it passes
config.system.service as a param so that the service
being defined can invoke other services

if this proves to be a good idea, all uses of
pkgs.callService should be changed to use it instead
2024-03-16 20:23:18 +00:00
Daniel Barlow da64f64ee9 deep thoughts 2024-03-16 20:22:30 +00:00
Daniel Barlow c46e228c60 failing test for ifwait 2024-03-12 23:41:46 +00:00
Daniel Barlow 0386452ddc deep thoughts 2024-03-12 23:40:49 +00:00
Daniel Barlow 4c549ac625 fix ifwait deps 2024-03-11 22:16:49 +00:00
Daniel Barlow 4d0061e90a deep thoughts 2024-03-08 22:37:26 +00:00
Daniel Barlow af966056c7 temporary fix for cmake cross-compilation 2024-03-08 22:36:42 +00:00
21 changed files with 561 additions and 204 deletions

View File

@ -4289,3 +4289,56 @@ Wed Mar 6 18:24:29 GMT 2024
What happens when we attempt to start the service but it fails? We
assume the start was successful so we won't try and restart it again
next time we get an event that should cause it to start.
Thu Mar 7 11:48:26 GMT 2024
what next?
- fennel script needs to know where s6-rc is
- some nix syntax
- update bridge module members.nix to use the new thing
I can't find a ci derivation that uses the bridge.
Mon Mar 11 20:31:45 GMT 2024
Create a qemu config where wan and lan devices are bridged into a
single bridge
start qemu paused
Use qemu monitor commands to no-carrier the network devices
set_link virtio-net-pci.1 off
set_link virtio-net-pci.0 off
Boot the system
See if both devices are bridge members
See if reboot is possible
Use qemu monitor commands to enable the network devices
set_link virtio-net-pci.1 on
set_link virtio-net-pci.0 on
See if both devices are bridge members
disable again,check if back to starting position
Wed Mar 13 00:00:16 GMT 2024
aside: "trigger" is the least bad word I've thought of so far for
these services that stop/start other services
telent: yeah, in general 'ps afuxww' (or s6-ps -H :)) is the way to solve this, look for hung s6-rc processes and in particular their s6-svlisten1 children, where the command line will show what service is still waiting for readiness
Sat Mar 16 23:28:10 GMT 2024
We have the gl-ar750 booting with the bridge members added by trigger, and
it seems to work.
One thing worth flagging is that we have to run s6-rc with the -b
(block if locked) flag in the trigger otherwise the triggered service
just doesn't come up if there's something else starting at the same
time. There are warnings in the s6-rc docs about doing this, though.
We need to think hard about whether we can end up calling s6-rc recursively

View File

@ -20,6 +20,9 @@ let
});
eval = pkgs.lib.evalModules {
specialArgs = {
modulesPath = builtins.toString ./modules;
};
modules = [
{ _module.args = { inherit pkgs; inherit (pkgs) lim; }; }
./modules/hardware.nix

View File

@ -8,9 +8,11 @@
config,
pkgs,
lib,
modulesPath,
...
}: let
secrets = import ./extneder-secrets.nix;
svc = config.system.service;
in rec {
boot = {
tftp = {
@ -20,47 +22,13 @@ in rec {
};
imports = [
../modules/profiles/wap.nix
../modules/vlan
"${modulesPath}/profiles/wap.nix"
"${modulesPath}/vlan"
"${modulesPath}/ssh"
];
hostname = "extneder";
kernel = {
config = {
NETFILTER_XT_MATCH_CONNTRACK = "y";
IP6_NF_IPTABLES = "y"; # do we still need these
IP_NF_IPTABLES = "y"; # if using nftables directly
# these are copied from rotuer and need review.
# we're not running a firewall, so why do we need
# nftables config?
IP_NF_NAT = "y";
IP_NF_TARGET_MASQUERADE = "y";
NETFILTER = "y";
NETFILTER_ADVANCED = "y";
NETFILTER_XTABLES = "y";
NFT_COMPAT = "y";
NFT_CT = "y";
NFT_LOG = "y";
NFT_MASQ = "y";
NFT_NAT = "y";
NFT_REJECT = "y";
NFT_REJECT_INET = "y";
NF_CONNTRACK = "y";
NF_NAT = "y";
NF_NAT_MASQUERADE = "y";
NF_TABLES = "y";
NF_TABLES_INET = "y";
NF_TABLES_IPV4 = "y";
NF_TABLES_IPV6 = "y";
};
};
profile.wap = {
interfaces = with config.hardware.networkInterfaces; [
lan
@ -79,6 +47,7 @@ in rec {
};
};
services.sshd = svc.ssh.build {};
users.root.passwd = lib.mkForce secrets.root.passwd;
defaultProfile.packages = with pkgs; [nftables strace tcpdump swconfig];
}

View File

@ -6,23 +6,18 @@
# problems.
{ config, pkgs, lib, ... } :
{ config, pkgs, lib, modulesPath, ... } :
let
secrets = {
domainName = "fake.liminix.org";
firewallRules = {};
} // (import ./rotuer-secrets.nix);
inherit (pkgs.liminix.services) oneshot longrun bundle;
inherit (pkgs.liminix.services) oneshot bundle;
inherit (pkgs) serviceFns;
svc = config.system.service;
wirelessConfig = {
country_code = "GB";
inherit (secrets) wpa_passphrase;
auth_algs = 1; # 1=wpa2, 2=wep, 3=both
wpa = 2; # 1=wpa, 2=wpa2, 3=both
wpa_key_mgmt = "WPA-PSK";
wpa_pairwise = "TKIP CCMP"; # auth for wpa (may not need this?)
rsn_pairwise = "CCMP"; # auth for wpa2
wmm_enabled = 1;
};
@ -36,65 +31,64 @@ in rec {
};
imports = [
../modules/wlan.nix
../modules/network
../modules/ppp
../modules/dnsmasq
../modules/dhcp6c
../modules/firewall
../modules/hostapd
../modules/bridge
../modules/ntp
../modules/schnapps
../modules/ssh
../modules/outputs/btrfs.nix
../modules/outputs/extlinux.nix
"${modulesPath}/profiles/gateway.nix"
"${modulesPath}/schnapps"
"${modulesPath}/outputs/btrfs.nix"
"${modulesPath}/outputs/extlinux.nix"
];
hostname = "rotuer";
rootfsType = "btrfs";
rootOptions = "subvol=@";
boot.loader.extlinux.enable = true;
services.hostap = svc.hostapd.build {
interface = config.hardware.networkInterfaces.wlan;
params = {
ssid = secrets.ssid;
hw_mode="g";
channel = "2";
ieee80211n = 1;
} // wirelessConfig;
};
services.hostap5 = svc.hostapd.build {
interface = config.hardware.networkInterfaces.wlan5;
params = rec {
ssid = "${secrets.ssid}5";
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;
};
services.int = svc.network.address.build {
interface = svc.bridge.primary.build { ifname = "int"; };
family = "inet"; address ="${secrets.lan.prefix}.1"; prefixLength = 24;
};
services.bridge = svc.bridge.members.build {
primary = services.int;
members = with config.hardware.networkInterfaces;
[ wlan
wlan5
lan0
lan1
lan2
lan3
lan4
];
profile.gateway = {
lan = {
interfaces = with config.hardware.networkInterfaces;
[
wlan wlan5
lan0 lan1 lan2 lan3 lan4
];
inherit (secrets.lan) prefix;
address = {
family = "inet"; address ="${secrets.lan.prefix}.1"; prefixLength = 24;
};
dhcp = {
start = 10;
end = 240;
hosts = { } // lib.optionalAttrs (builtins.pathExists ./static-leases.nix) (import ./static-leases.nix);
localDomain = "lan";
};
};
wan = {
interface = config.hardware.networkInterfaces.wan;
username = secrets.l2tp.name;
password = secrets.l2tp.password;
dhcp6.enable = true;
};
firewall = {
enable = true;
rules =
let defaults = import ./demo-firewall.nix;
in lib.recursiveUpdate defaults secrets.firewallRules;
};
wireless.networks = {
"${secrets.ssid}" = {
interface = config.hardware.networkInterfaces.wlan;
hw_mode="g";
channel = "2";
ieee80211n = 1;
} // wirelessConfig;
"${secrets.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;
};
};
services.ntp = svc.ntp.build {
@ -106,95 +100,6 @@ in rec {
users.root = secrets.root;
services.dns =
let interface = services.int;
in svc.dnsmasq.build {
resolvconf = services.resolvconf;
inherit interface;
ranges = [
"${secrets.lan.prefix}.10,${secrets.lan.prefix}.240"
# ra-stateless: sends router advertisements with the O and A
# bits set, and provides a stateless DHCP service. The client
# will use a SLAAC address, and use DHCP for other
# configuration information.
"::,constructor:$(output ${interface} ifname),ra-stateless"
];
# You can add static addresses for the DHCP server here. I'm
# not putting my actual MAC addresses in a public git repo ...
hosts = { } // lib.optionalAttrs (builtins.pathExists ./static-leases.nix) (import ./static-leases.nix);
upstreams = [ "/${secrets.domainName}/" ];
domain = secrets.domainName;
};
services.wan = svc.pppoe.build {
interface = config.hardware.networkInterfaces.wan;
ppp-options = [
"debug" "+ipv6" "noauth"
"name" secrets.l2tp.name
"password" secrets.l2tp.password
];
};
services.resolvconf = oneshot rec {
dependencies = [ services.wan ];
name = "resolvconf";
up = ''
. ${serviceFns}
( in_outputs ${name}
echo "nameserver $(output ${services.wan} ns1)" > resolv.conf
echo "nameserver $(output ${services.wan} ns2)" >> resolv.conf
chmod 0444 resolv.conf
)
'';
};
filesystem =
let inherit (pkgs.pseudofile) dir symlink;
in dir {
etc = dir {
"resolv.conf" = symlink "${services.resolvconf}/.outputs/resolv.conf";
};
};
services.defaultroute4 = svc.network.route.build {
via = "$(output ${services.wan} address)";
target = "default";
dependencies = [ services.wan ];
};
services.defaultroute6 = svc.network.route.build {
via = "$(output ${services.wan} ipv6-peer-address)";
target = "default";
interface = services.wan;
};
services.firewall = svc.firewall.build {
ruleset =
let defaults = import ./demo-firewall.nix;
in lib.recursiveUpdate defaults secrets.firewallRules;
};
services.packet_forwarding = svc.network.forward.build { };
services.dhcp6c =
let client = svc.dhcp6c.client.build {
interface = services.wan;
};
in bundle {
name = "dhcp6c";
contents = [
(svc.dhcp6c.prefix.build {
inherit client;
interface = services.int;
})
(svc.dhcp6c.address.build {
inherit client;
interface = services.wan;
})
];
};
defaultProfile.packages = with pkgs; [
min-collect-garbage
nftables

View File

@ -29,6 +29,10 @@ in {
services = mkOption {
type = types.attrsOf type_service;
};
system.callService = mkOption {
type = types.functionTo (types.functionTo types.anything);
};
filesystem = mkOption {
type = types.anything;
description = ''
@ -111,6 +115,31 @@ in {
"fw_devlink=off"
] ++ lib.optional (config.rootOptions != null) "rootflags=${config.rootOptions}";
system.callService = path : parameters :
let
typeChecked = caller: type: value:
let
inherit (lib) types mergeDefinitions;
defs = [{ file = caller; inherit value; }];
type' = types.submodule { options = type; };
in (mergeDefinitions [] type' defs).mergedValue;
cp = lib.callPackageWith(pkgs // { svc = config.system.service; });
pkg = cp path {};
checkTypes = t : p : typeChecked (builtins.toString path) t p;
in {
inherit parameters;
build = { dependencies ? [], ... } @ args :
let
s = pkg (checkTypes parameters
(builtins.removeAttrs args ["dependencies"]));
in s.overrideAttrs (o: {
dependencies = (builtins.map (d: d.name) dependencies) ++ o.dependencies;
buildInputs = dependencies ++ o.buildInputs;
});
};
users.root = {
uid = 0; gid= 0; gecos = "Root of all evaluation";
dir = "/home/root/";

View File

@ -14,6 +14,8 @@ let
inherit (pkgs) liminix;
in
{
imports = [ ../ifwait ];
options = {
system.service.bridge = {
primary = mkOption { type = liminix.lib.types.serviceDefn; };
@ -27,7 +29,7 @@ in
description = "bridge interface name to create";
};
};
members = liminix.callService ./members.nix {
members = config.system.callService ./members.nix {
primary = mkOption {
type = liminix.lib.types.interface;
description = "primary bridge interface";
@ -47,5 +49,5 @@ in
# a better way to test for the existence of vlan config:
# maybe the module should set an `enabled` attribute?
BRIDGE_VLAN_FILTERING = "y";
};
};
}

View File

@ -2,6 +2,7 @@
liminix
, ifwait
, lib
, svc
}:
{ members, primary } :
@ -10,14 +11,20 @@ let
inherit (liminix.services) bundle oneshot;
inherit (lib) mkOption types;
addif = member :
oneshot {
name = "${primary.name}.member.${member.name}";
up = ''
dev=$(output ${member} ifname)
${ifwait}/bin/ifwait $dev running && ip link set dev $dev master $(output ${primary} ifname)
'';
down = "ip link set dev $(output ${member} ifname) nomaster";
# how do we get sight of services from here? maybe we need to
# implement ifwait as a regualr derivation instead of a
# servicedefinition
svc.ifwait.build {
state = "running";
interface = member;
dependencies = [ primary member ];
service = oneshot {
name = "${primary.name}.member.${member.name}";
up = ''
ip link set dev $(output ${member} ifname) master $(output ${primary} ifname)
'';
down = "ip link set dev $(output ${member} ifname) nomaster";
};
};
in bundle {
name = "${primary.name}.members";

View File

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

15
modules/ifwait/ifwait.nix Normal file
View File

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

View File

@ -0,0 +1,199 @@
{ config, pkgs, lib, ... } :
let
svc = config.system.service;
cfg = config.profile.gateway;
inherit (lib) mkOption mkEnableOption mkIf mdDoc types optional optionals;
inherit (pkgs) liminix serviceFns;
inherit (liminix.services) bundle oneshot;
hostaps =
let
defaults = {
auth_algs = 1; # 1=wpa2, 2=wep, 3=both
wpa = 2; # 1=wpa, 2=wpa2, 3=both
wpa_key_mgmt = "WPA-PSK";
wpa_pairwise = "TKIP CCMP"; # auth for wpa (may not need this?)
rsn_pairwise = "CCMP"; # auth for wpa2
};
in lib.mapAttrs'
(name : value :
let
attrs = defaults // { ssid = name; } // value;
in lib.nameValuePair
"hostap-${name}"
(svc.hostapd.build {
interface = attrs.interface;
params = lib.filterAttrs (k: v: k != "interface") attrs;
}))
cfg.wireless.networks;
in {
options.profile.gateway = {
lan = {
interfaces = mkOption {
type = types.listOf liminix.lib.types.interface;
default = [];
};
address = mkOption {
type = types.attrs;
};
prefix = mkOption { type = types.str; };
dhcp = {
start = mkOption { type = types.int; };
end = mkOption { type = types.int; };
hosts = mkOption { type = types.attrs; };
localDomain = mkOption { type = types.str; };
};
};
firewall = {
enable = mkEnableOption "firewall";
rules = mkOption { type = types.attrsOf types.attrs; };
};
wan = {
interface = mkOption { type = liminix.lib.types.interface; };
username = mkOption { type = types.str; };
password = mkOption { type = types.str; };
dhcp6.enable = mkOption { type = types.bool; };
};
wireless = mkOption {
type = types.attrsOf types.anything;
};
};
imports = [
../wlan.nix
../network
../ppp
../dnsmasq
../dhcp6c
../firewall
../hostapd
../bridge
../ntp
../ssh
{ config.services = hostaps; }
];
config = {
services.int = svc.network.address.build ({
interface = svc.bridge.primary.build { ifname = "int"; };
} // cfg.lan.address);
services.bridge = svc.bridge.members.build {
primary = config.services.int;
members = cfg.lan.interfaces;
};
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.dhcp6c =
let
client = svc.dhcp6c.client.build {
interface = config.services.wan;
};
bundl = bundle {
name = "dhcp6c";
contents = [
(svc.dhcp6c.prefix.build {
inherit client;
interface = config.services.int;
})
(svc.dhcp6c.address.build {
inherit client;
interface = config.services.wan;
})
];
};
in mkIf cfg.wan.dhcp6.enable bundl;
services.dns =
let interface = config.services.int;
dcfg = cfg.lan.dhcp;
in svc.dnsmasq.build {
resolvconf = config.services.resolvconf;
inherit interface;
ranges = [
"${cfg.lan.prefix}.${toString dcfg.start},${cfg.lan.prefix}.${toString dcfg.end}"
# ra-stateless: sends router advertisements with the O and A
# bits set, and provides a stateless DHCP service. The client
# will use a SLAAC address, and use DHCP for other
# configuration information.
"::,constructor:$(output ${interface} ifname),ra-stateless"
];
hosts = dcfg.hosts;
upstreams = [ "/${dcfg.localDomain}/" ];
domain = dcfg.localDomain;
};
services.defaultroute4 = svc.network.route.build {
via = "$(output ${config.services.wan} address)";
target = "default";
dependencies = [ config.services.wan ];
};
services.defaultroute6 = svc.network.route.build {
via = "$(output ${config.services.wan} ipv6-peer-address)";
target = "default";
interface = config.services.wan;
};
services.firewall = mkIf cfg.firewall.enable
(svc.firewall.build {
ruleset = cfg.firewall.rules;
});
services.resolvconf = oneshot rec {
dependencies = [ config.services.wan ];
name = "resolvconf";
up = ''
. ${serviceFns}
( in_outputs ${name}
echo "nameserver $(output ${config.services.wan} ns1)" > resolv.conf
echo "nameserver $(output ${config.services.wan} ns2)" >> resolv.conf
chmod 0444 resolv.conf
)
'';
};
filesystem =
let inherit (pkgs.pseudofile) dir symlink;
in dir {
etc = dir {
"resolv.conf" = symlink "${config.services.resolvconf}/.outputs/resolv.conf";
};
};
};
# services.defaultroute4 = svc.network.route.build {
# via = "$(output ${services.wan} address)";
# target = "default";
# dependencies = [ services.wan ];
# };
# services.defaultroute6 = svc.network.route.build {
# via = "$(output ${services.wan} ipv6-peer-address)";
# target = "default";
# interface = services.wan;
# };
# services.firewall = svc.firewall.build {
# ruleset =
# let defaults = import ./demo-firewall.nix;
# in lib.recursiveUpdate defaults secrets.firewallRules;
# };
}

View File

@ -40,7 +40,6 @@ in {
../network
../hostapd
../bridge
../ssh
{ config.services = hostaps; }
];
@ -54,7 +53,6 @@ in {
};
};
config = {
services.sshd = svc.ssh.build {};
services.int = svc.bridge.primary.build {
ifname = "int";

View File

@ -77,6 +77,22 @@ extraPkgs // {
};
# luarocks wants a cross-compiled cmake (which seems like a bug,
# we're never going to run luarocks on the device, but ...)
# but https://github.com/NixOS/nixpkgs/issues/284734
# so we do surgery on the cmake derivation until that's fixed
cmake = prev.cmake.overrideAttrs(o:
# don't override the build cmake or we'll have to rebuild
# half the known universe to no useful benefit
if final.stdenv.buildPlatform != final.stdenv.hostPlatform
then {
preConfigure =
builtins.replaceStrings
["$configureFlags"] ["$configureFlags $cmakeFlags"] o.preConfigure;
}
else {}
);
dnsmasq =
let d = prev.dnsmasq.overrideAttrs(o: {

View File

@ -1,6 +1,6 @@
(local netlink (require :netlink))
(local { : view } (require :fennel))
; (local { : view } (require :fennel))
(fn events [groups]
(let [sock (netlink.socket)]

View File

@ -3,8 +3,9 @@
, netlink-lua
, writeFennelScript
, runCommand
, anoia
}:
runCommand "ifwait" {} ''
mkdir -p $out/bin
cp -p ${writeFennelScript "ifwait" [netlink-lua] ./ifwait.fnl} $out/bin/ifwait
cp -p ${writeFennelScript "ifwait" [anoia netlink-lua] ./ifwait.fnl} $out/bin/ifwait
''

View File

@ -37,8 +37,8 @@
(when (not (= up wanted?))
(set up
(if wanted?
(pcall system (.. "s6-rc -u change " service))
(not (pcall system (.. "s6-rc -d change " service)))))
(pcall system (.. "s6-rc -b -u change " service))
(not (pcall system (.. "s6-rc -b -d change " service)))))
))
(fn run [args event-fn]

View File

@ -11,7 +11,7 @@ test -n "$contents" && for d in $contents; do
touch $out/${name}/contents.d/$d
done
for i in run notification-fd up down consumer-for producer-for pipeline-name ; do
for i in timeout-up timeout-down run notification-fd up down consumer-for producer-for pipeline-name ; do
test -n "$(printenv $i)" && (echo "$(printenv $i)" > $out/${name}/$i)
done

View File

@ -31,6 +31,8 @@ let
, producer-for ? null
, consumer-for ? null
, pipeline-name ? null
, timeout-up ? 30000 # milliseconds
, timeout-down ? 0
, dependencies ? []
, contents ? []
, buildInputs ? []
@ -39,7 +41,7 @@ let
# we use stdenvNoCC to avoid generating derivations with names
# like foo.service-mips-linux-musl
inherit name serviceType up down run notification-fd
producer-for consumer-for pipeline-name;
producer-for consumer-for pipeline-name timeout-up timeout-down;
buildInputs = buildInputs ++ dependencies ++ contents;
dependencies = builtins.map (d: d.name) dependencies;
contents = builtins.map (d: d.name) contents;
@ -52,6 +54,7 @@ let
, outputs ? []
, notification-fd ? null
, dependencies ? []
, buildInputs ? []
, ...
} @ args:
let logger = service {
@ -63,7 +66,7 @@ let
pipeline-name = "${name}-pipeline";
};
in service (args // {
buildInputs = [ logger ];
buildInputs = buildInputs ++ [ logger ];
serviceType = "longrun";
run = serviceScript "${run}\n${cleanupScript name}";
producer-for = "${name}-log";

View File

@ -8,4 +8,5 @@
min-copy-closure = import ./min-copy-closure/test.nix;
fennel = import ./fennel/test.nix;
tftpboot = import ./tftpboot/test.nix;
updown = import ./updown/test.nix;
}

View File

@ -0,0 +1,55 @@
{ config, pkgs, lib, ... } :
let
inherit (pkgs.liminix.services) bundle oneshot longrun;
inherit (pkgs) serviceFns;
# EDIT: you can pick your preferred RFC1918 address space
# for NATted connections, if you don't like this one.
ipv4LocalNet = "10.8.0";
svc = config.system.service;
in rec {
imports = [
../../modules/bridge
../../modules/dhcp6c
../../modules/dnsmasq
../../modules/firewall
../../modules/hostapd
../../modules/network
../../modules/ssh
../../modules/vlan
../../modules/wlan.nix
];
rootfsType = "jffs2";
hostname = "updown";
services.int = svc.network.address.build {
interface = svc.bridge.primary.build { ifname = "int"; };
family = "inet"; address = "${ipv4LocalNet}.1"; prefixLength = 16;
};
services.bridge = svc.bridge.members.build {
primary = services.int;
members = with config.hardware.networkInterfaces;
[ lan ];
};
services.sshd = svc.ssh.build { };
# users.root = {
# # EDIT: choose a root password and then use
# # "mkpasswd -m sha512crypt" to determine the hash.
# # It should start wirh $6$.
# passwd = "$6$6HG7WALLQQY1LQDE$428cnouMJ7wVmyK9.dF1uWs7t0z9ztgp3MHvN5bbeo0M4Kqg/u2ThjoSHIjCEJQlnVpDOaEKcOjXAlIClHWN21";
# openssh.authorizedKeys.keys = [
# # EDIT: you can add your ssh pubkey here
# # "ssh-rsa AAAAB3NzaC1....H6hKd user@example.com";
# ];
# };
defaultProfile.packages = with pkgs; [
min-collect-garbage
# strace
# ethtool
tcpdump
];
}

View File

@ -0,0 +1,64 @@
set timeout 10
spawn socat unix-connect:vm/monitor -
set monitor_id $spawn_id
expect "(qemu)"
send "set_link virtio-net-pci.1 off\n"
expect "(qemu)"
send "set_link virtio-net-pci.0 off\n"
expect "(qemu)"
send "c\r\n"
spawn socat unix-connect:vm/console -
set console_id $spawn_id
expect "BusyBox"
expect "#" { send "PS1=RE\\ADY_\\ ; stty -echo \r" }
expect "READY_" { send "s6-rc -b -a list\r" } ; # -b waits for s6-rc lock
expect "READY_" { send "ls /sys/class/net/lan/master\r" }
expect {
"No such file or directory" { }
timeout { exit 1 }
}
expect "READY_" { send "cat /sys/class/net/lan/operstate\r" }
expect {
"down" { }
"up" { exit 1 }
}
expect "READY_" { send "s6-rc -a -u change\r" }
expect {
"unable to take locks" { exit 1 }
"READY_" { send "\r" }
}
set spawn_id $monitor_id
send "\r"
expect "(qemu)"
send "set_link virtio-net-pci.1 on\n"
expect "(qemu)"
send "set_link virtio-net-pci.0 on\n"
expect "(qemu)"
set spawn_id $console_id
expect "entered forwarding state"
send "\r"
expect "READY_" { send "cat /sys/class/net/lan/operstate\r" }
expect {
"down" { exit 1 }
"up" { }
}
expect "READY_" { send "cat /sys/class/net/lan/master/uevent\r" }
expect {
"INTERFACE=int" { }
timeout { exit 1 }
}
expect "READY_" { send "s6-rc listall int.link.a.10.8.0.1.member.lan.link ; hostname\r" }
expect {
"updown" {}
timeout { exit 1 }
}

19
tests/updown/test.nix Normal file
View File

@ -0,0 +1,19 @@
{
liminix
, nixpkgs
}:
let img = (import liminix {
device = import "${liminix}/devices/qemu/";
liminix-config = ./configuration.nix;
}).outputs.vmroot;
pkgs = import <nixpkgs> { overlays = [(import ../../overlay.nix)]; };
in pkgs.runCommand "check" {
nativeBuildInputs = with pkgs; [
expect
socat
] ;
} ''
mkdir vm
${img}/run.sh --flag -S --background ./vm
expect ${./script.expect} | tee $out
''