NixOS on Odroid HC4

Created 2024-07-12
Updated 2024-07-16

Table of contents

I'm running 3 Odroid HC4 storage servers, they're really neat, especially if you don't use the provided toaster case. For the past few weeks I have been slowly migrating all my computers to NixOS and I'd very much like to migrate these aarch64 SBCs as well, especially since I've been running Armbian on them which is missing a lot of packages I need. I was scared of installing NixOS on ARM because the docs seem to be written for people who know what they're doing, and I still feel like I don't know anything about NixOS.

The simplest thing first

Starting off with NixOS on ARM/Installation wiki page I downloaded a prebuilt image from hydra. Specifically nixos-sd-image-24.11pre650378.655a58a72a66-aarch64-linux.img which was the latest one when I did this.

Then, after making a backup of it, I tried flashing the image directly to the SD card and (somewhat expectedly) nothing happened, the "alive" LED stayed off, which means no kernel has booted, or that there are missing drivers.

UART Console

To be able to debug this I'll need a console to see what the firmware/uboot is doing. The board's UART port is documented on the odroid-hc4:hardware page of the Hardkernel/Odroid wiki. It has this cute ascii art drawing of it:

_____UART____
|Pin 4 - GND|
|Pin 3 - RXD|
|Pin 2 - TXD|
|Pin 1 - VCC|
\___________|

CON5
3.3V LVTTL

I've connected a USB-UART adapter to GND/RX/TX pins of the connector and on the second try when I got the RX/TX order right running sterm /dev/ttyUSB0 gave me some output, but it was a bunch of unicode symbol placeholders and some nonsensical chinese characters :(

Reading the manual (wiki)

Then I noticed there are steps for patching the default image for Odroid HC4 on its own NixOS on ARM/ODROID-HC4 wiki page on the NixOS wiki. The most interesting bit is this shell snippet. (I've already removed Petitboot when installing Armbian years ago and I don't use either HDMI nor the builtin fan.)

# Clone content of samueldr's wip/odroidc4 branch, edit the defconfig file, and build
git clone https://github.com/samueldr/nixpkgs --depth 1 -b wip/odroidc4 && cd nixpkgs
test "$(uname)" '==' 'Darwin' && sed -i '' 's/defconfig = "odroid-c4_defconfig"/defconfig = "odroid-hc4_defconfig"/' pkgs/misc/uboot/default.nix || sed -i 's/defconfig = "odroid-c4_defconfig"/defconfig = "odroid-hc4_defconfig"/' pkgs/misc/uboot/default.nix
nix-build -I "nixpkgs=$PWD" -A pkgsCross.aarch64-multiplatform.ubootOdroidC4
sudo dd if=result/u-boot.bin of=PATH/TO/nixos-sd-image-21.05.XXXX.XXXXXXXX-aarch64-linux.img  conv=fsync,notrunc bs=512 seek=1

