Creating a nixos live cd for whole system

I have been attempting to follow the NixOS guide for generating a Live CD, but I am uncertain as to how to incorporate it into my Flake configuration. My goal is to make an ISO image that covers the whole system.

{
  description = "My NixOS configuration";

  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
    home-manager = {
      url = github:nix-community/home-manager;
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };

  outputs = inputs@{ self, nixpkgs, home-manager, ... }:
    let
      user = "lukas";
      configDir = "nix-config";
    in {
      nixosConfigurations = import ./hosts {
        inherit (nixpkgs) lib;
        inherit inputs nixpkgs home-manager user configDir;
      };
    }; 
}

What do I have to add to the flake above so nix build works?

Below you can see a working example. It’s what I do, with some bits removed:

run with nix build .#iso

# flake.nix
{
  inputs = {
    nixpkgs.url = "flake:nixpkgs/nixos-unstable";

    flake-utils.url = "github:numtide/flake-utils";

    nixos-generators.url = "github:nix-community/nixos-generators";
    nixos-generators.inputs.nixpkgs.follows = "nixpkgs";
  };

  outputs = inputs@{ self, nixpkgs, flake-utils, ... }: {
    ## nix build .#iso
    ## nixcfg --build-iso && nixcfg --burn-iso 00000111112222333
    packages.x86_64-linux.iso = inputs.nixos-generators.nixosGenerate {
      system = "x86_64-linux";
      format = "install-iso";
      specialArgs = {
        inherit inputs;
      };
      modules = [
        "${inputs.nixpkgs}/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix"
        ./images/iso.nix
        {
          system.stateVersion = "23.11";
        }
      ];
    };
  };
}

# images/iso.nix
{ lib
, pkgs
, ...
}:
##   - Build iso:
# nix build .#iso
##   - Find installation device (eg. /dev/sdX):
# lsblk
##   - Write to thumb-drive:
# sudo dd bs=4M if=result/iso/my-nixos-live.iso of=/dev/sdX status=progress oflag=sync
{
  imports = [
    ./base-config.nix
  ];

  isoImage.volumeID = lib.mkForce "my-nixos-live";
  isoImage.isoName = lib.mkForce "my-nixos-live.iso";
  # Use zstd instead of xz for compressing the liveUSB image, it's 6x faster and 15% bigger.
  isoImage.squashfsCompression = "zstd -Xcompression-level 6";
}
# images/base-config.nix
{ lib
, pkgs
, ...
}:
{
  imports = [
    # ../modules/base-system.nix
    # ../modules/services/numlock-on-tty
  ];

  networking = {
    useDHCP = false;
    hostName = "my-nixos-live"; # default: "nixos"
    usePredictableInterfaceNames = false;
    interfaces.eth0.useDHCP = true;
    # interfaces.eth0.ipv4.addresses = [
    #   {
    #     address = "192.168.1.2";
    #     prefixLength = 24;
    #   }
    # ];
    # defaultGateway = "192.168.1.1";
    # nameservers = [ "192.168.1.1" "1.1.1.1" "8.8.8.8" ];
  };

  boot.supportedFilesystems = [ "zfs" "f2fs" ];
  # serial connection for apu
  boot.kernelParams = [ "console=ttyS0,115200n8" ];

  users.mutableUsers = false;
  users.users.root.openssh.authorizedKeys.keys = [
    "ssh-ed25519 xxxx me@mypc"
  ];
  users.users = {
    "nixos" = {
      isNormalUser = true;
      home = "/home/nixos";
      password = "";
      uid = 1000;
      extraGroups = [ "systemd-journal" "wheel" ];
    };
  };

  # sshd
  services.openssh = {
    enable = true;
    settings.PasswordAuthentication = false;
    settings.PermitRootLogin = lib.mkDefault "prohibit-password";
    hostKeys = [
      { type = "rsa"; bits = 4096; path = "/etc/ssh/ssh_host_rsa_key"; }
      { type = "ed25519"; path = "/etc/ssh/ssh_host_ed25519_key"; }
    ];
  };

  services.avahi = {
    enable = true;
    nssmdns = true;
    publish.addresses = true;
    publish.domain = true;
    publish.enable = true;
    publish.userServices = true;
    publish.workstation = true;
  };

  # Turn on flakes.
  nix.package = pkgs.nixVersions.stable;
  nix.extraOptions = ''
    experimental-features = nix-command flakes
  '';

  # includes this flake in the live iso : "/etc/nixcfg"
  environment.etc.nixcfg.source =
    builtins.filterSource
      (path: type:
        baseNameOf path
        != ".git"
        && type != "symlink"
        && !(pkgs.lib.hasSuffix ".qcow2" path)
        && baseNameOf path != "secrets")
      ../.;

  environment.systemPackages = with pkgs; [
    git
    htop
    tmux
    tree
    nano
    rsync
    ripgrep
    cryptsetup
    nixpkgs-fmt
  ];

  ## FIX for running out of space / tmp, which is used for building
  fileSystems."/nix/.rw-store" = {
    fsType = "tmpfs";
    options = [ "mode=0755" "nosuid" "nodev" "relatime" "size=14G" ];
    neededForBoot = true;
  };





  # Part of base-system.nix:
  time.timeZone = lib.mkDefault "Etc/UTC";

  i18n = {
    defaultLocale = "en_IE.UTF-8";
    extraLocaleSettings = {
      LC_TIME = "en_GB.UTF-8";
    };
    supportedLocales = lib.mkDefault [
      "en_GB.UTF-8/UTF-8"
      "en_IE.UTF-8/UTF-8"
      "en_US.UTF-8/UTF-8"
    ];
  };
  environment.variables = {
    TERM = "xterm-256color";
  };

  # # Use a high-res font.
  # boot.loader.systemd-boot.consoleMode = "0";
  console = {
    # https://github.com/NixOS/nixpkgs/issues/114698
    earlySetup = true; # Sets the font size much earlier in the boot process
    colors = [
      # # frappe colors
      "51576d"
      "e78284"
      "a6d189"
      "e5c890"
      "8caaee"
      "f4b8e4"
      "81c8be"
      "b5bfe2"
      "626880"
      "e78284"
      "a6d189"
      "e5c890"
      "8caaee"
      "f4b8e4"
      "81c8be"
      "a5adce"
    ];
    font = "Lat2-Terminus16";
    useXkbConfig = true; # Use same config for linux console
  };

  services.xserver = {
    enable = lib.mkDefault false; # but still here so we can copy the XKB config to TTYs
    autoRepeatDelay = 300;
    autoRepeatInterval = 35;
  } // lib.optionalAttrs false {
    xkbVariant = "colemak";
    xkbOptions = "caps:super,compose:ralt,shift:both_capslock";
  };
}
2 Likes

Thank you! I’ll look into it!

the import

