Created
February 27, 2026 15:46
-
-
Save CertainLach/c2ea61168705c2c48a014cf7e2706d63 to your computer and use it in GitHub Desktop.
NixOS configuration for btrfs + luks2 sdImage builder
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Copy of https://github.com/NixOS/nixpkgs/blob/8647f7d06de5f1e5f5e34a740bdd94d5af09917c/nixos/lib/make-btrfs-fs.nix | |
| # with LUKS2 encryption added on top. | |
| # Includes this patch: https://github.com/NixOS/nixpkgs/pull/434122 | |
| { | |
| stdenv, | |
| buildPackages, | |
| storePaths, | |
| populateImageCommands ? "", | |
| volumeLabel, | |
| uuid ? "44444444-4444-4444-8888-888888888888", | |
| luksVolumeLabel ? "NIXOS_LUKS", | |
| luksUuid ? "44444444-4444-4444-9999-888888888888", | |
| btrfs-progs, | |
| libfaketime, | |
| util-linux, | |
| cryptsetup, | |
| qemu-utils, | |
| luksPassphrase ? "changeme", | |
| # Ignored for luks | |
| compressImage ? false, | |
| }: | |
| let | |
| sdClosureInfo = buildPackages.closureInfo { rootPaths = storePaths; }; | |
| in | |
| stdenv.mkDerivation { | |
| name = "btrfs-luks-fs.img"; | |
| nativeBuildInputs = [ | |
| btrfs-progs | |
| libfaketime | |
| util-linux | |
| cryptsetup | |
| qemu-utils | |
| ]; | |
| buildCommand = '' | |
| set -x | |
| ( | |
| mkdir -p ./files | |
| ${populateImageCommands} | |
| ) | |
| mkdir -p ./rootImage/nix/store | |
| xargs -I % cp -a --reflink=auto % -t ./rootImage/nix/store/ < ${sdClosureInfo}/store-paths | |
| ( | |
| GLOBIGNORE=".:.." | |
| shopt -u dotglob | |
| for f in ./files/*; do | |
| cp -a --reflink=auto -t ./rootImage/ "$f" | |
| done | |
| ) | |
| cp ${sdClosureInfo}/registration ./rootImage/nix-path-registration | |
| needed=$(du -sb ./rootImage | awk '{print $1}') | |
| # I had troubles resizing image created with btrfs --shrink. It should work in theory, but with smaller images and | |
| # less built-in services | |
| size=$(( needed * 120 / 100 + 512 * 1024 * 1024 )) | |
| truncate -s $size unencrypted.img | |
| unshare --map-root-user faketime -f "1970-01-01 00:00:01" mkfs.btrfs -L ${volumeLabel} -U ${uuid} -r ./rootImage unencrypted.img | |
| if ! btrfs check $img; then | |
| echo "--- 'btrfs check' failed for BTRFS image ---" | |
| return 1 | |
| fi | |
| # Cryptsetup doesn't have commands for encryption of raw disk image, it is only possible to do without sandboxing. | |
| # Qemu-img on the other hand can convert disk image to LUKS1. | |
| # Should I add the necessary code to cryptsetup instead?.. | |
| # Or should I add luks2 support to qemu-img?.. | |
| qemu-img convert -f raw -O luks \ | |
| --object secret,id=sec0,data=${luksPassphrase} \ | |
| -o key-secret=sec0 \ | |
| unencrypted.img luks1.img | |
| # But because qemu-img produces LUKS1... How it can be converted to LUKS2? Manually, I hate this code too. | |
| # Extract headers and master key from luks1, to reconstruct luks2 later. | |
| LUKS1_DUMP=$(cryptsetup luksDump luks1.img) | |
| L1_OFF=$(echo "$LUKS1_DUMP" | awk '/Payload offset/{print $3}') | |
| MK_BITS=$(echo "$LUKS1_DUMP" | awk '/MK bits/{print $3}') | |
| CIPHER_NAME=$(echo "$LUKS1_DUMP" | awk '/Cipher name/{print $3}') | |
| CIPHER_MODE=$(echo "$LUKS1_DUMP" | awk '/Cipher mode/{print $3}') | |
| ENC_SIZE=$(( $(stat -c %s luks1.img) - L1_OFF * 512 )) | |
| echo -n "${luksPassphrase}" | cryptsetup luksDump \ | |
| --dump-volume-key --volume-key-file ./master-key \ | |
| --batch-mode --key-file - luks1.img | |
| # And with dumped LUKS1 parameters, we can create a LUKS2 image with the same encryption params, and copy the data. | |
| # Note that 512 is used for sector size, qemu-img does not support other sector sizes | |
| L2_OFF=32768 | |
| truncate -s $(( L2_OFF * 512 + ENC_SIZE )) $out | |
| echo -n "${luksPassphrase}" | cryptsetup luksFormat \ | |
| --type luks2 --batch-mode \ | |
| --volume-key-file ./master-key --key-size "$MK_BITS" \ | |
| --cipher "$CIPHER_NAME-$CIPHER_MODE" \ | |
| --sector-size 512 \ | |
| --label ${luksVolumeLabel} --uuid ${luksUuid} \ | |
| --offset $L2_OFF \ | |
| --key-file - $out | |
| dd if=luks1.img of=$out bs=512 skip=$L1_OFF seek=$L2_OFF conv=notrunc | |
| ''; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| # Only relevant configuration is included, you should still add the necessary modules so your system can | |
| # be built into an ext4 sdCard image, and then this configuration can be added on top | |
| sdImage.rootFilesystemCreator = ./btrfs-luks2.nix; | |
| boot.initrd.luks.devices.cryptroot = { | |
| device = "/dev/disk/by-uuid/44444444-4444-4444-9999-888888888888"; | |
| }; | |
| # sdImage module defines root as ext4 with extra options, mkForce is used to drop everything that is not relevant for btrfs | |
| fileSystems."/" = lib.mkForce { | |
| device = "/dev/mapper/cryptroot"; | |
| fsType = "btrfs"; | |
| }; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment