Nix: начало

Всем привет!

Итак, от идейной теории перейдём к практике. Надо же, наконец, попробовать этот Nix на вкус…

Платформа

Я долго думал, где же проводить все свои эксперименты, и решил-таки делать их на NixOS, через VirtualBox. Конечно, заявлено о работе Nix и на OS X, но уж лучше на NixOS, нативнее будет. ;-) Сказано - сделано. Иду сюда и скачиваю последний (а именно версии 15.09) образ для VB. Импортирую, запускаю, вижу знакомый KDE. Начнём. Все манипуляции проводятся в Konsole.

ВАЖНО! У меня нет цели тупо продублировать официальное Nix-руководство на русском языке. В рамках этой и следующих статей я лишь хочу пройтись по интересующим меня практичным аспектам работы с Nix. Разумеется, особое ударение будет сделано на Haskell-разработку в Nix-среде. ;-)

Устанавливаем пакет

В начале мы имеем фактически голую систему, в которой нет даже Vim. Вот с него и начнём.

Простейший способ установить какой-либо пакет на NixOS таков: идём сюда и ищем пакет. Нахожу vim-7.4.827, нажимаю на него - и вижу следующее:

Install command:    $ nix-env -iA nixos.pkgs.vim (NixOS channel)
Nix expression:     pkgs/applications/editors/vim/default.nix
Platforms:          i686-linux, x86_64-linux
Homepage:           http://www.vim.org
License:            Not specified
Maintainers:        Jason O'Conal <[email protected]>
Long description:   Not specified

Первая строка - то что нам нужно, название Install command говорит само за себя. Ввожу:

$ nix-env -iA nixos.pkgs.vim
installing ‘vim-7.4.827’
these paths will be fetched (5.65 MiB download, 27.06 MiB unpacked):
  /nix/store/yblqgyrn4jgwfg89qp9041i0n2z26v5b-vim-7.4.827
fetching path ‘/nix/store/yblqgyrn4jgwfg89qp9041i0n2z26v5b-vim-7.4.827’...

*** Downloading ‘https://cache.nixos.org/nar/1pxi5nvpvfn27lxi789qjqb5gp2dbsabagc2y7ldnj8c20py9ix1.nar.xz’ (signed by ‘cache.nixos.org-1’) to ‘/nix/store/yblqgyrn4jgwfg89qp9041i0n2z26v5b-vim-7.4.827’...
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 5782k  100 5782k    0     0   601k      0  0:00:09  0:00:09 --:--:--  681k

building path(s) ‘/nix/store/77qhgp784j2qia0v9dj98b76cra70qmm-user-environment’
created 2 symlinks in user environment

Сразу после этого команда vim уже доступна в консоли. А теперь давайте разбираться.

Где

Во-первых, куда именно установился vim? Как мы уже знаем, Nix устанавливает каждый пакет в своё уникальное место, где-то в недрах /nix/store/. Более того, мы уже видели это место, оно было показано нам в сообщении при установке:

these paths will be fetched (5.65 MiB download, 27.06 MiB unpacked):
  /nix/store/yblqgyrn4jgwfg89qp9041i0n2z26v5b-vim-7.4.827

Давайте проверим:

$ ls /nix/store/ | grep vim
yblqgyrn4jgwfg89qp9041i0n2z26v5b-vim-7.4.827

Да, это тот самый хэш. Заглянем внутрь:

$ ls /nix/store/yblqgyrn4jgwfg89qp9041i0n2z26v5b-vim-7.4.827/
bin share

Вполне ожидаемое содержимое: внутри bin лежат исполняемые файлы (такие как vim, vimdiff, vimtutor), а внутри share/vim/vim74/ - Vim-плагины.

Профиль и окружение

Итак, vim теперь живёт в своём уютном мирке. Однако если мы проверим, откуда доступна команда vim, то увидим следующее:

$ which vim
/home/demo/.nix-profile/bin/vim

Хм… Странно. Разве мы не должны были увидеть /nix/store/yblqgyrn4jgwfg89qp9041i0n2z26v5b-vim-7.4.827/bin/vim? Что это за ~/.nix-profile/ такой?

Посмотрим-ка на него внимательнее:

$ ls -al ~/.nix-profile
... .nix-profile -> /nix/var/nix/profiles/per-user/demo/profile

Ага, перед нами символьная ссылка, и ведёт она куда-то в недра /nix. Заглянем поглубже:

$ ls -al /nix/var/nix/profiles/per-user/demo/profile
... /nix/var/nix/profiles/per-user/demo/profile -> profile-1-link

Опять ссылка. Глубже:

$ ls -al /nix/var/nix/profiles/per-user/demo/profile-1-link
... /nix/var/nix/profiles/per-user/demo/profile-1-link -> /nix/store/77qhgp784j2qia0v9dj98b76cra70qmm-user-environment

