`libalternatives` в openSUSE
Tumbleweed не дає розслабитись як update-alternatives поступається місцем libalternatives
Я вже звик до того, що мій openSUSE Tumbleweed час від часу влаштовує мені невеликі сюрпризи. Не в сенсі «всё пропало». Скоріше в сенсі «щось звичне тихо зникло, і знову треба сідати розбиратись, що трапилось». Нагадує мені університетські часи з лабораторними заняттями. Так було з переходом з AppArmor на SELinux. І ось нещодавно знову сюрприз, на цей раз із Java.
Предісторія: я маю три JDK, 21шу, 25ту та GraalVm. Дві з них використовую постійно, перемикаючись через update-alternatives.
Після чергового zypper dup хочу щось скомпілювати, і отримую що java не знайдено. В мене динамічно прописані PATH, щоб при зміні версії JDK перемикатися без додаткових дій.
Предісторія 2: навіщо взагалі той update-alternatives?
До прикладу, у вас встановлені vim, nano і emacs. Якась стороння програма хоче відкрити текстовий редактор для редагування листа. Яку команду викликати? vim? nano? А якщо на іншій машині стоїть тільки emacs?
Тут на сцену виходить update-alternatives. Ідея проста: кожен пакет реєструє себе як альтернативу для певної ролі (editor, java, python тощо), і система зберігає симлінки в /etc/alternatives/, вказуючи на поточну «виграшну» альтернативу з найвищим пріоритетом.
Механізм розповсюджений у всіх дистрибутивах. Напочатку це була Debian тулза, але всі популярні дистрибутиви її швидко підхопили.
Чому update-alternatives вже не задовольняє потреби сучасних дистрибутивів
Насправді задовольняє)))) Всіх, крім одного. Як завжди, геніальні німецькі інженери не можуть просто нагромаджувати костилі на існуючі рішення і шукають елегантніший шлях.
Проблема перша: тільки одна активна альтернатива
Симлінк — це симлінк. Він вказує на одне місце. Поки мова йде про вибір між редакторами — це цілком нормально. Але буває ситуація, коли треба запустити конкретну версію. Наприклад, сьогодні у нас є Node.js 18, Node.js 20 і Node.js 22, і всі вони можуть бути встановлені одночасно. /usr/bin/node вказує на якусь одну версію через update-alternatives.
І тут починається цирк. Ви запускаєте програму явно через node18, вона виконує дочірній процес через #!/usr/bin/node — і той дочірній процес запускається в Node.js 22. Бо симлінк. Ви в цей момент не контролюєте нічого, і помилки можуть бути найдивнішими.
Проблема друга: немає юзер-левел налаштувань
update-alternatives — інструмент системного адміністратора. Звичайний користувач на багатокористувацькій машині не може сказати «я хочу, щоб для мене editor запускав vim, а не nano». Без прав root — ніяк. Єдиний вихід — костилі через ~/.local/bin і ручні симлінки. Це не рішення, це обхідний шлях.
Проблема третя і найважливіша для openSUSE: /var і снапшоти
Ось тут починається найцікавіше, і саме через це openSUSE і почав рухатись у бік заміни.
update-alternatives зберігає свій стан у /var/lib/alternatives/.У openSUSE коренева файлова система снапшотується. А /var — ні. /var навмисно виведений в окремий btrfs-subvolume, який не входить до снапшота.
Чому це важливо? Тому що коли ви встановлюєте пакет, RPM-скрипти викликають update-alternatives, який записує стан у /var/lib/alternatives/. Але цей запис існує поза снапшотом. Якщо ви зробите rollback системи до попереднього снапшота — /var залишиться з новим станом. Стан alternatives і стан пакетів у снапшоті розійдуться. Система стає потенційно неконсистентною.
Для звичайного Tumbleweed-десктопу це може бути просто неприємно. Для MicroOS або будь-якої image-based системи — це вже концептуальна несумісність. Коли ціла ОС побудована навколо атомарних оновлень і гарантованих rollback’ів, механізм, який зберігає стан за межами снапшотів, є архітектурним протиріччям.
Що таке libalternatives
libalternatives народився у 2021 році — його запропонував Adam Majer в розсилці openSUSE Factory. Початкова мотивація була конкретною: прибрати проблему з Node.js і перенести логіку вибору версії з «магічного симлінка» в бібліотеку.
Ключова відмінність у підході: update-alternatives — це інструмент часу встановлення пакета, який модифікує глобальний стан. libalternatives — це бібліотека часу виконання, яка читає конфігурацію з файлів прямо під час запуску програми.
Як це виглядає технічно? Пакет кладе в /usr/share/libalternatives/<ім'я_програми>/<пріоритет>.conf файл з описом альтернативи. Сам бінарник /usr/bin/node, /usr/bin/java тощо стає симлінком на /usr/bin/alts. Коли ви запускаєте java, насправді запускається alts, який через argv[0] розуміє, що від нього хочуть java, читає конфіги з /usr/share/libalternatives/java/, враховує системні та юзерські уподобання — і передає виклик потрібному бінарнику через exec().
Конфіги — звичайні файли в /usr/share/, тобто вони є частиною пакета і входять до знімку файлової системи. Ніяких записів у /var/. Rollback — і стан alternatives повертається разом з усім іншим. Атомарність збережена.
Доступ до управління через alts:
# переглянути поточний стан для java
alts --display java
# перемкнути системний default (потрібен sudo)
sudo alts --set java /usr/lib/jvm/java-21-openjdk/bin/java
# або — і це ключова фіча — перемкнути тільки для свого юзера
alts --set java /usr/lib/jvm/java-25-graalvm/bin/java Юзерські налаштування живуть у ~/.config/libalternatives/ і мають найвищий пріоритет. Системний адміністратор може встановити одне, пакет — запропонувати інше, але ваш власний вибір переважить обидва.
Плюси libalternatives
Консистентність зі снапшотами — це головне. Конфіги в /usr/share/ — частина знімку. Rollback повертає все в узгоджений стан.
Юзер-левел переваги — нарешті-то. Можна мати Java 21 як системний default і Java 25 або GraalVM для себе, без sudo і без симлінків у ~/.local/bin.
Ієрархія пріоритетів прозора: юзер → адміністратор → пакет. Зрозуміло, хто перемагає і чому.
Немає scriptlet-хаосу — пакетники openSUSE знають, скільки болю приносили RPM-скрипти з %post і %preun, де треба було правильно викликати update-alternatives з правильними аргументами, і одна помилка ламала оновлення. З libalternatives пакет просто кладе файл конфігурації — і все.
Мінуси libalternatives
По суті, мінус тільки один, але давайте по порядку.
Невеликий overhead при запуску — замість прямого симлінку на бінарник тепер запускається alts, який читає файли конфігурації і потім робить exec(). Для редактора або Java-процесу це мікросекунди, яких ніхто не помітить. Але якщо хтось викликає альтернативу тисячі разів у скрипті — теоретично відчутно.
Не всі програми можна так просто замінити — деякі перевіряють argv[0] і покладаються на його конкретне значення. libalternatives має опцію options=KeepArgv0 для цього, але не всі edge cases покриті.
Сумісність з існуючими інструментами — те, що ви звикли перевіряти через update-alternatives --list, тепер треба перевіряти через alts --display. Скрипти, які покладалися на update-alternatives, треба адаптувати.
Поширення поза openSUSE — нульове — і це головний мінус, я б навіть сказав - мінусяра. Це важливо розуміти. Debian, Ubuntu, Fedora, RHEL — всі продовжують жити з update-alternatives. Якщо ви пишете Ansible-ролі або скрипти, що мають працювати на різних дистрибутивах, — вам все одно доведеться мати справу з update-alternatives. І тут SUSE неприємно відходить в сторону від стандартів.
Що буде з update-alternatives в openSUSE і взагалі
З того що я бачу в розсилках та на форумах - проект офіційно задокументував три можливі стратегії для мейнтейнерів пакетів. Перша — мігрувати на libalternatives. Друга — використати механізм Provides/Conflicts в RPM-специфікаціях (для взаємовиключних пакетів, наприклад різних мажорних версій Go). Третя — залишити update-alternatives, якщо альтернативи реально не підходять.
На практиці пакети мігрують поступово. Java — вже перейшла, звідси й мій неприємний сюрприз після оновлення. Інші — в процесі або ще чекають своєї черги. update-alternatives нікуди не зникне, але поступово він відходить на другий план у межах openSUSE-екосистеми, особливо для ключових пакетів типу Java та Node.js.
Поза openSUSE — все стабільно. update-alternatives є де-факто стандартом і нікуди не дівається. Він вбудований у Debian Policy, він є в RHEL-пакетах. Навряд чи хтось рипнеться піти шляхом SUSE.
Але, Tumbleweed продовжує тримати мене в тонусі. Після мого “раптового” переходу з AppArmor на SELinux я розібрався в мандатному контролі доступу краще, ніж за роки до цього. Тепер, завдяки Java і alts, я нарешті розібрався в тому, чому update-alternatives взагалі існував і чому йому на зміну має прийти щось розумніше. Ну і ще раз продивився як працює snapper і підтвердив, чому я надаю перевагу btrfs.
Comments
Please ensure Giscus is configured with your correct Repository ID and Category ID at giscus.app to enable comments.