komiterm/README.md
2026-07-17 09:30:05 +03:00

486 lines
38 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# KomiTerm — SSH/Telnet клиент на Rust
Минималистичная альтернатива MobaXterm под Linux: session manager + SSH/Telnet
в терминале, без X11-сервера и RDP/VNC (сознательно вырезано ради MVP).
## Архитектура
```
main.rs — CLI (clap): запуск сессии + subcommand `profile add/list/remove`
session.rs — конфиг сессий (~/.config/komiterm/sessions.toml), AuthMethod/PassphraseSource
profiles.rs — именованные credential-профили (~/.config/komiterm/credentials.toml)
hotkeys.rs — конфигурируемые хоткеи табов (~/.config/komiterm/hotkeys.toml)
sftp.rs — обёртка над russh_sftp::client::SftpSession (листинг/cd/download/upload)
localfs.rs — локальный файловый браузер для выбора файла при закачке (u в SFTP-режиме)
hoststats.rs — парсинг CPU/памяти/load average из /proc для строки состояния
sshconfig.rs — парсер ~/.ssh/config, включая Include (см. ниже)
resolve.rs — слияние приоритетов: session > credential-профиль > ssh_config > дефолты
transport/
mod.rs — трейт Transport (write/resize/close/open_sftp/run_command) — общий интерфейс
ssh.rs — реализация через russh, поддерживает Password/Key/Agent + open_sftp/run_command
telnet.rs — реализация telnet с ответом на DO/WILL (не просто дроп)
serial.rs — последовательный порт (COM/tty) через tokio-serial
term.rs — обёртка над alacritty_terminal (парсинг escape-последовательностей)
ui.rs — рендер сетки терминала + боковая панель (сессии / SFTP-браузер)
app.rs — главный цикл: incoming -> term_state -> render, ввод -> transport
```
## Аутентификация и профили
Способ входа можно задать двумя способами:
1. **Прямо в сессии** — поле `auth` в `sessions.toml`.
2. **Через именованный профиль** — заведи один раз в `credentials.toml`
(командой `komiterm profile add`), затем ссылайся на него по имени полем
`credential` из любой сессии. Удобно, когда один логин/ключ используется
для нескольких хостов.
Приоритет при разрешении (см. `resolve.rs`): explicit `auth` в сессии >
`credential`-профиль > подсказка из `~/.ssh/config` (IdentityFile) > запрос
пароля как последний вариант.
```bash
komiterm profile add # интерактивно: имя, логин, пароль/ключ/agent*
komiterm profile list
komiterm profile remove myprofile
```
\* ssh-agent как способ входа временно не работает (см. TODO ниже) —
профиль с ним создать можно, но подключение с таким профилем откажет.
Пароли и passphrase **никогда не сохраняются на диск** — только логин,
путь к ключу и признак "спросить passphrase при подключении". Файл
`credentials.toml` на Unix создаётся с правами 600.
## known_hosts / защита от MITM
`check_server_key` в `transport/ssh.rs` больше не доверяет серверу
безусловно — сверяется с `~/.ssh/known_hosts` через `russh::keys::check_known_hosts`:
- **Ключ совпадает с записанным** — подключаемся молча, как обычно.
- **Записи для хоста нет вообще** (первое подключение) — печатается
отпечаток ключа, и спрашивается подтверждение (Trust On First Use, как
делает обычный `ssh` при "authenticity of host can't be established").
При согласии ключ дописывается в known_hosts — своей маленькой функцией
(`transport/ssh.rs::learn_known_hosts`), а не `russh::keys::learn_known_hosts`:
в используемой версии russh (0.62.2) это имя не реэкспортируется на
верхнем уровне `russh::keys` (только `check_known_hosts`), так что строку
формируем сами через `PublicKey::to_openssh()` — формат known_hosts
простой и стабилен десятилетиями, велосипед тут минимальный.
- **Запись есть, но ключ другой** — это подозрение на MITM или
переустановленный сервер. Здесь **нет** интерактивного "продолжить всё
равно": подключение отклоняется безусловно, с инструкцией вручную
почистить строку в `~/.ssh/known_hosts`, если пользователь уверен, что
смена ключа легитимна. Слабое место так просто не обойти повторным нажатием Enter.
Из-за этого пришлось поднять версию `russh` с `0.44` до текущей (`>=0.50`,
реально резолвится в `0.62.x`) — модуль работы с ключами в старой версии
жил отдельным крейтом `russh-keys` и не имел этих функций в удобном виде;
в текущей версии `russh::keys` включает и агента, и известные хосты, и
работу с ключами в одном месте. Заодно поменялся API аутентификации по
ключу (`authenticate_publickey` теперь принимает `PrivateKeyWithHashAlg`
вместо голого `Arc<PrivateKey>`) — учтено в текущем коде.
## ~/.ssh/config
Если у сессии `use_ssh_config = true` (по умолчанию), поле `host` сначала
ищется как алиас в `~/.ssh/config` — так же, как если бы ты написал
`ssh myalias`. Оттуда подтягиваются `HostName`, `User`, `Port`,
`IdentityFile`, если сессия их не переопределяет явно.
Путь один и тот же на всех трёх платформах (`<домашняя_директория>/.ssh/config`),
поэтому специального кода под Windows не потребовалось — OpenSSH для Windows
использует ту же схему.
Поддержана директива `Include` (в т.ч. с `*`-wildcard в имени файла и путями
относительно директории текущего конфига — `Include config.d/*` работает).
Совпадения `*`-паттерна обходятся в отсортированном порядке, как это делает
сам OpenSSH, иначе порядок Include стал бы недетерминированным. Есть защита
от циклов (`a.conf` включает `b.conf` включает `a.conf`) через отслеживание
уже разобранных файлов и предел глубины. Из не поддержанного: `Match`-блоки
и полноценные glob-паттерны (`?`, `[abc]`, отрицания через `!`) — см. TODO
в конце `sshconfig.rs`.
## Табы (мультиплексирование сессий)
```bash
komiterm myserver staging-box old-switch # три таба сразу при запуске
komiterm # один таб, сессия через менеджер
```
Хоткеи читаются из `~/.config/komiterm/hotkeys.toml` (создаётся при первом
запуске с показанными ниже значениями по умолчанию; `Alt+1..Alt+9` в конфиг
не вынесены — см. комментарий в `hotkeys.rs`, почему):
| Комбинация | Действие |
|------------------------|----------------------------------------|
| `Ctrl+PageUp/PageDown` | предыдущий / следующий таб |
| `Alt+1`..`Alt+9` | прыжок на таб N |
| `F1` | фокус на боковую панель / обратно на терминал |
| `F2` | новый таб (выбор сессии из списка) |
| `Ctrl+F2` | закрыть активный таб |
| `F3` | свернуть/развернуть боковую панель |
| `F4` | SFTP-браузер для активного таба (только SSH) |
| `F10` | выйти из KomiTerm целиком |
Все табы держат живое соединение и вычитывают входящие данные каждый кадр
(фон не "замирает", даже если ты сейчас смотришь в другой таб) — рисуется
только активный. Открытие нового таба (`F2`) и начальный выбор сессии (если
она не передана аргументом) показывают список сессий прямо поверх текущего
экрана через `ratatui::widgets::List` — без выхода из raw-режима и без
"грязного" экрана, как было в первой версии.
Если сохранённых сессий вообще нет (первый запуск), список не ошибка, а
пустой экран с подсказкой — нажми `n`, чтобы создать сессию прямо тут же,
не выходя из TUI и не редактируя `sessions.toml` руками. `n` работает и
при непустом списке — добавить ещё одну сессию можно в любой момент, когда
показан этот список (при `F2` или при начальном выборе). Мастер — короткий
пошаговый опрос (протокол → имя → поля по протоколу, включая способ входа
для SSH), каждое поле — Enter продолжить, Esc отменяет всё создание сразу.
Готовая сессия дописывается в `sessions.toml` и сразу открывается табом.
**Как закрыть/выйти из одной сессии** (не из всего komiterm): `Ctrl+F2`
закрывает активный таб явно. Выйти изнутри сессии обычным способом
(`exit` или `Ctrl+D` в шелле) тоже работает — таб закрывается сам, как
только замечает, что канал от удалённой стороны закрылся (проверяется
каждый кадр). Если все табы закрылись — komiterm завершается сам, выходить
из него отдельно уже не нужно.
## Боковая панель сессий
Слева — как в MobaXterm — постоянная панель со списком всех сохранённых
сессий (`ui::render_sidebar`), фиксированной ширины (`SIDEBAR_WIDTH = 24`
колонки). Открытые сейчас как табы сессии помечены точкой, активная —
подсвечена инверсией. Сворачивается/разворачивается по `F3`, освобождая
эти 24 колонки под содержимое терминала.
Важный нюанс: при сворачивании/разворачивании панели меняется реальная
ширина видимой области терминала, поэтому `F3` не просто перерисовывает
UI — он ресайзит PTY на удалённой стороне (`transport.resize`) для всех
табов, как при обычном ресайзе окна. Без этого full-screen приложения на
той стороне (vim, htop, less) не узнали бы о появившемся/пропавшем месте.
**Панель интерактивна**, но не одновременно с терминалом — есть явный
фокус, переключаемый `F1` (если панель была скрыта, `F1` заодно её
показывает). В фокусе рамка панели становится жёлтой — это единственный
визуальный признак того, куда сейчас уйдёт следующая клавиша. Пока панель
в фокусе, **любая** клавиша принадлежит ей, а не терминалу (даже
нераспознанная — просто игнорируется), чтобы не было пограничных случаев
вида "стрелка ушла и туда, и туда":
- `↑`/`↓` (или `j`/`k`) — выбор сессии в списке
- `Enter` — переключиться на уже открытый таб этой сессии, либо открыть
новый (без модального picker'а — сессия уже выбрана курсором)
- `Esc` — вернуть фокус терминалу (панель остаётся видна)
`F4` (SFTP) при открытии сразу переводит фокус на панель — незачем
открывать браузер файлов и не иметь возможности сразу же по нему
перемещаться. `Esc` внутри SFTP-режима возвращает и режим панели к списку
сессий, и фокус терминалу — одним действием.
## SFTP-браузер
`F4` открывает в той же боковой панели файловый браузер для **активного**
таба (переиспользует уже открытое SSH-соединение — отдельный channel с
subsystem `sftp`, без нового пароля/ключа). Работает только для SSH,
для Telnet-сессий покажет заглушку "SFTP недоступен".
Пока панель в режиме SFTP, стрелки/`j`/`k`/Enter/Esc уходят в неё, а не в
шелл — так и должно быть, иначе непонятно, куда идёт навигация:
- `↑`/`↓` (или `j`/`k`) — выбор записи в списке
- `Enter` на папке — зайти внутрь; `..` наверху списка — выйти на уровень выше
- `Enter` на файле — скачать в `~/Downloads/komiterm/<имя_сессии>/`
- `u` — открыть локальный файловый браузер и закачать выбранный файл в
текущую директорию (см. ниже)
- `Esc` — вернуть панель к списку сессий (сама SFTP-сессия при этом не
закрывается — переключение обратно на `F4` покажет тот же листинг без
повторного похода на сервер)
- Повторный `F4` — то же самое, что `Esc`
### Закачка файлов (`u`)
`u` внутри SFTP-режима открывает `localfs::LocalBrowser` — тот же список
файлов, но по локальному диску, начиная с домашней директории (обычный
`std::fs`, никакого SSH). Навигация та же (`↑`/`↓`/`j`/`k`, `..` наверху),
`Enter` на файле закачивает его в текущую директорию SFTP-браузера
активного таба под тем же именем и возвращает в SFTP-режим с уже
обновлённым листингом (закачанный файл сразу виден в списке). `Esc`
отмена, назад в SFTP-режим без закачки.
`sftp.rs` — тонкая обёртка над `russh_sftp::client::SftpSession`: листинг
директории (папки впереди файлов, дальше по алфавиту), `cd` через
`canonicalize` (чтобы `..` и симлинки резолвил сам сервер, а не наша
склейка строк), download/upload. Download/upload грузят файл целиком в
память, для реально больших файлов нужно будет стримить чанками — см.
TODO в конце `sftp.rs`.
API `russh_sftp` сверен с реальными примерами из репозитория
(`AspectUnk/russh-sftp/examples/client.rs`, тот же паттерн — в
`russh/examples/sftp_client.rs`), а не написан по памяти.
## Строка состояния хоста
Внизу экрана (под содержимым терминала, отдельной строкой) — CPU/память/
load average активного таба, обновляется раз в `STATS_INTERVAL` (5 секунд
по умолчанию, константа в `app.rs`).
Данные берутся не парсингом вывода интерактивного шелла (это было бы
ненадёжно — зависит от промпта, алиасов, запущенной программы), а отдельным
**exec-каналом** той же SSH-сессии — `Transport::run_command()`, новый
метод трейта, третий по счёту наряду с `open_sftp` (тоже дефолт `Ok(None)`
для Telnet). Команда гоняет `cat /proc/loadavg`, первые строки
`/proc/meminfo` и первую строку `/proc/stat`, разделяя секции меткой `@@@`
для надёжного парсинга (`hoststats::parse`). Соответственно работает
только на Linux-хостах и только по SSH — для Telnet и не-Linux строка
состояния просто показывает имя сессии без метрик, без ошибок в интерфейсе.
CPU% — не то же самое, что load average: это доля "не-idle" времени между
двумя последовательными снятиями `/proc/stat` (как считает `top`/`htop`),
поэтому появляется только со второго опроса — на первом кадре после
подключения будет прочерк, пока не накопится дельта.
Честный компромисс: опрос выполняется прямо в основном цикле и блокирует
отрисовку/ввод на время round-trip команды (обычно доли секунды, но на
дальних/медленных соединениях может быть заметно) — раз в 5 секунд это
редкость, но не бесплатно. Опрашивается только активный таб — у фоновых
вкладок статистика не обновляется, пока на них не переключишься.
## Serial / COM-подключения
Для консольных портов сетевого/embedded-оборудования — как в MobaXterm, но
без модема и прочего "телефонного" наследия PuTTY. Транспорт (`serial.rs`)
использует `tokio-serial` — тот же паттерн read/write-задач, что и у telnet,
просто без протокола поверх (сырые байты в обе стороны, никакого IAC).
```toml
[[session]]
name = "switch-console"
protocol = "serial"
port = "/dev/ttyUSB0" # Linux/macOS; на Windows — "COM3", "COM15" и т.п.
baud_rate = 115200 # выбор скорости — обязательное поле, дефолта нет
# Остальное — опционально, дефолт 8N1 без flow control (подходит для
# подавляющего большинства консольных портов):
# data_bits = "eight" # five | six | seven | eight
# parity = "none" # none | odd | even
# stop_bits = "one" # one | two
# flow_control = "none" # none | software | hardware
```
При подключении к serial-сессии в терминал печатается небольшая справка
(один раз, при открытии таба) — те самые нюансы, которые не видны нигде
в интерфейсе:
```
--- Serial-подключение: /dev/ttyUSB0 @ 115200 бод ---
- Порт занят другим приложением? Один порт — одно соединение одновременно.
- Linux: Permission denied при открытии? Добавь себя в группу dialout
(или uucp) и перелогинься: sudo usermod -aG dialout $USER
- data bits/parity/stop bits/flow control должны совпадать с устройством
на другом конце — по умолчанию тут 8N1 без flow control.
```
Ограничения, которые стоит держать в голове:
- **Занятость порта.** ОС не даёт двум процессам открыть один и тот же
serial-порт одновременно — если он уже занят (в другом терминале,
в MobaXterm, в `screen`/`minicom`), подключение вернёт ошибку открытия,
а не зависнет.
- **Права доступа только на Linux/macOS.** На Windows обычно ничего
дополнительно настраивать не нужно; на Linux без группы `dialout`
(`uucp` на некоторых дистрибутивах) — `Permission denied`.
- **Настройки порта должны совпадать с устройством.** Неверный baud
rate/parity — не ошибка подключения, а нечитаемая ерунда вместо текста
("mojibake"), потому что порт открывается успешно независимо от того,
правильно ли задан протокол данных поверх него.
- **Нет ни SFTP, ни строки состояния хоста** — оба используют SSH-специфику
(`open_sftp`/`run_command`), а serial-транспорт не переопределяет эти
методы трейта, так что дефолтные `Ok(None)` отключают их автоматически,
без специального кода в UI. Строка состояния просто покажет имя сессии,
как для Telnet.
- **`resize()` — no-op.** У физического провода нет понятия "размер
терминала" — устройство на другом конце просто пишет вслепую в свой
обычный vt100/ANSI, ресайз окна KomiTerm ни на что не влияет.
## Рендер терминала
`ui::render_terminal` проходит по сетке `alacritty_terminal` напрямую через
`grid[Line(row)][Column(col)]`, а не через `display_iter()` — так проще
гарантированно сохранить построчную раскладку. Поддержано: цвет текста/фона
(Named/Indexed/Spec — включая true color), bold/dim/italic/underline/
strikeout/inverse, курсор (инверсией текущей ячейки). Соседние ячейки с
одинаковым стилем схлопываются в один `Span`, чтобы не плодить по Span на
символ на длинных однотонных строках.
## Что реально работает
Ниже — история того, что уже прогонялось через настоящий `cargo build` на
реальной машине (не только в песочнице), а не только через сверку с
документацией. Первый прогон нашёл 7 реальных ошибок компиляции, второй —
ещё одну (сигнатура `Channel::data`). После этого **`cargo build --release`
прошёл полностью** — остались только предупреждения о неиспользуемом коде
(почищены). Живое подключение к серверу/устройству пока не проверялось.
**Что было сломано и как исправлено (см. git-историю/этот README для
контекста, если он у тебя есть):**
- `async-trait` не был объявлен в `Cargo.toml`, хотя использовался everywhere
для `Transport` — добавлено.
- `russh::keys::learn_known_hosts` не реэкспортируется на верхнем уровне в
версии 0.62.2 (там есть только `check_known_hosts`) — записываем строку в
`~/.ssh/known_hosts` сами, через `PublicKey::to_openssh()` + дозапись в
файл, без обращения к приватным/непереэкспортированным функциям russh.
- `authenticate_publickey`/`authenticate_password` возвращают `AuthResult`
(с методом `.success() -> bool`), а не голый `bool`, как предполагалось
изначально — поправлено.
- `Handle::window_change` в этой версии не существует — только
`Channel::window_change`. Раз уж пришлось трогать эту часть, канал
(shell/PTY) целиком переехал в отдельную задачу, которая мультиплексирует
входящие сообщения от сервера и команды (write/resize/close) от
`Transport` через `tokio::select!`, вместо того чтобы держать `ChannelId`
и слать всё через `Handle`.
- `alacritty_terminal::event::WindowSize` не реализует нужный трейт
`Dimensions` для `Term::new`/`Term::resize` (это разные вещи: `WindowSize`
— размер в пикселях для PTY, `Dimensions` — размер в колонках/строках
символьной сетки) — завели свою маленькую структуру `GridSize` в `term.rs`.
- `Handle::data(...)` возвращает `Result<(), Bytes>` (не пропущенные данные
возвращаются как есть, а не завёрнуты в нормальный `Error`) — обёрнуто
через `map_err`, чтобы `?` не спотыкался о недостающий `impl StdError`.
- (Второй раунд, после того как первый прогон дошёл до `Channel::data`)
`Channel::data<R: AsyncRead + Unpin>(&self, data: R)` принимает
источник для чтения, а не готовый буфер/что-то конвертируемое через
`Into` (в отличие от `Handle::data`, у которого сигнатура другая) — вместо
`data.into()` теперь `channel.data(&data[..])`, `&[u8]` сам реализует
`AsyncRead`.
- (Третий раунд — уже не ошибка компиляции, а баг в поведении: экран при
запуске не очищался.) `enable_raw_mode()` включает построчный ввод, но
сам по себе не переключает терминал на отдельный "alternate screen
buffer" — тот самый механизм, из-за которого `vim`/`htop`/`less` рисуют
на чистом экране, а при выходе возвращают терминал ровно в то состояние,
в котором он был (включая историю команд шелла). Без него komiterm рисовал
свой UI поверх текущего скроллбэка — добавлены `enter_tui()`/`leave_tui()`
в `app.rs`, вызывающие `EnterAlternateScreen`/`LeaveAlternateScreen` из
`crossterm` парно с `enable_raw_mode()`/`disable_raw_mode()` на каждом
пути выхода из `run()` (их несколько — отмена выбора сессии, ошибка
подключения, обычное завершение).
- (Четвёртый раунд — **первое подключение к реальному SSH-серверу прошло
успешно**, но нашлось два визуальных бага.) Промпт passphrase от
`rpassword` (и TOFU-промпт known_hosts) пишет прямо в tty в обход
`ratatui``Terminal` про эту запись не знает и её не перерисовывает,
поэтому огрызок текста промпта оставался на экране поверх строки
состояния. Добавлен `terminal.clear()?` сразу после каждого
`transport::connect()`, случающегося уже внутри TUI (стартовый picker,
`F2`, `Enter` по сессии в фокусированной панели) — форсирует полную
перерисовку экрана с нуля вместо диффинга, который не видел "чужой" записи.
- (Пятый раунд, тот же скриншот.) Название сессии в боковой панели не
помещалось / было не видно целиком. Вероятная причина — "тяжёлые"
юникод-символы (● ➤ 📁 и т.п.), чья реальная ширина в конкретном шрифте
терминала может не совпадать с тем, что предполагает расчёт колонок в
`ratatui` (типичная проблема для символов вне базовой латиницы/кириллицы —
не все шрифты меряют их как честную одну ячейку). Заменил все декоративные
символы на простой ASCII (`*`, `>`, `[D]`, `_`, `-`) и увеличил
`SIDEBAR_WIDTH` с 24 до 30 колонок с запасом. Если после этого текст
всё ещё обрезается — это, вероятно, уже про шрифт терминала (кириллица
не всегда идеально моноширинная в некоторых шрифтах), а не про код.
- (Шестой раунд — не баг, а конфликт хоткея, плюс найденный заодно баг.)
Дефолт `quit` сменён с `ctrl+alt+c` на `f10` (конфликтовал с DE/WM у
пользователя; существующие `hotkeys.toml` это не трогает — старые
конфиги правятся вручную). Заодно нашлась дыра: если сессия завершалась
сама (`exit`/`Ctrl+D` в шелле), таб не закрывался, а следующая же клавиша
в нём валила **всё приложение**`tab.transport.write(...).await?`
пробрасывал ошибку записи в мёртвый канал через весь `main_loop`. Теперь
канал каждого таба каждый кадр проверяется на `TryRecvError::Disconnected`
(не просто "пусто сейчас", а "закрыт навсегда") — таб с завершившейся
сессией закрывается автоматически, без валящего приложение падения.
**Что осталось (честные пробелы):**
1. **ssh.rs**: `AuthMethod::Agent` **временно отключён** — метод, которым
это было изначально реализовано (`authenticate_future` на `Handle`, взят
из старого примера в репозитории russh), в используемой версии (0.62.2)
не существует. Актуальную замену не удалось подтвердить без живой
компиляции — вместо третьей попытки угадать API вслепую, честно
возвращаю ошибку с указанием использовать `Key` или `Password`. Если
agent важен — дай знать, разберёмся предметно по фактической ошибке
компилятора, как в этот раз.
2. **ui.rs**: мастер создания сессии (`n` в списке сессий) — текстовые поля
только добавляют/стирают символы с конца строки, без перемещения курсора
внутри неё и без валидации на лету (например, нечисловой порт просто
тихо упадёт на дефолт при парсинге). Для тех же целей `komiterm profile
add` (создание credential-профиля) по-прежнему только через
`komiterm profile add` в терминале, не из этого мастера — они пока не
связаны. Из боковой панели в режиме списка сессий (`F1`, не `F2`) `n`
пока не работает — только из самого пикера при `F2`/начальном запуске.
3. **sftp.rs**: закачка привязана к UI, но download/upload по-прежнему
грузят файл целиком в память, нет rename/mkdir/delete в браузере (у
SftpSession методы есть) — см. TODO в конце `sftp.rs`.
4. **hoststats.rs**: только Linux (`/proc`) и только SSH; опрос блокирует
главный цикл на время round-trip. Не проверено на реальном хосте.
5. **serial.rs**: создать serial-сессию можно и через мастер (`n`), и руками
в `sessions.toml` — но нет `SerialPort::available_ports()` ни там, ни
там (списка реально подключённых портов, а не текстового поля вслепую).
Не проверено на реальном устройстве.
6. **telnet.rs**: отвечаем на DO/WILL только для ECHO и SUPPRESS-GO-AHEAD;
NAWS (передача размера окна) не реализован.
7. **sshconfig.rs**: `Match`-блоки и полноценные glob-паттерны не поддержаны.
8. **profiles.rs**: добавление профиля — простые вопросы в stdin, не форма
в ratatui (осознанно: это CLI-подкоманда вне raw-режима основного TUI).
9. Версии крейтов в `Cargo.toml` по-прежнему не проверялись сборкой **в
песочнице** (тот же древний `rustc 1.75`, упирается в edition2024 на
первом же транзитивном пакете) — но теперь у нас есть подтверждение с
реальной машины на актуальном тулчейне, и именно оттуда взяты все
исправления выше.
## Дальше по roadmap (после MVP)
- SSH-туннели (local/remote/dynamic port forwarding) — russh это умеет
- Логирование сессии в файл
- NAWS для telnet (передача размера окна серверу)
- rename/mkdir/delete в SFTP-браузере (методы у SftpSession уже есть)
## Сборка
```bash
cargo build --release
```
Сессии редактируются в `~/.config/komiterm/sessions.toml`, формат:
```toml
# Явный ключ прямо в сессии, host — реальный, ssh_config не трогаем
[[session]]
name = "myserver"
protocol = "ssh"
host = "example.com"
port = 22
user = "vladimir"
use_ssh_config = false
[session.auth]
method = "key"
path = "/home/vladimir/.ssh/id_ed25519"
passphrase = { source = "prompt" }
# host — алиас из ~/.ssh/config; HostName/User/IdentityFile подтянутся
# оттуда сами, если тут не переопределены
[[session]]
name = "prod-box"
protocol = "ssh"
host = "prod" # ищется в ~/.ssh/config как `Host prod`
# Логин/способ входа переиспользуются из именованного профиля
[[session]]
name = "staging-box"
protocol = "ssh"
host = "staging.internal"
credential = "work-key" # см. `komiterm profile add` / credentials.toml
[[session]]
name = "old-switch"
protocol = "telnet"
host = "192.168.1.1"
port = 23
```
Профили входа лежат отдельно в `~/.config/komiterm/credentials.toml` и
управляются через `komiterm profile add/list/remove` (см. раздел выше) —
руками их редактировать не обязательно.