btrfs_setup.sh 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. #!/bin/bash
  2. # Цвета
  3. C_RESET='\033[0m'
  4. C_RED='\033[0;31m'
  5. C_GREEN='\033[0;32m'
  6. C_YELLOW='\033[0;33m'
  7. C_BLUE='\033[0;34m'
  8. C_CYAN='\033[0;36m'
  9. # Файл для сохранения состояния
  10. CONFIG_FILE=".btrfs_setup.conf"
  11. # --- Функции Помощники ---
  12. info() { echo -e "${C_CYAN}INFO:${C_RESET} $1"; }
  13. warn() { echo -e "${C_YELLOW}WARN:${C_RESET} $1"; }
  14. error() { echo -e "${C_RED}ERROR:${C_RESET} $1"; }
  15. success() { echo -e "${C_GREEN}SUCCESS:${C_RESET} $1"; }
  16. cmd() { echo -e "${C_BLUE}CMD:${C_RESET} $1"; }
  17. ask_confirm() {
  18. while true; do
  19. read -p "$(echo -e "${C_YELLOW}CONFIRM:${C_RESET} $1 (y/n): ")" yn
  20. case $yn in
  21. [Yy]* ) return 0;;
  22. [Nn]* ) return 1;;
  23. * ) echo "Ответьте 'y' или 'n'.";;
  24. esac
  25. done
  26. }
  27. # --- Управление Конфигурацией ---
  28. # Загрузка переменных из файла
  29. load_config() {
  30. if [ -f "$CONFIG_FILE" ]; then
  31. source "$CONFIG_FILE"
  32. fi
  33. }
  34. # Сохранение переменной в файл
  35. save_config() {
  36. local key=$1
  37. local value=$2
  38. # Удаляем старую запись, если она есть
  39. sed -i "/^${key}=/d" "$CONFIG_FILE"
  40. # Добавляем новую
  41. echo "${key}='${value}'" >> "$CONFIG_FILE"
  42. }
  43. # --- Главы Установки ---
  44. # Глава 0: Проверка Live-среды
  45. step0_check_live() {
  46. info "--- Глава 0: Проверка Live-среды ---"
  47. info "Проверяю наличие 'btrfs-progs'..."
  48. if ! pacman -S --needed --noconfirm btrfs-progs; then
  49. error "Не удалось установить 'btrfs-progs'. Проверьте интернет."
  50. return 1
  51. fi
  52. success "Live-среда готова."
  53. }
  54. # Глава 1: Форматирование
  55. step1_format() {
  56. info "--- Глава 1: Форматирование ---"
  57. read -p "EFI раздел [${DEFAULT_EFI_PART:-/dev/sda1}]: " EFI_PART
  58. EFI_PART=${EFI_PART:-${DEFAULT_EFI_PART:-/dev/sda1}}
  59. read -p "BTRFS раздел [${DEFAULT_BTRFS_PART:-/dev/sda2}]: " BTRFS_PART
  60. BTRFS_PART=${BTRFS_PART:-${DEFAULT_BTRFS_PART:-/dev/sda2}}
  61. if [ -z "$EFI_PART" ] || [ -z "$BTRFS_PART" ]; then
  62. error "Разделы не могут быть пустыми."; return 1;
  63. fi
  64. warn "!! ВНИМАНИЕ !! Это действие необратимо."
  65. if ask_confirm "Отформатировать $EFI_PART (fat32) и $BTRFS_PART (btrfs)?"; then
  66. cmd "mkfs.fat -F 32 $EFI_PART"
  67. mkfs.fat -F 32 "$EFI_PART" || { error "Ошибка форматирования EFI."; return 1; }
  68. cmd "mkfs.btrfs -f $BTRFS_PART"
  69. mkfs.btrfs -f "$BTRFS_PART" || { error "Ошибка форматирования Btrfs."; return 1; }
  70. save_config "DEFAULT_EFI_PART" "$EFI_PART"
  71. save_config "DEFAULT_BTRFS_PART" "$BTRFS_PART"
  72. success "Форматирование завершено."
  73. else
  74. info "Форматирование отменено."; return 1;
  75. fi
  76. }
  77. # Глава 2: Создание подтомов
  78. step2_create_subvols() {
  79. info "--- Глава 2: Создание подтомов ---"
  80. if [ -z "$DEFAULT_BTRFS_PART" ]; then
  81. error "Сначала выполните Главу 1 (Форматирование)."; return 1;
  82. fi
  83. cmd "mount -o compress=zstd:1 $DEFAULT_BTRFS_PART /mnt"
  84. mount -o compress=zstd:1 "$DEFAULT_BTRFS_PART" /mnt || { error "Не удалось смонтировать /mnt."; return 1; }
  85. local default_vols=("@" "@home" "@srv" "@logs" "@cache" "@snapshots" "@swap")
  86. local SUBVOLS_RAW=${DEFAULT_SUBVOLS:-${default_vols[*]}}
  87. info "Текущий список подтомов: $SUBVOLS_RAW"
  88. if ask_confirm "Хотите настроить список подтомов?"; then
  89. read -p "Введите новый список (через пробел): " SUBVOLS_RAW
  90. fi
  91. local SUBVOLS_LIST=($SUBVOLS_RAW)
  92. info "Создаю подтома: ${SUBVOLS_LIST[*]}"
  93. for vol in "${SUBVOLS_LIST[@]}"; do
  94. cmd "btrfs subvolume create /mnt/$vol"
  95. btrfs subvolume create "/mnt/$vol"
  96. done
  97. save_config "DEFAULT_SUBVOLS" "$SUBVOLS_RAW"
  98. success "Подтома созданы."
  99. cmd "umount /mnt"
  100. umount /mnt
  101. }
  102. # Глава 3: Монтирование подтомов
  103. step3_mount_all() {
  104. info "--- Глава 3: Монтирование подтомов ---"
  105. if [ -z "$DEFAULT_BTRFS_PART" ] || [ -z "$DEFAULT_SUBVOLS" ]; then
  106. error "Сначала выполните Главы 1 и 2."; return 1;
  107. fi
  108. local SUBVOLS_LIST=($DEFAULT_SUBVOLS)
  109. declare -A MOUNT_MAP
  110. MOUNT_MAP["@"]="/mnt"
  111. MOUNT_MAP["@home"]="/mnt/home"
  112. MOUNT_MAP["@srv"]="/mnt/srv"
  113. MOUNT_MAP["@logs"]="/mnt/var/log"
  114. MOUNT_MAP["@cache"]="/mnt/var/cache"
  115. MOUNT_MAP["@snapshots"]="/mnt/.snapshots"
  116. MOUNT_MAP["@swap"]="/mnt/swap"
  117. # Добавляем кастомные, если они были
  118. for vol in "${SUBVOLS_LIST[@]}"; do
  119. if [[ -z "${MOUNT_MAP[$vol]}" ]]; then
  120. read -p "Введите точку монтирования для '$vol' (e.g., /mnt/custom): " custom_mount
  121. MOUNT_MAP["$vol"]="$custom_mount"
  122. fi
  123. done
  124. read -p "Уровень сжатия по умолчанию [zstd:3]: " DEFAULT_COMPRESS
  125. DEFAULT_COMPRESS=${DEFAULT_COMPRESS:-zstd:3}
  126. info "Монтирование корня (@)..."
  127. local root_opts="defaults,compress=$DEFAULT_COMPRESS"
  128. if [[ " ${SUBVOLS_LIST[*]} " =~ " @cache " ]]; then # Корень не должен иметь noatime
  129. root_opts="defaults,compress=$DEFAULT_COMPRESS"
  130. fi
  131. mkdir -p "${MOUNT_MAP['@']}"
  132. cmd "mount -o $root_opts,subvol=@ $DEFAULT_BTRFS_PART ${MOUNT_MAP['@']}"
  133. mount -o "$root_opts,subvol=@" "$DEFAULT_BTRFS_PART" "${MOUNT_MAP['@']}" || { error "Ошибка монтирования корня."; return 1; }
  134. # Монтируем все остальное
  135. for vol in "${SUBVOLS_LIST[@]}"; do
  136. if [[ "$vol" == "@" ]]; then continue; fi
  137. local mount_point="${MOUNT_MAP[$vol]}"
  138. local default_opts="defaults,compress=$DEFAULT_COMPRESS"
  139. if [[ "$vol" == "@cache" || "$vol" == "@swap" ]]; then
  140. default_opts="defaults,noatime"
  141. fi
  142. read -p "Опции для '$vol' [${default_opts}]: " custom_opts
  143. local opts=${custom_opts:-$default_opts}
  144. info "Монтирование '$vol' в '$mount_point'..."
  145. mkdir -p "$mount_point"
  146. cmd "mount -o $opts,subvol=$vol $DEFAULT_BTRFS_PART $mount_point"
  147. mount -o "$opts,subvol=$vol" "$DEFAULT_BTRFS_PART" "$mount_point" || warn "Не удалось смонтировать $vol."
  148. done
  149. info "Монтирование EFI..."
  150. mkdir -p /mnt/efi
  151. cmd "mount $DEFAULT_EFI_PART /mnt/efi"
  152. mount "$DEFAULT_EFI_PART" /mnt/efi || { error "Ошибка монтирования EFI."; return 1; }
  153. success "Все подтома смонтированы."
  154. }
  155. # Глава 4: Swap-файл
  156. step4_swapfile() {
  157. info "--- Глава 4: Swap-файл ---"
  158. if [[ ! " $DEFAULT_SUBVOLS " =~ " @swap " ]]; then
  159. warn "Подтом '@swap' не найден, пропускаю."; return 0;
  160. fi
  161. read -p "Размер swap-файла [4G]: " SWAP_SIZE
  162. SWAP_SIZE=${SWAP_SIZE:-4G}
  163. info "Создаю swap-файл /mnt/swap/swapfile..."
  164. cmd "btrfs filesystem mkswapfile --size $SWAP_SIZE --uuid clear /mnt/swap/swapfile"
  165. btrfs filesystem mkswapfile --size "$SWAP_SIZE" --uuid clear /mnt/swap/swapfile
  166. cmd "swapon /mnt/swap/swapfile"
  167. swapon /mnt/swap/swapfile
  168. save_config "SWAP_SIZE" "$SWAP_SIZE"
  169. success "Swap-файл создан и активирован."
  170. }
  171. # Глава 5: Pacstrap
  172. step5_pacstrap() {
  173. info "--- Глава 5: Установка (pacstrap) ---"
  174. # Предлагаем "умный" список
  175. local BASE_PKGS="base linux linux-firmware nano"
  176. local BTRFS_UTILS=""
  177. if [[ " $DEFAULT_SUBVOLS " =~ " @snapshots " ]]; then
  178. BTRFS_UTILS="btrfs-progs grub efibootmgr snapper grub-btrfs snap-pac inotify-tools"
  179. else
  180. BTRFS_UTILS="btrfs-progs grub efibootmgr"
  181. fi
  182. local DEFAULT_PACKAGES="${BASE_PKGS} ${BTRFS_UTILS}"
  183. info "Рекомендуемый список пакетов:"
  184. info "$DEFAULT_PACKAGES"
  185. read -p "Введите список пакетов (Enter = принять): " PACKAGES
  186. PACKAGES=${PACKAGES:-$DEFAULT_PACKAGES}
  187. info "Запуск pacstrap (это займет время)..."
  188. cmd "pacstrap -K /mnt $PACKAGES"
  189. if pacstrap -K /mnt $PACKAGES; then
  190. save_config "INSTALLED_PACKAGES" "$PACKAGES"
  191. success "Базовая система установлена."
  192. else
  193. error "Ошибка pacstrap!"; return 1;
  194. fi
  195. }
  196. # Глава 6: Fstab
  197. step6_fstab() {
  198. info "--- Глава 6: Fstab ---"
  199. info "Генерирую fstab..."
  200. cmd "genfstab -U /mnt >> /mnt/etc/fstab"
  201. genfstab -U /mnt >> /mnt/etc/fstab
  202. if [ -n "$SWAP_SIZE" ]; then
  203. info "Добавляю swap-файл в fstab..."
  204. cmd "echo '/swap/swapfile none swap defaults 0 0' >> /mnt/etc/fstab"
  205. echo "/swap/swapfile none swap defaults 0 0" >> /mnt/etc/fstab
  206. fi
  207. warn "ВАЖНО: genfstab мог 'забыть' опции сжатия."
  208. if ask_confirm "Открыть /mnt/etc/fstab в nano для ручной проверки?"; then
  209. nano /mnt/etc/fstab
  210. fi
  211. success "Fstab готов."
  212. }
  213. # Глава 7: Шпаргалка (Chroot)
  214. step7_show_helper() {
  215. info "--- Глава 7: Шпаргалка по настройке (внутри chroot) ---"
  216. if [ -z "$INSTALLED_PACKAGES" ]; then
  217. warn "Пакеты еще не установлены (Глава 5). Шпаргалка может быть неполной."
  218. fi
  219. echo -e "${C_CYAN}==========================================================="
  220. echo " Дальнейшие шаги (выполнять в 'arch-chroot /mnt') "
  221. echo "===========================================================${C_RESET}"
  222. echo ""
  223. echo "Не забудьте про: passwd, ln -sf /usr/share/zoneinfo/..., hwclock, locale.gen, hostname, hosts"
  224. echo ""
  225. # --- Динамическая секция Snapper ---
  226. if [[ "$INSTALLED_PACKAGES" == *"snapper"* ]]; then
  227. echo -e "${C_YELLOW}### 1. Настройка Snapper ###${C_RESET}"
  228. warn "Рекомендация: этот шаг лучше делать ПОСЛЕ первой загрузки, а не в chroot."
  229. echo "cmd: snapper -c root create-config /"
  230. echo "cmd: rm -fdir /.snapshots"
  231. echo "cmd: mkdir /.snapshots"
  232. echo "cmd: mount -a"
  233. echo "cmd: nano /etc/snapper/configs/root"
  234. echo " > Измени: TIMELINE_CREATE=\"no\""
  235. echo ""
  236. fi
  237. # --- Динамическая секция GRUB ---
  238. if [[ "$INSTALLED_PACKAGES" == *"grub"* ]]; then
  239. echo -e "${C_YELLOW}### 2. Настройка GRUB ###${C_RESET}"
  240. echo "cmd: grub-install --target=x86_64-efi --efi-directory=/efi --bootloader-id=Arch"
  241. if [[ "$INSTALLED_PACKAGES" == *"grub-btrfs"* ]]; then
  242. echo "info: Добавляем поддержку снимков в GRUB..."
  243. echo "cmd: nano /etc/default/grub"
  244. echo " > Добавь/раскомментируй: GRUB_BTRFS_SNAPSHOT_BOOT=true"
  245. fi
  246. echo "cmd: grub-mkconfig -o /boot/grub/grub.cfg"
  247. echo ""
  248. fi
  249. # --- Динамическая секция Сервисов ---
  250. if [[ "$INSTALLED_PACKAGES" == *"snapper"* || "$INSTALLED_PACKAGES" == *"grub-btrfs"* ]]; then
  251. echo -e "${C_YELLOW}### 3. Включение сервисов ###${C_RESET}"
  252. if [[ "$INSTALLED_PACKAGES" == *"snapper"* ]]; then
  253. echo "cmd: systemctl enable snapper-timeline.timer"
  254. echo "cmd: systemctl enable snapper-cleanup.timer"
  255. fi
  256. if [[ "$INSTALLED_PACKAGES" == *"grub-btrfs"* ]]; then
  257. echo "cmd: systemctl enable grub-btrfsd.service"
  258. fi
  259. echo ""
  260. fi
  261. echo -e "${C_CYAN}===========================================================${C_RESET}"
  262. }
  263. # --- Главное Меню ---
  264. main_menu() {
  265. while true; do
  266. clear
  267. load_config # Загружаем состояние при каждом показе меню
  268. echo -e "${C_GREEN}--- Интерактивный Btrfs-помощник (v2.0) ---${C_RESET}"
  269. echo "Файл состояния: $CONFIG_FILE"
  270. echo ""
  271. echo -e " ${C_CYAN}Разделы:${C_RESET} ${DEFAULT_EFI_PART:-Не задан} | ${DEFAULT_BTRFS_PART:-Не задан}"
  272. echo -e " ${C_CYAN}Подтома:${C_RESET} ${DEFAULT_SUBVOLS:-Не заданы}"
  273. echo -e " ${C_CYAN}Пакеты:${C_RESET} ${INSTALLED_PACKAGES:-Не установлены}"
  274. echo ""
  275. echo "Выберите главу:"
  276. echo "-----------------------------------"
  277. echo " 0. Проверить Live-среду (pacman)"
  278. echo " 1. Форматирование разделов"
  279. echo " 2. Создание подтомов Btrfs"
  280. echo " 3. Монтирование подтомов"
  281. echo " 4. Создание Swap-файла"
  282. echo "-----------------------------------"
  283. echo " 5. Установка системы (pacstrap)"
  284. echo " 6. Генерация Fstab"
  285. echo " 7. Показать шпаргалку (Chroot)"
  286. echo "-----------------------------------"
  287. echo " q. Выход"
  288. echo ""
  289. read -p "Ваш выбор: " choice
  290. # Переменная для паузы
  291. local pause=true
  292. case $choice in
  293. 0) step0_check_live ;;
  294. 1) step1_format ;;
  295. 2) step2_create_subvols ;;
  296. 3) step3_mount_all ;;
  297. 4. | 4) step4_swapfile ;;
  298. 5) step5_pacstrap ;;
  299. 6) step6_fstab ;;
  300. 7) step7_show_helper ;;
  301. q|Q) break ;;
  302. *) warn "Неверный выбор." ;;
  303. esac
  304. if [ "$pause" = true ]; then
  305. echo ""
  306. read -p "Нажмите Enter для возврата в меню..."
  307. fi
  308. done
  309. info "Готово. Удачи!"
  310. }
  311. # --- Запуск ---
  312. main_menu