diff --git a/default.nix b/default.nix index bf3570e..65e4903 100644 --- a/default.nix +++ b/default.nix @@ -17,6 +17,7 @@ let }; in { + inherit eval; lib = lib.warn "the .lib.lib output is deprecated" diskoLib; # legacy alias diff --git a/disko.nu b/disko.nu new file mode 100755 index 0000000..3d89c9e --- /dev/null +++ b/disko.nu @@ -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 +} diff --git a/disko.test.nu b/disko.test.nu new file mode 100755 index 0000000..943fb7b --- /dev/null +++ b/disko.test.nu @@ -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" +} \ No newline at end of file diff --git a/eval-config.nix b/eval-config.nix new file mode 100644 index 0000000..8866213 --- /dev/null +++ b/eval-config.nix @@ -0,0 +1,49 @@ +{ pkgs ? import { } +, 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