{ pkgs, lib, modulesPath, inputs, ... }: let installScript = pkgs.writeShellScriptBin "nixos-install-host" '' set -euo pipefail BOLD='\033[1m' RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' header() { echo -e "\n''${BOLD}==> $1''${NC}"; } ok() { echo -e "''${GREEN}[ok]''${NC} $1"; } warn() { echo -e "''${YELLOW}[warn]''${NC} $1"; } die() { echo -e "''${RED}[error]''${NC} $1"; exit 1; } # ------------------------------------------------------------------ # # Network # # ------------------------------------------------------------------ # header "Network" echo "An internet connection is required to download packages during installation." echo "If you need Wi-Fi, connect now with: nmtui" echo "" read -p "Press Enter when the network is ready (or Ctrl-C to abort)..." # ------------------------------------------------------------------ # # Host selection # # ------------------------------------------------------------------ # header "NixOS Installer" echo "Evaluating flake..." HOSTS_JSON=$(nix eval "path:/etc/nixos-config#nixosConfigurations" \ --apply builtins.attrNames --json) \ || die "Failed to evaluate the flake at /etc/nixos-config." echo "Available hosts:" echo "$HOSTS_JSON" | ${pkgs.jq}/bin/jq -r '.[]' | grep -v installer | sed 's/^/ /' echo "" read -p "Host to install: " HOST [ -z "$HOST" ] && die "No host specified." # Verify the selected host has disko configured before touching anything. # local.notebook.disk is only defined for hosts that include notebook-disko.nix. echo "Checking that '$HOST' supports automated installation..." nix eval "path:/etc/nixos-config#nixosConfigurations.$HOST.config.local.notebook.disk" \ --raw > /dev/null 2>&1 \ || die "Host '$HOST' does not have a disko configuration. Only 'notebook' is supported." ok "Host '$HOST' is supported." # ------------------------------------------------------------------ # # Hardware detection # # ------------------------------------------------------------------ # header "Detecting hardware" echo "Running nixos-generate-config..." DETECTED_HW=$(nixos-generate-config --no-filesystems --show-hardware-config 2>/dev/null) \ || die "Failed to detect hardware. Is nixos-generate-config available?" ok "Hardware detected." # Build a writable copy of the embedded flake and inject the detected # hardware config so both disko and nixos-install use the right modules. echo "Preparing installation flake..." rm -rf /tmp/nixos-flake cp -rL /etc/nixos-config /tmp/nixos-flake echo "$DETECTED_HW" > /tmp/nixos-flake/modules/_hardware/notebook-hardware.nix ok "Hardware config written to flake." FLAKE="path:/tmp/nixos-flake" # ------------------------------------------------------------------ # # Disk selection # # ------------------------------------------------------------------ # header "Available disks" lsblk -d -o NAME,SIZE,MODEL --noheadings | grep -v loop echo "" CONFIGURED_DISK=$(nix eval "$FLAKE#nixosConfigurations.$HOST.config.local.notebook.disk" \ --raw 2>/dev/null || echo "/dev/nvme0n1") echo -e "Disk configured for this host: ''${BOLD}$CONFIGURED_DISK''${NC}" read -p "Target disk [$CONFIGURED_DISK]: " DISK_INPUT DISK="''${DISK_INPUT:-$CONFIGURED_DISK}" [ ! -b "$DISK" ] && die "$DISK is not a valid block device." if [ "$DISK" != "$CONFIGURED_DISK" ]; then warn "Disk $DISK differs from configured $CONFIGURED_DISK." warn "The disko config will use $DISK regardless, but update local.notebook.disk in" warn "modules/_hardware/notebook-disko.nix and commit it after installation." fi # ------------------------------------------------------------------ # # Age key # # ------------------------------------------------------------------ # header "Age key (secrets decryption)" KEYS_PATH="" # Search already-mounted media (standard auto-mount paths) for candidate in \ /run/media/nixos/*/keys.txt \ /run/media/*/keys.txt \ /run/media/*/*/keys.txt \ /tmp/keys.txt; do if [ -f "$candidate" ]; then KEYS_PATH="$candidate" ok "Found at $KEYS_PATH" break fi done # When booting via Ventoy the USB data partition (label "Ventoy") is not # auto-mounted in the live environment — only the ISO loop device is set up. # Detect it by label, mount read-only, copy keys.txt to /tmp, then unmount. if [ -z "$KEYS_PATH" ]; then VENTOY_DEV=$(${pkgs.util-linux}/bin/blkid -t LABEL="Ventoy" -o device 2>/dev/null | head -1 || true) if [ -n "$VENTOY_DEV" ]; then warn "keys.txt not found in mounted media. Trying Ventoy partition ($VENTOY_DEV)..." VENTOY_MNT=$(mktemp -d) if mount -o ro "$VENTOY_DEV" "$VENTOY_MNT" 2>/dev/null; then if [ -f "$VENTOY_MNT/keys.txt" ]; then cp "$VENTOY_MNT/keys.txt" /tmp/keys.txt chmod 600 /tmp/keys.txt KEYS_PATH="/tmp/keys.txt" ok "Found on Ventoy USB — copied to /tmp/keys.txt" else warn "Ventoy partition mounted but no keys.txt found at its root." echo "Contents of Ventoy root:" ls "$VENTOY_MNT" | sed 's/^/ /' fi umount "$VENTOY_MNT" 2>/dev/null || true rm -rf "$VENTOY_MNT" else warn "Could not mount Ventoy partition $VENTOY_DEV." fi fi fi if [ -z "$KEYS_PATH" ]; then warn "Age key not found automatically." echo "Options:" echo " 1. Place keys.txt at the root of the Ventoy USB (alongside the .iso)" echo " 2. Mount another USB and it will be found at /run/media/*" echo " 3. Enter the path manually below" read -p "Path to keys.txt: " KEYS_PATH fi [ ! -f "$KEYS_PATH" ] && die "Age key not found at $KEYS_PATH" # ------------------------------------------------------------------ # # Confirmation # # ------------------------------------------------------------------ # header "Confirmation" echo -e " Host : ''${BOLD}$HOST''${NC}" echo -e " Disk : ''${BOLD}$DISK''${NC} (ALL DATA WILL BE ERASED)" echo -e " Keys : ''${BOLD}$KEYS_PATH''${NC}" echo "" read -p "Type 'yes' to continue: " CONFIRM [ "$CONFIRM" != "yes" ] && { echo "Aborted."; exit 1; } # ------------------------------------------------------------------ # # Installation # # ------------------------------------------------------------------ # header "Partitioning and formatting" echo "(You will be prompted to set the LUKS passphrase.)" disko --mode destroy,format,mount --flake "$FLAKE#$HOST" header "Copying age key" mkdir -p /mnt/etc/sops/age install -m 600 "$KEYS_PATH" /mnt/etc/sops/age/keys.txt ok "Age key installed." header "Installing NixOS" nixos-install --root /mnt --flake "$FLAKE#$HOST" --no-root-passwd # ------------------------------------------------------------------ # # Post-install: save config to the new system # # ------------------------------------------------------------------ # header "Saving configuration" # Copy the flake (including the detected hardware config) to the user's # working directory on the installed system so they can commit it. mkdir -p /mnt/home/zoty/repos cp -r /tmp/nixos-flake /mnt/home/zoty/repos/nixos # UID 1000 is the first normal user, matching the zoty account. chown -R 1000:1000 /mnt/home/zoty/repos/nixos ok "Configuration saved to ~/repos/nixos on the installed system." echo "" ok "Installation complete! Remove the USB drive and reboot." echo "" echo "After the first boot, commit the detected hardware config:" echo " cd ~/repos/nixos" echo " git add modules/_hardware/notebook-hardware.nix" echo " git commit -m 'add notebook hardware config'" echo " git push" ''; in { imports = [ "${modulesPath}/installer/cd-dvd/installation-cd-minimal.nix" ]; # Ventoy presents the ISO as a virtual CDROM without the filesystem label # that NixOS stage-1 expects (nixos-minimal-*). Stage-1 cannot find the # loop device for nix-store.squashfs, causing "unable to read id index table". # copytoram loads the entire ISO into RAM before the squashfs mount, removing # the dependency on the Ventoy device remaining accessible during boot. # See: https://github.com/NixOS/nixpkgs/issues/245101 boot.kernelParams = [ "copytoram" ]; boot.zfs.forceImportRoot = false; services.getty.autologinUser = lib.mkForce "root"; nix.settings = { experimental-features = [ "nix-command" "flakes" ]; tarball-ttl = 0; }; # NetworkManager instead of wpa_supplicant for nmtui support networking.networkmanager.enable = true; networking.wireless.enable = lib.mkForce false; environment.systemPackages = [ installScript inputs.disko.packages.${pkgs.stdenv.hostPlatform.system}.default pkgs.git pkgs.age pkgs.sops pkgs.jq pkgs.neovim ]; # Embed the flake source so the install script can copy it at install time. # The script copies it to /tmp/nixos-flake (writable) and injects the # detected hardware config before running disko and nixos-install. environment.etc."nixos-config".source = ../.; documentation.enable = lib.mkForce false; documentation.nixos.enable = lib.mkForce false; system.stateVersion = "25.11"; }