forked from jan-leila/nix-config
		
	
		
			
				
	
	
		
			267 lines
		
	
	
	
		
			8.4 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
			
		
		
	
	
			267 lines
		
	
	
	
		
			8.4 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
| {
 | |
|   lib,
 | |
|   pkgs,
 | |
|   config,
 | |
|   inputs,
 | |
|   ...
 | |
| }: let
 | |
|   # there currently is a bug with disko that causes long disk names to be generated improperly this hash function should alleviate it when used for disk names instead of what we are defaulting to
 | |
|   # max gpt length is 36 and disk adds formats it like disk-xxxx-zfs which means we need to be 9 characters under that
 | |
|   hashDisk = drive: (builtins.substring 0 27 (builtins.hashString "sha256" drive));
 | |
| 
 | |
|   vdevs =
 | |
|     builtins.map (
 | |
|       disks:
 | |
|         builtins.map (disk: lib.attrsets.nameValuePair (hashDisk disk) disk) disks
 | |
|     )
 | |
|     config.host.storage.pool.vdevs;
 | |
|   cache =
 | |
|     builtins.map (
 | |
|       disk: lib.attrsets.nameValuePair (hashDisk disk) disk
 | |
|     )
 | |
|     config.host.storage.pool.cache;
 | |
| 
 | |
|   datasets = config.host.storage.pool.datasets // config.host.storage.pool.extraDatasets;
 | |
