11#! /usr/bin/env bash
22
3- # shellcheck disable=SC2034,SC2155,SC2015
4-
5- set -euo pipefail
3+ set -euxo pipefail
64shopt -s inherit_errexit 2> /dev/null || true
75
86readonly SCRIPT_NAME=" $( basename " $0 " ) "
97readonly REQUIRED_CMDS=(parted mkfs.vfat mkfs.btrfs btrfs nixos-generate-config nixos-install nixos-enter)
10- readonly RED=' \033[0;31m' GREEN=' \033[0;32m' YELLOW=' \033[0;33m' BLUE=' \033[0;34m' NC=' \033[0m'
118
12- # State
139DISK=" "
1410MODE=" "
1511BOOT_PART=" "
1612ROOT_PART=" "
1713
18- log () { echo -e " ${GREEN} [INFO]${NC} $* " >&2 ; }
19- warn () { echo -e " ${YELLOW} [WARN]${NC} $* " >&2 ; }
20- err () { echo -e " ${RED} [ERROR]${NC} $* " >&2 ; exit 1; }
21- step () { echo -e " ${BLUE} [STEP]${NC} $* " >&2 ; }
22-
2314cleanup () {
2415 local code=$?
25- [[ $code -ne 0 ]] && warn " Script exited with code $code "
26- log " Cleaning up mounts & swap..."
2716 swapoff /mnt/var/swapfile 2> /dev/null || true
2817 umount -R /mnt 2> /dev/null || true
29-
30- if [[ $code -eq 0 ]]; then
31- log " [*] Cleanup complete."
32- else
33- warn " [!] Cleanup finished (verify mounts manually if needed)."
34- fi
18+ [[ $code -ne 0 ]] && echo " Script exited with code $code . Verify mounts manually if needed." >&2
3519}
3620trap cleanup EXIT
3721
3822usage () {
39- echo -e " ${RED} Usage:${NC} $SCRIPT_NAME <BLOCK_DEVICE> <install|rescue>"
40- echo -e " BLOCK_DEVICE Target disk (e.g., /dev/nvme0n1, /dev/sda)"
41- echo -e " MODE 'install' for fresh setup, 'rescue' to chroot"
42- echo -e " ${YELLOW} No defaults. Both arguments are strictly required.${NC} "
23+ echo " Usage: $SCRIPT_NAME <BLOCK_DEVICE> <install|rescue>"
24+ echo " BLOCK_DEVICE Target disk (e.g., /dev/nvme0n1, /dev/sda)"
25+ echo " MODE 'install' for fresh setup, 'rescue' to chroot"
4326 exit 1
4427}
4528
4629check_dependencies () {
4730 for cmd in " ${REQUIRED_CMDS[@]} " ; do
48- command -v " $cmd " & > /dev/null || err " Missing required command: $cmd "
31+ command -v " $cmd " & > /dev/null || { echo " Missing required command: $cmd " >&2 ; exit 1 ; }
4932 done
5033}
5134
5235check_root () {
53- [[ $EUID -ne 0 ]] && err " This script must be run as root."
36+ [[ $EUID -ne 0 ]] && { echo " This script must be run as root." >&2 ; exit 1; }
37+ }
38+
39+ set_partition_names () {
40+ local disk=" $1 "
41+ if [[ " $disk " =~ (nvme| mmcblk)[0-9] ]]; then
42+ BOOT_PART=" ${disk} p1"
43+ ROOT_PART=" ${disk} p2"
44+ else
45+ BOOT_PART=" ${disk} 1"
46+ ROOT_PART=" ${disk} 2"
47+ fi
5448}
5549
5650parse_args () {
5751 [[ $# -lt 2 ]] && usage
5852 DISK=" $1 "
5953 MODE=" $2 "
6054
61- [[ ! -b " $DISK " ]] && err " '$DISK ' is not a valid block device."
62- [[ " $DISK " =~ [0-9]$ ]] && err " Specify the base disk (e.g., /dev/sda), not a partition."
63- [[ " $MODE " != " install" && " $MODE " != " rescue" ]] && err " Mode must be 'install' or 'rescue'."
55+ [[ ! -b " $DISK " ]] && { echo " '$DISK ' is not a valid block device." >&2 ; exit 1; }
56+
57+ if [[ " $DISK " =~ nvme[0-9]+n[0-9]+p[0-9]+$ ]] || \
58+ [[ " $DISK " =~ mmcblk[0-9]+p[0-9]+$ ]] || \
59+ [[ " $DISK " =~ [a-z][0-9]+$ && ! " $DISK " =~ nvme && ! " $DISK " =~ mmcblk ]]; then
60+ echo " Specify the base disk (e.g., /dev/nvme0n1, /dev/sda), not a partition." >&2
61+ exit 1
62+ fi
63+
64+ [[ " $MODE " != " install" && " $MODE " != " rescue" ]] && { echo " Mode must be 'install' or 'rescue'." >&2 ; exit 1; }
65+
66+ set_partition_names " $DISK "
6467}
6568
6669validate () {
67- # Prevent targeting the booted disk
68- local root_src
70+ local root_src root_disk
6971 root_src=$( findmnt -n -o SOURCE / 2> /dev/null || echo " " )
7072 if [[ -n " $root_src " ]]; then
71- local root_disk
7273 root_disk=$( lsblk -ndo PKNAME " $root_src " 2> /dev/null || echo " " )
73- if [[ " /dev/$root_disk " == " $DISK " ]]; then
74- err " Refusing to operate on the currently booted disk: $DISK "
75- fi
74+ [[ " /dev/$root_disk " == " $DISK " ]] && { echo " Refusing to operate on the currently booted disk: $DISK " >&2 ; exit 1; }
7675 fi
7776
78- # Check for active mounts
7977 if lsblk -n -o MOUNTPOINTS " $DISK " | grep -qE ' \S' ; then
80- err " Disk '$DISK ' or its partitions are mounted. Unmount them first."
78+ echo " Disk '$DISK ' or its partitions are mounted. Unmount them first." >&2
79+ exit 1
8180 fi
8281
83- BOOT_PART=" ${DISK} p1"
84- ROOT_PART=" ${DISK} p2"
85-
86- # Rescue mode expects existing partitions
8782 if [[ " $MODE " == " rescue" ]]; then
88- [[ ! -b " $BOOT_PART " || ! -b " $ROOT_PART " ]] && err " Rescue mode requires existing: $BOOT_PART and $ROOT_PART "
83+ [[ ! -b " $BOOT_PART " || ! -b " $ROOT_PART " ]] && { echo " Rescue mode requires existing partitions : $BOOT_PART and $ROOT_PART " >&2 ; exit 1 ; }
8984 else
90- # 4. Install mode warns about existing data
91- if lsblk -n " $DISK " | grep -q " part" ; then
92- warn " Disk '$DISK ' contains partitions. ALL DATA WILL BE DESTROYED."
93- fi
85+ lsblk -n " $DISK " | grep -q " part" && echo " WARNING: '$DISK ' contains partitions. ALL DATA WILL BE DESTROYED." >&2
9486 fi
9587
96- # Explicit confirmation (NO --force bypass)
9788 local prompt
9889 if [[ " $MODE " == " install" ]]; then
99- prompt=" [!] This will WIPE '$DISK ' and install NixOS. Type 'yes' to continue: "
90+ prompt=" This will WIPE '$DISK ' and install NixOS. Type 'yes' to continue: "
10091 else
101- prompt=" [!] Proceed with '$MODE ' on '$DISK '? Type 'yes' to continue: "
92+ prompt=" Proceed with '$MODE ' on '$DISK '? Type 'yes' to continue: "
10293 fi
10394
104- read -r -p " $( echo -e " ${RED} $ prompt${NC} " ) " confirm
105- [[ " $confirm " != " yes" ]] && { log " Aborted by user ." ; exit 0; }
95+ read -r -p " $prompt " confirm
96+ [[ " $confirm " != " yes" ]] && { echo " Aborted." ; exit 0; }
10697}
10798
10899partition_and_format () {
109- step " Partitioning $DISK ..."
100+ echo " Partitioning $DISK ..."
110101 parted " $DISK " --script -- \
111102 mklabel gpt \
112103 mkpart ESP fat32 1MiB 513MiB \
113104 set 1 esp on \
114- mkpart primary 513MiB 100% || err " Partitioning failed"
105+ mkpart primary 513MiB 100% || { echo " Partitioning failed." >&2 ; exit 1; }
106+
107+ partprobe " $DISK " 2> /dev/null || udevadm settle
115108
116- step " Formatting partitions..."
117- mkfs.vfat -F32 -n " NIX-BOOT" " $BOOT_PART " || err " Boot format failed"
118- mkfs.btrfs -f -L " nix-root" " $ROOT_PART " || err " Root format failed"
109+ echo " Formatting partitions..."
110+ mkfs.vfat -F32 -n " NIX-BOOT" " $BOOT_PART " || { echo " Boot format failed. " >&2 ; exit 1 ; }
111+ mkfs.btrfs -f -L " nix-root" " $ROOT_PART " || { echo " Root format failed. " >&2 ; exit 1 ; }
119112}
120113
121- mount_system () {
122- step " Creating Btrfs subvolumes..."
114+ create_subvolumes () {
115+ echo " Creating Btrfs subvolumes..."
123116 mount " $ROOT_PART " /mnt
124117 btrfs subvolume create /mnt/@
125118 btrfs subvolume create /mnt/@home
126119 btrfs subvolume create /mnt/@nix
127120 btrfs subvolume create /mnt/@var
128121 umount /mnt
122+ }
129123
130- step " Mounting filesystems..."
131- mount -o subvol=@,noatime,compress=zstd:3 " $ROOT_PART " /mnt
124+ mount_subvolumes () {
125+ echo " Mounting filesystems..."
126+ mount -o subvol=@ " $ROOT_PART " /mnt
132127 mkdir -p /mnt/{home,nix,var,boot}
133- mount -o subvol=@home,noatime,compress=zstd:3 " $ROOT_PART " /mnt/home
134- mount -o subvol=@nix,noatime,compress=zstd:3 " $ROOT_PART " /mnt/nix
135- mount -o subvol=@var,noatime,compress=zstd:3 " $ROOT_PART " /mnt/var
128+ mount -o subvol=@home " $ROOT_PART " /mnt/home
129+ mount -o subvol=@nix " $ROOT_PART " /mnt/nix
130+ mount -o subvol=@var " $ROOT_PART " /mnt/var
136131 mount " $BOOT_PART " /mnt/boot
137132}
138133
139134setup_swap () {
140- step " Creating swapfile..."
141- btrfs filesystem mkswapfile --size 7G /mnt/var/swapfile || err " Swapfile creation failed"
135+ echo " Creating swapfile..."
136+ if btrfs filesystem mkswapfile --size 7G /mnt/var/swapfile 2> /dev/null; then
137+ :
138+ else
139+ echo " btrfs mkswapfile unavailable, falling back to manual method." >&2
140+ truncate -s 0 /mnt/var/swapfile
141+ chattr +C /mnt/var/swapfile 2> /dev/null || true
142+ fallocate -l 7G /mnt/var/swapfile || dd if=/dev/zero of=/mnt/var/swapfile bs=1M count=7168 status=progress
143+ mkswap /mnt/var/swapfile || { echo " mkswap failed." >&2 ; exit 1; }
144+ fi
142145 chmod 600 /mnt/var/swapfile
143- swapon /mnt/var/swapfile || warn " Swap activation failed ( continuing without swap) "
146+ swapon /mnt/var/swapfile || echo " Swap activation failed, continuing without swap. " >&2
144147}
145148
146149run_install () {
147150 partition_and_format
148- mount_system
151+ create_subvolumes
152+ mount_subvolumes
149153 setup_swap
150154
151- step " Generating NixOS configuration..."
152- nixos-generate-config --root /mnt || err " Config generation failed"
155+ echo " Generating NixOS configuration..."
156+ nixos-generate-config --root /mnt || { echo " Config generation failed. " >&2 ; exit 1 ; }
153157
154- step " Installing NixOS..."
158+ echo " Installing NixOS..."
155159 if [[ -f flake.nix ]]; then
156- nixos-install --flake " .#nixos" --no-root-passwd || err " Flake installation failed"
160+ local flake_host
161+ flake_host=$( grep -oP ' (?<=hostName = ").*(?=")' /mnt/etc/nixos/configuration.nix 2> /dev/null | head -1 || echo " " )
162+ [[ -z " $flake_host " ]] && { echo " Could not detect hostname, defaulting flake target to 'nixos'." >&2 ; flake_host=" nixos" ; }
163+ nixos-install --flake " .#${flake_host} " --no-root-passwd || { echo " Flake installation failed." >&2 ; exit 1; }
157164 else
158- nixos-install --no-root-passwd || err " Installation failed"
165+ nixos-install --no-root-passwd || { echo " Installation failed. " >&2 ; exit 1 ; }
159166 fi
160- log " [*] Installation complete. Reboot when ready."
167+ echo " Installation complete. Reboot when ready."
161168}
162169
163170run_rescue () {
164- mount_system
165- if [[ -f /mnt/var/swapfile ]]; then
166- swapon /mnt/var/swapfile 2> /dev/null || warn " Swapfile found but failed to activate"
167- fi
168-
169- log " Launching nixos-enter. Type 'exit' to return to host."
170- nixos-enter || err " nixos-enter failed"
171+ mount_subvolumes
172+ [[ -f /mnt/var/swapfile ]] && { swapon /mnt/var/swapfile 2> /dev/null || echo " Swapfile found but failed to activate." >&2 ; }
173+ echo " Launching nixos-enter. Type 'exit' to return to host."
174+ nixos-enter || { echo " nixos-enter failed." >&2 ; exit 1; }
171175}
172176
173177main () {
@@ -178,7 +182,7 @@ main() {
178182
179183 case " $MODE " in
180184 install) run_install ;;
181- rescue) run_rescue ;;
185+ rescue) run_rescue ;;
182186 esac
183187}
184188
0 commit comments