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 success=true
24 break
25 else
26 target=$(blkid --uuid $uuid)
27 if [ $? == 0 ]; then
28 success=true
29 break
30 fi
31 fi
32 echo -n "."
33 sleep 1
34 done
35 printf "\n"
36 if [ $success == true ]; then
37 path=$target
38 fi
39
40 if bcachefs unlock -c $path > /dev/null 2> /dev/null; then # test for encryption
41 prompt $name
42 until bcachefs unlock $path 2> /dev/null; do # repeat until successfully unlocked
43 printf "unlocking failed!\n"
44 prompt $name
45 done
46 printf "unlocking successful.\n"
47 else
48 echo "Cannot unlock device $uuid with path $path" >&2
49 fi
50 }
51 '';
52
53 # we need only unlock one device manually, and cannot pass multiple at once
54 # remove this adaptation when bcachefs implements mounting by filesystem uuid
55 # also, implement automatic waiting for the constituent devices when that happens
56 # bcachefs does not support mounting devices with colons in the path, ergo we don't (see #49671)
57 firstDevice = fs: lib.head (lib.splitString ":" fs.device);
58
59 openCommand = name: fs: ''
60 tryUnlock ${name} ${firstDevice fs}
61 '';
62
63 mkUnits = prefix: name: fs: let
64 mountUnit = "${utils.escapeSystemdPath (prefix + (lib.removeSuffix "/" fs.mountPoint))}.mount";
65 device = firstDevice fs;
66 deviceUnit = "${utils.escapeSystemdPath device}.device";
67 in {
68 name = "unlock-bcachefs-${utils.escapeSystemdPath fs.mountPoint}";
69 value = {
70 description = "Unlock bcachefs for ${fs.mountPoint}";
71 requiredBy = [ mountUnit ];
72 before = [ mountUnit ];
73 bindsTo = [ deviceUnit ];
74 after = [ deviceUnit ];
75 unitConfig.DefaultDependencies = false;
76 serviceConfig = {
77 Type = "oneshot";
78 ExecCondition = "${pkgs.bcachefs-tools}/bin/bcachefs unlock -c \"${device}\"";
79 Restart = "on-failure";
80 RestartMode = "direct";
81 # Ideally, this service would lock the key on stop.
82 # As is, RemainAfterExit doesn't accomplish anything.
83 RemainAfterExit = true;
84 };
85 script = ''
86 ${config.boot.initrd.systemd.package}/bin/systemd-ask-password --timeout=0 "enter passphrase for ${name}" | exec ${pkgs.bcachefs-tools}/bin/bcachefs unlock "${device}"
87 '';
88 };
89 };
90
91 assertions = [
92 {
93 assertion = let
94 kernel = config.boot.kernelPackages.kernel;
95 in (
96 kernel.kernelAtLeast "6.7" || (
97 lib.elem (kernel.structuredExtraConfig.BCACHEFS_FS or null) [
98 lib.kernel.module
99 lib.kernel.yes
100 lib.kernel.option.yes
101 ]
102 )
103 );
104
105 message = "Linux 6.7-rc1 at minimum or a custom linux kernel with bcachefs support is required";
106 }
107 ];
108in
109
110{
111 config = lib.mkIf (lib.elem "bcachefs" config.boot.supportedFilesystems) (lib.mkMerge [
112 {
113 inherit assertions;
114 # needed for systemd-remount-fs
115 system.fsPackages = [ pkgs.bcachefs-tools ];
116
117 # FIXME: Replace this with `linuxPackages_testing` after NixOS 23.11 is released
118 # FIXME: Replace this with `linuxPackages_latest` when 6.7 is released, remove this line when the LTS version is at least 6.7
119 boot.kernelPackages = lib.mkDefault (
120 # FIXME: Remove warning after NixOS 23.11 is released
121 lib.warn "Please upgrade to Linux 6.7-rc1 or later: 'linuxPackages_testing_bcachefs' is deprecated. Use 'boot.kernelPackages = pkgs.linuxPackages_testing;' to silence this warning"
122 pkgs.linuxPackages_testing_bcachefs
123 );
124
125 systemd.services = lib.mapAttrs' (mkUnits "") (lib.filterAttrs (n: fs: (fs.fsType == "bcachefs") && (!utils.fsNeededForBoot fs)) config.fileSystems);
126 }
127
128 (lib.mkIf ((lib.elem "bcachefs" config.boot.initrd.supportedFilesystems) || (bootFs != {})) {
129 inherit assertions;
130 # chacha20 and poly1305 are required only for decryption attempts
131 boot.initrd.availableKernelModules = [ "bcachefs" "sha256" "chacha20" "poly1305" ];
132 boot.initrd.systemd.extraBin = {
133 # do we need this? boot/systemd.nix:566 & boot/systemd/initrd.nix:357
134 "bcachefs" = "${pkgs.bcachefs-tools}/bin/bcachefs";
135 "mount.bcachefs" = "${pkgs.bcachefs-tools}/bin/mount.bcachefs";
136 };
137 boot.initrd.extraUtilsCommands = lib.mkIf (!config.boot.initrd.systemd.enable) ''
138 copy_bin_and_libs ${pkgs.bcachefs-tools}/bin/bcachefs
139 copy_bin_and_libs ${pkgs.bcachefs-tools}/bin/mount.bcachefs
140 '';
141 boot.initrd.extraUtilsCommandsTest = lib.mkIf (!config.boot.initrd.systemd.enable) ''
142 $out/bin/bcachefs version
143 '';
144
145 boot.initrd.postDeviceCommands = lib.mkIf (!config.boot.initrd.systemd.enable) (commonFunctions + lib.concatStrings (lib.mapAttrsToList openCommand bootFs));
146
147 boot.initrd.systemd.services = lib.mapAttrs' (mkUnits "/sysroot") bootFs;
148 })
149 ]);
150}