forked from dan/liminix
Compare commits
162 Commits
Author | SHA1 | Date |
---|---|---|
Daniel Barlow | 471c63b399 | |
Daniel Barlow | 782feaeafa | |
Daniel Barlow | ac54c89427 | |
Daniel Barlow | 5a3646cb29 | |
Daniel Barlow | e249f48cff | |
Daniel Barlow | 6661e42684 | |
Daniel Barlow | b9ba9ef835 | |
Daniel Barlow | 8b69dcc209 | |
Daniel Barlow | 9b3a3b9ff7 | |
Daniel Barlow | 7d08497bcb | |
Daniel Barlow | 0e84adaa0e | |
Daniel Barlow | 660ed5df8f | |
Daniel Barlow | 792a11c8c0 | |
Daniel Barlow | 7e4a05bbf8 | |
Daniel Barlow | a4ba5c85e1 | |
Daniel Barlow | 723ef73d5a | |
Daniel Barlow | 3d4e782929 | |
Daniel Barlow | 1b6a05aec5 | |
Daniel Barlow | 80628a3d90 | |
Daniel Barlow | bf0cafffed | |
Daniel Barlow | e49aba127c | |
Daniel Barlow | 324465bc18 | |
Daniel Barlow | b33249a050 | |
Daniel Barlow | b9c084415e | |
Daniel Barlow | cf9cadd212 | |
Daniel Barlow | a116fe084a | |
Daniel Barlow | 74cf3e0711 | |
Daniel Barlow | 9795f03da4 | |
Daniel Barlow | cdb23b147c | |
Daniel Barlow | dbd1264352 | |
Daniel Barlow | 834858d5bc | |
Daniel Barlow | 18335b95e3 | |
Daniel Barlow | 6bee2f67ac | |
Daniel Barlow | b4ba3eea21 | |
Daniel Barlow | 16af3984c9 | |
Daniel Barlow | ce7e395295 | |
Daniel Barlow | 7e13e017eb | |
Daniel Barlow | bbf2f53c0e | |
Daniel Barlow | 032d0f8aca | |
Daniel Barlow | b8ac9e5279 | |
Daniel Barlow | ff2604ca5d | |
Daniel Barlow | 72789984ce | |
Daniel Barlow | 90d9d0e811 | |
Daniel Barlow | 97a8ae1c84 | |
Daniel Barlow | 52eb283a26 | |
Daniel Barlow | cbb1de804e | |
Daniel Barlow | f9c03998b8 | |
Daniel Barlow | 50de1b090f | |
Daniel Barlow | 648382f64a | |
Daniel Barlow | e9370358ae | |
Daniel Barlow | 762ce7b6b8 | |
Daniel Barlow | b1c0560f4f | |
Daniel Barlow | e34135c41a | |
Daniel Barlow | 712c9b266f | |
Daniel Barlow | 4df963996c | |
Daniel Barlow | 349bfecbb8 | |
Daniel Barlow | 450d3820b2 | |
Daniel Barlow | 771585546d | |
Daniel Barlow | 73abf952d5 | |
Daniel Barlow | 8af4e9fd5b | |
Daniel Barlow | 7e19d80130 | |
Daniel Barlow | 0f0688c802 | |
Daniel Barlow | b43f17f655 | |
Daniel Barlow | adf62d4483 | |
Daniel Barlow | 68eb1360f6 | |
Daniel Barlow | 19ad6cd278 | |
Daniel Barlow | 00076c7b81 | |
Daniel Barlow | 721e7499f3 | |
Daniel Barlow | fc723b9a35 | |
Daniel Barlow | a5f16dfa81 | |
Daniel Barlow | 41a4b1f7ef | |
Daniel Barlow | 42a5699326 | |
Daniel Barlow | ea2b25168e | |
Daniel Barlow | 5564cf0554 | |
Daniel Barlow | f3a13630d3 | |
Daniel Barlow | f233acf9ff | |
Daniel Barlow | b6a054c588 | |
Daniel Barlow | b231664a06 | |
Daniel Barlow | f4bf3029fa | |
Daniel Barlow | 05f2c9a2f7 | |
Daniel Barlow | 5df5c822ea | |
Daniel Barlow | 4795dd05b7 | |
Daniel Barlow | a192f08881 | |
Daniel Barlow | a873dc6608 | |
Daniel Barlow | 2fb4756a7f | |
Daniel Barlow | 04f5174425 | |
Daniel Barlow | dca2e4def1 | |
Daniel Barlow | b60126775a | |
Daniel Barlow | 76f11bcc93 | |
Daniel Barlow | efcfdcc21d | |
Daniel Barlow | 77f1a78331 | |
Daniel Barlow | 28a5dec7dd | |
Daniel Barlow | fad0a47b75 | |
Daniel Barlow | af52aafc84 | |
Daniel Barlow | 34442b6069 | |
Daniel Barlow | b8a46fc05e | |
Daniel Barlow | 8ac2c6cec1 | |
Daniel Barlow | 8879b2d1ba | |
Daniel Barlow | 83e346d5a0 | |
Daniel Barlow | 156b1fe64a | |
Daniel Barlow | 1a314e55b7 | |
Daniel Barlow | 9263b21faa | |
Daniel Barlow | 0a820a702a | |
Daniel Barlow | 4ea518e296 | |
Daniel Barlow | 98318b450d | |
Daniel Barlow | e4ac7f19dc | |
Daniel Barlow | 9c22744850 | |
Daniel Barlow | c697be8c28 | |
dan | 202a37221a | |
Florian Klink | 436eb03a7b | |
Daniel Barlow | e5963ae3f7 | |
Daniel Barlow | f164f19d95 | |
Daniel Barlow | dd4ab41f6a | |
Daniel Barlow | 5d5dff6729 | |
Daniel Barlow | 570d29c368 | |
Daniel Barlow | 725af00dc9 | |
Daniel Barlow | e1b932ec27 | |
Daniel Barlow | 7173b6fb1c | |
Daniel Barlow | ed9548f21d | |
Daniel Barlow | 0787807a7f | |
Daniel Barlow | 38ed91f641 | |
Daniel Barlow | ffe9603c39 | |
Daniel Barlow | cbd3dfefc5 | |
Daniel Barlow | 018c1868b5 | |
Daniel Barlow | 5184ff63f7 | |
Daniel Barlow | 35909c9a23 | |
Daniel Barlow | 4383462199 | |
Daniel Barlow | 9730cdd63b | |
dan | 095853214b | |
Daniel Barlow | 9d6e50cbbc | |
Daniel Barlow | 94dbc56595 | |
Daniel Barlow | 2cd7f932eb | |
sinavir | 27c7735f02 | |
sinavir | 29c9de248d | |
Daniel Barlow | 3ca0d87c27 | |
Daniel Barlow | 8f30db58ae | |
Daniel Barlow | f9ab0590a6 | |
Daniel Barlow | 84fa8d65f4 | |
Daniel Barlow | 9b0149ecb7 | |
Raito Bezarius | baf3cf7413 | |
Raito Bezarius | c5145b5fc9 | |
Raito Bezarius | 628f4dfdbe | |
Raito Bezarius | da59e2a349 | |
Raito Bezarius | c0a9571a13 | |
Raito Bezarius | d6ffdd7be6 | |
Raito Bezarius | 985f982435 | |
Raito Bezarius | a893c0dc4c | |
Raito Bezarius | 3ec29dc1b9 | |
Raito Bezarius | 0e81953b67 | |
Raito Bezarius | 3c70a0d037 | |
Raito Bezarius | 422f3edab1 | |
Raito Bezarius | c14b2f6356 | |
Raito Bezarius | cdafff2095 | |
Raito Bezarius | 13f1bb9f52 | |
Raito Bezarius | 019fef6929 | |
Raito Bezarius | 63007859c2 | |
Raito Bezarius | e9ab8d7183 | |
Raito Bezarius | 3dc58de0eb | |
Raito Bezarius | dde8386f75 | |
Raito Bezarius | c59364d623 | |
Raito Bezarius | b76c5b4abe | |
Raito Bezarius | 0a8343be66 |
25
NEWS
25
NEWS
|
@ -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,3 +80,26 @@ 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;
|
||||
};
|
||||
})
|
||||
];
|
||||
|
|
630
THOUGHTS.txt
630
THOUGHTS.txt
|
@ -4111,3 +4111,633 @@ 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?
|
||||
|
|
|
@ -4,6 +4,10 @@ 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 ''
|
||||
|
@ -108,6 +112,7 @@ in {
|
|||
tufted
|
||||
iptables
|
||||
usbutils
|
||||
busybox
|
||||
];
|
||||
security.sudo.wheelNeedsPassword = false;
|
||||
networking = {
|
||||
|
@ -122,6 +127,7 @@ in {
|
|||
isNormalUser = true;
|
||||
uid = 1000;
|
||||
extraGroups = [ "wheel"];
|
||||
openssh.authorizedKeys.keys = cfg.keys;
|
||||
};
|
||||
services.getty.autologinUser = "liminix";
|
||||
};
|
||||
|
|
9
ci.nix
9
ci.nix
|
@ -9,9 +9,14 @@ let
|
|||
borderVmConf = ./bordervm.conf-example.nix;
|
||||
inherit (pkgs.lib.attrsets) genAttrs;
|
||||
devices = [
|
||||
"gl-ar750" "gl-mt300n-v2" "gl-mt300a"
|
||||
"qemu" "qemu-aarch64" "qemu-armv7l"
|
||||
"gl-ar750"
|
||||
"gl-mt300a"
|
||||
"gl-mt300n-v2"
|
||||
"qemu"
|
||||
"qemu-aarch64"
|
||||
"qemu-armv7l"
|
||||
"tp-archer-ax23"
|
||||
"zyxel-nwa50ax"
|
||||
];
|
||||
vanilla = ./vanilla-configuration.nix;
|
||||
for-device = name:
|
||||
|
|
12
default.nix
12
default.nix
|
@ -1,8 +1,10 @@
|
|||
{
|
||||
device
|
||||
deviceName ? null
|
||||
, device ? (import ./devices/${deviceName} )
|
||||
, liminix-config ? <liminix-config>
|
||||
, nixpkgs ? <nixpkgs>
|
||||
, borderVmConf ? ./bordervm.conf.nix
|
||||
, imageType ? "primary"
|
||||
}:
|
||||
|
||||
let
|
||||
|
@ -19,17 +21,24 @@ 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;
|
||||
|
@ -68,6 +77,7 @@ in {
|
|||
min-copy-closure
|
||||
fennelrepl
|
||||
lzma
|
||||
lua
|
||||
];
|
||||
};
|
||||
}
|
||||
|
|
|
@ -73,7 +73,7 @@
|
|||
MTK_INFRACFG = "y";
|
||||
|
||||
MTK_PMIC_WRAP = "y";
|
||||
MTK_EFUSE="y";
|
||||
NVMEM_MTK_EFUSE="y";
|
||||
# MTK_HSDMA="y";
|
||||
MTK_SCPSYS="y";
|
||||
MTK_SCPSYS_PM_DOMAINS="y";
|
||||
|
@ -92,7 +92,6 @@
|
|||
|
||||
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
|
||||
|
|
|
@ -110,13 +110,11 @@
|
|||
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";
|
||||
|
@ -126,7 +124,8 @@
|
|||
};
|
||||
boot.tftp = {
|
||||
loadAddress = lim.parseInt "0x00A00000";
|
||||
};
|
||||
appendDTB = true;
|
||||
};
|
||||
|
||||
kernel = {
|
||||
src = pkgs.fetchurl {
|
||||
|
@ -136,6 +135,7 @@
|
|||
};
|
||||
extraPatchPhase = ''
|
||||
${openwrt.applyPatches.ramips}
|
||||
${openwrt.applyPatches.rt2x00}
|
||||
'';
|
||||
config = {
|
||||
|
||||
|
|
|
@ -97,7 +97,7 @@
|
|||
swconfig dev switch0 vlan 2 set ports '0 6t'
|
||||
swconfig dev switch0 set apply
|
||||
'';
|
||||
down = "swconfig dev switch0 set reset";
|
||||
down = "${pkgs.swconfig}/bin/swconfig dev switch0 set reset";
|
||||
};
|
||||
in rec {
|
||||
eth = link.build { ifname = "eth0"; dependencies = [swconfig]; };
|
||||
|
@ -122,6 +122,7 @@
|
|||
# 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 = {
|
||||
|
|
|
@ -0,0 +1,155 @@
|
|||
#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";
|
||||
};
|
||||
};
|
|
@ -0,0 +1,155 @@
|
|||
#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";
|
||||
};
|
||||
};
|
|
@ -0,0 +1,367 @@
|
|||
{
|
||||
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
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
#include "mt7621_zyxel_nwa-ax-for-ab.dtsi"
|
||||
|
||||
#include <dt-bindings/gpio/gpio.h>
|
||||
#include <dt-bindings/input/input.h>
|
||||
|
||||
/ {
|
||||
compatible = "zyxel,nwa50ax", "mediatek,mt7621-soc";
|
||||
model = "ZyXEL NWA50AX";
|
||||
|
||||
aliases {
|
||||
led-boot = &led_system_green;
|
||||
led-failsafe = &led_system_red;
|
||||
led-running = &led_system_green;
|
||||
led-upgrade = &led_system_red;
|
||||
};
|
||||
|
||||
leds {
|
||||
compatible = "gpio-leds";
|
||||
|
||||
led_system_red: system_red {
|
||||
label = "red:system";
|
||||
gpios = <&gpio 6 GPIO_ACTIVE_HIGH>;
|
||||
};
|
||||
|
||||
led_system_green: system_green {
|
||||
label = "green:system";
|
||||
gpios = <&gpio 7 GPIO_ACTIVE_HIGH>;
|
||||
};
|
||||
|
||||
system_blue {
|
||||
label = "blue:system";
|
||||
gpios = <&gpio 8 GPIO_ACTIVE_HIGH>;
|
||||
};
|
||||
};
|
||||
|
||||
keys {
|
||||
compatible = "gpio-keys";
|
||||
|
||||
reset {
|
||||
label = "reset";
|
||||
gpios = <&gpio 30 GPIO_ACTIVE_LOW>;
|
||||
linux,code = <KEY_RESTART>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
ðernet {
|
||||
pinctrl-0 = <&mdio_pins>, <&rgmii1_pins>;
|
||||
};
|
||||
|
||||
&state_default {
|
||||
gpio {
|
||||
groups = "uart3", "rgmii2";
|
||||
function = "gpio";
|
||||
};
|
||||
};
|
|
@ -28,34 +28,12 @@ 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;
|
||||
|
@ -105,7 +83,7 @@ in rec {
|
|||
};
|
||||
|
||||
services.mount_external_disk = svc.mount.build {
|
||||
device = "LABEL=backup-disk";
|
||||
partlabel = "backup-disk";
|
||||
mountpoint = "/srv";
|
||||
fstype = "ext4";
|
||||
};
|
||||
|
@ -159,5 +137,17 @@ in rec {
|
|||
gid=500; usernames = ["backup"];
|
||||
};
|
||||
|
||||
defaultProfile.packages = with pkgs; [e2fsprogs strace tcpdump ];
|
||||
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;
|
||||
};
|
||||
})
|
||||
];
|
||||
}
|
||||
|
|
|
@ -158,7 +158,6 @@ in rec {
|
|||
};
|
||||
|
||||
services.firewall = svc.firewall.build {
|
||||
ruleset = import ./demo-firewall.nix;
|
||||
};
|
||||
|
||||
services.packet_forwarding = svc.network.forward.build { };
|
||||
|
|
|
@ -8,12 +8,10 @@
|
|||
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 = {
|
||||
|
@ -24,113 +22,32 @@ in rec {
|
|||
};
|
||||
|
||||
imports = [
|
||||
../modules/wlan.nix
|
||||
../modules/vlan
|
||||
../modules/network
|
||||
../modules/hostapd
|
||||
../modules/bridge
|
||||
../modules/ssh
|
||||
"${modulesPath}/profiles/wap.nix"
|
||||
"${modulesPath}/vlan"
|
||||
"${modulesPath}/ssh"
|
||||
];
|
||||
|
||||
hostname = "extneder";
|
||||
|
||||
kernel = {
|
||||
config = {
|
||||
|
||||
NETFILTER_XT_MATCH_CONNTRACK = "y";
|
||||
|
||||
IP6_NF_IPTABLES = "y"; # do we still need these
|
||||
IP_NF_IPTABLES = "y"; # if using nftables directly
|
||||
|
||||
# these are copied from rotuer and need review.
|
||||
# we're not running a firewall, so why do we need
|
||||
# nftables config?
|
||||
IP_NF_NAT = "y";
|
||||
IP_NF_TARGET_MASQUERADE = "y";
|
||||
NETFILTER = "y";
|
||||
NETFILTER_ADVANCED = "y";
|
||||
NETFILTER_XTABLES = "y";
|
||||
|
||||
NFT_COMPAT = "y";
|
||||
NFT_CT = "y";
|
||||
NFT_LOG = "y";
|
||||
NFT_MASQ = "y";
|
||||
NFT_NAT = "y";
|
||||
NFT_REJECT = "y";
|
||||
NFT_REJECT_INET = "y";
|
||||
|
||||
NF_CONNTRACK = "y";
|
||||
NF_NAT = "y";
|
||||
NF_NAT_MASQUERADE = "y";
|
||||
NF_TABLES = "y";
|
||||
NF_TABLES_INET = "y";
|
||||
NF_TABLES_IPV4 = "y";
|
||||
NF_TABLES_IPV6 = "y";
|
||||
};
|
||||
};
|
||||
|
||||
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; [
|
||||
profile.wap = {
|
||||
interfaces = 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";
|
||||
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;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
services.defaultroute4 = svc.network.route.build {
|
||||
via = "$(output ${services.dhcpc} router)";
|
||||
target = "default";
|
||||
dependencies = [services.dhcpc];
|
||||
};
|
||||
|
||||
services.sshd = svc.ssh.build {};
|
||||
users.root.passwd = lib.mkForce secrets.root.passwd;
|
||||
defaultProfile.packages = with pkgs; [nftables strace tcpdump swconfig];
|
||||
}
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
{ 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 ];
|
||||
}
|
|
@ -6,23 +6,18 @@
|
|||
# problems.
|
||||
|
||||
|
||||
{ config, pkgs, lib, ... } :
|
||||
{ config, pkgs, lib, modulesPath, ... } :
|
||||
let
|
||||
secrets = {
|
||||
domainName = "fake.liminix.org";
|
||||
firewallRules = {};
|
||||
} // (import ./rotuer-secrets.nix);
|
||||
inherit (pkgs.liminix.services) oneshot longrun bundle;
|
||||
inherit (pkgs.liminix.services) oneshot bundle;
|
||||
inherit (pkgs) serviceFns;
|
||||
svc = config.system.service;
|
||||
wirelessConfig = {
|
||||
country_code = "GB";
|
||||
inherit (secrets) wpa_passphrase;
|
||||
auth_algs = 1; # 1=wpa2, 2=wep, 3=both
|
||||
wpa = 2; # 1=wpa, 2=wpa2, 3=both
|
||||
wpa_key_mgmt = "WPA-PSK";
|
||||
wpa_pairwise = "TKIP CCMP"; # auth for wpa (may not need this?)
|
||||
rsn_pairwise = "CCMP"; # auth for wpa2
|
||||
wmm_enabled = 1;
|
||||
};
|
||||
|
||||
|
@ -36,65 +31,62 @@ in rec {
|
|||
};
|
||||
|
||||
imports = [
|
||||
../modules/wlan.nix
|
||||
../modules/network
|
||||
../modules/ppp
|
||||
../modules/dnsmasq
|
||||
../modules/dhcp6c
|
||||
../modules/firewall
|
||||
../modules/hostapd
|
||||
../modules/bridge
|
||||
../modules/ntp
|
||||
../modules/schnapps
|
||||
../modules/ssh
|
||||
../modules/outputs/btrfs.nix
|
||||
../modules/outputs/extlinux.nix
|
||||
"${modulesPath}/profiles/gateway.nix"
|
||||
"${modulesPath}/schnapps"
|
||||
"${modulesPath}/outputs/btrfs.nix"
|
||||
"${modulesPath}/outputs/extlinux.nix"
|
||||
];
|
||||
hostname = "rotuer";
|
||||
rootfsType = "btrfs";
|
||||
rootOptions = "subvol=@";
|
||||
boot.loader.extlinux.enable = true;
|
||||
|
||||
services.hostap = svc.hostapd.build {
|
||||
interface = config.hardware.networkInterfaces.wlan;
|
||||
params = {
|
||||
ssid = secrets.ssid;
|
||||
hw_mode="g";
|
||||
channel = "2";
|
||||
ieee80211n = 1;
|
||||
} // wirelessConfig;
|
||||
};
|
||||
|
||||
services.hostap5 = svc.hostapd.build {
|
||||
interface = config.hardware.networkInterfaces.wlan5;
|
||||
params = rec {
|
||||
ssid = "${secrets.ssid}5";
|
||||
hw_mode="a";
|
||||
channel = 36;
|
||||
ht_capab = "[HT40+]";
|
||||
vht_oper_chwidth = 1;
|
||||
vht_oper_centr_freq_seg0_idx = channel + 6;
|
||||
ieee80211n = 1;
|
||||
ieee80211ac = 1;
|
||||
} // wirelessConfig;
|
||||
};
|
||||
|
||||
services.int = svc.network.address.build {
|
||||
interface = svc.bridge.primary.build { ifname = "int"; };
|
||||
family = "inet"; address ="${secrets.lan.prefix}.1"; prefixLength = 24;
|
||||
};
|
||||
|
||||
services.bridge = svc.bridge.members.build {
|
||||
primary = services.int;
|
||||
members = with config.hardware.networkInterfaces;
|
||||
[ wlan
|
||||
wlan5
|
||||
lan0
|
||||
lan1
|
||||
lan2
|
||||
lan3
|
||||
lan4
|
||||
];
|
||||
profile.gateway = {
|
||||
lan = {
|
||||
interfaces = with config.hardware.networkInterfaces;
|
||||
[
|
||||
wlan wlan5
|
||||
lan0 lan1 lan2 lan3 lan4
|
||||
];
|
||||
inherit (secrets.lan) prefix;
|
||||
address = {
|
||||
family = "inet"; address ="${secrets.lan.prefix}.1"; prefixLength = 24;
|
||||
};
|
||||
dhcp = {
|
||||
start = 10;
|
||||
end = 240;
|
||||
hosts = { } // lib.optionalAttrs (builtins.pathExists ./static-leases.nix) (import ./static-leases.nix);
|
||||
localDomain = "lan";
|
||||
};
|
||||
};
|
||||
wan = {
|
||||
interface = config.hardware.networkInterfaces.wan;
|
||||
username = secrets.l2tp.name;
|
||||
password = secrets.l2tp.password;
|
||||
dhcp6.enable = true;
|
||||
};
|
||||
firewall = {
|
||||
enable = true;
|
||||
rules = secrets.firewallRules;
|
||||
};
|
||||
wireless.networks = {
|
||||
"${secrets.ssid}" = {
|
||||
interface = config.hardware.networkInterfaces.wlan;
|
||||
hw_mode="g";
|
||||
channel = "2";
|
||||
ieee80211n = 1;
|
||||
} // wirelessConfig;
|
||||
"${secrets.ssid}5" = rec {
|
||||
interface = config.hardware.networkInterfaces.wlan5;
|
||||
hw_mode="a";
|
||||
channel = 36;
|
||||
ht_capab = "[HT40+]";
|
||||
vht_oper_chwidth = 1;
|
||||
vht_oper_centr_freq_seg0_idx = channel + 6;
|
||||
ieee80211n = 1;
|
||||
ieee80211ac = 1;
|
||||
} // wirelessConfig;
|
||||
};
|
||||
};
|
||||
|
||||
services.ntp = svc.ntp.build {
|
||||
|
@ -106,95 +98,6 @@ in rec {
|
|||
|
||||
users.root = secrets.root;
|
||||
|
||||
services.dns =
|
||||
let interface = services.int;
|
||||
in svc.dnsmasq.build {
|
||||
resolvconf = services.resolvconf;
|
||||
inherit interface;
|
||||
ranges = [
|
||||
"${secrets.lan.prefix}.10,${secrets.lan.prefix}.240"
|
||||
# ra-stateless: sends router advertisements with the O and A
|
||||
# bits set, and provides a stateless DHCP service. The client
|
||||
# will use a SLAAC address, and use DHCP for other
|
||||
# configuration information.
|
||||
"::,constructor:$(output ${interface} ifname),ra-stateless"
|
||||
];
|
||||
|
||||
# You can add static addresses for the DHCP server here. I'm
|
||||
# not putting my actual MAC addresses in a public git repo ...
|
||||
hosts = { } // lib.optionalAttrs (builtins.pathExists ./static-leases.nix) (import ./static-leases.nix);
|
||||
upstreams = [ "/${secrets.domainName}/" ];
|
||||
domain = secrets.domainName;
|
||||
};
|
||||
|
||||
services.wan = svc.pppoe.build {
|
||||
interface = config.hardware.networkInterfaces.wan;
|
||||
ppp-options = [
|
||||
"debug" "+ipv6" "noauth"
|
||||
"name" secrets.l2tp.name
|
||||
"password" secrets.l2tp.password
|
||||
];
|
||||
};
|
||||
|
||||
services.resolvconf = oneshot rec {
|
||||
dependencies = [ services.wan ];
|
||||
name = "resolvconf";
|
||||
up = ''
|
||||
. ${serviceFns}
|
||||
( in_outputs ${name}
|
||||
echo "nameserver $(output ${services.wan} ns1)" > resolv.conf
|
||||
echo "nameserver $(output ${services.wan} ns2)" >> resolv.conf
|
||||
chmod 0444 resolv.conf
|
||||
)
|
||||
'';
|
||||
};
|
||||
|
||||
filesystem =
|
||||
let inherit (pkgs.pseudofile) dir symlink;
|
||||
in dir {
|
||||
etc = dir {
|
||||
"resolv.conf" = symlink "${services.resolvconf}/.outputs/resolv.conf";
|
||||
};
|
||||
};
|
||||
|
||||
services.defaultroute4 = svc.network.route.build {
|
||||
via = "$(output ${services.wan} address)";
|
||||
target = "default";
|
||||
dependencies = [ services.wan ];
|
||||
};
|
||||
|
||||
services.defaultroute6 = svc.network.route.build {
|
||||
via = "$(output ${services.wan} ipv6-peer-address)";
|
||||
target = "default";
|
||||
interface = services.wan;
|
||||
};
|
||||
|
||||
services.firewall = svc.firewall.build {
|
||||
ruleset =
|
||||
let defaults = import ./demo-firewall.nix;
|
||||
in lib.recursiveUpdate defaults secrets.firewallRules;
|
||||
};
|
||||
|
||||
services.packet_forwarding = svc.network.forward.build { };
|
||||
|
||||
services.dhcp6c =
|
||||
let client = svc.dhcp6c.client.build {
|
||||
interface = services.wan;
|
||||
};
|
||||
in bundle {
|
||||
name = "dhcp6c";
|
||||
contents = [
|
||||
(svc.dhcp6c.prefix.build {
|
||||
inherit client;
|
||||
interface = services.int;
|
||||
})
|
||||
(svc.dhcp6c.address.build {
|
||||
inherit client;
|
||||
interface = services.wan;
|
||||
})
|
||||
];
|
||||
};
|
||||
|
||||
defaultProfile.packages = with pkgs; [
|
||||
min-collect-garbage
|
||||
nftables
|
||||
|
|
|
@ -9,28 +9,29 @@
|
|||
./busybox.nix
|
||||
./dhcp6c
|
||||
./dnsmasq
|
||||
./outputs/ext4fs.nix
|
||||
./firewall
|
||||
./hardware.nix
|
||||
./hostapd
|
||||
./hostname.nix
|
||||
./outputs/initramfs.nix
|
||||
./outputs/jffs2.nix
|
||||
./kernel
|
||||
./outputs/kexecboot.nix
|
||||
./mdevd.nix
|
||||
./mount
|
||||
./network
|
||||
./ntp
|
||||
./outputs.nix
|
||||
./outputs/vmroot.nix
|
||||
./outputs/ubimage.nix
|
||||
./outputs/ext4fs.nix
|
||||
./outputs/initramfs.nix
|
||||
./outputs/jffs2.nix
|
||||
./outputs/kexecboot.nix
|
||||
./outputs/mtdimage.nix
|
||||
./outputs/tftpboot.nix
|
||||
./outputs/ubifs.nix
|
||||
./outputs/ubimage.nix
|
||||
./outputs/vmroot.nix
|
||||
./ppp
|
||||
./ramdisk.nix
|
||||
./squashfs.nix
|
||||
./ssh
|
||||
./outputs/tftpboot.nix
|
||||
./outputs/ubifs.nix
|
||||
./users.nix
|
||||
./vlan
|
||||
./watchdog
|
||||
|
|
|
@ -12,9 +12,6 @@ let
|
|||
type_service = pkgs.liminix.lib.types.service;
|
||||
|
||||
in {
|
||||
imports = [
|
||||
./kernel # kernel is a separate module for doc purposes
|
||||
];
|
||||
options = {
|
||||
defaultProfile = {
|
||||
packages = mkOption {
|
||||
|
@ -29,6 +26,10 @@ in {
|
|||
services = mkOption {
|
||||
type = types.attrsOf type_service;
|
||||
};
|
||||
system.callService = mkOption {
|
||||
type = types.functionTo (types.functionTo types.anything);
|
||||
};
|
||||
|
||||
filesystem = mkOption {
|
||||
type = types.anything;
|
||||
description = ''
|
||||
|
@ -58,6 +59,15 @@ 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";
|
||||
|
@ -102,6 +112,31 @@ in {
|
|||
"fw_devlink=off"
|
||||
] ++ lib.optional (config.rootOptions != null) "rootflags=${config.rootOptions}";
|
||||
|
||||
system.callService = path : parameters :
|
||||
let
|
||||
typeChecked = caller: type: value:
|
||||
let
|
||||
inherit (lib) types mergeDefinitions;
|
||||
defs = [{ file = caller; inherit value; }];
|
||||
type' = types.submodule { options = type; };
|
||||
in (mergeDefinitions [] type' defs).mergedValue;
|
||||
cp = lib.callPackageWith(pkgs // { svc = config.system.service; });
|
||||
pkg = cp path {};
|
||||
checkTypes = t : p : typeChecked (builtins.toString path) t p;
|
||||
in {
|
||||
inherit parameters;
|
||||
build = { dependencies ? [], ... } @ args :
|
||||
let
|
||||
s = pkg (checkTypes parameters
|
||||
(builtins.removeAttrs args ["dependencies"]));
|
||||
in s.overrideAttrs (o: {
|
||||
dependencies = (builtins.map (d: d.name) dependencies) ++ o.dependencies;
|
||||
buildInputs = dependencies ++ o.buildInputs;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
users.root = {
|
||||
uid = 0; gid= 0; gecos = "Root of all evaluation";
|
||||
dir = "/home/root/";
|
||||
|
|
|
@ -14,6 +14,8 @@ let
|
|||
inherit (pkgs) liminix;
|
||||
in
|
||||
{
|
||||
imports = [ ../ifwait ];
|
||||
|
||||
options = {
|
||||
system.service.bridge = {
|
||||
primary = mkOption { type = liminix.lib.types.serviceDefn; };
|
||||
|
@ -27,7 +29,7 @@ in
|
|||
description = "bridge interface name to create";
|
||||
};
|
||||
};
|
||||
members = liminix.callService ./members.nix {
|
||||
members = config.system.callService ./members.nix {
|
||||
primary = mkOption {
|
||||
type = liminix.lib.types.interface;
|
||||
description = "primary bridge interface";
|
||||
|
@ -47,5 +49,5 @@ in
|
|||
# a better way to test for the existence of vlan config:
|
||||
# maybe the module should set an `enabled` attribute?
|
||||
BRIDGE_VLAN_FILTERING = "y";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
liminix
|
||||
, ifwait
|
||||
, lib
|
||||
, svc
|
||||
}:
|
||||
{ members, primary } :
|
||||
|
||||
|
@ -10,14 +11,20 @@ let
|
|||
inherit (liminix.services) bundle oneshot;
|
||||
inherit (lib) mkOption types;
|
||||
addif = member :
|
||||
oneshot {
|
||||
name = "${primary.name}.member.${member.name}";
|
||||
up = ''
|
||||
dev=$(output ${member} ifname)
|
||||
${ifwait}/bin/ifwait $dev running && ip link set dev $dev master $(output ${primary} ifname)
|
||||
'';
|
||||
down = "ip link set dev $(output ${member} ifname) nomaster";
|
||||
# how do we get sight of services from here? maybe we need to
|
||||
# implement ifwait as a regualr derivation instead of a
|
||||
# servicedefinition
|
||||
svc.ifwait.build {
|
||||
state = "running";
|
||||
interface = member;
|
||||
dependencies = [ primary member ];
|
||||
service = oneshot {
|
||||
name = "${primary.name}.member.${member.name}";
|
||||
up = ''
|
||||
ip link set dev $(output ${member} ifname) master $(output ${primary} ifname)
|
||||
'';
|
||||
down = "ip link set dev $(output ${member} ifname) nomaster";
|
||||
};
|
||||
};
|
||||
in bundle {
|
||||
name = "${primary.name}.members";
|
||||
|
|
|
@ -32,23 +32,21 @@ 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" "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" "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"
|
||||
];
|
||||
in {
|
||||
options = {
|
||||
|
|
|
@ -3,8 +3,9 @@
|
|||
, linotify
|
||||
, anoia
|
||||
, lua
|
||||
, lualinux
|
||||
}:
|
||||
writeFennel "acquire-delegated-prefix" {
|
||||
packages = [ linotify anoia lua.pkgs.luafilesystem ];
|
||||
packages = [ linotify anoia lualinux ];
|
||||
mainFunction = "run";
|
||||
} ./acquire-delegated-prefix.fnl
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
(local subject (require :acquire-wan-address))
|
||||
(local { : view } (require :fennel))
|
||||
(import-macros { : expect= } :anoia.assert)
|
||||
(local { : merge : dup } (require :anoia))
|
||||
|
||||
;; nix-shell --run "cd modules/dhcp6c && fennelrepl acquire-wan-address-test.fnl"
|
||||
|
||||
(local a1
|
||||
{
|
||||
|
@ -47,19 +48,6 @@
|
|||
}
|
||||
)
|
||||
|
||||
(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
|
||||
|
|
|
@ -2,9 +2,10 @@
|
|||
writeFennel
|
||||
, linotify
|
||||
, anoia
|
||||
, lualinux
|
||||
, lua
|
||||
}:
|
||||
writeFennel "acquire-wan-address" {
|
||||
packages = [ linotify anoia lua.pkgs.luafilesystem ];
|
||||
packages = [ linotify anoia lualinux ];
|
||||
mainFunction = "run";
|
||||
} ./acquire-wan-address.fnl
|
||||
|
|
|
@ -56,8 +56,14 @@ in
|
|||
config = {
|
||||
system.service.firewall =
|
||||
let svc = liminix.callService ./service.nix {
|
||||
ruleset = mkOption {
|
||||
extraRules = mkOption {
|
||||
type = types.attrsOf types.attrs;
|
||||
description = "firewall ruleset";
|
||||
default = {};
|
||||
};
|
||||
rules = mkOption {
|
||||
type = types.attrsOf types.attrs; # we could usefully tighten this a bit :-)
|
||||
default = import ./default-rules.nix;
|
||||
description = "firewall ruleset";
|
||||
};
|
||||
};
|
||||
|
@ -68,13 +74,17 @@ 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";
|
||||
|
|
|
@ -4,12 +4,12 @@
|
|||
, firewallgen
|
||||
, nftables
|
||||
}:
|
||||
{ ruleset }:
|
||||
{ rules, extraRules }:
|
||||
let
|
||||
inherit (liminix.services) oneshot;
|
||||
inherit (liminix.lib) typeChecked;
|
||||
inherit (lib) mkOption types;
|
||||
script = firewallgen "firewall.nft" ruleset;
|
||||
script = firewallgen "firewall.nft" (lib.recursiveUpdate rules extraRules);
|
||||
in oneshot {
|
||||
name = "firewall";
|
||||
up = script;
|
||||
|
|
|
@ -67,6 +67,7 @@ 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
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
{ config, pkgs, lib, ... } :
|
||||
let
|
||||
inherit (pkgs) liminix;
|
||||
inherit (lib) mkOption types;
|
||||
in {
|
||||
options.system.service.ifwait =
|
||||
mkOption { type = liminix.lib.types.serviceDefn; };
|
||||
|
||||
config.system.service.ifwait = config.system.callService ./ifwait.nix {
|
||||
state = mkOption { type = types.str; };
|
||||
interface = mkOption {
|
||||
type = liminix.lib.types.interface;
|
||||
};
|
||||
service = mkOption {
|
||||
type = liminix.lib.types.service;
|
||||
};
|
||||
};
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
{ 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}
|
||||
'';
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
{ 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];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
|
@ -19,28 +19,39 @@ in {
|
|||
type = liminix.lib.types.serviceDefn;
|
||||
};
|
||||
};
|
||||
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";
|
||||
};
|
||||
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.programs.busybox = {
|
||||
applets = ["blkid" "findfs"];
|
||||
options = {
|
||||
|
|
|
@ -1,18 +1,26 @@
|
|||
{
|
||||
liminix
|
||||
, uevent-watch
|
||||
, lib
|
||||
}:
|
||||
{ device, mountpoint, options, fstype }:
|
||||
{ partlabel, mountpoint, options, fstype }:
|
||||
let
|
||||
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}
|
||||
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
|
||||
'';
|
||||
down = "umount ${mountpoint}";
|
||||
}
|
||||
|
|
|
@ -111,7 +111,8 @@ in
|
|||
};
|
||||
uimage = liminix.builders.uimage {
|
||||
commandLine = concatStringsSep " " config.boot.commandLine;
|
||||
inherit (config.hardware) loadAddress entryPoint;
|
||||
inherit (config.boot) commandLineDtbNode;
|
||||
inherit (config.hardware) loadAddress entryPoint alignment;
|
||||
inherit (config.boot) imageFormat;
|
||||
inherit (o) kernel dtb;
|
||||
};
|
||||
|
|
|
@ -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 bootargs "$cmd"
|
||||
fdtput -t s dtb /chosen ${config.boot.commandLineDtbNode} "$cmd"
|
||||
|
||||
dtbSize=$(binsize ./dtb )
|
||||
|
||||
|
|
|
@ -12,9 +12,16 @@ in
|
|||
imports = [
|
||||
./initramfs.nix
|
||||
];
|
||||
|
||||
options.system.outputs.rootubifs = mkOption {
|
||||
type = types.package;
|
||||
internal = true;
|
||||
};
|
||||
|
||||
options.hardware.ubi = {
|
||||
minIOSize = mkOption { type = types.str; };
|
||||
eraseBlockSize = mkOption { type = types.str; }; # LEB
|
||||
logicalEraseBlockSize = mkOption { type = types.str; }; # LEB
|
||||
physicalEraseBlockSize = mkOption { type = types.str; }; # PEB
|
||||
maxLEBcount = mkOption { type = types.str; }; # LEB
|
||||
};
|
||||
|
||||
|
@ -26,7 +33,7 @@ in
|
|||
};
|
||||
boot.initramfs.enable = true;
|
||||
system.outputs = {
|
||||
rootfs =
|
||||
rootubifs =
|
||||
let
|
||||
inherit (pkgs.pkgsBuildBuild) runCommand mtdutils;
|
||||
cfg = config.hardware.ubi;
|
||||
|
@ -35,7 +42,7 @@ in
|
|||
} ''
|
||||
mkdir tmp
|
||||
tree=${o.bootablerootdir}
|
||||
mkfs.ubifs -x favor_lzo -c ${cfg.maxLEBcount} -m ${cfg.minIOSize} -e ${cfg.eraseBlockSize} -y -r $tree --output $out --squash-uids -o $out
|
||||
mkfs.ubifs -x favor_lzo -c ${cfg.maxLEBcount} -m ${cfg.minIOSize} -e ${cfg.logicalEraseBlockSize} -y -r $tree --output $out --squash-uids -o $out
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
{
|
||||
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;
|
||||
};
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
{
|
||||
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
|
||||
'';
|
||||
};
|
||||
}
|
|
@ -0,0 +1,178 @@
|
|||
{ 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";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
{
|
||||
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";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
|
@ -34,7 +34,7 @@ fi
|
|||
|
||||
### If your services are managed by s6-rc:
|
||||
### (replace /run/service with your scandir)
|
||||
s6-rc-init /run/service -d -c /etc/s6-rc/compiled
|
||||
s6-rc-init -d -c /etc/s6-rc/compiled /run/service
|
||||
|
||||
|
||||
### 2. Starting the wanted set of services
|
||||
|
|
|
@ -15,4 +15,5 @@ in oneshot rec {
|
|||
)
|
||||
'';
|
||||
down = "ip link set down dev ${ifname}";
|
||||
dependencies = [ primary ];
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
liminix
|
||||
, lib
|
||||
, s6
|
||||
}:
|
||||
{ watched, headStart } :
|
||||
let
|
||||
|
@ -8,5 +9,5 @@ let
|
|||
in longrun {
|
||||
name = "watchdog";
|
||||
run =
|
||||
"HEADSTART=${toString headStart} ${./gaspode.sh} ${lib.concatStringsSep " " (builtins.map (s: s.name) watched)}";
|
||||
"PATH=${s6}/bin:$PATH HEADSTART=${toString headStart} ${./gaspode.sh} ${lib.concatStringsSep " " (builtins.map (s: s.name) watched)}";
|
||||
}
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
## 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.";
|
||||
};
|
||||
};
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
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
|
||||
'';
|
||||
}
|
16
overlay.nix
16
overlay.nix
|
@ -77,6 +77,22 @@ extraPkgs // {
|
|||
|
||||
};
|
||||
|
||||
# luarocks wants a cross-compiled cmake (which seems like a bug,
|
||||
# we're never going to run luarocks on the device, but ...)
|
||||
# but https://github.com/NixOS/nixpkgs/issues/284734
|
||||
# so we do surgery on the cmake derivation until that's fixed
|
||||
|
||||
cmake = prev.cmake.overrideAttrs(o:
|
||||
# don't override the build cmake or we'll have to rebuild
|
||||
# half the known universe to no useful benefit
|
||||
if final.stdenv.buildPlatform != final.stdenv.hostPlatform
|
||||
then {
|
||||
preConfigure =
|
||||
builtins.replaceStrings
|
||||
["$configureFlags"] ["$configureFlags $cmakeFlags"] o.preConfigure;
|
||||
}
|
||||
else {}
|
||||
);
|
||||
|
||||
dnsmasq =
|
||||
let d = prev.dnsmasq.overrideAttrs(o: {
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
|
||||
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 $< > $@
|
|
@ -0,0 +1,21 @@
|
|||
;; 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= }
|
|
@ -1,22 +1,27 @@
|
|||
{
|
||||
fennel
|
||||
, stdenv
|
||||
, linotify
|
||||
, lua
|
||||
, lualinux
|
||||
, cpio
|
||||
}:
|
||||
let pname = "anoia";
|
||||
in stdenv.mkDerivation {
|
||||
inherit pname;
|
||||
version = "0.1";
|
||||
src = ./.;
|
||||
nativeBuildInputs = [ fennel ];
|
||||
buildInputs = with lua.pkgs; [ luafilesystem ];
|
||||
buildPhase = ''
|
||||
for f in *.fnl ; do
|
||||
fennel --compile $f > `basename $f .fnl`.lua
|
||||
done
|
||||
'';
|
||||
nativeBuildInputs = [ fennel cpio ];
|
||||
buildInputs = with lua.pkgs; [ linotify lualinux ];
|
||||
outputs = [ "out" "dev" ];
|
||||
|
||||
doCheck = true;
|
||||
|
||||
installPhase = ''
|
||||
mkdir -p "$out/share/lua/${lua.luaversion}/${pname}"
|
||||
cp *.lua "$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}"
|
||||
'';
|
||||
}
|
||||
|
|
|
@ -1,7 +1,30 @@
|
|||
(local lfs (require :lfs))
|
||||
(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))))
|
||||
|
||||
(fn directory? [pathname]
|
||||
(= (lfs.symlinkattributes pathname :mode) "directory"))
|
||||
(= (file-type pathname) :directory))
|
||||
|
||||
(fn mktree [pathname]
|
||||
(if (or (= pathname "") (= pathname "/"))
|
||||
|
@ -10,27 +33,37 @@
|
|||
(or (directory? pathname)
|
||||
(let [parent (string.gsub pathname "/[^/]+/?$" "")]
|
||||
(or (directory? parent) (mktree parent))
|
||||
(assert (lfs.mkdir pathname)))))
|
||||
(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)))))
|
||||
|
||||
(fn rmtree [pathname]
|
||||
(case (lfs.symlinkattributes pathname)
|
||||
(case (file-type pathname)
|
||||
nil true
|
||||
{:mode "directory"}
|
||||
:directory
|
||||
(do
|
||||
(each [f (lfs.dir pathname)]
|
||||
(each [f (dir pathname)]
|
||||
(when (not (or (= f ".") (= f "..")))
|
||||
(rmtree ( .. pathname "/" f)))
|
||||
(lfs.rmdir pathname)))
|
||||
{:mode "file"}
|
||||
(ll.rmdir pathname)))
|
||||
:file
|
||||
(os.remove pathname)
|
||||
{:mode "link"}
|
||||
:link
|
||||
(os.remove pathname)
|
||||
unknown
|
||||
(error (.. "can't remove " pathname " of kind \"" unknown.mode "\""))))
|
||||
(error (.. "can't remove " pathname " of mode \"" unknown "\""))))
|
||||
|
||||
|
||||
{
|
||||
: mktree
|
||||
: rmtree
|
||||
: directory?
|
||||
: dir
|
||||
: file-type
|
||||
:symlink (fn [from to] (ll.symlink from to))
|
||||
}
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
(fn assoc [tbl k v]
|
||||
(tset tbl k v)
|
||||
tbl)
|
||||
|
||||
(fn merge [table1 table2]
|
||||
(collect [k v (pairs table2) &into table1]
|
||||
k v))
|
||||
|
@ -14,9 +18,15 @@
|
|||
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 res
|
||||
res (do (print (.. "Executed \"" s "\", exit code " (tostring res))) res)
|
||||
(nil err) (error (.. "Error executing \"" s "\" (" err ")"))))
|
||||
|
||||
(fn hash [str]
|
||||
|
@ -62,4 +72,15 @@
|
|||
(s:sub 1 (- (# s) pad))))
|
||||
|
||||
|
||||
{ : merge : split : file-exists? : system : hash : base64url : dup }
|
||||
{
|
||||
: assoc
|
||||
: base64url
|
||||
: basename
|
||||
: dirname
|
||||
: dup
|
||||
: file-exists?
|
||||
: hash
|
||||
: merge
|
||||
: split
|
||||
: system
|
||||
}
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
|
||||
#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)
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
(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 }
|
|
@ -1,7 +1,6 @@
|
|||
(local inotify (require :inotify))
|
||||
(local { : file-exists? } (require :anoia))
|
||||
(local { : directory? } (require :anoia.fs))
|
||||
(local lfs (require :lfs))
|
||||
(local { : file-type : dir &as fs } (require :anoia.fs))
|
||||
|
||||
(fn read-line [name]
|
||||
(with-open [f (assert (io.open name :r) (.. "can't open file " name))]
|
||||
|
@ -20,15 +19,15 @@
|
|||
handle))
|
||||
|
||||
(fn read-value [pathname]
|
||||
(case (lfs.symlinkattributes pathname)
|
||||
(case (file-type pathname)
|
||||
nil nil
|
||||
{:mode "directory"}
|
||||
(collect [f (lfs.dir pathname)]
|
||||
:directory
|
||||
(collect [f (fs.dir pathname)]
|
||||
(when (not (or (= f ".") (= f "..")))
|
||||
(values f (read-value ( .. pathname "/" f)))))
|
||||
{:mode "file"}
|
||||
:file
|
||||
(read-line pathname)
|
||||
{:mode "link"}
|
||||
:link
|
||||
(read-line pathname)
|
||||
unknown
|
||||
(error (.. "can't read " pathname " of kind \"" unknown.mode "\""))))
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
(local nl (require :anoia.nl))
|
||||
(local { : view } (require :fennel))
|
||||
|
||||
(let [events (nl.events {:link true})]
|
||||
(each [ev events]
|
||||
(print "got one ")
|
||||
(print (view ev))))
|
|
@ -1,4 +1,4 @@
|
|||
(local svc (require :anoia.svc))
|
||||
(local svc (require :svc))
|
||||
(local { : view } (require :fennel))
|
||||
|
||||
(local ex (svc.open "./example-output"))
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
(local { : hash : base64url } (require :anoia))
|
||||
(local { : hash : base64url } (require :init))
|
||||
(import-macros { : expect= } :assert)
|
||||
|
||||
(assert (= (hash "") 5381))
|
||||
(expect= (hash "") 5381)
|
||||
|
||||
;; these examples from https://theartincode.stanis.me/008-djb2/
|
||||
(assert (= (hash "Hello") 210676686969))
|
||||
(assert (= (hash "Hello!") 6952330670010))
|
||||
(expect= (hash "Hello") 210676686969)
|
||||
(expect= (hash "Hello!") 6952330670010)
|
||||
|
||||
(assert (= (base64url "hello world") "aGVsbG8gd29ybGQ"))
|
||||
(expect= (base64url "hello world") "aGVsbG8gd29ybGQ")
|
||||
|
|
|
@ -56,6 +56,7 @@ in {
|
|||
# please keep the rest of this list alphabetised :-)
|
||||
|
||||
anoia = callPackage ./anoia {};
|
||||
devout = callPackage ./devout {};
|
||||
fennel = callPackage ./fennel {};
|
||||
fennelrepl = callPackage ./fennelrepl {};
|
||||
firewallgen = callPackage ./firewallgen {};
|
||||
|
@ -70,6 +71,7 @@ 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
|
||||
|
@ -79,8 +81,11 @@ 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 {};
|
||||
|
@ -104,6 +109,7 @@ 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 {};
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
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
|
||||
'';
|
||||
}
|
|
@ -0,0 +1,173 @@
|
|||
(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 }
|
|
@ -0,0 +1,210 @@
|
|||
(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"))
|
|
@ -5,16 +5,20 @@
|
|||
, lib
|
||||
, luaPackages
|
||||
, lua
|
||||
, lualinux
|
||||
, writeScriptBin
|
||||
, linotify
|
||||
, anoia
|
||||
, netlink-lua
|
||||
, fennel
|
||||
}:
|
||||
let packages = [
|
||||
linotify
|
||||
anoia
|
||||
fennel
|
||||
lua.pkgs.luafilesystem
|
||||
lualinux
|
||||
netlink-lua
|
||||
lua.pkgs.readline
|
||||
];
|
||||
join = ps: builtins.concatStringsSep ";" ps;
|
||||
luapath = join (builtins.map (f:
|
||||
|
@ -29,6 +33,7 @@ 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
|
||||
|
|
|
@ -3,8 +3,9 @@
|
|||
, netlink-lua
|
||||
, writeFennelScript
|
||||
, runCommand
|
||||
, anoia
|
||||
}:
|
||||
runCommand "ifwait" {} ''
|
||||
mkdir -p $out/bin
|
||||
cp -p ${writeFennelScript "ifwait" [netlink-lua] ./ifwait.fnl} $out/bin/ifwait
|
||||
cp -p ${writeFennelScript "ifwait" [anoia netlink-lua] ./ifwait.fnl} $out/bin/ifwait
|
||||
''
|
||||
|
|
|
@ -0,0 +1,195 @@
|
|||
{: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"}
|
|
@ -1,52 +1,64 @@
|
|||
(local netlink (require :netlink))
|
||||
(local sock (netlink.socket))
|
||||
(local nl (require :anoia.nl))
|
||||
(local { : assoc : system } (require :anoia))
|
||||
|
||||
; (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))
|
||||
|
||||
(local parameters
|
||||
(or
|
||||
(parse-args arg)
|
||||
(assert false (.. "Usage: " (. arg 0) " [-v] ifname [present|up|running]"))))
|
||||
(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}
|
||||
|
||||
(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 :up :yes}
|
||||
{:present :true :up true}
|
||||
|
||||
{:event "newlink" :name parameters.link :up :yes}
|
||||
{:present :true :up true}
|
||||
{:event "newlink" :name params.link}
|
||||
{:present true }
|
||||
|
||||
{:event "newlink" :name parameters.link}
|
||||
{:present true }
|
||||
_
|
||||
{})]
|
||||
(not (not (. got params.expecting)))))
|
||||
|
||||
_
|
||||
{})]
|
||||
(when (. got parameters.expecting)
|
||||
(os.exit 0)))))
|
||||
(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)))))
|
||||
))
|
||||
|
||||
(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)))
|
||||
|
||||
(when parameters.verbose
|
||||
(print (.. (. arg 0) ": 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))))
|
||||
|
||||
(run-events (sock:query {:link true}))
|
||||
(when (not (= (. arg 0) "test"))
|
||||
(run arg #(nl.events {:link true})))
|
||||
|
||||
(while (sock:poll) (run-events (sock:event)))
|
||||
{ : run }
|
||||
|
|
|
@ -0,0 +1,117 @@
|
|||
(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")
|
|
@ -15,10 +15,12 @@ let
|
|||
in {
|
||||
kernel
|
||||
, commandLine
|
||||
, commandLineDtbNode ? "bootargs"
|
||||
, entryPoint
|
||||
, extraName ? "" # e.g. socFamily
|
||||
, loadAddress
|
||||
, imageFormat
|
||||
, alignment ? null
|
||||
, dtb ? null
|
||||
} : stdenv.mkDerivation {
|
||||
name = "kernel.image";
|
||||
|
@ -39,7 +41,7 @@ in {
|
|||
'';
|
||||
mungeDtbPhase = ''
|
||||
dtc -I dtb -O dts -o tmp.dts ${dtb}
|
||||
echo '/{ chosen { bootargs = ${builtins.toJSON commandLine}; }; };' >> tmp.dts
|
||||
echo '/{ chosen { ${commandLineDtbNode} = ${builtins.toJSON commandLine}; }; };' >> tmp.dts
|
||||
dtc -I dts -O dtb -o tmp.dtb tmp.dts
|
||||
'';
|
||||
|
||||
|
@ -69,7 +71,7 @@ in {
|
|||
};
|
||||
};
|
||||
_VARS
|
||||
mkimage -f mkimage.its kernel.uimage
|
||||
mkimage -f mkimage.its ${lib.optionalString (alignment != null) "-B 0x${lib.toHexString alignment}"} kernel.uimage
|
||||
mkimage -l kernel.uimage
|
||||
'';
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
, systemconfig
|
||||
, execline
|
||||
, lib
|
||||
, services ? null
|
||||
, config ? {}
|
||||
, liminix
|
||||
, pseudofile
|
||||
, pkgs
|
||||
|
@ -12,6 +12,7 @@
|
|||
let
|
||||
inherit (pseudofile) dir symlink;
|
||||
inherit (liminix.services) oneshot;
|
||||
paramConfig = config;
|
||||
newRoot = "/run/maintenance";
|
||||
sysconfig =
|
||||
let
|
||||
|
@ -25,8 +26,8 @@ let
|
|||
emptyenv chroot . /bin/init
|
||||
'';
|
||||
base = {...} : {
|
||||
config = {
|
||||
services = services // {
|
||||
config = {
|
||||
services = {
|
||||
banner = oneshot {
|
||||
name = "banner";
|
||||
up = "cat /etc/banner > /dev/console";
|
||||
|
@ -60,6 +61,7 @@ let
|
|||
../../modules/users.nix
|
||||
../../modules/busybox.nix
|
||||
base
|
||||
({ ... } : paramConfig)
|
||||
../../modules/s6
|
||||
];
|
||||
};
|
||||
|
|
|
@ -11,7 +11,7 @@ test -n "$contents" && for d in $contents; do
|
|||
touch $out/${name}/contents.d/$d
|
||||
done
|
||||
|
||||
for i in run notification-fd up down consumer-for producer-for pipeline-name ; do
|
||||
for i in timeout-up timeout-down run notification-fd up down finish consumer-for producer-for pipeline-name restart-on-upgrade; do
|
||||
test -n "$(printenv $i)" && (echo "$(printenv $i)" > $out/${name}/$i)
|
||||
done
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ let
|
|||
${commands}
|
||||
'';
|
||||
cleanupScript = name : ''
|
||||
#!/bin/sh
|
||||
if test -d ${prefix}/${name} ; then rm -rf ${prefix}/${name} ; fi
|
||||
'';
|
||||
service = {
|
||||
|
@ -26,20 +27,25 @@ 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 notification-fd
|
||||
producer-for consumer-for pipeline-name;
|
||||
inherit name serviceType up down run finish notification-fd
|
||||
producer-for consumer-for pipeline-name timeout-up timeout-down;
|
||||
restart-on-upgrade = isTrigger;
|
||||
buildInputs = buildInputs ++ dependencies ++ contents;
|
||||
dependencies = builtins.map (d: d.name) dependencies;
|
||||
contents = builtins.map (d: d.name) contents;
|
||||
|
@ -52,6 +58,7 @@ let
|
|||
, outputs ? []
|
||||
, notification-fd ? null
|
||||
, dependencies ? []
|
||||
, buildInputs ? []
|
||||
, ...
|
||||
} @ args:
|
||||
let logger = service {
|
||||
|
@ -63,9 +70,10 @@ let
|
|||
pipeline-name = "${name}-pipeline";
|
||||
};
|
||||
in service (args // {
|
||||
buildInputs = [ logger ];
|
||||
buildInputs = buildInputs ++ [ logger ];
|
||||
serviceType = "longrun";
|
||||
run = serviceScript "${run}\n${cleanupScript name}";
|
||||
run = serviceScript run;
|
||||
finish = cleanupScript name;
|
||||
producer-for = "${name}-log";
|
||||
});
|
||||
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
{ 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}/"
|
||||
'';
|
||||
|
||||
}
|
|
@ -1,12 +1,19 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
ssh_command=${SSH_COMMAND-ssh}
|
||||
if [ "$1" = "--no-reboot" ] ; then
|
||||
reboot="true"
|
||||
shift
|
||||
else
|
||||
reboot="reboot"
|
||||
fi
|
||||
|
||||
reboot="reboot"
|
||||
|
||||
case "$1" in
|
||||
"--no-reboot")
|
||||
unset reboot
|
||||
shift
|
||||
;;
|
||||
"--fast")
|
||||
reboot="soft"
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
|
||||
target_host=$1
|
||||
shift
|
||||
|
@ -20,7 +27,16 @@ 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
|
||||
$ssh_command $target_host "sync; source /etc/profile; reboot -f"
|
||||
case "$reboot" in
|
||||
reboot)
|
||||
$ssh_command $target_host "sync; source /etc/profile; reboot"
|
||||
;;
|
||||
soft)
|
||||
$ssh_command $target_host $toplevel/bin/restart-services
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
else
|
||||
echo Rebuild failed
|
||||
fi
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
{ 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}/"
|
||||
'';
|
||||
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
{ 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}/"
|
||||
'';
|
||||
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
#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;
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
local nellie = require('nellie')
|
||||
print('dfg')
|
||||
local f = nellie.open(2)
|
||||
|
||||
print(string.byte(f:read(1000), 0, 60))
|
||||
print("CLOSED", f:close())
|
|
@ -2,5 +2,6 @@
|
|||
writeFennelScript
|
||||
, anoia
|
||||
, lua
|
||||
, lualinux
|
||||
}:
|
||||
writeFennelScript "odhcpc-script" [anoia lua.pkgs.luafilesystem] ./odhcp6-script.fnl
|
||||
writeFennelScript "odhcpc-script" [anoia lualinux] ./odhcp6-script.fnl
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
fetchFromGitHub
|
||||
, writeShellScript
|
||||
, pkgsBuildBuild
|
||||
}:
|
||||
let
|
||||
src = fetchFromGitHub {
|
||||
|
@ -14,6 +15,7 @@ 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() {
|
||||
|
@ -35,4 +37,25 @@ 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
|
||||
'';
|
||||
}
|
||||
|
|
|
@ -21,11 +21,27 @@ 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
|
||||
'';
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
{
|
||||
writeText
|
||||
, writeFennelScript
|
||||
, lib
|
||||
, s6-init-bin
|
||||
, closureInfo
|
||||
|
@ -52,7 +53,7 @@ let
|
|||
chown = if uid>0 || gid>0
|
||||
then "\nCHOWN(${qpathname},${toString uid},${toString gid});\n"
|
||||
else "";
|
||||
in "${cmd} ${chown}";
|
||||
in "unlink(${qpathname}); ${cmd} ${chown}";
|
||||
in mapAttrsToList (makeFile prefix) attrset;
|
||||
activateScript = attrset: writeText "makedevs.c" ''
|
||||
#include "defs.h"
|
||||
|
@ -80,6 +81,7 @@ 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-/}
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
(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"))
|
||||
)
|
|
@ -0,0 +1,12 @@
|
|||
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
|
|
@ -0,0 +1,12 @@
|
|||
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
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
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;
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
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
|
|
@ -0,0 +1,33 @@
|
|||
(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")
|
|
@ -0,0 +1,77 @@
|
|||
(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 }
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
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";
|
||||
};
|
||||
}
|
|
@ -8,4 +8,6 @@
|
|||
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;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
{ 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";
|
||||
};
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
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 }
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
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
|
||||
|
||||
''
|
|
@ -32,9 +32,5 @@ in {
|
|||
};
|
||||
|
||||
rootfsType = "jffs2";
|
||||
services.default = lib.mkForce (target {
|
||||
name = "default";
|
||||
contents = with config.services; [ loopback ntp defaultroute4 sshd dhcpv4 ];
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,14 +1,26 @@
|
|||
#!/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)
|
||||
|
||||
|
@ -31,7 +43,7 @@ mkdir ./vm
|
|||
|
||||
cat ${rootfs} > rootfs
|
||||
|
||||
truncate -s 24M rootfs
|
||||
truncate -s 32M rootfs
|
||||
resize2fs rootfs
|
||||
|
||||
dd if=rootfs of=disk-image bs=512 seek=4 conv=sync
|
||||
|
@ -50,9 +62,12 @@ echo "READY"
|
|||
touch known_hosts
|
||||
export SSH_COMMAND="ssh -o UserKnownHostsFile=${work}/known_hosts -o StrictHostKeyChecking=no -p 2022 -i ${here}/id"
|
||||
|
||||
(cd ${top} && liminix-rebuild root@localhost -I liminix-config=${here}/with-figlet.nix --arg device "import ./devices/qemu-armv7l")
|
||||
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
|
||||
|
||||
ls -l vm
|
||||
|
||||
cd ${work} && expect $here/wait-for-reboot.expect
|
||||
cd / ; rm -rf $work
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
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"
|
|
@ -5,4 +5,9 @@ send "\r\n"
|
|||
expect {
|
||||
"# " { send "hostname\r\n" };
|
||||
}
|
||||
expect "(none)"
|
||||
|
||||
expect {
|
||||
"(none)" {}
|
||||
"liminix" {}
|
||||
timeout { exit(1) }
|
||||
}
|
|
@ -4,4 +4,11 @@
|
|||
defaultProfile.packages = with pkgs; [
|
||||
figlet
|
||||
];
|
||||
services.ripvanwinkle = pkgs.liminix.services.longrun {
|
||||
name = "winkle";
|
||||
run = ''
|
||||
echo SLEEPING > /dev/console
|
||||
sleep 3600
|
||||
'';
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
{ config, pkgs, lib, ... } :
|
||||
let
|
||||
inherit (pkgs.liminix.services) bundle oneshot longrun;
|
||||
inherit (pkgs) serviceFns;
|
||||
# EDIT: you can pick your preferred RFC1918 address space
|
||||
# for NATted connections, if you don't like this one.
|
||||
ipv4LocalNet = "10.8.0";
|
||||
svc = config.system.service;
|
||||
|
||||
in rec {
|
||||
imports = [
|
||||
../../modules/bridge
|
||||
../../modules/dhcp6c
|
||||
../../modules/dnsmasq
|
||||
../../modules/firewall
|
||||
../../modules/hostapd
|
||||
../../modules/network
|
||||
../../modules/ssh
|
||||
../../modules/vlan
|
||||
../../modules/wlan.nix
|
||||
];
|
||||
rootfsType = "jffs2";
|
||||
hostname = "updown";
|
||||
|
||||
services.int = svc.network.address.build {
|
||||
interface = svc.bridge.primary.build { ifname = "int"; };
|
||||
family = "inet"; address = "${ipv4LocalNet}.1"; prefixLength = 16;
|
||||
};
|
||||
|
||||
services.bridge = svc.bridge.members.build {
|
||||
primary = services.int;
|
||||
members = with config.hardware.networkInterfaces;
|
||||
[ lan ];
|
||||
};
|
||||
|
||||
services.sshd = svc.ssh.build { };
|
||||
|
||||
# users.root = {
|
||||
# # EDIT: choose a root password and then use
|
||||
# # "mkpasswd -m sha512crypt" to determine the hash.
|
||||
# # It should start wirh $6$.
|
||||
# passwd = "$6$6HG7WALLQQY1LQDE$428cnouMJ7wVmyK9.dF1uWs7t0z9ztgp3MHvN5bbeo0M4Kqg/u2ThjoSHIjCEJQlnVpDOaEKcOjXAlIClHWN21";
|
||||
# openssh.authorizedKeys.keys = [
|
||||
# # EDIT: you can add your ssh pubkey here
|
||||
# # "ssh-rsa AAAAB3NzaC1....H6hKd user@example.com";
|
||||
# ];
|
||||
# };
|
||||
|
||||
defaultProfile.packages = with pkgs; [
|
||||
min-collect-garbage
|
||||
# strace
|
||||
# ethtool
|
||||
tcpdump
|
||||
];
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
set timeout 10
|
||||
|
||||
spawn socat unix-connect:vm/monitor -
|
||||
set monitor_id $spawn_id
|
||||
|
||||
expect "(qemu)"
|
||||
send "set_link virtio-net-pci.1 off\n"
|
||||
expect "(qemu)"
|
||||
send "set_link virtio-net-pci.0 off\n"
|
||||
expect "(qemu)"
|
||||
send "c\r\n"
|
||||
spawn socat unix-connect:vm/console -
|
||||
set console_id $spawn_id
|
||||
|
||||
expect "BusyBox"
|
||||
expect "#" { send "PS1=RE\\ADY_\\ ; stty -echo \r" }
|
||||
expect "READY_" { send "s6-rc -b -a list\r" } ; # -b waits for s6-rc lock
|
||||
expect "READY_" { send "ls /sys/class/net/lan/master\r" }
|
||||
expect {
|
||||
"No such file or directory" { }
|
||||
timeout { exit 1 }
|
||||
}
|
||||
|
||||
expect "READY_" { send "cat /sys/class/net/lan/operstate\r" }
|
||||
expect {
|
||||
"down" { }
|
||||
"up" { exit 1 }
|
||||
}
|
||||
expect "READY_" { send "s6-rc -a -u change\r" }
|
||||
expect {
|
||||
"unable to take locks" { exit 1 }
|
||||
"READY_" { send "\r" }
|
||||
}
|
||||
|
||||
set spawn_id $monitor_id
|
||||
send "\r"
|
||||
expect "(qemu)"
|
||||
send "set_link virtio-net-pci.1 on\n"
|
||||
expect "(qemu)"
|
||||
send "set_link virtio-net-pci.0 on\n"
|
||||
expect "(qemu)"
|
||||
set spawn_id $console_id
|
||||
|
||||
expect "entered forwarding state"
|
||||
send "\r"
|
||||
expect "READY_" { send "cat /sys/class/net/lan/operstate\r" }
|
||||
expect {
|
||||
"down" { exit 1 }
|
||||
"up" { }
|
||||
}
|
||||
|
||||
expect "READY_" { send "cat /sys/class/net/lan/master/uevent\r" }
|
||||
expect {
|
||||
"INTERFACE=int" { }
|
||||
timeout { exit 1 }
|
||||
}
|
||||
|
||||
expect "READY_" { send "s6-rc listall int.link.a.10.8.0.1.member.lan.link ; hostname\r" }
|
||||
|
||||
expect {
|
||||
"updown" {}
|
||||
timeout { exit 1 }
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue