# 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`) — учтено в текущем коде. ## ~/.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(&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` (см. раздел выше) — руками их редактировать не обязательно.