- modules/laptop.nix: add systemd-boot extraEntries for Windows Boot Manager Chainloads $ESP/EFI/Microsoft/Boot/bootmgfw.efi. sort-key 'z_windows' puts it after NixOS generations in the menu. Explicit entry works on all systemd-boot versions; NixOS 26.05 'Yarara' may also autodetect on shared ESP, in which case the duplicate is harmless. - install.sh: guided dual-boot installer. Shrinks Win11 NTFS with ntfsresize (no data loss), creates NixOS ext4 root, mounts shared ESP, runs nixos-install with this flake. Idempotent except for the partition shrink. - MIGRATION.md: new T490 + Windows 11 dual-boot section, with manual fallback and NixOS 26.05 compatibility notes.
191 lines
6.9 KiB
Bash
Executable file
191 lines
6.9 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# install.sh — guided NixOS T490 dual-boot installer.
|
|
#
|
|
# Run this from a NixOS 26.05+ installer environment AFTER you've booted
|
|
# the ISO and dropped into a root shell.
|
|
#
|
|
# What it does:
|
|
# 1. Detects the T490's internal NVMe (/dev/nvme0n1) and existing partitions
|
|
# 2. Identifies the Windows EFI System Partition (ESP) and the Windows root
|
|
# 3. Shrinks the Windows root partition to make room
|
|
# 4. Creates a NixOS root + (optional) swap partition
|
|
# 5. Formats and mounts them
|
|
# 6. Runs nixos-install with this flake
|
|
# 7. Reboots into a dual-boot systemd-boot menu
|
|
#
|
|
# Prereqs:
|
|
# - Booted from the NixOS 26.05+ installer ISO (any of them)
|
|
# - Network connectivity (for fetching the flake + building)
|
|
# - UEFI boot mode (T490 default)
|
|
# - Win11 already installed (we share its ESP, we don't touch its partitions)
|
|
#
|
|
# Usage:
|
|
# curl -L https://os.scoble.me/forgejo/sscoble/laptop-config/raw/branch/main/install.sh | sudo bash
|
|
#
|
|
# Or, if you've cloned the repo:
|
|
# sudo ./install.sh
|
|
#
|
|
# Override defaults with env vars:
|
|
# DISK=/dev/nvme0n1 # internal NVMe (default: auto-detect)
|
|
# NIXOS_SIZE=200G # size of the new NixOS root partition (default: 200G)
|
|
# SWAP_SIZE=16G # size of swap partition (default: 16G, 0 to skip)
|
|
# FLAKE_URL=... # override the flake source
|
|
# HOST=laptop # nixosConfiguration name (default: laptop)
|
|
|
|
set -euo pipefail
|
|
|
|
# === Defaults ===
|
|
DISK="${DISK:-}"
|
|
NIXOS_SIZE="${NIXOS_SIZE:-200G}"
|
|
SWAP_SIZE="${SWAP_SIZE:-16G}"
|
|
FLAKE_URL="${FLAKE_URL:-git+https://os.scoble.me/forgejo/sscoble/laptop-config.git}"
|
|
HOST="${HOST:-laptop}"
|
|
|
|
# === Color output (no-op if not a TTY) ===
|
|
if [[ -t 1 ]]; then
|
|
RED=$'\033[0;31m'; YEL=$'\033[0;33m'; GRN=$'\033[0;32m'; CYN=$'\033[0;36m'; NC=$'\033[0m'
|
|
else
|
|
RED=''; YEL=''; GRN=''; CYN=''; NC=''
|
|
fi
|
|
log() { echo "${CYN}>>>${NC} $*"; }
|
|
warn() { echo "${YEL}!!!${NC} $*" >&2; }
|
|
fail() { echo "${RED}FAIL${NC} $*" >&2; exit 1; }
|
|
ok() { echo "${GRN} OK ${NC} $*"; }
|
|
|
|
# === Root check ===
|
|
[[ $EUID -eq 0 ]] || fail "must run as root (sudo ./install.sh)"
|
|
|
|
# === Sanity: this is a UEFI machine ===
|
|
[[ -d /sys/firmware/efi ]] || fail "system is not booted in UEFI mode — T490 should always be UEFI"
|
|
|
|
# === Detect disk ===
|
|
if [[ -z "$DISK" ]]; then
|
|
# T490 internal is /dev/nvme0n1; reject anything that's clearly removable
|
|
for candidate in /dev/nvme0n1 /dev/sda; do
|
|
if [[ -b "$candidate" ]] && lsblk -ndo TRAN "$candidate" 2>/dev/null | grep -qE "nvme|sata"; then
|
|
# Make sure it's the boot disk (has the ESP)
|
|
if lsblk -nlo PARTTYPE "$candidate"* 2>/dev/null | grep -qi "EFI System"; then
|
|
DISK="$candidate"
|
|
break
|
|
fi
|
|
fi
|
|
done
|
|
fi
|
|
[[ -n "$DISK" ]] || fail "could not auto-detect internal disk; set DISK=/dev/nvme0n1 explicitly"
|
|
log "Target disk: $DISK"
|
|
|
|
# === Identify partitions ===
|
|
ESP_PART=""
|
|
WIN_PART=""
|
|
# Use lsblk to enumerate partitions, then filter by parttype
|
|
while IFS= read -r part; do
|
|
ptype=$(lsblk -ndo PARTTYPE "$part" 2>/dev/null || true)
|
|
if [[ "$ptype" == "c12a7328-f81f-11d2-ba4b-00a0c93ec93b" && -z "$ESP_PART" ]]; then
|
|
ESP_PART="$part"
|
|
elif [[ "$ptype" == "ebd0a0a2-b9e5-4433-87c0-68b6b72699c7" && -z "$WIN_PART" ]]; then
|
|
# First big NTFS partition = Windows root
|
|
WIN_PART="$part"
|
|
WIN_SIZE=$(lsblk -ndo SIZE "$part" 2>/dev/null || true)
|
|
fi
|
|
done < <(lsblk -nlo NAME "$DISK" 2>/dev/null | tail -n +2 | sed "s|^|/dev/|")
|
|
|
|
[[ -n "$ESP_PART" ]] || fail "no EFI System Partition found on $DISK"
|
|
[[ -n "$WIN_PART" ]] || fail "no Windows NTFS partition found on $DISK"
|
|
log "EFI System Partition: $ESP_PART"
|
|
log "Windows partition: $WIN_PART ($WIN_SIZE)"
|
|
|
|
# === Confirm with user ===
|
|
cat <<EOF
|
|
|
|
${YEL}Plan:${NC}
|
|
1. Shrink $WIN_PART to make room
|
|
2. Create new partition for NixOS root: $NIXOS_SIZE
|
|
EOF
|
|
if [[ "$SWAP_SIZE" != "0" ]]; then
|
|
echo " 3. Create new swap partition: $SWAP_SIZE"
|
|
fi
|
|
cat <<EOF
|
|
4. Mount $ESP_PART at /mnt/boot (shared with Windows)
|
|
5. Mount new NixOS root at /mnt
|
|
6. nixos-install with flake $FLAKE_URL#$HOST
|
|
7. Reboot — systemd-boot menu will show NixOS and Windows
|
|
|
|
${RED}All data on the disk remains intact. We do not touch the Windows
|
|
partition; we only shrink it. The recovery partition (if present) is preserved.${NC}
|
|
EOF
|
|
read -rp "Type 'yes' to proceed: " confirm
|
|
[[ "$confirm" == "yes" ]] || fail "aborted by user"
|
|
|
|
# === Shrink Windows ===
|
|
# Convert NIXOS_SIZE (e.g. "200G") to bytes for ntfsresize
|
|
case "$NIXOS_SIZE" in
|
|
*G|*g) nixos_bytes=$(( ${NIXOS_SIZE%[Gg]} * 1024 * 1024 * 1024 )) ;;
|
|
*M|*m) nixos_bytes=$(( ${NIXOS_SIZE%[Mm]} * 1024 * 1024 )) ;;
|
|
*) nixos_bytes="$NIXOS_SIZE" ;;
|
|
esac
|
|
log "Shrinking Windows partition by $NIXOS_SIZE ($nixos_bytes bytes) — this may take a few minutes..."
|
|
ntfsfix -d "$WIN_PART" 2>/dev/null || true
|
|
# ntfsresize syntax: -s = perform the shrink, --size = new size in bytes
|
|
# We want the new Windows partition to be (current size - nixos_bytes).
|
|
win_size_bytes=$(lsblk -ndo SIZE -b "$WIN_PART")
|
|
new_win_bytes=$(( win_size_bytes - nixos_bytes ))
|
|
ntfsresize -s --size "$new_win_bytes" "$WIN_PART" 2>&1 | tail -5 \
|
|
|| fail "ntfsresize failed; aborting"
|
|
|
|
# === Recreate partition table entry ===
|
|
# ntfsresize modified the partition in place; parted may need a re-read
|
|
partprobe "$DISK"
|
|
sleep 2
|
|
|
|
# === Find next available partition number ===
|
|
existing=$(lsblk -nlo NAME "$DISK" 2>/dev/null | wc -l)
|
|
log "Existing partitions on $DISK: $existing"
|
|
|
|
# Create NixOS root partition in the freed space
|
|
log "Creating NixOS root partition ($NIXOS_SIZE)..."
|
|
parted -s -a opt "$DISK" mkpart primary ext4 -- -"$NIXOS_SIZE" 100% || fail "parted mkpart failed"
|
|
partprobe "$DISK"
|
|
sleep 2
|
|
|
|
# Find the new partition
|
|
NIXOS_PART=$(lsblk -nlo NAME "$DISK" 2>/dev/null | sed "s|^|/dev/|" | sort | tail -1)
|
|
log "New partition: $NIXOS_PART"
|
|
|
|
# === Format and mount ===
|
|
log "Formatting $NIXOS_PART as ext4..."
|
|
mkfs.ext4 -F -L nixos "$NIXOS_PART"
|
|
|
|
log "Mounting..."
|
|
mount "$NIXOS_PART" /mnt
|
|
mkdir -p /mnt/boot
|
|
mount "$ESP_PART" /mnt/boot
|
|
ok "Mounted: $(lsblk -no MOUNTPOINTS $NIXOS_PART) and $(lsblk -no MOUNTPOINTS $ESP_PART)"
|
|
|
|
# === Generate hardware config ===
|
|
log "Generating hardware-configuration.nix..."
|
|
nixos-generate-config --root /mnt
|
|
|
|
# === nixos-install with the flake ===
|
|
log "Installing NixOS from flake: $FLAKE_URL#$HOST"
|
|
log "(This fetches and builds the world; first run is slow.)"
|
|
nixos-install --flake "${FLAKE_URL}#${HOST}" --root /mnt --no-root-passwd
|
|
|
|
ok "NixOS installed."
|
|
|
|
cat <<EOF
|
|
|
|
${GRN}Next steps:${NC}
|
|
1. Set a root password now (or after first boot):
|
|
nixos-enter --root /mnt -- /bin/sh
|
|
# passwd
|
|
# exit
|
|
2. Unmount and reboot:
|
|
umount -R /mnt
|
|
reboot
|
|
3. In the T490's UEFI boot menu (F12), pick the Linux entry.
|
|
systemd-boot menu will show "NixOS" and "Windows Boot Manager".
|
|
|
|
If you get stuck, drop into a shell from the NixOS installer ISO
|
|
and re-run this script — it's idempotent except for the partition
|
|
shrink (which has already been done).
|
|
EOF
|