From 3c97587281e099f6ac71bf919929945a773357f8 Mon Sep 17 00:00:00 2001 From: Galin Stefanov Date: Tue, 7 Jan 2025 10:57:20 +0200 Subject: [PATCH] feat(modules/mcl-disko): Create ZFS configuration module --- modules/default.nix | 1 + modules/mcl-disko/default.nix | 162 ++++++++++++++++++++ modules/mcl-disko/primaryZfsPartition.nix | 49 ++++++ modules/mcl-disko/secondaryZfsPartition.nix | 18 +++ modules/mcl-disko/zpool.nix | 118 ++++++++++++++ 5 files changed, 348 insertions(+) create mode 100644 modules/mcl-disko/default.nix create mode 100644 modules/mcl-disko/primaryZfsPartition.nix create mode 100644 modules/mcl-disko/secondaryZfsPartition.nix create mode 100644 modules/mcl-disko/zpool.nix diff --git a/modules/default.nix b/modules/default.nix index ed5802d4..7852cc41 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -5,5 +5,6 @@ ./pyroscope ./folder-size-metrics ./shard-split + ./mcl-disko ]; } diff --git a/modules/mcl-disko/default.nix b/modules/mcl-disko/default.nix new file mode 100644 index 00000000..cf904bd3 --- /dev/null +++ b/modules/mcl-disko/default.nix @@ -0,0 +1,162 @@ +{ withSystem, inputs, ... }: +{ + flake.modules.nixos.mcl-disko = + { + pkgs, + config, + lib, + ... + }: + with lib; + let + cfg = config.mcl.disko; + in + { + imports = [ + inputs.disko.nixosModules.disko + ]; + options.mcl.disko = { + enable = mkEnableOption "Enable Module"; + + legacyBoot = mkOption { + type = types.bool; + default = false; + example = true; + description = "Declare if the configuration is for a Hetzner server or not"; + }; + + swapSize = mkOption { + type = types.str; + default = "32G"; + example = "32768M"; + description = "The size of the hard disk space used when RAM is full"; + }; + + espSize = mkOption { + type = types.str; + default = "4G"; + example = "4096M"; + description = "The size of the hard disk space used for the ESP filesystem"; + }; + + disks = mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ + "/dev/disk/sda" + "/dev/disk/sdb" + "/dev/disk/sdc" + ]; + description = "The disk partitions to be used when ZFS is being created"; + }; + + zpool = { + name = mkOption { + type = types.str; + default = "zfs_root"; + description = "The name of the ZFS Pool"; + }; + + mode = mkOption { + type = types.enum [ + "stripe" + "mirror" + "raidz1" + "raidz2" + "raidz3" + ]; + default = "stripe"; + description = "Set ZFS Pool redundancy - e.g. 'mirror', 'raidz1', etc."; + }; + + extraDatasets = mkOption { + type = types.attrsOf ( + types.submodule ( + { name, ... }: + { + options = { + mountpoint = mkOption { + type = types.nullOr types.str; + default = name; + example = "/var/lib"; + description = "The ZFS dataset mountpoint"; + }; + + type = mkOption { + type = types.enum [ + "zfs_fs" + "zfs_volume" + ]; + default = "zfs_fs"; + description = "Type of ZFS dataset"; + }; + snapshot = mkEnableOption "Whether to enable ZFS snapshots"; + }; + + } + ) + ); + default = { }; + example = { + "/opt".snapshot = false; + "/opt/downloads".snapshot = false; + "/opt/downloads/vm-images" = { + snapshot = false; + options = { + quota = "120G"; + }; + }; + }; + description = "Extra ZFS Pool datasets"; + }; + }; + }; + + config.disko = + let + makePrimaryZfsDisk = import ./primaryZfsPartition.nix; + makeSecondaryZfsDisk = import ./secondaryZfsPartition.nix; + + first = builtins.head cfg.disks; + rest = builtins.tail cfg.disks; + secondaryDisks = builtins.listToAttrs ( + builtins.map (disk: { + name = disk; + value = + if cfg.zpool.mode != "stripe" then + makePrimaryZfsDisk { + disk = first; + espSize = cfg.espSize; + swapSize = cfg.swapSize; + legacyBoot = cfg.legacyBoot; + poolName = cfg.zpool.name; + } + else + makeSecondaryZfsDisk { + poolName = cfg.zpool.name; + inherit disk; + }; + }) rest + ); + in + lib.mkIf cfg.enable { + devices = { + disk = secondaryDisks // { + "${first}" = makePrimaryZfsDisk { + disk = first; + espSize = cfg.espSize; + swapSize = cfg.swapSize; + legacyBoot = cfg.legacyBoot; + poolName = cfg.zpool.name; + }; + }; + zpool = import ./zpool.nix { + poolName = cfg.zpool.name; + poolMode = cfg.zpool.mode; + poolExtraDatasets = cfg.zpool.extraDatasets; + inherit lib; + }; + }; + }; + }; +} diff --git a/modules/mcl-disko/primaryZfsPartition.nix b/modules/mcl-disko/primaryZfsPartition.nix new file mode 100644 index 00000000..650a0759 --- /dev/null +++ b/modules/mcl-disko/primaryZfsPartition.nix @@ -0,0 +1,49 @@ +{ + disk, + espSize, + swapSize, + legacyBoot, + poolName, +}: +{ + type = "disk"; + device = disk; + content = { + type = "gpt"; + partitions = { + ESP = { + device = "${disk}-part1"; + priority = 0; + size = espSize; + type = "EF00"; + content = { + type = "filesystem"; + format = "vfat"; + mountpoint = "/boot"; + mountOptions = [ "umask=0077" ]; + }; + }; + + zfs = { + device = "${disk}-part2"; + priority = 1; + end = "-${swapSize}"; + type = "BF00"; + content = { + type = "zfs"; + pool = "${poolName}"; + }; + }; + + swap = { + device = "${disk}-part3"; + priority = 2; + size = if legacyBoot == true then "100%" else swapSize; + content = { + type = "swap"; + randomEncryption = true; + }; + }; + }; + }; +} diff --git a/modules/mcl-disko/secondaryZfsPartition.nix b/modules/mcl-disko/secondaryZfsPartition.nix new file mode 100644 index 00000000..ee058538 --- /dev/null +++ b/modules/mcl-disko/secondaryZfsPartition.nix @@ -0,0 +1,18 @@ +{ poolName, disk }: +{ + type = "disk"; + device = disk; + content = { + type = "gpt"; + partitions = { + zfs = { + device = "${disk}-part1"; + size = "100%"; + content = { + type = "zfs"; + pool = "${poolName}"; + }; + }; + }; + }; +} diff --git a/modules/mcl-disko/zpool.nix b/modules/mcl-disko/zpool.nix new file mode 100644 index 00000000..4dabc78d --- /dev/null +++ b/modules/mcl-disko/zpool.nix @@ -0,0 +1,118 @@ +{ + lib, + poolName, + poolMode, + poolExtraDatasets, +}: +let + translateDataSetToDiskoConfig = + dataset@{ snapshot, ... }: + lib.recursiveUpdate dataset { + type = "zfs_fs"; + options = { + "com.sun:auto-snapshot" = if dataset.snapshot then "on" else "off"; + canmount = "on"; + }; + }; + + restructuredDatasets = builtins.mapAttrs ( + n: v: (builtins.removeAttrs (translateDataSetToDiskoConfig poolExtraDatasets.${n}) [ "snapshot" ]) + ) poolExtraDatasets; +in +{ + ${poolName} = { + type = "zpool"; + mode = if poolMode == "stripe" then "" else poolMode; + rootFsOptions = { + acltype = "posixacl"; + atime = "off"; + canmount = "off"; + checksum = "sha512"; + compression = "lz4"; + xattr = "sa"; + mountpoint = "none"; + "com.sun:auto-snapshot" = "false"; + }; + options = { + autotrim = "on"; + listsnapshots = "on"; + }; + + postCreateHook = "zfs snapshot ${poolName}@blank"; + + datasets = { + root = { + mountpoint = "/"; + type = "zfs_fs"; + options = { + "com.sun:auto-snapshot" = "false"; + mountpoint = "legacy"; + }; + }; + + "root/nix" = { + mountpoint = "/nix"; + type = "zfs_fs"; + options = { + "com.sun:auto-snapshot" = "false"; + canmount = "on"; + mountpoint = "legacy"; + refreservation = "100GiB"; + }; + }; + + "root/var" = { + mountpoint = "/var"; + type = "zfs_fs"; + options = { + "com.sun:auto-snapshot" = "true"; + canmount = "on"; + mountpoint = "legacy"; + }; + }; + + "root/var/lib" = { + mountpoint = "/var/lib"; + type = "zfs_fs"; + options = { + "com.sun:auto-snapshot" = "true"; + canmount = "on"; + mountpoint = "legacy"; + }; + }; + + "root/home" = { + mountpoint = "/home"; + type = "zfs_fs"; + options = { + "com.sun:auto-snapshot" = "true"; + canmount = "on"; + mountpoint = "legacy"; + refreservation = "200GiB"; + }; + }; + + "root/var/lib/docker" = { + mountpoint = "/var/lib/docker"; + type = "zfs_fs"; + options = { + "com.sun:auto-snapshot" = "false"; + canmount = "on"; + mountpoint = "legacy"; + refreservation = "100GiB"; + }; + }; + + "root/var/lib/containers" = { + mountpoint = "/var/lib/containers"; + type = "zfs_fs"; + options = { + "com.sun:auto-snapshot" = "false"; + canmount = "on"; + mountpoint = "legacy"; + refreservation = "100GiB"; + }; + }; + } // restructuredDatasets; + }; +}