| in {
 | |
|   options.host.storage = {
 | |
|     enable = lib.mkEnableOption "are we going create zfs disks with disko on this device";
 | |
|     encryption = lib.mkEnableOption "is the vdev going to be encrypted";
 | |
|     notifications = {
 | |
|       enable = lib.mkEnableOption "are notifications enabled";
 | |
|       host = lib.mkOption {
 | |
|         type = lib.types.str;
 | |
|         description = "what is the host that we are going to send the email to";
 | |
|       };
 | |
|       port = lib.mkOption {
 | |
|         type = lib.types.port;
 | |
|         description = "what port is the host using to receive mail on";
 | |
|       };
 | |
|       to = lib.mkOption {
 | |
|         type = lib.types.str;
 | |
|         description = "what account is the email going to be sent to";
 | |
|       };
 | |
|       user = lib.mkOption {
 | |
|         type = lib.types.str;
 | |
|         description = "what user is the email going to be set from";
 | |
|       };
 | |
|       tokenFile = lib.mkOption {
 | |
|         type = lib.types.str;
 | |
|         description = "file containing the password to be used by msmtp for notifications";
 | |
|       };
 | |
|     };
 | |
|     pool = {
 | |
|       mode = lib.mkOption {
 | |
|         type = lib.types.str;
 | |
|         default = "raidz2";
 | |
|         description = "what level of redundancy should this pool have";
 | |
|       };
 | |
|       # list of drives in pool that will have a boot partition put onto them
 | |
|       bootDrives = lib.mkOption {
 | |
|         type = lib.types.listOf lib.types.str;
 | |
|         description = "list of disks that are going to have a boot partition installed on them";
 | |
|         default = lib.lists.flatten config.host.storage.pool.vdevs;
 | |
|       };
 | |
|       # shorthand for vdevs if you only have 1 vdev
 | |
|       drives = lib.mkOption {
 | |
|         type = lib.types.listOf lib.types.str;
 | |
|         description = "list of drives that are going to be in the vdev";
 | |
|         default = [];
 | |
|       };
 | |
|       # list of all drives in each vdev
 | |
|       vdevs = lib.mkOption {
 | |
|         type = lib.types.listOf (lib.types.listOf lib.types.str);
 | |
|         description = "list of disks that are going to be in";
 | |
|         default = [config.host.storage.pool.drives];
 | |
|       };
 | |
|       # list of cache drives for pool
 | |
|       cache = lib.mkOption {
 | |
|         type = lib.types.listOf lib.types.str;
 | |
|         description = "list of drives that are going to be used as cache";
 | |
|         default = [];
 | |
|       };
 | |
|       # Default datasets that are needed to make a functioning system
 | |
|       datasets = lib.mkOption {
 | |
|         type = lib.types.attrsOf (inputs.disko.lib.subType {
 | |
|           types = {inherit (inputs.disko.lib.types) zfs_fs zfs_volume;};
 | |
|         });
 | |
|         default = {
 | |
|           "local" = {
 | |
|             type = "zfs_fs";
 | |
|             options.canmount = "off";
 | |
|           };
 | |
|           # nix directory needs to be available pre persist and doesn't need to be snapshotted or backed up
 | |
|           "local/system/nix" = {
 | |
|             type = "zfs_fs";
 | |
|             mountpoint = "/nix";
 | |
|             options = {
 | |
|               atime = "off";
 | |
|               relatime = "off";
 | |
|               canmount = "on";
 | |
|             };
 | |
|           };
 | |
|           # dataset for root that gets rolled back on every boot
 | |
|           "local/system/root" = {
 | |
|             type = "zfs_fs";
 | |
|             mountpoint = "/";
 | |
|             options = {
 | |
|               canmount = "on";
 | |
|             };
 | |
|             postCreateHook = ''
 | |
|               zfs snapshot rpool/local/system/root@blank
 | |
|             '';
 | |
|           };
 | |
|         };
 | |
|       };
 | |
|       extraDatasets = lib.mkOption {
 | |
|         type = lib.types.attrsOf (inputs.disko.lib.subType {
 | |
|           types = {inherit (inputs.disko.lib.types) zfs_fs zfs_volume;};
 | |
|         });
 | |
|         description = "List of datasets to define";
 | |
|         default = {};
 | |
|       };
 | |
|     };
 | |
|   };
 | |
| 
 | |
|   config = lib.mkIf config.host.storage.enable {
 | |
|     programs.msmtp = lib.mkIf config.host.storage.notifications.enable {
 | |
|       enable = true;
 | |
|       setSendmail = true;
 | |
|       defaults = {
 | |
|         aliases = "/etc/aliases";
 | |
|         port = config.host.storage.notifications.port;
 | |
|         tls_trust_file = "/etc/ssl/certs/ca-certificates.crt";
 | |
|         tls = "on";
 | |
|         auth = "login";
 | |
|         tls_starttls = "off";
 | |
|       };
 | |
|       accounts = {
 | |
|         zfs_notifications = {
 | |
|           auth = true;
 | |
|           tls = true;
 | |
|           host = config.host.storage.notifications.host;
 | |
|           passwordeval = "cat ${config.host.storage.notifications.tokenFile}";
 | |
|           user = config.host.storage.notifications.user;
 | |
|           from = config.host.storage.notifications.user;
 | |
|         };
 | |
|       };
 | |
|     };
 | |
| 
 | |
|     services.zfs = {
 | |
|       autoScrub.enable = true;
 | |
|       autoSnapshot.enable = true;
 | |
| 
 | |
|       zed = lib.mkIf config.host.storage.notifications.enable {
 | |
|         enableMail = true;
 | |
| 
 | |
|         settings = {
 | |
|           ZED_DEBUG_LOG = "/tmp/zed.debug.log";
 | |
|           ZED_EMAIL_ADDR = [config.host.storage.notifications.to];
 | |
|           ZED_EMAIL_PROG = "${pkgs.msmtp}/bin/msmtp";
 | |
|           ZED_EMAIL_OPTS = "-a zfs_notifications @ADDRESS@";
 | |
| 
 | |
|           ZED_NOTIFY_INTERVAL_SECS = 3600;
 | |
|           ZED_NOTIFY_VERBOSE = true;
 | |
| 
 | |
|           ZED_USE_ENCLOSURE_LEDS = true;
 | |
|           ZED_SCRUB_AFTER_RESILVER = true;
 | |
|         };
 | |
|       };
 | |
|     };
 | |
| 
 | |
|     disko.devices = {
 | |
|       disk = (
 | |
|         builtins.listToAttrs (
 | |
|           builtins.map
 | |
|           (drive:
 | |
|             lib.attrsets.nameValuePair (drive.name) {
 | |
|               type = "disk";
 | |
|               device = "/dev/disk/by-id/${drive.value}";
 | |
|               content = {
 | |
|                 type = "gpt";
 | |
|                 partitions = {
 | |
|                   ESP = lib.mkIf (builtins.elem drive.value config.host.storage.pool.bootDrives) {
 | |
|                     # The 2GB here for the boot partition might be a bit overkill we probably only need like 1/4th of that but storage is cheap
 | |
|                     size = "2G";
 | |
|                     type = "EF00";
 | |
|                     content = {
 | |
|                       type = "filesystem";
 | |
|                       format = "vfat";
 | |
|                       mountpoint = "/boot";
 | |
|                       mountOptions = ["umask=0077"];
 | |
|                     };
 | |
|                   };
 | |
|                   zfs = {
 | |
|                     size = "100%";
 | |
|                     content = {
 | |
|                       type = "zfs";
 | |
|                       pool = "rpool";
 | |
|                     };
 | |
|                   };
 | |
|                 };
 | |
|               };
 | |
|             })
 | |
|           (
 | |
|             (lib.lists.flatten vdevs) ++ cache
 | |
|           )
 | |
|         )
 | |
|       );
 | |
|       zpool = {
 | |
|         rpool = {
 | |
|           type = "zpool";
 | |
|           mode = {
 | |
|             topology = {
 | |
|               type = "topology";
 | |
|               vdev = (
 | |
|                 builtins.map (disks: {
 | |
|                   mode = config.host.storage.pool.mode;
 | |
|                   members =
 | |
|                     builtins.map (disk: disk.name) disks;
 | |
|                 })
 | |
|                 vdevs
 | |
|               );
 | |
|               cache = builtins.map (disk: disk.name) cache;
 | |
|             };
 | |
|           };
 | |
| 
 | |
|           options = {
 | |
|             ashift = "12";
 | |
|             autotrim = "on";
 | |
|           };
 | |
| 
 | |
|           rootFsOptions =
 | |
|             {
 | |
|               canmount = "off";
 | |
|               mountpoint = "none";
 | |
| 
 | |
|               xattr = "sa";
 | |
|               acltype = "posixacl";
 | |
|               relatime = "on";
 | |
| 
 | |
|               compression = "lz4";
 | |
| 
 | |
|               "com.sun:auto-snapshot" = "false";
 | |
|             }
 | |
|             // (
 | |
|               lib.attrsets.optionalAttrs config.host.storage.encryption {
 | |
|                 encryption = "on";
 | |
|                 keyformat = "hex";
 | |
|                 keylocation = "prompt";
 | |
|               }
 | |
|             );
 | |
| 
 | |
|           datasets = lib.mkMerge [
 | |
|             (
 | |
|               lib.attrsets.mapAttrs (name: value: {
 | |
|                 type = value.type;
 | |
|                 options = value.options;
 | |
|                 mountpoint = value.mountpoint;
 | |
|                 postCreateHook = value.postCreateHook;
 | |
|               })
 | |
|               datasets
 | |
|             )
 | |
|           ];
 | |
|         };
 | |
|       };
 | |
|     };
 | |
|   };
 | |
| }
 |