-
-
Save plmercereau/0c8e6ed376dc77617a7231af319e3d29 to your computer and use it in GitHub Desktop.
{ config, lib, pkgs, ... }: | |
{ | |
imports = [ | |
<nixpkgs/nixos/modules/installer/sd-card/sd-image-aarch64-installer.nix> | |
./sd-image.nix | |
]; | |
system.stateVersion = "23.11"; | |
# Pi Zero 2 struggles to work without swap | |
sdImage.swap.enable = true; | |
sdImage.extraFirmwareConfig = { | |
# Give up VRAM for more Free System Memory | |
# - Disable camera which automatically reserves 128MB VRAM | |
start_x = 0; | |
# - Reduce allocation of VRAM to 16MB minimum for non-rotated (32MB for rotated) | |
gpu_mem = 16; | |
}; | |
# bzip2 compression takes loads of time with emulation, skip it. Enable this if you're low on space. | |
sdImage.compressImage = false; | |
networking = { | |
interfaces."wlan0".useDHCP = true; | |
wireless = { | |
enable = true; | |
interfaces = [ "wlan0" ]; | |
networks = { | |
"<ssid>" = { | |
psk = "<ssid-key>"; | |
}; | |
}; | |
}; | |
}; | |
# Enable OpenSSH out of the box. | |
services.sshd.enable = true; | |
# NTP time sync. | |
services.timesyncd.enable = true; | |
} |
# This module extends the official sd-image.nix with the following: | |
# - ability to add a swap partition to the built image | |
# - ability to add options to the config.txt firmware | |
# - fix the uboot bug with pi zero 2 | |
# Related issue: https://github.com/NixOS/nixpkgs/issues/216886 | |
# Original file: https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/installer/sd-card/sd-image.nix | |
{ config, lib, pkgs, ... }: | |
with lib; | |
let | |
rootfsImage = pkgs.callPackage <nixpkgs/nixos/lib/make-ext4-fs.nix> ({ | |
inherit (config.sdImage) storePaths; | |
compressImage = config.sdImage.compressImage; | |
populateImageCommands = config.sdImage.populateRootCommands; | |
volumeLabel = "NIXOS_SD"; | |
} // optionalAttrs (config.sdImage.rootPartitionUUID != null) { | |
uuid = config.sdImage.rootPartitionUUID; | |
}); | |
in | |
{ | |
options.sdImage = { | |
swap = { | |
enable = mkEnableOption "Create a swap partition."; | |
partitionName = mkOption { | |
type = types.str; | |
default = "SWAP"; | |
description = lib.mdDoc '' | |
Name of the partition which holds the swap. | |
''; | |
}; | |
size = mkOption { | |
type = types.int; | |
default = 2 * 1024; | |
description = lib.mdDoc '' | |
Size of the swap partition, in megabytes. | |
''; | |
}; | |
}; | |
extraFirmwareConfig = mkOption { | |
type = types.attrs; | |
default = { }; | |
description = lib.mdDoc '' | |
Extra configuration to be added to config.txt. | |
''; | |
}; | |
}; | |
config = { | |
# Override of the sd image build to optionally add a swap partition | |
system.build.sdImage = lib.mkForce (pkgs.callPackage | |
({ stdenv | |
, dosfstools | |
, e2fsprogs | |
, mtools | |
, libfaketime | |
, util-linux | |
, zstd | |
}: stdenv.mkDerivation { | |
name = config.sdImage.imageName; | |
nativeBuildInputs = [ dosfstools e2fsprogs libfaketime mtools util-linux ] | |
++ lib.optional config.sdImage.compressImage zstd; | |
inherit (config.sdImage) imageName compressImage; | |
buildCommand = '' | |
mkdir -p $out/nix-support $out/sd-image | |
export img=$out/sd-image/${config.sdImage.imageName} | |
echo "${pkgs.stdenv.buildPlatform.system}" > $out/nix-support/system | |
if test -n "$compressImage"; then | |
echo "file sd-image $img.zst" >> $out/nix-support/hydra-build-products | |
else | |
echo "file sd-image $img" >> $out/nix-support/hydra-build-products | |
fi | |
root_fs=${rootfsImage} | |
${lib.optionalString config.sdImage.compressImage '' | |
root_fs=./root-fs.img | |
echo "Decompressing rootfs image" | |
zstd -d --no-progress "${rootfsImage}" -o $root_fs | |
''} | |
# Set swap size. Set it to 0 it swap is disabled. | |
swapSize=${toString (if config.sdImage.swap.enable then config.sdImage.swap.size else 0)} | |
# The root partition is #2 if there is no swap, but is #3 is there is one | |
rootPartitionNumber=${toString (if config.sdImage.swap.enable then 3 else 2)} | |
# Gap in front of the first partition, in MiB | |
gap=${toString config.sdImage.firmwarePartitionOffset} | |
# Create the image file sized to fit /boot/firmware and /, plus slack for the gap. | |
rootSizeBlocks=$(du -B 512 --apparent-size $root_fs | awk '{ print $1 }') | |
firmwareSizeBlocks=$((${toString config.sdImage.firmwareSize} * 1024 * 1024 / 512)) | |
# Note: swap size is 0 if swap is disabled | |
imageSize=$((rootSizeBlocks * 512 + firmwareSizeBlocks * 512 + gap * 1024 * 1024 + swapSize * 1024 * 1024)) | |
truncate -s $imageSize $img | |
# type=b is 'W95 FAT32', type=82 is Swap, type=83 is 'Linux'. | |
# The "bootable" partition is where u-boot will look file for the bootloader | |
# information (dtbs, extlinux.conf file). | |
sfdisk $img <<EOF | |
label: dos | |
label-id: ${config.sdImage.firmwarePartitionID} | |
start=''${gap}M, size=$firmwareSizeBlocks, type=b | |
${lib.optionalString config.sdImage.swap.enable '' | |
start=$((gap + ${toString config.sdImage.firmwareSize}))M, size=''${swapSize}M, type=82 | |
''} | |
start=$((gap + ${toString config.sdImage.firmwareSize} + swapSize))M, type=83, bootable | |
EOF | |
# Copy the rootfs into the SD image | |
eval $(partx $img -o START,SECTORS --nr $rootPartitionNumber --pairs) | |
dd conv=notrunc if=$root_fs of=$img seek=$START count=$SECTORS | |
# * Create the swap if it is enabled | |
${lib.optionalString config.sdImage.swap.enable '' | |
# Create the swap | |
eval $(partx $img -o START,SECTORS --nr 2 --pairs) | |
dd if=/dev/zero of=swap.img bs=''${swapSize}M count=1 | |
mkswap -L "${config.sdImage.swap.partitionName}" swap.img | |
dd conv=notrunc if=swap.img of=$img seek=$START count=$SECTORS | |
''} | |
# Create a FAT32 /boot/firmware partition of suitable size into firmware_part.img | |
eval $(partx $img -o START,SECTORS --nr 1 --pairs) | |
truncate -s $((SECTORS * 512)) firmware_part.img | |
mkfs.vfat --invariant -i ${config.sdImage.firmwarePartitionID} -n ${config.sdImage.firmwarePartitionName} firmware_part.img | |
# Populate the files intended for /boot/firmware | |
mkdir firmware | |
${config.sdImage.populateFirmwareCommands} | |
find firmware -exec touch --date=2000-01-01 {} + | |
# Copy the populated /boot/firmware into the SD image | |
cd firmware | |
# Force a fixed order in mcopy for better determinism, and avoid file globbing | |
for d in $(find . -type d -mindepth 1 | sort); do | |
faketime "2000-01-01 00:00:00" mmd -i ../firmware_part.img "::/$d" | |
done | |
for f in $(find . -type f | sort); do | |
mcopy -pvm -i ../firmware_part.img "$f" "::/$f" | |
done | |
cd .. | |
# Verify the FAT partition before copying it. | |
fsck.vfat -vn firmware_part.img | |
dd conv=notrunc if=firmware_part.img of=$img seek=$START count=$SECTORS | |
${config.sdImage.postBuildCommands} | |
if test -n "$compressImage"; then | |
zstd -T$NIX_BUILD_CORES --rm $img | |
fi | |
''; | |
}) | |
{ }); | |
swapDevices = lib.mkIf config.sdImage.swap.enable [{ | |
device = "/dev/disk/by-label/${config.sdImage.swap.partitionName}"; | |
}]; | |
sdImage.populateFirmwareCommands = lib.mkIf ((lib.length (lib.attrValues config.sdImage.extraFirmwareConfig)) > 0) | |
( | |
let | |
# Convert the set into a string of lines of "key=value" pairs. | |
keyValueMap = name: value: name + "=" + toString value; | |
keyValueList = lib.mapAttrsToList keyValueMap config.sdImage.extraFirmwareConfig; | |
extraFirmwareConfigString = lib.concatStringsSep "\n" keyValueList; | |
in | |
lib.mkAfter | |
'' | |
config=firmware/config.txt | |
# The initial file has just been created without write permissions. Add them to be able to append the file. | |
chmod u+w $config | |
echo "\n# Extra configuration" >> $config | |
echo "${extraFirmwareConfigString}" >> $config | |
chmod u-w $config | |
'' | |
); | |
# Ugly hack to make it work with Pi Zero 2 | |
sdImage.populateRootCommands = lib.mkForce '' | |
mkdir -p ./files/boot | |
${config.boot.loader.generic-extlinux-compatible.populateCmd} -c ${config.system.build.toplevel} -d ./files/boot | |
DTBS_DIR=$(ls -d ./files/boot/nixos/*-dtbs)/broadcom | |
chmod u+w $DTBS_DIR | |
cp ${config.system.build.toplevel}/dtbs/broadcom/bcm2837-rpi-zero-2-w.dtb $DTBS_DIR/bcm2837-rpi-zero-2.dtb | |
chmod u-w $DTBS_DIR | |
''; | |
}; | |
} |
Hi @plmercereau I just come across this when trying to figure out how to create a swap on my cross compiled image for raspberry pi, could you upsteam these changes to https://github.com/nixos/nixpkgs? Thanks!
@sullyj3 ever figure this out? I'm in the same boat
I did not!
I did not!
I've changed https://gist.github.com/plmercereau/0c8e6ed376dc77617a7231af319e3d29#file-sd-image-nix-L13 to
rootfsImage = pkgs.callPackage "${modulesPath}/../../nixos/lib/make-ext4-fs.nix" ({
You also need to make sure that modulesPath is added here https://gist.github.com/plmercereau/0c8e6ed376dc77617a7231af319e3d29#file-sd-image-nix-L8
Also, need to modify this line to also call modulesPath like so
https://gist.github.com/plmercereau/0c8e6ed376dc77617a7231af319e3d29#file-raspberry-pi-zero-2-nix-L4
"${modulesPath}/installer/sd-card/sd-image-aarch64-installer.nix"
Then it's just a matter of adding a new nixosConfiguration
nixosConfigurations = {
// nixos-stable is the stable nixos channel input in my flake
rpizero2wImage = nixos-stable.lib.nixosSystem {
system = "aarch64-linux";
// rpizero2w.nix is raspberry-pi-zero-2.nix from this gist
modules = [ ./modules/arm/rpizero2w.nix ];
};
};
Build using the following command. Drop -L
if you don't want to see lots of logs scrolling on your screen.
nix build -L .#nixosConfigurations.rpizero2wImage.config.system.build.sdImage
That allowed me to build the image using flakes on my arm64 builder. I haven't tried booting it yet though, but at least it builds (:
Please see this flake example , and this comment
@plmercereau thanks so much! @jpraczyk I was so close! I figured it needed modulesPath
but didn't have enough time to go the extra mile − many thanks for also working on it cc @sullyj3
Thanks very much!
For @sullyj3 @rjpcasalino and anyone else interested in related projects, I extended this project to implement several things:
- A custom kernel patch to enable USB host mode in the DTB
- A cross-compiled version to build on x86_64-linux systems
- Versions that work with both NixOS and Nix package manager on non-NixOS systems
- agenix encrypted secrets for the WiFi and admin user configuration
- Automation scripts to help build everything
You can check it out here:
https://github.com/pete3n/nix-pi/tree/main
Thanks for sharing this, Pete, that's a very inspiring repo. I wonder what and how we could submit upstream in order to simplify things a bit
this looks cool will def check it out when I've got time. Thanks @pete3n
Thanks, I hope you find it helpful. I have many projects involving different Raspberry Pis and other small board computers. As I get more proficient with Nix and build out my own library I will definitely look at submitting stuff upstream.
Nice! You both have a much better handle on nix lang than I do so I suggest looking into upstreaming stuff as soon as you can. https://github.com/samueldr seems to be a go to person for embedded stuff (check out https://github.com/Tow-Boot/Tow-Boot). I've started to wonder if there is a Raspberry Pi working group or some such. Anyway, please join https://discourse.nixos.org/ if you want. I'll share some of these links after I've played around with them so more. The community would love this stuff!
Thanks for this! Could I get some pointers on how to use it?
I want to add a raspberry pi zero 2 w image as an output of a flake, but I'm not entirely sure how to do so. If it makes a difference I'm on non-nixos, x86-64 linux.
My hazy guess of what to do, along with areas of uncertainty, is:
raspberry-pi-zero-2.nix
nixos-rebuild
ornix build