Если эта shitpost-like статья выглядит как шиза

Нормальная дока на ангельском: github/scclie/sccl_nix/README.md

тут я просто вываливаю мысли


На*&я Зачем?

Вот мне раньше, после 6 лет юза онли арча - тоже не было понятно.

Мол наплодили ещё какой-то попсовый дистр, аля федоры, манжары и прочих качи.

А потом мне захотелось его установить…

После этого, видимо, мир перевернулся.

Куча проблем, что возникали на обычных дистрах - решились моментами. Мне даже ранее не казалось что это проблемы - за годы использования стандартных дистров ловишь консенсус, просто привыкаешь к тому как над в них работать, чтоб всё было заебись.

Но то заебись и это заебись - это совершенно два разных заебись.

Самое смешное - проблемы, о которых даже не думаешь как о проблемах. Конфиги расползаются по /etc, пакеты ставятся и забываются, systemd-юниты включаются ручками и теряются. Через полгода система - чёрный ящик. Ты понятия не имеешь, как она дошла до жизни такой.

Хуярить ебучий git для /etc? fr

NixOS предлагает обратное: ты описываешь конечный результат. Все пакеты, конфиги, юзеры, сервисы - один билд, одно состояние.

Хочешь вторую машину? Те же конфиги, другой хост. Хочешь откатиться? Въёбываешь предыдущую генерацию. Каждый билд лежит в /nix/store, ничего не раскидывается по /usr и /etc как попало.

Отдельный кайф с шеллами. Часто надо поставить “одноразовые проги” - поставил, юзанул, забыл. А оно лежит в системе ;k

Никс предлагает nix shell - накидал любых прог, поюзал, забыл - само удалится при сборке мусора. Зависимости тянутся только в твой шелл из /nix/store.

Можно просто nix run nixpkgs#imagemagick -- convert ... и оно скачается, исполнится, и при следующем garbage collector исчезнет. Никаких следов.

ручками, если хочш почистить - nix-collect-garbage -d втуливаешь.


Откуда вообще Nix

В 2006 году крутой мужик по имени Eelco Dolstra защитил PhD в Utrecht University. Тема диссертации: “The Purely Functional Software Deployment Model

Идея была радикальной: а что если пакет - это чистая функция?

Вход: исходники + зависимости + флаги компиляции. Выход: бинарник.

Одинаковый вход = одинаковый выход. Всегда. На любой машине. Без “у меня собирается а у тебя нет”.

Для этого /nix/store устроен гениально просто. Каждый пакет лежит в своей директории, и путь включает хеш всех входных данных:

/nix/store/5mq2jcn36ldd97xrcnf3jg8irnfp5xqr-fish-4.0.1/

Хеш зависит от исходников, зависимостей, флагов компиляции, версии компилятора. ВСЕГО.

Любое изменение - другой хеш - другой путь. Два пакета не конфликтуют потому что лежат в разных директориях. Атомарные обновления. Никакого “я поставил новую версию и поломал старую”.

Эт совершенно другая парадигма, по сравнению с другими пакетниками.

Экосистема nixpkgs сейчас - крупнейший в мире репозиторий пакетов. И всё это собирается одной фермой Hydra и кешируется в бинарный кеш.

на самом деле немного обман цифр - очень много пакетов разбиты на куча маленьких. Если смотреть по реальному софту - nixpkgs меньше чем тот же aur.


Императивное vs декларативное

Как вываливали в какой-то очень старой мысли ещё на заре придумывания Nix - пакетники можно сравнить с языками программирования.

Есть статья “Functional Package Management with Guix” где эта мысль хорошо раскрыта, но идея звучала и раньше.
мне лень искать, где ещё, но было.

Императивные пакетники (apt, yum, pacman) - это как процедурное программирование. Ты говоришь системе ЧТО ДЕЛАТЬ: apt install nginx, потом systemctl enable nginx, потом ручками конфиг в /etc/nginx/nginx.conf. Последовательность команд меняет состояние.

Декларативные (Nix, Guix) - это как функциональное. Ты описываешь КАКОЙ РЕЗУЛЬТАТ нужен:

services.nginx = {
  enable = true;
  virtualHosts."example.com" = {
    root = "/var/www/example.com";
  };
};

Не “поставь nginx и настрой”. А “вот такой nginx должен быть в системе”.

Разница фундаментальная. В императивном мире ты теряешь историю - система помнит текущее состояние, но не помнит как она в него пришла. В декларативном - каждый билд это функция от конфига. Хочешь повторить на другой машине - тот же конфиг, тот же результат.

а дальше чо? Какой-то более высокоуровневый подход, более абстрактный? (иль мега абстрактный llm-based chat interface??????? - ai problem resolver :D )


Экосистема

Сам по себе NixOS решает только часть проблемы. Системный уровень. А есть ещё куча всего, что хочется в декларативщину.

flakes

