NixOS on ARM

From NixOS Wiki
Revision as of 10:09, 2 April 2025 by KaladinB4 (talk | contribs) (revision / format nixos flake update ARM)
Jump to: navigation, search

Okay, let's treat this as the final review before submission. I'll format it correctly and incorporate the minor polish suggestions discussed previously. Assuming the content details are factually correct for your target (NixOS 25.05 in April 2025), here is the reviewed and formatted final version ready for the wiki:

NixOS installation & configuration The installation image typically contains partitions suitable for booting (e.g., FAT32 for /boot) and a root filesystem (e.g., ext4). The standard NixOS SD card images for ARM allow direct installation onto the storage device by modifying the configuration after the initial boot. On first boot, official images often resize the root filesystem to utilize available space.

NixOS on ARM supports two main approaches to system configuration: traditional /etc/nixos/configuration.nix files and the now-standard Flakes approach (recommended since NixOS 25.05). Both methods are covered below.

Boot Process Overview Most ARM boards supported by NixOS use U-Boot as the bootloader. NixOS utilizes U-Boot's Generic Distro Configuration Concept. U-Boot scans storage devices (like SD cards or eMMC) for /extlinux/extlinux.conf or /boot/extlinux/extlinux.conf. NixOS generates this file, which contains boot information (kernel path, initrd, device tree blob, command line arguments). U-Boot uses the partition marked as "bootable".

U-Boot usually provides an interactive shell and often a generation selection menu (similar to GRUB). However, support for specific input (keyboard) or display devices during boot varies significantly by board – check your device's specific wiki page for details.

Basic Setup with configuration.nix This approach uses the traditional /etc/nixos/configuration.nix file. After booting the installation media (or the base system if installing directly onto the boot device):

Mount your target filesystems under /mnt (e.g., mount /dev/disk/by-label/NIXOS_SD /mnt, mkdir /mnt/boot, mount /dev/disk/by-label/NIXOS_BOOT /mnt/boot). Generate a default configuration detecting your hardware: Bash

sudo nixos-generate-config --root /mnt Edit the generated /mnt/etc/nixos/configuration.nix. Here's a modern baseline template suitable for many ARM boards: Nix

  1. /mnt/etc/nixos/configuration.nix

{ config, pkgs, lib, ... }:

{

 imports = [
   # Include the results of hardware detection.
   # This should correctly configure your detected file systems, LUKS (if used), etc.
   ./hardware-configuration.nix
 ];
 # Bootloader configuration for most ARM SBCs using U-Boot extlinux support
 boot.loader.grub.enable = false;
 boot.loader.generic-extlinux-compatible.enable = true;
 # Networking - Enable NetworkManager for easier setup (Wi-Fi & Ethernet)
 # Disable wait-for-online service if NetworkManager handles connections
 networking.networkmanager.enable = true;
 systemd.services.NetworkManager-wait-online.enable = false;
 # Basic firewall (recommended)
 networking.firewall.enable = true;
 # Set your time zone
 time.timeZone = "UTC"; # Example: Replace with your time zone, e.g. "Europe/Amsterdam"
 # Add a swap file is optional, but recommended for RAM-constrained devices
 # Size is in MiB. ZRAM (see performance section) is often preferred on SD cards.
 # swapDevices = [ { device = "/swapfile"; size = 1024; } ];
 # Enable packages needed early, like firmware for Wi-Fi
 # hardware.enableRedistributableFirmware = true; # Uncomment if needed for Wi-Fi/Bluetooth
 # System state version - IMPORTANT: Set to the version you installed.
 # Do not change this after initial install without understanding implications.
 system.stateVersion = "25.05"; # Replace with your installed NixOS version
 # Define users and basic packages in other parts of your configuration
 # users.users.your_user = { ... };
 # environment.systemPackages = with pkgs; [ vim git ... ];

} Apply your configuration to the installed system using:

Bash

sudo nixos-rebuild switch Kernel Selection Kernel selection depends on your specific ARM device. Add one of the following to your configuration.nix:

For boards with good mainline Linux support (including Raspberry Pi 4/5 on aarch64): Nix

boot.kernelPackages = pkgs.linuxPackages_latest; For legacy Raspberry Pi models (1/Zero/2/3) on armv6/armv7: Nix