"${inputs.nixpkgs}/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix"

should not be needed as it is already imported by the format from nixos-generators. Or did you run into issues without that line?

2 Likes

I removed it and it still builds. It was a line from before I used the generator.

1 Like

@Carrot thank you very much for your example above! I generated my image and booted my VPS into it. How can I install it on the system? How do you do it?

I’m trying this, but I get an error telling me to use --impure, cause “/nix/store” is forbidden in pure eval mode

If I try to use --impure, I get

BASHY $ nix build .#iso --impure
trace: warning: The option `services.avahi.nssmdns' defined in `/nix/store/zhl7mjj562c2sdndkrl4kdprbp65vlj5-source/base-config.nix' has been renamed to `services.avahi.nssmdns4'.
evaluating derivation 'path:/home/b0ef/proj/2024-01-24.iso-for-a-dead-manerror (ignored): error: reached end of FramedSource
error: opening file '/nix/store/0wbygchdl9aq921xqmp06kcsvjpmhshq-ext4-fs.img.zst.lock': Permission denied
(use '--show-trace' to show detailed location information)

I use my custom script, which looks for a host with the live image on the local network, and installs one of my hosts defined in the flake.
I’m working on a version which installs from an encrypted thumbdrive. This usb contains my flake and other secrets.
I’ll have to look into nixos-anywhere, It’ll be useful for your vps usage. But I don’t think it supports all the things I need. (eg. host secrets shouldn’t leave the host, so no injection)

The script expect all configurations in the flake to have settings for disko(my disko options).
These settings I use for:

  • disko during install
  • disko in system configuration (build,boot,switching)

my disko module which uses a disko-ext4 or disko-zfs module.

          modules = [
            {
              ncfg = {
                disko.disks = [ "/dev/disk/by-id/ata-SAMSUNGxxxxxxxxxxx" "/dev/disk/by-id/ataxxxxxx" ];
                disko.bootloader = "grub"; # Mirrored Disks only with grub
                disko.filesystem = "zfs";
                disko.luks_encryption = true;
                # ssh root@10.10.10.10 -p 2222
                # systemd-tty-ask-password-agent
                disko.luks_remote_unlock = true;
                disko.zfs_ashift = 12;
                disko.zfs_refreservationInGB = 10;
 };}];
#!/usr/bin/env bash
VERSION="20240105"

ARG_USE_ROOT_USERNAME=false
ARG_IGNORE_UNMOUNT=false
ARG_REBOOT_AFTER=false
ARG_SHUTDOWN_AFTER=false

# REMOTE_CONFIG_PATH="/tmp"
HOSTNAME_IMAGE="my-nixos"          # TODO CHANGE
HOSTNAME_INSTALLER="my-nixos-live" # TODO CHANGE

# DEFAULTS:
LOCAL_CONFIG_PATH="/PathToYourFlake" # TODO CHANGE
DEFAULT_DESTINATION="root@${HOSTNAME_INSTALLER}.local"

#################################################

L_0EMERGENCY=0
L_1ALERT=1
L_2CRITICAL=2
L_3ERROR=3
L_4WARNING=4
L_5NOTICE=5
L_6INFO=6
L_7DEBUG=7
LOG_LEVEL=$L_5NOTICE

_COLOR_RESET="\033[0m"
_COLOR_START="\033["
_COLOR_END="m"
_NORMAL=0
_BOLD=1
_UNDERLINED=4
_BLINKING=5
_REVERSE_VIDEO=7
_FG_BLACK=30
_FG_RED=31
_FG_GREEN=32
_FG_YELLOW=33
_FG_BLUE=34
_FG_MAGENTA=35
_FG_CYAN=36
_FG_WHITE=37
_BG_BLACK=40
_BG_RED=41
_BG_GREEN=42
_BG_YELLOW=43
_BG_BLUE=44
_BG_MAGENTA=45
_BG_WHITE=47
_BG_CYAN=46

set -euo pipefail
set -f
# set -x # Print all executed commands to the terminal

function usage() {
    printf "%b" "
Helper script to manage my nixos configurations.

Usage

  $(basename "${BASH_SOURCE[0]}") [options]

Options:

    -h, --help                        Display this message
        --version                     Show version
    -v, --verbose                     Show what is being done
        --debug                       Show everything that can be shown

    -t  --tty                         
    -p  --ssh-port                    
        --ssh-option                  
        --config=PATH                 nixos config path (default: ${LOCAL_CONFIG_PATH})
        --config-path=PATH            nixos config path (default: ${LOCAL_CONFIG_PATH})
        --dest=user@host              user@host of the live image (default: ${DEFAULT_DESTINATION})
        --destination=user@host       user@host of the live image (default: ${DEFAULT_DESTINATION})
        --root                        Use root@ instead of the primaryUser
        --poweroff                    Shutdown the system after
        --shutdown                    Shutdown the system after
        --reboot                      Reboot the system after

        --rollback                    Rollback to the previous generation
        --offline                     In some cases capable of handling rebuilds without network connections


Arguments:
    ssh
    build-iso                         Generate iso
    build-image-rpi4                  Generate iso
    burn-iso [serial]                 Write iso to usb with serial
    burn-image [serial]               Write image to usb with serial
    is-uefi-mode                      Is it using uefi mode or legacy
    install [hostname]                Install a nixos config: remote or locally.
    install-anywhere [hostname]       Install a nixos config
    unmount                           Unmount /mnt & export zpool
    restart-sway
    reboot
    shutdown
    poweroff
    update                            Update flake
    build                            
    build-vm                            
    build-vm-with-bootloader
    test                            
    boot                            
    switch                            
    list                              Show generations
    compare-generations               Show difference between generations
    delete                            Delete old generations, except 1day
    delete-all                        Delete old generations
"
}

function ask_question() {
    echo -e "\n${_COLOR_START}${_BOLD};${_FG_WHITE};${_BG_GREEN}${_COLOR_END} > $1${_COLOR_RESET}"
}

function ask_question_yn() {
    ask_question "$1"
    read -n 1 -r
    echo
}

function set_log_level() {
    local level=$1
    if [ "$level" -gt "$LOG_LEVEL" ]; then
        LOG_LEVEL=$level
    fi
}

