add diskco pkg
This commit is contained in:
841
pkgs/disko/lib/default.nix
Normal file
841
pkgs/disko/lib/default.nix
Normal file
@@ -0,0 +1,841 @@
|
||||
{ lib ? import <nixpkgs/lib>
|
||||
, rootMountPoint ? "/mnt"
|
||||
, makeTest ? import <nixpkgs/nixos/tests/make-test-python.nix>
|
||||
, eval-config ? import <nixpkgs/nixos/lib/eval-config.nix>
|
||||
}:
|
||||
let
|
||||
outputs = import ../default.nix { inherit lib diskoLib; };
|
||||
diskoLib = {
|
||||
testLib = import ./tests.nix { inherit lib makeTest eval-config; };
|
||||
# like lib.types.oneOf but instead of a list takes an attrset
|
||||
# uses the field "type" to find the correct type in the attrset
|
||||
subType = { types, extraArgs ? { parent = { type = "rootNode"; name = "root"; }; } }: lib.mkOptionType {
|
||||
name = "subType";
|
||||
description = "one of ${lib.concatStringsSep "," (lib.attrNames types)}";
|
||||
check = x: if x ? type then types.${x.type}.check x else throw "No type option set in:\n${lib.generators.toPretty {} x}";
|
||||
merge = loc: lib.foldl'
|
||||
(_res: def: types.${def.value.type}.merge loc [
|
||||
# we add a dummy root parent node to render documentation
|
||||
(lib.recursiveUpdate { value._module.args = extraArgs; } def)
|
||||
])
|
||||
{ };
|
||||
nestedTypes = types;
|
||||
};
|
||||
|
||||
# option for valid contents of partitions (basically like devices, but without tables)
|
||||
_partitionTypes = { inherit (diskoLib.types) btrfs filesystem zfs mdraid luks lvm_pv swap; };
|
||||
partitionType = extraArgs: lib.mkOption {
|
||||
type = lib.types.nullOr (diskoLib.subType {
|
||||
types = diskoLib._partitionTypes;
|
||||
inherit extraArgs;
|
||||
});
|
||||
default = null;
|
||||
description = "The type of partition";
|
||||
};
|
||||
|
||||
# option for valid contents of devices
|
||||
_deviceTypes = { inherit (diskoLib.types) table gpt btrfs filesystem zfs mdraid luks lvm_pv swap; };
|
||||
deviceType = extraArgs: lib.mkOption {
|
||||
type = lib.types.nullOr (diskoLib.subType {
|
||||
types = diskoLib._deviceTypes;
|
||||
inherit extraArgs;
|
||||
});
|
||||
default = null;
|
||||
description = "The type of device";
|
||||
};
|
||||
|
||||
/* deepMergeMap takes a function and a list of attrsets and deep merges them
|
||||
|
||||
deepMergeMap :: (AttrSet -> AttrSet ) -> [ AttrSet ] -> Attrset
|
||||
|
||||
Example:
|
||||
deepMergeMap (x: x.t = "test") [ { x = { y = 1; z = 3; }; } { x = { bla = 234; }; } ]
|
||||
=> { x = { y = 1; z = 3; bla = 234; t = "test"; }; }
|
||||
*/
|
||||
deepMergeMap = f: lib.foldr (attr: acc: (lib.recursiveUpdate acc (f attr))) { };
|
||||
|
||||
/* 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 inherit (lib) match; in
|
||||
if match "/dev/([vs]|(xv)d).+" dev != null then
|
||||
dev + toString index # /dev/{s,v,xv}da style
|
||||
else if match "/dev/(disk|zvol)/.+" dev != null then
|
||||
"${dev}-part${toString index}" # /dev/disk/by-id/xxx style, also used by zfs's zvolumes
|
||||
else if match "/dev/((nvme|mmcblk).+|md/.*[[:digit:]])" dev != null then
|
||||
"${dev}p${toString index}" # /dev/nvme0n1p1 style
|
||||
else if match "/dev/md/.+" dev != null then
|
||||
"${dev}${toString index}" # /dev/md/raid1 style
|
||||
else if match "/dev/mapper/.+" dev != null then
|
||||
"${dev}${toString index}" # /dev/mapper/vg-lv1 style
|
||||
else if match "/dev/loop[[:digit:]]+" dev != null
|
||||
then "${dev}p${toString index}" # /dev/mapper/vg-lv1 style
|
||||
else
|
||||
abort ''
|
||||
${dev} seems not to be a supported disk format. Please add this to disko in https://github.com/nix-community/disko/blob/master/lib/default.nix
|
||||
'';
|
||||
|
||||
/* Escape a string as required to be used in udev symlinks
|
||||
|
||||
The allowed characters are "0-9A-Za-z#+-.:=@_/", valid UTF-8 character sequences, and "\x00" hex encoding.
|
||||
Everything else is escaped as "\xXX" where XX is the hex value of the character.
|
||||
|
||||
The source of truth for the list of allowed characters is the udev documentation:
|
||||
https://www.freedesktop.org/software/systemd/man/latest/udev.html#SYMLINK1
|
||||
|
||||
This function is implemented as a best effort. It is not guaranteed to be 100% in line
|
||||
with the udev implementation, and we hope that you're not crazy enough to try to break it.
|
||||
|
||||
hexEscapeUdevSymlink :: str -> str
|
||||
|
||||
Example:
|
||||
hexEscapeUdevSymlink "Boot data partition"
|
||||
=> "Boot\x20data\x20partition"
|
||||
|
||||
hexEscapeUdevSymlink "Even(crazier)par&titi^onName"
|
||||
=> "Even\x28crazier\x29par\x26titi\x5EonName"
|
||||
|
||||
hexEscapeUdevSymlink "all0these@char#acters+_are-allow.ed"
|
||||
=> "all0these@char#acters+_are-allow.ed"
|
||||
*/
|
||||
hexEscapeUdevSymlink =
|
||||
let
|
||||
allowedChars = "[0-9A-Za-z#+-.:=@_/]";
|
||||
charToHex = c: lib.toHexString (lib.strings.charToInt c);
|
||||
in
|
||||
lib.stringAsChars
|
||||
(c: if lib.match allowedChars c != null || c == "" then c else "\\x" + charToHex c);
|
||||
|
||||
/* get the index an item in a list
|
||||
|
||||
indexOf :: (a -> bool) -> [a] -> int -> int
|
||||
|
||||
Example:
|
||||
indexOf (x: x == 2) [ 1 2 3 ] 0
|
||||
=> 2
|
||||
|
||||
indexOf (x: x == "x") [ 1 2 3 ] 0
|
||||
=> 0
|
||||
*/
|
||||
indexOf = f: list: fallback:
|
||||
let
|
||||
iter = index: list:
|
||||
if list == [ ] then
|
||||
fallback
|
||||
else if f (lib.head list) then
|
||||
index
|
||||
else
|
||||
iter (index + 1) (lib.tail list);
|
||||
in
|
||||
iter 1 list;
|
||||
|
||||
|
||||
/* indent takes a multiline string and indents it by 2 spaces starting on the second line
|
||||
|
||||
indent :: str -> str
|
||||
|
||||
Example:
|
||||
indent "test\nbla"
|
||||
=> "test\n bla"
|
||||
*/
|
||||
indent = lib.replaceStrings [ "\n" ] [ "\n " ];
|
||||
|
||||
/* A nix option type representing a json datastructure, vendored from nixpkgs to avoid dependency on pkgs */
|
||||
jsonType =
|
||||
let
|
||||
valueType = lib.types.nullOr
|
||||
(lib.types.oneOf [
|
||||
lib.types.bool
|
||||
lib.types.int
|
||||
lib.types.float
|
||||
lib.types.str
|
||||
lib.types.path
|
||||
(lib.types.attrsOf valueType)
|
||||
(lib.types.listOf valueType)
|
||||
]) // {
|
||||
description = "JSON value";
|
||||
};
|
||||
in
|
||||
valueType;
|
||||
|
||||
/* Given a attrset of deviceDependencies and a devices attrset
|
||||
returns a sorted list by deviceDependencies. aborts if a loop is found
|
||||
|
||||
sortDevicesByDependencies :: AttrSet -> AttrSet -> [ [ str str ] ]
|
||||
*/
|
||||
sortDevicesByDependencies = deviceDependencies: devices:
|
||||
let
|
||||
dependsOn = a: b:
|
||||
lib.elem a (lib.attrByPath b [ ] deviceDependencies);
|
||||
maybeSortedDevices = lib.toposort dependsOn (diskoLib.deviceList devices);
|
||||
in
|
||||
if (lib.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:
|
||||
lib.concatLists (lib.mapAttrsToList (n: v: (map (x: [ n x ]) (lib.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: lib.optionalString (x != null) x;
|
||||
|
||||
/* Takes a Submodules config and options argument and returns a serializable
|
||||
subset of config variables as a shell script snippet.
|
||||
*/
|
||||
defineHookVariables = { options }:
|
||||
let
|
||||
sanitizeName = lib.replaceStrings [ "-" ] [ "_" ];
|
||||
isAttrsOfSubmodule = o: o.type.name == "attrsOf" && o.type.nestedTypes.elemType.name == "submodule";
|
||||
isSerializable = n: o: !(
|
||||
lib.hasPrefix "_" n
|
||||
|| lib.hasSuffix "Hook" n
|
||||
|| isAttrsOfSubmodule o
|
||||
# TODO don't hardcode diskoLib.subType options.
|
||||
|| n == "content" || n == "partitions" || n == "datasets" || n == "swap"
|
||||
|| n == "mode"
|
||||
);
|
||||
in
|
||||
lib.toShellVars
|
||||
(lib.mapAttrs'
|
||||
(n: o: lib.nameValuePair (sanitizeName n) o.value)
|
||||
(lib.filterAttrs isSerializable options));
|
||||
|
||||
mkHook = description: lib.mkOption {
|
||||
inherit description;
|
||||
type = lib.types.lines;
|
||||
default = "";
|
||||
};
|
||||
|
||||
mkSubType = module: lib.types.submodule [
|
||||
module
|
||||
|
||||
{
|
||||
options = {
|
||||
preCreateHook = diskoLib.mkHook "shell commands to run before create";
|
||||
postCreateHook = diskoLib.mkHook "shell commands to run after create";
|
||||
preMountHook = diskoLib.mkHook "shell commands to run before mount";
|
||||
postMountHook = diskoLib.mkHook "shell commands to run after mount";
|
||||
preUnmountHook = diskoLib.mkHook "shell commands to run before unmount";
|
||||
postUnmountHook = diskoLib.mkHook "shell commands to run after unmount";
|
||||
};
|
||||
config._module.args = {
|
||||
inherit diskoLib rootMountPoint;
|
||||
};
|
||||
}
|
||||
];
|
||||
|
||||
mkCreateOption = { config, options, default }@attrs:
|
||||
lib.mkOption {
|
||||
internal = true;
|
||||
readOnly = true;
|
||||
type = lib.types.str;
|
||||
default = ''
|
||||
( # ${config.type} ${lib.concatMapStringsSep " " (n: toString (config.${n} or "")) ["name" "device" "format" "mountpoint"]} #
|
||||
${diskoLib.indent (diskoLib.defineHookVariables { inherit options; })}
|
||||
${diskoLib.indent config.preCreateHook}
|
||||
${diskoLib.indent attrs.default}
|
||||
${diskoLib.indent config.postCreateHook}
|
||||
)
|
||||
'';
|
||||
description = "Creation script";
|
||||
};
|
||||
|
||||
mkMountOption = { config, options, default }@attrs:
|
||||
lib.mkOption {
|
||||
internal = true;
|
||||
readOnly = true;
|
||||
type = diskoLib.jsonType;
|
||||
default = lib.mapAttrsRecursive
|
||||
(_name: value:
|
||||
if builtins.isString value then ''
|
||||
(
|
||||
${diskoLib.indent (diskoLib.defineHookVariables { inherit options; })}
|
||||
${diskoLib.indent config.preMountHook}
|
||||
${diskoLib.indent value}
|
||||
${diskoLib.indent config.postMountHook}
|
||||
)
|
||||
'' else value)
|
||||
attrs.default;
|
||||
description = "Mount script";
|
||||
};
|
||||
|
||||
mkUnmountOption = { config, options, default }@attrs:
|
||||
lib.mkOption {
|
||||
internal = true;
|
||||
readOnly = true;
|
||||
type = diskoLib.jsonType;
|
||||
default = lib.mapAttrsRecursive
|
||||
(_name: value:
|
||||
if builtins.isString value then ''
|
||||
(
|
||||
${diskoLib.indent (diskoLib.defineHookVariables { inherit options; })}
|
||||
${diskoLib.indent config.preUnmountHook}
|
||||
${diskoLib.indent value}
|
||||
${diskoLib.indent config.postUnmountHook}
|
||||
)
|
||||
'' else value)
|
||||
attrs.default;
|
||||
description = "Unmount script";
|
||||
};
|
||||
|
||||
/* Writer for optionally checking bash scripts before writing them to the store
|
||||
|
||||
writeCheckedBash :: AttrSet -> str -> str -> derivation
|
||||
*/
|
||||
writeCheckedBash = { pkgs, checked ? false, noDeps ? false }: pkgs.writers.makeScriptWriter {
|
||||
interpreter = if noDeps then "/usr/bin/env bash" else "${pkgs.bash}/bin/bash";
|
||||
check = lib.optionalString (checked && !pkgs.stdenv.hostPlatform.isRiscV64 && !pkgs.stdenv.hostPlatform.isx86_32) (pkgs.writeScript "check" ''
|
||||
set -efu
|
||||
# SC2054: our toShellVars function doesn't quote list elements with commas
|
||||
# SC2034: We don't use all variables exported by hooks.
|
||||
${pkgs.shellcheck}/bin/shellcheck -e SC2034,SC2054 "$1"
|
||||
'');
|
||||
};
|
||||
|
||||
|
||||
/* Takes a disko device specification, returns an attrset with metadata
|
||||
|
||||
meta :: lib.types.devices -> AttrSet
|
||||
*/
|
||||
meta = toplevel: toplevel._meta;
|
||||
|
||||
/* Takes a disko device specification and returns a string which formats the disks
|
||||
|
||||
create :: lib.types.devices -> str
|
||||
*/
|
||||
create = toplevel: toplevel._create;
|
||||
/* Takes a disko device specification and returns a string which mounts the disks
|
||||
|
||||
mount :: lib.types.devices -> str
|
||||
*/
|
||||
mount = toplevel: toplevel._mount;
|
||||
|
||||
/* takes a disko device specification and returns a string which unmounts, destroys all disks and then runs create and mount
|
||||
|
||||
zapCreateMount :: lib.types.devices -> str
|
||||
*/
|
||||
zapCreateMount = toplevel:
|
||||
''
|
||||
set -efux
|
||||
${toplevel._disko}
|
||||
'';
|
||||
/* Takes a disko device specification and returns a nixos configuration
|
||||
|
||||
config :: lib.types.devices -> nixosConfig
|
||||
*/
|
||||
config = toplevel: toplevel._config;
|
||||
|
||||
/* Takes a disko device specification and returns a function to get the needed packages to format/mount the disks
|
||||
|
||||
packages :: lib.types.devices -> pkgs -> [ derivation ]
|
||||
*/
|
||||
packages = toplevel: toplevel._packages;
|
||||
|
||||
/* Checks whether nixpkgs is recent enough for vmTools to support the customQemu argument.
|
||||
|
||||
Returns false, which is technically incorrect, for a few commits on 2024-07-08, but we can't be more accurate.
|
||||
Make sure to pass lib, not pkgs.lib! See https://github.com/nix-community/disko/issues/904
|
||||
|
||||
vmToolsSupportsCustomQemu :: final_lib -> bool
|
||||
*/
|
||||
vmToolsSupportsCustomQemu = final_lib: lib.versionAtLeast final_lib.version "24.11.20240709";
|
||||
|
||||
optionTypes = rec {
|
||||
filename = lib.mkOptionType {
|
||||
name = "filename";
|
||||
check = lib.isString;
|
||||
merge = lib.mergeOneOption;
|
||||
description = "A filename";
|
||||
};
|
||||
|
||||
absolute-pathname = lib.mkOptionType {
|
||||
name = "absolute pathname";
|
||||
check = x: lib.isString x && lib.substring 0 1 x == "/" && pathname.check x;
|
||||
merge = lib.mergeOneOption;
|
||||
description = "An absolute path";
|
||||
};
|
||||
|
||||
pathname = lib.mkOptionType {
|
||||
name = "pathname";
|
||||
check = x:
|
||||
with lib; 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 = lib.mergeOneOption;
|
||||
description = "A path name";
|
||||
};
|
||||
};
|
||||
|
||||
/* topLevel type of the disko config, takes attrsets of disks, mdadms, zpools, nodevs, and lvm vgs.
|
||||
*/
|
||||
toplevel = lib.types.submodule (cfg:
|
||||
let
|
||||
devices = { inherit (cfg.config) disk mdadm zpool lvm_vg nodev; };
|
||||
in
|
||||
{
|
||||
options = {
|
||||
disk = lib.mkOption {
|
||||
type = lib.types.attrsOf diskoLib.types.disk;
|
||||
default = { };
|
||||
description = "Block device";
|
||||
};
|
||||
mdadm = lib.mkOption {
|
||||
type = lib.types.attrsOf diskoLib.types.mdadm;
|
||||
default = { };
|
||||
description = "mdadm device";
|
||||
};
|
||||
zpool = lib.mkOption {
|
||||
type = lib.types.attrsOf diskoLib.types.zpool;
|
||||
default = { };
|
||||
description = "ZFS pool device";
|
||||
};
|
||||
lvm_vg = lib.mkOption {
|
||||
type = lib.types.attrsOf diskoLib.types.lvm_vg;
|
||||
default = { };
|
||||
description = "LVM VG device";
|
||||
};
|
||||
nodev = lib.mkOption {
|
||||
type = lib.types.attrsOf diskoLib.types.nodev;
|
||||
default = { };
|
||||
description = "A non-block device";
|
||||
};
|
||||
_meta = lib.mkOption {
|
||||
internal = true;
|
||||
description = ''
|
||||
meta informationen generated by disko
|
||||
currently used for building a dependency list so we know in which order to create the devices
|
||||
'';
|
||||
default = diskoLib.deepMergeMap (dev: dev._meta) (lib.flatten (map lib.attrValues (lib.attrValues devices)));
|
||||
};
|
||||
_packages = lib.mkOption {
|
||||
internal = true;
|
||||
description = ''
|
||||
packages required by the disko configuration
|
||||
coreutils is always included
|
||||
'';
|
||||
default = pkgs: with lib; unique ((flatten (map (dev: dev._pkgs pkgs) (flatten (map attrValues (attrValues devices))))) ++ [ pkgs.coreutils-full ]);
|
||||
};
|
||||
_scripts = lib.mkOption {
|
||||
internal = true;
|
||||
description = ''
|
||||
The scripts generated by disko
|
||||
'';
|
||||
default = { pkgs, checked ? false }:
|
||||
let
|
||||
throwIfNoDisksDetected = _: v: if devices.disk == { } then throw "No disks defined, did you forget to import your disko config?" else v;
|
||||
destroyDependencies = with pkgs; [
|
||||
util-linux
|
||||
e2fsprogs
|
||||
mdadm
|
||||
zfs
|
||||
lvm2
|
||||
bash
|
||||
jq
|
||||
gnused
|
||||
gawk
|
||||
coreutils-full
|
||||
];
|
||||
in
|
||||
lib.mapAttrs throwIfNoDisksDetected {
|
||||
destroy = (diskoLib.writeCheckedBash { inherit pkgs checked; }) "/bin/disko-destroy" ''
|
||||
export PATH=${lib.makeBinPath destroyDependencies}:$PATH
|
||||
${cfg.config._destroy}
|
||||
'';
|
||||
format = (diskoLib.writeCheckedBash { inherit pkgs checked; }) "/bin/disko-format" ''
|
||||
export PATH=${lib.makeBinPath (cfg.config._packages pkgs)}:$PATH
|
||||
${cfg.config._create}
|
||||
'';
|
||||
mount = (diskoLib.writeCheckedBash { inherit pkgs checked; }) "/bin/disko-mount" ''
|
||||
export PATH=${lib.makeBinPath (cfg.config._packages pkgs)}:$PATH
|
||||
${cfg.config._mount}
|
||||
'';
|
||||
unmount = (diskoLib.writeCheckedBash { inherit pkgs checked; }) "/bin/disko-unmount" ''
|
||||
export PATH=${lib.makeBinPath (cfg.config._packages pkgs)}:$PATH
|
||||
${cfg.config._unmount}
|
||||
'';
|
||||
formatMount = (diskoLib.writeCheckedBash { inherit pkgs checked; }) "/bin/disko-format-mount" ''
|
||||
export PATH=${lib.makeBinPath ((cfg.config._packages pkgs) ++ [ pkgs.bash ])}:$PATH
|
||||
${cfg.config._formatMount}
|
||||
'';
|
||||
destroyFormatMount = (diskoLib.writeCheckedBash { inherit pkgs checked; }) "/bin/disko-destroy-format-mount" ''
|
||||
export PATH=${lib.makeBinPath ((cfg.config._packages pkgs) ++ [ pkgs.bash ] ++ destroyDependencies)}:$PATH
|
||||
${cfg.config._destroyFormatMount}
|
||||
'';
|
||||
|
||||
# These are useful to skip copying executables uploading a script to an in-memory installer
|
||||
destroyNoDeps = (diskoLib.writeCheckedBash { inherit pkgs checked; noDeps = true; }) "/bin/disko-destroy" ''
|
||||
${cfg.config._destroy}
|
||||
'';
|
||||
formatNoDeps = (diskoLib.writeCheckedBash { inherit pkgs checked; noDeps = true; }) "/bin/disko-format" ''
|
||||
${cfg.config._create}
|
||||
'';
|
||||
mountNoDeps = (diskoLib.writeCheckedBash { inherit pkgs checked; noDeps = true; }) "/bin/disko-mount" ''
|
||||
${cfg.config._mount}
|
||||
'';
|
||||
unmountNoDeps = (diskoLib.writeCheckedBash { inherit pkgs checked; noDeps = true; }) "/bin/disko-unmount" ''
|
||||
${cfg.config._unmount}
|
||||
'';
|
||||
formatMountNoDeps = (diskoLib.writeCheckedBash { inherit pkgs checked; noDeps = true; }) "/bin/disko-format-mount" ''
|
||||
${cfg.config._formatMount}
|
||||
'';
|
||||
destroyFormatMountNoDeps = (diskoLib.writeCheckedBash { inherit pkgs checked; noDeps = true; }) "/bin/disko-destroy-format-mount" ''
|
||||
${cfg.config._destroyFormatMount}
|
||||
'';
|
||||
|
||||
|
||||
# Legacy scripts, to be removed in version 2.0.0
|
||||
# They are generally less useful, because the scripts are directly written to their $out path instead of
|
||||
# into the $out/bin directory, which makes them incompatible with `nix run`
|
||||
# (see https://github.com/nix-community/disko/pull/78), `lib.buildEnv` and thus `environment.systemPackages`,
|
||||
# `user.users.<name>.packages` and `home.packages`, see https://github.com/nix-community/disko/issues/454
|
||||
destroyScript = (diskoLib.writeCheckedBash { inherit pkgs checked; }) "disko-destroy" ''
|
||||
export PATH=${lib.makeBinPath destroyDependencies}:$PATH
|
||||
${cfg.config._legacyDestroy}
|
||||
'';
|
||||
|
||||
formatScript = (diskoLib.writeCheckedBash { inherit pkgs checked; }) "disko-format" ''
|
||||
export PATH=${lib.makeBinPath (cfg.config._packages pkgs)}:$PATH
|
||||
${cfg.config._create}
|
||||
'';
|
||||
|
||||
mountScript = (diskoLib.writeCheckedBash { inherit pkgs checked; }) "disko-mount" ''
|
||||
export PATH=${lib.makeBinPath (cfg.config._packages pkgs)}:$PATH
|
||||
${cfg.config._mount}
|
||||
'';
|
||||
|
||||
diskoScript = (diskoLib.writeCheckedBash { inherit pkgs checked; }) "disko" ''
|
||||
export PATH=${lib.makeBinPath ((cfg.config._packages pkgs) ++ [ pkgs.bash ] ++ destroyDependencies)}:$PATH
|
||||
${cfg.config._disko}
|
||||
'';
|
||||
|
||||
# These are useful to skip copying executables uploading a script to an in-memory installer
|
||||
destroyScriptNoDeps = (diskoLib.writeCheckedBash { inherit pkgs checked; noDeps = true; }) "disko-destroy" ''
|
||||
${cfg.config._legacyDestroy}
|
||||
'';
|
||||
|
||||
formatScriptNoDeps = (diskoLib.writeCheckedBash { inherit pkgs checked; noDeps = true; }) "disko-format" ''
|
||||
${cfg.config._create}
|
||||
'';
|
||||
|
||||
mountScriptNoDeps = (diskoLib.writeCheckedBash { inherit pkgs checked; noDeps = true; }) "disko-mount" ''
|
||||
${cfg.config._mount}
|
||||
'';
|
||||
|
||||
diskoScriptNoDeps = (diskoLib.writeCheckedBash { inherit pkgs checked; noDeps = true; }) "disko" ''
|
||||
${cfg.config._disko}
|
||||
'';
|
||||
};
|
||||
};
|
||||
_legacyDestroy = lib.mkOption {
|
||||
internal = true;
|
||||
type = lib.types.str;
|
||||
description = ''
|
||||
The script to unmount (& destroy) all devices defined by disko.devices
|
||||
Does not ask for confirmation! Depracated in favor of _destroy
|
||||
'';
|
||||
default = ''
|
||||
umount -Rv "${rootMountPoint}" || :
|
||||
|
||||
# shellcheck disable=SC2043
|
||||
for dev in ${toString (lib.catAttrs "device" (lib.attrValues devices.disk))}; do
|
||||
$BASH ${../disk-deactivate}/disk-deactivate "$dev"
|
||||
done
|
||||
'';
|
||||
};
|
||||
_destroy = lib.mkOption {
|
||||
internal = true;
|
||||
type = lib.types.str;
|
||||
description = ''
|
||||
The script to unmount (& destroy) all devices defined by disko.devices
|
||||
'';
|
||||
default =
|
||||
let
|
||||
selectedDisks = lib.escapeShellArgs (lib.catAttrs "device" (lib.attrValues devices.disk));
|
||||
in
|
||||
''
|
||||
if [ "$1" != "--yes-wipe-all-disks" ]; then
|
||||
echo "WARNING: This will destroy all data on the disks defined in disko.devices, which are:"
|
||||
echo
|
||||
# shellcheck disable=SC2043,2041
|
||||
for dev in ${selectedDisks}; do
|
||||
echo " - $dev"
|
||||
done
|
||||
echo
|
||||
echo " (If you want to skip this dialogue, pass --yes-wipe-all-disks)"
|
||||
echo
|
||||
echo "Are you sure you want to wipe the devices listed above?"
|
||||
read -rp "Type 'yes' to continue, anything else to abort: " confirmation
|
||||
|
||||
if [ "$confirmation" != "yes" ]; then
|
||||
echo "Aborted."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
umount -Rv "${rootMountPoint}" || :
|
||||
|
||||
# shellcheck disable=SC2043
|
||||
for dev in ${selectedDisks}; do
|
||||
$BASH ${../disk-deactivate}/disk-deactivate "$dev"
|
||||
done
|
||||
'';
|
||||
};
|
||||
_create = lib.mkOption {
|
||||
internal = true;
|
||||
type = lib.types.str;
|
||||
description = ''
|
||||
The script to create all devices defined by disko.devices
|
||||
'';
|
||||
default =
|
||||
with lib; let
|
||||
sortedDeviceList = diskoLib.sortDevicesByDependencies (cfg.config._meta.deviceDependencies or { }) devices;
|
||||
in
|
||||
''
|
||||
set -efux
|
||||
|
||||
disko_devices_dir=$(mktemp -d)
|
||||
trap 'rm -rf "$disko_devices_dir"' EXIT
|
||||
mkdir -p "$disko_devices_dir"
|
||||
|
||||
${concatMapStrings (dev: (attrByPath (dev ++ [ "_create" ]) {} devices)) sortedDeviceList}
|
||||
'';
|
||||
};
|
||||
_mount = lib.mkOption {
|
||||
internal = true;
|
||||
type = lib.types.str;
|
||||
description = ''
|
||||
The script to mount all devices defined by disko.devices
|
||||
'';
|
||||
default =
|
||||
with lib; let
|
||||
fsMounts = diskoLib.deepMergeMap (dev: dev._mount.fs or { }) (flatten (map attrValues (attrValues devices)));
|
||||
sortedDeviceList = diskoLib.sortDevicesByDependencies (cfg.config._meta.deviceDependencies or { }) devices;
|
||||
in
|
||||
''
|
||||
set -efux
|
||||
# first create the necessary devices
|
||||
${concatMapStrings (dev: (attrByPath (dev ++ [ "_mount" ]) {} devices).dev or "") sortedDeviceList}
|
||||
|
||||
# and then mount the filesystems in alphabetical order
|
||||
${concatStrings (attrValues fsMounts)}
|
||||
'';
|
||||
};
|
||||
_unmount = lib.mkOption {
|
||||
internal = true;
|
||||
type = lib.types.str;
|
||||
description = ''
|
||||
The script to unmount all devices defined by disko.devices
|
||||
'';
|
||||
default =
|
||||
with lib; let
|
||||
fsMounts = diskoLib.deepMergeMap (dev: dev._unmount.fs or { }) (flatten (map attrValues (attrValues devices)));
|
||||
sortedDeviceList = diskoLib.sortDevicesByDependencies (cfg.config._meta.deviceDependencies or { }) devices;
|
||||
in
|
||||
''
|
||||
set -efux
|
||||
# first unmount the filesystems in reverse alphabetical order
|
||||
${concatStrings (lib.reverseList (attrValues fsMounts))}
|
||||
|
||||
# Than close the devices
|
||||
${concatMapStrings (dev: (attrByPath (dev ++ [ "_unmount" ]) {} devices).dev or "") (lib.reverseList sortedDeviceList)}
|
||||
'';
|
||||
};
|
||||
_disko = lib.mkOption {
|
||||
internal = true;
|
||||
type = lib.types.str;
|
||||
description = ''
|
||||
The script to umount, create and mount all devices defined by disko.devices
|
||||
Deprecated in favor of _destroyFormatMount
|
||||
'';
|
||||
default = ''
|
||||
${cfg.config._legacyDestroy}
|
||||
${cfg.config._create}
|
||||
${cfg.config._mount}
|
||||
'';
|
||||
};
|
||||
_destroyFormatMount = lib.mkOption {
|
||||
internal = true;
|
||||
type = lib.types.str;
|
||||
description = ''
|
||||
The script to unmount, create and mount all devices defined by disko.devices
|
||||
'';
|
||||
default = ''
|
||||
${cfg.config._destroy}
|
||||
${cfg.config._create}
|
||||
${cfg.config._mount}
|
||||
'';
|
||||
};
|
||||
_formatMount = lib.mkOption {
|
||||
internal = true;
|
||||
type = lib.types.str;
|
||||
description = ''
|
||||
The script to create and mount all devices defined by disko.devices, without wiping the disks first
|
||||
'';
|
||||
default = ''
|
||||
${cfg.config._create}
|
||||
${cfg.config._mount}
|
||||
'';
|
||||
};
|
||||
_config = lib.mkOption {
|
||||
internal = true;
|
||||
description = ''
|
||||
The NixOS config generated by disko
|
||||
'';
|
||||
default =
|
||||
with lib; let
|
||||
configKeys = flatten (map attrNames (flatten (map (dev: dev._config) (flatten (map attrValues (attrValues devices))))));
|
||||
collectedConfigs = flatten (map (dev: dev._config) (flatten (map attrValues (attrValues devices))));
|
||||
in
|
||||
genAttrs configKeys (key: mkMerge (catAttrs key collectedConfigs));
|
||||
};
|
||||
};
|
||||
});
|
||||
|
||||
# import all the types from the types directory
|
||||
types = lib.listToAttrs (
|
||||
map
|
||||
(file: lib.nameValuePair
|
||||
(lib.removeSuffix ".nix" file)
|
||||
(diskoLib.mkSubType (./types + "/${file}"))
|
||||
)
|
||||
(lib.attrNames (builtins.readDir ./types))
|
||||
);
|
||||
|
||||
|
||||
# render types into an json serializable format
|
||||
serializeType = type:
|
||||
let
|
||||
options = lib.filter (x: !lib.hasPrefix "_" x) (lib.attrNames type.options);
|
||||
in
|
||||
lib.listToAttrs (
|
||||
map
|
||||
(option: lib.nameValuePair
|
||||
option
|
||||
type.options.${option}
|
||||
)
|
||||
options
|
||||
);
|
||||
|
||||
typesSerializerLib = {
|
||||
rootMountPoint = "";
|
||||
options = null;
|
||||
config = {
|
||||
_module = {
|
||||
args.name = "<self.name>";
|
||||
args._parent.name = "<parent.name>";
|
||||
args._parent.type = "<parent.type>";
|
||||
};
|
||||
name = "<config.name>";
|
||||
};
|
||||
parent = { };
|
||||
device = "/dev/<device>";
|
||||
# Spoof part of nixpkgs/lib to analyze the types
|
||||
lib = lib // {
|
||||
mkOption = option: {
|
||||
inherit (option) type;
|
||||
description = option.description or null;
|
||||
default = option.defaultText or option.default or null;
|
||||
};
|
||||
types = {
|
||||
attrsOf = subType: {
|
||||
type = "attrsOf";
|
||||
inherit subType;
|
||||
};
|
||||
listOf = subType: {
|
||||
type = "listOf";
|
||||
inherit subType;
|
||||
};
|
||||
nullOr = subType: {
|
||||
type = "nullOr";
|
||||
inherit subType;
|
||||
};
|
||||
oneOf = types: {
|
||||
type = "oneOf";
|
||||
inherit types;
|
||||
};
|
||||
either = t1: t2: {
|
||||
type = "oneOf";
|
||||
types = [ t1 t2 ];
|
||||
};
|
||||
enum = choices: {
|
||||
type = "enum";
|
||||
inherit choices;
|
||||
};
|
||||
anything = "anything";
|
||||
nonEmptyStr = "str";
|
||||
strMatching = _: "str";
|
||||
str = "str";
|
||||
bool = "bool";
|
||||
int = "int";
|
||||
submodule = x: x {
|
||||
inherit (diskoLib.typesSerializerLib) lib config options;
|
||||
name = "<self.name>";
|
||||
};
|
||||
};
|
||||
};
|
||||
diskoLib = {
|
||||
optionTypes.absolute-pathname = "absolute-pathname";
|
||||
# Spoof these types to avoid infinite recursion
|
||||
deviceType = _: "<deviceType>";
|
||||
partitionType = _: "<partitionType>";
|
||||
subType = { types, ... }: {
|
||||
type = "oneOf";
|
||||
types = lib.attrNames types;
|
||||
};
|
||||
mkCreateOption = option: "_create";
|
||||
};
|
||||
};
|
||||
|
||||
jsonTypes = lib.listToAttrs
|
||||
(
|
||||
map
|
||||
(file: lib.nameValuePair
|
||||
(lib.removeSuffix ".nix" file)
|
||||
(diskoLib.serializeType (import (./types + "/${file}") diskoLib.typesSerializerLib))
|
||||
)
|
||||
(lib.filter (name: lib.hasSuffix ".nix" name) (lib.attrNames (builtins.readDir ./types)))
|
||||
) // {
|
||||
partitionType = {
|
||||
type = "oneOf";
|
||||
types = lib.attrNames diskoLib._partitionTypes;
|
||||
};
|
||||
deviceType = {
|
||||
type = "oneOf";
|
||||
types = lib.attrNames diskoLib._deviceTypes;
|
||||
};
|
||||
};
|
||||
|
||||
} // outputs;
|
||||
in
|
||||
diskoLib
|
||||
81
pkgs/disko/lib/interactive-vm.nix
Normal file
81
pkgs/disko/lib/interactive-vm.nix
Normal file
@@ -0,0 +1,81 @@
|
||||
{ diskoLib, modulesPath, config, pkgs, lib, ... }:
|
||||
|
||||
let
|
||||
vm_disko = (diskoLib.testLib.prepareDiskoConfig config diskoLib.testLib.devices).disko;
|
||||
cfg_ = (lib.evalModules {
|
||||
modules = lib.singleton {
|
||||
# _file = toString input;
|
||||
imports = lib.singleton { disko.devices = vm_disko.devices; };
|
||||
options = {
|
||||
disko.devices = lib.mkOption {
|
||||
type = diskoLib.toplevel;
|
||||
};
|
||||
disko.testMode = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
}).config;
|
||||
disks = lib.attrValues cfg_.disko.devices.disk;
|
||||
rootDisk = {
|
||||
name = "root";
|
||||
file = ''"$tmp"/${lib.escapeShellArg (builtins.head disks).name}.qcow2'';
|
||||
driveExtraOpts.cache = "writeback";
|
||||
driveExtraOpts.werror = "report";
|
||||
deviceExtraOpts.bootindex = "1";
|
||||
deviceExtraOpts.serial = "root";
|
||||
};
|
||||
otherDisks = map
|
||||
(disk: {
|
||||
name = disk.name;
|
||||
file = ''"$tmp"/${lib.escapeShellArg disk.name}.qcow2'';
|
||||
driveExtraOpts.werror = "report";
|
||||
})
|
||||
(builtins.tail disks);
|
||||
|
||||
diskoBasedConfiguration = {
|
||||
# generated from disko config
|
||||
virtualisation.fileSystems = cfg_.disko.devices._config.fileSystems;
|
||||
boot = cfg_.disko.devices._config.boot or { };
|
||||
swapDevices = cfg_.disko.devices._config.swapDevices or [ ];
|
||||
};
|
||||
|
||||
hostPkgs = config.virtualisation.host.pkgs;
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
(modulesPath + "/virtualisation/qemu-vm.nix")
|
||||
diskoBasedConfiguration
|
||||
];
|
||||
|
||||
disko.testMode = true;
|
||||
|
||||
disko.imageBuilder.copyNixStore = false;
|
||||
disko.imageBuilder.extraConfig = {
|
||||
disko.devices = cfg_.disko.devices;
|
||||
};
|
||||
disko.imageBuilder.imageFormat = "qcow2";
|
||||
|
||||
virtualisation.useEFIBoot = config.disko.tests.efi;
|
||||
virtualisation.memorySize = lib.mkDefault config.disko.memSize;
|
||||
virtualisation.useDefaultFilesystems = false;
|
||||
virtualisation.diskImage = null;
|
||||
virtualisation.qemu.drives = [ rootDisk ] ++ otherDisks;
|
||||
boot.zfs.devNodes = "/dev/disk/by-uuid"; # needed because /dev/disk/by-id is empty in qemu-vms
|
||||
boot.zfs.forceImportAll = true;
|
||||
boot.zfs.forceImportRoot = lib.mkForce true;
|
||||
|
||||
system.build.vmWithDisko = hostPkgs.writers.writeDashBin "disko-vm" ''
|
||||
set -efux
|
||||
export tmp=$(${hostPkgs.coreutils}/bin/mktemp -d)
|
||||
trap 'rm -rf "$tmp"' EXIT
|
||||
${lib.concatMapStringsSep "\n" (disk: ''
|
||||
${hostPkgs.qemu}/bin/qemu-img create -f qcow2 \
|
||||
-b ${config.system.build.diskoImages}/${lib.escapeShellArg disk.name}.qcow2 \
|
||||
-F qcow2 "$tmp"/${lib.escapeShellArg disk.name}.qcow2
|
||||
'') disks}
|
||||
set +f
|
||||
${config.system.build.vm}/bin/run-*-vm
|
||||
'';
|
||||
}
|
||||
228
pkgs/disko/lib/make-disk-image.nix
Normal file
228
pkgs/disko/lib/make-disk-image.nix
Normal file
@@ -0,0 +1,228 @@
|
||||
{ config
|
||||
, diskoLib
|
||||
, lib
|
||||
, extendModules
|
||||
, options
|
||||
, ...
|
||||
}:
|
||||
let
|
||||
diskoCfg = config.disko;
|
||||
cfg = diskoCfg.imageBuilder;
|
||||
inherit (cfg) pkgs imageFormat;
|
||||
checked = diskoCfg.checkScripts;
|
||||
|
||||
configSupportsZfs = config.boot.supportedFilesystems.zfs or false;
|
||||
vmTools = pkgs.vmTools.override
|
||||
{
|
||||
rootModules = [
|
||||
"9p" "9pnet_virtio" # we can drop those in future if we stop supporting 24.11
|
||||
|
||||
"virtiofs"
|
||||
"virtio_pci"
|
||||
"virtio_blk"
|
||||
"virtio_balloon"
|
||||
"virtio_rng"
|
||||
]
|
||||
++ (lib.optional configSupportsZfs "zfs")
|
||||
++ cfg.extraRootModules;
|
||||
kernel = pkgs.aggregateModules
|
||||
(with cfg.kernelPackages; [ kernel ]
|
||||
++ lib.optional (lib.elem "zfs" cfg.extraRootModules || configSupportsZfs) zfs);
|
||||
}
|
||||
// lib.optionalAttrs (diskoLib.vmToolsSupportsCustomQemu lib)
|
||||
{
|
||||
customQemu = cfg.qemu;
|
||||
};
|
||||
cleanedConfig = diskoLib.testLib.prepareDiskoConfig config diskoLib.testLib.devices;
|
||||
systemToInstall = extendModules {
|
||||
modules = [
|
||||
cfg.extraConfig
|
||||
{
|
||||
disko.testMode = true;
|
||||
disko.devices = lib.mkForce cleanedConfig.disko.devices;
|
||||
boot.loader.grub.devices = lib.mkForce cleanedConfig.boot.loader.grub.devices;
|
||||
}
|
||||
];
|
||||
};
|
||||
dependencies = with pkgs; [
|
||||
bash
|
||||
coreutils
|
||||
gnused
|
||||
parted # for partprobe
|
||||
systemdMinimal
|
||||
nix
|
||||
util-linux
|
||||
findutils
|
||||
kmod
|
||||
] ++ cfg.extraDependencies;
|
||||
preVM = ''
|
||||
# shellcheck disable=SC2154
|
||||
mkdir -p "$out"
|
||||
${lib.concatMapStringsSep "\n" (disk:
|
||||
# shellcheck disable=SC2154
|
||||
"${pkgs.qemu}/bin/qemu-img create -f ${imageFormat} \"$out/${disk.imageName}.${imageFormat}\" ${disk.imageSize}"
|
||||
) (lib.attrValues diskoCfg.devices.disk)}
|
||||
# This makes disko work, when canTouchEfiVariables is set to true.
|
||||
# Technically these boot entries will no be persisted this way, but
|
||||
# in most cases this is OK, because we can rely on the standard location for UEFI executables.
|
||||
install -m600 ${pkgs.OVMF.variables} efivars.fd
|
||||
'';
|
||||
|
||||
closureInfo = pkgs.closureInfo {
|
||||
rootPaths = [ systemToInstall.config.system.build.toplevel ];
|
||||
};
|
||||
partitioner = ''
|
||||
set -efux
|
||||
# running udev, stolen from stage-1.sh
|
||||
echo "running udev..."
|
||||
ln -sfn /proc/self/fd /dev/fd
|
||||
ln -sfn /proc/self/fd/0 /dev/stdin
|
||||
ln -sfn /proc/self/fd/1 /dev/stdout
|
||||
ln -sfn /proc/self/fd/2 /dev/stderr
|
||||
mkdir -p /etc/udev
|
||||
mount -t efivarfs none /sys/firmware/efi/efivars
|
||||
ln -sfn ${systemToInstall.config.system.build.etc}/etc/udev/rules.d /etc/udev/rules.d
|
||||
mkdir -p /dev/.mdadm
|
||||
${pkgs.systemdMinimal}/lib/systemd/systemd-udevd --daemon
|
||||
partprobe
|
||||
udevadm trigger --action=add
|
||||
udevadm settle
|
||||
|
||||
${lib.optionalString diskoCfg.testMode ''
|
||||
export IN_DISKO_TEST=1
|
||||
''}
|
||||
${lib.getExe systemToInstall.config.system.build.destroyFormatMount} --yes-wipe-all-disks
|
||||
'';
|
||||
|
||||
installer = lib.optionalString cfg.copyNixStore ''
|
||||
# populate nix db, so nixos-install doesn't complain
|
||||
export NIX_STATE_DIR=${systemToInstall.config.disko.rootMountPoint}/nix/var/nix
|
||||
nix-store --load-db < "${closureInfo}/registration"
|
||||
|
||||
# We copy files with cp because `nix copy` seems to have a large memory leak
|
||||
mkdir -p ${systemToInstall.config.disko.rootMountPoint}/nix/store
|
||||
time xargs cp --recursive --target ${systemToInstall.config.disko.rootMountPoint}/nix/store < ${closureInfo}/store-paths
|
||||
|
||||
${systemToInstall.config.system.build.nixos-install}/bin/nixos-install --root ${systemToInstall.config.disko.rootMountPoint} --system ${systemToInstall.config.system.build.toplevel} --keep-going --no-channel-copy -v --no-root-password --option binary-caches ""
|
||||
umount -Rv ${systemToInstall.config.disko.rootMountPoint}
|
||||
'';
|
||||
|
||||
QEMU_OPTS = lib.concatStringsSep " " ([
|
||||
"-drive if=pflash,format=raw,unit=0,readonly=on,file=${pkgs.OVMF.firmware}"
|
||||
"-drive if=pflash,format=raw,unit=1,file=efivars.fd"
|
||||
] ++ builtins.map
|
||||
(disk:
|
||||
"-drive file=\"$out\"/${disk.imageName}.${imageFormat},if=virtio,cache=unsafe,werror=report,format=${imageFormat}"
|
||||
)
|
||||
(lib.attrValues diskoCfg.devices.disk));
|
||||
in
|
||||
{
|
||||
system.build.diskoImages = vmTools.runInLinuxVM (pkgs.runCommand cfg.name
|
||||
{
|
||||
buildInputs = dependencies;
|
||||
inherit preVM QEMU_OPTS;
|
||||
postVm = cfg.extraPostVM;
|
||||
inherit (diskoCfg) memSize;
|
||||
}
|
||||
(partitioner + installer));
|
||||
|
||||
system.build.diskoImagesScript = diskoLib.writeCheckedBash { inherit checked pkgs; } cfg.name ''
|
||||
set -efu
|
||||
export PATH=${lib.makeBinPath dependencies}
|
||||
showUsage() {
|
||||
cat <<\USAGE
|
||||
Usage: $script [options]
|
||||
|
||||
Options:
|
||||
* --pre-format-files <src> <dst>
|
||||
copies the src to the dst on the VM, before disko is run
|
||||
This is useful to provide secrets like LUKS keys, or other files you need for formatting
|
||||
* --post-format-files <src> <dst>
|
||||
copies the src to the dst on the finished image
|
||||
These end up in the images later and is useful if you want to add some extra stateful files
|
||||
They will have the same permissions but will be owned by root:root
|
||||
* --build-memory <amt>
|
||||
specify the amount of memory in MiB that gets allocated to the build VM
|
||||
This can be useful if you want to build images with a more involed NixOS config
|
||||
The default is disko.memSize which defaults to ${builtins.toString options.disko.memSize.default} MiB
|
||||
USAGE
|
||||
}
|
||||
|
||||
export out=$PWD
|
||||
TMPDIR=$(mktemp -d); export TMPDIR
|
||||
trap 'rm -rf "$TMPDIR"' EXIT
|
||||
cd "$TMPDIR"
|
||||
|
||||
mkdir copy_before_disko copy_after_disko
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--pre-format-files)
|
||||
src=$2
|
||||
dst=$3
|
||||
cp --reflink=auto -r "$src" copy_before_disko/"$(echo "$dst" | base64)"
|
||||
shift 2
|
||||
;;
|
||||
--post-format-files)
|
||||
src=$2
|
||||
dst=$3
|
||||
cp --reflink=auto -r "$src" copy_after_disko/"$(echo "$dst" | base64)"
|
||||
shift 2
|
||||
;;
|
||||
--build-memory)
|
||||
regex="^[0-9]+$"
|
||||
if ! [[ $2 =~ $regex ]]; then
|
||||
echo "'$2' is not a number"
|
||||
exit 1
|
||||
fi
|
||||
build_memory=$2
|
||||
shift 1
|
||||
;;
|
||||
*)
|
||||
showUsage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
export preVM=${diskoLib.writeCheckedBash { inherit pkgs checked; } "preVM.sh" ''
|
||||
set -efu
|
||||
mv copy_before_disko copy_after_disko xchg/
|
||||
origBuilder=${pkgs.writeScript "disko-builder" ''
|
||||
set -eu
|
||||
export PATH=${lib.makeBinPath dependencies}
|
||||
for src in /tmp/xchg/copy_before_disko/*; do
|
||||
[ -e "$src" ] || continue
|
||||
dst=$(basename "$src" | base64 -d)
|
||||
mkdir -p "$(dirname "$dst")"
|
||||
cp -r "$src" "$dst"
|
||||
done
|
||||
set -f
|
||||
${partitioner}
|
||||
set +f
|
||||
for src in /tmp/xchg/copy_after_disko/*; do
|
||||
[ -e "$src" ] || continue
|
||||
dst=/mnt/$(basename "$src" | base64 -d)
|
||||
mkdir -p "$(dirname "$dst")"
|
||||
cp -r "$src" "$dst"
|
||||
done
|
||||
${installer}
|
||||
''}
|
||||
echo "export origBuilder=$origBuilder" >> xchg/saved-env
|
||||
${preVM}
|
||||
''}
|
||||
export postVM=${diskoLib.writeCheckedBash { inherit pkgs checked; } "postVM.sh" cfg.extraPostVM}
|
||||
|
||||
build_memory=''${build_memory:-${builtins.toString diskoCfg.memSize}}
|
||||
# shellcheck disable=SC2016
|
||||
QEMU_OPTS=${lib.escapeShellArg QEMU_OPTS}
|
||||
# replace quoted $out with the actual path
|
||||
QEUM_OPTS=''${QEMU_OPTS//\$out/$out}
|
||||
QEMU_OPTS+=" -m $build_memory"
|
||||
export QEMU_OPTS
|
||||
|
||||
${pkgs.bash}/bin/sh -e ${vmTools.vmRunCommand vmTools.qemuCommandLinux}
|
||||
cd /
|
||||
'';
|
||||
}
|
||||
337
pkgs/disko/lib/tests.nix
Normal file
337
pkgs/disko/lib/tests.nix
Normal file
@@ -0,0 +1,337 @@
|
||||
{ lib
|
||||
, makeTest
|
||||
, eval-config
|
||||
, ...
|
||||
}:
|
||||
|
||||
let
|
||||
testLib = {
|
||||
# this takes a nixos config and changes the disk devices so we can run them inside the qemu test runner
|
||||
# basically changes all the disk.*.devices to something like /dev/vda or /dev/vdb etc.
|
||||
prepareDiskoConfig = cfg: devices:
|
||||
let
|
||||
cleanedTopLevel = lib.filterAttrsRecursive (n: _: !lib.hasPrefix "_" n) cfg;
|
||||
|
||||
preparedDisks = lib.foldlAttrs
|
||||
(acc: n: v: {
|
||||
devices = lib.tail acc.devices;
|
||||
grub-devices = acc.grub-devices ++ (lib.optional (lib.any (part: (part.type or "") == "EF02") (lib.attrValues (v.content.partitions or { }))) (lib.head acc.devices));
|
||||
disks = acc.disks // {
|
||||
"${n}" = v // {
|
||||
device = lib.head acc.devices;
|
||||
content = v.content // { device = lib.head acc.devices; };
|
||||
};
|
||||
};
|
||||
})
|
||||
{
|
||||
inherit devices;
|
||||
grub-devices = [ ];
|
||||
disks = { };
|
||||
}
|
||||
cleanedTopLevel.disko.devices.disk;
|
||||
in
|
||||
cleanedTopLevel // {
|
||||
boot.loader.grub.devices = if (preparedDisks.grub-devices != [ ]) then preparedDisks.grub-devices else [ "nodev" ];
|
||||
disko.devices = cleanedTopLevel.disko.devices // {
|
||||
disk = preparedDisks.disks;
|
||||
};
|
||||
};
|
||||
|
||||
# list of devices generated inside qemu
|
||||
devices = [
|
||||
"/dev/vda"
|
||||
"/dev/vdb"
|
||||
"/dev/vdc"
|
||||
"/dev/vdd"
|
||||
"/dev/vde"
|
||||
"/dev/vdf"
|
||||
"/dev/vdg"
|
||||
"/dev/vdh"
|
||||
"/dev/vdi"
|
||||
"/dev/vdj"
|
||||
"/dev/vdk"
|
||||
"/dev/vdl"
|
||||
"/dev/vdm"
|
||||
"/dev/vdn"
|
||||
"/dev/vdo"
|
||||
];
|
||||
|
||||
# This is the test generator for a disko test
|
||||
makeDiskoTest =
|
||||
{ name
|
||||
, disko-config
|
||||
, extendModules ? null
|
||||
, pkgs ? import <nixpkgs> { }
|
||||
, extraTestScript ? ""
|
||||
, bootCommands ? ""
|
||||
, extraInstallerConfig ? { }
|
||||
, extraSystemConfig ? { }
|
||||
, efi ? !pkgs.stdenv.hostPlatform.isRiscV64
|
||||
, postDisko ? ""
|
||||
, testMode ? "module" # can be one of direct module cli
|
||||
, testBoot ? true # if we actually want to test booting or just create/mount
|
||||
, enableOCR ? false
|
||||
}:
|
||||
let
|
||||
makeTest' = args:
|
||||
makeTest args {
|
||||
inherit pkgs;
|
||||
inherit (pkgs) system;
|
||||
};
|
||||
# for installation we skip /dev/vda because it is the test runner disk
|
||||
|
||||
importedDiskoConfig =
|
||||
if builtins.isPath disko-config then
|
||||
import disko-config
|
||||
else
|
||||
disko-config;
|
||||
|
||||
diskoConfigWithArgs =
|
||||
if builtins.isFunction importedDiskoConfig then
|
||||
importedDiskoConfig { inherit lib; }
|
||||
else
|
||||
importedDiskoConfig;
|
||||
testConfigInstall = testLib.prepareDiskoConfig diskoConfigWithArgs (lib.tail testLib.devices);
|
||||
# we need to shift the disks by one because the first disk is the /dev/vda of the test runner
|
||||
# so /dev/vdb becomes /dev/vda etc.
|
||||
testConfigBooted = testLib.prepareDiskoConfig diskoConfigWithArgs testLib.devices;
|
||||
|
||||
tsp-generator = pkgs.callPackage ../. { checked = true; };
|
||||
tsp-format = (tsp-generator._cliFormat testConfigInstall) pkgs;
|
||||
tsp-mount = (tsp-generator._cliMount testConfigInstall) pkgs;
|
||||
tsp-unmount = (tsp-generator._cliUnmount testConfigInstall) pkgs;
|
||||
tsp-disko = (tsp-generator._cliDestroyFormatMount testConfigInstall) pkgs;
|
||||
tsp-config = tsp-generator.config testConfigBooted;
|
||||
num-disks = builtins.length (lib.attrNames testConfigBooted.disko.devices.disk);
|
||||
|
||||
installed-system = { config, ... }: {
|
||||
imports = [
|
||||
(lib.optionalAttrs (testMode == "direct") tsp-config)
|
||||
(lib.optionalAttrs (testMode == "module") {
|
||||
disko.enableConfig = true;
|
||||
imports = [
|
||||
../module.nix
|
||||
testConfigBooted
|
||||
];
|
||||
})
|
||||
];
|
||||
|
||||
# config for tests to make them run faster or work at all
|
||||
documentation.enable = false;
|
||||
hardware.enableAllFirmware = lib.mkForce false;
|
||||
# FIXME: we don't have an systemd in stage-1 equialvent for this
|
||||
boot.initrd.preDeviceCommands = lib.mkIf (!config.boot.initrd.systemd.enable) ''
|
||||
echo -n 'secretsecret' > /tmp/secret.key
|
||||
'';
|
||||
boot.consoleLogLevel = lib.mkForce 100;
|
||||
boot.loader.systemd-boot.enable = lib.mkDefault efi;
|
||||
};
|
||||
|
||||
installed-system-eval = eval-config {
|
||||
modules = [ installed-system ];
|
||||
inherit (pkgs) system;
|
||||
};
|
||||
|
||||
installedTopLevel = ((if extendModules != null then extendModules else installed-system-eval.extendModules) {
|
||||
modules = [
|
||||
({ config, ... }: {
|
||||
imports = [
|
||||
extraSystemConfig
|
||||
({ modulesPath, ... }: {
|
||||
imports = [
|
||||
(modulesPath + "/testing/test-instrumentation.nix") # we need these 2 modules always to be able to run the tests
|
||||
(modulesPath + "/profiles/qemu-guest.nix")
|
||||
];
|
||||
disko.devices = lib.mkForce testConfigBooted.disko.devices;
|
||||
})
|
||||
];
|
||||
|
||||
# since we boot on a different machine, the efi payload needs to be portable
|
||||
boot.loader.grub.efiInstallAsRemovable = efi;
|
||||
boot.loader.grub.efiSupport = efi;
|
||||
boot.loader.systemd-boot.graceful = true;
|
||||
|
||||
# we always want the bind-mounted nix store. otherwise tests take forever
|
||||
fileSystems."/nix/store" = lib.mkForce {
|
||||
device = "nix-store";
|
||||
fsType = "9p";
|
||||
neededForBoot = true;
|
||||
options = [ "trans=virtio" "version=9p2000.L" "cache=loose" ];
|
||||
};
|
||||
boot.zfs.devNodes = "/dev/disk/by-uuid"; # needed because /dev/disk/by-id is empty in qemu-vms
|
||||
|
||||
# grub will install to these devices, we need to force those or we are offset by 1
|
||||
# we use mkOveride 70, so that users can override this with mkForce in case they are testing grub mirrored boots
|
||||
boot.loader.grub.devices = lib.mkOverride 70 testConfigInstall.boot.loader.grub.devices;
|
||||
|
||||
assertions = [
|
||||
{
|
||||
assertion = builtins.length config.boot.loader.grub.mirroredBoots > 1 -> config.boot.loader.grub.devices == [ ];
|
||||
message = ''
|
||||
When using `--vm-test` in combination with `mirroredBoots`,
|
||||
it is necessary to configure `boot.loader.grub.devices` as an empty list by setting `boot.loader.grub.devices = lib.mkForce [];`.
|
||||
This adjustment is crucial because the `--vm-test` mechanism automatically overrides the grub boot devices as part of the virtual machine test.
|
||||
'';
|
||||
}
|
||||
];
|
||||
})
|
||||
];
|
||||
}).config.system.build.toplevel;
|
||||
|
||||
in
|
||||
makeTest' {
|
||||
name = "disko-${name}";
|
||||
meta.timeout = 600; # 10 minutes
|
||||
inherit enableOCR;
|
||||
|
||||
nodes.machine = { pkgs, ... }: {
|
||||
imports = [
|
||||
(lib.optionalAttrs (testMode == "module") {
|
||||
imports = [
|
||||
../module.nix
|
||||
];
|
||||
disko = {
|
||||
enableConfig = false;
|
||||
checkScripts = true;
|
||||
devices = testConfigInstall.disko.devices;
|
||||
};
|
||||
})
|
||||
extraInstallerConfig
|
||||
|
||||
# from base.nix
|
||||
({ config, ... }: {
|
||||
boot.supportedFilesystems =
|
||||
[ "btrfs" "cifs" "f2fs" "jfs" "ntfs" "reiserfs" "vfat" "xfs" ] ++
|
||||
lib.optional (config.networking.hostId != null && lib.meta.availableOn pkgs.stdenv.hostPlatform config.boot.zfs.package) "zfs";
|
||||
})
|
||||
|
||||
(if lib.versionAtLeast (lib.versions.majorMinor lib.version) "23.11" then {
|
||||
boot.swraid.enable = true;
|
||||
} else {
|
||||
boot.initrd.services.swraid.enable = true;
|
||||
})
|
||||
];
|
||||
|
||||
systemd.services.mdmonitor.enable = false; # silence some weird warnings
|
||||
|
||||
environment.systemPackages = [
|
||||
pkgs.jq
|
||||
];
|
||||
|
||||
# speed-up eval
|
||||
documentation.enable = false;
|
||||
|
||||
nix.settings = {
|
||||
substituters = lib.mkForce [ ];
|
||||
hashed-mirrors = null;
|
||||
connect-timeout = 1;
|
||||
};
|
||||
|
||||
networking.hostId = lib.mkIf
|
||||
(
|
||||
(testConfigInstall ? networking.hostId) && (testConfigInstall.networking.hostId != null)
|
||||
)
|
||||
testConfigInstall.networking.hostId;
|
||||
|
||||
virtualisation.emptyDiskImages = builtins.genList (_: 4096) num-disks;
|
||||
|
||||
# useful for debugging via repl
|
||||
system.build.systemToInstall = installed-system-eval;
|
||||
};
|
||||
|
||||
testScript = { nodes, ... }: ''
|
||||
def disks(oldmachine, num_disks):
|
||||
disk_flags = []
|
||||
for i in range(num_disks):
|
||||
disk_flags += [
|
||||
'-drive',
|
||||
f"file={oldmachine.state_dir}/empty{i}.qcow2,id=drive{i + 1},if=none,index={i + 1},werror=report",
|
||||
'-device',
|
||||
f"virtio-blk-pci,drive=drive{i + 1}"
|
||||
]
|
||||
return disk_flags
|
||||
|
||||
def create_test_machine(
|
||||
oldmachine=None, **kwargs
|
||||
): # taken from <nixpkgs/nixos/tests/installer.nix>
|
||||
start_command = [
|
||||
"${pkgs.qemu_test}/bin/qemu-kvm",
|
||||
"-cpu",
|
||||
"max",
|
||||
"-m",
|
||||
"1024",
|
||||
"-virtfs",
|
||||
"local,path=/nix/store,security_model=none,mount_tag=nix-store",
|
||||
*disks(oldmachine, ${toString num-disks})
|
||||
]
|
||||
${lib.optionalString efi ''
|
||||
start_command += ["-drive",
|
||||
"if=pflash,format=raw,unit=0,readonly=on,file=${pkgs.OVMF.firmware}",
|
||||
"-drive",
|
||||
"if=pflash,format=raw,unit=1,readonly=on,file=${pkgs.OVMF.variables}"
|
||||
]
|
||||
''}
|
||||
machine = create_machine(start_command=" ".join(start_command), **kwargs)
|
||||
driver.machines.append(machine)
|
||||
return machine
|
||||
|
||||
machine.start()
|
||||
machine.succeed("echo -n 'additionalSecret' > /tmp/additionalSecret.key")
|
||||
machine.succeed("echo -n 'secretsecret' > /tmp/secret.key")
|
||||
${lib.optionalString (testMode == "direct") ''
|
||||
# running direct mode
|
||||
machine.succeed("${lib.getExe tsp-format}")
|
||||
machine.succeed("${lib.getExe tsp-mount}")
|
||||
machine.succeed("${lib.getExe tsp-mount}") # verify that mount is idempotent
|
||||
machine.succeed("${lib.getExe tsp-unmount}")
|
||||
machine.succeed("${lib.getExe tsp-unmount}") # verify that umount is idempotent
|
||||
machine.succeed("${lib.getExe tsp-mount}") # verify that mount is idempotent
|
||||
machine.succeed("${lib.getExe tsp-disko} --yes-wipe-all-disks") # verify that we can destroy and recreate
|
||||
machine.succeed("mkdir -p /mnt/home")
|
||||
machine.succeed("touch /mnt/home/testfile")
|
||||
machine.succeed("${lib.getExe tsp-format}") # verify that format is idempotent
|
||||
machine.succeed("test -e /mnt/home/testfile")
|
||||
''}
|
||||
${lib.optionalString (testMode == "module") ''
|
||||
# running module mode
|
||||
machine.succeed("${lib.getExe nodes.machine.system.build.format}")
|
||||
machine.succeed("${lib.getExe nodes.machine.system.build.mount}")
|
||||
machine.succeed("${lib.getExe nodes.machine.system.build.mount}") # verify that mount is idempotent
|
||||
machine.succeed("${lib.getExe nodes.machine.system.build.destroyFormatMount} --yes-wipe-all-disks") # verify that we can destroy and recreate again
|
||||
machine.succeed("mkdir -p /mnt/home")
|
||||
machine.succeed("touch /mnt/home/testfile")
|
||||
machine.succeed("${lib.getExe nodes.machine.system.build.format}") # verify that format is idempotent
|
||||
machine.succeed("test -e /mnt/home/testfile")
|
||||
''}
|
||||
|
||||
${postDisko}
|
||||
|
||||
${lib.optionalString testBoot ''
|
||||
# mount nix-store in /mnt
|
||||
machine.succeed("mkdir -p /mnt/nix/store")
|
||||
machine.succeed("mount --bind /nix/store /mnt/nix/store")
|
||||
|
||||
machine.succeed("nix-store --load-db < ${pkgs.closureInfo {rootPaths = [installedTopLevel];}}/registration")
|
||||
|
||||
# fix "this is not a NixOS installation"
|
||||
machine.succeed("mkdir -p /mnt/etc")
|
||||
machine.succeed("touch /mnt/etc/NIXOS")
|
||||
|
||||
machine.succeed("mkdir -p /mnt/nix/var/nix/profiles")
|
||||
machine.succeed("nix-env -p /mnt/nix/var/nix/profiles/system --set ${installedTopLevel}")
|
||||
machine.succeed("NIXOS_INSTALL_BOOTLOADER=1 nixos-enter --root /mnt -- ${installedTopLevel}/bin/switch-to-configuration boot")
|
||||
machine.succeed("sync")
|
||||
machine.shutdown()
|
||||
|
||||
machine = create_test_machine(oldmachine=machine, name="booted_machine")
|
||||
machine.start()
|
||||
${bootCommands}
|
||||
machine.wait_for_unit("local-fs.target")
|
||||
''}
|
||||
|
||||
${extraTestScript}
|
||||
'';
|
||||
};
|
||||
};
|
||||
in
|
||||
testLib
|
||||
272
pkgs/disko/lib/types/btrfs.nix
Normal file
272
pkgs/disko/lib/types/btrfs.nix
Normal file
@@ -0,0 +1,272 @@
|
||||
{ config, options, diskoLib, lib, rootMountPoint, parent, device, ... }:
|
||||
let
|
||||
swapType = lib.mkOption {
|
||||
type = lib.types.attrsOf (lib.types.submodule ({ name, ... }: {
|
||||
options = {
|
||||
size = lib.mkOption {
|
||||
type = lib.types.strMatching "^([0-9]+[KMGTP])?$";
|
||||
description = "Size of the swap file (e.g. 2G)";
|
||||
};
|
||||
|
||||
path = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = name;
|
||||
description = "Path to the swap file (relative to the mountpoint)";
|
||||
};
|
||||
|
||||
priority = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.int;
|
||||
default = null;
|
||||
description = ''
|
||||
Specify the priority of the swap file. Priority is a value between 0 and 32767.
|
||||
Higher numbers indicate higher priority.
|
||||
null lets the kernel choose a priority, which will show up as a negative value.
|
||||
'';
|
||||
};
|
||||
|
||||
options = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.nonEmptyStr;
|
||||
default = [ "defaults" ];
|
||||
example = [ "nofail" ];
|
||||
description = "Options used to mount the swap.";
|
||||
};
|
||||
};
|
||||
}));
|
||||
default = { };
|
||||
description = "Swap files";
|
||||
};
|
||||
|
||||
swapConfig = { mountpoint, swap }:
|
||||
{
|
||||
swapDevices = builtins.map
|
||||
(file: {
|
||||
device = "${mountpoint}/${file.path}";
|
||||
inherit (file) priority options;
|
||||
})
|
||||
(lib.attrValues swap);
|
||||
};
|
||||
|
||||
swapCreate = mountpoint: swap:
|
||||
lib.concatMapStringsSep
|
||||
"\n"
|
||||
(file: ''
|
||||
if ! test -e "${mountpoint}/${file.path}"; then
|
||||
btrfs filesystem mkswapfile --size ${file.size} "${mountpoint}/${file.path}"
|
||||
fi
|
||||
'')
|
||||
(lib.attrValues swap);
|
||||
|
||||
in
|
||||
{
|
||||
options = {
|
||||
type = lib.mkOption {
|
||||
type = lib.types.enum [ "btrfs" ];
|
||||
internal = true;
|
||||
description = "Type";
|
||||
};
|
||||
device = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = device;
|
||||
description = "Device to use";
|
||||
};
|
||||
extraArgs = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ ];
|
||||
description = "Extra arguments";
|
||||
};
|
||||
mountOptions = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ "defaults" ];
|
||||
description = "A list of options to pass to mount.";
|
||||
};
|
||||
subvolumes = lib.mkOption {
|
||||
type = lib.types.attrsOf (lib.types.submodule ({ config, ... }: {
|
||||
options = {
|
||||
name = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = config._module.args.name;
|
||||
description = "Name of the BTRFS subvolume.";
|
||||
};
|
||||
type = lib.mkOption {
|
||||
type = lib.types.enum [ "btrfs_subvol" ];
|
||||
default = "btrfs_subvol";
|
||||
internal = true;
|
||||
description = "Type";
|
||||
};
|
||||
extraArgs = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ ];
|
||||
description = "Extra arguments";
|
||||
};
|
||||
mountOptions = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ "defaults" ];
|
||||
description = "Options to pass to mount";
|
||||
};
|
||||
mountpoint = lib.mkOption {
|
||||
type = lib.types.nullOr diskoLib.optionTypes.absolute-pathname;
|
||||
default = null;
|
||||
description = "Location to mount the subvolume to.";
|
||||
};
|
||||
swap = swapType;
|
||||
};
|
||||
}));
|
||||
default = { };
|
||||
description = "Subvolumes to define for BTRFS.";
|
||||
};
|
||||
mountpoint = lib.mkOption {
|
||||
type = lib.types.nullOr diskoLib.optionTypes.absolute-pathname;
|
||||
default = null;
|
||||
description = "A path to mount the BTRFS filesystem to.";
|
||||
};
|
||||
swap = swapType;
|
||||
_parent = lib.mkOption {
|
||||
internal = true;
|
||||
default = parent;
|
||||
};
|
||||
_meta = lib.mkOption {
|
||||
internal = true;
|
||||
readOnly = true;
|
||||
type = lib.types.functionTo diskoLib.jsonType;
|
||||
default = _dev: { };
|
||||
description = "Metadata";
|
||||
};
|
||||
_create = diskoLib.mkCreateOption {
|
||||
inherit config options;
|
||||
default = ''
|
||||
# create the filesystem only if the device seems empty
|
||||
if ! (blkid "${config.device}" -o export | grep -q '^TYPE='); then
|
||||
mkfs.btrfs "${config.device}" ${toString config.extraArgs}
|
||||
fi
|
||||
${lib.optionalString (config.swap != {} || config.subvolumes != {}) ''
|
||||
if (blkid "${config.device}" -o export | grep -q '^TYPE=btrfs$'); then
|
||||
${lib.optionalString (config.swap != {}) ''
|
||||
(
|
||||
MNTPOINT=$(mktemp -d)
|
||||
mount ${device} "$MNTPOINT" -o subvol=/
|
||||
trap 'umount $MNTPOINT; rm -rf $MNTPOINT' EXIT
|
||||
${swapCreate "$MNTPOINT" config.swap}
|
||||
)
|
||||
''}
|
||||
${lib.concatMapStrings (subvol: ''
|
||||
(
|
||||
MNTPOINT=$(mktemp -d)
|
||||
mount "${config.device}" "$MNTPOINT" -o subvol=/
|
||||
trap 'umount $MNTPOINT; rm -rf $MNTPOINT' EXIT
|
||||
SUBVOL_ABS_PATH="$MNTPOINT/${subvol.name}"
|
||||
mkdir -p "$(dirname "$SUBVOL_ABS_PATH")"
|
||||
if ! btrfs subvolume show "$SUBVOL_ABS_PATH" > /dev/null 2>&1; then
|
||||
btrfs subvolume create "$SUBVOL_ABS_PATH" ${toString subvol.extraArgs}
|
||||
fi
|
||||
${swapCreate "$SUBVOL_ABS_PATH" subvol.swap}
|
||||
)
|
||||
'') (lib.attrValues config.subvolumes)}
|
||||
fi
|
||||
''}
|
||||
'';
|
||||
};
|
||||
_mount = diskoLib.mkMountOption {
|
||||
inherit config options;
|
||||
default =
|
||||
let
|
||||
subvolMounts = lib.concatMapAttrs
|
||||
(_: subvol:
|
||||
lib.warnIf (subvol.mountOptions != (options.subvolumes.type.getSubOptions [ ]).mountOptions.default && subvol.mountpoint == null)
|
||||
"Subvolume ${subvol.name} has mountOptions but no mountpoint. See upgrade guide (2023-07-09 121df48)."
|
||||
lib.optionalAttrs
|
||||
(subvol.mountpoint != null)
|
||||
{
|
||||
${subvol.mountpoint} = ''
|
||||
if ! findmnt "${config.device}" "${rootMountPoint}${subvol.mountpoint}" > /dev/null 2>&1; then
|
||||
mount "${config.device}" "${rootMountPoint}${subvol.mountpoint}" \
|
||||
${lib.concatMapStringsSep " " (opt: "-o ${opt}") (subvol.mountOptions ++ [ "subvol=${subvol.name}" ])} \
|
||||
-o X-mount.mkdir
|
||||
fi
|
||||
'';
|
||||
}
|
||||
)
|
||||
config.subvolumes;
|
||||
in
|
||||
{
|
||||
fs = subvolMounts // lib.optionalAttrs (config.mountpoint != null) {
|
||||
${config.mountpoint} = ''
|
||||
if ! findmnt "${config.device}" "${rootMountPoint}${config.mountpoint}" > /dev/null 2>&1; then
|
||||
mount "${config.device}" "${rootMountPoint}${config.mountpoint}" \
|
||||
${lib.concatMapStringsSep " " (opt: "-o ${opt}") config.mountOptions} \
|
||||
-o X-mount.mkdir
|
||||
fi
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
_unmount = diskoLib.mkUnmountOption {
|
||||
inherit config options;
|
||||
default =
|
||||
let
|
||||
subvolMounts = lib.concatMapAttrs
|
||||
(_: subvol:
|
||||
lib.optionalAttrs
|
||||
(subvol.mountpoint != null)
|
||||
{
|
||||
${subvol.mountpoint} = ''
|
||||
if findmnt "${config.device}" "${rootMountPoint}${subvol.mountpoint}" > /dev/null 2>&1; then
|
||||
umount "${rootMountPoint}${subvol.mountpoint}"
|
||||
fi
|
||||
'';
|
||||
}
|
||||
)
|
||||
config.subvolumes;
|
||||
in
|
||||
{
|
||||
fs = subvolMounts // lib.optionalAttrs (config.mountpoint != null) {
|
||||
${config.mountpoint} = ''
|
||||
if findmnt "${config.device}" "${rootMountPoint}${config.mountpoint}" > /dev/null 2>&1; then
|
||||
umount "${rootMountPoint}${config.mountpoint}"
|
||||
fi
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
_config = lib.mkOption {
|
||||
internal = true;
|
||||
readOnly = true;
|
||||
default = [
|
||||
(map
|
||||
(subvol:
|
||||
lib.optional (subvol.mountpoint != null) {
|
||||
fileSystems.${subvol.mountpoint} = {
|
||||
device = config.device;
|
||||
fsType = "btrfs";
|
||||
options = subvol.mountOptions ++ [ "subvol=${subvol.name}" ];
|
||||
};
|
||||
}
|
||||
)
|
||||
(lib.attrValues config.subvolumes))
|
||||
(lib.optional (config.mountpoint != null) {
|
||||
fileSystems.${config.mountpoint} = {
|
||||
device = config.device;
|
||||
fsType = "btrfs";
|
||||
options = config.mountOptions;
|
||||
};
|
||||
})
|
||||
(map
|
||||
(subvol: swapConfig {
|
||||
inherit (subvol) mountpoint swap;
|
||||
})
|
||||
(lib.attrValues config.subvolumes))
|
||||
(swapConfig {
|
||||
inherit (config) mountpoint swap;
|
||||
})
|
||||
];
|
||||
description = "NixOS configuration";
|
||||
};
|
||||
_pkgs = lib.mkOption {
|
||||
internal = true;
|
||||
readOnly = true;
|
||||
type = lib.types.functionTo (lib.types.listOf lib.types.package);
|
||||
default = pkgs:
|
||||
[ pkgs.btrfs-progs pkgs.gnugrep ];
|
||||
description = "Packages";
|
||||
};
|
||||
};
|
||||
}
|
||||
71
pkgs/disko/lib/types/disk.nix
Normal file
71
pkgs/disko/lib/types/disk.nix
Normal file
@@ -0,0 +1,71 @@
|
||||
{ config, options, lib, diskoLib, ... }:
|
||||
{
|
||||
options = {
|
||||
name = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = lib.replaceStrings [ "/" ] [ "_" ] config._module.args.name;
|
||||
description = "Device name";
|
||||
};
|
||||
type = lib.mkOption {
|
||||
type = lib.types.enum [ "disk" ];
|
||||
default = "disk";
|
||||
internal = true;
|
||||
description = "Type";
|
||||
};
|
||||
device = lib.mkOption {
|
||||
type = diskoLib.optionTypes.absolute-pathname; # TODO check if subpath of /dev ? - No! eg: /.swapfile
|
||||
description = "Device path";
|
||||
};
|
||||
imageName = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = config.name;
|
||||
description = ''
|
||||
name of the image when disko images are created
|
||||
is used as an argument to "qemu-img create ..."
|
||||
'';
|
||||
};
|
||||
imageSize = lib.mkOption {
|
||||
type = lib.types.strMatching "[0-9]+[KMGTP]?";
|
||||
description = ''
|
||||
size of the image when disko images are created
|
||||
is used as an argument to "qemu-img create ..."
|
||||
'';
|
||||
default = "2G";
|
||||
};
|
||||
content = diskoLib.deviceType { parent = config; device = config.device; };
|
||||
_meta = lib.mkOption {
|
||||
internal = true;
|
||||
readOnly = true;
|
||||
type = diskoLib.jsonType;
|
||||
default =
|
||||
lib.optionalAttrs (config.content != null) (config.content._meta [ "disk" config.device ]);
|
||||
description = "Metadata";
|
||||
};
|
||||
_create = diskoLib.mkCreateOption {
|
||||
inherit config options;
|
||||
default = config.content._create;
|
||||
};
|
||||
_mount = diskoLib.mkMountOption {
|
||||
inherit config options;
|
||||
default = lib.optionalAttrs (config.content != null) config.content._mount;
|
||||
};
|
||||
_unmount = diskoLib.mkUnmountOption {
|
||||
inherit config options;
|
||||
default = lib.optionalAttrs (config.content != null) config.content._unmount;
|
||||
};
|
||||
_config = lib.mkOption {
|
||||
internal = true;
|
||||
readOnly = true;
|
||||
default =
|
||||
lib.optional (config.content != null) config.content._config;
|
||||
description = "NixOS configuration";
|
||||
};
|
||||
_pkgs = lib.mkOption {
|
||||
internal = true;
|
||||
readOnly = true;
|
||||
type = lib.types.functionTo (lib.types.listOf lib.types.package);
|
||||
default = pkgs: [ pkgs.jq ] ++ lib.optionals (config.content != null) (config.content._pkgs pkgs);
|
||||
description = "Packages";
|
||||
};
|
||||
};
|
||||
}
|
||||
109
pkgs/disko/lib/types/filesystem.nix
Normal file
109
pkgs/disko/lib/types/filesystem.nix
Normal file
@@ -0,0 +1,109 @@
|
||||
{ config, options, lib, diskoLib, rootMountPoint, parent, device, ... }:
|
||||
{
|
||||
options = {
|
||||
type = lib.mkOption {
|
||||
type = lib.types.enum [ "filesystem" ];
|
||||
internal = true;
|
||||
description = "Type";
|
||||
};
|
||||
device = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = device;
|
||||
description = "Device to use";
|
||||
};
|
||||
extraArgs = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ ];
|
||||
description = "Extra arguments";
|
||||
};
|
||||
mountOptions = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ "defaults" ];
|
||||
description = "Options to pass to mount";
|
||||
};
|
||||
mountpoint = lib.mkOption {
|
||||
type = lib.types.nullOr diskoLib.optionTypes.absolute-pathname;
|
||||
default = null;
|
||||
description = "Path to mount the filesystem to";
|
||||
};
|
||||
format = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Format of the filesystem";
|
||||
};
|
||||
_parent = lib.mkOption {
|
||||
internal = true;
|
||||
default = parent;
|
||||
};
|
||||
_meta = lib.mkOption {
|
||||
internal = true;
|
||||
readOnly = true;
|
||||
type = lib.types.functionTo diskoLib.jsonType;
|
||||
default = _dev: { };
|
||||
description = "Metadata";
|
||||
};
|
||||
_create = diskoLib.mkCreateOption {
|
||||
inherit config options;
|
||||
default = ''
|
||||
if ! (blkid "${config.device}" | grep -q 'TYPE='); then
|
||||
mkfs.${config.format} \
|
||||
${lib.escapeShellArgs config.extraArgs} \
|
||||
"${config.device}"
|
||||
fi
|
||||
'';
|
||||
};
|
||||
_mount = diskoLib.mkMountOption {
|
||||
inherit config options;
|
||||
default = lib.optionalAttrs (config.mountpoint != null) {
|
||||
fs.${config.mountpoint} = ''
|
||||
if ! findmnt "${config.device}" "${rootMountPoint}${config.mountpoint}" >/dev/null 2>&1; then
|
||||
mount "${config.device}" "${rootMountPoint}${config.mountpoint}" \
|
||||
-t "${config.format}" \
|
||||
${lib.concatMapStringsSep " " (opt: "-o ${lib.escapeShellArg opt}") config.mountOptions} \
|
||||
-o X-mount.mkdir
|
||||
fi
|
||||
'';
|
||||
};
|
||||
};
|
||||
_unmount = diskoLib.mkUnmountOption {
|
||||
inherit config options;
|
||||
default = lib.optionalAttrs (config.mountpoint != null) {
|
||||
fs.${config.mountpoint} = ''
|
||||
if findmnt "${config.device}" "${rootMountPoint}${config.mountpoint}" >/dev/null 2>&1; then
|
||||
umount "${rootMountPoint}${config.mountpoint}"
|
||||
fi
|
||||
'';
|
||||
};
|
||||
};
|
||||
_config = lib.mkOption {
|
||||
internal = true;
|
||||
readOnly = true;
|
||||
default = lib.optional (config.mountpoint != null) {
|
||||
fileSystems.${config.mountpoint} = {
|
||||
device = config.device;
|
||||
fsType = config.format;
|
||||
options = config.mountOptions;
|
||||
};
|
||||
};
|
||||
description = "NixOS configuration";
|
||||
};
|
||||
_pkgs = lib.mkOption {
|
||||
internal = true;
|
||||
readOnly = true;
|
||||
# type = lib.types.functionTo (lib.types.listOf lib.types.package);
|
||||
default = pkgs:
|
||||
[ pkgs.util-linux pkgs.gnugrep ] ++ (
|
||||
# TODO add many more
|
||||
if (config.format == "xfs") then [ pkgs.xfsprogs ]
|
||||
else if (config.format == "btrfs") then [ pkgs.btrfs-progs ]
|
||||
else if (config.format == "vfat") then [ pkgs.dosfstools ]
|
||||
else if (config.format == "ext2") then [ pkgs.e2fsprogs ]
|
||||
else if (config.format == "ext3") then [ pkgs.e2fsprogs ]
|
||||
else if (config.format == "ext4") then [ pkgs.e2fsprogs ]
|
||||
else if (config.format == "bcachefs") then [ pkgs.bcachefs-tools ]
|
||||
else if (config.format == "f2fs") then [ pkgs.f2fs-tools ]
|
||||
else [ ]
|
||||
);
|
||||
description = "Packages";
|
||||
};
|
||||
};
|
||||
}
|
||||
297
pkgs/disko/lib/types/gpt.nix
Normal file
297
pkgs/disko/lib/types/gpt.nix
Normal file
@@ -0,0 +1,297 @@
|
||||
{ config, options, lib, diskoLib, parent, device, ... }:
|
||||
let
|
||||
sortedPartitions = lib.sort (x: y: x.priority < y.priority) (lib.attrValues config.partitions);
|
||||
sortedHybridPartitions = lib.filter (p: p.hybrid != null) sortedPartitions;
|
||||
in
|
||||
{
|
||||
options = {
|
||||
type = lib.mkOption {
|
||||
type = lib.types.enum [ "gpt" ];
|
||||
internal = true;
|
||||
description = "Partition table";
|
||||
};
|
||||
device = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = device;
|
||||
description = "Device to use for the partition table";
|
||||
};
|
||||
partitions = lib.mkOption {
|
||||
type = lib.types.attrsOf (lib.types.submodule ({ name, ... }@partition: {
|
||||
options = {
|
||||
type = lib.mkOption {
|
||||
type =
|
||||
let
|
||||
hexPattern = len: "[A-Fa-f0-9]{${toString len}}";
|
||||
in
|
||||
lib.types.either
|
||||
(lib.types.strMatching (hexPattern 4))
|
||||
(lib.types.strMatching (lib.concatMapStringsSep "-" hexPattern [ 8 4 4 4 12 ]));
|
||||
default = if partition.config.content != null && partition.config.content.type == "swap" then "8200" else "8300";
|
||||
defaultText = ''8300 (Linux filesystem) normally, 8200 (Linux swap) if content.type is "swap"'';
|
||||
description = ''
|
||||
Filesystem type to use.
|
||||
This can either be an sgdisk-specific short code (run sgdisk -L to see what is available),
|
||||
or a fully specified GUID (see https://en.wikipedia.org/wiki/GUID_Partition_Table#Partition_type_GUIDs).
|
||||
'';
|
||||
};
|
||||
device = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default =
|
||||
if config._parent.type == "mdadm" then
|
||||
# workaround because mdadm partlabel do not appear in /dev/disk/by-partlabel
|
||||
"/dev/disk/by-id/md-name-any:${config._parent.name}-part${toString partition.config._index}"
|
||||
else
|
||||
"/dev/disk/by-partlabel/${diskoLib.hexEscapeUdevSymlink partition.config.label}";
|
||||
defaultText = ''
|
||||
if the parent is an mdadm device:
|
||||
/dev/disk/by-id/md-name-any:''${config._parent.name}-part''${toString partition.config._index}
|
||||
|
||||
otherwise:
|
||||
/dev/disk/by-partlabel/''${diskoLib.hexEscapeUdevSymlink partition.config.label}
|
||||
'';
|
||||
description = "Device to use for the partition";
|
||||
};
|
||||
priority = lib.mkOption {
|
||||
type = lib.types.int;
|
||||
default =
|
||||
if partition.config.size or "" == "100%" then
|
||||
9001
|
||||
else if partition.config.type == "EF02" then
|
||||
# Boot partition should be created first, because some BIOS implementations require it.
|
||||
# Priority defaults to 100 here to support any potential use-case for placing partitions prior to EF02
|
||||
100
|
||||
else
|
||||
1000;
|
||||
defaultText = ''
|
||||
1000: normal partitions
|
||||
9001: partitions with 100% size
|
||||
100: boot partitions (EF02)
|
||||
'';
|
||||
description = "Priority of the partition, smaller values are created first";
|
||||
};
|
||||
name = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Name of the partition";
|
||||
default = name;
|
||||
};
|
||||
label = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default =
|
||||
let
|
||||
# 72 bytes is the maximum length of a GPT partition name
|
||||
# the labels seem to be in UTF-16, so 2 bytes per character
|
||||
limit = 36;
|
||||
label = "${config._parent.type}-${config._parent.name}-${partition.config.name}";
|
||||
in
|
||||
if (lib.stringLength label) > limit then
|
||||
builtins.substring 0 limit (builtins.hashString "sha256" label)
|
||||
else
|
||||
label;
|
||||
defaultText = ''
|
||||
''${config._parent.type}-''${config._parent.name}-''${partition.config.name}
|
||||
|
||||
or a truncated hash of the above if it is longer than 36 characters
|
||||
'';
|
||||
};
|
||||
size = lib.mkOption {
|
||||
type = lib.types.either (lib.types.enum [ "100%" ]) (lib.types.strMatching "[0-9]+[KMGTP]?");
|
||||
default = "0";
|
||||
description = ''
|
||||
Size of the partition, in sgdisk format.
|
||||
sets end automatically with the + prefix
|
||||
can be 100% for the whole remaining disk, will be done last in that case.
|
||||
'';
|
||||
};
|
||||
alignment = lib.mkOption {
|
||||
type = lib.types.int;
|
||||
default = if (builtins.substring (builtins.stringLength partition.config.start - 1) 1 partition.config.start == "s" || (builtins.substring (builtins.stringLength partition.config.end - 1) 1 partition.config.end == "s")) then 1 else 0;
|
||||
defaultText = "1 if the unit of start or end is sectors, 0 otherwise";
|
||||
description = "Alignment of the partition, if sectors are used as start or end it can be aligned to 1";
|
||||
};
|
||||
start = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "0";
|
||||
description = "Start of the partition, in sgdisk format, use 0 for next available range";
|
||||
};
|
||||
end = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = if partition.config.size == "100%" then "-0" else "+${partition.config.size}";
|
||||
defaultText = ''
|
||||
if partition.config.size == "100%" then "-0" else "+''${partition.config.size}";
|
||||
'';
|
||||
description = ''
|
||||
End of the partition, in sgdisk format.
|
||||
Use + for relative sizes from the partitions start
|
||||
or - for relative sizes from the disks end
|
||||
'';
|
||||
};
|
||||
content = diskoLib.partitionType { parent = config; device = partition.config.device; };
|
||||
hybrid = lib.mkOption {
|
||||
type = lib.types.nullOr (lib.types.submodule ({ ... } @ hp: {
|
||||
options = {
|
||||
mbrPartitionType = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "MBR type code";
|
||||
};
|
||||
mbrBootableFlag = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Set the bootable flag (aka the active flag) on any or all of your hybridized partitions";
|
||||
};
|
||||
_create = diskoLib.mkCreateOption {
|
||||
inherit config options;
|
||||
default = ''
|
||||
${lib.optionalString (hp.config.mbrPartitionType != null) ''
|
||||
sfdisk --label-nested dos --part-type "${parent.device}" ${(toString partition.config._index)} ${hp.config.mbrPartitionType}
|
||||
udevadm trigger --subsystem-match=block
|
||||
udevadm settle
|
||||
''}
|
||||
${lib.optionalString hp.config.mbrBootableFlag ''
|
||||
sfdisk --label-nested dos --activate "${parent.device}" ${(toString partition.config._index)}
|
||||
''}
|
||||
'';
|
||||
};
|
||||
};
|
||||
}));
|
||||
default = null;
|
||||
description = "Entry to add to the Hybrid MBR table";
|
||||
};
|
||||
_index = lib.mkOption {
|
||||
type = lib.types.int;
|
||||
internal = true;
|
||||
default = diskoLib.indexOf (x: x.name == partition.config.name) sortedPartitions 0;
|
||||
defaultText = null;
|
||||
};
|
||||
};
|
||||
}));
|
||||
default = { };
|
||||
description = "Attrs of partitions to add to the partition table";
|
||||
};
|
||||
efiGptPartitionFirst = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = "Place EFI GPT (0xEE) partition first in MBR (good for GRUB)";
|
||||
};
|
||||
_parent = lib.mkOption {
|
||||
internal = true;
|
||||
default = parent;
|
||||
};
|
||||
_meta = lib.mkOption {
|
||||
internal = true;
|
||||
readOnly = true;
|
||||
type = lib.types.functionTo diskoLib.jsonType;
|
||||
default = dev:
|
||||
lib.foldr lib.recursiveUpdate { } (map
|
||||
(partition:
|
||||
lib.optionalAttrs (partition.content != null) (partition.content._meta dev)
|
||||
)
|
||||
(lib.attrValues config.partitions));
|
||||
description = "Metadata";
|
||||
};
|
||||
_create = diskoLib.mkCreateOption {
|
||||
inherit config options;
|
||||
default = ''
|
||||
if ! blkid "${config.device}" >&2; then
|
||||
sgdisk --clear "${config.device}"
|
||||
fi
|
||||
${lib.concatStrings (map (partition: ''
|
||||
# try to create the partition, if it fails, try to change the type and name
|
||||
if ! sgdisk \
|
||||
--align-end ${lib.optionalString (partition.alignment != 0) ''--set-alignment=${builtins.toString partition.alignment}''} \
|
||||
--new=${toString partition._index}:${partition.start}:${partition.end} \
|
||||
--change-name="${toString partition._index}:${partition.label}" \
|
||||
--typecode=${toString partition._index}:${partition.type} \
|
||||
"${config.device}"
|
||||
then sgdisk \
|
||||
--change-name="${toString partition._index}:${partition.label}" \
|
||||
--typecode=${toString partition._index}:${partition.type} \
|
||||
"${config.device}"
|
||||
fi
|
||||
# ensure /dev/disk/by-path/..-partN exists before continuing
|
||||
partprobe "${config.device}" || : # sometimes partprobe fails, but the partitions are still up2date
|
||||
udevadm trigger --subsystem-match=block
|
||||
udevadm settle
|
||||
'') sortedPartitions)}
|
||||
|
||||
${
|
||||
lib.optionalString (sortedHybridPartitions != [])
|
||||
("sgdisk -h "
|
||||
+ (lib.concatStringsSep ":" (map (p: (toString p._index)) sortedHybridPartitions))
|
||||
+ (
|
||||
lib.optionalString (!config.efiGptPartitionFirst) ":EE "
|
||||
)
|
||||
+ ''"${parent.device}"'')
|
||||
}
|
||||
${lib.concatMapStrings (p:
|
||||
p.hybrid._create
|
||||
)
|
||||
sortedHybridPartitions
|
||||
}
|
||||
|
||||
${lib.concatStrings (map (partition: ''
|
||||
${lib.optionalString (partition.content != null) partition.content._create}
|
||||
'') sortedPartitions)}
|
||||
'';
|
||||
};
|
||||
_mount = diskoLib.mkMountOption {
|
||||
inherit config options;
|
||||
default =
|
||||
let
|
||||
partMounts = lib.foldr lib.recursiveUpdate { } (map
|
||||
(partition:
|
||||
lib.optionalAttrs (partition.content != null) partition.content._mount
|
||||
)
|
||||
(lib.attrValues config.partitions));
|
||||
in
|
||||
{
|
||||
dev = partMounts.dev or "";
|
||||
fs = partMounts.fs or { };
|
||||
};
|
||||
};
|
||||
_unmount = diskoLib.mkUnmountOption {
|
||||
inherit config options;
|
||||
default =
|
||||
let
|
||||
partMounts = lib.foldr lib.recursiveUpdate { } (map
|
||||
(partition:
|
||||
lib.optionalAttrs (partition.content != null) partition.content._unmount
|
||||
)
|
||||
(lib.attrValues config.partitions));
|
||||
in
|
||||
{
|
||||
dev = partMounts.dev or "";
|
||||
fs = partMounts.fs or { };
|
||||
};
|
||||
};
|
||||
_config = lib.mkOption {
|
||||
internal = true;
|
||||
readOnly = true;
|
||||
default = (map
|
||||
(partition:
|
||||
lib.optional (partition.content != null) partition.content._config
|
||||
)
|
||||
(lib.attrValues config.partitions))
|
||||
++ (lib.optional (lib.any (part: part.type == "EF02") (lib.attrValues config.partitions)) {
|
||||
boot.loader.grub.devices = [ config.device ];
|
||||
});
|
||||
description = "NixOS configuration";
|
||||
};
|
||||
_pkgs = lib.mkOption {
|
||||
internal = true;
|
||||
readOnly = true;
|
||||
type = lib.types.functionTo (lib.types.listOf lib.types.package);
|
||||
default = pkgs:
|
||||
[
|
||||
pkgs.gptfdisk
|
||||
pkgs.systemdMinimal
|
||||
pkgs.parted # for partprobe
|
||||
] ++ lib.flatten (map
|
||||
(partition:
|
||||
lib.optional (partition.content != null) (partition.content._pkgs pkgs)
|
||||
)
|
||||
(lib.attrValues config.partitions));
|
||||
description = "Packages";
|
||||
};
|
||||
};
|
||||
}
|
||||
210
pkgs/disko/lib/types/luks.nix
Normal file
210
pkgs/disko/lib/types/luks.nix
Normal file
@@ -0,0 +1,210 @@
|
||||
{ config, options, lib, diskoLib, parent, device, ... }:
|
||||
let
|
||||
keyFile =
|
||||
if config.settings ? "keyFile"
|
||||
then config.settings.keyFile
|
||||
else if config.askPassword
|
||||
then ''<(set +x; echo -n "$password"; set -x)''
|
||||
else if config.passwordFile != null
|
||||
# do not print the password to the console
|
||||
then ''<(set +x; echo -n "$(cat ${config.passwordFile})"; set -x)''
|
||||
else if config.keyFile != null
|
||||
then
|
||||
lib.warn
|
||||
("The option `keyFile` is deprecated."
|
||||
+ "Use passwordFile instead if you want to use interactive login or settings.keyFile if you want to use key file login")
|
||||
config.keyFile
|
||||
else null;
|
||||
keyFileArgs = ''
|
||||
${lib.optionalString (keyFile != null) "--key-file ${keyFile}"} \
|
||||
${lib.optionalString (lib.hasAttr "keyFileSize" config.settings) "--keyfile-size ${builtins.toString config.settings.keyFileSize}"} \
|
||||
${lib.optionalString (lib.hasAttr "keyFileOffset" config.settings) "--keyfile-offset ${builtins.toString config.settings.keyFileOffset}"} \
|
||||
'';
|
||||
cryptsetupOpen = ''
|
||||
cryptsetup open "${config.device}" "${config.name}" \
|
||||
${lib.optionalString (config.settings.allowDiscards or false) "--allow-discards"} \
|
||||
${lib.optionalString (config.settings.bypassWorkqueues or false) "--perf-no_read_workqueue --perf-no_write_workqueue"} \
|
||||
${toString config.extraOpenArgs} \
|
||||
${keyFileArgs} \
|
||||
'';
|
||||
in
|
||||
{
|
||||
options = {
|
||||
type = lib.mkOption {
|
||||
type = lib.types.enum [ "luks" ];
|
||||
internal = true;
|
||||
description = "Type";
|
||||
};
|
||||
device = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Device to encrypt";
|
||||
default = device;
|
||||
};
|
||||
name = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Name of the LUKS";
|
||||
};
|
||||
keyFile = lib.mkOption {
|
||||
type = lib.types.nullOr diskoLib.optionTypes.absolute-pathname;
|
||||
default = null;
|
||||
description = "DEPRECATED use passwordFile or settings.keyFile. Path to the key for encryption";
|
||||
example = "/tmp/disk.key";
|
||||
};
|
||||
passwordFile = lib.mkOption {
|
||||
type = lib.types.nullOr diskoLib.optionTypes.absolute-pathname;
|
||||
default = null;
|
||||
description = "Path to the file which contains the password for initial encryption";
|
||||
example = "/tmp/disk.key";
|
||||
};
|
||||
askPassword = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = config.keyFile == null && config.passwordFile == null && (! config.settings ? "keyFile");
|
||||
defaultText = "true if neither keyFile nor passwordFile are set";
|
||||
description = "Whether to ask for a password for initial encryption";
|
||||
};
|
||||
settings = lib.mkOption {
|
||||
type = lib.types.attrsOf lib.types.anything;
|
||||
default = { };
|
||||
description = "LUKS settings (as defined in configuration.nix in boot.initrd.luks.devices.<name>)";
|
||||
example = ''{
|
||||
keyFile = "/tmp/disk.key";
|
||||
keyFileSize = 2048;
|
||||
keyFileOffset = 1024;
|
||||
fallbackToPassword = true;
|
||||
allowDiscards = true;
|
||||
};
|
||||
'';
|
||||
};
|
||||
additionalKeyFiles = lib.mkOption {
|
||||
type = lib.types.listOf diskoLib.optionTypes.absolute-pathname;
|
||||
default = [ ];
|
||||
description = "Path to additional key files for encryption";
|
||||
example = [ "/tmp/disk2.key" ];
|
||||
};
|
||||
initrdUnlock = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
description = "Whether to add a boot.initrd.luks.devices entry for the specified disk.";
|
||||
};
|
||||
extraFormatArgs = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ ];
|
||||
description = "Extra arguments to pass to `cryptsetup luksFormat` when formatting";
|
||||
example = [ "--pbkdf argon2id" ];
|
||||
};
|
||||
extraOpenArgs = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ ];
|
||||
description = "Extra arguments to pass to `cryptsetup luksOpen` when opening";
|
||||
example = [ "--timeout 10" ];
|
||||
};
|
||||
content = diskoLib.deviceType { parent = config; device = "/dev/mapper/${config.name}"; };
|
||||
_parent = lib.mkOption {
|
||||
internal = true;
|
||||
default = parent;
|
||||
};
|
||||
_meta = lib.mkOption {
|
||||
internal = true;
|
||||
readOnly = true;
|
||||
type = lib.types.functionTo diskoLib.jsonType;
|
||||
default = dev:
|
||||
lib.optionalAttrs (config.content != null) (config.content._meta dev);
|
||||
description = "Metadata";
|
||||
};
|
||||
_create = diskoLib.mkCreateOption {
|
||||
inherit config options;
|
||||
default = ''
|
||||
if ! blkid "${config.device}" >/dev/null || ! (blkid "${config.device}" -o export | grep -q '^TYPE='); then
|
||||
${lib.optionalString config.askPassword ''
|
||||
askPassword() {
|
||||
if [ -z ''${IN_DISKO_TEST+x} ]; then
|
||||
set +x
|
||||
echo "Enter password for ${config.device}: "
|
||||
IFS= read -r -s password
|
||||
echo "Enter password for ${config.device} again to be safe: "
|
||||
IFS= read -r -s password_check
|
||||
export password
|
||||
[ "$password" = "$password_check" ]
|
||||
set -x
|
||||
else
|
||||
export password=disko
|
||||
fi
|
||||
}
|
||||
until askPassword; do
|
||||
echo "Passwords did not match, please try again."
|
||||
done
|
||||
''}
|
||||
cryptsetup -q luksFormat "${config.device}" ${toString config.extraFormatArgs} ${keyFileArgs}
|
||||
${cryptsetupOpen} --persistent
|
||||
${toString (lib.forEach config.additionalKeyFiles (keyFile: ''
|
||||
cryptsetup luksAddKey "${config.device}" ${keyFile} ${keyFileArgs}
|
||||
''))}
|
||||
fi
|
||||
${lib.optionalString (config.content != null) config.content._create}
|
||||
'';
|
||||
};
|
||||
_mount = diskoLib.mkMountOption {
|
||||
inherit config options;
|
||||
default =
|
||||
let
|
||||
contentMount = config.content._mount;
|
||||
in
|
||||
{
|
||||
dev = ''
|
||||
if ! cryptsetup status "${config.name}" >/dev/null 2>/dev/null; then
|
||||
${lib.optionalString config.askPassword ''
|
||||
if [ -z ''${IN_DISKO_TEST+x} ]; then
|
||||
set +x
|
||||
echo "Enter password for ${config.device}"
|
||||
IFS= read -r -s password
|
||||
export password
|
||||
set -x
|
||||
else
|
||||
export password=disko
|
||||
fi
|
||||
''}
|
||||
${cryptsetupOpen}
|
||||
fi
|
||||
${lib.optionalString (config.content != null) contentMount.dev or ""}
|
||||
'';
|
||||
fs = lib.optionalAttrs (config.content != null) contentMount.fs or { };
|
||||
};
|
||||
};
|
||||
_unmount = diskoLib.mkUnmountOption {
|
||||
inherit config options;
|
||||
default =
|
||||
let
|
||||
contentUnmount = config.content._unmount;
|
||||
in
|
||||
{
|
||||
dev = ''
|
||||
${lib.optionalString (config.content != null) contentUnmount.dev or ""}
|
||||
if cryptsetup status "${config.name}" >/dev/null 2>/dev/null; then
|
||||
cryptsetup close "${config.name}"
|
||||
fi
|
||||
'';
|
||||
};
|
||||
};
|
||||
_config = lib.mkOption {
|
||||
internal = true;
|
||||
readOnly = true;
|
||||
default = [ ]
|
||||
# If initrdUnlock is true, then add a device entry to the initrd.luks.devices config.
|
||||
++ (lib.optional config.initrdUnlock [
|
||||
{
|
||||
boot.initrd.luks.devices.${config.name} = {
|
||||
inherit (config) device;
|
||||
} // config.settings;
|
||||
}
|
||||
]) ++ (lib.optional (config.content != null) config.content._config);
|
||||
description = "NixOS configuration";
|
||||
};
|
||||
_pkgs = lib.mkOption {
|
||||
internal = true;
|
||||
readOnly = true;
|
||||
type = lib.types.functionTo (lib.types.listOf lib.types.package);
|
||||
default = pkgs: [ pkgs.gnugrep pkgs.cryptsetup ] ++ (lib.optionals (config.content != null) (config.content._pkgs pkgs));
|
||||
description = "Packages";
|
||||
};
|
||||
};
|
||||
}
|
||||
62
pkgs/disko/lib/types/lvm_pv.nix
Normal file
62
pkgs/disko/lib/types/lvm_pv.nix
Normal file
@@ -0,0 +1,62 @@
|
||||
{ config, options, lib, diskoLib, parent, device, ... }:
|
||||
{
|
||||
options = {
|
||||
type = lib.mkOption {
|
||||
type = lib.types.enum [ "lvm_pv" ];
|
||||
internal = true;
|
||||
description = "Type";
|
||||
};
|
||||
device = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Device";
|
||||
default = device;
|
||||
};
|
||||
vg = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Volume group";
|
||||
};
|
||||
_parent = lib.mkOption {
|
||||
internal = true;
|
||||
default = parent;
|
||||
};
|
||||
_meta = lib.mkOption {
|
||||
internal = true;
|
||||
readOnly = true;
|
||||
type = lib.types.functionTo diskoLib.jsonType;
|
||||
default = dev: {
|
||||
deviceDependencies.lvm_vg.${config.vg} = [ dev ];
|
||||
};
|
||||
description = "Metadata";
|
||||
};
|
||||
_create = diskoLib.mkCreateOption {
|
||||
inherit config options;
|
||||
default = ''
|
||||
if ! (blkid "${config.device}" | grep -q 'TYPE='); then
|
||||
pvcreate "${config.device}"
|
||||
fi
|
||||
echo "${config.device}" >>"$disko_devices_dir"/lvm_${lib.escapeShellArg config.vg}
|
||||
'';
|
||||
};
|
||||
_mount = diskoLib.mkMountOption {
|
||||
inherit config options;
|
||||
default = { };
|
||||
};
|
||||
_unmount = diskoLib.mkUnmountOption {
|
||||
inherit config options;
|
||||
default = { };
|
||||
};
|
||||
_config = lib.mkOption {
|
||||
internal = true;
|
||||
readOnly = true;
|
||||
default = [ ];
|
||||
description = "NixOS configuration";
|
||||
};
|
||||
_pkgs = lib.mkOption {
|
||||
internal = true;
|
||||
readOnly = true;
|
||||
type = lib.types.functionTo (lib.types.listOf lib.types.package);
|
||||
default = pkgs: [ pkgs.gnugrep pkgs.lvm2 ];
|
||||
description = "Packages";
|
||||
};
|
||||
};
|
||||
}
|
||||
183
pkgs/disko/lib/types/lvm_vg.nix
Normal file
183
pkgs/disko/lib/types/lvm_vg.nix
Normal file
@@ -0,0 +1,183 @@
|
||||
{ config, options, lib, diskoLib, ... }:
|
||||
let
|
||||
# Load kernel modules to ensure device mapper types are available
|
||||
kernelModules =
|
||||
[
|
||||
# Prevent unbootable systems if LVM snapshots are present at boot time.
|
||||
"dm-snapshot"
|
||||
] ++
|
||||
lib.filter (x: x != "") (map
|
||||
(lv: lib.optionalString (lv.lvm_type != null && lv.lvm_type != "thinlv") "dm-${lv.lvm_type}")
|
||||
(lib.attrValues config.lvs));
|
||||
in
|
||||
{
|
||||
options = {
|
||||
name = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = config._module.args.name;
|
||||
description = "Name of the volume group";
|
||||
};
|
||||
type = lib.mkOption {
|
||||
type = lib.types.enum [ "lvm_vg" ];
|
||||
internal = true;
|
||||
description = "Type";
|
||||
};
|
||||
lvs = lib.mkOption {
|
||||
type = lib.types.attrsOf (lib.types.submodule ({ name, ... }@lv: {
|
||||
options = {
|
||||
name = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = name;
|
||||
description = "Name of the logical volume";
|
||||
};
|
||||
priority = lib.mkOption {
|
||||
type = lib.types.int;
|
||||
default = (if lv.config.lvm_type == "thin-pool" then 501 else 1000) + (if lib.hasInfix "100%" lv.config.size then 251 else 0);
|
||||
defaultText = lib.literalExpression ''
|
||||
if (lib.hasInfix "100%" lv.config.size) then 9001 else 1000
|
||||
'';
|
||||
description = "Priority of the logical volume, smaller values are created first";
|
||||
};
|
||||
size = lib.mkOption {
|
||||
type = lib.types.str; # TODO lvm size type
|
||||
description = "Size of the logical volume";
|
||||
};
|
||||
lvm_type = lib.mkOption {
|
||||
# TODO: add raid10
|
||||
type = lib.types.nullOr (lib.types.enum [ "mirror" "raid0" "raid1" "raid4" "raid5" "raid6" "thin-pool" "thinlv" ]); # TODO add all lib.types
|
||||
default = null; # maybe there is always a default type?
|
||||
description = "LVM type";
|
||||
};
|
||||
extraArgs = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ ];
|
||||
description = "Extra arguments";
|
||||
};
|
||||
pool = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
description = "Name of pool LV that this LV belongs to";
|
||||
};
|
||||
content = diskoLib.partitionType { parent = config; device = "/dev/${config.name}/${lv.config.name}"; };
|
||||
};
|
||||
}));
|
||||
default = { };
|
||||
description = "LVS for the volume group";
|
||||
};
|
||||
_meta = lib.mkOption {
|
||||
internal = true;
|
||||
readOnly = true;
|
||||
type = diskoLib.jsonType;
|
||||
default =
|
||||
diskoLib.deepMergeMap
|
||||
(lv:
|
||||
lib.optionalAttrs (lv.content != null) (lv.content._meta [ "lvm_vg" config.name ])
|
||||
)
|
||||
(lib.attrValues config.lvs);
|
||||
description = "Metadata";
|
||||
};
|
||||
_create = diskoLib.mkCreateOption {
|
||||
inherit config options;
|
||||
default =
|
||||
let
|
||||
sortedLvs = lib.sort (a: b: a.priority < b.priority) (lib.attrValues config.lvs);
|
||||
in
|
||||
''
|
||||
${lib.concatMapStringsSep "\n" (k: ''modprobe "${k}"'') kernelModules}
|
||||
readarray -t lvm_devices < <(cat "$disko_devices_dir"/lvm_${lib.escapeShellArg config.name})
|
||||
if ! vgdisplay "${config.name}" >/dev/null; then
|
||||
vgcreate "${config.name}" \
|
||||
"''${lvm_devices[@]}"
|
||||
fi
|
||||
${lib.concatMapStrings (lv: ''
|
||||
if ! lvdisplay "${config.name}/${lv.name}"; then
|
||||
lvcreate \
|
||||
--yes \
|
||||
${if (lv.lvm_type == "thinlv") then "-V"
|
||||
else if lib.hasInfix "%" lv.size then "-l" else "-L"} \
|
||||
${if lib.hasSuffix "%" lv.size then "${lv.size}FREE" else lv.size} \
|
||||
-n "${lv.name}" \
|
||||
${lib.optionalString (lv.lvm_type == "thinlv") "--thinpool=${lv.pool}"} \
|
||||
${lib.optionalString (lv.lvm_type != null && lv.lvm_type != "thinlv") "--type=${lv.lvm_type}"} \
|
||||
${toString lv.extraArgs} \
|
||||
"${config.name}"
|
||||
fi
|
||||
'') sortedLvs}
|
||||
|
||||
${lib.concatMapStrings (lv: ''
|
||||
${lib.optionalString (lv.content != null) lv.content._create}
|
||||
'') sortedLvs}
|
||||
'';
|
||||
};
|
||||
_mount = diskoLib.mkMountOption {
|
||||
inherit config options;
|
||||
default =
|
||||
let
|
||||
lvMounts = diskoLib.deepMergeMap
|
||||
(lv:
|
||||
lib.optionalAttrs (lv.content != null) lv.content._mount
|
||||
)
|
||||
(lib.attrValues config.lvs);
|
||||
in
|
||||
{
|
||||
dev = ''
|
||||
vgchange -a y
|
||||
${lib.concatMapStrings (x: x.dev or "") (lib.attrValues lvMounts)}
|
||||
'';
|
||||
fs = lvMounts.fs or { };
|
||||
};
|
||||
};
|
||||
_unmount = diskoLib.mkUnmountOption {
|
||||
inherit config options;
|
||||
default =
|
||||
let
|
||||
lvMounts = diskoLib.deepMergeMap
|
||||
(lv:
|
||||
lib.optionalAttrs (lv.content != null) lv.content._unmount
|
||||
)
|
||||
(lib.attrValues config.lvs);
|
||||
in {
|
||||
dev = ''
|
||||
${lib.concatMapStrings (x: x.dev or "") (lib.attrValues lvMounts)}
|
||||
vgchange -a n
|
||||
'';
|
||||
fs = lvMounts.fs or { };
|
||||
};
|
||||
};
|
||||
_config = lib.mkOption {
|
||||
internal = true;
|
||||
readOnly = true;
|
||||
default = [{ boot.initrd.kernelModules = kernelModules; }] ++
|
||||
map
|
||||
(lv: [
|
||||
(lib.optional (lv.content != null) lv.content._config)
|
||||
(lib.optional (lv.lvm_type != null) {
|
||||
boot.initrd.kernelModules = [
|
||||
(if lv.lvm_type == "mirror" then "dm-mirror" else "dm-raid")
|
||||
]
|
||||
++ lib.optional (lv.lvm_type == "raid0") "raid0"
|
||||
++ lib.optional (lv.lvm_type == "raid1") "raid1"
|
||||
# ++ lib.optional (lv.lvm_type == "raid10") "raid10"
|
||||
++ lib.optional
|
||||
(lv.lvm_type == "raid4" ||
|
||||
lv.lvm_type == "raid5" ||
|
||||
lv.lvm_type == "raid6") "raid456";
|
||||
|
||||
})
|
||||
])
|
||||
(lib.attrValues config.lvs);
|
||||
description = "NixOS configuration";
|
||||
};
|
||||
_pkgs = lib.mkOption {
|
||||
internal = true;
|
||||
readOnly = true;
|
||||
type = lib.types.functionTo (lib.types.listOf lib.types.package);
|
||||
default = pkgs: lib.flatten (map
|
||||
(lv:
|
||||
lib.optional (lv.content != null) (lv.content._pkgs pkgs)
|
||||
)
|
||||
(lib.attrValues config.lvs));
|
||||
description = "Packages";
|
||||
};
|
||||
};
|
||||
}
|
||||
99
pkgs/disko/lib/types/mdadm.nix
Normal file
99
pkgs/disko/lib/types/mdadm.nix
Normal file
@@ -0,0 +1,99 @@
|
||||
{ config, options, lib, diskoLib, ... }:
|
||||
{
|
||||
options = {
|
||||
name = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = config._module.args.name;
|
||||
description = "Name";
|
||||
};
|
||||
type = lib.mkOption {
|
||||
type = lib.types.enum [ "mdadm" ];
|
||||
default = "mdadm";
|
||||
internal = true;
|
||||
description = "Type";
|
||||
};
|
||||
level = lib.mkOption {
|
||||
type = lib.types.int;
|
||||
default = 1;
|
||||
description = "mdadm level";
|
||||
};
|
||||
metadata = lib.mkOption {
|
||||
type = lib.types.enum [ "1" "1.0" "1.1" "1.2" "default" "ddf" "imsm" ];
|
||||
default = "default";
|
||||
description = "Metadata";
|
||||
};
|
||||
content = diskoLib.deviceType { parent = config; device = "/dev/md/${config.name}"; };
|
||||
_meta = lib.mkOption {
|
||||
internal = true;
|
||||
readOnly = true;
|
||||
type = diskoLib.jsonType;
|
||||
default =
|
||||
lib.optionalAttrs (config.content != null) (config.content._meta [ "mdadm" config.name ]);
|
||||
description = "Metadata";
|
||||
};
|
||||
_create = diskoLib.mkCreateOption {
|
||||
inherit config options;
|
||||
default = ''
|
||||
if ! test -e "/dev/md/${config.name}"; then
|
||||
readarray -t disk_devices < <(cat "$disko_devices_dir"/raid_${lib.escapeShellArg config.name})
|
||||
echo 'y' | mdadm --create "/dev/md/${config.name}" \
|
||||
--level=${toString config.level} \
|
||||
--raid-devices="$(wc -l "$disko_devices_dir"/raid_${lib.escapeShellArg config.name} | cut -f 1 -d " ")" \
|
||||
--metadata=${config.metadata} \
|
||||
--force \
|
||||
--homehost=any \
|
||||
"''${disk_devices[@]}"
|
||||
partprobe "/dev/md/${config.name}"
|
||||
udevadm trigger --subsystem-match=block
|
||||
udevadm settle
|
||||
# for some reason mdadm devices spawn with an existing partition table, so we need to wipe it
|
||||
sgdisk --zap-all "/dev/md/${config.name}"
|
||||
fi
|
||||
${lib.optionalString (config.content != null) config.content._create}
|
||||
'';
|
||||
};
|
||||
_mount = diskoLib.mkMountOption {
|
||||
inherit config options;
|
||||
default =
|
||||
lib.optionalAttrs (config.content != null) config.content._mount;
|
||||
# TODO we probably need to assemble the mdadm somehow
|
||||
};
|
||||
_unmount = diskoLib.mkUnmountOption {
|
||||
inherit config options;
|
||||
default = let
|
||||
content = lib.optionalAttrs (config.content != null) config.content._unmount;
|
||||
in {
|
||||
fs = content.fs;
|
||||
dev = ''
|
||||
${content.dev or ""}
|
||||
if [ -e "/dev/md/${config.name}" ]; then
|
||||
mdadm --stop "/dev/md/${config.name}"
|
||||
fi
|
||||
'';
|
||||
};
|
||||
};
|
||||
_config = lib.mkOption {
|
||||
internal = true;
|
||||
readOnly = true;
|
||||
default =
|
||||
[
|
||||
(if lib.versionAtLeast (lib.versions.majorMinor lib.version) "23.11" then {
|
||||
boot.swraid.enable = true;
|
||||
} else {
|
||||
boot.initrd.services.swraid.enable = true;
|
||||
})
|
||||
] ++
|
||||
lib.optional (config.content != null) config.content._config;
|
||||
description = "NixOS configuration";
|
||||
};
|
||||
_pkgs = lib.mkOption {
|
||||
internal = true;
|
||||
readOnly = true;
|
||||
type = lib.types.functionTo (lib.types.listOf lib.types.package);
|
||||
default = pkgs: [
|
||||
pkgs.parted # for partprobe
|
||||
] ++ (lib.optionals (config.content != null) (config.content._pkgs pkgs));
|
||||
description = "Packages";
|
||||
};
|
||||
};
|
||||
}
|
||||
60
pkgs/disko/lib/types/mdraid.nix
Normal file
60
pkgs/disko/lib/types/mdraid.nix
Normal file
@@ -0,0 +1,60 @@
|
||||
{ config, options, lib, diskoLib, parent, device, ... }:
|
||||
{
|
||||
options = {
|
||||
type = lib.mkOption {
|
||||
type = lib.types.enum [ "mdraid" ];
|
||||
internal = true;
|
||||
description = "Type";
|
||||
};
|
||||
device = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Device";
|
||||
default = device;
|
||||
};
|
||||
|
||||
name = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Name";
|
||||
};
|
||||
_parent = lib.mkOption {
|
||||
internal = true;
|
||||
default = parent;
|
||||
};
|
||||
_meta = lib.mkOption {
|
||||
internal = true;
|
||||
readOnly = true;
|
||||
type = lib.types.functionTo diskoLib.jsonType;
|
||||
default = dev: {
|
||||
deviceDependencies.mdadm.${config.name} = [ dev ];
|
||||
};
|
||||
description = "Metadata";
|
||||
};
|
||||
_create = diskoLib.mkCreateOption {
|
||||
inherit config options;
|
||||
default = ''
|
||||
echo "${config.device}" >>"$disko_devices_dir"/raid_${lib.escapeShellArg config.name}
|
||||
'';
|
||||
};
|
||||
_mount = diskoLib.mkMountOption {
|
||||
inherit config options;
|
||||
default = { };
|
||||
};
|
||||
_unmount = diskoLib.mkUnmountOption {
|
||||
inherit config options;
|
||||
default = { };
|
||||
};
|
||||
_config = lib.mkOption {
|
||||
internal = true;
|
||||
readOnly = true;
|
||||
default = [ ];
|
||||
description = "NixOS configuration";
|
||||
};
|
||||
_pkgs = lib.mkOption {
|
||||
internal = true;
|
||||
readOnly = true;
|
||||
type = lib.types.functionTo (lib.types.listOf lib.types.package);
|
||||
default = pkgs: [ pkgs.mdadm ];
|
||||
description = "Packages";
|
||||
};
|
||||
};
|
||||
}
|
||||
82
pkgs/disko/lib/types/nodev.nix
Normal file
82
pkgs/disko/lib/types/nodev.nix
Normal file
@@ -0,0 +1,82 @@
|
||||
{ lib, config, options, diskoLib, rootMountPoint, ... }:
|
||||
{
|
||||
options = {
|
||||
type = lib.mkOption {
|
||||
type = lib.types.enum [ "nodev" ];
|
||||
default = "nodev";
|
||||
internal = true;
|
||||
description = "Device type";
|
||||
};
|
||||
fsType = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "File system type";
|
||||
};
|
||||
device = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "none";
|
||||
description = "Device to use";
|
||||
};
|
||||
mountpoint = lib.mkOption {
|
||||
type = lib.types.nullOr diskoLib.optionTypes.absolute-pathname;
|
||||
default = config._module.args.name;
|
||||
description = "Location to mount the file system at";
|
||||
};
|
||||
mountOptions = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ "defaults" ];
|
||||
description = "Options to pass to mount";
|
||||
};
|
||||
_meta = lib.mkOption {
|
||||
internal = true;
|
||||
readOnly = true;
|
||||
type = diskoLib.jsonType;
|
||||
default = { };
|
||||
description = "Metadata";
|
||||
};
|
||||
_create = diskoLib.mkCreateOption {
|
||||
inherit config options;
|
||||
default = "";
|
||||
};
|
||||
_mount = diskoLib.mkMountOption {
|
||||
inherit config options;
|
||||
default = lib.optionalAttrs (config.mountpoint != null) {
|
||||
fs.${config.mountpoint} = ''
|
||||
if ! findmnt ${config.fsType} "${rootMountPoint}${config.mountpoint}" > /dev/null 2>&1; then
|
||||
mount -t ${config.fsType} "${config.device}" "${rootMountPoint}${config.mountpoint}" \
|
||||
${lib.concatMapStringsSep " " (opt: "-o ${opt}") config.mountOptions} \
|
||||
-o X-mount.mkdir
|
||||
fi
|
||||
'';
|
||||
};
|
||||
};
|
||||
_unmount = diskoLib.mkUnmountOption {
|
||||
inherit config options;
|
||||
default = lib.optionalAttrs (config.mountpoint != null) {
|
||||
fs.${config.mountpoint} = ''
|
||||
if findmnt ${config.fsType} "${rootMountPoint}${config.mountpoint}" > /dev/null 2>&1; then
|
||||
umount "${rootMountPoint}${config.mountpoint}"
|
||||
fi
|
||||
'';
|
||||
};
|
||||
};
|
||||
_config = lib.mkOption {
|
||||
internal = true;
|
||||
readOnly = true;
|
||||
default = lib.optional (config.mountpoint != null) {
|
||||
fileSystems.${config.mountpoint} = {
|
||||
device = config.device;
|
||||
fsType = config.fsType;
|
||||
options = config.mountOptions;
|
||||
};
|
||||
};
|
||||
description = "NixOS configuration";
|
||||
};
|
||||
_pkgs = lib.mkOption {
|
||||
internal = true;
|
||||
readOnly = true;
|
||||
type = lib.types.functionTo (lib.types.listOf lib.types.package);
|
||||
default = _pkgs: [ ];
|
||||
description = "Packages";
|
||||
};
|
||||
};
|
||||
}
|
||||
134
pkgs/disko/lib/types/swap.nix
Normal file
134
pkgs/disko/lib/types/swap.nix
Normal file
@@ -0,0 +1,134 @@
|
||||
{ diskoLib, config, options, lib, parent, device, ... }:
|
||||
{
|
||||
options = {
|
||||
type = lib.mkOption {
|
||||
type = lib.types.enum [ "swap" ];
|
||||
internal = true;
|
||||
description = "Type";
|
||||
};
|
||||
device = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = device;
|
||||
description = "Device";
|
||||
};
|
||||
discardPolicy = lib.mkOption {
|
||||
default = null;
|
||||
example = "once";
|
||||
type = lib.types.nullOr (lib.types.enum [ "once" "pages" "both" ]);
|
||||
description = ''
|
||||
Specify the discard policy for the swap device. If "once", then the
|
||||
whole swap space is discarded at swapon invocation. If "pages",
|
||||
asynchronous discard on freed pages is performed, before returning to
|
||||
the available pages pool. With "both", both policies are activated.
|
||||
See swapon(8) for more information.
|
||||
'';
|
||||
};
|
||||
extraArgs = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ ];
|
||||
description = "Extra arguments";
|
||||
};
|
||||
mountOptions = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.nonEmptyStr;
|
||||
default = [ "defaults" ];
|
||||
example = [ "nofail" ];
|
||||
description = "Options used to mount the swap.";
|
||||
};
|
||||
priority = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.int;
|
||||
default = null;
|
||||
description = ''
|
||||
Specify the priority of the swap device. Priority is a value between 0 and 32767.
|
||||
Higher numbers indicate higher priority.
|
||||
null lets the kernel choose a priority, which will show up as a negative value.
|
||||
'';
|
||||
};
|
||||
randomEncryption = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Whether to randomly encrypt the swap";
|
||||
};
|
||||
resumeDevice = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Whether to use this as a boot.resumeDevice";
|
||||
};
|
||||
_parent = lib.mkOption {
|
||||
internal = true;
|
||||
default = parent;
|
||||
};
|
||||
_meta = lib.mkOption {
|
||||
internal = true;
|
||||
readOnly = true;
|
||||
type = lib.types.functionTo diskoLib.jsonType;
|
||||
default = _dev: { };
|
||||
description = "Metadata";
|
||||
};
|
||||
_create = diskoLib.mkCreateOption {
|
||||
inherit config options;
|
||||
# TODO: we don't support encrypted swap yet
|
||||
default = lib.optionalString (!config.randomEncryption) ''
|
||||
if ! blkid "${config.device}" -o export | grep -q '^TYPE='; then
|
||||
mkswap \
|
||||
${toString config.extraArgs} \
|
||||
"${config.device}"
|
||||
fi
|
||||
'';
|
||||
};
|
||||
_mount = diskoLib.mkMountOption {
|
||||
inherit config options;
|
||||
# TODO: we don't support encrypted swap yet
|
||||
default = lib.optionalAttrs (!config.randomEncryption) {
|
||||
fs.${config.device} = ''
|
||||
if test "''${DISKO_SKIP_SWAP:-}" != 1 && ! swapon --show | grep -q "^$(readlink -f "${config.device}") "; then
|
||||
swapon ${
|
||||
lib.optionalString (config.discardPolicy != null)
|
||||
"--discard${lib.optionalString (config.discardPolicy != "both")
|
||||
"=${config.discardPolicy}"
|
||||
}"} ${
|
||||
lib.optionalString (config.priority != null)
|
||||
"--priority=${toString config.priority}"
|
||||
} \
|
||||
--options=${lib.concatStringsSep "," config.mountOptions} \
|
||||
"${config.device}"
|
||||
fi
|
||||
'';
|
||||
};
|
||||
};
|
||||
_unmount = diskoLib.mkUnmountOption {
|
||||
inherit config options;
|
||||
default = lib.optionalAttrs (!config.randomEncryption) {
|
||||
fs.${config.device} = ''
|
||||
if swapon --show | grep -q "^$(readlink -f "${config.device}") "; then
|
||||
swapoff "${config.device}"
|
||||
fi
|
||||
'';
|
||||
};
|
||||
};
|
||||
_config = lib.mkOption {
|
||||
internal = true;
|
||||
readOnly = true;
|
||||
default = [{
|
||||
swapDevices = [{
|
||||
device = config.device;
|
||||
inherit (config) discardPolicy priority;
|
||||
randomEncryption = {
|
||||
enable = config.randomEncryption;
|
||||
# forward discard/TRIM attempts through dm-crypt
|
||||
allowDiscards = config.discardPolicy != null;
|
||||
};
|
||||
options = config.mountOptions;
|
||||
}];
|
||||
boot.resumeDevice = lib.mkIf config.resumeDevice config.device;
|
||||
}];
|
||||
description = "NixOS configuration";
|
||||
};
|
||||
_pkgs = lib.mkOption {
|
||||
internal = true;
|
||||
readOnly = true;
|
||||
type = lib.types.functionTo (lib.types.listOf lib.types.package);
|
||||
default = pkgs: [ pkgs.gnugrep pkgs.util-linux ];
|
||||
description = "Packages";
|
||||
};
|
||||
};
|
||||
}
|
||||
179
pkgs/disko/lib/types/table.nix
Normal file
179
pkgs/disko/lib/types/table.nix
Normal file
@@ -0,0 +1,179 @@
|
||||
{ config, options, lib, diskoLib, parent, device, ... }:
|
||||
{
|
||||
options = lib.warn ''
|
||||
The legacy table is outdated and should not be used. We recommend using the gpt type instead.
|
||||
Please note that certain features, such as the test framework, may not function properly with the legacy table type.
|
||||
If you encounter errors similar to:
|
||||
"error: The option `disko.devices.disk.disk1.content.partitions."[definition 1-entry 1]".content._config` is read-only, but it's set multiple times,"
|
||||
this is likely due to the use of the legacy table type.
|
||||
for a migration you can follow the guide at https://github.com/nix-community/disko/blob/master/docs/table-to-gpt.md
|
||||
''
|
||||
{
|
||||
type = lib.mkOption {
|
||||
type = lib.types.enum [ "table" ];
|
||||
internal = true;
|
||||
description = "Partition table";
|
||||
};
|
||||
device = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = device;
|
||||
description = "Device to partition";
|
||||
};
|
||||
format = lib.mkOption {
|
||||
type = lib.types.enum [ "gpt" "msdos" ];
|
||||
default = "gpt";
|
||||
description = "The kind of partition table";
|
||||
};
|
||||
partitions = lib.mkOption {
|
||||
type = lib.types.listOf (lib.types.submodule ({ name, ... }@partition: {
|
||||
options = {
|
||||
part-type = lib.mkOption {
|
||||
type = lib.types.enum [ "primary" "logical" "extended" ];
|
||||
default = "primary";
|
||||
description = "Partition type";
|
||||
};
|
||||
fs-type = lib.mkOption {
|
||||
type = lib.types.nullOr (lib.types.enum [ "btrfs" "ext2" "ext3" "ext4" "fat16" "fat32" "hfs" "hfs+" "linux-swap" "ntfs" "reiserfs" "udf" "xfs" ]);
|
||||
default = null;
|
||||
description = "Filesystem type to use";
|
||||
};
|
||||
name = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
description = "Name of the partition";
|
||||
};
|
||||
start = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "0%";
|
||||
description = "Start of the partition";
|
||||
};
|
||||
end = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "100%";
|
||||
description = "End of the partition";
|
||||
};
|
||||
flags = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ ];
|
||||
description = "Partition flags";
|
||||
};
|
||||
bootable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Whether to make the partition bootable";
|
||||
};
|
||||
content = diskoLib.partitionType { parent = config; device = diskoLib.deviceNumbering config.device partition.config._index; };
|
||||
_index = lib.mkOption {
|
||||
type = lib.types.int;
|
||||
internal = true;
|
||||
default = lib.toInt (lib.head (builtins.match ".*entry ([[:digit:]]+)]" name));
|
||||
defaultText = null;
|
||||
};
|
||||
};
|
||||
}));
|
||||
default = [ ];
|
||||
description = "List of partitions to add to the partition table";
|
||||
};
|
||||
_parent = lib.mkOption {
|
||||
internal = true;
|
||||
default = parent;
|
||||
};
|
||||
_meta = lib.mkOption {
|
||||
internal = true;
|
||||
readOnly = true;
|
||||
type = lib.types.functionTo diskoLib.jsonType;
|
||||
default = dev:
|
||||
lib.foldr lib.recursiveUpdate { } (lib.imap
|
||||
(_index: partition:
|
||||
lib.optionalAttrs (partition.content != null) (partition.content._meta dev)
|
||||
)
|
||||
config.partitions);
|
||||
description = "Metadata";
|
||||
};
|
||||
_create = diskoLib.mkCreateOption {
|
||||
inherit config options;
|
||||
default = ''
|
||||
if ! blkid "${config.device}" >/dev/null; then
|
||||
parted -s "${config.device}" -- mklabel ${config.format}
|
||||
${lib.concatStrings (map (partition: ''
|
||||
${lib.optionalString (config.format == "gpt") ''
|
||||
parted -s "${config.device}" -- mkpart "${diskoLib.hexEscapeUdevSymlink partition.name}" ${diskoLib.maybeStr partition.fs-type} ${partition.start} ${partition.end}
|
||||
''}
|
||||
${lib.optionalString (config.format == "msdos") ''
|
||||
parted -s "${config.device}" -- mkpart ${partition.part-type} ${diskoLib.maybeStr partition.fs-type} ${partition.start} ${partition.end}
|
||||
''}
|
||||
# ensure /dev/disk/by-path/..-partN exists before continuing
|
||||
partprobe "${config.device}"
|
||||
udevadm trigger --subsystem-match=block
|
||||
udevadm settle
|
||||
${lib.optionalString partition.bootable ''
|
||||
parted -s "${config.device}" -- set ${toString partition._index} boot on
|
||||
''}
|
||||
${lib.concatMapStringsSep "" (flag: ''
|
||||
parted -s "${config.device}" -- set ${toString partition._index} ${flag} on
|
||||
'') partition.flags}
|
||||
# ensure further operations can detect new partitions
|
||||
partprobe "${config.device}"
|
||||
udevadm trigger --subsystem-match=block
|
||||
udevadm settle
|
||||
'') config.partitions)}
|
||||
fi
|
||||
${lib.concatStrings (map (partition: ''
|
||||
${lib.optionalString (partition.content != null) partition.content._create}
|
||||
'') config.partitions)}
|
||||
'';
|
||||
};
|
||||
_mount = diskoLib.mkMountOption {
|
||||
inherit config options;
|
||||
default =
|
||||
let
|
||||
partMounts = lib.foldr lib.recursiveUpdate { } (map
|
||||
(partition:
|
||||
lib.optionalAttrs (partition.content != null) partition.content._mount
|
||||
)
|
||||
config.partitions);
|
||||
in
|
||||
{
|
||||
dev = partMounts.dev or "";
|
||||
fs = partMounts.fs or { };
|
||||
};
|
||||
};
|
||||
_unmount = diskoLib.mkUnmountOption {
|
||||
inherit config options;
|
||||
default =
|
||||
let
|
||||
partMounts = lib.foldr lib.recursiveUpdate { } (map
|
||||
(partition:
|
||||
lib.optionalAttrs (partition.content != null) partition.content._unmount
|
||||
)
|
||||
config.partitions);
|
||||
in
|
||||
{
|
||||
dev = partMounts.dev or "";
|
||||
fs = partMounts.fs or { };
|
||||
};
|
||||
};
|
||||
_config = lib.mkOption {
|
||||
internal = true;
|
||||
readOnly = true;
|
||||
default =
|
||||
map
|
||||
(partition:
|
||||
lib.optional (partition.content != null) partition.content._config
|
||||
)
|
||||
config.partitions;
|
||||
description = "NixOS configuration";
|
||||
};
|
||||
_pkgs = lib.mkOption {
|
||||
internal = true;
|
||||
readOnly = true;
|
||||
type = lib.types.functionTo (lib.types.listOf lib.types.package);
|
||||
default = pkgs:
|
||||
[ pkgs.parted pkgs.systemdMinimal ] ++ lib.flatten (map
|
||||
(partition:
|
||||
lib.optional (partition.content != null) (partition.content._pkgs pkgs)
|
||||
)
|
||||
config.partitions);
|
||||
description = "Packages";
|
||||
};
|
||||
};
|
||||
}
|
||||
59
pkgs/disko/lib/types/zfs.nix
Normal file
59
pkgs/disko/lib/types/zfs.nix
Normal file
@@ -0,0 +1,59 @@
|
||||
{ config, options, lib, diskoLib, parent, device, ... }:
|
||||
{
|
||||
options = {
|
||||
type = lib.mkOption {
|
||||
type = lib.types.enum [ "zfs" ];
|
||||
internal = true;
|
||||
description = "Type";
|
||||
};
|
||||
device = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = device;
|
||||
description = "Device";
|
||||
};
|
||||
pool = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Name of the ZFS pool";
|
||||
};
|
||||
_parent = lib.mkOption {
|
||||
internal = true;
|
||||
default = parent;
|
||||
};
|
||||
_meta = lib.mkOption {
|
||||
internal = true;
|
||||
readOnly = true;
|
||||
type = lib.types.functionTo diskoLib.jsonType;
|
||||
default = dev: {
|
||||
deviceDependencies.zpool.${config.pool} = [ dev ];
|
||||
};
|
||||
description = "Metadata";
|
||||
};
|
||||
_create = diskoLib.mkCreateOption {
|
||||
inherit config options;
|
||||
default = ''
|
||||
echo "${config.device}" >>"$disko_devices_dir"/zfs_${lib.escapeShellArg config.pool}
|
||||
'';
|
||||
};
|
||||
_mount = diskoLib.mkMountOption {
|
||||
inherit config options;
|
||||
default = { };
|
||||
};
|
||||
_unmount = diskoLib.mkUnmountOption {
|
||||
inherit config options;
|
||||
default = { };
|
||||
};
|
||||
_config = lib.mkOption {
|
||||
internal = true;
|
||||
readOnly = true;
|
||||
default = [ ];
|
||||
description = "NixOS configuration";
|
||||
};
|
||||
_pkgs = lib.mkOption {
|
||||
internal = true;
|
||||
readOnly = true;
|
||||
type = lib.types.functionTo (lib.types.listOf lib.types.package);
|
||||
default = pkgs: [ pkgs.zfs ];
|
||||
description = "Packages";
|
||||
};
|
||||
};
|
||||
}
|
||||
160
pkgs/disko/lib/types/zfs_fs.nix
Normal file
160
pkgs/disko/lib/types/zfs_fs.nix
Normal file
@@ -0,0 +1,160 @@
|
||||
{ config, options, lib, diskoLib, rootMountPoint, parent, ... }:
|
||||
{
|
||||
options = {
|
||||
name = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = config._module.args.name;
|
||||
description = "Name of the dataset";
|
||||
};
|
||||
|
||||
_name = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "${config._parent.name}/${config.name}";
|
||||
internal = true;
|
||||
description = "Fully quantified name for dataset";
|
||||
};
|
||||
|
||||
type = lib.mkOption {
|
||||
type = lib.types.enum [ "zfs_fs" ];
|
||||
default = "zfs_fs";
|
||||
internal = true;
|
||||
description = "Type";
|
||||
};
|
||||
|
||||
options = lib.mkOption {
|
||||
type = lib.types.attrsOf lib.types.str;
|
||||
default = { };
|
||||
description = "Options to set for the dataset";
|
||||
};
|
||||
|
||||
mountOptions = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ "defaults" ];
|
||||
description = "Mount options";
|
||||
};
|
||||
|
||||
mountpoint = lib.mkOption {
|
||||
type = lib.types.nullOr diskoLib.optionTypes.absolute-pathname;
|
||||
default = null;
|
||||
description = "Path to mount the dataset to";
|
||||
};
|
||||
|
||||
_parent = lib.mkOption {
|
||||
internal = true;
|
||||
default = parent;
|
||||
};
|
||||
|
||||
_meta = lib.mkOption {
|
||||
internal = true;
|
||||
readOnly = true;
|
||||
type = lib.types.functionTo diskoLib.jsonType;
|
||||
default = _dev: { };
|
||||
description = "Metadata";
|
||||
};
|
||||
|
||||
_createFilesystem = lib.mkOption {
|
||||
internal = true;
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
};
|
||||
|
||||
_create = diskoLib.mkCreateOption
|
||||
{
|
||||
inherit config options;
|
||||
# -u prevents mounting newly created datasets, which is
|
||||
# important to prevent accidental shadowing of mount points
|
||||
# since (create order != mount order)
|
||||
# -p creates parents automatically
|
||||
default =
|
||||
let
|
||||
createOptions = (lib.optionalAttrs (config.mountpoint != null) { mountpoint = config.mountpoint; }) // config.options;
|
||||
# All options defined as PROP_ONETIME or PROP_ONETIME_DEFAULT in https://github.com/openzfs/zfs/blob/master/module/zcommon/zfs_prop.c
|
||||
onetimeProperties = [
|
||||
"encryption"
|
||||
"casesensitivity"
|
||||
"utf8only"
|
||||
"normalization"
|
||||
"volblocksize"
|
||||
"pbkdf2iters"
|
||||
"pbkdf2salt"
|
||||
"keyformat"
|
||||
];
|
||||
updateOptions = builtins.removeAttrs createOptions onetimeProperties;
|
||||
in
|
||||
''
|
||||
if ! zfs get type ${config._name} >/dev/null 2>&1; then
|
||||
${if config._createFilesystem then ''
|
||||
zfs create -up ${config._name} \
|
||||
${lib.concatStringsSep " " (lib.mapAttrsToList (n: v: "-o ${n}=${v}") (createOptions))}
|
||||
'' else ''
|
||||
# don't create anything for root dataset of zpools
|
||||
true
|
||||
''}
|
||||
${lib.optionalString (updateOptions != {}) ''
|
||||
else
|
||||
zfs set -u ${lib.concatStringsSep " " (lib.mapAttrsToList (n: v: "${n}=${v}") updateOptions)} ${config._name}
|
||||
''}
|
||||
fi
|
||||
'';
|
||||
};
|
||||
|
||||
_mount = diskoLib.mkMountOption {
|
||||
inherit config options;
|
||||
default =
|
||||
(lib.optionalAttrs (config.options.keylocation or "none" != "none") {
|
||||
dev = ''
|
||||
if [ "$(zfs get keystatus ${config._name} -H -o value)" == "unavailable" ]; then
|
||||
zfs load-key ${config._name}
|
||||
fi
|
||||
'';
|
||||
}) // lib.optionalAttrs (config.options.mountpoint or "" != "none" && config.options.canmount or "" != "off") {
|
||||
fs.${config.mountpoint} = ''
|
||||
if ! findmnt ${config._name} "${rootMountPoint}${config.mountpoint}" >/dev/null 2>&1; then
|
||||
mount ${config._name} "${rootMountPoint}${config.mountpoint}" \
|
||||
-o X-mount.mkdir \
|
||||
${lib.concatMapStringsSep " " (opt: "-o ${opt}") config.mountOptions} \
|
||||
${lib.optionalString ((config.options.mountpoint or "") != "legacy") "-o zfsutil"} \
|
||||
-t zfs
|
||||
fi
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
_unmount = diskoLib.mkUnmountOption {
|
||||
inherit config options;
|
||||
default =
|
||||
(lib.optionalAttrs (config.options.keylocation or "none" != "none") {
|
||||
dev = "zfs unload-key ${config.name}";
|
||||
}) // lib.optionalAttrs (config.options.mountpoint or "" != "none" && config.options.canmount or "" != "off") {
|
||||
fs.${config.mountpoint} = ''
|
||||
if findmnt ${config._name} "${rootMountPoint}${config.mountpoint}" >/dev/null 2>&1; then
|
||||
umount "${rootMountPoint}${config.mountpoint}"
|
||||
fi
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
_config = lib.mkOption {
|
||||
internal = true;
|
||||
readOnly = true;
|
||||
default =
|
||||
lib.optional (config.options.mountpoint or "" != "none" && config.options.canmount or "" != "off") {
|
||||
fileSystems.${config.mountpoint} = {
|
||||
device = "${config._name}";
|
||||
fsType = "zfs";
|
||||
options = config.mountOptions ++ lib.optional ((config.options.mountpoint or "") != "legacy") "zfsutil";
|
||||
};
|
||||
};
|
||||
description = "NixOS configuration";
|
||||
};
|
||||
|
||||
_pkgs = lib.mkOption {
|
||||
internal = true;
|
||||
readOnly = true;
|
||||
type = lib.types.functionTo (lib.types.listOf lib.types.package);
|
||||
default = pkgs: [ pkgs.util-linux ];
|
||||
description = "Packages";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
113
pkgs/disko/lib/types/zfs_volume.nix
Normal file
113
pkgs/disko/lib/types/zfs_volume.nix
Normal file
@@ -0,0 +1,113 @@
|
||||
{ config, options, lib, diskoLib, parent, ... }:
|
||||
{
|
||||
options = {
|
||||
name = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = config._module.args.name;
|
||||
description = "Name of the dataset";
|
||||
};
|
||||
type = lib.mkOption {
|
||||
type = lib.types.enum [ "zfs_volume" ];
|
||||
default = "zfs_volume";
|
||||
internal = true;
|
||||
description = "Type";
|
||||
};
|
||||
options = lib.mkOption {
|
||||
type = lib.types.attrsOf lib.types.str;
|
||||
default = { };
|
||||
description = "Options to set for the dataset";
|
||||
};
|
||||
extraArgs = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ ];
|
||||
description = "Extra arguments passed to `zfs create`";
|
||||
};
|
||||
mountOptions = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ "defaults" ];
|
||||
description = "Mount options";
|
||||
};
|
||||
|
||||
# volume options
|
||||
size = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str; # TODO size
|
||||
default = null;
|
||||
description = "Size of the dataset";
|
||||
};
|
||||
|
||||
content = diskoLib.partitionType { parent = config; device = "/dev/zvol/${config._parent.name}/${config.name}"; };
|
||||
|
||||
_parent = lib.mkOption {
|
||||
internal = true;
|
||||
default = parent;
|
||||
};
|
||||
_meta = lib.mkOption {
|
||||
internal = true;
|
||||
readOnly = true;
|
||||
type = lib.types.functionTo diskoLib.jsonType;
|
||||
default = dev:
|
||||
lib.optionalAttrs (config.content != null) (config.content._meta dev);
|
||||
description = "Metadata";
|
||||
};
|
||||
_create = diskoLib.mkCreateOption {
|
||||
inherit config options;
|
||||
default = ''
|
||||
if ! zfs get type "${config._parent.name}/${config.name}" >/dev/null 2>&1; then
|
||||
zfs create "${config._parent.name}/${config.name}" \
|
||||
${lib.concatStringsSep " " (lib.mapAttrsToList (n: v: "-o ${n}=${v}") config.options)} \
|
||||
-V ${config.size} ${toString (builtins.map lib.escapeShellArg config.extraArgs)}
|
||||
zvol_wait
|
||||
partprobe "/dev/zvol/${config._parent.name}/${config.name}"
|
||||
udevadm trigger --subsystem-match=block
|
||||
udevadm settle
|
||||
fi
|
||||
${lib.optionalString (config.content != null) config.content._create}
|
||||
'';
|
||||
};
|
||||
_mount = diskoLib.mkMountOption {
|
||||
inherit config options;
|
||||
default = {
|
||||
dev = ''
|
||||
${lib.optionalString (config.options.keylocation or "none" != "none") ''
|
||||
if [ "$(zfs get keystatus ${config.name} -H -o value)" == "unavailable" ]; then
|
||||
zfs load-key ${config.name}
|
||||
fi
|
||||
''}
|
||||
|
||||
${config.content._mount.dev or ""}
|
||||
'';
|
||||
fs = config.content._mount.fs or { };
|
||||
};
|
||||
};
|
||||
_unmount = diskoLib.mkUnmountOption {
|
||||
inherit config options;
|
||||
default = {
|
||||
dev = ''
|
||||
${lib.optionalString (config.options.keylocation or "none" != "none") "zfs unload-key ${config.name}"}
|
||||
|
||||
${config.content._unmount.dev or ""}
|
||||
'';
|
||||
|
||||
fs = config.content._unmount.fs;
|
||||
};
|
||||
};
|
||||
_config = lib.mkOption {
|
||||
internal = true;
|
||||
readOnly = true;
|
||||
default =
|
||||
lib.optional (config.content != null) config.content._config;
|
||||
description = "NixOS configuration";
|
||||
};
|
||||
_pkgs = lib.mkOption {
|
||||
internal = true;
|
||||
readOnly = true;
|
||||
type = lib.types.functionTo (lib.types.listOf lib.types.package);
|
||||
default = pkgs: [
|
||||
pkgs.util-linux
|
||||
pkgs.parted # for partprobe
|
||||
] ++ lib.optionals (config.content != null) (config.content._pkgs pkgs);
|
||||
description = "Packages";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
390
pkgs/disko/lib/types/zpool.nix
Normal file
390
pkgs/disko/lib/types/zpool.nix
Normal file
@@ -0,0 +1,390 @@
|
||||
{ config, options, lib, diskoLib, rootMountPoint, ... }:
|
||||
let
|
||||
# TODO: Consider expanding to handle `file` and `draid` mode options.
|
||||
modeOptions = [
|
||||
""
|
||||
"mirror"
|
||||
"raidz"
|
||||
"raidz1"
|
||||
"raidz2"
|
||||
"raidz3"
|
||||
];
|
||||
in
|
||||
{
|
||||
options = {
|
||||
name = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = config._module.args.name;
|
||||
description = "Name of the ZFS pool";
|
||||
};
|
||||
type = lib.mkOption {
|
||||
type = lib.types.enum [ "zpool" ];
|
||||
default = "zpool";
|
||||
internal = true;
|
||||
description = "Type";
|
||||
};
|
||||
mode = lib.mkOption {
|
||||
default = "";
|
||||
example = {
|
||||
mode = {
|
||||
topology = {
|
||||
type = "topology";
|
||||
vdev = [
|
||||
{
|
||||
# Members can be either specified by a full path or by a disk name
|
||||
# This is example uses the full path
|
||||
members = [ "/dev/disk/by-id/wwn-0x5000c500af8b2a14" ];
|
||||
}
|
||||
];
|
||||
log = [
|
||||
{
|
||||
# Example using gpt partition labels
|
||||
# This expects an disk called `ssd` with a gpt partition called `zfs`
|
||||
# disko.devices.disk.ssd = {
|
||||
# type = "disk";
|
||||
# device = "/dev/nvme0n1";
|
||||
# content = {
|
||||
# type = "gpt";
|
||||
# partitions = {
|
||||
# zfs = {
|
||||
# size = "100%";
|
||||
# content = {
|
||||
# type = "zfs";
|
||||
# # use your own pool name here
|
||||
# pool = "zroot";
|
||||
# };
|
||||
# };
|
||||
# };
|
||||
# };
|
||||
# };
|
||||
members = [ "ssd" ];
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
type = (lib.types.oneOf [
|
||||
(lib.types.enum modeOptions)
|
||||
(lib.types.attrsOf (diskoLib.subType {
|
||||
types = {
|
||||
topology =
|
||||
let
|
||||
vdev = lib.types.submodule ({ ... }: {
|
||||
options = {
|
||||
mode = lib.mkOption {
|
||||
type = lib.types.enum modeOptions;
|
||||
default = "";
|
||||
description = "Mode of the zfs vdev";
|
||||
};
|
||||
members = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
description = "Members of the vdev";
|
||||
};
|
||||
};
|
||||
});
|
||||
in
|
||||
lib.types.submodule
|
||||
({ ... }: {
|
||||
options = {
|
||||
type = lib.mkOption {
|
||||
type = lib.types.enum [ "topology" ];
|
||||
default = "topology";
|
||||
internal = true;
|
||||
description = "Type";
|
||||
};
|
||||
# zfs device types
|
||||
vdev = lib.mkOption {
|
||||
type = lib.types.listOf vdev;
|
||||
default = [ ];
|
||||
description = ''
|
||||
A list of storage vdevs. See
|
||||
https://openzfs.github.io/openzfs-docs/man/master/7/zpoolconcepts.7.html#Virtual_Devices_(vdevs)
|
||||
for details.
|
||||
'';
|
||||
example = [
|
||||
{
|
||||
mode = "mirror";
|
||||
members = [ "x" "y" ];
|
||||
}
|
||||
{
|
||||
members = [ "z" ];
|
||||
}
|
||||
];
|
||||
};
|
||||
spare = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ ];
|
||||
description = ''
|
||||
A list of devices to use as hot spares. See
|
||||
https://openzfs.github.io/openzfs-docs/man/master/7/zpoolconcepts.7.html#Hot_Spares
|
||||
for details.
|
||||
'';
|
||||
example = [ "x" "y" ];
|
||||
};
|
||||
log = lib.mkOption {
|
||||
type = lib.types.listOf vdev;
|
||||
default = [ ];
|
||||
description = ''
|
||||
A list of vdevs used for the zfs intent log (ZIL). See
|
||||
https://openzfs.github.io/openzfs-docs/man/master/7/zpoolconcepts.7.html#Intent_Log
|
||||
for details.
|
||||
'';
|
||||
example = [
|
||||
{
|
||||
mode = "mirror";
|
||||
members = [ "x" "y" ];
|
||||
}
|
||||
{
|
||||
members = [ "z" ];
|
||||
}
|
||||
];
|
||||
};
|
||||
dedup = lib.mkOption {
|
||||
type = lib.types.listOf vdev;
|
||||
default = [ ];
|
||||
description = ''
|
||||
A list of vdevs used for the deduplication table. See
|
||||
https://openzfs.github.io/openzfs-docs/man/master/7/zpoolconcepts.7.html#dedup
|
||||
for details.
|
||||
'';
|
||||
example = [
|
||||
{
|
||||
mode = "mirror";
|
||||
members = [ "x" "y" ];
|
||||
}
|
||||
{
|
||||
members = [ "z" ];
|
||||
}
|
||||
];
|
||||
};
|
||||
special = lib.mkOption {
|
||||
type = lib.types.either (lib.types.listOf vdev) (lib.types.nullOr vdev);
|
||||
default = [ ];
|
||||
description = ''
|
||||
A list of vdevs used as special devices. See
|
||||
https://openzfs.github.io/openzfs-docs/man/master/7/zpoolconcepts.7.html#special
|
||||
for details.
|
||||
'';
|
||||
example = [
|
||||
{
|
||||
mode = "mirror";
|
||||
members = [ "x" "y" ];
|
||||
}
|
||||
{
|
||||
members = [ "z" ];
|
||||
}
|
||||
];
|
||||
};
|
||||
cache = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ ];
|
||||
description = ''
|
||||
A dedicated zfs cache device (L2ARC). See
|
||||
https://openzfs.github.io/openzfs-docs/man/master/7/zpoolconcepts.7.html#Cache_Devices
|
||||
for details.
|
||||
'';
|
||||
example = [ "x" "y" ];
|
||||
};
|
||||
};
|
||||
});
|
||||
};
|
||||
extraArgs.parent = config;
|
||||
}))
|
||||
]);
|
||||
description = "Mode of the ZFS pool";
|
||||
};
|
||||
options = lib.mkOption {
|
||||
type = lib.types.attrsOf lib.types.str;
|
||||
default = { };
|
||||
description = "Options for the ZFS pool";
|
||||
};
|
||||
rootFsOptions = lib.mkOption {
|
||||
type = lib.types.attrsOf lib.types.str;
|
||||
default = { };
|
||||
description = "Options for the root filesystem";
|
||||
};
|
||||
mountpoint = lib.mkOption {
|
||||
type = lib.types.nullOr diskoLib.optionTypes.absolute-pathname;
|
||||
default = null;
|
||||
description = "The mountpoint of the pool";
|
||||
};
|
||||
mountOptions = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ "defaults" ];
|
||||
description = "Options to pass to mount";
|
||||
};
|
||||
datasets = lib.mkOption {
|
||||
type = lib.types.attrsOf (diskoLib.subType {
|
||||
types = { inherit (diskoLib.types) zfs_fs zfs_volume; };
|
||||
extraArgs.parent = config;
|
||||
});
|
||||
description = "List of datasets to define";
|
||||
};
|
||||
_meta = lib.mkOption {
|
||||
internal = true;
|
||||
readOnly = true;
|
||||
type = diskoLib.jsonType;
|
||||
default =
|
||||
diskoLib.deepMergeMap (dataset: dataset._meta [ "zpool" config.name ]) (lib.attrValues config.datasets);
|
||||
description = "Metadata";
|
||||
};
|
||||
_create = diskoLib.mkCreateOption {
|
||||
inherit config options;
|
||||
default =
|
||||
let
|
||||
formatOutput = type: mode: members: ''
|
||||
entries+=("${type} ${mode}=${
|
||||
lib.concatMapStringsSep " "
|
||||
(d: if lib.strings.hasPrefix "/" d then d else "/dev/disk/by-partlabel/disk-${d}-zfs") members
|
||||
}")
|
||||
'';
|
||||
formatVdev = type: vdev: formatOutput type vdev.mode vdev.members;
|
||||
formatVdevList = type: vdevs: lib.concatMapStrings
|
||||
(formatVdev type)
|
||||
(builtins.sort (a: _: a.mode == "") vdevs);
|
||||
hasTopology = !(builtins.isString config.mode);
|
||||
mode = if hasTopology then "prescribed" else config.mode;
|
||||
topology = lib.optionalAttrs hasTopology config.mode.topology;
|
||||
in
|
||||
''
|
||||
readarray -t zfs_devices < <(cat "$disko_devices_dir/zfs_${config.name}")
|
||||
if [ ''${#zfs_devices[@]} -eq 0 ]; then
|
||||
echo "no devices found for zpool ${config.name}. Did you misspell the pool name?" >&2
|
||||
exit 1
|
||||
fi
|
||||
# Try importing the pool without mounting anything if it exists.
|
||||
# This allows us to set mounpoints.
|
||||
if zpool import -N -f "${config.name}" || zpool list "${config.name}"; then
|
||||
echo "not creating zpool ${config.name} as a pool with that name already exists" >&2
|
||||
else
|
||||
continue=1
|
||||
for dev in "''${zfs_devices[@]}"; do
|
||||
if ! blkid "$dev" >/dev/null; then
|
||||
# blkid fails, so device seems empty
|
||||
:
|
||||
elif (blkid "$dev" -o export | grep '^PTUUID='); then
|
||||
echo "device $dev already has a partuuid, skipping creating zpool ${config.name}" >&2
|
||||
continue=0
|
||||
elif (blkid "$dev" -o export | grep '^TYPE=zfs_member'); then
|
||||
# zfs_member is a zfs partition, so we try to add the device to the pool
|
||||
:
|
||||
elif (blkid "$dev" -o export | grep '^TYPE='); then
|
||||
echo "device $dev already has a partition, skipping creating zpool ${config.name}" >&2
|
||||
continue=0
|
||||
fi
|
||||
done
|
||||
if [ $continue -eq 1 ]; then
|
||||
topology=""
|
||||
# For shell check
|
||||
mode="${mode}"
|
||||
if [ "$mode" != "prescribed" ]; then
|
||||
topology="${mode} ''${zfs_devices[*]}"
|
||||
else
|
||||
entries=()
|
||||
${lib.optionalString (hasTopology && topology.vdev != null)
|
||||
(formatVdevList "" topology.vdev)}
|
||||
${lib.optionalString (hasTopology && topology.spare != [])
|
||||
(formatOutput "spare" "" topology.spare)}
|
||||
${lib.optionalString (hasTopology && topology.log != [])
|
||||
(formatVdevList "log" topology.log)}
|
||||
${lib.optionalString (hasTopology && topology.dedup != [])
|
||||
(formatVdevList "dedup" topology.dedup)}
|
||||
${lib.optionalString (hasTopology && topology.special != null && topology.special != [])
|
||||
(formatVdevList "special" (lib.lists.toList topology.special))}
|
||||
${lib.optionalString (hasTopology && topology.cache != [])
|
||||
(formatOutput "cache" "" topology.cache)}
|
||||
all_devices=()
|
||||
last_type=
|
||||
for line in "''${entries[@]}"; do
|
||||
# lineformat is type mode=device1 device2 device3
|
||||
mode="''${line%%=*}"
|
||||
type="''${mode%% *}"
|
||||
mode="''${mode#"$type "}"
|
||||
devs="''${line#*=}"
|
||||
IFS=' ' read -r -a devices <<< "$devs"
|
||||
all_devices+=("''${devices[@]}")
|
||||
if ! [ "$type" = "$last_type" ]; then
|
||||
topology+=" $type"
|
||||
last_type="$type"
|
||||
fi
|
||||
topology+=" ''${mode} ''${devices[*]}"
|
||||
done
|
||||
# all_devices sorted should equal zfs_devices sorted
|
||||
all_devices_list=$(echo "''${all_devices[*]}" | tr ' ' '\n' | sort)
|
||||
zfs_devices_list=$(echo "''${zfs_devices[*]}" | tr ' ' '\n' | sort)
|
||||
if [[ "$all_devices_list" != "$zfs_devices_list" ]]; then
|
||||
echo "not all disks accounted for, skipping creating zpool ${config.name}" >&2
|
||||
diff <(echo "$all_devices_list" ) <(echo "$zfs_devices_list") >&2
|
||||
continue=0
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
if [ $continue -eq 1 ]; then
|
||||
zpool create -f "${config.name}" \
|
||||
-R ${rootMountPoint} \
|
||||
${lib.concatStringsSep " " (lib.mapAttrsToList (n: v: "-o ${n}=${v}") config.options)} \
|
||||
${lib.concatStringsSep " " (lib.mapAttrsToList (n: v: "-O ${n}=${v}") config.rootFsOptions)} \
|
||||
''${topology:+ $topology}
|
||||
if [[ $(zfs get -H mounted "${config.name}" | cut -f3) == "yes" ]]; then
|
||||
zfs unmount "${config.name}"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
${lib.concatMapStrings (dataset: dataset._create) (lib.attrValues config.datasets)}
|
||||
'';
|
||||
};
|
||||
_mount = diskoLib.mkMountOption {
|
||||
inherit config options;
|
||||
default =
|
||||
let
|
||||
datasetFilesystemsMounts = diskoLib.deepMergeMap (dataset: dataset._mount.fs or {}) (lib.attrValues config.datasets);
|
||||
in
|
||||
{
|
||||
dev = ''
|
||||
zpool list "${config.name}" >/dev/null 2>/dev/null ||
|
||||
zpool import -l -R ${rootMountPoint} "${config.name}"
|
||||
|
||||
${lib.concatMapStrings (x: x._mount.dev or "") (lib.attrValues config.datasets)}
|
||||
'';
|
||||
fs = datasetFilesystemsMounts;
|
||||
};
|
||||
};
|
||||
_unmount = diskoLib.mkUnmountOption {
|
||||
inherit config options;
|
||||
default = {
|
||||
dev = ''
|
||||
${lib.concatMapStrings (dataset: dataset._unmount.dev or "") (lib.attrValues config.datasets)}
|
||||
|
||||
if zpool list "${config.name}" >/dev/null 2>/dev/null; then
|
||||
zpool export "${config.name}"
|
||||
fi
|
||||
'';
|
||||
fs = diskoLib.deepMergeMap (dataset: dataset._unmount.fs or {}) (lib.attrValues config.datasets);
|
||||
};
|
||||
};
|
||||
_config = lib.mkOption {
|
||||
internal = true;
|
||||
readOnly = true;
|
||||
default = map (dataset: dataset._config) (lib.attrValues config.datasets);
|
||||
description = "NixOS configuration";
|
||||
};
|
||||
_pkgs = lib.mkOption {
|
||||
internal = true;
|
||||
readOnly = true;
|
||||
type = lib.types.functionTo (lib.types.listOf lib.types.package);
|
||||
default = pkgs: [ pkgs.gnugrep pkgs.util-linux ] ++ lib.flatten (map (dataset: dataset._pkgs pkgs) (lib.attrValues config.datasets));
|
||||
description = "Packages";
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
datasets."__root" = {
|
||||
_name = config.name;
|
||||
_createFilesystem = false;
|
||||
type = "zfs_fs";
|
||||
mountpoint = config.mountpoint;
|
||||
options = config.rootFsOptions;
|
||||
mountOptions = config.mountOptions;
|
||||
};
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user