- C# 96.3%
- Shell 2.6%
- PowerShell 1.1%
|
|
||
|---|---|---|
| .forgejo/workflows | ||
| build | ||
| docs | ||
| src | ||
| tests/CaviCodeVPN.Core.Tests | ||
| .gitignore | ||
| AGENTS.md | ||
| CaviCodeVPN.Desktop.sln | ||
| Changelog.md | ||
| global.json | ||
| NuGet.Config | ||
| README.md | ||
| SUBS.md | ||
CaviCode VPN Desktop
Десктопный GUI-клиент с поддержкой нескольких VPN-протоколов (Avalonia UI + .NET 8).
Поддерживаемые протоколы:
- TrustTunnel — маскировка трафика под HTTP/1.1, HTTP/2, QUIC (CLI-бинарник
trusttunnel_client) - AmneziaWG — обфусцированный WireGuard (CLI-бинарник
awg-quick)
Приложение — графическая оболочка над CLI-бинарниками:
управляет процессами через System.Diagnostics.Process, парсит stdout/stderr в реальном времени,
ведёт историю соединений и логи в SQLite. Архитектура многопротокольная —
см. docs/multi-protocol.md.
Поддерживаемые платформы: Windows 10+ (x64) · Linux x64/arm64 (Alt Linux, Ubuntu 20.04+, Debian 11+) · macOS 12+ (Universal — Apple Silicon + Intel).
Содержание
- Требования
- Структура репозитория
- Быстрый старт
- Сборка
- Запуск
- Функциональность
- Настройки и данные
- Архитектура
- Разработка
- Лицензия
Требования
- .NET 8 SDK (версия зафиксирована в global.json —
8.0.121,rollForward: latestFeature). - Git — для клонирования репозитория.
- TrustTunnel Client — не требуется для сборки, нужен только в рантайме:
- Linux:
/opt/trusttunnel/trusttunnel_client - Windows:
C:\Program Files\TrustTunnel\trusttunnel_client.exe - macOS:
/opt/trusttunnel/trusttunnel_client(Universal-бинарник) - Либо любой путь из
PATH, либо указанный вручную в Настройки → TrustTunnel. - Установить клиент можно прямо из GUI на вкладке Установщик (скачает релиз с GitHub).
- Linux:
На Linux дополнительно нужны GTK/X11-библиотеки (обычно уже стоят в десктоп-дистрибутиве) и polkit/pkexec для эскалации привилегий.
На macOS нужен Command Line Tools для Xcode (xcode-select --install) — для сборки Universal-пакета используются lipo, iconutil, sips. Эскалация привилегий идёт через osascript (модальный системный промт пароля администратора).
Структура репозитория
CaviCodeVPN-Desktop/
├── CaviCodeVPN.Desktop.sln # Solution
├── src/
│ ├── CaviCodeVPN.App/ # Avalonia UI, точка входа, ViewModels, Views
│ ├── CaviCodeVPN.Core/ # Модели, перечисления, интерфейсы (без зависимостей)
│ ├── CaviCodeVPN.Infrastructure/ # SQLite, Process IPC, HTTP, платформо-специфика
│ └── CaviCodeVPN.UI/ # Переиспользуемые Avalonia-компоненты
├── tests/ # Тестовые проекты
├── global.json # Пин версии .NET SDK
└── NuGet.Config # Источники пакетов (nuget.org)
CaviCodeVPN.Core
Чистая библиотека без внешних зависимостей.
| Путь | Содержимое |
|---|---|
Enums/ |
VpnStatus, NavigationItem, AppTheme, AppThemeMode, SessionStatus, LogLevel, RoutingAction, UpdatePolicy, … |
Interfaces/ |
ITrustTunnelService, IConnectionActivityRepository, ISessionRepository, ILogRepository, IConfigRepository, ISettingsService, IUpdateService, IPlatformService, … |
Models/ |
VpnState, VpnConfig, ConnectionActivity, ConnectionSession, LogEntry, AppSettings, RoutingRule, UpdateInfo, … |
CaviCodeVPN.Infrastructure
| Путь | Содержимое |
|---|---|
Services/TrustTunnelService.cs |
Управление процессом trusttunnel_client, парсинг логов, публикация VpnState через SimpleSubject<T> |
Services/InstallerService.cs |
Скачивание и установка бинарника с GitHub Releases |
Services/UpdateService.cs |
Проверка обновлений через Octokit |
Services/DeeplinkService.cs |
Декодирование tt://-URI, NamedPipe для single-instance |
Services/SettingsService.cs |
Чтение/запись settings.json |
Repositories/ |
ConnectionActivityRepository, SessionRepository, LogRepository, ConfigRepository, EventRepository — всё через Dapper + SQLite |
Database/DatabaseInitializer.cs |
Идемпотентная инициализация схемы, retention-очистка |
Platform/ |
WindowsPlatformService, LinuxPlatformService |
CaviCodeVPN.App
| Путь | Содержимое |
|---|---|
Program.cs |
Точка входа: эскалация прав, deeplink, DI-контейнер, Avalonia host |
App.axaml.cs |
Инициализация темы/языка, трей, диалог обновлений, определение запущенных туннелей |
Views/MainWindow.axaml |
Корневое окно: кастомный хром, навигация (два ListBox), resize-ручки |
ViewModels/MainWindowViewModel.cs |
Корневая VM: маршрутизация навигации, онбординг, туториал |
Views/ |
Dashboard, Configs, Logs, History, Diagnostics, Installer, Settings, About, Onboarding, Tutorial, Updater |
Services/ |
ThemeService, LocalizationService, TrayIconService, MessageBox, InMemoryLogSink |
Converters/ |
BytesToHumanReadableConverter, ConnectionActionToBrushConverter, VpnStatusToColorConverter, … |
Localization/ |
Strings.resx (RU, default), Strings.en.resx (EN) |
CaviCodeVPN.UI
Переиспользуемые UI-компоненты для экранов приложения.
| Путь | Содержимое |
|---|---|
Controls/SettingsSection.cs |
Общая рамка секции настроек с заголовком |
Controls/SettingsRow.cs |
Строка настроек: подпись слева, контрол справа |
Styles/SettingsControls.axaml |
Шаблоны и стили общих контролов |
Быстрый старт
git clone <repo-url>
cd CaviCodeVPN-Desktop
dotnet restore
dotnet run --project src/CaviCodeVPN.App/CaviCodeVPN.App.csproj
При первом запуске приложение создаёт рабочий каталог:
| ОС | Путь |
|---|---|
| Windows | %AppData%\CaviCodeVPN\ |
| Linux | ~/.config/CaviCodeVPN/ |
| macOS | ~/Library/Application Support/CaviCodeVPN/ |
Внутри: app.db, configs/, logs/, settings.json. Схема БД и retention выполняются автоматически.
Сборка
Debug
dotnet build CaviCodeVPN.Desktop.sln -c Debug
Release
dotnet build CaviCodeVPN.Desktop.sln -c Release
Self-contained (единый исполняемый файл)
Linux x64:
dotnet publish src/CaviCodeVPN.App/CaviCodeVPN.App.csproj -c Release -r linux-x64 \
--self-contained true -p:PublishSingleFile=true -o ./publish/linux-x64
Linux arm64: заменить -r linux-x64 на -r linux-arm64.
Windows x64:
dotnet publish src\CaviCodeVPN.App\CaviCodeVPN.App.csproj -c Release -r win-x64 ^
--self-contained true -p:PublishSingleFile=true -o .\publish\win-x64
Итоговый бинарник — publish/<rid>/CaviCodeVPN.App[.exe].
macOS Universal (Apple Silicon + Intel в одном .app):
./build/pack-macos.sh <version>
# напр.: ./build/pack-macos.sh 2.1.0
Скрипт публикует osx-arm64 и osx-x64 self-contained, сливает их через lipo
в один Mach-O universal-бинарь (apphost + нативные .dylib) и собирает
.app-bundle с Info.plist (включая CFBundleURLTypes для схем tt://,
awg://, cavinet://) и .icns. Результат —
build/out/osx-universal/CaviCodeVPN.app. Подпись/нотаризация не выполняется;
при первом запуске пользователю нужно разрешить запуск в System Settings →
Privacy & Security.
Запуск
Из исходников
dotnet run --project src/CaviCodeVPN.App/CaviCodeVPN.App.csproj
Из собранного бинарника
./publish/linux-x64/CaviCodeVPN.App # Linux
.\publish\win-x64\CaviCodeVPN.App.exe # Windows
open ./build/out/osx-universal/CaviCodeVPN.app # macOS
Права администратора
trusttunnel_client поднимает TUN-адаптер и меняет системный DNS — нужны права root/admin.
- Windows — GUI запускается без повышения прав (
asInvoker), чтобы настройки, профили и deeplink-и оставались в профиле вошедшего пользователя. Для обычной установки вC:\Program Files\TrustTunnel\Desktop ставитCaviCodeVPNHelperкак Windows service и общается с ним через NamedPipe: UAC нужен один раз при установке helper-а, а последующие Connect идут без повторного запроса. Старый elevated helper через UAC остаётся fallback для нестандартного путиtrusttunnel_client. - Linux — GUI работает от обычного пользователя (трей, DBus). При Connect бинарник запускается через
pkexec— polkit показывает графический промт. При Disconnect отправляет SIGTERM черезpkexec /bin/kill(непривилегированный процесс не может сигналить root-потомка). - macOS — GUI работает от обычного пользователя. При Connect запускается привилегированный helper через
osascript(системный модальный промт пароля администратора — один запрос за сессию). Helper держит Unix-domain сокет, GUI общается с ним по JSON-lines;trusttunnel_clientработает как root-потомок helper-а. При Disconnect команда уходит в helper через сокет, helper отправляет SIGTERM root-потомку. Разрыв сокета (закрытие GUI) приводит к завершению helper-а и остановке VPN.
Чтобы убрать промты совсем — выдать CAP_NET_ADMIN:
sudo setcap cap_net_admin,cap_net_raw=eip /opt/trusttunnel/trusttunnel_client
Защита от второго экземпляра
Desktop-клиент держит single-instance guard: на Windows через named mutex,
на Linux через FileStream.Lock на lock-файле в пользовательской директории,
на macOS через эксклюзивное открытие lock-файла (FileShare.None — FileStream.Lock недоступен на macOS). Если обычный
второй экземпляр уже запущен, новый процесс показывает ошибку запуска и
завершается до инициализации UI и SQLite.
Обнаружение запущенных туннелей
При старте приложение проверяет наличие уже запущенных процессов trusttunnel_client.
Если найдены — показывает диалог с предложением закрыть их, чтобы избежать конфликта сетевых интерфейсов.
Если после обычного отключения TrustTunnel/WinTun всё ещё активны, на Dashboard
есть аварийная команда «Убить VPN»: она останавливает текущую сессию и
принудительно завершает зависшие процессы trusttunnel_client (на Windows —
через CaviCodeVPNHelper, если helper service установлен, иначе через отдельный
elevated helper с UAC).
Пункт Выход в меню трея при активном подключении сначала поднимает окно и
показывает подтверждение: приложение сообщает, что VPN сейчас активен, и только
после согласия останавливает текущую сессию и закрывается.
Обнаружение локальных DPI-инструментов
При старте приложение проверяет процессы ByeDPI, Zapret, GoodbyeDPI и их типовые
компоненты (winws, nfqws, tpws, ciadpi). Если они запущены, Desktop
показывает предупреждение: такие инструменты могут вмешиваться в TLS/HTTP2, DNS
или маршруты и ломать TrustTunnel даже при исправном сервере.
Deeplink tt://
CaviCodeVPN.App --deeplink "tt://import?config=<base64_toml>"
CaviCodeVPN.App "tt://import?config=<base64_toml>" # короткая форма
Если инстанс уже запущен — URI доставляется в него через NamedPipe, второй процесс завершается без открытия второго окна.
На macOS схемы tt://, awg://, cavinet:// регистрируются в Info.plist
(CFBundleURLTypes) — ОС открывает .app и передаёт URI как аргумент.
IPC между экземплярами идёт через Unix-domain сокет с коротким именем ccvpn-dl
(macOS ограничивает путь Unix-сокета 104 байтами, а per-user $TMPDIR длинный).
Функциональность
Дашборд
Отображает текущее состояние VPN (Disconnected / Connecting / Connected / Error),
активный профиль, время подключения, скорость и суммарный трафик сессии.
Кнопки Connect / Disconnect управляют процессом trusttunnel_client.
Переключатель «Удерживать подключение» включён по умолчанию: при ошибке
или аварийном отключении от сервера Desktop будет пытаться переподключиться,
пока пользователь не выключит режим, не нажмёт Disconnect или «Убить VPN».
Пока VPN находится в Connecting или Connected, карточки профилей на Dashboard
не переключают активный профиль, удаление текущего активного профиля заблокировано,
а редактор этого профиля открывается только для просмотра настроек.
Перед запуском туннеля выполняется preflight-проверка: если не удаётся подключиться к Endpoint — отдельно различаются кейсы «нет интернета» и «Endpoint недоступен», с понятной ошибкой на Dashboard.
Стартовое сообщение WinTun о том, что адаптер по имени не найден перед созданием
нового адаптера, остаётся в логах, но не считается причиной ошибки подключения.
Для типовых ошибок TrustTunnel Dashboard показывает приоритетную причину:
сертификат endpoint, авторизация, некорректный профиль, WinTun/TUN и маршруты,
DNS, протокольный/marker mismatch, socket-коды Windows/Linux и только затем
ping/location timeout. При ошибках выбора endpoint приложение сохраняет короткий
хвост диагностических строк pinger/TLS/socket/TUN, даже когда debug-логи скрыты,
и добавляет его к финальной ошибке подключения, но менее точный timeout больше не
перекрывает найденную причину вроде WCRYPT_E_TRUST_STATUS.
В левой панели главного окна есть быстрый выбор исходящего интерфейса: значение
сохраняется в bound_if активного TUN-профиля. На Windows Desktop пишет ifIndex
интерфейса, который TrustTunnelClient умеет использовать напрямую; Авто
очищает сохранённое значение, но runtime-копия профиля всё равно выбирает
лучший физический Ethernet/Wi-Fi интерфейс и исключает VPN/виртуальные адаптеры
вроде Radmin VPN, Wintun, Tailscale, WireGuard, OpenVPN и Teredo.
Пока VPN находится в Connecting или Connected, выбор исходящего интерфейса
в левой панели заблокирован: bound_if применяется при следующем старте
TrustTunnel, поэтому менять его во время активной сессии нельзя.
При подготовке runtime-копии TUN-профиля Desktop резолвит адреса endpoint и
добавляет их как /32 или /128 в excluded_routes, чтобы соединение с самим
VPN-сервером не зацикливалось через только что поднятый TUN.
Runtime-копия также защищает full-tunnel от ошибочных GeoIP/ручных маршрутов:
для general режима гарантируется 0.0.0.0/0 в included_routes, а из
excluded_routes удаляются default-route записи вроде 0.0.0.0/0, которые
делают подключение успешным, но оставляют весь IPv4-трафик мимо VPN.
Если в профиле явно указано has_ipv6 = false, runtime-копия приводит
included_routes к IPv4-only, даже если старый TOML ещё содержит 2000::/3.
Конфигурации
Список профилей VPN в формате TOML (trusttunnel_client.toml).
Поддерживает создание, редактирование (встроенный текстовый редактор с подсветкой ошибок),
удаление и импорт через deeplink tt://.
Если редактор открыт для профиля, который сейчас используется активным подключением,
визуальные поля и TOML становятся read-only до отключения VPN.
Для TUN-профилей с разрешённым IPv6 full-tunnel включает IPv4 0.0.0.0/0 и
публичный IPv6 2000::/3; при has_ipv6 = false runtime-копия остаётся
IPv4-only. Локальные/private диапазоны остаются в excluded_routes.
Default-route записи в excluded_routes не применяются в runtime-копии: они
вычитают из TUN весь интернет-трафик и приводят к ситуации, когда VPN
подключён, но внешний IP остаётся адресом провайдера.
В редакторе профиля есть вкладка Фильтры: она редактирует клиентский routing,
правила доступа TrustTunnel ([[rule]] в TOML профиля), импорт geoIP/geoSite
категорий и показывает предпросмотр итогового TOML до сохранения.
Подписки
Вкладка Подписки хранит HTTP/HTTPS endpoint, логин, пароль и TLS Prefix,
делает POST-запрос и разбирает ответ как построчный список клиентских ссылок.
Desktop импортирует только поддержанные TrustTunnel-ссылки tt://. Строки
xray://, vless:// и другие неподдержанные форматы не передаются в парсер
профиля, не ломают синхронизацию и учитываются в статусе подписки как
«не поддержано». Битые tt:// строки считаются ошибками импорта, но не мешают
остальным профилям из той же подписки.
Фильтрация
Фильтрация настраивается внутри редактирования конкретного профиля, а не отдельным
разделом навигации. Управляет правилами маршрутизации (RoutingRule): какой трафик
пускать через туннель, обходить (bypass) или блокировать.
Блок Правила доступа TrustTunnel не меняет сервер из клиента: он сохраняет
[[rule]] в профиль, а TrustTunnel применяет эти правила при подключении, если
endpoint их поддерживает. Для обычных подписок этот блок можно оставить пустым.
В клиентском routing можно включить kill switch (killswitch_enabled): интернет-адреса
из included_routes блокируются, если идут мимо TUN. При сохранении Desktop
добавляет стандартные local/private CIDR в excluded_routes, чтобы локальная сеть
оставалась доступной напрямую.
Поиск в импорте geoIP/geoSite фильтрует список категорий в popup, а правая часть
popup показывает отмеченные категории перед добавлением в профиль.
Подключения (История)
Журнал соединений текущей и прошлых сессий из таблицы ConnectionActivity.
- Динамическая подгрузка: первые 200 записей грузятся сразу, следующие страницы — по мере прокрутки (каждые 15% от конца списка), максимум 1000 записей в памяти.
- Очистка при подключении: при каждом новом подключении список сбрасывается и показывает только текущую сессию.
- Авто-обновление: пока VPN активен — список обновляется каждые 5 секунд.
- Фильтры (работают на уровне SQL): поиск по домену/адресу, фильтр по протоколу (TCP/UDP) и действию (Tunnel / Bypass / Reject / Default / Destroyed).
- Сводка: суммарные счётчики соединений, отправленных и полученных байт.
- Экспорт в CSV.
Логи
Поток логов trusttunnel_client в реальном времени (через InMemoryLogSink Serilog).
Фильтрация по уровню и поиск по тексту. Экспорт в файл. Timestamp-строки
TrustTunnel формата dd.MM.yyyy HH:mm:ss.ffffff парсятся как локальное время,
чтобы экспорт не путал день и месяц.
Диагностика
Набор автоматических проверок: системные сетевые настройки Windows/Linux, доступность
бинарника, активный профиль, интернет без TrustTunnel, DNS/TCP/ping к endpoint, пробное
TrustTunnel-подключение, интернет через TrustTunnel, сравнение внешнего IP, MTU и IPv6.
В системном тесте диагностика также перечисляет найденные локальные DPI/anti-DPI
процессы (ByeDPI, Zapret, GoodbyeDPI, winws, nfqws, tpws, ciadpi)
и даёт рекомендацию временно отключить их перед повторной проверкой TrustTunnel.
При запуске диагностика создаёт отдельный каталог артефактов во временной директории,
пишет туда лог пробного подключения trusttunnel.log, а экспорт отчёта включает
результаты всех тестов и хвост этого лога. Если VPN уже был подключён до диагностики,
существующая сессия не перезапускается и не отключается; отчёт помечает прямые проверки
как потенциально прошедшие через текущий туннель.
HTTP-проверки прямого интернета и интернета через TT используют несколько независимых
HTTPS endpoint'ов, поэтому таймаут одного captive-portal URL не должен давать ложный
красный статус, если через туннель успешно открывается другой публичный HTTPS-сервис.
Тест «Интернет через TrustTunnel» добавляет в отчёт снимок route table после
подключения: на Windows это route print/netsh, на Linux — ip route/ip rule.
Если внешний IP не меняется, этот блок показывает, ушёл ли default IPv4 в Wintun/TUN
или остался на обычном интерфейсе.
При падении endpoint-pinger диагностика разбирает trusttunnel.log: если TCP к
endpoint установлен и TLS/TT handshake стартует, но затем приходит PING_TIMEDOUT,
отчёт указывает проверять серверные логи TT Gateway/tt-shared, а не DNS/TCP клиента.
Если в логе есть Unexpected protocol, диагностика трактует это как успешный TLS
до endpoint с ALPN не h2: чаще всего Gateway отправил marked ClientHello в
cover-ветку из-за no_marker_match, поэтому нужно сверить client_random профиля
с активным TLS Prefix в TT Gateway/Core и при смене prefix перегенерировать профиль.
После первого VPN_SS_CONNECTED тест пробного подключения держит короткое окно
стабильности: если клиент сразу получает HTTP/2 407 / Authorization Required
и уходит в VPN_SS_DISCONNECTED, отчёт помечает TT-подключение как отказ
авторизации на SOCKS5 Egress, а не как успешное подключение.
Даже когда пользовательские debug-логи TrustTunnel скрыты, диагностический лог
пропускает важные debug-строки pinger/socket/TUN: OS_TUNNEL, SO_BINDTODEVICE,
ip route/ip rule, Starting TLS handshake и таймауты endpoint. Это нужно,
чтобы отличать локальную маршрутизацию от server-side no_marker_match/Egress.
Основная кнопка запуска меняет режим по наличию диагностического токена: без
токена выполняется клиентская диагностика подключения, с токеном добавляется
серверный сбор через Core API. В поля нужно указать API URL
(http://net.cavicode.tech:8080 по умолчанию) и 24-часовой диагностический
токен из панели управления Core. Кнопка «Проверить API» отдельно вызывает
/api/diagnostics/validate и показывает, доступен ли Core API, принят ли токен
и не указывает ли URL на чужой сервис без diagnostics endpoint. Полный сценарий
выполняет эту проверку перед тяжёлым сбором логов: если она
не прошла, server-report не запрашивается. При запросе server-report Desktop
передаёт fromUtc как старт клиентского сценария минус 1 минуту; Core собирает
серверные логи и метрики только за это окно до момента запроса, чтобы отчёт не
выгружал всю историю сервера. Ответ /api/diagnostics/server-report сохраняется
как server-diagnostics.json рядом с trusttunnel.log; небольшие server JSON
включаются в TXT целиком, а большие отчёты попадают в TXT как превью с указанием
локального файла, чтобы экспорт и отправка в Core не превращались в сотни мегабайт.
В raw-output серверного теста Desktop отдельно выводит Gateway prelude-сводку:
сколько соединений пришло с preludeBytes=0, сколько оборвалось до TLS Random,
сколько уже содержало randomPrefixHex, и сравнивает это с клиентскими строками
Starting TLS handshake / PING_TIMEDOUT из trusttunnel.log.
Дополнительно Desktop разбирает containerLogs из server-report: если Gateway
runtime уже пишет tt upstream connected / tt splice finished, а Egress пишет
socks auth failed или network is unreachable для IPv6-destination, этот
runtime-сигнал имеет приоритет над ещё не успевшими batch connection logs из Core.
При IPv6 network is unreachable отчёт рекомендует отключить IPv6 при генерации
TrustTunnel-профилей или настроить настоящий IPv6 outbound для Egress.
Если Core вернул частичный server-report с errors[], Desktop показывает
Diagnostic section errors и помечает серверный тест предупреждением, но не
теряет остальные собранные серверные логи.
После завершения сценария Desktop автоматически отправляет итоговый TXT-отчёт в
POST /api/diagnostics/reports, поэтому администратор видит его в Core UI на
вкладке «Диагностика» без ручной пересылки файла пользователем. Если
server-diagnostics.json больше безопасного лимита загрузки Desktop, он не
вкладывается в поле serverReportJson, а в рекомендации отчёта добавляется путь
к полному локальному файлу.
Установщик
Проверяет GitHub Releases, скачивает и устанавливает свежую версию trusttunnel_client
(с прогресс-баром). Поддерживает Linux (tar.gz) и Windows (zip/exe).
На финальном шаге «Системная интеграция»:
- Windows устанавливает
CaviCodeVPNHelper, чтобы VPN-подключения не запрашивали UAC каждый раз. - Linux сохраняет прежнюю опцию systemd-сервиса
trusttunnel_clientдля активного профиля. - Общие переключатели включают автозапуск GUI при входе в систему и автоподключение активного профиля после старта приложения.
Настройки
| Категория | Параметры |
|---|---|
| Интерфейс | Тема (SleepyPrincess / DemonessEngineer), режим (Light / Dark / System), язык (RU / EN) |
| Поведение | Сворачивать в трей при закрытии, автозапуск приложения, автоподключение активного профиля, удерживать подключение при ошибках |
| Обновления | AutoInstall / NotifyOnly / Never |
| TrustTunnel | Путь к бинарнику trusttunnel_client |
Автозапуск GUI не требует прав администратора: Windows пишет значение в
HKCU\Software\Microsoft\Windows\CurrentVersion\Run, Linux создаёт
~/.config/autostart/cavicode-vpn.desktop по XDG Autostart. Тема и язык
применяются мгновенно (live-preview). Настройки сохраняются в settings.json.
Онбординг и туториал
При первом запуске — визард выбора темы и языка, обучающий тур по интерфейсу.
Системный трей
Иконка в трее с меню Connect/Disconnect и быстрым переключением профилей. Двойной клик — восстановить окно.
Обновления приложения
Фоновый воркер проверяет обновления при старте и раз в 24 часа.
При NotifyOnly показывает диалог с changelog (Markdown); при AutoInstall — устанавливает тихо.
Настройки и данные
| Файл / каталог | Назначение |
|---|---|
app.db |
SQLite: Sessions, LogEntries, AppEvents, ConnectionActivity, RoutingRules |
configs/*.toml |
Профили VPN |
configs/*.geodata.json |
Sidecar-выбор geoIP/geoSite категорий для профиля |
logs/app-YYYYMMDD.log |
Логи приложения (Serilog, rolling daily, хранятся 14 дней) |
settings.json |
Тема, язык, политика обновлений, путь к бинарнику, автозапуск, автоподключение, режим удержания подключения |
Retention по умолчанию: логи TrustTunnel — 30 дней, сессии и события — 90 дней.
Схема базы данных
Sessions -- VPN-сессии: время, профиль, статус, трафик
LogEntries -- Строки логов trusttunnel_client
AppEvents -- События приложения (старт, подключение, ошибки)
ConnectionActivity-- Соединения: домен, адрес, протокол, действие, байты
RoutingRules -- Правила доступа TrustTunnel (вкладка «Фильтры» редактора профиля)
Архитектура
Стек
| Слой | Технологии |
|---|---|
| UI | Avalonia 11.2, FluentTheme, DataGrid, Material.Icons |
| MVVM | CommunityToolkit.Mvvm 8.3 (source generators, [ObservableProperty], [RelayCommand]) |
| DI | Microsoft.Extensions.DependencyInjection 8 |
| БД | SQLite (Microsoft.Data.Sqlite) + Dapper |
| Логирование | Serilog 4 (File + InMemorySink) |
| Обновления | Octokit (GitHub API) |
| Конфиги | Tomlyn (TOML) |
| Архивы | SharpZipLib |
Поток данных VPN
trusttunnel_client (stdout/stderr)
↓ парсинг строк (TrustTunnelService)
├─→ SimpleSubject<VpnState> → DashboardVM, HistoryVM, TrayIconService
├─→ SimpleSubject<LogEntry> → LogsVM, DiagnosticsVM
└─→ ConnectionActivityRepository (SQLite) → HistoryVM
Навигация
Два ListBox в левой панели:
Основная: Dashboard · Logs · History Системная: Diagnostics · Settings · About
Оверлеи (ZIndex): OnboardingWizard (20) · TutorialView (30).
Темы
Две «именных» темы, каждая меняет:
- Акцентный градиент (
AccentGradientBrush) - Декоративные глифы фона (
ThemeBackgroundField)
| Тема | Описание |
|---|---|
DemonessEngineer |
Рыже-красный градиент |
SleepyPrincess |
Фиолетово-синий градиент |
Поверх — Light / Dark / System (Fluent).
Оконный хром
SystemDecorations="None" + собственные элементы:
- Drag-полоса (32 px сверху, ZIndex 25)
- 8 прозрачных Border-ручек ресайза (ZIndex 26): 4 угла + 4 стороны
- Кнопки min/max/close (ZIndex 27)
Разработка
Правила
- Вся бизнес-логика — во ViewModel, в
.axaml.csтолько платформенные обработчики. - Интерфейсы — в
CaviCodeVPN.Core, реализации — вCaviCodeVPN.Infrastructure. ViewModel зависит только от интерфейсов. - Все строки UI — через
Loc.Get("key"). Никаких хардкодных строк в XAML. - Доступ к SQLite — всегда через
await, не блокировать UI-поток. - Платформо-специфичный код — через
IPlatformService. - Compiled bindings (
AvaloniaUseCompiledBindingsByDefault=true): DataTemplate обязан иметьx:DataType, неDataType. - DataGrid требует явного
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml" />вApp.axaml— без него рендерится пустым.
Локализация
src/CaviCodeVPN.App/Localization/Strings.resx— RU (по умолчанию)src/CaviCodeVPN.App/Localization/Strings.en.resx— EN- Новый ключ — добавлять в оба файла.
- Во ViewModel строки-геттеры (
get => Loc.Get("key")) пересчитываются поLoc.LanguageChanged.
Добавление новой вкладки
- Добавить значение в
NavigationItem(Core/Enums). - Создать
XxxViewModel : ViewModelBaseсHeaderKey = "nav_xxx". - Создать
XxxView : UserControl+ зарегистрировать вViewLocator. - Добавить DI-регистрацию в
Program.BuildServiceProvider(). - Добавить
ListBoxItemвMainWindow.axamlи case вMainWindowViewModel.NavigateTo. - Добавить ключи
nav_xxxв оба.resx.
Релизы и автообновления
Пайплайн: Windows — Velopack (Setup.exe + per-machine .msi + delta-nupkg), Linux — системные пакеты .deb/.rpm + pkexec-установка. CI — Forgejo Actions, доставка — rsync на DEPLOY_HOST:22 в DEPLOY_PATH, раздача — cavicode.tech/apps/vpn.
Пайплайн одного релиза
- Поднять версию (передаётся аргументом в pack-скрипты).
git tag v1.2.3 && git push --tags— триггерит.forgejo/workflows/release.yml. Либо в Forgejo: Actions → release → Run workflow и указатьtag, напримерv1.2.3(тег должен уже существовать в репозитории).- CI параллельно собирает три таргета. Перед упаковкой каждый job готовит
build/generated-release-notes/release-notes.mdи каталогbuild/generated-release-notes/release-notes/: если естьbuild/release-notes/vX.Y.Z.mdилиbuild/release-notes/X.Y.Z.md, берёт его; иначе генерирует Markdown из commit subject'ов между предыдущим тегом и текущим. Вrelease-notes/index.txtпубликуется список версий, чтобы клиент мог показать изменения за все пропущенные релизы.- win-x64 — отдельный Windows runner:
dotnet publish→vpk pack --msi --instLocation PerMachine→Setup.exe+ per-machine.msi+*-full.nupkg+*-delta.nupkg. - linux-x64-deb — Docker runner: checkout копируется во вложенный
mcr.microsoft.com/dotnet/sdk:8.0-bookworm-slimчерезdocker cp→build/pack-linux-deb.sh(раскладка в/opt/cavicode-vpn/, wrapper в/usr/bin/cavicode-vpn,.desktopи иконка в системных путях). - linux-x64-rpm — Docker runner: тот же контейнерный подход через
docker cp→build/pack-linux-rpm.sh(rpmbuild spec prebuilt из того же publish-output).
- win-x64 — отдельный Windows runner:
- Deploy-job делает rsync в
win-x64/,linux-x64-deb/,linux-x64-rpm/и обновляет стабильные симлинки (cavicode-vpn_latest_amd64.deb,cavicode-vpn-latest.x86_64.rpm,CaviCodeVPN-Setup.exe,CaviCodeVPN-Setup.msi). - Клиент Windows —
UpdateService(Velopack-адаптер) читаетRELEASES-stable, качает дельту,ApplyUpdatesAndRestart. - Клиент Linux —
LinuxPackageUpdateServiceчитаетLATEST, качает нужный пакет (.debили.rpmпо/etc/os-release), запускает системный установщик черезpkexec(apt/apt-getдля.deb,apt-get/aptдля ALT Linux.rpm,dnf/yum/zypperдля остальных RPM-дистрибутивов), после успешной установки detached-helper черезsetsid sh -c "sleep 2; exec /usr/bin/cavicode-vpn"перезапускает приложение.
Инфраструктура на сервере
- Корень релизов на хосте задаётся секретом
DEPLOY_PATH(например,/mnt/data/apps-releases/vpn). - Артефакты:
${DEPLOY_PATH}/releases/stable/win-x64/—RELEASES-stable,*.nupkg,*-Setup.exe,*.msi,*-Portable.zip,release-notes.md,release-notes/+ алиасыCaviCodeVPN-Setup.exe/CaviCodeVPN-Setup.msi/CaviCodeVPN-Portable.zip; Markdown release notes встроены в Velopack-манифест.${DEPLOY_PATH}/releases/stable/linux-x64-deb/—*.deb,LATEST,release-notes.md,release-notes/, алиасcavicode-vpn_latest_amd64.deb.${DEPLOY_PATH}/releases/stable/linux-x64-rpm/—*.rpm,LATEST,release-notes.md,release-notes/, алиасcavicode-vpn-latest.x86_64.rpm.
- Nginx MIME/каш-политики для
.deb/.rpm— вCaviCode-Landing/deploy/nginx/cavicode-tech-vpn-desktop.conf.
CI Secrets (Forgejo → Repo Settings → Secrets)
| Имя | Назначение |
|---|---|
USER_PASSWORD |
SSH-пароль DEPLOY_USER для rsync через sshpass |
DEPLOY_HOST |
SSH-хост релизов без схемы и web-порта, например 192.168.1.40 |
DEPLOY_USER |
unix-пользователь с rw на DEPLOY_PATH, например ci-deploy |
DEPLOY_PATH |
корень релизов на сервере, например /mnt/data/apps-releases/vpn |
Локальная проверка упаковки
# Linux .deb (dpkg-deb встроен в Debian/Ubuntu/Mint)
./build/pack-linux-deb.sh 1.0.0
# Linux .rpm — требуется rpmbuild: sudo apt install rpm / sudo dnf install rpm-build
./build/pack-linux-rpm.sh 1.0.0
# Windows (Velopack)
pwsh ./build/pack-win.ps1 -Version 1.0.0
Для обычной установки Windows используйте CaviCodeVPN-Setup.msi: он ставит
приложение в Program Files\CaviCode\CaviCode VPN и запрашивает UAC до записи
файлов. CaviCodeVPN-Setup.exe остаётся Velopack one-click/per-user
вариантом для тестов и автообновлений.
Установка и удаление для пользователя (Linux)
# Debian / Ubuntu / Mint / Pop!_OS
sudo apt install ./cavicode-vpn_1.0.0_amd64.deb
sudo apt remove cavicode-vpn
# Fedora / RHEL / openSUSE
sudo dnf install ./cavicode-vpn-1.0.0-1.x86_64.rpm
sudo dnf remove cavicode-vpn
# ALT Linux
sudo apt-get install ./cavicode-vpn-1.0.0-1.x86_64.rpm
sudo apt-get remove cavicode-vpn
Пользовательские данные — настройки, логи, SQLite-БД — лежат в ~/.config/CaviCodeVPN/ и не удаляются пакетным менеджером. Для полной очистки: rm -rf ~/.config/CaviCodeVPN/.
Подпись
На старте — без Authenticode (self-signed вариант для Windows) и без GPG (для .deb/.rpm это означает, что apt install попросит --allow-untrusted или dpkg -i). Добавление — отдельная задача: Authenticode через vpk pack --signParams, GPG-подпись .deb/.rpm через dpkg-sig / rpm --addsign в CI после создания пакета.
Клиентские настройки
Секция Updates в appsettings.json (рядом с бинарником):
"Updates": {
"FeedBaseUrl": "https://cavicode.tech/apps/vpn/releases",
"Channel": "stable"
}
Итоговый URL фида:
- Windows:
{FeedBaseUrl}/{Channel}/win-x64/RELEASES-stable(читает Velopack). - Windows versioned notes:
{FeedBaseUrl}/{Channel}/win-x64/release-notes/index.txtи{FeedBaseUrl}/{Channel}/win-x64/release-notes/vX.Y.Z.md. - Linux (.deb-family):
{FeedBaseUrl}/{Channel}/linux-x64-deb/LATEST+{FeedBaseUrl}/{Channel}/linux-x64-deb/release-notes.md+{FeedBaseUrl}/{Channel}/linux-x64-deb/release-notes/index.txt. - Linux (.rpm-family):
{FeedBaseUrl}/{Channel}/linux-x64-rpm/LATEST+{FeedBaseUrl}/{Channel}/linux-x64-rpm/release-notes.md+{FeedBaseUrl}/{Channel}/linux-x64-rpm/release-notes/index.txt. - При старте приложение проверяет обновления в фоне; в Settings есть ручная кнопка проверки, которая открывает тот же диалог обновления.
Release notes
Описание обновления — обычный Markdown:
- Для красивого пользовательского текста добавьте файл
build/release-notes/vX.Y.Z.mdперед пушем тегаvX.Y.Z. - Если файла нет, CI сгенерирует
build/generated-release-notes/release-notes/vX.Y.Z.mdиз коммитов между предыдущим semver-тегом и текущим тегом. release-notes.mdостаётся совместимой сводкой для старых клиентов и встраивается в Windows черезvpk pack --releaseNotes.- Новые Windows/Linux клиенты сначала читают
release-notes/index.txt, выбирают версии> currentи<= targetи склеивают соответствующиеvX.Y.Z.md.
Лицензия
MIT