Каждый, кто хоть раз устанавливал Linux вручную, знает этот ритуал: загрузиться с Live‑образа, запустить fdisk или parted, создать таблицу разделов, отформатировать их в btrfs/ext4, создать субтома, а затем вручную примонтировать всё это в /mnt.

Конечно, такой подход можно использовать и при установке NixOs. Но вопрос — зачем?

Это не только полностью противоречит самой сути NixOS — системы, где всё должно быть описано декларативно в коде, но и существенно влияет вообще на удобство использования этой системы в будущем. Стоит вам захотеть развернуть точно такую же конфигурацию на другом сервере или ноутбуке, и весь ручной процесс разметки приходится повторять заново, вспоминая точные флаги монтирования вместо того, чтобы написать всего лишь одну команду.

В этой статье мы этим вопросом и займёмся, а поможет нам в этом известная утилита disko. Мы напишем чистый декларативный конфигурационный файл в ДВУХ вариантах:

  1. Простой: с boot и root‑разделом.

  2. LUKS+Swap + LVM: шифрование + раздел подкачки + LVM

Для кого эта статья? Для абсолютных новичков, которые сейчас только‑только начинают касаться NixOs и хотят сразу сделать всё так, как надо. Поэтому я буду объяснять всё максимально подробно, разжёвывая каждую строчку кода. Люди, которые с никсой и disko уже до этого работали, вряд ли найдут здесь что‑нибудь новое и полезное.

Моё рабочее пространство: live‑образ на виртуалке QEMU.


Шаг 1 — после входа в live: подключение к интернету

Если у вас подключён кабель, то:

Вам ничего делать не надо, система уже подключена к интернету и вы можете сразу переходить к шагу 2.

Если у вас WiFi, то вводите следующие команды:

$ nmcli device wifi list
# Вам выдаст список wifi-сетей
$ nmcli device wifi connect "<SSID>" password "<PASSWORD>"

Шаг 2 — разметка диска через disko

Тут мы и приступаем к написанию нашей первой конфигурации — разметке диска. В этом нам поможет утилита disko, которая позволит нам разметить диск при помощи специального файла, в котором мы опишем структуру нашего диска: разделы, выделяемая память для каждого раздела, файловая система и многое другое.

В этой статье я покажу вам две конфигурации: обычную разметку с корневым(/) и загрузочным(/boot) разделом, а затем разметку с luks‑шифрованием и swap‑разделом. Уверяю, это совсем не сложно.

  1. Переходим в папку /tmp:

    Обязательно переходим в папку /tmp, где временно и будем хранить наш файл disko.nix:

    cd /tmp
  2. Узнаём имя нашего диска:

    Для этого воспользуемся командой lsblk. Так как я устанавливаю NixOs на виртуальную машину, мой диск называется vda, у вас же может быть sda или nvme0n1:

    $ lsblk
    NAME    MAJ:MIN  RM   SIZE RO TYPE  MOUNTPOINTS
    loop0     7:0    0    1.5G  1  loop /mix/.ro-store
    sr0      11:0    1    1.6G  1  rom  /iso
    vda     253:0    0    100G  0  disk
  3. Создаём файл с нуля или скачиваем его из интернета

    Тут есть два варианта: мы можем сами, с нуля написать всю конфигурацию нашего будущего диска, а можем и СКАЧАТЬ шаблон из официального репозитория на гитхабе.

    Как поступим мы? Мы скачаем шаблон из гитхаба, а затем построчно разберём, что за что тут отвечает. При желании вы можете вручную переписать весь код, что опишу здесь я.

    Ссылка на официальный репозиторий: https://github.com/nix‑community/disko

  4. Выбор и скачивание шаблона

    Я же для нашего первого примера выберу стандартный шаблон(в нём нет ни swap‑раздела, ни шифрования, только корневой и загрузочный разделы — минимум, необходимый для пользования системой), который загружу через curl:

    $ curl -O https://raw.githubusercontent.com/nix-community/disko/master/example/simple-efi.nix
    $ ls
    simple-efi.nix
    
    # Переименуем файл для удобства
    $ mv simple-efi.nix disko-config.nix

    Мы скачали шаблон. Стоит отметить, что все файлы в disko/master/examples — это проверененные и рабочие конфигурации диска. Так что если вам не интересно, за что тут отвечает каждая строчка(или вы уже всё понимаете), то просто поменяйте значение disko.devices.disk.main.device на название вашего диска(который вы узнали при помощи lsblk) и переходите к финальному шагу 3, где мы и применим наш disko-config.nix

    Или вы можете вместе со мной улучшить нашу стандартную конфигурацию и добавить к ней шифрование со swap‑разделом.

