Перейти до вмісту

Конфігурація як дані

Коли оновлення Arch у вівторок уранці потребує нового обхідного рішення для певного iGPU від Intel, — що робить традиційний інсталятор? Нічого, доки хтось не внесе латку в сценарій bash, який ним керує, не випустить новий ISO-образ (1—3 ГБ) і не дочекається, поки користувачі його завантажать. У кращому разі на це йдуть дні, частіше — тижні.

Ditana використовує принципово інший підхід: конфігурація як дані.

Три окремі частини

Розділ «Три окремі частини»

Архітектуру Ditana розділено на три сховища:

  1. Рушій (ditana-installer) — невелика універсальна програма, яка знає, як малювати діалогові вікна, розбивати диски на розділи, виявляти обладнання й запускати pacstrap.

  2. База знань (ditana-config) — структурована база даних, написана мовою KDL v2. Вона оголошує кожне налаштування, кожну особливість обладнання, кожну залежність пакунків, кожен сценарій життєвого циклу та логічні зв’язки між ними.

  3. Конвеєр пакунків (ditana-build) — те, що збирає пакунки, названі в базі знань. Сховище Ditana спирається на сховище Arch, а рецепти в ньому цей конвеєр перезбирає з вихідного коду, перевіряє й підписує, а не бере такими, як вони є.

Коли ви завантажуєтеся з ISO-образу Ditana, рушій підключається до GitHub і отримує найновіший випуск бази знань (а якщо підключення до інтернету немає, як резервний варіант використовує офлайн-знімок, вбудований в ISO-образ).

Варто знати й те, чого не охоплює отримання під час роботи. Сам рушій і конвертер, яким він читає KDL, містяться на носії й змінюються лише з новим носієм. А цей канал так само легко доставляє помилку, як і виправлення: конфігурація, у якій названо пакунок, якого не може надати сховище старішого носія, унеможливлює встановлення з цього носія, — саме це й сталося, коли у вересні 2026 року Arch вилучив bubblewrap-suid.

Три переваги, упорядковані за безпосередністю впливу:

  • Оновлення без перевипуску ISO-образу. Якщо користувач повідомляє, що недавнє оновлення Arch потребує нового обхідного рішення, виправлення потрапляє в ditana-config як невелика правка KDL. Уже наступна людина, яка завантажиться з ISO-образу Ditana, автоматично отримає оновлену логіку. Виправлення одразу доходить до користувачів, і нікому не потрібно завантажувати новий ISO-образ.
  • Прозоре обґрунтування. Оскільки налаштування оголошено як структуровані дані з явними умовами, правила можна перевірити від початку до кінця. Будь-хто може прочитати файли KDL і точно побачити, чому встановлюється пакунок, яка комбінація налаштувань спричиняє розгортання файлу конфігурації або яка властивість обладнання визначає доступність параметра. Жодної детективної роботи в bash.
  • Внески через відгалуження. Додавання обхідного рішення для обладнання, нового підлаштування стільничного середовища чи нового рішення щодо пакунків зазвичай зводиться до зміни одного файлу KDL. Жодних змін у рушії інсталятора — а поза детекторами обладнання, які є однорядковими виразами Raku, узагалі жодного Raku. README сховища ditana-config докладно знайомить учасників зі схемою; більшість запитів на внесення змін (pull request) стосується одного файлу.

Чому KDL, а не JSON чи YAML

Розділ «Чому KDL, а не JSON чи YAML»

База знань могла мати будь-який структурований формат. KDL вибрано тому, що він має властивості, яких справді потребує конфігурація Ditana:

  • Вбудовані коментарі. І YAML, і JSON тут мають труднощі — YAML підтримує коментарі, але їх підтримка в інструментах нерівномірна; у JSON коментарів немає взагалі. Налаштування Ditana містять розлогі коментарі з обґрунтуванням, які пояснюють, чому типове значення саме таке. Саме в цих коментарях і суть.
  • Необроблені рядки. Вбудовані фрагменти коду оболонки, вирази sed і шаблони регулярних виразів — повноцінні елементи. Жодного подвійного екранування " і \ заради синтаксичного аналізатора.
  • Ієрархічність без церемоній. Дочірні вузли, властивості й аргументи без проблем співіснують. Той самий вузол може описувати налаштування та містити вкладені вузли для його пакунків, сценаріїв і файлів.
  • Зручний для порівняння змін. Порядок вставлення зберігається. Під час перевірки коду додавання одного рядка дає diff в один рядок, а не структуру зі зміненим порядком.

Формат розбирає невеликий конвертер на Rust (json-kdl-converter, версії якого теж ведуться разом зі схемою), перш ніж інсталятор на Raku отримає його у вигляді JSON. У наступному розділі пояснено, як дані перевіряються від початку до кінця ще до всього цього.

Як це виглядає на практиці

Розділ «Як це виглядає на практиці»

Повне робоче налаштування в ditana-config виглядає так:

