Рабочую машину Ева правит руками, а не однострочниками #1

Open
evamaid wants to merge 1 commit from eva-hands into master
Owner

Серверная рука файловых инструментов (src/tools/server_fs.rs) переезжает на
eva-hands — демон со своим протоколом,
который сам слушает и сам аутентифицирует.

Транспорт

Демон слушает свой порт и пускает внутрь только владельца приватного ssh-ключа,
публичная половина которого лежит в его authorized_keys. Ssh-демон на машине
для этого не нужен, ssh-бинарь у ядра — тоже.

Своей криптографии нет. Транспорт и аутентификация — готовая реализация
протокола SSH (russh, серверная сторона внутри демона и клиентская в ядре):
обмен ключами, шифр, проверка подписи, проверка ключа хоста. Наше — только
кого пускать и что течёт внутри канала.

Ключ машины заводится сам при первом запуске; ядро может его запинать
(host_key в конфиге) и тогда не примет чужого, вставшего на тот же адрес.
Без пиннинга демон честно пишет об этом в лог при каждом соединении.

Что меняется

Раньше каждая операция поднимала свой ssh, содержимое возилось base64 через
cat/dd/tail/head, а код возврата конвейера принадлежал последней
команде — и пропавший файл легко выглядел пустым. Теперь:

  • одно долгое соединение вместо рукопожатия на каждый чих; операции идут по
    нему вперемешку, у каждой свой id;
  • сырые байты мимо base64 и мимо шелла — ничего не ломается о кавычки,
    переводы строк и не-UTF8, и не зависит от набора утилит той стороны
    (dd oflag=seek_bytes, base64 -w0 есть не везде);
  • у каждого отказа свой код (not_found, too_large, denied, …):
    пропавший файл больше не может выглядеть пустым;
  • запись целиком атомарна — сосед и rename вместо обрубка на месте
    прежнего содержимого, права существующего файла переживают замену;
  • поиск образца идёт на той машине, где лежит файл: гигабайтный архив
    визуальной новеллы больше не едет по проводу ради четырёх байт. Потоковое
    чтение с перекрытием окна и обрывом по лимиту находок переехало в демон как
    есть;
  • ~ разворачивается — путь в кавычках шелла оставался тильдой, и файл не
    находился по вине кавычек;
  • своя машина обслуживается теми же операциями прямо в процессе, без сети и
    без шелла: одна реализация на оба случая.

Новая секция конфига shell.remote.handsaddress (host:port демона),
key (приватный ssh-ключ ядра, явным полем, а не -i внутри args), user
(по умолчанию eva), host_key (публичный ключ машины, его печатает
eva-hands host-key). Секции нет — файлы правятся шеллом, как и раньше.

Что остаётся

  • Шелл-однострочники — запасной путь. Руки могут не отвечать: демон не
    поднят, порт закрыт, ключ не принят. Тогда ядро откатывается на прежнюю
    реализацию (она вся сохранена в struct Shell) и пробует руки снова через
    пять минут; ssh для этого настроен всё равно, ради shell. Роняет на
    запасной путь только недоступность рук: отказ самой операции — честный
    ответ машины, и обходить его нельзя. Это разделение под тестом. Отказ в
    аутентификации тоже уводит на запасной путь, но пишет в лог warn — молча
    такое не проходит.
  • Для модели не меняется ничего: те же инструменты, тот же параметр on,
    те же гейты. Мостам показывать нечего.
  • API ядра и протокол модулей не задеты — eva-sdk это не касается.
  • Потолок чтения целиком (4 МБ) остаётся в ядре: он про контекст модели, а не
    про машину. Там же остались отказ записи за концом файла и окно поиска
    запасного пути (Shell.chunk) — обе правки из свежего разбора ревью.

Новых операций протокола (список каталога, метаданные, mkdir/remove/rename)
ядро пока не показывает модели — это отдельная фича, не этот MR. shell тоже
пока ходит своим ssh; перевести его на exec рук — очевидный следующий шаг,
и он же уберёт последнюю нужду в ssh-демоне на той машине.

Как разворачивать

  1. На рабочей машине поднять руки — флейк eva/hands даёт пакет и
    NixOS-модуль:

    imports = [ inputs.eva-hands.nixosModules.default ];
    services.eva-hands = {
      enable = true;
      listen = "0.0.0.0:2222";
      authorizedKeys = [ "ssh-ed25519 AAAA… eva-kernel" ];   # публичный ключ ядра
      openFirewall = true;
    };
    

    Порт лучше держать во внутренней сети: ключ — не повод светить его наружу.
    Публичный ключ машины после первого старта — eva-hands host-key.

  2. Ядру дать приватный ключ (sops) и секцию shell.remote.hands с адресом,
    путём к ключу и host_key с прошлого шага.