1. Построчный разбор стандартной конфигурации диска

/* Как выглядит simple-efi.nix */
{
  disko.devices = {
    disk = {
      main = {
        device = "/dev/disk/by-id/some-disk-id"; # ЗАМЕНИТЕ НА ВАШ ДИСК
        type = "disk";
        content = {
          type = "gpt";
          partitions = {
            ESP = {
              type = "EF00";
              size = "500M";
              content = {
                type = "filesystem";
                format = "vfat";
                mountpoint = "/boot";
                mountOptions = [ "umask=0077" ];
              };
            };
            root = {
              size = "100%";
              content = {
                type = "filesystem";
                format = "ext4";
                mountpoint = "/";
              };
            };
          };
        };
      };
    };
  };
}
  1. Корневой атрибут disko.devices

    Disko ожидает, что в конфиге будет атрибут disko.devices — именно туда Disko смотрит, чтобы понять, какие диски и как размечать.

    Зачем такая вложенность? Disko позволяет описывать не только диски, но и RAID‑массивы, LVM, BtrFS‑пулы и другие схемы. Всё это будет внутри disko.devices.

  2. disk = {} — описание блока с дисками

    Внутри disko.devices мы создаём атрибут disk. Он содержит все физические диски, которые мы хотим разметить.

    Внутри disk мы можем создать несколько дисков, например: system(для системы), storage(для данных) и так далее. У каждого диска будет своё имя и своя конфигурация. В нашем случае мы будем конфигурировать только один диск, который назовём main

  3. Создаём конкретный диск с именем main

    {
      disko.devices = {
        disk = {
          main = { ... };
        };
      };
    }

    Как я и говорил, main — это имя диска. Вы можете назвать его как угодно: ssd, nvme, sda, disk1. Это имя используется только внутри конфига, на реальное устройство оно не влияет.

    Зачем же тогда давать имена? Если у вас несколько дисков, вы сможете ссылаться на них по именам в других частях конфига.

  4. disk.main.type — указываем тип устройства

    disk = {
      main = {
        type = "disk"; # ! Указываем "disk"
      };
    };

    Атрибут type говорит Disko: «то, что я описываю — это физический диск». Другие возможные значения: "mdadm"(RAID), "lvm_vg"(LVM‑группа), "zpool" (ZFS‑пул) и так далее

  5. disk.main.device — пусть к устройству(ВАЖНЕЙШИЙ ШАГ!)

    Помните, мы узнавали имя нашего диска при помощи команды lsblk? Теперь нужно вставить его в device. В моём случае это /dev/vda, так как я использую виртуальную машину:

    disk = {
      main = {
        device = "/dev/vda"; # ЗАМЕНИТЕ НА ВАШ ДИСК!
        type = "disk";
      };
    };
  6. disk.main.content = {} — описываем содержимое диска

    Атрибут content = {} описывает, что именно будет внутри этого диска. Это может быть:

    • "gpt" — таблица разделов GPT

    • "mbr" — старая добрая MBR

    • "lvm_pv" — физический том LVM

    • "zfs" — ZFS‑пул

  7. disk.main.content.type — выбираем тип таблицы разделов

    Если у вас новенький комп с UEFI, то вам нужно ставить gpt, если старенький, который работает на BIOS(Legacy), то ставьте mbr.

    disk = {
      main = {
        device = "/dev/vda";
        type = "disk";
        content = { 
          type = "gpt";    # Я ставлю GPT
        };
      };
    };
  8. disk.main.content.partitions = {} — Начинаем описывать разделы

    Атрибут partitions содержит список всех разделов на диске. Каждый раздел будет иметь своё имя и свои параметры.

    disk = {
      main = {
        device = "/dev/vda";
        type = "disk";
        content = { 
          type = "gpt";
          
          partitions = {}; # В этом атрибуте мы и будем описывать разделы
        };
      };
    };
  9. Создаём загрузочный раздел(EFI)

    Назовём наш раздел ESP и выделим ему 500 мегабайт. Затем отформатируем его в FAT32 и примонтируем к /boot:

    partitions = {
    	ESP = {
            type = "EF00";
            size = "500M";
            content = {
                type = "filesystem";
                format = "vfat";
                mountpoint = "/boot";
                mountOptions = [ "umask=0077" ];
            };
        };
    };

    Теперь объясню значение каждой строчки:

    • ESP.type = "EF00" — задаёт тип раздела. Код «EF00» — это стандартный шестнадцатеричный индентификатор. Он указывает материнской плате, что здесь лежат файлы загрузки.

    • size = "500M" — здесь всё и так ясно: задаём размер

    • content.type = "filesystem"; — указывает, что раздел инициализируется как обычная файловая система для хранения файлов, а не как голый RAID‑массив или LVM‑том.

    • content.format = "vfat"; — форматирует раздел в файловую систему FAT32(в Linux она обозначается как vfat). Это обязательное условие, чтобы UEFI смог прочитать файлы.

    • content.mountpoint = "/boot" — точка монтирования системы. При загрузке системы этот раздел подключится в папку /boot, где загрузчик будет искать ядра ОС.

    • content.mountOptions = [ "umask=0077" ]; — это уже параметры безопасности при подключении раздела. Маска «umask=0077» означает, что доступ к чтению и записи файлов на этом разделе будет иметь ТОЛЬКО администратор(root). Для всех остальных пользователей системы доступ полностью закрыт. Это должно защитить загрузочные файлы от кражи или модификации.

  10. Финал: Создаём корневой раздел

    Назовём наш раздел root и выделим ему всё оставшееся место на диске.

    partitions = {
    	root = {
            size = "100%";
            content = {
    		    type = "filesystem";
    	        format = "ext4";
                mountpoint = "/";
            };
        };
    };

    Здесь особо ничего не поменялось, кроме того, что в качестве файловой системы для раздела мы выбрали ext4, а в качестве точки монтирования корневой каталог /. Ну и в size вместо точного значения в мегабайтах поставили 100% — disko сам расчитает оставшуюся после разметки память и выделит её root‑разделу.

