{ config, lib, ... }: let cfg = config.disko.disks; in { options.disko.disks = lib.mkOption { description = lib.mdDoc "Disk names to format."; type = with lib.types; listOf (submodule (_: { options = { device = lib.mkOption { type = lib.types.str; default = null; example = "/dev/sda"; description = "Path of the disk."; }; name = lib.mkOption { type = lib.types.str; default = null; example = "ssd0"; description = "Name of the disk."; }; partitionTableFormat = lib.mkOption { type = lib.types.enum [ "gpt" "msdos" ]; default = "gpt"; description = "Which parition table format to use."; }; withBoot = lib.mkOption { type = lib.types.bool; default = true; description = "Wether to include a boot partition."; }; withCeph = lib.mkOption { type = lib.types.bool; default = false; description = "Wether to include a ceph partition."; }; withLuks = lib.mkOption { type = lib.types.bool; default = true; description = "Wether to encrypt the paritions."; }; withZfs = lib.mkOption { type = lib.types.bool; default = true; description = "Wether to include a zfs parition."; }; }; })); default = [ ]; }; config = { assertions = lib.mkIf (cfg != [ ]) (lib.head (map (disk: [ { assertion = disk.withCeph || disk.withZfs; message = "Must enable ceph or zfs!"; } { assertion = disk.withCeph -> disk.withLuks; message = "Ceph requires Luks!"; } ]) cfg)); disko = { devices = lib.mkIf (cfg != [ ]) (lib.head (map (disk: let diskName = if disk.name != "" then "-${disk.name}" else ""; luksName = "crypt-${config.networking.hostName}${diskName}"; zfs = { size = "100%FREE"; content = { pool = zfsName; type = "zfs"; }; }; zfsName = "${config.networking.hostName}${diskName}"; in { disk.${disk.device} = { inherit (disk) device; type = "disk"; content = { type = "table"; format = disk.partitionTableFormat; partitions = lib.optional disk.withZfs { name = "ESP"; start = "1MiB"; end = "512MiB"; bootable = true; content = { type = "filesystem"; format = "vfat"; mountpoint = "/boot"; }; } ++ [ { name = "root"; start = if disk.withZfs then "512MiB" else "1MiB"; end = "100%"; part-type = "primary"; content = lib.optionalAttrs disk.withLuks { type = "luks"; name = luksName; askPassword = true; inherit (zfs) content; } // lib.optionalAttrs (!disk.withLuks) zfs.content; } ]; }; }; } // { zpool.${zfsName} = { type = "zpool"; # -O rootFsOptions = { acltype = "posixacl"; compression = "zstd"; dnodesize = "auto"; normalization = "formD"; xattr = "sa"; }; # -o options = { ashift = "12"; autotrim = "on"; }; datasets = let dataset = mountpoint: { inherit mountpoint; options = { canmount = "on"; inherit mountpoint; }; type = "zfs_fs"; }; datasetNoMount = { mountpoint = null; options = { canmount = "off"; mountpoint = "none"; }; type = "zfs_fs"; }; in { "root" = dataset "/"; "data" = datasetNoMount; # used by services.postgresqlBackup and later by restic "data/backup" = dataset "/var/backup"; "data/etc" = dataset "/etc"; "data/lib" = dataset "/var/lib"; "home" = dataset "/home"; "nix" = lib.recursiveUpdate (dataset "/nix") { options.atime = "off"; }; "nix/store" = dataset "/nix/store"; "nix/var" = dataset "/nix/var"; # zfs uses copy on write and requires some free space to delete files when the disk is completely filled "reserved" = lib.recursiveUpdate (dataset "reserved") { mountpoint = null; options = { canmount = "off"; mountpoint = "none"; reservation = "5GiB"; }; type = "zfs_fs"; }; }; }; }) cfg)); # we do not want changes to this module render machines unbootable enableConfig = false; }; }; }