И вновь ссылка, на этот раз на нечто любопытное: в хранилище /nix/store мы видим некое user-environment. Так, пользовательское окружение… Уж не то ли самое это окружение, куда устанавливается мой софт? Проверим:

$ ls -al /nix/store/77qhgp784j2qia0v9dj98b76cra70qmm-user-environment/
total 92
... bin -> /nix/store/yblqgyrn4jgwfg89qp9041i0n2z26v5b-vim-7.4.827/bin
... manifest.nix -> /nix/store/2i6jvy2q2m8gcgiszsh4zw5dvx7133hd-env-manifest.nix
... share -> /nix/store/yblqgyrn4jgwfg89qp9041i0n2z26v5b-vim-7.4.827/share

Так вот же оно! Внутри моего окружения я вижу ссылки на части установленного мною vim. Теперь-то понятно, почему команда vim была доступна через ~/.nix-profile/: это просто цепочка ссылок. Концептуально её можно изобразить так:

my-profile -> user-environment -> software

Только что мы узнали нечто очень важное. Оказывается, утверждение, что “у каждого пользователя есть своё собственное окружение, куда он устанавливает софт”, не совсем корректно, ибо может ввести нас в заблуждение. Складывается впечатление, будто в моём домашнем каталоге есть какой-то особый каталог, в который устанавливаются мои пакеты, типа /home/demo/.my-nix-packages/trali-vali/vim-7.4.827/. В действительности же это не так: все пакеты хранятся в /nix/store, а в моём профиле есть лишь упоминание о некоторых из них.

Кстати, а давайте проверим эту догадку. Вдруг мы неправы?

Общий vim

Создадим-ка второго пользователя, по имени demo2, и, войдя под ним, проверим:

$ vim
vim: command not found

Мы знаем, что vim в системе уже существует, но пользователь demo2 не знает об этом. Давайте повторим процедуру установки vim:

$ nix-env -iA nixos.pkgs.vim
installing ‘vim-7.4.827’

Опа, как мы и предполагали! Пользователь demo2 запросил установку пакета vim, но поскольку этот пакет уже живёт в нашей системе, никакой установки в действительности не произошло. Уверен, вы уже догадались, что же произошло на самом деле: в окружении пользователя demo2 просто появились символьные ссылки на тот же самый пакет vim. Убедимся же в этом:

$ which vim
/home/demo2/.nix-profile/bin/vim

$ ls -al ~/.nix-profile
... /home/demo2/.nix-profile -> /nix/var/nix/profiles/per-user/demo2/profile

$ ls -al /nix/var/nix/profiles/per-user/demo2/profile
... /nix/var/nix/profiles/per-user/demo2/profile -> profile-1-link

$ ls -al /nix/var/nix/profiles/per-user/demo2/profile-1-link
... /nix/var/nix/profiles/per-user/demo2/profile-1-link -> /nix/store/77qhgp784j2qia0v9dj98b76cra70qmm-user-environment

$ ls -al /nix/store/77qhgp784j2qia0v9dj98b76cra70qmm-user-environment
total 92
... bin -> /nix/store/yblqgyrn4jgwfg89qp9041i0n2z26v5b-vim-7.4.827/bin
... manifest.nix -> /nix/store/2i6jvy2q2m8gcgiszsh4zw5dvx7133hd-env-manifest.nix
... share -> /nix/store/yblqgyrn4jgwfg89qp9041i0n2z26v5b-vim-7.4.827/share

Это именно то, что мы и ожидали увидеть: тот же самый хэш, та же самая версия vim! Таким образом, концептуально сложившуюся ситуацию можно изобразить так:

demo-profile  -> /nix/store/user-environment -> /nix/store/vim-7.4.827
              /
demo2-profile

Два пользователя пользуются общим пакетом vim! Это в высшей степени рациональный подход, поскольку нет никакого дубляжа. Даже если бы в нашей системе было сто пользователей, то, когда все они запросят установку пакета vim одной и той же версии, тогда один-единственный пакет vim просто расшарится между ними, через ссылки в их профилях.

Видит око, да зуб неймёт

Постойте, возразите вы, но если два пользователя demo и demo2 пользуются одним и тем же пакетом vim - это же опасно! А что если demo2 возьмёт и удалит его! Ну или сломает чего-нибудь! Это что же, у demo vim тоже исчезнет или поломается??!

Резонное возражение. Давайте похулиганим от имени demo2. Попробуем переименовать исполняемый файл vim:

$ cd /nix/store/yblqgyrn4jgwfg89qp9041i0n2z26v5b-vim-7.4.827/bin

$ mv vim vimTraliVali
mv: cannot move ‘vim’ to ‘vimTraliVali’: Read-only file system

