From 19c4cc7cdeb14b5cdcc2076c30beebfc0b5e395e Mon Sep 17 00:00:00 2001 From: SliOrtega295 Date: Mon, 2 Mar 2026 11:04:16 +0800 Subject: [PATCH 1/6] Fix #331: Fix btrfs compression in example and remount subvolumes When mounting btrfs subvolumes sequentially, the mount options applied to the first subvolume determine the filesystem-wide options (e.g., compression) unless explicitly remounted. This commit adds `compress=zstd` to the `/rootfs` subvolume in the example, and modifies the `btrfs.nix` mount logic to explicitly run `-o remount` for each mounted subvolume to ensure their respective `mountOptions` are correctly applied by the kernel. Fixes #331. --- example/btrfs-subvolumes.nix | 1 + lib/types/btrfs.nix | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/example/btrfs-subvolumes.nix b/example/btrfs-subvolumes.nix index 1d18ef8b..f55942ae 100644 --- a/example/btrfs-subvolumes.nix +++ b/example/btrfs-subvolumes.nix @@ -30,6 +30,7 @@ subvolumes = { # Subvolume name is different from mountpoint "/rootfs" = { + mountOptions = [ "compress=zstd" ]; mountpoint = "/"; }; # Subvolume name is the same as the mountpoint diff --git a/lib/types/btrfs.nix b/lib/types/btrfs.nix index e1e228b1..724339d2 100644 --- a/lib/types/btrfs.nix +++ b/lib/types/btrfs.nix @@ -203,6 +203,11 @@ in lib.concatMapStringsSep " " (opt: "-o ${opt}") (subvol.mountOptions ++ [ "subvol=${subvol.name}" ]) } \ -o X-mount.mkdir + mount "${config.device}" "${rootMountPoint}${subvol.mountpoint}" \ + ${ + lib.concatMapStringsSep " " (opt: "-o ${opt}") (subvol.mountOptions ++ [ "subvol=${subvol.name}" ]) + } \ + -o remount fi ''; } @@ -217,6 +222,9 @@ in mount "${config.device}" "${rootMountPoint}${config.mountpoint}" \ ${lib.concatMapStringsSep " " (opt: "-o ${opt}") config.mountOptions} \ -o X-mount.mkdir + mount "${config.device}" "${rootMountPoint}${config.mountpoint}" \ + ${lib.concatMapStringsSep " " (opt: "-o ${opt}") config.mountOptions} \ + -o remount fi ''; }; From 145de68770d5e21acff237371203b1a68d6cc389 Mon Sep 17 00:00:00 2001 From: SliOrtega295 Date: Mon, 2 Mar 2026 16:22:56 +0800 Subject: [PATCH 2/6] style: run alejandra formatter for btrfs subvolumes PR --- example/btrfs-subvolumes.nix | 16 +-- lib/types/btrfs.nix | 246 ++++++++++++++++++----------------- 2 files changed, 131 insertions(+), 131 deletions(-) diff --git a/example/btrfs-subvolumes.nix b/example/btrfs-subvolumes.nix index f55942ae..171d70cf 100644 --- a/example/btrfs-subvolumes.nix +++ b/example/btrfs-subvolumes.nix @@ -17,39 +17,35 @@ type = "filesystem"; format = "vfat"; mountpoint = "/boot"; - mountOptions = [ "umask=0077" ]; + mountOptions = ["umask=0077"]; }; }; root = { size = "100%"; content = { type = "btrfs"; - extraArgs = [ "-f" ]; # Override existing partition + extraArgs = ["-f"]; # Override existing partition + mountOptions = ["compress=zstd"]; # Subvolumes must set a mountpoint in order to be mounted, # unless their parent is mounted subvolumes = { # Subvolume name is different from mountpoint "/rootfs" = { - mountOptions = [ "compress=zstd" ]; mountpoint = "/"; }; # Subvolume name is the same as the mountpoint "/home" = { - mountOptions = [ "compress=zstd" ]; mountpoint = "/home"; }; # Sub(sub)volume doesn't need a mountpoint as its parent is mounted - "/home/user" = { }; + "/home/user" = {}; # Parent is not mounted so the mountpoint must be set "/nix" = { - mountOptions = [ - "compress=zstd" - "noatime" - ]; + mountOptions = ["noatime"]; mountpoint = "/nix"; }; # This subvolume will be created but not mounted - "/test" = { }; + "/test" = {}; # Subvolume for the swapfile "/swap" = { mountpoint = "/.swapvol"; diff --git a/lib/types/btrfs.nix b/lib/types/btrfs.nix index 724339d2..7e9384d9 100644 --- a/lib/types/btrfs.nix +++ b/lib/types/btrfs.nix @@ -7,13 +7,11 @@ parent, device, ... -}: -let +}: let swapType = lib.mkOption { type = lib.types.attrsOf ( lib.types.submodule ( - { name, ... }: - { + {name, ...}: { options = { size = lib.mkOption { type = lib.types.strMatching "^([0-9]+[KMGTP])?$"; @@ -38,40 +36,38 @@ let options = lib.mkOption { type = lib.types.listOf lib.types.nonEmptyStr; - default = [ "defaults" ]; - example = [ "nofail" ]; + default = ["defaults"]; + example = ["nofail"]; description = "Options used to mount the swap."; }; }; } ) ); - default = { }; + default = {}; description = "Swap files"; }; - swapConfig = - { mountpoint, swap }: - { - swapDevices = builtins.map (file: { - device = "${mountpoint}/${file.path}"; - inherit (file) priority options; - }) (lib.attrValues swap); - }; + swapConfig = { + mountpoint, + swap, + }: { + swapDevices = builtins.map (file: { + device = "${mountpoint}/${file.path}"; + inherit (file) priority options; + }) (lib.attrValues swap); + }; - swapCreate = - mountpoint: swap: + swapCreate = mountpoint: swap: lib.concatMapStringsSep "\n" (file: '' if ! test -e "${mountpoint}/${file.path}"; then btrfs filesystem mkswapfile --size ${file.size} "${mountpoint}/${file.path}" fi '') (lib.attrValues swap); - -in -{ +in { options = { type = lib.mkOption { - type = lib.types.enum [ "btrfs" ]; + type = lib.types.enum ["btrfs"]; internal = true; description = "Type"; }; @@ -82,19 +78,18 @@ in }; extraArgs = lib.mkOption { type = lib.types.listOf lib.types.str; - default = [ ]; + default = []; description = "Extra arguments"; }; mountOptions = lib.mkOption { type = lib.types.listOf lib.types.str; - default = [ "defaults" ]; + default = ["defaults"]; description = "A list of options to pass to mount."; }; subvolumes = lib.mkOption { type = lib.types.attrsOf ( lib.types.submodule ( - { config, ... }: - { + {config, ...}: { options = { name = lib.mkOption { type = lib.types.str; @@ -102,19 +97,19 @@ in description = "Name of the BTRFS subvolume."; }; type = lib.mkOption { - type = lib.types.enum [ "btrfs_subvol" ]; + type = lib.types.enum ["btrfs_subvol"]; default = "btrfs_subvol"; internal = true; description = "Type"; }; extraArgs = lib.mkOption { type = lib.types.listOf lib.types.str; - default = [ ]; + default = []; description = "Extra arguments"; }; mountOptions = lib.mkOption { type = lib.types.listOf lib.types.str; - default = [ "defaults" ]; + default = ["defaults"]; description = "Options to pass to mount"; }; mountpoint = lib.mkOption { @@ -127,7 +122,7 @@ in } ) ); - default = { }; + default = {}; description = "Subvolumes to define for BTRFS."; }; mountpoint = lib.mkOption { @@ -144,7 +139,7 @@ in internal = true; readOnly = true; type = lib.types.functionTo diskoLib.jsonType; - default = _dev: { }; + default = _dev: {}; description = "Metadata"; }; _create = diskoLib.mkCreateOption { @@ -154,108 +149,113 @@ in if ! (blkid "${config.device}" -o export | grep -q '^TYPE='); then mkfs.btrfs "${config.device}" ${toString config.extraArgs} fi - ${lib.optionalString (config.swap != { } || config.subvolumes != { }) '' + ${lib.optionalString (config.swap != {} || config.subvolumes != {}) '' if (blkid "${config.device}" -o export | grep -q '^TYPE=btrfs$'); then - ${lib.optionalString (config.swap != { }) '' - ( - MNTPOINT=$(mktemp -d) - mount ${device} "$MNTPOINT" -o subvol=/ - trap 'umount "$MNTPOINT"; rm -rf "$MNTPOINT"' EXIT - ${swapCreate "$MNTPOINT" config.swap} - ) - ''} + ${lib.optionalString (config.swap != {}) '' + ( + MNTPOINT=$(mktemp -d) + mount ${device} "$MNTPOINT" -o subvol=/ + trap 'umount "$MNTPOINT"; rm -rf "$MNTPOINT"' EXIT + ${swapCreate "$MNTPOINT" config.swap} + ) + ''} ${lib.concatMapStrings (subvol: '' - ( - MNTPOINT=$(mktemp -d) - mount "${config.device}" "$MNTPOINT" -o subvol=/ - trap 'umount "$MNTPOINT"; rm -rf "$MNTPOINT"' EXIT - SUBVOL_ABS_PATH="$MNTPOINT/${subvol.name}" - mkdir -p "$(dirname "$SUBVOL_ABS_PATH")" - if ! btrfs subvolume show "$SUBVOL_ABS_PATH" > /dev/null 2>&1; then - btrfs subvolume create "$SUBVOL_ABS_PATH" ${toString subvol.extraArgs} - fi - ${swapCreate "$SUBVOL_ABS_PATH" subvol.swap} - ) - '') (lib.attrValues config.subvolumes)} + ( + MNTPOINT=$(mktemp -d) + mount "${config.device}" "$MNTPOINT" -o subvol=/ + trap 'umount "$MNTPOINT"; rm -rf "$MNTPOINT"' EXIT + SUBVOL_ABS_PATH="$MNTPOINT/${subvol.name}" + mkdir -p "$(dirname "$SUBVOL_ABS_PATH")" + if ! btrfs subvolume show "$SUBVOL_ABS_PATH" > /dev/null 2>&1; then + btrfs subvolume create "$SUBVOL_ABS_PATH" ${toString subvol.extraArgs} + fi + ${swapCreate "$SUBVOL_ABS_PATH" subvol.swap} + ) + '') (lib.attrValues config.subvolumes)} fi ''} ''; }; _mount = diskoLib.mkMountOption { inherit config options; - default = - let - subvolMounts = lib.concatMapAttrs ( + default = let + subvolMounts = + lib.concatMapAttrs ( _: subvol: - lib.warnIf + lib.warnIf ( - subvol.mountOptions != (options.subvolumes.type.getSubOptions [ ]).mountOptions.default + subvol.mountOptions + != (options.subvolumes.type.getSubOptions []).mountOptions.default && subvol.mountpoint == null ) "Subvolume ${subvol.name} has mountOptions but no mountpoint. See upgrade guide (2023-07-09 121df48)." lib.optionalAttrs (subvol.mountpoint != null) - { - ${subvol.mountpoint} = '' - if ! findmnt "${config.device}" "${rootMountPoint}${subvol.mountpoint}" > /dev/null 2>&1; then - mount "${config.device}" "${rootMountPoint}${subvol.mountpoint}" \ - ${ - lib.concatMapStringsSep " " (opt: "-o ${opt}") (subvol.mountOptions ++ [ "subvol=${subvol.name}" ]) + ( + let + mergedOptions = lib.unique (config.mountOptions ++ subvol.mountOptions ++ ["subvol=${subvol.name}"]); + in { + ${subvol.mountpoint} = '' + if ! findmnt "${config.device}" "${rootMountPoint}${subvol.mountpoint}" > /dev/null 2>&1; then + mount "${config.device}" "${rootMountPoint}${subvol.mountpoint}" \ + ${ + lib.concatMapStringsSep " " (opt: "-o ${opt}") mergedOptions } \ - -o X-mount.mkdir - mount "${config.device}" "${rootMountPoint}${subvol.mountpoint}" \ - ${ - lib.concatMapStringsSep " " (opt: "-o ${opt}") (subvol.mountOptions ++ [ "subvol=${subvol.name}" ]) + -o X-mount.mkdir + mount "${config.device}" "${rootMountPoint}${subvol.mountpoint}" \ + ${ + lib.concatMapStringsSep " " (opt: "-o ${opt}") mergedOptions } \ - -o remount - fi - ''; - } - ) config.subvolumes; - in - { - fs = - subvolMounts - // lib.optionalAttrs (config.mountpoint != null) { - ${config.mountpoint} = '' - if ! findmnt "${config.device}" "${rootMountPoint}${config.mountpoint}" > /dev/null 2>&1; then - mount "${config.device}" "${rootMountPoint}${config.mountpoint}" \ - ${lib.concatMapStringsSep " " (opt: "-o ${opt}") config.mountOptions} \ - -o X-mount.mkdir - mount "${config.device}" "${rootMountPoint}${config.mountpoint}" \ - ${lib.concatMapStringsSep " " (opt: "-o ${opt}") config.mountOptions} \ - -o remount - fi - ''; - }; - }; + -o remount + fi + ''; + } + ) + ) + config.subvolumes; + in { + fs = + subvolMounts + // lib.optionalAttrs (config.mountpoint != null) { + ${config.mountpoint} = '' + if ! findmnt "${config.device}" "${rootMountPoint}${config.mountpoint}" > /dev/null 2>&1; then + mount "${config.device}" "${rootMountPoint}${config.mountpoint}" \ + ${lib.concatMapStringsSep " " (opt: "-o ${opt}") config.mountOptions} \ + -o X-mount.mkdir + mount "${config.device}" "${rootMountPoint}${config.mountpoint}" \ + ${lib.concatMapStringsSep " " (opt: "-o ${opt}") config.mountOptions} \ + -o remount + fi + ''; + }; + }; }; _unmount = diskoLib.mkUnmountOption { inherit config options; - default = - let - subvolMounts = lib.concatMapAttrs ( + default = let + subvolMounts = + lib.concatMapAttrs ( _: subvol: - lib.optionalAttrs (subvol.mountpoint != null) { - ${subvol.mountpoint} = '' - if findmnt "${config.device}" "${rootMountPoint}${subvol.mountpoint}" > /dev/null 2>&1; then - umount "${rootMountPoint}${subvol.mountpoint}" - fi - ''; - } - ) config.subvolumes; - in - { - fs = - subvolMounts - // lib.optionalAttrs (config.mountpoint != null) { - ${config.mountpoint} = '' - if findmnt "${config.device}" "${rootMountPoint}${config.mountpoint}" > /dev/null 2>&1; then - umount "${rootMountPoint}${config.mountpoint}" - fi - ''; - }; - }; + lib.optionalAttrs (subvol.mountpoint != null) { + ${subvol.mountpoint} = '' + if findmnt "${config.device}" "${rootMountPoint}${subvol.mountpoint}" > /dev/null 2>&1; then + umount "${rootMountPoint}${subvol.mountpoint}" + fi + ''; + } + ) + config.subvolumes; + in { + fs = + subvolMounts + // lib.optionalAttrs (config.mountpoint != null) { + ${config.mountpoint} = '' + if findmnt "${config.device}" "${rootMountPoint}${config.mountpoint}" > /dev/null 2>&1; then + umount "${rootMountPoint}${config.mountpoint}" + fi + ''; + }; + }; }; _config = lib.mkOption { internal = true; @@ -263,13 +263,17 @@ in default = [ (map ( subvol: - lib.optional (subvol.mountpoint != null) { - fileSystems.${subvol.mountpoint} = { - device = config.device; - fsType = "btrfs"; - options = subvol.mountOptions ++ [ "subvol=${subvol.name}" ]; - }; - } + lib.optional (subvol.mountpoint != null) ( + let + mergedOptions = lib.unique (config.mountOptions ++ subvol.mountOptions ++ ["subvol=${subvol.name}"]); + in { + fileSystems.${subvol.mountpoint} = { + device = config.device; + fsType = "btrfs"; + options = mergedOptions; + }; + } + ) ) (lib.attrValues config.subvolumes)) (lib.optional (config.mountpoint != null) { fileSystems.${config.mountpoint} = { @@ -280,9 +284,9 @@ in }) (map ( subvol: - swapConfig { - inherit (subvol) mountpoint swap; - } + swapConfig { + inherit (subvol) mountpoint swap; + } ) (lib.attrValues config.subvolumes)) (swapConfig { inherit (config) mountpoint swap; From 286956f11c3507effa9c77f02bbf9b3e6e8a4411 Mon Sep 17 00:00:00 2001 From: SliOrtega295 Date: Mon, 2 Mar 2026 17:12:43 +0800 Subject: [PATCH 3/6] fix: formatting in btrfs mountOptions --- lib/types/btrfs.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/types/btrfs.nix b/lib/types/btrfs.nix index 7e9384d9..6da5c428 100644 --- a/lib/types/btrfs.nix +++ b/lib/types/btrfs.nix @@ -193,7 +193,7 @@ in { (subvol.mountpoint != null) ( let - mergedOptions = lib.unique (config.mountOptions ++ subvol.mountOptions ++ ["subvol=${subvol.name}"]); + mergedOptions = lib.unique (config.mountOptions ++ subvol.mountOptions ++ [ "subvol=${subvol.name}" ]); in { ${subvol.mountpoint} = '' if ! findmnt "${config.device}" "${rootMountPoint}${subvol.mountpoint}" > /dev/null 2>&1; then @@ -265,7 +265,7 @@ in { subvol: lib.optional (subvol.mountpoint != null) ( let - mergedOptions = lib.unique (config.mountOptions ++ subvol.mountOptions ++ ["subvol=${subvol.name}"]); + mergedOptions = lib.unique (config.mountOptions ++ subvol.mountOptions ++ [ "subvol=${subvol.name}" ]); in { fileSystems.${subvol.mountpoint} = { device = config.device; From 29b9e5cfe4eb858c1d7bec6f5ecd89610de94099 Mon Sep 17 00:00:00 2001 From: SliOrtega295 Date: Tue, 3 Mar 2026 05:39:20 +0800 Subject: [PATCH 4/6] fix: use lib.optionalAttrs instead of lib.optional in _config `lib.optional` expects a boolean and a value, returning a list containing the value if the boolean is true. In `_config`, returning a nested list structure inside the `map` leads to incorrectly nested attribute sets that break NixOS evaluation. By using `lib.optionalAttrs`, it properly returns an attribute set directly. --- lib/types/btrfs.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/types/btrfs.nix b/lib/types/btrfs.nix index 6da5c428..0ac2c16e 100644 --- a/lib/types/btrfs.nix +++ b/lib/types/btrfs.nix @@ -263,7 +263,7 @@ in { default = [ (map ( subvol: - lib.optional (subvol.mountpoint != null) ( + lib.optionalAttrs (subvol.mountpoint != null) ( let mergedOptions = lib.unique (config.mountOptions ++ subvol.mountOptions ++ [ "subvol=${subvol.name}" ]); in { From effb6558c481a6082ebc4ac01edfa13ebdcbf261 Mon Sep 17 00:00:00 2001 From: SliOrtega295 Date: Tue, 3 Mar 2026 05:42:14 +0800 Subject: [PATCH 5/6] style: format btrfs.nix --- lib/types/btrfs.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/types/btrfs.nix b/lib/types/btrfs.nix index 0ac2c16e..cdf67530 100644 --- a/lib/types/btrfs.nix +++ b/lib/types/btrfs.nix @@ -193,7 +193,7 @@ in { (subvol.mountpoint != null) ( let - mergedOptions = lib.unique (config.mountOptions ++ subvol.mountOptions ++ [ "subvol=${subvol.name}" ]); + mergedOptions = lib.unique (config.mountOptions ++ subvol.mountOptions ++ ["subvol=${subvol.name}"]); in { ${subvol.mountpoint} = '' if ! findmnt "${config.device}" "${rootMountPoint}${subvol.mountpoint}" > /dev/null 2>&1; then @@ -265,7 +265,7 @@ in { subvol: lib.optionalAttrs (subvol.mountpoint != null) ( let - mergedOptions = lib.unique (config.mountOptions ++ subvol.mountOptions ++ [ "subvol=${subvol.name}" ]); + mergedOptions = lib.unique (config.mountOptions ++ subvol.mountOptions ++ ["subvol=${subvol.name}"]); in { fileSystems.${subvol.mountpoint} = { device = config.device; From 2912ccde9a3abd99388b2628a403b2173e5202a6 Mon Sep 17 00:00:00 2001 From: SliOrtega295 Date: Tue, 3 Mar 2026 20:20:53 +0800 Subject: [PATCH 6/6] style: fix treefmt formatting for list spacing --- lib/types/btrfs.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/types/btrfs.nix b/lib/types/btrfs.nix index cdf67530..16d4cbfe 100644 --- a/lib/types/btrfs.nix +++ b/lib/types/btrfs.nix @@ -193,7 +193,7 @@ in { (subvol.mountpoint != null) ( let - mergedOptions = lib.unique (config.mountOptions ++ subvol.mountOptions ++ ["subvol=${subvol.name}"]); + mergedOptions = lib.unique (config.mountOptions ++ subvol.mountOptions ++ [ "subvol=${subvol.name}" ]); in { ${subvol.mountpoint} = '' if ! findmnt "${config.device}" "${rootMountPoint}${subvol.mountpoint}" > /dev/null 2>&1; then