Compare commits

..

No commits in common. "main" and "main" have entirely different histories.
main ... main

102 changed files with 482 additions and 4177 deletions

25
NEWS
View File

@ -34,7 +34,7 @@ Upstream changes that have led to incompatible Liminix changes are:
2024-01-30
New port! Thanks to Arnout Engelen <arnout@bzzt.net>, Liminix
now runs on the TP-Link Archer AX23.
now runs on the TP-Link Archer AX23
2024-02-12
@ -80,26 +80,3 @@ Turris Omnia and has been serving my family's internet needs for most
of this week. Thanks to NGI0 Entrust and the NLnet Foundation for
sponsoring this development (and funding the hardware)
2024-02-21
New port! Thanks to Raito Bezarius, Liminix now runs on the Zyxel NWA50AX,
an MT7621 (MIPS EL) dual radio WiFi AP.
2024-04-29
The setup for using `levitate` has changed: now it accepts an entire
config fragment, not just a list of services. Hopefully this makes it
a bit more useful :-)
defaultProfile.packages = with pkgs; [
...
(levitate.override {
config = {
services = {
inherit (config.services) dhcpc sshd watchdog;
};
defaultProfile.packages = [ mtdutils ];
users.root.openssh.authorizedKeys.keys = secrets.root.keys;
};
})
];

View File

@ -4111,633 +4111,3 @@ that is no longer in new-addresses
If the upstream has changed length, "ip addr change" is ignored,
so it needs to be in deleted as well as added/changed
Fri Feb 16 19:37:08 GMT 2024
[ 3.839775] cfg80211: module verification failed: signature and/or required key missing - tainting kernel
[ 4.156952] ath10k_pci 0000:00:00.0: enabling device (0000 -> 0002)
[ 4.165756] ath10k_pci 0000:00:00.0: pci irq legacy oper_irq_mode 1 irq_mode 0 reset_mode 0
[ 4.399285] ath10k_pci 0000:00:00.0: qca9887 hw1.0 target 0x4100016d chip_id 0x004000ff sub 0000:0000
[ 4.408906] ath10k_pci 0000:00:00.0: kconfig debug 1 debugfs 0 tracing 0 dfs 0 testmode 0
[ 4.420096] ath10k_pci 0000:00:00.0: firmware ver 10.2.4-1.0-00047 api 5 features no-p2p,ignore-otp,ski
p-clock-init,mfp,allows-mesh-bcast crc32 62f7565f
[ 4.467443] ath10k_pci 0000:00:00.0: board_file api 1 bmi_id N/A crc32 546cca0d
[ 5.472096] ath10k_pci 0000:00:00.0: htt-ver 2.1 wmi-op 5 htt-op 2 cal file max-sta 128 raw 0 hwcrypto
[ 5.585796] ath: EEPROM regdomain: 0x0
[ 5.589712] ath: EEPROM indicates default country code should be used
[ 5.596364] ath: doing EEPROM country->regdmn map search
[ 5.601875] ath: country maps to regdmn code: 0x3a
[ 5.606831] ath: Country alpha2 being used: US
[ 5.611425] ath: Regpair used: 0x3a
[ 6.742365] ath10k_pci 0000:00:00.0: pdev param 0 not supported by firmware
[ 6.903389] random: hostapd: uninitialized urandom read (1027 bytes read)
[ 8.169901] ath10k_pci 0000:00:00.0: pdev param 0 not supported by firmware
[ 14.450193] ath10k_pci 0000:00:00.0: pdev param 0 not supported by firmware
[ 15.518682] random: hostapd: uninitialized urandom read (1027 bytes read)
[ 16.762697] ath10k_pci 0000:00:00.0: pdev param 0 not supported by firmware
[ 23.030622] ath10k_pci 0000:00:00.0: pdev param 0 not supported by firmware
[
Tue Feb 27 23:16:27 GMT 2024
We made it a full week with rotuer running internet chez nous and no
need for an intervention, so I am happy to call it "production". There are
still things that need fixing but they're mostly within scope for
a services refresh
I have embarked on "profiles" by creating a wap.nix
I think we could have a service module for resolvconf
It would be good to build a wap.nix example for the belkin and we
could start looking at ubifs
I've lost a chunk of notes about using events to drive desired service
state. There is probably only going to be one udev listener, so
what if we have udev as a config key thusly
udev.rules = [
{
match = {
SUBSYSTEM = "rpmsg";
ATTR.name = "DATA5_CNTL";
};
service = longrun {
name = "lte-modem";
run = "blah blah blah";
};
}
# this one would be provided by the bridge module instead of
# adding bridge member services to the default target
{
match = {
SUBSYSTEM="net";
ID_PATH="pci-0000:04:00.0";
ATTR.operstate = "up";
};
service = oneshot {
up = "ip link set dev $dev master $(output ${primary} ifname)";
down = "ip link set dev $(output ${member} ifname) nomaster";
}
}
]
This works for udev/sysfs, but we want a similar architecture(sic) for
user-generated target state so we could have services that run on e.g.
"is the ppp0 service healthy" or not. Probably there isn't a top-level
config key for each service though
services.wan = svc.ppoe.build { .... };
services.lte = watcher.build {
watching = services.wan;
match = {
# an expression matching the outputs of the service
# to be watched
health = "failing";
};
service = oneshot {
run = "start_lte_blah";
};
}
thing is, we could use this syntax also for sysfs watches, but not vice versa
... but it's not quite the same because here we're doing static matches
on contents of files, whereas the udev one is a query expression on the
sysfs database. we might need that flexibiity to implement "mount the
backup drive no matter _which_ damn sda_n_ device it appears as". I don't
know if there's the same need for service outputs - postulate the
existence of a collection of services which are all similar enough that
some other service can watch them all and do $something when one of
the changes state. Or a single service with very complicated outputs.
For example, something could watch the snmp database and update service
status depending on what it finds. Or something something mqtt...
we find that the "match" needs to be interpreted differently according
to the thing being watched. perhaps the service being watched needs to
provide a "watch me" interface somehow which accepts match criteria and
outputs a true/false. Something else then needs to
services.addmember = services.udev.watch {
match = {
SUBSYSTEM = "net";
ID_PATH = "pci-0000:04:00.0";
ATTR.operstate = "up";
};
service = oneshot {
up = "ip link set dev $dev master $(output ${primary} ifname)";
down = "ip link set dev $(output ${member} ifname) nomaster";
};
}
Sat Mar 2 15:37:29 GMT 2024
Simply put, what I think it boils down to is that we want a service
which acts as an actuator or control switch for another service,
and will start/stop that controlled service according to some
criteria.
services.addmember = svc.network.ifwatch.build {
interface = config.hardware.networkInterfaces.lan1;
# this should be part of the definition not the params
service = oneshot {
name = "member-${bridge}-${interface}";
up = "ip link set dev $dev master $(output ${primary} ifname)";
down = "ip link set dev $(output ${member} ifname) nomaster";
};
}
we could start by writing this. we need to adapt ifwait
Sun Mar 3 17:09:21 GMT 2024
this is annoyingly hard to test. the tests we'd like to write are
1) when it gets events that don't match the requirement, nothing happens
2) when it gets an event that should start the service, the
service starts
3) when stop should stop
4) when start and already started, nothing happens
5) when stop and already stopped, nothing happens
what do we do if service fails to start? s6-rc will eventually reset it
to "down", I think: do we need to take action?
Mon Mar 4 20:46:55 GMT 2024
# relevant but not correct for this model: https://www.forked.net/forums/viewtopic.php?f=13&t=3490
# power on port 5
snmpset -v 1 -c private 192.168.5.14 .1.3.6.1.4.1.318.1.1.4.4.2.1.3.5 integer 1
# power off port 5
snmpset -v 1 -c private 192.168.5.14 .1.3.6.1.4.1.318.1.1.4.4.2.1.3.5 integer 2
# toggle off/on port 5
snmpset -v 1 -c private 192.168.5.14 .1.3.6.1.4.1.318.1.1.4.4.2.1.3.5 integer 3
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
Wed Mar 20 19:34:36 GMT 2024
Because I forgot hoe to rebuild rotuer, I tihnk it is time to improve
support for out-of-tree configurations. So I've made
modules/profiles/gateway.nix and now I can copy rotuer.nix to
telent-nixos-config.
Probably I should make nix-build work on the top-level derivation
and install liminix-rebuild as a binary?
would be good if an out-of-tree config could specify the device
it was targeting?
Fri Mar 22 20:49:54 GMT 2024
Ideally liminix-rebuild could accept a configuration file that
specifies a liminix-config file, a target hostname (maybe plus ssh
port, credentials etc) and the device name. Not going to work on that
just now but it does mean we can punt on specifying the device inside the
liminix-config which is unreasonably circular.
Maybe we'll just chuck a makefile in telent-nixos-config
Fri Mar 22 22:14:32 GMT 2024
For the service failover milestone we said
a. A configuration demonstrating a service which is restarted when it crashes
b. A failover config where service B runs iff service A is unavailable
c. A config showing different pppd behaviour when interface is flakey (retry) vs ppp password is wrong (report error, wait for resolution)
Sun Mar 24 23:41:27 GMT 2024
TODO
1) make liminix-rebuild bounce only affected services instead of
full reboot (what does it do about triggered services?)
2) sniproxy
3) see if arhcive still works. usb disk hotplug would be a good candidate for
switching to triggers
Mon Mar 25 19:35:47 GMT 2024
to make the liminix-rebuild thing restart only affected services, it needs to
know when the new service is not like the old one. By default it does not
restart a service with a changed up/down/run script unless the name has
also changed, so we need to figure out how to generate a "conversion"
file with the services that are different
pkgs/s6-rc-database/default.nix creates $out/compiled, we could add
$out/hashes to this
the other thing making this fun is that we will need to run `activate`
(which is usually done in preinit) otherwise the new configuration's
fhs directories won't exist.
so the plan woyuld be
in liminix-rebuild, when reboot was not chosen,
- run activate
- compare /run/s6-rc/compiled/hashes (old services) with
/etc/s6-rc/compiled/hashes (new services)
- whenever both files have the same column 1 and different
column 2, add that name to restart list
(need to turn restarts.fnl into a lua script)
s6-rc-update /etc/s6-rc/compiled/hashes restarts
Tue Mar 26 23:18:53 GMT 2024
activate overwrites /etc/s6-rc/compiled, which is a problem because
s6-rc-update expects to find the old compiled database here so that
it can know what to update
Maybe config.filesystem should specify /etc/s6-rc/compiled.new
and something in early boot could symlink /etc/s6-rc/compiled to it
Sat Mar 30 18:41:14 GMT 2024
soft restart doesn't restart services that are invoked by trigger,
because it has to do -p -u default so that it prunes services that
were in the old config but not the new one. Ideally we need somehow
to notify the trigger that it should respawn its service. Maybe
we could add triggers to the force restart list, if there's a way
to detect which they are? don't want to do it by adding files in
the service state directory if there may be oneshot triggers. Can
there be oneshot triggers?
The hashes file is built when we build the service database, so we
could easily(?) add something in there to mark services that
need poking whenever there's a restart. It's not perfect because the
triggered services will be bounced unnecessarily, but remember that
the alternative is a reboot ...
Mon Apr 1 00:18:50 BST 2024
i) I don't know if digressing into remote log shipping is a tangent or
an important part of making services work well.
ii) Should there be a single "machine state" value for all of the
trigger services to reference, or is it better that each trigger
service has its own private state, or (third option) one state
per "state source"? We previously handwaved that a state source
is a service
services.addmember = services.udev.watch {
match = {
SUBSYSTEM = "net";
ID_PATH = "pci-0000:04:00.0";
ATTR.operstate = "up";
};
service = oneshot {
up = "ip link set dev $dev master $(output ${primary} ifname)";
down = "ip link set dev $(output ${member} ifname) nomaster";
};
}
Tue Apr 2 19:55:25 BST 2024
We could do a test script for udev usb disk mounting, which uses the
qemu monitor to add/remove a disk.
./result/run.sh --flag -device --flag usb-ehci,id=xhci --flag -drive --flag if=none,id=usbstick,format=raw,file=./stick.img
(qemu) device_add usb-storage,bus=xhci.0,drive=usbstick
Fri Apr 5 17:11:46 BST 2024
1) write a fennel thing that reads from the udev rebroadcast socket
2) and can check sysfs for state
3) set up mdevd in liminix
Sat Apr 6 13:23:02 BST 2024
I wonder if we could replace preinit with an execline script? One for
the TODO stack
Sun Apr 7 14:03:29 BST 2024
1) we want to know what messages are sent from mdevd under various circumstances
- actually, right now the only relevant circumstances are updown and inout
2) we might get a wider variety of messages from real hardware?
3) if we log the raw messages, pref. with timestamps, then we can
write tests for the parsing
therefore: write a program that opens the netlink socket and logs
all data received
----
what's the minimum we need here? we need the inout test to open a
uevent socket and use uevents to update some state that says whether the
backup drive is plugged in
rather awkwardly, uevents don't have filesystem labels. so we also need
to run blkid to find the label of each partition, and ideally we do this
while the partition is present, not each time we get an event for it.
We have DEVNAME, DEVTYPE, SUBSYSTEM to indicate that a filesystem of interest
may be present, we should use that as a trigger to scan any known
add@/devices/pci0000:00/0000:00:13.0/usb1/1-1/1-1:1.0/host0/target0:0:0/0:0:0:0/block/sda/sda1
ACTION=add
DEVPATH=/devices/pci0000:00/0000:00:13.0/usb1/1-1/1-1:1.0/host0/target0:0:0/0:0:0:0/block/sda/sda1
SUBSYSTEM=block
MAJOR=8
MINOR=1
DEVNAME=sda1
DEVTYPE=partition
DISKSEQ=2
PARTN=1
SEQNUM=1528
Some disks on loaclhost and noetbook have PARTNAME field - I assume
this is because they're GPT disks. Would it actually be better to use this
field than grovelling for filesystem label?
Tue Apr 9 21:07:50 BST 2024
Having waited for the appropriately labelled disk to appear, we then
also have to communicate its path to the service that mounts it
- create a symlink
- or use an instanced service
Creating a symlink will be fine if we can pass the symlink name as
a param to fswait
Wed Apr 10 20:53:48 BST 2024
We think that fswait will evolve into a more general
waiting-for-uevents tool. Maybe we could provide the matchers on the
command line:
waituevent -l /dev/volumes/backup-disk -s mount-srv devtype=partition partname=backup-disk
Thu Apr 11 23:09:43 BST 2024
lcommit d3a2e3a4cb80b631df2ab79d463c2c4d1adef37b
commit 4a58cf9335116ce673fcf08f70f3bca921a4c9ad
commit afca6d4b63dd39062f02827b3c29e16904770216
Sun Apr 14 19:50:27 BST 2024
how to get this on to main:
- make uevent-watcher package (it's fswait renamed)
- make mount service use it
- module for mdevd
- add nellie (generalise for other netlink uses w/params pid/family/groups)
Mon Apr 15 19:59:43 BST 2024
plan:
introduce uevent-watcher command, update test to use it
make mount service use it
Tue Apr 16 18:59:25 BST 2024
Another idea for maybe-not-now: tftp local/peer addresses could be
provided as top-level params (e.g. to nix-build).
Wed Apr 17 18:57:49 BST 2024
I hatched a plan (and forgot to save this file) to build a service
that subscribes to uevents and retains state so that other services
can know about things that happened before they started. I'm wondering
if it's really needed though, because there could be one process to
read the socket and start/stop *all* the udev triggered services. Not
sure how we'd describe this in nix though: how do all the other
services
How we would do a uevent database service (sysfsq):
for each event e from socket
if e.action in (add, change)
path[e.path] = e.attribues
if e.action == 'remove'
path.remove e.path
(update-indices e)
(fn update-indices [event]
for each k in (keys event)
index.k.v += e)
we also want to not maintain indexes when there are so many values in
the index entry to make searching it worthless.
to retrieve, look at each criterion that has an index and choose the
index with fewest elements in the value. scan that index for the other
criteria
there are 813 uevent files in sysfs on arhcive, is this all overkill?
maybe we could simplify using a hardcoded stopword list - e.g. don't
have indices for MAJOR, MINOR
what are we going to use for querying? can't be netlink because that's
a shared medium (broadcast/multicast). unix dgram socket? alternative
would be to somehow use the filesystem as a database
Wed Apr 17 22:00:29 BST 2024
tests. assuming the sysfs setup from all-events.txt, we can write tests lik
- there is a path for $foo
- the attributes are x, y, z
- when I add a device with $attributes, I can recall it
- by path
- by attribute value
- when I remove it again, I cannot access it by path or attributes
- when I add a device with $attributes major minor foo bar baz
it is added to indices for foo bar baz but not major minor
- when I remove it, it can no longer be found by looking in any index
- when I query with multiple attributes, the search is performed
using the most specific attribute (= the attribute whose
value at this key has fewest elements)
I am still looking for ways to avoid doing this, but it is potentially
the first of several "database" services that triggers could want to
use so maybe it's an emerging pattern.
https://github.com/philanc/minisock useful? we could almost replace
nellie with it only not quite (it hardcodes 0 as the "protocol" param
to socket())
Fri Apr 19 20:55:22 BST 2024
We could have a service that's present only when a devdb entry is
present. For example mount_disk only runs when partlabel=foo
Or we could have a service that continues to run as the $somedatabase
service state changes and does different things depending on the
nature of those changes. For example, [I can't think of an example
now, but it was definitely an issue the other day, maybe I dreamt it]
I don't think this will be such an issue for devdb becuase there isn't
much in it that has continuously varying values. Maybe battery health
is the exception there
The step ahead we're thinking here is: how do clients do a request? A
single one-of request for state is fine but chances are that a client
will do that to get initial state and then need to open a netlink
socket to get updates: well, if we can feed them the initial state
filtered for their needs why can't we send them the relevant updates
as well? This makes the database server design a bit more complicated
as it needs to remember each client and their subscriptions, and then
send only relevant updates to each subscribed client
* should a client be allowed multiple subscriptions on the same
connection?
* do we guarantee that every message sent is matching the subscription
or can we send other stuff as well if it makes implementation easier?
it might defeat the purpose a bit because it means the client also
needs to filter, but the client will anyway have to do some message
parsing so they can distinguish add from remove
* where do we start?
Sun Apr 21 13:31:48 BST 2024
We have the mechanics of it working (albeit implemented in the
simplest possible terms), we need to glue it to some I/O
1) open a netlink socket and read the events from it
2) "create a PF_UNIX socket of type SOCK_STREAM, and accept connections on it, then each time you accept a connection, you get a new fd"
- accept connection
- read terms from it
- register callback that writes event to connected socket
minisock has no support for "test if fd is ready" or "wait for [fds]
to become ready", either we need poll() or we could add a call for "is
this fd ready to read" and use coroutines. Fork minisock or add as
another library?
[ if we fork minisock we could expose the protocol param to Lua
so we could use it for netlink ]
Tue Apr 23 19:13:45 BST 2024
we could convert from minisock to lualinux. if we can also use that to
get rid of nellie and/or lfs, the size tradeoff is minimal
---
Is there some way we could test the devout event loop?
I can register a fd with a callback
when the fd is ready, my callback is called
when the callback return true it remains registered
when the callback return true it is unregistered and the fd is closed
loop.register
loop.registered?
loop.feed
Tue Apr 23 20:34:03 BST 2024
I think we could make the event loop abstraction leak less?
It's not actually a _loop_, all the actual GOTO 10 happens
outside of it
[X] 1) see if we can do netlink in lualinux
[X] 2) if so, convert it to lualinux
[X] 3) add netlink socket to event loop
[X] 4) make it send messages to subscribers
5) package it
6) make uevent-watcher use it instead of netlink directly
7) write an inout test variant that has the stick inserted
at boot time already
I'm also thinking we could wrap the raw fds from lualinux into small
objects with read and close methods? It would make testing easier if
nothing else - also use of with-open. Maybe do that in anoia.
when a subscriber connects we need to send them their matching current
state before subscribing them [ needs a test ]
figure out what event format the subscribers want? lua-ish or send the
same messages as udev would? If we're going to send the originals,
should we store them alongside the parsed, or reconstruct from parsed?

