Add bcachefs type with encryption and multi-disk support
This update introduces a bcachefs type with encryption support and advanced formatting options. It includes a new example (`examples/bcachefs-multi-disk.nix`) to demonstrate multi-disk setups and available options. Key changes: - Deterministic UUID generation. - Addressed limitations with multi-disk root setups due to bcachefs and systemd issues. - Provided a systemd-mount alternative for fileSystems configuration. - Added subvolume support and updated scripts for clarity and functionality. --------- Co-authored-by: Jonas Heinrich <onny@project-insanity.org> Co-authored-by: Jörg Thalheim <Mic92@users.noreply.github.com> Co-authored-by: Kyle Petryszak <6314611+ProjectInitiative@users.noreply.github.com> Update * Add examples * Improve descriptions Remove debugging Remove comment Use `unique` to dedup lists
This commit is contained in:

committed by
Jörg Thalheim

parent
c5140c6079
commit
ca27b88c88
@@ -1,15 +1,15 @@
|
|||||||
{
|
{
|
||||||
disko.devices = {
|
disko.devices = {
|
||||||
disk = {
|
disk = {
|
||||||
main = {
|
vdb = {
|
||||||
device = "/dev/disk/by-path/pci-0000:02:00.0-nvme-1";
|
device = "/dev/vdb";
|
||||||
type = "disk";
|
type = "disk";
|
||||||
content = {
|
content = {
|
||||||
type = "gpt";
|
type = "gpt";
|
||||||
partitions = {
|
partitions = {
|
||||||
ESP = {
|
vdb1 = {
|
||||||
end = "500M";
|
|
||||||
type = "EF00";
|
type = "EF00";
|
||||||
|
size = "100M";
|
||||||
content = {
|
content = {
|
||||||
type = "filesystem";
|
type = "filesystem";
|
||||||
format = "vfat";
|
format = "vfat";
|
||||||
@@ -17,18 +17,232 @@
|
|||||||
mountOptions = [ "umask=0077" ];
|
mountOptions = [ "umask=0077" ];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
root = {
|
|
||||||
name = "root";
|
vdb2 = {
|
||||||
end = "-0";
|
size = "100%";
|
||||||
content = {
|
content = {
|
||||||
type = "filesystem";
|
type = "bcachefs";
|
||||||
format = "bcachefs";
|
# This refers to a filesystem in the `bcachefs_filesystems` attrset below.
|
||||||
mountpoint = "/";
|
filesystem = "unmounted_subvolumes_in_multi";
|
||||||
|
label = "group_a.vdb2";
|
||||||
|
extraFormatArgs = [
|
||||||
|
"--discard"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
vdc = {
|
||||||
|
device = "/dev/vdc";
|
||||||
|
type = "disk";
|
||||||
|
content = {
|
||||||
|
type = "gpt";
|
||||||
|
partitions = {
|
||||||
|
vdc1 = {
|
||||||
|
size = "100%";
|
||||||
|
content = {
|
||||||
|
type = "bcachefs";
|
||||||
|
filesystem = "unmounted_subvolumes_in_multi";
|
||||||
|
label = "group_a.vdc1";
|
||||||
|
extraFormatArgs = [
|
||||||
|
"--discard"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
vdd = {
|
||||||
|
device = "/dev/vdd";
|
||||||
|
type = "disk";
|
||||||
|
content = {
|
||||||
|
type = "gpt";
|
||||||
|
partitions = {
|
||||||
|
vdd1 = {
|
||||||
|
size = "100%";
|
||||||
|
content = {
|
||||||
|
type = "bcachefs";
|
||||||
|
filesystem = "unmounted_subvolumes_in_multi";
|
||||||
|
label = "group_b.vdd1";
|
||||||
|
extraFormatArgs = [
|
||||||
|
"--force"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
vde = {
|
||||||
|
device = "/dev/vde";
|
||||||
|
type = "disk";
|
||||||
|
content = {
|
||||||
|
type = "gpt";
|
||||||
|
partitions = {
|
||||||
|
vde1 = {
|
||||||
|
size = "100%";
|
||||||
|
content = {
|
||||||
|
type = "bcachefs";
|
||||||
|
filesystem = "mounted_subvolumes_in_multi";
|
||||||
|
label = "group_a.vde1";
|
||||||
|
extraFormatArgs = [
|
||||||
|
"--discard"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
vdf = {
|
||||||
|
device = "/dev/vdf";
|
||||||
|
type = "disk";
|
||||||
|
content = {
|
||||||
|
type = "gpt";
|
||||||
|
partitions = {
|
||||||
|
vdf1 = {
|
||||||
|
size = "100%";
|
||||||
|
content = {
|
||||||
|
type = "bcachefs";
|
||||||
|
filesystem = "mounted_subvolumes_in_multi";
|
||||||
|
label = "group_a.vdf1";
|
||||||
|
extraFormatArgs = [
|
||||||
|
"--discard"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
vdg = {
|
||||||
|
device = "/dev/vdg";
|
||||||
|
type = "disk";
|
||||||
|
content = {
|
||||||
|
type = "gpt";
|
||||||
|
partitions = {
|
||||||
|
vdd1 = {
|
||||||
|
size = "100%";
|
||||||
|
content = {
|
||||||
|
type = "bcachefs";
|
||||||
|
filesystem = "mounted_subvolumes_in_multi";
|
||||||
|
label = "group_b.vdg1";
|
||||||
|
extraFormatArgs = [
|
||||||
|
"--force"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
vdh = {
|
||||||
|
device = "/dev/vdh";
|
||||||
|
type = "disk";
|
||||||
|
content = {
|
||||||
|
type = "gpt";
|
||||||
|
partitions = {
|
||||||
|
vdd1 = {
|
||||||
|
size = "100%";
|
||||||
|
content = {
|
||||||
|
type = "bcachefs";
|
||||||
|
filesystem = "no_reliance_on_external_subvolume";
|
||||||
|
label = "group_a.vdh1";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
vdi = {
|
||||||
|
device = "/dev/vdi";
|
||||||
|
type = "disk";
|
||||||
|
content = {
|
||||||
|
type = "gpt";
|
||||||
|
partitions = {
|
||||||
|
vdd1 = {
|
||||||
|
size = "100%";
|
||||||
|
content = {
|
||||||
|
type = "bcachefs";
|
||||||
|
filesystem = "relies_on_external_subvolume";
|
||||||
|
label = "group_a.vdi1";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
bcachefs_filesystems = {
|
||||||
|
# Example showing unmounted subvolumes in a multi-disk configuration.
|
||||||
|
unmounted_subvolumes_in_multi = {
|
||||||
|
type = "bcachefs_filesystem";
|
||||||
|
passwordFile = "/tmp/secret.key";
|
||||||
|
extraFormatArgs = [
|
||||||
|
"--compression=lz4"
|
||||||
|
"--background_compression=lz4"
|
||||||
|
];
|
||||||
|
mountOptions = [
|
||||||
|
"verbose"
|
||||||
|
];
|
||||||
|
mountpoint = "/";
|
||||||
|
subvolumes = {
|
||||||
|
"subvolumes/rootfs" = { };
|
||||||
|
"subvolumes/home" = { };
|
||||||
|
"subvolumes/home/user" = { };
|
||||||
|
"subvolumes/nix" = { };
|
||||||
|
"subvolumes/test" = { };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# # Example showing mounted subvolumes in a multi-disk configuration (not yet working).
|
||||||
|
# mounted_subvolumes_in_multi = {
|
||||||
|
# type = "bcachefs_filesystem";
|
||||||
|
# passwordFile = "/tmp/secret.key";
|
||||||
|
# extraFormatArgs = [
|
||||||
|
# "--compression=lz4"
|
||||||
|
# "--background_compression=lz4"
|
||||||
|
# ];
|
||||||
|
# mountOptions = [
|
||||||
|
# "verbose"
|
||||||
|
# ];
|
||||||
|
# subvolumes = {
|
||||||
|
# # Subvolume name is different from mountpoint
|
||||||
|
# "foo" = {
|
||||||
|
# mountpoint = "/bar";
|
||||||
|
# };
|
||||||
|
# # Subvolume name is the same as the mountpoint
|
||||||
|
# "home" = {
|
||||||
|
# mountpoint = "/home";
|
||||||
|
# };
|
||||||
|
# # Sub(sub)volume doesn't need a mountpoint as its parent is mounted
|
||||||
|
# "home/user" = {
|
||||||
|
# };
|
||||||
|
# # Parent is not mounted so the mountpoint must be set
|
||||||
|
# "nix" = {
|
||||||
|
# mountpoint = "/nix";
|
||||||
|
# };
|
||||||
|
# # This subvolume will be created but not mounted
|
||||||
|
# "test" = {
|
||||||
|
# };
|
||||||
|
# };
|
||||||
|
# };
|
||||||
|
|
||||||
|
# Example showing another bcachefs filesystem.
|
||||||
|
no_reliance_on_external_subvolume = {
|
||||||
|
type = "bcachefs_filesystem";
|
||||||
|
mountpoint = "/sometestdir";
|
||||||
|
};
|
||||||
|
|
||||||
|
# # Example showing another bcachefs filesystem that relies on a subvolume
|
||||||
|
# # in another filesystem being mounted (not yet working).
|
||||||
|
# relies_on_external_subvolume = {
|
||||||
|
# type = "bcachefs_filesystem";
|
||||||
|
# mountpoint = "/home/somedir/vdf1";
|
||||||
|
# };
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -44,6 +44,7 @@ let
|
|||||||
# option for valid contents of partitions (basically like devices, but without tables)
|
# option for valid contents of partitions (basically like devices, but without tables)
|
||||||
_partitionTypes = {
|
_partitionTypes = {
|
||||||
inherit (diskoLib.types)
|
inherit (diskoLib.types)
|
||||||
|
bcachefs
|
||||||
btrfs
|
btrfs
|
||||||
filesystem
|
filesystem
|
||||||
zfs
|
zfs
|
||||||
@@ -69,6 +70,7 @@ let
|
|||||||
# option for valid contents of devices
|
# option for valid contents of devices
|
||||||
_deviceTypes = {
|
_deviceTypes = {
|
||||||
inherit (diskoLib.types)
|
inherit (diskoLib.types)
|
||||||
|
bcachefs
|
||||||
table
|
table
|
||||||
gpt
|
gpt
|
||||||
btrfs
|
btrfs
|
||||||
@@ -611,6 +613,7 @@ let
|
|||||||
let
|
let
|
||||||
devices = {
|
devices = {
|
||||||
inherit (cfg.config)
|
inherit (cfg.config)
|
||||||
|
bcachefs_filesystems
|
||||||
disk
|
disk
|
||||||
mdadm
|
mdadm
|
||||||
zpool
|
zpool
|
||||||
@@ -621,6 +624,11 @@ let
|
|||||||
in
|
in
|
||||||
{
|
{
|
||||||
options = {
|
options = {
|
||||||
|
bcachefs_filesystems = lib.mkOption {
|
||||||
|
type = lib.types.attrsOf diskoLib.types.bcachefs_filesystem;
|
||||||
|
default = { };
|
||||||
|
description = "bcachefs filesystem";
|
||||||
|
};
|
||||||
disk = lib.mkOption {
|
disk = lib.mkOption {
|
||||||
type = lib.types.attrsOf diskoLib.types.disk;
|
type = lib.types.attrsOf diskoLib.types.disk;
|
||||||
default = { };
|
default = { };
|
||||||
@@ -687,6 +695,7 @@ let
|
|||||||
throw "No disks defined, did you forget to import your disko config?"
|
throw "No disks defined, did you forget to import your disko config?"
|
||||||
else
|
else
|
||||||
v;
|
v;
|
||||||
|
# @todo Do we need to add bcachefs-tools or not?
|
||||||
destroyDependencies = with pkgs; [
|
destroyDependencies = with pkgs; [
|
||||||
util-linux
|
util-linux
|
||||||
e2fsprogs
|
e2fsprogs
|
||||||
|
100
lib/types/bcachefs.nix
Normal file
100
lib/types/bcachefs.nix
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
{
|
||||||
|
config,
|
||||||
|
device,
|
||||||
|
diskoLib,
|
||||||
|
lib,
|
||||||
|
options,
|
||||||
|
parent,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
options = {
|
||||||
|
type = lib.mkOption {
|
||||||
|
type = lib.types.enum [ "bcachefs" ];
|
||||||
|
internal = true;
|
||||||
|
description = "Type.";
|
||||||
|
};
|
||||||
|
device = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = device;
|
||||||
|
description = "Device to use.";
|
||||||
|
example = "/dev/sda";
|
||||||
|
};
|
||||||
|
filesystem = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
description = "Name of the bcachefs filesystem this partition belongs to.";
|
||||||
|
example = "main_bcachefs_filesystem";
|
||||||
|
};
|
||||||
|
# These are passed as arguments to the device corresponding to this one in the invocation of the `bcachefs format` command
|
||||||
|
# in the bcachefs_filesystem type defined in bcachefs_filesystem.nix used to format the bcachefs filesystem that this device is a part of.
|
||||||
|
extraFormatArgs = lib.mkOption {
|
||||||
|
type = lib.types.listOf lib.types.str;
|
||||||
|
default = [ ];
|
||||||
|
description = "Extra arguments passed to the bcachefs format command.";
|
||||||
|
example = [ "--discard" ];
|
||||||
|
};
|
||||||
|
# This value is passed to the `--label` option for the device corresponding to this one in the invocation of the `bcachefs format` command
|
||||||
|
# in the bcachefs_filesystem type defined in bcachefs_filesystem.nix used to format the bcachefs filesystem that this device is a part of.
|
||||||
|
label = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "";
|
||||||
|
description = ''
|
||||||
|
Label to use for this device.
|
||||||
|
This value is passed as the `--label` argument to the `bcachefs format` command when formatting the device.
|
||||||
|
'';
|
||||||
|
example = "group_a.sda2";
|
||||||
|
};
|
||||||
|
_parent = lib.mkOption {
|
||||||
|
internal = true;
|
||||||
|
default = parent;
|
||||||
|
};
|
||||||
|
_meta = lib.mkOption {
|
||||||
|
internal = true;
|
||||||
|
readOnly = true;
|
||||||
|
type = lib.types.functionTo diskoLib.jsonType;
|
||||||
|
# Ensures that this file's `_create` will be ran for all member devices that are part of the filesystem being created,
|
||||||
|
# before the `_create` in bcachefs_filesystem.nix is ran.
|
||||||
|
default = dev: {
|
||||||
|
deviceDependencies.bcachefs_filesystems.${config.filesystem} = [ dev ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
_create = diskoLib.mkCreateOption {
|
||||||
|
inherit config options;
|
||||||
|
# The bcachefs_filesystem type defined in bcachefs_filesystem.nix will include this device when formatting and mounting the filesystem.
|
||||||
|
# The current file should not run the `bcachefs format` command. Instead, the`bcachefs format` command will be ran
|
||||||
|
# in the `_create` attribute in bcachefs_filesystem.nix, once it has collected and generated the arguments specifying the devices that should be part of the filesystem.
|
||||||
|
default = ''
|
||||||
|
# Write device arguments to temporary directory for bcachefs_filesystem.
|
||||||
|
{
|
||||||
|
printf '%s\n' '--label="${config.label}"';
|
||||||
|
${lib.concatMapStrings (args: ''printf '%s\n' '${args}';'') config.extraFormatArgs}
|
||||||
|
printf '%s\n' '${config.device}';
|
||||||
|
} >> "$disko_devices_dir/bcachefs-${lib.escapeShellArg config.filesystem}";
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
_mount = diskoLib.mkMountOption {
|
||||||
|
inherit config options;
|
||||||
|
# Empty, since mounting will be handled by the bcachefs_filesystem type defined in bcachefs_filesystem.nix.
|
||||||
|
default = { };
|
||||||
|
};
|
||||||
|
_unmount = diskoLib.mkUnmountOption {
|
||||||
|
inherit config options;
|
||||||
|
# Empty, since unmounting will be handled by the bcachefs_filesystem type defined in bcachefs_filesystem.nix.
|
||||||
|
default = { };
|
||||||
|
};
|
||||||
|
_config = lib.mkOption {
|
||||||
|
internal = true;
|
||||||
|
readOnly = true;
|
||||||
|
# Empty, since NixOS configuration will be handled by the bcachefs_filesystem type defined in bcachefs_filesystem.nix.
|
||||||
|
default = { };
|
||||||
|
description = "NixOS configuration.";
|
||||||
|
};
|
||||||
|
_pkgs = lib.mkOption {
|
||||||
|
internal = true;
|
||||||
|
readOnly = true;
|
||||||
|
type = lib.types.functionTo (lib.types.listOf lib.types.package);
|
||||||
|
default = pkgs: [ ];
|
||||||
|
description = "Packages.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
331
lib/types/bcachefs_filesystem.nix
Normal file
331
lib/types/bcachefs_filesystem.nix
Normal file
@@ -0,0 +1,331 @@
|
|||||||
|
{
|
||||||
|
config,
|
||||||
|
diskoLib,
|
||||||
|
lib,
|
||||||
|
options,
|
||||||
|
parent,
|
||||||
|
rootMountPoint,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
options = {
|
||||||
|
name = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = config._module.args.name;
|
||||||
|
description = "Name of the bcachefs filesystem.";
|
||||||
|
example = "main_bcachefs_filesystem";
|
||||||
|
};
|
||||||
|
type = lib.mkOption {
|
||||||
|
type = lib.types.enum [ "bcachefs_filesystem" ];
|
||||||
|
internal = true;
|
||||||
|
description = "Type.";
|
||||||
|
};
|
||||||
|
extraFormatArgs = lib.mkOption {
|
||||||
|
type = lib.types.listOf lib.types.str;
|
||||||
|
default = [ ];
|
||||||
|
description = "Extra arguments passed to the `bcachefs format` command.";
|
||||||
|
example = [
|
||||||
|
"--compression=lz4"
|
||||||
|
"--background_compression=lz4"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
mountOptions = lib.mkOption {
|
||||||
|
type = lib.types.listOf lib.types.str;
|
||||||
|
default = [ "X-mount.mkdir" ];
|
||||||
|
description = ''
|
||||||
|
Options to pass to mount.
|
||||||
|
The "X-mount.mkdir" option is always automatically added.
|
||||||
|
'';
|
||||||
|
example = [
|
||||||
|
"noatime"
|
||||||
|
"verbose"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
mountpoint = lib.mkOption {
|
||||||
|
type = lib.types.nullOr diskoLib.optionTypes.absolute-pathname;
|
||||||
|
default = null;
|
||||||
|
description = "Path to mount the bcachefs filesystem to.";
|
||||||
|
example = "/";
|
||||||
|
};
|
||||||
|
uuid = lib.mkOption {
|
||||||
|
type = lib.types.strMatching "[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{12}";
|
||||||
|
default =
|
||||||
|
let
|
||||||
|
# Generate a deterministic but random-looking UUID based on the filesystem name
|
||||||
|
# This avoids the need for impure access to nixpkgs at evaluation time
|
||||||
|
hash = builtins.hashString "sha256" "${config.name}";
|
||||||
|
hexChars = builtins.substring 0 32 hash;
|
||||||
|
p1 = builtins.substring 0 8 hexChars;
|
||||||
|
p2 = builtins.substring 8 4 hexChars;
|
||||||
|
p3 = builtins.substring 12 4 hexChars;
|
||||||
|
p4 = builtins.substring 16 4 hexChars;
|
||||||
|
p5 = builtins.substring 20 12 hexChars;
|
||||||
|
in
|
||||||
|
"${p1}-${p2}-${p3}-${p4}-${p5}";
|
||||||
|
defaultText = "generated deterministically based on filesystem name";
|
||||||
|
example = "809b3a2b-828a-4730-95e1-75b6343e415a";
|
||||||
|
description = ''
|
||||||
|
The UUID of the bcachefs filesystem.
|
||||||
|
If not provided, a deterministic UUID will be generated based on the filesystem name.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
passwordFile = lib.mkOption {
|
||||||
|
type = lib.types.nullOr diskoLib.optionTypes.absolute-pathname;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
Path to the file containing the password for encryption.
|
||||||
|
Setting this option will automatically cause the `--encrypted` option to be passed to `bcachefs format` and cause the filesystem to have encryption enabled.
|
||||||
|
'';
|
||||||
|
example = "/tmp/disk.key";
|
||||||
|
};
|
||||||
|
subvolumes = lib.mkOption {
|
||||||
|
type = lib.types.attrsOf (
|
||||||
|
lib.types.submodule (
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
|
options = {
|
||||||
|
name = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = config._module.args.name;
|
||||||
|
description = ''
|
||||||
|
Path of the subvolume within the filesystem.
|
||||||
|
Leading forward slashes are automatically removed.
|
||||||
|
'';
|
||||||
|
example = "subvolumes/home";
|
||||||
|
};
|
||||||
|
type = lib.mkOption {
|
||||||
|
type = lib.types.enum [ "bcachefs_subvolume" ];
|
||||||
|
default = "bcachefs_subvolume";
|
||||||
|
internal = true;
|
||||||
|
description = "Type.";
|
||||||
|
};
|
||||||
|
mountOptions = lib.mkOption {
|
||||||
|
type = lib.types.listOf lib.types.str;
|
||||||
|
default = lib.naturalSort [
|
||||||
|
"X-mount.mkdir"
|
||||||
|
"X-mount.subdir=${lib.removePrefix "/" config.name}"
|
||||||
|
];
|
||||||
|
description = ''
|
||||||
|
Options to pass to mount.
|
||||||
|
The "X-mount.mkdir" and "X-mount.subdir" options are always automatically added.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
mountpoint = lib.mkOption {
|
||||||
|
type = lib.types.nullOr diskoLib.optionTypes.absolute-pathname;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
Path to mount the subvolume to.
|
||||||
|
DO NOT USE. Currently not working.
|
||||||
|
'';
|
||||||
|
example = "/";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
default = { };
|
||||||
|
description = "List of subvolumes to define.";
|
||||||
|
example = {
|
||||||
|
"subvolumes/home" = { };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
_parent = lib.mkOption {
|
||||||
|
internal = true;
|
||||||
|
default = parent;
|
||||||
|
};
|
||||||
|
_meta = lib.mkOption {
|
||||||
|
internal = true;
|
||||||
|
readOnly = true;
|
||||||
|
type = lib.types.functionTo diskoLib.jsonType;
|
||||||
|
default = dev: { };
|
||||||
|
description = "Metadata";
|
||||||
|
};
|
||||||
|
_create = diskoLib.mkCreateOption {
|
||||||
|
inherit config options;
|
||||||
|
# This sets a string variable containing arguments to be passed to the `bcachefs format` command.
|
||||||
|
# This string will consist of `--label` and other arguments that correspond to the values of the `label` and `extraFormatArgs` attributes, respectively,
|
||||||
|
# from each of the bcachefs devices in this filesystem specified in the configuration.
|
||||||
|
# Then, it sets the `default` attribute to a string containing shell commands that calls the `bcachefs format` command, passing in the arguments generated, as well as a `--uuid` value.
|
||||||
|
default = ''
|
||||||
|
if ! test -s "$disko_devices_dir/bcachefs-${config.name}"; then
|
||||||
|
printf "\033[31mERROR:\033[0m No devices found for bcachefs filesystem \"${config.name}\"!\nDid you forget to add some or misspell the filesystem name?\n" >&2;
|
||||||
|
exit 1;
|
||||||
|
fi;
|
||||||
|
|
||||||
|
# Create the filesystem.
|
||||||
|
(
|
||||||
|
# Empty out $@.
|
||||||
|
set --;
|
||||||
|
# Collect devices and arguments to $@.
|
||||||
|
while IFS= read -r line; do
|
||||||
|
# Append current line as a new positional parameter
|
||||||
|
set -- "$@" "$line";
|
||||||
|
done < "$disko_devices_dir/bcachefs-${config.name}";
|
||||||
|
|
||||||
|
# Format the filesystem with all devices and arguments.
|
||||||
|
if ! blkid -o export "$(blkid -lU ${config.uuid})" | grep -q 'TYPE=bcachefs' >&2 2>&1; then
|
||||||
|
bcachefs format \
|
||||||
|
"$@" \
|
||||||
|
--uuid="${config.uuid}" \
|
||||||
|
${lib.concatStringsSep " \\\n" config.extraFormatArgs} \
|
||||||
|
${
|
||||||
|
lib.optionalString (config.passwordFile != null) ''--encrypted < "${config.passwordFile}"''
|
||||||
|
};
|
||||||
|
fi;
|
||||||
|
);
|
||||||
|
|
||||||
|
# Mount the bcachefs filesystem onto a temporary directory,
|
||||||
|
# then, create the subvolumes from inside of that directory.
|
||||||
|
${lib.optionalString (config.subvolumes != { }) ''
|
||||||
|
if blkid -o export "$(blkid -lU ${config.uuid})" | grep -q 'TYPE=bcachefs' >&2 2>&1; then
|
||||||
|
${lib.concatMapStrings (subvolume: ''
|
||||||
|
(
|
||||||
|
TEMPDIR="$(mktemp -d)";
|
||||||
|
MNTPOINT="$(mktemp -d)";
|
||||||
|
${lib.optionalString (
|
||||||
|
config.passwordFile != null
|
||||||
|
) ''bcachefs unlock -k session "/dev/disk/by-uuid/${config.uuid}" < "${config.passwordFile}";''}
|
||||||
|
bcachefs mount \
|
||||||
|
-o "${lib.concatStringsSep "," (lib.unique ([ "X-mount.mkdir" ] ++ config.mountOptions))}" \
|
||||||
|
UUID="${config.uuid}" \
|
||||||
|
"$MNTPOINT";
|
||||||
|
trap 'umount "$MNTPOINT"; rm -rf "$MNTPOINT"; rm -rf "$TEMPDIR";' EXIT;
|
||||||
|
SUBVOL_ABS_PATH="$MNTPOINT/${subvolume.name}";
|
||||||
|
# Check if it's already a subvolume (using snapshot).
|
||||||
|
if ! bcachefs subvolume snapshot "$SUBVOL_ABS_PATH" "$TEMPDIR/" >&2 2>&1; then
|
||||||
|
# It's not a subvolume, now check if it's a directory.
|
||||||
|
if ! test -d "$SUBVOL_ABS_PATH"; then
|
||||||
|
# It's not a subvolume AND not a directory, so create it.
|
||||||
|
mkdir -p -- "$(dirname -- "$SUBVOL_ABS_PATH")";
|
||||||
|
bcachefs subvolume create "$SUBVOL_ABS_PATH";
|
||||||
|
fi
|
||||||
|
fi;
|
||||||
|
)
|
||||||
|
'') (lib.attrValues config.subvolumes)}
|
||||||
|
fi;
|
||||||
|
''}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
_mount = diskoLib.mkMountOption {
|
||||||
|
inherit config options;
|
||||||
|
default =
|
||||||
|
let
|
||||||
|
subvolumeMounts = diskoLib.deepMergeMap (
|
||||||
|
subvolume:
|
||||||
|
lib.optionalAttrs (subvolume.mountpoint != null) {
|
||||||
|
${subvolume.mountpoint} = ''
|
||||||
|
if ! findmnt "${rootMountPoint}${subvolume.mountpoint}" >&2 2>&1; then
|
||||||
|
# @todo Figure out why the "X-mount.mkdir" option here doesn't seem to work,
|
||||||
|
# necessitating running `mkdir` here.
|
||||||
|
mkdir -p "${rootMountPoint}${subvolume.mountpoint}";
|
||||||
|
${lib.optionalString (
|
||||||
|
config.passwordFile != null
|
||||||
|
) ''bcachefs unlock -k session "/dev/disk/by-uuid/${config.uuid}" < "${config.passwordFile}";''}
|
||||||
|
bcachefs mount \
|
||||||
|
-o "${
|
||||||
|
lib.concatStringsSep "," (
|
||||||
|
lib.unique (
|
||||||
|
[
|
||||||
|
"X-mount.mkdir"
|
||||||
|
"X-mount.subdir=${lib.removePrefix "/" subvolume.name}"
|
||||||
|
]
|
||||||
|
++ subvolume.mountOptions
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}" \
|
||||||
|
UUID="${config.uuid}" \
|
||||||
|
"${rootMountPoint}${subvolume.mountpoint}";
|
||||||
|
fi;
|
||||||
|
'';
|
||||||
|
}
|
||||||
|
) (lib.attrValues config.subvolumes);
|
||||||
|
in
|
||||||
|
{
|
||||||
|
fs =
|
||||||
|
subvolumeMounts
|
||||||
|
// lib.optionalAttrs (config.mountpoint != null) {
|
||||||
|
${config.mountpoint} = ''
|
||||||
|
if ! findmnt "${rootMountPoint}${config.mountpoint}" >&2 2>&1; then
|
||||||
|
# @todo Figure out why the "X-mount.mkdir" option here doesn't seem to work,
|
||||||
|
# necessitating running `mkdir` here.
|
||||||
|
mkdir -p "${rootMountPoint}${config.mountpoint}";
|
||||||
|
${lib.optionalString (
|
||||||
|
config.passwordFile != null
|
||||||
|
) ''bcachefs unlock -k session "/dev/disk/by-uuid/${config.uuid}" < "${config.passwordFile}";''}
|
||||||
|
bcachefs mount \
|
||||||
|
-o "${lib.concatStringsSep "," (lib.unique ([ "X-mount.mkdir" ] ++ config.mountOptions))}" \
|
||||||
|
UUID="${config.uuid}" \
|
||||||
|
"${rootMountPoint}${config.mountpoint}";
|
||||||
|
fi;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
_unmount = diskoLib.mkUnmountOption {
|
||||||
|
inherit config options;
|
||||||
|
default =
|
||||||
|
let
|
||||||
|
subvolumeMounts = lib.concatMapAttrs (
|
||||||
|
_: subvolume:
|
||||||
|
lib.optionalAttrs (subvolume.mountpoint != null) {
|
||||||
|
${subvolume.mountpoint} = ''
|
||||||
|
if findmnt "UUID=${config.uuid}" "${rootMountPoint}${subvolume.mountpoint}" >&2 2>&1; then
|
||||||
|
umount "${rootMountPoint}${subvolume.mountpoint}";
|
||||||
|
fi;
|
||||||
|
'';
|
||||||
|
}
|
||||||
|
) config.subvolumes;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
fs =
|
||||||
|
subvolumeMounts
|
||||||
|
// lib.optionalAttrs (config.mountpoint != null) {
|
||||||
|
${config.mountpoint} = ''
|
||||||
|
if findmnt "UUID=${config.uuid}" "${rootMountPoint}${config.mountpoint}" >&2 2>&1; then
|
||||||
|
umount "${rootMountPoint}${config.mountpoint}";
|
||||||
|
fi;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
_config = lib.mkOption {
|
||||||
|
internal = true;
|
||||||
|
readOnly = true;
|
||||||
|
# @todo Check that this implementation is correct:
|
||||||
|
default =
|
||||||
|
(lib.optional (config.mountpoint != null) {
|
||||||
|
fileSystems.${config.mountpoint} = {
|
||||||
|
device = "UUID=${config.uuid}";
|
||||||
|
fsType = "bcachefs";
|
||||||
|
options = lib.unique ([ "X-mount.mkdir" ] ++ config.mountOptions);
|
||||||
|
neededForBoot = true;
|
||||||
|
};
|
||||||
|
})
|
||||||
|
++ (map (subvolume: {
|
||||||
|
fileSystems.${subvolume.mountpoint} = {
|
||||||
|
device = "UUID=${config.uuid}";
|
||||||
|
fsType = "bcachefs";
|
||||||
|
options = lib.unique (
|
||||||
|
[
|
||||||
|
"X-mount.mkdir"
|
||||||
|
"X-mount.subdir=${lib.removePrefix "/" subvolume.name}"
|
||||||
|
]
|
||||||
|
++ subvolume.mountOptions
|
||||||
|
);
|
||||||
|
neededForBoot = true;
|
||||||
|
};
|
||||||
|
}) (lib.filter (subvolume: subvolume.mountpoint != null) (lib.attrValues config.subvolumes)));
|
||||||
|
description = "NixOS configuration.";
|
||||||
|
};
|
||||||
|
_pkgs = lib.mkOption {
|
||||||
|
internal = true;
|
||||||
|
readOnly = true;
|
||||||
|
type = lib.types.functionTo (lib.types.listOf lib.types.package);
|
||||||
|
default = pkgs: [
|
||||||
|
pkgs.bcachefs-tools
|
||||||
|
pkgs.util-linux
|
||||||
|
];
|
||||||
|
description = "Packages.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
@@ -55,6 +55,8 @@
|
|||||||
fs-type = lib.mkOption {
|
fs-type = lib.mkOption {
|
||||||
type = lib.types.nullOr (
|
type = lib.types.nullOr (
|
||||||
lib.types.enum [
|
lib.types.enum [
|
||||||
|
# @todo Check if this is needed
|
||||||
|
"bcachefs"
|
||||||
"btrfs"
|
"btrfs"
|
||||||
"ext2"
|
"ext2"
|
||||||
"ext3"
|
"ext3"
|
||||||
|
@@ -6,12 +6,40 @@ diskoLib.testLib.makeDiskoTest {
|
|||||||
inherit pkgs;
|
inherit pkgs;
|
||||||
name = "bcachefs";
|
name = "bcachefs";
|
||||||
disko-config = ../example/bcachefs.nix;
|
disko-config = ../example/bcachefs.nix;
|
||||||
|
enableOCR = true;
|
||||||
extraTestScript = ''
|
extraTestScript = ''
|
||||||
machine.succeed("mountpoint /");
|
machine.succeed("mountpoint /");
|
||||||
|
# @todo Verify all devices are part of the filesystem.
|
||||||
|
# @todo Check device labels and group assignments.
|
||||||
|
# Verify mount options were applied.
|
||||||
|
machine.succeed("mount | grep ' / ' | grep -q 'compression=lz4'");
|
||||||
|
machine.succeed("mount | grep ' / ' | grep -q 'background_compression=lz4'");
|
||||||
|
# @todo Verify mountpoint dependency order was respected.
|
||||||
|
# @todo Add tests for subvolumes.
|
||||||
|
# Print debug information.
|
||||||
machine.succeed("lsblk >&2");
|
machine.succeed("lsblk >&2");
|
||||||
|
machine.succeed("lsblk -f >&2");
|
||||||
|
machine.succeed("mount >&2");
|
||||||
|
'';
|
||||||
|
# extraSystemConfig = { pkgs, ... }: {
|
||||||
|
# # @todo Do we need to add any attributes here?
|
||||||
|
# boot = {
|
||||||
|
# supportedFilesystems = [ "bcachefs" ];
|
||||||
|
# initrd = {
|
||||||
|
# supportedFilesystems = [ "bcachefs" ];
|
||||||
|
# # systemd.enable = false;
|
||||||
|
# };
|
||||||
|
# };
|
||||||
|
# environment.systemPackages = [
|
||||||
|
# pkgs.bcachefs-tools
|
||||||
|
# pkgs.util-linux
|
||||||
|
# ];
|
||||||
|
# };
|
||||||
|
# extraInstallerConfig = {
|
||||||
|
# # @todo Do we need to add any attributes here?
|
||||||
|
# };
|
||||||
|
bootCommands = ''
|
||||||
|
machine.wait_for_text("enter passphrase for");
|
||||||
|
machine.send_chars("secretsecret\n");
|
||||||
'';
|
'';
|
||||||
# so that the installer boots with a bcachefs enabled kernel
|
|
||||||
extraInstallerConfig = {
|
|
||||||
boot.supportedFilesystems = [ "bcachefs" ];
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user