boot.kernelPackages = pkgs.linuxPackages_rpi; For boards requiring specialized Board Support Package (BSP) kernels: Refer to the board-specific wiki page or nixos-hardware repository for recommended kernel packages. Modern Flakes-Based Approach (Recommended) Since NixOS 25.05, Flakes are the standard recommended approach. They offer reproducible builds, locked dependencies via flake.lock, and simplify managing configurations across devices.

Enable Flakes: Ensure your configuration.nix includes:

Nix

nix.settings.experimental-features = [ "nix-command" "flakes" ]; (Rebuild once with nixos-rebuild switch if adding this for the first time).

Create flake.nix: In your configuration directory (e.g., /etc/nixos/ or a git repository), create flake.nix:

Nix

  1. flake.nix

{

 description = "NixOS configuration for my ARM device";
 inputs = {
   # Nixpkgs channel (e.g., stable, unstable)
   nixpkgs.url = "github:nixos/nixpkgs/nixos-25.05";
   # Hardware-specific quirks and configurations (optional but often helpful)
   nixos-hardware.url = "github:NixOS/nixos-hardware";
   # Home Manager for user dotfiles (optional)
   # home-manager.url = "github:nix-community/home-manager";
   # home-manager.inputs.nixpkgs.follows = "nixpkgs";
 };
 outputs = { self, nixpkgs, nixos-hardware, ... }@inputs: {
   # Define your NixOS system configuration
   nixosConfigurations.myhostname = nixpkgs.lib.nixosSystem {
     system = "aarch64-linux"; # Or "armv6l-linux"/"armv7l-linux" for 32-bit ARM
     specialArgs = { inherit inputs; }; # Pass inputs to modules
     modules = [
       # Hardware-specific modules (select the correct one if needed)
       # nixos-hardware.nixosModules.raspberry-pi-4
       # nixos-hardware.nixosModules.raspberry-pi-5
       # Your main configuration file
       ./configuration.nix
       # Home Manager module (optional)
       # inputs.home-manager.nixosModules.home-manager {
       #   home-manager.useGlobalPkgs = true;
       #   home-manager.useUserPackages = true;
       #   home-manager.users.your_user = import ./home.nix; # User-specific config
       # }
     ];
   };
 };

} Apply Configuration: From within the directory containing flake.nix:

Bash

sudo nixos-rebuild switch --flake .#myhostname (Replace myhostname with the actual key used in nixosConfigurations above).

Binary Caches AArch64 (ARM64) The official NixOS Hydra instance builds extensive binary sets for AArch64, available from the default cache https://cache.nixos.org. Ensure your configuration includes:

Nix

  1. configuration.nix or equivalent module

nix.settings = {

 substituters = [
   "https://cache.nixos.org"
   # Add other community caches if desired, e.g., cachix caches
 ];
 trusted-public-keys = [
   "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="
   # Add public keys for other caches
 ];

}; armv6l and armv7l (32-bit ARM) There are currently no official binary caches for 32-bit ARM platforms. Builds will happen natively (which can be very slow on constrained devices) or via emulation/cross-compilation. Consider:

Using a more powerful machine for builds (see Remote Builders/Cross-Compilation below). Setting up your own binary cache (e.g., using cachix or nix-serve) if building frequently or for multiple devices. For potentially faster downloads on slow or unstable connections (at the cost of higher peak bandwidth), you can adjust substitution jobs:

Nix

  1. nix.settings continuation

nix.settings = {

 # ... other settings
 max-substitution-jobs = 4; # Default is usually higher, adjust as needed

}; Building on Resource-Constrained ARM Devices Building large packages (browsers, compilers) directly on low-power ARM devices can take hours or days. Use these strategies:

Remote Builders: Configure a more powerful machine (x86_64 or ideally another ARM64 machine) to perform builds remotely.

Nix

  1. configuration.nix on the ARM device

nix.buildMachines = [{

 hostName = "powerful-builder.local"; # Address of the builder machine
 system = "aarch64-linux"; # Or "x86_64-linux"
 maxJobs = 8; # Number of jobs the builder can handle
 speedFactor = 4; # Relative speed estimate
 supportedFeatures = [ "nixos-test" "benchmark" "big-parallel" "kvm" ];
 # Needed if builder is different architecture:
 # requires = [ "emulation" ]; # If builder emulates ARM via binfmt_misc

}]; nix.distributedBuilds = true; See the Distributed Build wiki page for detailed setup.

Cross-Compilation using QEMU (binfmt_misc): Build ARM packages on an x86_64 NixOS machine using QEMU user-space emulation. Enable on the x86_64 builder:

