Мы уже делали обзор NixOS, весьма необычном и удобном Linux-дистрибутиве, позволяющем описать всю конфигурацию системы в одном файле и развернуть ее на любой машине в любой момент. В той статье мы познакомили вас лишь с особенностями системы. Сегодня я расскажу о том, как пользоваться NixOS, решать повседневные задачи и выходить из трудных ситуаций.
Упомяну, что значит NixOS для меня. Долгие время я использовал Gentoo, и NixOS стал для меня Gentoo done right, когда, несмотря на гибкость системы и возможность простой сборки всего необходимого из исходных кодов, я получил глобальный бинарный кеш для аналогичных базовым пакетов, а также простоту развертывания.
NixOS также служит отличным примером того, как нужно работать с комьюнити, — запросы на слияние (pull request) рассматриваются намного быстрее и качественнее всех существующих на данный момент дистрибутивов. Даже если вы не занимаетесь непосредственно разработкой, вы не столкнетесь со старыми бюрократами из таких проектов, как Debian и Gentoo.
Из последнего следует один немаловажный и крайне приятный факт: ни оверлеи (overlay — сторонние репозитории, содержащие сценарии сборки ebuild для пакетов), ни их аналоги из других дистрибутивов больше не являются необходимостью. Вы можете отправлять свои патчи сразу в апстрим!
- Готовые конфигурации
- Сборка пакета из исходников
- Бинарные пакеты и FHS
- Приятные мелочи
- Hardened
- Хранение секретов для публичных конфигураций
- Декларативные контейнеры
- Декларативная генерация виртуальных машин
- Стабильность
- Более радикальное решение проблем с FHS
- Home manager
- Чем вы можете помочь NixOS?
- Где получить помощь?
- Вместо заключения
Готовые конфигурации
Так как дистрибутив NixOS предоставляет простую возможность распространять настройки своих машин так же, как и уже ставшая обычной традиция делиться своими конфигурациями для GNU Emacs, мы можем использовать подготовленные другими пользователями конфигурации в качестве базы для своей, тем самым не решая те проблемы, которые уже кто-то решал.
Для NixOS существует проект Simple Nixos Mailserver, который предоставляет готовую конфигурацию для почтового сервера. Я использую этот проект, а полный пример конфигурации можно посмотреть на моем Git-сервере.
Мы можем включить чужую конфигурацию, забрав ее с любого ресурса (добавь эти строки в configuration.nix):
1 2 3 4 5 6 7 |
imports = [ ... (builtins.fetchTarball { url = "https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/archive/v2.2.0/nixos-mailserver-v2.2.0.tar.gz"; sha256 = "0gqzgy50hgb5zmdjiffaqp277a68564vflfpjvk1gv6079zahksc"; }) ]; |
SHA-256 позволяет быть уверенным в аутентичности используемого архива.
Также вместо fetchTarball можно использовать fetchGit. Далее мы уже работаем с конфигурацией mailserver таким же образом, как если бы это было просто локальное включение файлов:
1 2 3 4 5 |
mailserver = { enable = true; fqdn = "mail.dumpstack.io"; ... }; |
Далее нам остается только (тут я намеренно опускаю настройку DNS, она не касается непосредственно настроек системы, и подобным придется заниматься что на NixOS, что на Ubuntu, что на других дистрибутивах) указать пользователей, alias’ы et cetera.
Таким же образом мы можем распространять что угодно, например управлять базовым слоем конфигурации для компании — настройкой домена, стандартных плагинов для браузера и так далее. Большой плюс данного подхода — он не ограничивает свободу пользователей, работающих в компании, так как нет чувства, что вашим компом управляет кто-то другой: человек может посмотреть на обновленную конфигурацию, изменив URL «перейти на нее», и продолжать работать.
Или, быть может, вам нужно работать со звуком и понадобилось реалтайм-аудио? Просто воспользуйтесь готовой конфигурацией от musnix.
Сборка пакета из исходников
Система сборки пакетов Nix немного напоминает систему ebuild для Gentoo, когда большую часть работы выполняет сам пакетный менеджер, который содержит информацию о том, как собирать при наличии Makefile, CMakeLists et cetera.
Сборочный файл для GNU Hello совсем не будет содержать указаний на то, как собирать:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
{ stdenv, fetchurl }: stdenv.mkDerivation rec { name = "hello-${version}"; version = "2.10"; src = fetchurl { url = "mirror://gnu/hello/${name}.tar.gz"; sha256 = "0ssi1wpaf7plaswqqjwigppsg5fyh99vdlb9kzl7c9lng89ndq1i"; }; doCheck = true; meta = with stdenv.lib; { description = "A program that produces a familiar, friendly greeting"; longDescription = '' GNU Hello is a program that prints "Hello, world!" when you run it. It is fully customizable. ''; homepage = https://www.gnu.org/software/hello/manual/; license = licenses.gpl3Plus; maintainers = [ maintainers.eelco ]; platforms = platforms.all; }; } |
Для сборки и установки необходимо только записать эти строки в файл (hello.nix, например) и выполнить следующую команду:
1 |
# nix-env -r -f ./hello.nix -i |
Если понадобится совершить какие-либо нестандартные действия (частично это будет рассмотрено на примере работы с бинарными пакетами далее), достаточно переопределить конкретные этапы.
В этой статье мы не будем подробно рассматривать все случаи сборки различных приложений, ибо вариантов бесчисленное множество. Не смотря на это, я советую посмотреть своими глазами на то, каким образом составляются описания для сборки различного ПО в репозитории nixpkgs. Я крайне редко (буду честным — никогда) пишу описания сборки с нуля, в nixpkgs легко найти пример для разных систем сборки, а также великолепный набор костылей на для любой ситуации.
Бинарные пакеты и FHS
NixOS отличается от других дистрибутивов тем, что разработчикам дистрибутива пришлось отказаться от совместимости с FHS — стандартом, определяющим местоположение библиотек, исполняемых файлов, конфигурации и прочего. Из-за этого бинарные утилиты, собранные для других дистрибутивов, без изменений работать не смогут: каталогов /usr/share и /usr/lib в NixOS попросту нет.
При этом, если разработчик проприетарного программного обеспечения будет собирать для Nix, ему это будет только в плюс, так как сам пакетный менеджер Nix на данный момент работает на всех дистрибутивах (включая Ubuntu, Fedora, Debian), а простая работа с зависимостями позволит не беспокоиться о том, заработает ли оно на конкретном.
Например, для создания пакета для BinaryNinja нам необязательно костылить с изменением бинарных файлов и зависимостей вручную (за исключением libpython, но это уже не зависит непосредственно от Nix).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
with import <nixpkgs> {}; pkgs.stdenv.mkDerivation { name = "binary-ninja"; src = ./BinaryNinja.zip; installPhase = '' mkdir -p $out/share/binary-ninja cp -r * $out/share/binary-ninja ln -s ${pkgs.python2}/lib/libpython2.7.so.1.0 $out/share/binary-ninja/libpython2.7.so.1 mkdir -p $out/bin ln -s $out/share/binary-ninja/binaryninja $out/bin/ ''; nativeBuildInputs = [ autoPatchelfHook ]; buildInputs = [ unzip stdenv.cc.cc glib fontconfig dbus libglvnd xlibs.libX11 xlibs.libXi xlibs.libXrender python2 ]; } |
Пояснения излишни — мы имеем некоторый бойлерплейт описания сборки, а также список зависимостей.
Далее достаточно выполнить команду
1 |
# nix-env -r -f ./binaryninja.nix -i |
После чего binaryninja будет установлен на уровне пользователя, но с управлением через менеджер пакетов.
Но есть нюанс, который нужно помнить: в том случае, если это бинарное ПО, которое хочет обновляться самостоятельно (как binaryninja), оно это сделать не сможет, так как каталог приложения при установке через пакетный менеджер доступен только для чтения.
Приятные мелочи
Для Chromium существует возможность управлять плагинами со стороны системы (небольшое описание есть тут).
NixOS позволяет управлять плагинами для Chromium прямо в конфигурации для системы. Например, чтобы установить по умолчанию HTTPS Everywhere и блокировщик рекламы uBlock Origin, достаточно всего лишь определить их в списке extensions:
1 2 3 4 5 6 7 |
programs.chromium = { enable = true; extensions = [ "cjpalhdlnbpafiamejdnhcphjbkeiagm" # uBlock Origin "gcbommkclmclpchllfjekcdonpmejbdp" # HTTPS Everywhere ]; }; |
Hardened
Для NixOS существует профиль Hardened, который объединяет в себе полезные для безопасности настройки системы. И у вас есть возможность откатить некоторые из них. Например, я делаю так:
1 2 3 4 5 6 7 8 |
imports = [ # Include the results of the hardware scan. ./hardware-configuration.nix <nixpkgs/nixos/modules/profiles/hardened.nix> ]; security.allowUserNamespaces = true; security.allowSimultaneousMultithreading = true; |
В данном случае я использую hardened-опции по умолчанию, но некоторые из них возвращаю в нужное мне состояние.
Значительно большего уровня защищенности можно достигнуть использованием специализированного hardened-ядра.
Все hardened-ядра в NixOS имеют постфикс _hardened, соответственно, возможные варианты можно найти простым поиском по пакетам (также я добавляю .kernel, так как по умолчанию nix search покажет и модули ядра):
1 |
# nix search linuxPackages '_hardened\.kernel' |
К примеру, на момент написания статьи были доступны следующие ядра:
1 2 3 4 5 |
* nixpkgs.linuxPackages_hardened.kernel (linux-4.14.105) * nixpkgs.linuxPackages_latest_hardened.kernel (linux-5.0) * nixpkgs.linuxPackages_latest_xen_dom0_hardened.kernel (linux-5.0) * nixpkgs.linuxPackages_testing_hardened.kernel (linux-5.0-rc8) * nixpkgs.linuxPackages_xen_dom0_hardened.kernel (linux-4.14.105) |
Подобным образом можно искать и обычные ядра:
1 |
# nix search linuxPackages '\.kernel' |
Стоит отметить, что hardened-ядра не включают опции из профиля Hardened по умолчанию, тем самым гибкость снижается только для той конфигурации ядра, которую невозможно изменять после загрузки посредством интерфейсов ядра.
Далее в configuration.nix указываем необходимое нам ядро, например:
1 |
boot.kernelPackages = pkgs.linuxPackages_latest_hardened; |
После чего ставший уже привычным nixos-rebuild switch и в случае со сменой ядра — перезагрузка (или kexec).
Хранение секретов для публичных конфигураций
Самый простой способ для того случая, когда вы распространяете конфигурацию, — использовать включение конфигурации через import. Например:
1 2 3 4 5 6 |
let secrets = import ./secrets.nix; in { users.extraUsers.root = { openssh.authorizedKeys.keys = [ secrets.pubkey ]; }; |
Декларативные контейнеры
Большинство из нас знает, многие любят и постоянно применяют, а некоторые даже изобретают свои системы контейнерной виртуализации, позволяющие запускать отдельные приложения или целые операционные системы, используя ядро хостовой ОС.
Что меня восхищает в контейнерах в NixOS — возможность использовать те же самые декларации, которые использовались ранее для хостовой системы. По сути, единственное, что отличает контейнер от основной системы, — другое пространство имен (namespace) в конфигурационном файле.
В качестве примера покажу одну из деклараций контейнера, которая у меня осталась где-то в конфигурациях. В данном случае определен контейнер с версией Chromium из релиза NixOS 18.03.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
containers.test = { autoStart = true; config = { config, pkgs, ... }: let stableTarball = fetchTarball "${tarball-base}/nixos-18.03.tar.gz"; in { nixpkgs.config = { allowUnfree = true; packageOverrides = pkgs: { stable = import stableTarball { config = config.nixpkgs.config; }; }; }; environment.systemPackages = with pkgs; [ stable.chromium ]; }; }; |
То есть, как вы уже могли заметить, только небольшой «контейнерный бойлерплейт», и мы получаем логически изолированную среду. При этом стоит отметить, что контейнеры в NixOS не предполагают изоляцию с целью усиления безопасности.
Декларативная генерация виртуальных машин
Вот ссылка на свою предыдущую статью про AppVM, которую я писал, еще когда пользовал Gentoo в качестве хостовой системы.
Использование виртуальных машин в рамках только пакетного менеджера Nix в других дистрибутивах и использование Nix в рамках NixOS в данном случае ничего не изменит. Тут можно продолжить оды Nix за внесение универсальности не только в рамках «материнского дистрибутива», но и в общем в экосистеме GNU/Linux, но буду придерживаться главной цели этой статьи и показывать больше непосредственно примеры использования в повседневной жизни.
Стабильность
Как-то раз я случайно обнаружил, что поставил новую систему из unstable-ветви. В NixOS unstable-ветвь собирается из тех пакетов, которые добавили непосредственно в Git-репозиторий nixpgs на GitHub и которые после успешно собрались. Тем самым я оказался на весьма суровом bleeding edge.
Очевидным результатом стало то, что однажды (на самом деле это было в первый день) я все-таки отхватил баг — virt-manager не работал.
Что я мог бы сделать, если бы использовал классические дистрибутивы? Добавить репозиторий со старыми пакетами, установить низкий приоритет, попробовать установить пакет и после — начать страдать от безысходности из-за того, что прошлая версия пакета требует одной версии определенной библиотеки, а другой установленный пакет в системе — другой версии этой же библиотеки.
В случае NixOS подобных проблем быть не может. Вопрос решился добавлением стабильной ветки:
1 2 3 4 5 6 7 8 |
nixpkgs.config = { allowUnfree = true; packageOverrides = pkgs: { stable = import (fetchTarball "${tarball-base}/nixos-18.09.tar.gz") { config = config.nixpkgs.config; }; }; }; |
где tarball-base определяется ранее:
1 2 3 4 |
let tarball-base = "https://github.com/NixOS/nixpkgs-channels/archive/"; in { ... |
После чего в списке используемых пакетов мне достаточно использовать префикс stable. для того, чтобы устанавливать пакеты из стабильных ветвей, например:
1 2 3 4 5 |
environment.systemPackages = with pkgs; [ ... stable.virt-manager ... ]; |
Для простоты понимания того, какая версия используется, есть возможность указывать симлинки явно, например:
1 2 3 4 5 |
environment.systemPackages = with pkgs; [ ... (pkgs.writeShellScriptBin "virt-manager-unstable" "${virtmanager}/bin/virt-manager [email protected]") (pkgs.writeShellScriptBin "virt-manager-stable" "${stable.virtmanager}/bin/virt-manager [email protected]") ]; |
Таким же образом можно использовать любой из снимков состояния репозитория nixpkgs для того, чтобы получать нужные версии.
Например, мы хотим найти версию снимка состояния репозитория nixpgs, в котором virt-manager имел версию 2.0.
Для этого нам достаточно перейти в каталог с нужным пакетом:
1 |
# cd nixpkgs/pkgs/applications/virtualization/virt-manager |
После чего, запустив git log, найти коммит (простым поиском по номеру версии), в котором данная версия была добавлена. Например, virt-manager 2.0 был добавлен в коммите db7e9408a1b5c0d7d103c28b91646b374968b045:
1 2 3 4 5 |
commit db7e9408a1b5c0d7d103c28b91646b374968b045 Author: Gabriel Ebner <gebner@gebner.org> Date: Wed Nov 7 22:25:09 2018 +0100 virtmanager: 1.5.1 -> 2.0.0 |
Следовательно, для получения virt-manager версии 2.0 нам достаточно использовать архив с этого коммита:
1 2 3 4 5 6 7 8 9 10 11 |
nixpkgs.config = { allowUnfree = true; packageOverrides = pkgs: { stable = import (fetchTarball "${tarball-base}/nixos-18.09.tar.gz") { config = config.nixpkgs.config; }; commit1 = import (fetchTarball "${tarball-base}/db7e9408a1b5c0d7d103c28b91646b374968b045.tar.gz") { config = config.nixpkgs.config; }; }; }; |
После мы можем таким же образом, как и ранее, добавить в bin исполняемый файл для определенной версии.
1 2 3 4 5 6 |
environment.systemPackages = with pkgs; [ ... (pkgs.writeShellScriptBin "virt-manager-unstable" "${virtmanager}/bin/virt-manager [email protected]") (pkgs.writeShellScriptBin "virt-manager-stable" "${stable.virtmanager}/bin/virt-manager [email protected]") (pkgs.writeShellScriptBin "virt-manager-2.0.0" "${commit1.virtmanager}/bin/virt-manager [email protected]") ]; |
Итого мы имеем virt-manager из последней стабильной ветки 18.09, той версии, которая на данный момент находится в Git, а также версии 2.0.0.
Это подход со стороны основной конфигурации системы. Таким образом можно определять каждую из версий пакетов, но значительный минус данного подхода в том, что каждый архив должен быть загружен, соответственно есть некоторый оверхед по размеру файлов.
Альтернативный вариант — установка Nix-пакета от пользователя, в этом случае достаточно просто запустить команду в каталоге с описанием для сборки.
Или же можно вернуться на стабильную ветку из unstable. Предположим, мы хотим переключиться на следующий релиз 19.03, для этого достаточно сделать так:
1 2 3 |
# nix-channel --remove nixos-19.03 # nix-channel --add https://nixos.org/channels/nixos-19.03 nixos # nixos-rebuild switch --upgrade |
Проблемы с даунгрейдом системы целиком? В разумных пределах не должны быть, а те, что будут, связаны с конфигурацией из вашего домашнего каталога, но никак не с базовой системой.
Каналы также можно использовать напрямую, например если мы добавим каналы для стабильного релиза, нестабильного и следующего планируемого стабильного.
1 2 3 4 |
nix-channel --add https://nixos.org/channels/nixos-19.03 nixos nix-channel --add https://nixos.org/channels/nixos-18.09 stable nix-channel --add https://nixos.org/channels/nixos-unstable unstable nix-channel --update |
Тот, что назван nixos, будет использоваться по умолчанию.
Далее мы можем определить каналы и использовать их для установки программ:
1 2 3 4 5 6 7 8 9 10 11 |
let unstable = import <unstable> {}; stable = import <stable> {}; in { ... environment.systemPackages = with pkgs; [ ... unstable.chromium ]; ... } |
Для того чтобы не пропустить улучшения в плане работы с тем, что было описано выше, советую заглянуть в статью на NixOS Wiki.
Более радикальное решение проблем с FHS
Проблема несовместимости NixOS с FHS в основном затрагивает бинарные пакеты (суть проблемы я описал в соответствующем разделе), но часто бывает, что написанный очень странными людьми код собирается, только если в системе есть условный /usr/lib/libastral.so по жестко заданному пути, и вне FHS-совместимого окружения собирать становится невыносимо.
Решение с использованием Docker в этом случае довольно очевидно и приземленно, поэтому оставим его хипстерам. Для своих задач я предпочитаю использовать обычное chroot-окружение с Debian, и я покажу, как его готовить таким образом, чтобы он не доставлял проблем.
Для начала просто генерируем chroot с Debian или любым другим дистрибутивом, который мы будем использовать для FHS окружения (я использую Debian и Gentoo):
1 2 |
# cd /home/user/chroots # debootstrap sid sid-root http://deb.debian.org/debian/ |
После чего добавляем в configuration.nix сервис с mount-костылями, который будет монтировать все нужные нам каталоги. Самое важное из этого — привязка (bind) домашнего каталога хостовой системы к системе в chroot.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
systemd = { services = { "sid-chroot-mounts" = { enable = true; description = "Setup mounts for debian sid chroot"; wantedBy = [ "multi-user.target" ]; script = '' ls /home/user/chroots/sid-root/home/user/.zshrc && exit ${pkgs.utillinux}/bin/mount --bind /home/user /home/user/chroots/sid-root/home/user ${pkgs.utillinux}/bin/mount --bind /dev /home/user/chroots/sid-root/dev ${pkgs.utillinux}/bin/mount --bind /proc /home/user/chroots/sid-root/proc ${pkgs.utillinux}/bin/mount --bind /sys /home/user/chroots/sid-root/sys ''; serviceConfig.Type = "oneshot"; }; }; }; |
Далее добавляем в скрипт инициализацию используемого шелла (у меня это .zshrc):
1 |
alias fhs="su -c 'chroot /home/user/chroots/sid-root /bin/su -l user'" |
Теперь достаточно набрать команду fhs, и мы окажемся в окружении Debian, использующем наш родной домашний каталог. Разве не круто?
Далее, для того чтобы не перепутать окружения, я советую добавить небольшую проверку в тот же файл инициализации шелла.
1 |
which apt >/dev/null 2>&1 && PS1="fhs-chroot $PS1" |
Что мы получили в итоге?
1 2 3 |
user@local ~ $ fhs Password: debian user@local ~ $ |
Home manager
Некоторые пользователи идут дальше и используют такой же подход к конфигурированию не только системы, но и домашнего каталога пользователя. Здесь стоит отметить, что это будет эффективно не только на NixOS, так как home-manager от Rycee работает на уровне пользователя.
Например, Nix + home-manager можно использовать на macOS.
Чем вы можете помочь NixOS?
На данный момент активна инициатива Zero Hydra Failures, которая предполагает устранение всех ошибок к следующему релизу 19.03.
Где получить помощь?
Англоязычную поддержку можно получить в канале #nixos на freenode, полный список каналов можно найти в списке c NixOS wiki.
Если вам нужна поддержка на русском языке, можете создать треды с тегами nix и nixos на ЛОРе, где на данный момент отвечают некоторые из русскоязычных разработчиков NixOS.
Вместо заключения
Сегодня я приоткрыл часть повседневности пользователя NixOS. Можно видеть, что NixOS значительно упрощает базовый слой системы в рамках конфигурирования, а также позволяет не изобретать велосипед при использовании одной конфигурации на разных машинах, даже когда это не просто разные дистрибутивы, а разные операционные системы.