|
3 | 3 | set -euxo pipefail |
4 | 4 | shopt -s inherit_errexit 2>/dev/null || true |
5 | 5 |
|
6 | | -readonly SCRIPT_NAME="$(basename "$0")" |
7 | | -readonly REQUIRED_CMDS=(parted mkfs.vfat mkfs.btrfs btrfs nixos-generate-config nixos-install nixos-enter) |
8 | | - |
9 | | -DISK="" |
10 | | -MODE="" |
| 6 | +DISK="$1" |
| 7 | +MODE="$2" |
11 | 8 | BOOT_PART="" |
12 | 9 | ROOT_PART="" |
| 10 | +MOUNTED=0 |
13 | 11 |
|
14 | 12 | cleanup() { |
15 | | - local code=$? |
| 13 | + [[ $MOUNTED -eq 1 ]] || return 0 |
16 | 14 | swapoff /mnt/var/swapfile 2>/dev/null || true |
17 | 15 | umount -R /mnt 2>/dev/null || true |
18 | | - [[ $code -ne 0 ]] && echo "Script exited with code $code. Verify mounts manually if needed." >&2 |
19 | 16 | } |
20 | 17 | trap cleanup EXIT |
21 | 18 |
|
22 | | -usage() { |
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" |
26 | | - exit 1 |
27 | | -} |
28 | | - |
29 | | -check_dependencies() { |
30 | | - for cmd in "${REQUIRED_CMDS[@]}"; do |
31 | | - command -v "$cmd" &>/dev/null || { echo "Missing required command: $cmd" >&2; exit 1; } |
32 | | - done |
33 | | -} |
34 | | - |
35 | | -check_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 |
48 | | -} |
| 19 | +[[ $EUID -ne 0 ]] && { echo "must be root" >&2; exit 1; } |
| 20 | +[[ $# -ne 2 ]] && { echo "usage: $0 <disk> <install|rescue>" >&2; exit 1; } |
| 21 | +[[ ! -b "$DISK" ]] && { echo "not a block device: $DISK" >&2; exit 1; } |
| 22 | +[[ "$MODE" != "install" && "$MODE" != "rescue" ]] && { echo "mode must be install or rescue" >&2; exit 1; } |
49 | 23 |
|
50 | | -parse_args() { |
51 | | - [[ $# -lt 2 ]] && usage |
52 | | - DISK="$1" |
53 | | - MODE="$2" |
| 24 | +if [[ "$DISK" =~ (nvme|mmcblk)[0-9] ]]; then |
| 25 | + BOOT_PART="${DISK}p1" |
| 26 | + ROOT_PART="${DISK}p2" |
| 27 | +else |
| 28 | + BOOT_PART="${DISK}1" |
| 29 | + ROOT_PART="${DISK}2" |
| 30 | +fi |
54 | 31 |
|
55 | | - [[ ! -b "$DISK" ]] && { echo "'$DISK' is not a valid block device." >&2; exit 1; } |
| 32 | +# safety checks |
| 33 | +root_disk=$(lsblk -ndo PKNAME "$(findmnt -n -o SOURCE /)" 2>/dev/null || true) |
| 34 | +[[ "/dev/$root_disk" == "$DISK" ]] && { echo "refusing to operate on booted disk" >&2; exit 1; } |
| 35 | +lsblk -n -o MOUNTPOINTS "$DISK" | grep -qE '\S' && { echo "disk has active mounts" >&2; exit 1; } |
56 | 36 |
|
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 |
| 37 | +if [[ "$MODE" == "rescue" ]]; then |
| 38 | + [[ ! -b "$BOOT_PART" || ! -b "$ROOT_PART" ]] && { echo "rescue requires existing partitions" >&2; exit 1; } |
| 39 | +fi |
63 | 40 |
|
64 | | - [[ "$MODE" != "install" && "$MODE" != "rescue" ]] && { echo "Mode must be 'install' or 'rescue'." >&2; exit 1; } |
| 41 | +read -r -p "wipe $DISK and $MODE? type yes: " confirm |
| 42 | +[[ "$confirm" != "yes" ]] && exit 0 |
65 | 43 |
|
66 | | - set_partition_names "$DISK" |
| 44 | +mount_subvolumes() { |
| 45 | + mount -o subvol=@ "$ROOT_PART" /mnt |
| 46 | + MOUNTED=1 |
| 47 | + mkdir -p /mnt/{home,nix,var,boot} |
| 48 | + mount -o subvol=@home "$ROOT_PART" /mnt/home |
| 49 | + mount -o subvol=@nix "$ROOT_PART" /mnt/nix |
| 50 | + mount -o subvol=@var "$ROOT_PART" /mnt/var |
| 51 | + mount "$BOOT_PART" /mnt/boot |
67 | 52 | } |
68 | 53 |
|
69 | | -validate() { |
70 | | - local root_src root_disk |
71 | | - root_src=$(findmnt -n -o SOURCE / 2>/dev/null || echo "") |
72 | | - if [[ -n "$root_src" ]]; then |
73 | | - root_disk=$(lsblk -ndo PKNAME "$root_src" 2>/dev/null || echo "") |
74 | | - [[ "/dev/$root_disk" == "$DISK" ]] && { echo "Refusing to operate on the currently booted disk: $DISK" >&2; exit 1; } |
75 | | - fi |
76 | | - |
77 | | - if lsblk -n -o MOUNTPOINTS "$DISK" | grep -qE '\S'; then |
78 | | - echo "Disk '$DISK' or its partitions are mounted. Unmount them first." >&2 |
79 | | - exit 1 |
80 | | - fi |
81 | | - |
82 | | - if [[ "$MODE" == "rescue" ]]; then |
83 | | - [[ ! -b "$BOOT_PART" || ! -b "$ROOT_PART" ]] && { echo "Rescue mode requires existing partitions: $BOOT_PART and $ROOT_PART" >&2; exit 1; } |
84 | | - else |
85 | | - lsblk -n "$DISK" | grep -q "part" && echo "WARNING: '$DISK' contains partitions. ALL DATA WILL BE DESTROYED." >&2 |
86 | | - fi |
87 | | - |
88 | | - local prompt |
89 | | - if [[ "$MODE" == "install" ]]; then |
90 | | - prompt="This will WIPE '$DISK' and install NixOS. Type 'yes' to continue: " |
91 | | - else |
92 | | - prompt="Proceed with '$MODE' on '$DISK'? Type 'yes' to continue: " |
93 | | - fi |
94 | | - |
95 | | - read -r -p "$prompt" confirm |
96 | | - [[ "$confirm" != "yes" ]] && { echo "Aborted."; exit 0; } |
97 | | -} |
| 54 | +if [[ "$MODE" == "install" ]]; then |
| 55 | + for cmd in parted mkfs.vfat mkfs.btrfs btrfs nixos-generate-config nixos-install; do |
| 56 | + command -v "$cmd" &>/dev/null || { echo "missing: $cmd" >&2; exit 1; } |
| 57 | + done |
98 | 58 |
|
99 | | -partition_and_format() { |
100 | | - echo "Partitioning $DISK..." |
101 | 59 | parted "$DISK" --script -- \ |
102 | 60 | mklabel gpt \ |
103 | 61 | mkpart ESP fat32 1MiB 513MiB \ |
104 | 62 | set 1 esp on \ |
105 | | - mkpart primary 513MiB 100% || { echo "Partitioning failed." >&2; exit 1; } |
| 63 | + mkpart primary 513MiB 100% |
106 | 64 |
|
107 | | - partprobe "$DISK" 2>/dev/null || udevadm settle |
| 65 | + partprobe "$DISK" 2>/dev/null || true |
| 66 | + udevadm settle |
108 | 67 |
|
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; } |
112 | | -} |
| 68 | + mkfs.vfat -F32 -n "NIX-BOOT" "$BOOT_PART" |
| 69 | + mkfs.btrfs -f -L "nix-root" "$ROOT_PART" |
113 | 70 |
|
114 | | -create_subvolumes() { |
115 | | - echo "Creating Btrfs subvolumes..." |
116 | 71 | mount "$ROOT_PART" /mnt |
117 | 72 | btrfs subvolume create /mnt/@ |
118 | 73 | btrfs subvolume create /mnt/@home |
119 | 74 | btrfs subvolume create /mnt/@nix |
120 | 75 | btrfs subvolume create /mnt/@var |
121 | 76 | umount /mnt |
122 | | -} |
123 | 77 |
|
124 | | -mount_subvolumes() { |
125 | | - echo "Mounting filesystems..." |
126 | | - mount -o subvol=@ "$ROOT_PART" /mnt |
127 | | - mkdir -p /mnt/{home,nix,var,boot} |
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 |
131 | | - mount "$BOOT_PART" /mnt/boot |
132 | | -} |
| 78 | + mount_subvolumes |
133 | 79 |
|
134 | | -setup_swap() { |
135 | | - echo "Creating swapfile..." |
136 | 80 | if btrfs filesystem mkswapfile --size 7G /mnt/var/swapfile 2>/dev/null; then |
137 | 81 | : |
138 | 82 | else |
139 | | - echo "btrfs mkswapfile unavailable, falling back to manual method." >&2 |
140 | 83 | truncate -s 0 /mnt/var/swapfile |
141 | 84 | 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; } |
| 85 | + dd if=/dev/zero of=/mnt/var/swapfile bs=1M count=7168 status=progress |
| 86 | + mkswap /mnt/var/swapfile |
144 | 87 | fi |
145 | 88 | chmod 600 /mnt/var/swapfile |
146 | | - swapon /mnt/var/swapfile || echo "Swap activation failed, continuing without swap." >&2 |
147 | | -} |
| 89 | + swapon /mnt/var/swapfile || true |
148 | 90 |
|
149 | | -run_install() { |
150 | | - partition_and_format |
151 | | - create_subvolumes |
152 | | - mount_subvolumes |
153 | | - setup_swap |
| 91 | + nixos-generate-config --root /mnt |
154 | 92 |
|
155 | | - echo "Generating NixOS configuration..." |
156 | | - nixos-generate-config --root /mnt || { echo "Config generation failed." >&2; exit 1; } |
157 | | - |
158 | | - echo "Installing NixOS..." |
159 | 93 | if [[ -f flake.nix ]]; then |
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; } |
| 94 | + flake_host=$(grep -oP '(?<=hostName = ").*(?=")' /mnt/etc/nixos/hardware-configuration.nix 2>/dev/null | head -1 || echo "nixos") |
| 95 | + [[ -z "$flake_host" ]] && flake_host="nixos" |
| 96 | + nixos-install --flake ".#${flake_host}" --no-root-passwd |
164 | 97 | else |
165 | | - nixos-install --no-root-passwd || { echo "Installation failed." >&2; exit 1; } |
| 98 | + nixos-install --no-root-passwd |
166 | 99 | fi |
167 | | - echo "Installation complete. Reboot when ready." |
168 | | -} |
169 | 100 |
|
170 | | -run_rescue() { |
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; } |
175 | | -} |
176 | | - |
177 | | -main() { |
178 | | - check_root |
179 | | - check_dependencies |
180 | | - parse_args "$@" |
181 | | - validate |
182 | | - |
183 | | - case "$MODE" in |
184 | | - install) run_install ;; |
185 | | - rescue) run_rescue ;; |
186 | | - esac |
187 | | -} |
| 101 | +else |
| 102 | + for cmd in btrfs nixos-enter; do |
| 103 | + command -v "$cmd" &>/dev/null || { echo "missing: $cmd" >&2; exit 1; } |
| 104 | + done |
188 | 105 |
|
189 | | -main "$@" |
| 106 | + mount_subvolumes |
| 107 | + swapon /mnt/var/swapfile 2>/dev/null || true |
| 108 | + nixos-enter |
| 109 | +fi |
0 commit comments