Compare commits
127 Commits
gateway-profile
...
lte
| Author | SHA1 | Date | |
|---|---|---|---|
| dc3ade7969 | |||
| 7ece8dde5e | |||
| 5a19af318e | |||
| 6d36f0eb13 | |||
| 5d246e1f1a | |||
| 0e2e44c6ee | |||
| e2c5615677 | |||
| ab59c852e2 | |||
| 98d198960b | |||
| d98f011292 | |||
| 991de7eb9e | |||
| 513c69cb5b | |||
| 15856d224c | |||
| abbdda7b30 | |||
| e56b761ba3 | |||
| 15ec172739 | |||
| 6ddba0f68b | |||
| 471c63b399 | |||
| 782feaeafa | |||
| ac54c89427 | |||
| 5a3646cb29 | |||
| e249f48cff | |||
| 6661e42684 | |||
| b9ba9ef835 | |||
| 8b69dcc209 | |||
| 9b3a3b9ff7 | |||
| 7d08497bcb | |||
| 0e84adaa0e | |||
| 660ed5df8f | |||
| 792a11c8c0 | |||
| 7e4a05bbf8 | |||
| a4ba5c85e1 | |||
| 723ef73d5a | |||
| 3d4e782929 | |||
| 1b6a05aec5 | |||
| 80628a3d90 | |||
| bf0cafffed | |||
| e49aba127c | |||
| 324465bc18 | |||
| b33249a050 | |||
| b9c084415e | |||
| cf9cadd212 | |||
| a116fe084a | |||
| 74cf3e0711 | |||
| 9795f03da4 | |||
| cdb23b147c | |||
| dbd1264352 | |||
| 834858d5bc | |||
| 18335b95e3 | |||
| 6bee2f67ac | |||
| b4ba3eea21 | |||
| 16af3984c9 | |||
| ce7e395295 | |||
| 7e13e017eb | |||
| bbf2f53c0e | |||
| 032d0f8aca | |||
| b8ac9e5279 | |||
| ff2604ca5d | |||
| 72789984ce | |||
| 90d9d0e811 | |||
| 97a8ae1c84 | |||
| 52eb283a26 | |||
| cbb1de804e | |||
| f9c03998b8 | |||
| 50de1b090f | |||
| 648382f64a | |||
| e9370358ae | |||
| 762ce7b6b8 | |||
| b1c0560f4f | |||
| e34135c41a | |||
| 712c9b266f | |||
| 4df963996c | |||
| 349bfecbb8 | |||
| 450d3820b2 | |||
| 771585546d | |||
| 73abf952d5 | |||
| 8af4e9fd5b | |||
| 7e19d80130 | |||
| 0f0688c802 | |||
| b43f17f655 | |||
| adf62d4483 | |||
| 68eb1360f6 | |||
| 19ad6cd278 | |||
| 00076c7b81 | |||
| 721e7499f3 | |||
| fc723b9a35 | |||
| a5f16dfa81 | |||
| 41a4b1f7ef | |||
| 42a5699326 | |||
| ea2b25168e | |||
| 5564cf0554 | |||
| f3a13630d3 | |||
| f233acf9ff | |||
| b6a054c588 | |||
| b231664a06 | |||
| f4bf3029fa | |||
| 05f2c9a2f7 | |||
| 5df5c822ea | |||
| 4795dd05b7 | |||
| a192f08881 | |||
| a873dc6608 | |||
| 2fb4756a7f | |||
| 04f5174425 | |||
| dca2e4def1 | |||
| b60126775a | |||
| 76f11bcc93 | |||
| efcfdcc21d | |||
| 77f1a78331 | |||
| 28a5dec7dd | |||
| fad0a47b75 | |||
| af52aafc84 | |||
| 34442b6069 | |||
| b8a46fc05e | |||
| 8ac2c6cec1 | |||
| 8879b2d1ba | |||
| 83e346d5a0 | |||
| 156b1fe64a | |||
| 1a314e55b7 | |||
| 9263b21faa | |||
| 0a820a702a | |||
| 4ea518e296 | |||
| 98318b450d | |||
| e4ac7f19dc | |||
| 9c22744850 | |||
| c697be8c28 | |||
| 202a37221a | |||
| 436eb03a7b |
@@ -83,4 +83,23 @@ 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.
|
||||
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;
|
||||
};
|
||||
})
|
||||
];
|
||||
|
||||
+562
@@ -4289,3 +4289,565 @@ 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
|
||||
[X] 5) package it
|
||||
[X] 6) make uevent-watcher use it instead of netlink directly
|
||||
[X] 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?
|
||||
|
||||
Sat Apr 27 21:52:11 BST 2024
|
||||
|
||||
We have a passing inout test. Next thing to do is try it on
|
||||
the actual arhcive hardware
|
||||
|
||||
Next big thing is some kind of failovery service. Almost-obvious
|
||||
candidate is LTE failover with aaisp l2tp tunnel
|
||||
|
||||
Tue Apr 30 23:27:30 BST 2024
|
||||
|
||||
I want to connect my new ip camera to arthur without letting it reach the
|
||||
internet, or the internet reach it.
|
||||
|
||||
we could plug it into a gl.inet box running dhcp server on lan
|
||||
and client on wan, then use NAT to expose the camera's http and rtsp
|
||||
ports on whatever address it has on the wan interface
|
||||
|
||||
Tue May 7 22:23:49 BST 2024
|
||||
|
||||
If we want to build a config with an l2tp upstream, it needs an
|
||||
underlying dhcp interface not pppoe as we can't use the bordervm l2tp
|
||||
account simultaneously. Having bordervm do dhcp might be quite useful
|
||||
anyway for other applications, although it will have to double-nat to
|
||||
the internet. We could give it an aaisp /64 and have routable ipv6 but
|
||||
maybe that's a level of faff too high.
|
||||
|
||||
Given that we can build xl2tpd and a service for it.
|
||||
|
||||
|
||||
're using the same l2tp account for thingy that we use to simulate ppp,
|
||||
we need an upstream which is not ppp
|
||||
|
||||
We need a less shit coldplug that copes with filenames containing spaces (!)
|
||||
|
||||
Fri May 10 00:33:14 BST 2024
|
||||
|
||||
Getting xl2tp hackily running turned out to be not a lot of work. However,
|
||||
we need to figure out routing
|
||||
|
||||
- we need a route on lan device to the dns to lookup l2tp.aaisp.net.uk
|
||||
- we need a route on lan device to l2tp.aaisp.net.uk
|
||||
|
||||
also it doesn't die when the tunnel closes, which is a bit shit
|
||||
|
||||
maybe this is where we lean into health check services
|
||||
|
||||
a health check service is just a service that watches another service
|
||||
and kills it if it's not healthy.
|
||||
|
||||
for xl2tpd, "not healthy" is "there is no ppp process" or "there is no
|
||||
tunnel" or "the tunnel has no sessions". I don't know how we
|
||||
(robustly) test for no ppp process associated with the l2tp peer
|
||||
|
||||
when ppp quits, does the tunnel come down?
|
||||
in xl2tld.c child_handler we respond to sigchld by closing c->fd
|
||||
and setting it to -1
|
||||
|
||||
Sat May 11 17:55:04 BST 2024
|
||||
|
||||
A better way to monitor the connection health would be to ping a
|
||||
computer on the internet (preferably one that doesn't mind being
|
||||
pinged). If we combine autodial with "is $isp still there" then we
|
||||
should have something fairly robust.
|
||||
|
||||
xl2tpd spawns pppd, we should equip it with config that writes the
|
||||
ppp outputs (ip address etc) to the xl2tp service directory so
|
||||
that it can be used like a regular ppp. This will also make
|
||||
it possible to have the health check work by pinging the peer address
|
||||
|
||||
Sun May 12 22:33:09 BST 2024
|
||||
|
||||
sleep until the interface is probably up
|
||||
failure counter = 0
|
||||
loop indefinitely
|
||||
get outputs/peer-address of watched ppp service
|
||||
ping it
|
||||
if ok
|
||||
reset failure counter
|
||||
else
|
||||
increment failure counter
|
||||
fi
|
||||
if failure counter > threshold
|
||||
bounce the ppp service
|
||||
exit, if previous action didn't do that already
|
||||
end
|
||||
sleep(check interval)
|
||||
end loop
|
||||
|
||||
|
||||
# ps ax | grep l2tp
|
||||
72 root 1316 S s6-supervise l2tp.aaisp.net.uk.l2tp
|
||||
73 root 1316 S s6-supervise l2tp.aaisp.net.uk.l2tp-log
|
||||
122 root 1428 S {run.user} /bin/sh ./run.user l2tp.aaisp.net.uk.l2tp
|
||||
1099 root 1428 S {run.user} /bin/sh ./run.user l2tp.aaisp.net.uk.l2tp
|
||||
1102 root 1104 S {xl2tpd} /nix/store/i1bbqh7vybam03l6jzf4sm4np3k4ack5
|
||||
1115 root 1420 S grep l2tp
|
||||
# s6-rc -d change l2tp.aaisp.net.uk.l2tp
|
||||
# ps ax | grep l2tp
|
||||
72 root 1316 S s6-supervise l2tp.aaisp.net.uk.l2tp
|
||||
73 root 1316 S s6-supervise l2tp.aaisp.net.uk.l2tp-log
|
||||
122 root 1428 S {run.user} /bin/sh ./run.user l2tp.aaisp.net.uk.l2tp
|
||||
1102 root 1104 S {xl2tpd} /nix/store/i1bbqh7vybam03l6jzf4sm4np3k4ack5
|
||||
1122 root 1420 S grep l2tp
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
we are the LAC, aaisp are the LNS
|
||||
|
||||
@@ -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 ''
|
||||
@@ -76,8 +80,19 @@ in {
|
||||
};
|
||||
};
|
||||
services.openssh.enable = true;
|
||||
services.dnsmasq = {
|
||||
enable = true;
|
||||
resolveLocalQueries = false;
|
||||
settings = {
|
||||
# domain-needed = true;
|
||||
dhcp-range = [ "10.0.0.10,10.0.0.240" ];
|
||||
interface = "eth1";
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.sshd.wantedBy = pkgs.lib.mkForce [ "multi-user.target" ];
|
||||
|
||||
|
||||
virtualisation = {
|
||||
qemu = {
|
||||
networkingOptions = [];
|
||||
@@ -108,6 +123,7 @@ in {
|
||||
tufted
|
||||
iptables
|
||||
usbutils
|
||||
busybox
|
||||
];
|
||||
security.sudo.wheelNeedsPassword = false;
|
||||
networking = {
|
||||
@@ -117,11 +133,17 @@ in {
|
||||
useDHCP = false;
|
||||
ipv4.addresses = [ { address = "10.0.0.1"; prefixLength = 24;}];
|
||||
};
|
||||
nat = {
|
||||
enable = true;
|
||||
internalInterfaces = [ "eth1" ];
|
||||
externalInterface ="eth0";
|
||||
};
|
||||
};
|
||||
users.users.liminix = {
|
||||
isNormalUser = true;
|
||||
uid = 1000;
|
||||
extraGroups = [ "wheel"];
|
||||
openssh.authorizedKeys.keys = cfg.keys;
|
||||
};
|
||||
services.getty.autologinUser = "liminix";
|
||||
};
|
||||
|
||||
+7
-1
@@ -1,5 +1,6 @@
|
||||
{
|
||||
device
|
||||
deviceName ? null
|
||||
, device ? (import ./devices/${deviceName} )
|
||||
, liminix-config ? <liminix-config>
|
||||
, nixpkgs ? <nixpkgs>
|
||||
, borderVmConf ? ./bordervm.conf.nix
|
||||
@@ -20,12 +21,16 @@ 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
|
||||
@@ -72,6 +77,7 @@ in {
|
||||
min-copy-closure
|
||||
fennelrepl
|
||||
lzma
|
||||
lua
|
||||
];
|
||||
};
|
||||
}
|
||||
|
||||
@@ -125,8 +125,14 @@
|
||||
networkInterfaces =
|
||||
let inherit (config.system.service.network) link;
|
||||
in {
|
||||
lan = link.build { ifname = "eth0"; };
|
||||
wan = link.build { ifname = "eth1"; };
|
||||
lan = link.build {
|
||||
ifname = "lan";
|
||||
devpath = "/devices/platform/ahb/19000000.eth";
|
||||
};
|
||||
wan = link.build {
|
||||
ifname = "wan";
|
||||
devpath = "/devices/platform/ahb/1a000000.eth";
|
||||
};
|
||||
wlan = link.build {
|
||||
ifname = "wlan0";
|
||||
dependencies = [ mac80211 ];
|
||||
@@ -149,6 +155,7 @@
|
||||
};
|
||||
boot.tftp = {
|
||||
loadAddress = lim.parseInt "0x00A00000";
|
||||
appendDTB = true;
|
||||
};
|
||||
kernel = {
|
||||
src = pkgs.pkgsBuildBuild.fetchurl {
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
+18
-30
@@ -18,8 +18,8 @@
|
||||
in rec {
|
||||
boot = {
|
||||
tftp = {
|
||||
serverip = "192.168.8.148";
|
||||
ipaddr = "192.168.8.251";
|
||||
serverip = "10.0.0.1";
|
||||
ipaddr = "10.0.0.8";
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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";
|
||||
};
|
||||
@@ -146,9 +124,7 @@ in rec {
|
||||
|
||||
users.root = {
|
||||
passwd = lib.mkForce secrets.root.passwd;
|
||||
# openssh.authorizedKeys.keys = [
|
||||
# (builtins.readFile "/home/dan/.ssh/id_rsa.pub")
|
||||
# ];
|
||||
openssh.authorizedKeys.keys = secrets.root.keys;
|
||||
};
|
||||
|
||||
users.backup = {
|
||||
@@ -159,5 +135,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 { };
|
||||
|
||||
+6
-37
@@ -8,9 +8,11 @@
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
modulesPath,
|
||||
...
|
||||
}: let
|
||||
secrets = import ./extneder-secrets.nix;
|
||||
svc = config.system.service;
|
||||
in rec {
|
||||
boot = {
|
||||
tftp = {
|
||||
@@ -20,47 +22,13 @@ in rec {
|
||||
};
|
||||
|
||||
imports = [
|
||||
../modules/profiles/wap.nix
|
||||
../modules/vlan
|
||||
"${modulesPath}/profiles/wap.nix"
|
||||
"${modulesPath}/vlan"
|
||||
"${modulesPath}/ssh"
|
||||
];
|
||||
|
||||
hostname = "extneder";
|
||||
|
||||
kernel = {
|
||||
config = {
|
||||
|
||||
NETFILTER_XT_MATCH_CONNTRACK = "y";
|
||||
|
||||
IP6_NF_IPTABLES = "y"; # do we still need these
|
||||
IP_NF_IPTABLES = "y"; # if using nftables directly
|
||||
|
||||
# these are copied from rotuer and need review.
|
||||
# we're not running a firewall, so why do we need
|
||||
# nftables config?
|
||||
IP_NF_NAT = "y";
|
||||
IP_NF_TARGET_MASQUERADE = "y";
|
||||
NETFILTER = "y";
|
||||
NETFILTER_ADVANCED = "y";
|
||||
NETFILTER_XTABLES = "y";
|
||||
|
||||
NFT_COMPAT = "y";
|
||||
NFT_CT = "y";
|
||||
NFT_LOG = "y";
|
||||
NFT_MASQ = "y";
|
||||
NFT_NAT = "y";
|
||||
NFT_REJECT = "y";
|
||||
NFT_REJECT_INET = "y";
|
||||
|
||||
NF_CONNTRACK = "y";
|
||||
NF_NAT = "y";
|
||||
NF_NAT_MASQUERADE = "y";
|
||||
NF_TABLES = "y";
|
||||
NF_TABLES_INET = "y";
|
||||
NF_TABLES_IPV4 = "y";
|
||||
NF_TABLES_IPV6 = "y";
|
||||
};
|
||||
};
|
||||
|
||||
profile.wap = {
|
||||
interfaces = with config.hardware.networkInterfaces; [
|
||||
lan
|
||||
@@ -79,6 +47,7 @@ in rec {
|
||||
};
|
||||
};
|
||||
|
||||
services.sshd = svc.ssh.build {};
|
||||
users.root.passwd = lib.mkForce secrets.root.passwd;
|
||||
defaultProfile.packages = with pkgs; [nftables strace tcpdump swconfig];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
...
|
||||
}: let
|
||||
secrets = import ./extneder-secrets.nix;
|
||||
rsecrets = import ./rotuer-secrets.nix;
|
||||
lns = "l2tp.aaisp.net.uk";
|
||||
inherit (pkgs.liminix.services) oneshot longrun bundle target;
|
||||
inherit (pkgs.pseudofile) dir symlink;
|
||||
inherit (pkgs) writeText dropbear ifwait serviceFns;
|
||||
svc = config.system.service;
|
||||
in rec {
|
||||
boot = {
|
||||
tftp = {
|
||||
serverip = "10.0.0.1";
|
||||
ipaddr = "10.0.0.8";
|
||||
};
|
||||
};
|
||||
|
||||
imports = [
|
||||
../modules/cdc-ncm
|
||||
../modules/network
|
||||
../modules/vlan
|
||||
../modules/ssh
|
||||
../modules/usb.nix
|
||||
../modules/watchdog
|
||||
../modules/mount
|
||||
../modules/ppp
|
||||
];
|
||||
hostname = "thing";
|
||||
|
||||
services.dhcpc = svc.network.dhcp.client.build {
|
||||
interface = config.services.wwan;
|
||||
dependencies = [ config.services.hostname ];
|
||||
};
|
||||
|
||||
services.sshd = svc.ssh.build { };
|
||||
|
||||
services.resolvconf = oneshot rec {
|
||||
dependencies = [ services.dhcpc ];
|
||||
name = "resolvconf";
|
||||
up = ''
|
||||
. ${serviceFns}
|
||||
( in_outputs ${name}
|
||||
for i in $(output ${services.dhcpc} dns); do
|
||||
echo "nameserver $i" > resolv.conf
|
||||
done
|
||||
)
|
||||
'';
|
||||
};
|
||||
filesystem = dir {
|
||||
etc = dir {
|
||||
"resolv.conf" = symlink "${services.resolvconf}/.outputs/resolv.conf";
|
||||
};
|
||||
srv = dir {};
|
||||
};
|
||||
|
||||
services.lnsroute = svc.network.route.build {
|
||||
via = "$(output ${services.dhcpc} router)";
|
||||
target = lns;
|
||||
dependencies = [services.dhcpc];
|
||||
};
|
||||
|
||||
services.l2tp = svc.l2tp.build {
|
||||
inherit lns;
|
||||
ppp-options = [
|
||||
"debug" "+ipv6" "noauth"
|
||||
"name" rsecrets.l2tp.name
|
||||
"password" rsecrets.l2tp.password
|
||||
];
|
||||
dependencies = [ services.lnsroute ];
|
||||
};
|
||||
|
||||
services.defaultroute4 = svc.network.route.build {
|
||||
via = "$(output ${services.l2tp} router)";
|
||||
target = "default";
|
||||
dependencies = [services.l2tp];
|
||||
};
|
||||
|
||||
users.root = {
|
||||
passwd = lib.mkForce secrets.root.passwd;
|
||||
openssh.authorizedKeys.keys = secrets.root.keys;
|
||||
};
|
||||
}
|
||||
+52
-149
@@ -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,29 +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
|
||||
./ubinize.nix
|
||||
./users.nix
|
||||
./vlan
|
||||
./watchdog
|
||||
|
||||
+29
-3
@@ -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 = ''
|
||||
@@ -111,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";
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
{ config, pkgs, lib, ... }:
|
||||
let
|
||||
inherit (pkgs.liminix.services) oneshot;
|
||||
svc = config.system.service;
|
||||
in {
|
||||
config = {
|
||||
kernel.config = {
|
||||
USB_NET_HUAWEI_CDC_NCM = "y";
|
||||
USB_USBNET = "y";
|
||||
USB_SERIAL = "y";
|
||||
USB_SERIAL_OPTION = "y";
|
||||
};
|
||||
|
||||
# https://www.0xf8.org/2017/01/flashing-a-huawei-e3372h-4g-lte-stick-from-hilink-to-stick-mode/
|
||||
|
||||
services.wwan = let
|
||||
chat = lib.escapeShellArgs [
|
||||
# Your usb modem thing might present as a tty that you run PPP
|
||||
# over, or as a network device ("ndis" or "ncm"). The latter
|
||||
# kind is to be preferred, at least in principle, because it's
|
||||
# faster. This initialization sequence works for the Huawei
|
||||
# E3372, and took much swearing: the error messages are *awful*
|
||||
"" "AT"
|
||||
"OK" "ATZ"
|
||||
# create PDP context
|
||||
"OK" "AT+CGDCONT=1,\"IP\",\"data.uk\""
|
||||
# activate PDP context
|
||||
"OK" "AT+CGACT=1,1"
|
||||
# setup username and password per requirements of sim provider.
|
||||
# (caret is special to chat, so needs escaping in AT commands)
|
||||
"OK" "AT\\^AUTHDATA=1,2,\"1p\",\"one2one\",\"user\""
|
||||
# start the thing (I am choosing to read this as "NDIS DialUP")
|
||||
"OK" "AT\\^NDISDUP=1,1"
|
||||
];
|
||||
modemConfig = oneshot {
|
||||
name = "modem-configure";
|
||||
# this is currently only going to work if there is one
|
||||
# modem only plugged in, it is plugged in already at boot,
|
||||
# and nothing else is providing a USB tty.
|
||||
# https://stackoverflow.com/questions/5477882/how-to-i-detect-whether-a-tty-belonging-to-a-gsm-3g-modem-is-a-data-or-control-p
|
||||
up = ''
|
||||
sleep 2
|
||||
${pkgs.usb-modeswitch}/bin/usb_modeswitch -v 12d1 -p 14fe --huawei-new-mode
|
||||
sleep 5
|
||||
${pkgs.ppp}/bin/chat -s -v ${chat} 0<>/dev/ttyUSB0 1>&0
|
||||
'';
|
||||
down = "chat -v '' ATZ OK </dev/ttyUSB0 >&0";
|
||||
};
|
||||
in svc.network.link.build {
|
||||
ifname = "wwan0";
|
||||
dependencies = [ modemConfig ];
|
||||
};
|
||||
|
||||
|
||||
};
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
liminix
|
||||
, dnsmasq
|
||||
, dnsmasqSmall
|
||||
, serviceFns
|
||||
, lib
|
||||
}:
|
||||
@@ -27,7 +27,7 @@ longrun {
|
||||
dependencies = [ interface ];
|
||||
run = ''
|
||||
. ${serviceFns}
|
||||
${dnsmasq}/bin/dnsmasq \
|
||||
${dnsmasqSmall}/bin/dnsmasq \
|
||||
--user=${user} \
|
||||
--domain=${domain} \
|
||||
--group=${group} \
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
+32
-21
@@ -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 = {
|
||||
|
||||
+19
-11
@@ -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}";
|
||||
}
|
||||
|
||||
@@ -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 )
|
||||
|
||||
|
||||
@@ -17,6 +17,9 @@ in {
|
||||
system.service.pppoe = mkOption {
|
||||
type = liminix.lib.types.serviceDefn;
|
||||
};
|
||||
system.service.l2tp = mkOption {
|
||||
type = liminix.lib.types.serviceDefn;
|
||||
};
|
||||
};
|
||||
config = {
|
||||
system.service.pppoe = pkgs.liminix.callService ./pppoe.nix {
|
||||
@@ -29,6 +32,16 @@ in {
|
||||
description = "options supplied on ppp command line";
|
||||
};
|
||||
};
|
||||
system.service.l2tp = pkgs.liminix.callService ./l2tp.nix {
|
||||
lns = mkOption {
|
||||
type = types.str;
|
||||
description = "hostname or address of the L2TP network server";
|
||||
};
|
||||
ppp-options = mkOption {
|
||||
type = types.listOf types.str;
|
||||
description = "options supplied on ppp command line";
|
||||
};
|
||||
};
|
||||
kernel = {
|
||||
config = {
|
||||
PPP = "y";
|
||||
@@ -36,6 +49,8 @@ in {
|
||||
PPP_DEFLATE = "y";
|
||||
PPP_ASYNC = "y";
|
||||
PPP_SYNC_TTY = "y";
|
||||
PPPOL2TP = "y";
|
||||
L2TP = "y";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
{
|
||||
liminix
|
||||
, lib
|
||||
, ppp
|
||||
, pppoe
|
||||
, writeAshScript
|
||||
, writeText
|
||||
, serviceFns
|
||||
, xl2tpd
|
||||
} :
|
||||
{ lns, ppp-options }:
|
||||
let
|
||||
inherit (liminix.services) longrun;
|
||||
name = "${lns}.l2tp";
|
||||
ip-up = writeAshScript "ip-up" {} ''
|
||||
. ${serviceFns}
|
||||
(in_outputs ${name}
|
||||
echo $1 > ifname
|
||||
echo $2 > tty
|
||||
echo $3 > speed
|
||||
echo $4 > address
|
||||
echo $5 > peer-address
|
||||
echo $DNS1 > ns1
|
||||
echo $DNS2 > ns2
|
||||
)
|
||||
echo >/proc/self/fd/10
|
||||
'';
|
||||
ip6-up = writeAshScript "ip6-up" {} ''
|
||||
. ${serviceFns}
|
||||
(in_outputs ${name}
|
||||
echo $4 > ipv6-address
|
||||
echo $5 > ipv6-peer-address
|
||||
)
|
||||
echo >/proc/self/fd/10
|
||||
'';
|
||||
ppp-options' = ppp-options ++ [
|
||||
"ip-up-script" ip-up
|
||||
"ipv6-up-script" ip6-up
|
||||
"ipparam" name
|
||||
"nodetach"
|
||||
"usepeerdns"
|
||||
"logfd" "2"
|
||||
];
|
||||
conf = writeText "xl2tpd.conf" ''
|
||||
[lac upstream]
|
||||
lns = ${lns}
|
||||
require authentication = no
|
||||
pppoptfile = ${writeText "ppp-options" ppp-options'}
|
||||
autodial = yes
|
||||
redial = yes
|
||||
'';
|
||||
control = "/run/xl2tpd/control-${name}";
|
||||
in
|
||||
longrun {
|
||||
inherit name;
|
||||
run = ''
|
||||
mkdir -p /run/xl2tpd
|
||||
touch ${control}
|
||||
exec ${xl2tpd}/bin/xl2tpd -D -p /run/xl2tpd/${name}.pid -c ${conf} -C ${control}
|
||||
'';
|
||||
notification-fd = 10;
|
||||
}
|
||||
@@ -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";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -40,7 +40,6 @@ in {
|
||||
../network
|
||||
../hostapd
|
||||
../bridge
|
||||
../ssh
|
||||
{ config.services = hostaps; }
|
||||
];
|
||||
|
||||
@@ -54,7 +53,6 @@ in {
|
||||
};
|
||||
};
|
||||
config = {
|
||||
services.sshd = svc.ssh.build {};
|
||||
|
||||
services.int = svc.bridge.primary.build {
|
||||
ifname = "int";
|
||||
|
||||
@@ -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)}";
|
||||
}
|
||||
|
||||
+19
-1
@@ -77,8 +77,24 @@ 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
|
||||
|
||||
dnsmasq =
|
||||
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 {}
|
||||
);
|
||||
|
||||
dnsmasqSmall =
|
||||
let d = prev.dnsmasq.overrideAttrs(o: {
|
||||
preBuild = ''
|
||||
makeFlagsArray=("COPTS=")
|
||||
@@ -255,6 +271,8 @@ extraPkgs // {
|
||||
'';
|
||||
};
|
||||
|
||||
libusb1 = prev.libusb1.override { enableUdev = false; };
|
||||
|
||||
util-linux-small = prev.util-linux.override {
|
||||
ncursesSupport = false;
|
||||
pamSupport = false;
|
||||
|
||||
@@ -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= }
|
||||
+13
-8
@@ -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}"
|
||||
'';
|
||||
}
|
||||
|
||||
+43
-10
@@ -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))
|
||||
}
|
||||
|
||||
+18
-1
@@ -18,6 +18,12 @@
|
||||
f (do (f:close) true)
|
||||
_ false))
|
||||
|
||||
(fn basename [path]
|
||||
(string.match path ".*/([^/]-)$"))
|
||||
|
||||
(fn dirname [path]
|
||||
(string.match path "(.*)/[^/]-$"))
|
||||
|
||||
(fn system [s]
|
||||
(match (os.execute s)
|
||||
res (do (print (.. "Executed \"" s "\", exit code " (tostring res))) res)
|
||||
@@ -66,4 +72,15 @@
|
||||
(s:sub 1 (- (# s) pad))))
|
||||
|
||||
|
||||
{ : assoc : 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)
|
||||
}
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
(local netlink (require :netlink))
|
||||
|
||||
(local { : view } (require :fennel))
|
||||
; (local { : view } (require :fennel))
|
||||
|
||||
(fn events [groups]
|
||||
(let [sock (netlink.socket)]
|
||||
|
||||
+6
-7
@@ -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 "\""))))
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
(local svc (require :anoia.svc))
|
||||
(local svc (require :svc))
|
||||
(local { : view } (require :fennel))
|
||||
|
||||
(local ex (svc.open "./example-output"))
|
||||
|
||||
+6
-5
@@ -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
|
||||
@@ -82,6 +84,8 @@ in {
|
||||
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 {};
|
||||
@@ -105,6 +109,8 @@ in {
|
||||
swconfig = callPackage ./swconfig {};
|
||||
systemconfig = callPackage ./systemconfig {};
|
||||
tufted = callPackage ./tufted {};
|
||||
uevent-watch = callPackage ./uevent-watch {};
|
||||
usb-modeswitch = callPackage ./usb-modeswitch {};
|
||||
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,6 +5,7 @@
|
||||
, lib
|
||||
, luaPackages
|
||||
, lua
|
||||
, lualinux
|
||||
, writeScriptBin
|
||||
, linotify
|
||||
, anoia
|
||||
@@ -15,8 +16,9 @@ let packages = [
|
||||
linotify
|
||||
anoia
|
||||
fennel
|
||||
lualinux
|
||||
netlink-lua
|
||||
lua.pkgs.luafilesystem
|
||||
lua.pkgs.readline
|
||||
];
|
||||
join = ps: builtins.concatStringsSep ";" ps;
|
||||
luapath = join (builtins.map (f:
|
||||
@@ -31,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
|
||||
''
|
||||
|
||||
@@ -37,8 +37,8 @@
|
||||
(when (not (= up wanted?))
|
||||
(set up
|
||||
(if wanted?
|
||||
(pcall system (.. "s6-rc -u change " service))
|
||||
(not (pcall system (.. "s6-rc -d change " service)))))
|
||||
(pcall system (.. "s6-rc -b -u change " service))
|
||||
(not (pcall system (.. "s6-rc -b -d change " service)))))
|
||||
))
|
||||
|
||||
(fn run [args event-fn]
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
(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))
|
||||
|
||||
(macro expect= [actual expected]
|
||||
`(let [ve# (view ,expected)
|
||||
va# (view ,actual)]
|
||||
(when (not (= ve# va#))
|
||||
(assert false
|
||||
(.. "\nexpected " ve# "\ngot " va#)
|
||||
))))
|
||||
|
||||
(fn event-generator [events]
|
||||
(coroutine.wrap
|
||||
@@ -117,3 +113,5 @@
|
||||
(set upsies [])
|
||||
(ifwait.run ["-s" "addmember" "dummy0" "up"] #gen)
|
||||
(expect= upsies [:u :u :u :u]))
|
||||
|
||||
(print "OK")
|
||||
|
||||
@@ -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 {
|
||||
@@ -36,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,39 @@
|
||||
# usb modeswitch without udev, tcl, coreutils, bash dependencies
|
||||
{ stdenv
|
||||
, lib
|
||||
, fetchurl
|
||||
, pkg-config
|
||||
, libusb1
|
||||
}:
|
||||
let
|
||||
pname = "usb-modeswitch";
|
||||
version = "2.6.0";
|
||||
in stdenv.mkDerivation {
|
||||
inherit pname version;
|
||||
|
||||
src = fetchurl {
|
||||
url = "http://www.draisberghof.de/usb_modeswitch/${pname}-${version}.tar.bz2";
|
||||
sha256 = "18wbbxc5cfsmikba0msdvd5qlaga27b32nhrzicyd9mdddp265f2";
|
||||
};
|
||||
|
||||
preBuild = ''
|
||||
makeFlagsArray+=(LIBS="$($PKG_CONFIG --libs --cflags libusb-1.0)")
|
||||
'';
|
||||
makeFlags = [
|
||||
"PREFIX=$(out)"
|
||||
"usb_modeswitch"
|
||||
];
|
||||
|
||||
buildInputs = [ libusb1 ];
|
||||
nativeBuildInputs = [ pkg-config ];
|
||||
|
||||
installPhase = ''
|
||||
mkdir -p $out/bin
|
||||
cp usb_modeswitch $out/bin
|
||||
'';
|
||||
|
||||
meta = {
|
||||
license = lib.licenses.gpl2;
|
||||
maintainers = [];
|
||||
};
|
||||
}
|
||||
@@ -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 ];
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
Regular → Executable
+22
-7
@@ -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 }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
liminix
|
||||
, nixpkgs
|
||||
}:
|
||||
let img = (import liminix {
|
||||
device = import "${liminix}/devices/qemu/";
|
||||
liminix-config = ./configuration.nix;
|
||||
}).outputs.vmroot;
|
||||
pkgs = import <nixpkgs> { overlays = [(import ../../overlay.nix)]; };
|
||||
in pkgs.runCommand "check" {
|
||||
nativeBuildInputs = with pkgs; [
|
||||
expect
|
||||
socat
|
||||
] ;
|
||||
} ''
|
||||
mkdir vm
|
||||
${img}/run.sh --flag -S --background ./vm
|
||||
expect ${./script.expect} | tee $out
|
||||
''
|
||||
@@ -16,7 +16,7 @@ in rec {
|
||||
in svc.network.dhcp.client.build { interface = iface; };
|
||||
|
||||
services.defaultroute4 = svc.network.route.build {
|
||||
via = "$(output ${services.dhcpv4} address)";
|
||||
via = "$(output ${services.dhcpv4} ip)";
|
||||
target = "default";
|
||||
dependencies = [ services.dhcpv4 ];
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user