at 24.11-pre 6.3 kB view raw
1{ config, lib, pkgs, utils, ... }: 2 3let 4 5 bootFs = lib.filterAttrs (n: fs: (fs.fsType == "bcachefs") && (utils.fsNeededForBoot fs)) config.fileSystems; 6 7 commonFunctions = '' 8 prompt() { 9 local name="$1" 10 printf "enter passphrase for $name: " 11 } 12 13 tryUnlock() { 14 local name="$1" 15 local path="$2" 16 local success=false 17 local target 18 local uuid=$(echo -n $path | sed -e 's,UUID=\(.*\),\1,g') 19 20 printf "waiting for device to appear $path" 21 for try in $(seq 10); do 22 if [ -e $path ]; then 23 target=$(readlink -f $path) 24 success=true 25 break 26 else 27 target=$(blkid --uuid $uuid) 28 if [ $? == 0 ]; then 29 success=true 30 break 31 fi 32 fi 33 echo -n "." 34 sleep 1 35 done 36 printf "\n" 37 if [ $success == true ]; then 38 path=$target 39 fi 40 41 if bcachefs unlock -c $path > /dev/null 2> /dev/null; then # test for encryption 42 prompt $name 43 until bcachefs unlock $path 2> /dev/null; do # repeat until successfully unlocked 44 printf "unlocking failed!\n" 45 prompt $name 46 done 47 printf "unlocking successful.\n" 48 else 49 echo "Cannot unlock device $uuid with path $path" >&2 50 fi 51 } 52 ''; 53 54 # we need only unlock one device manually, and cannot pass multiple at once 55 # remove this adaptation when bcachefs implements mounting by filesystem uuid 56 # also, implement automatic waiting for the constituent devices when that happens 57 # bcachefs does not support mounting devices with colons in the path, ergo we don't (see #49671) 58 firstDevice = fs: lib.head (lib.splitString ":" fs.device); 59 60 useClevis = fs: config.boot.initrd.clevis.enable && (lib.hasAttr (firstDevice fs) config.boot.initrd.clevis.devices); 61 62 openCommand = name: fs: if useClevis fs then '' 63 if clevis decrypt < /etc/clevis/${firstDevice fs}.jwe | bcachefs unlock ${firstDevice fs} 64 then 65 printf "unlocked ${name} using clevis\n" 66 else 67 printf "falling back to interactive unlocking...\n" 68 tryUnlock ${name} ${firstDevice fs} 69 fi 70 '' else '' 71 tryUnlock ${name} ${firstDevice fs} 72 ''; 73 74 mkUnits = prefix: name: fs: let 75 mountUnit = "${utils.escapeSystemdPath (prefix + (lib.removeSuffix "/" fs.mountPoint))}.mount"; 76 device = firstDevice fs; 77 deviceUnit = "${utils.escapeSystemdPath device}.device"; 78 in { 79 name = "unlock-bcachefs-${utils.escapeSystemdPath fs.mountPoint}"; 80 value = { 81 description = "Unlock bcachefs for ${fs.mountPoint}"; 82 requiredBy = [ mountUnit ]; 83 after = [ deviceUnit ]; 84 before = [ mountUnit "shutdown.target" ]; 85 bindsTo = [ deviceUnit ]; 86 conflicts = [ "shutdown.target" ]; 87 unitConfig.DefaultDependencies = false; 88 serviceConfig = { 89 Type = "oneshot"; 90 ExecCondition = "${pkgs.bcachefs-tools}/bin/bcachefs unlock -c \"${device}\""; 91 Restart = "on-failure"; 92 RestartMode = "direct"; 93 # Ideally, this service would lock the key on stop. 94 # As is, RemainAfterExit doesn't accomplish anything. 95 RemainAfterExit = true; 96 }; 97 script = let 98 unlock = ''${pkgs.bcachefs-tools}/bin/bcachefs unlock "${device}"''; 99 unlockInteractively = ''${config.boot.initrd.systemd.package}/bin/systemd-ask-password --timeout=0 "enter passphrase for ${name}" | exec ${unlock}''; 100 in if useClevis fs then '' 101 if ${config.boot.initrd.clevis.package}/bin/clevis decrypt < "/etc/clevis/${device}.jwe" | ${unlock} 102 then 103 printf "unlocked ${name} using clevis\n" 104 else 105 printf "falling back to interactive unlocking...\n" 106 ${unlockInteractively} 107 fi 108 '' else '' 109 ${unlockInteractively} 110 ''; 111 }; 112 }; 113 114 assertions = [ 115 { 116 assertion = let 117 kernel = config.boot.kernelPackages.kernel; 118 in ( 119 kernel.kernelAtLeast "6.7" || ( 120 lib.elem (kernel.structuredExtraConfig.BCACHEFS_FS or null) [ 121 lib.kernel.module 122 lib.kernel.yes 123 (lib.kernel.option lib.kernel.yes) 124 ] 125 ) 126 ); 127 128 message = "Linux 6.7-rc1 at minimum or a custom linux kernel with bcachefs support is required"; 129 } 130 ]; 131in 132 133{ 134 config = lib.mkIf (config.boot.supportedFilesystems.bcachefs or false) (lib.mkMerge [ 135 { 136 inherit assertions; 137 # needed for systemd-remount-fs 138 system.fsPackages = [ pkgs.bcachefs-tools ]; 139 # FIXME: Remove this line when the LTS (default) kernel is at least version 6.7 140 boot.kernelPackages = lib.mkDefault pkgs.linuxPackages_latest; 141 services.udev.packages = [ pkgs.bcachefs-tools ]; 142 143 systemd = { 144 packages = [ pkgs.bcachefs-tools ]; 145 services = lib.mapAttrs' (mkUnits "") (lib.filterAttrs (n: fs: (fs.fsType == "bcachefs") && (!utils.fsNeededForBoot fs)) config.fileSystems); 146 }; 147 } 148 149 (lib.mkIf ((config.boot.initrd.supportedFilesystems.bcachefs or false) || (bootFs != {})) { 150 inherit assertions; 151 # chacha20 and poly1305 are required only for decryption attempts 152 boot.initrd.availableKernelModules = [ "bcachefs" "sha256" "chacha20" "poly1305" ]; 153 boot.initrd.systemd.extraBin = { 154 # do we need this? boot/systemd.nix:566 & boot/systemd/initrd.nix:357 155 "bcachefs" = "${pkgs.bcachefs-tools}/bin/bcachefs"; 156 "mount.bcachefs" = "${pkgs.bcachefs-tools}/bin/mount.bcachefs"; 157 }; 158 boot.initrd.extraUtilsCommands = lib.mkIf (!config.boot.initrd.systemd.enable) '' 159 copy_bin_and_libs ${pkgs.bcachefs-tools}/bin/bcachefs 160 copy_bin_and_libs ${pkgs.bcachefs-tools}/bin/mount.bcachefs 161 ''; 162 boot.initrd.extraUtilsCommandsTest = lib.mkIf (!config.boot.initrd.systemd.enable) '' 163 $out/bin/bcachefs version 164 ''; 165 166 boot.initrd.postDeviceCommands = lib.mkIf (!config.boot.initrd.systemd.enable) (commonFunctions + lib.concatStrings (lib.mapAttrsToList openCommand bootFs)); 167 168 boot.initrd.systemd.services = lib.mapAttrs' (mkUnits "/sysroot") bootFs; 169 }) 170 ]); 171}