diary at Telent Netowrks

NixWRT Nics#

Sun, 21 Nov 2021 19:17:29 +0000

According to Git history I've been back on hacking NixWRT fairly regularly for a couple of months, and am pleased to be able to write that by dint of saying "screw it" to every previous attempt to write a replacement init system, I have got to the point that I can create an image that runs in Qemu which actually routes packets.

Given that it's qemu, we don't even have to attach eth1 to any real hardware. The nixwrt vm is started with options including

  -netdev socket,id=mynet1,listen=:5133 \
  -device virtio-net-pci,netdev=mynet1

and then I start a second VM with

qemu-system-x86_64 -enable-kvm \
 -netdev socket,id=mynet0,connect=:5133 \
 -device virtio-net-pci,netdev=mynet0 \
 -cdrom sysrescue.iso   -m 2048

whose eth0 can see nixwrt's eth1, and which successfully gets a (real! globally routable!) IPV6 address from it.

At some point I should try it on some real hardware,but there are a few other things to do first. DNS would be nice, for one. So would NAT (so I can have IPv4 as well as v6) and some kind of firewalling.

In replacement init system news, I am now using shell scripts to start services where I was previously implementing them as Monit services. The codebase is in a very transitional state right now: existing services (anything defined with services.foo - q.v.) continue to be started using Monit, for the moment, but new services go into the config under the svcs key - see this dnsmasq example. Most likely I will rename this back to services once I've moved everything over.

New-style service definitions can also specify changes to the config, meaning they can require busybox applets or kernel config. This means that if service B depends on service A it doesn't have to also know what A's system requirements are.

Cross product#

Sun, 31 Oct 2021 15:47:10 +0000

I had cause this afternoon to remember the Monad Tutorial Fallacy, which has been summarised as saying that when you finally understand them, you lose the ability to explain it to others.

I hypothesise that the same is probably true of cross-compilation in the Nix package system, and therefore I present these notes not as a superior alternative to any of the existing documentation, but because I wrote them down to aid my understanding and now need to put them somewhere I can refer back to them.

So. Let's suppose we're building NixWRT. Most likely we're building on an x86-64 system, to produce an image which will run on a MIPS device. In the event that there are any programs in that image which generate code (which is unlikely as we're not shipping compilers), we want them also to generate MIPS code. Thus, in the standard terminology we have:

(This naming convention comes from Autoconf, and so we are stuck with it. To make it make sense, consider the built product rather than the build process: we are describing a thing that was built on x86-64, is hosted on MIPS, and would - if it emitted any code - emit code that runs on MIPS)

However, not all of the software we create (or depend on) will be needed on the MIPS system - some of it (e.g. buid tools, compilers and other kinds of translators) will have to run on x86-64. So how do we keep track of it all?

Let's look at some examples:

Why am I caring about this right now? I rearranged bits of NixWRT and updated it to a recent Nixpkgs revision, causing OCaml to stop building for reasons I didn't fully understand

So, here is what I think is happening:

Clear? If this doesn't help, I invite you to consider the possibility that cross-compilation is like a burrito.

Hard pass#

Sat, 14 Aug 2021 21:31:26 +0000

I've got the key
I've got the secret
– From the Urban Cookie Collective's guide to password management

For reasons that seemed good at the time, I've written a password manager. It's a lot like pass ("the standard unix password manager") - which I have been using up 'til now - but it uses age instead of GPG to do the heavy lifting.

moss, the Maybe-Ok Secrets Store, is a 400-line Ruby script that uses only libraries provided by a default Ruby installation, plus 520 lines of testing code (Cucumber and RSpec).

Some random observations follow:

It's been a long time since I wrote more than about 5 lines of Ruby for anything outside of a work context: for 'fun' projects I tend to pick languages which I don't get a chance to use 9-5. Ruby for this task was definitely less than awful, though.

Slightly too much Kodi for fun#

Wed, 04 Aug 2021 22:02:24 +0000

I'd been vacillating for a while about buying a new monitor, but eventually I pulled the hammer (is that the idiom?) on a spangly new Dell S2721SQ, which arrived yesterday and provided the incentive to look at NixElec again. Because it (the monitor) has speakers, which means I have the hardware to fix the audio issues without having to commandeer the family TV.

Second rate

I don't claim to understand how ALSA works, and Kodi's approach to ALSA is even more weird, but I did eventually make it work for 44.1kHz sources: define an ALSA fixed-rate pcm for Kodi that is hardcoded to S16_LE format, and then tell Kodi about it in advancedsettings.xml

A sticky GUI mess

To the extent that Kodi can be configured through files, they're XML files. There is a toXML builtin in Nix, but it only generates a particular XML representation that would need XSLT to turn into files that Kodi likes - and XSLT for me is assigned firmly to into the same "tried it once, not going back to that" bucket as are m4 and Java applet programming.