declare -A LOG_LEVELS
# https://en.wikipedia.org/wiki/Syslog#Severity_level
LOG_LEVELS=([$L_0EMERGENCY]="emerg" [$L_1ALERT]="alert" [$L_2CRITICAL]="crit" [$L_3ERROR]="ERROR" [$L_4WARNING]="warning" [$L_5NOTICE]="notice" [$L_6INFO]="info" [$L_7DEBUG]="debug")
function .log() {
    local level=$1
    shift
    # Trim trailing whitespace
    msg=$(echo "${@}" | awk '{gsub(/ +$/,"")} {print $0 }')

    if [ "$LOG_LEVEL" -ge "$level" ]; then
        if [ "$level" == 0 ]; then
            echo -e " ${_COLOR_START}${_BOLD};${_FG_MAGENTA};${_BLINKING};${_UNDERLINED}${_COLOR_END}[${LOG_LEVELS[$level]}] $msg${_COLOR_RESET}"
        elif [ "$level" == 1 ]; then
            echo -e " ${_COLOR_START}${_BOLD};${_FG_MAGENTA};${_BLINKING}${_COLOR_END}[${LOG_LEVELS[$level]}] $msg${_COLOR_RESET}"
        elif [ "$level" == 2 ]; then
            echo -e " ${_COLOR_START}${_NORMAL};${_FG_MAGENTA}${_COLOR_END}[${LOG_LEVELS[$level]}] $msg${_COLOR_RESET}"
        elif [ "$level" == 3 ]; then
            echo -e " ${_COLOR_START}${_NORMAL};${_FG_RED}${_COLOR_END}[${LOG_LEVELS[$level]}] $msg${_COLOR_RESET}"
        elif [ "$level" == 4 ]; then
            echo -e " ${_COLOR_START}${_NORMAL};${_FG_WHITE}${_COLOR_END}[${LOG_LEVELS[$level]}] $msg${_COLOR_RESET}"
        elif [ "$level" == 5 ]; then
            echo "$msg"
        elif [ "$level" == 6 ]; then
            echo -e " ${_COLOR_START}${_NORMAL};${_FG_WHITE}${_COLOR_END}[${LOG_LEVELS[$level]}] $msg${_COLOR_RESET}"
        elif [ "$level" == 7 ]; then
            echo -e " ${_COLOR_START}${_NORMAL};${_FG_WHITE}${_COLOR_END}[${LOG_LEVELS[$level]}] $msg${_COLOR_RESET}"
        fi
    fi

}
function clear_warning_line() {
    echo -ne "\033[0K\r" # clear warning line
}

function warn_countdown() {
    timeout=$(($1 * 10)) # in seconds
    message=$2
    while [ ${timeout} -gt 0 ]; do
        echo -ne " ${_COLOR_START}${_NORMAL};${_BG_MAGENTA}${_COLOR_END}[WARNING] $message $timeout${_COLOR_RESET}\033[0K\r"
        ((timeout--))
        sleep 0.1
    done
    if [ "${3-}" == "clear" ]; then
        clear_warning_line
    else
        echo # show warning line
    fi
}

function now() {
    date +"%Y%m%d_%H%M%S"
}

function get_date() {
    # returns the date x days in the future or past
    date --date="$1 day" +%Y%m%d
}

function formatTime() {
    printf '%02d:%02d:%02d\n' $(($1 / 3600)) $(($1 % 3600 / 60)) $(($1 % 60))
}

function die() {
    .log $L_2CRITICAL "$*"
    exit 1
}

function validateConfigPath() {
    # Validate $LOCAL_CONFIG_PATH
    if [ ! -f "${LOCAL_CONFIG_PATH}/flake.nix" ]; then
        .log $L_3ERROR "nixos config not found: '${LOCAL_CONFIG_PATH}/flake.nix'"
        exit 1
    fi
}

# ssh wrapper
function timeout_ssh_() {
    # timeout 10 ssh -i "$ssh_key_dir"/nixos-anywhere -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "${ssh_args[@]}" "$ssh_connection" "$@"
    timeout 10 ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "${ssh_args[@]}" "$dest" "$@"
}
function ssh_() {
    # ssh "$ssh_tty_param" -i "$ssh_key_dir"/nixos-anywhere -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "${ssh_args[@]}" "$ssh_connection" "$@"
    ssh "$ssh_tty_param" -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "${ssh_args[@]}" "$dest" "$@"
}

function nix_copy() {
    # NIX_SSHOPTS="-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i $ssh_key_dir/nixos-anywhere ${ssh_args[*]}" nix copy \
    NIX_SSHOPTS="-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no ${ssh_args[*]}" nix copy \
        "${nix_options[@]}" \
        "$@"
    # "${nix_copy_options[@]}" \
}
function nix_build() {
    # NIX_SSHOPTS="-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i $ssh_key_dir/nixos-anywhere ${ssh_args[*]}" nix build \
    NIX_SSHOPTS="-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no ${ssh_args[*]}" nix build \
        --print-out-paths \
        --no-link \
        "${nix_options[@]}" \
        "$@"
}

# true if the current OS in the nixos live iso
function is_iso() {
    grep -q 'VARIANT_ID=installer' < "/etc/os-release"
}
function exit_when_iso() {
    if is_iso; then
        .log $L_3ERROR "Not supported on live iso"
        exit 1
    fi
}

function exit_when_stateversion_between_iso_and_flake_system_missmatch() {
    hostname="$1"
    version_in_iso="$2"
    # version_in_iso="$(grep "VERSION_ID=" < /etc/os-release | cut -d '=' -f2 | xargs)"
    version_in_flake_for_system="$(nix eval "${LOCAL_CONFIG_PATH}#nixosConfigurations.${hostname}.config.ncfg.stateVersion" | xargs)"

    if [ "$version_in_flake_for_system" != "$version_in_iso" ]; then
        .log $L_3ERROR "stateVersions don't match for >$hostname< : $version_in_flake_for_system (flake) != $version_in_iso (iso)"
        exit 1
    fi
}