Just in case it gets lost (like the original PR nixpkgs/#101454 this branch is a backup of) I've setup my own mirror.

The wierd test && sed shell invocation results in this diff (after changing the == to = to make it run in fish):

--- a/pkgs/misc/uboot/default.nix
+++ b/pkgs/misc/uboot/default.nix
@@ -273,7 +273,7 @@ in {
   };
 
   ubootOdroidC4 = buildUBoot {
-    defconfig = "odroid-c4_defconfig";
+    defconfig = "odroid-hc4_defconfig";
 
     postBuild = ''
       ${buildPackages.meson64-tools}/bin/pkg --type bl30 --output bl30_new.bin \

Running the nix-build invocation requires allowing unfree code, presumably to build Hardkernel firmware.

set -gx NIXPKGS_ALLOW_UNFREE 1
nix-build -I "nixpkgs=$PWD" -A pkgsCross.aarch64-multiplatform.ubootOdroidC4

The result directory also contains a script sd_fusing.sh which contains the same dd invocation as the wiki page above.

dd if=result/u-boot.bin of=nixos-sd-image-24.11pre650378.655a58a72a66-aarch64-linux.img conv=fsync,notrunc bs=512 seek=1
2229+1 records in
2229+1 records out
1141616 bytes (1,1 MB, 1,1 MiB) copied, 0,02349 s, 48,6 MB/s

Huh, that's not much data, what did it touch?

Disk /dev/mmcblk0: 7,49 GiB, 8044675072 bytes, 15712256 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x2178694e

Device         Boot Start      End  Sectors  Size Id Type
/dev/mmcblk0p1      16384    77823    61440   30M  b W95 FAT32
/dev/mmcblk0p2 *    77824 15712255 15634432  7,5G 83 Linux

We skipped the first 1 block/512 bytes of the partition table and then wrote 2229 blocks, but the weirdly tiny 30MB BOOT (?) partition doesn't even start until block 16384 (8MB in). Is that where the board looks for a bootloader?

Anyway, I flashed the patched image and now it boots! Sort of, I still get a bunch of garbage in the UART but the "alive" LED starts to blink, suggesting a linux kernel with the right drivers has started up.

Odroid HC4 single-board computer laying on a desk. Connected to power, ethernet
and via an USB UART adapter to a laptop. The blue "alive" LED is lit - it has
successfully booted into Linux!

OH so the default parameters of sterm must be wrong, because minicom works and I can see some output:

U-Boot 2022.01 (Jan 10 2022 - 18:46:34 +0000) odroid-hc4

Model: Hardkernel ODROID-HC4
SoC:   Amlogic Meson SM1 (S905X3) Revision 2b:c (10:2)
DRAM:  3.8 GiB
MMC:   sd@ffe05000: 0
Loading Environment from nowhere... OK
In:    serial
Out:   serial
Err:   serial
Board variant: hc4
Net:   eth0: ethernet@ff3f0000
Hit any key to stop autoboot:  0 
switch to partitions #0, OK
mmc0 is current device
Scanning mmc 0:2...
Found /boot/extlinux/extlinux.conf
Retrieving file: /boot/extlinux/extlinux.conf
1:      NixOS - Default
Retrieving file: /boot/extlinux/../nixos/ghywydzklfd8hn3cwazdchyfypb7155s-initrd-linux-6.9.8-initrd
Retrieving file: /boot/extlinux/../nixos/k4jjifq6lnc830xpmdb49wcjgas3p1ck-linux-6.9.8-Image
append: init=/nix/store/53nsmqr561s0a7d9ry6zd547c71ywrg9-nixos-system-nixos-24.11pre650378.655a58a72a66/init console=ttyS0,115200n8 console=ttyAMA0,115200n8 console=tty0 loglevel=7
Retrieving file: /boot/extlinux/../nixos/k4jjifq6lnc830xpmdb49wcjgas3p1ck-linux-6.9.8-dtbs/amlogic/meson-sm1-odroid-hc4.dtb
Moving Image from 0x8080000 to 0x8200000, end=cb20000
## Flattened Device Tree blob at 08008000
   Booting using the fdt blob at 0x8008000
   Loading Ramdisk to 3e8d8000, end 3ffff8fe ... OK
   Loading Device Tree to 000000003e8c2000, end 000000003e8d7842 ... OK

Starting kernel ...

Then nothing, looks like serial is not enabled in Linux - I know it has to be booted thanks to the "alive" LED.

Out of curiosity I tried starting it up again after removing the SD card to see if there are sensible characters with no boot device with minicom, there indeed were. The bit after the LOOP:n repeats indefinitely every few seconds. It looks like a loop searching for bootable devices.

SM1:BL:511f6b:81ca2f;FEAT:A0F83180:20282000;POC:A;RCY:0;SPINOR:0;CHK:1F;EMMC:800;NAND:81;SD?:20000;USB:8;LOOP:1;SPINOR:0;CHK:1F;EMMC:800;NAND:81;SD?:20000;USB:8;LOOP:2;

Looking into the BOOT FIRMWARE partition (that explains why it's so small, there are no kernels here) are a lot of files that seem irrelevant for us.

   size filename
    512 armstub8-gic.bin
  55808 bcm2711-rpi-400.dtb
  55804 bcm2711-rpi-4-b.dtb
  56444 bcm2711-rpi-cm4.dtb
  53215 bcm2711-rpi-cm4s.dtb
  52476 bootcode.bin
    946 config.txt
   3210 fixup4cd.dat
   5436 fixup4.dat
   8423 fixup4db.dat
   8425 fixup4x.dat
   3210 fixup_cd.dat
   7303 fixup.dat
  10266 fixup_db.dat
  10266 fixup_x.dat
 809884 start4cd.elf
3754312 start4db.elf
2257216 start4.elf
3004808 start4x.elf
 809884 start_cd.elf
4826216 start_db.elf
2981376 start.elf
3728456 start_x.elf
 595072 u-boot-rpi3.bin
 644328 u-boot-rpi4.bin

Several U-Boot images for various SBCs, Raspberry Pi DTBs, and a bunch of unrecognizable to me .dat files. But there is also a config.txt file like in Raspberry Pi images (and unlike the Armbian image that's currently running on my other HC4). I don't know if it's even used on the odroid, but it seems the UART console is enabled:

[all]
# Boot in 64-bit mode.
arm_64bit=1

# U-Boot needs this to work, regardless of whether UART is actually used or not.
# Look in arch/arm/mach-bcm283x/Kconfig in the U-Boot tree to see if this is still
# a requirement in the future.
enable_uart=1

Next thing to check is the kernel console= parameter. We can helpfully see them in the U-Boot log above we can see NixOS tries to set console=ttyS0,115200n8, console=ttyAMA0,115200n8 and console=tty0. However checking /proc/cmdline on my running HC4 shows console=ttyAML0,115200 and console=tty1, we need to change that.

Since it doesn't seem like the kernel command line is set in the FIRMWARE partition (even rg -uuu -F console= found nothing in those binary files) let's look at the larger NIXOS_SD partition. Searching for console= returned a lot of results from the nix store which I can't touch but also /boot/extlinux/extlinux.conf which looks like the perfect target! I just have to remember to also change the value in configuration.nix before running nixos-rebuild the first time.

# Generated file, all changes will be lost on nixos-rebuild!

# Change this to e.g. nixos-42 to temporarily boot to an older configuration.
DEFAULT nixos-default

MENU TITLE ------------------------------------------------------------
TIMEOUT 50

LABEL nixos-default
  MENU LABEL NixOS - Default
  LINUX ../nixos/k4jjifq6lnc830xpmdb49wcjgas3p1ck-linux-6.9.8-Image
  INITRD ../nixos/ghywydzklfd8hn3cwazdchyfypb7155s-initrd-linux-6.9.8-initrd
  APPEND init=/nix/store/53nsmqr561s0a7d9ry6zd547c71ywrg9-nixos-system-nixos-24.11pre650378.655a58a72a66/init console=ttyS0,115200n8 console=ttyAMA0,115200n8 console=tty0 loglevel=7
  FDTDIR ../nixos/k4jjifq6lnc830xpmdb49wcjgas3p1ck-linux-6.9.8-dtbs
-  APPEND init=/nix/store/53nsmqr561s0a7d9ry6zd547c71ywrg9-nixos-system-nixos-24.11pre650378.655a58a72a66/init console=ttyS0,115200n8 console=ttyAMA0,115200n8 console=tty0 loglevel=7
+  APPEND init=/nix/store/53nsmqr561s0a7d9ry6zd547c71ywrg9-nixos-system-nixos-24.11pre650378.655a58a72a66/init console=ttyAML0,115200 console=tty1 loglevel=7

And it works, I have a NixOS bash console over UART!

Initial configuration

Unfortunately /etc/nixos is empty, it doesn't contain the configuration which was used to generate the installation image, so I'll have to make a working config from scratch-ish. Or do I? NixOS on ARM/Initial Configuration says to use nixos-generate-config and it seems to magically generate a config that looks like it could work? At least it sets the bootloader to extlinux. I'll need to look into how nixos-generate-config even works some time.

I've used nix-env -iA nixos.helix to install helix, which looked very funky over the serial console but it worked, and edited the generated file with what I thought I'd need. Most notably:

# bootloader, as generated by nixos-generate-config, i left that in place
boot.loader.grub.enable = false;
boot.loader.generic-extlinux-compatible.enable = true;

# use latest kernel and add UART console config
boot.kernelPackages = pkgs.linuxPackages_latest;
# with an experiment i've determined only this console is needed
boot.kernelParams = ["console=ttyAML0,115200"];

# systemd-networkd configuration with DHCP on all ports
networking.networkmanager.enable = false;
networking.useNetworkd = true;
networking.useDHCP = true;

# systemd-resolved
services.resolved.enable = true;

# my user
users.users."max" = {
  isNormalUser = true;
  extraGroups = ["wheel"];
};
security.sudo.wheelNeedsPassword = false;

# some basic packages i'll need for further configuration
environment.systemPackages = with pkgs; [ git helix ];

Then sudo nixos-rebuild boot and then reboot. And it works again! I shouldn't be surprised when NixOS works, it's almost always worked so far, but it's still a little suspicious to me when a computer works the first time.

After a bit of gentle editting it's now part of my nix configuration.

That's Nix though, I want to run Lix

So if you for some reason read all the relevant code from the link above, you may have noticed that I use Lix for all my other machines, but I made an exception for this one. The reason is that I have to compile Lix on the target system, I don't have any more powerful aarch64 machines I could use as a remote builder and this one would both take forever and definitely run out of disk space since I only have 8GB root and it's getting full. Right after running nix-collect-garbage --delete-old we're sitting at 71% usage!

Filesystem      Size  Used Avail Use% Mounted on
devtmpfs        188M     0  188M   0% /dev
tmpfs           1.9G     0  1.9G   0% /dev/shm
tmpfs           937M  8.3M  929M   1% /run
tmpfs           1.9G  2.2M  1.9G   1% /run/wrappers
/dev/mmcblk0p2  7.3G  4.9G  2.1G  71% /
tmpfs           375M  4.0K  375M   1% /run/user/1001

Not-so-brief storage optimisation detour

There is a handy page on the (old, unofficial) NixOS wiki on Storage optimization. Thanks to nix-store --gc --print-roots I found that I left behind a result symlink to the pre-flake system in /etc/nixos which almost doubled the space needed /o\ Removing it saved me ~1.5GB. Then nix-store --optimize saved another 200MB. Now we're sitting at 3.1GB - 44% disk usage, that's not lean by any measure but at least less catastrophic.

Using du the next biggest space hog I found is /var/log, since we're booting off of a MicroSD card it'd be a good idea to move it to tmpfs anyway, so let's do that and save a bit of space too.

zram-generator failure in NixOS

I have wanted to use the services.zram-generator configuration to setup 3 zram devices: one backing /var/log, one backing /tmp and one for swap. Unfortunately there seems to be an issue somewhere (in the NixOS module?) and I couldn't get zram devices for anything that wasn't swap setup using zram-generator. I can set them up and mount them manually but using the zram-generator services always resulted in the device getting double initialized, which breaks the service (even though the device is there and initialized) the systemd dependency chain for the mounts so nothing useful comes out of it. Here some quick info in case someone (future me probably) wants to debug this properly.

systemctl status systemd-zram-setup@zram1.service
[...]
systemd[1]: Starting Create swap on /dev/zram1...
zram-generator[760]: Error: Failed to configure disk size into /sys/block/zram1/disksize
zram-generator[760]: Caused by:
zram-generator[760]:     Device or resource busy (os error 16)
systemd[1]: systemd-zram-setup@zram1.service: Main process exited, code=exited, status=1/FAILURE
systemd[1]: systemd-zram-setup@zram1.service: Failed with result 'exit-code'.
systemd[1]: Failed to start Create swap on /dev/zram1.
dmesg
[...]
zram: Cannot change disksize for initialized device
echo 1 > /sys/block/zram1/disksize
-bash: echo: write error: Device or resource busy

Manually trying to run the command of the service works if I reset the device first.

systemctl cat systemd-zram-setup@zram1.service
[...]
ExecStart=/nix/store/64hb4vgqx2qqv901w9z7x60yxdmrs74h-zram-generator-1.1.2/lib/systemd/system-generators/zram-generator --setup-device '%i'
ExecStop=/nix/store/64hb4vgqx2qqv901w9z7x60yxdmrs74h-zram-generator-1.1.2/lib/systemd/system-generators/zram-generator --reset-device '%i'
# works
/nix/store/64hb4vgqx2qqv901w9z7x60yxdmrs74h-zram-generator-1.1.2/lib/systemd/system-generators/zram-generator --reset-device zram1
/nix/store/64hb4vgqx2qqv901w9z7x60yxdmrs74h-zram-generator-1.1.2/lib/systemd/system-generators/zram-generator --setup-device zram1
# doesn't work ???
/nix/store/64hb4vgqx2qqv901w9z7x60yxdmrs74h-zram-generator-1.1.2/lib/systemd/system-generators/zram-generator --reset-device zram1
systemctl restart systemd-zram-setup@zram1.service

There must be some other unit initializing the zram device just before the one I'm looking at above, but I couldn't find it and I've burned enough time on this already. So for now using tmpfs will do.

systemd.mounts = [{
  what = "tmpfs";
  where = "/tmp";
  type = "tmpfs";
  mountConfig.Options = "mode=1777,strictatime,rw,nosuid,nodev,size=50%";
} {
  what = "tmpfs";
  where = "/var/log";
  type = "tmpfs";
  mountConfig.Options = "rw,nodev,size=100M";
}];

zramSwap.enable = true;

And this effort saved us... 100MB, well exactly as much as was in /tmp and /var/log, it was expected. The main benefit of this is less wear on the SD card.

Can I boot it from btrfs?? (and enable compression)

One friend suggested on mastodon, well we sure can try?

I believe the easiest way to swap the filesystem would be to first come up with UUIDs (uuidgen), write the new filesystem structure in nix config in a way that assumes the future structure, run nixos-rebuild boot --flake ., turn the board off and then repartition the sd card offline on my laptop, using the UUIDs I made up for those partitions earlier. Booting the new config afterwards should work? On a non-NixOS system I'd edit the /etc/fstab manually after repartitioning but that seems like a recipe for disaster on NixOS.

--- a/hosts/lemon/hardware.nix
+++ b/hosts/lemon/hardware.nix
@@ -22,7 +22,12 @@
 
   fileSystems = {
     "/" = {
-      device = "/dev/disk/by-uuid/44444444-4444-4444-8888-888888888888";
+      device = "/dev/disk/by-uuid/78fae9ea-182e-41e7-a390-45ab1a153c4d";
+      fsType = "btrfs";
+      options = ["rw" "relatime" "lazytime" "compress=zstd:3" "subvol=@"];
+    };
+    "/boot" = {
+      device = "/dev/disk/by-uuid/14e841ee-a326-4168-b6c3-5893c781e134";
       fsType = "ext4";
     };
   };

After making a backup of both the fully working ext4 setup using dd if=/dev/ mmcblk0 as a raw image - in case I need an easy way to restore later. And the prepared-to-boot-from-btrfs setup using rsync -a. Now we can start poking the partitions. This is what the original layout looked like:

Disk /dev/mmcblk0: 7,49 GiB, 8044675072 bytes, 15712256 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x2178694e

Device         Boot Start      End  Sectors  Size Id Type
/dev/mmcblk0p1      16384    77823    61440   30M  b W95 FAT32
/dev/mmcblk0p2 *    77824 15712255 15634432  7,5G 83 Linux

Considering how much the files take up,

23M   FIRMWARE
161M  BOOT
5,7G  NIXOS

I'll keep the 30MB firmware partition untouched, and give the BOOT partition 500MB to fit ~3 kernel builds. The rest will be taken up by the new btrfs root partition. Using fdisk's CLI:

> d
> 2
Partition 2 has been deleted.
> n
> p
> 2
> 77824
> +500M
Created a new partition 2 of type 'Linux' and of size 500 MiB.
> a
> 2
The bootable flag on partition 2 is enabled now.
> F
Unpartitioned space /dev/mmcblk0: 6,97 GiB, 7487881216 bytes, 14624768 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes

  Start      End  Sectors Size
   2048    16383    14336   7M
1101824 15712255 14610432   7G
> n
> p
> 3
> 1101824
> 15712255
Created a new partition 3 of type 'Linux' and of size 7 GiB.
> p
Disk /dev/mmcblk0: 7,49 GiB, 8044675072 bytes, 15712256 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x2178694e

Device         Boot   Start      End  Sectors  Size Id Type
/dev/mmcblk0p1        16384    77823    61440   30M  b W95 FAT32
/dev/mmcblk0p2 *      77824  1101823  1024000  500M 83 Linux
/dev/mmcblk0p3      1101824 15712255 14610432    7G 83 Linux

Now we need to recreate the presumed filesystems.

mkfs.ext4 -U 14e841ee-a326-4168-b6c3-5893c781e134 -L BOOT /dev/mmcblk0p2
mkfs.btrfs -U 78fae9ea-182e-41e7-a390-45ab1a153c4d -L NIXOS /dev/mmcblk0p3
mount /dev/mmcblk0p3 /mnt/sd
btrfs subvolume create /mnt/sd/@
umount /mnt/sd
mount -o rw,relatime,lazytime,compress=zstd:3,subvol=@ /dev/mmcblk0p3 /mnt/sd
mkdir /mnt/sd/boot
mount /dev/mmcblk0p2 /mnt/sd/boot

And restore using rsync -a again. Now comes the moment of thruth, did my "definitely the easiest way" work at all? It boots!!

And how much space did we save?

Filesystem      Size  Used Avail Use% Mounted on
devtmpfs        188M     0  188M   0% /dev
tmpfs           1.9G     0  1.9G   0% /dev/shm
tmpfs           937M  8.4M  929M   1% /run
tmpfs           1.9G  2.2M  1.9G   1% /run/wrappers
/dev/mmcblk0p3  7.0G  1.6G  4.8G  26% /
tmpfs           1.9G     0  1.9G   0% /tmp
tmpfs           100M  2.8M   98M   3% /var/log
/dev/mmcblk0p2  459M  160M  271M  38% /boot
tmpfs           375M  4.0K  375M   1% /run/user/1001

Nice! Filesystem compression is pretty overpowered, I should do this with every SBC. Now it's time to, uhh, try Lix? Maybe all the build deps will fit if they also compress this well. We've effectively made our 8GB card into a 16GB one.

Actually installing Lix now?

In the meantime I've also enabled my desktop elderberry as a remote aarch64 builder, it's a 4 core A53 chil with only 4GB of memory, it's going to take forever if I build it locally.

--- a/hosts/elderberry/default.nix
+++ b/hosts/elderberry/default.nix
@@ -20,6 +20,9 @@
   boot.kernelModules = ["kvm-amd"];
   boot.extraModulePackages = [];
 
+  # enable building aarch64 packages from remote hosts
+  boot.binfmt.emulatedSystems = ["aarch64-linux"];
+
   hardware.graphics = {
     enable = true;
     enable32Bit = true;
--- a/hosts/lemon/default.nix
+++ b/hosts/lemon/default.nix
@@ -26,8 +26,18 @@
 
   time.timeZone = "Europe/Prague";
 
-  nix.settings = {
-    experimental-features = ["nix-command" "flakes"];
-    trusted-users = ["root" "@wheel"];
+  nix = {
+    distributedBuilds = true;
+    buildMachines = [{
+      hostName = "elderberry";
+      system = "aarch64-linux";
+      maxJobs = 4;
+    }];
+    settings = {
+      experimental-features = ["nix-command" "flakes"];
+      trusted-users = ["root" "@wheel"];
+      max-jobs = 1;
+      cores = 2;
+    };
   };
 }

Now add the Lix module to lemon, pray and run nixos-rebuild boot --flake ..

--- a/flake.nix
+++ b/flake.nix
@@ -223,6 +223,7 @@
         system = "aarch64-linux";
         specialArgs = { inherit inputs; };
         modules = [
+          lix-module.nixosModules.default
           ./modules/nebula.nix
           ./modules/openssh.nix
           ./hosts/lemon

Unfortunately I was not able to build Lix like that, neither through the remote (thanks to some issues with the qemu-user builder, some lix functional tests didn't finish) nor locally (there is not enough space on the tmpfs /tmp). So for now we'll have to settle for using Lix from nixpkgs.

--- a/flake.nix
+++ b/flake.nix
@@ -223,6 +223,7 @@
         system = "aarch64-linux";
         specialArgs = { inherit inputs; };
         modules = [
-          lix-module.nixosModules.default
+          ({ pkgs, ... }: { nix.package = pkgs.lix; })
           ./modules/nebula.nix
           ./modules/openssh.nix
           ./hosts/lemon