На этом стандартным конфигом всё. Теперь у нас есть конфигурация диска. Но давайте зайдём дальше и добавим LUKS‑шифрование + Swap‑раздел? Если же вам это не интересно, то пролистайте в раздел «Шаг 3 — Финал».

2. Добавляем шифрование LUKS и swap‑раздел

{
  disko.devices = {
    disk = {
      main = {
        type = "disk";
        device = "ваш_диск";
        content = {
          type = "gpt";
          partitions = {
            # Загрузочный раздел
            ESP = {
              size = "500M";
              content = {
                type = "filesystem";
                format = "vfat";
                mountpoint = "/boot";
                mountOptions = [ "umask=0077" ];
              };
            };
            
            # Шифрованный раздел(всё остальное место)
            luks = {
              size = "100%";
              content = {
                type = "luks";
                name = "crypted";
                settings.allowDiscards = true;
                content = {
                  type = "lvm_pv";
                  vg = "pool";
                };
              };
            };
          };
        };
      };
    };
    
    # LVM-тома внутри luks-раздела
    lvm_vg = {
      pool = {
        type = "lvm_vg";
        lvs = {
          # Раздел подкачки
          swap = {
            size = "8G";
            content = {
              type = "swap";
              resumeDevice = true;
            };
          };
          
          # Корневой раздел
          root = {
            size = "100%";
            content = {
              type = "filesystem";
              format = "ext4";
              mountpoint = "/";
            };
          };
        };
      };
    };
  };
}

Здесь мы воспользуемся LVM. Но даже если вы с ним не сталкивались или вообще слышите про него впервые — ничего страшного, я вам всё объясню.