Ага, разбежались! Вы должны помнить из предыдущей статьи, что Nix воспринимает пакеты так же, как значения в Haskell-коде: будучи единожды созданными, они уже не могут быть изменены. В этом и заключается Nix-чистота (purity). Оба пользователя ссылаются на пакет vim, но ни один из них не владеет им.

Ну хорошо, возразите вы вновь, но ведь Nix же умеет удалять пакеты?! Ладно, не будем ничего ломать, но что произойдёт, если demo2 удалит vim?

Разумеется, Nix умеет удалять пакеты, сделаем же это:

$ nix-env --uninstall vim
uninstalling ‘vim-7.4.827’
building path(s) ‘/nix/store/0fw4wpbrgix115lh93ahfik2r0hvv7n0-user-environment’
created 0 symlinks in user environment

$ vim
vim: command not found

О нет, что же мы натворили-то?! Как же теперь быть бедному demo? А давайте проверим. Перелогинимся под demo и:

$ vim --version
VIM - Vi IMproved 7.4 (2013 Aug 10, compiled Jan 01 1970 00:00:01)
Included patches: 1-827

Магия? Вовсе нет. Когда мы выполнили команду:

$ nix-env --uninstall vim

никакого удаления в действительности не произошло. В самом деле, если бы пакет vim реально исчез - как бы тогда его мог видеть пользователь demo? Как вы уже, вероятно, догадались, произошло только одно: удаление той самой символьной ссылки на пакет vim из профиля demo2. И это вполне логично: если с конкретным пакетом пользователя связывает лишь соответствующая ссылка из его профиля, тогда удаление пакета сводится к удалению этой ссылки. Это похоже на указатель в языке C: если мы потеряли некую “ссылку” на область памяти в куче - всё, эта память потеряна для нас навсегда, однако это вовсе не означает, что эта память тут же была очищена, ведь на эту же область памяти вполне может ссылаться кто-то другой.

Пока пользователь demo продолжает ссылаться на пакет vim, этот пакет никак не может быть удалён. Оно и понятно: если бы мы могли удалить пакет, на который кто-то ссылается, тогда у кого-то что-нибудь сломалось бы, а это недопустимо.

Ну хорошо, а что же будет, если и demo удалит vim? Сказано - сделано:

$ nix-env --uninstall vim
uninstalling ‘vim-7.4.827’

$ vim
vim: command not found

Всё, скончался наш vim. Впрочем, скончался ли? Ведь мы же помним тот самый хэш, ну-ка проверим:

$ ls -al /nix/store/yblqgyrn4jgwfg89qp9041i0n2z26v5b-vim-7.4.827/bin/vim
... /nix/store/yblqgyrn4jgwfg89qp9041i0n2z26v5b-vim-7.4.827/bin/vim

Опа… А почему же vim никуда не исчез? А потому что мы мусор не почистили.

Помните идею GC (сборщика мусора)? Удалять только то, что никому не нужно. Так вот в Nix есть собственный GC. Пакет vim больше никому не нужен, ведь оба пользователя, когда-то работающие с ним, удалили его. Значит, теперь можно почистить мусор. Это не происходит автоматически, нужно запустить команду:

$ nix-collect-garbage -d

И вот теперь - всё. Помимо всего прочего, мы увидим строку:

deleting ‘/nix/store/yblqgyrn4jgwfg89qp9041i0n2z26v5b-vim-7.4.827’

Покойся с миром, vim.

А если воскресить?

Давайте заново установим vim. В конце концов, он мне нужен, привык я к нему:

$ nix-env -iA nixos.pkgs.vim
installing ‘vim-7.4.827’
...

И вот vim вновь с нами. Но самое интересное в том, что если мы посмотрим в /nix/store/, то увидим:

$ ls /nix/store/ | grep vim
yblqgyrn4jgwfg89qp9041i0n2z26v5b-vim-7.4.827

Круто, правда? Хэш-то тот же самый! А всё потому, что это не просто какое-то случайное криптозначение, а хэш от входных конфигурационных параметров для данного пакета. Ну а раз vim тот же самый и входные параметры те же - значит и хэш останется прежним. И сколько бы раз мы ни удаляли и не устанавливали пакет vim одной и той же версии - результат будет гарантированно одинаковым. Да-да, это хорошо нам знакомая философия чистой функции - если на вход подали те же значения, то и на выходе всегда получим одно и то же.

В итоге

В итоге мы поняли, что Nix - весьма умная система. Лишнюю работу не делает, чистоту хранит, безопасно удаляет. Ну точно в духе Haskell! ;-)

Разумеется, всё вышеизложенное - это лишь азы азов. А вот в следующих статьях начнутся вещи поинтереснее.