Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

NixOS Modules

All NixOS modules live in the modules/ directory. base.nix imports all service modules and is itself imported by the hardware-specific modules (hardware-rock64.nix, hardware-qemu.nix).

Module Dependency Graph

flowchart TD
    KERNEL["kernel-config.nix<br/>shared stripped kernel baseline"]

    subgraph HARDWARE["hardware targets"]
        direction LR
        ROCK64["hardware-rock64.nix"]
        QEMU["hardware-qemu.nix"]
    end

    ROCK64 --> BASE["base.nix"]
    QEMU --> BASE

    subgraph IMPORTS["base.nix imports"]
        direction LR
        LOGGING["logging.nix"]
        NETWORKING["networking.nix"]
        FIREWALL["firewall.nix"]
        LAN["lan-gateway.nix"]
        OPENVPN["openvpn.nix"]
        RAUC["rauc.nix"]
        FIRSTBOOT["first-boot.nix"]
        VERIFY["os-verification.nix"]
        UPGRADE["os-upgrade.nix"]
        WATCHDOG["watchdog.nix"]
    end

    BASE --> NETWORKING
    BASE --> LOGGING
    BASE --> FIREWALL
    BASE --> LAN
    BASE --> OPENVPN
    BASE --> RAUC
    BASE --> FIRSTBOOT
    BASE --> VERIFY
    BASE --> UPGRADE
    BASE --> WATCHDOG

    KERNEL -. shared baseline .-> ROCK64
    KERNEL -. shared baseline .-> QEMU

base.nix

Purpose: Shared NixOS configuration for both hardware and QEMU targets. Defines the core system layout, filesystem mounts, user accounts, and system packages.

Key configuration:

SettingValueNotes
system.stateVersion"25.11"NixOS release
networking.hostName"gateway"
nix.enablefalseNo Nix daemon on read-only rootfs
documentation.enablefalseSaves closure space
security.sudo.enablefalseUses run0 instead

Filesystem layout (OverlayFS root):

The root filesystem uses a single OverlayFS assembled in the initrd from the selected squashfs slot and tmpfs-backed upper/work directories:

