zoryn devenv¶
zoryn devenv готовит изолированное окружение для сборки и тестирования пакета: разрешает зависимости сборки (BuildRequires) и устанавливает их под нужную ветку ALT, а каталог проекта монтирует на чтение и запись. В нём можно:
- собирать и тестировать пакет под конкретный сценарий — ветку, набор зависимостей, инструменты;
- править код — и на хосте, и прямо в окружении — и сразу перезапускать сборку;
- обустраивать окружение под себя — декларативно, через конфигурацию (
packages, шагиbuild/build_user, фичи), доставлять в него пакеты и сторонние инструменты, либо доустанавливать их на лету командойzoryn devenv install; - добавлять пакеты из тестового сборочного задания gyle (
--with-task-repo) или выходного репозитория билдера (--with-builder-repo) и отлаживать сборку на них; - запускать LLM-помощников (claude, codex, opencode, kimi, pi) в изоляции от хоста.
Окружение кэшируется и переиспользуется между запусками.
Экспериментально
zoryn devenv — экспериментальная команда. Интерфейс и поведение кэширования могут измениться в будущих версиях.
Использование¶
zoryn devenv # подготовить (или переиспользовать) окружение и открыть shell
zoryn devenv --backend bwrap # использовать бэкенд bubblewrap
zoryn devenv --backend podman # использовать бэкенд podman
zoryn devenv --image registry.altlinux.org/p11/alt:latest # переопределить базовый образ podman
zoryn devenv --profile p11 # использовать профиль конфигурации [devenv.profiles.p11]
zoryn devenv --branch p11 # разовый оверрайд ветки (без правки конфига)
zoryn devenv --backend podman -u root # только podman: войти под указанным пользователем
zoryn devenv --rebuild # снести контейнер+образ окружения и пересоздать (кэш слоёв)
zoryn devenv --rebuild --no-cache # то же, полностью с нуля (podman build --no-cache)
zoryn devenv --no-build-deps # пропустить резолвинг BuildRequires (арх-ограниченный спек, сборка не здесь)
zoryn devenv -s # тихо: скрыть вывод podman build/install
zoryn devenv -- make check # запустить команду вместо интерактивного shell
zoryn devenv -- rpmbuild -bi *.spec # запустить rpmbuild внутри окружения
zoryn devenv packages # пакеты, которые окружение поставило бы (без подготовки)
zoryn вычисляет BuildRequires: пакета, раскрывая макросы спека внутри подготовленного окружения (две итерации, см. Набор зависимостей), объединяет с настроенными дополнительными пакетами (см. Конфигурация) и открывает shell в текущем каталоге пакета. Каталог проекта монтируется read-write, поэтому правки на хосте сразу видны внутри.
Каталог проекта — это корень git-репозитория: zoryn devenv можно запускать из любого подкаталога (например, .gear/), команда всё равно работает с корнем репозитория. Вне git-репозитория используется текущий каталог.
zoryn devenv -- CMD запускает CMD без выделения TTY (подходит для пайпов и CI). Интерактивный shell открывается только когда stdin является терминалом.
При подготовке вывод podman build и установки зависимостей идёт в реальном времени, приглушённым цветом. Флаг -v/--verbose показывает его как есть, без приглушения; -s/--silent скрывает вывод, оставляя только отметки стадий. После раскрытия спека печатается итоговый список BuildRequires.
Бэкенды¶
bwrap¶
Существенно беднее podman
Бэкенд bwrap/hasher лишь устанавливает BuildRequires: спека в chroot и монтирует каталог проекта. Возможности, доступные только в podman, — фичи, шаги сборки образа build/build_user, свой prompt, outbound_interface, dns, проброс SSH-агента, --with-task-repo и установка на лету zoryn devenv install — здесь недоступны. (Дополнительные mounts и --mount-bind на bwrap поддерживаются — монтируются в момент входа.) Для полного набора функций используйте podman; bwrap — только там, где podman недоступен.
Использует bubblewrap поверх chroot, подготовленного через hasher. BuildRequires: пакета устанавливаются в chroot через hasher; каталог проекта монтируется read-write поверх. Повторный вход в кэшированное окружение происходит быстро — chroot переиспользуется без повторной установки пакетов.
git всегда включён в bwrap-chroot.
По умолчанию hasher инициализирует chroot из apt-конфигурации хоста, поэтому bwrap-бэкенд не следует за branch автоматически. Задайте [devenv].apt_config — путь к apt-конфигу хоста: файл в формате apt.conf, файл sources.list или каталог с любым из них — и chroot наполнится из выбранных вами репозиториев (например, apt-конфиг p11 для сборки под p11). Сам hsh --apt-config принимает только файл в формате apt.conf, поэтому apt.conf передаётся как есть, а sources.list автоматически оборачивается в сгенерированный apt.conf в ~/.cache/zoryn/devenv-aptconf/. Значение раскрывает тильду и должно существовать; изменение пересобирает chroot (входит в ключ кэша). Только машинная конфигурация (~/.zoryn, projects.d/). Бэкенд podman тоже учитывает apt_config: используется как sources.list контейнера, если в том же слое конфигурации не задан apt_builder или apt_sources.
Требует bubblewrap и hasher.
podman¶
Сначала настройте rootless podman
Бэкенд podman запускает контейнеры в режиме rootless. Заранее настройте podman на хосте (диапазоны subuid/subgid, хранилище, реестры) по руководству ALT Linux: https://www.altlinux.org/Podman. Доменным учётным записям (LDAP/AD/SSSD) особенно важны явные записи в /etc/subuid и /etc/subgid (например, через usermod --add-subuids/--add-subgids) — без них rootless podman вообще не сможет ни собирать, ни запускать. zoryn дополнительно переназначает host-id вне диапазона на фиксированный id при сборке и использует --userns=keep-id:uid=…,gid=… (podman 4.3+), чтобы контейнер по-прежнему сопоставлялся с вашим реальным id; см. ниже.
Бэкенд podman готовит окружение в два этапа: один раз собирает небольшой производный образ, затем запускает из него постоянный контейнер, который переиспользуется при каждом последующем входе.
Сборка образа. При первой подготовке zoryn собирает на основе образа ALT производный образ — zdevenv-<пакет>-<ветка>:<ключ> (имя каталога пакета, ветка и короткий хеш ключа кэша). Базовый образ берётся директивой FROM (по умолчанию registry.altlinux.org/<ветка>/alt:latest; меняется через image в [devenv] или флагом --image). Образ наполняется во время сборки от имени root — до того, как включится переотображение идентификаторов пользователя (user namespace), — поэтому каталоги блокировок и кэша apt доступны на запись. На этом этапе образ:
- выполняет
apt-get update && apt-get dist-upgrade -y(обновление обязательно: базовые образы ALT часто устаревают); - ставит набор зависимостей плюс
sudo,bash,gitиrpm-build; - запекает пользователя, соответствующего вашим UID/GID на хосте, с беспарольным
sudo. Если ваш id вне диапазона — больше 60000, например доменная учётная запись (LDAP/AD/SSSD), — пользователь запекается с фиксированным id из диапазона (1000), посколькуuseradd(tcb-shadow в ALT) иchownв rootless-сборке не могут использовать id вне вашего диапазонаsubuid.
Постоянный контейнер. Затем zoryn запускает по одному контейнеру на ключ кэша (с именем zdevenv-<пакет>-<ветка>-<ключ>) через podman run -d --userns=keep-id … sleep infinity, монтируя каталог проекта read-write через -v:
- ремап uid (keep-id) выполняется один раз, при создании контейнера; если пользователь запечён с переназначенным id, zoryn использует
--userns=keep-id:uid=1000,gid=1000, чтобы ваш реальный id на хосте по-прежнему сопоставлялся с ним и примонтированные файлы оставались вашими; USERобраза — это запечённый пользователь с вашими UID/GID, поэтомуpodman execвходит под вами (не root), и файлы, записанные в примонтированный проект, остаются вашими;- так как контейнер сохраняется, последующие входы и
install(который тожеexecв тот же контейнер) — мгновенные; - передайте
--user/-u(только podman), чтобы войти под другим пользователем — например,-u rootили-u 1000:1000; это отображается наpodman exec --user.
Сбор зомби и лимит pid. Контейнер запускается с --init, чтобы минимальный PID 1 пожинал зомби: sleep infinity этого не делает, и подпроцессы сборки/тестов (в частности фоновый авто-git gc после коммитов) иначе копятся как defunct-процессы и исчерпывают лимит pid. Лимит задаётся --pids-limit=<n> из [devenv].pids_limit (по умолчанию 4096 — дефолт podman 2048 мал для параллельных сборок; 0 или отрицательное значение — без лимита). То же значение выставляется как --ulimit nproc=<n>:<n>: лимит cgroup сам по себе не поднимает собственный лимит процессов на пользователя в podman (ulimit -u, около 512), а параллельная сборка упирается именно в него — с ошибкой posix_spawnp: Resource temporarily unavailable в gcc/as/lto1. Мягкий лимит только повышается: ниже того, что уже даёт хост, он не опускается и жёсткий лимит хоста не превышает (в rootless-режиме podman не может его поднять). В ключ кэша он не входит, поэтому изменение вступает в силу только при следующем пересоздании контейнера (например, через --rebuild).
Прочее. Контейнеру задаётся отдельный --hostname (по имени каталога пакета), чтобы приглашение shell внутри визуально отличалось от хоста. zoryn devenv install добавляет пакеты на лету в работающий контейнер от имени root внутри контейнера (podman exec --user root … apt-get install) — без пересборки образа. --rebuild сносит контейнер и образ и пересоздаёт их с нуля (добавьте --no-cache, чтобы сбросить и кэш слоёв podman build; сам по себе --no-cache — лишь модификатор сборки, он ничего не пересобирает и не удаляет). Базовый образ должен быть доступен для загрузки (image / --image); обновляйте его вручную при необходимости (например, podman pull registry.altlinux.org/sisyphus/alt:latest).
Произвольные шаги сборки образа (build, build_user)¶
Два массива shell-команд, становящихся строками RUN в Containerfile на этапе сборки образа:
build— от root — системная настройка (собрать/поставить инструмент, положить конфиг).build_user— от пользователя хоста, в егоHOME— для per-user установщиков. Например, claude (curl … | bash) ставится в~/.local/bin; черезbuildэто попало бы в/root(невидимо в твоей оболочке), поэтому клади вbuild_user— встанет в твойHOMEи будет вPATH. Для root-шагов здесь есть беспарольныйsudo.
Оба выполняются в /bin/sh контейнера, записываются дословно и входят в ключ кэша (изменение любого пересобирает образ). Ваши шаги располагаются в проектном «хвосте» образа, после слоёв фич: сначала идёт базовый слой (bash, git, rpm-build), затем по группе слоёв на каждую фичу — её пакеты, её шаги run/run_user — и только потом пакеты самого проекта, ваши build-шаги (от root) и в самом конце build_user. Слои фич podman берёт из кэша для всех проектов, и слои каждой фичи переживают изменения фич, идущих после неё.
Это только машинная конфигурация — принимается в ~/.zoryn и ~/.config/zoryn/projects.d/<проект>.toml, но не в закоммиченном .gear/devenv (предупреждение о неизвестном ключе). Шаги сборки — выбор машины и разработчика, их нельзя выполнять из закоммиченного файла репозитория.
[devenv]
build = ["ln -s /usr/bin/python3 /usr/local/bin/python"] # от root
build_user = ["curl -fsSL https://claude.ai/install.sh | bash"] # от тебя
Требует podman.
Дополнительные bind-монтирования (mounts)¶
Ключ mounts в [devenv] — это массив путей хоста, монтируемых в постоянный контейнер, в дополнение к каталогу проекта (он всегда монтируется на чтение и запись). Каждая запись — одно из:
- чистый путь хоста — монтируется по тому же пути внутри контейнера; либо
- спецификация
host:container[:opts], передаваемая вpodman run -vбез изменений (например,/data:/data:ro).
Поведение:
- Начальный
~/~/раскрывается в$HOMEна обеих сторонах записи (podman тильду не раскрывает; при--userns=keep-idдомашний каталог контейнера совпадает с$HOMEхоста). Поле опций (например,ro) остаётся без изменений. - Монтирования становятся флагами
-vпри создании контейнера, поэтому их изменение пересоздаёт контейнер (список монтирований входит в ключ кэша). - Источник в пределах
$HOME, которого ещё нет, создаётся до запуска контейнера — как пустой файл, если имя выглядит как файл (например,~/.claude.json), иначе как каталог — чтобы podman не создал каталог там, где ожидается файл. - Источник только для чтения (
:ro) не создаётся автоматически, если отсутствует: он считается входными данными от пользователя (отсутствующий~/.vimrcостаётся отсутствующим, а не создаётся пустым). - В спецификации можно ссылаться на
{devenv}(имя контейнера окружения, как вzoryn devenv list; в bwrap, где контейнера нет, — hostnamedevenv-<project>),{project}(basename каталога проекта),{branch}(действующая ветка ALT) и{profile}(активный профиль;default, если не задан).{project}/{branch}/{profile}раскрываются раньше всего остального — проверки существования, автосоздания, ключа кэша.{devenv}раскрывается позже, когда имя контейнера уже известно (в имя входит хэш ключа кэша, который сами mounts и формируют), поэтому в ключе остаётся литеральный токен; изменение конфигурации, пересоздающее контейнер, направит{devenv}в новый каталог — каталог, переживающий пересоздания, делайте через{project}. Одна запись даёт каждому окружению собственный каталог на хосте:mounts = ["~/.config/herdr/sessions/{devenv}"]. Неизвестные{токены}остаются как написаны. Плейсхолдеры работают в записях конфига, в--mount-bindи вmountsфич.
Это только машинная конфигурация — mounts принимается в ~/.zoryn и ~/.config/zoryn/projects.d/<проект>.toml, но не в закоммиченном .gear/devenv (там ключ mounts вызовет предупреждение о неизвестном ключе).
mounts поддерживается обоими бэкендами. В podman монтирования становятся флагами -v при создании контейнера. В bwrap они монтируются в chroot в момент входа как --bind (или --ro-bind для спецификации с :ro), причём только если источник на хосте существует; описанное выше автосоздание источников в $HOME действует только для podman.
Разовый --mount-bind¶
Чтобы добавить монтирование на один запуск, не правя конфиг, передайте --mount-bind SPEC (флаг можно повторять). SPEC использует ровно тот же синтаксис, что и запись mounts, и эти спецификации добавляются после настроенных в конфиге. Относительный путь на стороне хоста разрешается относительно текущего каталога (после применения -C):
zoryn devenv --mount-bind /srv/data # rw, один и тот же путь с обеих сторон
zoryn devenv --mount-bind ~/ref:~/ref:ro # только для чтения
zoryn devenv --mount-bind ../OpenUSD # относительный → преобразуется в абсолютный
zoryn devenv --mount-bind /a:/a --mount-bind /b:/b # сразу несколько
podman: изменение монтирований пересоздаёт контейнер
podman фиксирует bind-монтирования в момент создания контейнера — к уже созданному контейнеру монтирование подключить нельзя. Поэтому --mount-bind входит в ключ кэша контейнера: запуск, чей набор монтирований отличается от набора существующего контейнера, создаёт новый контейнер из закэшированного образа. Это дёшево (слои образа переиспользуются), но изменения, сделанные внутри файловой системы прежнего контейнера — вне смонтированных путей, — в новый не переносятся. Повторный запуск с тем же набором --mount-bind переиспользует тот же контейнер, а запуск без флага возвращает к прежнему. Контейнеры, оставшиеся после смены монтирований, удаляет zoryn devenv clean.
В bwrap монтирование выполняется в момент входа, поэтому подключается к уже подготовленному chroot без пересборки.
podman: подменённый на хосте смонтированный файл устаревает
Bind-монтирование отдельного файла привязано к его иноде. Когда программа на хосте перезаписывает такой файл через временную копию и переименование (так сохраняется большинство конфигов, включая ~/.claude.json), путь на хосте получает новую иноду, а контейнер продолжает смотреть на старую: смонтированный файл внутри devenv незаметно перестаёт следовать за хостом. zoryn записывает иноды файловых монтирований при создании контейнера и сверяет их при каждом входе — если файл был подменён, об этом честно сообщается прямо перед открытием оболочки с рекомендацией выполнить zoryn devenv --rebuild, чтобы пересоздать окружение с актуальными файлами.
Пользовательское приглашение оболочки (prompt)¶
Ключ prompt в [devenv] — это строка bash PS1, встраиваемая в производный образ: она дописывается в ~/.bashrc пользователя хоста во время сборки образа, поэтому интерактивная оболочка podman exec … bash использует именно её. Значение сохраняется дословно (внутри заключается в одинарные кавычки), поэтому обычные escape-последовательности приглашения — \u (пользователь), \h (хост), \w (рабочий каталог) и цветовые последовательности вроде \[\e[32m\]…\[\e[0m\] — интерпретируются bash в момент вывода приглашения. Изменение значения пересобирает образ (приглашение входит в ключ кэша).
Это только машинная конфигурация — prompt принимается в ~/.zoryn и ~/.config/zoryn/projects.d/<проект>.toml, но не в закоммиченном .gear/devenv (там ключ prompt вызовет предупреждение о неизвестном ключе). Применяется только к бэкенду podman.
Требует podman.
Часовой пояс контейнера (timezone, --timezone ZONE)¶
По умолчанию контейнер podman использует часовой пояс базового образа (UTC). Задайте зону IANA, чтобы podman run --tz настроил TZ и /etc/localtime внутри контейнера — удобно при отладке кода, чувствительного к часовому поясу:
Флаг важнее конфига; значение из ~/.config/zoryn/projects.d/<проект>.toml важнее глобального ~/.zoryn. Только машинная конфигурация — timezone не читается из закоммиченного .gear/devenv. Только бэкенд podman — bwrap наследует TZ хоста.
podman: смена зоны пересоздаёт контейнер
--tz фиксируется при создании контейнера. Действующая зона сравнивается с записанной на существующем контейнере (label zoryn.timezone); при несовпадении контейнер пересоздаётся из закэшированного образа. Следующий запуск без --timezone пересоздаст его обратно с зоной из конфига.
Исходящий интерфейс (outbound_interface, --outbound-interface ИНТЕРФЕЙС)¶
Ключ outbound_interface в [devenv] — это имя сетевого интерфейса хоста (например, eth1, wg0, eth0.10). Если он задан, контейнер переключается с режима по умолчанию --network host на rootless-сеть pasta в отдельном сетевом пространстве имён, исходящий трафик которой форсится через этот интерфейс через --outbound-if4/--outbound-if6 (pasta биндит свои хостовые сокеты к интерфейсу через SO_BINDTODEVICE), поэтому все исходящие соединения контейнера выходят именно через него, независимо от таблицы маршрутизации хоста. Привязываются только семьи адресов, присутствующие и на интерфейсе, и на шаблоне, — интерфейс только с IPv4 (например, WireGuard wg0) не тянет за собой падающую привязку IPv6, а семья, которую шаблон обслужить не может (например, IPv6, когда у интерфейса маршрута по умолчанию нет глобального IPv6), отбрасывается, так как pasta отвергает её с External interface not usable. Если ключ не задан, сохраняется поведение по умолчанию --network host. Тот же --network применяется и при сборке образа, поэтому apt на этапе сборки тоже идёт через выбранный интерфейс.
Интерфейс используется только для выхода трафика, а не как шаблон pasta: у point-to-point / tun / WireGuard-интерфейсов нет пригодного L2, и шаблоном они быть не могут (External interface not usable). Поэтому шаблоном (-i) передаётся интерфейс маршрута по умолчанию хоста — контейнер видит адреса/маршруты обычного интерфейса, а трафик уходит через выбранный. На выбранном интерфейсе должен быть хотя бы один пригодный IP-адрес, и должен существовать маршрут по умолчанию, иначе создание контейнера завершится с понятной ошибкой. Итоговый аргумент: --network pasta:-i,<интерфейс-по-умолчанию>,--outbound-if4,<интерфейс>.
Если на выбранном интерфейсе есть только одна семья адресов, pasta ограничивается ей (-4 или -6). Это убирает неиспользуемую семью из контейнера и, что важно, предотвращает утечку: у шаблонного интерфейса может быть другая семья, трафик которой иначе ушёл бы по маршруту шаблона, а не через выбранный интерфейс. Так интерфейс только с IPv4 (WireGuard wg0) даёт контейнер только с IPv4 и без какого-либо пути для IPv6.
Поскольку контейнер получает собственное сетевое пространство имён, сервисы на хостовом localhost больше не разделяются так, как при --network host (сервис, который хост слушает на 127.0.0.1, недоступен изнутри контейнера, и наоборот). Изменение значения пересоздаёт контейнер из закэшированного образа: интерфейс входит в ключ кэша контейнера, но не в тег образа, поскольку задаёт лишь маршрут, по которому сборка качает пакеты. Прежний контейнер сохраняется, так что возврат к прежнему интерфейсу возвращает и его; ненужные удаляет zoryn devenv clean --all. Имя проверяется по безопасному набору символов (буквы, цифры, . _ @ -).
Это только машинная конфигурация — outbound_interface принимается в ~/.zoryn и ~/.config/zoryn/projects.d/<проект>.toml, но не в закоммиченном .gear/devenv (там ключ outbound_interface вызовет предупреждение о неизвестном ключе). Применяется только к бэкенду podman.
zoryn devenv --outbound-interface wg0 # разово, переопределяет конфиг на этот запуск
zoryn devenv -I '' # разово: не учитывать интерфейс из конфига, войти с --network host
Флаг важнее конфига; значение из ~/.config/zoryn/projects.d/<проект>.toml важнее глобального ~/.zoryn. Поскольку интерфейс входит в ключ кэша, запуск с другим -I попадает в другое окружение (со своими образом и контейнером), а не пересоздаёт настроенное — zoryn devenv clean без флага его не тронет, поэтому удаляйте его через zoryn devenv clean --all.
Требует podman (с поддержкой pasta; в современном podman pasta — сетевой режим rootless по умолчанию).
Настройки резолвера (dns, dns_search, dns_option)¶
Три ключа-массива в [devenv] повторяют три поля /etc/resolv.conf и соответствуют собственным флагам podman: dns → --dns (адреса серверов имён, IPv4 или IPv6), dns_search → --dns-search (домены поиска), dns_option → --dns-option (опции резолвера, например ndots:2). В каждом может быть несколько записей, порядок сохраняется. Все три применяются и при сборке образа, и в контейнере, поэтому apt разрешает имена как на этапе сборки, так и внутри работающего окружения. Если не задано ничего, разрешение имён остаётся на усмотрение podman: контейнер наследует резолверы хоста.
Слои для этих трёх ключей выбираются, а не складываются, — как у apt_sources и timezone и в отличие от mounts: парный одноразовый флаг (--dns АДРЕС, --dns-search ДОМЕН, --dns-option ОПЦИЯ, каждый можно повторять) приоритетнее проектного projects.d/<проект>.toml, а тот — глобального ~/.zoryn; слой, в котором ключ задан, заменяет нижележащий, а не дописывается к нему. Порядок записей значим (первый сервер имён опрашивается первым), поэтому проект должен иметь возможность поставить свой резолвер впереди общесистемного. Слой выбирается для каждого ключа отдельно: проект может переопределить dns, продолжая наследовать глобальный dns_search.
Ключ нужен там, где резолверы хоста из контейнера недоступны, — чаще всего вместе с outbound_interface. Привязка к WireGuard-интерфейсу wg0 уводит в туннель и DNS-запросы, а собственные резолверы хоста на них не отвечают; указание сервера имён, доступного через туннель (или публичного), чинит разрешение имён без строки DNS = в конфигурации WireGuard на хосте — она поменяла бы разрешение имён для всей системы.
[devenv]
outbound_interface = "wg0"
dns = ["10.0.0.53", "8.8.8.8"]
dns_search = ["corp.example.com"]
dns_option = ["ndots:2"]
zoryn devenv --dns 10.0.0.53 # один запуск, один сервер имён
zoryn devenv --dns 10.0.0.53 --dns 8.8.8.8 # несколько, по порядку
zoryn devenv --dns-search corp.example.com # домен поиска
zoryn devenv --dns-option ndots:2 # опция резолвера
Почему записи mounts для /etc/resolv.conf недостаточно
Монтирование файла с резолверами (mounts = ["~/my-resolv.conf:/etc/resolv.conf:ro"]) действует только на контейнер: podman фиксирует bind-монтирования в момент его создания. Сборка образа — отдельный шаг, никаких таких монтирований в нём нет, поэтому apt-get update внутри Containerfile по-прежнему падает с Temporary failure resolving 'ftp.altlinux.org'. Заметить это можно далеко не сразу: пока образ лежит в кэше слоёв, шаг сборки просто не выполняется, и поломка всплывает лишь при следующей пересборке (изменились packages/features/build или запущено --rebuild). dns закрывает оба шага.
Записи проверяются: в dns — адрес IPv4 или IPv6 (допускается идентификатор зоны IPv6, например fe80::1%eth0) либо литерал none, маркер podman «оставить контейнер без серверов имён»; в dns_search — домен либо ., чтобы очистить список поиска; в dns_option — одиночный флаг (rotate) или пара имя:значение (ndots:2). Всё остальное считается ошибкой конфигурации.
Эти настройки не входят в ключ кэша: доступность сети — не часть идентичности окружения, и её изменение не должно плодить брошенные контейнеры. Вместо этого все три записываются в метку контейнера zoryn.dns, и запуск с другими настройками пересоздаёт контейнер (слои образа переиспользуются) — как zoryn devenv, так и zoryn devenv install: проверка у них общая с сокетом SSH-агента, часовым поясом, опубликованными портами и записями env. Если же с неправильными серверами имён собрался сам образ — тот самый сбой apt-get update, — пересоберите его принудительно через --rebuild.
Только машинная конфигурация (~/.zoryn, projects.d/) — не читается из закоммиченного .gear/devenv. Только бэкенд podman; bwrap разрешает имена через хост.
Фильтрованный доступ в LAN ([devenv.lan])¶
С заданным outbound_interface контейнер отрезан от LAN: каждое соединение уходит через выбранный интерфейс (обычно туннель). Субтаблица [devenv.lan] пробивает фильтрованное отверстие обратно — второй pasta-интерфейс (zoryn-lan0), который сайдкар-процесс подключает к сетевому пространству контейнера и который несёт маршруты ровно до разрешённых адресов:
[devenv]
outbound_interface = "wg0"
[devenv.lan]
interface = "eth0"
allow = ["192.168.50.10", "192.168.50.0/24"]
Начиная с zoryn 0.49.0 (после перехода на парсер otoml) субтаблицу можно записывать и inline — ключом lan = { interface = "eth0", allow = ["192.168.50.10", "192.168.50.0/24"] } прямо в блоке [devenv], до первого заголовка [devenv.*].
interface— LAN-интерфейс хоста, к которому сайдкар привязывает свои сокеты. Должен отличаться отoutbound_interfaceи иметь хотя бы один пригодный адрес — если его нет (интерфейс погас или переименован), zoryn предупреждает и входит без LAN-политики, а не отказывает в шелле.allow— IP-адреса или CIDR, маршрутизируемые вinterface. Голый адрес означает хост-маршрут/32. Имена хостов намеренно не принимаются — DNS по-прежнему уходит в туннель, так что никакого разрешения имён через LAN-путь не происходит.
Маршруты — это и есть весь фильтр: разрешённый префикс уходит через LAN-интерфейс, всё остальное идёт по маршруту по умолчанию, чей сокет привязан к outbound_interface и до LAN не достаёт. Фильтр действует только на соединения, которые контейнер устанавливает сам — входящие (например, на опубликованный порт) и ответы на них идут обычным путём, так что хост из разрешённой подсети тоже может достучаться до контейнера. Записи, которые нарушили бы изоляцию, отклоняются заранее: пересекающиеся с link-local (169.254.0.0/16, fe80::/10 — их podman использует для DNS контейнера и доступа к хосту), накрывающие маршрут по умолчанию и собственный адрес хоста.
Список разрешённых — состояние времени выполнения, а не идентичность контейнера: его правка не пересоздаёт контейнер — маршруты подгоняются в работающем при следующем zoryn devenv / enter / install. zoryn devenv inspect показывает актуальные маршруты, прочитанные из пространства имён, а не протухшую метку. Полное удаление [devenv.lan] снимает сайдкар при следующем запуске.
Ограничения, заложенные намеренно:
- Сборка образа идёт в собственном пространстве имён на каждый шаг, поэтому LAN-зеркало apt на этапе сборки недоступно.
- Фильтрация по адресу назначения: все порты разрешённого адреса доступны.
- Никакого разрешения имён через LAN-путь (см. выше).
- Один хостовой интерфейс.
- Исходящее и входящее разделяются по адресу источника, поэтому правило работает для TCP, но не для UDP-службы, слушающей на всех адресах: у её ответов на момент маршрутизации источник не задан, и они по-прежнему уходят в LAN-путь. Зеркальный случай — сокет, который перед исходящим соединением привязывается к собственному адресу контейнера: он пойдёт обычным путём и до LAN не достанет.
- Разрешённый префикс выигрывает у собственной подсети контейнера: с
allow = ["10.0.0.0/8"]на контейнере с адресом из10.88.5.0/24соединения в эту подсеть тоже уходят через LAN-интерфейс.
Маршруты живут в отдельной таблице маршрутизации за парой policy-правил, поэтому ip route внутри контейнера их не покажет — смотрите zoryn devenv inspect либо ip route show table 23205 и ip rule show в пространстве имён.
Только машинная конфигурация — [devenv.lan] принимается в ~/.zoryn и ~/.config/zoryn/projects.d/<проект>.toml, но никогда в закоммиченном .gear/devenv, так что пакет не может открыть дыру в вашу LAN. Только бэкенд podman.
Публикация портов (ports)¶
Dev-сервер внутри изолированного контейнера доступен из браузера хоста через [devenv] ports:
Каждая запись — "порт" (один и тот же порт с обеих сторон) или "хост:гость". Публикация идёт через собственный pasta podman'а как -p 127.0.0.1:хост:гость, так что http://localhost:18080 на хосте ведёт на порт 8080 контейнера. Порты хоста проверяются раньше, чем zoryn трогает закэшированный контейнер: занятый порт падает с devenv.ports: host port 18080 is already in use … вместо ошибки podman, не отбирая у вас уже готовое окружение и не оставляя недосозданное. Порт, который публикует сам текущий контейнер, занятым не считается — он вот-вот его освободит.
Публикация не только на loopback¶
По умолчанию порт публикуется на 127.0.0.1 — доступен с хоста и больше ниоткуда. Чтобы отдать его шире, добавьте перед записью имя интерфейса или адрес (localhost принимается как синоним 127.0.0.1):
[devenv]
ports = [
"9229", # 127.0.0.1:9229 — поведение по умолчанию
"eth0:8080", # адрес хоста на eth0, порт 8080
"192.0.2.10:18080:8080", # явный адрес, хост 18080 → гость 8080
"*:3000", # все интерфейсы (0.0.0.0)
]
Имя интерфейса разрешается в его первый IPv4-адрес при каждом запуске, поэтому смена адреса подхватывается без правки конфига. Важно, что значит «подхватывается»: разрешённый адрес входит в записанный список портов, так что новый DHCP-адрес при следующем zoryn devenv пересоздаёт контейнер — слои образа переиспользуются, но всё, что ставилось вживую через zoryn devenv install, пропадает, как при любом пересоздании. Интерфейс, у которого в данный момент нет IPv4-адреса (VPN упал, кабель выдернут), не запирает вас снаружи уже созданного окружения: zoryn предупреждает и входит с теми портами, которые оно уже публикует, — если их самих ещё можно поднять. Когда записанные порты окружения называют исчезнувший адрес, никакой запуск его не привяжет (pasta упала бы ровно там же), поэтому zoryn сообщает о мёртвой привязке вместо предупреждения с последующим падением. Создание окружения всегда требует адрес и падает с devenv.ports: interface eth0 has no IPv4 address to publish on; адрес, которого на хосте нет, валит предстартовую проверку с devenv.ports: address 192.0.2.10 is not configured on this host.
Привязка шире loopback открывает сервис контейнера всем, кто может достучаться до этого адреса: своего файрвола у контейнера нет, так что запись в списке и есть всё решение о доступе. Только IPv4. Один и тот же порт может встречаться на нескольких разных конкретных адресах, но не на пересекающихся привязках: "8080" вместе с "*:8080", как и две записи, разрешающиеся в один адрес с разными гостевыми портами, отклоняются до того, как что-либо собрано, — обе привязки дрались бы за один сокет. Два написания одного и того же проброса ("8080" и "8080:8080", или интерфейс рядом со своим же адресом) просто публикуются один раз.
Порты для одного проекта¶
ports объединяются между слоями конфигурации (как mounts, в отличие от настроек резолвера): машинный список в ~/.zoryn задаёт то, что публикует каждое окружение, а ~/.config/zoryn/projects.d/<проект>.toml добавляет сверху порты конкретного проекта.
# ~/.zoryn — для всех окружений
[devenv]
ports = ["9229"]
# ~/.config/zoryn/projects.d/mypkg.toml — дополнительно для этого пакета
[devenv]
ports = ["eth0:8080"]
mypkg опубликует и 127.0.0.1:9229, и eth0:8080. Запись, встречающаяся в обоих слоях, публикуется один раз. Выключателя на уровне проекта намеренно нет: список проекта добавляет, но не вычитает, поэтому пустым списком в проекте машинные порты не отключить. Держите в глобальном списке только порты, нужные действительно везде: хостовый порт — ресурс единственный, и с глобальной записью окружения двух разных проектов не смогут работать одновременно — автоматически арбитрируются только окружения одного проекта, а держатель из чужого проекта выдаст already in use.
Замечания:
- Порт хоста может встречаться только один раз на каждом адресе привязки. Список
["8080:80", "8080:81"]отклоняется заранее: pasta привяжет порт для первой пары и упадёт на второй, так что создание умерло бы на списке, который поэлементно выглядит корректным. Один и тот же гостевой порт за двумя хостовыми — нормально. - Изменение чего угодно из ключа кэша (пакеты, монтирования, фичи, образ, ветка…) создаёт новый контейнер, а прежний продолжает работать — и продолжает держать порт. zoryn останавливает это другое окружение того же проекта, чтобы текущее смогло привязать порт, и печатает, какой контейнер остановлен. Именно останавливает, а не удаляет: всё, что вы в нём поставили, сохраняется, а вход в него снова его запустит. Вернётесь обратно — тот же разбор сработает в обратную сторону: порт достаётся тому окружению, в которое вы входите, потому что хостовый порт один и кому-то из двоих приходится уступить. Это работает независимо от того, как вы добираетесь до контейнера:
zoryn devenv,zoryn devenv enter <имя>,installиfeature addразбирают порты перед его запуском. - Остановка держателя обрывает всё, что в нём выполняется, поэтому она делается как можно позже и только когда действительно поможет. Сначала проверяется каждый порт, который мог бы занять посторонний процесс: если один порт держит соседнее окружение, а другой — посторонний процесс, не останавливается ничего, а ошибка называет порт. На пути создания держатель останавливается лишь после успешной сборки образа, непосредственно перед созданием контейнера, — отклонённый список портов или неудачная сборка не будут стоить вам работающей сессии. Остановка выполняется по принципу «всё или ничего»: если какой-то держатель остановить не удалось, порт всё равно не освободится, поэтому уже остановленные запускаются обратно, а ошибка называет контейнер, который не остановился.
- Хостовый порт считается отдельно для каждого адреса привязки:
["8080", "eth0:8080"]публикует один и тот же порт и на loopback, и в LAN, и разбираются они независимо. Wildcard пересекается с любым адресом, поэтому смена привязки записи (скажем,"8080"→"*:8080") корректно считает старую привязку работающего контейнера заменяемой, а не чужой. - Соединение, установленное с хоста, приходит в контейнер с адресом tap/шлюза вместо настоящего адреса хоста. LAN-клиенты, пришедшие через более широкую привязку, сохраняют свои реальные адреса — pasta не NAT-ит входящий трафик с других машин, — так что логи доступа и ACL внутри контейнера видят настоящих пиров.
- Игнорируется, если
outbound_interfaceне задан, — с--network hostконтейнер и так делит порты с хостом, публикация избыточна. - Список не входит в ключ кэша: его изменение пересоздаёт контейнер на месте (отслеживается через метку
zoryn.ports), какtimezone.
Только машинная конфигурация, только бэкенд podman.
Проброс SSH-агента (forward_ssh_agent / --ssh-agent)¶
Включает проброс SSH-агента хоста в контейнер, чтобы инструменты внутри (git по SSH, ssh на удалённые билдеры) пользовались ключами вашего агента — в том числе ключами, проброшенными в ваш логин через ssh -A. Включается флагом --ssh-agent (-A) либо постоянно через [devenv].forward_ssh_agent = true; флаг приоритетнее конфига.
Когда включено, zoryn монтирует хостовый $SSH_AUTH_SOCK на фиксированный путь внутри контейнера (/run/ssh-agent.sock) и выставляет туда SSH_AUTH_SOCK, так что podman exec (команда входа) его наследует. $SSH_AUTH_SOCK должен быть задан на хосте и указывать на существующий сокет, иначе проброс пропускается с предупреждением.
Сокет агента не входит в ключ кэша — его путь меняется на каждый вход (например, ssh -A создаёт новый /tmp/ssh-XXXX/agent.<pid> на сессию). Вместо этого путь сокета записывается на контейнер меткой zoryn.ssh_auth_sock; при следующем zoryn devenv, если он отличается от текущего $SSH_AUTH_SOCK (новый вход или переключение проброса), контейнер пересоздаётся, чтобы привязать живой сокет. Сборка образа при этом — попадание в кэш слоёв, а последний вычисленный набор BuildRequires запечён в образ прогревочным слоем, так что перерезолвинг у пересозданного контейнера доустанавливает только изменившееся — обычно ничего. В рамках одного входа сокет стабилен, поэтому повторные входы мгновенны.
Только машинный конфиг (~/.zoryn, projects.d/), только бэкенд podman.
Конфигурация apt (podman)¶
Бэкенд podman может сгенерировать собственный sources.list, заменяя источники базового образа, и монтировать локальные репозитории file: (только для чтения), чтобы apt мог работать с ними как на этапе сборки образа, так и при установке пакетов.
Конфигурацию apt можно задать одним из четырёх способов (приоритет: --apt-conf > конфиг проекта > глобальный конфиг; в рамках одного слоя задайте только один ключ):
--apt-conf <путь>— файлsources.listили каталог apt-конфига в стиле builder--apt-conf builder:ИМЯ— взять apt-конфиг из билдераИМЯapt_builder = "ИМЯ"(конфиг) — переиспользовать apt-конфиг билдераapt_config = "<путь>"(конфиг) — файл sources.list, файл apt.conf или каталог apt-конфига (также используется бэкендом bwrap, нормализуется в apt.conf дляhsh --apt-config)apt_sources = ["rpm file:///… x86_64 classic", …](конфиг) — строки источников напрямую
Локальные репозитории file: монтируются внутрь контейнера только для чтения по тому же пути; если указанный путь не существует на хосте — это ошибка.
Если заданный источник разрешается в файл без записей репозиториев (пустой или только комментарии — например, пустой /etc/apt/sources.list, когда настоящие записи живут в sources.list.d/), zoryn выводит предупреждение с именем файла и оставляет стандартные источники apt контейнера, а не завершается с ошибкой.
Источник из слоя конфигурации, чьи репозитории относятся к другой ветке ALT, нежели действующая ветка окружения, тоже отбрасывается с предупреждением, и остаются собственные источники базового образа — глобальный apt_config, указывающий, скажем, на зеркало Сизифа, больше не ломает окружение с -B p11 (образ p11 и так несёт правильные источники p11). Явное значение --apt-conf применяется как есть, даже при несовпадении ветки. Пустое значение — --apt-conf '' — это разовый сброс: любой настроенный источник apt игнорируется, остаются собственные источники базового образа.
Только машинная конфигурация (~/.zoryn, projects.d/); apt_builder и apt_sources — только бэкенд podman.
Репозиторий вывода билдера (--with-builder-repo BUILDER)¶
--with-builder-repo BUILDER добавляет аддитивный источник apt-rpm типа rpm-dir, указывающий на выходной репозиторий hasher-билдера, — так только что собранные локально пакеты можно устанавливать внутри dev-окружения наряду с пакетами из обычных репозиториев.
- Локальные билдеры: выходной каталог hasher-а (
<hasher_dir>/repo) монтируется в контейнер только для чтения по тому же пути хоста; копирование не производится. - Удалённые билдеры: выходной репозиторий синхронизируется с хоста билдера через rsync в
~/.cache/zoryn/devenv-repos/<builder>/repo/до подготовки окружения; затем используется локальный кэш. - Архитектура билдера должна совпадать с архитектурой devenv; при несоответствии zoryn завершается с ошибкой.
- Бэкенд podman: дроп-ин
sources.list.d(zoryn-extra.list) записывается в контейнер, не затрагивая настроенные базовые репозитории. - Бэкенд bwrap: строка репозитория объединяется с существующими источниками apt (из
apt_configили/etc/apt/sources.list, если не задан), и вhsh --apt-configпередаётся сгенерированный apt.conf, ссылающийся на объединённый sources.list; базовые репозитории остаются нетронутыми. - Строка репозитория и путь к нему входят в ключ кэша, поэтому изменение или удаление флага пересобирает окружение.
- Взаимно исключает
--with-task-repo.
Репозиторий задачи gyle (--with-task-repo TASK_ID)¶
--with-task-repo TASK_ID загружает RPM-пакеты, собранные задачей gyle, в локальный кэш и открывает их как аддитивный источник apt-rpm типа rpm-dir внутри dev-окружения, — так пакеты из выполняющейся или завершённой задачи gyle можно устанавливать наряду с пакетами из обычных репозиториев.
- RPM-пакеты задачи загружаются по HTTP в
~/.cache/zoryn/devenv-task-repos/<TASK_ID>/до подготовки окружения; кэш переиспользуется при следующих запусках, если не задан--no-cache. - Кэш загруженных RPM в
~/.cache/zoryn/devenv-task-repos/<TASK_ID>/не удаляется командойzoryn devenv clean— при необходимости его можно удалить вручную. - Среди загруженных RPM должны быть пакеты для архитектуры devenv; если пакеты этой архитектуры отсутствуют, zoryn завершается с ошибкой.
- Только бэкенд podman — на остальных бэкендах (
--backend bwrapи др.) сразу завершается с ошибкой. - Взаимно исключает
--with-builder-repo. - Строка репозитория и каталог кэша входят в ключ кэша, поэтому изменение или удаление флага пересобирает окружение.
hasher (зарезервировано)¶
Имя бэкенда hasher зарезервировано для будущего использования. Передача --backend hasher завершается ошибкой (project-mount scheme TBD).
Конфигурация¶
Секция [devenv] управляет дополнительными пакетами и выбором бэкенда.
Источники конфигурации¶
[devenv] можно задать в трёх местах с разной областью действия:
| Файл | Область | Допустимые ключи |
|---|---|---|
~/.zoryn | Глобально — все пакеты на машине | backend, packages, image, branch, build, build_user, mounts, pids_limit, prompt, timezone, outbound_interface, dns, dns_search, dns_option, ports, env, forward_ssh_agent, apt_config, apt_builder, apt_sources, default_profile; таблицы [devenv.lan], [devenv.features.<id>] |
~/.config/zoryn/projects.d/<проект>.toml | Конкретный проект, локально на машине | то же, что ~/.zoryn |
.gear/devenv | Конкретный пакет, закоммичен, общий для команды | только packages |
.gear/devenv намеренно не принимает backend, image, branch, build, build_user, mounts, pids_limit, prompt, outbound_interface, dns, dns_search, dns_option, env, forward_ssh_agent, профили и таблицы [devenv.features.<id>] — это выбор машины и разработчика, который не следует хранить в репозитории.
Профили¶
Профиль — именованная, полностью независимая конфигурация [devenv], задаётся как [devenv.profiles.<имя>] (в ~/.zoryn или projects.d/<проект>.toml). Выбирается флагом --profile <имя>/-p <имя>; если флаг не задан, используется [devenv].default_profile, а если и он не задан — применяется только базовая секция. Профили удобны, чтобы держать рядом, например, окружения sisyphus и p11 и переключаться между ними.
Когда профиль активен, каждый ключ [devenv] (backend, packages, image, branch, build, build_user, mounts, pids_limit, prompt, timezone, outbound_interface, dns, dns_search, dns_option, ports, env, forward_ssh_agent, apt_config, apt_builder, apt_sources, [devenv.lan] и [devenv.features.*]) читается только из [devenv.profiles.<имя>]. Базовая секция [devenv] при этом не учитывается — её значения не наследуются и не объединяются с профилем. База применяется только когда профиль не активен. Ключ, отсутствующий в активном профиле, просто не задан, а не берётся из базы.
Единственное исключение: собственные BuildRequires пакета (вычисленные из его spec) и пакеты, закоммиченные в .gear/devenv, устанавливаются всегда, независимо от профиля — они описывают сам пакет, а не окружение разработчика.
Сам default_profile всегда читается из базовой [devenv] (выбирать профиль изнутри профиля было бы циклично).
Ключ branch задаёт целевую ветку ALT (по умолчанию sisyphus); когда image не задан, из неё выводится базовый образ registry.altlinux.org/<ветка>/alt:latest. zoryn devenv --branch <имя>/-B — разовый оверрайд, который побеждает настроенный branch без правки конфига. Каждый разрешённый профиль (или --branch) даёт своё кэшированное окружение (ветка, пакеты и образ входят в ключ кэша), поэтому два сосуществуют и оба видны в zoryn devenv list.
Как branch влияет на окружение, зависит от бэкенда: podman тянет соответствующий базовый образ, поэтому его apt-репозитории действительно принадлежат этой ветке; bwrap использует hasher, который читает apt-конфиг хоста, поэтому branch только именует/разделяет кэшированный chroot — направьте hasher на репозитории ветки через apt_config (ниже).
[devenv]
packages = ["gdb"] # используется ТОЛЬКО когда профиль не активен
default_profile = "sisyphus" # используется, когда --profile не задан
[devenv.profiles.sisyphus]
branch = "sisyphus"
packages = ["gdb"] # независимо: укажите здесь, если нужно
[devenv.profiles.p11]
branch = "p11"
packages = ["vim"] # → только vim (базовый "gdb" НЕ наследуется)
Фичи¶
Фича — именованный переиспользуемый набор: добавляет в окружение разработки пакеты, шаги установки и bind-монтирования. Описывается файлом feature.toml.
Фичи задаются таблицей, а не списком. Фича подключается добавлением таблицы [devenv.features.<id>] в конфигурацию. Пустая таблица подключает фичу без опций; ключи внутри таблицы — её типизированные опции.
[devenv.features.claude] # выбрана, без опций
[devenv.features.opam] # выбрана, с опцией (локальная фича opam)
version = "5.3.0"
Начиная с zoryn 0.49.0 (после перехода на парсер otoml) тот же набор можно записать одной строкой — inline-таблицей TOML вместо отдельного заголовка на каждую фичу:
Обе записи равнозначны. Единственная тонкость — собственное правило TOML: inline-ключ features = ... должен стоять внутри блока [devenv] до первого заголовка [devenv.*], потому что голая строка ключ = значение относится к ближайшему заголовку секции над ней.
Профили: фичи подключаются послойно. Как и любой другой ключ [devenv], набор фич определяется слоем, а профиль полностью независим от базы:
- Профиль не активен — действуют только таблицы
[devenv.features.<id>](базовый слой). - Профиль активен — действуют только таблицы
[devenv.profiles.<p>.features.<id>](слой профиля); базовые[devenv.features.*]не наследуются.
[devenv.features.claude] # только без --profile
[devenv.profiles.p11.features.gdb] # только с --profile p11 (claude НЕ включается)
Опции фичи берутся из её таблицы в активном слое. Между файлами (глобальный ~/.zoryn + проектный) идентификаторы объединяются, а опции берутся из проектного файла (приоритет выше) — в рамках активного слоя.
Фича добавляет:
packages— объединяется сapt installвместе сBuildRequires:из спека и вашими собственнымиpackages.run— массив shell-команд, выполняемых от root (аналогbuild) при сборке образа.run_user— массив shell-команд, выполняемых от пользователя хоста (аналогbuild_user) после переключенияUSER.mounts— дополнительные bind-монтирования в контейнер; плейсхолдеры{devenv}/{project}/{branch}/{profile}раскрываются как в одноимённом ключе конфига (см. mounts).env— массив записейNAME=value, выставляемых в оболочке окружения (podman--envпри создании контейнера, bwrap--setenvпри входе); в значениях работают плейсхолдеры{devenv}/{project}/{branch}/{profile}, как вmounts. Запись без=или с именем, не являющимся корректным идентификатором, отклоняется при разборе фичи. Записи из[devenv].envвашей конфигурации применяются после записей фич, так что при совпадении имени побеждает ваша конфигурация. Изменение итогового набора пересоздаёт podman-контейнер (слои образа переиспользуются).devices— пути хостовых устройств, пробрасываемых в контейнер через--device(например,/dev/net/tun). Если устройства нет на хосте, оно пропускается с предупреждением, а не срывает создание контейнера. Как иmounts, устройства фиксируются в момент создания контейнера, поэтому их изменение пересоздаёт контейнер.capabilities— имена capability, добавляемые контейнеру через--cap-add(например,SYS_ADMIN; префиксCAP_необязателен — podman принимает оба написания). Всё, что не укладывается в[A-Z][A-Z0-9_]*, отклоняется ещё при разборе фичи. Как иmounts, capability фиксируются в момент создания контейнера, поэтому их изменение пересоздаёт контейнер.security_opts— записи--security-opt, применяемые к контейнеру (например,unmask=/proc/*). Каждая запись должна иметь видключ=значениес известным podman ключом (seccomp,unmask,mask,label,apparmor,no-new-privileges,proc-opts) и значением без пробелов и shell-метасимволов, кроме*; всё остальное отклоняется при разборе фичи. Тоже фиксируются в момент создания контейнера.
Шаги фич выполняются до ваших команд build/build_user (фичи — первыми). В образе podman это не просто порядок, а граница кэша слоёв: у каждой фичи своя группа слоёв — apt-слой с пакетами фичи, затем её шаги run/run_user — и только потом всё проектное (пакеты самого проекта, ваши шаги сборки, prompt). Слои фичи зависят только от фич, идущих до неё, поэтому проекты с общим префиксом фич делят эти слои байт-в-байт, а добавление или удаление фичи не трогает слои фич, перечисленных раньше: установщик фичи (тот же curl … | bash у claude) выполняется один раз на машину — при неизменных базовом образе, ветке и apt-конфигурации — а не для каждого проекта и не заново при расширении набора фич после неё. Обратная сторона такого порядка: шаг фичи не может рассчитывать на пакеты проекта, на ваши build-шаги и на пакеты фич после неё — они выполняются позже, — поэтому своя фича должна перечислять всё нужное в собственном packages (встроенные фичи так и делают). Типизированные опции options фичи передаются её командам как переменные окружения: имя каждой опции переводится в верхний регистр и экспортируется в окружение команды (например, channel → CHANNEL). Префикс с переменными добавляется к каждой записи run/run_user по отдельности.
Подстановки ${VAR} в тексте команды нет
zoryn не выполняет подстановку ${VAR} в строке команды. Если написать ${CHANNEL} дословно в записи run/run_user, оно не будет раскрыто в значение опции — префикс CHANNEL='latest' cmd не делает ${CHANNEL} видимым в списке аргументов самой cmd в POSIX sh (раскрытие параметра происходит до того, как присваивание вступает в силу, поэтому оно пустое). Значение доходит до программы только через окружение: команда (или установщик, в который она пайпит) должна сама прочитать $CHANNEL через getenv.
Внутри записей mounts и env, напротив, на опцию можно сослаться как ${NAME} (имя опции в верхнем регистре) — оно подставляется текстуально значением опции до того, как запись станет флагом -v / --env (например, ~/.x:~/.x:${CONFIG_ACCESS} при config_access = "ro" даёт ~/.x:~/.x:ro). Это дополнение к экспорту опций в переменные окружения для run/run_user; так опция может параметризовать монтирование или запись окружения (спецификации -v и --env — не shell-команды, поэтому механизм env-префикса там не работает). В записи env параметризуется только значение — сама запись обязана быть корректной NAME=value уже при разборе фичи, а результат подстановки, содержащий управляющий символ, пробел в конце или фигурную скобку, отличную от плейсхолдеров {devenv}/{project}/{branch}/{profile}, отклоняется с ошибкой, называющей фичу.
Приоритет при поиске: локальное определение имеет приоритет над встроенным с тем же id. Оно лежит в ~/.config/zoryn/devenv/features/<id>.toml либо в каталоге как <id>/feature.toml (удобно для вспомогательных файлов рядом); при наличии обоих побеждает форма с каталогом.
Встроенные фичи:
claude— устанавливает Claude Code CLI в~/.local/bin; монтирует~/.claudeи~/.claude.json, чтобы авторизация и настройки сохранялись между запусками контейнера. Опцияinstaller:native(по умолчанию) запускает официальныйinstall.sh, который скачивает готовый бинарник;npmвместо этого ставит@anthropic-ai/claude-codeиз реестра npm (подтягиваяnpm/node) — пригодится там, где загрузка бинарника идёт медленно или блокируется, поскольку учитывает настроенное у вас зеркало npm.GLM-claude-code— то же, чтоclaude(та же опцияinstaller), но монтирует отдельный профиль: хостовый~/.config/claude-glm→~/.config/claudeв контейнере и хостовый~/.claude-glm.json→~/.claude.json, чтобы держать GLM-авторизацию и настройки отдельно от профиляclaude.codex— устанавливает OpenAI Codex CLI официальным инсталлером; монтирует~/.codex, чтобы логин сохранялся.opencode— устанавливает opencode CLI; монтирует~/.local/share/opencode,~/.config/opencodeи~/.agents, чтобы логин, настройки и описания агентов сохранялись.pi— устанавливает кодингового агента pi.dev в~/.local/bin; монтирует хостовые каталоги~/.pi/agentи общий~/.agentsна чтение и запись, поэтому авторизация у провайдеров, настройки, сессии, расширения, скиллы и пакеты доступны внутри окружения, а сделанные там изменения сохраняются на хосте. Проектные каталоги.piи.agentsуже доступны через монтирование проекта.kimi— устанавливает Kimi Code CLI (MoonshotAI) в~/.local/kimi(бинарник лежит вне монтируемого каталога состояния) и монтирует~/.kimi-code, чтобы логин,config.toml, MCP-серверы и сессии сохранялись.vim— устанавливаетvim-consoleиvim-plugin-spec_alt-ftplugin, монтирует конфигурацию vim хоста:~/.vimrc— только для чтения, и~/.vim— по умолчанию тоже только для чтения. Задайте[devenv.features.vim] config_access = "rw", чтобы монтировать~/.vimна чтение и запись, иначе рабочие записи vim (swap, undo, netrwhist, проверка орфографии, данные плагинов) будут падать с ошибкой.~/.viminfoлежит в$HOME, а не в~/.vim, поэтому история сохраняется в любом случае.hasher— собирать пакеты черезhasherпрямо в окружении: ставитhasherиhasher-kayfabe(не ниже 0.2.1 — на более старом сборка образа останавливается с внятным сообщением), заводит сателлитных пользователей и подставляет обёртки передhsh,hsh-runи остальными. Обёртки поднимаютhasher-privdпри первом обращении, так что окружение, в котором ничего не собирали, демона не держит.hasher-privтребует от системы того, чего rootless-контейнер не даёт: сверяет неймспейсы с вызывающим, считает read-only cgroupfs фатальной ошибкой и создаёт узлы устройств в чруте черезmknod()— на каждый из этих случаевhasher-kayfabeотвечает тем, что процесс увидел бы на живой машине. Кроме того, фича дописывает в/etc/hasher-priv/systemстрокиallowed_mountpoints=/proc,/dev/ptsиallowed_devices=/dev/kvm: hasher-priv отвергает любую точку монтирования, которой там нет, а команды сборки самого zoryn передают--mountpoints=/proc,/dev/pts. Окружение получаетCAP_SYS_ADMIN(hasher-priv монтирует), и это ослабляет изоляцию — включайте осознанно; больше ничего не послабляется,/procостаётся замаскированным так, как его оставил podman. Root для запуска демона даёт setuid-хелпер, умеющий ровно одно действие и только для группыhashman, кудаhasher-useraddзаносит пользователя, так что фичаsudoне нужна — но членство в этой группе само по себе открывает путь к root внутри контейнера, как это сделал бы иsudo. Два ограничения, о которых стоит знать: cgroupfs в контейнере остаётся доступной только для чтения, поэтому каждая сборка печатает одно предупреждение и идёт без пожобных лимитов hasher; а пробросить в контейнер хостовый/dev/kvm— отдельное решение ([devenv].devicesили спек, который его запрашивает). Хостовый~/.hasher/configмонтируется только для чтения, чтобы окружение собирало с вашими собственными настройками hasher — прежде всего с пакаджером: hasher берёт его именно оттуда (или изhsh --packager) и никогда из~/.rpmmacros, а без него запекает в чрутAutomated package hasher <hasher@localhost>, иsisyphus_checkотвергает любую сборку с «wrong PACKAGER». Файл читается шеллом, поэтому значение с пробелами нужно закавычить:packager='Ваше Имя <you@altlinux.org>'. Читается он при создании чрута, так что изменённый пакаджер вступит в силу на следующемhsh --initroot. Если файла на хосте нет, монтирование пропускается с предупреждением, где он назван, а не подсовывается созданный podman каталог — окружение на такой машине всё равно поднимется и честно скажет, чего не принесло. Хостовый~/.rpmmacrosмонтируется тем же способом и тоже только для чтения — он нужен сборкам, которые идут в окружении напрямую (gear-rpm, rpmbuild): hasher этот файл не читает, а всё остальное берёт%packagerименно из него. Опции:[devenv.features.hasher] tmpfs— монтирование рабочего каталога, по умолчаниюtmpfs:~/hasher:size=8g,mode=1777(чрут — это тысячи мелких файлов, и на оверлее контейнера каждый из них уходит записью на диск); поставьте"", чтобы оставить каталог на оверлее, или измените размер прямо здесь.hasher_config— то самое монтирование только для чтения,""— оставить конфигурацию hasher внутри окружения своей собственной.sudo— даёт пользователю беспарольный root внутри контейнера. Без этой фичи прав root в окружении нет вовсе: она и прописывает правило в/etc/sudoers.d/<user>, и решает вторую проблему — в ALT/usr/bin/sudoимеет режим 4710root:wheel(control sudo wheelonly), так что пользователь вне группыwheelне может даже запустить бинарь. Фича переводит фасилити в режимpublic(4711root:root), а кому что позволено, по-прежнему решаетsudoers. Включайте её, когда внутри окружения нужно ставить пакеты или как-то иначе требуются права root.ssh— устанавливаетopenssh-clientsи монтирует~/.sshхоста только для чтения, чтобы ключи,configиknown_hostsбыли доступны внутри (например, для git-over-ssh и удалённых билдеров). Из-за режима только для чтения измененияknown_hostsизнутри контейнера не сохраняются.chromium— устанавливает Chromium со шрифтами DejaVu (fontconfig в комплекте) для headless-браузинга и скриншотов — обычно им пользуются ИИ-агенты. Setuid-хелперchrome-sandboxне работает под rootless podman, поэтому фича дописывает--disable-setuid-sandboxв/etc/chromium/default; namespace-sandbox самого Chromium при этом продолжает работать, так что изоляция страниц сохраняется. Браузеры, которые агенты скачивают сами (puppeteer/playwright), тоже начинают запускаться — пакет приносит все нужные им разделяемые библиотеки.podman— вложенный rootless podman внутри контейнера: можно запускать и собирать образы и даже поднимать вложенныйzoryn devenv(в паре с фичейzoryn). Устанавливает podman с сетью pasta и выделяет пользователю sub-id-диапазоны, укладывающиеся в user namespace самого контейнера. Хранилище образов живёт на хосте в~/.local/share/zoryn-devenv/containers— оно прокинуто внутрь, чтобы работал нативный драйвер overlay (на overlay-ФС самого контейнера podman молча откатился бы на медленный vfs); каталог общий для всех ваших devenv-контейнеров, так что скачанные образы кешируются между ними. Хостовый/dev/net/tunпробрасывается в контейнер: pasta — сетевой бэкенд вложенного podman — открывает его, чтобы создать свой tap-интерфейс. Учтите: окружения с этой фичей работают сCAP_SYS_ADMINи размаскированным/proc— вложенному podman это нужно, чтобы монтировать собственный procfs и задавать hostname своим контейнерам, — что ослабляет изоляцию контейнера; впрочем, в rootless-режиме эта capability ограничена user namespace самого контейнера и не даёт никаких привилегий на хосте, а фильтрация seccomp остаётся полностью включённой. Требуется ядро хоста 5.13 или новее. Хелперамnewuidmap/newgidmapвыдаются возможностиcap_setuid/cap_setgidвместо стандартного для ALT набора возможностей (который контейнер podman не допускает), а/etc/login.defsвнутри контейнера открывается на чтение всем, посколькуnewuidmapчитает его при старте.zoryn— устанавливает пакетzorynиз репозитория ветки контейнера и монтирует конфигурацию zoryn хоста (~/.zorynи~/.config/zoryn, где лежитbuilders.d/) только для чтения, чтобы внутри окруженияzorynработал с вашими хостовыми билдерами/настройками, не имея возможности их изменить. Agent Skill пакет ставит сам в/usr/share/zoryn/skills/, поэтому монтировать скиллы не нужно. Пакет должен присутствовать в репозитории целевой ветки.
Это только машинная конфигурация — таблицы [devenv.features.<id>] принимаются в ~/.zoryn и ~/.config/zoryn/projects.d/<проект>.toml, но не в закоммиченном .gear/devenv. Только бэкенд podman; если фича не объявляет текущий бэкенд в своём списке backends, она пропускается с предупреждением.
Пример определения локальной фичи:
# ~/.config/zoryn/devenv/features/claude/feature.toml
id = "claude"
version = "1"
backends = ["podman"]
packages = ["curl"]
run_user = ["curl -fsSL https://claude.ai/install.sh | bash"]
mounts = ["~/.claude", "~/.claude.json"]
[options.channel]
type = "enum"
values = ["stable", "latest"]
default = "stable"
Опция channel экспортируется как CHANNEL в окружение установщика, поэтому скрипт install.sh подхватит её, если читает $CHANNEL (через getenv); в саму строку команды значение не подставляется.
Разовый --feature¶
-F ID, --feature ID — разовое включение фичи devenv ID для текущего запуска, поверх фич из [devenv.features] или активного профиля (с устранением дубликатов; ничего не удаляется). Можно указывать несколько раз. Неизвестный ID — ошибка. Для фичи, уже описанной в конфиге, используются её опции из конфига; фича, включённая только через --feature, берёт значения по умолчанию.
# Подключить фичу 'claude' на один запуск, не трогая конфиг
zoryn devenv --feature claude
# Сразу несколько
zoryn devenv -F claude -F vim
Набор зависимостей¶
Окружение наполняется из двух источников:
1. BuildRequires: из спека, вычисляемые внутри окружения в две итерации (по образцу hasher hsh-rebuild), так что rpm-макросы и условия %if раскрываются настоящим rpmbuild для нужной архитектуры — а не текстовым парсом на хосте:
- Итерация 1 ставит
BuildRequires(pre):(пакеты, определяющие макросы, напримерrpm-build-python3), взятые из сырого спека с отбрасыванием токенов-макросов. - Итерация 2 запускает
rpmbuild -bEна спеке (макро-пакеты уже установлены) и ставит полный раскрытый наборBuildRequires. Так же подхватываются динамические сборочные зависимости, которые добавляют макросы из тела спека (%pyproject_build,%set_gcc_versionи подобные). Такие зависимости попадают не в преамбулу, а в макрос%_buildrequires_build, поэтому zoryn раскрывает копию спека, дописав после тела строку%{?_buildrequires_build}, и устанавливает всё, что окажется в этом списке (например,python3-module-pyproject-installer, который требуется макросам%pyproject_*). При раскрытии%_sourcedirуказывает на каталог самого спека (.gear/), поэтому макросы, читающие оттуда вспомогательные исходные файлы — например%pyproject_builddeps_*из rpm-build-pyproject, вычисляющие зависимости поpyproject_deps.json, — тоже отдают свой сгенерированный список. Если же правилоcopy:в.gear/rulesберёт файл из другого каталога, такой файл этим макросам не виден — резолвинг выведет предупреждение с его именем.
rpm-build всегда присутствует в окружении, чтобы спек можно было раскрыть (вместе с ним ставятся bash и git). А вот sudo — нет: права root появляются только вместе с фичей sudo. Capability-зависимости (pkgconfig(...), perl(...)) резолвятся apt (podman) / hasher (bwrap). Ограничения версий отбрасываются — окружение разработки ставит по имени. Каталоги-маркеры /.host, /.in, /.out, которые проверяют скриптлеты пакетов, требующих hasher-окружение (например, rpm-build-vm-run), создаются в podman-контейнере всегда — такой пакет может прийти и зависимостью к явно запрошенному (rpm-build-vm тянет rpm-build-vm-run) (бэкенд bwrap и так является hasher-чрутом). Ядро, установленное как сборочная зависимость, не генерирует initrd и не создаёт симлинки в /boot: в образ вшивается /etc/sysconfig/installkernel (NOFLAVOUR=y, NODEFAULT=y, INITRD_AUTOUPDATE=none), и installkernel сразу выходит — контейнеру разработки ядро нужно только для сборки, а не для загрузки. Если спеку нужен KVM для VM-сборки (BuildRequires /dev/kvm или KVM-тянущий пакет вроде rpm-build-vm-run) и на хосте есть /dev/kvm, он прокидывается в podman-контейнер через --device /dev/kvm.
2. Настроенные дополнительные пакеты — объединение (без дублей) packages из [devenv] в ~/.zoryn (личный набор, например gdb, vim, strace), .gear/devenv (общекомандные) и ~/.config/zoryn/projects.d/<проект>.toml (локальные для проекта).
Изменение спека пересоздаёт окружение (его дайджест входит в ключ кэша).
После каждого успешного вычисления zoryn запоминает полный набор зависимостей для проекта+ветки+архитектуры (в ~/.cache/zoryn/devenv/buildreqs/) и запекает его в производный образ последним, «прогревочным» слоем. Контейнер, пересозданный по причине вне ключа кэша — смена env/ports/dns/timezone, новый сокет SSH-агента, --rebuild, — стартует с уже установленными сборочными зависимостями, и авторитетный перерезолвинг внутри контейнера доустанавливает только разницу. Слой собирается с || true: устаревшая запись (пакет переименовали, сменилась ветка) не может сорвать сборку образа — установка просто произойдёт внутри контейнера, как раньше.
Для шаблонных спеков с gear-плейсхолдерами specsubst (например, Name: kernel-image-@kflavour@ у kernel-image) значения @var@ раскрываются перед вычислением зависимостей — так же, как zoryn build делает для short-circuit сборки секции — беря каждое значение из git config gear.specsubst.<var> или секции [specsubst] файла .gear/version-up. Если значение не задано, zoryn devenv останавливается и просит его задать.
Пропуск резолвинга (--no-build-deps)¶
Некоторые спеки на текущей машине вычислить нельзя в принципе: ExclusiveArch:/ExcludeArch:, исключающие архитектуру хоста, заставляют rpmbuild -bE отвергнуть спек (error: Architecture is not included: x86_64). Это типично для пакетов прошивок и загрузчиков вроде u-boot-*, которые правят локально, а собирают на удалённом билдере нужной архитектуры. Для такого сценария передайте --no-build-deps: спек пропускается целиком, и окружение содержит только настроенные пакеты из [devenv]. Когда резолвинг падает именно с этой ошибкой rpmbuild, zoryn devenv печатает подсказку с рекомендацией этого флага.
Флаг разовый: следующий запуск без него снова попытается резолвить (и снова упадёт), так что для такого пакета передавайте его при каждом zoryn devenv.
Порядок приоритета бэкенда¶
Флаг --backend → ~/.config/zoryn/projects.d/<проект>.toml → ~/.zoryn → уже подготовленное кэшированное окружение пакета → по умолчанию (podman, если установлен, иначе bwrap).
Если бэкенд не задан, zoryn devenv автоматически входит в существующее кэшированное окружение: если ровно у одного из podman/bwrap есть подготовленное окружение для текущего набора зависимостей, используется он. Поэтому после однократной сборки podman-окружения обычный zoryn devenv входит в него без пересборки. Если кэша тоже нет, по умолчанию используется podman (когда podman установлен), иначе bwrap.
Порядок приоритета образа (только podman)¶
Флаг --image → ~/.config/zoryn/projects.d/<проект>.toml → ~/.zoryn → registry.altlinux.org/<ветка>/alt:latest.
Задайте image (или передайте --image), если образ по умолчанию недоступен на хосте (например, вы используете локальное зеркало или реестр с другой схемой тегов).
Пример ~/.zoryn¶
[devenv]
backend = "bwrap"
packages = ["gdb", "vim", "strace", "ltrace"]
# image = "registry.example.com/alt/sisyphus" # если образ по умолчанию недоступен
# build = ["go install golang.org/x/tools/cmd/goimports@latest"] # podman: доп. шаги RUN при сборке образа
Пример .gear/devenv¶
Пример ~/.config/zoryn/projects.d/<проект>.toml¶
zoryn devenv install¶
zoryn devenv install gdb strace # добавить gdb и strace в подготовленное окружение
zoryn devenv install --save heaptrack # добавить heaptrack и запомнить в .gear/devenv
zoryn devenv install --backend podman ltrace
zoryn devenv install --container blender-abc123 gdb # в конкретный контейнер
zoryn devenv install --container . gdb # в контейнер текущего каталога
Устанавливает дополнительные пакеты в подготовленное окружение текущего пакета (кэшированную базу), чтобы они были доступны в следующей сессии zoryn devenv без полной пересборки. Эта команда выполняется на хосте, потому что shell-среда bwrap работает без привилегий поверх read-only chroot — установить пакеты изнутри неё нельзя.
Если окружение ещё не подготовлено, install сначала подготавливает его (полный разрешённый набор зависимостей, как это сделала бы zoryn devenv), а затем устанавливает дополнительные пакеты поверх. Для бэкенда podman они устанавливаются на лету в работающий контейнер от имени root внутри контейнера (podman exec --user root … apt-get install), без пересборки образа; для bwrap установка идёт в кэшированный chroot.
--container¶
По умолчанию целевое окружение вычисляется из конфигурации текущего каталога — тот же ключ кэша, что использовал бы обычный zoryn devenv. --container CONTAINER (только podman) вместо этого устанавливает пакеты прямо в указанный существующий контейнер (имя/ID как в zoryn devenv list, либо . — контейнер текущего каталога), не разрешая конфигурацию проекта — работает из любого каталога, как zoryn devenv enter. Используйте его, когда ключ кэша контейнера отличается от обычной конфигурации, например для контейнера, созданного с разовым --mount-bind. Флаги выбора окружения (--backend, --image, --apt-conf, …) при --container игнорируются.
--save по-прежнему пишет в .gear/devenv текущего каталога, поэтому сочетать его с --container можно только когда контейнер принадлежит текущему каталогу (его метка zoryn.project совпадает); иначе команда откажет до установки чего-либо.
--save¶
Дополнительно добавляет указанные пакеты в [devenv].packages в .gear/devenv как отсортированное объединение без дублей с уже имеющимися записями. Это позволяет пакетам пережить пересборку кэша и делает их общими для команды (файл коммитится). Прочие ключи файла сохраняются. Если каталога .gear/ нет, сохранение пропускается с предупреждением.
Бэкенд hasher здесь не поддерживается (та же ошибка project-mount scheme TBD, что и у zoryn devenv).
zoryn devenv feature add¶
Добавляет одну или несколько фич devenv в уже запущенный podman-контейнер текущего проекта, на месте — без пересоздания. Это аналог zoryn devenv install, но для фич: пакеты фичи (packages) устанавливаются, а её шаги run/run_user выполняются прямо в работающем контейнере через podman exec.
В отличие от --feature/-F, которая включает фичу в идентичность окружения и потому пересоздаёт контейнер, чтобы собрать его с этой фичей, feature add действует на уже запущенном контейнере. Поэтому эффект временный: как и пакеты, добавленные через zoryn devenv install, фича живёт только в этом контейнере и исчезает при следующей полной пересборке (--rebuild или zoryn devenv clean).
Ограничения:
- Только podman. Если окружение ещё не подготовлено, сначала выполните
zoryn devenv. - Фичу, которая объявляет bind-mount, запись окружения, устройство, capability или security-опцию, к уже запущенному контейнеру подключить нельзя, поэтому команда откажет — используйте
zoryn devenv -F ID, чтобы пересоздать окружение уже с ними. - Неизвестный id фичи — ошибка.
zoryn devenv clean¶
zoryn devenv clean # удалить кэш окружения для текущего пакета
zoryn devenv clean --backend podman # удалить именно кэш бэкенда podman
zoryn devenv clean --all # podman: удалить ВСЕ окружения этого проекта (любой ключ кэша)
zoryn devenv clean --project /path/to/proj # podman: удалить окружения указанного проекта, из любого места
Удаляет кэшированную базу окружения текущего пакета, чтобы следующий запуск zoryn devenv собрал его заново с нуля.
- bwrap: запускает
hsh-rmchrootдля демонтажа chroot (требуется, так как подкаталог chroot принадлежит hasher-priv subuids и не может быть удалён обычнымrm), затем удаляет весь каталог кэша. - podman: удаляет постоянный контейнер командой
podman rm -fи собранный образ (zdevenv-<пакет>-<ветка>:<ключ>) командойpodman rmi -f.
Целевое окружение определяется по текущим значениям бэкенда, набора пакетов, архитектуры, ветки и образа. Смена --backend, ветки или образа (через конфигурацию) затрагивает другой кэш.
--all (podman)¶
Обычный clean удаляет только одно окружение, соответствующее текущей конфигурации. Изменение параметров, входящих в ключ кэша, оставляет прежнее окружение работать, поэтому устаревшие окружения со временем накапливаются: prompt, дополнительные packages и образ дают новый образ и контейнер, а mounts и outbound_interface — новый контейнер из того же образа.
zoryn devenv clean --all удаляет все окружения текущего проекта сразу, независимо от ключа кэша. Контейнеры при создании помечаются меткой zoryn.project=<каталог-проекта>; --all удаляет все контейнеры с меткой текущего проекта, затем делает rmi их образов (без -f, поэтому образ, на который ссылается контейнер другого проекта, сохраняется). Окружения других проектов не затрагиваются. Контейнеры, созданные до появления этой метки, её не имеют и удаляются вручную (podman rm -f <имя>).
--project PATH (podman)¶
zoryn devenv clean --project PATH удаляет все окружения проекта по PATH — это путь к каталогу проекта (значение zoryn.project, которое показывает zoryn devenv list) или его basename — из любого места, не находясь в этом каталоге. Удобно освобождать место от окружений другого проекта прямо по выводу list. Не требует подготовленного окружения или разрешимой конфигурации в текущем каталоге (поэтому работает, даже когда конфиг текущего пакета не разрешается). Образы удаляются rmi без -f. Имеет приоритет над --all и над целью «текущий каталог».
--all применяется только к бэкенду podman; для bwrap удаляется лишь текущий chroot (chroot’ы bwrap общие по набору зависимостей, а не по проекту).
Если кэшированного окружения нет, clean ничего не делает и выводит соответствующее сообщение.
Бэкенд hasher не поддерживается (та же ошибка project-mount scheme TBD, что и у zoryn devenv).
zoryn devenv list¶
Показывает все окружения, созданные zoryn, по всем проектам и ключам кэша — удобно находить устаревшие, накопившиеся при смене конфигурации или зависимостей (каждая отдельная конфигурация получает своё окружение).
- podman: каждый контейнер с меткой
zoryn.devenv, колонками под заголовком —PROJECT,CONTAINER,STATUS,FEATURES,PROFILE. Последние две и отличают друг от друга окружения одного проекта: ключ кэша в имени контейнера ничего не говорит о том, из чего он получился. Контейнер, собранный без фич, вне профиля или до появления этих меток, показывает-; полную записанную конфигурацию печатаетzoryn devenv inspect <имя>. --verboseдобавляет для каждого проекта, у которого окружений больше одного, список меток, значения которых совпадают не у всех — на практике это вычисленныеpackages(изменившийсяBuildRequires:) илиspec_hash, и именно из-за них расходятся окружения с одинаковым набором фич. Если не различается ничего записанного, значит ключи разошлись из-за того, что меткой не сохраняется, — например из-за разового--mount-bind. Длинные значения печатаются с того места, где они начинают расходиться (…hasher-fake:/…): у списка монтирований или пакетов общее начало иначе занимает всю строку.- bwrap: подготовленные кэш-каталоги chroot под
~/.cache/zoryn/devenv/(адресуются по набору зависимостей, поэтому без имени проекта — только путь).
Удаление: zoryn devenv clean --all (из проекта — сносит его podman-окружения), zoryn devenv clean (текущей конфигурации) или podman rm -f <имя> для конкретного контейнера.
zoryn devenv packages¶
zoryn devenv packages # по одному пакету в строке
zoryn devenv packages --verbose # с группировкой по источнику
zoryn devenv packages --profile p11 # в рамках этого профиля
zoryn devenv packages --no-build-deps # только конфиг и фичи
zoryn devenv packages -F vim # плюс пакеты этой фичи
Печатает пакеты, которые zoryn devenv поставил бы в окружение, не подготавливая его. По умолчанию — отсортированное объединение без повторов, по одному имени в строке: удобно скармливать другим командам.
Список собирается так же, как при реальном запуске, из трёх источников:
- Статические
BuildRequires:/BuildPreReq:спека (включая(pre)), прочитанные из сырого текста. Макросы отбрасываются;rpmbuild -bEвнутри окружения не запускается, поэтому динамические зависимости из тела спека (%pyproject_buildи подобные) сюда не попадают. - Настроенные
packagesиз~/.zoryn,.gear/devenvиprojects.d/<проект>.toml, с учётом активного профиля (явный--profile, иначе[devenv].default_profile, иначе базовая секция). - Пакеты выбранных фич (из конфига плюс
--feature/-F), отфильтрованные по поддерживаемому бэкенду (флаг, затем конфиг, затем podman). Фича, которая бэкенд не поддерживает, пропускается с предупреждением — как в самомzoryn devenv.
--verbose печатает каждый источник отдельной группой (buildrequires, ~/.zoryn, .gear/devenv, projects.d/<проект>.toml, features/<id>), а не плоский список. --no-build-deps пропускает спек — тот же смысл, что у одноимённого флага zoryn devenv. --profile, --backend и --feature значат то же, что у основной команды.
Полностью раскрытые BuildRequires — после двух проходов внутри уже подготовленного окружения — записываются на контейнер и показываются командой zoryn devenv inspect.
zoryn devenv enter¶
zoryn devenv enter # войти в контейнер текущего каталога
zoryn devenv enter <CONTAINER> # войти в существующий контейнер по имени/ID
zoryn devenv enter <CONTAINER> --root # войти под root
zoryn devenv enter <CONTAINER> -u 1000 # войти под указанным пользователем
zoryn devenv enter <CONTAINER> -- make # выполнить команду вместо shell
zoryn devenv enter . -- make # выполнить команду в контейнере по умолчанию
Входит в уже созданный podman-контейнер dev-окружения напрямую по имени или ID (как показывает zoryn devenv list), запуская его, если он остановлен. В отличие от обычного zoryn devenv, здесь не разрешается конфигурация проекта и не нужно находиться в каталоге пакета — удобно сразу зайти в конкретный контейнер (например, принадлежащий другому проекту). Shell открывается в рабочем каталоге контейнера (том проекте, для которого он создан).
CONTAINER можно не указывать: тогда выбирается (самый свежий) контейнер, созданный для текущего каталога, а если каталогу не соответствует ни один — единственный существующий контейнер. . — явный placeholder того же умолчания: с ним можно совместить контейнер по умолчанию и команду, например zoryn devenv enter . -- make.
Войти можно только в контейнеры, созданные zoryn devenv (с меткой zoryn.devenv); любое другое имя отклоняется. --root — шорткат для --user root (имеет приоритет над явным --user). Команда после -- выполняется вместо интерактивного shell.
--mount-bind здесь нет: podman не умеет подключать bind-mount к уже созданному контейнеру. Чтобы добавить или изменить монтирования, запустите обычный zoryn devenv --mount-bind SPEC из каталога проекта — контейнер пересоздастся с новыми монтированиями (слои образа переиспользуются, так что это дёшево).
zoryn devenv remove¶
Удаляет один podman-контейнер dev-окружения напрямую по имени или ID (как показывает zoryn devenv list) вместе с его образом (образ удаляется без -f, поэтому используемый другим контейнером сохраняется). Не разрешает конфигурацию проекта и не требует находиться в каталоге пакета. Удалить можно только контейнеры, созданные zoryn devenv (с меткой zoryn.devenv); любое другое имя отклоняется.
Используй, чтобы убрать одно конкретное окружение из вывода list; zoryn devenv clean --project PATH сносит все окружения проекта сразу, а zoryn devenv clean — то, что соответствует текущей конфигурации.
zoryn devenv inspect¶
Печатает конфигурацию zoryn, записанную в podman-контейнер dev-окружения при создании — ключ кэша, образ, ветку, архитектуру, настроенные packages, выбранные features, mounts, шаги build/build_user, prompt/outbound_interface/dns/apt_config и digest спека. Всё хранится как метки zoryn.* контейнера, так что каждое окружение самоописывается — из чего оно собрано.
Также печатает фактически установленные resolved BuildRequires, записанные внутрь контейнера при подготовке и разделённые по проходам hasher — build_requires(pre) (проход 1, пакеты BuildRequires(pre), определяющие макросы) и build_requires (проход 2, полный раскрытый набор) — чтобы стадии можно было различать и сравнивать по отдельности.
Удобно понять непрозрачную запись в zoryn devenv list или сравнить два окружения одного пакета — inspect обоих и сравни вывод, чтобы увидеть, какой именно вход отличается (например, изменился spec_hash, добавилась фича или другой packages/build_requires). Контейнеры, созданные до появления этих меток, покажут только базовые. (Данные меток также в podman inspect <name> --format '{{json .Config.Labels}}'; build-deps — в /var/lib/zoryn-devenv/ внутри контейнера.)
Спек не входит в podman-ключ кэша. Идентичность podman-окружения — это его конфиг (образ, ветка, packages, features, mounts, build-шаги, …), а не спек, поэтому правка спека никогда не создаёт новый контейнер. Вместо этого при следующем zoryn devenv существующий контейнер переиспользуется, а сборочные зависимости сверяются:
- zoryn сравнивает digest спека до секции
%changelog(spec_hash, записанный внутри контейнера какbr_digest). Совпал → сразу вход; не совпал → зависимости резолвятся заново, недостающие доустанавливаются, запись обновляется. - При изменении zoryn заново резолвит
BuildRequiresспека внутри контейнера иapt-ставит только недостающие (идемпотентно — уже стоящие = no-op). - Тело сборки входит в digest, потому что именно оно порождает динамические
BuildRequires(%pyproject_buildи подобные) — значит, его правка тоже запускает повторный резолв; из digest исключён только%changelog(он часто меняется и на зависимости не влияет). - Добавление
BuildRequiresпросто доустанавливает пакет — без пересборки. УдалённыеBuildRequiresостаются установленными (безвредно для dev-среды; для чистого состояния —zoryn devenv clean).
Бэкенд bwrap по-прежнему завязан на спек (его chroot пересобирается при смене спека, а не сверяется).
zoryn devenv profile list¶
zoryn devenv profile list # только имена
zoryn devenv profile list --verbose # итоговая конфигурация каждого профиля
Выводит имена всех профилей [devenv.profiles.<name>], объявленных в машинной конфигурации (~/.zoryn) и в локальной конфигурации проекта (projects.d/<project>.toml), по одному в строке, отсортированными и без повторов. Профили, которые встречаются только через вложенный заголовок [devenv.profiles.<name>.features.<id>], тоже попадают в список.
С флагом --verbose вместо имён выводится итоговая конфигурация [devenv] каждого профиля — ключи и фичи, к которым он разрешается (backend, image, branch, packages, build, features, …), со слиянием машинной и локальной конфигураций проекта так же, как это делает рантайм. Показываются только заданные ключи.
Именно простую форму этой команды вызывает автодополнение оболочки, чтобы предложить корректные значения для zoryn devenv --profile <TAB>, поэтому подсказки всегда отражают ваши реальные профили, а не жёстко прописанный список.
Кэширование¶
Подготовленные окружения кэшируются в ~/.cache/zoryn/devenv/<ключ>/ (bwrap), где ключ вычисляется из набора пакетов, бэкенда, архитектуры, ветки и — для бэкенда podman — базового образа image. Поэтому изменение image затрагивает другое окружение, а не переиспользует устаревшее. Кэш переиспользуется при следующих запусках без повторной установки пакетов.
Архитектура входит в ключ, но окружение всегда собирается под архитектуру хоста — кросс-архитектурная эмуляция не выполняется.
Для бэкенда podman кэш — это собранный образ (zdevenv-<пакет>-<ветка>:<ключ>) плюс созданный из него постоянный контейнер (zdevenv-<пакет>-<ветка>-<ключ>), а не каталог. Контейнер переиспользуется — перезапускается, если был остановлен — при каждом запуске; zoryn devenv clean удаляет и контейнер, и образ.
Монтирование каталога проекта в контейнер фиксируется при его создании. Рабочий каталог не входит в ключ кэша, поэтому если каталог пакета позже переместится (или вы запустите zoryn devenv для него по другому абсолютному пути), переиспользуемый контейнер будет по-прежнему примонтирован к исходному пути, и ваши правки не будут видны внутри. Выполните zoryn devenv clean (или --rebuild), чтобы пересоздать контейнер для нового расположения.
Передайте --rebuild, чтобы снести кэшированное окружение и пересоздать его (для podman это пересобирает образ и пересоздаёт контейнер); добавьте --no-cache, чтобы заодно сбросить кэш слоёв podman build и заново выполнить каждый слой (apt dist-upgrade, зависимости). Сам по себе --no-cache — лишь модификатор сборки, он ничего не пересобирает и не удаляет. Пересборка полезна после изменения BuildRequires: или когда базовый образ устарел с момента подготовки.
См. также¶
- Конфигурация — справочник
~/.zoryn - Песочница — изоляция на основе bubblewrap и hasher для хуков zoryn