diff --git a/.gitignore b/.gitignore index 8e7f782..6cdb1c5 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,6 @@ result-* # Created by the NixOS interactive test driver .nixos-test-history + +# Created by `:bl` in `nix repl` +repl-result-out \ No newline at end of file diff --git a/example/bcachefs.nix b/example/bcachefs.nix index fff0503..37ab460 100644 --- a/example/bcachefs.nix +++ b/example/bcachefs.nix @@ -23,7 +23,7 @@ content = { type = "bcachefs"; # This refers to a filesystem in the `bcachefs_filesystems` attrset below. - filesystem = "unmounted_subvolumes_in_multi"; + filesystem = "mounted_subvolumes_in_multi"; label = "group_a.vdb2"; extraFormatArgs = [ "--discard" @@ -44,7 +44,7 @@ size = "100%"; content = { type = "bcachefs"; - filesystem = "unmounted_subvolumes_in_multi"; + filesystem = "mounted_subvolumes_in_multi"; label = "group_a.vdc1"; extraFormatArgs = [ "--discard" @@ -65,7 +65,7 @@ size = "100%"; content = { type = "bcachefs"; - filesystem = "unmounted_subvolumes_in_multi"; + filesystem = "mounted_subvolumes_in_multi"; label = "group_b.vdd1"; extraFormatArgs = [ "--force" @@ -79,87 +79,6 @@ 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 = { @@ -168,7 +87,7 @@ content = { type = "bcachefs"; filesystem = "relies_on_external_subvolume"; - label = "group_a.vdi1"; + label = "group_a.vde1"; }; }; }; @@ -177,72 +96,52 @@ }; bcachefs_filesystems = { - # Example showing unmounted subvolumes in a multi-disk configuration. - unmounted_subvolumes_in_multi = { + # Example showing mounted subvolumes in a multi-disk configuration. + mounted_subvolumes_in_multi = { type = "bcachefs_filesystem"; passwordFile = "/tmp/secret.key"; extraFormatArgs = [ "--compression=lz4" "--background_compression=lz4" ]; + subvolumes = { + # Subvolume name is different from mountpoint. + "subvolumes/root" = { + mountpoint = "/"; + mountOptions = [ + "verbose" + ]; + }; + # Subvolume name is the same as the mountpoint. + "subvolumes/home" = { + mountpoint = "/home"; + }; + # Nested subvolume doesn't need a mountpoint as its parent is mounted. + "subvolumes/home/user" = { + }; + # Parent is not mounted so the mountpoint must be set. + "subvolumes/nix" = { + mountpoint = "/nix"; + }; + # This subvolume will be created but not mounted. + "subvolumes/test" = { + }; + }; + }; + + # Example showing a bcachefs filesystem without subvolumes + # and which relies on a subvolume in another filesystem being mounted. + relies_on_external_subvolume = { + type = "bcachefs_filesystem"; + mountpoint = "/home/Documents"; + 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"; - # }; }; }; } diff --git a/lib/types/bcachefs_filesystem.nix b/lib/types/bcachefs_filesystem.nix index 5b6ee5a..3d6dbfe 100644 --- a/lib/types/bcachefs_filesystem.nix +++ b/lib/types/bcachefs_filesystem.nix @@ -113,10 +113,7 @@ 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. - ''; + description = "Path to mount the subvolume to."; example = "/"; }; }; @@ -126,7 +123,19 @@ default = { }; description = "List of subvolumes to define."; example = { - "subvolumes/home" = { }; + "subvolumes/root" = { + mountpoint = "/"; + extraFormatArgs = [ + "--compression=lz4" + "--background_compression=lz4" + ]; + mountOptions = [ + "verbose" + ]; + }; + "subvolumes/home" = { + mountpoint = "/home"; + }; }; }; _parent = lib.mkOption { @@ -291,7 +300,6 @@ _config = lib.mkOption { internal = true; readOnly = true; - # @todo Check that this implementation is correct: default = (lib.optional (config.mountpoint != null) { fileSystems.${config.mountpoint} = { diff --git a/tests/bcachefs.nix b/tests/bcachefs.nix index 347b5cc..4becbde 100644 --- a/tests/bcachefs.nix +++ b/tests/bcachefs.nix @@ -7,39 +7,120 @@ diskoLib.testLib.makeDiskoTest { name = "bcachefs"; disko-config = ../example/bcachefs.nix; enableOCR = true; + bootCommands = '' + machine.wait_for_text("enter passphrase for /"); + machine.send_chars("secretsecret\n"); + machine.wait_for_text("enter passphrase for /home"); + machine.send_chars("secretsecret\n"); + machine.wait_for_text("enter passphrase for /nix"); + machine.send_chars("secretsecret\n"); + ''; + extraSystemConfig = { + environment.systemPackages = [ + pkgs.jq + ]; + }; extraTestScript = '' - 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("ls -la /subvolumes >&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"); + machine.succeed("bcachefs show-super /dev/vda2 >&2"); + machine.succeed("bcachefs show-super /dev/vdd1 >&2"); + machine.succeed("findmnt --json >&2"); + + # Verify subvolume structure. + machine.succeed("test -d /subvolumes/root"); + machine.succeed("test -d /subvolumes/home"); + machine.succeed("test -d /subvolumes/home/user"); + machine.succeed("test -d /subvolumes/nix"); + machine.succeed("test -d /subvolumes/test"); + machine.fail("test -d /subvolumes/non-existent"); + + # Verify existence of mountpoints. + machine.succeed("mountpoint /"); + machine.succeed("mountpoint /home"); + machine.succeed("mountpoint /nix"); + machine.succeed("mountpoint /home/Documents"); + machine.fail("mountpoint /non-existent"); + + # Verify device membership and labels. + machine.succeed("bcachefs show-super /dev/vda2 | grep 'Devices:' | grep -q '3'"); + machine.succeed("bcachefs show-super /dev/vdd1 | grep 'Devices:' | grep -q '1'"); + machine.succeed("bcachefs show-super /dev/vda2 | grep 'Label:' | grep -q 'vdb2'"); + machine.succeed("bcachefs show-super /dev/vda2 | grep 'Label:' | grep -q 'vdc1'"); + machine.succeed("bcachefs show-super /dev/vda2 | grep 'Label:' | grep -q 'vdd1'"); + machine.succeed("bcachefs show-super /dev/vdd1 | grep 'Label:' | grep -q 'vde1'"); + machine.fail("bcachefs show-super /dev/vda2 | grep 'Label:' | grep -q 'non-existent'"); + + # @todo Verify format arguments. + + # Verify mount options from configuration. + machine.succeed(""" + findmnt --json \ + | jq -e ' \ + .filesystems[] \ + | select(.target == "/") \ + | .options \ + | split(",") \ + | contains(["verbose", "compression=lz4", "background_compression=lz4"]) \ + ' + """); + + machine.succeed(""" + findmnt --json \ + | jq -e ' \ + .filesystems[] \ + | .. \ + | select(.target? == "/home/Documents") \ + | .options \ + | split(",") \ + | contains(["verbose"]) \ + ' + """); + + machine.fail(""" + findmnt --json \ + | jq -e ' \ + .filesystems[] \ + | select(.target == "/") \ + | .options \ + | split(",") \ + | contains(["non-existent"]) \ + ' + """); + + # Verify device composition of filesystems. + machine.succeed(""" + findmnt --json \ + | jq -e ' \ + .filesystems[] \ + | select(.target == "/") \ + | .source | split(":") \ + | contains(["/dev/vda2", "/dev/vdb1", "/dev/vdc1"]) \ + ' + """); + + machine.succeed(""" + findmnt --json \ + | jq -e ' \ + .filesystems[] \ + | .. \ + | select(.target? == "/home/Documents") \ + | .source \ + | contains("/dev/vdd1") \ + ' + """); + + machine.fail(""" + findmnt --json \ + | jq -e ' \ + .filesystems[] \ + | select(.target == "/") \ + | .source | split(":") \ + | contains(["/dev/non-existent"]) \ + ' + """); ''; }