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