Один хост і двадцять один файл: NixOS Flake, який не заважає працювати

Перекладено ШІ 0 JustSteveKing 05 серпня, 2026

Дізнайтеся, як побудувати архітектуру NixOS для повної синхронізації системи та середовища розробника за допомогою лише однієї команди. Розповідаємо, чому варто замінити dotfiles на packages та як керувати кольорами всього інтерфейсу з одного файлу.

Мій основний комп’ютер працює на NixOS. Це Framework Desktop з процесором AMD AI Max 300-ї серії, на якому запущено Wayland-композитор niri та оболонка noctalia. Конфігурація складається з двадцяти одного .nix файлу (всього 26 файлів у репозиторії), а один білд створює одночасно і систему, і користувацьке середовище.

Список пакетів — найнудніша частина будь-якого конфігу. Я ж хочу зосередитися на внутрішній структурі, оскільки три ключові рішення визначають усе інше. Мені не потрібно вручну оновлювати список імпортів. Десктопні програми налаштовуються через створення окремих пакетів, а не через dot-файли. І лише один файл на 39 рядків керує кольорами всього інтерфейсу.

Кожне з цих рішень має свої нюанси, які я виявив на власному досвіді. Спочатку розберемо саму конфігурацію, а потім — де саме вона може «вкусити».

Флейк — це лише заготовка

Ось повний flake.nix. Лише вхідні дані (inputs) та один рядок вихідних (outputs).

{
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";

    flake-parts.url = "github:hercules-ci/flake-parts";
    import-tree.url = "github:vic/import-tree";

    wrapper-modules.url = "github:BirdeeHub/nix-wrapper-modules";

    home-manager = {
      url = "github:nix-community/home-manager";
      inputs.nixpkgs.follows = "nixpkgs";
    };

    nixos-hardware.url = "github:NixOS/nixos-hardware/master";

    stylix = {
      url = "github:nix-community/stylix";
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };

  outputs = inputs: inputs.flake-parts.lib.mkFlake { inherit inputs; }
    (inputs.import-tree ./modules);
}

Останній рядок робить усю магію. flake-parts забезпечує модульну систему для самого flake, а import-tree рекурсивно передає йому кожен .nix файл із директорії ./modules. Додаєте новий файл у дерево — і він автоматично підключається. Немає потреби синхронізувати список imports = [ ... ], що зазвичай є найнуднішою частиною підтримки Nix-конфігурації.

Варто чітко окреслити компроміси, оскільки вони не очевидні відразу:

  • Кожен файл у modules/ має бути модулем flake-parts (рівні flake.*, perSystem, config тощо). Якщо покласти туди звичайний NixOS-модуль, евалюація завершиться помилкою, яка не вкаже на реальну причину.
  • import-tree ігнорує файли, назва яких починається з підкреслення. Це зручно: _scratch.nix можна тримати в репозиторії без активації.
  • Файли без розширення .nix повністю ігноруються, тому випадковий JSON-файл ні на що не вплине.

Через перше правило два елементи живуть поза межами modules/. Файл palette.nix лежить у корені, бо це звичайний attrset, а не модуль. Директорія users/ містить модулі Home Manager, які також не є модулями flake-parts, тому вони імпортуються за шляхом. Це має свою ціну: файли в users/ доводиться прописувати вручну.

Модулі — це значення, а не файли

У цій конфігурації нічого не імпортується за прямим шляхом до файлу (окрім згаданого вище винятку). Натомість кожен файл визначає іменований output.

flake.nixosModules.stylixTheme = { pkgs, lib, ... }: { /* ... */ };

Інші файли використовують його за іменем через self:

imports = [
  self.nixosModules.frameworkHardware
  self.nixosModules.niri
  self.nixosModules.stylixTheme
  self.nixosModules.devServices
];

Повний ланцюжок від виходу flake до стилізованого додатка виглядає так:

