configuration.nix/installer/mirea-install.sh
2026-08-28 21:20:15 +03:00

366 lines
15 KiB
Shell
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
# MIREA NixOS Installer
# Устанавливает NixOS рядом с Windows или на чистый диск.
# NixOS root: 50 GiB, EFI (при чистой установке): 512 MiB.
# Идемпотентен: если установка прервалась, просто запустите повторно.
set -euo pipefail
CONFIG_REPO_URL="@CONFIG_REPO_URL@"
NIXOS_SIZE_GIB=50
EFI_SIZE_MIB=512
MNT=/mnt
R='\033[0;31m' G='\033[0;32m' Y='\033[1;33m' B='\033[0;34m' N='\033[0m'
info() { echo -e "${G}[i]${N} $*"; }
warn() { echo -e "${Y}[!]${N} $*"; }
error() { echo -e "${R}[x]${N} $*" >&2; exit 1; }
step() { echo -e "\n${B}══${N} $* ${B}══${N}"; }
skip() { echo -e "${Y}[→]${N} уже выполнено: $* — пропуск"; }
# ── Проверки ──────────────────────────────────────────────────────────────────
[[ $EUID -eq 0 ]] || error "Запустите от root"
for cmd in sgdisk parted mkfs.fat mkfs.ext4 blkid findmnt; do
command -v "$cmd" &>/dev/null || error "$cmd не найден"
done
# ── Утилиты ───────────────────────────────────────────────────────────────────
# Размер наибольшего свободного блока на диске в ГиБ
get_free_gib() {
local disk="$1"
local pttype
pttype=$(lsblk -d -o PTTYPE -n "$disk" 2>/dev/null | tr -d ' ')
if [[ "$pttype" == "gpt" ]]; then
local sector_size free_first free_last
sector_size=$(cat "/sys/block/$(basename "$disk")/queue/logical_block_size" 2>/dev/null || echo 512)
free_first=$(sgdisk -f "$disk" 2>/dev/null || echo 0)
free_last=$(sgdisk -E "$disk" 2>/dev/null || echo 0)
if (( free_last > free_first )); then
echo $(( (free_last - free_first + 1) * sector_size / 1024 / 1024 / 1024 ))
else
echo 0
fi
else
lsblk -d -b -o SIZE -n "$disk" 2>/dev/null | awk '{print int($1/1024/1024/1024)}'
fi
}
blkid_attr() { blkid -s "$1" -o value "$2" 2>/dev/null || true; }
# ── Выбор диска ───────────────────────────────────────────────────────────────
step "Выбор диска"
echo
mapfile -t DISK_NAMES < <(lsblk -d -o NAME -n | grep -Ev '^(loop|sr|fd|ram)')
(( ${#DISK_NAMES[@]} > 0 )) || error "Блочные устройства не найдены"
DEFAULT_NUM=0
declare -a DISK_FREE DISK_STATUS
for i in "${!DISK_NAMES[@]}"; do
dev="/dev/${DISK_NAMES[$i]}"
free_gib=$(get_free_gib "$dev")
DISK_FREE[$i]=$free_gib
# Диск с существующим разделом NixOS — продолжение прерванной установки
if lsblk -r -o PARTLABEL -n "$dev" 2>/dev/null | grep -q "^NixOS$"; then
DISK_STATUS[$i]="→продолж."
[[ $DEFAULT_NUM -eq 0 ]] && DEFAULT_NUM=$(( i + 1 ))
else
DISK_STATUS[$i]="${free_gib} ГиБ"
if [[ $DEFAULT_NUM -eq 0 ]] && (( free_gib >= NIXOS_SIZE_GIB )); then
DEFAULT_NUM=$(( i + 1 ))
fi
fi
done
printf " %-4s %-14s %-8s %-12s %s\n" "№" "Диск" "Размер" "Свободно" "Модель"
printf " %-4s %-14s %-8s %-12s %s\n" "─" "──────────────" "──────" "────────────" "──────"
for i in "${!DISK_NAMES[@]}"; do
dev="/dev/${DISK_NAMES[$i]}"
size=$(lsblk -d -o SIZE -n "$dev" 2>/dev/null | tr -d ' ')
model=$(lsblk -d -o MODEL -n "$dev" 2>/dev/null | sed 's/[[:space:]]*$//')
num=$(( i + 1 ))
if [[ $num -eq $DEFAULT_NUM ]]; then
printf "${G} [%s] %-14s %-8s %-12s %s ← рекомендуется${N}\n" \
"$num" "$dev" "$size" "${DISK_STATUS[$i]}" "$model"
else
printf " [%s] %-14s %-8s %-12s %s\n" "$num" "$dev" "$size" "${DISK_STATUS[$i]}" "$model"
fi
done
echo
while true; do
if [[ $DEFAULT_NUM -gt 0 ]]; then
read -rp "Выберите номер диска [${DEFAULT_NUM}]: " choice
[[ -z "$choice" ]] && choice=$DEFAULT_NUM
else
read -rp "Выберите номер диска: " choice
fi
if [[ "$choice" =~ ^[0-9]+$ ]] && (( choice >= 1 && choice <= ${#DISK_NAMES[@]} )); then
DISK="/dev/${DISK_NAMES[$(( choice - 1 ))]}"
break
fi
warn "Введите число от 1 до ${#DISK_NAMES[@]}"
done
info "Выбран диск: $DISK"
# ── Определение наличия Windows ───────────────────────────────────────────────
step "Проверка наличия Windows"
HAS_WINDOWS=false
EFI_PART=""
NEED_NEW_EFI=false
pttype=$(lsblk -d -o PTTYPE -n "$DISK" 2>/dev/null | tr -d ' ')
if [[ "$pttype" == "gpt" ]]; then
while IFS= read -r pdev; do
tmpdir=$(mktemp -d)
if mount -t vfat "$pdev" "$tmpdir" 2>/dev/null; then
if [[ -d "$tmpdir/EFI/Microsoft" ]]; then
HAS_WINDOWS=true
EFI_PART="$pdev"
info "Windows обнаружена, EFI раздел: $EFI_PART"
fi
umount "$tmpdir"
fi
rmdir "$tmpdir"
done < <(lsblk -r -o NAME,PARTTYPE -n "$DISK" 2>/dev/null \
| awk '/c12a7328-f81f-11d2-ba4b-00a0c93ec93b/{print "/dev/"$1}')
fi
$HAS_WINDOWS || info "Windows не обнаружена"
# ── Определение существующих разделов (идемпотентность) ───────────────────────
NIXOS_PART=$(lsblk -r -o NAME,PARTLABEL -n "$DISK" 2>/dev/null \
| awk '$2 == "NixOS" {print "/dev/"$1}' | tail -1)
ALREADY_PARTITIONED=false
if [[ -b "$NIXOS_PART" ]]; then
ALREADY_PARTITIONED=true
info "Найден существующий раздел NixOS: $NIXOS_PART — разметка пропускается"
# Ищем EFI по меткам
nixos_efi=$(lsblk -r -o NAME,PARTLABEL -n "$DISK" 2>/dev/null \
| awk '$2 == "NixOS-EFI" {print "/dev/"$1}' | tail -1)
if [[ -b "$nixos_efi" ]]; then
# Ранее создан отдельный EFI для NixOS (Windows EFI был мал)
EFI_PART="$nixos_efi"
NEED_NEW_EFI=true
elif [[ -z "$EFI_PART" ]]; then
# Чистая установка: ищем раздел с меткой EFI
efi_clean=$(lsblk -r -o NAME,PARTLABEL -n "$DISK" 2>/dev/null \
| awk '$2 == "EFI" {print "/dev/"$1}' | tail -1)
[[ -b "$efi_clean" ]] && EFI_PART="$efi_clean"
fi
# Иначе EFI_PART уже содержит Windows EFI из предыдущего блока
fi
# ── Разметка ──────────────────────────────────────────────────────────────────
if ! $ALREADY_PARTITIONED; then
if $HAS_WINDOWS; then
step "Двойная загрузка: проверка EFI раздела"
efi_size_b=$(lsblk -b -d -o SIZE -n "$EFI_PART" 2>/dev/null | tr -d ' ')
efi_size_mib=$(( efi_size_b / 1024 / 1024 ))
info "Размер EFI ($EFI_PART): ${efi_size_mib} MiB"
if (( efi_size_mib < 256 )); then
warn "EFI раздел слишком мал — будет создан отдельный NixOS EFI 500 MiB."
NEED_NEW_EFI=true
else
info "EFI достаточного размера, будет использован совместно с Windows."
fi
step "Поиск свободного места"
sector_size=$(cat "/sys/block/$(basename "$DISK")/queue/logical_block_size")
free_first=$(sgdisk -f "$DISK" 2>/dev/null)
free_last=$(sgdisk -E "$DISK" 2>/dev/null)
free_bytes=$(( (free_last - free_first + 1) * sector_size ))
free_gib=$(( free_bytes / 1024 / 1024 / 1024 ))
if $NEED_NEW_EFI; then
required_bytes=$(( (500 * 1024 * 1024) + (NIXOS_SIZE_GIB * 1024 * 1024 * 1024) ))
required_label="${NIXOS_SIZE_GIB} ГиБ + 500 MiB EFI"
else
required_bytes=$(( NIXOS_SIZE_GIB * 1024 * 1024 * 1024 ))
required_label="${NIXOS_SIZE_GIB} ГиБ"
fi
(( free_bytes >= required_bytes )) \
|| error "Свободного места ${free_gib} ГиБ < ${required_label}. Освободите место в Windows и повторите."
info "Свободного места: ${free_gib} ГиБ"
echo
if $NEED_NEW_EFI; then
warn "Создаётся: NixOS EFI 500 MiB + раздел NixOS ${NIXOS_SIZE_GIB} ГиБ."
warn "Windows EFI ($EFI_PART, ${efi_size_mib} MiB) не затрагивается и не используется."
else
warn "Создаётся: раздел NixOS ${NIXOS_SIZE_GIB} ГиБ."
warn "EFI Windows ($EFI_PART) будет использован совместно."
fi
warn "Данные Windows не затрагиваются."
echo
read -rp "Продолжить? (yes/no): " ans
[[ "$ans" == "yes" ]] || error "Установка отменена"
if $NEED_NEW_EFI; then
sgdisk \
--new=0:"${free_first}s":"+500MiB" \
--typecode=0:ef00 \
--change-name=0:"NixOS-EFI" \
--new=0:0:"+${NIXOS_SIZE_GIB}GiB" \
--typecode=0:8304 \
--change-name=0:"NixOS" \
"$DISK"
partprobe "$DISK"; sleep 2
EFI_PART=$(lsblk -r -o NAME,PARTLABEL -n "$DISK" 2>/dev/null \
| awk '$2 == "NixOS-EFI" {print "/dev/"$1}' | tail -1)
else
sgdisk \
--new=0:"${free_first}s":"+${NIXOS_SIZE_GIB}GiB" \
--typecode=0:8304 \
--change-name=0:"NixOS" \
"$DISK"
partprobe "$DISK"; sleep 2
fi
NIXOS_PART=$(lsblk -r -o NAME,PARTLABEL -n "$DISK" 2>/dev/null \
| awk '$2 == "NixOS" {print "/dev/"$1}' | tail -1)
else
step "Чистая установка"
disk_gib=$(lsblk -d -b -o SIZE -n "$DISK" | awk '{print int($1/1024/1024/1024)}')
(( disk_gib >= NIXOS_SIZE_GIB + 1 )) \
|| error "Диск слишком мал: ${disk_gib} ГиБ (нужно минимум $((NIXOS_SIZE_GIB + 1)) ГиБ)"
echo
warn "ВСЕ ДАННЫЕ НА ДИСКЕ $DISK (${disk_gib} ГиБ) БУДУТ УНИЧТОЖЕНЫ!"
echo
read -rp "Введите имя диска для подтверждения ($(basename "$DISK")): " ans
[[ "$ans" == "$(basename "$DISK")" ]] || error "Установка отменена"
sgdisk --zap-all "$DISK"
sgdisk \
--new=1:2048:"+${EFI_SIZE_MIB}MiB" \
--typecode=1:ef00 \
--change-name=1:"EFI" \
--new=2:0:"+${NIXOS_SIZE_GIB}GiB" \
--typecode=2:8304 \
--change-name=2:"NixOS" \
"$DISK"
partprobe "$DISK"; sleep 2
EFI_PART=$(lsblk -r -o NAME,PARTLABEL -n "$DISK" 2>/dev/null \
| awk '$2 == "EFI" {print "/dev/"$1}' | tail -1)
NIXOS_PART=$(lsblk -r -o NAME,PARTLABEL -n "$DISK" 2>/dev/null \
| awk '$2 == "NixOS" {print "/dev/"$1}' | tail -1)
fi
fi
[[ -b "$NIXOS_PART" ]] || error "Не удалось определить раздел NixOS"
[[ -b "$EFI_PART" ]] || error "Не удалось определить EFI раздел"
info "EFI раздел: $EFI_PART"
info "NixOS раздел: $NIXOS_PART"
# ── Форматирование ────────────────────────────────────────────────────────────
step "Форматирование"
if ! $HAS_WINDOWS || $NEED_NEW_EFI; then
if [[ "$(blkid_attr TYPE "$EFI_PART")" == "vfat" ]]; then
skip "EFI (FAT32)"
else
mkfs.fat -F32 -n EFI "$EFI_PART"
info "EFI отформатирован (FAT32)"
fi
else
info "EFI Windows используется как есть"
fi
nixos_fs=$(blkid_attr TYPE "$NIXOS_PART")
nixos_label=$(blkid_attr LABEL "$NIXOS_PART")
if [[ "$nixos_fs" == "ext4" && "$nixos_label" == "nixos" ]]; then
skip "NixOS (ext4, label=nixos)"
else
mkfs.ext4 -L nixos -F "$NIXOS_PART"
info "NixOS отформатирован (ext4)"
fi
# ── Монтирование ──────────────────────────────────────────────────────────────
step "Монтирование"
current_root=$(findmnt -n -o SOURCE "$MNT" 2>/dev/null || true)
if [[ "$current_root" == "$NIXOS_PART" ]]; then
skip "NixOS уже смонтирован в $MNT"
else
[[ -n "$current_root" ]] && umount -R "$MNT"
mount "$NIXOS_PART" "$MNT"
fi
mkdir -p "$MNT/boot"
current_boot=$(findmnt -n -o SOURCE "$MNT/boot" 2>/dev/null || true)
if [[ "$current_boot" == "$EFI_PART" ]]; then
skip "EFI уже смонтирован в $MNT/boot"
else
[[ -n "$current_boot" ]] && umount "$MNT/boot"
mount "$EFI_PART" "$MNT/boot"
fi
info "Смонтировано в $MNT"
# ── Конфигурация ──────────────────────────────────────────────────────────────
step "Генерация hardware-configuration.nix"
if [[ -f "$MNT/etc/nixos/hardware-configuration.nix" ]]; then
skip "hardware-configuration.nix"
else
nixos-generate-config --root "$MNT"
awk '
/fsType = "ext4"/ {
print
print " options = [ \"noatime\" ];"
next
}
{ print }
' "$MNT/etc/nixos/hardware-configuration.nix" > /tmp/hw-patched.nix
mv /tmp/hw-patched.nix "$MNT/etc/nixos/hardware-configuration.nix"
info "noatime добавлен к ext4 разделам"
fi
step "Загрузка конфигурации MIREA"
if [[ -f "$MNT/etc/nixos/flake.nix" ]]; then
skip "конфигурация MIREA уже загружена"
else
tmpconf=$(mktemp -d)
git clone --depth=1 "$CONFIG_REPO_URL" "$tmpconf/config" \
|| error "Не удалось клонировать $CONFIG_REPO_URL. Проверьте сетевое подключение."
cp "$tmpconf/config/flake.nix" "$MNT/etc/nixos/"
cp "$tmpconf/config/flake.lock" "$MNT/etc/nixos/"
cp "$tmpconf/config/configuration.nix" "$MNT/etc/nixos/"
rm -rf "$tmpconf"
info "Конфигурация MIREA установлена"
fi
# ── Установка ─────────────────────────────────────────────────────────────────
step "Установка NixOS (это займёт время)"
nixos-install \
--root "$MNT" \
--flake "/mnt/etc/nixos#nixos" \
--no-root-passwd
# ── Завершение ────────────────────────────────────────────────────────────────
step "Готово"
umount -R "$MNT" || true
echo
info "NixOS установлена на $DISK"
info "Извлеките USB-носитель и перезагрузите систему"
echo
read -rp "Перезагрузить сейчас? (yes/no): " ans
[[ "$ans" == "yes" ]] && reboot || true