222 lines
6.9 KiB
Nix
222 lines
6.9 KiB
Nix
{ config, pkgs, lib, ... }:
|
|
|
|
with lib;
|
|
|
|
let
|
|
cfg = config.services.naxos.storage;
|
|
in {
|
|
options.services.naxos.storage = {
|
|
enable = mkEnableOption "NaxOS Declarative ZFS Storage Engine";
|
|
|
|
arcMaxBytes = mkOption {
|
|
type = types.nullOr types.ints.positive;
|
|
default = 4294967296; # 4 GiB default
|
|
description = "Maximum ZFS ARC cache size in bytes.";
|
|
};
|
|
|
|
autoScrub = {
|
|
enable = mkOption {
|
|
type = types.bool;
|
|
default = true;
|
|
description = "Enable automated regular scrubbing of ZFS pools.";
|
|
};
|
|
interval = mkOption {
|
|
type = types.str;
|
|
default = "monthly";
|
|
description = "Interval for auto-scrub (e.g. monthly, weekly).";
|
|
};
|
|
};
|
|
|
|
autoTrim = {
|
|
enable = mkOption {
|
|
type = types.bool;
|
|
default = true;
|
|
description = "Enable periodic TRIM for SSD/NVMe vdevs.";
|
|
};
|
|
};
|
|
|
|
autoSnapshot = {
|
|
enable = mkOption {
|
|
type = types.bool;
|
|
default = true;
|
|
description = "Enable automatic snapshot retention policies.";
|
|
};
|
|
hourly = mkOption {
|
|
type = types.int;
|
|
default = 24;
|
|
description = "Number of hourly snapshots to keep.";
|
|
};
|
|
daily = mkOption {
|
|
type = types.int;
|
|
default = 7;
|
|
description = "Number of daily snapshots to keep.";
|
|
};
|
|
weekly = mkOption {
|
|
type = types.int;
|
|
default = 4;
|
|
description = "Number of weekly snapshots to keep.";
|
|
};
|
|
monthly = mkOption {
|
|
type = types.int;
|
|
default = 12;
|
|
description = "Number of monthly snapshots to keep.";
|
|
};
|
|
};
|
|
|
|
importExistingPools = mkOption {
|
|
type = types.listOf types.str;
|
|
default = [];
|
|
example = [ "tank" "datapool" ];
|
|
description = "List of existing foreign ZFS pools (e.g. from TrueNAS or nixos-lukas) to safely import without formatting.";
|
|
};
|
|
|
|
pools = mkOption {
|
|
type = types.attrsOf (types.submodule {
|
|
options = {
|
|
enable = mkOption {
|
|
type = types.bool;
|
|
default = true;
|
|
description = "Whether to manage this pool.";
|
|
};
|
|
ashift = mkOption {
|
|
type = types.int;
|
|
default = 12;
|
|
description = "ZFS vdev ashift alignment value (12 = 4K sectors).";
|
|
};
|
|
layout = mkOption {
|
|
type = types.enum [ "stripe" "mirror" "raidz1" "raidz2" "raidz3" ];
|
|
default = "mirror";
|
|
description = "Vdev topology layout.";
|
|
};
|
|
devices = mkOption {
|
|
type = types.listOf types.str;
|
|
default = [];
|
|
example = [ "/dev/disk/by-id/nvme-..." "/dev/disk/by-id/ata-..." ];
|
|
description = "Member disk devices or partitions.";
|
|
};
|
|
datasets = mkOption {
|
|
type = types.attrsOf (types.submodule {
|
|
options = {
|
|
mountpoint = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = "Mountpoint for this dataset. If null, uses default ZFS mount.";
|
|
};
|
|
compression = mkOption {
|
|
type = types.enum [ "on" "off" "lz4" "zstd" "zstd-fast" "gzip" ];
|
|
default = "lz4";
|
|
description = "ZFS compression algorithm.";
|
|
};
|
|
recordsize = mkOption {
|
|
type = types.str;
|
|
default = "128K";
|
|
description = "Record size for dataset (e.g. 1M for media, 16K for databases).";
|
|
};
|
|
quota = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = "Optional dataset quota (e.g. 500G).";
|
|
};
|
|
reservation = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = "Optional dataset reservation.";
|
|
};
|
|
owner = mkOption {
|
|
type = types.str;
|
|
default = "root";
|
|
description = "POSIX owner of mountpoint.";
|
|
};
|
|
group = mkOption {
|
|
type = types.str;
|
|
default = "root";
|
|
description = "POSIX group of mountpoint.";
|
|
};
|
|
mode = mkOption {
|
|
type = types.str;
|
|
default = "0755";
|
|
description = "POSIX directory mode permissions.";
|
|
};
|
|
};
|
|
});
|
|
default = {};
|
|
description = "Sub-datasets belonging to this pool.";
|
|
};
|
|
};
|
|
});
|
|
default = {};
|
|
description = "Declaratively defined ZFS pools and datasets.";
|
|
};
|
|
};
|
|
|
|
config = mkIf cfg.enable {
|
|
boot.supportedFilesystems = [ "zfs" ];
|
|
boot.kernelPackages = pkgs.linuxPackages;
|
|
|
|
# Kernel parameter for ZFS ARC Max limit
|
|
boot.kernelParams = mkIf (cfg.arcMaxBytes != null) [
|
|
"zfs.zfs_arc_max=${toString cfg.arcMaxBytes}"
|
|
];
|
|
|
|
# Safe pool import list (includes both configured pools and imported existing pools)
|
|
boot.zfs.extraPools = unique (
|
|
(attrNames (filterAttrs (n: v: v.enable) cfg.pools)) ++
|
|
cfg.importExistingPools
|
|
);
|
|
boot.zfs.forceImportRoot = false;
|
|
|
|
# Automated ZFS services
|
|
services.zfs = {
|
|
autoScrub = {
|
|
enable = cfg.autoScrub.enable;
|
|
interval = cfg.autoScrub.interval;
|
|
};
|
|
trim = {
|
|
enable = cfg.autoTrim.enable;
|
|
};
|
|
autoSnapshot = {
|
|
enable = cfg.autoSnapshot.enable;
|
|
hourly = cfg.autoSnapshot.hourly;
|
|
daily = cfg.autoSnapshot.daily;
|
|
weekly = cfg.autoSnapshot.weekly;
|
|
monthly = cfg.autoSnapshot.monthly;
|
|
};
|
|
};
|
|
|
|
# System tools for storage diagnosis and disk monitoring
|
|
environment.systemPackages = with pkgs; [
|
|
zfs
|
|
smartmontools
|
|
hdparm
|
|
nvme-cli
|
|
parted
|
|
gptfdisk
|
|
iotop
|
|
ncdu
|
|
sanoid # Includes sanoid and syncoid
|
|
];
|
|
|
|
# Generate systemd.tmpfiles rules for configured datasets with custom owners/modes
|
|
systemd.tmpfiles.rules = flatten (mapAttrsToList (poolName: poolCfg:
|
|
mapAttrsToList (dsName: dsCfg:
|
|
mkIf (dsCfg.mountpoint != null)
|
|
"d ${dsCfg.mountpoint} ${dsCfg.mode} ${dsCfg.owner} ${dsCfg.group} -"
|
|
) poolCfg.datasets
|
|
) cfg.pools);
|
|
|
|
# Generate fileSystems definitions for datasets with explicit mountpoints
|
|
fileSystems = foldl' (acc: pool:
|
|
let
|
|
poolName = pool.name;
|
|
poolCfg = pool.value;
|
|
in acc // (mapAttrs' (dsName: dsCfg:
|
|
nameValuePair dsCfg.mountpoint {
|
|
device = "${poolName}/${dsName}";
|
|
fsType = "zfs";
|
|
options = [ "nofail" ];
|
|
}
|
|
) (filterAttrs (n: v: v.mountpoint != null) poolCfg.datasets))
|
|
) {} (mapAttrsToList (name: value: { inherit name value; }) (filterAttrs (n: v: v.enable) cfg.pools));
|
|
};
|
|
}
|