flake.nixosConfigurations.framework          (hosts/framework/default.nix)
├─ nixos-hardware framework-desktop-amd-ai-max-300-series
├─ self.nixosModules.frameworkConfiguration  (hosts/framework/configuration.nix)
│   ├─ self.nixosModules.frameworkHardware   (hosts/framework/hardware.nix)
│   ├─ self.nixosModules.niri                (features/niri.nix)
│   ├─ self.nixosModules.stylixTheme         (features/stylix.nix)
│   └─ self.nixosModules.devServices         (features/devservices.nix)
└─ home-manager.nixosModules.home-manager
    └─ users."steve" = users/steve.nix
        └─ users/features/*.nix              (вказані вручну)

self та inputs передаються через specialArgs та extraSpecialArgs, тому модулі NixOS та Home Manager мають доступ до виходів flake:

flake.nixosConfigurations.framework = inputs.nixpkgs.lib.nixosSystem {
  specialArgs = { inherit self inputs; };
  modules = [
    # ...
    {
      home-manager = {
        useGlobalPkgs = true;
        useUserPackages = true;
        backupFileExtension = "hm-bak";
        extraSpecialArgs = { inherit self inputs; };
        users."steve" = import ../../../users/steve.nix;
      };
    }
  ];
};

Home Manager працює як NixOS-модуль, а не автономно, тому nixos-rebuild switch — єдина команда, яка мені потрібна. Системні та користувацькі налаштування активуються синхронно.

Рядок із backupFileExtension важливіший, ніж здається. Home Manager відмовляється перезаписувати файли, які він не створював. Встановлення розширення для бекапу дозволяє перейменувати старий файл, щоб активація пройшла успішно.

Два контексти обчислення

Це концепція, яка пояснює більшість незрозумілих помилок у flake-parts. Її варто засвоїти якомога раніше.

Існує два окремі світи:

  • flake.* — обчислюються один раз для всього flake (наприклад, nixosConfigurations та nixosModules).
  • perSystem — обчислюються окремо для кожної архітектури (x86_64-linux тощо), кожна зі своїм pkgs. Це стосується packages, devShells та formatter.

modules/parts.nix оголошує системи (у моєму випадку — чотири), навіть якщо використовується лише одна:

{
  config.systems = [
    "x86_64-linux" "x86_64-darwin" "aarch64-linux" "aarch64-darwin"
  ];
}

Головна пастка: perSystem не бачить конфігурацію NixOS. Це зовсім інший процес обчислення з власним pkgs і без доступу до config.*. Це має два наслідки.

По-перше, пропрієтарні пакети. Я встановлюю nixpkgs.config.allowUnfree = true всередині модуля NixOS, але це не діє на pkgs, які бачить perSystem. Якщо звернутися до pkgs.vscode всередині perSystem, обчислення перерветься. Саме тому гарячі клавіші в моєму конфігу викликають програми через PATH, а не через пряме посилання на пакет:

# Свідомо не lib.getExe pkgs.vscode
"Mod+C".spawn-sh = "code";

Це також дозволяє не тримати 400 МБ редактора в closure композитора, що пришвидшує роботу.

По-друге, кольори тем. config.lib.stylix.colors так само недоступний із perSystem. Саме тому моя палітра — це звичайний файл, який можна імпортувати, а не значення, що зчитується зі Stylix під час обчислення.

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

Програми — це пакети, а не dot-файли

Ні niri, ні noctalia не налаштовуються через опції Home Manager або ручні конфіги. Натомість perSystem створює попередньо налаштований wrapper за допомогою wrapper-modules:

perSystem = { pkgs, lib, self', ... }: {
  packages.myNiri = inputs.wrapper-modules.wrappers.niri.wrap {
    inherit pkgs;
    settings = {
      spawn-at-startup = [ [ (lib.getExe self'.packages.myNoctalia) ] ];
      layout.gaps = 5;
      binds = {
        "Mod+Return".spawn-sh = lib.getExe pkgs.ghostty;
        "Mod+Q".close-window = { };
        # ...
      };
    };
  };
};

Модуль NixOS просто встановлює цей пакет:

flake.nixosModules.niri = { pkgs, ... }: {
  programs.niri = {
    enable = true;
    package = self.packages.${pkgs.stdenv.hostPlatform.system}.myNiri;
  };
};

Уся конфігурація композитора тепер знаходиться всередині derivation. Змінили гарячу клавішу — отримали новий store path. У ~/.config нічого не пишеться, тому конфігурація нікуди не «пливе».

Де ця модель дає збій

Я хочу бути відвертим щодо проблем: деякі програми орієнтовані на GUI. Їхні панелі налаштувань редагують конфіг-файл, а не просто накладають зміни. Noctalia — одна з них. За замовчуванням wrapper вказує змінній середовища на конфіг у Nix store, який доступний лише для читання. Усі зміни, які я робив у самій програмі, тихо не зберігалися, тоді як справжній файл у ~/.config ігнорувався.

Ще гірше те, що самі налаштування просто відкидалися. Noctalia завантажує конфіг через QML JsonAdapter, який ігнорує невідомі ключі без жодних повідомлень у логах. Ручний блок Nix із ключами на кшталт bar.height створював оболонку з дефолтними параметрами, і жодна система не казала мені, чому.

Два висновки, які корисні не лише для цієї програми:

  1. Перевіряйте ключі за реальною схемою програми. У моєму випадку істина ховалася в $out/share/noctalia-shell/Assets/settings-default.json.
  2. Перевіряйте, чи програма розраховує сама записувати свій конфіг. Якщо так, то шлях у store — погане рішення. Wrapper-modules має механізм outOfStoreConfig, але це компроміс, на який треба йти свідомо.

Декларативний шар конфігурації та програма, що сама зберігає свій стан, завжди конфліктуватимуть, і програма зазвичай перемагає мовчки.

Один файл для всіх кольорів

palette.nix — це звичайний attrset формату base16 (Catppuccin Mocha), який ні від чого не залежить:

{
  base00 = "1e1e2e"; # base: фон за замовчуванням
  base01 = "181825"; # mantle: світліший фон
  # ...
  base0D = "89b4fa"; # blue: функції, заголовки
  base0E = "cba6f7"; # mauve: ключові слова
}

Stylix використовує ці дані та стилізує близько 130 додатків: GTK, Qt, VS Code, ghostty, fuzzel, bat, GRUB та інші. Шрифти налаштовуються так само. Жодна програма в цьому конфігу не має власних налаштувань кольору.

Редагування одного файлу перефарбовує весь десктоп після наступного перезбірання.

Але тут є дві пастки з перевизначенням (override). Таргети Stylix використовують mkDefault, тому будь-яке явне налаштування у вашому конфігу мовчки переможе, і здаватиметься, що Stylix нічого не робить. Якщо ви самі прописали font-family або workbench.colorTheme — ось чому тема не застосовується.

Структура та конвенції

flake.nix                      inputs + один рядок outputs
palette.nix                    кольори base16, не модуль
modules/
  parts.nix                    список систем
  hosts/framework/
    default.nix                nixosConfigurations.framework
    configuration.nix          системний конфіг
    hardware.nix               генерований файл, UUID та модулі ядра
  features/
    niri.nix                   пакет композитора + NixOS-модуль
    noctalia.nix               пакет оболонки
    stylix.nix                 стилізація
    devservices.nix            бази даних у контейнерах
users/
  steve.nix                    індекс користувача, ідентичність + імпорти
  features/                    файли за напрямками
    cli.nix  desktop.nix  dev.nix  direnv.nix  firefox.nix
    git.nix  shell.nix  ssh.nix  terminal.nix  vscode.nix

Поділ іде за областю дії (scope), а не за темою. Усе, що стосується користувача, лежить у users/features/: пакети, аліаси, скрипти в ~/.local/bin. Системні налаштування — у configuration.nix.

Це працює завдяки тому, що home.packages — це список, а home.shellAliases — attrset. Кожен файл може додавати до них свої значення, і вони об'єднуються без конфліктів.

Важливі нюанси

Flakes бачать лише файли, що відстежуються в git. Якщо ви створили .nix файл, але не зробили git add, білд його не побачить, а помилка виглядатиме як відсутність опції. Також тримайте .gitignore максимально лаконічним.

Не запускайте nix flake update через sudo. Це оновлює flake.lock, який належить вашому користувачу. Якщо зробити це від імені root, файл змінить власника, і наступні спроби оновлення без привілеїв видаватимуть помилку доступу.

Фіксуйте образи контейнерів за digest, а не за тегом. oci-containers підтягує образ лише тоді, коли його немає локально. Тег :18 зафіксує версію, яка була актуальною на момент першого запуску. Digest робить це відтворюваним:

image = "docker.io/library/postgres:18@sha256:3a82e1f56c8f0f5616a11103ac...";

Цикл розробки

Мій цикл перевірки складається з двох або трьох команд:

nix flake check                                 # перевірка всіх виходів
sudo nixos-rebuild build --flake .#framework    # збірка без активації
sudo nixos-rebuild switch --flake .#framework   # застосування

Для роботи над десктопом збірка одного пакета значно швидша за повний ребілд системи:

nix build .#myNiri
nix build .#myNoctalia

Це головна перевага підходу з обгорнутими пакетами (wrapped packages). Зміна гарячої клавіші — це збірка пакета, а не активація всієї системи. Ви можете перевірити згенерований конфіг прямо в store:

nix build .#myNoctalia --no-link --print-out-paths

Що дає такий дизайн

  • Одна команда: rebuild створює і систему, і середовище користувача. Ніяких окремих home-manager switch.
  • Автоматизація: Ніякої бухгалтерії з імпортами в modules/. Новий файл — нова функція.
  • Контроль: Десктоп-конфігурація адресована за контентом (content-addressed). Жодних мутабельних файлів, що можуть змінитися з часом.
  • Єдність: Один файл для всієї палітри кольорів.

Яка ціна

  • Контекстна межа: Треба постійно пам'ятати про розділення між flake та perSystem. Помилки тут не вказують на справжню причину.
  • Усе або нічого: Автоімпорт вимагає, щоб кожен файл був модулем певного типу.
  • Проблеми з GUI-програмами: Усе, що хоче самостійно записувати свій стан, конфліктує з Nix.
  • Тихі відмови: Це найскладніше. Stylix, JSON-адаптери або не додані в git файли не видають помилок — вони просто створюють систему, де ваші налаштування ігноруються.

Головна звичка, яку варто виробити: перевіряйте згенерований артефакт, а не тільки вхідні дані, які ви передали.

Популярні

Інше, що варто прочитати

104 Оновлено 26 червня, 2026

Все, що потрібно знати про Laravel 13

Laravel 13 вийде в березні 2026 року й вимагатиме мінімум PHP 8.3. Хочете дізнатися, як PHP‑атрибути для моделей, нові налаштування черг і метод Cache::touch() вплинуть на вашу розробку?

13 Оновлено 26 червня, 2026

Удосконалюйте свої проєкти Laravel за допомогою справжнього штучного інтелекту для кодування з Laravel Boost!

Готові підняти свій робочий процес у Laravel на новий рівень? У цій статті я розгляну Laravel Boost, інноваційний AI-допомічник для програмування, який зробить вашу розробку швидшою та продуктивнішою

13 Оновлено 26 червня, 2026

Генерація документації в Laravel за допомогою штучного інтелекту

Docudoodle — це потужний пакет для генерації документації в Laravel, який допомагає легко аналізувати вашу кодову базу та створювати документацію за допомогою обраного вами AI. Чи готові ви дізнатися, як цей інструмент може спростити вашу роботу з документуванням коду? Читайте далі!