HOSTNAMES=""
function get_all_hostnames() {
    if [ -z "$HOSTNAMES" ]; then
        HOSTNAMES=$(nix eval "${LOCAL_CONFIG_PATH}#nixosConfigurations" --apply 'pkgs: builtins.concatStringsSep " " (builtins.attrNames pkgs)' | xargs | tr ' ' '\n')
    fi
    echo "${HOSTNAMES}"
}
# Check if argument hostname exists in flake
function check_hostname_exists() {
    if ! get_all_hostnames | grep -q "^${1}\$"; then
        .log $L_3ERROR "hostname not found '$1'"
        echo "hostnames: "
        get_all_hostnames
        exit 1
    fi
}
function get_all_hostnames_localhost_first() {
    local all_but_local
    all_but_local=$(get_all_hostnames | grep --invert-match "^${HOSTNAME}\$")
    printf '%s\n%s\n' "${HOSTNAME}   (localhost)" "${all_but_local}"
}
function get_all_hostnames_installer_first() {
    printf '%s\n%s\n' "${HOSTNAME_INSTALLER}   (installer)" "$(get_all_hostnames)"
}
function validate_hostname_or_choose_when_empty() {
    local hostname
    if [ $# -eq 0 ] || [ -z "$1" ]; then
        hostname=$(get_all_hostnames | gum choose | cut -d ' ' -f1)
    else
        hostname="$1"
        check_hostname_exists "$hostname"
    fi
    if [ ! "$hostname" ] || [ -z "$hostname" ] || [ "$hostname" == "" ]; then
        .log $L_3ERROR "No hostname .."
        exit 1
    fi
    echo "$hostname"
}
function validate_hostname_or_choose_with_localhost_first_when_empty() {
    local hostname
    if [ $# -eq 0 ] || [ -z "$1" ]; then
        hostname=$(get_all_hostnames_localhost_first | gum choose | cut -d ' ' -f1)
    else
        hostname="$1"
        check_hostname_exists "$hostname"
    fi
    if [ ! "$hostname" ] || [ -z "$hostname" ] || [ "$hostname" == "" ]; then
        .log $L_3ERROR "No hostname ..."
        exit 1
    fi
    echo "$hostname"
}
function validate_hostname_or_choose_with_installer_first_when_empty() {
    local hostname
    if [ $# -eq 0 ] || [ -z "$1" ]; then
        hostname=$(get_all_hostnames_installer_first | gum choose | cut -d ' ' -f1)
    else
        hostname="$1"
        if [ "$hostname" != "$HOSTNAME_INSTALLER" ]; then
            check_hostname_exists "$hostname"
        fi
    fi
    if [ ! "$hostname" ] || [ -z "$hostname" ] || [ "$hostname" == "" ]; then
        .log $L_3ERROR "No hostname ...."
        exit 1
    fi
    echo "$hostname"
}

function extract_hostname_from_destination() {
    local destination str
    destination="$1"
    # Subtract username@, only keep last after delimiter
    str=${destination##*@}
    # Remove .local if at the end of the string
    str=${str%".local"}
    echo "$str"
}
function empty_when_localhost() {
    local destination hostname
    destination="$1"
    hostname=$(extract_hostname_from_destination "$destination")
    if [ "$hostname" == "$HOSTNAME" ]; then
        destination=""
    fi
    echo "$destination"
}

function convert_hostname_in_destination() {
    local use_root_user destination
    use_root_user="$1"
    destination="$2"
    destination="$(empty_when_localhost "$destination")"
    if [ "$destination" == "" ]; then
        # Empty destination for localhost
        echo ""
    else
        # The argument can be:
        # - hostname (live iso OR nixos configuration in flake)
        # - username@hostname
        # - username@hostname.local
        # - hostname.local
        # - ipv4
        # - ipv6
        # - username@ipv4
        # - username@ipv6
        local userName
        if [ "$use_root_user" = true ]; then
            # Use root user as default
            userName="root"
        else
            # Use primary user as default
            hostname=$(extract_hostname_from_destination "$destination")
            userName="$(nix eval "${LOCAL_CONFIG_PATH}#nixosConfigurations.${hostname}.config.ncfg.primaryUserName" | xargs)"
        fi
        if [ "$userName" == "" ]; then
            userName="root"
        fi
        [[ $destination =~ ^.+@.+$ ]] || destination="${userName}@${destination}"
        # Append .local if it's no ipv4 or ipv6
        [[ $destination =~ ^.+\.local$ ]] || contains_ip_address "$destination" || destination="${destination}.local"
        echo "$destination"
    fi
}

function is_ipv4_address() {
    # Set up local variables
    local ip="$1"
    local IFS=.
    local -a a=("$ip")
    # Start with a regex format test
    [[ $ip =~ ^[0-9]+(\.[0-9]+){3}$ ]] || return 1
    # Test values of quads
    local quad
    for quad in {0..3}; do
        [[ ${a[$quad]} -gt 255 ]] && return 1
    done
    return 0
}
function is_ipv6_address() {
    [[ $1 =~ ^([0-9a-fA-F]{0,4}:){0,7}[0-9a-fA-F]{1,4}$ ]]
}
function contains_ip_address() {
    str="$1"
    # Subtract username@, only keep last after delimiter
    str=${str##*@}
    if is_ipv4_address "$str"; then
        return 0
    elif is_ipv6_address "$str"; then
        return 0
    else
        return 1
    fi
}

function get_device_path_by_serial() {
    local device_serial device_path
    device_serial=$1
    if [ -z "$device_serial" ]; then
        .log $L_3ERROR "   device serial not supplied"
        exit 1
    fi
    device_path=$(sudo lsblk -n -o serial,path | grep "${device_serial}" | head -n1 | awk -F' ' '{print $2}')
    if [ -z "$device_path" ]; then
        .log $L_3ERROR "   device path not found"
        exit 1
    fi
    echo "${device_path}"
}

function check_uefi_mode() {
    local dest
    dest="$1"
    .log $L_5NOTICE " > Checking Uefi mode: ${dest}"
    if is_iso || [ -z "$dest" ]; then
        if [ -d "/sys/firmware/efi/efivars" ]; then
            echo "uefi supported"
        else
            echo "uefi not supported"
        fi
    else
        ssh_ test -d "/sys/firmware/efi/efivars" && echo "uefi supported" || echo "uefi not supported"
    fi
}

function install() {
    local hostname dest
    hostname="$1"
    dest="$2"
    if is_iso; then
        .log $L_5NOTICE " > install (localhost)"

        version_in_iso="$(grep "VERSION_ID=" < /etc/os-release | cut -d '=' -f2 | xargs)"
        exit_when_stateversion_between_iso_and_flake_system_missmatch "$hostname" "$version_in_iso"

        # TODO Install through live iso on host machine (localhost)
        # TODO use drvi to mount drive
        .log $L_3ERROR "Not YET supported on live iso"
        exit 1
    else
        .log $L_5NOTICE " > install (remote)"

        version_in_iso=$(ssh_ '( grep "VERSION_ID=" < /etc/os-release | cut -d '=' -f2 | xargs )')
        exit_when_stateversion_between_iso_and_flake_system_missmatch "$hostname" "$version_in_iso"

        .log $L_5NOTICE " > Extract hardware-configuration.nix:"
        ssh_ '( nixos-generate-config --no-filesystems --root /mnt --show-hardware-config )' > "${LOCAL_CONFIG_PATH}/hosts/${hostname}/hardware-configuration.nix"
        nixpkgs-fmt "${LOCAL_CONFIG_PATH}/hosts/${hostname}/hardware-configuration.nix"
        .log $L_5NOTICE " > Extract machine-id:"

        rsync -ah "${dest}:/etc/machine-id" "${LOCAL_CONFIG_PATH}/hosts/${hostname}/"

        # disks="$(nix eval "${LOCAL_CONFIG_PATH}#nixosConfigurations.${hostname}.config.ncfg.disko.disks")"
        # # TODO: Check the disks from flake are found on the destination, otherwise exit 1
        # swapInGB="$(nix eval "${LOCAL_CONFIG_PATH}#nixosConfigurations.${hostname}.config.ncfg.disko.swapInGB")"
        # filesystem="$(nix eval "${LOCAL_CONFIG_PATH}#nixosConfigurations.${hostname}.config.ncfg.disko.filesystem" | xargs)"
        # luks="$(nix eval "${LOCAL_CONFIG_PATH}#nixosConfigurations.${hostname}.config.ncfg.disko.luks")"
        # disko_zfs_options=""
        # if [ "$filesystem" == "zfs" ]; then
        #     zfs_ashift="$(nix eval "${LOCAL_CONFIG_PATH}#nixosConfigurations.${hostname}.config.ncfg.disko.zfs_ashift")"
        #     disks_string=$(echo "$disks" | tr -d '[]' | xargs)
        #     remote_zfs_ashift=$(ssh_ -- ashift-calculation "$disks_string")
        #     if [ "$zfs_ashift" == "$remote_zfs_ashift" ]; then
        #         .log $L_5NOTICE " ashift = <$zfs_ashift>"
        #     else
        #         .log $L_4WARNING "ashift is not optimal  flake:$zfs_ashift  calculation:$remote_zfs_ashift"
        #         warn_countdown 10 " Please cancel script execution and change ashift, ignore warning & continue in:"
        #     fi
        #     zfs_refreservationInGB="$(nix eval "${LOCAL_CONFIG_PATH}#nixosConfigurations.${hostname}.config.ncfg.disko.zfs_refreservationInGB")"
        #     disko_zfs_options="--arg zfs_ashift '$zfs_ashift' --arg zfs_refreservationInGB '$zfs_refreservationInGB'"
        # fi

        # .log $L_5NOTICE " > Push disko config to remote:"
        # rsync --mkpath -ah --delete "${LOCAL_CONFIG_PATH}/diskos/disko_${filesystem}.nix" "${dest}:${REMOTE_CONFIG_PATH}"
        # .log $L_5NOTICE " > Execute disko: partition, format, mount:"
        # ssh_ << SSH
        # ssh_ disko --mode disko ${REMOTE_CONFIG_PATH}/disko_${filesystem}.nix --arg disks "$disks" --arg swapInGB "$swapInGB" --arg luks "$luks" ${disko_zfs_options}

        # echo "$dest disko --mode disko ${REMOTE_CONFIG_PATH}/disko_${filesystem}.nix --arg disks '$disks' --arg swapInGB '$swapInGB' --arg luks '$luks' ${disko_zfs_options}" | xargs bash -x -c "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -tt ${0}" {}

        .log $L_5NOTICE " > Build disko config:"
        # disko_script=$(nix_build "${flake}#nixosConfigurations.\"${flakeAttr}\".config.system.build.diskoScript")
        disko_script=$(nix_build "${LOCAL_CONFIG_PATH}#nixosConfigurations.${hostname}.config.system.build.diskoScript")
        # nix_copy --to "ssh://$ssh_connection" "$disko_script"
        .log $L_5NOTICE " > Copy disko config to remote:"
        nix_copy --to "ssh://$dest?remote-store=local?root=/mnt" "$disko_script"
        nix_copy --to "ssh://$dest" "$disko_script"
        # nix copy --to "ssh://$dest?remote-store=local?root=/mnt" "$sys" |& nom
        # NIX_SSHOPTS='-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no' nix copy --to "ssh://${dest}" "$disko"
        # # NIX_SSHOPTS='-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no' nix copy --to "ssh://${dest}?remote-store=local?root=/mnt" "$disko"
        .log $L_5NOTICE " > Execute disko: partition, format, mount:"
        ssh_ "$disko_script"

        # sys="$(nix eval --raw "${LOCAL_CONFIG_PATH}#nixosConfigurations.${hostname}.config.system.build.toplevel")"
        # ssh_ "pkill -KILL -u ${user}"
        # ssh_ << SSH
        #     bash -l -c "bash";
        #     disko --mode disko ${REMOTE_CONFIG_PATH}/disko_${filesystem}.nix --arg disks '$disks' --arg swapInGB '$swapInGB' --arg luks '$luks' ${disko_zfs_options}
        # SSH

        # warn_countdown 100 "manual disko for luks moment"

        .log $L_5NOTICE " > Extract hardware-configuration.nix:"
        ssh_ '( nixos-generate-config --no-filesystems --root /mnt --show-hardware-config  )' > "${LOCAL_CONFIG_PATH}/hosts/${hostname}/hardware-configuration.nix"
        nixpkgs-fmt "${LOCAL_CONFIG_PATH}/hosts/${hostname}/hardware-configuration.nix"
        # NIX_SSHOPTS='-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no' nix copy --to "ssh://${dest}" "$disko"
        # # NIX_SSHOPTS='-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no' nix copy --to "ssh://${dest}?remote-store=local?root=/mnt" "$disko"
        # # NIX_SSHOPTS='-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no' nix-copy-closure --to "$dest" "$disko"
        nixpkgs-fmt "${LOCAL_CONFIG_PATH}/hosts/${hostname}/hardware-configuration.nix"

        ssh_ '( mount | grep /mnt )'

        luks_remote_unlock_enabled="$(nix eval "${LOCAL_CONFIG_PATH}#nixosConfigurations.${hostname}.config.ncfg.disko.luks_remote_unlock" | xargs)"
        if [ "$luks_remote_unlock_enabled" = true ]; then
            ## [ "/persist/etc/secrets/initrd/host_ecdsa_key" ]
            init_hostkey_path="/mnt$(nix eval "${LOCAL_CONFIG_PATH}#nixosConfigurations.${hostname}.config.boot.initrd.network.ssh.hostKeys" | tr -d '[]' | xargs)"
            ## /mnt/persist/etc/secrets/initrd/host_ecdsa_key
            init_hostkey_dir="$(dirname "${init_hostkey_path}")"
            .log $L_5NOTICE " > ssh init: Execute mkdir ${init_hostkey_dir}:"
            ssh_ "( mkdir -p ${init_hostkey_dir} )"
            .log $L_5NOTICE " > ssh init: Execute ssh-keygen ${init_hostkey_path}:"
            ssh_ "( ssh-keygen -t ecdsa -N '' -f ${init_hostkey_path} )"
        fi

        .log $L_5NOTICE " > Execute mkdir /mnt/etc/ssh:"
        ssh_ "( mkdir -p /mnt/etc/ssh )"
        .log $L_5NOTICE " > Execute mkdir /mnt/persist/etc/ssh:"
        ssh_ "( mkdir -p /mnt/persist/etc/ssh )"
        .log $L_5NOTICE " > Extract ssh key.pub:"
        rsync -ah "${dest}:/etc/ssh/ssh_host_ed25519_key.pub" "${LOCAL_CONFIG_PATH}/hosts/${hostname}/key.pub"
        .log $L_5NOTICE " > Execute cp ssh keys from iso to /mnt:"
        ssh_ "( cp /etc/ssh/ssh_host_ed25519_key* /mnt/etc/ssh/ )"
        .log $L_5NOTICE " > Execute cp ssh keys from iso to /mnt/persist:"
        ssh_ "( cp /etc/ssh/ssh_host_ed25519_key* /mnt/persist/etc/ssh/ )"
        .log $L_5NOTICE " > Rekey agenix:"
        cd "${LOCAL_CONFIG_PATH}"
        agenix --rekey
        git add "${LOCAL_CONFIG_PATH}/secrets"
        git add "${LOCAL_CONFIG_PATH}/hosts/${hostname}"
        git commit -m "add host ${hostname}"

        .log $L_5NOTICE " > system eval"
        sys="$(nix eval --raw "${LOCAL_CONFIG_PATH}#nixosConfigurations.${hostname}.config.system.build.toplevel")"
        .log $L_5NOTICE " > system build"
        nix build "${LOCAL_CONFIG_PATH}#nixosConfigurations.${hostname}.config.system.build.toplevel" --out-link "$(mktemp -d)/result" |& nom
        .log $L_5NOTICE " > system copy"
        nix copy --to "ssh://$dest?remote-store=local?root=/mnt" "$sys" |& nom
        .log $L_5NOTICE " > system nixos-install"
        ssh_ << SSH
        nixos-install --no-root-passwd --no-channel-copy --system "$sys"
SSH

        # # format
        # if ! (ssh_ -- mountpoint /mnt); then
        #     if ! (ssh_ -- type -p nix); then
        #         nix run github:nix-community/nixos-anywhere -- --stop-after-disko --store-paths "$(nix-build --no-out-link -I stockholm="$HOME/sync/stockholm" -I nixos-config="$HOME"/sync/stockholm/lass/1systems/"$hostname"/physical.nix '<nixpkgs/nixos>' -A config.system.build.diskoNoDeps)" /dev/null "$dest"
        #     else
        #         disko=$(nix-build -I stockholm="$HOME/sync/stockholm" -I secrets="$HOME/sync/stockholm/lass/2configs/tests/dummy-secrets" -I nixos-config="$HOME/sync/stockholm/lass/1systems/$hostname/physical.nix" '<nixpkgs/nixos>' -A config.system.build.disko)
        #         NIX_SSHOPTS='-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no' nix-copy-closure --to "$dest" "$disko"
        #         ssh_ -- "$disko"
        #     fi
        # fi

        # # echo "  > $FUTURE_HOSTNAME : nixos-anywhere Test in VM" ; nixos-anywhere --flake .#${FUTURE_HOSTNAME} --vm-test
        # # echo "  > $FUTURE_HOSTNAME : nixos-anywhere Install:" ; nixos-anywhere --flake .#${FUTURE_HOSTNAME} root@$REMOTE_IP

        # # populate
        # $(nix-build --no-out-link "$HOME"/sync/stockholm/lass/krops.nix -A populate --argstr name "$hostname" --argstr target "$TARGET"/mnt/var/src --arg force true)

        # NIXOS_CONFIG=/var/src/nixos-config nixos-install --no-root-password -I /mnt/var/src
        # nixos-enter -- nixos-rebuild -I /var/src switch --install-bootloader

        unmount_and_export_pool "$dest"
    fi
}

function unmount_and_export_pool() {
    local dest
    dest="$1"
    if [ "$ARG_IGNORE_UNMOUNT" = false ]; then
        if is_iso; then
            .log $L_5NOTICE " > unmount_and_export_pool: localhost"
            .log $L_5NOTICE " > Execute unmount:"
            ssh_ '( umount -R /mnt )'
            .log $L_5NOTICE " > Execute zpool export:"
            ssh_ '( zpool export -fa )'
        else
            .log $L_5NOTICE " > unmount_and_export_pool: $dest"
            .log $L_5NOTICE " > Execute unmount:"
            ssh_ '( umount -R /mnt )'
            .log $L_5NOTICE " > Execute zpool export:"
            ssh_ '( zpool export -fa )'
        fi
    fi
}

function restart_sway() {
    dest="${1:-""}"
    .log $L_5NOTICE " > Restart Sway: ${dest}"
    warn_countdown 2 "Restarting Sway in:"
    if [ -n "$dest" ]; then
        local user
        hostname=$(extract_hostname_from_destination "$dest")
        user="$(nix eval "${LOCAL_CONFIG_PATH}#nixosConfigurations.${hostname}.config.ncfg.primaryUserName")"
        # TODO: Check if flake has sway running/enabled
        ssh_ "pkill -KILL -u ${user}"
    else
        pkill -KILL -u "$USER"
    fi
}

function shutdown_or_reboot() {
    dest="${1:-""}"
    # ssh: Could not resolve hostname hp-t520-5: Name or service not known # TODO TODO TOODOO tttttttttttttt xxxxxxxx

    if [ "$ARG_SHUTDOWN_AFTER" = true ]; then
        .log $L_5NOTICE " > Poweroff: ${dest}"
        warn_countdown 2 "Poweroff in:"
        if is_iso || [ -z "$dest" ]; then
            systemctl poweroff -i
        else
            ssh_ '( sudo systemctl poweroff -i )'
        fi
    elif [ "$ARG_REBOOT_AFTER" = true ]; then
        .log $L_5NOTICE " > Reboot: ${dest}"
        warn_countdown 2 "Rebooting in:"
        if is_iso || [ -z "$dest" ]; then
            systemctl reboot -i
        else
            ssh_ '( sudo systemctl reboot -i )'
        fi
    fi
}

function flake_update() {
    .log $L_5NOTICE " > Flake update"
    nix flake update --commit-lock-file "$LOCAL_CONFIG_PATH"
}

function rebuild() {
    local mode hostname destination
    mode="${1}"
    if [ -z "$mode" ]; then
        .log $L_3ERROR "mode not found"
    fi
    hostname="${2}"
    destination="${3:-""}"

    local remote_part1 remote_part2
    remote_part1=()
    remote_part2=()
    if [ -n "$destination" ]; then
        .log $L_5NOTICE " > Remote: ${hostname}  ${destination}"
        remote_part1=(/#"${hostname}")
        if [ "$mode" != "build" ] && [ "$mode" != "build-vm" ] && [ "$mode" != "build-vm-with-bootloader" ]; then
            # setting --build-host localhost Works the same as without it, except that it'll use sude, which will require you to accept keys for your localUser and your localRoot
            # remote_part2=(--target-host "${destination}" --build-host localhost)
            remote_part2=(--target-host "${destination}")
        fi
    fi
    if [ "$LOG_LEVEL" -ge 7 ]; then
        (
            set -x
            nixos-rebuild --use-remote-sudo "${mode}" --flake "${LOCAL_CONFIG_PATH}${remote_part1[*]}" "${remote_part2[@]}" "${rollback[@]}" "${offline[@]}" -L |& nom
        )
    else
        (
            set -x
            nixos-rebuild --use-remote-sudo "${mode}" --flake "${LOCAL_CONFIG_PATH}${remote_part1[*]}" "${remote_part2[@]}" "${rollback[@]}" "${offline[@]}" |& nom
        )
    fi
}

function list_generations() {
    dest="${1:-""}"
    .log $L_5NOTICE " > List Generations: ${dest}"
    if [ -n "$dest" ]; then
        ssh_ '( sudo nix-env --list-generations --profile /nix/var/nix/profiles/system )'
    else
        sudo nix-env --list-generations --profile /nix/var/nix/profiles/system
        # find /nix/var/nix/profiles/system*  -printf "%f\n"
        ##TODO: https://github.com/NixOS/nixpkgs/pull/105910
    fi
}
function show_difference_between_generations() {
    dest="${1:-""}"
    .log $L_5NOTICE " > Show Generation's Differences: ${dest}"
    if [ -n "$dest" ]; then
        ssh_ '( sudo nix profile diff-closures --profile /nix/var/nix/profiles/system )'
    else
        nix profile diff-closures --profile /nix/var/nix/profiles/system
    fi
}
function delete_previous_generations() {
    dest="${1:-""}"
    .log $L_5NOTICE " > nix-collect-garbage --delete-old --delete-older-than 1d: ${dest}"
    if [ -n "$dest" ]; then
        ssh_ '( sudo nix-collect-garbage --delete-old --delete-older-than 1d )'
    else
        ## You can no longer rollback after running this
        sudo nix-collect-garbage --delete-old --delete-older-than 1d
        ## They will still be listed in grub, but won't work.
        ## switch your configuration to remove them from grub
    fi
}
function delete_all_previous_generations() {
    dest="${1:-""}"
    .log $L_5NOTICE " > nix-collect-garbage --delete-old: ${dest}"
    if [ -n "$dest" ]; then
        ssh_ '( sudo nix-collect-garbage --delete-old )'
    else
        ## You can no longer rollback after running this
        sudo nix-collect-garbage --delete-old
        ## They will still be listed in grub, but won't work.
        ## switch your configuration to remove them from grub
    fi
}
1 Like

part2


nix_options=(
    --extra-experimental-features 'nix-command flakes'
    "--no-write-lock-file"
)
ssh_tty_param="-T"
# declare -a nix_copy_options
declare -a ssh_args

rollback=()
offline=()
function _main() {
    # uuid: (the single colon means the option has a required argument, double colon means optional https://www.bahmanm.com/2015/01/command-line-options-parse-with-getopt.html)
    # getopt -o vhu12c34s5r
    if ! OPTS=$(getopt -o vhtp --long debug,verbose,version,help,tty,ssh-port:,ssh-option:,config:,config-path:,dest:,destination:,root,ignore-unmount,reboot,shutdown,poweroff,rollback,offline -n 'parse-options' -- "$@"); then
        echo "Failed parsing options." >&2
        exit 1
    fi
    eval set -- "$OPTS"
    while true; do
        case "$1" in
            --debug)
                set_log_level $L_7DEBUG
                set -x
                shift
                ;;
            -v | --verbose)
                set_log_level $L_6INFO
                shift
                ;;
            --version)
                echo $VERSION
                exit 0
                ;;
            -h | --help)
                usage
                exit 0
                ;;
            -t | --tty)
                ssh_tty_param="-t"
                shift
                ;;
            -p | --ssh-port)
                ssh_args+=("-p" "$2")
                shift 2
                ;;
            --ssh-option)
                ssh_args+=("-o" "$2")
                shift 2
                ;;
            # --from)
            #     nix_copy_options+=("--from" "$2")
            #     shift
            #     ;;
            # --option)
            #     key=$2
            #     shift
            #     value=$2
            #     shift
            #     nix_options+=("--option" "$key" "$value")
            #     ;;
            --config)
                LOCAL_CONFIG_PATH=$2
                shift 2
                ;;
            --config-path)
                LOCAL_CONFIG_PATH=$2
                shift 2
                ;;
            --dest)
                ARG_DEST=$2
                shift 2
                ;;
            --destination)
                ARG_DEST=$2
                shift 2
                ;;
            --root)
                ARG_USE_ROOT_USERNAME=true
                shift
                ;;
            --ignore-unmount)
                ARG_IGNORE_UNMOUNT=true
                shift
                ;;
            --reboot)
                ARG_REBOOT_AFTER=true
                shift
                ;;
            --shutdown)
                ARG_SHUTDOWN_AFTER=true
                shift
                ;;
            --poweroff)
                ARG_SHUTDOWN_AFTER=true
                shift
                ;;
            --rollback)
                rollback=(--rollback)
                shift
                ;;
            --offline)
                offline=(--option substitute false)
                shift
                ;;
            --)
                shift
                break
                ;;
            *) break ;;
        esac
    done

    if [ "${1-}" == "ssh" ]; then
        shift
        exit_when_iso
        dest=$(convert_hostname_in_destination "$ARG_USE_ROOT_USERNAME" "$(validate_hostname_or_choose_with_installer_first_when_empty "${1-}")")
        ssh_tty_param="-t" # force interactive
        .log $L_5NOTICE " > $dest"
        # TODO: add general args option
        ssh_
    elif [ "${1-}" == "build-iso" ]; then
        shift
        exit_when_iso
        validateConfigPath
        .log $L_5NOTICE " > Build Iso"
        nix build "${LOCAL_CONFIG_PATH}#iso" |& nom
    elif [ "${1-}" == "build-image-rpi4" ]; then
        shift
        exit_when_iso
        validateConfigPath
        .log $L_5NOTICE " > Build Image rpi4"
        nix build "${LOCAL_CONFIG_PATH}#image-rpi4" |& nom
    elif [ "${1-}" == "burn-iso" ]; then
        shift
        exit_when_iso
        validateConfigPath
        .log $L_5NOTICE " > Burn Iso"
        local device_path
        device_path=$(get_device_path_by_serial "$1")
        # TODO: error when it's mounted already
        sudo dd bs=4M if="${LOCAL_CONFIG_PATH}/result/iso/${HOSTNAME_INSTALLER}.iso" of="${device_path}" status=progress oflag=sync
        sudo sync
    elif [ "${1-}" == "burn-image" ]; then
        shift
        exit_when_iso
        validateConfigPath
        .log $L_5NOTICE " > Burn Image"
        local device_path
        device_path=$(get_device_path_by_serial "$1")
        # zstdcat "${LOCAL_CONFIG_PATH}/result/sd-image/${HOSTNAME_IMAGE}.img.zst" | sudo dd bs=4M iflag=fullblock of="${device_path}" status=progress oflag=sync
        # TODO: error when it's mounted already
        sudo dd bs=4M if="${LOCAL_CONFIG_PATH}/result/sd-image/${HOSTNAME_IMAGE}.img" of="${device_path}" status=progress oflag=sync
        sudo sync
    elif [ "${1-}" == "is-uefi-mode" ]; then
        shift
        hostname=$(empty_when_localhost "$(validate_hostname_or_choose_with_installer_first_when_empty "${1-}")")
        dest=$(convert_hostname_in_destination "$ARG_USE_ROOT_USERNAME" "$hostname")
        check_uefi_mode "$dest"
    elif [ "${1-}" == "install" ]; then
        shift
        validateConfigPath
        hostname=$(empty_when_localhost "$(validate_hostname_or_choose_when_empty "${1-}")")
        dest="${ARG_DEST:-$DEFAULT_DESTINATION}"
        install "$hostname" "$dest"
        shutdown_or_reboot "$dest"
    elif [ "${1-}" == "install-anywhere" ]; then
        shift
        validateConfigPath
        .log $L_3ERROR "TODO: not yet implemented: install-anywhere using nixos-anywhere"
        # hostname=$(empty_when_localhost "$(validate_hostname_or_choose_when_empty "${1-}")")
        # TODO nixos-anywhere

    elif [ "${1-}" == "unmount" ]; then
        shift
        ARG_IGNORE_UNMOUNT=false
        dest="${ARG_DEST:-$DEFAULT_DESTINATION}"
        unmount_and_export_pool "$dest"
    elif [ "${1-}" == "restart-sway" ]; then
        shift
        exit_when_iso
        dest=$(convert_hostname_in_destination "$ARG_USE_ROOT_USERNAME" "$(validate_hostname_or_choose_with_localhost_first_when_empty "${1-}")")
        restart_sway "$dest"
    elif [ "${1-}" == "reboot" ]; then
        shift
        ARG_REBOOT_AFTER=true
        ARG_SHUTDOWN_AFTER=false
        hostname=$(empty_when_localhost "$(validate_hostname_or_choose_with_installer_first_when_empty "${1-}")")
        dest=$(convert_hostname_in_destination "$ARG_USE_ROOT_USERNAME" "$hostname")
        ssh_tty_param="-t" # force interactive
        shutdown_or_reboot "$dest"
    elif [[ ${1-} == "shutdown" ]] || [[ ${1-} == "poweroff" ]]; then
        shift
        ARG_SHUTDOWN_AFTER=true
        ARG_REBOOT_AFTER=false
        hostname=$(empty_when_localhost "$(validate_hostname_or_choose_with_installer_first_when_empty "${1-}")")
        dest=$(convert_hostname_in_destination "$ARG_USE_ROOT_USERNAME" "$hostname")
        ssh_tty_param="-t" # force interactive
        shutdown_or_reboot "$dest"
    elif [ "${1-}" == "update" ]; then
        shift
        exit_when_iso
        validateConfigPath
        flake_update
    elif [ "${1-}" == "build" ]; then
        shift
        exit_when_iso
        validateConfigPath
        hostname=$(empty_when_localhost "$(validate_hostname_or_choose_with_localhost_first_when_empty "${1-}")")
        dest=$(convert_hostname_in_destination "$ARG_USE_ROOT_USERNAME" "$hostname")
        rebuild "build" "$hostname" "$dest"
    elif [ "${1-}" == "build-vm" ]; then
        shift
        exit_when_iso
        validateConfigPath
        hostname=$(empty_when_localhost "$(validate_hostname_or_choose_with_localhost_first_when_empty "${1-}")")
        dest=$(convert_hostname_in_destination "$ARG_USE_ROOT_USERNAME" "$hostname")
        rebuild "build-vm" "$hostname" "$dest"
    elif [ "${1-}" == "build-vm-with-bootloader" ]; then
        shift
        exit_when_iso
        validateConfigPath
        hostname=$(empty_when_localhost "$(validate_hostname_or_choose_with_localhost_first_when_empty "${1-}")")
        dest=$(convert_hostname_in_destination "$ARG_USE_ROOT_USERNAME" "$hostname")
        rebuild "build-vm-with-bootloader" "$hostname" "$dest"
    elif [ "${1-}" == "test" ]; then
        shift
        exit_when_iso
        validateConfigPath
        hostname=$(empty_when_localhost "$(validate_hostname_or_choose_with_localhost_first_when_empty "${1-}")")
        dest=$(convert_hostname_in_destination "$ARG_USE_ROOT_USERNAME" "$hostname")
        rebuild "test" "$hostname" "$dest"
        shutdown_or_reboot "$dest"
    elif [ "${1-}" == "boot" ]; then
        shift
        exit_when_iso
        validateConfigPath
        hostname=$(empty_when_localhost "$(validate_hostname_or_choose_with_localhost_first_when_empty "${1-}")")
        dest=$(convert_hostname_in_destination "$ARG_USE_ROOT_USERNAME" "$hostname")
        rebuild "boot" "$hostname" "$dest"
        shutdown_or_reboot "$dest"
    elif [ "${1-}" == "switch" ]; then
        shift
        exit_when_iso
        validateConfigPath
        hostname=$(empty_when_localhost "$(validate_hostname_or_choose_with_localhost_first_when_empty "${1-}")")
        dest=$(convert_hostname_in_destination "$ARG_USE_ROOT_USERNAME" "$hostname")
        rebuild "switch" "$hostname" "$dest"
    elif [ "${1-}" == "list" ]; then
        shift
        exit_when_iso
        dest=$(convert_hostname_in_destination "$ARG_USE_ROOT_USERNAME" "$(validate_hostname_or_choose_with_localhost_first_when_empty "${1-}")")
        list_generations "$dest"
    elif [ "${1-}" == "compare-generations" ]; then
        shift
        exit_when_iso
        dest=$(convert_hostname_in_destination "$ARG_USE_ROOT_USERNAME" "$(validate_hostname_or_choose_with_localhost_first_when_empty "${1-}")")
        show_difference_between_generations "$dest"
    elif [ "${1-}" == "delete" ]; then
        shift
        exit_when_iso
        dest=$(convert_hostname_in_destination "$ARG_USE_ROOT_USERNAME" "$(validate_hostname_or_choose_with_localhost_first_when_empty "${1-}")")
        delete_previous_generations "$dest"
    elif [ "${1-}" == "delete-all" ]; then
        shift
        exit_when_iso
        dest=$(convert_hostname_in_destination "$ARG_USE_ROOT_USERNAME" "$(validate_hostname_or_choose_with_localhost_first_when_empty "${1-}")")
        delete_all_previous_generations "$dest"
    else
        usage
    fi
}

_main "${@}"

1 Like