Сам LVM — это просто инструмент для управления дисками. Он позволяет нам собирать множество мелких дисков(PV) в один большой виртуальный(VG), а затем делить его на части(LV). Что такое PV, VG и LV?

  • PV (Physical Volume) — физические тома(это могут быть разделы или целые неразбитые диски).

  • VG (Volume Group) — группа томов. Объединяем физические тома (PV) в группу, создаём единый диск, который будем дальше разбивать так, как нам хочется, на LV.

  • LV (Logical Volume) — логические разделы. Собственно, это и есть раздел нашего «единого диска»(или Группы томов), который мы потом форматируем и используем как обычный раздел, обычного жёсткого диска.

Иными словами: мы объединяем PV в VG, а потом делим наш VG на LV.

P. S. Информацию об LVM брал с этой статьи(крайне советую к прочтению, если интересно): https://habr.com/ru/articles/67283/

Прежде чем продолжить

Я думаю, вы заметили, что конфигурация сильно увеличилась в размерах, но фундамент(секция boot, диск main) остался прежним, а потому мы проигнорируем всё и сразу же приступим к LUKS и LVM.

  1. Шифрованный LUKS‑раздел:

    luks = { # Помните, что название раздела не имеет значения, всё решает type
    	size = "100%";
        content = {
            type = "luks";
            name = "crypted";
            settings.allowDiscards = true;
            content = {
                type = "lvm_pv";
                vg = "pool";
            };
        };
    };

    Давайте теперь разберём этот раздел построчно:

    • content.type = "luks" — сообщаем disko, что внутри этого раздела будет luks‑шифрование.

    • content.name = "crypted" — имя, под которым будет доступен расшифрованный том(появится в /dev/mapper/crypted)

    • content.settings.allowDiscards = true — Разрешает TRIM‑команды для зашифрованного SSD(важно для производительности и долговечности)

    Внутренний слой(content.content = {}):

    Сейчас мы имеем чистый блок устройства, на котором в будущем создадим два логических раздела при помощи LVM.

    • type = "lvm_pv" — включает LVM. Говорит системе: «Сделай эту зашифрованную область Физическим томом(Physical Volume или PV)»

    • vg = "pool" — отправляет наш физический том в группу томов под названием pool.

  2. Описание LVM‑томов:

        lvm_vg = {
          pool = {
            type = "lvm_vg";
            lvs = {
              # Раздел подкачки
              swap = {
                size = "8G";
                content = {
                  type = "swap";
                  resumeDevice = true;
                };
              };
              
              # Корневой раздел
              root = {
                size = "100%";
                content = {
                  type = "filesystem";
                  format = "ext4";
                  mountpoint = "/";
                };
              };
            };
          };
        };

    Я думаю, вы заметили, что наш lvm_vg находится в одной секции с disk, а не внутри него. Как и говорилось в начале разбора обычной конфигурации диска, disko в disk.devices позволяет описывать нам не только диски, но и RAID‑массивы, LVM‑тома и прочее. Не суть.

    Теперь разберём всё построчно:

    • lvm_vg = { ... }; — объявляет секцию, где описываются Группы томов (Volume Groups) для LVM, среди которых наш pool, в который мы засунули luks‑раздел.

    • pool = { ... }; — указывает имя группы томов, которую мы настраиваем. Оно совпадает с vg = "pool" из нашего luks‑раздела.

    • pool.type = "lvm_vg"; — подтверждает тип блока: «Это группа томов LVM».

    • pool.lvs = { ... }; — открывает список Логических томов(Logical Volumes), которые мы хотим сделать из pool

    А дальше мы создаём два вирутальных диска: swap‑раздел и root‑раздел. Расскажу подробнее про swap‑раздел(или раздел подкачки):

    Файл подкачки (swap) — это область на диске, используемая для временного хранения данных, которые не помещаются в оперативную память (RAM). Когда оперативная память переполнена, система перемещает неактивные страницы памяти в swap, освобождая RAM для более приоритетных процессов. Это помогает предотвратить зависания системы, но доступ к данным на диске медленнее, чем к данным в RAM. Swap может быть реализован как отдельный раздел на диске или как файл.

    Источник: https://kb.fastfox.pro/vds/rabota‑s‑serverom/swap‑linux/

    Построчный разбор блока swap:

    swap = {
        size = "8G";
        content = {
            type = "swap";
            resumeDevice = true;
        };
    };
    • swap = { ... }; — создает логический том с именем swap. Компьютер увидит его как диск /dev/pool/swap.

    • size = "8G"; — выделяет под этот раздел 8 Гигабайт из общей памяти в pool.

    • type = "swap"; — указывает тип: «Это раздел подкачки».

    • resumeDevice = true; — важнейшая строка для ноутбуков. Она разрешает использовать этот раздел для гибернации(режима сна, когда всё содержимое оперативки сохраняется на диск и компьютер полностью выключается).