LayerMountFilesystemSizeDescription
overlay (combined)/overlayUnified writable root presented to userspace
lower (read-only)/run/rootfs-basesquashfsImmutable NixOS system from the selected RAUC rootfs slot
upper (writable)/run/overlay-root/*tmpfsruntimeEphemeral writes, lost on reboot
persistent state/dataf2fsdynamicCreated on first boot (PARTLABEL=data, nofail, noatime)

The overlay is assembled in the initrd before switch_root:

  1. boot.scr passes root=fstab and atomicnix.lowerdev=/dev/... for the selected squashfs slot
  2. initrd-prepare-overlay-lower.service mounts that slot read-only at /run/rootfs-base
  3. sysroot.mount mounts / as overlay with lowerdir=/run/rootfs-base, upperdir=/run/overlay-root/upper, and workdir=/run/overlay-root/work
  4. sysroot-run.mount bind-mounts /run into the switched root

This approach replaces the older /sysroot mutation logic and keeps the root mount fstab-driven, which fits systemd’s initrd model more cleanly.

The lower squashfs is selected by U-Boot/RAUC, while /data remains outside the A/B slots and survives updates.

Sandboxing note: nsncd (the NSS lookup daemon) runs as root due to permission issues on the overlay filesystem.

Network wait: systemd-networkd-wait-online is configured with a 30s timeout and anyInterface=true.

Build ID: The NixOS login banner (/etc/issue) displays the build ID for easy identification.

Data partition: Not included in the flashable image. Initrd systemd-repart creates it from the remaining eMMC space on first boot.

tmpfiles.d rules (created on boot):

/var/empty, /var/lib, /var/lib/systemd/network, /var/lib/private,
/var/lib/private/systemd/resolve, /var/lib/chrony, /var/lib/dnsmasq,
/var/cache, /var/cache/nscd, /var/log, /var/log/journal, /var/db, /var/run

User accounts:

UserGroupsAuthentication
rootLocked by default; Rock64 serial-root recovery only when _RUT_OH_=1
adminwheelSSH key from /data/config/ssh-authorized-keys/admin; password remains locked

System packages: nano, htop, curl, jq, f2fs-tools, kmod


logging.nix

Purpose: Configure the runtime logging path: volatile journald as ingress, buffered rsyslog appends to /data/logs, and a shutdown flush hook.

Key configuration:

SettingValueNotes
journald storageStorage=volatileKeeps runtime logs in tmpfs-backed journal storage
journald capRuntimeMaxUse=32MBounds memory use for runtime logs
rsyslog outputbuffered omfile appends to /data/logs/*.logUses async buffered writes instead of direct per-line sync
Podman log driverjournaldRoutes container stdout/stderr into the same journald path

Services:

ServicePurpose
syslog.serviceRuns rsyslogd and drains journald into buffered files
logging-shutdown-flush.serviceFlushes journald and asks rsyslog to sync buffered output

This module no longer installs slot-local forensic helpers. Runtime service and script output is expected to go to stdout/stderr under systemd, which places it into journald and then through the buffered rsyslog path.


hardware-rock64.nix

Purpose: Rock64 (RK3328) hardware-specific kernel, device tree, and RAUC slot mapping.

Kernel configuration:

CategoryDriversBuild
eMMCMMC_DW, MMC_DW_ROCKCHIPbuilt-in (=y)
EthernetSTMMAC_ETH, DWMAC_ROCKCHIPbuilt-in
USBDWC2, USB_XHCI_HCD, USB_EHCI_HCD, USB_OHCI_HCDbuilt-in
WatchdogDW_WATCHDOGbuilt-in
FilesystemsSQUASHFS, SQUASHFS_ZSTD, F2FS_FS, OVERLAY_FSbuilt-in
USB EthernetUSB_RTL8152, USB_NET_AX88179_178A, USB_NET_CDCETHERmodule (=m)
USB SerialFTDI_SIO, CP210Xmodule
WiFi/BTWLAN, CFG80211, MAC80211, RFKILL, BTunsupported

RAUC slot mapping:

atomicnix.rauc.slots = {
  boot0 = "/dev/mmcblk1p1";     # boot-a
  boot1 = "/dev/mmcblk1p3";     # boot-b
  rootfs0 = "/dev/mmcblk1p2";   # rootfs-a
  rootfs1 = "/dev/mmcblk1p4";   # rootfs-b
};

Serial console: ttyS2 at 1.5 Mbaud (Rock64 UART2), enabled via serial-getty@ttyS2.service.


kernel-config.nix

Purpose: Shared stripped kernel baseline used by both Rock64 and QEMU so the VM target stays close to the real device kernel.

Contents:

  • baseKernelConfig: the common stripped ARM64 gateway kernel baseline
  • optionalKernelConfig: isolated optional USB serial support

hardware-qemu.nix imports this file and layers only the minimal aarch64-virt, virtio, and test-harness-specific requirements on top.


hardware-qemu.nix

Purpose: QEMU aarch64-virt configuration for development and testing.

Differences from hardware-rock64.nix:

SettingRock64QEMU
Boot methodU-Boot boot.scrextlinux
Block devices/dev/mmcblk1pN/dev/vdN (virtio)
RAUC backendubootcustom (file-based)
Kernel modulesHardware-specificvirtio_pci, virtio_blk, etc.

The QEMU RAUC tests share their slot mapping through nix/tests/rauc-qemu-config.nix:

atomicnix.rauc = {
  slots = {
    boot0 = "/dev/vdb";
    boot1 = "/dev/vdc";
    rootfs0 = "/dev/vdd";
    rootfs1 = "/dev/vde";
  };
  bootloader = "custom";
};

networking.nix

Purpose: Deterministic NIC naming and systemd-networkd configuration.

Link files:

PriorityMatchResult
10-onboard-ethPlatform platform-ff540000.ethernetName = eth0
20-usb-ethDrivers r8152, ax88179_178a, cdc_etherEnabled as modules in Rock64 kernel config
WiFiUnsupported until hardware selectionnot part of current Rock64 image

Network files:

PriorityInterfaceConfiguration
10-waneth0DHCP v4, uses DHCP DNS, no NTP from DHCP
20-laneth1Static 172.20.30.1/24, no DHCP

Sysctl: net.ipv4.ip_forward = 0, net.ipv6.conf.all.forwarding = 0


firewall.nix

Purpose: nftables firewall with per-interface rules and dynamic SSH-on-WAN toggle.

nftables rules (inet filter):

ChainPolicyRules
inputdroplo: accept; established: accept; eth1: UDP 53, UDP 67-68, UDP 123, TCP 22, TCP 53, TCP 8080; tun0: TCP 22
forwarddrop(no exceptions)
outputaccept

Dynamic SSH toggle services:

ServiceWhenWhat
ssh-wan-toggleBoot (after nftables)Reads flag file, adds SSH rule if present
ssh-wan-reloadOn demandRemoves old rule, re-adds if flag file exists

Flag file: /data/config/ssh-wan-enabled

Provisioned WAN inbound: /data/config/firewall-inbound.json is applied by provisioned-firewall-inbound.service. The baseline eth0 policy remains closed for TCP/443 and UDP/1194 until those ports are provisioned.


lan-gateway.nix

Purpose: DHCP and NTP server for isolated LAN devices.

dnsmasq configuration:

SettingValue
Interfaceeth1 (bind-dynamic)
DHCP rangeprovisioned range, fallback 172.20.30.10172.20.30.254, 24h lease
Gateway optionprovisioned gateway IP, fallback 172.20.30.1
DNS optionprovisioned gateway IP (gateway-local DNS only)
NTP optionprovisioned gateway IP
DNS port53 (local-only, no upstream forwarding)

chrony configuration:

SettingValue
Upstreampool pool.ntp.org iburst
Serve toprovisioned LAN subnet, fallback 172.20.30.0/24
Fallbacklocal stratum 10

rauc.nix

Purpose: RAUC A/B update system configuration. Defines project options (atomicnix.rauc.*) and maps them onto the upstream NixOS services.rauc module.

Custom NixOS options (atomicnix.rauc.*):

OptionTypeDefaultDescription
compatiblestring"rock64"RAUC compatible string
bootloaderenum"uboot"Backend (uboot, custom, etc.)
statusFilestring/data/rauc/status.raucsRAUC status file
bundleFormatslist of strings[-plain, +verity]Allowed bundle formats
slots.boot0string(required)Boot slot A device path
slots.boot1string(required)Boot slot B device path
slots.rootfs0string(required)Rootfs slot A device path
slots.rootfs1string(required)Rootfs slot B device path

When bootloader = "custom", a file-based shell script is generated that simulates U-Boot environment management using files in /var/lib/rauc/.


watchdog.nix

Purpose: systemd hardware watchdog integration plus boot-count and rollback bookkeeping.

systemd.settings.Manager = {
  # RuntimeWatchdogSec = "30s";
  # RebootWatchdogSec = "10min";
};

The hardware watchdog manager settings remain disabled during development, but watchdog-boot-count.service is installed so the real boot-count and rollback path records lifecycle markers to the journal through normal service stdout.


os-verification.nix

Purpose: Post-update health-check service.

SettingValue
Typeoneshot
ConditionConditionPathExists=/data/.completed_first_boot
Timeout180s
Scriptscripts/os-verification.sh
PATHrauc, jq, systemd, iproute2

os-upgrade.nix

Purpose: OTA update polling service.

Custom NixOS options (os-upgrade.*):

OptionTypeDefaultDescription
useHawkbitboolfalseReserve hawkBit path and install package
pollingIntervalstring"1h"Timer interval
serverUrlstring"http://localhost/updates"Update server URL

Timer: OnBootSec=5min, OnUnitActiveSec=<pollingInterval>, RandomizedDelaySec=10min

When useHawkbit = true, AtomicNix disables the polling service and installs rauc-hawkbit-updater, but does not configure an operational hawkBit systemd service in the current image.


first-boot.nix

Purpose: One-time first-boot provisioning and optional slot confirmation.

SettingValue
Typeoneshot
ConditionConditionPathExists=!/data/.completed_first_boot
Scriptscripts/first-boot.sh
Effectprovision config, optionally rauc status mark-good, then write sentinel

Mutually exclusive with os-verification.service via the sentinel file.


openvpn.nix

Purpose: OpenVPN recovery tunnel.

SettingValue
Config path/data/config/openvpn/client.conf
Auto-startfalse
ConditionConditionPathExists=/data/config/openvpn/client.conf