No description
  • C# 96.3%
  • Shell 2.6%
  • PowerShell 1.1%
Find a file
2026-07-04 00:29:28 +03:00
.forgejo/workflows CI: alpine-deploy → deploy (новое имя образа) 2026-06-23 21:19:56 +03:00
build Merge pull request 'Обновление билда под macOS, создание .dmg, исправление бага с brew dotnet@8' (#5) from iostream-changes-macOS into main 2026-07-04 00:29:28 +03:00
docs Amnezia, Мультипротокольность, Мелкие фиксы 2026-06-29 01:51:15 +03:00
src Ну чё, подписьки и поддержка macOS 2026-07-03 23:57:42 +03:00
tests/CaviCodeVPN.Core.Tests
.gitignore
AGENTS.md
CaviCodeVPN.Desktop.sln Amnezia, Мультипротокольность, Мелкие фиксы 2026-06-29 01:51:15 +03:00
Changelog.md Обновление билда под macOS, создание .dmg, исправление бага с brew dotnet@8 2026-07-04 00:22:12 +03:00
global.json
NuGet.Config
README.md Ну чё, подписьки и поддержка macOS 2026-07-03 23:57:42 +03:00
SUBS.md Ну чё, подписьки и поддержка macOS 2026-07-03 23:57:42 +03:00

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.json8.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 дополнительно нужны 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.NoneFileStream.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 даже при исправном сервере.

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)

Разработка

Правила

  1. Вся бизнес-логика — во ViewModel, в .axaml.cs только платформенные обработчики.
  2. Интерфейсы — в CaviCodeVPN.Core, реализации — в CaviCodeVPN.Infrastructure. ViewModel зависит только от интерфейсов.
  3. Все строки UI — через Loc.Get("key"). Никаких хардкодных строк в XAML.
  4. Доступ к SQLite — всегда через await, не блокировать UI-поток.
  5. Платформо-специфичный код — через IPlatformService.
  6. Compiled bindings (AvaloniaUseCompiledBindingsByDefault=true): DataTemplate обязан иметь x:DataType, не DataType.
  7. 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.

Добавление новой вкладки

  1. Добавить значение в NavigationItem (Core/Enums).
  2. Создать XxxViewModel : ViewModelBase с HeaderKey = "nav_xxx".
  3. Создать XxxView : UserControl + зарегистрировать в ViewLocator.
  4. Добавить DI-регистрацию в Program.BuildServiceProvider().
  5. Добавить ListBoxItem в MainWindow.axaml и case в MainWindowViewModel.NavigateTo.
  6. Добавить ключи 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.

Пайплайн одного релиза

  1. Поднять версию (передаётся аргументом в pack-скрипты).
  2. git tag v1.2.3 && git push --tags — триггерит .forgejo/workflows/release.yml. Либо в Forgejo: Actions → release → Run workflow и указать tag, например v1.2.3 (тег должен уже существовать в репозитории).
  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 publishvpk pack --msi --instLocation PerMachineSetup.exe + per-machine .msi + *-full.nupkg + *-delta.nupkg.
    • linux-x64-deb — Docker runner: checkout копируется во вложенный mcr.microsoft.com/dotnet/sdk:8.0-bookworm-slim через docker cpbuild/pack-linux-deb.sh (раскладка в /opt/cavicode-vpn/, wrapper в /usr/bin/cavicode-vpn, .desktop и иконка в системных путях).
    • linux-x64-rpm — Docker runner: тот же контейнерный подход через docker cpbuild/pack-linux-rpm.sh (rpmbuild spec prebuilt из того же publish-output).
  4. 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).
  5. Клиент Windows — UpdateService (Velopack-адаптер) читает RELEASES-stable, качает дельту, ApplyUpdatesAndRestart.
  6. Клиент 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