Compare commits
94 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 71b583a756 | |||
| 98e7536e59 | |||
| e72d78ab64 | |||
| 17035ca3b7 | |||
| dece70b336 | |||
| 50ea144dec | |||
| fc84435985 | |||
| 06b725cb77 | |||
| c74543c4ff | |||
| 54526c1e11 | |||
| f81aa54444 | |||
| 56261f77b0 | |||
| 8600dfc8cf | |||
| bb280c6d97 | |||
| b7e805c97f | |||
| 9223fa7ec4 | |||
| 0f31afee2b | |||
| 98c63e7498 | |||
| c6faf88dd1 | |||
| 35c7f1643f | |||
| 70fb9f86d3 | |||
| b36272f99e | |||
| 0abe4f96a7 | |||
| f7b30939b5 | |||
| 4fd1b5f08b | |||
| 106a429b3d | |||
| b3e505abf4 | |||
| 92e107d77c | |||
| fa040a194c | |||
| 3bdb7754d3 | |||
| 8f97c5bf3c | |||
| 7904c6bfe9 | |||
| 74b8c98aaf | |||
| 0a737c62cd | |||
| 343d3b6508 | |||
| 96e19767e9 | |||
| 6f2389dd75 | |||
| d49cbbb8ed | |||
| 7683ed69de | |||
| 3ff55d3aad | |||
| 22275f311c | |||
| 870da62a1e | |||
| 0312f7a999 | |||
| 9dd3cf23b4 | |||
| 4e9227dff3 | |||
| eaa45906ff | |||
| 7fc5d2934d | |||
| 12e25722fa | |||
| 09fe21260e | |||
| 4bd3ccc8fd | |||
| 3e163d4253 | |||
| 9487cb2567 | |||
| 907a9de773 | |||
| b25103be2e | |||
| 353a199ab2 | |||
| 91e957ced7 | |||
| 899f096346 | |||
| 0cf4733327 | |||
| 0a6af46364 | |||
| a24c2a23a0 | |||
| 9e52faa0b6 | |||
| 3bdc986dd7 | |||
| 83092b7b73 | |||
| 6805e0090d | |||
| 3b9c5635b8 | |||
| d3dc9752f8 | |||
| 7ad848cb77 | |||
| ef666c34cd | |||
| d7336679c4 | |||
| 66ccea1487 | |||
| 535eb70bb9 | |||
| 51ad051443 | |||
| 92970e8ed0 | |||
| e04ec2e959 | |||
| d60aab728d | |||
| 3609d8d5ee | |||
| 44c1fb7632 | |||
| e577caa15f | |||
| 7faf620c0b | |||
| b094220466 | |||
| f1dfb1f976 | |||
| 333327be75 | |||
| efa1919e04 | |||
| de77635490 | |||
| aecbe08f08 | |||
| ff2d3e1a63 | |||
| 8688d47c65 | |||
| e86daf9bbc | |||
| 23ccfec5fb | |||
| 00c8ea66ea | |||
| 31f0213b6f | |||
| 1580857fde | |||
| 04b59536d8 | |||
| 540a1dfd76 |
@@ -5,3 +5,4 @@ result-*
|
||||
*.qcow2
|
||||
_build
|
||||
*-secrets.nix
|
||||
examples/static-leases.nix
|
||||
|
||||
+397
-1
@@ -2039,4 +2039,400 @@ to finish service/modules milestone
|
||||
- anything else in rotuer.nix that we should servicify
|
||||
- services for liminix.networking
|
||||
- a nice way to specify service dependencies
|
||||
- do another video
|
||||
[done] - do another video
|
||||
|
||||
Mon Aug 21 20:02:55 BST 2023
|
||||
|
||||
a nice way to do dependencies would be somethng like
|
||||
|
||||
services.thething =
|
||||
let s = svc.thing { .... };
|
||||
in addDependencies s (with config.services; [otherthing yetanother]);
|
||||
|
||||
except that addDependencies is a really klunky name. dependsOn is very
|
||||
slightly better? or maybe it could be a function of the derivation?
|
||||
|
||||
services.thething =
|
||||
svc.thing { .... }.depends (with config.services; [otherthing yetanother]);
|
||||
|
||||
---
|
||||
|
||||
what does it mean to be dependent on an interface? that's it up? running?
|
||||
has an address? has a collection of addresses?
|
||||
|
||||
|
||||
services.defaultroute4 = route {
|
||||
name = "defaultroute4";
|
||||
via = "$(output ${services.wan} address)";
|
||||
target = "default";
|
||||
dependencies = [ services.wan ];
|
||||
};
|
||||
|
||||
- this route requires the interface to have an address (if wan is an
|
||||
interface, anyway ...)
|
||||
|
||||
- but otoh a dhcp client doesn't want to wait for an address, because
|
||||
it is assigning the address.
|
||||
|
||||
should an address provider have "interface name" as an output?
|
||||
is there a set of outputs that every address provider should have -
|
||||
whether static, dhcp, pppoe?
|
||||
|
||||
maybe we're in decision paralysis and should just move forward with
|
||||
what we know
|
||||
|
||||
Wed Aug 23 18:56:08 BST 2023
|
||||
|
||||
We may want to change the hardware device files to specify network
|
||||
interface names not services. Otherwise hardware devices (boards)
|
||||
depend on module-based-services, which is a bit weird.
|
||||
|
||||
Thu Aug 24 18:54:03 BST 2023
|
||||
|
||||
- we want network and bridge to be separate modules, because bridge
|
||||
introduces extra kernel config
|
||||
|
||||
- bridge/service wants to create a network device ("ip link"),
|
||||
using quite similar code as network/link.nix
|
||||
|
||||
- but bridge/service is a derivation: it has sight of pkgs but not
|
||||
config
|
||||
|
||||
offtopic: useful s6-rc commands at https://www.skarnet.org/software/s6-rc/faq.html
|
||||
|
||||
Fri Aug 25 23:37:57 BST 2023
|
||||
|
||||
where we left off: bridge is a bundle, and bundles can't have outputs,
|
||||
so how do we set the ifname of the bridge?
|
||||
|
||||
- ifname of the primary is set
|
||||
- actually, most things that depend on the bridge really just depend
|
||||
on the primary anyway (it's OK if 1 <= n < #members are down)
|
||||
- but *something* should depeond on all the members
|
||||
|
||||
turns out maybe we needed two services after all?
|
||||
|
||||
Sun Aug 27 23:50:18 BST 2023
|
||||
|
||||
I've done enough to make rotuer build, but in the process
|
||||
trashed vanilla-configuration as I entirely forgot we don't have
|
||||
a dhcpv4 client service. Need to fix that ...
|
||||
|
||||
- anything else in rotuer.nix that we should servicify
|
||||
- anything in vanilla-configuration ditto
|
||||
- and arhcive (rsync, watchdog)
|
||||
- services for liminix.networking
|
||||
- tidy up the dependency handling in serviceDefn build
|
||||
(interface is fine, implementation is a bit brutal)
|
||||
- write a blog entry
|
||||
|
||||
Mon Aug 28 16:58:49 BST 2023
|
||||
|
||||
- [done] ntp is not setting the time
|
||||
- nftables syntax error
|
||||
|
||||
Thu Aug 31 23:53:54 BST 2023
|
||||
|
||||
- anything else in rotuer.nix that we should servicify
|
||||
[done] - packet forwarding
|
||||
- dhcp6 client
|
||||
- what to do with acquire-{wan,lan} scripts?
|
||||
- resolvconf
|
||||
- [done] anything in vanilla-configuration ditto
|
||||
- packet forwarding
|
||||
- and arhcive
|
||||
- [not doing] rsync
|
||||
- [done] watchdog
|
||||
- [done] mount
|
||||
- nftables syntax error
|
||||
- tidy up the dependency handling in serviceDefn build
|
||||
(interface is fine, implementation is a bit brutal)
|
||||
- [done] services for liminix.networking
|
||||
- [done] write a blog entry
|
||||
- [done] ntp is not setting the time
|
||||
- [done] static dhcp(6) lease support reqd for dogfooding
|
||||
|
||||
Sat Sep 2 21:35:41 BST 2023
|
||||
|
||||
Considerations for "mount" service: each filesystem needs to depend on
|
||||
any mount points for its parent directories, and maybe also on other
|
||||
services (e.g. filesystem modules, network devices, routes)
|
||||
|
||||
mountpoints = {
|
||||
mnt = {
|
||||
media = svc.mountpoint.build {
|
||||
fstype = "msdos";
|
||||
device = "/dev/sda1";
|
||||
options = [ ...];
|
||||
};
|
||||
archive = svc.mountpoint.build {
|
||||
fstype = "ext4";
|
||||
device = "/dev/sda2";
|
||||
options = [ ...];
|
||||
mountpoints = {
|
||||
remote = svc.mountpoint.build {
|
||||
fstype = "nfs";
|
||||
device = "doc.ic.ac.uk:/public";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
services.somethingelse = svc.ftpd.build {
|
||||
# ...
|
||||
dependencies = [ mountpoints.mnt.archive ];
|
||||
}
|
||||
|
||||
what don't we like about this? we have to walk the nested attrset in a
|
||||
weird way, because the services may contain other mountpoints. Maybe
|
||||
just keep it simple and do
|
||||
|
||||
|
||||
services.mountpoints = bundle {
|
||||
name = "mountpoints";
|
||||
contents = [
|
||||
svc.mountpoint.build {
|
||||
device = "/dev/sda2"; fstype = "ext4"; directory = "/mnt/isos";
|
||||
};
|
||||
svc.mountpoint.build {
|
||||
device = "/dev/sdb1"; fstype = "msdos"; directory = "/mnt/backup";
|
||||
dependencies = [ load-vfat-module ];
|
||||
};
|
||||
];
|
||||
}
|
||||
|
||||
Sun Sep 3 17:34:36 BST 2023
|
||||
|
||||
how to dogfood
|
||||
|
||||
DHCP6 server: static lease support
|
||||
DHCP client and acquire-{lan-prefix,wan-address}
|
||||
|
||||
The emergency boot thingy in glinet u-boot won't help because it
|
||||
expects to flash from its tftp request instead of booting it. So we
|
||||
could use kexec instead except that the openwrt install doesn't have
|
||||
it. So we could swap the hardware devices, the only downside of that
|
||||
being that then I don't have a test system any more. Or we could YOLO it.
|
||||
|
||||
Sun Sep 3 22:11:02 BST 2023
|
||||
|
||||
I think we should rejigger the documentation ...
|
||||
|
||||
- "getting started": worked example, building and installing Liminix
|
||||
with a very simple config (wifi AP with ssh daemon)
|
||||
|
||||
- using modules
|
||||
- link to module reference
|
||||
|
||||
- creating custom services
|
||||
- longrun or oneshot
|
||||
- dependencies
|
||||
- outputs
|
||||
|
||||
- creating your own modules
|
||||
|
||||
- hacking on Liminix itself
|
||||
|
||||
- contributing
|
||||
|
||||
- external links and resources
|
||||
|
||||
- module reference
|
||||
|
||||
- hardware device reference
|
||||
|
||||
---
|
||||
|
||||
I think we might rename wlan_24 to wlan and wlan_5 to wlan1.
|
||||
This is on the assumption that almost no device is 5GHz only, so
|
||||
would make it easier to write a basic wlan example that works
|
||||
both on 2.4GHz boards and dual radio boards
|
||||
|
||||
Mon Sep 4 23:15:26 BST 2023
|
||||
|
||||
If dhcpcd parsed the update-script output into separate files, half
|
||||
the complexity of acquire-lan-prefix would go away. The other half is
|
||||
because it subscribes to changes in the outputs instead of just
|
||||
running once. Perhaps there's a better way to do that?
|
||||
|
||||
Could separate prefixes and addresses something like this...
|
||||
|
||||
outputs/prefix/2001\:8b0\:de3a\:40dc\:\:/prefix
|
||||
outputs/prefix/2001\:8b0\:de3a\:40dc\:\:/length
|
||||
outputs/prefix/2001\:8b0\:de3a\:40dc\:\:/preferred
|
||||
outputs/prefix/2001\:8b0\:de3a\:40dc\:\:/valid
|
||||
outputs/prefix/2001\:8b0\:de3a\:80\:\:/prefix
|
||||
outputs/prefix/2001\:8b0\:de3a\:80\:\:/length
|
||||
outputs/prefix/2001\:8b0\:de3a\:80\:\:/preferred
|
||||
outputs/prefix/2001\:8b0\:de3a\:80\:\:/valid
|
||||
|
||||
the directory name is arbitrary as long as it's unique. Might even be better to
|
||||
remove the colons
|
||||
|
||||
outputs/prefix/200108b0de3a40dc/valid
|
||||
|
||||
or we could adopt the MS convention and replace with hyphens
|
||||
|
||||
outputs/prefix/2001-8b0-de3a-40dc--/prefix
|
||||
|
||||
Also: we should write some kind of test for this...
|
||||
|
||||
|
||||
Tue Sep 5 21:36:39 BST 2023
|
||||
|
||||
How do we set the cpu governor?
|
||||
|
||||
|
||||
Fri Sep 8 21:26:36 BST 2023
|
||||
|
||||
We want a fennel thing that reads a filesystem tree into a nested
|
||||
table. And a thing to diff two tables
|
||||
|
||||
Sat Sep 9 22:40:50 BST 2023
|
||||
|
||||
Subscribers to odhcp6c outputs need to be able to tell which addresses
|
||||
are new and which have been removed since the last run, which now we
|
||||
have ohdcp-script producing parsed data means they need to compare
|
||||
tables by value. Which is a faff.
|
||||
|
||||
What if the directory name were a hash of all the relevant fields
|
||||
such that clients could just say "new directory, must be new address"
|
||||
|
||||
We can have literal prefix, then need to encode
|
||||
length,preferred,valid,extra space-efficiently. I cannot currently see
|
||||
any way to use whatever hashing Lua uses for its table lookups,
|
||||
which is a bit disapppointing, so we might have to make our own
|
||||
|
||||
|
||||
https://gist.github.com/scheler/26a942d34fb5576a68c111b05ac3fabe
|
||||
this is DJBHash, though doesn't appear to deal with integer overflow
|
||||
|
||||
|
||||
function hash(str)
|
||||
h = 5381;
|
||||
|
||||
for c in str:gmatch"." do
|
||||
h = ((h << 5) + h) + string.byte(c)
|
||||
end
|
||||
return h
|
||||
end
|
||||
|
||||
|
||||
Mon Sep 11 20:31:25 BST 2023
|
||||
|
||||
acquire-lan/wan-foo have no tests, and the test setup is a bit of a
|
||||
faff as they are both waiting on the filesystem
|
||||
|
||||
also, testing lua scripts is a faff without splitting them into
|
||||
script/module
|
||||
|
||||
am wondering if we could do some kind of convention that we only write
|
||||
modules not scripts but something in the fennel->lua can call the
|
||||
module's `run` method.
|
||||
|
||||
Tuesday
|
||||
|
||||
Here is a working shebang for write-fennel:
|
||||
|
||||
#!/nix/store/5iwv3h2jjbk2vib2bpwx3g9knpb02x3y-lua-5.3.6/bin/lua -e dofile(arg[0]).run()
|
||||
|
||||
Tue Sep 12 20:47:52 BST 2023
|
||||
|
||||
We don't handle unbound or stopped states in odhcp consumers. I think
|
||||
probably we should do this in odhcp-script by deleting the outputs,
|
||||
rather than making each consumer do it.
|
||||
|
||||
... turns out that odhcp6c itself unsets ADDRESSES and PREFIXES before
|
||||
calling the script with "unbound", so maybe we don't need to do
|
||||
anything special.
|
||||
|
||||
Wed Sep 13 17:55:33 BST 2023
|
||||
|
||||
|
||||
|
||||
@400000000000001f2723b3cb eth1.link.pppoe Script /nix/store/nyks8zl86dcp44k5sjcc76digrnfgm17-ip-up finished (pid 403), status = 0x0
|
||||
@400000000000001f27b2db3b eth1.link.pppoe Script /nix/store/ds0lc4qd1zfiyxsva87rpplyr21awjh1-ip6-up finished (pid 404), status = 0x1
|
||||
|
||||
@400000000000001f30a7c5c5 /nix/store/v9ijgyywizqbbd9y73r2wifkxc0d1jjm-route-default-1a22c69d0e1f-up: line 4: input: not found
|
||||
@400000000000001f31abf9b5 ip: command line is not complete, try "help"
|
||||
@400000000000001f31ca1395 s6-rc: warning: unable to start service route-default-1a22c69d0e1f: command exited 1
|
||||
@400000000000001f31f236b4 s6-rc: info: service route-default-d2586cf00da0 successfully started
|
||||
@
|
||||
|
||||
|
||||
Wed Sep 13 18:05:38 BST 2023
|
||||
|
||||
TODO
|
||||
|
||||
- service for dhcp6 client
|
||||
- move acquire-{wan,lan} scripts out of examples/
|
||||
- service for resolvconf
|
||||
- nftables syntax error
|
||||
- tidy up the dependency handling in serviceDefn build
|
||||
(interface is fine, implementation is a bit brutal)
|
||||
- docs
|
||||
|
||||
considerations:
|
||||
|
||||
1) in some ways, we should be able to specify acquire-{wan,lan} as if
|
||||
they were just additional addresses on the respective
|
||||
interfaces. However, they're longruns so the implementation of
|
||||
"address" doesn't really fit.
|
||||
|
||||
2) should they be bundled into a dhcp client service? I think the
|
||||
answer is "no" because which of the dhcp config we want to
|
||||
honour locally (and how) is policy not mechainmsm
|
||||
|
||||
svc.dhcp6c.client.build { interface = wan; };
|
||||
svc.dhcp6c.address.build {
|
||||
inherit client;
|
||||
interface = lan;
|
||||
};
|
||||
svc.dhcp6c.address.build {
|
||||
inherit client;
|
||||
interface = wan;
|
||||
};
|
||||
svc.dhcp6c.prefix.build {
|
||||
inherit client;
|
||||
interface = lan;
|
||||
index = 1; # default to first interface
|
||||
};
|
||||
svc.dhcp6c.prefix.build {
|
||||
inherit client;
|
||||
interface = vpn;
|
||||
index = 2;
|
||||
};
|
||||
|
||||
|
||||
|
||||
Fri Sep 15 12:04:25 BST 2023
|
||||
|
||||
Qemu worked example provides dhcp and ssh service
|
||||
|
||||
Hardware worked example needs to be plugged into same lan as build
|
||||
machine if we are going to tftp the image onto it - so it might be
|
||||
awkward if we run dhcp on it
|
||||
|
||||
The device I have lying around is the A
|
||||
|
||||
How do we do the actual flash step? Assuming the device is running
|
||||
stock firmware, from a laptop we can wifi to it and use the web ui to
|
||||
upgrade
|
||||
|
||||
we can't build the hellonet config because it requires tftp
|
||||
|
||||
plug in mt300a
|
||||
put stock firmware on it
|
||||
|
||||
Sun Sep 17 00:08:03 BST 2023
|
||||
|
||||
I don't think the user manual needs a full justification of why we
|
||||
have the module/service split. Maybe we should have "decision records"
|
||||
in the git tree instead
|
||||
|
||||
Sun Sep 17 16:44:31 BST 2023
|
||||
|
||||
Can we figure out which bits of the old doc are missing from the new
|
||||
one and just transplant those? Then we can merge it sooner
|
||||
instead of blocking on writig all the new stuff
|
||||
|
||||
@@ -75,16 +75,17 @@
|
||||
dd if=/dev/$part of=data iflag=skip_bytes,fullblock bs=${toString size} skip=${toString offset} count=1
|
||||
)
|
||||
'';
|
||||
down = "true";
|
||||
};
|
||||
inherit (pkgs.pseudofile) dir symlink;
|
||||
inherit (pkgs.liminix.networking) interface;
|
||||
in {
|
||||
imports = [ ../../modules/network];
|
||||
|
||||
programs.busybox.options = {
|
||||
FEATURE_DD_IBS_OBS = "y"; # ath10k_cal_data needs skip_bytes,fullblock
|
||||
};
|
||||
hardware = {
|
||||
defaultOutput = "tftpboot";
|
||||
defaultOutput = "flashimage";
|
||||
loadAddress = "0x80060000";
|
||||
entryPoint = "0x80060000";
|
||||
flash = {
|
||||
@@ -100,19 +101,20 @@
|
||||
];
|
||||
};
|
||||
|
||||
networkInterfaces = {
|
||||
lan = interface { device = "eth0"; };
|
||||
wan = interface { device = "eth1"; };
|
||||
|
||||
wlan_24 = interface {
|
||||
device = "wlan0";
|
||||
dependencies = [ mac80211 ];
|
||||
networkInterfaces =
|
||||
let inherit (config.system.service.network) link;
|
||||
in {
|
||||
lan = link.build { ifname = "eth0"; };
|
||||
wan = link.build { ifname = "eth1"; };
|
||||
wlan_24 = link.build {
|
||||
ifname = "wlan0";
|
||||
dependencies = [ mac80211 ];
|
||||
};
|
||||
wlan_5 = link.build {
|
||||
ifname = "wlan1";
|
||||
dependencies = [ mac80211 ath10k_cal_data ];
|
||||
};
|
||||
};
|
||||
wlan_5 = interface {
|
||||
device = "wlan1";
|
||||
dependencies = [ mac80211 ath10k_cal_data ];
|
||||
};
|
||||
};
|
||||
};
|
||||
filesystem = dir {
|
||||
lib = dir {
|
||||
@@ -201,8 +203,6 @@
|
||||
|
||||
PARTITION_ADVANCED = "y";
|
||||
PRINTK_TIME = "y";
|
||||
SQUASHFS = "y";
|
||||
SQUASHFS_XZ = "y";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
support into the kernel even if we're using TFTP root
|
||||
'';
|
||||
|
||||
module = { pkgs, config, ...}:
|
||||
module = { pkgs, config, lib, ...}:
|
||||
let
|
||||
inherit (pkgs.liminix.networking) interface;
|
||||
inherit (pkgs) openwrt;
|
||||
@@ -28,7 +28,7 @@
|
||||
};
|
||||
in {
|
||||
hardware = {
|
||||
defaultOutput = "tftpboot";
|
||||
defaultOutput = "flashimage";
|
||||
loadAddress = "0x80000000";
|
||||
entryPoint = "0x80000000";
|
||||
|
||||
@@ -55,28 +55,43 @@
|
||||
"${openwrt.src}/target/linux/ramips/dts"
|
||||
];
|
||||
};
|
||||
networkInterfaces = rec {
|
||||
# lan and wan ports are both behind a switch on eth0
|
||||
eth = interface { device = "eth0"; };
|
||||
lan = interface {
|
||||
type = "vlan";
|
||||
device = "eth0.1";
|
||||
link = "eth0";
|
||||
id = "1";
|
||||
dependencies = [eth];
|
||||
networkInterfaces =
|
||||
let
|
||||
inherit (config.system.service.network) link;
|
||||
inherit (config.system.service) vlan;
|
||||
inherit (pkgs.liminix.services) oneshot;
|
||||
swconfig = oneshot {
|
||||
name = "swconfig";
|
||||
up = ''
|
||||
PATH=${pkgs.swconfig}/bin:$PATH
|
||||
swconfig dev switch0 set reset
|
||||
swconfig dev switch0 set enable_vlan 1
|
||||
swconfig dev switch0 vlan 1 set ports '1 2 3 4 6t'
|
||||
swconfig dev switch0 vlan 2 set ports '0 6t'
|
||||
swconfig dev switch0 set apply
|
||||
'';
|
||||
down = "${pkgs.swconfig}/bin/swconfig dev switch0 set reset";
|
||||
};
|
||||
in rec {
|
||||
eth = link.build { ifname = "eth0"; };
|
||||
# lan and wan ports are both behind a switch on eth0
|
||||
lan = vlan.build {
|
||||
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";
|
||||
dependencies = [ mac80211 ];
|
||||
};
|
||||
};
|
||||
wan = interface {
|
||||
type = "vlan";
|
||||
device = "eth0.2";
|
||||
id = "2";
|
||||
link = "eth0";
|
||||
dependencies = [eth];
|
||||
};
|
||||
wlan = interface {
|
||||
device = "wlan0";
|
||||
dependencies = [ mac80211 ];
|
||||
};
|
||||
};
|
||||
};
|
||||
boot.tftp = {
|
||||
loadAddress = "0x00A00000";
|
||||
@@ -115,6 +130,7 @@
|
||||
NET_RALINK_MDIO = "y";
|
||||
NET_RALINK_MDIO_MT7620 = "y";
|
||||
NET_RALINK_MT7620 = "y";
|
||||
SWPHY = "y";
|
||||
|
||||
SPI = "y";
|
||||
MTD_SPI_NOR = "y";
|
||||
@@ -123,18 +139,6 @@
|
||||
SPI_MASTER= "y";
|
||||
SPI_MEM= "y";
|
||||
|
||||
# both the ethernet ports on this device (lan and wan)
|
||||
# are behind a switch, so we need VLANs to do anything
|
||||
# useful with them
|
||||
|
||||
VLAN_8021Q = "y";
|
||||
SWCONFIG = "y";
|
||||
SWPHY = "y";
|
||||
|
||||
BRIDGE = "y";
|
||||
BRIDGE_VLAN_FILTERING = "y";
|
||||
BRIDGE_IGMP_SNOOPING = "y";
|
||||
|
||||
MTD = "y";
|
||||
MTD_CMDLINE_PARTS = "y";
|
||||
MTD_BLOCK = "y"; # fix undefined ref to register_mtd_blktrans_devs
|
||||
@@ -149,8 +153,8 @@
|
||||
|
||||
PARTITION_ADVANCED = "y";
|
||||
PRINTK_TIME = "y";
|
||||
SQUASHFS = "y";
|
||||
SQUASHFS_XZ = "y";
|
||||
} // lib.optionalAttrs (config.system.service ? vlan) {
|
||||
SWCONFIG = "y";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
};
|
||||
};
|
||||
|
||||
module = { pkgs, config, ...}:
|
||||
module = { pkgs, config, lib, ...}:
|
||||
let
|
||||
inherit (pkgs.liminix.networking) interface;
|
||||
inherit (pkgs.liminix.services) oneshot;
|
||||
@@ -35,7 +35,7 @@
|
||||
};
|
||||
};
|
||||
hardware = {
|
||||
defaultOutput = "tftpboot";
|
||||
defaultOutput = "flashimage";
|
||||
loadAddress = "0x80000000";
|
||||
entryPoint = "0x80000000";
|
||||
|
||||
@@ -52,44 +52,40 @@
|
||||
"${openwrt.src}/target/linux/ramips/dts"
|
||||
];
|
||||
};
|
||||
networkInterfaces = rec {
|
||||
# lan and wan ports are both behind a switch on eth0
|
||||
eth =
|
||||
let swconfig = oneshot {
|
||||
name = "swconfig";
|
||||
up = ''
|
||||
PATH=${pkgs.swconfig}/bin:$PATH
|
||||
swconfig dev switch0 set reset
|
||||
swconfig dev switch0 set enable_vlan 1
|
||||
swconfig dev switch0 vlan 1 set ports '1 2 3 4 6t'
|
||||
swconfig dev switch0 vlan 2 set ports '0 6t'
|
||||
swconfig dev switch0 set apply
|
||||
'';
|
||||
down = "swconfig dev switch0 set reset";
|
||||
};
|
||||
in interface {
|
||||
device = "eth0";
|
||||
dependencies = [swconfig];
|
||||
networkInterfaces =
|
||||
let
|
||||
inherit (config.system.service.network) link;
|
||||
inherit (config.system.service) vlan;
|
||||
swconfig = oneshot {
|
||||
name = "swconfig";
|
||||
up = ''
|
||||
PATH=${pkgs.swconfig}/bin:$PATH
|
||||
swconfig dev switch0 set reset
|
||||
swconfig dev switch0 set enable_vlan 1
|
||||
swconfig dev switch0 vlan 1 set ports '1 2 3 4 6t'
|
||||
swconfig dev switch0 vlan 2 set ports '0 6t'
|
||||
swconfig dev switch0 set apply
|
||||
'';
|
||||
down = "swconfig dev switch0 set reset";
|
||||
};
|
||||
in rec {
|
||||
eth = link.build { ifname = "eth0"; dependencies = [swconfig]; };
|
||||
# lan and wan ports are both behind a switch on eth0
|
||||
lan = vlan.build {
|
||||
ifname = "eth0.1";
|
||||
primary = eth;
|
||||
vid = "1";
|
||||
};
|
||||
wan = vlan.build {
|
||||
ifname = "eth0.2";
|
||||
primary = eth;
|
||||
vid = "2";
|
||||
};
|
||||
wlan = link.build {
|
||||
ifname = "wlan0";
|
||||
dependencies = [ mac80211 ];
|
||||
};
|
||||
lan = interface {
|
||||
type = "vlan";
|
||||
device = "eth0.1";
|
||||
link = "eth0";
|
||||
id = "1";
|
||||
dependencies = [eth];
|
||||
};
|
||||
wan = interface {
|
||||
type = "vlan";
|
||||
device = "eth0.2";
|
||||
id = "2";
|
||||
link = "eth0";
|
||||
dependencies = [eth];
|
||||
};
|
||||
wlan = interface {
|
||||
device = "wlan0";
|
||||
dependencies = [ mac80211 ];
|
||||
};
|
||||
};
|
||||
};
|
||||
boot.tftp = {
|
||||
# 20MB seems to give enough room to uncompress the kernel
|
||||
@@ -148,23 +144,8 @@
|
||||
NET_VENDOR_RALINK = "y";
|
||||
NET_RALINK_RT3050 = "y";
|
||||
NET_RALINK_SOC="y";
|
||||
|
||||
# both the ethernet ports on this device (lan and wan)
|
||||
# are behind a switch, so we need VLANs to do anything
|
||||
# useful with them
|
||||
|
||||
VLAN_8021Q = "y";
|
||||
SWCONFIG = "y";
|
||||
SWPHY = "y";
|
||||
|
||||
BRIDGE = "y";
|
||||
BRIDGE_VLAN_FILTERING = "y";
|
||||
BRIDGE_IGMP_SNOOPING = "y";
|
||||
|
||||
WATCHDOG = "y";
|
||||
RALINK_WDT = "y"; # watchdog
|
||||
MT7621_WDT = "y"; # or it might be this one
|
||||
|
||||
GPIOLIB="y";
|
||||
GPIO_MT7621 = "y";
|
||||
|
||||
@@ -175,8 +156,11 @@
|
||||
|
||||
PARTITION_ADVANCED = "y";
|
||||
PRINTK_TIME = "y";
|
||||
SQUASHFS = "y";
|
||||
SQUASHFS_XZ = "y";
|
||||
} // lib.optionalAttrs (config.system.service ? vlan) {
|
||||
SWCONFIG = "y";
|
||||
} // lib.optionalAttrs (config.system.service ? watchdog) {
|
||||
RALINK_WDT = "y"; # watchdog
|
||||
MT7621_WDT = "y"; # or it might be this one
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -30,9 +30,6 @@
|
||||
MTD_BLKDEVS = "y";
|
||||
MTD_BLOCK = "y";
|
||||
|
||||
SQUASHFS = "y";
|
||||
SQUASHFS_XZ = "y";
|
||||
|
||||
VIRTIO_MENU = "y";
|
||||
PCI = "y";
|
||||
VIRTIO_PCI = "y";
|
||||
@@ -51,19 +48,20 @@
|
||||
drivers = ["mac80211_hwsim"];
|
||||
klibBuild = config.system.outputs.kernel.modulesupport;
|
||||
};
|
||||
inherit (pkgs.liminix.networking) interface;
|
||||
in {
|
||||
defaultOutput = "vmroot";
|
||||
flash.eraseBlockSize = "65536"; # c.f. pkgs/mips-vm/mips-vm.sh
|
||||
networkInterfaces = {
|
||||
lan = interface { device = "eth0"; };
|
||||
wan = interface { device = "eth1"; };
|
||||
networkInterfaces =
|
||||
let inherit (config.system.service.network) link;
|
||||
in {
|
||||
wan = link.build { ifname = "eth0"; };
|
||||
lan = link.build { ifname = "eth1"; };
|
||||
|
||||
wlan_24 = interface {
|
||||
device = "wlan0";
|
||||
dependencies = [ mac80211 ];
|
||||
wlan_24 = link.build {
|
||||
ifname = "wlan0";
|
||||
dependencies = [ mac80211 ];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
+188
@@ -0,0 +1,188 @@
|
||||
System Administration
|
||||
#####################
|
||||
|
||||
Services on a running system
|
||||
****************************
|
||||
|
||||
* add an s6-rc cheatsheet here
|
||||
|
||||
|
||||
Flashing and updating
|
||||
*********************
|
||||
|
||||
|
||||
|
||||
Flashing from an existing Liminix system with :command:`flashcp`
|
||||
================================================================
|
||||
|
||||
The flash procedure from an existing Liminix-system has two steps.
|
||||
First we reboot the device (using "kexec") into an "ephemeral"
|
||||
RAM-based version of the new configuration, then when we're happy it
|
||||
works we can flash the image - and if it doesn't work we can reboot
|
||||
the device again and it will boot from the old image.
|
||||
|
||||
|
||||
Building the RAM-based image
|
||||
----------------------------
|
||||
|
||||
To create the ephemeral image, build ``outputs.kexecboot`` instead of
|
||||
``outputs.default``. This generates a directory containing the root
|
||||
filesystem image and kernel, along with an executable called `kexec`
|
||||
and a `boot.sh` script that runs it with appropriate arguments.
|
||||
|
||||
For example
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
nix-build --show-trace -I liminix-config=./examples/arhcive.nix \
|
||||
--arg device "import ./devices/gl-ar750"
|
||||
-A outputs.kexecboot && \
|
||||
(tar chf - result | ssh root@the-device tar -C /run -xvf -)
|
||||
|
||||
and then login to the device and run
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
cd /run/result
|
||||
sh ./boot.sh .
|
||||
|
||||
|
||||
This will load the new kernel and map the root filesystem into a RAM
|
||||
disk, then start executing the new kernel. *This is effectively a
|
||||
reboot - be sure to close all open files and finish anything else
|
||||
you were doing first.*
|
||||
|
||||
If the new system crashes or is rebooted, then the device will revert
|
||||
to the old configuration it finds in flash.
|
||||
|
||||
|
||||
Building the second (permanent) image
|
||||
-------------------------------------
|
||||
|
||||
While running in the kexecboot system, you can copy the permanent
|
||||
image to the device with :command:`ssh`
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
build-machine$ tar chf - result/firmware.bin | \
|
||||
ssh root@the-device tar -C /run -xvf -
|
||||
|
||||
Next you need to connect to the device and locate the "firmware"
|
||||
partition, which you can do with a combination of :command:`dmesg`
|
||||
output and the contents of :file:`/proc/mtd`
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
<5>[ 0.469841] Creating 4 MTD partitions on "spi0.0":
|
||||
<5>[ 0.474837] 0x000000000000-0x000000040000 : "u-boot"
|
||||
<5>[ 0.480796] 0x000000040000-0x000000050000 : "u-boot-env"
|
||||
<5>[ 0.487056] 0x000000050000-0x000000060000 : "art"
|
||||
<5>[ 0.492753] 0x000000060000-0x000001000000 : "firmware"
|
||||
|
||||
# cat /proc/mtd
|
||||
dev: size erasesize name
|
||||
mtd0: 00040000 00001000 "u-boot"
|
||||
mtd1: 00010000 00001000 "u-boot-env"
|
||||
mtd2: 00010000 00001000 "art"
|
||||
mtd3: 00fa0000 00001000 "firmware"
|
||||
mtd4: 002a0000 00001000 "kernel"
|
||||
mtd5: 00d00000 00001000 "rootfs"
|
||||
|
||||
Now run (in this example)
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
flashcp -v firmware.bin /dev/mtd3
|
||||
|
||||
|
||||
"I know my new image is good, can I skip the intermediate step?"
|
||||
----------------------------------------------------------------
|
||||
|
||||
In addition to giving you a chance to see if the new image works, this
|
||||
two-step process ensures that you're not copying the new image over
|
||||
the top of the active root filesystem. Sometimes it works, but you
|
||||
will at least need physical access to the device to power-cycle it
|
||||
because it will be effectively frozen afterwards.
|
||||
|
||||
|
||||
Flashing from the boot monitor
|
||||
==============================
|
||||
|
||||
If you are prepared to open the device and have a TTL serial adaptor
|
||||
of some kind to connect it to, you can probably flash it using U-Boot.
|
||||
This is quite hardware-specific, and sometimes involves soldering:
|
||||
please refer to the Developer Manual.
|
||||
|
||||
|
||||
Flashing from OpenWrt (not currently advised!)
|
||||
==============================================
|
||||
|
||||
.. CAUTION:: At your own risk! This will (at least in some
|
||||
circumstances) lead to bricking the device: we think this
|
||||
flash method is currently incompatible with use of a
|
||||
writeable (jffs2) filesystem.
|
||||
|
||||
If your device is running OpenWrt then it probably has the
|
||||
:command:`mtd` command installed. After transferring the image onto the
|
||||
device using e.g. :command:`ssh`, you can run it as follows:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
mtd -r write /tmp/firmware.bin firmware
|
||||
|
||||
For more information, please see the `OpenWrt manual <https://openwrt.org/docs/guide-user/installation/sysupgrade.cli>`_ which may also contain (hardware-dependent) instructions on how to flash an image using the vendor firmware - perhaps even from a web interface.
|
||||
|
||||
Updating an installed system (JFFS2)
|
||||
************************************
|
||||
|
||||
|
||||
Adding packages
|
||||
===============
|
||||
|
||||
If your device is running a JFFS2 root filesystem, you can build
|
||||
extra packages for it on your build system and copy them to the
|
||||
device: any package in Nixpkgs or in the Liminix overlay is available
|
||||
with the ``pkgs`` prefix:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
nix-build -I liminix-config=./my-configuration.nix \
|
||||
--arg device "import ./devices/mydevice" -A pkgs.tcpdump
|
||||
|
||||
nix-shell -p min-copy-closure root@the-device result/
|
||||
|
||||
Note that this only copies the package to the device: it doesn't update
|
||||
any profile to add it to ``$PATH``
|
||||
|
||||
|
||||
Rebuilding the system
|
||||
=====================
|
||||
|
||||
:command:`liminix-rebuild` is the Liminix analogue of :command:`nixos-rebuild`, although its operation is a bit different because it expects to run on a build machine and then copy to the host device. Run it with the same ``liminix-config`` and ``device`` parameters as you would run :command:`nix-build`, and it will build any new/changed packages and then copy them to the device using SSH. For example:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
liminix-rebuild root@the-device -I liminix-config=./examples/rotuer.nix --arg device "import ./devices/gl-ar750"
|
||||
|
||||
This will
|
||||
|
||||
* build anything that needs building
|
||||
* copy new or changed packages to the device
|
||||
* reboot the device
|
||||
|
||||
It doesn't delete old packages automatically: to do that run
|
||||
:command:`min-collect-garbage`, which will delete any packages not in
|
||||
the current system closure. Note that Liminix does not have the NixOS
|
||||
concept of environments or generations, and there is no way back from
|
||||
this except for building the previous configuration again.
|
||||
|
||||
|
||||
Caveats
|
||||
~~~~~~~
|
||||
|
||||
* it needs there to be enough free space on the device for all the new
|
||||
packages in addition to all the packages already on it - which may be
|
||||
a problem if a lot of things have changed (e.g. a new version of
|
||||
nixpkgs).
|
||||
|
||||
* it cannot upgrade the kernel, only userland
|
||||
@@ -0,0 +1,6 @@
|
||||
Architecture Decision Records
|
||||
#############################
|
||||
|
||||
In this directory you will find descriptions of Liminix architecture
|
||||
decisions.
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
Module system
|
||||
#############
|
||||
|
||||
**Status:** Adopted; implemented in July-September 2023
|
||||
|
||||
|
||||
Context
|
||||
*******
|
||||
|
||||
Liminix users need a way to assemble a full system configuration by
|
||||
combining smaller, more isolated and reusable components, otherwise
|
||||
systems will be unwieldy and copy-and-paste will be rife.
|
||||
|
||||
|
||||
Alternatives
|
||||
************
|
||||
|
||||
NixOS module system
|
||||
===================
|
||||
|
||||
The NixOS module system addresses many of these concerns. A module is
|
||||
a Nix function which accepts a ``configuration`` attrset and some
|
||||
other parameters, and returns a new fragment of ``configuration``
|
||||
which is merged into it. It includes a DSL describing the permitted
|
||||
types of values for each key in the configuration, which is used for
|
||||
checking that the supplied parameters are valid and also governs what
|
||||
to do if two modules both specify a value for the same key. (Usually
|
||||
they are "merged", using some type-appropriate concept of merging.)
|
||||
|
||||
Usually a NixOS module looks only (or mostly only) at a particular
|
||||
subtree of the overall configuration which is hardcoded in the module
|
||||
definition, but the configuration fragment it returns may touch any
|
||||
part of the schema. For example, the factorio module refers to
|
||||
``config.services.factorio``, and it returns values for keys in
|
||||
``systemd.services.factorio`` and ``networking.firewall``. There is no
|
||||
way to use this module to run **two** factorio services with different
|
||||
config (e.g. on different ports) - the only way to make that
|
||||
possible would be to extend the module definition so that it
|
||||
accepts a collection of game configurations and then create
|
||||
a systemd service for each.
|
||||
|
||||
|
||||
NixWRT module system
|
||||
====================
|
||||
|
||||
NixWRT, the (now defunct) predecessor of Liminix, used a homegrown
|
||||
module system modelled on the Nixpkgs overlay pattern. Each module is
|
||||
a function that accepts ``super`` and ``self`` parameters, and
|
||||
using <handwaves>that fixpoint magic thing</handwaves>
|
||||
is called in a chain with the configuration returned by the previous
|
||||
module and the final configuration.
|
||||
|
||||
NixWRT modules mostly don't refer to the configuration object to
|
||||
decide how to configure themselves, but accept their parameters
|
||||
directly as function parameters. For example, the configuration
|
||||
file for "arhcive" (a backup server) includes this text:
|
||||
|
||||
.. code-block:: nix
|
||||
|
||||
(sshd {
|
||||
hostkey = secrets.sshHostKey;
|
||||
authkeys = { root = lib.splitString "\n" secrets.myKeys; };
|
||||
})
|
||||
busybox
|
||||
(usbdisk {
|
||||
label = "backup-disk";
|
||||
mountpoint = "/srv";
|
||||
fstype = "ext4";
|
||||
options = "rw";
|
||||
})
|
||||
|
||||
This gives us flexibility that NixOS modules don't: for example, if we
|
||||
want to mount two USB disks, we can simply repeat that module twice
|
||||
with different parameters - and the module definition doesn't have to
|
||||
handle it specially.
|
||||
|
||||
However, the downside of this system is that we didn't implement any
|
||||
concept of "types" - there is no type information, so there is no
|
||||
systematic checking that parameters are valid, and if two modules set
|
||||
the same config key then the rules for merging are entirely ad hoc.
|
||||
|
||||
There is a further (arguable) downside, which is that the
|
||||
configuration is not just data - it's now part code. While it could be
|
||||
feasible (though I've never seen it done) to encode a NixOS
|
||||
configuration using Yaml or XML and then manipulate it as data, this
|
||||
is not even possible using the NixWRT system.
|
||||
|
||||
|
||||
Use services for everything
|
||||
===========================
|
||||
|
||||
The most common properties that a Liminix configuration needs to
|
||||
define are:
|
||||
|
||||
* which services (processes) to run
|
||||
* what packages to install
|
||||
* permitted users and groups
|
||||
* Linux kernel configuration options
|
||||
* Busybox applets
|
||||
* filesystem layout
|
||||
|
||||
Suppose we only had services?
|
||||
|
||||
A Liminix service is (also) a derivation, so it is able to
|
||||
create any files it likes inside its own store path, and
|
||||
transitively require other packages simply by referring to them.
|
||||
If it needs particular kernel options it could define them
|
||||
as kernel modules to be loaded on demand when the service
|
||||
starts (see the nftables module for an example). However:
|
||||
|
||||
* there is no way for a service to add busybox modules
|
||||
|
||||
* it cannot create files outside of its store path, so
|
||||
wouldn't be able to make e.g. :file:`/etc/something.conf`
|
||||
|
||||
* no way to create users/groups. We could steal the DynamicUsers idea
|
||||
from systemd and make them on demand, but this starts to get a bit
|
||||
more complicated.
|
||||
|
||||
These limitations force us to reject this option as a general
|
||||
solution - though we should strive *where possible* to implement
|
||||
functionality as services and to minimise the proportion of Liminix
|
||||
that manipulates the global configuration.
|
||||
|
||||
|
||||
Decision
|
||||
********
|
||||
|
||||
"Why not both?" None of these options is sufficient alone, so we are
|
||||
going to do a mixture.
|
||||
|
||||
We will use the NixOS module system, but instead of expecting modules
|
||||
to create systemd services as instances, they will expose "service
|
||||
templates": functions that accept an attrset and return an
|
||||
appropriately configured service that can be assigned by the caller
|
||||
to a key in ``config.services``.
|
||||
|
||||
We will typecheck the service template function parameters using the
|
||||
same type-checking code as NixOS uses for its modules.
|
||||
|
||||
An example may make this clearer: to add an NTP
|
||||
service you first add :file:`modules/ntp` to your ``imports`` list,
|
||||
then you create a service by calling
|
||||
:code:`config.system.service.ntp.build { .... }` with the appropriate
|
||||
service-dependent configuration parameters.
|
||||
|
||||
.. code-block:: nix
|
||||
|
||||
let svc = config.system.service;
|
||||
in {
|
||||
# ...
|
||||
imports = [
|
||||
./modules/ntp
|
||||
# ....
|
||||
];
|
||||
config.services.ntp = svc.ntp.build {
|
||||
pools = { "pool.ntp.org" = ["iburst"]; };
|
||||
makestep = { threshold = 1.0; limit = 3; };
|
||||
};
|
||||
|
||||
Merely including the module won't define the service on its own: it
|
||||
only creates the template in ``config.system.service.foo`` and you
|
||||
have to create the actual service using the template.
|
||||
|
||||
|
||||
|
||||
Consequences
|
||||
************
|
||||
|
||||
This decision has both good and bad consequences
|
||||
|
||||
Pro
|
||||
===
|
||||
|
||||
* We have a workable system for reusing configuration elements in
|
||||
Liminix.
|
||||
|
||||
* We have type checking for most imortant things, reducing the risk of
|
||||
deploying an invalid configuration.
|
||||
|
||||
* We have a simple mechanism for creating multiple services based on
|
||||
the same module, without buulding that logic into the module
|
||||
definition itself. For example, we could create two SSH daemons on
|
||||
different ports, or DHCP clients with different configurations on
|
||||
different network devices.
|
||||
|
||||
* We expect to be able to automate the generation of module
|
||||
documentation.
|
||||
|
||||
Con
|
||||
===
|
||||
|
||||
|
||||
* By departing somewhat from the NixOS conventions we increase the
|
||||
amount of code we have to write/maintain ourselves - and the
|
||||
learning burden on users who are already familiar with that system.
|
||||
|
||||
* Liminix configurations contain function calls and aren't just data,
|
||||
which means we can ony realistically interpret or introspect
|
||||
them with the Nix interpreter itself - we can't query them
|
||||
as data with other non-Nix tools.
|
||||
@@ -0,0 +1,156 @@
|
||||
Configuration
|
||||
#############
|
||||
|
||||
Liminix uses the Nix language to provide congruent configuration
|
||||
management. This means that to change anything about the way in
|
||||
which a Liminix system works, you make that change in
|
||||
your :file:`configuration.nix` (or one of the other files it references),
|
||||
and rerun :command:`nix-build` or :command:`liminix-rebuild` to action
|
||||
the change. It is not possible (at least, without shenanigans) to make
|
||||
changes by logging into the device and running imperative commands
|
||||
whose effects may later be overridden: :file:`configuration.nix`
|
||||
always describes the entire system and can be used to recreate that
|
||||
system at any time. You can usefully keep it under version control.
|
||||
|
||||
If you are familiar with NixOS, you will notice some similarities
|
||||
between NixOS and Liminix configuration, and also some
|
||||
differences. Sometimes the differences are due to the
|
||||
resource-constrained devices we deploy onto, sometimes due to
|
||||
differences in the uses these devices are put to.
|
||||
|
||||
|
||||
Configuration taxonomy
|
||||
**********************
|
||||
|
||||
There are many things you can specify in a configuration, but these
|
||||
are the ones you most commonly need to change:
|
||||
|
||||
* which services (processes) to run
|
||||
* what packages to install
|
||||
* permitted users and groups
|
||||
* Linux kernel configuration options
|
||||
* Busybox applets
|
||||
* filesystem layout
|
||||
|
||||
|
||||
Modules
|
||||
*******
|
||||
|
||||
**Modules** are a means of abstraction which allow "bundling"
|
||||
of configuration options related to a common purpose or theme. For
|
||||
example, the ``dnsmasq`` module defines a template for a dnsmasq
|
||||
service, ensures that the dnsmasq package is installed, and provides a
|
||||
dnsmasq user and group for the service to run as. The ``ppp`` module
|
||||
defines a service template and also enables various PPP-related kernel
|
||||
configuration.
|
||||
|
||||
Not all modules are included in the configuration by default, because
|
||||
that would mean that the kernel (and the Busybox binary providing
|
||||
common CLI tools) was compiled with many unnecessary bells and whistles
|
||||
and therefore be bigger than needed. (This is not purely an academic concern
|
||||
if your device has little flash storage). Therefore, specifying a
|
||||
service is usually a two-step process. For example, to add an NTP
|
||||
service you first add :file:`modules/ntp` to your ``imports`` list,
|
||||
then you create a service by calling
|
||||
:code:`config.system.service.ntp.build { .... }` with the appropriate
|
||||
service-dependent configuration parameters.
|
||||
|
||||
.. code-block:: nix
|
||||
|
||||
let svc = config.system.service;
|
||||
in {
|
||||
# ...
|
||||
imports = [
|
||||
./modules/ntp
|
||||
# ....
|
||||
];
|
||||
config.services.ntp = svc.ntp.build {
|
||||
pools = { "pool.ntp.org" = ["iburst"]; };
|
||||
makestep = { threshold = 1.0; limit = 3; };
|
||||
};
|
||||
|
||||
Merely including the module won't define the service on its own: it
|
||||
only creates the template in ``config.system.service.foo`` and you
|
||||
have to create an actual service using the template. This is an
|
||||
intentional choice to allow the creation of multiple
|
||||
differently-configured services based on the same template - perhaps
|
||||
e.g. when you have multiple networks (VPNs etc) in different trust
|
||||
domains, or you want to run two SSH daemons on different ports.
|
||||
(For the background to this, please refer to the :doc:`architecture decision record <adr/module-system>`)
|
||||
|
||||
.. tip:: Liminix modules should be quite familiar (but also different)
|
||||
if you already know how to use NixOS modules. We use the
|
||||
NixOS module infrastructure code, meaning that you should
|
||||
recognise the syntax, the type system, the rules for
|
||||
combining configuration values from different sources. We
|
||||
don't use the NixOS modules themselves, because the
|
||||
underlying system is not similar enough for them to work.
|
||||
|
||||
|
||||
Services
|
||||
********
|
||||
|
||||
We use the `s6-rc service manager <https://www.skarnet.org/software/s6-rc/overview.html>`_ to start/stop/restart services and handle
|
||||
service dependencies. Any attribute in `config.services` will become
|
||||
part of the default set of services that s6-rc will try to bring up on
|
||||
boot.
|
||||
|
||||
For the most part, for common use cases, hopefully the services you
|
||||
need will be defined by modules and you will only have to pass the
|
||||
right parameters to ``build``.
|
||||
|
||||
Should you need to create a custom service of your own devising, use
|
||||
the `oneshot` or `longrun` functions:
|
||||
|
||||
* a "longrun" service is the "normal" service concept: it has a
|
||||
``run`` action which describes the process to start, and it watches
|
||||
that process to restart it if it exits. The process should not
|
||||
attempt to daemonize or "background" itself, otherwise s6-rc will think
|
||||
it died. Whatever it prints to standard output/standard error
|
||||
will be logged.
|
||||
|
||||
.. code-block:: nix
|
||||
|
||||
config.services.cowsayd = pkgs.liminix.services.longrun {
|
||||
name = "cowsayd";
|
||||
run = "${pkgs.cowsayd}/bin/cowsayd --port 3001 --breed hereford";
|
||||
# don't start this until the lan interface is ready
|
||||
dependencies = [ config.services.lan ];
|
||||
}
|
||||
|
||||
|
||||
* a "oneshot" service doesn't have a process attached. It consists of
|
||||
``up`` and ``down`` actions which are bits of shell script that
|
||||
are run at the appropriate points in the service lifecycle
|
||||
|
||||
.. code-block:: nix
|
||||
|
||||
config.services.greenled = pkgs.liminix.services.oneshot {
|
||||
name = "greenled";
|
||||
up = ''
|
||||
echo 17 > /sys/class/gpio/export
|
||||
echo out > /sys/class/gpio/gpio17/direction
|
||||
echo 0 > /sys/class/gpio/gpio17/value
|
||||
'';
|
||||
down = ''
|
||||
echo 0 > /sys/class/gpio/gpio17/value
|
||||
'';
|
||||
}
|
||||
|
||||
Services may have dependencies: as you see above in the ``cowsayd``
|
||||
example, it depends on some service called ``config.services.lan``,
|
||||
meaning that it won't be started until that other service is up.
|
||||
|
||||
..
|
||||
TODO: explain service outputs
|
||||
|
||||
..
|
||||
TODO: outputs that change, and services that poll other services
|
||||
|
||||
Module implementation
|
||||
*********************
|
||||
|
||||
TODO: make your own modules
|
||||
|
||||
* how a module exposes services
|
||||
* defining types
|
||||
@@ -1,14 +1,14 @@
|
||||
Developer Manual
|
||||
################
|
||||
Development
|
||||
###########
|
||||
|
||||
As a developer working on Liminix, or implementing a service or
|
||||
module, you probably want to test your changes more conveniently
|
||||
than by building and flashing a new image every time. This manual
|
||||
than by building and flashing a new image every time. This section
|
||||
documents various affordances for iteration and experiments.
|
||||
|
||||
In general, packages and tools that run on the "build" machine are
|
||||
available in the ``buildEnv`` derivation and can most easily
|
||||
be added to your environment by running :command:`nix-shell`
|
||||
be added to your environment by running :command:`nix-shell`.
|
||||
|
||||
|
||||
|
||||
-51
@@ -1,51 +0,0 @@
|
||||
The Future
|
||||
##########
|
||||
|
||||
What about NixWRT?
|
||||
|
||||
This is an in-progress rewrite of NixWRT, incorporating Lessons
|
||||
Learned. That said, as of today it is not yet at feature parity.
|
||||
|
||||
Liminix will eventually provide these differentiators over NixWRT:
|
||||
|
||||
* a writable filesystem so that software updates or reconfiguration
|
||||
(e.g. changing passwords) don't require taking the device offline to
|
||||
reflash it.
|
||||
|
||||
* more flexible service management with dependencies, to allow
|
||||
configurations such as "route through PPPoE if it is healthy, with
|
||||
fallback to LTE"
|
||||
|
||||
* a spec for valid configuration options (a la NixOS module options)
|
||||
to that we can detect errors at evaluation time instead of producing
|
||||
a bad image.
|
||||
|
||||
* a network-based mechanism for secrets management so that changes can
|
||||
be pushed from a central location to several Liminix devices at once
|
||||
|
||||
* send device metrics and logs to a monitoring/alerting/o11y
|
||||
infrastructure
|
||||
|
||||
Today though, it does approximately none of these things and certainly
|
||||
not on real hardware.
|
||||
|
||||
|
||||
Articles of interest
|
||||
####################
|
||||
|
||||
* `Build Safety of Software in 28 Popular Home Routers <https://cyber-itl.org/assets/papers/2018/build_safety_of_software_in_28_popular_home_routers.pdf>`_: "of the access
|
||||
points and routers we reviewed, not a single one took full
|
||||
advantage of the basic application armoring features provided by
|
||||
the operating system. Indeed, only one or two models even came
|
||||
close, and no brand did well consistently across all models tested"
|
||||
|
||||
* `A PPPoE Implementation for Linux <https://static.usenix.org/publications/library/proceedings/als00/2000papers/papers/full_papers/skoll/skoll_html/index.html>`_:
|
||||
"Many DSL service providers use PPPoE for residential broadband
|
||||
Internet access. This paper briefly describes the PPPoE protocol,
|
||||
presents strategies for implementing it under Linux and describes in
|
||||
detail a user-space implementation of a PPPoE client."
|
||||
|
||||
* `PPP IPV6CP vs DHCPv6 at AAISP <https://www.revk.uk/2011/01/ppp-ipv6cp-vs-dhcpv6.html>`_
|
||||
|
||||
|
||||
* `Creating a Home IPv6 Network (James Bottomley) <https://blog.hansenpartnership.com/creating-a-home-ipv6-network/>`_
|
||||
+4
-3
@@ -6,9 +6,10 @@ Liminix
|
||||
:caption: Contents:
|
||||
|
||||
intro
|
||||
user
|
||||
developer
|
||||
etc
|
||||
tutorial
|
||||
configuration
|
||||
admin
|
||||
development
|
||||
|
||||
|
||||
Indices and tables
|
||||
|
||||
@@ -0,0 +1,257 @@
|
||||
Getting Started
|
||||
###############
|
||||
|
||||
Liminix is very configurable, which can make it initially quite
|
||||
daunting, especially if you're learning Nix or Linux or networking
|
||||
concepts at the same time. In this section we build some "worked
|
||||
example" Liminix images to introduce the concepts. If you follow the
|
||||
examples exactly, they should work. If you change things as you go
|
||||
along, they may work differently or not at all, but the experience
|
||||
should be educational either way.
|
||||
|
||||
|
||||
.. warning:: The first example we will look at runs under emulation,
|
||||
so there is no danger of bricking your hardware
|
||||
device. For the second example you may (if you have
|
||||
appropriate hardware and choose to do so) flash the
|
||||
configuration onto an actual router. There is always a
|
||||
risk of rendering the device unbootable when you do this,
|
||||
and various ways to recover depending on what went wrong.
|
||||
We'll write more about that at the appropriate point
|
||||
|
||||
|
||||
Requirements
|
||||
************
|
||||
|
||||
You will need a reasonably powerful computer running Nix. Target
|
||||
devices for Liminix are unlikely to have the CPU power and disk space
|
||||
to be able to build it in situ, so the build process is based around
|
||||
"cross-compilation" from another computer. The build machine can be
|
||||
any reasonably powerful desktop/laptop/server PC running NixOS.
|
||||
Standalone Nixpkgs installations on other Linux distributions - or on
|
||||
MacOS, or even in a Docker container - also ought to work but are
|
||||
untested.
|
||||
|
||||
|
||||
Running in Qemu
|
||||
***************
|
||||
|
||||
You can try out Liminix without even having a router to play with.
|
||||
Clone the Liminix git repository and change into its directory
|
||||
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
git clone https://gti.telent.net/dan/liminix
|
||||
cd liminix
|
||||
|
||||
Now build Liminix
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
nix-build -I liminix-config=./examples/hello-from-qemu.nix \
|
||||
--arg device "import ./devices/qemu" -A outputs.default
|
||||
|
||||
In this command ``liminix-config`` points to the desired software
|
||||
configuration (e.g. services, users, filesystem, secrets) and
|
||||
``device`` describes the hardware (or emulated hardware) to run it on.
|
||||
``outputs.default`` tells Liminix that we want the default image
|
||||
output for flashing to the device: for the Qemu "hardware" it's an
|
||||
alias for ``outputs.vmbuild``, which creates a directory containing a
|
||||
root filesystem image and a kernel.
|
||||
|
||||
.. tip:: The first time you run this it may take several hours,
|
||||
because it builds all of the dependencies including a full
|
||||
MIPS gcc and library toolchain. Once those intermediate build
|
||||
products are in the nix store, subsequent builds will be much
|
||||
faster - practically instant, if nothing has changed.
|
||||
|
||||
Now you can try it:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
nix-shell --run "mips-vm ./result/vmlinux ./result/rootfs"
|
||||
|
||||
This starts the Qemu emulator with a bunch of useful options, to run
|
||||
the Liminix configuration you just built. It connects the emulated
|
||||
device's serial console and the `QEMU monitor
|
||||
<https://www.qemu.org/docs/master/system/monitor.html>`_ to
|
||||
stdin/stdout.
|
||||
|
||||
You should now see Linux boot messages and after a few seconds be
|
||||
presented with a login prompt. You can login on the console as
|
||||
``root`` (password is "secret") and poke around to see what processes are
|
||||
running. To kill the emulator, press ^P (Control P) then c to enter the
|
||||
"QEMU Monitor", then type ``quit`` at the ``(qemu)`` prompt.
|
||||
|
||||
To see that it's running network services we need to connect to its
|
||||
emulated network. Start the machine again, if you had stopped it, and
|
||||
open up a second terminal on your build machine. We're going to run
|
||||
another virtual machine attached to the virtual network, which will
|
||||
request an IP address from our Liminix system and give you a shell you
|
||||
can run ssh from.
|
||||
|
||||
We use `System Rescue <https://www.system-rescue.org/>`_ in tty
|
||||
mode (no graphical output) for this example, but if you have some
|
||||
other favourite Linux Live CD ISO - or, for that matter, any other OS
|
||||
image that QEMU can boot - adjust the command to suit.
|
||||
|
||||
Download the System Rescue ISO:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
curl https://fastly-cdn.system-rescue.org/releases/10.01/systemrescue-10.01-amd64.iso -O
|
||||
|
||||
and run it
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
nix-shell -p qemu --run " \
|
||||
qemu-system-x86_64 \
|
||||
-echr 16 \
|
||||
-m 1024 \
|
||||
-cdrom systemrescue-10.01-amd64.iso \
|
||||
-netdev socket,mcast=230.0.0.1:1235,localaddr=127.0.0.1,id=lan \
|
||||
-device virtio-net,disable-legacy=on,disable-modern=off,netdev=lan,mac=ba:ad:3d:ea:21:01 \
|
||||
-display none -serial mon:stdio"
|
||||
|
||||
System Rescue displays a boot menu at which you should select the
|
||||
"serial console" option, then after a few moments it boots to a root
|
||||
prompt. You can now try things out:
|
||||
|
||||
* run :command:`ip a` and see that it's been allocated an IP address in the range 10.3.0.0/16.
|
||||
|
||||
* run :command:`ping 10.3.0.1` to see that the Liminix VM responds
|
||||
|
||||
* run :command:`ssh root@10.3.0.1` to try logging into it.
|
||||
|
||||
Congratulations! You have installed your first Liminix system - albeit
|
||||
it has no practical use and it's not even real. The next step is to try
|
||||
running it on hardware.
|
||||
|
||||
Installing on hardware
|
||||
**********************
|
||||
|
||||
For the next example, we're going to install onto an actual hardware
|
||||
device. These steps have been tested using a GL-iNet GL-MT300A, which
|
||||
has been chosen for the purpose because it's cheap and easy to
|
||||
unbrick. Using some other Liminix-supported MIPS hardware device also
|
||||
*ought* to work here, but you accept the slightly greater bricking
|
||||
risk if it doesn't.
|
||||
|
||||
You may want to acquire a `USB TTL serial cable
|
||||
<https://cpc.farnell.com/ftdi/ttl-232r-rpi/cable-debug-ttl-232-usb-rpi/dp/SC12825?st=usb%20to%20uart%20cable>`_
|
||||
when you start working with Liminix on real hardware. You
|
||||
won't *need* it for this example, assuming it works, but it
|
||||
allows you
|
||||
to see the boot monitor and kernel messages, and to login directly to
|
||||
the device if for some reason it doesn't bring its network up. You have options
|
||||
here: the FTDI-based cables are the Rolls Royce of serial cables,
|
||||
whereas the ones based on PL2303 and CP2102 chipsets are cheaper but
|
||||
also fussier - or you could even get creative and use e.g. a
|
||||
`Raspberry Pi <https://pinout.xyz/#>`_ or other SBC with a UART and
|
||||
TX/RX/GND header pins. Make sure that the voltages are compatible:
|
||||
this is a 3.3v device and you don't want to be sending it 5v or (even
|
||||
worse) 12v.
|
||||
|
||||
Now we can build Liminix. Although we could use the same example
|
||||
configuration as we did for Qemu, you might not want to plug a DHCP
|
||||
server into your working LAN because it will compete with the real
|
||||
DHCP service. So we're going to use a different configuration with a
|
||||
DHCP client: this is :file:`examples/hello-from-mt300.nix`
|
||||
|
||||
It's instructive to compare the two configurations:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
diff -u examples/hello-from-qemu.nix examples/hello-from-mt300.nix
|
||||
|
||||
You'll see a new ``boot.tftp`` stanza which you can ignore,
|
||||
``services.dns`` has been removed, and the static IP address allocation
|
||||
has been replaced by a ``dhcp.client`` service.
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
nix-build -I liminix-config=./examples/hello-from-mt300.nix \
|
||||
--arg device "import ./devices/gl-mt300a" -A outputs.default
|
||||
|
||||
.. tip:: The first time you run this it may take several hours.
|
||||
Again? Yes, even if you ran the previous example. Qemu is
|
||||
set up as a big-endian system whereas the MediaTek SoC
|
||||
on this device is little-endian - so it requires building
|
||||
all of the dependencies including an entirely different
|
||||
MIPS gcc and library toolchain to the other one.
|
||||
|
||||
This time in :file:`result/` you will see a bunch of files. Most of
|
||||
them you can ignore for the moment, but :file:`result/firmware.bin` is
|
||||
the firmware image you can flash.
|
||||
|
||||
|
||||
Flashing
|
||||
========
|
||||
|
||||
Again, there are a number of different ways you could do this: using
|
||||
TFTP with a serial cable, through the stock firmware's web UI, or
|
||||
using the `vendor's "debrick" process
|
||||
<https://docs.gl-inet.com/router/en/3/tutorials/debrick/>`_. The last
|
||||
of these options has a lot to recommend it for a first attempt:
|
||||
|
||||
* it works no matter what firmware is currently installed
|
||||
|
||||
* it doesn't require plugging a router into the same network as your
|
||||
build system and potentially messing up your actual upstream
|
||||
|
||||
* no need to open the device and add cables
|
||||
|
||||
You can read detailed instructions on the vendor site, but the short version is:
|
||||
|
||||
1. turn the device off
|
||||
2. connect it by ethernet cable to a computer
|
||||
3. configure the computer to have static ip address 192.168.1.10
|
||||
4. while holding down the Reset button, turn the device on
|
||||
5. after about five seconds you can release the Reset button
|
||||
6. visit http://192.168.1.1/ using a web browser on the connected computer
|
||||
7. click on "Browse" and choose :file:`result/firmware.bin`
|
||||
8. click on "Update firmware"
|
||||
9. wait a minute or so while it updates.
|
||||
|
||||
There's no feedback from the web interface when the flashing is
|
||||
finished, but what should happen is that the router reboots and
|
||||
starts running Liminix. Now you need to figure out what address it got
|
||||
from DHCP - e.g. by checking the DHCP server logs, or maybe by pinging
|
||||
``hello.lan`` or something. Once you've found it on the
|
||||
network you can ping it and ssh to it just like you did the Qemu
|
||||
example, but this time for real.
|
||||
|
||||
.. warning:: Do not leave the default root password in place on any
|
||||
device exposed to the internet! Although it has no
|
||||
writable storage and no default route, a motivated attacker
|
||||
with some imagination could probably still do something
|
||||
awful using it.
|
||||
|
||||
Congratulations Part II! You have installed your first Liminix system on
|
||||
actual hardware - albeit that it *still* has no practical use.
|
||||
|
||||
Exercise for the reader: change the default password by editing
|
||||
:file:`examples/hello-from-mt300.nix`, and then create and upload a
|
||||
new image that has it set to something less hopeless.
|
||||
|
||||
|
||||
Final thoughts
|
||||
**************
|
||||
|
||||
* These are demonstration configs for pedagogical purposes. If you'd
|
||||
like to see some more realistic uses of Liminix,
|
||||
:file:`examples/rotuer,arhcive,extneder.nix` are based on some
|
||||
actual real hosts in my home network.
|
||||
|
||||
* These example images are not writable. Later we will explain how to
|
||||
generate an image that can be changed after installation, and
|
||||
even use :command:`liminix-rebuild` (analogous to :command:`nixos-rebuild`)
|
||||
to keep it up to date.
|
||||
|
||||
* The technique used here for flashing was chosen mostly because it
|
||||
doesn't need much infrastructure/tooling, but it is a bit of a faff
|
||||
(requires physical access, vendor specific). There are slicker ways
|
||||
to do it that need a bit more setup - we'll talk about that later as
|
||||
well.
|
||||
-347
@@ -1,347 +0,0 @@
|
||||
User Manual
|
||||
###########
|
||||
|
||||
This manual is an early work in progress, not least because Liminix is
|
||||
not yet really ready for users who are not also developers. Your
|
||||
feedback to improve it is very welcome.
|
||||
|
||||
Installation
|
||||
************
|
||||
|
||||
The Liminix installation process is not quite like installing NixOS on
|
||||
a real computer, but some NixOS experience will nevertheless be
|
||||
helpful in understanding it. The steps are as follows:
|
||||
|
||||
* Decide whether you want the device to be updatable in-place (there
|
||||
are advantages and disadvantages), or if you are happy to generate
|
||||
and flash a new image whenever changes are required.
|
||||
|
||||
* Create a :file:`configuration.nix` describing the system you want
|
||||
|
||||
* Build an image
|
||||
|
||||
* Flash it to the device
|
||||
|
||||
Supported devices
|
||||
=================
|
||||
|
||||
For a list of devices that Liminix (present or previous versions)
|
||||
has run on, refer to `devices/ in the source repo <https://gti.telent.net/dan/liminix/src/branch/main/devices>`_. For devices that _currently_ build,
|
||||
cross-reference it with `the CI status <https://build.liminix.org/jobset/liminix/build#tabs-jobs>`_. Everything that builds is (usually) expected
|
||||
to run, so if you end up with an image that builds but doesn't
|
||||
boot, please report it as a bug.
|
||||
|
||||
As of June 2023 the device list is a little thin. Adding devices based
|
||||
on the Atheros or Mediatek (Ralink) platform should be quite
|
||||
straightforward if you have some C/Linux kernel experience and are
|
||||
prepared to open it up and attach serial wires: please refer to the
|
||||
Developer Manual.
|
||||
|
||||
|
||||
Choosing a flavour (read-only or updatable)
|
||||
===========================================
|
||||
|
||||
Liminix installations come in two "flavours"- read-only or in-place
|
||||
updatable:
|
||||
|
||||
* a read-only installation can't be updated once it is flashed to your
|
||||
device, and so must be reinstalled in its entirety every time you
|
||||
want to change it. It uses the ``squashfs`` filesystem which has
|
||||
very good compression ratios and so you can pack quite a lot of
|
||||
useful stuff onto your device. This is good if you don't expect
|
||||
to change it often.
|
||||
|
||||
* an updatable installation has a writable filesystem so that you can
|
||||
update configuration, upgrade packages and install new packages over
|
||||
the network after installation. This uses the `jffs2
|
||||
<http://www.linux-mtd.infradead.org/doc/jffs2.html>`_ filesystem:
|
||||
although it does compress the data, the need to support writes means
|
||||
that it can't pack quite as small as squashfs, so you will not have
|
||||
as much space to play with.
|
||||
|
||||
Updatability caveats
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
At the time of writing this manual the read-only squashfs support is
|
||||
much more mature. Consider also that it may not be possible to perform
|
||||
"larger" updates in-place even if you do opt for updatability. If you
|
||||
have (for example) an 11MB system on a 16MB device, you won't be able
|
||||
to do an in-place update of something fundamental like the C library
|
||||
(libc), as this will temporarily require 22MB to install all the
|
||||
packages needing the new library before the packages using the old
|
||||
library can be removed. A writable system will be more useful for
|
||||
smaller updates such as installing a new package (perhaps you
|
||||
temporarily need tcpdump to diagnose a network problem) or for
|
||||
changing configuration files.
|
||||
|
||||
Note also that the kernel is not part of the filesystem so cannot be
|
||||
updated this way. Kernel changes require a full reflash.
|
||||
|
||||
|
||||
|
||||
Creating configuration.nix
|
||||
==========================
|
||||
|
||||
|
||||
You need to create a :file:`configuration.nix` that describes your
|
||||
device and the services that you want to run on it. The best way to
|
||||
get started is by reading one of the examples such as
|
||||
:file:`examples/rotuer.nix` and modifying it to your needs.
|
||||
|
||||
:file:`configuration.nix` conventionally describes the packages, services,
|
||||
user accounts etc of the device. It does not describe the hardware
|
||||
itself, which is specified separately in the build command (as you
|
||||
will see below).
|
||||
|
||||
Most of the functionality of a Liminix system is driven by *services*
|
||||
which are declared by *modules*: thus, to add for example an NTP service
|
||||
you first add :file:`modules/ntp` to your ``imports`` list, then
|
||||
you create a service by calling :code:`config.system.service.ntp.build { .... }`
|
||||
with the appropriate service-dependent configuration parameters.
|
||||
|
||||
.. code-block:: nix
|
||||
|
||||
let svc = config.system.service;
|
||||
in {
|
||||
# ...
|
||||
imports = [
|
||||
./modules/ntp
|
||||
# ....
|
||||
];
|
||||
config.services.ntp = svc.ntp.build {
|
||||
pools = { "pool.ntp.org" = ["iburst"]; };
|
||||
makestep = { threshold = 1.0; limit = 3; };
|
||||
};
|
||||
|
||||
A :ref:`full list of module options <module-options>` is provided
|
||||
later in this manual.
|
||||
|
||||
You *most likely* want to include the ``standard`` module unless you
|
||||
have a quite unusual use case for a very minimal system, in which case
|
||||
you will understand what it does and what happens if you leave it out.
|
||||
|
||||
.. code-block:: nix
|
||||
|
||||
imports = [
|
||||
./modules/standard.nix
|
||||
]
|
||||
configuration.rootfsType = "jffs2"; # or "squashfs"
|
||||
|
||||
|
||||
|
||||
Building
|
||||
========
|
||||
|
||||
Build Liminix using the :file:`default.nix` in the project toplevel
|
||||
directory, passing it arguments for configuration and hardware. For
|
||||
example:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
nix-build -I liminix-config=./tests/smoke/configuration.nix \
|
||||
--arg device "import ./devices/qemu" -A outputs.default
|
||||
|
||||
In this command ``<liminix-config>`` points to your
|
||||
:file:`configuration.nix`, ``device`` is the file for your hardware device
|
||||
definition, and ``outputs.default`` will generate some kind of
|
||||
Liminix image output appropriate to that device.
|
||||
|
||||
For the qemu device in this example, ``outputs.default`` is an alias
|
||||
for ``outputs.vmbuild``, which creates a directory containing a
|
||||
squashfs root image and a kernel. You can use the :command:`mips-vm` command to
|
||||
run this.
|
||||
|
||||
For the currently supported hardware devices, ``outputs.default``
|
||||
creates a directory containing a file called ``firmware.bin``. This
|
||||
is a raw image file that can be written directly to the firmware flash
|
||||
partition.
|
||||
|
||||
|
||||
Flashing
|
||||
========
|
||||
|
||||
|
||||
Flashing from the boot monitor
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If you are prepared to open the device and have a TTL serial adaptor
|
||||
of some kind to connect it to, you can probably flash it using U-Boot.
|
||||
This is quite hardware-specific, and sometimes involves soldering:
|
||||
please refer to the Developer Manual.
|
||||
|
||||
|
||||
Flashing from an existing Liminix system with :command:`flashcp`
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The flash procedure from an existing Liminix-system is two-step.
|
||||
First we reboot the device (using "kexec") into an "ephemeral"
|
||||
RAM-based version of the new configuration, then when we're happy it
|
||||
works we can flash the image - and if it doesn't work we can reboot
|
||||
the device again and it will boot from the old image.
|
||||
|
||||
|
||||
|
||||
Building the RAM-based image
|
||||
............................
|
||||
|
||||
To create the ephemeral image, build ``outputs.kexecboot`` instead of
|
||||
``outputs.default``. This generates a directory containing the root
|
||||
filesystem image and kernel, along with an executable called `kexec`
|
||||
and a `boot.sh` script that runs it with appropriate arguments.
|
||||
|
||||
For example
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
nix-build --show-trace -I liminix-config=./examples/arhcive.nix \
|
||||
--arg device "import ./devices/gl-ar750"
|
||||
-A outputs.kexecboot && \
|
||||
(tar chf - result | ssh root@the-device tar -C /run -xvf -)
|
||||
|
||||
and then login to the device and run
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
cd /run/result
|
||||
sh ./boot.sh .
|
||||
|
||||
|
||||
This will load the new kernel and map the root filesystem into a RAM
|
||||
disk, then start executing the new kernel. *This is effectively a
|
||||
reboot - be sure to close all open files and finish anything else
|
||||
you were doing first.*
|
||||
|
||||
If the new system crashes or is rebooted, then the device will revert
|
||||
to the old configuration it finds in flash.
|
||||
|
||||
|
||||
Building the second (permanent) image
|
||||
.....................................
|
||||
|
||||
While running in the kexecboot system, you can copy the permanent
|
||||
image to the device with :command:`ssh`
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
build-machine$ tar chf - result/firmware.bin | \
|
||||
ssh root@the-device tar -C /run -xvf -
|
||||
|
||||
Next you need to connect to the device and locate the "firmware"
|
||||
partition, which you can do with a combination of :command:`dmesg`
|
||||
output and the contents of :file:`/proc/mtd`
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
<5>[ 0.469841] Creating 4 MTD partitions on "spi0.0":
|
||||
<5>[ 0.474837] 0x000000000000-0x000000040000 : "u-boot"
|
||||
<5>[ 0.480796] 0x000000040000-0x000000050000 : "u-boot-env"
|
||||
<5>[ 0.487056] 0x000000050000-0x000000060000 : "art"
|
||||
<5>[ 0.492753] 0x000000060000-0x000001000000 : "firmware"
|
||||
|
||||
# cat /proc/mtd
|
||||
dev: size erasesize name
|
||||
mtd0: 00040000 00001000 "u-boot"
|
||||
mtd1: 00010000 00001000 "u-boot-env"
|
||||
mtd2: 00010000 00001000 "art"
|
||||
mtd3: 00fa0000 00001000 "firmware"
|
||||
mtd4: 002a0000 00001000 "kernel"
|
||||
mtd5: 00d00000 00001000 "rootfs"
|
||||
|
||||
Now run (in this example)
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
flashcp -v firmware.bin /dev/mtd3
|
||||
|
||||
|
||||
"I know my new image is good, can I skip the intemediate step?"
|
||||
```````````````````````````````````````````````````````````````
|
||||
|
||||
In addition to giving you a chance to see if the new image works, this
|
||||
two-step process ensures that you're not copying the new image over
|
||||
the top of the active root filesystem. It might work, or it might
|
||||
crash in surprising ways.
|
||||
|
||||
|
||||
|
||||
Flashing from OpenWrt (not currently advised!)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. CAUTION:: At your own risk! This will (at least in some
|
||||
circumstances) lead to bricking the device: we think this
|
||||
flash method is currently incompatible with use of a
|
||||
writeable (jffs2) filesystem.
|
||||
|
||||
If your device is running OpenWrt then it probably has the
|
||||
:command:`mtd` command installed. After transferring the image onto the
|
||||
device using e.g. :command:`ssh`, you can run it as follows:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
mtd -r write /tmp/firmware.bin firmware
|
||||
|
||||
For more information, please see the `OpenWrt manual <https://openwrt.org/docs/guide-user/installation/sysupgrade.cli>`_ which may also contain (hardware-dependent) instructions on how to flash an image using the vendor firmware - perhaps even from a web interface.
|
||||
|
||||
|
||||
Updating an installed system (JFFS2)
|
||||
************************************
|
||||
|
||||
Adding packages
|
||||
===============
|
||||
|
||||
|
||||
If your device is running a JFFS2 root filesystem, you can build
|
||||
extra packages for it on your build system and copy them to the
|
||||
device: any package in Nixpkgs or in the Liminix overlay is available
|
||||
with the ``pkgs`` prefix:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
nix-build -I liminix-config=./my-configuration.nix \
|
||||
--arg device "import ./devices/mydevice" -A pkgs.tcpdump
|
||||
|
||||
nix-shell -p min-copy-closure root@the-device result/
|
||||
|
||||
Note that this only copies the package to the device: it doesn't update
|
||||
any profile to add it to ``$PATH``
|
||||
|
||||
|
||||
Rebuilding the system
|
||||
=====================
|
||||
|
||||
:command:`liminix-rebuild` is the Liminix analogue of :command:`nixos-rebuild`, although its operation is a bit different because it expects to run on a build machine and then copy to the host device. Run it with the same ``liminix-config`` and ``device`` parameters as you would run :command:`nix-build`, and it will build any new/changed packages and then copy them to the device using SSH. For example:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
liminix-rebuild root@the-device -I liminix-config=./examples/rotuer.nix --arg device "import ./devices/gl-ar750"
|
||||
|
||||
This will
|
||||
|
||||
* build anything that needs building
|
||||
* copy new or changed packages to the device
|
||||
* reboot the device
|
||||
|
||||
It doesn't delete old packages automatically: to do that run
|
||||
:command:`min-collect-garbage`, which will delete any packages not in
|
||||
the current system closure. Note that Liminix does not have the NixOS
|
||||
concept of environments or generations, and there is no way back from
|
||||
this except for building the previous configuration again.
|
||||
|
||||
|
||||
|
||||
Caveats
|
||||
~~~~~~~
|
||||
|
||||
* it needs there to be enough free space on the device for all the new
|
||||
packages in addition to all the packages already on it - which may be
|
||||
a problem if a lot of things have changed (e.g. a new version of
|
||||
nixpkgs).
|
||||
|
||||
* it cannot upgrade the kernel, only userland
|
||||
|
||||
Configuration options
|
||||
*********************
|
||||
|
||||
.. _module-options:
|
||||
|
||||
.. include:: modules.rst
|
||||
@@ -1,59 +1,31 @@
|
||||
(local { : merge : split : file-exists? : system } (require :anoia))
|
||||
(local { : system } (require :anoia))
|
||||
(local svc (require :anoia.svc))
|
||||
|
||||
(fn parse-prefix [str]
|
||||
(fn parse-extra [s]
|
||||
(let [out {}]
|
||||
(each [name val (string.gmatch s ",(.-)=([^,]+)")]
|
||||
(tset out name val))
|
||||
out))
|
||||
(let [(prefix len preferred valid extra)
|
||||
(string.match str "(.-)::/(%d+),(%d+),(%d+)(.*)$")]
|
||||
(merge {: prefix : len : preferred : valid} (parse-extra extra))))
|
||||
|
||||
|
||||
;; Format: <prefix>/<length>,preferred,valid[,excluded=<excluded-prefix>/<length>][,class=<prefix class #>]
|
||||
|
||||
;;(parse-prefix "2001:8b0:de3a:40dc::/64,7198,7198")
|
||||
;;(parse-prefix "2001:8b0:de3a:1001::/64,7198,7188,excluded=1/2,thi=10")
|
||||
|
||||
|
||||
(local bound-states
|
||||
{ :bound true
|
||||
:rebound true
|
||||
:informed true
|
||||
:updated true
|
||||
:ra-updated true
|
||||
})
|
||||
|
||||
; (local { : view } (require :fennel))
|
||||
|
||||
(fn changes [old-prefixes new-prefixes]
|
||||
(fn changes [old-addresses new-addresses]
|
||||
(let [added {}
|
||||
deleted {}
|
||||
old-set (collect [_ v (ipairs old-prefixes)] (values v true))
|
||||
new-set (collect [_ v (ipairs new-prefixes)] (values v true))]
|
||||
(each [_ prefix (ipairs new-prefixes)]
|
||||
(if (not (. old-set prefix))
|
||||
(table.insert added (parse-prefix prefix))))
|
||||
(each [_ prefix (ipairs old-prefixes)]
|
||||
(if (not (. new-set prefix))
|
||||
(table.insert deleted (parse-prefix prefix))))
|
||||
deleted {}]
|
||||
(each [n address (pairs new-addresses)]
|
||||
(if (not (. old-addresses n))
|
||||
(table.insert added address)))
|
||||
(each [n address (pairs old-addresses)]
|
||||
(if (not (. new-addresses n))
|
||||
(table.insert deleted address)))
|
||||
(values added deleted)))
|
||||
|
||||
(let [[state-directory lan-device] arg
|
||||
dir (svc.open state-directory)]
|
||||
(var prefixes [])
|
||||
(while true
|
||||
(while (not (dir:ready?)) (dir:wait))
|
||||
(if (. bound-states (dir:output "state"))
|
||||
(let [new-prefixes (split " " (dir:output "/prefixes"))
|
||||
(added deleted) (changes prefixes new-prefixes)]
|
||||
(each [_ p (ipairs added)]
|
||||
(system
|
||||
(.. "ip address add " p.prefix "::1/" p.len " dev " lan-device)))
|
||||
(each [_ p (ipairs deleted)]
|
||||
(system
|
||||
(.. "ip address del " p.prefix "::1/" p.len " dev " lan-device)))
|
||||
(set prefixes new-prefixes)))
|
||||
(dir:wait)))
|
||||
(fn update-prefixes [device prefixes new-prefixes]
|
||||
(let [(added deleted) (changes prefixes new-prefixes)]
|
||||
(each [_ p (ipairs added)]
|
||||
(system
|
||||
(.. "ip address add " p.address "1/" p.len " dev " device)))
|
||||
(each [_ p (ipairs deleted)]
|
||||
(system
|
||||
(.. "ip address del " p.address "1/" p.len " dev " device)))))
|
||||
|
||||
(fn run []
|
||||
(let [[state-directory lan-device] arg
|
||||
dir (svc.open state-directory)]
|
||||
(accumulate [addresses []
|
||||
v (dir:events)]
|
||||
(update-prefixes lan-device addresses (v:output "prefix")))))
|
||||
|
||||
{ : changes : run }
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
{
|
||||
writeFennelScript
|
||||
writeFennel
|
||||
, linotify
|
||||
, anoia
|
||||
, lua
|
||||
}:
|
||||
writeFennelScript "acquire-delegated-prefix"
|
||||
[ linotify anoia ]
|
||||
./acquire-delegated-prefix.fnl
|
||||
writeFennel "acquire-delegated-prefix" {
|
||||
packages = [ linotify anoia lua.pkgs.luafilesystem ];
|
||||
mainFunction = "run";
|
||||
} ./acquire-delegated-prefix.fnl
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
(local subject (require :acquire-wan-address))
|
||||
(local { : view } (require :fennel))
|
||||
(local { : merge : dup } (require :anoia))
|
||||
|
||||
|
||||
(local a1
|
||||
{
|
||||
"2001-ab-cd-ef_hjgKHGhKJH" {
|
||||
:address "2001:ab:cd:ef"
|
||||
:len "64"
|
||||
:preferred "200"
|
||||
:valid "200"
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
(local a2
|
||||
{
|
||||
"2001-0-1-2-3_aNteBnb" {
|
||||
:address "2001:0:1:2:3"
|
||||
:len "64"
|
||||
:preferred "200"
|
||||
:valid "200"
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
(macro expect [assertion]
|
||||
(let [msg (.. "expectation failed: " (view assertion))]
|
||||
`(when (not ,assertion)
|
||||
(assert false ,msg))))
|
||||
|
||||
(fn first-address []
|
||||
(let [(add del)
|
||||
(subject.changes
|
||||
{ }
|
||||
a1
|
||||
)]
|
||||
(expect (= (# del) 0))
|
||||
(expect (= (# add) 1))
|
||||
(let [[first] add]
|
||||
(expect (= first.address "2001:ab:cd:ef")))))
|
||||
|
||||
(fn second-address []
|
||||
(let [(add del)
|
||||
(subject.changes
|
||||
a1
|
||||
(merge (dup a1) a2)
|
||||
)]
|
||||
(expect (= (# del) 0))
|
||||
(expect (= (# add) 1))
|
||||
(let [[first] add] (expect (= first.address "2001:0:1:2:3")))))
|
||||
|
||||
(fn less-address []1
|
||||
(let [(add del)
|
||||
(subject.changes
|
||||
(merge (dup a1) a2)
|
||||
a1
|
||||
)]
|
||||
(expect (= (# add) 0))
|
||||
(expect (= (# del) 1))
|
||||
|
||||
(let [[first] del] (expect (= first.address "2001:0:1:2:3")))))
|
||||
|
||||
|
||||
(first-address)
|
||||
(second-address)
|
||||
(less-address)
|
||||
@@ -1,60 +1,35 @@
|
||||
(local { : merge : split : file-exists? : system } (require :anoia))
|
||||
(local { : system } (require :anoia))
|
||||
(local svc (require :anoia.svc))
|
||||
|
||||
;; structurally this is remarkably similar to
|
||||
;; acquire-lan-prefix.fnl. maybe they should be merged: if not then
|
||||
;; we could at least extract some common code
|
||||
|
||||
;; (alternatively we could move all the parsing code into the thing in
|
||||
;; the odhcp service that *writes* this stuff)
|
||||
|
||||
; (parse-address "2001:8b0:1111:1111:0:ffff:51bb:4cf2/128,3600,7200")
|
||||
|
||||
|
||||
(fn parse-address [str]
|
||||
(fn parse-extra [s]
|
||||
(let [out {}]
|
||||
(each [name val (string.gmatch s ",(.-)=([^,]+)")]
|
||||
(tset out name val))
|
||||
out))
|
||||
(let [(address len preferred valid extra)
|
||||
(string.match str "(.-)/(%d+),(%d+),(%d+)(.*)$")]
|
||||
(merge {: address : len : preferred : valid} (parse-extra extra))))
|
||||
|
||||
(local bound-states
|
||||
{ :bound true
|
||||
:rebound true
|
||||
:informed true
|
||||
:updated true
|
||||
:ra-updated true
|
||||
})
|
||||
;; acquire-delegated-prefix has very similar code: we'd like to move
|
||||
;; this to anoia.svc when we see what the general form would look like
|
||||
|
||||
(fn changes [old-addresses new-addresses]
|
||||
(let [added {}
|
||||
deleted {}
|
||||
old-set (collect [_ v (ipairs old-addresses)] (values v true))
|
||||
new-set (collect [_ v (ipairs new-addresses)] (values v true))]
|
||||
(each [_ address (ipairs new-addresses)]
|
||||
(if (not (. old-set address))
|
||||
(table.insert added (parse-address address))))
|
||||
(each [_ address (ipairs old-addresses)]
|
||||
(if (not (. new-set address))
|
||||
(table.insert deleted (parse-address address))))
|
||||
deleted {}]
|
||||
(each [n address (pairs new-addresses)]
|
||||
(if (not (. old-addresses n))
|
||||
(table.insert added address)))
|
||||
(each [n address (pairs old-addresses)]
|
||||
(if (not (. new-addresses n))
|
||||
(table.insert deleted address)))
|
||||
(values added deleted)))
|
||||
|
||||
(let [[state-directory wan-device] arg
|
||||
dir (svc.open state-directory)]
|
||||
(var addresses [])
|
||||
(while true
|
||||
(while (not (dir:ready?)) (dir:wait))
|
||||
(if (. bound-states (dir:output "state"))
|
||||
(let [new-addresses (split " " (dir:output "/addresses"))
|
||||
(added deleted) (changes addresses new-addresses)]
|
||||
(each [_ p (ipairs added)]
|
||||
(system
|
||||
(.. "ip address add " p.address "/" p.len " dev " wan-device)))
|
||||
(each [_ p (ipairs deleted)]
|
||||
(system
|
||||
(.. "ip address del " p.address "/" p.len " dev " wan-device)))
|
||||
(set addresses new-addresses)))
|
||||
(dir:wait)))
|
||||
(fn update-addresses [wan-device addresses new-addresses]
|
||||
(let [(added deleted) (changes addresses new-addresses)]
|
||||
(each [_ p (ipairs added)]
|
||||
(system
|
||||
(.. "ip address add " p.address "/" p.len " dev " wan-device)))
|
||||
(each [_ p (ipairs deleted)]
|
||||
(system
|
||||
(.. "ip address del " p.address "/" p.len " dev " wan-device)))
|
||||
new-addresses))
|
||||
|
||||
(fn run []
|
||||
(let [[state-directory wan-device] arg
|
||||
dir (svc.open state-directory)]
|
||||
(accumulate [addresses []
|
||||
v (dir:events)]
|
||||
(update-addresses wan-device addresses (v:output "address")))))
|
||||
|
||||
{ : update-addresses : changes : run }
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
{
|
||||
writeFennelScript
|
||||
writeFennel
|
||||
, linotify
|
||||
, anoia
|
||||
, lua
|
||||
}:
|
||||
writeFennelScript "acquire-wan-address"
|
||||
[ linotify anoia ]
|
||||
./acquire-wan-address.fnl
|
||||
writeFennel "acquire-wan-address" {
|
||||
packages = [ linotify anoia lua.pkgs.luafilesystem ];
|
||||
mainFunction = "run";
|
||||
} ./acquire-wan-address.fnl
|
||||
|
||||
+20
-86
@@ -11,16 +11,10 @@
|
||||
...
|
||||
}: let
|
||||
secrets = import ./extneder-secrets.nix;
|
||||
inherit
|
||||
(pkgs.liminix.networking)
|
||||
address
|
||||
udhcpc
|
||||
interface
|
||||
route
|
||||
;
|
||||
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 = {
|
||||
@@ -32,6 +26,11 @@ in rec {
|
||||
imports = [
|
||||
../modules/standard.nix
|
||||
../modules/wlan.nix
|
||||
../modules/network
|
||||
../modules/vlan
|
||||
../modules/ssh
|
||||
../modules/watchdog
|
||||
../modules/mount
|
||||
];
|
||||
|
||||
hostname = "arhcive";
|
||||
@@ -62,55 +61,17 @@ in rec {
|
||||
};
|
||||
|
||||
services.dhcpc =
|
||||
let iface = config.hardware.networkInterfaces.lan;
|
||||
in (udhcpc iface {
|
||||
let iface = config.hardware.networkInterfaces.lan;
|
||||
in svc.network.dhcp.client.build {
|
||||
interface = iface;
|
||||
dependencies = [ config.services.hostname ];
|
||||
}) // { inherit (iface) device; };
|
||||
|
||||
services.sshd = longrun {
|
||||
name = "sshd";
|
||||
run = ''
|
||||
mkdir -p /run/dropbear
|
||||
${dropbear}/bin/dropbear -E -P /run/dropbear.pid -R -F
|
||||
'';
|
||||
};
|
||||
|
||||
services.watchdog =
|
||||
let
|
||||
watched = with config.services ; [ sshd dhcpc ];
|
||||
spinupGrace = 60;
|
||||
script = pkgs.writeAshScript "gaspode" {
|
||||
runtimeInputs = [ pkgs.s6 ];
|
||||
} ''
|
||||
deadline=$(expr $(date +%s) + ${toString spinupGrace})
|
||||
services=$@
|
||||
echo started feeding the dog
|
||||
exec 3> ''${WATCHDOG-/dev/watchdog}
|
||||
|
||||
healthy(){
|
||||
test $(date +%s) -le $deadline && return 0
|
||||
|
||||
for i in $services; do
|
||||
if test "$(s6-svstat -o up /run/service/$i)" != "true" ; then
|
||||
echo "service $i is down"
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
while healthy ;do
|
||||
sleep 10
|
||||
echo >&3
|
||||
done
|
||||
echo "stopped feeding the dog"
|
||||
sleep 6000 # don't want s6-rc to restart
|
||||
'';
|
||||
in longrun {
|
||||
name = "watchdog";
|
||||
run =
|
||||
"${script} ${lib.concatStringsSep " " (builtins.map (s: s.name) watched)}";
|
||||
};
|
||||
|
||||
services.sshd = svc.ssh.build { };
|
||||
|
||||
services.watchdog = svc.watchdog.build {
|
||||
watched = with config.services ; [ sshd dhcpc ];
|
||||
};
|
||||
|
||||
services.resolvconf = oneshot rec {
|
||||
dependencies = [ services.dhcpc ];
|
||||
@@ -123,9 +84,6 @@ in rec {
|
||||
done
|
||||
)
|
||||
'';
|
||||
down = ''
|
||||
rm -rf /run/service-state/${name}/
|
||||
'';
|
||||
};
|
||||
filesystem = dir {
|
||||
etc = dir {
|
||||
@@ -134,35 +92,25 @@ in rec {
|
||||
srv = dir {};
|
||||
};
|
||||
|
||||
services.defaultroute4 = route {
|
||||
name = "defaultroute";
|
||||
services.defaultroute4 = svc.network.route.build {
|
||||
via = "$(output ${services.dhcpc} router)";
|
||||
target = "default";
|
||||
dependencies = [services.dhcpc];
|
||||
};
|
||||
|
||||
programs.busybox = {
|
||||
applets = ["blkid" "lsusb" "findfs" "tar"];
|
||||
applets = ["lsusb" "tar"];
|
||||
options = {
|
||||
FEATURE_LS_TIMESTAMPS = "y";
|
||||
FEATURE_LS_SORTFILES = "y";
|
||||
FEATURE_BLKID_TYPE = "y";
|
||||
FEATURE_MOUNT_FLAGS = "y";
|
||||
FEATURE_MOUNT_LABEL = "y";
|
||||
FEATURE_VOLUMEID_EXT = "y";
|
||||
};
|
||||
};
|
||||
|
||||
services.mount_external_disk = oneshot {
|
||||
name = "mount_external_disk";
|
||||
up = ''
|
||||
while ! findfs LABEL=backup-disk; do
|
||||
echo waiting for backup-disk
|
||||
sleep 1
|
||||
done
|
||||
mount -t ext4 LABEL=backup-disk /srv
|
||||
'';
|
||||
down = "umount /srv";
|
||||
services.mount_external_disk = svc.mount.build {
|
||||
device = "LABEL=backup-disk";
|
||||
mountpoint = "/srv";
|
||||
fstype = "ext4";
|
||||
};
|
||||
|
||||
services.rsync =
|
||||
@@ -199,20 +147,6 @@ in rec {
|
||||
] ;
|
||||
};
|
||||
|
||||
services.default = target {
|
||||
name = "default";
|
||||
contents =
|
||||
let links = config.hardware.networkInterfaces;
|
||||
in with config.services; [
|
||||
links.lo
|
||||
defaultroute4
|
||||
resolvconf
|
||||
sshd
|
||||
rsync
|
||||
watchdog
|
||||
];
|
||||
};
|
||||
|
||||
users.root = {
|
||||
passwd = lib.mkForce secrets.root_password;
|
||||
# openssh.authorizedKeys.keys = [
|
||||
|
||||
+27
-62
@@ -11,18 +11,10 @@
|
||||
...
|
||||
}: let
|
||||
secrets = import ./extneder-secrets.nix;
|
||||
inherit
|
||||
(pkgs.liminix.networking)
|
||||
address
|
||||
udhcpc
|
||||
hostapd
|
||||
interface
|
||||
route
|
||||
;
|
||||
inherit (pkgs.liminix.services) oneshot longrun bundle target;
|
||||
inherit (pkgs.pseudofile) dir symlink;
|
||||
inherit (pkgs) dropbear ifwait serviceFns
|
||||
;
|
||||
inherit (pkgs) dropbear ifwait serviceFns;
|
||||
svc = config.system.service;
|
||||
in rec {
|
||||
boot = {
|
||||
tftp = {
|
||||
@@ -33,6 +25,11 @@ in rec {
|
||||
|
||||
imports = [
|
||||
../modules/wlan.nix
|
||||
../modules/vlan
|
||||
../modules/network
|
||||
../modules/hostapd
|
||||
../modules/bridge
|
||||
../modules/ssh
|
||||
../modules/standard.nix
|
||||
];
|
||||
|
||||
@@ -46,7 +43,9 @@ in rec {
|
||||
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
|
||||
# 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";
|
||||
@@ -71,7 +70,8 @@ in rec {
|
||||
};
|
||||
};
|
||||
|
||||
services.hostap = hostapd (config.hardware.networkInterfaces.wlan) {
|
||||
services.hostap = svc.hostapd.build {
|
||||
interface = config.hardware.networkInterfaces.wlan;
|
||||
params = {
|
||||
country_code = "GB";
|
||||
hw_mode = "g";
|
||||
@@ -86,42 +86,25 @@ in rec {
|
||||
};
|
||||
};
|
||||
|
||||
services.int = interface {
|
||||
type = "bridge";
|
||||
device = "int";
|
||||
services.int = svc.bridge.primary.build {
|
||||
ifname = "int";
|
||||
};
|
||||
|
||||
services.dhcpc = (udhcpc services.int {
|
||||
services.dhcpc = svc.network.dhcp.client.build {
|
||||
interface = services.int;
|
||||
dependencies = [ config.services.hostname ];
|
||||
}) // { device = "int"; };
|
||||
|
||||
services.bridge = let
|
||||
primary = services.int;
|
||||
addif = dev:
|
||||
oneshot {
|
||||
name = "add-${dev.device}-to-bridge";
|
||||
up = "${ifwait}/bin/ifwait -v ${dev.device} running && ip link set dev ${dev.device} master ${primary.device}";
|
||||
down = "ip link set dev ${dev} nomaster";
|
||||
dependencies = [primary dev];
|
||||
};
|
||||
in
|
||||
bundle {
|
||||
name = "bridge-members";
|
||||
contents = with config.hardware.networkInterfaces;
|
||||
map addif [
|
||||
lan
|
||||
wlan
|
||||
];
|
||||
};
|
||||
|
||||
services.sshd = longrun {
|
||||
name = "sshd";
|
||||
run = ''
|
||||
mkdir -p /run/dropbear
|
||||
${dropbear}/bin/dropbear -E -P /run/dropbear.pid -R -F
|
||||
'';
|
||||
};
|
||||
|
||||
services.bridge = svc.bridge.members.build {
|
||||
primary = services.int;
|
||||
members = with config.hardware.networkInterfaces; [
|
||||
lan
|
||||
wlan
|
||||
];
|
||||
};
|
||||
|
||||
services.sshd = svc.ssh.build {};
|
||||
|
||||
services.resolvconf = oneshot rec {
|
||||
dependencies = [ services.dhcpc ];
|
||||
name = "resolvconf";
|
||||
@@ -136,9 +119,6 @@ in rec {
|
||||
done
|
||||
)
|
||||
'';
|
||||
down = ''
|
||||
rm -rf /run/service-state/${name}/
|
||||
'';
|
||||
};
|
||||
filesystem = dir {
|
||||
etc = dir {
|
||||
@@ -146,27 +126,12 @@ in rec {
|
||||
};
|
||||
};
|
||||
|
||||
services.defaultroute4 = route {
|
||||
name = "defaultroute";
|
||||
services.defaultroute4 = svc.network.route.build {
|
||||
via = "$(output ${services.dhcpc} router)";
|
||||
target = "default";
|
||||
dependencies = [services.dhcpc];
|
||||
};
|
||||
|
||||
services.default = target {
|
||||
name = "default";
|
||||
contents =
|
||||
let links = config.hardware.networkInterfaces;
|
||||
in with config.services; [
|
||||
links.lo links.eth links.wlan
|
||||
int
|
||||
bridge
|
||||
hostap
|
||||
defaultroute4
|
||||
resolvconf
|
||||
sshd
|
||||
];
|
||||
};
|
||||
users.root.passwd = lib.mkForce secrets.root_password;
|
||||
defaultProfile.packages = with pkgs; [nftables strace tcpdump swconfig];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
{ config, pkgs, lib, ... } :
|
||||
let
|
||||
inherit (pkgs) serviceFns;
|
||||
svc = config.system.service;
|
||||
|
||||
in rec {
|
||||
imports = [
|
||||
../modules/network
|
||||
../modules/ssh
|
||||
../modules/vlan
|
||||
../modules/flashimage.nix
|
||||
];
|
||||
|
||||
boot.tftp = {
|
||||
# IP addresses to use in the boot monitor when flashing/ booting
|
||||
# over TFTP. If you are flashing using the stock firmware's Web UI
|
||||
# then these dummy values are fine
|
||||
ipaddr = "192.0.2.115"; # my address
|
||||
serverip = "192.0.2.5"; # build machine or other tftp server
|
||||
};
|
||||
|
||||
hostname = "hello";
|
||||
|
||||
services.dhcpc = svc.network.dhcp.client.build {
|
||||
interface = config.hardware.networkInterfaces.lan;
|
||||
|
||||
# don't start DHCP until the hostname is configured,
|
||||
# so it can identify itself to the DHCP server
|
||||
dependencies = [ config.services.hostname ];
|
||||
};
|
||||
|
||||
services.sshd = svc.ssh.build { };
|
||||
|
||||
users.root = {
|
||||
# the password is "secret". Use mkpasswd -m sha512crypt to
|
||||
# create this hashed password string
|
||||
passwd = "$6$y7WZ5hM6l5nriLmo$5AJlmzQZ6WA.7uBC7S8L4o19ESR28Dg25v64/vDvvCN01Ms9QoHeGByj8lGlJ4/b.dbwR9Hq2KXurSnLigt1W1";
|
||||
};
|
||||
|
||||
defaultProfile.packages = with pkgs; [
|
||||
figlet
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
{ config, pkgs, lib, ... } :
|
||||
let
|
||||
inherit (pkgs) serviceFns;
|
||||
svc = config.system.service;
|
||||
|
||||
in rec {
|
||||
imports = [
|
||||
../modules/network
|
||||
../modules/dnsmasq
|
||||
../modules/ssh
|
||||
];
|
||||
|
||||
hostname = "hello";
|
||||
|
||||
# configure the internal network (LAN) with an address
|
||||
services.int = svc.network.address.build {
|
||||
interface = config.hardware.networkInterfaces.lan;
|
||||
family = "inet"; address ="10.3.0.1"; prefixLength = 16;
|
||||
};
|
||||
|
||||
services.sshd = svc.ssh.build { };
|
||||
|
||||
users.root = {
|
||||
# the password is "secret". Use mkpasswd -m sha512crypt to
|
||||
# create this hashed password string
|
||||
passwd = "$6$y7WZ5hM6l5nriLmo$5AJlmzQZ6WA.7uBC7S8L4o19ESR28Dg25v64/vDvvCN01Ms9QoHeGByj8lGlJ4/b.dbwR9Hq2KXurSnLigt1W1";
|
||||
};
|
||||
|
||||
services.dns =
|
||||
let interface = services.int;
|
||||
in svc.dnsmasq.build {
|
||||
inherit interface;
|
||||
ranges = [
|
||||
"10.3.0.10,10.3.0.240"
|
||||
"::,constructor:$(output ${interface} ifname),ra-stateless"
|
||||
];
|
||||
|
||||
domain = "example.org";
|
||||
};
|
||||
|
||||
defaultProfile.packages = with pkgs; [
|
||||
figlet
|
||||
];
|
||||
}
|
||||
+37
-69
@@ -9,17 +9,8 @@
|
||||
{ config, pkgs, lib, ... } :
|
||||
let
|
||||
secrets = import ./rotuer-secrets.nix;
|
||||
inherit (pkgs.liminix.networking)
|
||||
address
|
||||
interface
|
||||
route;
|
||||
inherit (pkgs.liminix.services) oneshot longrun bundle target;
|
||||
inherit (pkgs)
|
||||
dropbear
|
||||
ifwait
|
||||
writeText
|
||||
writeFennelScript
|
||||
serviceFns;
|
||||
inherit (pkgs.liminix.services) oneshot longrun;
|
||||
inherit (pkgs) serviceFns;
|
||||
svc = config.system.service;
|
||||
wirelessConfig = {
|
||||
country_code = "GB";
|
||||
@@ -44,6 +35,7 @@ in rec {
|
||||
imports = [
|
||||
../modules/wlan.nix
|
||||
../modules/standard.nix
|
||||
../modules/network
|
||||
../modules/ppp
|
||||
../modules/dnsmasq
|
||||
../modules/firewall
|
||||
@@ -78,16 +70,16 @@ in rec {
|
||||
} // wirelessConfig;
|
||||
};
|
||||
|
||||
services.int =
|
||||
let iface = svc.bridge.build {
|
||||
ifname = "int";
|
||||
members = with config.hardware.networkInterfaces; [
|
||||
wlan_24 lan wlan_5
|
||||
];
|
||||
};
|
||||
in address iface {
|
||||
family = "inet4"; address ="10.8.0.1"; prefixLength = 16;
|
||||
};
|
||||
services.int = svc.network.address.build {
|
||||
interface = svc.bridge.primary.build { ifname = "int"; };
|
||||
family = "inet"; address ="10.8.0.1"; prefixLength = 16;
|
||||
};
|
||||
|
||||
services.bridge = svc.bridge.members.build {
|
||||
primary = services.int;
|
||||
members = with config.hardware.networkInterfaces;
|
||||
[ wlan_24 wlan_5 lan ];
|
||||
};
|
||||
|
||||
services.ntp = svc.ntp.build {
|
||||
pools = { "pool.ntp.org" = ["iburst"]; };
|
||||
@@ -105,8 +97,17 @@ in rec {
|
||||
inherit interface;
|
||||
ranges = [
|
||||
"10.8.0.10,10.8.0.240"
|
||||
"::,constructor:${interface.device},ra-stateless"
|
||||
# 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);
|
||||
|
||||
domain = "fake.liminix.org";
|
||||
};
|
||||
|
||||
@@ -127,48 +128,36 @@ in rec {
|
||||
( in_outputs ${name}
|
||||
echo "nameserver $(output ${services.wan} ns1)" > resolv.conf
|
||||
echo "nameserver $(output ${services.wan} ns2)" >> resolv.conf
|
||||
chmod 0444 resolv.conf
|
||||
)
|
||||
'';
|
||||
down = ''
|
||||
rm -rf /run/service-state/${name}/
|
||||
'';
|
||||
};
|
||||
|
||||
services.defaultroute4 = route {
|
||||
name = "defaultroute4";
|
||||
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 = route {
|
||||
name = "defaultroute6";
|
||||
services.defaultroute6 = svc.network.route.build {
|
||||
via = "$(output ${services.wan} ipv6-peer-address)";
|
||||
target = "default";
|
||||
dev = "$(output ${services.wan} ifname)";
|
||||
dependencies = [ services.wan ];
|
||||
interface = services.wan;
|
||||
};
|
||||
|
||||
services.firewall = svc.firewall.build {
|
||||
ruleset = import ./rotuer-firewall.nix;
|
||||
};
|
||||
|
||||
services.packet_forwarding =
|
||||
let
|
||||
ip4 = "/proc/sys/net/ipv4/conf/all/forwarding";
|
||||
ip6 = "/proc/sys/net/ipv6/conf/all/forwarding";
|
||||
in oneshot {
|
||||
name = "let-the-ip-flow";
|
||||
up = ''
|
||||
echo 1 > ${ip4}
|
||||
echo 1 > ${ip6}
|
||||
'';
|
||||
down = ''
|
||||
echo 0 > ${ip4};
|
||||
echo 0 > ${ip6};
|
||||
'';
|
||||
dependencies = [ services.firewall ];
|
||||
};
|
||||
services.packet_forwarding = svc.network.forward.build { };
|
||||
|
||||
services.dhcp6 =
|
||||
let
|
||||
@@ -188,7 +177,7 @@ in rec {
|
||||
let script = pkgs.callPackage ./acquire-delegated-prefix.nix { };
|
||||
in longrun {
|
||||
name = "acquire-lan-prefix";
|
||||
run = "${script} /run/service-state/dhcp6c.wan ${services.int.device}";
|
||||
run = "${script} /run/service-state/dhcp6c.wan $(output ${services.int} ifname)";
|
||||
dependencies = [ services.dhcp6 ];
|
||||
};
|
||||
|
||||
@@ -200,27 +189,6 @@ in rec {
|
||||
dependencies = [ services.dhcp6 ];
|
||||
};
|
||||
|
||||
services.default = target {
|
||||
name = "default";
|
||||
contents = with config.services; [
|
||||
config.hardware.networkInterfaces.lo
|
||||
config.hardware.networkInterfaces.lan
|
||||
int
|
||||
hostap
|
||||
hostap5
|
||||
ntp
|
||||
defaultroute4
|
||||
defaultroute6
|
||||
packet_forwarding
|
||||
dns
|
||||
resolvconf
|
||||
sshd
|
||||
config.services.hostname
|
||||
dhcp6
|
||||
acquire-lan-prefix
|
||||
acquire-wan-address
|
||||
];
|
||||
};
|
||||
defaultProfile.packages = with pkgs; [
|
||||
min-collect-garbage
|
||||
];
|
||||
|
||||
@@ -80,18 +80,6 @@ in {
|
||||
defaultProfile.packages = with pkgs;
|
||||
[ s6 s6-init-bin execline s6-linux-init s6-rc ];
|
||||
|
||||
hardware.networkInterfaces = {
|
||||
lo =
|
||||
let iface = interface { type = "loopback"; device = "lo";};
|
||||
in bundle {
|
||||
name = "loopback";
|
||||
contents = [
|
||||
(address iface { family = "inet4"; address ="127.0.0.1"; prefixLength = 8;})
|
||||
(address iface { family = "inet6"; address ="::1"; prefixLength = 128;})
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
boot.commandLine = [
|
||||
"console=ttyS0,115200 panic=10 oops=panic init=/bin/init loglevel=8"
|
||||
"root=${config.hardware.rootDevice}"
|
||||
|
||||
@@ -15,21 +15,37 @@ let
|
||||
in
|
||||
{
|
||||
options = {
|
||||
system.service.bridge = mkOption {
|
||||
type = liminix.lib.types.serviceDefn;
|
||||
system.service.bridge = {
|
||||
primary = mkOption { type = liminix.lib.types.serviceDefn; };
|
||||
members = mkOption { type = liminix.lib.types.serviceDefn; };
|
||||
};
|
||||
};
|
||||
config.system.service = {
|
||||
bridge = liminix.callService ./service.nix {
|
||||
members = mkOption {
|
||||
type = types.listOf liminix.lib.types.service;
|
||||
description = "interfaces to add to the bridge";
|
||||
};
|
||||
config.system.service.bridge = {
|
||||
primary = liminix.callService ./primary.nix {
|
||||
ifname = mkOption {
|
||||
type = types.str;
|
||||
description = "bridge interface name to create";
|
||||
};
|
||||
};
|
||||
members = liminix.callService ./members.nix {
|
||||
primary = mkOption {
|
||||
type = liminix.lib.types.interface;
|
||||
description = "primary bridge interface";
|
||||
};
|
||||
|
||||
members = mkOption {
|
||||
type = types.listOf liminix.lib.types.interface;
|
||||
description = "interfaces to add to the bridge";
|
||||
};
|
||||
};
|
||||
};
|
||||
config.kernel.config.BRIDGE = "y";
|
||||
config.kernel.config = {
|
||||
BRIDGE = "y";
|
||||
BRIDGE_IGMP_SNOOPING = "y";
|
||||
} // lib.optionalAttrs (config.system.service ? vlan) {
|
||||
# depends on bridge _and_ vlan. I would like there to be
|
||||
# a better way to test for the existence of vlan config:
|
||||
# maybe the module should set an `enabled` attribute?
|
||||
BRIDGE_VLAN_FILTERING = "y";
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
liminix
|
||||
, ifwait
|
||||
, lib
|
||||
}:
|
||||
{ members, primary } :
|
||||
|
||||
let
|
||||
inherit (liminix.networking) interface;
|
||||
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";
|
||||
dependencies = [ primary member ];
|
||||
};
|
||||
in bundle {
|
||||
name = "${primary.name}.members";
|
||||
contents = map addif members;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
liminix
|
||||
, ifwait
|
||||
, lib
|
||||
}:
|
||||
{ ifname } :
|
||||
let
|
||||
inherit (liminix.services) bundle oneshot;
|
||||
inherit (lib) mkOption types;
|
||||
in oneshot rec {
|
||||
name = "${ifname}.link";
|
||||
up = ''
|
||||
ip link add name ${ifname} type bridge
|
||||
${liminix.networking.ifup name ifname}
|
||||
'';
|
||||
down = "ip link set down dev ${ifname}";
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
{
|
||||
liminix
|
||||
, ifwait
|
||||
, lib
|
||||
}:
|
||||
{ members, ifname } :
|
||||
let
|
||||
inherit (liminix.networking) interface;
|
||||
inherit (liminix.services) bundle oneshot;
|
||||
inherit (lib) mkOption types;
|
||||
primary = interface {
|
||||
device = ifname;
|
||||
type = "bridge";
|
||||
};
|
||||
addif = member :
|
||||
oneshot {
|
||||
name = "add-${member.device}-to-br-${primary.device}";
|
||||
up = "${ifwait}/bin/ifwait ${member.device} running && ip link set dev ${member.device} master ${primary.device}";
|
||||
down = "ip link set dev ${member.device} nomaster";
|
||||
dependencies = [ primary member ];
|
||||
};
|
||||
|
||||
in (bundle {
|
||||
name = "bridge-${primary.device}-members";
|
||||
contents = [ primary ] ++ map addif members;
|
||||
}) // { device = primary.device; }
|
||||
@@ -42,6 +42,38 @@ in {
|
||||
ranges = mkOption {
|
||||
type = types.listOf types.str;
|
||||
};
|
||||
hosts = mkOption {
|
||||
default = {};
|
||||
type = types.attrsOf (types.submodule {
|
||||
options = {
|
||||
mac = mkOption {
|
||||
description = ''
|
||||
MAC or other hardware address to match on. For Ethernet
|
||||
this is a 48 bit address represented as colon-separated
|
||||
hex bytes, or "id:clientid" to match a presented
|
||||
client id (IPv6 DUID)
|
||||
'';
|
||||
type = types.str;
|
||||
example = "01:20:31:4a:50";
|
||||
};
|
||||
v4 = mkOption {
|
||||
description = "IPv4 address to assign to this client";
|
||||
example = "192.0.2.1";
|
||||
type = types.str;
|
||||
};
|
||||
v6 = mkOption {
|
||||
type = types.listOf types.str;
|
||||
description = "IPv6 addresses or interface-ids to assign to this client";
|
||||
default = [];
|
||||
example = [ "fe80::42:1eff:fefd:b341" "::1234"];
|
||||
};
|
||||
leasetime = mkOption {
|
||||
type = types.int;
|
||||
default = 86400;
|
||||
};
|
||||
};
|
||||
});
|
||||
};
|
||||
domain = mkOption {
|
||||
# this can be given multiple times so probably should be
|
||||
# domains plural and list of string
|
||||
|
||||
@@ -10,13 +10,17 @@
|
||||
, domain
|
||||
, group
|
||||
, ranges
|
||||
, hosts
|
||||
, upstreams
|
||||
, resolvconf
|
||||
}:
|
||||
let
|
||||
name = "${interface.device}.dnsmasq";
|
||||
name = "${interface.name}.dnsmasq";
|
||||
inherit (liminix.services) longrun;
|
||||
inherit (lib) concatStringsSep;
|
||||
inherit (lib) concatStrings concatStringsSep mapAttrsToList;
|
||||
hostOpt = name : { mac, v4, v6, leasetime } @ attrs:
|
||||
let v6s = concatStrings (map (a : ",[${a}]") v6);
|
||||
in "--dhcp-host=${mac},${v4}${v6s},${name},${builtins.toString leasetime}";
|
||||
in
|
||||
longrun {
|
||||
inherit name;
|
||||
@@ -27,12 +31,13 @@ longrun {
|
||||
--user=${user} \
|
||||
--domain=${domain} \
|
||||
--group=${group} \
|
||||
--interface=${interface.device} \
|
||||
--interface=$(output ${interface} ifname) \
|
||||
${lib.concatStringsSep " " (builtins.map (r: "--dhcp-range=${r}") ranges)} \
|
||||
${lib.concatStringsSep " " (builtins.map (r: "--server=${r}") upstreams)} \
|
||||
--keep-in-foreground \
|
||||
--dhcp-authoritative \
|
||||
${if resolvconf != null then "--resolv-file=$(output_path ${resolvconf} resolv.conf)" else "--no-resolv"} \
|
||||
${lib.concatStringsSep " " (mapAttrsToList hostOpt hosts)} \
|
||||
--no-hosts \
|
||||
--log-dhcp \
|
||||
--enable-ra \
|
||||
|
||||
@@ -54,6 +54,7 @@ in {
|
||||
'';
|
||||
flashimage =
|
||||
let o = config.system.outputs; in
|
||||
# could use trivial-builders.linkFarmFromDrvs here?
|
||||
pkgs.runCommand "flashimage" {} ''
|
||||
mkdir $out
|
||||
cd $out
|
||||
|
||||
@@ -17,7 +17,7 @@ let
|
||||
# we'll add them as top-level attributes and rename params to
|
||||
# extraParams
|
||||
|
||||
name = "${interface.device}.hostapd";
|
||||
name = "${interface.name}.hostapd";
|
||||
defaults = {
|
||||
driver = "nl80211";
|
||||
logger_syslog = "-1";
|
||||
@@ -35,5 +35,5 @@ let
|
||||
in longrun {
|
||||
inherit name;
|
||||
dependencies = [ interface ];
|
||||
run = "${hostapd}/bin/hostapd -i ${interface.device} -P /run/${name}.pid -S ${conf}";
|
||||
run = "${hostapd}/bin/hostapd -i $(output ${interface} ifname) -P /run/${name}.pid -S ${conf}";
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
## Mount
|
||||
##
|
||||
## Mount filesystems
|
||||
|
||||
|
||||
{ lib, pkgs, config, ...}:
|
||||
let
|
||||
inherit (lib) mkOption types;
|
||||
inherit (pkgs) liminix;
|
||||
mkBoolOption = description : mkOption {
|
||||
type = types.bool;
|
||||
inherit description;
|
||||
default = true;
|
||||
};
|
||||
|
||||
in {
|
||||
options = {
|
||||
system.service.mount = mkOption {
|
||||
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";
|
||||
};
|
||||
};
|
||||
};
|
||||
config.programs.busybox = {
|
||||
applets = ["blkid" "findfs"];
|
||||
options = {
|
||||
FEATURE_BLKID_TYPE = "y";
|
||||
FEATURE_MOUNT_FLAGS = "y";
|
||||
FEATURE_MOUNT_LABEL = "y";
|
||||
FEATURE_VOLUMEID_EXT = "y";
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
liminix
|
||||
, lib
|
||||
}:
|
||||
{ device, 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}
|
||||
'';
|
||||
down = "umount ${mountpoint}";
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
liminix
|
||||
, ifwait
|
||||
, serviceFns
|
||||
, lib
|
||||
}:
|
||||
{interface, family, address, prefixLength} :
|
||||
let
|
||||
inherit (liminix.services) oneshot;
|
||||
# rather depending on the assumption that nobody will
|
||||
# ever add two addresses which are the same but with different
|
||||
# prefixes, or the same but different protocols
|
||||
name = "${interface.name}.a.${address}";
|
||||
up = ''
|
||||
. ${serviceFns}
|
||||
dev=$(output ${interface} ifname)
|
||||
ip address add ${address}/${toString prefixLength} dev $dev
|
||||
(in_outputs ${name}
|
||||
echo ${address} > address
|
||||
echo ${toString prefixLength} > prefix-length
|
||||
echo ${family} > family
|
||||
echo $dev > ifname
|
||||
)
|
||||
'';
|
||||
in oneshot {
|
||||
inherit name up;
|
||||
down = "true"; # this has been broken for ~ ages
|
||||
dependencies = [ interface ];
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
## Network
|
||||
## =======
|
||||
##
|
||||
## Basic network services for creating hardware ethernet devices
|
||||
## and adding addresses
|
||||
|
||||
|
||||
{ lib, pkgs, config, ...}:
|
||||
let
|
||||
inherit (lib) mkOption types;
|
||||
inherit (pkgs) liminix;
|
||||
inherit (pkgs.liminix.services) bundle;
|
||||
in {
|
||||
options = {
|
||||
system.service.network = {
|
||||
link = mkOption {
|
||||
description = "hardware network interface";
|
||||
type = liminix.lib.types.serviceDefn;
|
||||
};
|
||||
address = mkOption {
|
||||
description = "network interface address";
|
||||
type = liminix.lib.types.serviceDefn;
|
||||
};
|
||||
route = mkOption {
|
||||
type = liminix.lib.types.serviceDefn;
|
||||
};
|
||||
forward = mkOption {
|
||||
type = liminix.lib.types.serviceDefn;
|
||||
};
|
||||
dhcp = {
|
||||
client = mkOption {
|
||||
# this needs to move to its own service as it has
|
||||
# busybox config
|
||||
description = "DHCP v4 client";
|
||||
type = liminix.lib.types.serviceDefn;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
config = {
|
||||
hardware.networkInterfaces = {
|
||||
lo =
|
||||
let
|
||||
net = config.system.service.network;
|
||||
iface = net.link.build { ifname = "lo";};
|
||||
in bundle {
|
||||
name = "loopback";
|
||||
contents = [
|
||||
( net.address.build {
|
||||
interface = iface;
|
||||
family = "inet";
|
||||
address ="127.0.0.1";
|
||||
prefixLength = 8;
|
||||
})
|
||||
( net.address.build {
|
||||
interface = iface;
|
||||
family = "inet6";
|
||||
address = "::1";
|
||||
prefixLength = 128;
|
||||
})
|
||||
];
|
||||
};
|
||||
};
|
||||
services.loopback = config.hardware.networkInterfaces.lo;
|
||||
|
||||
system.service.network = {
|
||||
link = liminix.callService ./link.nix {
|
||||
ifname = mkOption {
|
||||
type = types.str;
|
||||
example = "eth0";
|
||||
};
|
||||
# other "ip link add" options could go here as well
|
||||
mtu = mkOption {
|
||||
type = types.nullOr types.int;
|
||||
example = 1480;
|
||||
};
|
||||
};
|
||||
address = liminix.callService ./address.nix {
|
||||
interface = mkOption {
|
||||
type = liminix.lib.types.service;
|
||||
};
|
||||
family = mkOption {
|
||||
type = types.enum [ "inet" "inet6" ];
|
||||
};
|
||||
address = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
prefixLength = mkOption {
|
||||
type = types.ints.between 0 128;
|
||||
};
|
||||
};
|
||||
|
||||
route = liminix.callService ./route.nix {
|
||||
interface = mkOption {
|
||||
type = types.nullOr liminix.lib.types.interface;
|
||||
default = null;
|
||||
description = "Interface to route through. May be omitted if it can be inferred from \"via\"";
|
||||
};
|
||||
target = mkOption {
|
||||
type = types.str;
|
||||
description = "host or network to add route to";
|
||||
};
|
||||
via = mkOption {
|
||||
type = types.str;
|
||||
description = "address of next hop";
|
||||
};
|
||||
metric = mkOption {
|
||||
type = types.int;
|
||||
description = "route metric";
|
||||
default = 100;
|
||||
};
|
||||
};
|
||||
|
||||
forward = liminix.callService ./forward.nix {
|
||||
enableIPv4 = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
};
|
||||
enableIPv6 = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
};
|
||||
};
|
||||
|
||||
dhcp.client = liminix.callService ./dhcpc.nix {
|
||||
interface = mkOption {
|
||||
type = liminix.lib.types.service;
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
{
|
||||
liminix
|
||||
, writeAshScript
|
||||
, serviceFns
|
||||
, lib
|
||||
} :
|
||||
{ interface }:
|
||||
let
|
||||
inherit (liminix.services) longrun;
|
||||
name = "${interface.name}.dhcpc";
|
||||
script = writeAshScript "dhcp-notify" { } ''
|
||||
. ${serviceFns}
|
||||
exec 2>&1
|
||||
action=$1
|
||||
|
||||
set_address() {
|
||||
ip address replace $ip/$mask dev $interface
|
||||
(in_outputs ${name}
|
||||
for i in lease mask ip router siaddr dns serverid subnet opt53 interface ; do
|
||||
printenv $i > $i
|
||||
done)
|
||||
}
|
||||
case $action in
|
||||
deconfig)
|
||||
ip address flush $interface
|
||||
ip link set up dev $interface
|
||||
;;
|
||||
bound)
|
||||
# this doesn't actually replace, it adds a new address.
|
||||
set_address
|
||||
echo >/proc/self/fd/10
|
||||
;;
|
||||
renew)
|
||||
set_address
|
||||
;;
|
||||
nak)
|
||||
echo "received NAK on $interface"
|
||||
;;
|
||||
esac
|
||||
'';
|
||||
in longrun {
|
||||
inherit name;
|
||||
run = "/bin/udhcpc -f -i $(output ${interface} ifname) -x hostname:$(cat /proc/sys/kernel/hostname) -s ${script}";
|
||||
notification-fd = 10;
|
||||
dependencies = [ interface ];
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
liminix
|
||||
, ifwait
|
||||
, serviceFns
|
||||
, lib
|
||||
}:
|
||||
{ enableIPv4, enableIPv6 }:
|
||||
let
|
||||
inherit (liminix.services) oneshot;
|
||||
ip4 = "/proc/sys/net/ipv4/conf/all/forwarding";
|
||||
ip6 = "/proc/sys/net/ipv6/conf/all/forwarding";
|
||||
opt = lib.optionalString;
|
||||
sysctls = b :
|
||||
""
|
||||
+ opt enableIPv4 "echo ${b} > ${ip4}\n"
|
||||
+ opt enableIPv6 "echo ${b} > ${ip6}\n";
|
||||
in oneshot {
|
||||
name = "forwarding${opt enableIPv4 "4"}${opt enableIPv6 "6"}";
|
||||
up = sysctls "1";
|
||||
down = sysctls "0";
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
liminix
|
||||
, ifwait
|
||||
, serviceFns
|
||||
, lib
|
||||
}:
|
||||
{ifname, mtu} :
|
||||
let
|
||||
inherit (liminix.services) longrun oneshot;
|
||||
inherit (lib) concatStringsSep;
|
||||
name = "${ifname}.link";
|
||||
up = liminix.networking.ifup name ifname;
|
||||
in oneshot {
|
||||
inherit name up;
|
||||
down = "ip link set down dev ${ifname}";
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
liminix
|
||||
, ifwait
|
||||
, serviceFns
|
||||
, lib
|
||||
}:
|
||||
{ target, via, interface ? null, metric }:
|
||||
let
|
||||
inherit (liminix.services) oneshot;
|
||||
with_dev = if interface != null then "dev $(output ${interface} ifname)" else "";
|
||||
in oneshot {
|
||||
name = "route-${target}-${builtins.substring 0 12 (builtins.hashString "sha256" "${via}-${if interface!=null then interface.name else ""}")}";
|
||||
up = ''
|
||||
ip route add ${target} via ${via} metric ${toString metric} ${with_dev}
|
||||
'';
|
||||
down = ''
|
||||
ip route del ${target} via ${via} ${with_dev}
|
||||
'';
|
||||
dependencies = [] ++ lib.optional (interface != null) interface;
|
||||
}
|
||||
@@ -22,7 +22,7 @@ let
|
||||
++
|
||||
(mapAttrsToList (name: opts: "peer ${name} ${concatStringsSep "" opts}")
|
||||
p.peers)
|
||||
++ [ "user ${p.user}" ]
|
||||
++ lib.optional (p.user != null) "user ${p.user}"
|
||||
++ (lib.optional (p.makestep != null) "makestep ${toString p.makestep.threshold} ${toString p.makestep.limit}")
|
||||
++ (map (n: "allow ${n}") p.allow)
|
||||
++ (lib.optional (p.bindaddress != null) "bindaddress ${p.bindaddress}")
|
||||
|
||||
@@ -75,6 +75,7 @@ in
|
||||
inherit kernel;
|
||||
inherit dtb;
|
||||
};
|
||||
# could use trivial-builders.linkFarmFromDrvs here?
|
||||
vmroot = pkgs.runCommand "qemu" {} ''
|
||||
mkdir $out
|
||||
cd $out
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
{ interface, ppp-options }:
|
||||
let
|
||||
inherit (liminix.services) longrun;
|
||||
name = "${interface.device}.pppoe";
|
||||
name = "${interface.name}.pppoe";
|
||||
ip-up = writeAshScript "ip-up" {} ''
|
||||
. ${serviceFns}
|
||||
(in_outputs ${name}
|
||||
@@ -42,7 +42,10 @@ let
|
||||
in
|
||||
longrun {
|
||||
inherit name;
|
||||
run = "${ppp}/bin/pppd pty '${pppoe}/bin/pppoe -I ${interface.device}' ${lib.concatStringsSep " " ppp-options'}" ;
|
||||
run = ''
|
||||
. ${serviceFns}
|
||||
${ppp}/bin/pppd pty "${pppoe}/bin/pppoe -I $(output ${interface} ifname)" ${lib.concatStringsSep " " ppp-options'}
|
||||
'';
|
||||
notification-fd = 10;
|
||||
dependencies = [ interface ];
|
||||
}
|
||||
|
||||
+14
-3
@@ -7,10 +7,21 @@ let
|
||||
s6-linux-init
|
||||
stdenvNoCC;
|
||||
inherit (pkgs.pseudofile) dir symlink;
|
||||
inherit (pkgs.liminix.services) bundle;
|
||||
|
||||
s6-rc-db = pkgs.s6-rc-database.override {
|
||||
services = builtins.attrValues config.services;
|
||||
};
|
||||
s6-rc-db =
|
||||
let
|
||||
defaultDefaultTarget = bundle {
|
||||
name = "default";
|
||||
contents = builtins.attrValues config.services;
|
||||
};
|
||||
servicesAttrs = {
|
||||
default = defaultDefaultTarget;
|
||||
} // config.services;
|
||||
in
|
||||
pkgs.s6-rc-database.override {
|
||||
services = builtins.attrValues servicesAttrs;
|
||||
};
|
||||
s6-init-scripts = stdenvNoCC.mkDerivation {
|
||||
name = "s6-scripts";
|
||||
src = ./scripts;
|
||||
|
||||
@@ -21,7 +21,7 @@ mount -t sysfs none /sys
|
||||
mkdir /dev/pts
|
||||
mount -t devpts none /dev/pts
|
||||
|
||||
mkdir -m 0750 /run/service-state
|
||||
mkdir -m 0751 /run/service-state
|
||||
chgrp system /run/service-state
|
||||
|
||||
### If your services are managed by s6-rc:
|
||||
|
||||
@@ -12,5 +12,9 @@ in
|
||||
config = mkIf (config.rootfsType == "squashfs") {
|
||||
system.outputs.rootfs =
|
||||
liminix.builders.squashfs config.filesystem.contents;
|
||||
kernel.config = {
|
||||
SQUASHFS = "y";
|
||||
SQUASHFS_XZ = "y";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -35,6 +35,8 @@ longrun {
|
||||
if test -d /persist; then
|
||||
mkdir -p /persist/secrets/dropbear
|
||||
ln -s /persist/secrets/dropbear /run
|
||||
else
|
||||
mkdir -p /run/dropbear
|
||||
fi
|
||||
. /etc/profile # sets PATH but do we need this? it's the same file as ashrc
|
||||
exec env -i ENV=/etc/ashrc PATH=$PATH ${dropbear}/bin/dropbear ${concatStringsSep " " options}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
## VLAN
|
||||
## ====
|
||||
##
|
||||
## Virtual LANs give you the ability to sub-divide a LAN. Linux can
|
||||
## accept VLAN tagged traffic and presents each VLAN ID as a
|
||||
## different network interface (eg: eth0.100 for VLAN ID 100)
|
||||
##
|
||||
## Some Liminix devices with multiple ethernet ports are implemented
|
||||
## using a network switch connecting the physical ports to the CPU,
|
||||
## and require using VLAN in order to send different traffic to
|
||||
## different ports (e.g. LAN vs WAN)
|
||||
|
||||
{ lib, pkgs, config, ...}:
|
||||
let
|
||||
inherit (lib) mkOption types;
|
||||
inherit (pkgs.liminix.services) oneshot;
|
||||
inherit (pkgs) liminix;
|
||||
in
|
||||
{
|
||||
options = {
|
||||
system.service.vlan = mkOption { type = liminix.lib.types.serviceDefn; };
|
||||
};
|
||||
config.system.service.vlan = liminix.callService ./service.nix {
|
||||
ifname = mkOption {
|
||||
type = types.str;
|
||||
description = "interface name to create";
|
||||
};
|
||||
primary = mkOption {
|
||||
description = "existing physical interface";
|
||||
type = liminix.lib.types.interface;
|
||||
};
|
||||
vid = mkOption {
|
||||
description = "VLAN identifier (VID) in range 1-4094";
|
||||
type = types.str;
|
||||
};
|
||||
};
|
||||
config.kernel.config = {
|
||||
VLAN_8021Q = "y";
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
liminix
|
||||
, lib
|
||||
}:
|
||||
{ ifname, primary, vid } :
|
||||
let
|
||||
inherit (liminix.services) oneshot;
|
||||
in oneshot rec {
|
||||
name = "${ifname}.link";
|
||||
up = ''
|
||||
ip link add link $(output ${primary} ifname) name ${ifname} type vlan id ${vid}
|
||||
${liminix.networking.ifup name ifname}
|
||||
(in_outputs ${name}
|
||||
echo ${ifname} > ifname
|
||||
)
|
||||
'';
|
||||
down = "ip link set down dev ${ifname}";
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
## Watchdog
|
||||
##
|
||||
## Enable hardware watchdog (for devices that support one) and
|
||||
## feed it by checking the health of specified critical services.
|
||||
## If the watchdog feeder stops, the device will reboot.
|
||||
|
||||
{ lib, pkgs, config, ...}:
|
||||
let
|
||||
inherit (lib) mkOption types;
|
||||
inherit (pkgs) liminix;
|
||||
in
|
||||
{
|
||||
options = {
|
||||
system.service.watchdog = mkOption {
|
||||
type = liminix.lib.types.serviceDefn;
|
||||
};
|
||||
};
|
||||
config.system.service.watchdog = liminix.callService ./watchdog.nix {
|
||||
watched = mkOption {
|
||||
description = "services to watch";
|
||||
type = types.listOf liminix.lib.types.service;
|
||||
};
|
||||
headStart = mkOption {
|
||||
description = "delay in seconds before watchdog starts checking service health";
|
||||
default = 60;
|
||||
type = types.int;
|
||||
};
|
||||
};
|
||||
config.kernel.config.WATCHDOG = "y";
|
||||
}
|
||||
Executable
+23
@@ -0,0 +1,23 @@
|
||||
#!/bin/sh
|
||||
deadline=$(expr $(date +%s) + ${HEADSTART})
|
||||
services=$@
|
||||
echo started feeding the dog
|
||||
exec 3> ${WATCHDOG-/dev/watchdog}
|
||||
|
||||
healthy(){
|
||||
test $(date +%s) -le $deadline && return 0
|
||||
|
||||
for i in $services; do
|
||||
if test "$(s6-svstat -o up /run/service/$i)" != "true" ; then
|
||||
echo "service $i is down"
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
while healthy ;do
|
||||
sleep 10
|
||||
echo >&3
|
||||
done
|
||||
echo "stopped feeding the dog"
|
||||
sleep 6000 # don't want s6-rc to restart
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
liminix
|
||||
, lib
|
||||
}:
|
||||
{ watched, headStart } :
|
||||
let
|
||||
inherit (liminix.services) longrun;
|
||||
in longrun {
|
||||
name = "watchdog";
|
||||
run =
|
||||
"HEADSTART=${toString headStart} ${./gaspode.sh} ${lib.concatStringsSep " " (builtins.map (s: s.name) watched)}";
|
||||
}
|
||||
+13
@@ -170,5 +170,18 @@ extraPkgs // {
|
||||
'';
|
||||
});
|
||||
|
||||
openssl = prev.openssl.overrideAttrs (o: {
|
||||
# we want to apply
|
||||
# https://patch-diff.githubusercontent.com/raw/openssl/openssl/pull/20273.patch";
|
||||
# which disables overriding the -march cflags to the wrong values,
|
||||
# but openssl is used for bootstrapping so that's easier said than
|
||||
# done. Do it the ugly way..
|
||||
postPatch =
|
||||
o.postPatch
|
||||
+ (with final;
|
||||
lib.optionalString (stdenv.buildPlatform != stdenv.hostPlatform)
|
||||
"\nsed -i.bak 's/linux.*-mips/linux-mops/' Configure\n");
|
||||
});
|
||||
|
||||
pppBuild = prev.ppp;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ in stdenv.mkDerivation {
|
||||
version = "0.1";
|
||||
src = ./.;
|
||||
nativeBuildInputs = [ fennel ];
|
||||
buildInputs = with lua.pkgs; [ luafilesystem ];
|
||||
buildPhase = ''
|
||||
for f in *.fnl ; do
|
||||
fennel --compile $f > `basename $f .fnl`.lua
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
a11
|
||||
@@ -0,0 +1 @@
|
||||
a33
|
||||
@@ -0,0 +1 @@
|
||||
a55
|
||||
@@ -0,0 +1 @@
|
||||
a66
|
||||
@@ -0,0 +1 @@
|
||||
000000
|
||||
@@ -0,0 +1 @@
|
||||
0000ff
|
||||
@@ -0,0 +1 @@
|
||||
00ff00
|
||||
@@ -0,0 +1 @@
|
||||
ff0000
|
||||
@@ -0,0 +1 @@
|
||||
eth1
|
||||
@@ -0,0 +1,36 @@
|
||||
(local lfs (require :lfs))
|
||||
|
||||
(fn directory? [pathname]
|
||||
(= (lfs.symlinkattributes pathname :mode) "directory"))
|
||||
|
||||
(fn mktree [pathname]
|
||||
(if (or (= pathname "") (= pathname "/"))
|
||||
(error (.. "can't mkdir " pathname)))
|
||||
|
||||
(or (directory? pathname)
|
||||
(let [parent (string.gsub pathname "/[^/]+/?$" "")]
|
||||
(or (directory? parent) (mktree parent))
|
||||
(assert (lfs.mkdir pathname)))))
|
||||
|
||||
(fn rmtree [pathname]
|
||||
(case (lfs.symlinkattributes pathname)
|
||||
nil true
|
||||
{:mode "directory"}
|
||||
(do
|
||||
(each [f (lfs.dir pathname)]
|
||||
(when (not (or (= f ".") (= f "..")))
|
||||
(rmtree ( .. pathname "/" f)))
|
||||
(lfs.rmdir pathname)))
|
||||
{:mode "file"}
|
||||
(os.remove pathname)
|
||||
{:mode "link"}
|
||||
(os.remove pathname)
|
||||
unknown
|
||||
(error (.. "can't remove " pathname " of kind \"" unknown.mode "\""))))
|
||||
|
||||
|
||||
{
|
||||
: mktree
|
||||
: rmtree
|
||||
: directory?
|
||||
}
|
||||
+51
-2
@@ -2,6 +2,9 @@
|
||||
(collect [k v (pairs table2) &into table1]
|
||||
k v))
|
||||
|
||||
(fn dup [table]
|
||||
(collect [k v (pairs table)] k v))
|
||||
|
||||
(fn split [sep string]
|
||||
(icollect [v (string.gmatch string (.. "([^" sep "]+)"))]
|
||||
v))
|
||||
@@ -11,6 +14,52 @@
|
||||
f (do (f:close) true)
|
||||
_ false))
|
||||
|
||||
(fn system [s] (assert (os.execute s)))
|
||||
(fn system [s]
|
||||
(match (os.execute s)
|
||||
res res
|
||||
(nil err) (error (.. "Error executing \"" s "\" (" err ")"))))
|
||||
|
||||
{ : merge : split : file-exists? : system }
|
||||
(fn hash [str]
|
||||
(accumulate [h 5381
|
||||
c (str:gmatch ".")]
|
||||
(+ (* h 33) (string.byte c))))
|
||||
|
||||
|
||||
(local
|
||||
base64-indices
|
||||
(doto [
|
||||
"B" "C" "D" "E" "F" "G" "H" "I" "J" "K" "L" "M" "N" "O" "P"
|
||||
"Q" "R" "S" "T" "U" "V" "W" "X" "Y" "Z" "a" "b" "c" "d" "e" "f"
|
||||
"g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s" "t" "u" "v"
|
||||
"w" "x" "y" "z" "0" "1" "2" "3" "4" "5" "6" "7" "8" "9" "-" "_"
|
||||
]
|
||||
(tset 0 "A")))
|
||||
|
||||
;; local function base64(s)
|
||||
;; local byte, rep = string.byte, string.rep
|
||||
;; local pad = 2 - ((#s-1) % 3)
|
||||
;; s = (s..rep('\0', pad)):gsub("...", function(cs)
|
||||
;; local a, b, c = byte(cs, 1, 3)
|
||||
;; return bs[a>>2] .. bs[(a&3)<<4|b>>4] .. bs[(b&15)<<2|c>>6] .. bs[c&63]
|
||||
;; end)
|
||||
;; return s:sub(1, #s-pad) .. rep('=', pad)
|
||||
;; end
|
||||
|
||||
(fn base64url [s]
|
||||
"URL-safe Base64-encoded form of s (no trailing padding)"
|
||||
(let [pad (- 2 (% (- (# s) 1) 3))
|
||||
bs base64-indices
|
||||
blank (string.rep "\0" pad)
|
||||
s (-> (.. s blank)
|
||||
(: :gsub
|
||||
"..."
|
||||
(fn [cs]
|
||||
(let [(a b c) (string.byte cs 1 3)]
|
||||
(.. (. bs (rshift a 2))
|
||||
(. bs (bor (lshift (band a 3) 4) (rshift b 4)))
|
||||
(. bs (bor (lshift (band b 15) 2) (rshift c 6)))
|
||||
(. bs (band c 63)))))))]
|
||||
(s:sub 1 (- (# s) pad))))
|
||||
|
||||
|
||||
{ : merge : split : file-exists? : system : hash : base64url : dup }
|
||||
|
||||
+27
-2
@@ -1,5 +1,7 @@
|
||||
(local inotify (require :inotify))
|
||||
(local { : file-exists? } (require :anoia))
|
||||
(local { : directory? } (require :anoia.fs))
|
||||
(local lfs (require :lfs))
|
||||
|
||||
(fn read-line [name]
|
||||
(with-open [f (assert (io.open name :r) (.. "can't open file " name))]
|
||||
@@ -17,15 +19,38 @@
|
||||
inotify.IN_CLOSE_WRITE)
|
||||
handle))
|
||||
|
||||
(fn read-value [pathname]
|
||||
(case (lfs.symlinkattributes pathname)
|
||||
nil nil
|
||||
{:mode "directory"}
|
||||
(collect [f (lfs.dir pathname)]
|
||||
(when (not (or (= f ".") (= f "..")))
|
||||
(values f (read-value ( .. pathname "/" f)))))
|
||||
{:mode "file"}
|
||||
(read-line pathname)
|
||||
{:mode "link"}
|
||||
(read-line pathname)
|
||||
unknown
|
||||
(error (.. "can't read " pathname " of kind \"" unknown.mode "\""))))
|
||||
|
||||
(fn events [self]
|
||||
(coroutine.wrap
|
||||
#(while true
|
||||
(while (not (self:ready?)) (self:wait))
|
||||
(coroutine.yield self)
|
||||
(self:wait))))
|
||||
|
||||
(fn open [directory]
|
||||
(let [watcher (watch-fsevents directory)
|
||||
has-file? (fn [filename] (file-exists? (.. directory "/" filename)))]
|
||||
{
|
||||
:wait (fn [] (watcher:read))
|
||||
:wait #(watcher:read)
|
||||
:ready? (fn [self]
|
||||
(and (has-file? "state") (not (has-file? ".lock"))))
|
||||
:output (fn [_ filename] (read-line (.. directory "/" filename)))
|
||||
:output (fn [_ filename]
|
||||
(read-value (.. directory "/" filename)))
|
||||
:close #(watcher:close)
|
||||
: events
|
||||
}))
|
||||
|
||||
{ : open }
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
(local svc (require :anoia.svc))
|
||||
(local { : view } (require :fennel))
|
||||
|
||||
(local ex (svc.open "./example-output"))
|
||||
|
||||
(assert (= (ex:output "name") "eth1"))
|
||||
|
||||
(assert (=
|
||||
(table.concat (ex:output "colours"))
|
||||
(table.concat { :red "ff0000" :green "00ff00" :blu "0000ff" :black "000000" })))
|
||||
|
||||
(assert (=
|
||||
(table.concat (ex:output "addresses"))
|
||||
(table.concat {:1 {:attribute "a11"}
|
||||
:3 {:attribute "a33"}
|
||||
:5 {:attribute "a55"}
|
||||
:6 {:attribute "a66"}})))
|
||||
@@ -0,0 +1,9 @@
|
||||
(local { : hash : base64url } (require :anoia))
|
||||
|
||||
(assert (= (hash "") 5381))
|
||||
|
||||
;; these examples from https://theartincode.stanis.me/008-djb2/
|
||||
(assert (= (hash "Hello") 210676686969))
|
||||
(assert (= (hash "Hello!") 6952330670010))
|
||||
|
||||
(assert (= (base64url "hello world") "aGVsbG8gd29ybGQ"))
|
||||
@@ -52,6 +52,7 @@ in {
|
||||
};
|
||||
};
|
||||
writeFennelScript = callPackage ./write-fennel-script {};
|
||||
writeFennel = callPackage ./write-fennel {};
|
||||
writeAshScript = callPackage ./write-ash-script {};
|
||||
systemconfig = callPackage ./systemconfig {};
|
||||
s6-init-bin = callPackage ./s6-init-bin {};
|
||||
|
||||
@@ -14,6 +14,7 @@ let packages = [
|
||||
linotify
|
||||
anoia
|
||||
fennel
|
||||
lua.pkgs.luafilesystem
|
||||
];
|
||||
join = ps: builtins.concatStringsSep ";" ps;
|
||||
luapath = join (builtins.map (f:
|
||||
@@ -27,14 +28,15 @@ in writeScriptBin "fennelrepl" ''
|
||||
package.path = ${lib.strings.escapeShellArg luapath} .. ";" .. package.path
|
||||
package.cpath = ${lib.strings.escapeShellArg luacpath} .. ";" .. (package.cpath or "")
|
||||
local fennel = require "fennel"
|
||||
fennel.install()
|
||||
table.insert(package.loaders or package.searchers,1, fennel.searcher)
|
||||
|
||||
local more_fennel = os.getenv("FENNEL_PATH")
|
||||
if more_fennel then
|
||||
fennel.path = more_fennel .. ";" .. fennel.path
|
||||
end
|
||||
if #arg > 0 then
|
||||
script = table.remove(arg, 1)
|
||||
fennel.dofile(script, {}, arg)
|
||||
fennel.dofile(script, {correlate = true}, arg)
|
||||
else
|
||||
fennel.repl()
|
||||
end
|
||||
|
||||
@@ -1,53 +1,14 @@
|
||||
{
|
||||
callPackage
|
||||
, liminix
|
||||
, ifwait
|
||||
, lib
|
||||
ifwait
|
||||
, serviceFns
|
||||
}:
|
||||
let
|
||||
inherit (liminix.services) oneshot longrun;
|
||||
inherit (lib) concatStringsSep optional;
|
||||
in {
|
||||
interface = { type ? "hardware", device, link ? null, primary ? null, id ? null, dependencies ? [] } @ args:
|
||||
let name = "${device}.link";
|
||||
ups =
|
||||
[]
|
||||
++ optional (type == "bridge")
|
||||
"ip link add name ${device} type bridge"
|
||||
++ optional (type == "vlan")
|
||||
"ip link add link ${link} name ${device} type vlan id ${id}"
|
||||
++ ["${ifwait}/bin/ifwait -v ${device} present"]
|
||||
++ ["ip link set up dev ${device}"]
|
||||
++ optional (primary != null)
|
||||
"ip link set dev ${device} master ${primary.device}";
|
||||
in oneshot {
|
||||
inherit name;
|
||||
up = lib.concatStringsSep "\n" ups;
|
||||
down = "ip link set down dev ${device}";
|
||||
dependencies = dependencies ++ lib.optional (primary != null) primary;
|
||||
} // {
|
||||
inherit device;
|
||||
};
|
||||
address = interface: { family, dependencies ? [], prefixLength, address } @ args:
|
||||
let inherit (builtins) toString;
|
||||
in oneshot {
|
||||
dependencies = [ interface ] ++ dependencies;
|
||||
name = "${interface.device}.addr.${address}";
|
||||
up = "ip address add ${address}/${toString prefixLength} dev ${interface.device} ";
|
||||
down = "ip address del ${address}/${toString prefixLength} dev ${interface.device} ";
|
||||
} // {
|
||||
inherit (interface) device;
|
||||
};
|
||||
route = { name, target, via, dependencies, dev ? null }:
|
||||
let with_dev = if dev != null then "dev ${dev}" else "";
|
||||
in oneshot {
|
||||
inherit name;
|
||||
up = ''
|
||||
ip route add ${target} via ${via} ${with_dev}
|
||||
'';
|
||||
down = ''
|
||||
ip route del ${target} via ${via} ${with_dev}
|
||||
'';
|
||||
inherit dependencies;
|
||||
};
|
||||
{
|
||||
ifup = name : ifname : ''
|
||||
. ${serviceFns}
|
||||
${ifwait}/bin/ifwait -v ${ifname} present
|
||||
ip link set up dev ${ifname}
|
||||
(in_outputs ${name}
|
||||
echo ${ifname} > ifname
|
||||
)
|
||||
'';
|
||||
}
|
||||
|
||||
@@ -9,13 +9,18 @@
|
||||
}:
|
||||
let
|
||||
inherit (builtins) concatStringsSep;
|
||||
output = service: name: "/run/service-state/${service.name}/${name}";
|
||||
prefix = "/run/service-state";
|
||||
output = service: name: "${prefix}/${service.name}/${name}";
|
||||
serviceScript = commands : ''
|
||||
#!/bin/sh
|
||||
exec 2>&1
|
||||
. ${serviceFns}
|
||||
${commands}
|
||||
'';
|
||||
cleanupScript = name : ''
|
||||
#!/bin/sh
|
||||
test -d ${prefix}/${name} && rm -rf ${prefix}/${name}
|
||||
'';
|
||||
service = {
|
||||
name
|
||||
, serviceType
|
||||
@@ -61,21 +66,23 @@ let
|
||||
in service (args // {
|
||||
buildInputs = [ logger ];
|
||||
serviceType = "longrun";
|
||||
run = serviceScript run;
|
||||
run = serviceScript "${run}\n${cleanupScript name}";
|
||||
producer-for = "${name}-log";
|
||||
});
|
||||
|
||||
oneshot = {
|
||||
name
|
||||
, up
|
||||
, down
|
||||
, down ? ""
|
||||
, outputs ? []
|
||||
, dependencies ? []
|
||||
, ...
|
||||
} @ args : service (args // {
|
||||
serviceType = "oneshot";
|
||||
up = writeScript "${name}-up" (serviceScript up);
|
||||
down= writeScript "${name}-down" (serviceScript down);
|
||||
down = writeScript
|
||||
"${name}-down"
|
||||
"${serviceScript down}\n${cleanupScript name}";
|
||||
});
|
||||
bundle = {
|
||||
name
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
{
|
||||
writeFennelScript
|
||||
, anoia
|
||||
, lua
|
||||
}:
|
||||
writeFennelScript "odhcpc-script" [] ./odhcp6-script.fnl
|
||||
writeFennelScript "odhcpc-script" [anoia lua.pkgs.luafilesystem] ./odhcp6-script.fnl
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
./address/2001-8b0-1111-1111-0-ffff-1234-5678_BVG8Gro50UM/address:2001:8b0:1111:1111:0:ffff:1234:5678
|
||||
./address/2001-8b0-1111-1111-0-ffff-1234-5678_BVG8Gro50UM/len:128
|
||||
./address/2001-8b0-1111-1111-0-ffff-1234-5678_BVG8Gro50UM/preferred:3600
|
||||
./address/2001-8b0-1111-1111-0-ffff-1234-5678_BVG8Gro50UM/valid:7200
|
||||
./addresses:2001:8b0:1111:1111:0:ffff:1234:5678/128,3600,7200
|
||||
./ifname:ppp0
|
||||
./option_1:000300018cfdf02420eb
|
||||
./option_2:000300010df0feca0df0
|
||||
./passthru:00170020200108b0000000000000000000002020200108b0000000000000000000002021
|
||||
./prefix/2001-8b0-de3a-22_hiaEztRS00M/address:2001:8b0:de3a:22::
|
||||
./prefix/2001-8b0-de3a-22_hiaEztRS00M/len:64
|
||||
./prefix/2001-8b0-de3a-22_hiaEztRS00M/preferred:7200
|
||||
./prefix/2001-8b0-de3a-22_hiaEztRS00M/valid:7200
|
||||
./prefix/2001-8b0-de3a-abcd_HmTdEWrIMEM/address:2001:8b0:de3a:abcd::
|
||||
./prefix/2001-8b0-de3a-abcd_HmTdEWrIMEM/len:64
|
||||
./prefix/2001-8b0-de3a-abcd_HmTdEWrIMEM/preferred:7200
|
||||
./prefix/2001-8b0-de3a-abcd_HmTdEWrIMEM/valid:7200
|
||||
./prefixes:2001:8b0:de3a:abcd::/64,7200,7200 2001:8b0:de3a:22::/64,7200,7200
|
||||
./ra_hoplimit:64
|
||||
./ra_mtu:0
|
||||
./ra_reachable:0
|
||||
./ra_retransmit:0
|
||||
./ra_routes:::/0,fe80::203:97ff:fed6:0,65533,512
|
||||
./rdnss:2001:8b0::2020 2001:8b0::2021
|
||||
./server:fe80::203:97ff:fed6:0
|
||||
./state:bound
|
||||
@@ -1,6 +1,8 @@
|
||||
(local { : split : merge : hash : base64url } (require :anoia))
|
||||
(local { : mktree : rmtree } (require :anoia.fs))
|
||||
|
||||
(local state-directory (assert (os.getenv "SERVICE_STATE")))
|
||||
(os.execute (.. "mkdir -p " state-directory))
|
||||
(mktree state-directory)
|
||||
|
||||
(fn write-value [name value]
|
||||
(let [path (.. state-directory "/" name)]
|
||||
@@ -10,10 +12,43 @@
|
||||
(fn write-value-from-env [name]
|
||||
(write-value name (os.getenv (string.upper name))))
|
||||
|
||||
(fn parse-address [str]
|
||||
(fn parse-extra [s]
|
||||
(let [out {}]
|
||||
(each [name val (string.gmatch s ",(.-)=([^,]+)")]
|
||||
(tset out name val))
|
||||
out))
|
||||
(let [(address len preferred valid extra)
|
||||
(string.match str "(.-)/(%d+),(%d+),(%d+)(.*)$")]
|
||||
(merge {: address : len : preferred : valid} (parse-extra extra))))
|
||||
|
||||
(fn write-addresses [prefix addresses]
|
||||
(each [_ a (ipairs (split " " addresses))]
|
||||
(let [address (parse-address a)
|
||||
suffix (base64url (string.pack "n" (hash a)))
|
||||
;; keydir should be a function of all the address
|
||||
;; attributes: we want it to change whenever anything changes
|
||||
;; so that clients can see which addresses are new without
|
||||
;; deep table comparisons
|
||||
keydir (..
|
||||
prefix
|
||||
(-> address.address
|
||||
(: :gsub "::$" "")
|
||||
(: :gsub ":" "-"))
|
||||
"_"
|
||||
suffix)]
|
||||
(mktree (.. state-directory "/" keydir))
|
||||
(each [k v (pairs address)]
|
||||
(write-value (.. keydir "/" k) v)))))
|
||||
|
||||
;; we remove state before updating to ensure that consumers don't get
|
||||
;; a half-updated snapshot
|
||||
(os.remove (.. state-directory "/state"))
|
||||
|
||||
;; remove parsed addresses/prefixes from any previous run
|
||||
(rmtree (.. state-directory "/prefix"))
|
||||
(rmtree (.. state-directory "/address"))
|
||||
|
||||
(let [wanted
|
||||
[
|
||||
:addresses
|
||||
@@ -48,7 +83,10 @@
|
||||
:sntp_fqdn
|
||||
]]
|
||||
(each [_ n (ipairs wanted)]
|
||||
(write-value-from-env n)))
|
||||
(write-value-from-env n))
|
||||
|
||||
(match (os.getenv :ADDRESSES) s (write-addresses "address/" s))
|
||||
(match (os.getenv :PREFIXES) s (write-addresses "prefix/" s)))
|
||||
|
||||
(let [[ifname state] arg
|
||||
ready (match state
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
RA_ADDRESSES=
|
||||
RA_REACHABLE=0
|
||||
USER=root
|
||||
CER=
|
||||
PASSTHRU=00170020200108b0000000000000000000002020200108b0000000000000000000002021
|
||||
SHLVL=2
|
||||
SERVER=fe80::203:97ff:fed6:0
|
||||
HOME=/home/root/
|
||||
RA_MTU=0
|
||||
RA_ROUTES=::/0,fe80::203:97ff:fed6:0,65533,512
|
||||
OPTION_1=000300018cfdf02420eb
|
||||
NTP_FQDN=
|
||||
OPTION_2=000300010df0feca0df0
|
||||
RA_DOMAINS=
|
||||
DOMAINS=
|
||||
LOGNAME=root
|
||||
AFTR=
|
||||
SIP_IP=
|
||||
NTP_IP=
|
||||
PREFIXES="2001:8b0:de3a:abcd::/64,7200,7200 2001:8b0:de3a:22::/64,7200,7200"
|
||||
RA_HOPLIMIT=64
|
||||
SHELL=/bin/sh
|
||||
RA_DNS=
|
||||
RDNSS="2001:8b0::2020 2001:8b0::2021"
|
||||
SNTP_IP=
|
||||
RA_RETRANSMIT=0
|
||||
SIP_DOMAIN=
|
||||
PWD=/home/root
|
||||
ADDRESSES=2001:8b0:1111:1111:0:ffff:1234:5678/128,3600,7200
|
||||
Executable
+21
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env sh
|
||||
set -e
|
||||
|
||||
statedir=$(mktemp -d)
|
||||
|
||||
cleanup(){
|
||||
test -n "$statedir" && test -d $statedir && rm -rf $statedir
|
||||
}
|
||||
trap 'exit 1' INT HUP QUIT TERM ALRM USR1
|
||||
trap 'cleanup' EXIT
|
||||
|
||||
# call the script twice with different state, to test that it cleans
|
||||
# out old values when it runs again
|
||||
|
||||
(set -a; . ./test.env ; ADDRESSES=2001:80:111:111:0:fff:123:567/128,3600,7200 SERVICE_STATE=$statedir fennelrepl odhcp6-script.fnl ppp0 bound) 10>&1
|
||||
|
||||
(set -a; . ./test.env ; SERVICE_STATE=$statedir fennelrepl odhcp6-script.fnl ppp0 bound) 10>&1
|
||||
|
||||
(cd $statedir && find . -type f | xargs grep '' | sort) > actual
|
||||
diff -u expected actual
|
||||
cmp expected actual
|
||||
@@ -4,7 +4,7 @@ writeText "service-fns.sh" ''
|
||||
output_path() { echo $(realpath $1/.outputs)/$2; }
|
||||
mkoutputs() {
|
||||
d=/run/service-state/$1
|
||||
mkdir -m 2750 -p $d && chown root:system $d
|
||||
mkdir -m 2751 -p $d && chown root:system $d
|
||||
echo $d
|
||||
}
|
||||
in_outputs() {
|
||||
|
||||
@@ -2,30 +2,8 @@
|
||||
lua
|
||||
, lib
|
||||
, fennel
|
||||
, writeFennel
|
||||
, stdenv
|
||||
}:
|
||||
name : packages : source :
|
||||
let
|
||||
luapath = builtins.map
|
||||
(f:
|
||||
"${f}/share/lua/${lua.luaversion}/?.lua;" +
|
||||
"${f}/share/lua/${lua.luaversion}/?/init.lua;")
|
||||
packages;
|
||||
luacpath = builtins.map (f: "${f}/lib/lua/${lua.luaversion}/?.so;") packages;
|
||||
in stdenv.mkDerivation {
|
||||
inherit name;
|
||||
src = ./.;
|
||||
nativeBuildInputs = [ fennel ];
|
||||
buildPhase = ''
|
||||
(
|
||||
echo "#!${lua}/bin/lua"
|
||||
echo "package.path = ${lib.strings.escapeShellArg (builtins.concatStringsSep "" luapath)} .. package.path"
|
||||
echo "package.cpath = ${lib.strings.escapeShellArg (builtins.concatStringsSep "" luacpath)} .. package.cpath"
|
||||
fennel --correlate --compile ${source}
|
||||
) > ${name}.lua
|
||||
'';
|
||||
installPhase = ''
|
||||
cp ${name}.lua $out
|
||||
chmod +x $out
|
||||
'';
|
||||
}
|
||||
writeFennel name { inherit packages; } source
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
{
|
||||
lua
|
||||
, lib
|
||||
, fennel
|
||||
, stdenv
|
||||
}:
|
||||
name :
|
||||
{
|
||||
packages ? [],
|
||||
correlate ? false,
|
||||
mainFunction ? null
|
||||
} @ options : source :
|
||||
let
|
||||
luapath = builtins.map
|
||||
(f:
|
||||
"${f}/share/lua/${lua.luaversion}/?.lua;" +
|
||||
"${f}/share/lua/${lua.luaversion}/?/init.lua;")
|
||||
packages;
|
||||
luacpath = builtins.map (f: "${f}/lib/lua/${lua.luaversion}/?.so;") packages;
|
||||
luaFlags = lib.optionalString (mainFunction !=null) "-e dofile(arg[0]).${mainFunction}()";
|
||||
in stdenv.mkDerivation {
|
||||
inherit name;
|
||||
src = ./.;
|
||||
nativeBuildInputs = [ fennel ];
|
||||
buildPhase = ''
|
||||
(
|
||||
echo "#!${lua}/bin/lua ${luaFlags}"
|
||||
echo "package.path = ${lib.strings.escapeShellArg (builtins.concatStringsSep "" luapath)} .. package.path"
|
||||
echo "package.cpath = ${lib.strings.escapeShellArg (builtins.concatStringsSep "" luacpath)} .. package.cpath"
|
||||
fennel ${if correlate then "--correlate" else ""} --compile ${source}
|
||||
) > ${name}.lua
|
||||
'';
|
||||
installPhase = ''
|
||||
cp ${name}.lua $out
|
||||
chmod +x $out
|
||||
'';
|
||||
}
|
||||
@@ -5,10 +5,11 @@ let
|
||||
liminix-config = ./vanilla-configuration.nix;
|
||||
inherit nixpkgs;
|
||||
});
|
||||
here = builtins.toString ./.;
|
||||
in liminix.buildEnv.overrideAttrs (o: {
|
||||
nativeBuildInputs = o.nativeBuildInputs ++ [ (import nixpkgs {}).sphinx ] ;
|
||||
shellHook = ''
|
||||
publish(){ make -C doc html && rsync -azv doc/_build/html/ myhtic.telent.net:/var/www/blogs/www.liminix.org/_site/doc; }
|
||||
'';
|
||||
FENNEL_PATH = "pkgs/?/init.fnl;pkgs/?.fnl";
|
||||
FENNEL_PATH = "${here}/pkgs/?/init.fnl;${here}/pkgs/?.fnl";
|
||||
})
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
{ config, pkgs, lib, ... } :
|
||||
let
|
||||
inherit (pkgs.liminix.networking) interface address route;
|
||||
inherit (pkgs.liminix.services) oneshot longrun bundle target output;
|
||||
svc = config.system.service;
|
||||
in rec {
|
||||
services.lan4 =
|
||||
let iface = interface { type = "hardware"; device = "eth1";};
|
||||
in address iface { family = "inet4"; address ="192.168.19.1"; prefixLength = 24;};
|
||||
services.lan4 = svc.network.address.build {
|
||||
interface = config.hardware.networkInterfaces.lan;
|
||||
family = "inet"; address ="192.168.19.1"; prefixLength = 24;
|
||||
};
|
||||
|
||||
imports = [
|
||||
../../modules/ppp
|
||||
../../modules/dnsmasq
|
||||
../../modules/network
|
||||
];
|
||||
|
||||
services.pppoe =
|
||||
config.system.service.pppoe.build {
|
||||
interface = interface { type = "hardware"; device = "eth0"; };
|
||||
svc.pppoe.build {
|
||||
interface = config.hardware.networkInterfaces.wan;
|
||||
ppp-options = [
|
||||
"debug" "+ipv6" "noauth"
|
||||
"name" "db123@a.1"
|
||||
@@ -22,39 +24,22 @@ in rec {
|
||||
];
|
||||
};
|
||||
|
||||
services.defaultroute4 = route {
|
||||
name = "defaultroute";
|
||||
services.defaultroute4 = svc.network.route.build {
|
||||
via = "$(output ${services.pppoe} address)";
|
||||
target = "default";
|
||||
dependencies = [ services.pppoe ];
|
||||
};
|
||||
|
||||
services.packet_forwarding =
|
||||
let
|
||||
iface = services.pppoe;
|
||||
filename = "/proc/sys/net/ipv4/conf/$(output ${iface} ifname)/forwarding";
|
||||
in oneshot {
|
||||
name = "let-the-ip-flow";
|
||||
up = "echo 1 > ${filename}";
|
||||
down = "echo 0 > ${filename}";
|
||||
dependencies = [iface];
|
||||
};
|
||||
services.packet_forwarding = svc.network.forward.build {
|
||||
dependencies = [services.pppoe];
|
||||
};
|
||||
|
||||
services.dns =
|
||||
config.system.service.dnsmasq.build {
|
||||
svc.dnsmasq.build {
|
||||
interface = services.lan4;
|
||||
ranges = ["192.168.19.10,192.168.19.253"];
|
||||
domain = "fake.liminix.org";
|
||||
};
|
||||
|
||||
services.default = target {
|
||||
name = "default";
|
||||
contents = with services; [
|
||||
config.hardware.networkInterfaces.lo
|
||||
defaultroute4
|
||||
packet_forwarding
|
||||
dns
|
||||
];
|
||||
};
|
||||
defaultProfile.packages = [ pkgs.hello ] ;
|
||||
}
|
||||
|
||||
@@ -3,11 +3,10 @@ let
|
||||
inherit (pkgs.liminix.networking) interface address hostapd route dnsmasq;
|
||||
inherit (pkgs.liminix.services) oneshot longrun bundle target;
|
||||
in rec {
|
||||
services.loopback = config.hardware.networkInterfaces.lo;
|
||||
|
||||
imports = [
|
||||
../../modules/wlan.nix
|
||||
../../modules/hostapd
|
||||
../../modules/network
|
||||
];
|
||||
|
||||
services.hostap = config.system.service.hostapd.build {
|
||||
@@ -28,12 +27,5 @@ in rec {
|
||||
};
|
||||
};
|
||||
|
||||
services.default = target {
|
||||
name = "default";
|
||||
contents = with config.services; [
|
||||
loopback
|
||||
hostap
|
||||
];
|
||||
};
|
||||
defaultProfile.packages = with pkgs; [ tcpdump ] ;
|
||||
}
|
||||
|
||||
@@ -1,51 +1,33 @@
|
||||
{ config, pkgs, ... } :
|
||||
let
|
||||
inherit (pkgs.liminix.networking) interface address udhcpc odhcpc route;
|
||||
inherit (pkgs.liminix.services) oneshot longrun bundle target;
|
||||
inherit (pkgs) writeText;
|
||||
svc = config.system.service;
|
||||
in rec {
|
||||
imports = [
|
||||
./modules/tftpboot.nix
|
||||
./modules/wlan.nix
|
||||
./modules/network
|
||||
./modules/ntp
|
||||
./modules/vlan
|
||||
];
|
||||
services.loopback = config.hardware.networkInterfaces.lo;
|
||||
|
||||
services.dhcpv4 =
|
||||
let iface = interface { type = "hardware"; device = "eth1"; };
|
||||
in udhcpc iface {};
|
||||
let iface = svc.network.link.build { ifname = "eth1"; };
|
||||
in svc.network.dhcp.client.build { interface = iface; };
|
||||
|
||||
services.dhcpv6 =
|
||||
let iface = interface { type = "hardware"; device = "eth1"; };
|
||||
in odhcpc iface { uid = "e7"; };
|
||||
|
||||
services.defaultroute4 = route {
|
||||
name = "defautlrote";
|
||||
services.defaultroute4 = svc.network.route.build {
|
||||
via = "$(output ${services.dhcpv4} address)";
|
||||
target = "default";
|
||||
dependencies = [ services.dhcpv4 ];
|
||||
};
|
||||
|
||||
services.packet_forwarding =
|
||||
let
|
||||
iface = services.dhcpv4;
|
||||
filename = "/proc/sys/net/ipv4/conf/$(output ${iface} ifname)/forwarding";
|
||||
in oneshot {
|
||||
name = "let-the-ip-flow";
|
||||
up = "echo 1 > ${filename}";
|
||||
down = "echo 0 > ${filename}";
|
||||
dependencies = [iface];
|
||||
};
|
||||
services.packet_forwarding = svc.network.forward.build { };
|
||||
|
||||
services.ntp = config.system.service.ntp.build {
|
||||
pools = { "pool.ntp.org" = ["iburst"] ; };
|
||||
};
|
||||
|
||||
services.default = target {
|
||||
name = "default";
|
||||
contents = with services; [ loopback ntp defaultroute4 ];
|
||||
};
|
||||
|
||||
boot.tftp = {
|
||||
serverip = "192.168.8.148";
|
||||
ipaddr = "192.168.8.251";
|
||||
|
||||
Reference in New Issue
Block a user