mirror of
https://github.com/oqyude/nixos.git
synced 2026-10-10 05:55:24 +03:00
245 lines
19 KiB
Markdown
245 lines
19 KiB
Markdown
# Инварианты: вопросы владельцу
|
||
|
||
Проход по репозиторию сверху вниз, 2026-10-05. 120 `.nix`, ~8.5k строк.
|
||
|
||
Структура:
|
||
- Сводный ответ, ядро и ловушки → **`AGENTS.md`** (корень репозитория).
|
||
- Подробная карта архитектуры с per-host деталями и инвентарём сервисов →
|
||
**`docs/arch/map.md`**.
|
||
- Этот файл → **открытые вопросы** (слои 0–8) + ещё непрочитанные **слои 9–11**
|
||
(home-manager, deploy, формат).
|
||
|
||
Пометки: `[!]` — найденный дефект, не вопрос. `[?]` — не смог определить по коду.
|
||
`[✓]` — отвечено владельцем 2026-10-05.
|
||
|
||
## Статус ответов (2026-10-05)
|
||
|
||
Отвечено: **2.1, 5.2, 6.1, 6.3, 6.5, 7.1, 7.2** (7 пунктов). Остальные ждут
|
||
ответа (таблица ниже).
|
||
|
||
Ключевое из ответов, что меняет картину:
|
||
|
||
- **6.5 — моя ошибка.** `100.64.0.0` не «сетевой адрес вместо интерфейса»:
|
||
это Tailscale-адрес sapphira, назначенный вручную.
|
||
- **6.3 — это не дыра, а осознанное решение.** Граница держится на роутере:
|
||
на сервер пробрасываются ровно 5 портов — **443, 80, 22000 (syncthing),
|
||
8443 (xray), 22 (ssh)**. `firewall.enable = false` на sapphira — следствие,
|
||
а не недосмотр. Проблема в другом: **список пробросов нигде не записан
|
||
в репозитории**, и именно его агент обязан уважать (D1 в `todo.md`).
|
||
- **7.2 — 3x-ui рабочий.** Откат сделан осознанно: панель последняя, ядро Xray
|
||
осталось на 26.7.28, миграция на 26.9 провалена, лишний код закомментирован.
|
||
Состояние — «заморожено», а не «сломано».
|
||
- **5.2 — подтверждённая дыра в защите данных.** Guard для несмонтированного
|
||
носителя не был продуман → задача B1.
|
||
|
||
## Сводка подтверждённых инвариантов
|
||
|
||
| # | Пункт | Краткая формулировка | См. |
|
||
|---|---|---|---|
|
||
| 1 | Все `outputs` флейка вычисляются | A1: правка `lib/xlib.nix` → `lib/xlib`; закрепить через `nix flake check` | todo A1 |
|
||
| 2 | External-диск монтируется до сервисов | mkServiceStorage + bind без guard'а → сервис стартует на пустой БД | todo B1 |
|
||
| 3 | Сетевая граница sapphira = роутер | 5 портов: 22, 80, 443, 8443, 22000; `firewall.enable = false` намеренно | todo D1 |
|
||
| 4 | `100.64.0.0` = Tailscale sapphira | Назначен вручную; в 4 файлах | AGENTS.md §5 |
|
||
| 5 | 3x-ui заморожен | Панель на latest; ядро Xray на 26.7.x; миграция 26.9 провалена | todo C1–C5 |
|
||
| 6 | nftables на VDS — явная финальная политика | Сейчас ruleset без финального правила + конфликт с `firewall.*` | todo A3 |
|
||
| 7 | sops-пути — через `config.sops.secrets.<name>.path` | Любой `path =` override на sops-блоке делает хардкод-потребителя молча сломанным: rebuild зелёный, сервис стартует, контент пустой | этот коммит, см. §S1 |
|
||
|
||
## Сводка по ловушкам
|
||
|
||
Полная таблица (10 пунктов) в **`AGENTS.md`** → раздел «Ловушки». Кратко:
|
||
`firewall.enable=false` намеренно · uid=1001 на sapphira · `image = …:latest`
|
||
намеренно для 3x-ui · `reality443Forwarding=true` — следствие отката ·
|
||
15 закомментированных модулей в server/default.nix · `serviceConfig` vs `Service`
|
||
в home-manager · nftables без финального правила · `100.64.0.0` не сеть ·
|
||
`/mnt/services` mode 0777 · `stateVersion` разный между хостами.
|
||
|
||
## Неотвеченные вопросы (слои 0–8)
|
||
|
||
Самые важные — выделены.
|
||
|
||
| ID | Вопрос | Что блокирует |
|
||
|---|---|---|
|
||
| 0.1 | Восстанавливать ли migration notes, удалённые в `22a19be`? | C1: реконструкция заметки 3x-ui |
|
||
| 0.2 | Комментарий-density 38/120 файлов без комментариев — нормально? | Стиль модулей |
|
||
| 0.3 | 15 закомментированных модулей: удалить или хранить как референс? | E3: чистота кода |
|
||
| 0.4 | README пустой, todo.md нет — норма? | E1: AGENTS.md/README |
|
||
| 1.3 | Лишние inputs в flake (`justray`, `nix-minecraft`, `proxy-suite`)? | Чистота flake |
|
||
| 1.5 | `nix-systems` через `follows` — оптимизация размера lock | Документация |
|
||
| **2.2** | **`vetymae` / `lamet` / `therima` / `soptur` — те же машины или хосты вне реестра?** | **DNS/nginx/identity** |
|
||
| 2.3 | sapphira uid=1001: блокер ли использование `/mnt/archive`/`/mnt/mobile`? | Миграция ФС |
|
||
| **2.5** | **stateVersion 24.05 / 24.11 / 25.05 / 26.05 — намеренный дрейф?** | **Миграции** |
|
||
| 2.6 | Есть ли escape hatch для per-host отличий в xlib? | Архитектура |
|
||
| 2.7 | `devices.termux` без NixOS-хоста — закрытый список | Документация |
|
||
| 3.2 | `any.nix` (minimal) нужны home-manager + sops + disko? | Минималка |
|
||
| **4.1** | **Как root получает доступ по SSH — authorizedKeys в коде нет** | **deploy, безопасность** |
|
||
| **4.2** | **Как разрешается цикл «ключ в секрете, а нужен для расшифровки»?** | **bootstrap, recovery** |
|
||
| 4.3 | Все файлы в `secrets/` покрыты `path_regex`? | sops |
|
||
| 4.4 | Как подключается вторая машина / второй человек при одном age-ключе? | sops, scale |
|
||
| 4.5 | `users.nix:87` — личный ключ или общий «ключ от деплоя»? | Безопасность |
|
||
| 5.1 | `/mnt/services` mode 0777 — осознанно? | Безопасность |
|
||
| 5.3 | NFS выключен, Samba работает — миграция? | Сетевые сервисы |
|
||
| 5.4 | NTFS-том `lamet-drive` `mask=0000` — что на нём? | Семантика |
|
||
| 5.5 | `therima` / `vetymae` / `soptur` в dirs.nix — реально смонтированы? | Семантика |
|
||
| 5.6 | Где бэкапы БД и 3x-ui? | B2 |
|
||
| 6.6 | `192.168.1.20` зашит в 30 мест — константа? | Рефакторинг |
|
||
| 6.7 | DNS ↔ сервисы — как ловим рассинхрон? | Документация, CI |
|
||
| **6.8** | **Публичные IP + SSH-алиасы в `home/termux.nix` — карта «хост → адреса» нужна?** | **Архитектура** |
|
||
| 6.9 | Какой путь REALITY правильный сейчас? | C5 |
|
||
| 7.4 | Почему не публиковать весь диапазон 14380-15379? | 3x-ui |
|
||
| 8.2 | `lamet.opencodes` → `:6061` (порт miniflux) — ошибка? | nginx |
|
||
| 8.4 | `onlyoffice` после трёх регрессов — работает? | Статус сервиса |
|
||
| 8.5 | Что слушает `:3002` (`/whiteboard` nextcloud)? | Карта сервисов |
|
||
| 8.6 | Бэкапы вне Nix — записать | Документация |
|
||
|
||
## Где это раньше лежало
|
||
|
||
До переноса в `AGENTS.md` / `map.md` здесь был подробный Q&A по слоям 0–8
|
||
с разделами «Вопрос», «Факт», «Риск», «Кандидат». Этот текст сохранён в
|
||
git-истории файла (последний коммит, где Q&A был полным). Восстановить:
|
||
`git log -p docs/arch/invariants.md | less`.
|
||
|
||
---
|
||
|
||
## Слой 9. home-manager
|
||
|
||
**9.1** `home/home.nix:52-57` — для пользователя импортируется
|
||
`home/${xlib.device.type}.nix`; для `root` — без профиля (строка 51).
|
||
**Вопрос:** почему у `root` нет home-профиля — сознательно?
|
||
**Кандидат:** `home/<type>.nix` = единственный источник «что есть на этом хосте»
|
||
для пользователя; добавление пакета в новый тип = правильный файл, а не
|
||
`home/default.nix`.
|
||
|
||
**9.2 [!]** `home/home.nix:28-43` для headless-хостов: `xdg.userDirs.* = null` и
|
||
`createDirectories = false`, при этом `lib/xlib/dirs.nix:26` обещает
|
||
`music-library = "${user-home}/Music"`.
|
||
**Вопрос:** кто создаёт `~/Music` и `~/Storage`? `createDirectories = false`
|
||
означает, что home-manager их не создаст, а `dirs.nix` на них ссылается.
|
||
**Риск:** на headless-хосте путь в конфиге есть, а каталога нет → тихий сбой
|
||
сервиса, который туда пишет.
|
||
|
||
**9.3 [!]** `home/modules/opencode.nix:339-350` (`c73a698`): в home-manager нельзя
|
||
писать `serviceConfig = { ... }` — рендерится литеральная секция `[serviceConfig]`,
|
||
которую systemd молча игнорирует («Unknown section 'serviceConfig'. Ignoring.»).
|
||
Правильно: `systemd.user.services.opencode-web.Service = { ... }`.
|
||
**Кандидат (готовый инвариант, стоит закрепить буквально в `AGENTS.md`):**
|
||
в home-manager cgroup-опции (`MemoryHigh`, `OOMScoreAdjust`, …) пишутся
|
||
в `systemd.user.services.<name>.Service`, **не** в `serviceConfig`. Ошибка
|
||
не диагностируется — она просто не применяется.
|
||
|
||
**9.4** `linger = true` добавлен ради `opencode-web` (`users.nix:71-75`) и включён
|
||
**для всех** хостов.
|
||
**Кандидат:** «user-сервисы переживают logout на всех хостах» — закрепить, потому
|
||
что это неочевидное поведение, влияющее на ресурсы и на безопасность.
|
||
|
||
**9.5** `home/modules/opencode.nix:286-303` — `opencode.web` слушает `0.0.0.0:4096`
|
||
(комментарий: nginx проксирует `127.0.0.1:4096`), и nginx на sapphira ходит туда
|
||
же по Tailscale у двух других хостов (см. 8.3).
|
||
**Кандидат:** `0.0.0.0` в `opencode.web` — обязательное условие для внешнего
|
||
доступа через `opencodes.*`; пароль приходит из sops-секрета `opencode_server`.
|
||
|
||
**9.6** Секреты opencode приходят в `~/.config/opencode/server.env` (dotenv),
|
||
`~/.local/share/opencode/auth.json` и `account.json` (json, `key = ""`).
|
||
**Кандидат:** эти три файла перезаписываются sops при каждой активации — ручные
|
||
правки в них теряются. Уже отражено в комментарии `users.nix:120-131`, стоит
|
||
закрепить как инвариант.
|
||
|
||
---
|
||
|
||
## Слой 10. deploy и проверка
|
||
|
||
**10.1** `deploy/default.nix:20-24` — цели: `sapphira` (server), `otrecа` (vds),
|
||
`rydiwo` (ноутбук). **Нет** `atoridu` (основной десктоп), `wsl`, `epral`.
|
||
**Вопрос:** почему не деплоится десктоп? И безопасно ли пересобирать ноутбук
|
||
`rydiwo` по SSH (он может быть выключен/на другом Wi-Fi)?
|
||
**Кандидат:** `deploy-rs` = только серверы + ноутбук; десктоп и WSL обновляются
|
||
вручную. Инвариант: не добавлять в `deploy.nodes` хост, который нельзя
|
||
пересобрать в любой момент без риска потерять доступ.
|
||
|
||
**10.2** `deploy/default.nix:18-19` — `sshUser = "oqyude"`, `user = "root"`.
|
||
См. 4.1: root-доход по SSH не описан в конфигурации.
|
||
**Кандидат:** деплой требует ручной настройки root-доступа на каждом из 3 хостов —
|
||
это скрытая зависимость, которую агент не выведет.
|
||
|
||
**10.3** `deploy/default.nix:27-29` — `checks = builtins.mapAttrs (... deployChecks)`.
|
||
**Вопрос:** `nix flake check` реально проходит сейчас? Учитывая 2.1 (`lib/xlib.nix`)
|
||
он должен падать на `nixOnDroidConfigurations`. Падает или `checks` покрывают
|
||
не всё дерево outputs?
|
||
**Кандидат (первое, что стоит сделать):** добиться, чтобы
|
||
`nix flake check` был зелёным — это единственная автоматическая защита от
|
||
подобных breakage'ов.
|
||
|
||
**10.4** CI нет, `flake check` не запускается автоматически.
|
||
**Кандидат:** минимальный локальный набор перед коммитом:
|
||
`nix flake check && nix build .#nixosConfigurations.<хост>.config.system.build.toplevel --dry-run`.
|
||
|
||
---
|
||
|
||
## Слой 11. Формат (то, что я предлагаю зафиксировать как процесс)
|
||
|
||
**11.1** Где будет жить итог: `AGENTS.md` в корне (читается агентом всегда),
|
||
`docs/arch/map.md` (карта хостов/сервисов), `docs/arch/invariants.md` (этот файл).
|
||
**Кандидат:** этот файл после ответов превращается в `docs/arch/invariants.md`
|
||
с колонкой «ответ» и становится источником для `AGENTS.md`; `AGENTS.md` — краткая
|
||
выжимка, без подробностей.
|
||
|
||
**11.2** Какие инварианты можно превратить в автоматическую проверку (тогда они
|
||
перестанут «забываться»):
|
||
1. ни одного `:latest` в образах (grep по `image =`);
|
||
2. `nix flake check` зелёный;
|
||
3. каждый домен из `coredns.nix` имеет vhost в `nginx.nix` и наоборот;
|
||
4. каждый сервис в `mkServiceStorage` имеет каталог в `/mnt/services` на
|
||
`External`-диске;
|
||
5. в самописном nftables-ruleset последнее правило цепочки явное;
|
||
6. каждый `listen.addr` — реально назначенный адрес, а не сеть;
|
||
7. все файлы в `secrets/` матчат `path_regex` из `.sops.yaml`.
|
||
|
||
**Вопрос:** какие из этих проверок ты хочешь, а какие — лишний CI?
|
||
|
||
---
|
||
|
||
## Шаблон инварианта
|
||
|
||
Этот шаблон — для добавления новых инвариантов в этот документ
|
||
(и для зеркалирования в `AGENTS.md`). Та же 4-осевая структура
|
||
используется, чтобы вытащить «невидимое знание владельца» из
|
||
существующего кода в явное утверждение.
|
||
|
||
1. **Утверждение** — что именно верно и нельзя менять без осознанного
|
||
решения. Один-два абзаца, никаких «может быть».
|
||
2. **Где** — конкретные файлы и строки. Агент не должен угадывать.
|
||
3. **Почему** — что происходит при нарушении. Лучше всего — сценарий
|
||
(rebuild / рантайм), а не абстрактный риск.
|
||
4. **Действие** — `todo X.Y`, ссылка на коммит, или явное
|
||
«закреплено автоматической проверкой (см. §11.2)».
|
||
|
||
Дополнительные поля по необходимости: «ловушка» (выглядит сломанным,
|
||
намеренно), «обратное» (где это уже было сломано раньше),
|
||
«как проверить» (grep / CI).
|
||
|
||
### S1 — sops-пути: `config.sops.secrets.<name>.path`
|
||
|
||
- **Утверждение.** Любой потребитель sops-секрета в `modules/` ссылается
|
||
на путь через `${config.sops.secrets.<attr>.path}`, а не через
|
||
литерал `"/run/secrets/<attr>"`. Атрибут sops-блока — единственный
|
||
источник истины для on-disk пути.
|
||
- **Где.** `modules/server/authelia.nix:107-108`,
|
||
`modules/containers/open-webui.nix:101`,
|
||
`modules/containers/tape-rotation.nix:63` — потребители
|
||
sops-материализации. Отдельный случай — композитный env-файл,
|
||
**не** sops: `modules/containers/remnawave.nix:16, 70, 138, 145`
|
||
— там `envFile` в `let`-биндинге, чтобы две копии пути не
|
||
разъехались.
|
||
- **Почему.** sops-nix материализует секреты на `/run/secrets/<attr>`
|
||
по умолчанию, но `sops.secrets.<attr>.path` это переопределяет.
|
||
Любой такой override в будущей правке делает хардкод-потребителя
|
||
**молча** сломанным: `nixos-rebuild` проходит, сервис стартует,
|
||
файл читается — но контент от прошлой версии или пустой. Симптом
|
||
приходит из рантайма, не из CI. В `remnawave.nix` тот же риск
|
||
был двойной: путь хардкожен и в генераторе, и в контейнере, и
|
||
расхождение двух копий → silent breakage.
|
||
- **Действие.** Закреплено в коммите этой правки. Автоматической
|
||
проверки пока нет (см. §11.2 — список потенциальных CI-проверок).
|
||
- **Обратное.** До правки: `authelia.nix:51,108,109` — через хелпер
|
||
`sopsPath = name: "/run/secrets/${name}"`; `open-webui.nix:95`,
|
||
`tape-rotation.nix:61`, `remnawave.nix:61, 129, 136` — литеральный
|
||
хардкод. |