disko2: Add basic config evaluation via nushell

You can call it like so:

    ./disko.nu mount example/simple-efi.nix
    ./disko.nu mount -f .#testmachine

and it will output a JSON representation of the evaluated disko
configuration.

eval-config.nix is based on cli.nix

The test script is a first attempt.
This commit is contained in:
Felix Uhl 2024-10-23 18:49:22 +02:00
parent d7d57edb72
commit 3d67130ff3
4 changed files with 421 additions and 0 deletions

View file

@ -17,6 +17,7 @@ let
};
in
{
inherit eval;
lib = lib.warn "the .lib.lib output is deprecated" diskoLib;
# legacy alias

96
disko.nu Executable file
View file

@ -0,0 +1,96 @@
#!/usr/bin/env nu
use std log
alias nix = ^nix --extra-experimental-features nix-command --extra-experimental-features flakes
alias nix-eval-expr = nix eval --impure --json --expr
def eval-config [args: record]: nothing -> record {
do { nix-eval-expr $"import ./eval-config.nix \(builtins.fromJSON ''($args | to json -r)'')" }
| complete
| if $in.exit_code != 0 {
{
success: false
messages: [
$"Failed to evaluate disko config with args ($args)!"
$"Error from nix eval: ($in.stderr)"
]
}
} else {
$in.stdout
| from json
| {
success: true
value: $in
}
}
}
export def eval-disko-file []: path -> record {
let config = $in
# If the file is valid JSON, parse and return it
open $config | try { into record } | if $in != null {
return {
success: true
value: $in
}
}
eval-config { diskoFile: $config }
}
export def eval-flake []: string -> record {
let flakeUri = $in
let parseResult = $flakeUri | parse "{flake}#{flakeAttr}" | if not ( $in | is-empty ) {
$in
} else {
return {
success: false
messages: [
$"Flake-uri ($flakeUri) does not contain an attribute."
"Please append an attribute like \"#foo\" to the flake-uri."
]
}
}
let flake = $parseResult.0.flake | if ($in | path exists) { $in | path expand } else { $in }
let flakeAttr = $parseResult.0.flakeAttr
eval-config { flake: $flake, flakeAttr: $flakeAttr }
}
def exit-on-error [context: string] {
if $in.success {
log debug $"Success: ($context)"
log debug $"Return value: ($in.value)"
return $in.value
}
log error $"Failed: ($context)"
for msg in $in.messages {
log error $msg
}
exit 1
}
def modes [] { ["destroy", "format", "mount", "format,mount", "destroy,format,mount"]}
def main [
mode: string@modes, # Mode to use. Allowed values are 'destroy', 'format', 'mount', 'format,mount', 'destroy,format,mount'
disko_file?: path, # File to read the disko configuration from. Can be a .nix file or a .json file
--flake (-f): string # Flake URI to search for the disko configuration
] {
if not ($flake != null xor $disko_file != null) {
log error "Either --flake or disko_file must be provided"
exit 1
}
let config = if $disko_file != null {
$disko_file | eval-disko-file | exit-on-error "eval-config"
} else {
$flake | eval-flake | exit-on-error "eval-flake"
}
$config | to json | print
}

275
disko.test.nu Executable file
View file

@ -0,0 +1,275 @@
#!/usr/bin/env nu
use disko.nu
use std assert
assert equal ("example/simple-efi.nix" | path expand | disko eval-disko-file) {
success: true,
value: {
"disk": {
"main": {
"content": {
"device": "/dev/disk/by-id/some-disk-id",
"efiGptPartitionFirst": true,
"partitions": {
"ESP": {
"alignment": 0,
"content": {
"device": "/dev/disk/by-partlabel/disk-main-ESP",
"extraArgs": [],
"format": "vfat",
"mountOptions": [
"umask=0077"
],
"mountpoint": "/boot",
"postCreateHook": "",
"postMountHook": "",
"preCreateHook": "",
"preMountHook": "",
"type": "filesystem"
},
"device": "/dev/disk/by-partlabel/disk-main-ESP",
"end": "+500M",
"hybrid": null,
"label": "disk-main-ESP",
"name": "ESP",
"priority": 1000,
"size": "500M",
"start": "0",
"type": "EF00"
},
"root": {
"alignment": 0,
"content": {
"device": "/dev/disk/by-partlabel/disk-main-root",
"extraArgs": [],
"format": "ext4",
"mountOptions": [
"defaults"
],
"mountpoint": "/",
"postCreateHook": "",
"postMountHook": "",
"preCreateHook": "",
"preMountHook": "",
"type": "filesystem"
},
"device": "/dev/disk/by-partlabel/disk-main-root",
"end": "-0",
"hybrid": null,
"label": "disk-main-root",
"name": "root",
"priority": 9001,
"size": "100%",
"start": "0",
"type": "8300"
}
},
"postCreateHook": "",
"postMountHook": "",
"preCreateHook": "",
"preMountHook": "",
"type": "gpt"
},
"device": "/dev/disk/by-id/some-disk-id",
"imageName": "main",
"imageSize": "2G",
"name": "main",
"postCreateHook": "",
"postMountHook": "",
"preCreateHook": "",
"preMountHook": "",
"type": "disk"
}
},
"lvm_vg": {},
"mdadm": {},
"nodev": {},
"zpool": {}
}
}
assert equal ("example/with-lib.nix" | path expand | disko eval-disko-file) {
success: true,
value: {
"disk": {
"/dev/vdb": {
"content": {
"device": "/dev/vdb",
"efiGptPartitionFirst": true,
"partitions": {
"boot": {
"alignment": 0,
"content": null,
"device": "/dev/disk/by-partlabel/disk-_dev_vdb-boot",
"end": "+1M",
"hybrid": null,
"label": "disk-_dev_vdb-boot",
"name": "boot",
"priority": 100,
"size": "1M",
"start": "0",
"type": "EF02"
},
"root": {
"alignment": 0,
"content": {
"device": "/dev/disk/by-partlabel/disk-_dev_vdb-root",
"extraArgs": [],
"format": "ext4",
"mountOptions": [
"defaults"
],
"mountpoint": "/",
"postCreateHook": "",
"postMountHook": "",
"preCreateHook": "",
"preMountHook": "",
"type": "filesystem"
},
"device": "/dev/disk/by-partlabel/disk-_dev_vdb-root",
"end": "-0",
"hybrid": null,
"label": "disk-_dev_vdb-root",
"name": "root",
"priority": 9001,
"size": "100%",
"start": "0",
"type": "8300"
}
},
"postCreateHook": "",
"postMountHook": "",
"preCreateHook": "",
"preMountHook": "",
"type": "gpt"
},
"device": "/dev/vdb",
"imageName": "_dev_vdb",
"imageSize": "2G",
"name": "_dev_vdb",
"postCreateHook": "",
"postMountHook": "",
"preCreateHook": "",
"preMountHook": "",
"type": "disk"
}
},
"lvm_vg": {},
"mdadm": {},
"nodev": {},
"zpool": {}
}
}
assert equal (".#testmachine" | disko eval-flake) {
success: true,
value: {
"disk": {
"main": {
"content": {
"device": "/dev/disk/by-id/ata-Samsung_SSD_850_EVO_250GB_S21PNXAGB12345",
"efiGptPartitionFirst": true,
"partitions": {
"ESP": {
"alignment": 0,
"content": {
"device": "/dev/disk/by-partlabel/disk-main-ESP",
"extraArgs": [],
"format": "vfat",
"mountOptions": [
"umask=0077"
],
"mountpoint": "/boot",
"postCreateHook": "",
"postMountHook": "",
"preCreateHook": "",
"preMountHook": "",
"type": "filesystem"
},
"device": "/dev/disk/by-partlabel/disk-main-ESP",
"end": "+512M",
"hybrid": null,
"label": "disk-main-ESP",
"name": "ESP",
"priority": 1000,
"size": "512M",
"start": "0",
"type": "EF00"
},
"boot": {
"alignment": 0,
"content": null,
"device": "/dev/disk/by-partlabel/disk-main-boot",
"end": "+1M",
"hybrid": null,
"label": "disk-main-boot",
"name": "boot",
"priority": 100,
"size": "1M",
"start": "0",
"type": "EF02"
},
"root": {
"alignment": 0,
"content": {
"device": "/dev/disk/by-partlabel/disk-main-root",
"extraArgs": [],
"format": "ext4",
"mountOptions": [
"defaults"
],
"mountpoint": "/",
"postCreateHook": "",
"postMountHook": "",
"preCreateHook": "",
"preMountHook": "",
"type": "filesystem"
},
"device": "/dev/disk/by-partlabel/disk-main-root",
"end": "-0",
"hybrid": null,
"label": "disk-main-root",
"name": "root",
"priority": 9001,
"size": "100%",
"start": "0",
"type": "8300"
}
},
"postCreateHook": "",
"postMountHook": "",
"preCreateHook": "",
"preMountHook": "",
"type": "gpt"
},
"device": "/dev/disk/by-id/ata-Samsung_SSD_850_EVO_250GB_S21PNXAGB12345",
"imageName": "main",
"imageSize": "2G",
"name": "main",
"postCreateHook": "",
"postMountHook": "",
"preCreateHook": "",
"preMountHook": "",
"type": "disk"
}
},
"lvm_vg": {},
"mdadm": {},
"nodev": {},
"zpool": {}
}
}
assert equal ("." | disko eval-flake) {
success: false,
messages: [
"Flake-uri . does not contain an attribute.",
"Please append an attribute like \"#foo\" to the flake-uri."
]
}
def main [] {
echo "All tests passed"
}

49
eval-config.nix Normal file
View file

@ -0,0 +1,49 @@
{ pkgs ? import <nixpkgs> { }
, lib ? pkgs.lib
, flake ? null
, flakeAttr ? null
, diskoFile ? null
, rootMountPoint ? "/mnt"
, ...
}@args:
let
disko = import ./. {
inherit rootMountPoint;
inherit lib;
};
flake' = (builtins.getFlake flake);
hasDiskoFile = diskoFile != null;
hasFlakeDiskoConfig = lib.hasAttrByPath [ "diskoConfigurations" flakeAttr ] flake';
hasFlakeDiskoModule =
lib.hasAttrByPath [ "nixosConfigurations" flakeAttr "config" "disko" "devices" ] flake';
diskFormat =
let
diskoConfig =
if hasDiskoFile then
import diskoFile
else
flake'.diskoConfigurations.${flakeAttr};
in
if builtins.isFunction diskoConfig then
diskoConfig ({ inherit lib; } // args)
else
diskoConfig;
evaluatedConfig =
if hasDiskoFile || hasFlakeDiskoConfig then
disko.eval diskFormat
else if (lib.traceValSeq hasFlakeDiskoModule) then
flake'.nixosConfigurations.${flakeAttr}
else
(builtins.abort "couldn't find `diskoConfigurations.${flakeAttr}` or `nixosConfigurations.${flakeAttr}.config.disko.devices`");
diskoConfig = evaluatedConfig.config.disko.devices;
finalConfig = lib.filterAttrsRecursive (name: value: !lib.hasPrefix "_" name) diskoConfig;
in
finalConfig