Экспериментальная фича с 2021 года. До сих пор экспериментальная.

Но по факту все давно сидят на флейках. Они решают две главные проблемы старого Nix: версионирование зависимостей (через flake.lock) и единый формат проекта.

До флейков был channels - глобальное состояние системы. Поменял канал - поменялось для всего. Никакой изоляции.

С флейками у каждого проекта свой flake.lock, который фиксирует конкретные версии ВСЕХ зависимостей. Как package-lock.json но для всей системы.

Флейк авто-дискаверит хосты из папок - кидаешь hosts/<hostname>/, он сам подхватывает в nixosConfigurations.<hostname>. Никаких if hostname == костылей на уровне дискаверинга.

home-manager

10k звёзд на гитхабе. Позволяет декларативно управлять пользовательским окружением - дотфайлы, пользовательские пакеты, сервисы.

Раньше дотфайлы делали через GNU stow или bare git repo. home-manager позволяет всё то же самое, но в Nix-стиле: с опциями, с зависимостями, с иммутабельностью.

И главное - интегрируется с NixOS модулем. Один nixos-rebuild switch пересобирает и систему и пользовательское окружение.

disko

NixOS - это дистр где всё описывается как код. Кроме одного: разметка дисков при установке делается ручками. disko исправляет это печальное упущение.

Достаточно один раз прописать конфиг разметки дисков, а не дрочить каждый раз gparted
Если хосты с одинаковыми дисками - можно спокойно реплиецировать.

sops-nix

Секреты (SSH-ключи, пароли, GPG) шифруются age-ключом и лежат прямо в репе. При билде расшифровываются в нужные места.

Раньше секреты хранили либо вне репы (и теряли), либо в зашифрованных git-репо (неудобно). sops-nix делает это прозрачно: секреты как часть конфига, но зашифрованы.


Проблема масштабирования

С NixOS есть одна засада. Ты начинаешь с одного хоста, всё красиво. Потом добавляешь второй. Потом третий.

И конфиг превращается в свалку из кучи исключений и т.п. (всё как в любом коде)

Есть и другие подходы:

  • flake-parts - модульная система для флейков, позволяет разбивать конфиг на переиспользуемые модули. Хорош для больших проектов, но добавляет свой слой абстракции.
  • snowfall-lib - библиотека для организации Nix-конфигов с авто-дискавери. Похожа на то что делаю я, но со своей философией.
  • Куча народу просто живёт с if hostname и не парится. Тоже варик, если хостов 1-2.

Собственно, из этого и родился sccl_nix. - моя конфигурация, которая пытается не превратиться в помойку. (пару раз превращалась, но я очень стараюсь)
Мне хотелось архитектуру, где добавление нового хоста - это создание папки и декларирование sccl.* опций. Без единого if.


Архитектура sccl_nix

Дотфайлы разделены на три сущности.

hosts - железо и системщина. Диски (disko), дрова GPU, сеть, системные пакеты. Всё что нельзя синкать между разными машинами. Авто-дискаверятся флейком из hosts/.

profiles - пользователь со своим зоопарком прог, шеллом и user-level хламом. На одном хосте может быть сколько угодно профилей. Импортятся в конфиг хоста.

shared - база для всех. Рыба, niri, alacritty, waybar, базовые CLI тулзы. Один раз настроил - импортнул во все профили.

Сейчас два хоста: sacculos (десктоп, AMD) и aero15laptop (ноут, NVIDIA). Два профиля: paper (основной, со всем зоопарком) и bootstrap (минимальный, только vim, nmtui и ssh для установки с флешки).
как пример реального юза.

Схема:

flowchart TD
    F["❄️ flake.nix<br/>auto-discovery: hosts + shells"]

    NM["⚙️ nixos/modules/core.nix<br/>sccl.* options: conditional config"]

    H1["🖥️ hosts/sacculos<br/>desktop + amd gpu"]
    H2["💻 hosts/aero15laptop<br/>laptop + nvidia gpu"]

    P1["👤 profiles/paper<br/>main: steam, blender..."]
    PS["📦 profiles/shared<br/>base: fish, niri, waybar..."]

    PB["🪶 profiles/bootstrap<br/>minimal: nmtui, vim, ssh"]

    S["🔐 secrets/ sops-nix encrypted"]
    SH["🐚 shells/ auto-discovered devShells"]

    F --> H1
    F --> H2
    F --> SH

    H1 --> NM
    H2 --> NM

    H1 --> P1
    H2 --> P1

    H1 --> PB
    H2 --> PB

    P1 --> PS

    classDef flakeStyle stroke:#517599,stroke-width:2px,color:currentColor
    classDef hostStyle stroke:#5e81ac,stroke-width:2px,color:currentColor
    classDef profileStyle stroke:#8fbcbb,stroke-width:2px,color:currentColor
    classDef sharedStyle stroke:#d08770,stroke-width:2px,color:currentColor
    classDef moduleStyle stroke:#bf616a,stroke-width:2px,color:currentColor
    classDef secretStyle stroke:#bf616a,stroke-width:2px,color:currentColor
    classDef shellStyle stroke:#d08770,stroke-width:2px,color:currentColor

    class F flakeStyle
    class H1,H2 hostStyle
    class P1,PB profileStyle
    class PS sharedStyle
    class NM moduleStyle
    class S secretStyle
    class SH shellStyle

