2018-11-03 02:07:36 +00:00
|
|
|
{
|
|
|
|
config,
|
|
|
|
lib,
|
|
|
|
pkgs,
|
|
|
|
utils,
|
|
|
|
...
|
|
|
|
}:
|
2017-08-31 05:24:48 -05:00
|
|
|
|
|
|
|
let
|
2025-06-10 21:12:58 +05:30
|
|
|
cfgScrub = config.services.bcachefs.autoScrub;
|
2017-08-31 05:24:48 -05:00
|
|
|
|
2023-11-18 04:31:47 -06:00
|
|
|
bootFs = lib.filterAttrs (
|
|
|
|
n: fs: (fs.fsType == "bcachefs") && (utils.fsNeededForBoot fs)
|
|
|
|
) config.fileSystems;
|
2018-11-03 02:07:36 +00:00
|
|
|
|
|
|
|
commonFunctions = ''
|
|
|
|
prompt() {
|
|
|
|
local name="$1"
|
|
|
|
printf "enter passphrase for $name: "
|
|
|
|
}
|
2023-09-23 15:34:38 +12:00
|
|
|
|
2018-11-03 02:07:36 +00:00
|
|
|
tryUnlock() {
|
|
|
|
local name="$1"
|
|
|
|
local path="$2"
|
2023-09-23 15:34:38 +12:00
|
|
|
local success=false
|
|
|
|
local target
|
|
|
|
local uuid=$(echo -n $path | sed -e 's,UUID=\(.*\),\1,g')
|
|
|
|
|
|
|
|
printf "waiting for device to appear $path"
|
|
|
|
for try in $(seq 10); do
|
|
|
|
if [ -e $path ]; then
|
2023-11-17 16:14:05 +01:00
|
|
|
target=$(readlink -f $path)
|
2023-09-23 15:34:38 +12:00
|
|
|
success=true
|
|
|
|
break
|
|
|
|
else
|
|
|
|
target=$(blkid --uuid $uuid)
|
|
|
|
if [ $? == 0 ]; then
|
|
|
|
success=true
|
|
|
|
break
|
|
|
|
fi
|
|
|
|
fi
|
|
|
|
echo -n "."
|
|
|
|
sleep 1
|
|
|
|
done
|
|
|
|
printf "\n"
|
|
|
|
if [ $success == true ]; then
|
|
|
|
path=$target
|
|
|
|
fi
|
|
|
|
|
2018-11-03 02:07:36 +00:00
|
|
|
if bcachefs unlock -c $path > /dev/null 2> /dev/null; then # test for encryption
|
|
|
|
prompt $name
|
2023-05-19 22:11:38 -04:00
|
|
|
until bcachefs unlock $path 2> /dev/null; do # repeat until successfully unlocked
|
2018-11-03 02:07:36 +00:00
|
|
|
printf "unlocking failed!\n"
|
|
|
|
prompt $name
|
|
|
|
done
|
|
|
|
printf "unlocking successful.\n"
|
2023-09-23 15:34:38 +12:00
|
|
|
else
|
|
|
|
echo "Cannot unlock device $uuid with path $path" >&2
|
2018-11-03 02:07:36 +00:00
|
|
|
fi
|
|
|
|
}
|
|
|
|
'';
|
|
|
|
|
2023-10-21 11:56:47 -04:00
|
|
|
# we need only unlock one device manually, and cannot pass multiple at once
|
|
|
|
# remove this adaptation when bcachefs implements mounting by filesystem uuid
|
|
|
|
# also, implement automatic waiting for the constituent devices when that happens
|
|
|
|
# bcachefs does not support mounting devices with colons in the path, ergo we don't (see #49671)
|
2023-11-18 04:31:47 -06:00
|
|
|
firstDevice = fs: lib.head (lib.splitString ":" fs.device);
|
2023-10-21 11:56:47 -04:00
|
|
|
|
2024-03-13 17:20:10 -06:00
|
|
|
useClevis =
|
|
|
|
fs:
|
|
|
|
config.boot.initrd.clevis.enable
|
|
|
|
&& (lib.hasAttr (firstDevice fs) config.boot.initrd.clevis.devices);
|
|
|
|
|
|
|
|
openCommand =
|
2023-10-21 11:56:47 -04:00
|
|
|
name: fs:
|
|
|
|
if useClevis fs then
|
2024-03-13 17:20:10 -06:00
|
|
|
''
|
|
|
|
if clevis decrypt < /etc/clevis/${firstDevice fs}.jwe | bcachefs unlock ${firstDevice fs}
|
|
|
|
then
|
|
|
|
printf "unlocked ${name} using clevis\n"
|
|
|
|
else
|
|
|
|
printf "falling back to interactive unlocking...\n"
|
|
|
|
tryUnlock ${name} ${firstDevice fs}
|
|
|
|
fi
|
|
|
|
''
|
|
|
|
else
|
|
|
|
''
|
|
|
|
tryUnlock ${name} ${firstDevice fs}
|
2018-11-03 02:07:36 +00:00
|
|
|
'';
|
2025-04-01 20:10:43 +02:00
|
|
|
|
2023-10-21 11:56:47 -04:00
|
|
|
mkUnits =
|
2024-03-13 17:20:10 -06:00
|
|
|
prefix: name: fs:
|
2025-04-01 20:10:43 +02:00
|
|
|
let
|
2023-10-21 11:56:47 -04:00
|
|
|
mountUnit = "${utils.escapeSystemdPath (prefix + (lib.removeSuffix "/" fs.mountPoint))}.mount";
|
|
|
|
device = firstDevice fs;
|
|
|
|
deviceUnit = "${utils.escapeSystemdPath device}.device";
|
2025-04-01 20:10:43 +02:00
|
|
|
in
|
|
|
|
{
|
2023-10-21 11:56:47 -04:00
|
|
|
name = "unlock-bcachefs-${utils.escapeSystemdPath fs.mountPoint}";
|
|
|
|
value = {
|
|
|
|
description = "Unlock bcachefs for ${fs.mountPoint}";
|
|
|
|
requiredBy = [ mountUnit ];
|
|
|
|
after = [ deviceUnit ];
|
2023-11-30 15:34:31 -08:00
|
|
|
before = [
|
|
|
|
mountUnit
|
|
|
|
"shutdown.target"
|
2025-04-01 20:10:43 +02:00
|
|
|
];
|
2023-11-30 15:34:31 -08:00
|
|
|
bindsTo = [ deviceUnit ];
|
|
|
|
conflicts = [ "shutdown.target" ];
|
2023-10-21 11:56:47 -04:00
|
|
|
unitConfig.DefaultDependencies = false;
|
|
|
|
serviceConfig = {
|
|
|
|
Type = "oneshot";
|
|
|
|
ExecCondition = "${pkgs.bcachefs-tools}/bin/bcachefs unlock -c \"${device}\"";
|
|
|
|
Restart = "on-failure";
|
|
|
|
RestartMode = "direct";
|
|
|
|
# Ideally, this service would lock the key on stop.
|
|
|
|
# As is, RemainAfterExit doesn't accomplish anything.
|
|
|
|
RemainAfterExit = true;
|
2025-04-01 20:10:43 +02:00
|
|
|
};
|
|
|
|
script =
|
|
|
|
let
|
2024-03-13 17:20:10 -06:00
|
|
|
unlock = ''${pkgs.bcachefs-tools}/bin/bcachefs unlock "${device}"'';
|
|
|
|
unlockInteractively = ''${config.boot.initrd.systemd.package}/bin/systemd-ask-password --timeout=0 "enter passphrase for ${name}" | exec ${unlock}'';
|
2025-04-01 20:10:43 +02:00
|
|
|
in
|
2024-03-13 17:20:10 -06:00
|
|
|
if useClevis fs then
|
2025-04-01 20:10:43 +02:00
|
|
|
''
|
2023-11-18 19:37:56 +00:00
|
|
|
if ${config.boot.initrd.clevis.package}/bin/clevis decrypt < "/etc/clevis/${device}.jwe" | ${unlock}
|
2025-04-01 20:10:43 +02:00
|
|
|
then
|
2024-03-13 17:20:10 -06:00
|
|
|
printf "unlocked ${name} using clevis\n"
|
2025-04-01 20:10:43 +02:00
|
|
|
else
|
2024-03-13 17:20:10 -06:00
|
|
|
printf "falling back to interactive unlocking...\n"
|
|
|
|
${unlockInteractively}
|
2025-04-01 20:10:43 +02:00
|
|
|
fi
|
|
|
|
''
|
|
|
|
else
|
|
|
|
''
|
2024-03-13 17:20:10 -06:00
|
|
|
${unlockInteractively}
|
2025-04-01 20:10:43 +02:00
|
|
|
'';
|
|
|
|
};
|
2023-10-21 11:56:47 -04:00
|
|
|
};
|
2017-08-31 05:24:48 -05:00
|
|
|
|
2023-11-18 07:55:19 -06:00
|
|
|
assertions = [
|
|
|
|
{
|
|
|
|
assertion =
|
|
|
|
let
|
|
|
|
kernel = config.boot.kernelPackages.kernel;
|
|
|
|
in
|
|
|
|
(
|
|
|
|
kernel.kernelAtLeast "6.7"
|
|
|
|
|| (lib.elem (kernel.structuredExtraConfig.BCACHEFS_FS or null) [
|
|
|
|
lib.kernel.module
|
|
|
|
lib.kernel.yes
|
2023-11-23 19:08:57 +13:00
|
|
|
(lib.kernel.option lib.kernel.yes)
|
2023-11-18 07:55:19 -06:00
|
|
|
])
|
|
|
|
);
|
|
|
|
|
|
|
|
message = "Linux 6.7-rc1 at minimum or a custom linux kernel with bcachefs support is required";
|
|
|
|
}
|
|
|
|
];
|
2017-08-31 05:24:48 -05:00
|
|
|
in
|
|
|
|
|
|
|
|
{
|
2025-06-10 21:12:58 +05:30
|
|
|
options.services.bcachefs.autoScrub = {
|
|
|
|
enable = lib.mkEnableOption "regular bcachefs scrub";
|
|
|
|
|
|
|
|
fileSystems = lib.mkOption {
|
|
|
|
type = lib.types.listOf lib.types.path;
|
|
|
|
example = [ "/" ];
|
|
|
|
description = ''
|
|
|
|
List of paths to bcachefs filesystems to regularly call {command}`bcachefs scrub` on.
|
|
|
|
Defaults to all mount points with bcachefs filesystems.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
interval = lib.mkOption {
|
|
|
|
default = "monthly";
|
|
|
|
type = lib.types.str;
|
|
|
|
example = "weekly";
|
|
|
|
description = ''
|
|
|
|
Systemd calendar expression for when to scrub bcachefs filesystems.
|
|
|
|
The recommended period is a month but could be less.
|
|
|
|
See
|
|
|
|
{manpage}`systemd.time(7)`
|
|
|
|
for more information on the syntax.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2024-02-18 23:35:17 +01:00
|
|
|
config = lib.mkIf (config.boot.supportedFilesystems.bcachefs or false) (
|
|
|
|
lib.mkMerge [
|
2018-11-03 02:07:36 +00:00
|
|
|
{
|
2023-11-18 07:55:19 -06:00
|
|
|
inherit assertions;
|
2023-09-23 15:34:38 +12:00
|
|
|
# needed for systemd-remount-fs
|
|
|
|
system.fsPackages = [ pkgs.bcachefs-tools ];
|
2024-01-09 07:50:44 -06:00
|
|
|
# FIXME: Remove this line when the LTS (default) kernel is at least version 6.7
|
2024-01-08 10:47:51 +03:00
|
|
|
boot.kernelPackages = lib.mkDefault pkgs.linuxPackages_latest;
|
2024-01-09 09:09:32 -06:00
|
|
|
services.udev.packages = [ pkgs.bcachefs-tools ];
|
2025-04-01 20:10:43 +02:00
|
|
|
|
2024-01-09 09:09:32 -06:00
|
|
|
systemd = {
|
|
|
|
packages = [ pkgs.bcachefs-tools ];
|
|
|
|
services = lib.mapAttrs' (mkUnits "") (
|
|
|
|
lib.filterAttrs (n: fs: (fs.fsType == "bcachefs") && (!utils.fsNeededForBoot fs)) config.fileSystems
|
|
|
|
);
|
|
|
|
};
|
2018-11-03 02:07:36 +00:00
|
|
|
}
|
2025-04-01 20:10:43 +02:00
|
|
|
|
2024-02-18 23:35:17 +01:00
|
|
|
(lib.mkIf ((config.boot.initrd.supportedFilesystems.bcachefs or false) || (bootFs != { })) {
|
2023-11-18 07:55:19 -06:00
|
|
|
inherit assertions;
|
2020-12-25 21:28:50 +01:00
|
|
|
# chacha20 and poly1305 are required only for decryption attempts
|
|
|
|
boot.initrd.availableKernelModules = [
|
|
|
|
"bcachefs"
|
|
|
|
"sha256"
|
|
|
|
"chacha20"
|
|
|
|
"poly1305"
|
|
|
|
];
|
2022-12-29 23:17:27 +01:00
|
|
|
boot.initrd.systemd.extraBin = {
|
2023-09-23 15:34:38 +12:00
|
|
|
# do we need this? boot/systemd.nix:566 & boot/systemd/initrd.nix:357
|
2022-12-29 23:17:27 +01:00
|
|
|
"bcachefs" = "${pkgs.bcachefs-tools}/bin/bcachefs";
|
2023-09-23 15:34:38 +12:00
|
|
|
"mount.bcachefs" = "${pkgs.bcachefs-tools}/bin/mount.bcachefs";
|
2022-12-29 23:17:27 +01:00
|
|
|
};
|
|
|
|
boot.initrd.extraUtilsCommands = lib.mkIf (!config.boot.initrd.systemd.enable) ''
|
2018-11-03 02:07:36 +00:00
|
|
|
copy_bin_and_libs ${pkgs.bcachefs-tools}/bin/bcachefs
|
2023-09-23 15:34:38 +12:00
|
|
|
copy_bin_and_libs ${pkgs.bcachefs-tools}/bin/mount.bcachefs
|
2018-11-03 02:07:36 +00:00
|
|
|
'';
|
2023-04-22 10:39:30 -04:00
|
|
|
boot.initrd.extraUtilsCommandsTest = lib.mkIf (!config.boot.initrd.systemd.enable) ''
|
2018-11-03 02:07:36 +00:00
|
|
|
$out/bin/bcachefs version
|
|
|
|
'';
|
2025-04-01 20:10:43 +02:00
|
|
|
|
2023-11-18 04:31:47 -06:00
|
|
|
boot.initrd.postDeviceCommands = lib.mkIf (!config.boot.initrd.systemd.enable) (
|
|
|
|
commonFunctions + lib.concatStrings (lib.mapAttrsToList openCommand bootFs)
|
|
|
|
);
|
2025-04-01 20:10:43 +02:00
|
|
|
|
2023-10-21 11:56:47 -04:00
|
|
|
boot.initrd.systemd.services = lib.mapAttrs' (mkUnits "/sysroot") bootFs;
|
2018-11-03 02:07:36 +00:00
|
|
|
})
|
2025-06-10 21:12:58 +05:30
|
|
|
|
|
|
|
(lib.mkIf (cfgScrub.enable) {
|
|
|
|
assertions = [
|
|
|
|
{
|
|
|
|
assertion = lib.versionAtLeast config.boot.kernelPackages.kernel.version "6.14";
|
|
|
|
message = "Bcachefs scrubbing is supported from kernel version 6.14 or later.";
|
|
|
|
}
|
|
|
|
{
|
|
|
|
assertion = cfgScrub.enable -> (cfgScrub.fileSystems != [ ]);
|
|
|
|
message = ''
|
|
|
|
If 'services.bcachefs.autoScrub' is enabled, you need to have at least one
|
|
|
|
bcachefs file system mounted via 'fileSystems' or specify a list manually
|
|
|
|
in 'services.bcachefs.autoScrub.fileSystems'.
|
|
|
|
'';
|
|
|
|
}
|
|
|
|
];
|
|
|
|
|
|
|
|
# This will remove duplicated units from either having a filesystem mounted multiple
|
|
|
|
# time, or additionally mounted subvolumes, as well as having a filesystem span
|
|
|
|
# multiple devices (provided the same device is used to mount said filesystem).
|
|
|
|
services.bcachefs.autoScrub.fileSystems =
|
|
|
|
let
|
|
|
|
isDeviceInList = list: device: builtins.filter (e: e.device == device) list != [ ];
|
|
|
|
|
|
|
|
uniqueDeviceList = lib.foldl' (
|
|
|
|
acc: e: if isDeviceInList acc e.device then acc else acc ++ [ e ]
|
|
|
|
) [ ];
|
|
|
|
in
|
|
|
|
lib.mkDefault (
|
|
|
|
map (e: e.mountPoint) (
|
|
|
|
uniqueDeviceList (
|
|
|
|
lib.mapAttrsToList (name: fs: {
|
|
|
|
mountPoint = fs.mountPoint;
|
|
|
|
device = fs.device;
|
|
|
|
}) (lib.filterAttrs (name: fs: fs.fsType == "bcachefs") config.fileSystems)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
);
|
|
|
|
|
|
|
|
systemd.timers =
|
|
|
|
let
|
|
|
|
scrubTimer =
|
|
|
|
fs:
|
|
|
|
let
|
|
|
|
fs' = utils.escapeSystemdPath fs;
|
|
|
|
in
|
|
|
|
lib.nameValuePair "bcachefs-scrub-${fs'}" {
|
|
|
|
description = "regular bcachefs scrub timer on ${fs}";
|
|
|
|
|
|
|
|
wantedBy = [ "timers.target" ];
|
|
|
|
timerConfig = {
|
|
|
|
OnCalendar = cfgScrub.interval;
|
|
|
|
AccuracySec = "1d";
|
|
|
|
Persistent = true;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
in
|
|
|
|
lib.listToAttrs (map scrubTimer cfgScrub.fileSystems);
|
|
|
|
|
|
|
|
systemd.services =
|
|
|
|
let
|
|
|
|
scrubService =
|
|
|
|
fs:
|
|
|
|
let
|
|
|
|
fs' = utils.escapeSystemdPath fs;
|
|
|
|
in
|
|
|
|
lib.nameValuePair "bcachefs-scrub-${fs'}" {
|
|
|
|
description = "bcachefs scrub on ${fs}";
|
|
|
|
# scrub prevents suspend2ram or proper shutdown
|
|
|
|
conflicts = [
|
|
|
|
"shutdown.target"
|
|
|
|
"sleep.target"
|
|
|
|
];
|
|
|
|
before = [
|
|
|
|
"shutdown.target"
|
|
|
|
"sleep.target"
|
|
|
|
];
|
|
|
|
|
|
|
|
script = "${lib.getExe pkgs.bcachefs-tools} data scrub ${fs}";
|
|
|
|
|
|
|
|
serviceConfig = {
|
|
|
|
Type = "oneshot";
|
|
|
|
Nice = 19;
|
|
|
|
IOSchedulingClass = "idle";
|
|
|
|
};
|
|
|
|
};
|
|
|
|
in
|
|
|
|
lib.listToAttrs (map scrubService cfgScrub.fileSystems);
|
|
|
|
})
|
2018-11-03 02:07:36 +00:00
|
|
|
]
|
|
|
|
);
|
2025-06-10 21:15:41 +05:30
|
|
|
|
|
|
|
meta = {
|
|
|
|
inherit (pkgs.bcachefs-tools.meta) maintainers;
|
|
|
|
};
|
2017-08-31 05:24:48 -05:00
|
|
|
}
|