diff --git a/README.md b/README.md index 9e4f127b..88a4f66d 100644 --- a/README.md +++ b/README.md @@ -99,7 +99,7 @@ a disk named /dev/sda, you would run the following command to partition, format and mount the disk. ```console -sudo nix --experimental-features "nix-command flakes" run github:nix-community/disko/latest -- --mode disko /tmp/disk-config.nix +sudo nix --experimental-features "nix-command flakes" run github:nix-community/disko/latest -- --mode destroy,format,mount /tmp/disk-config.nix ``` ## Related Tools diff --git a/cli.nix b/cli.nix index 3203cedf..e66b470e 100644 --- a/cli.nix +++ b/cli.nix @@ -17,21 +17,29 @@ let diskoAttr = if noDeps then { - format = "formatScriptNoDeps"; - mount = "mountScriptNoDeps"; - disko = "diskoScriptNoDeps"; + destroy = "_cliDestroyNoDeps"; + format = "_cliFormatNoDeps"; + mount = "_cliMountNoDeps"; + + "format,mount" = "_cliFormatMountNoDeps"; + "destroy,format,mount" = "_cliDestroyFormatMountNoDeps"; # legacy aliases + disko = "diskoScriptNoDeps"; create = "createScriptNoDeps"; zap_create_mount = "diskoScriptNoDeps"; }.${mode} else { - format = "formatScript"; - mount = "mountScript"; - disko = "diskoScript"; + destroy = "_cliDestroy"; + format = "_cliFormat"; + mount = "_cliMount"; + + "format,mount" = "_cliFormatMount"; + "destroy,format,mount" = "_cliDestroyFormatMount"; # legacy aliases + disko = "diskoScript"; create = "createScript"; zap_create_mount = "diskoScript"; }.${mode}; diff --git a/default.nix b/default.nix index bf3570e2..4260af92 100644 --- a/default.nix +++ b/default.nix @@ -15,29 +15,47 @@ let }; }; }; + # We might instead reuse some of the deprecated output names to refer to the values the _cli* outputs currently do, + # but this warning allows us to get feedback from users early in case they have a use case we haven't considered. + warnDeprecated = name: lib.warn "the ${name} output is deprecated and will be removed, please open an issue if you're using it!"; in { - lib = lib.warn "the .lib.lib output is deprecated" diskoLib; + lib = warnDeprecated ".lib.lib" diskoLib; - # legacy alias - create = cfg: builtins.trace "the create output is deprecated, use format instead" (eval cfg).config.disko.devices._create; - createScript = cfg: pkgs: builtins.trace "the create output is deprecated, use format instead" ((eval cfg).config.disko.devices._scripts { inherit pkgs checked; }).formatScript; - createScriptNoDeps = cfg: pkgs: builtins.trace "the create output is deprecated, use format instead" ((eval cfg).config.disko.devices._scripts { inherit pkgs checked; }).formatScriptNoDeps; + _cliDestroy = cfg: pkgs: ((eval cfg).config.disko.devices._scripts { inherit pkgs checked; }).destroy; + _cliDestroyNoDeps = cfg: pkgs: ((eval cfg).config.disko.devices._scripts { inherit pkgs checked; }).destroyNoDeps; - format = cfg: (eval cfg).config.disko.devices._create; - formatScript = cfg: pkgs: ((eval cfg).config.disko.devices._scripts { inherit pkgs checked; }).formatScript; - formatScriptNoDeps = cfg: pkgs: ((eval cfg).config.disko.devices._scripts { inherit pkgs checked; }).formatScriptNoDeps; + _cliFormat = cfg: pkgs: ((eval cfg).config.disko.devices._scripts { inherit pkgs checked; }).format; + _cliFormatNoDeps = cfg: pkgs: ((eval cfg).config.disko.devices._scripts { inherit pkgs checked; }).formatNoDeps; - mount = cfg: (eval cfg).config.disko.devices._mount; - mountScript = cfg: pkgs: ((eval cfg).config.disko.devices._scripts { inherit pkgs checked; }).mountScript; - mountScriptNoDeps = cfg: pkgs: ((eval cfg).config.disko.devices._scripts { inherit pkgs checked; }).mountScriptNoDeps; + _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; - disko = cfg: (eval cfg).config.disko.devices._disko; - diskoScript = cfg: pkgs: ((eval cfg).config.disko.devices._scripts { inherit pkgs checked; }).diskoScript; - diskoScriptNoDeps = cfg: pkgs: ((eval cfg).config.disko.devices._scripts { inherit pkgs checked; }).diskoScriptNoDeps; + _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; + + _cliDestroyFormatMount = cfg: pkgs: ((eval cfg).config.disko.devices._scripts { inherit pkgs checked; }).destroyFormatMount; + _cliDestroyFormatMountNoDeps = cfg: pkgs: ((eval cfg).config.disko.devices._scripts { inherit pkgs checked; }).destroyFormatMountNoDeps; + + # legacy aliases + create = cfg: warnDeprecated "create" (eval cfg).config.disko.devices._create; + createScript = cfg: pkgs: warnDeprecated "createScript" ((eval cfg).config.disko.devices._scripts { inherit pkgs checked; }).formatScript; + createScriptNoDeps = cfg: pkgs: warnDeprecated "createScriptNoDeps" ((eval cfg).config.disko.devices._scripts { inherit pkgs checked; }).formatScriptNoDeps; + + format = cfg: warnDeprecated "format" (eval cfg).config.disko.devices._create; + formatScript = cfg: pkgs: warnDeprecated "formatScript" ((eval cfg).config.disko.devices._scripts { inherit pkgs checked; }).formatScript; + formatScriptNoDeps = cfg: pkgs: warnDeprecated "formatScriptNoDeps" ((eval cfg).config.disko.devices._scripts { inherit pkgs checked; }).formatScriptNoDeps; + + mount = cfg: warnDeprecated "mount" (eval cfg).config.disko.devices._mount; + mountScript = cfg: pkgs: warnDeprecated "mountScript" ((eval cfg).config.disko.devices._scripts { inherit pkgs checked; }).mountScript; + mountScriptNoDeps = cfg: pkgs: warnDeprecated "mountScriptNoDeps" ((eval cfg).config.disko.devices._scripts { inherit pkgs checked; }).mountScriptNoDeps; + + disko = cfg: warnDeprecated "disko" (eval cfg).config.disko.devices._disko; + diskoScript = cfg: pkgs: warnDeprecated "diskoScript" ((eval cfg).config.disko.devices._scripts { inherit pkgs checked; }).diskoScript; + diskoScriptNoDeps = cfg: pkgs: warnDeprecated "diskoScriptNoDeps" ((eval cfg).config.disko.devices._scripts { inherit pkgs checked; }).diskoScriptNoDeps; # we keep this old output for backwards compatibility - diskoNoDeps = cfg: pkgs: builtins.trace "the diskoNoDeps output is deprecated, please use disko instead" ((eval cfg).config.disko.devices._scripts { inherit pkgs checked; }).diskoScriptNoDeps; + diskoNoDeps = cfg: pkgs: warnDeprecated "diskoNoDeps" ((eval cfg).config.disko.devices._scripts { inherit pkgs checked; }).diskoScriptNoDeps; config = cfg: (eval cfg).config.disko.devices._config; packages = cfg: (eval cfg).config.disko.devices._packages; diff --git a/disko b/disko index 4ef771a9..97700b67 100755 --- a/disko +++ b/disko @@ -9,6 +9,7 @@ declare disko_config # mount was chosen as the default mode because it's less destructive mode=mount nix_args=() +skip_destroy_safety_check=false # DISKO_VERSION is set by the wrapper in package.nix DISKO_VERSION="${DISKO_VERSION:="unknown! This is a bug, please report it!"}" @@ -25,10 +26,12 @@ or else from the disko module of a NixOS configuration of that name under .nixos Options: * -m, --mode mode - set the mode, either format, mount or disko - format: create partition tables, zpools, lvms, raids and filesystems - mount: mount the partition at the specified root-mountpoint - disko: first unmount and destroy all filesystems on the disks we want to format, then run the create and mount mode + set the mode, either distroy, format, mount, 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 + format,mount: run format and mount in sequence + destroy,format,mount: run all three modes in sequence. Previously known as --mode disko * -f, --flake uri fetch the disko config relative to this flake's root * --arg name value @@ -42,6 +45,8 @@ Options: * --no-deps avoid adding another dependency closure to an in-memory installer requires all necessary dependencies to be available in the environment +* --yes-wipe-all-disks + skip the safety check for destroying partitions, useful for automation * --debug run with set -x * --help @@ -93,6 +98,9 @@ while [[ $# -gt 0 ]]; do --no-deps) nix_args+=(--arg noDeps true) ;; + --yes-wipe-all-disks) + skip_destroy_safety_check=true + ;; --show-trace) nix_args+=("$1") ;; @@ -126,8 +134,16 @@ nixBuild() { fi } -if ! { [[ $mode = "format" ]] || [[ $mode = "mount" ]] || [[ $mode = "disko" ]] || [[ $mode = "create" ]] || [[ $mode = "zap_create_mount" ]] ; }; then - abort "mode must be either format, mount or disko" +if ! { + # Base modes + [[ $mode = "destroy" ]] || [[ $mode = "format" ]] || [[ $mode = "mount" ]] || + # Combined modes + [[ $mode = "format,mount" ]] || + [[ $mode = "destroy,format,mount" ]] || # Replaces --mode disko + # Legacy modes, will be removed in next major version + [[ $mode = "disko" ]] || [[ $mode = "create" ]] || [[ $mode = "zap_create_mount" ]] ; +}; then + abort 'mode must be one of "destroy", "format", "mount", "destroy,format,mount" or "format,mount"' fi if [[ -n "${flake+x}" ]]; then @@ -159,8 +175,14 @@ script=$(nixBuild "${libexec_dir}"/cli.nix \ --argstr mode "$mode" \ "${nix_args[@]}" ) + +command=("$(echo "$script"/bin/*)") +if [[ $mode = "destroy,format,mount" && $skip_destroy_safety_check = true ]]; then + command+=("--yes-wipe-all-disks") +fi + if [[ -n "${dry_run+x}" ]]; then - echo "$script" + echo "${command[@]}" else - exec "$script" + exec "${command[@]}" fi diff --git a/docs/quickstart.md b/docs/quickstart.md index 30d0c891..e511ee8c 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -129,7 +129,7 @@ The following step will partition and format your disk, and mount it to `/mnt`. **Please note: This will erase any existing data on your disk.** ```console -sudo nix --experimental-features "nix-command flakes" run github:nix-community/disko/latest -- --mode disko /tmp/disk-config.nix +sudo nix --experimental-features "nix-command flakes" run github:nix-community/disko/latest -- --mode destroy,format,mount /tmp/disk-config.nix ``` After the command has run, your file system should have been formatted and diff --git a/docs/reference.md b/docs/reference.md index 104ba1da..3d390b97 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -21,10 +21,12 @@ or else from the disko module of a NixOS configuration of that name under .nixos Options: * -m, --mode mode - set the mode, either format, mount or disko - format: create partition tables, zpools, lvms, raids and filesystems - mount: mount the partition at the specified root-mountpoint - disko: first unmount and destroy all filesystems on the disks we want to format, then run the create and mount mode + set the mode, either distroy, format, mount, 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 + format,mount: run format and mount in sequence + destroy,format,mount: run all three modes in sequence. Previously known as --mode disko * -f, --flake uri fetch the disko config relative to this flake's root * --arg name value @@ -40,4 +42,6 @@ Options: requires all necessary dependencies to be available in the environment * --debug run with set -x +* --yes-wipe-all-disks + skip the safety check for destroying partitions, useful for automation ``` diff --git a/lib/default.nix b/lib/default.nix index 7cd6ef99..91005557 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -444,10 +444,54 @@ let ]; in lib.mapAttrs throwIfNoDisksDetected { - destroyScript = (diskoLib.writeCheckedBash { inherit pkgs checked; }) "disko-destroy" '' + 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} + ''; + 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} + ''; + 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..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 @@ -466,7 +510,7 @@ let # 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._destroy} + ${cfg.config._legacyDestroy} ''; formatScriptNoDeps = (diskoLib.writeCheckedBash { inherit pkgs checked; noDeps = true; }) "disko-format" '' @@ -482,11 +526,12 @@ let ''; }; }; - _destroy = lib.mkOption { + _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}" || : @@ -497,6 +542,44 @@ let 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 + 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; @@ -542,6 +625,19 @@ let 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} @@ -549,6 +645,17 @@ let ${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 = '' diff --git a/lib/make-disk-image.nix b/lib/make-disk-image.nix index 73432325..8ff1cd13 100644 --- a/lib/make-disk-image.nix +++ b/lib/make-disk-image.nix @@ -84,7 +84,7 @@ let ${lib.optionalString diskoCfg.testMode '' export IN_DISKO_TEST=1 ''} - ${systemToInstall.config.system.build.diskoScript} + ${lib.getExe systemToInstall.config.system.build.destroyFormatMount} --yes-wipe-all-disks ''; installer = lib.optionalString cfg.copyNixStore '' diff --git a/lib/tests.nix b/lib/tests.nix index fd24a807..65bfd1af 100644 --- a/lib/tests.nix +++ b/lib/tests.nix @@ -80,9 +80,9 @@ let testConfigBooted = testLib.prepareDiskoConfig diskoConfigWithArgs testLib.devices; tsp-generator = pkgs.callPackage ../. { checked = true; }; - tsp-format = (tsp-generator.formatScript testConfigInstall) pkgs; - tsp-mount = (tsp-generator.mountScript testConfigInstall) pkgs; - tsp-disko = (tsp-generator.diskoScript testConfigInstall) pkgs; + tsp-format = (tsp-generator._cliFormat testConfigInstall) pkgs; + tsp-mount = (tsp-generator._cliMount 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); @@ -260,24 +260,24 @@ let machine.succeed("echo -n 'secretsecret' > /tmp/secret.key") ${lib.optionalString (testMode == "direct") '' # running direct mode - machine.succeed("${tsp-format}") - machine.succeed("${tsp-mount}") - machine.succeed("${tsp-mount}") # verify that mount is idempotent - machine.succeed("${tsp-disko}") # verify that we can destroy and recreate + 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-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("${tsp-format}") # verify that format is idempotent + 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("${nodes.machine.system.build.formatScript}") - machine.succeed("${nodes.machine.system.build.mountScript}") - 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("${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("${nodes.machine.system.build.formatScript}") # verify that format is idempotent + machine.succeed("${lib.getExe nodes.machine.system.build.format}") # verify that format is idempotent machine.succeed("test -e /mnt/home/testfile") ''}