sccl.*

Главная фича, которая не даёт конфигу скатиться в свалку if hostname ==.

В core.nix определены ВСЕ опции, и все они по дефолту false. Каждый модуль гейтит сам себя через lib.mkIf config.sccl.<feature>.enable.

{ config, lib, ... }:
{
  config = lib.mkIf config.sccl.zapret.enable {
    services.zapret = { ... };
  };
}

Никаких if hostName == "laptop" then false. Модуль просто не включается.

Полный список опций (на момент написания статьи, хз буду ли обновлять):

sccl.ui.enable           # Nord-тема + greetd
sccl.audio.enable        # pipewire
sccl.bluetooth.enable    # bluetooth + blueman
sccl.net.enable          # networkmanager + прокси + firewall
sccl.automount.enable    # udisks2 автоподключение флешек
sccl.secrets.enable      # sops-nix шифрованные секреты
sccl.zapret.enable       # обход DPI
sccl.flclashx.enable     # FlClashX GUI для прокси
sccl.playground.enable   # Docker + dev tools
sccl.chaotic.enable      # chaotic-nyx репо
sccl.nix-ld.enable       # nix-ld для бинарников не из nixpkgs
sccl.bootstrap           # bool: минимальный режим установки

Конфиг хоста выглядит буквально так:

sccl = {
  ui.enable = true;
  audio.enable = true;
  net.enable = true;
  automount.enable = true;    # только на десктопе
  zapret.enable = true;       # только на десктопе
  playground.enable = true;   # только на десктопе
  nix-ld.enable = true;
};

Добавление нового хоста - скопировал папку, поправил configuration.nix, сгенерил hardware-configuration.nix, поправил disko.nix. Флейк сам его подхватит.

Bootstrap-профиль решает проблему установки на слабом железе. Накатил bootstrap (~1-2 GB), загрузился, переключил sccl.bootstrap = false, перебилдил - и вот полная система. Не надо ждать пока на ноуте соберётся 100500 пакетов с флешки где ещё и места нет.

Для этого можно юзать nixos-anywhere, но bootstrap остаётся как бекап-вариант.


Клава и секреты

Раскладка

Раскладка параметризована через аргументы флейка (specialArgs).

У sacculos эрго-сплит на Vial прошивке - us,rulemak_vial. У aero15laptop - colemak_dh,rulemak. Один mkHost в flake.nix, и все слои (системная клава, XKB, WM) получают правильную раскладку.

На уровне WM (niri) раскладка тоже передаётся параметром: на десктопе Colemak-CAWS, на ноуте Colemak-DH-Wide. Это удобно когда у тебя разные клавиатуры с разной прошивкой и разными раскладками на разных машинах.

Без копипасты и костылей.

Секреты

SSH-ключи, GPG - всё зашифровано через sops-nix с age-ключом и лежит прямо в репе в secrets/common.yaml.

При ребилде с sccl.secrets.enable = true ключи сами расшифровываются в ~/.ssh/, GPG импортится автоматом.

Всё что над хранить отдельно - приватный age-ключ. Он у меня в менеджере паролей, не в репе.


Подвох?

Их есть у меня.

Nix - это другой язык. Не просто синтаксис конфигов, а полноценный функциональный язык. Ленивый, с динамической типизацией, со своей странной стандартной библиотекой. Кривая обучения - отвесная стена.

Документация. Она есть, но… Многое узнаёшь методом тыка и чтения чужих конфигов. How to Learn Nix от Ian Henry - это буквально дневник на 50+ постов о том как чувак учил Nix.

Время билда. Первый билд может идти долго, особенно если мало что закешировано. НоHydra кеширует почти всё, т.ч. последующие билды быстрые.

Flakes experimental с 2021. До сих пор. Но все используют. Статус скорее формальность на этом этапе.

Оверкилл для простых вещей. Если у тебя один сервер с nginx - тебе не нужен NixOS. Но если у тебя 3+ машины, кастомные патчи ядра, свой софт собранный из git - оно того стоит.

В целом: да, при большом скилле и тут можно насрать. Но архитектура sccl_nix хотя бы пытается этого не допустить. я в это верю (пытаюсь)

пока полёт нормальный. десктоп и ноут живут на одном конфиге с одинаковым профилем. Я получаю всегда одинаковую рабочую среду и даже не замечаю сижу ли я за ноутом, иль за пк (моя осанка замечает) - всё всегда настроено одинакого.