Делим конфиг NixOS на модули
К этому моменту у нас есть скелет системы, а текущая структура /etc/nixos
выглядит так:
$ tree
.
├── flake.lock
├── flake.nix
├── home.nix
└── configuration.nix
Роли четырех перечисленных файлов:
flake.lock
: автоматически сгенерированыый version-lock, хранящий используемые во флейке инпуты, их хэши и версии (обеспечивает "повторяемость" системы).flake.nix
: Условно, заголовок флейка, его ищетsudo nixos-rebuild switch
. С доступными там опциями можно ознакомиться в Flakes - NixOS Wiki.configuration.nix
: Nix-модуль, который мы импортируем в flake.nix, пока что там конфигурируется весь "системный" софт. См. Configuration - NixOS Manual для определения доступных опций.home.nix
: Импортируется через Home-Manager, устанавливает и конфигурирует софт для пользователяryan
. Опции: Appendix A. Configuration Options - Home-Manager.
Модификация configuration.nix
и home.nix
меняет состояние системы и домашней директории пользователя соответственно.
Однако с увеличением количества установленного софта configuration.nix
и home.nix
запросто могут стать огромными кусками трудночитаемого кода. Более удобный вариант - использовать систему модулей Nix, чтобы удобно разложить конфиги программ в отдельные файлы.
Для упрощения этой задачи в системе модулей Nix есть атрибут imports
. Он берет на вход список .nix
-файлов и импортирует их в текущий модуль. Стоит заметить, что imports
не переписывает повторяющиеся опции в конфиге. Например, если пользователь прописал program.packages = [...]
в нескольких модулях, imports
соединит все program.packages
в один список, с attrset-ами ситуация та же.
I only found a description of
imports
in Nixpkgs-Unstable Official Manual - evalModules Parameters:A list of modules. These are merged together to form the final configuration.
It's a bit ambiguous...
Ожидаемо, с помощью imports
, можно разделить home.nix
и configuration.nix
на несколько модулей, лежащих в своих .nix
-файлах.
Например, в ryan4yin/nix-config/v0.0.2 можно найти конфиг NixOS [автора книги], в котором используется оконник i3. Структура конфига такая:
├── flake.lock
├── flake.nix
├── home
│ ├── default.nix # тут используется imports = [...] для импорта других модулей
│ ├── fcitx5 # конфигурация fcitx5 (см. главу про оверрайды/оверлеи)
│ │ ├── default.nix
│ │ └── rime-data-flypy
│ ├── i3 # конфиги оконника
│ │ ├── config
│ │ ├── default.nix
│ │ ├── i3blocks.conf
│ │ ├── keybindings
│ │ └── scripts
│ ├── programs
│ │ ├── browsers.nix
│ │ ├── common.nix
│ │ ├── default.nix # Снова используется imports = [...]
│ │ ├── git.nix
│ │ ├── media.nix
│ │ ├── vscode.nix
│ │ └── xdg.nix
│ ├── rofi # рофленые конфиги rofi
│ │ ├── configs
│ │ │ ├── arc_dark_colors.rasi
│ │ │ ├── arc_dark_transparent_colors.rasi
│ │ │ ├── power-profiles.rasi
│ │ │ ├── powermenu.rasi
│ │ │ ├── rofidmenu.rasi
│ │ │ └── rofikeyhint.rasi
│ │ └── default.nix
│ └── shell # конфиги шелла/терминала
│ ├── common.nix
│ ├── default.nix
│ ├── nushell
│ │ ├── config.nu
│ │ ├── default.nix
│ │ └── env.nu
│ ├── starship.nix
│ └── terminals.nix
├── hosts
│ ├── msi-rtx4090 # Конфиги рабочей машины
│ │ ├── default.nix # когда-то был `configuration.nix`-ом, но потом сбросил вес и поделился на модули.
│ │ └── hardware-configuration.nix # результат хардварного скана, автоматически сгенерированный
│ └── nixos-test # тестовый конфиг
│ ├── default.nix
│ └── hardware-configuration.nix
├── modules # модули, пригодные к многократному использованию
│ ├── i3.nix
│ └── system.nix
└── wallpaper.jpg # обоина
Всем желающим посмотреть реализацию конфига в деталях предлагается пройти в упомянутую репу.
lib.mkOverride
, lib.mkDefault
и lib.mkForce
В ряде конфигов встречается использование lib.mkDefault
и lib.mkForce
для работы с дефолтными значениями параметров. Первая функция задает такое значение, вторая - форсирует его изменение.
Исходники этих функций можно посмотреть, запустив nix repl -f '<nixpkgs>'
и введя туда :e lib.mkDefault
, а если в nix repl
ввести :?
, он покажет свой мануал.
Итак, сырцы:
# ......
mkOverride = priority: content:
{ _type = "override";
inherit priority content;
};
mkOptionDefault = mkOverride 1500; # priority of option defaults
mkDefault = mkOverride 1000; # used in config sections of non-user modules to set a default
mkImageMediaOverride = mkOverride 60; # image media profiles can be derived by inclusion into host config, hence needing to override host config, but do allow user to mkForce
mkForce = mkOverride 50;
mkVMOverride = mkOverride 10; # used by ‘nixos-rebuild build-vm’
# ......
Понимаем, что lib.mkDefault
ставит опции приоритет 1000, а lib.mkForce
- 50. Если ввести значение атрибута руками, оно будет иметь тот же приоритет, что и с lib.mkDefault
(1000).
Чем ниже значение приоритета, тем "важнее" на самом деле значение. Соответственно, значение, установленное через lib.mkForce
перепишет установленное через lib.mkDefault
, а попытка несколько раз задать атрибуту значения с одним и тем же приоритетом приведут к ошибке.
При делении конфига на модули использование этих функций позволяет задать ряд дефолтных значений в базовом модуле и переписать их на что-то другое в модулях, основанных на нем.
Например, в конфигурации [автора] ryan4yin/nix-config/blob/main/modules/nixos/core-server.nix#L30 есть такой дефолт:
{ lib, pkgs, ... }:
{
# ......
nixpkgs.config.allowUnfree = lib.mkDefault false;
# ......
}
В дальнейшем в конфиге десктопа ryan4yin/nix-config/blob/main/modules/nixos/core-desktop.nix#L15 оно меняется на true
:
{ lib, pkgs, ... }:
{
# import the base module
imports = [
./core-server.nix
];
# override the default value defined in the base module
nixpkgs.config.allowUnfree = lib.mkForce true;
# ......
}
lib.mkOrder
, lib.mkBefore
и lib.mkAfter
Помимо lib.mkDefault
и lib.mkForce
, существуют lib.mkBefore
и lib.mkAfter
, определяющие приоритет объединения списков, что также полезно в задачах модуляризации.
Также lib.mkOrder
, lib.mkBefore
и lib.mkAfter
позволяют указать порядок объединения атрибутов с одинаковым приоритетом и не получить ошибку.
Посмотрим на исходники lib.mkBefore
: nix repl -f '<nixpkgs>'
, затем :e lib.mkBefore
:
# ......
mkOrder = priority: content:
{ _type = "order";
inherit priority content;
};
mkBefore = mkOrder 500;
mkAfter = mkOrder 1500;
# The default priority for things that don't have a priority specified.
defaultPriority = 100;
# ......
Понимаем, что lib.mkBefore
эквивалентен lib.mkOrder 500
, а lib.mkAfter
-- lib.mkOrder 1500
.
Для демонстрации использования lib.mkBefore
и lib.mkAfter
создадим небольшой флейк:
› cat <<EOF | sudo tee flake.nix
{
description = "Ryan's NixOS Flake";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.05";
};
outputs = { self, nixpkgs, ... }@inputs: {
nixosConfigurations = {
"nixos-test" = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
# Demo module 1: вставляем гит в начало списка
({lib, pkgs, ...}: {
environment.systemPackages = lib.mkBefore [pkgs.git];
})
# Demo module 2: вставляем 'vim' в конец списка
({lib, pkgs, ...}: {
environment.systemPackages = lib.mkAfter [pkgs.vim];
})
# Demo module 3: (просто) добавляем curl
({lib, pkgs, ...}: {
environment.systemPackages = with pkgs; [curl];
})
];
};
};
};
}
EOF
# создаем flake.lock
› nix flake update
› nix repl
Welcome to Nix 2.13.3. Type :? for help.
# загружаем созданный ранее флейк
nix-repl> :lf .
Added 9 variables.
# смотрим systemPackages
nix-repl> outputs.nixosConfigurations.nixos-test.config.environment.systemPackages
[ «derivation /nix/store/0xvn7ssrwa0ax646gl4hwn8cpi05zl9j-git-2.40.1.drv»
«derivation /nix/store/7x8qmbvfai68sf73zq9szs5q78mc0kny-curl-8.1.1.drv»
«derivation /nix/store/bly81l03kh0dfly9ix2ysps6kyn1hrjl-nixos-container.drv»
......
......
«derivation /nix/store/qpmpv
q5azka70lvamsca4g4sf55j8994-vim-9.0.1441.drv» ]
Видим, что порядок systemPackages
следующий: git -> curl -> дефолтные пакеты -> vim
, что соответствует порядку, заданному в flake.nix
.
Хотя изменение порядка пакетов в
systemPackages
никому не интересно, фича полезна в других сценариях.