## Руководство по кнопкам GUI I2PChat Документ актуален для **I2PChat 1.3.0** (номер версии в корне репозитория: [`VERSION`](../VERSION)). Заметки к релизу: [**releases/RELEASE_1.3.0.md**](releases/RELEASE_1.3.0.md). Про **`SessionManager`** (транспорт с v1.2.6): [**ARCHITECTURE.md**](ARCHITECTURE.md) и [**releases/RELEASE_1.2.6.md**](releases/RELEASE_1.2.6.md). ### Окно выбора профиля При запуске GUI без указания профиля появляется диалог выбора профиля: Choose profile dialog - заголовок окна: **I2PChat**; - подзаголовок: **Choose profile**; - подсказка: `Use random_address for a one-time session, or enter a name to save your identity.` - поле **Profile:** с комбобоксом (список + ввод), текущее значение `random_address` (встроенный TRANSIENT-профиль); - подсказка под полем: `Click the list on the right to pick an existing profile, or type a new name above.` - строка **Profiles folder: ** (кликабельная, открывает папку профилей); - внизу — две кнопки: **Cancel** и **OK**. Как использовать: - **`random_address`** (TRANSIENT): - оставьте, если нужен одноразовый профиль без привязки к одному пиру; - важный момент безопасности: TOFU‑пины доверия не сохраняются между перезапусками; - имя **`default`** в аргументах командной строки по-прежнему принимается как синоним того же профиля и каталога данных; - **выбор из списка**: - откройте список справа от поля и выберите уже существующий профиль (каждый хранится в `profiles/<имя>/` как `<имя>.dat`); - **ввод нового имени**: - напечатайте своё имя профиля (например, `alice`); - допустимы только символы `a-z`, `A-Z`, `0-9`, `.`, `_`, `-` (длина 1..64); - профильный `.dat` создаётся сразу: ключи сохраняются в `profiles/<имя>/<имя>.dat` (или в keyring), а `Lock to peer` дописывает туда адрес пира и делает профиль одно‑к‑одному. **Каталог данных приложения** зависит от ОС: на **macOS** — `~/Library/Application Support/I2PChat`, в **Windows** — `%APPDATA%\I2PChat`, в **Linux** и др. — `~/.i2pchat`. Права доступа к папке на Unix ограничиваются владельцем (0700). #### Где лежат файлы профиля (.dat и остальное) Для каждого сохранённого профиля (например, `alice`) приложение использует подпапку **`profiles/<имя>/`**: там лежат `alice.dat`, `alice.trust.json`, история чатов, контакты и другие файлы с префиксом имени. В **корне** каталога данных остаются общие вещи: папки `downloads/`, `images/`, файл `ui_prefs.json` и т.п. Старые установки могли хранить всё плоско в корне; при первом запуске с таким профилем файлы **переносятся** в `profiles/<имя>/` автоматически. | ОС | Корень данных I2PChat | Пример пути к `.dat` профиля `alice` | |----------|-------------------------|--------------------------------------| | Windows | `%APPDATA%\I2PChat` — обычно **`C:\Users\<ваше_имя>\AppData\Roaming\I2PChat`** | `...\I2PChat\profiles\alice\alice.dat` | | macOS | `~/Library/Application Support/I2PChat` | `.../I2PChat/profiles/alice/alice.dat` | | Linux | `~/.i2pchat` | `~/.i2pchat/profiles/alice/alice.dat` | Папку данных можно открыть из диалога выбора профиля — строка с путём кликабельна на всех ОС. Формат профиля `.dat` в актуальной версии: - строка 1 — приватный ключ профиля (если он не хранится в keyring); - строка 2 — закреплённый peer (`stored peer`) при использовании `Lock to peer`. Если identity хранится в keyring, файл `.dat` может содержать только адрес закреплённого пира. После выбора или ввода имени нажмите **OK**, чтобы продолжить, или **Cancel**, чтобы закрыть окно и не запускать чат. ### 3. Основной интерфейс (главное окно чата) После выбора профиля открывается главное окно чата: Главное окно I2PChat: чат, ввод, панель действий - **Заголовок окна** — `I2PChat @ <имя_профиля>` (например, `I2PChat @ alice`). - **Строка статуса** — вверху, над чатом: в полном виде есть `Net`, профиль (`Prof`), `Link`, `Peer`, `Stored`, `Secure`, текущий маршрут отправки (`Send:*`), состояние **BlindBox** (короткая подпись) и `ACKdrop`. При **сужении окна** показывается укороченный вариант (в том числе `Tx:` и `BB:`). **Наведите курсор** на строку статуса — откроется подсказка с полной диагностикой, маршрутом отправки и пояснением BlindBox. При важных изменениях сети/безопасности и ошибках строка статуса временно раскрывается для читаемости, затем возвращается в компактный режим. - **Переключатель темы** — справа от статуса (иконка солнца/луны). Меняет темы `ligth` и `night`. - **Saved peers** — опциональная **левая** боковая панель с книгой контактов профиля; подробнее в **§3.1**. - **Область чата** — отображаются сообщения (ваши и собеседника), системные уведомления, статус передачи файлов. Сообщения можно выделять и копировать (правый клик или контекстное меню). - **Поле ввода** — под областью чата: введите текст. **По умолчанию** на **macOS** **Enter** отправляет, а **Shift+Enter** вставляет новую строку; на **Windows/Linux** **Enter** вставляет новую строку, а **Ctrl+Enter** отправляет. На **macOS** в обоих режимах отправка срабатывает и по **⌘+Enter**, и по **Ctrl+Enter**. В меню **`⋯`** можно переключить **Enter sends message: ON/OFF** под свой привычный режим: при **ON** **Enter** отправляет, при **OFF** **Enter** вставляет новую строку. Подсказка-плейсхолдер в поле соответствует режиму. Настройка сохраняется в **`ui_prefs.json`**. **Растровое изображение** из буфера можно вставить (**Ctrl+V** / **⌘V** или **Paste** в меню поля) — отправка как у пункта **Send picture** (PNG, JPEG, WebP). Текст в поле ввода хранится **отдельно для каждого пира** (по нормализованному адресу): при смене адреса в панели действий или при смене текущего пира восстанавливается черновик для выбранного контакта; после успешной отправки черновик для этого пира очищается. Черновики сохраняются на диск в файл `profiles/<профиль>/<профиль>.compose_drafts.json` (с ограничением числа записей; при старой плоской раскладке читается и переносится в эту папку). - **Панель действий** — в самом низу окна: адрес пира, кнопки подключения и меню **`⋯`** (см. раздел 4). `Connect` нужен для живого чата и первичной инициализации офлайн-доставки. Если BlindBox уже готов, отправка текста может сразу уйти в офлайн-очередь даже без активного live-соединения. #### 3.1. Боковая панель Saved peers (книга контактов) Слева от чата — список **Saved peers**: локальная **книга контактов** текущего профиля. Данные хранятся в `profiles/<профиль>/<профиль>.contacts.json` (рядом с `<профиль>.dat`). - **Строки** — отображаемое имя (или сокращённый base32‑идентификатор без суффикса), подпись (превью последнего сообщения или ваша заметка), подсветка непрочитанного, если это не активный чат. - **Клик** по строке — подставляет адрес пира в поле (как ручной ввод) и синхронизирует черновик; если профиль **залочен** на другого пира, переключение может быть недоступно (см. сообщения в интерфейсе). - **◀ / ▶** — свернуть или развернуть панель; при **Lock to peer** панель может открываться **свёрнутой**, чтобы отдать место чату. - **Перетаскивание** узкой полоски между списком и чатом — изменение ширины панели (в пределах ограничений). - **ПКМ** по контакту — **Edit name & note…** (только локальные подписи), **Contact details…** (адрес, TOFU, при необходимости снятие pin), **Remove from saved peers…** (с опциями также удалить шифрованную историю, TOFU pin, lock профиля и локальный файл состояния BlindBox для этого пира, где применимо). ### 4. Панель действий (управление подключением и профилями) Панель действий располагается **внизу окна**, под областью ввода, и содержит: - поле ввода **адреса пира**; - кнопки **`Connect`** и **`Disconnect`**; - кнопку **`⋯` (Ещё действия)**, открывающую меню: - **Load profile (.dat)**; - **Send picture**; - **Send file**; - **BlindBox diagnostics**; - **Export profile backup…** / **Import profile backup…**; - **Export history backup…** / **Import history backup…**; - **Lock to peer**; - **Forget pinned peer key**; - **Copy my address**; - **Chat history: ON/OFF** (подпись отражает текущее состояние); - **Clear history**; - **History retention…**; - **Privacy mode: ON/OFF**; - **Enter sends message: ON/OFF**; - **Notification sound: ON/OFF**. Все элементы панели имеют одинаковую высоту и выстроены в строку. **Горячие клавиши** (Connect, Disconnect, `⋯`, тема, Saved peers, пункты меню и т.д.) привязаны к **физическим позициям US QWERTY** — тем же клавишам, что на стандартной английской (US) клавиатуре. **Русская и другие раскладки не отключают эти сочетания**; переключать язык не обязательно. На **Linux** используются типичные **evdev**-сканы и распространённое смещение **X11** keycode. #### 4.1. Меню `⋯` (Ещё действия) Кнопка **`⋯`** или **Ctrl+.** (Windows/Linux) / **⌘+.** (macOS) открывает или закрывает то же всплывающее меню; подсказка на кнопке содержит строку **Shortcut:**. В меню — действия по профилю и подключению: Меню «⋯»: профиль, отправка, BlindBox, backup профиля и истории, lock и доверие, история и приватность, переключатели уведомлений - **Load profile (.dat)** — выбор файла для загрузки профиля из `.dat`. - **Send picture** — отправить изображение подключённому пиру. - **Send file** — отправить любой файл подключённому пиру. - **BlindBox diagnostics** — текстовая сводка по BlindBox/офлайн-маршруту и репликам (дополняет строку статуса и раздел 4.9). - **Export profile backup…** / **Import profile backup…** — парольно защищённые архивы текущего профиля (`.dat` и поддерживаемые sidecar); при импорте при конфликте имени выбирается свободное имя профиля. - **Export history backup…** / **Import history backup…** — только зашифрованные per-peer файлы истории; при импорте запрашивается, перезаписывать совпадения или добавлять только отсутствующие. - **Check for updates…** — сравнить текущую сборку с именами ZIP на странице релизов (см. раздел 4.12). - **Open App dir** — открыть каталог данных приложения в файловом менеджере. - **I2P router…** — открыть диалог backend’а роутера (**Ctrl/Cmd+R**): переключение между системным `i2pd` по SAM и встроенным роутером, настройка портов backend’а, открытие каталога/лога роутера и перезапуск bundled router. - **Lock to peer** — привязать текущий профиль к подключённому пиру (см. раздел 4.7). - **Forget pinned peer key** — удалить TOFU-pinning ключа подписи текущего пира (см. раздел 4.10). - **Copy my address** — скопировать ваш I2P-адрес в буфер. - **Chat history: ON/OFF** — включить/выключить сохранение локальной истории (см. раздел 4.11); подпись в меню показывает текущее состояние. - **Clear history** — удалить локальный файл истории для текущего пира. - **History retention…** — лимиты: максимум сообщений на peer и максимальный возраст в днях до записи зашифрованной истории. - **Privacy mode: ON/OFF** — при ON: в трее скрывается текст сообщения (заголовок может содержать имя/адрес пира); пока это окно в фокусе — без тостов в трее и без звука уведомлений (в т.ч. для других чатов). При OFF эти эффекты отключены. Подпись показывает состояние. - **Enter sends message: ON/OFF** — при **ON**: **Enter** отправляет сообщение, **Shift+Enter** — новая строка (**Ctrl/⌘+Enter** по-прежнему отправляет). При **OFF**: **Enter** вставляет новую строку, **Shift+Enter** тоже вставляет новую строку, а **Ctrl+Enter** отправляет (**на macOS: ⌘+Enter или Ctrl+Enter**). Текст-подсказка в поле ввода меняется вместе с режимом; выбор сохраняется (см. **`ui_prefs.json`**). - **Notification sound: ON/OFF** — звук входящих, когда он должен проигрываться; при OFF путь к кастомному звуку сохраняется (при активном Privacy mode звук в фокусе всё равно подавляется). Пример окна **I2P router** (**⋯ → I2P router…** / **Ctrl/Cmd+R**): Диалог I2P router: выбор system или bundled i2pd, настройка портов, открытие путей роутера и перезапуск bundled router #### 4.2. Поле адреса пира Поле адреса пира принимает **канонический идентификатор**: строка **base32** (обычно 40 символов) **без** суффикса `.b32.i2p`. Такой же вид используется внутри приложения (контакты, сессии, группы). При вводе из буфера по-прежнему принимается и полная форма **`.b32.i2p`** — она нормализуется к базовому host. **Copy my address** копирует в буфер **канонический** адрес — **base32 без** суффикса `.b32.i2p` (как в контактах и поле пира). - Можно вводить/вставлять адрес вручную. - Если профиль «залочен» на пира и поле пустое, адрес автоматически заполняется из сохранённого значения. #### 4.3. Кнопка `Connect` Кнопка **`Connect`** инициирует живое подключение к текущему адресу в поле. **Несколько live-сессий:** можно держать несколько параллельных SAM-потоков к разным сохранённым пирам (лимит по умолчанию **8**, задаётся переменной **`I2PCHAT_MAX_LIVE_SESSIONS`**, диапазон 1–64). **`Connect`** остаётся доступной, если уже есть живой чат, но выбранный пир ещё не подключён и лимит не исчерпан. Повторное подключение к тому же пиру отклоняется. **Горячая клавиша:** **Ctrl+1** в Windows/Linux, **⌘1** на macOS — то же, что нажатие **`Connect`**, когда кнопка активна (срабатывает и при фокусе в поле ввода сообщения). Логика: 1. Если поле **не пустое**: - запускается попытка подключения через ядро (`connect_to_peer`). 2. Если поле **пустое**: - если есть сохранённый пир (`stored_peer`), он подставляется в поле и используется; - иначе показывается предупреждение: ```text Please enter peer address ``` После успешного подключения: - обновляется строка статуса; - приходящие сообщения отображаются в области чата; - по сети могут пойти файловые/системные и другие события. Зачем нужен `Connect`, если пир офлайн: - для **live-чата** (онлайн-обмен в реальном времени); - для **первой инициализации BlindBox root** (одна успешная защищённая сессия с этим пиром); - для диагностики доступности пира. При первом контакте с новым ключом пира появится диалог **Trust on First Use (TOFU)**: - в окне показываются адрес пира, короткий fingerprint и префикс публичного ключа; - в диалоге есть предупреждение, что TOFU без OOB-сверки не подтверждает личность; - выберите **Yes**, чтобы доверить и закрепить ключ, либо **No**, чтобы прервать соединение; - для повышенной безопасности сверяйте fingerprint с собеседником по независимому каналу. **Состояние кнопки:** **`Connect`** **неактивна** (серая), пока в статусе сети не появятся **Pending** или **Visible** (сессия I2P готова), пока нет адреса пира или сохранённого закреплённого пира, пока уже есть **live-сессия с этим же пиром**, пока достигнут лимит одновременных сессий, либо пока идёт исходящая попытка подключения. Пока выполняется dial-out, **`Connect`** остаётся неактивной; повторное нажатие ядро игнорирует. **Подсказки** на кнопке объясняют причину (дождаться Pending/Visible, ввести адрес, уже подключены к этому пиру, лимит сессий и т.д.) и содержат строку **Shortcut:** для **Ctrl+1** / **⌘1**. Если BlindBox уже готов к офлайн-очереди, tooltip у `Connect` явно помечает подключение как **optional**. #### 4.4. Кнопка `Disconnect` Кнопка **`Disconnect`** разрывает **активную** live-сессию (пир, выбранный для чата; при одном потоке поведение как раньше). При нескольких параллельных сессиях отключается поток активного пира. **Горячая клавиша:** **Ctrl+0** в Windows/Linux, **⌘0** на macOS — то же, что **`Disconnect`**, когда кнопка активна. **Состояние кнопки:** **`Disconnect`** **неактивна**, пока нет активной сессии с пиром; при наведении — пояснение и строка **Shortcut:** **Ctrl+0** / **⌘0**. После нажатия: - ядро инициирует отключение; - в чате может появиться системное сообщение о разрыве соединения; - строка статуса обновляется. #### 4.5. Действие `Copy my address` (меню `⋯`) Пункт **`Copy my address`** в меню **`⋯`** копирует ваш собственный I2P‑адрес в буфер обмена. Логика: 1. Если локальный адрес ещё не инициализирован: - показывается окно (заголовок **Copy My Addr**): ```text Local destination is not initialized yet. ``` 2. Если адрес уже есть: - в буфер обмена копируется строка **base32** (без `.b32.i2p`); - в чате появляется системное сообщение: ```text My address copied to clipboard. ``` Это удобно для быстрой передачи вашего адреса собеседнику через любой другой канал. #### 4.6. Действие `Send file` (меню `⋯`) Пункт **`Send file`** в меню **`⋯`** отправляет файл текущему подключённому пиру. После выбора: 1. Открывается диалог выбора файла (`Select file to send`). 2. Если путь не выбран — отправка отменяется. 3. Если файл выбран: - ядро начинает передачу (`send_file(path)`). Прогресс передачи отображается в области чата сообщениями вида: ```text <имя_файла>: <получено>/<размер> bytes ``` Область чата: прогресс отправки файла в ленте сообщений На принимающей стороне: - при входящем файле сначала показывается диалог **`Incoming file`**: - с вопросом `Accept incoming file?`; - с информацией о имени и размере; - если пользователь выбирает **`No`**: - временный файл удаляется; - в чате появляется сообщение об отклонении: ```text Incoming file rejected: <имя_файла> ``` - при совпадении имени с уже существующим файлом в `downloads` новый файл сохраняется как `<имя> (1).`, `<имя> (2).` и т.д. без перезаписи. Пункт **`Send picture`** работает аналогично, но предназначен для отправки изображений (PNG, JPEG или WebP) и отображается в чате как встроенное изображение. Палитра эмодзи (сетка смайлов) у поля ввода #### 4.7. Кнопка `Lock to peer` Кнопка **`Lock to peer`** **не обязательна** к использованию — чат отлично работает и без неё. По умолчанию, если вы никогда не жали `Lock to peer`, профиль ведёт себя как **почтовый ящик**: - **любой** узел, который знает ваш адрес, может написать этому профилю; - вы сами можете подключаться к разным пирам со временем. Если же нажать **`Lock to peer`**, профиль становится **жёстко привязан к одному собеседнику**: - в профильный `.dat`‑файл сохраняется адрес собеседника в каноничном формате (строка 1 — ключ, строка 2 — peer; при keyring может быть только peer); - при следующих запусках этого профиля адрес будет подставляться автоматически как `stored_peer`; - входящие соединения от других адресов ядро может отклонять как «неавторизованные». Ограничения и поведение: 1. Если текущий профиль — `random_address` (режим `TRANSIENT`; в CLI алиас `default`): - появится предупреждение: ```text Cannot lock in TRANSIENT mode. Restart with a profile name. ``` 2. Если профиль уже «залочен» (`stored_peer` не пустой): - показывается информационное окно с уже сохранённым адресом. 3. Если нет подтверждённого адреса пира (`current_peer_addr` пустой): - показывается предупреждение: ```text Peer address not yet verified. Establish a connection first. ``` 4. В остальных случаях: - `Lock to peer` доступен только после криптографической верификации binding адреса пира; - создаётся/обновляется файл `profiles/<имя_профиля>/<имя_профиля>.dat` (каноничный формат без дублей строк); - в чате появляется системное сообщение: ```text Identity is now locked to this peer. ``` #### 4.8. Кнопка `Load .dat` Кнопка **`Load .dat`** позволяет переключиться на другой профиль, выбрав существующий `.dat`‑файл. После нажатия: 1. Открывается диалог `Select profile (.dat)`: - по умолчанию указывает на **корень каталога данных приложения** (в Windows — `%APPDATA%\I2PChat`, в Linux — `~/.i2pchat`, на macOS — `~/Library/Application Support/I2PChat` — папку, внутри которой лежит `profiles/`); - фильтрует файлы по маске `*.dat`. 2. Если файл не выбран — операция отменяется. 3. Если файл выбран: - из пути берётся имя файла без расширения (``); - `.dat` копируется в `profiles//.dat`, при необходимости создаётся `profiles//` (если такого пути ещё нет); - происходит асинхронное переключение профиля: - текущее ядро корректно останавливается (`shutdown`); - окно обновляет заголовок на `I2PChat @ <имя_профиля>`; - создаётся новое ядро для этого профиля; - повторно инициируется сессия I2P. Таким образом, через GUI можно: - быстро импортировать готовый профиль; - переключаться между несколькими профилями без перезапуска приложения. #### 4.9. Опционально: BlindBox (офлайн-текст) **BlindBox** — функция офлайн-очереди текста для закреплённого пира, когда **нет живой защищённой сессии**. Для **именованных** профилей (`persistent`) включена по умолчанию; для эфемерного профиля `random_address` отключена. - Нужен **постоянный профиль** и **Lock to peer**. Для межхостовой офлайн-доставки задайте общие **Blind Box**-серверы через `I2PCHAT_BLINDBOX_REPLICAS`. Для дефолта на весь деплой используйте `I2PCHAT_BLINDBOX_DEFAULT_REPLICAS`. Для централизованной прод-настройки — `I2PCHAT_BLINDBOX_DEFAULT_REPLICAS_FILE`. В **готовых сборках** дополнительно зашита **пара адресов** в `DEFAULT_RELEASE_BLINDBOX_ENDPOINTS` в `i2pchat/core/i2p_chat_core.py` (`tcglilyjadosrez5gu3kqvrdpu6ri622jwrzamtpburtnpge7wgq.b32.i2p:19444`, `dzyhukukogujr6r2vwfy667cwm7vg3oomhx2sryxhb6mn4i4wbjq.b32.i2p:19444`; перекрывается переменными выше; отключить: `I2PCHAT_BLINDBOX_NO_BUILTIN_DEFAULTS=1`). См. [**RELEASE_0.6.0.md**](releases/RELEASE_0.6.0.md) — без повторения криптодеталей). Опционально только для локальной/дев-сборки: `I2PCHAT_BLINDBOX_LOCAL_FALLBACK=1` поднимает локальный Blind Box (`127.0.0.1:19444`). **Локальный токен:** задайте **`I2PCHAT_BLINDBOX_LOCAL_TOKEN`** в окружении процесса **I2PChat** (и тем же значением — отдельный демон-реплику на том же `host:port`, если он у вас есть). В режиме **`local-auto`**, если переменная не задана, ядро генерирует одноразовый токен на запуск (удобно для простой разработки, но не для стыковки с внешним процессом). Для raw TCP / loopback-реплик токен стоит сохранять. **Секрет на реплику (именованные профили):** в окне **BlindBox diagnostics** можно задать опциональные токены для конкретных endpoint’ов (по одной строке: `endpointтокен`). Они хранятся в `.blindbox_replicas.json` в поле **`replica_auth`** (формат файла **версия 2**; старые файлы **версии 1** читаются как только список реплик). Клиент подставляет токен в `PUT`/`GET` только для этого адреса. На своей Python-реплике задайте **`BLINDBOX_AUTH_TOKEN`** тем же значением (см. `i2pchat/blindbox/blindbox_server_example.py`). Токен в протоколе **не заменяет** доверие к I2P destination — он лишь ограничивает доступ к сырому TCP-протоколу. Для публичной реплики, доступной только через I2P tunnel, токен можно оставить пустым, но лимиты TTL / quota всё равно должны оставаться включёнными. Для package-style деплоя используйте **`python -m i2pchat.blindbox.daemon`**; вместе с репозиторием идут подходящие примеры `systemd`, env, install/bundle helper scripts, one-shot `install.sh` и fail2ban в `i2pchat/blindbox/daemon/`. Принудительно выключить BlindBox можно `I2PCHAT_BLINDBOX_ENABLED=0`. **Кворум PUT:** по умолчанию `I2PCHAT_BLINDBOX_PUT_QUORUM=1` (успех, если хотя бы один **Blind Box** принял blob). Значение `=2` — строгий режим: ответ должны дать все перечисленные Blind Box. - `Send` в GUI работает как «умный маршрут»: - при живой защищённой сессии отправляет онлайн; - при `Send: offline queue` ставит текст в BlindBox-очередь (без обязательного ручного Connect); - при `Send: need Connect once` сохраняет текст в поле ввода и подсказывает сделать один live Connect для инициализации root. - В режиме готовой офлайн-очереди подпись кнопки меняется на `Send offline` (в две строки на кнопке). - После завершения **живой защищённой сессии** (handshake) подпись снова **`Send`** — **сразу**, без необходимости отправлять первое сообщение, чтобы кнопка обновилась. - Отладочные строки BlindBox про queued/received в ленте чата не показываются; детали остаются в статусе и tooltip. - Состояние видно в **строке статуса** (поля `Send:*` и BlindBox); при наведении — подсказки, если что-то не настроено. - **Совместимость:** у пиров на старых сборках BlindBox может не поддерживаться; обычный чат, файлы и картинки работают как раньше. - **Текстовые группы:** офлайн-доставка в группу **не использует отдельный «групповой» ключ BlindBox**. Сообщение рассылается **каждому** участнику (кроме отправителя) по **тому же парному** каналу BlindBox, что и личный чат с этим пиром: нужен **BlindBox root** с каждым таким участником (обычно после хотя бы одной успешной защищённой live-сессии 1:1). К подключённым в данный момент пирам сообщение может уйти по live, к остальным — в очередь BlindBox, если для пары ключи готовы. После отправки в ленте группы показывается сводка доставки; при сбоях по отдельным пирам — строка **Details** с причиной (например `blindbox-await-root`). Пример окна **BlindBox diagnostics** (**⋯ → BlindBox diagnostics**): сводка телеметрии, список endpoint’ов реплик (если можно править), опциональная **replica auth**, кнопки **Example server…** и **Save and restart**. Окно BlindBox diagnostics: сводка, реплики, auth, Example server и Save and restart Окно **Blind Box setup examples** (**BlindBox diagnostics → Example server…**): вкладки с текстами (в т.ч. **`install.sh`** и **I2pd**), кнопки **Get install** (сохранить скрипт) и **Copy curl** (однострочник для сервера) для своей реплики. Blind Box setup examples: вкладка install.sh, Get install и Copy curl для кастомной реплики #### 4.10. Действие `Forget pinned peer key` (меню `⋯`) Пункт **`Forget pinned peer key`** удаляет сохранённый TOFU pin публичного signing-key для текущего пира. Когда это нужно: - если у собеседника легитимно сменился ключ; - если вы хотите вручную сбросить доверие и выполнить TOFU заново. Что происходит: 1. GUI запрашивает подтверждение. 2. Запись для текущего peer удаляется из trust store (`.trust.json`). 3. При следующем защищённом подключении к этому peer появится TOFU-подтверждение заново. #### 4.11. История чата (зашифрована локально) История чата хранится **локально по каждому peer** в отдельном зашифрованном файле. - Файлы истории создаются в `profiles//`, формат имени: - `.history..enc` - Шифрование: - полезная нагрузка шифруется `NaCl SecretBox`; - ключ истории выводится через HKDF из identity-ключа профиля; - для каждого peer используется отдельный file-key (через salt + peer context). - Запись атомарная (через temp-файл + `fsync` + `replace`), чтобы снизить риск порчи при аварийном завершении. Управление из меню `⋯`: - **Chat history: ON/OFF**: - `ON` — новые сообщения текущей защищённой сессии добавляются в локальную историю; - `OFF` — новые сообщения в историю не пишутся и не сохраняются. - **Clear history**: - удаляет файл истории для текущего peer. - **History retention…**: - диалог лимитов: сколько сообщений на peer хранить и максимальный возраст в днях; `0` дней — ограничение только по числу сообщений. Поведение в работе: - история загружается после установления защищённого канала; - при отключении/закрытии окна история сохраняется; - также используется периодический flush (если есть несохранённые изменения). Ограничение размера: - по умолчанию сохраняются последние `1000` сообщений на peer; - лимит можно переопределить в `ui_prefs.json` через `history_max_messages`. #### 4.12. Проверка обновлений и доверие к загрузкам Пункт меню **⋯ → Check for updates…** (**Ctrl/Cmd+U**) загружает **HTML** страницы релизов, находит имена ZIP по шаблону и **сравнивает номер версии** с локальной (файл **`VERSION`** в корне при запуске из исходников). Для адресов `*.i2p` запрос по умолчанию идёт через **HTTP-прокси активного backend’а роутера** (или через `I2PCHAT_UPDATE_HTTP_PROXY`, если он задан). Приложение **не скачивает** архив и **не проверяет** хеш или подпись. **Цепочка доверия при установке вручную:** 1. Скачайте ZIP с официальной страницы релизов (или зеркала, которому доверяете). 2. Сверьте SHA256 с файлом **`SHA256SUMS`** из того же релиза. 3. Проверьте отсоединённую подпись GPG для `SHA256SUMS` (**`SHA256SUMS.asc`**) ключом релизов (**отпечаток** `2BA0C56D8240077F9773248A2C05CFB3F6DFDF99`, UID **metanoicarmor@gmail.com** — на [keys.openpgp.org](https://keys.openpgp.org/search?q=metanoicarmor%40gmail.com)). При необходимости: `gpg --keyserver keys.openpgp.org --recv-keys 2BA0C56D8240077F9773248A2C05CFB3F6DFDF99`, затем `gpg --verify SHA256SUMS.asc SHA256SUMS`. Если задана переменная **`I2PCHAT_RELEASES_PAGE_URL`**, источник страницы релизов меняется — относитесь к нему как к произвольному HTTP-источнику. При первой проверке обновлений GUI покажет предупреждение; его можно принять только если вы осознанно доверяете этому URL. ### 5. Системные уведомления и звук GUI‑клиент использует системный трей (`QSystemTrayIcon`) и при поддерживаемых платформах — звуковые уведомления (`QSoundEffect`) для входящих сообщений. #### 5.1. Системные уведомления - При получении входящего сообщения от пира (тип `peer`) вызывается обработчик `handle_notify`. - Если окно/приложение **не активно** (свернуто или в фоне): - создаётся короткий заголовок: - базовый текст — `New message`; - при наличии адреса пира добавляется обрезанный адрес: `New message from `. - через `QSystemTrayIcon` показывается системное уведомление (тост) на 5 секунд. - Если окно активно, GUI полагается на визуальное обновление чата без всплывающих уведомлений. - Для входящего подключения показывается уведомление **Incoming connection** с адресом пира (если он известен). #### 5.2. Звуковые уведомления - В меню **`⋯`** доступны переключатели **Privacy mode** и **Notification sound** наряду с описанным ниже поведением. - Если модуль `QtMultimedia` доступен: - создаётся `QSoundEffect`; - при заданной переменной окружения `I2PCHAT_NOTIFY_SOUND` используется указанный локальный аудиофайл; - громкость по умолчанию — около 70%. - При входящем сообщении, когда окно не активно: - проигрывается кастомный звук (если задан и доступен); - если проигрывание не удалось — используется резервный `QApplication.beep()`. ### 6. Типичные сценарии использования #### 6.0. Установка: Debian / Ubuntu **Сейчас без отдельного мейнтейнера зеркала:** ставьте **`.deb`** с [релизов](https://github.com/MetanoicArmor/I2PChat/releases) (GUI: `i2pchat_<версия>_amd64.deb` / `arm64.deb`; TUI: `i2pchat-tui_…`): ```bash sudo apt install ./i2pchat_*_amd64.deb # или: sudo apt install ./i2pchat-tui_*_amd64.deb ``` Нужен системный **`i2pd`** с SAM (в **`.deb`** встроенного роутера нет). **Опциональное apt-зеркало** на GitHub Pages появится только после настройки секретов CI и публикации — до этого **`curl …/KEY.gpg`** даст **404**. Когда зеркало будет жить, команды подключения (**deb822**) — в [`packaging/apt/README.md`](../packaging/apt/README.md) и в [`docs/INSTALL.md`](INSTALL.md). #### 6.1. Первый запуск и отправка сообщения 1. Выберите backend I2P‑роутера: - либо убедитесь, что системный I2P‑роутер с SAM (`127.0.0.1:7656`) запущен; - либо переключите I2PChat на встроенный роутер в **More actions → I2P router…**. 2. Запустите I2PChat в зависимости от платформы: - **Windows**: распакуйте релизный архив и запустите `I2PChat.exe`. - **Linux**: сделайте AppImage исполняемым (`chmod +x I2PChat-x86_64.AppImage`) и запустите `./I2PChat-x86_64.AppImage`. - **macOS**: перенесите `I2PChat.app` в `/Applications` (или удобное место) и откройте обычным способом. 3. В диалоге `Choose profile`: - оставьте `random_address` или введите своё имя профиля (например, `alice`). 4. В главном окне: - дождитесь в строке статуса **Pending** или **Visible** (тогда станет доступна **`Connect`**); - при необходимости скопируйте свой адрес через меню `⋯` → `Copy my address` и передайте его собеседнику другим каналом. 5. Когда у вас есть адрес пира: - вставьте его в поле `Peer .b32.i2p address`; - нажмите `Connect`. 6. После установления соединения: - в нижнем поле ввода напишите сообщение; - отправьте сообщение клавишей `Enter` на macOS или `Ctrl+Enter` на Windows/Linux; при желании можно нажать кнопку `Send`. 7. Новое сообщение появится в правой части чата (как отправленное вами). #### 6.2. Отправка файла собеседнику 1. Убедитесь, что соединение с пиром установлено (подключились через `Connect`). 2. Откройте меню **`⋯`** и выберите **`Send file`**. 3. Выберите нужный файл в открывшемся диалоге. 4. Наблюдайте прогресс в области чата по сообщениям: ```text <имя_файла>: <получено>/<размер> bytes ``` На стороне получателя при входящем файле: - появится диалог подтверждения приёма; - при отказе файл будет удалён, а в чате появится сообщение об отклонении. #### 6.3. Переключение на постоянный профиль и «локация» пира 1. Запустите I2PChat с именем профиля (опционально, через аргумент командной строки): - **Windows**: `I2PChat.exe myprofile`. - **Linux**: `./I2PChat-x86_64.AppImage myprofile`. - **macOS**: `open -a I2PChat --args myprofile`. 2. Подключитесь к нужному пиру через поле адреса и кнопку `Connect`. 3. Убедитесь, что соединение установлено и вы можете обмениваться сообщениями. 4. Если вы хотите, чтобы профиль работал как **жёсткий канал один‑к‑одному** (написать может только этот пир), нажмите кнопку **`Lock to peer`**: - убедитесь, что профиль не эфемерный (`random_address` / алиас `default`); - при успешной записи появится сообщение: ```text Identity myprofile is now locked to this peer. ``` 5. В дальнейшем при запуске профиля `myprofile`: - в строке статуса появится `Stored: <адрес>`; - при пустом поле ввода адреса этот адрес будет подставлен автоматически; - входящие соединения от других адресов для этого профиля больше не принимаются. #### 6.4. Импорт готового профиля из `.dat` 1. Убедитесь, что у вас есть файл профиля, например `friend.dat`. 2. Запустите GUI (с любым профилем или через эфемерный `random_address` / `default`). 3. Нажмите **`Load .dat`**. 4. В открывшемся диалоге выберите `friend.dat`: - файл будет скопирован в `profiles/friend/friend.dat` (при необходимости создаётся `profiles/friend/`, если такого пути ещё нет); - профиль автоматически переключится на `friend`; - состояние ядра будет перезапущено под новым профилем. ### 7. Типичные проблемы на уровне GUI #### 7.1. В чате не появляются сообщения Проверьте: - строку статуса: - нет ли там ошибок подключения к SAM/I2P; - есть ли нормальное состояние (не `initializing` бесконечно долго); - корректность адреса пира в поле: - адрес должен заканчиваться на `.b32.i2p`; - без лишних пробелов и символов; - факт нажатия `Connect` и отсутствия ошибок в чате (`ERROR`, `disconnect`). Если всё выглядит корректно, но трафика нет — проблема, скорее всего, **на стороне сети/I2P**, а не GUI. #### 7.2. Не удаётся подключиться к пиру Убедитесь, что: - запущен I2P‑роутер, SAM‑порт доступен; - адрес пира введён полностью (включая `.b32.i2p`); - пир в онлайне и использует совместимый клиент (legacy‑клиенты до `0.3.x`/`0.4.x` не поддерживаются). GUI в этом случае отобразит соответствующие системные/ошибочные сообщения в области чата. #### 7.3. Не видно запросов на приём файла При входящем файле GUI должен показать диалог `Incoming file` с вопросом `Accept incoming file?`. Если диалог не появляется: - проверьте, не блокирует ли его другая модальная форма (диалоги могут быть «за» главным окном); - убедитесь, что приложение не находится в «подвисшем» состоянии из‑за сетевых проблем. #### 7.4. Не работает копирование текста сообщений Проверьте: - выбрано ли сообщение (клик по нужному «баблу»); - используете ли вы стандартную комбинацию копирования: - `Ctrl+C` на Windows/Linux; - `Cmd+C` на macOS; - можно также воспользоваться контекстным меню (`Copy text` / `Copy with timestamp`). #### 7.5. Qt не загружает плагин **xcb** (Linux) При запуске GUI **из исходников** на **Debian/Ubuntu** в сессии **X11** для **PyQt6 6.5+** нужен системный пакет **`libxcb-cursor0`**. Если в терминале появляются строки про `xcb-cursor0` / `libxcb-cursor0 is needed` или `Could not load the Qt platform plugin "xcb"`, установите: ```bash sudo apt install libxcb-cursor0 ``` Затем снова запустите приложение. (В **README** репозитория эта команда тоже указана в блоке про запуск из исходников.) #### 7.6. Сборка из исходников: **uv** и слой **i2pchat.sam** (для разработчиков) Если вы запускаете I2PChat из **git-репозитория** (не из готового архива): - установите **[uv](https://docs.astral.sh/uv/)** и синхронизируйте зависимости по **`pyproject.toml`** / **`uv.lock`** — см. **README** репозитория (`uv sync`, затем `uv run python -m …`); - **I2P SAM** (управляющее соединение с роутером, сессии, потоки, NAMING) реализован **в этом репозитории** пакетом **`i2pchat.sam`**. Пакет **`i2plib`** с PyPI для I2PChat **не используется**, вендорная копия удалена. ### 8. Метаданные протокола и padding Несмотря на шифрование после handshake, на транспортном уровне остаются наблюдаемыми: - тип кадра (`TYPE`); - длина кадра (`LEN`); - pre-handshake обмен identity preface. Для снижения утечек по длине в зашифрованном режиме используется профиль padding: - по умолчанию: `balanced` (выравнивание до блоков 128 байт); - опционально: `off` (без padding). Переопределение профиля через переменную окружения: ```bash I2PCHAT_PADDING_PROFILE=off python3 -m i2pchat.gui ``` **Диагностика:** переменная **`I2PCHAT_LOG_LEVEL`** (`DEBUG` или `INFO`) включает вывод логов пакета `i2pchat` в stderr (фрейминг, HMAC, транспорт). Пример: `I2PCHAT_LOG_LEVEL=DEBUG python3 -m i2pchat.gui`. Точки входа при запуске из исходников (из корня репозитория): графический клиент — `python3 -m i2pchat.gui` или `python3 -m i2pchat.run_gui` (тот же код, что и скрипт [`i2pchat/run_gui.py`](../i2pchat/run_gui.py)); терминальный TUI — `python3 -m i2pchat.tui` (эквивалент `python3 -m i2pchat.gui.chat_python`). Прикладной код располагается только под `i2pchat/`; отдельных «плоских» шимов в корне нет. Компромисс: больше padding -> меньше сигналов для traffic analysis, но выше сетевые накладные расходы. #### 8.1 Устройство ядра (для сопровождения) Схема **`I2PChatCore`**, **`SessionManager`** (состояние транспорта по пиру, политика исходящей отправки, потоки, reconnect) и связь со строкой статуса и маршрутом **Send** — в [**ARCHITECTURE.md**](ARCHITECTURE.md). Текущий релиз: [**releases/RELEASE_1.3.0.md**](releases/RELEASE_1.3.0.md). Рефакторинг транспорта (v1.2.6): [**releases/RELEASE_1.2.6.md**](releases/RELEASE_1.2.6.md). ### 9. Резюме GUI‑клиент I2PChat предоставляет: - наглядный чат с цветными «баблами»; - переключение тем `ligth`/`night` и единый кроссплатформенный стиль; - информативную строку статуса (Net/Link/Peer/Secure/ACKdrop); - удобную панель для управления профилями и подключением; - отправку файлов и изображений; - локальную зашифрованную историю чата с переключателем ON/OFF; - системные уведомления и мягкий звук при входящих сообщениях. Для повседневного использования достаточно: 1. Запустить приложение I2PChat (exe / AppImage / `.app`) с нужным профилем. 2. Вставить адрес пира и нажать `Connect`. 3. Общаться через поле ввода и кнопку `Send`. 4. При необходимости отправлять файлы/изображения и использовать «лок» профиля на постоянного собеседника.