2022-09-13 11:21:37 +00:00
|
|
|
|
{ lib }:
|
2022-09-04 11:09:10 +00:00
|
|
|
|
with lib;
|
2022-08-29 09:45:19 +00:00
|
|
|
|
with builtins;
|
|
|
|
|
|
|
|
|
|
rec {
|
|
|
|
|
|
|
|
|
|
diskoLib = {
|
|
|
|
|
# like types.oneOf but instead of a list takes an attrset
|
|
|
|
|
# uses the field "type" to find the correct type in the attrset
|
|
|
|
|
subType = typeAttr: mkOptionType rec {
|
|
|
|
|
name = "subType";
|
|
|
|
|
description = "one of ${attrNames typeAttr}";
|
2022-09-04 09:46:37 +00:00
|
|
|
|
check = x: if x ? type then typeAttr.${x.type}.check x else throw "No type option set in:\n${generators.toPretty {} x}";
|
2022-08-29 09:45:19 +00:00
|
|
|
|
merge = loc: defs:
|
|
|
|
|
foldl' (res: def: typeAttr.${def.value.type}.merge loc [def]) {} defs;
|
|
|
|
|
nestedTypes = typeAttr;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
# option for valid contents of partitons (basically like devices, but without tables)
|
|
|
|
|
partitionType = mkOption {
|
|
|
|
|
type = types.nullOr (diskoLib.subType { inherit btrfs filesystem zfs mdraid luks lvm_pv; });
|
|
|
|
|
default = null;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
# option for valid contents of devices
|
|
|
|
|
deviceType = mkOption {
|
|
|
|
|
type = types.nullOr (diskoLib.subType { inherit table btrfs filesystem zfs mdraid luks lvm_pv; });
|
|
|
|
|
default = null;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/* deepMergeMap takes a function and a list of attrsets and deep merges them
|
|
|
|
|
|
|
|
|
|
deepMergeMap :: -> (AttrSet -> AttrSet ) -> [ AttrSet ] -> Attrset
|
|
|
|
|
|
|
|
|
|
Example:
|
2022-10-01 19:29:36 +00:00
|
|
|
|
deepMergeMap (x: x.t = "test") [ { x = { y = 1; z = 3; }; } { x = { bla = 234; }; } ]
|
|
|
|
|
=> { x = { y = 1; z = 3; bla = 234; t = "test"; }; }
|
2022-08-29 09:45:19 +00:00
|
|
|
|
*/
|
|
|
|
|
deepMergeMap = f: listOfAttrs:
|
|
|
|
|
foldr (attr: acc: (recursiveUpdate acc (f attr))) {} listOfAttrs;
|
|
|
|
|
|
|
|
|
|
/* get a device and an index to get the matching device name
|
|
|
|
|
|
|
|
|
|
deviceNumbering :: str -> int -> str
|
|
|
|
|
|
|
|
|
|
Example:
|
|
|
|
|
deviceNumbering "/dev/sda" 3
|
|
|
|
|
=> "/dev/sda3"
|
|
|
|
|
|
|
|
|
|
deviceNumbering "/dev/disk/by-id/xxx" 2
|
|
|
|
|
=> "/dev/disk/by-id/xxx-part2"
|
|
|
|
|
*/
|
|
|
|
|
deviceNumbering = dev: index:
|
|
|
|
|
let
|
|
|
|
|
schemas = {
|
|
|
|
|
dev__da = dev + toString index; # /dev/{s,v}da style
|
|
|
|
|
dev_disk = "${dev}-part${toString index}"; # /dev/disk/by-id/xxx style
|
2022-09-05 12:34:54 +00:00
|
|
|
|
dev_nvme = "${dev}p${toString index}"; # /dev/nvme0n1p1 style
|
2022-08-29 09:45:19 +00:00
|
|
|
|
dev_md = "${dev}p${toString index}"; # /dev/nvme0n1p1 style
|
|
|
|
|
};
|
|
|
|
|
detectSchema =
|
|
|
|
|
if match "/dev/[vs]d.*" dev != null then "dev__da" else
|
|
|
|
|
if match "/dev/disk/.*" dev != null then "dev_disk" else
|
|
|
|
|
if match "/dev/nvme.*" dev != null then "dev_nvme" else
|
|
|
|
|
if match "/dev/md/.*" dev != null then "dev_md" else
|
|
|
|
|
abort "${dev} seems not to be a supported disk format";
|
|
|
|
|
in schemas.${detectSchema};
|
|
|
|
|
|
2022-09-13 11:21:37 +00:00
|
|
|
|
/* A nix option type representing a json datastructure, vendored from nixpkgs to avoid dependency on pkgs */
|
|
|
|
|
jsonType = let
|
|
|
|
|
valueType = types.nullOr (types.oneOf [
|
|
|
|
|
types.bool
|
|
|
|
|
types.int
|
|
|
|
|
types.float
|
|
|
|
|
types.str
|
|
|
|
|
types.path
|
|
|
|
|
(types.attrsOf valueType)
|
|
|
|
|
(types.listOf valueType)
|
|
|
|
|
]) // {
|
|
|
|
|
description = "JSON value";
|
|
|
|
|
};
|
|
|
|
|
in valueType;
|
|
|
|
|
|
2022-08-29 09:45:19 +00:00
|
|
|
|
/* Given a attrset of dependencies and a devices attrset
|
|
|
|
|
returns a sorted list by dependencies. aborts if a loop is found
|
|
|
|
|
|
|
|
|
|
sortDevicesByDependencies :: AttrSet -> AttrSet -> [ [ str str ] ]
|
|
|
|
|
*/
|
|
|
|
|
sortDevicesByDependencies = dependencies: devices:
|
|
|
|
|
let
|
|
|
|
|
dependsOn = a: b:
|
|
|
|
|
elem a (attrByPath b [] dependencies);
|
|
|
|
|
maybeSortedDevices = toposort dependsOn (diskoLib.deviceList devices);
|
|
|
|
|
in
|
|
|
|
|
if (hasAttr "cycle" maybeSortedDevices) then
|
|
|
|
|
abort "detected a cycle in your disk setup: ${maybeSortedDevices.cycle}"
|
|
|
|
|
else
|
|
|
|
|
maybeSortedDevices.result;
|
|
|
|
|
|
|
|
|
|
/* Takes a devices attrSet and returns it as a list
|
|
|
|
|
|
|
|
|
|
deviceList :: AttrSet -> [ [ str str ] ]
|
|
|
|
|
|
|
|
|
|
Example:
|
|
|
|
|
deviceList { zfs.pool1 = {}; zfs.pool2 = {}; mdadm.raid1 = {}; }
|
|
|
|
|
=> [ [ "zfs" "pool1" ] [ "zfs" "pool2" ] [ "mdadm" "raid1" ] ]
|
|
|
|
|
*/
|
|
|
|
|
deviceList = devices:
|
|
|
|
|
concatLists (mapAttrsToList (n: v: (map (x: [ n x ]) (attrNames v))) devices);
|
|
|
|
|
|
|
|
|
|
/* Takes either a string or null and returns the string or an empty string
|
|
|
|
|
|
|
|
|
|
maybeStr :: Either (str null) -> str
|
|
|
|
|
|
|
|
|
|
Example:
|
|
|
|
|
maybeStr null
|
|
|
|
|
=> ""
|
|
|
|
|
maybeSTr "hello world"
|
|
|
|
|
=> "hello world"
|
|
|
|
|
*/
|
|
|
|
|
maybeStr = x: optionalString (!isNull x) x;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
optionTypes = rec {
|
|
|
|
|
# POSIX.1‐2017, 3.281 Portable Filename
|
|
|
|
|
filename = mkOptionType {
|
|
|
|
|
name = "POSIX portable filename";
|
|
|
|
|
check = x: isString x && builtins.match "[0-9A-Za-z._][0-9A-Za-z._-]*" x != null;
|
|
|
|
|
merge = mergeOneOption;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
# POSIX.1‐2017, 3.2 Absolute Pathname
|
|
|
|
|
absolute-pathname = mkOptionType {
|
|
|
|
|
name = "POSIX absolute pathname";
|
|
|
|
|
check = x: isString x && substring 0 1 x == "/" && pathname.check x;
|
|
|
|
|
merge = mergeOneOption;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
# POSIX.1-2017, 3.271 Pathname
|
|
|
|
|
pathname = mkOptionType {
|
|
|
|
|
name = "POSIX pathname";
|
|
|
|
|
check = x:
|
|
|
|
|
let
|
|
|
|
|
# The filter is used to normalize paths, i.e. to remove duplicated and
|
|
|
|
|
# trailing slashes. It also removes leading slashes, thus we have to
|
|
|
|
|
# check for "/" explicitly below.
|
|
|
|
|
xs = filter (s: stringLength s > 0) (splitString "/" x);
|
|
|
|
|
in
|
|
|
|
|
isString x && (x == "/" || (length xs > 0 && all filename.check xs));
|
|
|
|
|
merge = mergeOneOption;
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/* topLevel type of the disko config, takes attrsets of disks mdadms zpools and lvm vgs.
|
|
|
|
|
exports create, mount, meta and config
|
|
|
|
|
*/
|
|
|
|
|
topLevel = types.submodule ({ config, ... }: {
|
|
|
|
|
options = {
|
|
|
|
|
devices = {
|
|
|
|
|
disk = mkOption {
|
|
|
|
|
type = types.attrsOf disk;
|
|
|
|
|
default = {};
|
|
|
|
|
};
|
|
|
|
|
mdadm = mkOption {
|
|
|
|
|
type = types.attrsOf mdadm;
|
|
|
|
|
default = {};
|
|
|
|
|
};
|
|
|
|
|
zpool = mkOption {
|
|
|
|
|
type = types.attrsOf zpool;
|
|
|
|
|
default = {};
|
|
|
|
|
};
|
|
|
|
|
lvm_vg = mkOption {
|
|
|
|
|
type = types.attrsOf lvm_vg;
|
|
|
|
|
default = {};
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
meta = mkOption {
|
|
|
|
|
readOnly = true;
|
|
|
|
|
default = diskoLib.deepMergeMap (dev: dev._meta) (flatten (map attrValues [
|
|
|
|
|
config.devices.disk
|
|
|
|
|
config.devices.lvm_vg
|
|
|
|
|
config.devices.mdadm
|
|
|
|
|
config.devices.zpool
|
|
|
|
|
])) // {
|
|
|
|
|
sortedDeviceList = diskoLib.sortDevicesByDependencies config.meta.dependencies config.devices;
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
create = mkOption {
|
|
|
|
|
readOnly = true;
|
|
|
|
|
type = types.str;
|
|
|
|
|
default = ''
|
|
|
|
|
set -efux
|
|
|
|
|
${concatStrings (map (dev: attrByPath (dev ++ [ "_create" ]) "" config.devices) config.meta.sortedDeviceList)}
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
mount = mkOption {
|
|
|
|
|
readOnly = true;
|
|
|
|
|
type = types.str;
|
|
|
|
|
default = let
|
|
|
|
|
fsMounts = diskoLib.deepMergeMap (dev: dev._mount.fs or {}) (flatten (map attrValues [
|
|
|
|
|
config.devices.disk
|
|
|
|
|
config.devices.lvm_vg
|
|
|
|
|
config.devices.mdadm
|
|
|
|
|
config.devices.zpool
|
|
|
|
|
]));
|
|
|
|
|
in ''
|
|
|
|
|
set -efux
|
|
|
|
|
# first create the neccessary devices
|
|
|
|
|
${concatStrings (map (dev: attrByPath (dev ++ [ "_mount" "dev" ]) "" config.devices) config.meta.sortedDeviceList)}
|
|
|
|
|
|
|
|
|
|
# and then mount the filesystems in alphabetical order
|
|
|
|
|
# attrValues returns values sorted by name. This is important, because it
|
|
|
|
|
# ensures that "/" is processed before "/foo" etc.
|
|
|
|
|
${concatStrings (attrValues fsMounts)}
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
config = mkOption {
|
|
|
|
|
readOnly = true;
|
2022-10-01 19:29:36 +00:00
|
|
|
|
default = { imports = flatten (map (dev: dev._config) (flatten (map attrValues [
|
2022-08-29 09:45:19 +00:00
|
|
|
|
config.devices.disk
|
|
|
|
|
config.devices.lvm_vg
|
|
|
|
|
config.devices.mdadm
|
|
|
|
|
config.devices.zpool
|
2022-10-01 19:29:36 +00:00
|
|
|
|
])));};
|
2022-08-29 09:45:19 +00:00
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
btrfs = types.submodule ({ config, ... }: {
|
|
|
|
|
options = {
|
|
|
|
|
type = mkOption {
|
|
|
|
|
type = types.enum [ "btrfs" ];
|
|
|
|
|
internal = true;
|
|
|
|
|
};
|
|
|
|
|
mountOptions = mkOption {
|
|
|
|
|
type = types.listOf types.str;
|
|
|
|
|
default = [];
|
|
|
|
|
};
|
|
|
|
|
subvolumes = mkOption {
|
|
|
|
|
type = types.listOf optionTypes.pathname;
|
|
|
|
|
default = [];
|
|
|
|
|
};
|
|
|
|
|
mountpoint = mkOption {
|
|
|
|
|
type = optionTypes.absolute-pathname;
|
|
|
|
|
};
|
|
|
|
|
_meta = mkOption {
|
|
|
|
|
internal = true;
|
|
|
|
|
readOnly = true;
|
2022-09-13 11:21:37 +00:00
|
|
|
|
type = types.functionTo diskoLib.jsonType;
|
2022-08-29 09:45:19 +00:00
|
|
|
|
default = dev: {
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
_create = mkOption {
|
|
|
|
|
internal = true;
|
|
|
|
|
readOnly = true;
|
|
|
|
|
type = types.functionTo types.str;
|
|
|
|
|
default = dev: ''
|
|
|
|
|
mkfs.btrfs ${dev}
|
|
|
|
|
${optionalString (!isNull config.subvolumes or null) ''
|
|
|
|
|
MNTPOINT=$(mktemp -d)
|
|
|
|
|
(
|
|
|
|
|
mount ${dev} "$MNTPOINT"
|
|
|
|
|
trap 'umount $MNTPOINT; rm -rf $MNTPOINT' EXIT
|
|
|
|
|
${concatMapStringsSep "\n" (subvolume: "btrfs subvolume create \"$MNTPOINT\"/${subvolume}") config.subvolumes}
|
|
|
|
|
)
|
|
|
|
|
''}
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
_mount = mkOption {
|
|
|
|
|
internal = true;
|
|
|
|
|
readOnly = true;
|
2022-09-13 11:21:37 +00:00
|
|
|
|
type = types.functionTo diskoLib.jsonType;
|
2022-08-29 09:45:19 +00:00
|
|
|
|
default = dev: {
|
|
|
|
|
fs.${config.mountpoint} = ''
|
|
|
|
|
if ! findmnt ${dev} "/mnt${config.mountpoint}" > /dev/null 2>&1; then
|
|
|
|
|
mount ${dev} "/mnt${config.mountpoint}" \
|
|
|
|
|
${concatStringsSep " " config.mountOptions} \
|
|
|
|
|
-o X-mount.mkdir
|
|
|
|
|
fi
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
_config = mkOption {
|
|
|
|
|
internal = true;
|
|
|
|
|
readOnly = true;
|
2022-10-01 19:29:36 +00:00
|
|
|
|
default = dev: [{
|
2022-08-29 09:45:19 +00:00
|
|
|
|
fileSystems.${config.mountpoint} = {
|
|
|
|
|
device = dev;
|
|
|
|
|
fsType = "btrfs";
|
|
|
|
|
};
|
2022-10-01 19:29:36 +00:00
|
|
|
|
}];
|
2022-08-29 09:45:19 +00:00
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
filesystem = types.submodule ({ config, ... }: {
|
|
|
|
|
options = {
|
|
|
|
|
type = mkOption {
|
|
|
|
|
type = types.enum [ "filesystem" ];
|
|
|
|
|
internal = true;
|
|
|
|
|
};
|
|
|
|
|
extraArgs = mkOption {
|
|
|
|
|
type = types.str;
|
|
|
|
|
default = "";
|
|
|
|
|
};
|
|
|
|
|
mountOptions = mkOption {
|
|
|
|
|
type = types.listOf types.str;
|
|
|
|
|
default = [];
|
|
|
|
|
};
|
|
|
|
|
options = mkOption {
|
|
|
|
|
type = types.listOf types.str;
|
|
|
|
|
default = [];
|
|
|
|
|
};
|
|
|
|
|
mountpoint = mkOption {
|
|
|
|
|
type = optionTypes.absolute-pathname;
|
|
|
|
|
};
|
|
|
|
|
format = mkOption {
|
|
|
|
|
type = types.str;
|
|
|
|
|
};
|
|
|
|
|
_meta = mkOption {
|
|
|
|
|
internal = true;
|
|
|
|
|
readOnly = true;
|
2022-09-13 11:21:37 +00:00
|
|
|
|
type = types.functionTo diskoLib.jsonType;
|
2022-08-29 09:45:19 +00:00
|
|
|
|
default = dev: {
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
_create = mkOption {
|
|
|
|
|
internal = true;
|
|
|
|
|
readOnly = true;
|
|
|
|
|
type = types.functionTo types.str;
|
|
|
|
|
default = dev: ''
|
|
|
|
|
mkfs.${config.format} \
|
|
|
|
|
${config.extraArgs} \
|
|
|
|
|
${dev}
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
_mount = mkOption {
|
|
|
|
|
internal = true;
|
|
|
|
|
readOnly = true;
|
2022-09-13 11:21:37 +00:00
|
|
|
|
type = types.functionTo diskoLib.jsonType;
|
2022-08-29 09:45:19 +00:00
|
|
|
|
default = dev: {
|
|
|
|
|
fs.${config.mountpoint} = ''
|
|
|
|
|
if ! findmnt ${dev} "/mnt${config.mountpoint}" > /dev/null 2>&1; then
|
|
|
|
|
mount ${dev} "/mnt${config.mountpoint}" \
|
|
|
|
|
${toString config.mountOptions} \
|
|
|
|
|
-o X-mount.mkdir
|
|
|
|
|
fi
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
_config = mkOption {
|
|
|
|
|
internal = true;
|
|
|
|
|
readOnly = true;
|
2022-10-01 19:29:36 +00:00
|
|
|
|
default = dev: [{
|
2022-08-29 09:45:19 +00:00
|
|
|
|
fileSystems.${config.mountpoint} = {
|
|
|
|
|
device = dev;
|
|
|
|
|
fsType = config.format;
|
|
|
|
|
};
|
2022-10-01 19:29:36 +00:00
|
|
|
|
}];
|
2022-08-29 09:45:19 +00:00
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
table = types.submodule ({ config, ... }: {
|
|
|
|
|
options = {
|
|
|
|
|
type = mkOption {
|
|
|
|
|
type = types.enum [ "table" ];
|
|
|
|
|
internal = true;
|
|
|
|
|
};
|
|
|
|
|
format = mkOption {
|
|
|
|
|
type = types.enum [ "gpt" "msdos" ];
|
|
|
|
|
default = "gpt";
|
|
|
|
|
};
|
|
|
|
|
partitions = mkOption {
|
|
|
|
|
type = types.listOf partition;
|
|
|
|
|
default = [];
|
|
|
|
|
};
|
|
|
|
|
_meta = mkOption {
|
|
|
|
|
internal = true;
|
|
|
|
|
readOnly = true;
|
2022-09-13 11:21:37 +00:00
|
|
|
|
type = types.functionTo diskoLib.jsonType;
|
2022-08-29 09:45:19 +00:00
|
|
|
|
default = dev:
|
|
|
|
|
diskoLib.deepMergeMap (partition: partition._meta dev) config.partitions;
|
|
|
|
|
};
|
|
|
|
|
_create = mkOption {
|
|
|
|
|
internal = true;
|
|
|
|
|
readOnly = true;
|
|
|
|
|
type = types.functionTo types.str;
|
|
|
|
|
default = dev: ''
|
|
|
|
|
parted -s ${dev} mklabel ${config.format}
|
|
|
|
|
${concatMapStrings (partition: partition._create dev config.format) config.partitions}
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
_mount = mkOption {
|
|
|
|
|
internal = true;
|
|
|
|
|
readOnly = true;
|
2022-09-13 11:21:37 +00:00
|
|
|
|
type = types.functionTo diskoLib.jsonType;
|
2022-08-29 09:45:19 +00:00
|
|
|
|
default = dev:
|
|
|
|
|
let
|
|
|
|
|
partMounts = diskoLib.deepMergeMap (partition: partition._mount dev) config.partitions;
|
|
|
|
|
in {
|
|
|
|
|
dev = ''
|
|
|
|
|
${concatStrings (map (x: x.dev or "") (attrValues partMounts))}
|
|
|
|
|
'';
|
|
|
|
|
fs = partMounts.fs or {};
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
_config = mkOption {
|
|
|
|
|
internal = true;
|
|
|
|
|
readOnly = true;
|
|
|
|
|
default = dev:
|
2022-10-01 19:29:36 +00:00
|
|
|
|
map (partition: partition._config dev) config.partitions;
|
2022-08-29 09:45:19 +00:00
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
partition = types.submodule ({ config, ... }: {
|
|
|
|
|
options = {
|
|
|
|
|
type = mkOption {
|
|
|
|
|
type = types.enum [ "partition" ];
|
|
|
|
|
internal = true;
|
|
|
|
|
};
|
|
|
|
|
part-type = mkOption {
|
|
|
|
|
type = types.enum [ "primary" "logical" "extended" ];
|
|
|
|
|
default = "primary";
|
|
|
|
|
};
|
|
|
|
|
fs-type = mkOption {
|
|
|
|
|
type = types.nullOr (types.enum [ "btrfs" "ext2" "ext3" "ext4" "fat16" "fat32" "hfs" "hfs+" "linux-swap" "ntfs" "reiserfs" "udf" "xfs" ]);
|
|
|
|
|
default = null;
|
|
|
|
|
};
|
|
|
|
|
name = mkOption {
|
|
|
|
|
type = types.nullOr types.str;
|
|
|
|
|
};
|
|
|
|
|
start = mkOption {
|
|
|
|
|
type = types.str;
|
|
|
|
|
default = "0%";
|
|
|
|
|
};
|
|
|
|
|
end = mkOption {
|
|
|
|
|
type = types.str;
|
|
|
|
|
default = "100%";
|
|
|
|
|
};
|
|
|
|
|
index = mkOption {
|
|
|
|
|
type = types.int;
|
|
|
|
|
# TODO find a better way to get the index
|
|
|
|
|
default = toInt (head (match ".*entry ([[:digit:]]+)]" config._module.args.name));
|
|
|
|
|
};
|
|
|
|
|
flags = mkOption {
|
|
|
|
|
type = types.listOf types.str;
|
|
|
|
|
default = [];
|
|
|
|
|
};
|
|
|
|
|
bootable = mkOption {
|
|
|
|
|
type = types.bool;
|
|
|
|
|
default = false;
|
|
|
|
|
};
|
|
|
|
|
content = diskoLib.partitionType;
|
|
|
|
|
_meta = mkOption {
|
|
|
|
|
internal = true;
|
|
|
|
|
readOnly = true;
|
2022-09-13 11:21:37 +00:00
|
|
|
|
type = types.functionTo diskoLib.jsonType;
|
2022-08-29 09:45:19 +00:00
|
|
|
|
default = dev:
|
|
|
|
|
optionalAttrs (!isNull config.content) (config.content._meta dev);
|
|
|
|
|
};
|
|
|
|
|
_create = mkOption {
|
|
|
|
|
internal = true;
|
|
|
|
|
readOnly = true;
|
|
|
|
|
type = types.functionTo (types.functionTo types.str);
|
|
|
|
|
default = dev: type: ''
|
|
|
|
|
${optionalString (type == "gpt") ''
|
|
|
|
|
parted -s ${dev} mkpart ${config.name} ${diskoLib.maybeStr config.fs-type} ${config.start} ${config.end}
|
|
|
|
|
''}
|
|
|
|
|
${optionalString (type == "msdos") ''
|
|
|
|
|
parted -s ${dev} mkpart ${config.part-type} ${diskoLib.maybeStr config.fs-type} ${diskoLib.maybeStr config.fs-type} ${config.start} ${config.end}
|
|
|
|
|
''}
|
|
|
|
|
# ensure /dev/disk/by-path/..-partN exists before continuing
|
|
|
|
|
udevadm trigger --subsystem-match=block; udevadm settle
|
|
|
|
|
${optionalString (config.bootable) ''
|
|
|
|
|
parted -s ${dev} set ${toString config.index} boot on
|
|
|
|
|
''}
|
|
|
|
|
${concatMapStringsSep "" (flag: ''
|
|
|
|
|
parted -s ${dev} set ${toString config.index} ${flag} on
|
|
|
|
|
'') config.flags}
|
2022-09-12 10:32:35 +00:00
|
|
|
|
# ensure further operations can detect new partitions
|
|
|
|
|
udevadm trigger --subsystem-match=block; udevadm settle
|
2022-08-29 09:45:19 +00:00
|
|
|
|
${optionalString (!isNull config.content) (config.content._create (diskoLib.deviceNumbering dev config.index))}
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
_mount = mkOption {
|
|
|
|
|
internal = true;
|
|
|
|
|
readOnly = true;
|
2022-09-13 11:21:37 +00:00
|
|
|
|
type = types.functionTo diskoLib.jsonType;
|
2022-08-29 09:45:19 +00:00
|
|
|
|
default = dev:
|
|
|
|
|
optionalAttrs (!isNull config.content) (config.content._mount (diskoLib.deviceNumbering dev config.index));
|
|
|
|
|
};
|
|
|
|
|
_config = mkOption {
|
|
|
|
|
internal = true;
|
|
|
|
|
readOnly = true;
|
|
|
|
|
default = dev:
|
2022-10-01 19:29:36 +00:00
|
|
|
|
optional (!isNull config.content) (config.content._config (diskoLib.deviceNumbering dev config.index));
|
2022-08-29 09:45:19 +00:00
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
lvm_pv = types.submodule ({ config, ... }: {
|
|
|
|
|
options = {
|
|
|
|
|
type = mkOption {
|
|
|
|
|
type = types.enum [ "lvm_pv" ];
|
|
|
|
|
internal = true;
|
|
|
|
|
};
|
|
|
|
|
vg = mkOption {
|
|
|
|
|
type = types.str;
|
|
|
|
|
};
|
|
|
|
|
_meta = mkOption {
|
|
|
|
|
internal = true;
|
|
|
|
|
readOnly = true;
|
2022-09-13 11:21:37 +00:00
|
|
|
|
type = types.functionTo diskoLib.jsonType;
|
2022-08-29 09:45:19 +00:00
|
|
|
|
default = dev: {
|
|
|
|
|
dependencies.lvm_vg.${config.vg} = [ dev ];
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
_create = mkOption {
|
|
|
|
|
internal = true;
|
|
|
|
|
readOnly = true;
|
|
|
|
|
type = types.functionTo types.str;
|
|
|
|
|
default = dev: ''
|
|
|
|
|
pvcreate ${dev}
|
|
|
|
|
LVMDEVICES_${config.vg}="''${LVMDEVICES_${config.vg}:-}${dev} "
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
_mount = mkOption {
|
|
|
|
|
internal = true;
|
|
|
|
|
readOnly = true;
|
2022-09-13 11:21:37 +00:00
|
|
|
|
type = types.functionTo diskoLib.jsonType;
|
2022-08-29 09:45:19 +00:00
|
|
|
|
default = dev:
|
|
|
|
|
{};
|
|
|
|
|
};
|
|
|
|
|
_config = mkOption {
|
|
|
|
|
internal = true;
|
|
|
|
|
readOnly = true;
|
2022-10-01 19:29:36 +00:00
|
|
|
|
default = dev: [];
|
2022-08-29 09:45:19 +00:00
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
lvm_vg = types.submodule ({ config, ... }: {
|
|
|
|
|
options = {
|
|
|
|
|
name = mkOption {
|
|
|
|
|
type = types.str;
|
|
|
|
|
default = config._module.args.name;
|
|
|
|
|
};
|
|
|
|
|
type = mkOption {
|
|
|
|
|
type = types.enum [ "lvm_vg" ];
|
|
|
|
|
internal = true;
|
|
|
|
|
};
|
|
|
|
|
lvs = mkOption {
|
|
|
|
|
type = types.attrsOf lvm_lv;
|
|
|
|
|
default = {};
|
|
|
|
|
};
|
|
|
|
|
_meta = mkOption {
|
|
|
|
|
internal = true;
|
|
|
|
|
readOnly = true;
|
2022-09-13 11:21:37 +00:00
|
|
|
|
type = diskoLib.jsonType;
|
2022-08-29 09:45:19 +00:00
|
|
|
|
default =
|
|
|
|
|
diskoLib.deepMergeMap (lv: lv._meta [ "lvm_vg" config.name ]) (attrValues config.lvs);
|
|
|
|
|
};
|
|
|
|
|
_create = mkOption {
|
|
|
|
|
internal = true;
|
|
|
|
|
readOnly = true;
|
|
|
|
|
type = types.str;
|
|
|
|
|
default = ''
|
|
|
|
|
vgcreate ${config.name} $LVMDEVICES_${config.name}
|
|
|
|
|
${concatMapStrings (lv: lv._create config.name) (attrValues config.lvs)}
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
_mount = mkOption {
|
|
|
|
|
internal = true;
|
|
|
|
|
readOnly = true;
|
2022-09-13 11:21:37 +00:00
|
|
|
|
type = diskoLib.jsonType;
|
2022-08-29 09:45:19 +00:00
|
|
|
|
default = let
|
|
|
|
|
lvMounts = diskoLib.deepMergeMap (lv: lv._mount config.name) (attrValues config.lvs);
|
|
|
|
|
in {
|
|
|
|
|
dev = ''
|
|
|
|
|
vgchange -a y
|
|
|
|
|
${concatStrings (map (x: x.dev or "") (attrValues lvMounts))}
|
|
|
|
|
'';
|
|
|
|
|
fs = lvMounts.fs;
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
_config = mkOption {
|
|
|
|
|
internal = true;
|
|
|
|
|
readOnly = true;
|
|
|
|
|
default =
|
2022-10-01 19:29:36 +00:00
|
|
|
|
map (lv: lv._config config.name) (attrValues config.lvs);
|
2022-08-29 09:45:19 +00:00
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
lvm_lv = types.submodule ({ config, ... }: {
|
|
|
|
|
options = {
|
|
|
|
|
name = mkOption {
|
|
|
|
|
type = types.str;
|
|
|
|
|
default = config._module.args.name;
|
|
|
|
|
};
|
|
|
|
|
type = mkOption {
|
|
|
|
|
type = types.enum [ "lvm_lv" ];
|
|
|
|
|
default = "lvm_lv";
|
|
|
|
|
internal = true;
|
|
|
|
|
};
|
|
|
|
|
size = mkOption {
|
|
|
|
|
type = types.str; # TODO lvm size type
|
|
|
|
|
};
|
|
|
|
|
lvm_type = mkOption {
|
|
|
|
|
type = types.nullOr (types.enum [ "mirror" "raid0" "raid1" ]); # TODO add all types
|
|
|
|
|
default = null; # maybe there is always a default type?
|
|
|
|
|
};
|
|
|
|
|
extraArgs = mkOption {
|
|
|
|
|
type = types.str;
|
|
|
|
|
default = "";
|
|
|
|
|
};
|
|
|
|
|
content = diskoLib.partitionType;
|
|
|
|
|
_meta = mkOption {
|
|
|
|
|
internal = true;
|
|
|
|
|
readOnly = true;
|
2022-09-13 11:21:37 +00:00
|
|
|
|
type = types.functionTo diskoLib.jsonType;
|
2022-08-29 09:45:19 +00:00
|
|
|
|
default = dev:
|
|
|
|
|
optionalAttrs (!isNull config.content) (config.content._meta dev);
|
|
|
|
|
};
|
|
|
|
|
_create = mkOption {
|
|
|
|
|
internal = true;
|
|
|
|
|
readOnly = true;
|
|
|
|
|
type = types.functionTo types.str;
|
|
|
|
|
default = vg: ''
|
|
|
|
|
lvcreate \
|
|
|
|
|
${if hasInfix "%" config.size then "-l" else "-L"} ${config.size} \
|
|
|
|
|
-n ${config.name} \
|
|
|
|
|
${optionalString (!isNull config.lvm_type) "--type=${config.lvm_type}"} \
|
|
|
|
|
${config.extraArgs} \
|
|
|
|
|
${vg}
|
|
|
|
|
${optionalString (!isNull config.content) (config.content._create "/dev/${vg}/${config.name}")}
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
_mount = mkOption {
|
|
|
|
|
internal = true;
|
|
|
|
|
readOnly = true;
|
2022-09-13 11:21:37 +00:00
|
|
|
|
type = types.functionTo diskoLib.jsonType;
|
2022-08-29 09:45:19 +00:00
|
|
|
|
default = vg:
|
|
|
|
|
optionalAttrs (!isNull config.content) (config.content._mount "/dev/${vg}/${config.name}");
|
|
|
|
|
};
|
|
|
|
|
_config = mkOption {
|
|
|
|
|
internal = true;
|
|
|
|
|
readOnly = true;
|
2022-09-30 17:50:32 +00:00
|
|
|
|
default = vg:
|
2022-10-01 19:29:36 +00:00
|
|
|
|
[
|
|
|
|
|
(optional (!isNull config.content) (config.content._config "/dev/${vg}/${config.name}"))
|
|
|
|
|
(optional (!isNull config.lvm_type) {
|
|
|
|
|
boot.initrd.kernelModules = [ "dm-${config.lvm_type}" ];
|
|
|
|
|
})
|
|
|
|
|
];
|
2022-08-29 09:45:19 +00:00
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
zfs = types.submodule ({ config, ... }: {
|
|
|
|
|
options = {
|
|
|
|
|
type = mkOption {
|
|
|
|
|
type = types.enum [ "zfs" ];
|
|
|
|
|
internal = true;
|
|
|
|
|
};
|
|
|
|
|
pool = mkOption {
|
|
|
|
|
type = types.str;
|
|
|
|
|
};
|
|
|
|
|
_meta = mkOption {
|
|
|
|
|
internal = true;
|
|
|
|
|
readOnly = true;
|
2022-09-13 11:21:37 +00:00
|
|
|
|
type = types.functionTo diskoLib.jsonType;
|
2022-08-29 09:45:19 +00:00
|
|
|
|
default = dev: {
|
|
|
|
|
dependencies.zpool.${config.pool} = [ dev ];
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
_create = mkOption {
|
|
|
|
|
internal = true;
|
|
|
|
|
readOnly = true;
|
|
|
|
|
type = types.functionTo types.str;
|
|
|
|
|
default = dev: ''
|
|
|
|
|
ZFSDEVICES_${config.pool}="''${ZFSDEVICES_${config.pool}:-}${dev} "
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
_mount = mkOption {
|
|
|
|
|
internal = true;
|
|
|
|
|
readOnly = true;
|
2022-09-13 11:21:37 +00:00
|
|
|
|
type = types.functionTo diskoLib.jsonType;
|
2022-08-29 09:45:19 +00:00
|
|
|
|
default = dev:
|
|
|
|
|
{};
|
|
|
|
|
};
|
|
|
|
|
_config = mkOption {
|
|
|
|
|
internal = true;
|
|
|
|
|
readOnly = true;
|
2022-10-01 19:29:36 +00:00
|
|
|
|
default = dev: [];
|
2022-08-29 09:45:19 +00:00
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
zpool = types.submodule ({ config, ... }: {
|
|
|
|
|
options = {
|
|
|
|
|
name = mkOption {
|
|
|
|
|
type = types.str;
|
|
|
|
|
default = config._module.args.name;
|
|
|
|
|
};
|
|
|
|
|
type = mkOption {
|
|
|
|
|
type = types.enum [ "zpool" ];
|
|
|
|
|
internal = true;
|
|
|
|
|
};
|
|
|
|
|
mode = mkOption {
|
|
|
|
|
type = types.str; # TODO zfs modes
|
|
|
|
|
default = "";
|
|
|
|
|
};
|
|
|
|
|
options = mkOption {
|
|
|
|
|
type = types.attrsOf types.str;
|
|
|
|
|
default = {};
|
|
|
|
|
};
|
|
|
|
|
rootFsOptions = mkOption {
|
|
|
|
|
type = types.attrsOf types.str;
|
|
|
|
|
default = {};
|
|
|
|
|
};
|
|
|
|
|
mountpoint = mkOption {
|
|
|
|
|
type = types.nullOr optionTypes.absolute-pathname;
|
|
|
|
|
default = null;
|
|
|
|
|
};
|
|
|
|
|
mountOptions = mkOption {
|
|
|
|
|
type = types.listOf types.str;
|
|
|
|
|
default = [];
|
|
|
|
|
};
|
|
|
|
|
datasets = mkOption {
|
|
|
|
|
type = types.attrsOf zfs_dataset;
|
|
|
|
|
};
|
|
|
|
|
_meta = mkOption {
|
|
|
|
|
internal = true;
|
|
|
|
|
readOnly = true;
|
2022-09-13 11:21:37 +00:00
|
|
|
|
type = diskoLib.jsonType;
|
2022-08-29 09:45:19 +00:00
|
|
|
|
default =
|
|
|
|
|
diskoLib.deepMergeMap (dataset: dataset._meta [ "zpool" config.name ]) (attrValues config.datasets);
|
|
|
|
|
};
|
|
|
|
|
_create = mkOption {
|
|
|
|
|
internal = true;
|
|
|
|
|
readOnly = true;
|
|
|
|
|
type = types.str;
|
|
|
|
|
default = ''
|
|
|
|
|
zpool create ${config.name} \
|
|
|
|
|
${config.mode} \
|
|
|
|
|
${concatStringsSep " " (mapAttrsToList (n: v: "-o ${n}=${v}") config.options)} \
|
|
|
|
|
${concatStringsSep " " (mapAttrsToList (n: v: "-O ${n}=${v}") config.rootFsOptions)} \
|
|
|
|
|
''${ZFSDEVICES_${config.name}}
|
|
|
|
|
${concatMapStrings (dataset: dataset._create config.name) (attrValues config.datasets)}
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
_mount = mkOption {
|
|
|
|
|
internal = true;
|
|
|
|
|
readOnly = true;
|
2022-09-13 11:21:37 +00:00
|
|
|
|
type = diskoLib.jsonType;
|
2022-08-29 09:45:19 +00:00
|
|
|
|
default = let
|
|
|
|
|
datasetMounts = diskoLib.deepMergeMap (dataset: dataset._mount config.name) (attrValues config.datasets);
|
|
|
|
|
in {
|
|
|
|
|
dev = ''
|
|
|
|
|
zpool list '${config.name}' >/dev/null 2>/dev/null || zpool import '${config.name}'
|
|
|
|
|
${concatStrings (map (x: x.dev or "") (attrValues datasetMounts))}
|
|
|
|
|
'';
|
|
|
|
|
fs = datasetMounts.fs // optionalAttrs (!isNull config.mountpoint) {
|
|
|
|
|
${config.mountpoint} = ''
|
|
|
|
|
if ! findmnt ${config.name} "/mnt${config.mountpoint}" > /dev/null 2>&1; then
|
|
|
|
|
mount ${config.name} "/mnt${config.mountpoint}" \
|
|
|
|
|
${optionalString ((config.options.mountpoint or "") != "legacy") "-o zfsutil"} \
|
|
|
|
|
${toString config.mountOptions} \
|
|
|
|
|
-o X-mount.mkdir \
|
|
|
|
|
-t zfs
|
|
|
|
|
fi
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
_config = mkOption {
|
|
|
|
|
internal = true;
|
|
|
|
|
readOnly = true;
|
|
|
|
|
default =
|
2022-10-01 19:29:36 +00:00
|
|
|
|
[
|
|
|
|
|
(map (dataset: dataset._config config.name) (attrValues config.datasets))
|
|
|
|
|
(optional (!isNull config.mountpoint) {
|
2022-08-29 09:45:19 +00:00
|
|
|
|
fileSystems.${config.mountpoint} = {
|
|
|
|
|
device = config.name;
|
2022-09-05 15:23:17 +00:00
|
|
|
|
fsType = "zfs";
|
2022-09-05 15:26:48 +00:00
|
|
|
|
options = lib.optional ((config.options.mountpoint or "") != "legacy") "zfsutil";
|
2022-08-29 09:45:19 +00:00
|
|
|
|
};
|
2022-10-01 19:29:36 +00:00
|
|
|
|
})
|
|
|
|
|
];
|
2022-08-29 09:45:19 +00:00
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
zfs_dataset = types.submodule ({ config, ... }: {
|
|
|
|
|
options = {
|
|
|
|
|
name = mkOption {
|
|
|
|
|
type = types.str;
|
|
|
|
|
default = config._module.args.name;
|
|
|
|
|
};
|
|
|
|
|
type = mkOption {
|
|
|
|
|
type = types.enum [ "zfs_dataset" ];
|
|
|
|
|
default = "zfs_dataset";
|
2022-09-04 09:46:37 +00:00
|
|
|
|
internal = true;
|
2022-08-29 09:45:19 +00:00
|
|
|
|
};
|
|
|
|
|
zfs_type = mkOption {
|
|
|
|
|
type = types.enum [ "filesystem" "volume" ];
|
|
|
|
|
};
|
|
|
|
|
options = mkOption {
|
|
|
|
|
type = types.attrsOf types.str;
|
|
|
|
|
default = {};
|
|
|
|
|
};
|
|
|
|
|
mountOptions = mkOption {
|
|
|
|
|
type = types.listOf types.str;
|
|
|
|
|
default = [];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
# filesystem options
|
|
|
|
|
mountpoint = mkOption {
|
|
|
|
|
type = types.nullOr optionTypes.absolute-pathname;
|
|
|
|
|
default = null;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
# volume options
|
|
|
|
|
size = mkOption {
|
|
|
|
|
type = types.nullOr types.str; # TODO size
|
|
|
|
|
default = null;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
content = diskoLib.partitionType;
|
|
|
|
|
_meta = mkOption {
|
|
|
|
|
internal = true;
|
|
|
|
|
readOnly = true;
|
2022-09-13 11:21:37 +00:00
|
|
|
|
type = types.functionTo diskoLib.jsonType;
|
2022-08-29 09:45:19 +00:00
|
|
|
|
default = dev:
|
|
|
|
|
optionalAttrs (!isNull config.content) (config.content._meta dev);
|
|
|
|
|
};
|
|
|
|
|
_create = mkOption {
|
|
|
|
|
internal = true;
|
|
|
|
|
readOnly = true;
|
|
|
|
|
type = types.functionTo types.str;
|
|
|
|
|
default = zpool: ''
|
|
|
|
|
zfs create ${zpool}/${config.name} \
|
|
|
|
|
${concatStringsSep " " (mapAttrsToList (n: v: "-o ${n}=${v}") config.options)} \
|
|
|
|
|
${optionalString (config.zfs_type == "volume") "-V ${config.size}"}
|
|
|
|
|
${optionalString (config.zfs_type == "volume") ''
|
|
|
|
|
udevadm trigger --subsystem-match=block; udevadm settle
|
|
|
|
|
${optionalString (!isNull config.content) (config.content._create "/dev/zvol/${zpool}/${config.name}")}
|
|
|
|
|
''}
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
_mount = mkOption {
|
|
|
|
|
internal = true;
|
|
|
|
|
readOnly = true;
|
2022-09-13 11:21:37 +00:00
|
|
|
|
type = types.functionTo diskoLib.jsonType;
|
2022-08-29 09:45:19 +00:00
|
|
|
|
default = zpool:
|
|
|
|
|
optionalAttrs (config.zfs_type == "volume" && !isNull config.content) (config.content._mount "/dev/zvol/${zpool}/${config.name}") //
|
|
|
|
|
optionalAttrs (config.zfs_type == "filesystem" && config.options.mountpoint or "" != "none") { fs.${config.mountpoint} = ''
|
|
|
|
|
if ! findmnt ${zpool}/${config.name} "/mnt${config.mountpoint}" > /dev/null 2>&1; then
|
|
|
|
|
mount ${zpool}/${config.name} "/mnt${config.mountpoint}" \
|
|
|
|
|
-o X-mount.mkdir \
|
|
|
|
|
${toString config.mountOptions} \
|
|
|
|
|
${optionalString ((config.options.mountpoint or "") != "legacy") "-o zfsutil"} \
|
|
|
|
|
-t zfs
|
|
|
|
|
fi
|
|
|
|
|
''; };
|
|
|
|
|
};
|
|
|
|
|
_config = mkOption {
|
|
|
|
|
internal = true;
|
|
|
|
|
readOnly = true;
|
|
|
|
|
default = zpool:
|
2022-10-01 19:29:36 +00:00
|
|
|
|
(optional (config.zfs_type == "volume" && !isNull config.content) (config.content._config "/dev/zvol/${zpool}/${config.name}")) ++
|
|
|
|
|
(optional (config.zfs_type == "filesystem" && config.options.mountpoint or "" != "none") {
|
|
|
|
|
fileSystems.${config.mountpoint} = {
|
|
|
|
|
device = "${zpool}/${config.name}";
|
|
|
|
|
fsType = "zfs";
|
|
|
|
|
options = lib.optional ((config.options.mountpoint or "") != "legacy") "zfsutil";
|
2022-08-29 09:45:19 +00:00
|
|
|
|
};
|
2022-10-01 19:29:36 +00:00
|
|
|
|
});
|
2022-08-29 09:45:19 +00:00
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
mdadm = types.submodule ({ config, ... }: {
|
|
|
|
|
options = {
|
|
|
|
|
name = mkOption {
|
|
|
|
|
type = types.str;
|
|
|
|
|
default = config._module.args.name;
|
|
|
|
|
};
|
|
|
|
|
type = mkOption {
|
|
|
|
|
type = types.enum [ "mdadm" ];
|
|
|
|
|
default = "mdadm";
|
2022-09-04 09:46:37 +00:00
|
|
|
|
internal = true;
|
2022-08-29 09:45:19 +00:00
|
|
|
|
};
|
|
|
|
|
level = mkOption {
|
|
|
|
|
type = types.int;
|
|
|
|
|
default = 1;
|
|
|
|
|
};
|
2022-10-01 17:03:42 +00:00
|
|
|
|
metadata = mkOption {
|
|
|
|
|
type = types.enum [ "1" "1.0" "1.1" "1.2" "default" "ddf" "imsm" ];
|
|
|
|
|
default = "default";
|
|
|
|
|
};
|
2022-08-29 09:45:19 +00:00
|
|
|
|
content = diskoLib.deviceType;
|
|
|
|
|
_meta = mkOption {
|
|
|
|
|
internal = true;
|
|
|
|
|
readOnly = true;
|
2022-09-13 11:21:37 +00:00
|
|
|
|
type = diskoLib.jsonType;
|
2022-08-29 09:45:19 +00:00
|
|
|
|
default =
|
|
|
|
|
optionalAttrs (!isNull config.content) (config.content._meta [ "mdadm" config.name ]);
|
|
|
|
|
};
|
|
|
|
|
_create = mkOption {
|
|
|
|
|
internal = true;
|
|
|
|
|
readOnly = true;
|
|
|
|
|
type = types.str;
|
|
|
|
|
default = ''
|
|
|
|
|
echo 'y' | mdadm --create /dev/md/${config.name} \
|
|
|
|
|
--level=${toString config.level} \
|
|
|
|
|
--raid-devices=''${RAIDDEVICES_N_${config.name}} \
|
2022-10-01 17:03:42 +00:00
|
|
|
|
--metadata=${config.metadata} \
|
|
|
|
|
--homehost=any \
|
2022-08-29 09:45:19 +00:00
|
|
|
|
''${RAIDDEVICES_${config.name}}
|
|
|
|
|
udevadm trigger --subsystem-match=block; udevadm settle
|
|
|
|
|
${optionalString (!isNull config.content) (config.content._create "/dev/md/${config.name}")}
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
_mount = mkOption {
|
|
|
|
|
internal = true;
|
|
|
|
|
readOnly = true;
|
2022-09-13 11:21:37 +00:00
|
|
|
|
type = diskoLib.jsonType;
|
2022-08-29 09:45:19 +00:00
|
|
|
|
default =
|
|
|
|
|
optionalAttrs (!isNull config.content) (config.content._mount "/dev/md/${config.name}");
|
|
|
|
|
# TODO we probably need to assemble the mdadm somehow
|
|
|
|
|
};
|
|
|
|
|
_config = mkOption {
|
|
|
|
|
internal = true;
|
|
|
|
|
readOnly = true;
|
|
|
|
|
default =
|
2022-10-01 19:29:36 +00:00
|
|
|
|
optional (!isNull config.content) (config.content._config "/dev/md/${config.name}");
|
2022-08-29 09:45:19 +00:00
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
mdraid = types.submodule ({ config, ... }: {
|
|
|
|
|
options = {
|
|
|
|
|
type = mkOption {
|
|
|
|
|
type = types.enum [ "mdraid" ];
|
2022-09-04 09:46:37 +00:00
|
|
|
|
internal = true;
|
2022-08-29 09:45:19 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
name = mkOption {
|
|
|
|
|
type = types.str;
|
|
|
|
|
};
|
|
|
|
|
_meta = mkOption {
|
|
|
|
|
internal = true;
|
|
|
|
|
readOnly = true;
|
2022-09-13 11:21:37 +00:00
|
|
|
|
type = types.functionTo diskoLib.jsonType;
|
2022-08-29 09:45:19 +00:00
|
|
|
|
default = dev: {
|
|
|
|
|
dependencies.mdadm.${config.name} = [ dev ];
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
_create = mkOption {
|
|
|
|
|
internal = true;
|
|
|
|
|
readOnly = true;
|
|
|
|
|
type = types.functionTo types.str;
|
|
|
|
|
default = dev: ''
|
|
|
|
|
RAIDDEVICES_N_${config.name}=$((''${RAIDDEVICES_N_${config.name}:-0}+1))
|
|
|
|
|
RAIDDEVICES_${config.name}="''${RAIDDEVICES_${config.name}:-}${dev} "
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
_mount = mkOption {
|
|
|
|
|
internal = true;
|
|
|
|
|
readOnly = true;
|
2022-09-13 11:21:37 +00:00
|
|
|
|
type = types.functionTo diskoLib.jsonType;
|
2022-08-29 09:45:19 +00:00
|
|
|
|
default = dev:
|
|
|
|
|
{};
|
|
|
|
|
};
|
|
|
|
|
_config = mkOption {
|
|
|
|
|
internal = true;
|
|
|
|
|
readOnly = true;
|
2022-10-01 19:29:36 +00:00
|
|
|
|
default = dev: [];
|
2022-08-29 09:45:19 +00:00
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
luks = types.submodule ({ config, ... }: {
|
|
|
|
|
options = {
|
|
|
|
|
type = mkOption {
|
|
|
|
|
type = types.enum [ "luks" ];
|
2022-09-04 09:46:37 +00:00
|
|
|
|
internal = true;
|
2022-08-29 09:45:19 +00:00
|
|
|
|
};
|
|
|
|
|
name = mkOption {
|
|
|
|
|
type = types.str;
|
|
|
|
|
};
|
|
|
|
|
keyFile = mkOption {
|
|
|
|
|
type = types.nullOr optionTypes.absolute-pathname;
|
|
|
|
|
default = null;
|
|
|
|
|
};
|
|
|
|
|
extraArgs = mkOption {
|
|
|
|
|
type = types.listOf types.str;
|
|
|
|
|
default = [];
|
|
|
|
|
};
|
|
|
|
|
content = diskoLib.deviceType;
|
|
|
|
|
_meta = mkOption {
|
|
|
|
|
internal = true;
|
|
|
|
|
readOnly = true;
|
2022-09-13 11:21:37 +00:00
|
|
|
|
type = types.functionTo diskoLib.jsonType;
|
2022-08-29 09:45:19 +00:00
|
|
|
|
default = dev:
|
|
|
|
|
optionalAttrs (!isNull config.content) (config.content._meta dev);
|
|
|
|
|
};
|
|
|
|
|
_create = mkOption {
|
|
|
|
|
internal = true;
|
|
|
|
|
readOnly = true;
|
|
|
|
|
type = types.functionTo types.str;
|
|
|
|
|
default = dev: ''
|
|
|
|
|
cryptsetup -q luksFormat ${dev} ${diskoLib.maybeStr config.keyFile} ${toString config.extraArgs}
|
|
|
|
|
cryptsetup luksOpen ${dev} ${config.name} ${optionalString (!isNull config.keyFile) "--key-file ${config.keyFile}"}
|
|
|
|
|
${optionalString (!isNull config.content) (config.content._create "/dev/mapper/${config.name}")}
|
|
|
|
|
'';
|
|
|
|
|
};
|
|
|
|
|
_mount = mkOption {
|
|
|
|
|
internal = true;
|
|
|
|
|
readOnly = true;
|
2022-09-13 11:21:37 +00:00
|
|
|
|
type = types.functionTo diskoLib.jsonType;
|
2022-08-29 09:45:19 +00:00
|
|
|
|
default = dev:
|
|
|
|
|
let
|
|
|
|
|
contentMount = config.content._mount "/dev/mapper/${config.name}";
|
|
|
|
|
in
|
|
|
|
|
{
|
|
|
|
|
dev = ''
|
|
|
|
|
cryptsetup status ${config.name} >/dev/null 2>/dev/null ||
|
|
|
|
|
cryptsetup luksOpen ${dev} ${config.name} ${optionalString (!isNull config.keyFile) "--key-file ${config.keyFile}"}
|
|
|
|
|
${optionalString (!isNull config.content) contentMount.dev or ""}
|
|
|
|
|
'';
|
|
|
|
|
fs = optionalAttrs (!isNull config.content) contentMount.fs or {};
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
_config = mkOption {
|
|
|
|
|
internal = true;
|
|
|
|
|
readOnly = true;
|
|
|
|
|
default = dev:
|
2022-10-01 19:29:36 +00:00
|
|
|
|
[
|
2022-08-29 09:45:19 +00:00
|
|
|
|
# TODO do we need this always in initrd and only there?
|
2022-10-01 19:29:36 +00:00
|
|
|
|
{ boot.initrd.luks.devices.${config.name}.device = dev; }
|
|
|
|
|
] ++ (optional (!isNull config.content) (config.content._config "/dev/mapper/${config.name}"));
|
2022-08-29 09:45:19 +00:00
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
disk = types.submodule ({ config, ... }: {
|
|
|
|
|
options = {
|
|
|
|
|
name = mkOption {
|
|
|
|
|
type = types.str;
|
|
|
|
|
default = config._module.args.name;
|
|
|
|
|
};
|
|
|
|
|
type = mkOption {
|
|
|
|
|
type = types.enum [ "disk" ];
|
|
|
|
|
};
|
|
|
|
|
device = mkOption {
|
|
|
|
|
type = optionTypes.absolute-pathname; # TODO check if subpath of /dev ?
|
|
|
|
|
};
|
|
|
|
|
content = diskoLib.deviceType;
|
|
|
|
|
_meta = mkOption {
|
|
|
|
|
internal = true;
|
|
|
|
|
readOnly = true;
|
2022-09-13 11:21:37 +00:00
|
|
|
|
type = diskoLib.jsonType;
|
2022-08-29 09:45:19 +00:00
|
|
|
|
default =
|
|
|
|
|
optionalAttrs (!isNull config.content) (config.content._meta [ "disk" config.device ]);
|
|
|
|
|
};
|
|
|
|
|
_create = mkOption {
|
|
|
|
|
internal = true;
|
|
|
|
|
readOnly = true;
|
|
|
|
|
type = types.str;
|
|
|
|
|
default = config.content._create config.device;
|
|
|
|
|
};
|
|
|
|
|
_mount = mkOption {
|
|
|
|
|
internal = true;
|
|
|
|
|
readOnly = true;
|
2022-09-13 11:21:37 +00:00
|
|
|
|
type = diskoLib.jsonType;
|
2022-08-29 09:45:19 +00:00
|
|
|
|
default =
|
|
|
|
|
optionalAttrs (!isNull config.content) (config.content._mount config.device);
|
|
|
|
|
};
|
|
|
|
|
_config = mkOption {
|
|
|
|
|
internal = true;
|
|
|
|
|
readOnly = true;
|
|
|
|
|
default =
|
2022-10-01 19:29:36 +00:00
|
|
|
|
optional (!isNull config.content) (config.content._config config.device);
|
2022-08-29 09:45:19 +00:00
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
}
|