Шаг 3 — Финал! Применяем конфигурацию

После всех добавлений не забываем сохранить наш файл disko.nix при помощи :w, если используете Vim.

Дальше нам нужно ввести эту команду, которую мы сейчас разберём вплоть до каждого аргумента:

sudo nix --experimental-features "nix-command flakes" run github:nix-community/disko/latest -- --mode destroy,format,mount /tmp/disko-config.nix
  1. nix — команда пакетного менеджера Nix.

  2. --experimental-features "nix-command flakes" — временно включает экспериментальные функции Nix, необходимые для работы современных команд и Flakes(в следующей статье их тоже разберём)

  3. run — подкоманда Nix. Она скачивает, собирает(при необходимости) и временно запускает указанную программу без её постоянной установки в систему.

  4. github:nix-community/disko/latest — URL‑адрес репозитория, откуда Nix должен взять программу. В данном случае это последняя версия(latest) утилиты Disko от сообщества nix-community.

  5. -- — это разделитель аргументов Он сообщает Nix, что все последующие флаги и аргументы относятся не к самому Nix, а к запускаемой программе(Disko).

  6. --mode destroy,format,mount — ключевой флаг для Disko, задающий режим работы через запятую:

    1. destroy — полностью стирает старые таблицы разделов и данные на целевом диске.

    2. format — создает новые разделы и форматирует их в указанные файловые системы.

    3. mount — монтирует созданную структуру диска в директорию /mnt для последующей установки NixOS.

  7. /tmp/disko-config.nix — путь к нашему файлу конфигурации.

Что произойдёт после выполнения:

  1. Disko прочитает ваш конфиг

  2. Удалит старую разметку на диске, если та была(destroy)

  3. Создаст новые разделы(format)

  4. Отформатирует их в нужные файловые системы

  5. Создаст LUKS‑раздел и попросит ВВЕСТИ ПАРОЛЬ ШИФРОВАНИЯ.

  6. Создаст LVM‑тома внутри раздела

  7. Примонтирует всё в /mnt

После того как команда завершится, введём lsblk, чтобы осмотреть результаты нашей с вами работы:

$ lsblk
NAME           MAJ:MIN RM  SIZE    RO   TYPE MOUNTPOINTS
loop0          7:0    0    1.5G    1    loop /nix/.ro-store
sr0            11:0   1    1.6G    1    rom  /iso
vda            253:0  0    100G    0    disk  
├─vda1         253:1  0    500M    0    part /mnt/boot
├─vda2         253:2  0    99.5G   0    part  
│ └─crypted    254:0  0    99.5G   0    crypt
│  ├─pool-swap 254:1  0    8G  0   lvm  [SWAP]
│  └─pool-root 254:2  0    91.5G 0 lvm  /mnt

Послесловие

На этом этапе наши диски полностью подготовлены, отформатированы и смонтированы в директорию /mnt. Мы создали надежный фундамент для нашей операционной системы. Теперь вы можете спокойно закинуть свой disko-config.nix на github и при переустановке системы загрузить и применить его одной командой.

Что дальше?
Разумеется, оставлять пустые диски бессмысленно, поэтому в следующем материале мы перейдем к финальному этапу — установке самой конфигурации NixOS на подготовленный нами фундамент. Мы разберем структуру configuration.nix, настроим загрузчик, добавим пользователей.