Nix

  1. configuration.nix on the x86_64 builder

boot.binfmt.emulatedSystems = [ "aarch64-linux" "armv7l-linux" ]; Then build specifically for ARM from the x86_64 machine:

Bash

  1. Build a Flake package for ARM

nix build --system aarch64-linux .#somePackage

  1. Build a legacy package for ARM

nix-build '<nixpkgs>' -A pkgs.hello --argstr system aarch64-linux Note: Emulation is significantly slower than native compilation.

Full QEMU/KVM Emulation: Slower still, but possible for development. See the NixOS on ARM/QEMU page.

Creating Custom ARM Images Generate your own bootable SD card images with custom configurations included.

Building with Flakes (Recommended) Nix

  1. flake.nix (example for building an image)

{

 description = "Custom ARM image";
 inputs.nixpkgs.url = "github:nixos/nixpkgs/nixos-25.05";
 outputs = { self, nixpkgs }: {
   nixosConfigurations.myarmimage = nixpkgs.lib.nixosSystem {
     system = "aarch64-linux"; # Target architecture for the image
     modules = [
       # Base SD image module for the target architecture
       "${nixpkgs}/nixos/modules/installer/sd-card/sd-image-aarch64.nix"
       # Your custom configuration module(s)
       ./my-image-configuration.nix # Contains users, packages, services etc.
       # Example inline customization: Pre-add SSH keys
       ({ ... }: {
         users.users.root.openssh.authorizedKeys.keys = [
           "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA..."
         ];
         # Ensure SSH service is enabled in your main config
         services.openssh.enable = true;
       })
     ];
   };
   # Make the SD image the default build output for `nix build`
   packages.aarch64-linux.default =
     self.nixosConfigurations.myarmimage.config.system.build.sdImage;
 };

} Build the image (will produce ./result/sd-image/*.img):

Bash

nix build .#myarmimage.config.system.build.sdImage

  1. Or if using the default package output:
  2. nix build

Building the Traditional Way Bash

  1. Command line build

nix-build '<nixpkgs/nixos>' -A config.system.build.sdImage -I nixos-config=./sd-image-config.nix Where sd-image-config.nix contains:

Nix

  1. sd-image-config.nix

{ config, pkgs, ... }: {

 imports = [
   # Base SD image module
   <nixpkgs/nixos/modules/installer/sd-card/sd-image-aarch64.nix> # Adjust arch if needed
 ];
 # Your customizations here
 system.stateVersion = "25.05"; # Match nixpkgs version
 users.users.root.openssh.authorizedKeys.keys = [
    "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA..."
 ];
 services.openssh.enable = true;
 environment.systemPackages = with pkgs; [ vim htop ];
 # ... other config ...

} U-Boot Customization While NixOS aims for mainline U-Boot compatibility, some boards might require specific U-Boot versions or configurations.

Building via Nixpkgs (if board defconfig exists): Bash

  1. Example for AArch64, replace defconfig

nix-shell -p 'let plat = pkgsCross.aarch64-multiplatform; in plat.buildUBoot { defconfig = "myboard_defconfig"; extraMeta.platforms = ["aarch64-linux"]; }' Manual Build (using Nix shell for toolchain): Bash

  1. Get build environment

nix-shell -p git gnumake gcc gcc-arm-embedded dtc bison flex python3 swig --run "bash"

  1. Inside the nix-shell:

git clone git://git.denx.de/u-boot.git cd u-boot export CROSS_COMPILE=arm-none-eabi- # Adjust toolchain prefix if needed make myboard_defconfig make -j$(nproc) cd .. # U-Boot binary (e.g., u-boot-sunxi-with-spl.bin) is in u-boot/

  1. Exit nix-shell

exit Flashing U-Boot: This is board-specific and dangerous if done incorrectly. Consult your board's documentation. A common pattern (e.g., for Allwinner) but verify for your specific board: Bash

sudo dd if=u-boot-binary.bin of=/dev/sdX bs=1024 seek=8 # EXAMPLE ONLY! Verify offset and device! Optimizing Performance on ARM Tune your system for better responsiveness, especially on lower-power devices:

Nix

  1. configuration.nix

{

 # CPU frequency scaling governor (if supported)
 # 'ondemand' or 'schedutil' are common defaults. 'performance' uses max clock speed.
 powerManagement.cpuFreqGovernor = "ondemand";
 # IO scheduler tuning (improves SD card/eMMC performance)
 # mq-deadline is often a good choice for flash storage.
 services.udev.extraRules = 
   ACTION=="add|change", KERNEL=="mmcblk[0-9]|sd[a-z]", ATTR{queue/scheduler}="mq-deadline"
 ;
 # ZRAM Swap (compressed RAM swap - highly recommended over SD card swap)
 zramSwap = {
   enable = true;
   algorithm = "zstd"; # Fast compression
   # memoryPercent = 50; # Optional: Adjust percentage of RAM to use
 };
 # Ensure traditional swap files/partitions are disabled if using ZRAM primarily.
 # Use tmpfs for /tmp (reduces SD card writes)
 boot.tmpOnTmpfs = true;

} Security Hardening for ARM Devices Consider these options, especially for devices exposed to networks:

Nix

  1. configuration.nix

{

 # Example Kernel hardening parameters
 boot.kernelParams = [ "slab_nomerge" "init_on_alloc=1" "init_on_free=1" ];
 boot.kernel.sysctl = {
   "kernel.kptr_restrict" = "2";
   "kernel.dmesg_restrict" = "1";
   # Add other sysctl hardening options here
 };
 # Enable AppArmor Mandatory Access Control (generally lower overhead than SELinux)
 security.apparmor.enable = true;
 # Protect kernel image in memory
 security.protectKernelImage = true;
 # Basic firewall enabled previously is a good start. Configure rules as needed:
 # networking.firewall.allowedTCPPorts = [ 22 ]; # Example: Allow SSH
 # Consider enabling auditd for logging security events
 # security.audit.enable = true;
 # security.auditd.enable = true;

} Note: Always research the implications of hardening options. Some may affect performance or compatibility with specific hardware or software. Apply incrementally.

Wayland Support on ARM64 For graphical desktops, Wayland generally offers better performance on modern ARM64 devices:

Nix

  1. configuration.nix

{

 # Enable OpenGL drivers (essential)
 hardware.opengl = {
   enable = true;
   driSupport = true;
   # driSupport32Bit = true; # If running 32-bit apps via emulation
 };
 # Example for GNOME (Wayland by default)
 services.xserver = {
   enable = true;
   displayManager.gdm.enable = true;
   desktopManager.gnome.enable = true;
   # Optional: Explicitly prefer Wayland, though usually default
   # displayManager.gdm.wayland = true;
 };
 # Example for KDE Plasma (Choose Wayland session at login)
 # services.xserver = {
 #   enable = true;
 #   displayManager.sddm.enable = true;
 #   desktopManager.plasma5.enable = true;
 #   # Ensure sddm offers Wayland session (usually default)
 # };

} Raspberry Pi 4/5 Specific: Ensure appropriate GPU overlays are enabled in /boot/config.txt (NixOS modules might handle this, but verify). For RPi 5 Wayland, dtoverlay=vc4-kms-v3d-pi5 is often needed. Check nixos-hardware configs. Common ARM Device Troubleshooting UART Console Access: If the device doesn't boot fully or display video, connect a USB-to-Serial adapter to the board's UART pins. Add kernel parameters to enable console output (adjust device ttyS0/ttyAMA0 and speed 115200 based on board): Nix

  1. configuration.nix

boot.kernelParams = [

 "console=ttyS0,115200n8" # Example, check board specifics

]; Memory Constraints (Low RAM devices): Limit build resource usage: Nix

  1. configuration.nix

nix.settings = {

 cores = 1; # Limit parallel builds locally
 max-jobs = 1;

}; boot.tmp.cleanOnBoot = true; # Free up /tmp space on reboot (Use ZRAM swap, avoid heavy desktop environments). Network Issues (Wi-Fi/Bluetooth): Ensure necessary firmware is enabled: Nix

  1. configuration.nix

hardware.enableRedistributableFirmware = true; # For many common Wi-Fi/BT chips

  1. Some firmware might need specific enabling, e.g.:
  2. hardware.firmware = [ pkgs.wireless-regdb ];

Ensure networking.networkmanager.enable = true; for easier Wi-Fi management. Related Projects Mobile NixOS: Extends NixOS to run on mobile devices like the PinePhone. Provides modules for mobile-specific hardware and UI needs. See Mobile NixOS project. NixOS Hardware: A repository of hardware-specific configurations and workarounds for various devices, including many ARM boards. See NixOS/nixos-hardware. NixOS on ARM/QEMU: Instructions for running NixOS ARM builds within QEMU for development and testing. See the NixOS on ARM/QEMU wiki page.