// заметки
Nix rootless: сборка не стартует и store-хеш под GC
Локальная сборка падает с unable to start build process, а захардкоженный путь к nss-cacert удаляет nix-collect-garbage. Два фикса: sandbox и вычисление пути
Локальная сборка в rootless Nix (установка без демона и без root, store в $HOME) падает с error: unable to start build process, хотя готовые бинарники из cache.nixos.org скачиваются без ошибок. Вторая поломка тише: путь к корневым сертификатам захардкожен и уже числится в списке мёртвых, так что первый nix-collect-garbage ломает TLS. Ниже обе причины, два фикса и чек-лист безопасной уборки store
Контекст: CachyOS, Nix 2.24.10, установка без демона, store в $HOME/.nix. Номера версий зависят от канала, дальше речь о схеме: rootless-инсталляция и обвязка вокруг неё
Симптом
Сборка не стартует, загрузка идёт
Из кеша приехал stdenv (базовое окружение сборки), но derivation (рецепт сборки пакета), которую нужно собрать локально, не стартовала
$ nix build .#default --no-link --print-out-paths
copying path '/nix/store/...-stdenv-linux-no-cc' from 'https://cache.nixos.org'...
error: unable to start build processSubstituters (подстановка готовых бинарей из кеша) при этом работают. Ломаются только те derivations, которые нужно собрать локально
Сертификаты в списке мёртвых
Путь к корневым сертификатам был прописан константой
# было: путь зашит константой
# CACERT=/nix/store/<hash>-nss-cacert-<version>/etc/ssl/certs/ca-bundle.crt
# export NIX_SSL_CERT_FILE="$CACERT"nix-store --gc --print-dead показал этот путь среди мусора. Строка выглядит рабочей, пока конкретный nss-cacert жив. До поломки оставался один запуск nix-collect-garbage -d: после него сертификаты исчезли бы, а загрузки упали бы с невнятной TLS-ошибкой
Диагностика
Пять команд для диагностики
# режим песочницы
nix config show sandbox
# что GC планирует удалить, ничего не трогая
nix-store --gc --print-dead | head -30
# хардкоды store-путей в своей обвязке
grep -rn "/nix/store/[a-z0-9]\{32\}" ~/.local/bin/ ~/.config/direnv/ ~/.config/fish/
# целостность store и размер
nix-store --verify --check-contents
du -sh ~/.nixnix config show sandboxвозвращаетtrue— значит Nix пытается изолировать сборку--print-deadтолько печатает план: путь к nss-cacert в выводе означает, что его удалят- grep ищет 32-символьные store-хеши в ваших скриптах и конфигах direnv и fish
nix-store --verify --check-contentsпроверяет целостность store, если есть подозрение на повреждённые путиdu -sh ~/.nixполезен, чтобы понимать объём перед уборкой
Если --print-dead показывает пути из ваших скриптов, сначала замените их глобом или gcroot, и только потом запускайте уборку. Пустой вывод по grep означает, что зашитых хешей в обвязке нет
Причина
Сначала о сборке, затем о сертификатах: две нити идут параллельно и не пересекаются
Песочница требует вложенного user-namespace
Rootless-установка без демона работает в одном user-namespace, который поднимает обёртка вроде nix-enter (имя примерное). Для сборки Nix пытается создать второй, вложенный namespace, а вложенность в такой конфигурации запрещена. Один уровень работает, два — нет, поэтому sandbox = true сдаётся на старте. Установка и обёртка разобраны отдельно: Nix на CachyOS: установка невозможна — rootless-запуск
Substituters сборку не запускают: скачивание из кеша не требует песочницы. Отсюда простой признак — падает ровно то, что нужно собрать локально
Store-хеш — адрес конкретной сборки, а не константа
/nix/store/<hash>-<name> — это путь конкретного результата. Он живёт, пока на него ссылается профиль, gcroot или другая derivation. Захардкоженная строка сама путь не удерживает: он удалится, когда на него не останется ссылок профиля или gcroot
Решение
Сборка: sandbox = false и пустой build-users-group
# $HOME/.nix/etc/nix/nix.conf
sandbox = false
build-users-group =build-users-group = оставлен пустым: группы nixbld в rootless-установке нет, сборка идёт от текущего пользователя. Предупреждение про отсутствующую группу сборщиков после этого пропадает
Цена решения: изоляции локальных сборок нет. Код из flake.nix выполняется с правами пользователя, поэтому недоверенные flake так собирать нельзя
Сертификаты: глоб вместо константы
CACERT=""
for _c in /nix/store/*-nss-cacert-*/etc/ssl/certs/ca-bundle.crt; do
[ -r "$_c" ] && CACERT="$_c"
done
[ -n "$CACERT" ] || CACERT=/etc/ssl/certs/ca-certificates.crt
export NIX_SSL_CERT_FILE="$CACERT"Цикл перебирает все nss-cacert в store и запоминает последний читаемый файл. Если ни одного не осталось, берётся системный bundle. В fish-конфиге логика та же: обход глоба, затем фолбэк. Кэшировать найденное значение в конфиге нельзя: при следующей уборке оно снова может стать мёртвым, а глоб выполняется при каждом старте оболочки и берёт актуальный путь
Для путей, которые должны жить долго, остаётся gcroot: отдельная ссылка, удерживающая store-путь от удаления. Она требует шага после каждой пересборки, зато не зависит от текущего содержимого store
Проверка
Проверяем три вещи: режим песочницы, что сборка доходит до конца и что сертификаты читаются
nix config show sandbox # ожидаем false
nix build .#default --no-link --print-out-paths
test -r "$NIX_SSL_CERT_FILE" && echo "certs ok"nix config show sandbox должен вернуть false, иначе локальная сборка снова упрётся в sandbox. У nix build флаг --no-link оставляет результат в store, не трогая профиль, а --print-out-paths печатает путь: если команда дошла до конца и вернула путь, сборка прошла. test -r проверяет, что NIX_SSL_CERT_FILE указывает на читаемый файл, а не на удалённый путь. После уборки путь не обязан совпадать с прежним: глоб подхватит другой экземпляр nss-cacert, а если его не осталось — системный bundle, поэтому test -r важнее сверки со старой строкой
Перед nix-collect-garbage -d учтите: -d удаляет старые поколения профиля, и откатиться к ним уже не получится. Сначала посмотрите план через nix-store --gc --print-dead и историю через nix profile history, а при сомнениях снимите копию: cp -a ~/.nix ~/.nix.bak. Копия занимает место, зато возвращает store и профиль к состоянию до уборки
Чек-лист после nix-collect-garbage -d
ls ~/.nix/store/*-nix-direnv-*/share/nix-direnv/direnvrc— если пусто, вернуть пакет черезnix profile install nixpkgs#nix-direnvtest -r "$NIX_SSL_CERT_FILE"— сертификаты на месте, TLS не сломан- новый
fishв проекте:echo $APP_ENVне пуст,jq --versionпоказывает версию из store
Профиль после уборки ломать не страшно: обёртка, которая поднимает namespace, берёт Nix из store, а не из профиля. Что это за обёртка — в заметке Nix devShell в fish: PATH молча не подхватывается
Подводные камни
sandbox = trueне мешает скачиванию: substituters работают при любой настройке песочницы, поэтому на неё указывает падение только локальных сборок- Хардкод store-хеша сам путь не удерживает:
/nix/store/<hash>-<name>удалится, когда на него не останется ссылок профиля или gcroot. Либо глоб, либо gcroot nix-collect-garbage -dнеобратим для старых поколений профиля: откат к удалённому поколению невозможен, поэтому план смотрят до запуска- Мёртвость пути проверяют до уборки:
--print-deadпоказывает план удаления, не меняя store - Отключение песочницы расширяет доверие: локальные сборки недоверенного кода идут с вашими правами
- Тот же класс ошибок — вшитые store-пути в обвязке nix-direnv: nix-direnv: «minimum required bash version is 4.4»
Итог
Две поломки выглядят разными, но растут из одного: rootless-установка живёт без демона и без прав на изоляцию. sandbox = false возвращает локальные сборки, глоб по store убирает зависимость от конкретного хеша, а nix-store --gc --print-dead перед уборкой показывает, что вы потеряете. Обёртка, которая поднимает namespace, разобрана отдельно: Nix на CachyOS: установка невозможна — rootless-запуск
Похожие заметки
Nix на CachyOS: установка невозможна — rootless-запуск
Nix не ставится, если корень read-only, sudo сломан, а путь /nix зашит в ELF. Разбор трёх ограничений и rootless-запуск через mount-namespace и chroot
Nix devShell в fish: PATH молча не подхватывается
Пакеты из Nix devShell не подхватываются в fish: PATH указывает внутрь mount-namespace. Диагностика молчаливой подмены и прозрачный re-exec оболочки
nix-direnv: «unshare failed: Операция не позволена»
direnv с use flake падает на unshare failed, хотя dotenv работает. Разбираю причину (вложенный user-namespace) и показываю фикс обёртки над nix