Порядок не важен и откат безопасен: ядро без рук работает шеллом, руки без
ядра просто никем не зовутся.

Инфраструктура (viendesu/infra) не тронута — это на Господина.

Проверки

  • cargo test ядра — 169 тестов зелёные. В server_fs добавлены: круг
    запасным путём целиком; отказ операции не уводит на обход; недоступность рук
    уводит и оставляет их в покое. Тест на стык окон поиска из разбора ревью
    сохранён и переведён на запасной путь (у рук свой такой же).
  • nix build .#eva-kernel собирается, хэш git-зависимости в outputHashes
    обновлён.
  • В самом eva-hands — 36 тестов: разбор кадра, границы окна поиска, запись по
    смещению, пропавший файл, атомарность записи, изгородь корней, и отдельно
    tests/network.rs, где демон слушает настоящий порт, клиент приходит по сети
    со своим ключом, чужой ключ получает отказ, а подменённый ключ машины не
    проходит пиннинг.

Co-Authored-By: Eva

Серверная рука файловых инструментов (`src/tools/server_fs.rs`) переезжает на [eva-hands](https://git.desu.church/eva/hands) — демон со своим протоколом, который **сам слушает и сам аутентифицирует**. ## Транспорт Демон слушает свой порт и пускает внутрь только владельца приватного ssh-ключа, публичная половина которого лежит в его `authorized_keys`. Ssh-демон на машине для этого не нужен, ssh-бинарь у ядра — тоже. **Своей криптографии нет.** Транспорт и аутентификация — готовая реализация протокола SSH (`russh`, серверная сторона внутри демона и клиентская в ядре): обмен ключами, шифр, проверка подписи, проверка ключа хоста. Наше — только кого пускать и что течёт внутри канала. Ключ машины заводится сам при первом запуске; ядро может его запинать (`host_key` в конфиге) и тогда не примет чужого, вставшего на тот же адрес. Без пиннинга демон честно пишет об этом в лог при каждом соединении. ## Что меняется Раньше каждая операция поднимала свой `ssh`, содержимое возилось base64 через `cat`/`dd`/`tail`/`head`, а код возврата конвейера принадлежал последней команде — и пропавший файл легко выглядел пустым. Теперь: - **одно долгое соединение** вместо рукопожатия на каждый чих; операции идут по нему вперемешку, у каждой свой `id`; - **сырые байты** мимо base64 и мимо шелла — ничего не ломается о кавычки, переводы строк и не-UTF8, и не зависит от набора утилит той стороны (`dd oflag=seek_bytes`, `base64 -w0` есть не везде); - **у каждого отказа свой код** (`not_found`, `too_large`, `denied`, …): пропавший файл больше не может выглядеть пустым; - **запись целиком атомарна** — сосед и `rename` вместо обрубка на месте прежнего содержимого, права существующего файла переживают замену; - **поиск образца идёт на той машине, где лежит файл**: гигабайтный архив визуальной новеллы больше не едет по проводу ради четырёх байт. Потоковое чтение с перекрытием окна и обрывом по лимиту находок переехало в демон как есть; - **`~` разворачивается** — путь в кавычках шелла оставался тильдой, и файл не находился по вине кавычек; - **своя машина** обслуживается теми же операциями прямо в процессе, без сети и без шелла: одна реализация на оба случая. Новая секция конфига `shell.remote.hands` — `address` (`host:port` демона), `key` (приватный ssh-ключ ядра, явным полем, а не `-i` внутри `args`), `user` (по умолчанию `eva`), `host_key` (публичный ключ машины, его печатает `eva-hands host-key`). Секции нет — файлы правятся шеллом, как и раньше. ## Что остаётся - **Шелл-однострочники — запасной путь.** Руки могут не отвечать: демон не поднят, порт закрыт, ключ не принят. Тогда ядро откатывается на прежнюю реализацию (она вся сохранена в `struct Shell`) и пробует руки снова через пять минут; ssh для этого настроен всё равно, ради `shell`. Роняет на запасной путь **только недоступность рук**: отказ самой операции — честный ответ машины, и обходить его нельзя. Это разделение под тестом. Отказ в аутентификации тоже уводит на запасной путь, но пишет в лог `warn` — молча такое не проходит. - **Для модели не меняется ничего**: те же инструменты, тот же параметр `on`, те же гейты. Мостам показывать нечего. - **API ядра и протокол модулей не задеты** — eva-sdk это не касается. - Потолок чтения целиком (4 МБ) остаётся в ядре: он про контекст модели, а не про машину. Там же остались отказ записи за концом файла и окно поиска запасного пути (`Shell.chunk`) — обе правки из свежего разбора ревью. Новых операций протокола (список каталога, метаданные, mkdir/remove/rename) ядро пока не показывает модели — это отдельная фича, не этот MR. `shell` тоже пока ходит своим ssh; перевести его на `exec` рук — очевидный следующий шаг, и он же уберёт последнюю нужду в ssh-демоне на той машине. ## Как разворачивать 1. На рабочей машине поднять руки — флейк `eva/hands` даёт пакет и NixOS-модуль: ```nix imports = [ inputs.eva-hands.nixosModules.default ]; services.eva-hands = { enable = true; listen = "0.0.0.0:2222"; authorizedKeys = [ "ssh-ed25519 AAAA… eva-kernel" ]; # публичный ключ ядра openFirewall = true; }; ``` Порт лучше держать во внутренней сети: ключ — не повод светить его наружу. Публичный ключ машины после первого старта — `eva-hands host-key`. 2. Ядру дать приватный ключ (sops) и секцию `shell.remote.hands` с адресом, путём к ключу и `host_key` с прошлого шага. Порядок не важен и откат безопасен: ядро без рук работает шеллом, руки без ядра просто никем не зовутся. Инфраструктура (`viendesu/infra`) **не тронута** — это на Господина. ## Проверки - `cargo test` ядра — 169 тестов зелёные. В `server_fs` добавлены: круг запасным путём целиком; отказ операции не уводит на обход; недоступность рук уводит и оставляет их в покое. Тест на стык окон поиска из разбора ревью сохранён и переведён на запасной путь (у рук свой такой же). - `nix build .#eva-kernel` собирается, хэш git-зависимости в `outputHashes` обновлён. - В самом eva-hands — 36 тестов: разбор кадра, границы окна поиска, запись по смещению, пропавший файл, атомарность записи, изгородь корней, и отдельно `tests/network.rs`, где демон слушает настоящий порт, клиент приходит по сети со своим ключом, чужой ключ получает отказ, а подменённый ключ машины не проходит пиннинг. Co-Authored-By: Eva
Серверная рука файловых инструментов работает через eva-hands: на своей
машине — прямо в процессе, на удалённой — демон, которого поднимает
ssh-сессия и который говорит своим протоколом по одному долгому соединению.

Каждая операция поднимала свой ssh, содержимое возилось base64 через
cat/dd/tail, а код возврата конвейера принадлежал последней команде — и
пропавший файл легко выглядел пустым. Теперь у каждого отказа свой код,
байты едут сырьём, запись целиком атомарна (сосед и rename вместо обрубка),
а поиск образца идёт на той машине, где лежит файл: гигабайтный архив
больше не едет по проводу ради четырёх байт.

Демона на машине может не оказаться — шелл-однострочники остались запасным
путём, ядро откатывается на них и пробует руки снова через пять минут.
Роняет на запасной путь только недоступность рук: отказ самой операции —
честный ответ машины, и обходить его нельзя.

Для модели ничего не изменилось: те же инструменты, те же гейты, тот же
параметр on — мостам показывать нечего. API ядра и протокол модулей не
задеты, eva-sdk это не касается.

Co-Authored-By: Eva
This pull request has changes conflicting with the target branch.
  • Cargo.lock
  • Cargo.toml
  • config.example.yaml
  • docs/src/config.md
  • flake.nix
  • src/config.rs
  • src/main.rs
  • src/tools/server_fs.rs
  • src/tools/shell.rs
View command line instructions

Manual merge helper

Use this merge commit message when completing the merge manually.

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin eva-hands:eva-hands
git switch eva-hands

Merge

Merge the changes and update on Forgejo.

Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.

git switch master
git merge --no-ff eva-hands
git switch eva-hands
git rebase master
git switch master
git merge --ff-only eva-hands
git switch eva-hands
git rebase master
git switch master
git merge --no-ff eva-hands
git switch master
git merge --squash eva-hands
git switch master
git merge --ff-only eva-hands
git switch master
git merge eva-hands
git push origin master
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
eva/kernel!1
No description provided.