Skip to content

Commit

Permalink
Merge pull request #568 from nix-community/idempotent-create
Browse files Browse the repository at this point in the history
types *: make create idempotent
  • Loading branch information
Lassulus authored Mar 13, 2024
2 parents fe064a6 + f6b72bf commit 59e50d4
Show file tree
Hide file tree
Showing 15 changed files with 215 additions and 141 deletions.
2 changes: 1 addition & 1 deletion disk-deactivate/disk-deactivate
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
set -efux -o pipefail
# dependencies: bash jq util-linux lvm2 mdadm zfs
# dependencies: bash jq util-linux lvm2 mdadm zfs gnugrep
disk=$(realpath "$1")

lsblk -a -f >&2
Expand Down
2 changes: 1 addition & 1 deletion example/zfs.nix
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
"com.sun:auto-snapshot" = "false";
};
mountpoint = "/";
postCreateHook = "zfs snapshot zroot@blank";
postCreateHook = "zfs list -t snapshot -H -o name | grep -E '^zroot@blank$' || zfs snapshot zroot@blank";

datasets = {
zfs_fs = {
Expand Down
12 changes: 10 additions & 2 deletions lib/tests.nix
Original file line number Diff line number Diff line change
Expand Up @@ -243,15 +243,23 @@ let
# running direct mode
machine.succeed("${tsp-format}")
machine.succeed("${tsp-mount}")
machine.succeed("${tsp-mount}") # verify that the command is idempotent
machine.succeed("${tsp-mount}") # verify that mount is idempotent
machine.succeed("${tsp-disko}") # verify that we can destroy and recreate
machine.succeed("mkdir -p /mnt/home")
machine.succeed("touch /mnt/home/testfile")
machine.succeed("${tsp-format}") # verify that format is idempotent
machine.succeed("test -e /mnt/home/testfile")
''}
${lib.optionalString (testMode == "module") ''
# running module mode
machine.succeed("${nodes.machine.system.build.formatScript}")
machine.succeed("${nodes.machine.system.build.mountScript}")
machine.succeed("${nodes.machine.system.build.mountScript}") # verify that the command is idempotent
machine.succeed("${nodes.machine.system.build.mountScript}") # verify that mount is idempotent
machine.succeed("${nodes.machine.system.build.diskoScript}") # verify that we can destroy and recreate again
machine.succeed("mkdir -p /mnt/home")
machine.succeed("touch /mnt/home/testfile")
machine.succeed("${nodes.machine.system.build.formatScript}") # verify that format is idempotent
machine.succeed("test -e /mnt/home/testfile")
''}
${postDisko}
Expand Down
55 changes: 33 additions & 22 deletions lib/types/btrfs.nix
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,11 @@ let
swapCreate = mountpoint: swap:
lib.concatMapStringsSep
"\n"
(file: ''btrfs filesystem mkswapfile --size ${file.size} "${mountpoint}/${file.path}"'')
(file: ''
if ! test -e "${mountpoint}/${file.path}"; then
btrfs filesystem mkswapfile --size ${file.size} "${mountpoint}/${file.path}"
fi
'')
(lib.attrValues swap);

in
Expand Down Expand Up @@ -112,26 +116,33 @@ in
_create = diskoLib.mkCreateOption {
inherit config options;
default = ''
mkfs.btrfs ${config.device} ${toString config.extraArgs}
${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")"
btrfs subvolume create "$SUBVOL_ABS_PATH" ${toString subvol.extraArgs}
${swapCreate "$SUBVOL_ABS_PATH" subvol.swap}
)
'') (lib.attrValues config.subvolumes)}
# 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
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 {
Expand Down Expand Up @@ -206,7 +217,7 @@ in
readOnly = true;
type = lib.types.functionTo (lib.types.listOf lib.types.package);
default = pkgs:
[ pkgs.btrfs-progs pkgs.coreutils ];
[ pkgs.btrfs-progs pkgs.coreutils pkgs.gnugrep ];
description = "Packages";
};
};
Expand Down
10 changes: 6 additions & 4 deletions lib/types/filesystem.nix
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,11 @@
_create = diskoLib.mkCreateOption {
inherit config options;
default = ''
mkfs.${config.format} \
${toString config.extraArgs} \
${config.device}
if ! (blkid '${config.device}' | grep -q 'TYPE='); then
mkfs.${config.format} \
${toString config.extraArgs} \
${config.device}
fi
'';
};
_mount = diskoLib.mkMountOption {
Expand Down Expand Up @@ -79,7 +81,7 @@
readOnly = true;
# type = lib.types.functionTo (lib.types.listOf lib.types.package);
default = pkgs:
[ pkgs.util-linux ] ++ (
[ 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 ]
Expand Down
60 changes: 35 additions & 25 deletions lib/types/gpt.nix
Original file line number Diff line number Diff line change
Expand Up @@ -153,34 +153,44 @@ in
_create = diskoLib.mkCreateOption {
inherit config options;
default = ''
${lib.concatStrings (map (partition: ''
sgdisk \
--align-end \
--new=${toString partition._index}:${partition.start}:${partition.end} \
--change-name=${toString partition._index}:${partition.label} \
--typecode=${toString partition._index}:${partition.type} \
${config.device}
# ensure /dev/disk/by-path/..-partN exists before continuing
partprobe ${config.device}
udevadm trigger --subsystem-match=block
udevadm settle
${lib.optionalString (partition.content != null) partition.content._create}
'') sortedPartitions)}
if ! blkid "${config.device}" >/dev/null; then
${lib.concatStrings (map (partition: ''
if sgdisk \
--info=${toString partition._index} \
${config.device} > /dev/null 2>&1
then
sgdisk \
--align-end \
--new=${toString partition._index}:${partition.start}:${partition.end} \
--change-name=${toString partition._index}:${partition.label} \
--typecode=${toString partition._index}:${partition.type} \
${config.device}
# ensure /dev/disk/by-path/..-partN exists before continuing
partprobe ${config.device}
udevadm trigger --subsystem-match=block
udevadm settle
fi
'') sortedPartitions)}
${
lib.optionalString (sortedHybridPartitions != [])
("sgdisk -h "
+ (lib.concatStringsSep ":" (map (p: (toString p._index)) sortedHybridPartitions))
+ (
lib.optionalString (!config.efiGptPartitionFirst) ":EE "
${
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
)
+ parent.device)
}
${lib.concatMapStrings (p:
p.hybrid._create
)
sortedHybridPartitions}
sortedHybridPartitions
}
fi
${lib.concatStrings (map (partition: ''
${lib.optionalString (partition.content != null) partition.content._create}
'') sortedPartitions)}
'';
};
_mount = diskoLib.mkMountOption {
Expand Down
44 changes: 23 additions & 21 deletions lib/types/luks.nix
Original file line number Diff line number Diff line change
Expand Up @@ -112,26 +112,28 @@ in
_create = diskoLib.mkCreateOption {
inherit config options;
default = ''
${lib.optionalString config.askPassword ''
set +x
askPassword() {
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" ]
}
until askPassword; do
echo "Passwords did not match, please try again."
done
set -x
''}
cryptsetup -q luksFormat ${config.device} ${toString config.extraFormatArgs} ${keyFileArgs}
${cryptsetupOpen} --persistent
${toString (lib.forEach config.additionalKeyFiles (keyFile: ''
cryptsetup luksAddKey ${config.device} ${keyFile} ${keyFileArgs}
''))}
if ! blkid "${config.device}" >/dev/null || ! (blkid "${config.device}" -o export | grep -q '^TYPE='); then
${lib.optionalString config.askPassword ''
set +x
askPassword() {
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" ]
}
until askPassword; do
echo "Passwords did not match, please try again."
done
set -x
''}
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}
'';
};
Expand Down Expand Up @@ -176,7 +178,7 @@ in
internal = true;
readOnly = true;
type = lib.types.functionTo (lib.types.listOf lib.types.package);
default = pkgs: [ pkgs.cryptsetup ] ++ (lib.optionals (config.content != null) (config.content._pkgs pkgs));
default = pkgs: [ pkgs.gnugrep pkgs.cryptsetup ] ++ (lib.optionals (config.content != null) (config.content._pkgs pkgs));
description = "Packages";
};
};
Expand Down
6 changes: 4 additions & 2 deletions lib/types/lvm_pv.nix
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@
_create = diskoLib.mkCreateOption {
inherit config options;
default = ''
pvcreate ${config.device}
if ! (blkid '${config.device}' | grep -q 'TYPE='); then
pvcreate ${config.device}
fi
echo "${config.device}" >>"$disko_devices_dir"/lvm_${config.vg}
'';
};
Expand All @@ -49,7 +51,7 @@
internal = true;
readOnly = true;
type = lib.types.functionTo (lib.types.listOf lib.types.package);
default = pkgs: [ pkgs.lvm2 ];
default = pkgs: [ pkgs.gnugrep pkgs.lvm2 ];
description = "Packages";
};
};
Expand Down
25 changes: 16 additions & 9 deletions lib/types/lvm_vg.nix
Original file line number Diff line number Diff line change
Expand Up @@ -68,16 +68,23 @@
in
''
readarray -t lvm_devices < <(cat "$disko_devices_dir"/lvm_${config.name})
vgcreate ${config.name} \
"''${lvm_devices[@]}"
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 lib.hasInfix "%" lv.size then "-l" else "-L"} ${lv.size} \
-n ${lv.name} \
${lib.optionalString (lv.lvm_type != null) "--type=${lv.lvm_type}"} \
${toString lv.extraArgs} \
${config.name}
fi
'') sortedLvs}
${lib.concatMapStrings (lv: ''
lvcreate \
--yes \
${if lib.hasInfix "%" lv.size then "-l" else "-L"} ${lv.size} \
-n ${lv.name} \
${lib.optionalString (lv.lvm_type != null) "--type=${lv.lvm_type}"} \
${toString lv.extraArgs} \
${config.name}
${lib.optionalString (lv.content != null) lv.content._create}
'') sortedLvs}
'';
Expand Down
28 changes: 15 additions & 13 deletions lib/types/mdadm.nix
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,21 @@
_create = diskoLib.mkCreateOption {
inherit config options;
default = ''
readarray -t disk_devices < <(cat "$disko_devices_dir"/raid_${config.name})
echo 'y' | mdadm --create /dev/md/${config.name} \
--level=${toString config.level} \
--raid-devices="$(wc -l "$disko_devices_dir"/raid_${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}
if ! test -e /dev/md/${config.name}; then
readarray -t disk_devices < <(cat "$disko_devices_dir"/raid_${config.name})
echo 'y' | mdadm --create /dev/md/${config.name} \
--level=${toString config.level} \
--raid-devices="$(wc -l "$disko_devices_dir"/raid_${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}
'';
};
Expand Down
8 changes: 5 additions & 3 deletions lib/types/swap.nix
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,11 @@
_create = diskoLib.mkCreateOption {
inherit config options;
default = ''
mkswap \
${toString config.extraArgs} \
${config.device}
if ! blkid "${config.device}" -o export | grep -q '^TYPE='; then
mkswap \
${toString config.extraArgs} \
${config.device}
fi
'';
};
_mount = diskoLib.mkMountOption {
Expand Down
Loading

0 comments on commit 59e50d4

Please sign in to comment.