// XFCE fallback for Wayland-only primary terminals (foot, cosmic-term).
// Fires when the user picks a Wayland-native terminal AND also installs
// XFCE. Writes to xfce-xdg-terminals.list only; xdg-terminals.list still
// points at the primary terminal for Wayland sessions.
- name="fallback-kitty-for-xfce" \
default-value="`(install-foot OR install-cosmic-term) AND install-xfce`" {
arch-packages "kitty" \
"imagemagick" \
"python-pygments" \
"libcanberra" \
"xdg-terminal-exec-git"
files "/etc/xdg/kitty/kitty.conf" \
"/usr/share/pixmaps/ditana-logo-tiny.png"
chroot-script "mkdir -p /etc/xdg" \
"echo kitty.desktop >/etc/xdg/xfce-xdg-terminals.list"
}

Цей вузол оголошує похідне налаштування без власного діалогового вікна, логічний вираз над трьома іншими налаштуваннями, пакунки, які слід встановити, коли вираз істинний, і файл, завдяки якому XFCE їх використовує. Обґрунтування стоїть поруч із правилом, де людина, яка його перевіряє, може побачити, чому воно таке. Повна документація схеми — у README сховища ditana-config.

Така сама форма описує й речі, які зовсім не є пакунками. Програма, якій для пісочниці потрібен непривілейований простір назв користувача (user namespace), зазначає це поруч із собою:

- name="flatpak" default-value=#true {
arch-packages "flatpak"
// Every Flatpak, not only the browsers, runs bwrap to build its outer
// sandbox, and bwrap needs a user namespace. This is the declaration
// that buys it one.
userns-allow "/usr/bin/bwrap"
}

Ditana не надає непривілейовані простори назв користувача кожній програмі. Мінімальна програма BPF, ditana-userns-guard, під’єднується до власного хука ядра userns_create і відмовляє в просторі назв будь-якому виконуваному файлу, для якого його не запитало жодне налаштування. Хук такого типу може лише відмовити й ніколи не може надати дозвіл, тому заборону просторів назв через sysctl знімають лише після того, як програму завантажено, — комп’ютер, який не може її завантажити, залишає їх повністю вимкненими. Це типовий стан одного з параметрів розділу System Hardening; вимкніть його — і простори назв стануть відкритими для всього, як у більшості дистрибутивів.

Ditana відмовляє в просторах назв користувача всьому, що їх не запитувало. Працездатним це робить те, що запит локальний: його записано там, де вибирається пакунок, тож ніщо не мусить вести синхронізований центральний список, а з вимкненням налаштування зникає і дозвіл. Інсталятор збирає оголошення тих налаштувань, що зрештою залишаються ввімкненими, і генерує з них список дозволених виконуваних файлів.

Перевірка: виявлення помилок до публікації

Розділ «Перевірка: виявлення помилок до публікації»

Така велика база знань потребує запобіжників. Для кожного коміту — і локально через хуки pre-commit, і віддалено в GitHub Actions — запускається багаторівневий конвеєр перевірок, який виявляє помилки, що їх може пропустити людина під час перевірки:

  • Синтаксис KDL (kdlfmt) — файл узагалі вдається розібрати.
  • Перевірка за схемою (ditana-schema.json) — кожне поле має правильний тип, немає невідомих властивостей і не бракує обов’язкових полів. Виявляє друкарські помилки в назвах полів, як-от arc-packages замість arch-packages.
  • Цілісність посилань на файли — кожен файл, на який посилається налаштування, справді існує в folders/; і навпаки, на кожен файл у folders/ посилається щонайменше одне налаштування. Покинуті файли позначаються. Посилання на каталоги (які мовчки нічого б не робили, бо інсталятор використовує cp без -R) відхиляються.
  • Коректність життєвого циклу — рядки chroot-script, що звертаються до /etc/skel/, відхиляються, бо користувача створюють після виконання chroot-script, — правильним полем є early-chroot-script. Це тонка помилка, яка інакше дала б систему, де в домашньому каталозі нового користувача немає очікуваних файлів.
  • Коректність сценаріїв оболонки — кожен фрагмент коду оболонки в будь-якому з полів зі списками сценаріїв перевіряється за допомогою bash -n на синтаксис і shellcheck на якість. Пропущена лапка чи помилка на кшталт [ "$x" = $y ] виявляється під час коміту, а не під час першого встановлення.
  • Оголошені дозволи й параметри ядра — запис userns-allow має бути абсолютним шляхом, а запис sysctl має називати справжній ключ sysctl і надавати йому значення, яке ядро прийме. Обидва поля додано в 0.9.4, і їх оголошено в ditana-schema.json.
  • Суперечливі параметри ядра — два налаштування, що записують той самий ключ sysctl з різними значеннями, зупиняють встановлення, і в повідомленні названо обидва. Коли все збиралося як текст оболонки, як це було до 0.9.4, просто перемагав останній записаний рядок, і ніхто про це не дізнавався.
  • Цілісність логічних виразів — зворотні апострофи мають бути парними; кожна назва налаштування, на яку посилається вираз default-value чи available, має справді існувати десь у базі знань. Друкарська помилка на кшталт install-cosmik замість install-cosmic виявляється під час коміту, а не спричиняє непомітний збій під час роботи.

Повна перевірка локально за допомогою pre-commit run --all-files триває лічені секунди й повторюється під час кожного надсилання змін на GitHub. Якщо локально все зелене, то й у CI все зелене; зламані коміти ніколи не потрапляють у випуск.

Це машинний переклад. Читачі вдосконалюють його на Weblate.