Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[wip] Add declarative unmount #891

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion cli.nix
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ let
destroy = "_cliDestroyNoDeps";
format = "_cliFormatNoDeps";
mount = "_cliMountNoDeps";
unmount = "_cliUnmountNoDeps";

"format,mount" = "_cliFormatMountNoDeps";
"destroy,format,mount" = "_cliDestroyFormatMountNoDeps";
Expand All @@ -32,6 +33,7 @@ let
destroy = "destroyNoDeps";
format = "formatNoDeps";
mount = "mountNoDeps";
unmount = "unmountNoDeps";

"format,mount" = "formatMountNoDeps";
"destroy,format,mount" = "destroyFormatMountNoDeps";
Expand All @@ -47,6 +49,7 @@ let
destroy = "_cliDestroy";
format = "_cliFormat";
mount = "_cliMount";
unmount = "_cliUnmount";

"format,mount" = "_cliFormatMount";
"destroy,format,mount" = "_cliDestroyFormatMount";
Expand All @@ -55,7 +58,8 @@ let
{
destroy = "destroy";
format = "format";
mount = "munt";
mount = "mount";
unmount = "unmount";

"format,mount" = "formatMount";
"destroy,format,mount" = "destroyFormatMount";
Expand Down
3 changes: 3 additions & 0 deletions default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ in
_cliMount = cfg: pkgs: ((eval cfg).config.disko.devices._scripts { inherit pkgs checked; }).mount;
_cliMountNoDeps = cfg: pkgs: ((eval cfg).config.disko.devices._scripts { inherit pkgs checked; }).mountNoDeps;

_cliUnmount = cfg: pkgs: ((eval cfg).config.disko.devices._scripts { inherit pkgs checked; }).unmount;
_cliUnmountNoDeps = cfg: pkgs: ((eval cfg).config.disko.devices._scripts { inherit pkgs checked; }).unmountNoDeps;

_cliFormatMount = cfg: pkgs: ((eval cfg).config.disko.devices._scripts { inherit pkgs checked; }).formatMount;
_cliFormatMountNoDeps = cfg: pkgs: ((eval cfg).config.disko.devices._scripts { inherit pkgs checked; }).formatMountNoDeps;

Expand Down
4 changes: 2 additions & 2 deletions disko
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ or else from the disko module of a NixOS configuration of that name under .nixos
Options:

* -m, --mode mode
set the mode, either distroy, format, mount, format,mount or destroy,format,mount
set the mode, either distroy, format, mount, unmount, format,mount or destroy,format,mount
destroy: unmount filesystems and destroy partition tables of the selected disks
format: create partition tables, zpools, lvms, raids and filesystems if they don't exist yet
mount: mount the partitions at the specified root-mountpoint
Expand Down Expand Up @@ -136,7 +136,7 @@ nixBuild() {

if ! {
# Base modes
[[ $mode = "destroy" ]] || [[ $mode = "format" ]] || [[ $mode = "mount" ]] ||
[[ $mode = "destroy" ]] || [[ $mode = "format" ]] || [[ $mode = "mount" ]] || [[ $mode = "unmount" ]] ||
# Combined modes
[[ $mode = "format,mount" ]] ||
[[ $mode = "destroy,format,mount" ]] || # Replaces --mode disko
Expand Down
35 changes: 28 additions & 7 deletions disko-install
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ Usage: $0 [OPTIONS]
Specify this option if you plan to boot from this disk on the current machine,
but not if you plan to move the disk to another machine.
--system-config JSON Merges the specified JSON object into the NixOS configuration.
--mount-point PATH Specify the mount point for the NixOS installation.
Default: /mnt/disko-install-root
EOF
}

Expand All @@ -46,6 +48,7 @@ dry_run=
extraSystemConfig="{}"
diskoAttr=diskoScript
writeEfiBootEntries=false
mountPoint=/mnt/disko-install-root
declare -A diskMappings
declare -A extraFiles

Expand Down Expand Up @@ -135,6 +138,14 @@ parseArgs() {
shift
shift
;;
--mount-point)
if [[ $# -lt 2 ]]; then
echo "Option $1 requires an argument" >&2
exit 1
fi
mountPoint=$2
shift
;;
*)
echo "Unknown option: $1" >&2
showUsage
Expand All @@ -146,7 +157,6 @@ parseArgs() {
}

cleanupMountPoint() {
mountPoint=$1
if mountpoint -q "${mountPoint}"; then
umount -R "${mountPoint}"
fi
Expand All @@ -161,6 +171,14 @@ nixBuild() {
fi
}

maybeRun () {
if [[ -z ${dry_run-} ]]; then
"$@"
else
echo "Would run: $*"
fi
}

main() {
parseArgs "$@"

Expand All @@ -170,7 +188,7 @@ main() {
fi

# check if we are root
if [[ "$EUID" -ne 0 ]]; then
if [[ "$EUID" -ne 0 ]] && [[ -z ${dry_run-} ]]; then
echo "This script must be run as root" >&2
exit 1
fi
Expand All @@ -190,12 +208,12 @@ main() {
exit 1
fi

mountPoint="/mnt/disko-install-root"
mkdir -p "${mountPoint}"
chmod 755 "${mountPoint}" # bcachefs wants 755
escapeMountPoint=$(printf '%q' "$mountPoint")
maybeRun mkdir -p "${mountPoint}"
maybeRun chmod 755 "${mountPoint}" # bcachefs wants 755
# shellcheck disable=SC2064
trap "cleanupMountPoint ${escapeMountPoint}" EXIT
if [[ -z ${dry_run-} ]]; then
trap cleanupMountPoint EXIT
fi

outputs=$(nixBuild "${libexec_dir}"/install-cli.nix \
"${nix_args[@]}" \
Expand All @@ -222,6 +240,9 @@ main() {
exit 0
fi

# We don't want swap as can break your running system in weird ways if you eject the disk
# Hopefully disko-install has enough RAM to run without swap, otherwise we can make this configurable in future.
export DISKO_SKIP_SWAP=1
"$disko_script"

for source in "${!extraFiles[@]}"; do
Expand Down
50 changes: 49 additions & 1 deletion lib/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,8 @@ let
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;
Expand Down Expand Up @@ -284,6 +286,25 @@ let
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
Expand Down Expand Up @@ -458,6 +479,10 @@ let
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}
Expand All @@ -477,6 +502,9 @@ let
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}
'';
Expand Down Expand Up @@ -616,12 +644,32 @@ let
''
set -efux
# first create the necessary devices
${concatMapStrings (dev: (attrByPath (dev ++ [ "_mount" ]) {} devices).dev or "") sortedDeviceList}
${concatMapStrings (dev: (attrByPath (dev ++ [ "_mount" ]) {} devices).dev or "") (lib.reverseList 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 (attrValues (lib.reverseList 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;
Expand Down
4 changes: 4 additions & 0 deletions lib/tests.nix
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ let
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);
Expand Down Expand Up @@ -282,6 +283,9 @@ let
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")
Expand Down
28 changes: 28 additions & 0 deletions lib/types/btrfs.nix
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,34 @@ in
};
};
};
_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;
Expand Down
4 changes: 4 additions & 0 deletions lib/types/disk.nix
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@
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._mount;
};
_config = lib.mkOption {
internal = true;
readOnly = true;
Expand Down
10 changes: 10 additions & 0 deletions lib/types/filesystem.nix
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,16 @@
'';
};
};
_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;
Expand Down
8 changes: 8 additions & 0 deletions lib/types/gpt.nix
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,14 @@ in
fs = partMounts.fs or { };
};
};
_unmount = diskoLib.mkUnmountOption {
inherit config options;
default = ''
${lib.concatStrings (map (partition: ''
${lib.optionalString (partition.content != null) partition.content._unmount}
'') sortedPartitions)}
'';
};
_config = lib.mkOption {
internal = true;
readOnly = true;
Expand Down
10 changes: 10 additions & 0 deletions lib/types/luks.nix
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,16 @@ in
fs = lib.optionalAttrs (config.content != null) contentMount.fs or { };
};
};
_unmount = diskoLib.mkUnmountOption {
inherit config options;
default = ''
${lib.optionalString (config.content != null) config.content._unmount}

if cryptsetup status "${config.name}" >/dev/null 2>/dev/null; then
cryptsetup close "${config.name}"
fi
'';
};
_config = lib.mkOption {
internal = true;
readOnly = true;
Expand Down
4 changes: 4 additions & 0 deletions lib/types/lvm_pv.nix
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@
inherit config options;
default = { };
};
_unmount = diskoLib.mkUnmountOption {
inherit config options;
default = { };
};
_config = lib.mkOption {
internal = true;
readOnly = true;
Expand Down
14 changes: 14 additions & 0 deletions lib/types/lvm_vg.nix
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,20 @@ in
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
''
${lib.concatMapStrings (x: x.dev or "") (lib.attrValues lvMounts)}
'';
};
_config = lib.mkOption {
internal = true;
readOnly = true;
Expand Down
Loading