forked from dan/liminix
Compare commits
165 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4ae1edbce7 | |||
| 1a5efff3ae | |||
| 8772e64b94 | |||
| e6ef4f78bb | |||
| d2f517a4e9 | |||
| 0f38ee0e9c | |||
| 61dc5beca8 | |||
| f3225c2bd5 | |||
| 8798ee9830 | |||
| 629914f65e | |||
| 0693cf23d8 | |||
| c341eb46b6 | |||
| 1a369ff3bf | |||
| 364c5faf9e | |||
| 80a09a9a9b | |||
| 3518e2ecca | |||
| fc2eb6ee4d | |||
| bd20f3e419 | |||
| f62ad0e1d7 | |||
| ed792e0dc0 | |||
| d025c33d30 | |||
| a755c9c3c5 | |||
| fdf74fa06b | |||
| b8dea2ed34 | |||
| c18f07f02f | |||
| 1c4412a1f4 | |||
| dbc16edf96 | |||
| 528d619d76 | |||
| 269e972970 | |||
| 2a5669c2cd | |||
| 4df248323c | |||
| b01840fa7f | |||
| a896c4e31c | |||
| caf8e85061 | |||
| 76f03ecf0f | |||
| f57997c605 | |||
| 3c483ebd9a | |||
| a30b658999 | |||
| 1ec7fcc197 | |||
| f9f934b40c | |||
| e3c8ab351e | |||
| 7e13eda490 | |||
| 3a2f074199 | |||
| 74f4e0a2f3 | |||
| 9133475103 | |||
| abd01a7809 | |||
| d0c2b3b274 | |||
| dfe7228b99 | |||
| ab147abd9b | |||
| bca0c9b26b | |||
| c59a228955 | |||
| 1673a71831 | |||
| 94e51db649 | |||
| 3205a38ac9 | |||
| 119d6ad379 | |||
| 8d356890b0 | |||
| be22fbbb0a | |||
| 9f87fd8625 | |||
| 3a3ee8bbbd | |||
| 381730d081 | |||
| 57eb55de58 | |||
| 4389fa15f7 | |||
| 4f29bdd3ed | |||
| f1c04c7979 | |||
| f75995e895 | |||
| 12b9feb8ee | |||
| b3a7a2246d | |||
| 6674344021 | |||
| c81ee62374 | |||
| dd0b1734c1 | |||
| 0a2588013a | |||
| 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
|
||||
|
||||
+652
-1
@@ -2039,4 +2039,655 @@ 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
|
||||
|
||||
Mon Sep 25 16:58:51 BST 2023
|
||||
|
||||
jffs2 on mt300a isn't finding root partition in initramfs, and it
|
||||
seems to be because MTD_SPLIT_UIMAGE_FW isn't working
|
||||
|
||||
[ 0.426792] spi spi0.0: force spi mode3
|
||||
[ 0.431305] spi-nor spi0.0: w25q128 (16384 Kbytes)
|
||||
[ 0.436322] 5 fixed-partitions partitions found on MTD device spi0.0
|
||||
[ 0.442875] OF: Bad cell count for /palmbus@10000000/spi@b00/flash@0/partitions
|
||||
[ 0.450400] OF: Bad cell count for /palmbus@10000000/spi@b00/flash@0/partitions
|
||||
[ 0.458208] OF: Bad cell count for /palmbus@10000000/spi@b00/flash@0/partitions
|
||||
[ 0.465751] OF: Bad cell count for /palmbus@10000000/spi@b00/flash@0/partitions
|
||||
[ 0.473522] Creating 5 MTD partitions on "spi0.0":
|
||||
[ 0.478466] 0x000000000000-0x000000030000 : "u-boot"
|
||||
[ 0.484447] 0x000000030000-0x000000040000 : "u-boot-env"
|
||||
[ 0.490888] 0x000000040000-0x000000050000 : "factory"
|
||||
[ 0.497110] 0x000000050000-0x000000fd0000 : "firmware"
|
||||
[ 0.596423] 0x000000ff0000-0x000001000000 : "art"
|
||||
[ 0.611508] gsw: setting port4 to ephy mode
|
||||
|
||||
with squashfs root it's the same but for the extra split partitions:
|
||||
|
||||
[ 0.468715] Creating 5 MTD partitions on "spi0.0":
|
||||
[ 0.473653] 0x000000000000-0x000000030000 : "u-boot"
|
||||
[ 0.479652] 0x000000030000-0x000000040000 : "u-boot-env"
|
||||
[ 0.486085] 0x000000040000-0x000000050000 : "factory"
|
||||
[ 0.492318] 0x000000050000-0x000000fd0000 : "firmware"
|
||||
[ 0.499304] 2 uimage-fw partitions found on MTD device firmware
|
||||
[ 0.505457] Creating 2 MTD partitions on "firmware":
|
||||
[ 0.510543] 0x000000000000-0x000000260000 : "kernel"
|
||||
[ 0.516616] 0x000000260000-0x000000f80000 : "rootfs"
|
||||
[ 0.522570] mtd: device 5 (rootfs) set to be root filesystem
|
||||
[ 0.528565] 0x000000ff0000-0x000001000000 : "art"
|
||||
|
||||
turns out this is because the device thinks it has 4k erase block size
|
||||
because MTD_SPI_NOR_USE_4K_SECTORS was set, and that was causing
|
||||
mtdsplit to look in the wrong place for a root filesystem
|
||||
|
||||
Mon Sep 25 18:50:05 BST 2023
|
||||
|
||||
No, that wasn't it. Turned out to be an endianness-dependent check for
|
||||
JFFS2 magic in mtdsplit.
|
||||
|
||||
setenv serverip 10.0.0.1
|
||||
setenv ipaddr 10.0.0.8
|
||||
tftp 0xa00000 result/uimage
|
||||
bootm 0xa00000
|
||||
|
||||
Fri Sep 29 20:50:39 BST 2023
|
||||
|
||||
setenv bootargs 'liminix mtdparts=phram0:M(rootfs) phram.phram=phram0,0x40411f28,4194304,65536 memmap=4194304$0x40411f28 root=/dev/mtdblock0 console=ttyAMA0,115200 earlycon'
|
||||
|
||||
setenv serverip 10.0.0.1
|
||||
setenv ipaddr 10.0.0.5
|
||||
setenv bootargs 'liminix console=ttyS0,115200 panic=10 oops=panic earlycon=uart8250,mmio32,0x11002000 root=/dev/mtdblock0'
|
||||
tftp 0x4007ff28 result/uimage ; tftp 0x40432f28 result/rootfs
|
||||
bootm 0x4007ff28
|
||||
|
||||
setenv bootargs 'liminix console=ttyS0,115200 panic=1 oops=panic earlycon root=/dev/mtdblock0'
|
||||
|
||||
md 0x42ff0000
|
||||
|
||||
Cannot find regmap for /infracfg@10000000: -524.
|
||||
|
||||
# "console=ttyAMA0,38400 panic=10 oops=panic init=/bin/init loglevel=8 root=/dev/mtdblock0 rootfstype=squashfs fw_devlink=off"
|
||||
|
||||
|
||||
40812468: 69726553 203a6c61 41424d41 304c5020 Serial: AMBA PL0
|
||||
40812478: 55203131 20545241 76697264 3c0a7265 11 UART driver.<
|
||||
40812488: 61723e33 706f6f6d 61203a73 6165726c 3>ramoops: alrea
|
||||
40812498: 69207964 6974696e 7a696c61 3c0a6465 dy initialized.<
|
||||
408124a8: 61723e34 706f6f6d 70203a73 65626f72 4>ramoops: probe
|
||||
408124b8: 20666f20 66663234 30303030 6d61722e of 42ff0000.ram
|
||||
408124c8: 73706f6f 69616620 2064656c 68746977 oops failed with
|
||||
408124d8: 72726520 2d20726f 3c0a3232 61433e33 error -22.<3>Ca
|
||||
408124e8: 746f6e6e 6e696620 65722064 70616d67 nnot find regmap
|
||||
408124f8: 726f6620 6e692f20 63617266 31406766 for /infracfg@1
|
||||
40812508: 30303030 3a303030 32352d20 333c0a34 0000000: -524.<3
|
||||
40812518: 6e61433e 20746f6e 646e6966 67657220 >Cannot find reg
|
||||
MT7622>
|
||||
40812528: 2070616d 20726f66 666e692f 66636172 map for /infracf
|
||||
40812538: 30314067 30303030 203a3030 3432352d g@10000000: -524
|
||||
40812548: 3e333c0a 6e6e6143 6620746f 20646e69 .<3>Cannot find
|
||||
40812558: 6d676572 66207061 2f20726f 72666e69 regmap for /infr
|
||||
40812568: 67666361 30303140 30303030 2d203a30 acfg@10000000: -
|
||||
40812578: 0a343235 433e333c 6f6e6e61 69662074 524.<3>Cannot fi
|
||||
40812588: 7220646e 616d6765 6f662070 702f2072 nd regmap for /p
|
||||
40812598: 63697265 31406766 32303030 3a303030 ericfg@10002000:
|
||||
408125a8: 32352d20 313c0a34 616e553e 20656c62 -524.<1>Unable
|
||||
408125b8: 68206f74 6c646e61 656b2065 6c656e72 to handle kernel
|
||||
408125c8: 67617020 20676e69 75716572 20747365 paging request
|
||||
408125d8: 76207461 75747269 61206c61 65726464 at virtual addre
|
||||
408125e8: 66207373 66666666 66666666 66666666 ss fffffffffffff
|
||||
408125f8: 0a656666 4d3e313c 61206d65 74726f62 ffe.<1>Mem abort
|
||||
|
||||
|
||||
CONFIG_SERIAL_8250_FSL=y
|
||||
CONFIG_SERIAL_8250_MT6577=y
|
||||
CONFIG_SERIAL_8250_NR_UARTS=3
|
||||
CONFIG_SERIAL_8250_RUNTIME_UARTS=3
|
||||
CONFIG_SERIAL_DEV_BUS=y
|
||||
CONFIG_SERIAL_DEV_CTRL_TTYPORT=y
|
||||
CONFIG_SERIAL_MCTRL_GPIO=y
|
||||
CONFIG_SERIAL_OF_PLATFORM=y
|
||||
|
||||
Mon Oct 2 10:17:04 BST 2023
|
||||
|
||||
We have a bootable aarch64 kernel for the Belkin, but it does not
|
||||
understand the memmap= parameter we're using to protect the
|
||||
phram image from being used as general memory.
|
||||
|
||||
One option is to add a reserved-memory stanza in the device tree,
|
||||
using u-boot "fdt" command, but we don't know the fdt address in u-boot
|
||||
because it doesn't have any commands to parse the image and set
|
||||
variables pointing at the sub-components. (There's iminfo, but it's
|
||||
onyl human-readable)
|
||||
|
||||
Second option is to amend the dtb in the tftpboot module: this
|
||||
would mean regenerating the uimage
|
||||
|
||||
Third option: for tftpboot do we _have_ to use FIT? maybe we could
|
||||
grab the fdt as a separate tftp transaction
|
||||
|
||||
we need to apply kernel patch 9401911f2d9f89035f7acebab16e72d43d1282fb
|
||||
to avoid using ioremap on sysem ram which is not allowed on arm
|
||||
|
||||
|
||||
Tue Oct 3 14:15:38 BST 2023
|
||||
|
||||
Progress on Liminix ARM support. The device I'm starting with is
|
||||
the Belkin RT3200 (also known as [Linksys E8450](https://openwrt.org/toh/linksys/e8450)) which seems to be a featureful piece of kit, and whioch I snagged for a
|
||||
very good price on the Bay of E
|
||||
|
||||
# Where are we right now?
|
||||
|
||||
* we can TFTP boot it to userland
|
||||
* ethernet works
|
||||
|
||||
## What else needs doing?
|
||||
|
||||
* it has dual band wifi with many interesting features. I've built the
|
||||
|
||||
* we're only running in RAM, probably need to add some kernel config
|
||||
to support the flash
|
||||
* initramfs support is not yet implemented
|
||||
* the flash is NAND flash and it's quite large compared with the
|
||||
existing Liminix devices, so we're going to add UBIFS which will
|
||||
use it better than JFFS2 does
|
||||
* all this work is on a branch and needs to be cleaned up a lot before
|
||||
I'm letting it into main
|
||||
|
||||
## What have we found?
|
||||
|
||||
There are some significant differences between this and the MIPS
|
||||
devices (yes, other than an entirely different architecture), mostly
|
||||
to do with "legacy boot" support or the lack thereof. For example:
|
||||
|
||||
* there aren't any options like `MIPS_RAW_APPENDED_DTB` to glom
|
||||
together a kernel and device tree (FDT), because the bootloader is
|
||||
expected to be able to provide a FDT following "standards".
|
||||
U-Boot will do this, provided that we use the newer "FIT" Uimage
|
||||
format which allows a kernel and DTB and initrd to be combined in
|
||||
the same container. (Sadly we can't use FIT everywhere because a
|
||||
lot of MIPS devices use really old forks of U-Boot that don't
|
||||
understand it)
|
||||
|
||||
* for tftpboot, on MIPS we use the `memmap` kernel command line option
|
||||
to reserve some RAM for the root filesystem. On Arm there's no such
|
||||
option, so we have to add a
|
||||
[reserved-memory](https://github.com/torvalds/linux/blob/master/Documentation/devicetree/bindings/reserved-memory/reserved-memory.yaml)
|
||||
node in the device tree instead. Which means, given that we _only_
|
||||
want to do this when tftp booting (the memory is wasted
|
||||
otherwise), we have to rewrite the device tree in that
|
||||
scenario.
|
||||
|
||||
* then it turns out that phram doesn't (didn't) work anyway, because
|
||||
it calls ioremap() and [you can't use ioremap on system memory on ARM](https://lwn.net/Articles/409689/). In newer kernels this is
|
||||
fixed: there is a [conditional](https://github.com/torvalds/linux/blob/master/drivers/mtd/devices/phram.c#L127) here to use whichever of ioremap or
|
||||
memremap is appropriate for the memory passed to phram, but it looks
|
||||
non-trivial to backport so I've gone for a [much less sophisticated approach](https://gti.telent.net/dan/liminix/commit/f7cd9c2b6e6c99a228e066b09e3febcf71c63fa1#diff-d8e355f1b2dcde7378ebb40c92cdd2ce3125753c)
|
||||
|
||||
* we're using [DSA](https://www.kernel.org/doc/Documentation/networking/dsa/dsa.txt) instead of the OpenWrt [swconfig](https://openwrt.org/docs/techref/swconfig) program. There was actually surprisingly little work needed to adjust to this.
|
||||
|
||||
|
||||
Other than that, it was mostly the usual process of "did the kernel
|
||||
crash silently, or has it just been unable to open a console device?".
|
||||
In this regard, *one neat trick*: even though U-Boot on this device
|
||||
doesn't support pstore, we can use it anyway if we don't do compression.
|
||||
Enable
|
||||
|
||||
```
|
||||
PSTORE = "y";
|
||||
PSTORE_RAM = "y";
|
||||
PSTORE_CONSOLE = "y";
|
||||
PSTORE_DEFLATE_COMPRESS = "n";
|
||||
```
|
||||
|
||||
then boot with `panic=3 oops=panic`, then when it resets use the
|
||||
U_Boot `md` command to see what happened:
|
||||
|
||||
```
|
||||
MT7622> md 0x42ff0000
|
||||
42ff0000: 43474244 00000000 00000ff4 3d3d3d3d DBGC........====
|
||||
42ff0010: 39342e31 36373031 500a442d 63696e61 1.491076-D.Panic
|
||||
42ff0020: 50203123 31747261 3e363c0a 20505050 #1 Part1.<6>PPP
|
||||
42ff0030: 20445342 706d6f43 73736572 206e6f69 BSD Compression
|
||||
[....]
|
||||
42ff0ca0: 20676e69 73756e75 6b206465 656e7265 ing unused kerne
|
||||
42ff0cb0: 656d206c 79726f6d 3731203a 0a4b3832 l memory: 1728K.
|
||||
42ff0cc0: 523e363c 2f206e75 74696e69 20736120 <6>Run /init as
|
||||
42ff0cd0: 74696e69 6f727020 73736563 3e373c0a init process.<7>
|
||||
```
|
||||
|
||||
Wed Oct 4 21:08:44 BST 2023
|
||||
|
||||
By randomly including chinks of the openwrt config we have made it
|
||||
find the mt7915e on the pcie bus. I just don't yet know which bit of
|
||||
the openwrt config it was.
|
||||
|
||||
It doesn't actually work yet though. 5GHz wifi gets calibration data
|
||||
from the flash, so it is not going to work unless (1) we reflash the
|
||||
firmware partition, or (2) we find another way to provide it
|
||||
calibration data.
|
||||
|
||||
https://forum.openwrt.org/t/belkin-rt3200-linksys-e8450-wifi-ax-discussion/94302/401
|
||||
|
||||
Sat Oct 7 22:56:40 BST 2023
|
||||
|
||||
We're almost ready to merge the rt3200 support (it's not finished
|
||||
but it mostly won't break mips) except for the uimage module
|
||||
which needs all that FIT stuff, and the tftpboot contortions
|
||||
to amend the dtb for memmap
|
||||
|
||||
For legacy uimage
|
||||
|
||||
1) add commandline params to dtb
|
||||
2) objcopy fdt into vmlinux.elf
|
||||
3) strip to raw image and compress
|
||||
4) mkimage
|
||||
|
||||
|
||||
For FIT uimage
|
||||
|
||||
1) add commandline params to dtb
|
||||
2) strip to raw image and compress
|
||||
3) create its file
|
||||
4) mkimage
|
||||
|
||||
Do we still want to handle the no-dtb case? what about
|
||||
standards-compliant boot, where u-boot is providing the dtb? No option
|
||||
to provide a commandline in that case, but maybe also no need to.
|
||||
|
||||
For tftpboot, am undecided. We could use the dtb rewriting thing
|
||||
everywhere, in the interest of consistency.
|
||||
|
||||
@@ -8,17 +8,21 @@ let
|
||||
pkgs = (import nixpkgs {});
|
||||
borderVmConf = ./bordervm.conf-example.nix;
|
||||
inherit (pkgs.lib.attrsets) genAttrs;
|
||||
devices = [ "qemu" "gl-ar750" "gl-mt300n-v2" "gl-mt300a" ];
|
||||
devices = {
|
||||
virt = [ "qemu" "qemu-aarch64" ];
|
||||
hw = [ "gl-ar750" "gl-mt300n-v2" "gl-mt300a" ];
|
||||
};
|
||||
vanilla = ./vanilla-configuration.nix;
|
||||
for-device = name:
|
||||
for-device = cfg: name:
|
||||
(import liminix {
|
||||
inherit nixpkgs borderVmConf;
|
||||
device = import (liminix + "/devices/${name}");
|
||||
liminix-config = vanilla;
|
||||
liminix-config = cfg;
|
||||
}).outputs.default;
|
||||
tests = import ./tests/ci.nix;
|
||||
jobs =
|
||||
(genAttrs devices (name: for-device name)) //
|
||||
(genAttrs devices.hw (name: for-device ./vanilla-configuration-hw.nix name)) //
|
||||
(genAttrs devices.virt (name: for-device vanilla name)) //
|
||||
tests //
|
||||
{
|
||||
buildEnv = (import liminix {
|
||||
@@ -35,7 +39,8 @@ let
|
||||
src = ./doc;
|
||||
buildPhase = ''
|
||||
cat ${(import ./doc/extract-options.nix).doc} > options.json
|
||||
cat options.json | fennel --correlate parse-options.fnl > modules.rst
|
||||
cat options.json | fennel --correlate parse-options.fnl > modules-generated.rst
|
||||
cp ${(import ./doc/hardware.nix)} hardware.rst
|
||||
make html
|
||||
'';
|
||||
installPhase = ''
|
||||
|
||||
+1
-1
@@ -54,7 +54,7 @@ in {
|
||||
tufted
|
||||
routeros.routeros
|
||||
routeros.ros-exec-script
|
||||
mips-vm
|
||||
run-liminix-vm
|
||||
borderVm.build.vm
|
||||
go-l2tp
|
||||
min-copy-closure
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
{
|
||||
description = ''
|
||||
Belkin RT-3200 / Linksys E8450
|
||||
******************************
|
||||
|
||||
This device is based on a 64 bit Mediatek MT7622 ARM platform,
|
||||
and is "work in progress" in Liminix.
|
||||
|
||||
The factory flash image contains ECC errors that make it incompatible
|
||||
with Liminix: you need to use the `OpenWrt UBI Installer <https://github.com/dangowrt/owrt-ubi-installer>`_ to rewrite the partition layout before
|
||||
you can flash Liminix onto it (or even use it with "tftpboot",
|
||||
if you want the wireless to work).
|
||||
|
||||
- MediaTek MT7622BV (1350MHz)
|
||||
- 128MB NAND flash
|
||||
- 512MB RAM
|
||||
- b/g/n wireless using MediaTek MT7622BV (MT7615E driver)
|
||||
- a/n/ac/ax wireless using MediaTek MT7915E
|
||||
'';
|
||||
|
||||
system = {
|
||||
crossSystem = {
|
||||
config = "aarch64-unknown-linux-musl";
|
||||
};
|
||||
};
|
||||
|
||||
module = {pkgs, config, lib, ... }:
|
||||
let firmware = pkgs.stdenv.mkDerivation {
|
||||
name = "wlan-firmware";
|
||||
phases = ["installPhase"];
|
||||
installPhase = ''
|
||||
mkdir $out
|
||||
cp ${pkgs.linux-firmware}/lib/firmware/mediatek/{mt7915,mt7615,mt7622}* $out
|
||||
'';
|
||||
};
|
||||
in {
|
||||
imports = [ ../../modules/arch/aarch64.nix ];
|
||||
kernel = {
|
||||
src = pkgs.pkgsBuildBuild.fetchurl {
|
||||
name = "linux.tar.gz";
|
||||
url = "https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.15.71.tar.gz";
|
||||
hash = "sha256-yhO2cXIeIgUxkSZf/4aAsF11uxyh+UUZu6D1h92vCD8=";
|
||||
};
|
||||
extraPatchPhase = ''
|
||||
${pkgs.openwrt.applyPatches.mediatek}
|
||||
'';
|
||||
config = {
|
||||
PCI = "y";
|
||||
ARCH_MEDIATEK = "y";
|
||||
# ARM_MEDIATEK_CPUFREQ = "y";
|
||||
|
||||
# needed for "Cannot find regmap for /infracfg@10000000"
|
||||
MFD_SYSCON = "y";
|
||||
MTK_INFRACFG = "y";
|
||||
|
||||
MTK_PMIC_WRAP = "y";
|
||||
MTK_EFUSE="y";
|
||||
# MTK_HSDMA="y";
|
||||
MTK_SCPSYS="y";
|
||||
MTK_SCPSYS_PM_DOMAINS="y";
|
||||
# MTK_THERMAL="y";
|
||||
MTK_TIMER="y";
|
||||
|
||||
COMMON_CLK_MT7622 = "y";
|
||||
COMMON_CLK_MT7622_ETHSYS = "y";
|
||||
COMMON_CLK_MT7622_HIFSYS = "y";
|
||||
COMMON_CLK_MT7622_AUDSYS = "y";
|
||||
PM_CLK="y";
|
||||
|
||||
REGMAP_MMIO = "y";
|
||||
CLKSRC_MMIO = "y";
|
||||
REGMAP = "y";
|
||||
|
||||
MEDIATEK_GE_PHY = "y";
|
||||
# MEDIATEK_MT6577_AUXADC = "y";
|
||||
# MEDIATEK_WATCHDOG = "y";
|
||||
NET_MEDIATEK_SOC = "y";
|
||||
NET_MEDIATEK_SOC_WED = "y";
|
||||
NET_MEDIATEK_STAR_EMAC = "y"; # this enables REGMAP_MMIO
|
||||
NET_VENDOR_MEDIATEK = "y";
|
||||
PCIE_MEDIATEK = "y";
|
||||
|
||||
BLOCK = "y"; # move this to base option
|
||||
|
||||
SPI_MASTER = "y";
|
||||
SPI = "y";
|
||||
SPI_MEM="y";
|
||||
SPI_MTK_NOR="y";
|
||||
SPI_MTK_SNFI = "y";
|
||||
|
||||
MTD = "y";
|
||||
MTD_BLOCK = "y";
|
||||
MTD_RAW_NAND = "y";
|
||||
MTD_NAND_MTK = "y";
|
||||
MTD_NAND_MTK_BMT = "y"; # Bad-block Management Table
|
||||
MTD_NAND_ECC_MEDIATEK= "y";
|
||||
MTD_NAND_ECC_SW_HAMMING= "y";
|
||||
MTD_SPI_NAND= "y";
|
||||
MTD_OF_PARTS = "y";
|
||||
MTD_NAND_CORE= "y";
|
||||
MTD_SPI_NOR= "y";
|
||||
MTD_SPLIT_FIRMWARE= "y";
|
||||
MTD_SPLIT_FIT_FW= "y";
|
||||
|
||||
|
||||
MMC = "y";
|
||||
MMC_BLOCK = "y";
|
||||
MMC_CQHCI = "y";
|
||||
MMC_MTK = "y";
|
||||
|
||||
# Distributed Switch Architecture is needed
|
||||
# to make the ethernet ports visible
|
||||
NET_DSA="y";
|
||||
NET_DSA_MT7530="y";
|
||||
NET_DSA_TAG_MTK="y";
|
||||
|
||||
PSTORE = "y";
|
||||
PSTORE_RAM = "y";
|
||||
PSTORE_CONSOLE = "y";
|
||||
PSTORE_DEFLATE_COMPRESS = "n";
|
||||
|
||||
SERIAL_8250 = "y";
|
||||
SERIAL_8250_CONSOLE = "y";
|
||||
SERIAL_8250_MT6577="y";
|
||||
# SERIAL_8250_NR_UARTS="3";
|
||||
# SERIAL_8250_RUNTIME_UARTS="3";
|
||||
SERIAL_OF_PLATFORM="y";
|
||||
};
|
||||
};
|
||||
boot = {
|
||||
commandLine = [ "console=ttyS0,115200" ];
|
||||
tftp.loadAddress = "0x4007ff28";
|
||||
imageFormat = "fit";
|
||||
};
|
||||
filesystem =
|
||||
let inherit (pkgs.pseudofile) dir symlink;
|
||||
in
|
||||
dir {
|
||||
lib = dir {
|
||||
firmware = dir {
|
||||
mediatek = symlink firmware;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
hardware =
|
||||
let
|
||||
openwrt = pkgs.openwrt;
|
||||
mac80211 = pkgs.mac80211.override {
|
||||
drivers = [
|
||||
"mt7615e"
|
||||
"mt7915e"
|
||||
];
|
||||
klibBuild = config.system.outputs.kernel.modulesupport;
|
||||
};
|
||||
in {
|
||||
ubi = {
|
||||
minIOSize = "2048";
|
||||
eraseBlockSize = "126976";
|
||||
maxLEBcount = "1024"; # guessing
|
||||
};
|
||||
|
||||
defaultOutput = "flashimage";
|
||||
# the kernel expects this to be on a 2MB boundary. U-Boot
|
||||
# (I don't know why) has a default of 0x41080000, which isn't.
|
||||
# We put it at the 32MB mark so that tftpboot can put its rootfs
|
||||
# image and DTB underneath, but maybe this is a terrible waste of
|
||||
# RAM unless the kernel is able to reuse it later. Oh well
|
||||
loadAddress = "0x42000000";
|
||||
entryPoint = "0x42000000";
|
||||
rootDevice = "ubi0:liminix";
|
||||
dts = {
|
||||
src = "${openwrt.src}/target/linux/mediatek/dts/mt7622-linksys-e8450-ubi.dts";
|
||||
includes = [
|
||||
"${openwrt.src}/target/linux/mediatek/dts"
|
||||
"${config.system.outputs.kernel.modulesupport}/arch/arm64/boot/dts/mediatek/"
|
||||
];
|
||||
};
|
||||
|
||||
flash.eraseBlockSize = "65536"; # this is probably wrong
|
||||
networkInterfaces =
|
||||
let
|
||||
inherit (config.system.service.network) link;
|
||||
inherit (config.system.service) bridge;
|
||||
in rec {
|
||||
wan = link.build { ifname = "wan"; };
|
||||
lan1 = link.build { ifname = "lan1"; };
|
||||
lan2 = link.build { ifname = "lan2"; };
|
||||
lan3 = link.build { ifname = "lan3"; };
|
||||
lan4 = link.build { ifname = "lan4"; };
|
||||
lan = lan3;
|
||||
|
||||
wlan = link.build {
|
||||
ifname = "wlan0";
|
||||
dependencies = [ mac80211 ];
|
||||
};
|
||||
wlan5 = link.build {
|
||||
ifname = "wlan1";
|
||||
dependencies = [ mac80211 ];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
}
|
||||
@@ -18,13 +18,22 @@
|
||||
};
|
||||
|
||||
description = ''
|
||||
GL.INet GL-AR750 "Creta" travel router
|
||||
GL.iNet GL-AR750
|
||||
****************
|
||||
|
||||
The GL-AR750 "Creta" travel router features:
|
||||
|
||||
- QCA9531 @650Mhz SoC
|
||||
- dual band wireless: IEEE 802.11a/b/g/n/ac
|
||||
- two 10/100Mbps LAN ports and one WAN
|
||||
- 128MB DDR2 RAM / 16MB NOR Flash
|
||||
- "ath79" soc family
|
||||
https://www.gl-inet.com/products/gl-ar750/
|
||||
- 128MB DDR2 RAM
|
||||
- 16MB NOR Flash
|
||||
- supported in OpenWrt by the "ath79" SoC family
|
||||
|
||||
As with many GL.iNet devices, the stock vendor firmware
|
||||
is a fork of OpenWrt, meaning that the plain binary
|
||||
``firmware.bin`` that Liminix builds can be flashed using the
|
||||
vendor web UI and the U-Boot emergency "unbrick" routine
|
||||
|
||||
The GL-AR750 has two distinct sets of wifi hardware. The 2.4GHz
|
||||
radio is part of the QCA9531 SoC, i.e. it's on the same silicon as
|
||||
@@ -34,8 +43,13 @@
|
||||
other hand, is provided by a QCA9887 PCIe (PCI embedded) WLAN chip:
|
||||
I haven't looked closely at the router innards to see if this is
|
||||
actually physically a separate board that could be unplugged, but
|
||||
as far as the Linux is concerned it behaves as one. This is
|
||||
as far as Linux is concerned it behaves as one. This is
|
||||
supported by the ath10k driver.
|
||||
|
||||
Vendor web page: https://www.gl-inet.com/products/gl-ar750/
|
||||
|
||||
OpenWrt web page: https://openwrt.org/toh/gl.inet/gl-ar750
|
||||
|
||||
'';
|
||||
|
||||
module = {pkgs, config, ... }:
|
||||
@@ -75,16 +89,20 @@
|
||||
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
|
||||
../../modules/arch/mipseb.nix
|
||||
];
|
||||
|
||||
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 +118,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 = link.build {
|
||||
ifname = "wlan0";
|
||||
dependencies = [ mac80211 ];
|
||||
};
|
||||
wlan5 = link.build {
|
||||
ifname = "wlan1";
|
||||
dependencies = [ mac80211 ath10k_cal_data ];
|
||||
};
|
||||
};
|
||||
wlan_5 = interface {
|
||||
device = "wlan1";
|
||||
dependencies = [ mac80211 ath10k_cal_data ];
|
||||
};
|
||||
};
|
||||
};
|
||||
filesystem = dir {
|
||||
lib = dir {
|
||||
@@ -137,9 +156,6 @@
|
||||
${openwrt.applyPatches.ath79}
|
||||
'';
|
||||
config = {
|
||||
MIPS_ELF_APPENDED_DTB = "y";
|
||||
OF = "y";
|
||||
USE_OF = "y";
|
||||
ATH79 = "y";
|
||||
PCI = "y";
|
||||
PCI_AR724X = "y";
|
||||
@@ -159,7 +175,6 @@
|
||||
CONSOLE_LOGLEVEL_QUIET = "4";
|
||||
|
||||
NET = "y";
|
||||
NETDEVICES = "y";
|
||||
ETHERNET = "y";
|
||||
NET_VENDOR_ATHEROS = "y";
|
||||
AG71XX = "y"; # ethernet (qca,qca9530-eth)
|
||||
@@ -167,7 +182,6 @@
|
||||
AR8216_PHY = "y"; # eth1 is behind a switch
|
||||
|
||||
MTD_SPI_NOR = "y";
|
||||
MTD_SPI_NOR_USE_4K_SECTORS = "n"; # jffs2 needs min 8k erase block
|
||||
|
||||
SPI_ATH79 = "y"; # these are copied from OpenWrt.
|
||||
SPI_MASTER= "y"; # At least one of them is necessary
|
||||
@@ -184,25 +198,18 @@
|
||||
SYSFS = "y";
|
||||
SPI = "y";
|
||||
MTD = "y";
|
||||
MTD_CMDLINE_PARTS = "y";
|
||||
MTD_BLOCK = "y"; # fix undefined ref to register_mtd_blktrans_devs
|
||||
|
||||
WATCHDOG = "y";
|
||||
ATH79_WDT = "y"; # watchdog timer
|
||||
|
||||
CPU_BIG_ENDIAN= "y";
|
||||
|
||||
# this is all copied from nixwrt ath79 config. Clearly not all
|
||||
# of it is device config, some of it is wifi config or
|
||||
# installation method config or ...
|
||||
|
||||
CMDLINE_PARTITION = "y";
|
||||
EARLY_PRINTK = "y";
|
||||
|
||||
PARTITION_ADVANCED = "y";
|
||||
PRINTK_TIME = "y";
|
||||
SQUASHFS = "y";
|
||||
SQUASHFS_XZ = "y";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# GL.INet GL-MT300A
|
||||
# GL.iNet GL-MT300A
|
||||
|
||||
{
|
||||
system = {
|
||||
@@ -12,13 +12,50 @@
|
||||
};
|
||||
|
||||
description = ''
|
||||
GL.iNet GL-MT300A
|
||||
********************
|
||||
|
||||
The GL-MT300A is based on a MT7620 chipset.
|
||||
|
||||
The GL.iNet pocket router range makes nice cheap hardware for
|
||||
playing with Liminix or similar projects. The manufacturers seem
|
||||
open to the DIY market, and the devices have a reasonable amount
|
||||
of RAM and are much easier to get serial connections than many
|
||||
COTS routers.
|
||||
|
||||
Wire up the serial connection: this probably involves opening
|
||||
the box, locating the serial header pins (TX, RX and GND) and
|
||||
connecting a USB TTL converter - e.g. a PL2303 based device - to
|
||||
it. The defunct OpenWRT wiki has a guide with some pictures. (If
|
||||
you don't have a USB TTL converter to hand, other options are
|
||||
available. For example, use the GPIO pins on a Raspberry Pi.)
|
||||
|
||||
Run a terminal emulator such as Minicom on whatever is on the
|
||||
other end of the link. I use 115200 8N1 and find it also helps
|
||||
to set "Line tx delay" to 1ms, "backspace sends DEL" and
|
||||
"lineWrap on".
|
||||
|
||||
When you turn the router on you should be greeted with some
|
||||
messages from U-Boot and a little bit of ASCII art, followed by
|
||||
the instruction to hit SPACE to stop autoboot. Do this and you
|
||||
will get a gl-mt300a> prompt.
|
||||
|
||||
For flashing from uboot, the firmware partition is from
|
||||
0xbc050000 to 0xbcfd0000.
|
||||
|
||||
WiFi on this device is provided by the rt2800soc module. It
|
||||
expects firmware to be present in the "factory" MTD partition, so
|
||||
- assuming we want to use the wireless - we need to build MTD
|
||||
support into the kernel even if we're using TFTP root
|
||||
support into the kernel even if we're using TFTP root.
|
||||
|
||||
|
||||
Vendor web page: https://www.gl-inet.com/products/gl-mt300a/
|
||||
|
||||
OpenWrt web page: https://openwrt.org/toh/gl.inet/gl-mt300a
|
||||
|
||||
'';
|
||||
|
||||
module = { pkgs, config, ...}:
|
||||
module = { pkgs, config, lib, ...}:
|
||||
let
|
||||
inherit (pkgs.liminix.networking) interface;
|
||||
inherit (pkgs) openwrt;
|
||||
@@ -26,9 +63,10 @@
|
||||
drivers = ["rt2800soc"];
|
||||
klibBuild = config.system.outputs.kernel.modulesupport;
|
||||
};
|
||||
in {
|
||||
in {
|
||||
imports = [ ../../modules/arch/mipsel.nix ];
|
||||
hardware = {
|
||||
defaultOutput = "tftpboot";
|
||||
defaultOutput = "flashimage";
|
||||
loadAddress = "0x80000000";
|
||||
entryPoint = "0x80000000";
|
||||
|
||||
@@ -55,28 +93,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";
|
||||
@@ -92,9 +145,6 @@
|
||||
${openwrt.applyPatches.ramips}
|
||||
'';
|
||||
config = {
|
||||
MIPS_ELF_APPENDED_DTB = "y";
|
||||
OF = "y";
|
||||
USE_OF = "y";
|
||||
|
||||
RALINK = "y";
|
||||
PCI = "y";
|
||||
@@ -109,12 +159,12 @@
|
||||
CONSOLE_LOGLEVEL_QUIET = "4";
|
||||
|
||||
NET = "y";
|
||||
NETDEVICES = "y";
|
||||
ETHERNET = "y";
|
||||
NET_VENDOR_RALINK = "y";
|
||||
NET_RALINK_MDIO = "y";
|
||||
NET_RALINK_MDIO_MT7620 = "y";
|
||||
NET_RALINK_MT7620 = "y";
|
||||
SWPHY = "y";
|
||||
|
||||
SPI = "y";
|
||||
MTD_SPI_NOR = "y";
|
||||
@@ -123,34 +173,17 @@
|
||||
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
|
||||
|
||||
CPU_LITTLE_ENDIAN = "y";
|
||||
|
||||
CMDLINE_PARTITION = "y";
|
||||
EARLY_PRINTK = "y";
|
||||
|
||||
NEW_LEDS = "y";
|
||||
LEDS_CLASS = "y"; # required by rt2x00lib
|
||||
|
||||
PARTITION_ADVANCED = "y";
|
||||
PRINTK_TIME = "y";
|
||||
SQUASHFS = "y";
|
||||
SQUASHFS_XZ = "y";
|
||||
} // lib.optionalAttrs (config.system.service ? vlan) {
|
||||
SWCONFIG = "y";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# GL.INet GL-MT300N v2
|
||||
|
||||
{
|
||||
system = {
|
||||
crossSystem = {
|
||||
@@ -11,7 +9,22 @@
|
||||
};
|
||||
};
|
||||
|
||||
module = { pkgs, config, ...}:
|
||||
description = ''
|
||||
GL.iNet GL-MT300N-v2
|
||||
********************
|
||||
|
||||
The GL-MT300N-v2 "Mango" is is very similar to the MT300A, but is
|
||||
based on MT7628 instead of MT7620. It's also marginally cheaper
|
||||
and comes in a yellow case not a blue one. It's different again
|
||||
to the v1, which has only half the RAM.
|
||||
|
||||
Vendor web page: https://www.gl-inet.com/products/gl-mt300n-v2/
|
||||
|
||||
OpenWrt web page: https://openwrt.org/toh/gl.inet/gl-mt300n_v2
|
||||
|
||||
'';
|
||||
|
||||
module = { pkgs, config, lib, ...}:
|
||||
let
|
||||
inherit (pkgs.liminix.networking) interface;
|
||||
inherit (pkgs.liminix.services) oneshot;
|
||||
@@ -27,6 +40,7 @@
|
||||
hash = "sha256:1dkhfznmdz6s50kwc841x3wj0h6zg6icg5g2bim9pvg66as2vmh9";
|
||||
};
|
||||
in {
|
||||
imports = [ ../../modules/arch/mipsel.nix ];
|
||||
filesystem = dir {
|
||||
lib = dir {
|
||||
firmware = dir {
|
||||
@@ -35,7 +49,7 @@
|
||||
};
|
||||
};
|
||||
hardware = {
|
||||
defaultOutput = "tftpboot";
|
||||
defaultOutput = "flashimage";
|
||||
loadAddress = "0x80000000";
|
||||
entryPoint = "0x80000000";
|
||||
|
||||
@@ -52,44 +66,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
|
||||
@@ -107,14 +117,10 @@
|
||||
${openwrt.applyPatches.ramips}
|
||||
'';
|
||||
config = {
|
||||
MIPS_ELF_APPENDED_DTB = "y";
|
||||
OF = "y";
|
||||
USE_OF = "y";
|
||||
|
||||
RALINK = "y";
|
||||
PCI = "y";
|
||||
SOC_MT7620 = "y";
|
||||
CPU_LITTLE_ENDIAN= "y";
|
||||
|
||||
SERIAL_8250_CONSOLE = "y";
|
||||
SERIAL_8250 = "y";
|
||||
@@ -125,7 +131,6 @@
|
||||
CONSOLE_LOGLEVEL_QUIET = "4";
|
||||
|
||||
MTD = "y";
|
||||
MTD_CMDLINE_PARTS = "y";
|
||||
MTD_BLOCK = "y"; # fix undefined ref to register_mtd_blktrans_dev
|
||||
|
||||
SPI = "y";
|
||||
@@ -138,7 +143,6 @@
|
||||
REGULATOR_FIXED_VOLTAGE = "y";
|
||||
|
||||
NET = "y";
|
||||
NETDEVICES = "y";
|
||||
ETHERNET = "y";
|
||||
|
||||
PHYLIB = "y";
|
||||
@@ -148,35 +152,21 @@
|
||||
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";
|
||||
|
||||
PHY_RALINK_USB = "y";
|
||||
|
||||
CMDLINE_PARTITION = "y";
|
||||
EARLY_PRINTK = "y";
|
||||
|
||||
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
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
# This "device" generates images that can be used with the QEMU
|
||||
# emulator. The default output is a directory containing separate
|
||||
# kernel ("Image" format) and root filesystem (squashfs or jffs2)
|
||||
# images
|
||||
{
|
||||
system = {
|
||||
crossSystem = {
|
||||
config = "aarch64-unknown-linux-musl";
|
||||
};
|
||||
};
|
||||
|
||||
# this device is described by the "qemu" device
|
||||
description = "";
|
||||
|
||||
|
||||
module = {pkgs, config, ... }: {
|
||||
imports = [ ../../modules/arch/aarch64.nix ];
|
||||
kernel = {
|
||||
src = pkgs.pkgsBuildBuild.fetchurl {
|
||||
name = "linux.tar.gz";
|
||||
url = "https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.15.71.tar.gz";
|
||||
hash = "sha256-yhO2cXIeIgUxkSZf/4aAsF11uxyh+UUZu6D1h92vCD8=";
|
||||
};
|
||||
config = {
|
||||
VIRTUALIZATION = "y";
|
||||
PCI_HOST_GENERIC="y";
|
||||
|
||||
MTD = "y";
|
||||
MTD_BLOCK2MTD = "y";
|
||||
MTD_BLOCK = "y";
|
||||
|
||||
VIRTIO_MENU = "y";
|
||||
PCI = "y";
|
||||
VIRTIO_PCI = "y";
|
||||
BLOCK = "y";
|
||||
VIRTIO_BLK = "y";
|
||||
VIRTIO_NET = "y";
|
||||
|
||||
SERIAL_EARLYCON_ARM_SEMIHOST = "y"; # earlycon=smh
|
||||
SERIAL_AMBA_PL011 = "y";
|
||||
SERIAL_AMBA_PL011_CONSOLE = "y";
|
||||
};
|
||||
};
|
||||
boot.commandLine = [
|
||||
"console=ttyAMA0,38400"
|
||||
];
|
||||
hardware =
|
||||
let
|
||||
mac80211 = pkgs.mac80211.override {
|
||||
drivers = ["mac80211_hwsim"];
|
||||
klibBuild = config.system.outputs.kernel.modulesupport;
|
||||
};
|
||||
in {
|
||||
defaultOutput = "vmroot";
|
||||
loadAddress = "0x0";
|
||||
entryPoint = "0x0";
|
||||
rootDevice = "/dev/mtdblock0";
|
||||
|
||||
flash.eraseBlockSize = "65536"; # c.f. pkgs/mips-vm/mips-vm.sh
|
||||
networkInterfaces =
|
||||
let inherit (config.system.service.network) link;
|
||||
in {
|
||||
wan = link.build { ifname = "eth0"; };
|
||||
lan = link.build { ifname = "eth1"; };
|
||||
|
||||
wlan_24 = link.build {
|
||||
ifname = "wlan0";
|
||||
dependencies = [ mac80211 ];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
}
|
||||
+39
-16
@@ -12,7 +12,35 @@
|
||||
};
|
||||
};
|
||||
|
||||
description = ''
|
||||
QEMU
|
||||
****
|
||||
|
||||
This is not a hardware device. This target produces an image for
|
||||
QEMU, the "generic and open source machine emulator and
|
||||
virtualizer".
|
||||
|
||||
Liminix can build QEMU for both MIPS (:code:`qemu` device) and Aarch64 (:code:`qemu-aarch64` device)
|
||||
|
||||
MIPS QEMU emulates a "Malta" board, which was an ATX form factor
|
||||
evaluation board made by MIPS Technologies, but mostly in Liminix
|
||||
we use paravirtualized devices (Virtio) instead of emulating
|
||||
hardware. For Aarch64 we use the QEMU "virt" board.
|
||||
|
||||
Building an image for QEMU results in a :file:`result/` directory
|
||||
containing ``run.sh`` ``vmlinux``, ``rootfs`` and possibly
|
||||
(architecture-dependent) ``Image``. To invoke the emulator,
|
||||
run ``run.sh``.
|
||||
|
||||
The configuration includes two emulated "hardware" ethernet
|
||||
devices and the kernel :code:`mac80211_hwsim` module to
|
||||
provide an emulated wlan device. To read more about how
|
||||
to connect to this network, refer to :ref:`qemu-networking`
|
||||
in the Development manual.
|
||||
|
||||
'';
|
||||
module = {pkgs, config, ... }: {
|
||||
imports = [ ../../modules/arch/mipseb.nix ];
|
||||
kernel = {
|
||||
src = pkgs.pkgsBuildBuild.fetchurl {
|
||||
name = "linux.tar.gz";
|
||||
@@ -21,24 +49,17 @@
|
||||
};
|
||||
config = {
|
||||
MIPS_MALTA= "y";
|
||||
CPU_LITTLE_ENDIAN= "n";
|
||||
CPU_BIG_ENDIAN= "y";
|
||||
CPU_MIPS32_R2= "y";
|
||||
|
||||
MTD = "y";
|
||||
MTD_BLOCK2MTD = "y";
|
||||
MTD_BLKDEVS = "y";
|
||||
MTD_BLOCK = "y";
|
||||
|
||||
SQUASHFS = "y";
|
||||
SQUASHFS_XZ = "y";
|
||||
|
||||
VIRTIO_MENU = "y";
|
||||
PCI = "y";
|
||||
VIRTIO_PCI = "y";
|
||||
BLOCK = "y";
|
||||
VIRTIO_BLK = "y";
|
||||
NETDEVICES = "y";
|
||||
VIRTIO_NET = "y";
|
||||
|
||||
SERIAL_8250= "y";
|
||||
@@ -51,19 +72,21 @@
|
||||
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"; };
|
||||
rootDevice = "/dev/mtdblock0";
|
||||
flash.eraseBlockSize = "65536"; # c.f. pkgs/run-liminix-vm/run-liminix-vm.sh
|
||||
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 ];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
{
|
||||
description = ''
|
||||
Turris Omnia
|
||||
************
|
||||
'';
|
||||
|
||||
system = {
|
||||
crossSystem = {
|
||||
config = "armv7l-unknown-linux-musleabihf";
|
||||
};
|
||||
};
|
||||
|
||||
module = {pkgs, config, lib, ... }:
|
||||
let openwrt = pkgs.openwrt; in {
|
||||
imports = [ ../../modules/arch/arm.nix ];
|
||||
kernel = {
|
||||
src = pkgs.pkgsBuildBuild.fetchurl {
|
||||
name = "linux.tar.gz";
|
||||
url = "https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.15.71.tar.gz";
|
||||
hash = "sha256-yhO2cXIeIgUxkSZf/4aAsF11uxyh+UUZu6D1h92vCD8=";
|
||||
};
|
||||
extraPatchPhase = ''
|
||||
${pkgs.openwrt.applyPatches.mvebu}
|
||||
'';
|
||||
config = {
|
||||
PCI = "y";
|
||||
OF = "y";
|
||||
MEMORY = "y"; # for MVEBU_DEVBUS
|
||||
DMADEVICES = "y"; # for MV_XOR
|
||||
CPU_V7 = "y";
|
||||
ARCH_MULTIPLATFORM = "y";
|
||||
ARCH_MVEBU = "y";
|
||||
ARCH_MULTI_V7= "y";
|
||||
PCI_MVEBU = "y";
|
||||
AHCI_MVEBU = "y";
|
||||
MACH_ARMADA_38X = "y";
|
||||
SMP = "y";
|
||||
NR_CPUS = "4";
|
||||
VFP = "y";
|
||||
NEON= "y";
|
||||
|
||||
# WARNING: unmet direct dependencies detected for ARCH_WANT_LIBATA_LEDS
|
||||
ATA = "y";
|
||||
|
||||
# switch is DSA
|
||||
# CONFIG_NET_DSA_MV88E6060=y
|
||||
# CONFIG_NET_DSA_MV88E6XXX=y
|
||||
# CONFIG_NET_DSA_MV88E6XXX_GLOBAL2=y
|
||||
|
||||
# CONFIG_REGMAP=y
|
||||
# CONFIG_REGMAP_I2C=y
|
||||
# CONFIG_REGMAP_SPI=y
|
||||
# CONFIG_REGMAP_MMIO=y
|
||||
|
||||
PSTORE = "y";
|
||||
PSTORE_RAM = "y";
|
||||
PSTORE_CONSOLE = "y";
|
||||
PSTORE_DEFLATE_COMPRESS = "n";
|
||||
|
||||
SERIAL_8250 = "y";
|
||||
SERIAL_8250_CONSOLE = "y";
|
||||
SERIAL_OF_PLATFORM="y";
|
||||
SERIAL_MVEBU_UART = "y";
|
||||
SERIAL_MVEBU_CONSOLE = "y";
|
||||
|
||||
SERIAL_8250_DMA= "y";
|
||||
SERIAL_8250_DW= "y";
|
||||
SERIAL_8250_EXTENDED= "y";
|
||||
SERIAL_8250_MANY_PORTS= "y";
|
||||
SERIAL_8250_SHARE_IRQ= "y";
|
||||
OF_ADDRESS= "y";
|
||||
OF_MDIO= "y";
|
||||
|
||||
MVEBU_DEVBUS= "y"; # "Device Bus controller ... flash devices such as NOR, NAND, SRAM, and FPGA"
|
||||
MVMDIO= "y";
|
||||
MVNETA= "y";
|
||||
MVNETA_BM= "y";
|
||||
MVNETA_BM_ENABLE= "y";
|
||||
MVPP2= "y";
|
||||
MV_XOR= "y";
|
||||
};
|
||||
};
|
||||
boot = {
|
||||
commandLine = [ "console=ttyS0,115200" ];
|
||||
imageFormat = "fit";
|
||||
};
|
||||
|
||||
hardware = {
|
||||
defaultOutput = "flashimage";
|
||||
loadAddress = "0x00008000";
|
||||
entryPoint = "0x00008000";
|
||||
rootDevice = "/dev/mtdblock0";
|
||||
dts = {
|
||||
src = "${config.system.outputs.kernel.modulesupport}/arch/arm/boot/dts/armada-385-turris-omnia.dts";
|
||||
includes = [
|
||||
# "${openwrt.src}/target/linux/mediatek/dts"
|
||||
"${config.system.outputs.kernel.modulesupport}/arch/arm/boot/dts/"
|
||||
];
|
||||
};
|
||||
|
||||
networkInterfaces =
|
||||
let
|
||||
inherit (config.system.service.network) link;
|
||||
inherit (config.system.service) bridge;
|
||||
in rec {
|
||||
lan = link.build { ifname = "eth0"; };
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
}
|
||||
+198
@@ -0,0 +1,198 @@
|
||||
System Administration
|
||||
#####################
|
||||
|
||||
Services on a running system
|
||||
****************************
|
||||
|
||||
* add an s6-rc cheatsheet here
|
||||
|
||||
|
||||
Flashing and updating
|
||||
*********************
|
||||
|
||||
|
||||
|
||||
Flashing from Liminix
|
||||
=====================
|
||||
|
||||
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 -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 build the permanent
|
||||
image and copy it to the device with :command:`ssh`
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
build-machine$ nix-build -I liminix-config=./examples/arhcive.nix \
|
||||
--arg device "import ./devices/gl-ar750"
|
||||
-A outputs.default && \
|
||||
(tar chf - result | ssh root@the-device tar -C /run -xvf -)
|
||||
|
||||
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 use U-Boot and a TFTP
|
||||
server to download and flash the image. This is quite
|
||||
hardware-specific, and sometimes involves soldering: please refer
|
||||
to the :ref:`development manual <tftp server>`.
|
||||
|
||||
|
||||
Flashing from OpenWrt
|
||||
=====================
|
||||
|
||||
.. CAUTION:: Untested! A previous version of these instructions
|
||||
(without the -e flag) led to bricking the device
|
||||
when flashing a jffs2 image. If you are reading
|
||||
this message, nobody has yet reported on whether the
|
||||
new instructions are any better.
|
||||
|
||||
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 -e -r write /tmp/firmware.bin firmware
|
||||
|
||||
The options to this command are for "erase before writing" and "reboot
|
||||
after writing".
|
||||
|
||||
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.
|
||||
+12
-1
@@ -13,7 +13,10 @@ author = 'Daniel Barlow'
|
||||
# -- General configuration ---------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
||||
|
||||
extensions = []
|
||||
extensions = [
|
||||
'sphinx.ext.autosectionlabel'
|
||||
]
|
||||
autosectionlabel_prefix_document = True
|
||||
|
||||
templates_path = ['_templates']
|
||||
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
||||
@@ -25,3 +28,11 @@ exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
||||
|
||||
html_theme = 'alabaster'
|
||||
html_static_path = ['_static']
|
||||
|
||||
html_theme_options = {
|
||||
'logo': '/logo.svg',
|
||||
'globaltoc_collapse': 'false',
|
||||
'page_width': '90%',
|
||||
'body_max_width': '90%',
|
||||
'description': 'A Nix-based OpenWrt-style embedded Linux system for consumer wifi routers'
|
||||
}
|
||||
|
||||
@@ -0,0 +1,259 @@
|
||||
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
|
||||
*********************
|
||||
|
||||
Modules in Liminix conventionally live in
|
||||
:file:`modules/somename/default.nix`. If you want or need to
|
||||
write your own, you may wish to refer to the
|
||||
examples there in conjunction with reading this section.
|
||||
|
||||
A module is a function that accepts ``{lib, pkgs, config, ... }`` and
|
||||
returns an attrset with keys ``imports, options config``.
|
||||
|
||||
* ``imports`` is a list of paths to the other modules required by this one
|
||||
|
||||
* ``options`` is a nested set of option declarations
|
||||
|
||||
* ``config`` is a nested set of option definitions
|
||||
|
||||
The NixOS manual section `Writing NixOS Modules
|
||||
<https://nixos.org/manual/nixos/stable/#sec-writing-modules>`_ is a
|
||||
quite comprehensive reference to writing NixOS modules, which is also
|
||||
mostly applicable to Liminix except that it doesn't cover
|
||||
service templates.
|
||||
|
||||
Service templates
|
||||
=================
|
||||
|
||||
To expose a service template in a module, it needs the following:
|
||||
|
||||
* an option declaration for ``system.service.myservicename`` with the
|
||||
type of ``liminix.lib.types.serviceDefn``
|
||||
|
||||
.. code-block:: nix
|
||||
|
||||
options = {
|
||||
system.service.cowsay = mkOption {
|
||||
type = liminix.lib.types.serviceDefn;
|
||||
};
|
||||
};
|
||||
|
||||
* an option definition for the same key, which specifies where to
|
||||
import the service template from (often :file:`./service.nix`)
|
||||
and the types of its parameters.
|
||||
|
||||
.. code-block:: nix
|
||||
|
||||
config.system.service.cowsay = liminix.callService ./service.nix {
|
||||
address = mkOption {
|
||||
type = types.str;
|
||||
default = "0.0.0.0";
|
||||
description = "Listen on specified address";
|
||||
example = "127.0.0.1";
|
||||
};
|
||||
port = mkOption {
|
||||
type = types.port;
|
||||
default = 22;
|
||||
description = "Listen on specified TCP port";
|
||||
};
|
||||
breed = mkOption {
|
||||
type = types.str;
|
||||
default = "British Friesian"
|
||||
description = "Breed of the cow";
|
||||
};
|
||||
};
|
||||
|
||||
Then you need to provide the service template itself, probably in
|
||||
:file:`./service.nix`:
|
||||
|
||||
.. code-block:: nix
|
||||
|
||||
{
|
||||
# any nixpkgs package can be named here
|
||||
liminix
|
||||
, cowsayd
|
||||
, serviceFns
|
||||
, lib
|
||||
}:
|
||||
# these are the parameters declared in the callService invocation
|
||||
{ address, port, breed} :
|
||||
let
|
||||
inherit (liminix.services) longrun;
|
||||
inherit (lib.strings) escapeShellArg;
|
||||
in longrun {
|
||||
name = "cowsayd";
|
||||
run = "${cowsayd}/bin/cowsayd --address ${address} --port ${builtins.toString port} --breed ${escapeShellArg breed}";
|
||||
}
|
||||
|
||||
.. tip::
|
||||
|
||||
Not relevant to module-based services specifically, but a common
|
||||
gotcha when specifiying services is forgetting to transform "rich"
|
||||
parameter values into text when composing a command for the shell
|
||||
to execute. Note here that the port number, an integer, is
|
||||
stringified with ``toString``, and the name of the breed,
|
||||
which may contain spaces, is
|
||||
escaped with ``escapeShellArg``
|
||||
|
||||
Types
|
||||
=====
|
||||
|
||||
All of the NixOS module types are available in Liminix. These
|
||||
Liminix-specific types also exist in ``pkgs.liminix.lib.types``:
|
||||
|
||||
* ``service``: an s6-rc service
|
||||
* ``interface``: an s6-rc service which specifies a network
|
||||
interface
|
||||
* ``serviceDefn``: a service "template" definition
|
||||
|
||||
In the future it is likely that we will extend this to include other
|
||||
useful types in the networking domain: for example; IP address,
|
||||
network prefix or netmask, protocol family and others as we find them.
|
||||
@@ -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`.
|
||||
|
||||
|
||||
|
||||
@@ -27,13 +27,13 @@ To build it,
|
||||
|
||||
nix-build -I liminix-config=path/to/your/configuration.nix --arg device "import ./devices/qemu" -A outputs.default
|
||||
|
||||
In a ``buildEnv`` nix-shell, you can use the :command:`mips-vm` command
|
||||
In a ``buildEnv`` nix-shell, you can use the :command:`run-liminix-vm` command
|
||||
to run Qemu with appropriate options. It connects the Liminix
|
||||
serial console and the `QEMU monitor <https://www.qemu.org/docs/master/system/monitor.html>`_ to stdin/stdout. Use ^P (not ^A) to switch to the monitor.
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
nix-shell --run "mips-vm result/vmlinux result/squashfs"
|
||||
nix-shell --run "run-liminix-vm result/vmlinux result/squashfs"
|
||||
|
||||
If you run with ``--background /path/to/some/directory`` as the first
|
||||
parameter, it will fork into the background and open Unix sockets in
|
||||
@@ -41,6 +41,8 @@ that directory for console and monitor. Use :command:`connect-vm`
|
||||
(also in the ``buildEnv`` environment) to connect to either of these
|
||||
sockets, and ^O to disconnect.
|
||||
|
||||
.. _qemu-networking:
|
||||
|
||||
Networking
|
||||
==========
|
||||
|
||||
@@ -53,7 +55,7 @@ the right way:
|
||||
* multicast 230.0.0.1:1235 : lan
|
||||
* multicast 230.0.0.1:1236 : world (the internet)
|
||||
|
||||
A VM started with :command:`mips-vm` is connected to "lan" and "access", and
|
||||
A VM started with :command:`run-liminix-vm` is connected to "lan" and "access", and
|
||||
the emulated border network gateway (see below) runs PPPoE and is
|
||||
connected to "access" and "world".
|
||||
|
||||
@@ -87,6 +89,8 @@ time with configurations for RP-PPPoE and/or Accel PPP.`
|
||||
Hardware devices
|
||||
****************
|
||||
|
||||
.. _tftp server:
|
||||
|
||||
How you get your image onto hardware will vary according to the
|
||||
device, but is likely to involve taking it apart to add wires to
|
||||
serial console pads/headers, then using U-Boot to fetch images over
|
||||
-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/>`_
|
||||
@@ -15,8 +15,9 @@ let
|
||||
builtins.attrNames
|
||||
(lib.filterAttrsRecursive
|
||||
(n: t:
|
||||
(t=="directory") ||
|
||||
((t=="regular") && ((builtins.match ".*\\.nix$" n) != null)))
|
||||
(n != "arch") &&
|
||||
((t=="directory") ||
|
||||
((t=="regular") && ((builtins.match ".*\\.nix$" n) != null))))
|
||||
(builtins.readDir ../modules));
|
||||
modulefiles = builtins.map (n: builtins.toPath "${../modules}/${n}") modulenames;
|
||||
eval = (lib.evalModules {
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
with import <nixpkgs> {} ;
|
||||
|
||||
let
|
||||
devices =
|
||||
builtins.readDir ../devices;
|
||||
texts = lib.mapAttrsToList (n: t:
|
||||
let d = import ../devices/${n}/default.nix;
|
||||
d' = { description = "no description for ${n}"; } // d;
|
||||
in d'.description )
|
||||
devices;
|
||||
in
|
||||
writeText "hwdoc" ''
|
||||
Supported hardware
|
||||
##################
|
||||
|
||||
${lib.concatStringsSep "\n\n" texts}
|
||||
|
||||
''
|
||||
+6
-3
@@ -6,9 +6,12 @@ Liminix
|
||||
:caption: Contents:
|
||||
|
||||
intro
|
||||
user
|
||||
developer
|
||||
etc
|
||||
tutorial
|
||||
configuration
|
||||
admin
|
||||
development
|
||||
modules
|
||||
hardware
|
||||
|
||||
|
||||
Indices and tables
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
Module options
|
||||
##############
|
||||
|
||||
.. include:: modules-generated.rst
|
||||
@@ -0,0 +1,329 @@
|
||||
Tutorial
|
||||
########
|
||||
|
||||
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.
|
||||
|
||||
|
||||
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
|
||||
|
||||
./result/run.sh
|
||||
|
||||
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 if necessary.
|
||||
|
||||
.. warning:: There is always a risk of rendering your device
|
||||
unbootable by flashing it with an image that doesn't
|
||||
work. The GL-MT300A has a builtin "debrick" procedure in
|
||||
the boot monitor and is also comparatively simple to
|
||||
attach serial cables to (soldering not required), so it
|
||||
is lower-risk than some devices. 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.
|
||||
|
||||
Routing
|
||||
*******
|
||||
|
||||
The third example :file:`examples/demo.nix` is a fully-functional home
|
||||
"WiFi router" - although you will have to edit it a bit before it will
|
||||
actually work for you. Copy :file:`examples/demo.nix` to
|
||||
:file:`my-router.nix` (or other name of your choice) and open it in
|
||||
your favourite text editor. Everywhere that the text :code:`EDIT`
|
||||
appears is either a place you probably want to change or a place you
|
||||
almost certainly need to change.
|
||||
|
||||
There's a lot going on in this configuration:
|
||||
|
||||
* it provides a wireless access point using the :code:`hostapd`
|
||||
service: in this stanza you can change the ssid, the channel,
|
||||
the passphrase etc.
|
||||
|
||||
* the wireless lan and wired lan are bridged together with the
|
||||
:code:`bridge` service, so that your wired and wireless clients appear
|
||||
to be on the same network.
|
||||
|
||||
.. tip:: If you were using a hardware device that provides both 2.4GHz
|
||||
and 5GHz wifi, you'd probably find that it has two wireless
|
||||
devices (often called wlan0 and wlan1). In Liminix we handle
|
||||
this by running two :code:`hostapd` services, and adding
|
||||
both of them to the network bridge along with the wired lan.
|
||||
(You can see an example in :file:`examples/rotuer.nix`)
|
||||
|
||||
* we use the combination DNS and DHCP daemon provided by the
|
||||
:code:`dnsmasq` service, which you can configure
|
||||
|
||||
* the upstream network is "PPP over Ethernet", provided by the
|
||||
:code:`pppoe` service. Assuming that your ISP uses this standard,
|
||||
they will have provided you with a PPP username and password
|
||||
(sometimes this will be listed as "PAP" or "CHAP") which you can edit
|
||||
into the configuration
|
||||
|
||||
* this example supports the new [#ipv6]_ Internet Protocol v6
|
||||
as well as traditional IPv4. Configuring IPv6 seems to
|
||||
vary from one ISP to the next: this example expects them
|
||||
to be providing IP address allocation and "prefix delegation"
|
||||
using DHCP6.
|
||||
|
||||
Build it using the same method as the previous example
|
||||
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
nix-build -I liminix-config=./my-router.nix \
|
||||
--arg device "import ./devices/gl-mt300a" -A outputs.default
|
||||
|
||||
and then you can flash it to the device.
|
||||
|
||||
|
||||
Bonus: in-place updates
|
||||
=======================
|
||||
|
||||
This configuration uses a writable filesystem (see the line
|
||||
:code:`rootfsType = "jffs2"`), which means that once you've flashed it
|
||||
for the first time, you can make further updates over SSH onto the
|
||||
running router. To try this, make a small change (I'd suggest changing
|
||||
the hostname) and then run
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
nix-shell --run "liminix-rebuild root@address-of-the-device -I liminix-config=./my-router.nix --arg device "import ./devices/gl-ar750""
|
||||
|
||||
(This requires the device to be network-accessible from your build
|
||||
machine, which for a test/demo system might involve a second network
|
||||
device in your build system - USB ethernet adapters are cheap - or
|
||||
a bit of messing around unplugging cables.)
|
||||
|
||||
For more information about :code:`liminix-rebuild`, see the manual section :ref:`admin:Rebuilding the system`.
|
||||
|
||||
|
||||
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.
|
||||
|
||||
* 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.
|
||||
|
||||
|
||||
|
||||
.. rubric:: Footnotes
|
||||
|
||||
.. [#ipv6] `RFC1883 Internet Protocol, Version 6 <https://datatracker.ietf.org/doc/html/rfc1883>`_ was published in 1995, so only "new" when Bill Clinton was US President
|
||||
-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 +0,0 @@
|
||||
(local { : merge : split : file-exists? : 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]
|
||||
(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))))
|
||||
(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)))
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
writeFennelScript
|
||||
, linotify
|
||||
, anoia
|
||||
}:
|
||||
writeFennelScript "acquire-delegated-prefix"
|
||||
[ linotify anoia ]
|
||||
./acquire-delegated-prefix.fnl
|
||||
@@ -1,60 +0,0 @@
|
||||
(local { : merge : split : file-exists? : 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
|
||||
})
|
||||
|
||||
(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))))
|
||||
(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)))
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
writeFennelScript
|
||||
, linotify
|
||||
, anoia
|
||||
}:
|
||||
writeFennelScript "acquire-wan-address"
|
||||
[ linotify anoia ]
|
||||
./acquire-wan-address.fnl
|
||||
+20
-87
@@ -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";
|
||||
@@ -52,7 +51,6 @@ in rec {
|
||||
SCSI = "y";
|
||||
BLK_DEV_SD = "y";
|
||||
USB_PRINTER = "y";
|
||||
PARTITION_ADVANCED = "y";
|
||||
MSDOS_PARTITION = "y";
|
||||
EFI_PARTITION = "y";
|
||||
EXT4_FS = "y";
|
||||
@@ -62,55 +60,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 +83,6 @@ in rec {
|
||||
done
|
||||
)
|
||||
'';
|
||||
down = ''
|
||||
rm -rf /run/service-state/${name}/
|
||||
'';
|
||||
};
|
||||
filesystem = dir {
|
||||
etc = dir {
|
||||
@@ -134,35 +91,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 +146,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 = [
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
# This is an example configuration for a "typical" small office/home
|
||||
# router and wifi access point.
|
||||
|
||||
# You need to copy it to another filename and change the configuration
|
||||
# wherever the text "EDIT" appears - please consult the tutorial
|
||||
# documentation for details.
|
||||
|
||||
{ config, pkgs, lib, ... } :
|
||||
let
|
||||
inherit (pkgs.liminix.services) bundle oneshot longrun;
|
||||
inherit (pkgs) serviceFns;
|
||||
# EDIT: you can pick your preferred RFC1918 address space
|
||||
# for NATted connections, if you don't like this one.
|
||||
ipv4LocalNet = "10.8.0";
|
||||
svc = config.system.service;
|
||||
|
||||
in rec {
|
||||
boot = {
|
||||
tftp = {
|
||||
freeSpaceBytes = 3 * 1024 * 1024;
|
||||
serverip = "10.0.0.1";
|
||||
ipaddr = "10.0.0.8";
|
||||
};
|
||||
};
|
||||
|
||||
imports = [
|
||||
../modules/bridge
|
||||
../modules/dhcp6c
|
||||
../modules/dnsmasq
|
||||
../modules/firewall
|
||||
../modules/hostapd
|
||||
../modules/network
|
||||
../modules/ntp
|
||||
../modules/ppp
|
||||
../modules/ssh
|
||||
../modules/standard.nix
|
||||
../modules/vlan
|
||||
../modules/wlan.nix
|
||||
];
|
||||
rootfsType = "jffs2";
|
||||
hostname = "the-internet"; # EDIT
|
||||
|
||||
services.hostap = svc.hostapd.build {
|
||||
interface = config.hardware.networkInterfaces.wlan;
|
||||
# EDIT: you will want to change the obvious things
|
||||
# here to values of your choice
|
||||
params = {
|
||||
ssid = "the-internet";
|
||||
channel = "1";
|
||||
country_code = "GB";
|
||||
wpa_passphrase = "not a real wifi password";
|
||||
|
||||
hw_mode="g";
|
||||
ieee80211n = 1;
|
||||
auth_algs = 1; # 1=wpa2, 2=wep, 3=both
|
||||
wpa = 2; # 1=wpa, 2=wpa2, 3=both
|
||||
wpa_key_mgmt = "WPA-PSK";
|
||||
wpa_pairwise = "TKIP CCMP"; # auth for wpa (may not need this?)
|
||||
rsn_pairwise = "CCMP"; # auth for wpa2
|
||||
wmm_enabled = 1;
|
||||
};
|
||||
};
|
||||
|
||||
services.int = svc.network.address.build {
|
||||
interface = svc.bridge.primary.build { ifname = "int"; };
|
||||
family = "inet"; address = "${ipv4LocalNet}.1"; prefixLength = 16;
|
||||
};
|
||||
|
||||
services.bridge = svc.bridge.members.build {
|
||||
primary = services.int;
|
||||
members = with config.hardware.networkInterfaces;
|
||||
[ wlan lan ];
|
||||
};
|
||||
|
||||
services.ntp = svc.ntp.build {
|
||||
pools = { "pool.ntp.org" = ["iburst"]; };
|
||||
makestep = { threshold = 1.0; limit = 3; };
|
||||
};
|
||||
|
||||
services.sshd = svc.ssh.build { };
|
||||
|
||||
users.root = {
|
||||
# EDIT: choose a root password and then use
|
||||
# "mkpasswd -m sha512crypt" to determine the hash.
|
||||
# It should start wirh $6$.
|
||||
passwd = "$6$6HG7WALLQQY1LQDE$428cnouMJ7wVmyK9.dF1uWs7t0z9ztgp3MHvN5bbeo0M4Kqg/u2ThjoSHIjCEJQlnVpDOaEKcOjXAlIClHWN21";
|
||||
openssh.authorizedKeys.keys = [
|
||||
# EDIT: you can add your ssh pubkey here
|
||||
# "ssh-rsa AAAAB3NzaC1....H6hKd user@example.com";
|
||||
];
|
||||
};
|
||||
|
||||
services.dns =
|
||||
let interface = services.int;
|
||||
in svc.dnsmasq.build {
|
||||
resolvconf = services.resolvconf;
|
||||
inherit interface;
|
||||
ranges = [
|
||||
"${ipv4LocalNet}.10,${ipv4LocalNet}.249"
|
||||
# EDIT: ... maybe. In this example we use "ra-stateless",
|
||||
# meaning dnsmasq 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.
|
||||
# If you didn't understand the preceding sentence then
|
||||
# the default is _probably_ fine, but if you need
|
||||
# a DHCP-only IPv6 network or some other different
|
||||
# configuration, this is the place to change it.
|
||||
"::,constructor:$(output ${interface} ifname),ra-stateless"
|
||||
];
|
||||
# EDIT: choose a domain name for the DNS names issued for your
|
||||
# DHCP-issued hosts
|
||||
domain = "lan.example.com";
|
||||
};
|
||||
|
||||
services.wan = svc.pppoe.build {
|
||||
interface = config.hardware.networkInterfaces.wan;
|
||||
ppp-options = [
|
||||
"debug" "+ipv6" "noauth"
|
||||
# EDIT: change the strings "chap-username"
|
||||
# and "chap-secret" to match the username/password
|
||||
# provided by your ISP for PPP logins
|
||||
"name" "chap-username"
|
||||
"password" "chap-secret"
|
||||
];
|
||||
};
|
||||
|
||||
services.resolvconf = oneshot rec {
|
||||
dependencies = [ services.wan ];
|
||||
name = "resolvconf";
|
||||
up = ''
|
||||
. ${serviceFns}
|
||||
( in_outputs ${name}
|
||||
echo "nameserver $(output ${services.wan} ns1)" > resolv.conf
|
||||
echo "nameserver $(output ${services.wan} ns2)" >> resolv.conf
|
||||
chmod 0444 resolv.conf
|
||||
)
|
||||
'';
|
||||
};
|
||||
|
||||
filesystem =
|
||||
let inherit (pkgs.pseudofile) dir symlink;
|
||||
in dir {
|
||||
etc = dir {
|
||||
"resolv.conf" = symlink "${services.resolvconf}/.outputs/resolv.conf";
|
||||
};
|
||||
};
|
||||
|
||||
services.defaultroute4 = svc.network.route.build {
|
||||
via = "$(output ${services.wan} address)";
|
||||
target = "default";
|
||||
dependencies = [ services.wan ];
|
||||
};
|
||||
|
||||
services.defaultroute6 = svc.network.route.build {
|
||||
via = "$(output ${services.wan} ipv6-peer-address)";
|
||||
target = "default";
|
||||
interface = services.wan;
|
||||
};
|
||||
|
||||
services.firewall = svc.firewall.build {
|
||||
ruleset = import ./demo-firewall.nix;
|
||||
};
|
||||
|
||||
services.packet_forwarding = svc.network.forward.build { };
|
||||
|
||||
# We expect the ISP uses DHCP6 to issue IPv6 addresses. There is a
|
||||
# service to request address information in the form of a DHCP
|
||||
# lease, and two dependent services that listen for updates to the
|
||||
# DHCP address information and update the addresses of the WAN and
|
||||
# LAN interfaces respectively.
|
||||
|
||||
services.dhcp6c =
|
||||
let client = svc.dhcp6c.client.build {
|
||||
interface = services.wan;
|
||||
};
|
||||
in bundle {
|
||||
name = "dhcp6c";
|
||||
contents = [
|
||||
(svc.dhcp6c.prefix.build {
|
||||
# if your ISP provides you a real IPv6 prefix for your local
|
||||
# network (usually a /64 or /48 or something in between the
|
||||
# two), this service subscribes to that "prefix delegation"
|
||||
# information, and uses it to assign an address to the LAN
|
||||
# device. dnsmasq will notice this address and use it to
|
||||
# form the addresses it hands out to devices on the lan
|
||||
inherit client;
|
||||
interface = services.int;
|
||||
})
|
||||
(svc.dhcp6c.address.build {
|
||||
# if your ISP provides you a regular global IPv6 address,
|
||||
# this service subscribes to that information and assigns
|
||||
# the address to the WAN device.
|
||||
inherit client;
|
||||
interface = services.wan;
|
||||
})
|
||||
];
|
||||
};
|
||||
|
||||
defaultProfile.packages = with pkgs; [
|
||||
min-collect-garbage
|
||||
];
|
||||
}
|
||||
+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
|
||||
];
|
||||
}
|
||||
+59
-100
@@ -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 bundle;
|
||||
inherit (pkgs) serviceFns;
|
||||
svc = config.system.service;
|
||||
wirelessConfig = {
|
||||
country_code = "GB";
|
||||
@@ -44,8 +35,10 @@ in rec {
|
||||
imports = [
|
||||
../modules/wlan.nix
|
||||
../modules/standard.nix
|
||||
../modules/network
|
||||
../modules/ppp
|
||||
../modules/dnsmasq
|
||||
../modules/dhcp6c
|
||||
../modules/firewall
|
||||
../modules/hostapd
|
||||
../modules/bridge
|
||||
@@ -56,7 +49,7 @@ in rec {
|
||||
hostname = "rotuer";
|
||||
|
||||
services.hostap = svc.hostapd.build {
|
||||
interface = config.hardware.networkInterfaces.wlan_24;
|
||||
interface = config.hardware.networkInterfaces.wlan;
|
||||
params = {
|
||||
ssid = "liminix";
|
||||
hw_mode="g";
|
||||
@@ -66,7 +59,7 @@ in rec {
|
||||
};
|
||||
|
||||
services.hostap5 = svc.hostapd.build {
|
||||
interface = config.hardware.networkInterfaces.wlan_5;
|
||||
interface = config.hardware.networkInterfaces.wlan5;
|
||||
params = rec {
|
||||
ssid = "liminix_5";
|
||||
hw_mode="a";
|
||||
@@ -78,16 +71,18 @@ 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
|
||||
wlan5
|
||||
lan ];
|
||||
};
|
||||
|
||||
services.ntp = svc.ntp.build {
|
||||
pools = { "pool.ntp.org" = ["iburst"]; };
|
||||
@@ -105,8 +100,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,100 +131,55 @@ 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;
|
||||
ruleset = import ./demo-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.dhcp6c =
|
||||
let client = svc.dhcp6c.client.build {
|
||||
interface = services.wan;
|
||||
};
|
||||
in bundle {
|
||||
name = "dhcp6c";
|
||||
contents = [
|
||||
(svc.dhcp6c.prefix.build {
|
||||
inherit client;
|
||||
interface = services.int;
|
||||
})
|
||||
(svc.dhcp6c.address.build {
|
||||
inherit client;
|
||||
interface = services.wan;
|
||||
})
|
||||
];
|
||||
};
|
||||
|
||||
services.dhcp6 =
|
||||
let
|
||||
name = "dhcp6c.wan";
|
||||
in longrun {
|
||||
inherit name;
|
||||
notification-fd = 10;
|
||||
run = ''
|
||||
export SERVICE_STATE=/run/service-state/${name}
|
||||
${pkgs.odhcp6c}/bin/odhcp6c -s ${pkgs.odhcp-script} -e -v -p /run/${name}.pid -P 48 $(output ${services.wan} ifname)
|
||||
)
|
||||
'';
|
||||
dependencies = [ services.wan ];
|
||||
};
|
||||
|
||||
services.acquire-lan-prefix =
|
||||
let script = pkgs.callPackage ./acquire-delegated-prefix.nix { };
|
||||
in longrun {
|
||||
name = "acquire-lan-prefix";
|
||||
run = "${script} /run/service-state/dhcp6c.wan ${services.int.device}";
|
||||
dependencies = [ services.dhcp6 ];
|
||||
};
|
||||
|
||||
services.acquire-wan-address =
|
||||
let script = pkgs.callPackage ./acquire-wan-address.nix { };
|
||||
in longrun {
|
||||
name = "acquire-wan-address";
|
||||
run = "${script} /run/service-state/dhcp6c.wan $(output ${services.wan} ifname)";
|
||||
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
|
||||
];
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
{
|
||||
lzma
|
||||
, stdenv
|
||||
, ubootTools
|
||||
, dtc
|
||||
} :
|
||||
let
|
||||
objcopy = "${stdenv.cc.bintools.targetPrefix}objcopy";
|
||||
in {
|
||||
kernel
|
||||
, commandLine
|
||||
, entryPoint
|
||||
, extraName ? "" # e.g. socFamily
|
||||
, loadAddress
|
||||
, dtb ? null
|
||||
} :
|
||||
stdenv.mkDerivation {
|
||||
name = "kernel.image";
|
||||
phases = [
|
||||
"preparePhase"
|
||||
(if dtb != null then "dtbPhase" else ":")
|
||||
"buildPhase"
|
||||
"installPhase" ];
|
||||
nativeBuildInputs = [
|
||||
lzma
|
||||
dtc
|
||||
stdenv.cc
|
||||
ubootTools
|
||||
];
|
||||
preparePhase = ''
|
||||
cp ${kernel} vmlinux.elf; chmod +w vmlinux.elf
|
||||
'';
|
||||
dtbPhase = ''
|
||||
dtc -I dtb -O dts -o tmp.dts ${dtb}
|
||||
echo '/{ chosen { bootargs = ${builtins.toJSON commandLine}; }; };' >> tmp.dts
|
||||
dtc -I dts -O dtb -o tmp.dtb tmp.dts
|
||||
${objcopy} --update-section .appended_dtb=tmp.dtb vmlinux.elf || ${objcopy} --add-section .appended_dtb=${dtb} vmlinux.elf
|
||||
'';
|
||||
|
||||
buildPhase = ''
|
||||
${objcopy} -O binary -R .reginfo -R .notes -R .note -R .comment -R .mdebug -R .note.gnu.build-id -S vmlinux.elf vmlinux.bin
|
||||
rm -f vmlinux.bin.lzma ; lzma -k -z vmlinux.bin
|
||||
mkimage -A mips -O linux -T kernel -C lzma -a ${loadAddress} -e ${entryPoint} -n 'MIPS Liminix Linux ${extraName}' -d vmlinux.bin.lzma kernel.uimage
|
||||
'';
|
||||
installPhase = ''
|
||||
cp kernel.uimage $out
|
||||
'';
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{ lib, pkgs, config, ...}:
|
||||
{
|
||||
config = {
|
||||
kernel.config = {
|
||||
CPU_LITTLE_ENDIAN= "y";
|
||||
CPU_BIG_ENDIAN= "n";
|
||||
# CMDLINE_FROM_BOOTLOADER availability is conditional
|
||||
# on CMDLINE being set to something non-empty
|
||||
CMDLINE="\"empty=false\"";
|
||||
CMDLINE_FROM_BOOTLOADER = "y";
|
||||
|
||||
OF = "y";
|
||||
# USE_OF = "y";
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{ lib, pkgs, config, ...}:
|
||||
{
|
||||
config = {
|
||||
kernel.config = {
|
||||
MIPS_ELF_APPENDED_DTB = "y";
|
||||
MIPS_BOOTLOADER_CMDLINE_REQUIRE_COOKIE = "y";
|
||||
MIPS_BOOTLOADER_CMDLINE_COOKIE = "\"liminix\"";
|
||||
MIPS_CMDLINE_DTB_EXTEND = "y";
|
||||
|
||||
OF = "y";
|
||||
USE_OF = "y";
|
||||
};
|
||||
boot.commandLine = [
|
||||
"console=ttyS0,115200" # true of all mips we've yet encountered
|
||||
];
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{ lib, pkgs, config, ...}:
|
||||
{
|
||||
imports = [ ./mips.nix ];
|
||||
config = {
|
||||
kernel.config = {
|
||||
CPU_BIG_ENDIAN = "y";
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{ lib, pkgs, config, ...}:
|
||||
{
|
||||
imports = [ ./mips.nix ];
|
||||
config = {
|
||||
kernel.config = {
|
||||
CPU_LITTLE_ENDIAN = "y";
|
||||
};
|
||||
};
|
||||
}
|
||||
+6
-14
@@ -39,7 +39,7 @@ in {
|
||||
};
|
||||
rootfsType = mkOption {
|
||||
default = "squashfs";
|
||||
type = types.enum ["squashfs" "jffs2"];
|
||||
type = types.enum ["squashfs" "jffs2" "ubifs"];
|
||||
};
|
||||
boot = {
|
||||
commandLine = mkOption {
|
||||
@@ -47,6 +47,10 @@ in {
|
||||
default = [];
|
||||
description = "Kernel command line";
|
||||
};
|
||||
imageFormat = mkOption {
|
||||
type = types.enum ["fit" "uimage"];
|
||||
default = "uimage";
|
||||
};
|
||||
tftp = {
|
||||
loadAddress = mkOption {
|
||||
type = types.str;
|
||||
@@ -80,20 +84,8 @@ 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"
|
||||
"panic=10 oops=panic init=/bin/init loglevel=8"
|
||||
"root=${config.hardware.rootDevice}"
|
||||
"rootfstype=${config.rootfsType}"
|
||||
"fw_devlink=off"
|
||||
|
||||
@@ -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; }
|
||||
@@ -0,0 +1,31 @@
|
||||
(local { : system } (require :anoia))
|
||||
(local svc (require :anoia.svc))
|
||||
|
||||
(fn changes [old-addresses new-addresses]
|
||||
(let [added {}
|
||||
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)))
|
||||
|
||||
(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 }
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
writeFennel
|
||||
, linotify
|
||||
, anoia
|
||||
, lua
|
||||
}:
|
||||
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)
|
||||
@@ -0,0 +1,35 @@
|
||||
(local { : system } (require :anoia))
|
||||
(local svc (require :anoia.svc))
|
||||
|
||||
;; 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 {}]
|
||||
(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)))
|
||||
|
||||
(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 }
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
writeFennel
|
||||
, linotify
|
||||
, anoia
|
||||
, lua
|
||||
}:
|
||||
writeFennel "acquire-wan-address" {
|
||||
packages = [ linotify anoia lua.pkgs.luafilesystem ];
|
||||
mainFunction = "run";
|
||||
} ./acquire-wan-address.fnl
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
liminix
|
||||
, lib
|
||||
, callPackage
|
||||
}:
|
||||
{ client, interface } :
|
||||
let
|
||||
inherit (liminix.services) longrun;
|
||||
inherit (lib) mkOption types;
|
||||
name = "dhcp6c.addr.${client.name}.${interface.name}";
|
||||
script = callPackage ./acquire-wan-address.nix { };
|
||||
in longrun {
|
||||
inherit name;
|
||||
run = "${script} /run/service-state/${client.name} $(output ${interface} ifname)";
|
||||
dependencies = [ client interface ];
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
liminix
|
||||
, lib
|
||||
, odhcp6c
|
||||
, odhcp-script
|
||||
}:
|
||||
{ interface } :
|
||||
let
|
||||
inherit (liminix.services) longrun;
|
||||
inherit (lib) mkOption types;
|
||||
name = "dhcp6c.${interface.name}";
|
||||
in longrun {
|
||||
inherit name;
|
||||
notification-fd = 10;
|
||||
run = ''
|
||||
export SERVICE_STATE=/run/service-state/${name}
|
||||
${odhcp6c}/bin/odhcp6c -s ${odhcp-script} -e -v -p /run/${name}.pid -P0 $(output ${interface} ifname)
|
||||
)
|
||||
'';
|
||||
dependencies = [ interface ];
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
## DHCP6 client module
|
||||
## ===================
|
||||
##
|
||||
## This is for use if you have an IPv6-capable upstream that provides
|
||||
## address information and/or prefix delegation using DHCP6. It
|
||||
## provides a service to request address information in the form of a
|
||||
## DHCP lease, and two dependent services that listen for updates
|
||||
## to the DHCP address information and can be used to update
|
||||
## addresses of network interfaces that you want to assign those
|
||||
## prefixes to
|
||||
|
||||
{ lib, pkgs, config, ...}:
|
||||
let
|
||||
inherit (lib) mkOption types;
|
||||
inherit (pkgs.liminix.services) oneshot;
|
||||
inherit (pkgs) liminix;
|
||||
in
|
||||
{
|
||||
options = {
|
||||
system.service.dhcp6c = {
|
||||
client = mkOption { type = liminix.lib.types.serviceDefn; };
|
||||
prefix = mkOption { type = liminix.lib.types.serviceDefn; };
|
||||
address = mkOption { type = liminix.lib.types.serviceDefn; };
|
||||
};
|
||||
};
|
||||
config.system.service.dhcp6c = {
|
||||
client = liminix.callService ./client.nix {
|
||||
interface = mkOption {
|
||||
type = liminix.lib.types.interface;
|
||||
description = "interface (usually WAN) to query for DHCP6";
|
||||
};
|
||||
};
|
||||
address = liminix.callService ./address.nix {
|
||||
client = mkOption {
|
||||
type = types.anything; # liminix.lib.types.service;
|
||||
};
|
||||
interface = mkOption {
|
||||
type = liminix.lib.types.interface;
|
||||
description = "interface to assign the address to";
|
||||
};
|
||||
};
|
||||
prefix = liminix.callService ./prefix.nix {
|
||||
client = mkOption {
|
||||
type = types.anything; # liminix.lib.types.service;
|
||||
};
|
||||
interface = mkOption {
|
||||
type = liminix.lib.types.interface;
|
||||
description = "interface to assign <prefix>::1 to";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
liminix
|
||||
, lib
|
||||
, callPackage
|
||||
}:
|
||||
{ client, interface } :
|
||||
let
|
||||
inherit (liminix.services) longrun;
|
||||
inherit (lib) mkOption types;
|
||||
name = "dhcp6c.prefix.${client.name}.${interface.name}";
|
||||
script = callPackage ./acquire-delegated-prefix.nix { };
|
||||
in longrun {
|
||||
inherit name;
|
||||
run = "${script} /run/service-state/${client.name} $(output ${interface} ifname)";
|
||||
dependencies = [ client interface ];
|
||||
}
|
||||
@@ -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 \
|
||||
|
||||
+10
-4
@@ -38,6 +38,9 @@ in {
|
||||
kernel = {
|
||||
config = {
|
||||
MTD_SPLIT_UIMAGE_FW = "y";
|
||||
} // lib.optionalAttrs (pkgs.stdenv.isMips) {
|
||||
# https://stackoverflow.com/questions/26466470/can-the-logical-erase-block-size-of-an-mtd-device-be-increased
|
||||
MTD_SPI_NOR_USE_4K_SECTORS = "n";
|
||||
};
|
||||
};
|
||||
|
||||
@@ -47,13 +50,16 @@ in {
|
||||
|
||||
system.outputs = {
|
||||
firmware =
|
||||
let o = config.system.outputs; in
|
||||
pkgs.runCommand "firmware" {} ''
|
||||
dd if=${o.uimage} of=$out bs=128k conv=sync
|
||||
dd if=${o.rootfs} of=$out bs=128k conv=sync,nocreat,notrunc oflag=append
|
||||
let
|
||||
o = config.system.outputs;
|
||||
bs = config.hardware.flash.eraseBlockSize;
|
||||
in pkgs.runCommand "firmware" {} ''
|
||||
dd if=${o.uimage} of=$out bs=${bs} conv=sync
|
||||
dd if=${o.rootfs} of=$out bs=${bs} conv=sync,nocreat,notrunc oflag=append
|
||||
'';
|
||||
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}";
|
||||
}
|
||||
|
||||
+3
-5
@@ -24,7 +24,7 @@ in {
|
||||
extraPatchPhase = mkOption {
|
||||
default = "true";
|
||||
type = types.lines;
|
||||
} ;
|
||||
};
|
||||
config = mkOption {
|
||||
description = ''
|
||||
Kernel config options, as listed in Kconfig* files in the
|
||||
@@ -56,10 +56,6 @@ in {
|
||||
MODULE_SIG = if modular then "y" else "n";
|
||||
DEBUG_FS = "y";
|
||||
|
||||
MIPS_BOOTLOADER_CMDLINE_REQUIRE_COOKIE = "y";
|
||||
MIPS_BOOTLOADER_CMDLINE_COOKIE = "\"liminix\"";
|
||||
MIPS_CMDLINE_DTB_EXTEND = "y";
|
||||
|
||||
# basic networking protocols
|
||||
NET = "y";
|
||||
UNIX = "y";
|
||||
@@ -68,6 +64,8 @@ in {
|
||||
PACKET = "y"; # for ppp, tcpdump ...
|
||||
SYSVIPC= "y";
|
||||
|
||||
NETDEVICES = "y"; # even PPP needs this
|
||||
|
||||
# disabling this option causes the kernel to use an "empty"
|
||||
# initramfs instead: it has a /dev/console node and not much
|
||||
# else. Note that pid 1 is started *before* the root
|
||||
|
||||
@@ -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}")
|
||||
|
||||
+32
-11
@@ -7,6 +7,12 @@
|
||||
let
|
||||
inherit (lib) mkOption types concatStringsSep;
|
||||
inherit (pkgs) liminix callPackage writeText;
|
||||
arch = let s = pkgs.stdenv; in
|
||||
if s.isAarch64
|
||||
then "aarch64"
|
||||
else if s.isMips
|
||||
then "mips"
|
||||
else throw "can't determine arch";
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
@@ -36,7 +42,7 @@ in
|
||||
type = types.package;
|
||||
description = ''
|
||||
Directory containing separate kernel and rootfs image for
|
||||
use with qemu (see mips-vm)
|
||||
use with qemu (see run-liminix-vm)
|
||||
'';
|
||||
};
|
||||
manifest = mkOption {
|
||||
@@ -62,27 +68,42 @@ in
|
||||
kernel = liminix.builders.kernel.override {
|
||||
inherit (config.kernel) config src extraPatchPhase;
|
||||
};
|
||||
dtb = (callPackage ../kernel/dtb.nix {}) {
|
||||
dtb = liminix.builders.dtb {
|
||||
inherit (config.boot) commandLine;
|
||||
dts = config.hardware.dts.src;
|
||||
includes = config.hardware.dts.includes ++ [
|
||||
"${kernel.headers}/include"
|
||||
];
|
||||
};
|
||||
uimage = (callPackage ../kernel/uimage.nix {}) {
|
||||
uimage = liminix.builders.uimage {
|
||||
commandLine = concatStringsSep " " config.boot.commandLine;
|
||||
inherit (config.hardware) loadAddress entryPoint;
|
||||
inherit (config.boot) imageFormat;
|
||||
inherit kernel;
|
||||
inherit dtb;
|
||||
};
|
||||
vmroot = pkgs.runCommand "qemu" {} ''
|
||||
mkdir $out
|
||||
cd $out
|
||||
ln -s ${config.system.outputs.rootfs} rootfs
|
||||
ln -s ${kernel} vmlinux
|
||||
ln -s ${manifest} manifest
|
||||
ln -s ${kernel.headers} build
|
||||
'';
|
||||
# could use trivial-builders.linkFarmFromDrvs here?
|
||||
vmroot =
|
||||
let
|
||||
cmdline = builtins.toJSON (concatStringsSep " " config.boot.commandLine);
|
||||
makeBootableImage = pkgs.runCommandCC "objcopy" {}
|
||||
(if pkgs.stdenv.isAarch64
|
||||
then "${pkgs.stdenv.cc.targetPrefix}objcopy -O binary -S ${kernel} $out"
|
||||
else "cp ${kernel} $out");
|
||||
in pkgs.runCommandCC "vmroot" {} ''
|
||||
mkdir $out
|
||||
cd $out
|
||||
ln -s ${config.system.outputs.rootfs} rootfs
|
||||
ln -s ${kernel} vmlinux
|
||||
ln -s ${manifest} manifest
|
||||
ln -s ${kernel.headers} build
|
||||
echo ${cmdline} > commandline
|
||||
cat > run.sh << EOF
|
||||
#!${pkgs.runtimeShell}
|
||||
CMDLINE=${cmdline} ${pkgs.pkgsBuildBuild.run-liminix-vm}/bin/run-liminix-vm --arch ${arch} \$* ${makeBootableImage} ${config.system.outputs.rootfs}
|
||||
EOF
|
||||
chmod +x run.sh
|
||||
'';
|
||||
|
||||
manifest = writeText "manifest.json" (builtins.toJSON config.filesystem.contents);
|
||||
};
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -7,5 +7,6 @@
|
||||
./kexecboot.nix
|
||||
./flashimage.nix
|
||||
./jffs2.nix
|
||||
./ubifs.nix
|
||||
];
|
||||
}
|
||||
|
||||
+20
-8
@@ -42,27 +42,39 @@ in {
|
||||
ln -s ${o.manifest} manifest
|
||||
ln -s ${o.kernel.headers} build
|
||||
ln -s ${o.uimage} uimage
|
||||
ln -s ${o.boot-scr} boot.scr
|
||||
ln -s ${o.boot-scr}/dtb dtb
|
||||
ln -s ${o.boot-scr}/script boot.scr
|
||||
'';
|
||||
|
||||
boot-scr =
|
||||
let
|
||||
inherit (pkgs.lib.trivial) toHexString;
|
||||
o = config.system.outputs;
|
||||
cmdline = concatStringsSep " " config.boot.commandLine;
|
||||
in
|
||||
pkgs.buildPackages.runCommand "boot-scr" {} ''
|
||||
pkgs.buildPackages.runCommand "boot-scr" { nativeBuildInputs = [ pkgs.pkgsBuildBuild.dtc ]; } ''
|
||||
uimageSize=$(($(stat -L -c %s ${o.uimage}) + 0x1000 &(~0xfff)))
|
||||
rootfsStart=0x$(printf %x $((${cfg.loadAddress} + 0x100000 + $uimageSize)))
|
||||
rootfsStart=0x$(printf %x $((${cfg.loadAddress} + 0x100000 + $uimageSize &(~0xfffff) )))
|
||||
rootfsBytes=$(($(stat -L -c %s ${o.rootfs}) + 0x100000 &(~0xfffff)))
|
||||
rootfsBytes=$(($rootfsBytes + ${toString cfg.freeSpaceBytes} ))
|
||||
cmd="mtdparts=phram0:''${rootfsMb}M(rootfs) phram.phram=phram0,''${rootfsStart},''${rootfsBytes},${config.hardware.flash.eraseBlockSize} memmap=''${rootfsBytes}\$''${rootfsStart} root=/dev/mtdblock0";
|
||||
rootfsMb=$(($rootfsBytes >> 20))
|
||||
cmd="mtdparts=phram0:''${rootfsMb}M(rootfs) phram.phram=phram0,''${rootfsStart},''${rootfsBytes},${config.hardware.flash.eraseBlockSize} root=/dev/mtdblock0";
|
||||
|
||||
cat > $out << EOF
|
||||
dtbStart=$(printf %x $((${cfg.loadAddress} + $rootfsBytes + 0x100000 + $uimageSize )))
|
||||
|
||||
mkdir $out
|
||||
cat ${o.dtb} > $out/dtb
|
||||
fdtput -p -t s $out/dtb /reserved-memory/phram-rootfs compatible phram
|
||||
fdtput -p -t lx $out/dtb /reserved-memory/phram-rootfs reg 0 $rootfsStart 0 $(printf %x $rootfsBytes)
|
||||
|
||||
dtbBytes=$(($(stat -L -c %s $out/dtb) + 0x1000 &(~0xfff)))
|
||||
|
||||
cat > $out/script << EOF
|
||||
setenv serverip ${cfg.serverip}
|
||||
setenv ipaddr ${cfg.ipaddr}
|
||||
setenv bootargs 'liminix $cmd'
|
||||
tftp 0x$(printf %x ${cfg.loadAddress}) result/uimage ; tftp 0x$(printf %x $rootfsStart) result/rootfs
|
||||
bootm 0x$(printf %x ${cfg.loadAddress})
|
||||
setenv bootargs 'liminix ${cmdline} $cmd'
|
||||
tftpboot 0x$(printf %x ${cfg.loadAddress}) result/uimage ; tftpboot 0x$(printf %x $rootfsStart) result/rootfs ; tftpboot 0x$dtbStart result/dtb
|
||||
bootm 0x$(printf %x ${cfg.loadAddress}) - 0x$dtbStart
|
||||
EOF
|
||||
'';
|
||||
};
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
{
|
||||
config
|
||||
, pkgs
|
||||
, lib
|
||||
, ...
|
||||
}:
|
||||
let
|
||||
inherit (lib) mkIf mkOption types;
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
./initramfs.nix
|
||||
];
|
||||
options.hardware.ubi = {
|
||||
minIOSize = mkOption { type = types.str; };
|
||||
eraseBlockSize = mkOption { type = types.str; }; # LEB
|
||||
maxLEBcount = mkOption { type = types.str; }; # LEB
|
||||
};
|
||||
|
||||
config = mkIf (config.rootfsType == "ubifs") {
|
||||
kernel.config = {
|
||||
MTD_UBI="y";
|
||||
|
||||
UBIFS_FS = "y";
|
||||
UBIFS_FS_SECURITY = "n";
|
||||
};
|
||||
boot.initramfs.enable = true;
|
||||
system.outputs = rec {
|
||||
systemConfiguration =
|
||||
pkgs.systemconfig config.filesystem.contents;
|
||||
rootfs =
|
||||
let
|
||||
inherit (pkgs.pkgsBuildBuild) runCommand mtdutils;
|
||||
cfg = config.hardware.ubi;
|
||||
in runCommand "mkfs.ubifs" {
|
||||
depsBuildBuild = [ mtdutils ];
|
||||
} ''
|
||||
mkdir -p $TMPDIR/empty/nix/store/ $TMPDIR/empty/secrets $TMPDIR/empty/boot
|
||||
cp ${systemConfiguration}/bin/activate $TMPDIR/empty/activate
|
||||
ln -s ${pkgs.s6-init-bin}/bin/init $TMPDIR/empty/init
|
||||
cp ${config.system.outputs.uimage} $TMPDIR/empty/boot/uimage
|
||||
mkdir -p $TMPDIR/empty/nix/store
|
||||
for path in $(cat ${systemConfiguration}/etc/nix-store-paths) ; do
|
||||
(cd $TMPDIR/empty && cp -a $path .$path)
|
||||
done
|
||||
mkfs.ubifs -x favor_lzo -c ${cfg.maxLEBcount} -m ${cfg.minIOSize} -e ${cfg.eraseBlockSize} -y -r $TMPDIR/empty --output $out --squash-uids -o $out
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
{
|
||||
config
|
||||
, pkgs
|
||||
, lib
|
||||
, ...
|
||||
}:
|
||||
let
|
||||
inherit (lib) mkIf mkEnableOption mkOption types concatStringsSep;
|
||||
loadaddr = config.boot.tftp.loadAddress;
|
||||
cfg = config.boot.tftp;
|
||||
instructions = pkgs.writeText "INSTALL.md" ''
|
||||
|
||||
# First-time installation
|
||||
|
||||
First-time installation of a UBIFS Liminix system presently can only
|
||||
be done from the U-Boot command line (or from a ramdisk-based recovery
|
||||
system: see "kexecboot" but we don't have detailed instructions for
|
||||
this process yet).
|
||||
|
||||
These steps were tested on a Belkin RT3200 (also known as Linksys
|
||||
E8450). Other devices may be set up differently, so use them as
|
||||
inspiration and don't just paste them blindly. Consult the Liminix
|
||||
manual for how to connect a serial cable to your device and get into
|
||||
U-Boot
|
||||
|
||||
1) determine which MTD device is being used for UBI and the partition name:
|
||||
|
||||
uboot> ubi part
|
||||
Device 0: ubi0, MTD partition ubi
|
||||
|
||||
In this case the important value is `ubi0`
|
||||
|
||||
2) list the available volumes and create a new one on which to install Liminix
|
||||
|
||||
uboot> ubi info l
|
||||
[ copious output scrolls past ]
|
||||
|
||||
Expect there to be existing volumes and for some or all of them to be
|
||||
important. Unless you know what you're doing, don't remove anything
|
||||
whose name suggests it's related to uboot, or any kind of backup or
|
||||
recovery partition. To see how much space is free:
|
||||
|
||||
uboot> ubi info
|
||||
[ ... ]
|
||||
UBI: available PEBs: 823
|
||||
|
||||
Now we can make our new root volume
|
||||
|
||||
uboot> ubi create liminix -
|
||||
|
||||
3) transfer the root filesystem from the build system and write it
|
||||
to the new volume
|
||||
|
||||
uboot> setenv serverip ${cfg.serverip}
|
||||
uboot> setenv ipaddr ${cfg.ipaddr}
|
||||
uboot> tftpboot ${loadaddr} result/rootfs
|
||||
uboot> ubi write ${loadaddr} liminix $filesize
|
||||
|
||||
Now we have the root filesystem installed on the device. You
|
||||
can even mount it and poke around using `ubifsmount ubi0:liminix;
|
||||
ubifsls /`
|
||||
|
||||
4) optional: before you configure the device to boot into Liminix
|
||||
automatically, you can try booting it by hand to see if it works:
|
||||
|
||||
uboot> ubifsmount ubi0:liminix
|
||||
uboot> ubifsload ${loadaddr} boot/uimage
|
||||
uboot> bootm ${loadaddr}
|
||||
|
||||
Once you've done this and you're happy with it, reset the device to
|
||||
U-Boot. You don't need to recreate the volume but you do need to
|
||||
repeat step 3.
|
||||
|
||||
5) Instructions for configuring autoboot are likely to be very
|
||||
device-dependent. On the Linksys E8450/Belkin RT3200, the environment
|
||||
variable `boot_production` governs what happens on a normal boot, so
|
||||
you could do
|
||||
|
||||
uboot> setenv boot_production 'led $bootled_pwr on ; ubifsmount ubi0:liminix; ubifsload ${loadaddr} boot/uimage; bootm ${loadaddr}'
|
||||
|
||||
On other devices, some detective work may be needed. Try running
|
||||
`printenv` and look for likely commands, try looking at the existing
|
||||
boot process, maybe even try looking for documentation for that device.
|
||||
|
||||
6) Now you can reboot the device into Liminix
|
||||
|
||||
uboot> reset
|
||||
|
||||
'';
|
||||
in {
|
||||
options.system.outputs = {
|
||||
ubimage = mkOption {
|
||||
type = types.package;
|
||||
description = ''
|
||||
UBIFS image and instructions to install it
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf (config.rootfsType == "ubifs") {
|
||||
system.outputs = {
|
||||
ubimage =
|
||||
let o = config.system.outputs; in
|
||||
pkgs.runCommand "ubimage" {} ''
|
||||
mkdir $out
|
||||
cd $out
|
||||
ln -s ${o.rootfs} rootfs
|
||||
ln -s ${instructions} INSTALL
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -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 = ''
|
||||
f | ||||