What I really wanted is something that would let me write out (or generate!) a nested attrset describing the structure I want, and turn it, possibly via JSON, into XML. Python's dict2xml is very nearly it, but has no support for XML attributes, so I had to invent something slightly more complicated.

Sadly, the extent that Kodi can be configured through files is not the full extent. Although the sources are defined in XML, the content of each source (tv shows? movies? music?) seems to be set in a Sqlite database, which is another level of complexity to manage. So there's still manual twattery on the GUI to deal with.

Just enough NixOS for Kodi#

Sat, 26 Jun 2021 21:56:17 +0000

I've had an Odroid C2 sitting under the TV for a a year or so, mostly used for playing the Shaun the Sheep videos that live on my PC upstairs. I put LibreELEC on it when I bought it, and subsequntly tweaked it in a succession of ways that I basically don't remember.

Quite recently I decided that I didn't need this pocket of divergence in my otherwise mostly congruent domestic computing infrastructure, so I set about installing NixOS on it. There's a description in the NixOS Wiki of how to do this which is a good starting point, but there was some other stuff I had to figure out.

Caveat

I still don't have everything figured out. In particular, it seems to want to play audio slightly too fast, I think because somehow it has decided that my 48kHz sound device will accept 44.1kHz PCM without need of resampling. I probably won't update this blog entry when I figure that out, but I will update the repo it points to.

I would caution against following these notes from start to end and doing everything manually as I discovered it, because there's a configuration.nix at the end that has most of it automated.

Initial install

The board has some weird requirement to install a binary blob and an u-boot image in a very specific part of the storage medium. "Note this assumes u-boot is in partition 1 of your board's connected eMMC", say the instructions. I am happy to report that the same merry dance works just fine on an SD card if like me you were too stingy (or poor) to spring for the eMMC.

Kernel rebuild

Although the generic NixOS aarch64 kernel will work to get you to a login prompt, it doesn't work well for video playback. This is because there's a meson_vdec module needed for hardware video decoding that isn't in it. After some experimenting I came up with the following configuration snippet

  nixpkgs.overlays = [
    (self: super: {
      linuxPackages = super.linuxPackages_latest.extend (lpself: lpsuper: {
        kernel = super.linuxPackages_latest.kernel.override {
          extraConfig = ''
             STAGING y
             STAGING_MEDIA y
             VIDEO_MESON_VDEC m
          '';
        };
      });
    })]

but this is where I found that the machine is slow as molasses at compiling - although to be fair the default NixOS aarch64 kernel build is a huge task on any hardware due to the large number of modules it builds.

Hot cross bins

So, time to figure out how to cross-compile it on an x86_64, and given that I want this to be repeatable, how to cross-compile the entire system instead of cross-building bits and native-building other bits and copying artifacts around by hand. Like this, is the short answer (adjust pathnames/hostnames as needed)

 # build the system
 NIXOS_CONFIG=/home/dan/src/odroid/nixelec/configuration.nix \
  nix-build -E 'let pkgs = (import /home/dan/src/nixpkgs) {};
  in (pkgs.pkgsCross.aarch64-multiplatform.nixos
      /home/dan/src/odroid/nixelec/configuration.nix)'.config.system.build.toplevel

 # copy it to the target device
 nix-copy-closure --to root@odroid.lan -v --include-outputs \
   ./result && ssh root@odroid.lan \
   `readlink result`/bin/switch-to-configuration switch

As usual with cross-compilation, this unearthed a bunch of packages that don't cross-compile because nobody really understands when to use buildInputs vs nativeBuildInputs (I'm projecting here, it might just be me), one package that doesn't cross-compile because it's magic - I refer of course to gobject-introspection - and some packages that need their derivations tweaking so that they don't depend on any of the other stuff that depends transitively on gobject-introspection.

Image problems

Careful readers will observe that the shell incantations above are predicated on having a running NixOS Odroid system already that you can ssh into - so, how do you get that in the first place? I augmented the configuration further so that it can also be used to produce an SD card image which has builtin the faffage needed to get the firmware and U-boot binaries injected at the right offsets. To generate this, we do

NIXOS_CONFIG=/home/dan/src/odroid/nixelec/configuration.nix \
 nix-build -E 'let pkgs = (import /home/dan/src/nixpkgs) {};
  in (pkgs.pkgsCross.aarch64-multiplatform.nixos
      /home/dan/src/odroid/cross/configuration.nix)'.config.system.build.sdImage

(this is very similar to the previous command except now we're building sdImage instead of toplevel)

and then find the output in result/sd-image/nixos-sd-image-21.11pre-git-aarch64-linux.img - again, Your Pathnames May Vary. dd this to whatever device corresponds to the SD card you plan to insert into the Odroid machine and you should be good to go. It should install a valid SSH key for the root user, but it would be as well to check.

Where is it?

https://github.com/telent/nixelec

What's left to do?