add disko-install command

disko-install combines disko and nixos-install into a single command.

Example usage:

  sudo ./disko-install --flake /someflake#eva --disk sda /dev/zvol/zroot/test
This commit is contained in:
Jörg Thalheim 2024-02-27 20:41:22 +01:00 committed by mergify[bot]
parent a13f36255c
commit bde7dd352c
6 changed files with 322 additions and 1 deletions

169
disko-install Executable file
View File

@ -0,0 +1,169 @@
#!/usr/bin/env bash
set -euo pipefail
showUsage() {
cat <<EOF
Usage: $0 [OPTIONS]
-f, --flake FLAKE_URI#ATTR Use the specified flake to install the NixOS configuration.
--dry-run Print the commands that would be run, but do not run them.
--show-trace Show the stack trace on error.
-h, --help Show this help message.
--reference-lock-file FILE Use the specified lock file as a reference for the Nix store.
EOF
}
serialiaseArrayToNix() {
local -n array=$1
nixExpr="{ "
# Iterate over the associative array to populate the Nix attrset string
for key in "${!array[@]}"; do
value=${array[$key]}
nixExpr+="\"${key//\"/\\\"}\" = \"${value//\"/\\\"}\"; "
done
nixExpr+="}"
echo "$nixExpr"
}
readonly libexec_dir="${0%/*}"
nix_args=(
--extra-experimental-features 'nix-command flakes'
"--option" "no-write-lock-file" "true"
)
dry_run=
diskoAttr=diskoScript
declare -A diskMappings
parseArgs() {
[[ $# -eq 0 ]] && {
showUsage
exit 1
}
while [[ $# -gt 0 ]]; do
case "$1" in
-d | --debug)
set -x
;;
-f | --flake)
flake=$2
shift
;;
-h | --help)
showUsage
exit 0
;;
--dry-run)
dry_run=y
;;
--show-trace)
nix_args+=("$1")
;;
--mode)
if [[ $# -lt 2 ]]; then
echo "Option $1 requires an argument" >&2
exit 1
fi
case $2 in
format)
diskoAttr=diskoScript
;;
mount)
diskoAttr=mountScript
;;
*)
echo "Invalid mode: $2" >&2
echo "Valid modes are: format, mount" >&2
exit 1
;;
esac
mode=$2
shift
;;
--option)
if [[ $# -lt 3 ]]; then
echo "Option $1 requires an argument" >&2
exit 1
fi
nix_args+=(--option "$2" "$3")
shift
shift
;;
--disk)
if [[ $# -lt 3 ]]; then
echo "Option $1 requires an argument" >&2
exit 1
fi
# shellcheck disable=SC2034
diskMappings[$2]=$3
shift
shift
;;
*)
echo "Unknown option: $1" >&2
showUsage
exit 1
;;
esac
shift
done
}
cleanupMountPoint() {
mountPoint=$1
if mountpoint -q "${mountPoint}"; then
umount -R "${mountPoint}"
fi
rmdir "${mountPoint}"
}
main() {
parseArgs "$@"
if [[ -z ${flake-} ]]; then
echo "Please specify the flake-uri with --flake to use for installation." >&2
exit 1
fi
if [[ $flake =~ ^(.*)\#([^\#\"]*)$ ]]; then
flake="${BASH_REMATCH[1]}"
flakeAttr="${BASH_REMATCH[2]}"
fi
if [[ -z ${flakeAttr-} ]]; then
echo "Please specify the name of the NixOS configuration to be installed, as a URI fragment in the flake-uri." >&2
echo 'For example, to use the output nixosConfigurations.foo from the flake.nix, append "#foo" to the flake-uri.' >&2
exit 1
fi
mountPoint=$(mktemp -d)
chmod 755 "${mountPoint}" # bchachefs wants 755
escapeMountPoint=$(printf '%q' "$mountPoint")
# shellcheck disable=SC2064
trap "cleanupMountPoint ${escapeMountPoint}" EXIT
IFS=$'\n' mapfile -t artifacts < <(nix-build "${libexec_dir}"/install-cli.nix \
"${nix_args[@]}" \
--no-out-link \
--impure \
--argstr flake "$flake" \
--argstr flakeAttr "$flakeAttr" \
--argstr rootMountPoint "$mountPoint" \
--arg diskMappings "$(serialiaseArrayToNix diskMappings)" \
-A installToplevel \
-A "$diskoAttr")
nixos_system=${artifacts[0]}
disko_script=${artifacts[1]}
if [[ -n ${dry_run-} ]]; then
echo "Would run: $disko_script"
echo "Would run: nixos-install --system '$nixos_system' --root '$mountPoint'"
exit 0
fi
"$disko_script"
nixos-install --no-root-password --system "$nixos_system" --root "$mountPoint"
}
main "$@"

26
disko-install.nix Normal file
View File

@ -0,0 +1,26 @@
{ stdenvNoCC, makeWrapper, lib }:
stdenvNoCC.mkDerivation {
name = "disko-install";
src = ./.;
nativeBuildInputs = [
makeWrapper
];
installPhase = ''
mkdir -p $out/bin $out/share/disko
cp -r install-cli.nix $out/share/disko
sed \
-e "s|libexec_dir=\".*\"|libexec_dir=\"$out/share/disko\"|" \
-e "s|#!/usr/bin/env.*|#!/usr/bin/env bash|" \
disko-install > $out/bin/disko-install
chmod 755 $out/bin/disko-install
wrapProgram $out/bin/disko-install
'';
meta = with lib; {
description = "Disko and nixos-install in one command";
homepage = "https://github.com/nix-community/disko";
license = licenses.mit;
maintainers = with maintainers; [ lassulus ];
platforms = platforms.linux;
};
}

View File

@ -28,6 +28,7 @@
in in
{ {
disko = pkgs.callPackage ./package.nix { }; disko = pkgs.callPackage ./package.nix { };
disko-install = pkgs.callPackage ./disko-install.nix { };
default = self.packages.${system}.disko; default = self.packages.${system}.disko;
} // pkgs.lib.optionalAttrs (!pkgs.buildPlatform.isRiscV64) { } // pkgs.lib.optionalAttrs (!pkgs.buildPlatform.isRiscV64) {
disko-doc = pkgs.callPackage ./doc.nix { }; disko-doc = pkgs.callPackage ./doc.nix { };
@ -43,6 +44,11 @@
makeTest = import (pkgs.path + "/nixos/tests/make-test-python.nix"); makeTest = import (pkgs.path + "/nixos/tests/make-test-python.nix");
eval-config = import (pkgs.path + "/nixos/lib/eval-config.nix"); eval-config = import (pkgs.path + "/nixos/lib/eval-config.nix");
}); });
disko-install = pkgs.callPackage ./tests/disko-install {
inherit self;
};
shellcheck = pkgs.runCommand "shellcheck" { nativeBuildInputs = [ pkgs.shellcheck ]; } '' shellcheck = pkgs.runCommand "shellcheck" { nativeBuildInputs = [ pkgs.shellcheck ]; } ''
cd ${./.} cd ${./.}
shellcheck disk-deactivate/disk-deactivate disko shellcheck disk-deactivate/disk-deactivate disko
@ -50,8 +56,17 @@
''; '';
in in
# FIXME: aarch64-linux seems to hang on boot # FIXME: aarch64-linux seems to hang on boot
nixpkgs.lib.optionalAttrs pkgs.hostPlatform.isx86_64 nixosTests // nixpkgs.lib.optionalAttrs pkgs.hostPlatform.isx86_64 (nixosTests // { inherit disko-install; }) //
pkgs.lib.optionalAttrs (!pkgs.buildPlatform.isRiscV64 && !pkgs.hostPlatform.isx86_32) { inherit shellcheck; }); pkgs.lib.optionalAttrs (!pkgs.buildPlatform.isRiscV64 && !pkgs.hostPlatform.isx86_32) { inherit shellcheck; });
nixosConfigurations.testmachine = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
./tests/disko-install/configuration.nix
./example/hybrid.nix
./module.nix
];
};
formatter = forAllSystems (system: formatter = forAllSystems (system:
let let
pkgs = nixpkgs.legacyPackages.${system}; pkgs = nixpkgs.legacyPackages.${system};

43
install-cli.nix Normal file
View File

@ -0,0 +1,43 @@
{ flake
, flakeAttr
, diskMappings
, canTouchEfiVariables ? false
, rootMountPoint ? "/mnt"
}:
let
originalSystem = (builtins.getFlake "${flake}").nixosConfigurations."${flakeAttr}";
diskoSystem =
let
lib = originalSystem.lib;
modifiedDisks = builtins.mapAttrs
(name: value: let
dev = if diskMappings ? ${name} then
diskMappings.${name}
else
throw "No device passed for disk '${name}'";
in value // {
device = dev;
content = value.content // { device = dev; };
})
originalSystem.config.disko.devices.disk;
cleanedDisks = lib.filterAttrsRecursive (n: _: !lib.hasPrefix "_" n) modifiedDisks;
in
originalSystem.extendModules {
modules = [{
disko.rootMountPoint = rootMountPoint;
disko.devices.disk = lib.mkVMOverride cleanedDisks;
}];
};
installSystem = originalSystem.extendModules {
modules = [({ lib, ... }: {
boot.loader.efi.canTouchEfiVariables = lib.mkVMOverride canTouchEfiVariables;
boot.loader.grub.devices = lib.mkVMOverride diskoSystem.config.boot.loader.grub.devices;
})];
};
in
{
installToplevel = installSystem.config.system.build.toplevel;
inherit (diskoSystem.config.system.build) formatScript mountScript diskoScript;
}

View File

@ -0,0 +1,23 @@
{ lib, pkgs, modulesPath, ... }: {
imports = [
(modulesPath + "/testing/test-instrumentation.nix")
(modulesPath + "/profiles/qemu-guest.nix")
(modulesPath + "/profiles/minimal.nix")
];
networking.hostName = "disko-machine";
# do not try to fetch stuff from the internet
nix.settings = {
substituters = lib.mkForce [ ];
hashed-mirrors = null;
connect-timeout = 3;
flake-registry = pkgs.writeText "flake-registry" ''{"flakes":[],"version":2}'';
experimental-features = [ "nix-command" "flakes" ];
};
services.openssh.enable = true;
boot.kernelParams = [ "console=tty0" ] ++
(lib.optional (pkgs.stdenv.hostPlatform.isAarch) "ttyAMA0,115200") ++
(lib.optional (pkgs.stdenv.hostPlatform.isRiscV64) "ttySIF0,115200") ++
[ "console=ttyS0,115200" ];
}

View File

@ -0,0 +1,45 @@
{ pkgs ? import <nixpkgs> { }, self }:
let
disko-install = pkgs.callPackage ../../disko-install.nix { };
toplevel = self.nixosConfigurations.testmachine.config.system.build.toplevel;
dependencies = [
pkgs.stdenv.drvPath
toplevel
self.nixosConfigurations.testmachine.config.system.build.diskoScript
] ++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs);
closureInfo = pkgs.closureInfo { rootPaths = dependencies; };
in
pkgs.nixosTest {
name = "disko-test";
nodes.machine = {
virtualisation.emptyDiskImages = [ 4096 ];
virtualisation.memorySize = 3000;
environment.etc."install-closure".source = "${closureInfo}/store-paths";
};
testScript = ''
def create_test_machine(oldmachine=None, args={}): # taken from <nixpkgs/nixos/tests/installer.nix>
machine = create_machine({
"qemuFlags":
'-cpu max -m 1024 -virtfs local,path=/nix/store,security_model=none,mount_tag=nix-store,'
f' -drive file={oldmachine.state_dir}/empty0.qcow2,id=drive1,if=none,index=1,werror=report'
f' -device virtio-blk-pci,drive=drive1',
} | args)
driver.machines.append(machine)
return machine
machine.succeed("lsblk >&2")
print(machine.succeed("tty"))
machine.succeed("${disko-install}/bin/disko-install --disk main /dev/vdb --flake ${../..}#testmachine")
# test idempotency
machine.succeed("${disko-install}/bin/disko-install --mode mount --disk main /dev/vdb --flake ${../..}#testmachine")
machine.shutdown()
new_machine = create_test_machine(oldmachine=machine, args={ "name": "after_install" })
new_machine.start()
name = new_machine.succeed("hostname").strip()
assert name == "disko-machine", f"expected hostname 'disko-machine', got {name}"
'';
}