View File

@ -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 ''
@ -112,7 +108,6 @@ in {
tufted
iptables
usbutils
busybox
];
security.sudo.wheelNeedsPassword = false;
networking = {
@ -127,7 +122,6 @@ in {
isNormalUser = true;
uid = 1000;
extraGroups = [ "wheel"];
openssh.authorizedKeys.keys = cfg.keys;
};
services.getty.autologinUser = "liminix";
};

9
ci.nix
View File

@ -9,14 +9,9 @@ let
borderVmConf = ./bordervm.conf-example.nix;
inherit (pkgs.lib.attrsets) genAttrs;
devices = [
"gl-ar750"
"gl-mt300a"
"gl-mt300n-v2"
"qemu"
"qemu-aarch64"
"qemu-armv7l"
"gl-ar750" "gl-mt300n-v2" "gl-mt300a"
"qemu" "qemu-aarch64" "qemu-armv7l"
"tp-archer-ax23"
"zyxel-nwa50ax"
];
vanilla = ./vanilla-configuration.nix;
for-device = name:

View File

@ -1,10 +1,8 @@
{
deviceName ? null
, device ? (import ./devices/${deviceName} )
device
, liminix-config ? <liminix-config>
, nixpkgs ? <nixpkgs>
, borderVmConf ? ./bordervm.conf.nix
, imageType ? "primary"
}:
let
@ -21,24 +19,17 @@ let
});
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
./modules/users.nix
./modules/outputs.nix
{
boot.imageType = imageType;
}
];
};
config = eval.config;
@ -77,7 +68,6 @@ in {
min-copy-closure
fennelrepl
lzma
lua
];
};
}

View File

@ -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

View File

@ -110,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";
@ -124,8 +126,7 @@
};
boot.tftp = {
loadAddress = lim.parseInt "0x00A00000";
appendDTB = true;
};
};
kernel = {
src = pkgs.fetchurl {
@ -135,7 +136,6 @@
};
extraPatchPhase = ''
${openwrt.applyPatches.ramips}
${openwrt.applyPatches.rt2x00}
'';
config = {

View File

@ -97,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]; };
@ -122,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 = {

View File

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

View File

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

View File

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

View File

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

View File

@ -28,12 +28,34 @@ in rec {
../modules/network
../modules/vlan
../modules/ssh
../modules/usb.nix
../modules/watchdog
../modules/mount
];
hostname = "arhcive";
kernel = {
config = {
USB = "y";
USB_EHCI_HCD = "y";
USB_EHCI_HCD_PLATFORM = "y";
USB_OHCI_HCD = "y";
USB_OHCI_HCD_PLATFORM = "y";
USB_SUPPORT = "y";
USB_COMMON = "y";
USB_STORAGE = "y";
USB_STORAGE_DEBUG = "n";
USB_UAS = "y";
USB_ANNOUNCE_NEW_DEVICES = "y";
SCSI = "y";
BLK_DEV_SD = "y";
USB_PRINTER = "y";
MSDOS_PARTITION = "y";
EFI_PARTITION = "y";
EXT4_FS = "y";
EXT4_USE_FOR_EXT2 = "y";
FS_ENCRYPTION = "y";
};
};
services.dhcpc =
let iface = config.hardware.networkInterfaces.lan;
@ -83,7 +105,7 @@ in rec {
};
services.mount_external_disk = svc.mount.build {
partlabel = "backup-disk";
device = "LABEL=backup-disk";
mountpoint = "/srv";
fstype = "ext4";
};
@ -137,17 +159,5 @@ in rec {
gid=500; usernames = ["backup"];
};
defaultProfile.packages = with pkgs; [
e2fsprogs
mtdutils
(levitate.override {
config = {
services = {
inherit (config.services) dhcpc sshd watchdog;
};
defaultProfile.packages = [ mtdutils ];
users.root.openssh.authorizedKeys.keys = secrets.root.keys;
};
})
];
defaultProfile.packages = with pkgs; [e2fsprogs strace tcpdump ];
}

View File

@ -158,6 +158,7 @@ in rec {
};
services.firewall = svc.firewall.build {
ruleset = import ./demo-firewall.nix;
};
services.packet_forwarding = svc.network.forward.build { };

View File

@ -8,10 +8,12 @@
config,
pkgs,
lib,
modulesPath,
...
}: let
secrets = import ./extneder-secrets.nix;
inherit (pkgs.liminix.services) oneshot longrun bundle target;
inherit (pkgs.pseudofile) dir symlink;
inherit (pkgs) dropbear ifwait serviceFns;
svc = config.system.service;
in rec {
boot = {
@ -22,32 +24,113 @@ in rec {
};
imports = [
"${modulesPath}/profiles/wap.nix"
"${modulesPath}/vlan"
"${modulesPath}/ssh"
../modules/wlan.nix
../modules/vlan
../modules/network
../modules/hostapd
../modules/bridge
../modules/ssh
];
hostname = "extneder";
profile.wap = {
interfaces = with config.hardware.networkInterfaces; [
lan
wlan
];
kernel = {
config = {
wireless = {
networks.${secrets.ssid} = {
interface = config.hardware.networkInterfaces.wlan;
inherit (secrets) channel wpa_passphrase;
country_code = "GB";
hw_mode = "g";
wmm_enabled = 1;
ieee80211n = 1;
};
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";
};
};
services.hostap = svc.hostapd.build {
interface = config.hardware.networkInterfaces.wlan;
params = {
country_code = "GB";
hw_mode = "g";
wmm_enabled = 1;
ieee80211n = 1;
inherit (secrets) ssid channel wpa_passphrase;
auth_algs = 1; # 1=wpa2, 2=wep, 3=both
wpa = 2; # 1=wpa, 2=wpa2, 3=both
wpa_key_mgmt = "WPA-PSK";
wpa_pairwise = "TKIP CCMP"; # auth for wpa (may not need this?)
rsn_pairwise = "CCMP"; # auth for wpa2
};
};
services.int = svc.bridge.primary.build {
ifname = "int";
};
services.dhcpc = svc.network.dhcp.client.build {
interface = services.int;
dependencies = [ config.services.hostname ];
};
services.bridge = svc.bridge.members.build {
primary = services.int;
members = with config.hardware.networkInterfaces; [
lan
wlan
];
};
services.sshd = svc.ssh.build {};
services.resolvconf = oneshot rec {
dependencies = [ services.dhcpc ];
name = "resolvconf";
# CHECK: https://udhcp.busybox.net/README.udhcpc says
# 'A list of DNS server' but doesn't say what separates the
# list members. Assuming it's a space or other IFS character
up = ''
. ${serviceFns}
( in_outputs ${name}
for i in $(output ${services.dhcpc} dns); do
echo "nameserver $i" > resolv.conf
done
)
'';
};
filesystem = dir {
etc = dir {
"resolv.conf" = symlink "${services.resolvconf}/.outputs/resolv.conf";
};
};
services.defaultroute4 = svc.network.route.build {
via = "$(output ${services.dhcpc} router)";
target = "default";
dependencies = [services.dhcpc];
};
users.root.passwd = lib.mkForce secrets.root.passwd;
defaultProfile.packages = with pkgs; [nftables strace tcpdump swconfig];
}

View File

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

View File

@ -6,18 +6,23 @@
# problems.
{ config, pkgs, lib, modulesPath, ... } :
{ config, pkgs, lib, ... } :
let
secrets = {
domainName = "fake.liminix.org";
firewallRules = {};
} // (import ./rotuer-secrets.nix);
inherit (pkgs.liminix.services) oneshot bundle;
inherit (pkgs.liminix.services) oneshot longrun 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;
};
@ -31,62 +36,65 @@ in rec {
};
imports = [
"${modulesPath}/profiles/gateway.nix"
"${modulesPath}/schnapps"
"${modulesPath}/outputs/btrfs.nix"
"${modulesPath}/outputs/extlinux.nix"
../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
];
hostname = "rotuer";
rootfsType = "btrfs";
rootOptions = "subvol=@";
boot.loader.extlinux.enable = true;
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 = 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.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
];
};
services.ntp = svc.ntp.build {
@ -98,6 +106,95 @@ 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

@ -9,29 +9,28 @@
./busybox.nix
./dhcp6c
./dnsmasq
./outputs/ext4fs.nix
./firewall
./hardware.nix
./hostapd
./hostname.nix
./outputs/initramfs.nix
./outputs/jffs2.nix
./kernel
./mdevd.nix
./outputs/kexecboot.nix
./mount
./network
./ntp
./outputs.nix
./outputs/ext4fs.nix
./outputs/initramfs.nix
./outputs/jffs2.nix
./outputs/kexecboot.nix
./outputs/mtdimage.nix
./outputs/tftpboot.nix
./outputs/ubifs.nix
./outputs/ubimage.nix
./outputs/vmroot.nix
./outputs/ubimage.nix
./outputs/mtdimage.nix
./ppp
./ramdisk.nix
./squashfs.nix
./ssh
./outputs/tftpboot.nix
./outputs/ubifs.nix
./users.nix
./vlan
./watchdog

View File

@ -12,6 +12,9 @@ let
type_service = pkgs.liminix.lib.types.service;
in {
imports = [
./kernel # kernel is a separate module for doc purposes
];
options = {
defaultProfile = {
packages = mkOption {
@ -26,10 +29,6 @@ in {
services = mkOption {
type = types.attrsOf type_service;
};
system.callService = mkOption {
type = types.functionTo (types.functionTo types.anything);
};
filesystem = mkOption {
type = types.anything;
description = ''
@ -59,15 +58,6 @@ in {
default = [];
description = "Kernel command line";
};
commandLineDtbNode = mkOption {
type = types.enum [ "bootargs" "bootargs-override" ];
default = "bootargs";
description = "Kernel command line's devicetree node";
};
imageType = mkOption {
type = types.enum [ "primary" "secondary" ];
default = "primary";
};
imageFormat = mkOption {
type = types.enum ["fit" "uimage"];
default = "uimage";
@ -112,31 +102,6 @@ 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,8 +14,6 @@ let
inherit (pkgs) liminix;
in
{
imports = [ ../ifwait ];
options = {
system.service.bridge = {
primary = mkOption { type = liminix.lib.types.serviceDefn; };
@ -29,7 +27,7 @@ in
description = "bridge interface name to create";
};
};
members = config.system.callService ./members.nix {
members = liminix.callService ./members.nix {
primary = mkOption {
type = liminix.lib.types.interface;
description = "primary bridge interface";
@ -49,5 +47,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,7 +2,6 @@
liminix
, ifwait
, lib
, svc
}:
{ members, primary } :
@ -11,20 +10,14 @@ let
inherit (liminix.services) bundle oneshot;
inherit (lib) mkOption types;
addif = member :
# 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;
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";
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

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

View File

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

View File

@ -1,8 +1,7 @@
(local subject (require :acquire-wan-address))
(import-macros { : expect= } :anoia.assert)
(local { : view } (require :fennel))
(local { : merge : dup } (require :anoia))
;; nix-shell --run "cd modules/dhcp6c && fennelrepl acquire-wan-address-test.fnl"
(local a1
{
@ -48,6 +47,19 @@
}
)
(macro expect [assertion]
(let [msg (.. "expectation failed: " (view assertion))]
`(when (not ,assertion)
(assert false ,msg))))
(macro expect= [actual expected]
`(let [ve# (view ,expected)
va# (view ,actual)]
(when (not (= ve# va#))
(assert false
(.. "\nexpected " ve# "\ngot " va#)
))))
(fn first-address []
(let [deleted
(subject.deletions

View File

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

View File

@ -56,14 +56,8 @@ in
config = {
system.service.firewall =
let svc = liminix.callService ./service.nix {
extraRules = mkOption {
type = types.attrsOf types.attrs;
description = "firewall ruleset";
default = {};
};
rules = mkOption {
ruleset = mkOption {
type = types.attrsOf types.attrs; # we could usefully tighten this a bit :-)
default = import ./default-rules.nix;
description = "firewall ruleset";
};
};
@ -74,17 +68,13 @@ in
};
in svc.build args' ;
};
programs.busybox.applets = [
"insmod" "rmmod"
];
kernel.config = {
NETFILTER = "y";
NETFILTER_ADVANCED = "y";
NETFILTER_NETLINK = "m";
NF_CONNTRACK = "m";
NETLINK_DIAG = "y";
IP6_NF_IPTABLES= "m";
IP_NF_IPTABLES = "m";
IP_NF_NAT = "m";

View File

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

View File

@ -67,7 +67,6 @@ in {
};
loadAddress = mkOption { type = types.ints.unsigned; default = null; };
entryPoint = mkOption { type = types.ints.unsigned; };
alignment = mkOption { type = types.nullOr types.ints.unsigned; default = null; description = "Alignment passed to `mkimage` for FIT"; };
radios = mkOption {
description = ''
Kernel modules (from mac80211 package) required for the

View File

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

View File

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

View File

@ -1,27 +0,0 @@
{ config, pkgs, lib, ...} :
let inherit (pkgs.liminix.services) oneshot longrun;
in {
config = {
services = rec {
mdevd = longrun {
name = "mdevd";
notification-fd = 3;
run = "${pkgs.mdevd}/bin/mdevd -D 3 -b 200000 -O4";
};
devout = longrun {
name = "devout";
notification-fd = 10;
run = "${pkgs.devout}/bin/devout /run/devout.sock 4";
};
coldplug = oneshot {
name ="coldplug";
# would love to know what mdevd-coldplug/udevadm trigger does
# that this doesn't
up = ''
for i in $(find /sys -name uevent); do ( echo change > $i ) ; done
'';
dependencies = [devout mdevd];
};
};
};
}

View File

@ -19,39 +19,28 @@ in {
type = liminix.lib.types.serviceDefn;
};
};
imports = [ ../mdevd.nix ];
config.system.service.mount =
let svc = liminix.callService ./service.nix {
partlabel = mkOption {
type = types.str;
example = "my-usb-stick";
};
mountpoint = mkOption {
type = types.str;
example = "/mnt/media";
};
options = mkOption {
type = types.listOf types.str;
default = [];
example = ["noatime" "ro" "sync"];
};
fstype = mkOption {
type = types.str;
default = "auto";
example = "vfat";
};
};
in svc // {
build = args:
let args' = args // {
dependencies = (args.dependencies or []) ++ [
config.services.mdevd
config.services.devout
];
};
in svc.build args' ;
config.system.service = {
mount = liminix.callService ./service.nix {
device = mkOption {
type = types.str;
example = "/dev/sda1";
};
mountpoint = mkOption {
type = types.str;
example = "/mnt/media";
};
options = mkOption {
type = types.listOf types.str;
default = [];
example = ["noatime" "ro" "sync"];
};
fstype = mkOption {
type = types.str;
default = "auto";
example = "vfat";
};
};
};
config.programs.busybox = {
applets = ["blkid" "findfs"];
options = {

View File

@ -1,26 +1,18 @@
{
liminix
, uevent-watch
, lib
}:
{ partlabel, mountpoint, options, fstype }:
{ device, mountpoint, options, fstype }:
let
inherit (liminix.services) longrun oneshot;
device = "/dev/disk/by-partlabel/${partlabel}";
options_string =
if options == [] then "" else "-o ${lib.concatStringsSep "," options}";
mount_service = oneshot {
name = "mount.${lib.escapeURL mountpoint}";
timeout-up = 3600;
up = "mount -t ${fstype} ${options_string} ${device} ${mountpoint}";
down = "umount ${mountpoint}";
};
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
inherit (liminix.services) oneshot;
in oneshot {
name = "mount.${lib.escapeURL mountpoint}";
up = ''
while ! findfs ${device}; do
echo waiting for device ${device}
sleep 1
done
mount -t ${fstype} -o ${lib.concatStringsSep "," options} ${device} ${mountpoint}
'';
down = "umount ${mountpoint}";
}

View File

@ -111,8 +111,7 @@ in
};
uimage = liminix.builders.uimage {
commandLine = concatStringsSep " " config.boot.commandLine;
inherit (config.boot) commandLineDtbNode;
inherit (config.hardware) loadAddress entryPoint alignment;
inherit (config.hardware) loadAddress entryPoint;
inherit (config.boot) imageFormat;
inherit (o) kernel dtb;
};

View File

@ -122,7 +122,7 @@ in {
fdtput -p -t lx dtb /reserved-memory/$node reg $ac_prefix $(hex $rootfsStart) $sz_prefix $(hex $rootfsSize)
cmd="liminix ${cmdline} mtdparts=phram0:''${rootfsSize}(rootfs) phram.phram=phram0,''${rootfsStart},''${rootfsSize},${toString config.hardware.flash.eraseBlockSize} root=/dev/mtdblock0";
fdtput -t s dtb /chosen ${config.boot.commandLineDtbNode} "$cmd"
fdtput -t s dtb /chosen bootargs "$cmd"
dtbSize=$(binsize ./dtb )

View File

@ -12,16 +12,9 @@ in
imports = [
./initramfs.nix
];
options.system.outputs.rootubifs = mkOption {
type = types.package;
internal = true;
};
options.hardware.ubi = {
minIOSize = mkOption { type = types.str; };
logicalEraseBlockSize = mkOption { type = types.str; }; # LEB
physicalEraseBlockSize = mkOption { type = types.str; }; # PEB
eraseBlockSize = mkOption { type = types.str; }; # LEB
maxLEBcount = mkOption { type = types.str; }; # LEB
};
@ -33,7 +26,7 @@ in
};
boot.initramfs.enable = true;
system.outputs = {
rootubifs =
rootfs =
let
inherit (pkgs.pkgsBuildBuild) runCommand mtdutils;
cfg = config.hardware.ubi;
@ -42,7 +35,7 @@ in
} ''
mkdir tmp
tree=${o.bootablerootdir}
mkfs.ubifs -x favor_lzo -c ${cfg.maxLEBcount} -m ${cfg.minIOSize} -e ${cfg.logicalEraseBlockSize} -y -r $tree --output $out --squash-uids -o $out
mkfs.ubifs -x favor_lzo -c ${cfg.maxLEBcount} -m ${cfg.minIOSize} -e ${cfg.eraseBlockSize} -y -r $tree --output $out --squash-uids -o $out
'';
};
};

View File

@ -1,91 +0,0 @@
{
config
, pkgs
, lib
, ...
}:
let
inherit (pkgs) liminix;
inherit (lib) mkIf mkOption types concatStringsSep optionalString;
in
{
imports = [
./initramfs.nix
./ubifs.nix
];
options.hardware.ubi = {
minIOSize = mkOption { type = types.str; };
eraseBlockSize = mkOption { type = types.str; }; # LEB
maxLEBcount = mkOption { type = types.str; }; # LEB
};
config = mkIf (config.rootfsType == "ubifs") {
kernel.config = {
MTD_UBI="y";
UBIFS_FS = "y";
UBIFS_FS_SECURITY = "n";
};
boot.initramfs.enable = true;
system.outputs.rootfs =
let
inherit (pkgs.pkgsBuildBuild) runCommand;
ubiVolume = ({ name, volumeId, image, flags ? [] }:
''
[${name}]
mode=ubi
vol_id=${toString volumeId}
vol_type=dynamic
vol_name=${name}
vol_alignment=1
${optionalString (image != null) ''
image=${image}
''}
${optionalString (image == null) ''
vol_size=1MiB
''}
${optionalString (flags != []) ''
vol_flags=${concatStringsSep "," flags}
''}
'');
ubiImage = (volumes:
let
ubinizeConfig = pkgs.writeText "ubinize.conf" (concatStringsSep "\n" volumes);
inherit (pkgs.pkgsBuildBuild) mtdutils;
in
runCommand "ubinize" {
depsBuildBuild = [ mtdutils ];
# block size := 128kb
# page size := 2048
# ubninize opts := -E 5
} ''
ubinize -Q "$SOURCE_DATE_EPOCH" -o $out \
-p ${config.hardware.ubi.physicalEraseBlockSize} -m ${config.hardware.ubi.minIOSize} \
-e ${config.hardware.ubi.logicalEraseBlockSize} \
${ubinizeConfig}
'');
ubiDisk = ({ initramfs }:
let
initramfsUbi = ubiVolume {
name = "rootfs";
volumeId = 0;
image = initramfs;
flags = [ "autoresize" ];
};
in
ubiImage [
initramfsUbi
]);
disk = ubiDisk {
initramfs = config.system.outputs.rootubifs; # liminix.builders.squashfs config.filesystem.contents; # # assert this is a proper FIT.
};
in
disk;
};
}

View File

@ -1,71 +0,0 @@
{
config
, pkgs
, lib
, ...
}:
let
inherit (lib) mkIf mkEnableOption mkOption types concatStringsSep;
models = "6b e1 6f e1 ff ff ff ff ff ff";
in {
options.system.outputs = {
zyxel-nwa-fit = mkOption {
type = types.package;
description = ''
zyxel-nwa-fit
*************
This output provides a FIT image for Zyxel NWA series
containing a kernel image and an UBIFS rootfs.
It can usually be used as a factory image to install Liminix
on a system with pre-existing firmware and OS.
'';
};
};
imports = [
./ubivolume.nix
];
config = mkIf (config.rootfsType == "ubifs") {
system.outputs.zyxel-nwa-fit =
let
o = config.system.outputs;
# 8129kb padding.
paddedKernel = pkgs.runCommand "padded-kernel" {} ''
cp --no-preserve=mode ${o.uimage} $out
dd if=/dev/zero of=$out bs=1 count=1 seek=8388607
'';
firmwareImage = pkgs.runCommand "firmware-image" {} ''
cat ${paddedKernel} ${o.rootfs} > $out
'';
dts = pkgs.writeText "image.its" ''
/dts-v1/;
/ {
description = "Zyxel FIT (Flattened Image Tree)";
compat-models = [${models}];
#address-cells = <1>;
images {
firmware {
data = /incbin/("${firmwareImage}");
type = "firmware";
compression = "none";
hash@1 {
algo = "sha1";
};
};
};
};
'';
in
pkgs.runCommand "zyxel-nwa-fit-${config.boot.imageType}" {
nativeBuildInputs = [ pkgs.pkgsBuildBuild.ubootTools pkgs.pkgsBuildBuild.dtc ];
} ''
mkimage -f ${dts} $out
'';
};
}

View File

@ -1,178 +0,0 @@
{ 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 {
extraRules = 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";
};
};
};
}

View File

@ -1,98 +0,0 @@
{
config,
pkgs,
lib,
...
}: let
inherit (pkgs) liminix;
inherit (lib) mkEnableOption mkOption types isDerivation hasAttr ;
inherit (pkgs.liminix.services) oneshot longrun bundle target;
inherit (pkgs.pseudofile) dir symlink;
inherit (pkgs) serviceFns;
svc = config.system.service;
cfg = config.profile.wap;
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 {
imports = [
../wlan.nix
../network
../hostapd
../bridge
{ config.services = hostaps; }
];
options.profile.wap = {
interfaces = mkOption {
type = types.listOf liminix.lib.types.interface;
default = [];
};
wireless = mkOption {
type = types.attrsOf types.anything;
};
};
config = {
services.int = svc.bridge.primary.build {
ifname = "int";
};
services.bridge = svc.bridge.members.build {
primary = config.services.int;
members = cfg.interfaces;
};
services.dhcpc = svc.network.dhcp.client.build {
interface = config.services.int;
dependencies = [ config.services.hostname ];
};
services.defaultroute4 = svc.network.route.build {
via = "$(output ${config.services.dhcpc} router)";
target = "default";
dependencies = [config.services.dhcpc];
};
services.resolvconf = oneshot rec {
dependencies = [ config.services.dhcpc ];
name = "resolvconf";
# CHECK: https://udhcp.busybox.net/README.udhcpc says
# 'A list of DNS server' but doesn't say what separates the
# list members. Assuming it's a space or other IFS character
up = ''
. ${serviceFns}
( in_outputs ${name}
for i in $(output ${config.services.dhcpc} dns); do
echo "nameserver $i" > resolv.conf
done
)
'';
};
filesystem = dir {
etc = dir {
"resolv.conf" = symlink "${config.services.resolvconf}/.outputs/resolv.conf";
};
};
};
}

View File

@ -34,7 +34,7 @@ fi
### If your services are managed by s6-rc:
### (replace /run/service with your scandir)
s6-rc-init -d -c /etc/s6-rc/compiled /run/service
s6-rc-init /run/service -d -c /etc/s6-rc/compiled
### 2. Starting the wanted set of services

View File

@ -15,5 +15,4 @@ in oneshot rec {
)
'';
down = "ip link set down dev ${ifname}";
dependencies = [ primary ];
}

View File

@ -1,7 +1,6 @@
{
liminix
, lib
, s6
}:
{ watched, headStart } :
let
@ -9,5 +8,5 @@ let
in longrun {
name = "watchdog";
run =
"PATH=${s6}/bin:$PATH HEADSTART=${toString headStart} ${./gaspode.sh} ${lib.concatStringsSep " " (builtins.map (s: s.name) watched)}";
"HEADSTART=${toString headStart} ${./gaspode.sh} ${lib.concatStringsSep " " (builtins.map (s: s.name) watched)}";
}

View File

@ -1,60 +0,0 @@
## Boot blessing via Zyxel
## =======================
## Boot blessing is the process to bless a particular boot configuration
## It is commonly encountered in devices with redundant partitions
## for automatic recovery of broken upgrades.
## This is also known as A/B schemas, where A represents the primary partition
## and B the secondary partition used for recovery.
## To use boot blessing on Liminix, you need to have the support of
## your bootloader to help you boot on the secondary partition in case of
## failure on the primary partition. The exact details are specifics to your device.
## See the Zyxel NWA50AX for an example.
## TODO: generalize this module.
{ config, lib, pkgs, ... }:
let
inherit (lib) mkOption types;
inherit (pkgs) liminix;
in
{
options.boot.zyxel-dual-image = mkOption {
type = liminix.lib.types.serviceDefn;
};
config.boot.zyxel-dual-image = liminix.callService ./service.nix {
ensureActiveImage = mkOption {
type = types.enum [ "primary" "secondary" ];
default = "primary";
description = ''At boot, ensure that the active image is the one specified.
If you are already on a broken image, you need to manually boot
into the right image via `atgo <image index>` in U-Boot.
'';
};
kernelCommandLineSource = mkOption {
type = types.enum [ "/proc/cmdline" "/proc/device-tree/chosen/bootargs" ];
default = "/proc/device-tree/chosen/bootargs";
description = ''Kernel command line arguments source file.
On MIPS, Liminix embeds the kernel command line in /proc/device-tree/chosen/bootargs-override.
In this instance, it does not get concatenated with `/proc/cmdline`.
Therefore you may prefer to source it from another place, like `/proc/device-tree/chosen/bootargs`.
'';
};
primaryMtdPartition = mkOption {
type = types.str;
description = "Primary MTD partition device node, i.e. for image 0.";
};
secondaryMtdPartition = mkOption {
type = types.str;
description = "Secondary MTD partition device node, i.e. for image 1.";
};
bootConfigurationMtdPartition = mkOption {
type = types.str;
description = "Boot configuration MTD partition device node.";
};
};
}

View File

@ -1,33 +0,0 @@
{
liminix
, lib
, zyxel-bootconfig
}:
{ ensureActiveImage, primaryMtdPartition, secondaryMtdPartition, bootConfigurationMtdPartition, kernelCommandLineSource }:
let
inherit (liminix.services) oneshot;
activeImageIndex = if ensureActiveImage == "primary" then 0 else 1;
in oneshot {
name = "zyxel-boot-configure";
up = ''
set -- $(cat /proc/device-tree/chosen/bootargs)
for x in "$@"; do
case "$x" in
bootImage=*)
BOOT_IMAGE="''${x#bootImage=}"
echo "Current boot image is $BOOT_IMAGE."
;;
esac
done
if test -z "$BOOT_IMAGE"; then
echo "No valid image was provided in the kernel command line."
exit 1
else
${lib.getExe zyxel-bootconfig} ${bootConfigurationMtdPartition} set-image-status "$BOOT_IMAGE" valid
${lib.getExe zyxel-bootconfig} ${bootConfigurationMtdPartition} set-active-image ${toString activeImageIndex}
echo "Active image is now ${ensureActiveImage}"
fi
'';
}

View File

@ -77,22 +77,6 @@ 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,14 +0,0 @@
default: fs.lua init.lua nl.lua svc.lua net/constants.lua
test:
ln -s . anoia
fennel test.fnl
fennel test-svc.fnl
net/constants.lua: net/constants.c
$(CC) -imacros sys/socket.h -imacros linux/netlink.h -E -P - < net/constants.c | sed 's/ *$$//g' | cat -s > net/constants.lua
%.lua: %.fnl
fennel --compile $< > $@

View File

@ -1,21 +0,0 @@
;; these are macros; this module should be imported
;; using import-macros
;; e.g. (import-macros { : expect= } :anoia.assert)
(fn expect [assertion]
(let [msg (.. "expectation failed: " (view assertion))]
`(when (not ,assertion)
(assert false ,msg))))
(fn expect= [actual expected]
`(let [view# (. (require :fennel) :view)
ve# (view# ,expected)
va# (view# ,actual)]
(when (not (= ve# va#))
(assert false
(.. "\nexpected " ve# "\ngot " va#)
))))
{ : expect : expect= }

View File

@ -1,27 +1,22 @@
{
fennel
, stdenv
, linotify
, lua
, lualinux
, cpio
}:
let pname = "anoia";
in stdenv.mkDerivation {
inherit pname;
version = "0.1";
src = ./.;
nativeBuildInputs = [ fennel cpio ];
buildInputs = with lua.pkgs; [ linotify lualinux ];
outputs = [ "out" "dev" ];
doCheck = true;
nativeBuildInputs = [ fennel ];
buildInputs = with lua.pkgs; [ luafilesystem ];
buildPhase = ''
for f in *.fnl ; do
fennel --compile $f > `basename $f .fnl`.lua
done
'';
installPhase = ''
mkdir -p "$out/share/lua/${lua.luaversion}/${pname}"
find . -name \*.lua | cpio -p -d "$out/share/lua/${lua.luaversion}/${pname}"
mkdir -p "$dev/share/lua/${lua.luaversion}/${pname}"
cp assert.fnl "$dev/share/lua/${lua.luaversion}/${pname}"
cp *.lua "$out/share/lua/${lua.luaversion}/${pname}"
'';
}

View File

@ -1,30 +1,7 @@
(local ll (require :lualinux))
(local S_IFMT 0xf000)
(local S_IFSOCK 0xc000)
(local S_IFLNK 0xa000)
(local S_IFREG 0x8000)
(local S_IFBLK 0x6000)
(local S_IFDIR 0x4000)
(local S_IFCHR 0x2000)
(local S_IFIFO 0x1000)
(fn ifmt-bits [mode] (and mode (band mode 0xf000)))
(fn file-type [pathname]
(. {
S_IFDIR :directory
S_IFSOCK :socket
S_IFLNK :link
S_IFREG :file
S_IFBLK :block-device
S_IFCHR :character-device
S_IFIFO :fifo
}
(ifmt-bits (ll.lstat3 pathname))))
(local lfs (require :lfs))
(fn directory? [pathname]
(= (file-type pathname) :directory))
(= (lfs.symlinkattributes pathname :mode) "directory"))
(fn mktree [pathname]
(if (or (= pathname "") (= pathname "/"))
@ -33,37 +10,27 @@
(or (directory? pathname)
(let [parent (string.gsub pathname "/[^/]+/?$" "")]
(or (directory? parent) (mktree parent))
(assert (ll.mkdir pathname)))))
(fn dir [name]
(let [dp (assert (ll.opendir name) name)]
(fn []
(match (ll.readdir dp)
(name type) (values name type)
(nil err) (do (if err (print err)) (ll.closedir dp) nil)))))
(assert (lfs.mkdir pathname)))))
(fn rmtree [pathname]
(case (file-type pathname)
(case (lfs.symlinkattributes pathname)
nil true
:directory
{:mode "directory"}
(do
(each [f (dir pathname)]
(each [f (lfs.dir pathname)]
(when (not (or (= f ".") (= f "..")))
(rmtree ( .. pathname "/" f)))
(ll.rmdir pathname)))
:file
(lfs.rmdir pathname)))
{:mode "file"}
(os.remove pathname)
:link
{:mode "link"}
(os.remove pathname)
unknown
(error (.. "can't remove " pathname " of mode \"" unknown "\""))))
(error (.. "can't remove " pathname " of kind \"" unknown.mode "\""))))
{
: mktree
: rmtree
: directory?
: dir
: file-type
:symlink (fn [from to] (ll.symlink from to))
}

View File

@ -1,7 +1,3 @@
(fn assoc [tbl k v]
(tset tbl k v)
tbl)
(fn merge [table1 table2]
(collect [k v (pairs table2) &into table1]
k v))
@ -18,15 +14,9 @@
f (do (f:close) true)
_ false))
(fn basename [path]
(string.match path ".*/([^/]-)$"))
(fn dirname [path]
(string.match path "(.*)/[^/]-$"))
(fn system [s]
(match (os.execute s)
res (do (print (.. "Executed \"" s "\", exit code " (tostring res))) res)
res res
(nil err) (error (.. "Error executing \"" s "\" (" err ")"))))
(fn hash [str]
@ -72,15 +62,4 @@
(s:sub 1 (- (# s) pad))))
{
: assoc
: base64url
: basename
: dirname
: dup
: file-exists?
: hash
: merge
: split
: system
}
{ : merge : split : file-exists? : system : hash : base64url : dup }

View File

@ -1,11 +0,0 @@
#define MACRO(c) [#c] = c,
return {
MACRO(SOCK_STREAM)
MACRO(SOCK_DGRAM)
MACRO(SOCK_RAW)
MACRO(AF_LOCAL)
MACRO(AF_NETLINK)
MACRO(NETLINK_KOBJECT_UEVENT)
}

View File

@ -1,15 +0,0 @@
(local netlink (require :netlink))
; (local { : view } (require :fennel))
(fn events [groups]
(let [sock (netlink.socket)]
(coroutine.wrap
(fn []
(each [_ e (ipairs (sock:query groups))]
(coroutine.yield e))
(while (sock:poll)
(each [_ e (ipairs (sock:event))]
(coroutine.yield e)))))))
{ : events }

View File

@ -1,6 +1,7 @@
(local inotify (require :inotify))
(local { : file-exists? } (require :anoia))
(local { : file-type : dir &as fs } (require :anoia.fs))
(local { : directory? } (require :anoia.fs))
(local lfs (require :lfs))
(fn read-line [name]
(with-open [f (assert (io.open name :r) (.. "can't open file " name))]
@ -19,15 +20,15 @@
handle))
(fn read-value [pathname]
(case (file-type pathname)
(case (lfs.symlinkattributes pathname)
nil nil
:directory
(collect [f (fs.dir pathname)]
{:mode "directory"}
(collect [f (lfs.dir pathname)]
(when (not (or (= f ".") (= f "..")))
(values f (read-value ( .. pathname "/" f)))))
:file
{:mode "file"}
(read-line pathname)
:link
{:mode "link"}
(read-line pathname)
unknown
(error (.. "can't read " pathname " of kind \"" unknown.mode "\""))))

View File

@ -1,7 +0,0 @@
(local nl (require :anoia.nl))
(local { : view } (require :fennel))
(let [events (nl.events {:link true})]
(each [ev events]
(print "got one ")
(print (view ev))))

View File

@ -1,4 +1,4 @@
(local svc (require :svc))
(local svc (require :anoia.svc))
(local { : view } (require :fennel))
(local ex (svc.open "./example-output"))

View File

@ -1,10 +1,9 @@
(local { : hash : base64url } (require :init))
(import-macros { : expect= } :assert)
(local { : hash : base64url } (require :anoia))
(expect= (hash "") 5381)
(assert (= (hash "") 5381))
;; these examples from https://theartincode.stanis.me/008-djb2/
(expect= (hash "Hello") 210676686969)
(expect= (hash "Hello!") 6952330670010)
(assert (= (hash "Hello") 210676686969))
(assert (= (hash "Hello!") 6952330670010))
(expect= (base64url "hello world") "aGVsbG8gd29ybGQ")
(assert (= (base64url "hello world") "aGVsbG8gd29ybGQ"))

View File

@ -56,7 +56,6 @@ in {
# please keep the rest of this list alphabetised :-)
anoia = callPackage ./anoia {};
devout = callPackage ./devout {};
fennel = callPackage ./fennel {};
fennelrepl = callPackage ./fennelrepl {};
firewallgen = callPackage ./firewallgen {};
@ -71,7 +70,6 @@ in {
levitate = callPackage ./levitate {};
libubootenv = callPackage ./libubootenv {};
linotify = callPackage ./linotify {};
lualinux = callPackage ./lualinux {};
# we need to build real lzma instead of using xz, because the lzma
# decoder in u-boot doesn't understand streaming lzma archives
@ -81,11 +79,8 @@ in {
lzma = callPackage ./lzma {};
mac80211 = callPackage ./mac80211 {};
zyxel-bootconfig = callPackage ./zyxel-bootconfig {};
min-collect-garbage = callPackage ./min-collect-garbage {};
min-copy-closure = callPackage ./min-copy-closure {};
minisock = callPackage ./minisock {};
nellie = callPackage ./nellie {};
netlink-lua = callPackage ./netlink-lua {};
odhcp-script = callPackage ./odhcp-script {};
odhcp6c = callPackage ./odhcp6c {};
@ -109,7 +104,6 @@ in {
swconfig = callPackage ./swconfig {};
systemconfig = callPackage ./systemconfig {};
tufted = callPackage ./tufted {};
uevent-watch = callPackage ./uevent-watch {};
writeAshScript = callPackage ./write-ash-script {};
writeFennel = callPackage ./write-fennel {};
writeFennelScript = callPackage ./write-fennel-script {};

View File

@ -1,26 +0,0 @@
{
lua
, nellie
, writeFennel
, anoia
, fennel
, stdenv
, fennelrepl
, lualinux
}:
stdenv.mkDerivation {
name = "devout";
src = ./.;
nativeBuildInputs = [ fennelrepl ];
postBuild = ''
LUA_CPATH=${lualinux}/lib/lua/5.3/?.so\;$LUA_CPATH \
fennelrepl ./test.fnl
'';
installPhase = ''
mkdir -p $out/bin
cp -p ${writeFennel "devout" {
packages = [fennel anoia nellie lualinux];
mainFunction = "run";
} ./devout.fnl} $out/bin/devout
'';
}

View File

@ -1,173 +0,0 @@
(local ll (require :lualinux))
(local {
: AF_LOCAL
: AF_NETLINK
: SOCK_STREAM
: SOCK_RAW
: NETLINK_KOBJECT_UEVENT
} (require :anoia.net.constants))
(local { : view } (require :fennel))
(fn trace [expr]
(do (print :TRACE (view expr)) expr))
(macro check-errno [expr]
(let [{ :view v } (require :fennel)]
`(case ,expr
val# val#
(nil err#) (error (string.format "%s failed: errno=%d" ,(v expr) err#)))))
(fn format-event [e]
(..
(string.format "%s@%s\0" e.action e.path)
(table.concat
(icollect [k v (pairs e.attributes)]
(string.format "%s=%s" (string.upper k) v ))
"\n")))
(fn event-matches? [e terms]
(accumulate [match? true
name value (pairs terms)]
(and match? (= value (. e.attributes name)))))
(fn parse-event [s]
(let [at (string.find s "@" 1 true)
(nl nxt) (string.find s "\0" 1 true)
attributes
(collect [k v (string.gmatch
(string.sub s (+ 1 nxt))
"(%g-)=(%g+)")]
(k:lower) v)]
{ : attributes
:path (string.sub s (+ at 1) (- nl 1))
:action (string.sub s 1 (- at 1))
:format format-event
:matches? event-matches?
}))
(fn find-in-database [db terms]
(accumulate [found []
_ e (pairs db)]
(if (e:matches? terms)
(doto found (table.insert e))
found)))
(fn record-event [db subscribers str]
(let [e (parse-event str)]
(match e.action
:add (tset db e.path e)
:change (tset db e.path e)
;; should we do something for bind?
:remove (tset db e.path nil)
)
(each [_ { : terms : callback } (pairs subscribers)]
(if (e:matches? terms) (callback e)))
e))
(fn database []
(let [db {}
subscribers []]
{
:find (fn [_ terms] (find-in-database db terms))
:add (fn [_ event-string] (when event-string (record-event db subscribers event-string)))
:at-path (fn [_ path] (. db path))
:subscribe (fn [_ id callback terms]
(let [past-events (find-in-database db terms)]
(each [_ e (pairs past-events)]
(callback e)))
(tset subscribers id {: callback : terms }))
:unsubscribe (fn [_ id] (tset subscribers id nil))
}))
;; grepped from kernel headers
(local POLLIN 0x0001)
(local POLLPRI 0x0002)
(local POLLOUT 0x0004)
(local POLLERR 0x0008)
(local POLLHUP 0x0010)
(local POLLNVAL 0x0020)
(fn unix-socket [name]
(let [addr (string.pack "=Hz" AF_LOCAL name)]
(case (ll.socket AF_LOCAL SOCK_STREAM 0)
fd (case (ll.bind fd addr)
0 (doto fd (ll.listen 32))
(nil err) (values nil err))
(nil err) (values nil err))))
(fn pollfds-for [fds]
(icollect [_ v (ipairs fds)]
(bor (lshift v 32) (lshift 1 16))))
(fn unpack-pollfds [pollfds]
(collect [_ v (ipairs pollfds)]
(let [fd (band (rshift v 32) 0xffffffff)
revent (band v 0xffff)]
(values fd (if (> revent 0) revent nil)))))
(fn parse-terms [str]
(collect [n (string.gmatch (str:gsub "\n+$" "") "([^ ]+)")]
(string.match n "(.-)=(.+)")))
(fn handle-client [db client]
(match (ll.read client)
"" (do
(db:unsubscribe client)
false)
s (do
(db:subscribe
client
(fn [e]
(ll.write client (format-event e)))
(parse-terms s))
true)
(nil err) (do (print err) false)))
(fn open-netlink [groups]
(match (ll.socket AF_NETLINK SOCK_RAW NETLINK_KOBJECT_UEVENT)
fd (doto fd (ll.bind (string.pack "I2I2I4I4" ; family pad pid groups
AF_NETLINK 0 0 groups)))
(nil errno) (values nil errno)))
(fn event-loop []
(let [fds {}]
{
:register #(tset fds $2 $3)
:feed (fn [_ revents]
(each [fd revent (pairs revents)]
(when (not ((. fds fd) fd))
(tset fds fd nil)
(ll.close fd))))
:fds #(icollect [fd _ (pairs fds)] fd)
:_tbl #(do fds) ;exposed for tests
}))
(fn run []
(let [[sockname nl-groups] arg
s (check-errno (unix-socket sockname))
db (database)
nl (check-errno (open-netlink nl-groups))
loop (event-loop)]
(loop:register
s
#(case
(ll.accept s)
(client addr)
(do
(loop:register client (partial handle-client db))
true)
(nil err)
(print (string.format "error accepting connection, errno=%d" err))))
(loop:register
nl
#(do (db:add (ll.read nl)) true))
(ll.write 10 "ready\n")
(while true
(let [pollfds (pollfds-for (loop:fds))]
(ll.poll pollfds 5000)
(loop:feed (unpack-pollfds pollfds))))))
{ : database : run : event-loop : parse-event }

View File

@ -1,210 +0,0 @@
(local { : database : event-loop : parse-event } (require :devout))
(local { : view } (require :fennel))
(local ll (require :lualinux))
(import-macros { : expect : expect= } :anoia.assert)
(var failed false)
(fn fail [d msg] (set failed true) (print :FAIL d (.. "\n" msg)))
(macro example [description & body]
(if (. body 1)
`(let [(ok?# err#) (xpcall (fn [] ,body) debug.traceback)]
(if ok?#
(print :PASS ,description)
(fail ,description err#)))
`(print :PENDING ,description)))
(local sda-uevent
"add@/devices/pci0000:00/0000:00:13.0/usb1/1-1/1-1:1.0/host0/target0:0:0/0:0:0:0/block/sda\0ACTION=add
DEVPATH=/devices/pci0000:00/0000:00:13.0/usb1/1-1/1-1:1.0/host0/target0:0:0/0:0:0:0/block/sda
SUBSYSTEM=block
MAJOR=8
MINOR=0
DEVNAME=sda
DEVTYPE=disk
DISKSEQ=2
SEQNUM=1527")
(local sdb1-insert
"add@/devices/pci0000:00/0000:00:14.0/usb1/1-3/1-3:1.0/host1/target1:0:0/1:0:0:0/block/sdb/sdb1\0ACTION=add
DEVPATH=/devices/pci0000:00/0000:00:14.0/usb1/1-3/1-3:1.0/host1/target1:0:0/1:0:0:0/block/sdb/sdb1
SUBSYSTEM=block
DEVNAME=/dev/sdb1
DEVTYPE=partition
DISKSEQ=33
PARTN=1
SEQNUM=82381
MAJOR=8
MINOR=17")
(local sdb1-remove
"remove@/devices/pci0000:00/0000:00:14.0/usb1/1-3/1-3:1.0/host1/target1:0:0/1:0:0:0/block/sdb/sdb1\0ACTION=remove
DEVPATH=/devices/pci0000:00/0000:00:14.0/usb1/1-3/1-3:1.0/host1/target1:0:0/1:0:0:0/block/sdb/sdb1
SUBSYSTEM=block
DEVNAME=/dev/sdb1
DEVTYPE=partition
DISKSEQ=33
PARTN=1
SEQNUM=82386
MAJOR=8
MINOR=17")
(example
"I can parse an event"
(let [e (parse-event sdb1-insert)]
(expect= e.attributes.seqnum "82381")
(expect= e.attributes.devname "/dev/sdb1")
(expect= e.path "/devices/pci0000:00/0000:00:14.0/usb1/1-3/1-3:1.0/host1/target1:0:0/1:0:0:0/block/sdb/sdb1")
(expect= e.action :add)
(expect= e (parse-event (e:format)))))
(example
"An event can match against terms"
(let [terms {:devname "foo" :partname "my-usbstick"}]
(expect= (: (parse-event "add@/\0SEQNUM=1") :matches? terms) false)
(expect= (: (parse-event "add@/\0DEVNAME=bill") :matches? terms) false)
(expect= (: (parse-event "add@/\0DEVNAME=foo\nPARTNAME=my-usbstick") :matches? terms) true)
(expect= (: (parse-event "add@/\0DEVNAME=foo\nPARTNAME=my-usbstick\nOTHERTHING=bar") :matches? terms) true)
))
(example
"given an empty database, searching it finds no entries"
(let [db (database)]
(expect= (db:find {:partname "boot"}) [])))
(example
"when I add a device, I can find it"
(let [db (database)]
(db:add sda-uevent)
(let [[m & more] (db:find {:devname "sda"})]
(expect= m.attributes.devname "sda")
(expect= m.attributes.major "8")
(expect= more []))))
(example
"when I add a device, I cannot find it with wrong terms"
(let [db (database)]
(db:add sda-uevent)
(expect= (db:find {:devname "sdb"}) [])))
(example
"when I add a device, I can retrieve it by path"
(let [db (database)]
(db:add sda-uevent)
(let [m (db:at-path "/devices/pci0000:00/0000:00:13.0/usb1/1-1/1-1:1.0/host0/target0:0:0/0:0:0:0/block/sda")]
(expect= m.attributes.devname "sda")
(expect= m.attributes.major "8"))))
(example
"when I add and then remove a device, I cannot retrieve it by path"
(let [db (database)]
(db:add sdb1-insert)
(db:add sdb1-remove)
(expect= (db:at-path "/devices/pci0000:00/0000:00:14.0/usb1/1-3/1-3:1.0/host1/target1:0:0/1:0:0:0/block/sdb/sdb1") nil)))
(example
"when I add and then remove a device, I cannot find it"
(let [db (database)]
(db:add sdb1-insert)
(db:add sda-uevent)
(db:add sdb1-remove)
(expect= (db:find {:devname "/dev/sdb1"}) [])))
(example
"when I search on multiple terms it uses all of them"
(let [db (database)]
(db:add sda-uevent)
(expect= (# (db:find {:devname "sda" :devtype "disk"})) 1)
(expect= (# (db:find {:devname "sda" :devtype "dosk"})) 0)))
;;; tests for indices
(example "when I add a device with $attributes major minor foo bar baz,
it is added to indices for foo bar baz but not major minor")
(example "a removed device can no longer be found by looking in any index")
(example "when I query with multiple attributes, the search is performed using the most specific attribute"
;; (= the attribute whose
;; value at this key has fewest elements)
)
;;; tests for subscriptions
(example
"I can subscribe to some search terms and be notified of matching events"
(var received [])
(let [db (database)
subscriber (fn [e] (table.insert received e))]
(db:subscribe :me subscriber {:devname "/dev/sdb1"})
(db:add sdb1-insert)
(db:add sda-uevent)
(db:add sdb1-remove)
(expect= (# received) 2)))
(example
"Subscribers get notifications of prior events for present devices"
(var received [])
(let [db (database)
subscriber (fn [e] (table.insert received e))]
(db:add sdb1-insert)
(db:add sda-uevent)
(db:subscribe :me subscriber {:devname "/dev/sdb1"})
(expect= (# received) 1)))
(example
"I can unsubscribe after subscribing"
(var received [])
(let [db (database)
subscriber (fn [e] (table.insert received e))]
(db:subscribe :me subscriber {:devname "/dev/sdb1"})
(db:unsubscribe :me)
(db:add sdb1-insert)
(db:add sda-uevent)
(db:add sdb1-remove)
(expect= (# received) 0)))
;;; test for event loop
(example
"I can register a fd with a callback"
(let [loop (event-loop)
cb #(print $1)]
(loop:register 3 cb)
(expect= (. (loop:_tbl) 3) cb)))
(example
"when the fd is ready, my callback is called"
(let [loop (event-loop)]
(var ran? false)
(loop:register 3 #(set ran? true))
(loop:feed {3 1})
(expect= ran? true)
))
(example
"when the callback returns true it remains registered"
(let [loop (event-loop)]
(loop:register 3 #true)
(loop:feed {3 1})
(expect (. (loop:_tbl) 3))
))
(fn new-fd []
(ll.open "/dev/zero" 0 0x1ff))
(example
"when the callback returns false it is unregistered and the fd is closed"
(let [loop (event-loop)
fd (new-fd)]
(expect (> fd 2))
(loop:register 3 #false)
(loop:feed {3 1})
(expect (not (. (loop:_tbl) 3)))
(assert (not (os.execute (string.format "test -e /dev/fd/%d" fd))))
))
(if failed (os.exit 1) (print "OK"))

View File

@ -5,20 +5,16 @@
, lib
, luaPackages
, lua
, lualinux
, writeScriptBin
, linotify
, anoia
, netlink-lua
, fennel
}:
let packages = [
linotify
anoia
fennel
lualinux
netlink-lua
lua.pkgs.readline
lua.pkgs.luafilesystem
];
join = ps: builtins.concatStringsSep ";" ps;
luapath = join (builtins.map (f:
@ -33,7 +29,6 @@ in writeScriptBin "fennelrepl" ''
package.cpath = ${lib.strings.escapeShellArg luacpath} .. ";" .. (package.cpath or "")
local fennel = require "fennel"
table.insert(package.loaders or package.searchers,1, fennel.searcher)
fennel['macro-path'] = "${anoia.dev}/share/lua/${lua.luaversion}/?.fnl;" .. fennel['macro-path']
local more_fennel = os.getenv("FENNEL_PATH")
if more_fennel then

View File

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

View File

@ -1,195 +0,0 @@
{:event "newlink"
:hwaddr "00:00:00:00:00:00"
:index 1
:mtu 65536
:name "lo"
:running "yes"
:stamp 857161382
:up "yes"}
{:event "newlink"
:hwaddr "50:3e:aa:08:df:52"
:index 2
:mtu 1500
:name "enp1s0"
:running "no"
:stamp 857161382
:up "yes"}
{:event "newlink"
:hwaddr "1c:1b:0d:9c:39:2d"
:index 3
:mtu 1500
:name "enp0s31f6"
:running "yes"
:stamp 857161382
:up "yes"}
{:event "newlink"
:hwaddr "da:4d:53:c3:54:43"
:index 4
:mtu 1500
:name "vbridge0"
:running "yes"
:stamp 857161382
:up "yes"}
{:event "newlink"
:hwaddr "00:28:f8:69:fa:14"
:index 6
:mtu 1500
:name "wlp4s0"
:running "no"
:stamp 857161382
:up "no"}
{:event "newlink"
:hwaddr "02:42:b1:e6:e5:bd"
:index 7
:mtu 1500
:name "br-7ddfef4820c5"
:running "no"
:stamp 857161382
:up "yes"}
{:event "newlink"
:hwaddr "02:42:8d:d4:36:34"
:index 8
:mtu 1500
:name "br-95da8b40a7cc"
:running "yes"
:stamp 857161382
:up "yes"}
{:event "newlink"
:hwaddr "02:42:bc:cf:a8:5e"
:index 9
:mtu 1500
:name "docker0"
:running "no"
:stamp 857161382
:up "yes"}
{:event "newlink"
:hwaddr "b6:66:50:69:33:a6"
:index 11
:mtu 1500
:name "veth2ff6ec3"
:running "yes"
:stamp 857161382
:up "yes"}
{:event "newlink"
:hwaddr "e6:94:c8:48:f3:97"
:index 13
:mtu 1500
:name "veth0913974"
:running "yes"
:stamp 857161382
:up "yes"}
{:event "newlink"
:hwaddr "9a:87:d8:f2:c6:96"
:index 15
:mtu 1500
:name "veth0e74156"
:running "yes"
:stamp 857161382
:up "yes"}
{:event "newlink"
:hwaddr "5e:d2:92:b9:5f:6d"
:index 17
:mtu 1500
:name "veth89a36b3"
:running "yes"
:stamp 857161382
:up "yes"}
{:event "newlink"
:hwaddr "ca:88:3f:09:bc:51"
:index 19
:mtu 1500
:name "veth73c1e0b"
:running "yes"
:stamp 857161382
:up "yes"}
{:event "newlink"
:hwaddr "b6:7d:5c:38:89:1d"
:index 21
:mtu 1500
:name "dummy0"
:running "no"
:stamp 857161382
:up "no"}
{:event "newlink"
:hwaddr "52:f0:46:da:0c:0c"
:index 22
:mtu 1500
:name "dummy1"
:running "yes"
:stamp 857161382
:up "yes"}
{:event "newneigh"
:hwaddr "00:22:61:3d:f7:54"
:index 4
:ip "192.168.8.140"
:probes 1
:stamp 857165355
:state "stale"}
{:event "delneigh"
:hwaddr "5c:60:ba:58:34:93"
:index 3
:stamp 857166891
:state "stale"}
{:event "newneigh"
:hwaddr "80:64:6f:9e:15:02"
:index 4
:ip "192.168.8.161"
:probes 1
:stamp 857172523
:state "stale"}
{:event "newneigh"
:hwaddr "e4:95:6e:42:c2:6c"
:index 3
:stamp 857174763
:state "reachable"}
{:event "newneigh"
:hwaddr "e4:b3:18:76:1b:23"
:index 4
:ip "2001:8b0:de3a:40de:4708:c700:4de2:9264"
:probes 1
:stamp 857175595
:state "stale"}
{:event "newneigh"
:hwaddr "80:64:6f:9e:10:c6"
:index 4
:ip "192.168.8.53"
:probes 1
:stamp 857176619
:state "stale"}
{:event "newneigh"
:hwaddr "80:64:6f:9e:15:02"
:index 4
:ip "192.168.8.161"
:probes 1
:stamp 857177643
:state "probe"}
{:event "newneigh"
:hwaddr "80:64:6f:9e:15:02"
:index 4
:ip "192.168.8.161"
:probes 1
:stamp 857177644
:state "reachable"}
{:event "newlink"
:hwaddr "b6:7d:5c:38:89:1d"
:index 21
:mtu 1500
:name "dummy0"
:running "yes"
:stamp 857178258
:up "yes"}
{:event "newlink"
:hwaddr "b6:7d:5c:38:89:1d"
:index 21
:mtu 1500
:name "dummy0"
:running "no"
:stamp 857181661
:up "no"}
{:event "newneigh"
:hwaddr "80:64:6f:9e:10:c6"
:index 4
:ip "192.168.8.53"
:probes 1
:stamp 857182251
:state "probe"}

View File

@ -1,64 +1,52 @@
(local nl (require :anoia.nl))
(local { : assoc : system } (require :anoia))
(local netlink (require :netlink))
(local sock (netlink.socket))
; (local { : view} (require :fennel))
(fn assoc [tbl k v]
(tset tbl k v)
tbl)
(fn parse-args [args]
(match args
["-v" & rest] (assoc (parse-args rest) :verbose true)
["-t" timeout & rest] (assoc (parse-args rest) :timeout (tonumber timeout))
["-s" service & rest] (assoc (parse-args rest) :service service)
[linkname "up"] {:link linkname :expecting "up"}
[linkname "running"] {:link linkname :expecting "running"}
[linkname "present"] {:link linkname :expecting "present"}
[linkname nil] {:link linkname :expecting "present"}
_ nil))
(fn event-matches? [params v]
(let [got
(match v
;; - up: Reflects the administrative state of the interface (IFF_UP)
;; - running: Reflects the operational state (IFF_RUNNING).
{:event "newlink" :name params.link :up :yes :running :yes}
{:present true :up true :running true}
(local parameters
(or
(parse-args arg)
(assert false (.. "Usage: " (. arg 0) " [-v] ifname [present|up|running]"))))
{:event "newlink" :name params.link :up :yes}
{:present :true :up true}
(fn run-events [evs]
(each [_ v (ipairs evs)]
(let [got
(match v
;; - up: Reflects the administrative state of the interface (IFF_UP)
;; - running: Reflects the operational state (IFF_RUNNING).
{:event "newlink" :name parameters.link :up :yes :running :yes}
{:present true :up true :running true}
{:event "newlink" :name params.link}
{:present true }
{:event "newlink" :name parameters.link :up :yes}
{:present :true :up true}
_
{})]
(not (not (. got params.expecting)))))
{:event "newlink" :name parameters.link}
{:present true }
(var up :unknown)
(fn toggle-service [service wanted?]
(when (not (= up wanted?))
(set up
(if wanted?
(pcall system (.. "s6-rc -b -u change " service))
(not (pcall system (.. "s6-rc -b -d change " service)))))
))
_
{})]
(when (. got parameters.expecting)
(os.exit 0)))))
(fn run [args event-fn]
(set up :unknown)
(let [parameters
(assert (parse-args args)
(.. "Usage: ifwait [-v] ifname [present|up|running]"))]
(when parameters.verbose
(print (.. "ifwait: waiting for "
parameters.link " to be " parameters.expecting)))
(if parameters.service
(each [e (event-fn)]
(if (= e.name parameters.link)
(toggle-service parameters.service (event-matches? parameters e))))
(each [e (event-fn)
&until (event-matches? parameters e)]
true))))
(when parameters.verbose
(print (.. (. arg 0) ": waiting for "
parameters.link " to be " parameters.expecting)))
(when (not (= (. arg 0) "test"))
(run arg #(nl.events {:link true})))
(run-events (sock:query {:link true}))
{ : run }
(while (sock:poll) (run-events (sock:event)))

View File

@ -1,117 +0,0 @@
(local { : view &as fennel } (require :fennel))
(local anoia (require :anoia))
(import-macros { : expect= } :anoia.assert)
;; nix-shell --run "cd pkgs/ifwait && fennelrepl test-ifwait.fnl"
(var fake-system (fn [s] (print "executing " s)))
(tset anoia :system #(fake-system $1))
(fn event-generator [events]
(coroutine.wrap
(fn []
(each [_ e (ipairs events)] (coroutine.yield e)))))
(fn file-events [path]
(let [data (with-open [e (io.open path "r")] (e:read "*a"))
parse (fennel.parser data)]
(icollect [_ ast parse]
ast)))
(set _G.arg (doto [] (tset 0 "test")))
(local ifwait (require :ifwait))
(let [gen (event-generator (file-events "events-fixture"))]
(ifwait.run ["dummy0" "up"] #gen)
(match (pcall gen)
(true _) true
(false msg) (error "didn't detect dummy0 up event")))
(var upsies [])
(set fake-system
(fn [s]
(if (s:match "-u change addmember")
(table.insert upsies :u)
(s:match "-d change addmember")
(table.insert upsies :d))))
(fn newlink [name up running]
{:event "newlink"
:hwaddr "b6:7d:5c:38:89:1d"
:index (string.unpack ">i2" name)
:mtu 1500
: name
: running
:stamp 857161382
: up })
"when it gets events that don't match the interface, nothing happens"
(let [gen (-> [(newlink "eth1" "no" "no")] event-generator)]
(set upsies [])
(ifwait.run [ "-s" "addmember" "dummy0" "up"] #gen)
(expect= upsies []))
"when it gets an event that should start the service, the service starts"
(let [gen (->
[(newlink "dummy0" "no" "no")
(newlink "dummy0" "yes" "no")
(newlink "eth1" "no" "no")]
event-generator)]
(set upsies [])
(ifwait.run ["-s" "addmember" "dummy0" "up"] #gen)
(expect= upsies [:d :u]))
"when it gets an event that should stop the service, the service stops"
(let [gen (->
[(newlink "dummy0" "no" "no")
(newlink "dummy0" "yes" "no")
(newlink "dummy0" "no" "no")
]
event-generator)]
(set upsies [])
(ifwait.run ["-s" "addmember" "dummy0" "up"] #gen)
(expect= upsies [:d :u :d]))
"it does not call s6-rc again if the service is already in required state"
(let [gen (->
[(newlink "dummy0" "no" "no")
(newlink "dummy0" "yes" "no")
(newlink "dummy0" "yes" "yes")
(newlink "dummy0" "yes" "yes")
(newlink "dummy0" "yes" "no")
(newlink "dummy0" "no" "no")
]
event-generator)]
(set upsies [])
(ifwait.run ["-s" "addmember" "dummy0" "up"] #gen)
(expect= upsies [:d :u :d]))
"it handles an error return from s6-rc"
(set fake-system
(fn [s]
(if (s:match "-u change addmember")
(table.insert upsies :u)
(s:match "-d change addmember")
(table.insert upsies :d))
(error "false")
))
(let [gen (->
[(newlink "dummy0" "yes" "no")
(newlink "dummy0" "yes" "yes")
(newlink "dummy0" "yes" "yes")
(newlink "dummy0" "yes" "no")
(newlink "dummy0" "no" "no")
]
event-generator)]
(set upsies [])
(ifwait.run ["-s" "addmember" "dummy0" "up"] #gen)
(expect= upsies [:u :u :u :u]))
(print "OK")

View File

@ -15,12 +15,10 @@ let
in {
kernel
, commandLine
, commandLineDtbNode ? "bootargs"
, entryPoint
, extraName ? "" # e.g. socFamily
, loadAddress
, imageFormat
, alignment ? null
, dtb ? null
} : stdenv.mkDerivation {
name = "kernel.image";
@ -41,7 +39,7 @@ in {
'';
mungeDtbPhase = ''
dtc -I dtb -O dts -o tmp.dts ${dtb}
echo '/{ chosen { ${commandLineDtbNode} = ${builtins.toJSON commandLine}; }; };' >> tmp.dts
echo '/{ chosen { bootargs = ${builtins.toJSON commandLine}; }; };' >> tmp.dts
dtc -I dts -O dtb -o tmp.dtb tmp.dts
'';
@ -71,7 +69,7 @@ in {
};
};
_VARS
mkimage -f mkimage.its ${lib.optionalString (alignment != null) "-B 0x${lib.toHexString alignment}"} kernel.uimage
mkimage -f mkimage.its kernel.uimage
mkimage -l kernel.uimage
'';

View File

@ -4,7 +4,7 @@
, systemconfig
, execline
, lib
, config ? {}
, services ? null
, liminix
, pseudofile
, pkgs
@ -12,7 +12,6 @@
let
inherit (pseudofile) dir symlink;
inherit (liminix.services) oneshot;
paramConfig = config;
newRoot = "/run/maintenance";
sysconfig =
let
@ -26,8 +25,8 @@ let
emptyenv chroot . /bin/init
'';
base = {...} : {
config = {
services = {
config = {
services = services // {
banner = oneshot {
name = "banner";
up = "cat /etc/banner > /dev/console";
@ -61,7 +60,6 @@ let
../../modules/users.nix
../../modules/busybox.nix
base
({ ... } : paramConfig)
../../modules/s6
];
};

View File

@ -11,7 +11,7 @@ test -n "$contents" && for d in $contents; do
touch $out/${name}/contents.d/$d
done
for i in timeout-up timeout-down run notification-fd up down finish consumer-for producer-for pipeline-name restart-on-upgrade; do
for i in 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

@ -18,7 +18,6 @@ let
${commands}
'';
cleanupScript = name : ''
#!/bin/sh
if test -d ${prefix}/${name} ; then rm -rf ${prefix}/${name} ; fi
'';
service = {
@ -27,25 +26,20 @@ let
, run ? null
, up ? null
, down ? null
, finish ? null
, outputs ? []
, notification-fd ? null
, producer-for ? null
, consumer-for ? null
, pipeline-name ? null
, timeout-up ? 30000 # milliseconds
, timeout-down ? 0
, dependencies ? []
, contents ? []
, buildInputs ? []
, isTrigger ? false
} @ args:
stdenvNoCC.mkDerivation {
# we use stdenvNoCC to avoid generating derivations with names
# like foo.service-mips-linux-musl
inherit name serviceType up down run finish notification-fd
producer-for consumer-for pipeline-name timeout-up timeout-down;
restart-on-upgrade = isTrigger;
inherit name serviceType up down run notification-fd
producer-for consumer-for pipeline-name;
buildInputs = buildInputs ++ dependencies ++ contents;
dependencies = builtins.map (d: d.name) dependencies;
contents = builtins.map (d: d.name) contents;
@ -58,7 +52,6 @@ let
, outputs ? []
, notification-fd ? null
, dependencies ? []
, buildInputs ? []
, ...
} @ args:
let logger = service {
@ -70,10 +63,9 @@ let
pipeline-name = "${name}-pipeline";
};
in service (args // {
buildInputs = buildInputs ++ [ logger ];
buildInputs = [ logger ];
serviceType = "longrun";
run = serviceScript run;
finish = cleanupScript name;
run = serviceScript "${run}\n${cleanupScript name}";
producer-for = "${name}-log";
});

View File

@ -1,25 +0,0 @@
{ lua, lib, fetchFromGitHub }:
let
pname = "lualinux";
src = fetchFromGitHub {
repo = "lualinux";
owner = "philanc";
rev = "1d4c962aad9cbe01c05df741b91e8b39c356362c";
hash = "sha256-+Ys4sERG+TI8nRzG38UP+KqbH0efspaX0j4IHCt56RI=";
};
in lua.pkgs.buildLuaPackage {
inherit pname;
version = "0.1"; # :shrug:
inherit src;
postPatch = ''
sed -i -e '/strip/d' Makefile
'';
makeFlags = [ "LUADIR=." "CC:=$(CC)" "STRIP=true" "lualinux.so" ];
installPhase = ''
mkdir -p "$out/lib/lua/${lua.luaversion}"
cp ${pname}.so "$out/lib/lua/${lua.luaversion}/"
'';
}

View File

@ -1,19 +1,12 @@
#!/usr/bin/env bash
ssh_command=${SSH_COMMAND-ssh}
reboot="reboot"
case "$1" in
"--no-reboot")
unset reboot
shift
;;
"--fast")
reboot="soft"
shift
;;
esac
if [ "$1" = "--no-reboot" ] ; then
reboot="true"
shift
else
reboot="reboot"
fi
target_host=$1
shift
@ -27,16 +20,7 @@ if toplevel=$(nix-build "$@" -A outputs.systemConfiguration --no-out-link); then
echo systemConfiguration $toplevel
min-copy-closure $target_host $toplevel
$ssh_command $target_host $toplevel/bin/install
case "$reboot" in
reboot)
$ssh_command $target_host "sync; source /etc/profile; reboot"
;;
soft)
$ssh_command $target_host $toplevel/bin/restart-services
;;
*)
;;
esac
$ssh_command $target_host "sync; source /etc/profile; reboot -f"
else
echo Rebuild failed
fi

View File

@ -1,22 +0,0 @@
{ lua, lib, fetchFromGitHub }:
let
pname = "minisock";
src = fetchFromGitHub {
repo = "minisock";
owner = "telent";
rev = "46e0470ff88c68f3a873dedbcf1dc351f4916b1a";
hash = "sha256-uTV5gpfEMvHMBgdu41Gy2uizc3K9bXtO5BiCY70cYUs=";
};
in lua.pkgs.buildLuaPackage {
inherit pname;
version = "0.1"; # :shrug:
inherit src;
makeFlags = [ "LUADIR=." "minisock.so" ];
installPhase = ''
mkdir -p "$out/lib/lua/${lua.luaversion}"
cp ${pname}.so "$out/lib/lua/${lua.luaversion}/"
'';
}

View File

@ -1,28 +0,0 @@
{ lua, lib, fetchpatch, fetchFromGitHub, stdenv }:
let pname = "nellie";
in lua.pkgs.buildLuaPackage {
inherit pname;
version = "0.1.1-1";
src = ./.;
buildPhase = "$CC -shared -l lua -o nellie.so nellie.c";
# for the checks to work you need to
# nix-build--option sandbox false
# otherwise the sandbox doesn't see any uevent messages
# doCheck = stdenv.hostPlatform == stdenv.buildPlatform;
checkPhase = ''
export LUA_CPATH=./?.so
lua test.lua
'';
installPhase = ''
mkdir -p "$out/lib/lua/${lua.luaversion}"
cp nellie.so "$out/lib/lua/${lua.luaversion}/"
'';
}

View File

@ -1,90 +0,0 @@
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
#include <linux/netlink.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
static int l_close_socket(lua_State *L) {
lua_getfield(L, 1, "fileno");
int fd = (int) lua_tointeger(L, -1);
close(fd);
return 0;
}
static int l_read_from_socket(lua_State *L) {
int length = 32;
if(lua_isnumber(L, 2))
length = lua_tointeger(L, 2);
lua_getfield(L, 1, "fileno");
int fd = (int) lua_tointeger(L, -1);
char *buf = (char *) malloc(length);
int bytes = recv(fd, buf, length, 0);
if(bytes > 0) {
lua_pushlstring(L, buf, bytes);
free(buf);
return 1;
} else {
free(buf);
return 0;
}
}
static int l_open_socket(lua_State *L) {
int netlink_fd = socket(AF_NETLINK, SOCK_RAW|SOCK_CLOEXEC, NETLINK_KOBJECT_UEVENT);
struct sockaddr_nl sa;
memset(&sa, 0, sizeof(sa));
sa.nl_family = AF_NETLINK;
sa.nl_pid = getpid();
if(lua_isnumber(L, 1)) {
sa.nl_groups = lua_tointeger(L, 1);
lua_pop(L, 1);
}
else {
sa.nl_groups = 4; /* group 4 is rebroadcasts from mdevd */
}
if(bind(netlink_fd, (struct sockaddr *) &sa, sizeof(sa))==0) {
lua_newtable(L);
lua_pushliteral(L, "fileno");
lua_pushinteger(L, netlink_fd);
lua_settable(L, 1);
lua_pushliteral(L, "read");
lua_pushcfunction(L, l_read_from_socket);
lua_settable(L, 1);
lua_pushliteral(L, "close");
lua_pushcfunction(L, l_close_socket);
lua_settable(L, 1);
return 1;
} else {
return 0;
}
}
static const struct luaL_Reg funcs [] = {
{"open", l_open_socket},
{NULL, NULL} /* sentinel */
};
/* "luaopen_" prefix is magic and tells lua to run this function
* when it dlopens the library
*/
int luaopen_nellie (lua_State *L) {
luaL_newlib(L, funcs);
return 1;
}

View File

@ -1,6 +0,0 @@
local nellie = require('nellie')
print('dfg')
local f = nellie.open(2)
print(string.byte(f:read(1000), 0, 60))
print("CLOSED", f:close())

View File

@ -2,6 +2,5 @@
writeFennelScript
, anoia
, lua
, lualinux
}:
writeFennelScript "odhcpc-script" [anoia lualinux] ./odhcp6-script.fnl
writeFennelScript "odhcpc-script" [anoia lua.pkgs.luafilesystem] ./odhcp6-script.fnl

View File

@ -1,7 +1,6 @@
{
fetchFromGitHub
, writeShellScript
, pkgsBuildBuild
}:
let
src = fetchFromGitHub {
@ -15,7 +14,6 @@ let
cp -av ${src}/target/linux/generic/files/* .
chmod -R u+w .
cp -av ${src}/target/linux/${family}/files/* .
chmod -R u+w .
test -d ${src}/target/linux/${family}/files-5.15/ && cp -av ${src}/target/linux/${family}/files-5.15/* .
chmod -R u+w .
patches() {
@ -37,25 +35,4 @@ in {
applyPatches.ramips = doPatch "ramips";
applyPatches.mediatek = doPatch "mediatek"; # aarch64
applyPatches.mvebu = doPatch "mvebu"; # arm
applyPatches.rt2x00 = ''
PATH=${pkgsBuildBuild.patchutils}/bin:$PATH
for i in ${src}/package/kernel/mac80211/patches/rt2x00/6*.patch ; do
fixed=$(basename $i).fixed
sed '/depends on m/d' < $i | sed 's/CPTCFG_/CONFIG_/g' | recountdiff | filterdiff -x '*/local-symbols' > $fixed
case $fixed in
606-*)
;;
611-*)
filterdiff -x '*/rt2x00.h' < $fixed | patch --forward -p1
;;
601-*|607-*)
filterdiff -x '*/rt2x00_platform.h' < $fixed | patch --forward -p1
;;
*)
cat $fixed | patch --forward -p1
;;
esac
done
'';
}

View File

@ -21,27 +21,11 @@ in stdenvNoCC.mkDerivation {
if test -d $i; then
for j in $i/* ; do
if test -f $j/type ; then
if test -e $j/restart-on-upgrade; then
flag=force-restart
else
unset flag
fi
case $(cat $j/type) in
longrun|oneshot)
# s6-rc-update only wants atomics in its
# restarts file
echo $(basename $j) " " ''${flag-$i} >> $out/hashes
;;
*)
;;
esac
srcs="$srcs $i"
fi
done
fi
done
s6-rc-compile $out/compiled $srcs
s6-rc-db -c $out/compiled contents default
mv $out/hashes $out/compiled
'';
}
'';
}

View File

@ -6,7 +6,6 @@
{
writeText
, writeFennelScript
, lib
, s6-init-bin
, closureInfo
@ -53,7 +52,7 @@ let
chown = if uid>0 || gid>0
then "\nCHOWN(${qpathname},${toString uid},${toString gid});\n"
else "";
in "unlink(${qpathname}); ${cmd} ${chown}";
in "${cmd} ${chown}";
in mapAttrsToList (makeFile prefix) attrset;
activateScript = attrset: writeText "makedevs.c" ''
#include "defs.h"
@ -81,7 +80,6 @@ in attrset:
cp $closure/store-paths $out/etc/nix-store-paths
$STRIP --remove-section=.note --remove-section=.comment --strip-all makedevs -o $out/bin/activate
ln -s ${s6-init-bin}/bin/init $out/bin/init
cp -p ${writeFennelScript "restart-services" [] ./restart-services.fnl} $out/bin/restart-services
cat > $out/bin/install <<EOF
#!/bin/sh -e
prefix=\''${1-/}

View File

@ -1,40 +0,0 @@
(fn hashes-from-file [name]
(with-open [f (assert (io.open name :r) name)]
(accumulate [h []
l #(f:read "*l")]
(let [(name hash) (string.match l "([^%s]+) +([^%s]+)")]
(if name
(doto h (tset name hash))
h)))))
(fn write-restarts [old new]
(let [old-hashes (hashes-from-file old)
new-hashes (hashes-from-file new)]
(with-open [f (io.open "/tmp/restarts" :w)]
(each [n h (pairs old-hashes)]
(let [new (. new-hashes n)]
(when (or (= h "force-restart")
(= new "force-restart")
(not (= h new)))
(f:write (.. n " restart\n"))))))))
(fn exec [text command]
(io.write (.. text ": "))
(match (os.execute command)
res (print "[OK]")
(nil err) (error (.. "[FAILED " err "]"))))
(let [mypath (: (. arg 0) :match "(.*/)")
activate (.. mypath "activate /")
old-compiled "/run/s6-rc/compiled/"
new-compiled "/etc/s6-rc/compiled/"]
(exec "installing FHS files" activate)
(write-restarts (.. old-compiled "hashes") (.. new-compiled "hashes"))
(exec "updating service database"
(.. "s6-rc-update -f /tmp/restarts " new-compiled))
(exec "starting services" (.. "s6-rc -u -p change default"))
)

View File

@ -1,12 +0,0 @@
loopback /nix/store/3k7yp08465k8z8a1514slansj0hbrvbq-loopback
default /nix/store/5247rr0mcr17x9lkaqjiywxmn4q3ibgd-default
lan.link.a.10.3.0.1 /nix/store/99ic5bryzbx12ip4v8sh9cdrv15c8qwl-lan.link.a.10.3.0.1
lo.link.a.-1 /nix/store/a2yvj42ghcg8iyvi51zcmzmpwwkrx30l-lo.link.a.-1
sshd /nix/store/afgznmavi6s9dxkv4vnn9xwd7qz5kk35-sshd
lo.link /nix/store/dz0nvlf16lqv9yndas0sk0xqh7jvx8gm-lo.link
lo.link.a.127.0.0.1 /nix/store/jl018kbgxcnyb70s0cfw19q1zqa0pval-lo.link.a.127.0.0.1
lan.link /nix/store/lxvmhb7ax46s2akj9lsivcq494kmx0hm-lan.link
lan.link.a.10.3.0.1.dnsmasq-log /nix/store/rk8265ikd9lj3fpibyrw0kal32h0ksqg-lan.link.a.10.3.0.1.dnsmasq-log
hostname /nix/store/xfy0ymvnd0zv6wcd26h405f85hfkd8wq-hostname
sshd-log /nix/store/xj58nxvkacsjg3rvwb4bxcqnhaav4d3s-sshd-log
lan.link.a.10.3.0.1.dnsmasq /nix/store/z4l7h9pr4an9aq921cs9wrlvf9b8xr48-lan.link.a.10.3.0.1.dnsmasq

View File

@ -1,12 +0,0 @@
loopback /nix/store/3k7yp08465k8z8a1514slansj0hbrvbq-loopback
default /nix/store/5247rr0mcr17x9lkaqjiywxmn4q3ibgd-default
lan.link.a.10.3.0.1 /nix/store/99ic5bryzbx12ip4v8sh9cdrv15c8qwl-lan.link.a.10.3.0.1
lo.link.a.-1 /nix/store/a2yvj42ghcg8iyvi51zcmzmpwwkrx30l-lo.link.a.-1
sshd /nix/store/afgznmavi6s9dxkv4vnn9xwd7qz5kk35-sshd
lo.link /nax/store/dz0nvlf16lqv9yndas0sk0xqh7jvx8gm-lo.link
lo.link.a.127.0.0.1 /nix/store/jl018kbgxcnyb70s0cfw19q1zqa0pval-lo.link.a.127.0.0.1
lan.link /nix/store/lxvmhb7ax46s2akj9lsivcq494kmx0hm-lan.link
lan.link.a.10.3.0.1.dnsmasq-log /nix/store/rk8265ikd9lj3fpibyrw0kal32h0ksqg-lan.link.a.10.3.0.1.dnsmasq-log
hostname /nax/store/xfy0ymvnd0zv6wcd26h405f85hfkd8wq-hostname
sshd-log /nax/store/xj58nxvkacsjg3rvwb4bxcqnhaav4d3s-sshd-log

View File

@ -1,27 +0,0 @@
{
lua
, nellie
, lualinux
, writeFennel
, runCommand
, anoia
, fennel
, stdenv
, fennelrepl
}:
stdenv.mkDerivation {
name = "uevent-watch";
src = ./.;
nativeBuildInputs = [ fennelrepl ];
installPhase = ''
mkdir -p $out/bin
cp -p ${writeFennel "uevent-watch" {
packages = [fennel anoia nellie lualinux];
mainFunction = "run";
} ./watch.fnl} $out/bin/uevent-watch
'';
checkPhase = ''
fennelrepl ./test.fnl
'';
doCheck = true;
}

View File

@ -1,25 +0,0 @@
add@/devices/pci0000:00/0000:00:14.0/usb1/1-3/1-3:1.0/host1/target1:0:0/1:0:0:0/block/sdb/sdb1
ACTION=add
DEVPATH=/devices/pci0000:00/0000:00:14.0/usb1/1-3/1-3:1.0/host1/target1:0:0/1:0:0:0/block/sdb/sdb1
SUBSYSTEM=block
DEVNAME=/dev/sdb1
DEVTYPE=partition
DISKSEQ=33
PARTN=1
SEQNUM=82381
MAJOR=8
PARTNAME=backup-disk
MINOR=17
remove@/devices/pci0000:00/0000:00:14.0/usb1/1-3/1-3:1.0/host1/target1:0:0/1:0:0:0/block/sdb/sdb1
ACTION=remove
DEVPATH=/devices/pci0000:00/0000:00:14.0/usb1/1-3/1-3:1.0/host1/target1:0:0/1:0:0:0/block/sdb/sdb1
SUBSYSTEM=block
DEVNAME=/dev/sdb1
DEVTYPE=partition
DISKSEQ=33
PARTN=1
SEQNUM=82386
PARTNAME=backup-disk
MAJOR=8
MINOR=17

View File

@ -1,33 +0,0 @@
(local watch (require :watch))
;; Events come from the devout socket as an initial summary line
;; followed by a NUL character followed by newline-separated key=value
;; pairs. For ease of editing we don't have NULs in events.txt,
;; so we need to massage it into shape here
(local events
(with-open [f (io.open "./events.txt" :r)]
(let [text (string.gsub (f:read "*a") "\n\n" "\0") ]
(icollect [n (string.gmatch text "([^\0]+)")]
(string.gsub n "\n" "\0" 1)))))
(fn next-event []
(var i 0)
(fn []
(let [i_ (+ 1 i)
e (. events i_)]
(set i i_)
e)))
;; this tests event parsing but not whether anything
;; happens as a result of processing them
(watch.run-with-fh
{
:read (next-event)
:write (fn [payload] )
}
["-s" "foo" "-n" (os.getenv "TMPDIR") "partname=backup-disk" ]
)
(print "OK")

View File

@ -1,77 +0,0 @@
(local { : assoc : system : dirname } (require :anoia))
(local { : mktree : rmtree : symlink } (require :anoia.fs))
(local { : AF_LOCAL : SOCK_STREAM } (require :anoia.net.constants))
(local ll (require :lualinux))
(local { : view } (require :fennel))
(fn parse-args [args]
(match args
["-s" service & rest] (assoc (parse-args rest) :service service)
["-n" path & rest] (assoc (parse-args rest) :linkname path)
matches { :matches (table.concat matches " ") }
_ nil))
(fn %% [fmt ...] (string.format fmt ...))
(var up :unknown)
(fn start-service [devname linkname service]
(match (symlink (.. "/dev/" devname ) linkname)
ok (pcall system (%% "s6-rc -b -u change %q" service))
(nil err) false))
(fn stop-service [linkname service]
(match (pcall system (%% "s6-rc -b -d change %q" linkname service))
ok (os.remove linkname)
(nil err) false))
(fn toggle-service [devname linkname service wanted?]
(when (not (= up wanted?))
(set up
(if wanted?
(start-service devname linkname service)
(not (stop-service linkname service))))))
(fn parse-uevent [s]
(when s
(let [(nl nxt) (string.find s "\0" 1 true)]
(collect [k v (string.gmatch
(string.sub s (+ 1 nxt))
"(%g-)=(%g+)")]
(k:lower) v))))
(fn run-with-fh [fh args]
(set up :unknown)
(let [parameters
(assert (parse-args args) (.. "can't parse args: " (table.concat args " ")))]
(mktree (dirname parameters.linkname))
(var finished? false)
(print "registering for events" (fh:write parameters.matches))
(while (not finished?)
(let [e (parse-uevent (fh:read))]
(when e
(let [wanted? (. {:add true :change true} e.action)]
(toggle-service e.devname parameters.linkname parameters.service wanted?)))
(set finished? (= e nil))
))))
(fn unix-connect [pathname]
(let [addr (string.pack "=Hz" AF_LOCAL pathname)]
(match (ll.socket AF_LOCAL SOCK_STREAM 0)
sock (doto sock (ll.connect addr))
(nil err) (error err))))
(fn run [args]
(let [fd (assert (unix-connect "/run/devout.sock"))
devout {
:read #(ll.read fd)
:write #(ll.write fd $2)
:close #(ll.close fd)
}]
(run-with-fh devout arg)))
{ : run : run-with-fh }

View File

@ -1,16 +0,0 @@
{
stdenv
, openwrt
}:
stdenv.mkDerivation {
name = "zyxel-bootconfig";
inherit (openwrt) src;
sourceRoot = "openwrt-source/package/utils/zyxel-bootconfig/src";
installPhase = ''
mkdir -p $out/bin
install -Dm544 zyxel-bootconfig $out/bin/zyxel-bootconfig
'';
meta = {
mainProgram = "zyxel-bootconfig";
};
}

View File

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

View File

@ -1,55 +0,0 @@
{ config, pkgs, lib, modulesPath, ... } :
let
inherit (pkgs.liminix.services) bundle oneshot longrun;
inherit (pkgs.pseudofile) dir symlink;
inherit (pkgs) serviceFns;
svc = config.system.service;
in rec {
imports = [
"${modulesPath}/dhcp6c"
"${modulesPath}/dnsmasq"
"${modulesPath}/firewall"
"${modulesPath}/hostapd"
"${modulesPath}/network"
"${modulesPath}/ssh"
"${modulesPath}/mount"
"${modulesPath}/mdevd.nix"
];
filesystem = dir { srv = dir {}; };
kernel = {
config = {
USB = "y";
USB_EHCI_HCD = "y";
USB_EHCI_HCD_PLATFORM = "y";
USB_OHCI_HCD = "y";
USB_OHCI_HCD_PLATFORM = "y";
USB_SUPPORT = "y";
USB_COMMON = "y";
USB_STORAGE = "y";
USB_STORAGE_DEBUG = "n";
USB_UAS = "y";
USB_ANNOUNCE_NEW_DEVICES = "y";
SCSI = "y";
BLK_DEV_SD = "y";
USB_PRINTER = "y";
MSDOS_PARTITION = "y";
EFI_PARTITION = "y";
EXT4_FS = "y";
EXT4_USE_FOR_EXT2 = "y";
FS_ENCRYPTION = "y";
};
};
rootfsType = "jffs2";
hostname = "inout";
services.mount_backup_disk = svc.mount.build {
partlabel = "backup-disk";
mountpoint = "/srv";
fstype = "ext4";
};
}

View File

@ -1,55 +0,0 @@
set timeout 10
set when [lindex $argv 0];
spawn socat -,echo=0,icanon=1 unix-connect:vm/monitor
set monitor_id $spawn_id
spawn socat unix-connect:vm/console -
set console_id $spawn_id
proc chat {instr outstr} {
expect {
$instr { send $outstr }
timeout { exit 1 }
}
}
proc adddevice { } {
global monitor_id console_id spawn_id
set spawn_id $monitor_id
chat "QEMU" "device_add usb-storage,bus=xhci.0,drive=usbstick\n"
chat "(qemu)" "version\r"
set spawn_id $console_id
expect {
"sda: sda1" { }
timeout { exit 1 }
}
}
if { $when eq "early" } {
adddevice
}
expect "BusyBox"
chat "#" "PS1=RE\\ADY_\\ ; stty -echo \r"
chat "READY_" "tail -f /run/uncaught-logs/current & \rs6-rc -b -a list\r"
chat "mount" "\r"
if { $when eq "late" } {
adddevice
}
send "\r"
set timeout 20
chat "READY_" "sleep 3; grep /srv /proc/mounts && hostname\r"
expect {
"inout" { }
timeout { exit 1 }
}

View File

@ -1,37 +0,0 @@
{
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
e2fsprogs
util-linux # for sfdisk, fallocate
parted
] ;
} ''
mkdir vm
dd if=/dev/zero of=./vm/stick.e2fs bs=1M count=32
mkfs.ext2 -L backup-disk ./vm/stick.e2fs
dd if=/dev/zero of=./vm/stick.img bs=1M count=38
dd if=./vm/stick.e2fs of=./vm/stick.img bs=512 seek=34 conv=notrunc
parted -s ./vm/stick.img -- mklabel gpt mkpart backup-disk ext2 34s -0M
{
${img}/run.sh --background ./vm --flag -device --flag usb-ehci,id=xhci --flag -drive --flag if=none,id=usbstick,format=raw,file=$(pwd)/vm/stick.img
expect ${./script.expect} late
kill $(cat ./vm/pid)
${img}/run.sh --background ./vm --flag -device --flag usb-ehci,id=xhci --flag -drive --flag if=none,id=usbstick,format=raw,file=$(pwd)/vm/stick.img
expect ${./script.expect} early
} | tee $out
''

View File

@ -32,5 +32,9 @@ in {
};
rootfsType = "jffs2";
services.default = lib.mkForce (target {
name = "default";
contents = with config.services; [ loopback ntp defaultroute4 sshd dhcpv4 ];
});
};
}

29
tests/min-copy-closure/test-liminix-rebuild.sh Executable file → Normal file
View File

@ -1,26 +1,14 @@
#!/usr/bin/env nix-shell
#! nix-shell -v -i bash -p expect socat
# This is a test for liminix-rebuild. It's not a CI test because
# liminix-rebuild calls nix-build so won't run inside a derivation,
# meaning you have to remember to run it manually when changing
# liminix-rebuild
# nix-shell -p expect socat --run "sh ./tests/min-copy-closure/test-liminix-rebuild.sh "
. tests/test-helpers.sh
set -e
while test -n "$1"; do
case $1 in
--fast)
FAST=true
;;
*)
;;
esac
shift
done
here=$(pwd)/tests/min-copy-closure
top=$(pwd)
@ -43,7 +31,7 @@ mkdir ./vm
cat ${rootfs} > rootfs
truncate -s 32M rootfs
truncate -s 24M rootfs
resize2fs rootfs
dd if=rootfs of=disk-image bs=512 seek=4 conv=sync
@ -62,12 +50,9 @@ echo "READY"
touch known_hosts
export SSH_COMMAND="ssh -o UserKnownHostsFile=${work}/known_hosts -o StrictHostKeyChecking=no -p 2022 -i ${here}/id"
if test -n "$FAST"; then
(cd ${top} && liminix-rebuild --fast root@localhost -I liminix-config=${here}/with-figlet.nix --argstr deviceName qemu-armv7l)
cd ${work} && expect $here/wait-for-soft-restart.expect
else
(cd ${top} && liminix-rebuild root@localhost -I liminix-config=${here}/with-figlet.nix --arg device "import ./devices/qemu-armv7l")
cd ${work} && expect $here/wait-for-reboot.expect
fi
(cd ${top} && liminix-rebuild root@localhost -I liminix-config=${here}/with-figlet.nix --arg device "import ./devices/qemu-armv7l")
ls -l vm
cd ${work} && expect $here/wait-for-reboot.expect
cd / ; rm -rf $work

View File

@ -1,13 +0,0 @@
proc chat {instr outstr} {
expect {
$instr { send $outstr }
timeout { exit 1 }
}
}
spawn socat unix-connect:vm/console -
send "exit\r"
chat "BusyBox" "\r"
chat "#" "stty -echo; type -p figlet\r"
chat "figlet-armv7l-unknown-linux" "s6-rc -a list |grep w\\inkle\r"
chat "winkle" "poweroff\r"

View File

@ -5,9 +5,4 @@ send "\r\n"
expect {
"# " { send "hostname\r\n" };
}
expect {
"(none)" {}
"liminix" {}
timeout { exit(1) }
}
expect "(none)"

View File

@ -4,11 +4,4 @@
defaultProfile.packages = with pkgs; [
figlet
];
services.ripvanwinkle = pkgs.liminix.services.longrun {
name = "winkle";
run = ''
echo SLEEPING > /dev/console
sleep 3600
'';
};
}

View File

@ -1,55 +0,0 @@
{ 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

@ -1,64 +0,0 @@
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 }
}

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