10 Commits
main ... GUI

53 changed files with 1773 additions and 50566 deletions

7
.gitignore vendored
View File

@@ -5,12 +5,6 @@ vpn-client.exe
*.dll *.dll
*.so *.so
*.dylib *.dylib
*.bat
*.sh
init.bat
init.sh
build.bat
build.sh
# Test binary # Test binary
*.test *.test
@@ -20,6 +14,7 @@ build.sh
# Dependency directories # Dependency directories
vendor/ vendor/
xray/
# Go workspace file # Go workspace file
go.work go.work

View File

@@ -1,136 +0,0 @@
# Руководство по созданию установщика VPN Client Go
## Требования
1. **Inno Setup 7** - скачайте с официального сайта: https://jrsoftware.org/isdl.php
2. Собранный файл `vpn-client-cli.exe`
## Шаги для создания установщика
### 1. Установите Inno Setup 7
Скачайте и установите Inno Setup 7 с официального сайта.
### 2. Подготовьте файлы
Убедитесь, что в папке `vpn_client_go` есть:
- `vpn-client-cli.exe` (собранный исполняемый файл)
- `installer.iss` (скрипт установщика)
- `LICENSE.txt` (лицензия)
- `.vpn_client/` (папка конфигурации - создастся автоматически)
Опционально (будут включены, если существуют):
- `xray/` (папка с Xray - можно не включать, загрузится автоматически)
- `v2ray/` (папка с V2Ray - можно не включать, загрузится автоматически)
- `README.md` (документация)
- `CHANGELOG.md` (список изменений)
### 3. Настройте installer.iss
Откройте `installer.iss` и измените:
```pascal
#define MyAppName "VPN Client Go"
#define MyAppVersion "1.0.0" ; Ваша версия
#define MyAppPublisher "Your Name" ; Ваше имя
#define MyAppURL "https://github.com/yourusername/vpn-client-go" ; Ваш URL
```
Опционально добавьте иконку:
```pascal
SetupIconFile=icon.ico ; Раскомментируйте и укажите путь к .ico файлу
```
### 4. Создайте установщик
#### Способ 1: Через GUI
1. Запустите Inno Setup Compiler
2. Откройте файл `installer.iss`
3. Нажмите `Build``Compile` (или F9)
4. Установщик будет создан в папке `installer_output/`
#### Способ 2: Через командную строку
```cmd
"C:\Program Files (x86)\Inno Setup 7\ISCC.exe" installer.iss
```
### 5. Результат
После компиляции в папке `installer_output/` появится файл:
```
VPNClientGo-Setup-1.0.0.exe
```
## Особенности установщика
**Автоматические права администратора** - установщик запрашивает права админа
**Поддержка русского и английского языков**
**Создание ярлыков** на рабочем столе и в меню Пуск
**Автоматическая загрузка Xray/V2Ray** при первом запуске
**Чистая деинсталляция** - удаляет все файлы и папки
## Что делает установщик
1. Устанавливает `vpn-client-cli.exe` в `C:\Program Files\VPN Client Go\`
2. Создает папки для конфигурации и логов
3. Копирует Xray и V2Ray (если они есть в исходниках)
4. Создает ярлыки с правами администратора
5. Регистрирует программу в списке установленных приложений
## Запуск после установки
Программа автоматически запускается с правами администратора через ярлыки.
## Деинсталляция
Используйте стандартную деинсталляцию Windows:
- Панель управления → Программы и компоненты
- Или через ярлык "Удалить VPN Client Go" в меню Пуск
## Дополнительные настройки
### Добавить иконку приложения
1. Создайте файл `icon.ico` (256x256 или 128x128)
2. Поместите его в папку `vpn_client_go/`
3. Раскомментируйте строку в `installer.iss`:
```pascal
SetupIconFile=icon.ico
```
### Изменить папку установки по умолчанию
В `installer.iss` измените:
```pascal
DefaultDirName={autopf}\{#MyAppName}
```
На например:
```pascal
DefaultDirName=C:\VPNClient
```
### Добавить дополнительные файлы
В секцию `[Files]` добавьте:
```pascal
Source: "your_file.txt"; DestDir: "{app}"; Flags: ignoreversion
```
## Автоматизация сборки
Создайте bat-файл `build_installer.bat`:
```batch
@echo off
echo Building VPN Client...
call build_cli_only.bat
echo Creating installer...
"C:\Program Files (x86)\Inno Setup 7\ISCC.exe" installer.iss
echo Done!
pause
```
Теперь можно собрать всё одной командой!

View File

@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2026 VPN Client Go
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,36 +0,0 @@
# Настройка запуска с правами администратора
## Автоматическая сборка с правами администратора
Скрипт `build_cli_only.bat` теперь автоматически встраивает манифест, требующий права администратора.
### Шаги для сборки:
1. Запустите `build_cli_only.bat`
2. Скрипт автоматически:
- Установит инструмент `rsrc` (если не установлен)
- Встроит манифест администратора в исполняемый файл
- Соберет `vpn-client-cli.exe`
### Запуск приложения:
После сборки `vpn-client-cli.exe` будет автоматически запрашивать права администратора при запуске.
- При двойном клике появится UAC запрос
- Из командной строки также появится UAC запрос
- Без прав администратора приложение не запустится
## Файлы манифеста:
- `admin.manifest` - XML манифест с требованием прав администратора
- `rsrc.syso` - временный файл (автоматически удаляется после сборки)
## Проверка прав администратора в коде (опционально):
Если нужно проверить права администратора в коде Go, можно добавить проверку в `main_cli.go`.
## Примечания:
- Приложение будет работать только с правами администратора
- UAC запрос появится при каждом запуске
- Это необходимо для работы с VPN интерфейсами и сетевыми настройками

View File

@@ -1,392 +0,0 @@
# Инструкции по сборке VPN Client GUI
## Требования
### Общие
- Go 1.21 или выше
- Git (для клонирования репозитория)
- Интернет соединение (для загрузки зависимостей)
### Windows
- Windows 10 или выше
- MinGW-w64 или TDM-GCC (для компиляции C кода Fyne)
- Или используйте готовые бинарники
### Linux
```bash
# Ubuntu/Debian
sudo apt-get install gcc libgl1-mesa-dev xorg-dev
# Fedora
sudo dnf install gcc mesa-libGL-devel libXcursor-devel libXrandr-devel libXinerama-devel libXi-devel libXxf86vm-devel
# Arch
sudo pacman -S go gcc libxcursor libxrandr libxinerama libxi
```
### macOS
```bash
# Установите Xcode Command Line Tools
xcode-select --install
```
## Быстрая сборка
### Windows
```bash
# 1. Инициализация (первый раз)
init_gui.bat
# 2. Сборка
build_gui.bat
# Результат: vpn-client-gui.exe
```
### Linux/macOS
```bash
# 1. Сделайте скрипты исполняемыми
chmod +x init_gui.sh build_gui.sh
# 2. Инициализация (первый раз)
./init_gui.sh
# 3. Сборка
./build_gui.sh
# Результат: vpn-client-gui
```
## Ручная сборка
### Шаг 1: Установка зависимостей
```bash
cd vpn_client_go
go mod download
go mod tidy
```
### Шаг 2: Сборка
**Windows (с GUI):**
```bash
go build -ldflags="-s -w -H windowsgui" -o vpn-client-gui.exe main.go
```
**Windows (с консолью для отладки):**
```bash
go build -ldflags="-s -w" -o vpn-client-gui.exe main.go
```
**Linux:**
```bash
go build -ldflags="-s -w" -o vpn-client-gui main.go
```
**macOS:**
```bash
go build -ldflags="-s -w" -o vpn-client-gui main.go
# Для создания .app bundle
mkdir -p VPNClient.app/Contents/MacOS
cp vpn-client-gui VPNClient.app/Contents/MacOS/
```
## Оптимизация размера
### Минимальный размер
```bash
# Используйте UPX для сжатия (опционально)
# Скачайте UPX: https://upx.github.io/
# Windows
upx --best --lzma vpn-client-gui.exe
# Linux/macOS
upx --best --lzma vpn-client-gui
```
### Результаты сжатия
- Без UPX: ~25-30 MB
- С UPX: ~10-12 MB
## Кросс-компиляция
### Из Linux в Windows
```bash
# Установите MinGW
sudo apt-get install gcc-mingw-w64
# Сборка
CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc GOOS=windows GOARCH=amd64 \
go build -ldflags="-s -w -H windowsgui" -o vpn-client-gui.exe main.go
```
### Из macOS в Linux
```bash
# Установите кросс-компилятор
brew install FiloSottile/musl-cross/musl-cross
# Сборка
CGO_ENABLED=1 CC=x86_64-linux-musl-gcc GOOS=linux GOARCH=amd64 \
go build -ldflags="-s -w" -o vpn-client-gui main.go
```
## Сборка только CLI
Если вам нужна только CLI версия без GUI:
```bash
# Используйте старые скрипты
# Windows
build.bat
# Linux/macOS
./build.sh
```
Или вручную:
```bash
go build -ldflags="-s -w" -tags cli -o vpn-client main.go
```
## Проверка сборки
### Тест запуска
```bash
# GUI режим
./vpn-client-gui
# CLI режим
./vpn-client-gui --cli
```
### Проверка зависимостей
```bash
# Список зависимостей
go list -m all
# Проверка на уязвимости
go list -json -m all | nancy sleuth
```
### Проверка размера
```bash
# Windows
dir vpn-client-gui.exe
# Linux/macOS
ls -lh vpn-client-gui
```
## Отладка проблем сборки
### Ошибка: "gcc not found"
**Windows:**
- Установите MinGW-w64: https://www.mingw-w64.org/
- Или TDM-GCC: https://jmeubank.github.io/tdm-gcc/
- Добавьте в PATH
**Linux:**
```bash
sudo apt-get install build-essential
```
**macOS:**
```bash
xcode-select --install
```
### Ошибка: "package fyne.io/fyne/v2 not found"
```bash
go clean -modcache
go mod download
go mod tidy
```
### Ошибка: "undefined reference to..."
Это проблема с CGO. Убедитесь, что:
1. Установлен GCC
2. CGO_ENABLED=1 (по умолчанию)
3. Установлены системные библиотеки (см. Требования)
### Ошибка при запуске: "cannot open display"
Это означает, что нет графической среды. Используйте CLI режим:
```bash
./vpn-client-gui --cli
```
## Создание установщика
### Windows (NSIS)
1. Установите NSIS: https://nsis.sourceforge.io/
2. Создайте installer.nsi:
```nsis
!define APP_NAME "VPN Client"
!define APP_VERSION "2.0.0"
Name "${APP_NAME}"
OutFile "vpn-client-installer.exe"
InstallDir "$PROGRAMFILES\${APP_NAME}"
Section "Install"
SetOutPath $INSTDIR
File "vpn-client-gui.exe"
File /r "xray"
CreateShortcut "$DESKTOP\${APP_NAME}.lnk" "$INSTDIR\vpn-client-gui.exe"
SectionEnd
```
3. Соберите:
```bash
makensis installer.nsi
```
### Linux (DEB пакет)
```bash
# Создайте структуру
mkdir -p vpn-client_2.0.0/DEBIAN
mkdir -p vpn-client_2.0.0/usr/bin
mkdir -p vpn-client_2.0.0/usr/share/applications
# Скопируйте файлы
cp vpn-client-gui vpn-client_2.0.0/usr/bin/
# Создайте control файл
cat > vpn-client_2.0.0/DEBIAN/control << EOF
Package: vpn-client
Version: 2.0.0
Architecture: amd64
Maintainer: Your Name <your@email.com>
Description: VPN Client with GUI
EOF
# Соберите пакет
dpkg-deb --build vpn-client_2.0.0
```
### macOS (DMG)
```bash
# Создайте .app bundle
mkdir -p VPNClient.app/Contents/MacOS
mkdir -p VPNClient.app/Contents/Resources
cp vpn-client-gui VPNClient.app/Contents/MacOS/
# Создайте Info.plist
cat > VPNClient.app/Contents/Info.plist << EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleExecutable</key>
<string>vpn-client-gui</string>
<key>CFBundleName</key>
<string>VPN Client</string>
<key>CFBundleVersion</key>
<string>2.0.0</string>
</dict>
</plist>
EOF
# Создайте DMG
hdiutil create -volname "VPN Client" -srcfolder VPNClient.app -ov -format UDZO vpn-client.dmg
```
## CI/CD
### GitHub Actions
Создайте `.github/workflows/build.yml`:
```yaml
name: Build
on: [push, pull_request]
jobs:
build:
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.21'
- name: Install dependencies (Linux)
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y gcc libgl1-mesa-dev xorg-dev
- name: Build
run: |
cd vpn_client_go
go mod download
go build -ldflags="-s -w" -o vpn-client-gui main.go
- name: Upload artifact
uses: actions/upload-artifact@v3
with:
name: vpn-client-${{ runner.os }}
path: vpn_client_go/vpn-client-gui*
```
## Производительность
### Время сборки
- Первая сборка: 2-5 минут (загрузка зависимостей)
- Последующие: 30-60 секунд
### Размер бинарника
- Windows: ~28 MB (без UPX), ~11 MB (с UPX)
- Linux: ~26 MB (без UPX), ~10 MB (с UPX)
- macOS: ~27 MB (без UPX), ~11 MB (с UPX)
### Потребление ресурсов
- RAM: 50-100 MB (GUI), 20-30 MB (CLI)
- CPU: <1% в режиме ожидания
- Диск: 100 MB (с зависимостями)
## Лицензирование
При распространении убедитесь, что включены:
- LICENSE файл
- Зависимости Xray (папка xray/)
- Документация (README.md, GUI_GUIDE.md)
## Поддержка
При проблемах со сборкой:
1. Проверьте версию Go: `go version`
2. Очистите кеш: `go clean -modcache`
3. Обновите зависимости: `go mod tidy`
4. Проверьте логи сборки
5. Убедитесь в наличии всех системных библиотек
---
**Успешной сборки! 🚀**

View File

@@ -1,185 +0,0 @@
# Changelog - GUI Version
## [2.0.0] - 2026-04-05
### Добавлено
- ✨ Современный графический интерфейс (GUI) на базе Fyne
- 🎨 Material Design стиль интерфейса
- 📊 Панель статуса в реальном времени с индикатором подключения
- ⏱️ Отображение времени подключения
- 📈 Статистика трафика для WireGuard (прием/передача)
- 🔄 Автоматическое обновление статуса каждые 2 секунды
- 🎯 Три вкладки: WireGuard, VLESS, Подписки
- 🧪 Тестирование серверов с визуальным прогрессом
- 📋 Просмотр конфигураций из подписок
- 🔍 Массовое тестирование серверов из подписки
- 💾 Добавление WireGuard конфигов из файла
- 🎨 Кастомная цветовая тема
- ⚡ Асинхронные операции с индикаторами прогресса
- 🔔 Диалоговые окна для подтверждения действий
- 📱 Адаптивный интерфейс
### Изменено
- 🔧 main.go теперь поддерживает два режима: GUI (по умолчанию) и CLI (--cli флаг)
- 📦 Обновлены зависимости в go.mod (добавлен Fyne v2.4.5)
- 📝 Обновлен README.md с инструкциями по GUI
- 🛠️ Новые скрипты сборки: build_gui.bat, build_gui.sh
- 🔧 Новые скрипты инициализации: init_gui.bat, init_gui.sh
### Сохранено
- ✅ Весь функционал CLI версии
- ✅ Поддержка WireGuard
- ✅ Поддержка VLESS через Xray
- ✅ Управление подписками
- ✅ Тестирование серверов
- ✅ Детальное логирование
- ✅ Кроссплатформенность
### Технические детали
#### Новые файлы
- `internal/gui/gui.go` - основной модуль GUI (1000+ строк)
- `build_gui.bat` - скрипт сборки для Windows
- `build_gui.sh` - скрипт сборки для Linux/macOS
- `init_gui.bat` - инициализация зависимостей для Windows
- `init_gui.sh` - инициализация зависимостей для Linux/macOS
- `GUI_GUIDE.md` - подробное руководство пользователя GUI
#### Архитектура GUI
**Структура интерфейса:**
```
MainWindow
├── Header (Title + Status)
│ ├── StatusIcon (Circle indicator)
│ ├── StatusLabel
│ └── ConnectionInfo
├── Tabs
│ ├── WireGuard Tab
│ │ ├── Config List
│ │ └── Action Buttons
│ ├── VLESS Tab
│ │ ├── Config List
│ │ └── Action Buttons
│ └── Subscriptions Tab
│ ├── Subscription List
│ └── Action Buttons
└── Footer
└── Disconnect Button
```
**Основные компоненты:**
1. **GUI struct**
- app: fyne.App
- window: fyne.Window
- statusLabel: динамический статус
- statusIcon: индикатор подключения
- connectionInfo: детальная информация
- statusTimer: таймер обновления
2. **Функции WireGuard**
- connectWireGuard()
- addWireGuardManual()
- addWireGuardFromFile()
- deleteWireGuard()
3. **Функции VLESS**
- connectVLESS()
- addVLESS()
- testVLESS()
- deleteVLESS()
4. **Функции подписок**
- addSubscription()
- updateSubscription()
- showSubscriptionConfigs()
- testSubscriptionConfigs()
- deleteSubscription()
5. **Утилиты**
- updateStatus() - обновление статуса
- disconnect() - отключение VPN
- makeCard() - создание карточек UI
- customTheme - кастомная тема
#### Особенности реализации
**Асинхронность:**
- Все длительные операции выполняются в горутинах
- Используются dialog.NewProgressInfinite для индикации
- UI остается отзывчивым во время операций
**Обновление статуса:**
- Автоматическое обновление каждые 2 секунды
- Изменение цвета индикатора (серый/зеленый)
- Отображение времени подключения в формате HH:MM:SS
- Статистика трафика для WireGuard
**Тестирование серверов:**
- Визуальный прогресс-бар
- Отображение текущего тестируемого сервера
- Сортировка результатов по пингу
- Топ-10 лучших серверов
**Диалоги:**
- Подтверждение удаления
- Формы ввода данных
- Информационные сообщения
- Ошибки с детальным описанием
#### Зависимости
**Новые:**
- fyne.io/fyne/v2 v2.4.5 - GUI фреймворк
- Множество транзитивных зависимостей Fyne
**Существующие:**
- github.com/fatih/color v1.16.0 - для CLI
- golang.org/x/sys v0.16.0 - системные вызовы
#### Сборка
**Windows:**
```bash
go build -ldflags="-s -w -H windowsgui" -o vpn-client-gui.exe main.go
```
- `-s -w` - уменьшение размера
- `-H windowsgui` - скрытие консоли
**Linux/macOS:**
```bash
go build -ldflags="-s -w" -o vpn-client-gui main.go
```
#### Размер приложения
- Исполняемый файл: ~25-30 MB (с GUI)
- CLI версия: ~10-15 MB
- Потребление RAM: 50-100 MB (GUI), 20-30 MB (CLI)
### Совместимость
- ✅ Windows 10/11
- ✅ Linux (Ubuntu, Debian, Fedora, Arch и др.)
- ✅ macOS 10.13+
- ✅ Обратная совместимость с CLI режимом
### Известные ограничения
- GUI требует графическую среду (X11/Wayland/Windows)
- Для серверов рекомендуется использовать CLI режим
- WireGuard на Windows требует установки официального клиента
### Планы на будущее
- [ ] Темная тема
- [ ] Системный трей
- [ ] Автозапуск при старте системы
- [ ] Экспорт/импорт конфигураций
- [ ] Графики статистики трафика
- [ ] Уведомления о событиях
- [ ] Мультиязычность
- [ ] Автоматическая настройка системного прокси
- [ ] Профили подключений
- [ ] История подключений

View File

@@ -1,290 +0,0 @@
# GUI Руководство пользователя
## Установка и запуск
### Windows
1. Инициализация зависимостей:
```bash
init_gui.bat
```
2. Сборка приложения:
```bash
build_gui.bat
```
3. Запуск:
```bash
vpn-client-gui.exe
```
### Linux/macOS
1. Инициализация зависимостей:
```bash
chmod +x init_gui.sh
./init_gui.sh
```
2. Сборка приложения:
```bash
chmod +x build_gui.sh
./build_gui.sh
```
3. Запуск:
```bash
./vpn-client-gui
```
## Интерфейс приложения
### Главное окно
Приложение имеет современный интерфейс с тремя основными вкладками:
1. **WireGuard** - управление WireGuard конфигурациями
2. **VLESS** - управление VLESS конфигурациями
3. **Подписки** - управление подписками на серверы
### Панель статуса
В верхней части окна отображается:
- **Индикатор подключения** (серый/зеленый кружок)
- **Статус**: "Не подключено" или "Подключено"
- **Информация о подключении**:
- Имя конфигурации
- Тип протокола
- Время подключения
- Прокси адрес (для VLESS)
- Статистика трафика (для WireGuard)
## Работа с WireGuard
### Добавление конфигурации
**Вручную:**
1. Перейдите на вкладку "WireGuard"
2. Нажмите "Добавить вручную"
3. Введите имя конфигурации
4. Вставьте текст конфигурации WireGuard
5. Нажмите "Добавить"
**Из файла:**
1. Нажмите "Из файла"
2. Выберите файл конфигурации (.conf)
3. Введите имя конфигурации
4. Нажмите "Добавить"
### Подключение
1. Выберите конфигурацию из списка
2. Нажмите "Подключиться"
3. Дождитесь подтверждения подключения
### Удаление конфигурации
1. Выберите конфигурацию из списка
2. Нажмите "Удалить"
3. Подтвердите удаление
## Работа с VLESS
### Добавление конфигурации
1. Перейдите на вкладку "VLESS"
2. Нажмите "Добавить"
3. Введите имя конфигурации
4. Вставьте VLESS URL (начинается с `vless://`)
5. Нажмите "Добавить"
### Тестирование сервера
1. Выберите конфигурацию из списка
2. Нажмите "Тест (Ping)"
3. Дождитесь результатов:
- Пинг в миллисекундах
- Оценка качества (Отлично/Хорошо/Средне/Плохо)
### Подключение
1. Выберите конфигурацию из списка
2. Нажмите "Подключиться"
3. После подключения используйте SOCKS5 прокси: `127.0.0.1:10808`
### Удаление конфигурации
1. Выберите конфигурацию из списка
2. Нажмите "Удалить"
3. Подтвердите удаление
## Работа с подписками
### Добавление подписки
1. Перейдите на вкладку "Подписки"
2. Нажмите "Добавить"
3. Введите имя подписки
4. Введите URL подписки
5. Нажмите "Добавить"
### Обновление конфигураций
1. Выберите подписку из списка
2. Нажмите "Обновить конфиги"
3. Дождитесь загрузки конфигураций
4. Конфиги появятся на вкладке "VLESS" с префиксом `[Имя подписки]`
### Просмотр конфигураций
1. Выберите подписку из списка
2. Нажмите "Показать конфиги"
3. Откроется окно со списком всех конфигов из подписки
### Тестирование серверов
1. Выберите подписку из списка
2. Нажмите "Тест серверов"
3. Дождитесь завершения тестирования всех серверов
4. Откроется окно с результатами:
- Топ-10 лучших серверов по пингу
- Количество доступных/недоступных серверов
### Удаление подписки
1. Выберите подписку из списка
2. Нажмите "Удалить"
3. Подтвердите удаление
## Отключение от VPN
1. Нажмите кнопку "Отключиться" внизу окна
2. Подтвердите отключение
3. Статус изменится на "Не подключено"
## Настройка браузера
После подключения к VLESS серверу настройте браузер:
### Firefox
1. Откройте: `about:preferences#general`
2. Прокрутите до "Параметры сети"
3. Нажмите "Настроить..."
4. Выберите "Ручная настройка прокси"
5. SOCKS Host: `127.0.0.1`
6. Port: `10808`
7. Выберите "SOCKS v5"
8. Отметьте "Использовать прокси DNS при использовании SOCKS v5"
9. Нажмите "OK"
### Chrome (с расширением Proxy SwitchyOmega)
1. Установите расширение "Proxy SwitchyOmega"
2. Создайте новый профиль
3. Protocol: SOCKS5
4. Server: `127.0.0.1`
5. Port: `10808`
6. Сохраните и активируйте профиль
## CLI режим
Для использования в консольном режиме (без GUI):
```bash
# Windows
vpn-client-gui.exe --cli
# Linux/macOS
./vpn-client-gui --cli
```
## Логи
Все логи сохраняются в папке `logs/`:
- `vless.log` - основной лог VLESS
- `vless_access.log` - лог доступа (IP адреса)
- `vless_error.log` - лог ошибок
- `vless_traffic_*.log` - логи трафика для каждого подключения
- `wireguard.log` - лог WireGuard
- `subscription.log` - лог подписок
## Горячие клавиши
- `Ctrl+Q` - Выход из приложения
- `F5` - Обновить списки
## Устранение неполадок
### Приложение не запускается
1. Убедитесь, что установлены все зависимости:
```bash
go mod tidy
```
2. Пересоберите приложение:
```bash
# Windows
build_gui.bat
# Linux/macOS
./build_gui.sh
```
### Не удается подключиться к VLESS
1. Проверьте правильность VLESS URL
2. Используйте "Тест (Ping)" для проверки доступности сервера
3. Проверьте логи в папке `logs/`
4. Убедитесь, что Xray находится в папке `xray/`
### Не удается подключиться к WireGuard
**Windows:**
- Убедитесь, что WireGuard установлен: https://www.wireguard.com/install/
- Запустите приложение от имени администратора
**Linux:**
```bash
sudo apt install wireguard
# или
sudo yum install wireguard-tools
```
**macOS:**
```bash
brew install wireguard-tools
```
### Прокси не работает
1. Убедитесь, что VPN подключен (зеленый индикатор)
2. Проверьте настройки прокси в браузере: `127.0.0.1:10808`
3. Проверьте логи на наличие ошибок
## Системные требования
- **ОС**: Windows 10+, Linux (любой дистрибутив), macOS 10.13+
- **RAM**: 50 MB минимум
- **Диск**: 100 MB свободного места
- **Дополнительно**:
- Xray-core (включен в проект)
- WireGuard (для использования WireGuard протокола)
## Безопасность
- Все конфигурации хранятся локально в папке `.vpn_client/`
- Пароли и ключи не передаются третьим лицам
- Логи содержат только техническую информацию
- Рекомендуется использовать надежные VPN провайдеры
## Поддержка
При возникновении проблем:
1. Проверьте логи в папке `logs/`
2. Убедитесь, что используете последнюю версию
3. Проверьте конфигурацию сервера
4. Попробуйте другой сервер из подписки

View File

@@ -1,82 +0,0 @@
# Установка GCC для Windows (для сборки GUI)
GUI версия требует GCC компилятор для работы с библиотекой Fyne. Вот несколько способов установки:
## Вариант 1: TDM-GCC (Рекомендуется)
Самый простой способ для Windows.
1. Скачайте TDM-GCC: https://jmeubank.github.io/tdm-gcc/download/
2. Выберите "tdm64-gcc-10.3.0-2.exe" (64-bit)
3. Запустите установщик
4. Выберите "Create" для новой установки
5. Оставьте путь по умолчанию: `C:\TDM-GCC-64`
6. Отметьте "Add to PATH"
7. Нажмите "Install"
8. Перезапустите терминал
Проверка:
```bash
gcc --version
```
## Вариант 2: MinGW-w64
1. Скачайте: https://www.mingw-w64.org/downloads/
2. Или используйте MSYS2: https://www.msys2.org/
3. Установите MSYS2
4. Откройте MSYS2 терминал и выполните:
```bash
pacman -S mingw-w64-x86_64-gcc
```
5. Добавьте в PATH: `C:\msys64\mingw64\bin`
## Вариант 3: Chocolatey (если установлен)
```bash
choco install mingw
```
## Вариант 4: Scoop (если установлен)
```bash
scoop install gcc
```
## После установки GCC
1. Перезапустите терминал
2. Проверьте установку:
```bash
gcc --version
```
3. Соберите GUI версию:
```bash
cd vpn_client_go
init_gui.bat
build_gui.bat
```
## Альтернатива: Используйте CLI версию
Если не хотите устанавливать GCC, используйте CLI версию:
```bash
# Сборка
build_cli_only.bat
# Запуск
vpn-client-cli.exe
```
CLI версия имеет весь функционал, но без графического интерфейса.
## Размеры
- CLI версия: ~10 MB (без GCC)
- GUI версия: ~28 MB (требует GCC для сборки)
## Готовые бинарники
Если не хотите собирать самостоятельно, можете скачать готовые бинарники из Releases на GitHub (когда будут доступны).

View File

@@ -1,143 +0,0 @@
# Быстрый старт - GUI версия
## 🚀 Установка за 3 шага
### Windows
```bash
# 1. Инициализация
init_gui.bat
# 2. Сборка
build_gui.bat
# 3. Запуск
vpn-client-gui.exe
```
### Linux/macOS
```bash
# 1. Инициализация
chmod +x init_gui.sh build_gui.sh
./init_gui.sh
# 2. Сборка
./build_gui.sh
# 3. Запуск
./vpn-client-gui
```
## 📱 Первое использование
### Вариант 1: Быстрое подключение (есть VLESS URL)
1. Запустите приложение
2. Перейдите на вкладку **VLESS**
3. Нажмите **Добавить**
4. Введите имя и вставьте VLESS URL
5. Нажмите **Подключиться**
6. Готово! Прокси: `127.0.0.1:10808`
### Вариант 2: Использование подписки
1. Запустите приложение
2. Перейдите на вкладку **Подписки**
3. Нажмите **Добавить**
4. Введите имя и URL подписки
5. Нажмите **Обновить конфиги**
6. Нажмите **Тест серверов** (опционально)
7. Перейдите на вкладку **VLESS**
8. Выберите сервер с префиксом `[Имя подписки]`
9. Нажмите **Подключиться**
10. Готово!
### Вариант 3: WireGuard
1. Запустите приложение
2. Перейдите на вкладку **WireGuard**
3. Нажмите **Из файла** или **Добавить вручную**
4. Добавьте конфигурацию
5. Нажмите **Подключиться**
6. Готово!
## 🌐 Настройка браузера
### Firefox (быстро)
1. `about:preferences#general` → Параметры сети → Настроить
2. SOCKS Host: `127.0.0.1`, Port: `10808`, SOCKS v5
3. ✓ Использовать прокси DNS
### Chrome + Proxy SwitchyOmega
1. Установите расширение Proxy SwitchyOmega
2. Создайте профиль: SOCKS5, `127.0.0.1:10808`
3. Активируйте профиль
## 🎯 Основные функции
| Функция | Описание |
|---------|----------|
| 🟢 Индикатор статуса | Серый = отключено, Зеленый = подключено |
| ⏱️ Таймер | Время текущего подключения |
| 📊 Статистика | Трафик для WireGuard |
| 🧪 Тест серверов | Проверка пинга перед подключением |
| 🔄 Автообновление | Статус обновляется каждые 2 секунды |
| 📋 Подписки | Автоматическая загрузка серверов |
## 🔧 CLI режим
Для использования без GUI:
```bash
vpn-client-gui --cli
```
## 📝 Логи
Все логи в папке `logs/`:
- `vless.log` - основной лог
- `vless_access.log` - IP адреса
- `vless_error.log` - ошибки
- `vless_traffic_*.log` - трафик
- `wireguard.log` - WireGuard
- `subscription.log` - подписки
## ❓ Проблемы?
**Не запускается:**
```bash
go mod tidy
# Пересоберите приложение
```
**VLESS не подключается:**
- Проверьте URL
- Используйте "Тест (Ping)"
- Проверьте логи
**WireGuard не работает:**
- Windows: Установите WireGuard с официального сайта
- Linux: `sudo apt install wireguard`
- macOS: `brew install wireguard-tools`
## 📚 Дополнительно
- Полное руководство: `GUI_GUIDE.md`
- История изменений: `CHANGELOG_GUI.md`
- Примеры: `EXAMPLES.md`
- README: `README.md`
## 💡 Советы
1. **Тестируйте серверы** перед подключением
2. **Обновляйте подписки** регулярно
3. **Проверяйте логи** при проблемах
4. **Используйте серверы с пингом < 100ms**
5. **Не забывайте отключаться** после использования
---
**Приятного использования! 🚀**

View File

@@ -1,197 +0,0 @@
# Инструкция по сборке
## ✅ CLI версия (Работает сейчас)
CLI версия собрана и готова к использованию!
### Запуск CLI
```bash
# Windows
vpn-client-cli.exe
# Или используйте старую версию
vpn-client.exe
```
### Сборка CLI (если нужно пересобрать)
```bash
build_cli_only.bat
```
**Преимущества CLI:**
-Не требует GCC
- ✅ Меньший размер (~10 MB)
- ✅ Быстрая сборка
- ✅ Весь функционал VPN клиента
- ✅ Работает на серверах без GUI
---
## 🎨 GUI версия (Требует GCC)
GUI версия требует установки GCC компилятора.
### Шаг 1: Установите GCC
**Вариант A: TDM-GCC (Рекомендуется)**
1. Скачайте: https://jmeubank.github.io/tdm-gcc/download/
2. Установите `tdm64-gcc-10.3.0-2.exe`
3. Отметьте "Add to PATH"
4. Перезапустите терминал
**Вариант B: MSYS2**
1. Установите MSYS2: https://www.msys2.org/
2. Выполните: `pacman -S mingw-w64-x86_64-gcc`
3. Добавьте в PATH: `C:\msys64\mingw64\bin`
**Проверка:**
```bash
gcc --version
```
### Шаг 2: Соберите GUI
```bash
# Инициализация (первый раз)
init_gui.bat
# Сборка
build_gui.bat
# Результат: vpn-client-gui.exe
```
### Запуск GUI
```bash
# GUI режим (по умолчанию)
vpn-client-gui.exe
# CLI режим
vpn-client-gui.exe --cli
```
**Преимущества GUI:**
- 🎨 Современный интерфейс
- 📊 Визуальный статус
- 🧪 Графическое тестирование
- 📈 Статистика в реальном времени
- 🔄 Автообновление статуса
---
## 📦 Что уже работает
### ✅ Собрано и готово:
- `vpn-client-cli.exe` - CLI версия (работает!)
- `vpn-client.exe` - старая CLI версия (работает!)
### 📋 Доступный функционал:
1. **WireGuard**
- Добавление конфигов
- Подключение/отключение
- Статистика трафика
2. **VLESS**
- Добавление конфигов
- Подключение через Xray
- Тестирование серверов (ping)
- SOCKS5 прокси: 127.0.0.1:10808
3. **Подписки**
- Добавление подписок
- Автообновление конфигов
- Массовое тестирование серверов
4. **Логирование**
- Детальные логи в папке `logs/`
- Логи трафика
- Логи ошибок
---
## 🚀 Быстрый старт (CLI)
```bash
# 1. Запустите CLI
vpn-client-cli.exe
# 2. Выберите протокол
# - WireGuard (1)
# - VLESS (2)
# - Подписки (3)
# 3. Добавьте конфиг или подписку
# 4. Подключитесь
# 5. Готово!
```
---
## 📊 Сравнение версий
| Функция | CLI | GUI |
|---------|-----|-----|
| Размер | ~10 MB | ~28 MB |
| Требует GCC | ❌ | ✅ |
| WireGuard | ✅ | ✅ |
| VLESS | ✅ | ✅ |
| Подписки | ✅ | ✅ |
| Тестирование | ✅ | ✅ |
| Визуальный статус | ❌ | ✅ |
| Графики | ❌ | ✅ |
| Работает на сервере | ✅ | ❌ |
| Работает без X11 | ✅ | ❌ |
---
## 🔧 Устранение проблем
### "gcc not found"
- Установите GCC (см. выше)
- Или используйте CLI версию
### "cannot open display"
- Используйте CLI версию
- Или запустите с `--cli` флагом
### Проблемы с подключением
1. Проверьте конфигурацию
2. Используйте тестирование (ping)
3. Проверьте логи в `logs/`
4. Убедитесь что Xray в папке `xray/`
---
## 📚 Документация
- `README.md` - Основная документация
- `GUI_GUIDE.md` - Руководство по GUI
- `QUICK_START_GUI.md` - Быстрый старт GUI
- `INSTALL_GCC_WINDOWS.md` - Установка GCC
- `BUILD_INSTRUCTIONS.md` - Детальные инструкции
- `EXAMPLES.md` - Примеры использования
---
## 💡 Рекомендации
**Для обычного использования:**
- Установите GCC и соберите GUI версию
- Удобный интерфейс и визуальный контроль
**Для серверов/автоматизации:**
- Используйте CLI версию
- Меньше зависимостей, работает везде
**Для разработки:**
- Используйте обе версии
- CLI для тестов, GUI для демонстрации
---
**Успешного использования! 🎉**

View File

@@ -1,285 +0,0 @@
# 🔒 Руководство по безопасности VPN клиента
## Обзор
VPN клиент теперь включает комплексную систему безопасности для защиты вашей конфиденциальности и предотвращения утечек данных.
## ⚠️ ВАЖНОЕ ПРЕДУПРЕЖДЕНИЕ О KILL SWITCH
**Kill Switch ОТКЛЮЧЕН ПО УМОЛЧАНИЮ!**
- Kill Switch может заблокировать весь интернет при неправильном использовании
- Включайте его ТОЛЬКО если понимаете, как он работает
- ВСЕГДА отключайте VPN через меню программы (не закрывайте крестиком)
- Если интернет сломался: запустите файл `ОТКЛЮЧИТЬ_KILLSWITCH.bat`
**Для восстановления интернета:**
1. Запустите `ОТКЛЮЧИТЬ_KILLSWITCH.bat` от имени администратора
2. Или см. файл `ЕСЛИ_СЛОМАЛСЯ_ИНТЕРНЕТ.txt`
## Основные функции безопасности
### 1. 🛡️ Kill Switch (Аварийное отключение)
**⚠️ ОТКЛЮЧЕНО ПО УМОЛЧАНИЮ - включайте только если понимаете риски!**
**Что это?**
Kill Switch автоматически блокирует весь интернет-трафик при разрыве VPN соединения, предотвращая утечку вашего реального IP адреса.
**Как работает:**
- При подключении к VPN создаются правила Windows Firewall
- Блокируется весь исходящий и входящий трафик
- Разрешается только трафик к VPN серверу и через VPN туннель
- При разрыве VPN весь интернет блокируется до восстановления соединения
**⚠️ ВАЖНО:**
- **ВСЕГДА отключайте VPN через меню программы!**
- **НЕ закрывайте программу крестиком при активном Kill Switch!**
- **Если интернет сломался:** запустите `ОТКЛЮЧИТЬ_KILLSWITCH.bat`
**Настройка:**
```
Меню → Безопасность и защита → Настроить Kill Switch → Включить
```
**Восстановление интернета:**
```
Запустите файл: ОТКЛЮЧИТЬ_KILLSWITCH.bat (от имени администратора)
Или см.: ЕСЛИ_СЛОМАЛСЯ_ИНТЕРНЕТ.txt
```
---
### 2. 🌐 Защита от DNS утечек
**Что это?**
Защита DNS гарантирует, что все DNS запросы идут через VPN, а не через DNS провайдера.
**Как работает:**
- Автоматически изменяет системные DNS серверы на безопасные (1.1.1.1, 8.8.8.8)
- Применяется ко всем сетевым интерфейсам
- Очищает DNS кэш для немедленного применения
- Восстанавливает оригинальные DNS при отключении VPN
**Настройка:**
```
Меню → Безопасность и защита → Настроить защиту DNS
```
**Рекомендуемые DNS серверы:**
- Cloudflare: `1.1.1.1`, `1.0.0.1`
- Google: `8.8.8.8`, `8.8.4.4`
- Quad9: `9.9.9.9`, `149.112.112.112`
---
### 3. 🔐 Шифрование конфигураций
**Что это?**
Все конфигурационные файлы (включая пароли и ключи VPN) шифруются с использованием AES-256-GCM.
**Как работает:**
- Использует алгоритм AES-256 в режиме GCM (Galois/Counter Mode)
- Ключ шифрования привязан к вашему компьютеру (hostname + путь к программе)
- Применяется PBKDF2 с 100,000 итераций для усиления ключа
- Каждый файл имеет уникальную соль и nonce
**Настройка:**
```
Меню → Безопасность и защита → Управление шифрованием конфигов
```
**Безопасность:**
- Конфигурации невозможно расшифровать на другом компьютере
- Защита от перебора паролей благодаря PBKDF2
- Аутентифицированное шифрование предотвращает подделку данных
---
### 4. 📁 Защита файлов и директорий
**Что это?**
Устанавливает строгие права доступа к конфигурационным файлам.
**Как работает:**
- Директория `.vpn_client/`: права 0700 (только владелец)
- Конфигурационные файлы: права 0600 (только чтение/запись владельцем)
- Предотвращает доступ других пользователей системы
**Настройка:**
```
Меню → Безопасность и защита → Защитить директорию конфигураций
```
---
### 5. 🌍 Системный прокси для всего ПК
**Что это?**
Автоматическая настройка системного прокси Windows для работы VPN во всех приложениях.
**Как работает:**
- Изменяет настройки прокси в реестре Windows
- Применяется ко всем приложениям, использующим системные настройки
- Использует SOCKS5 прокси на `127.0.0.1:10808`
- Исключает локальные адреса из прокси
**Автоматическая настройка:**
При подключении к VLESS серверу системный прокси настраивается автоматически.
**Ручная настройка:**
1. Откройте: Настройки Windows → Сеть и Интернет → Прокси
2. Включите "Использовать прокси-сервер"
3. Адрес: `127.0.0.1`, Порт: `10808`
4. Тип: SOCKS5
---
## Проверка безопасности
### Проверка утечек
Используйте встроенную функцию проверки:
```
Меню → Безопасность и защита → Проверить утечки
```
Проверяет:
- ✅ DNS утечки
- ✅ Статус Kill Switch
- ✅ Правильность настройки прокси
### Внешние сервисы проверки
После подключения к VPN проверьте:
1. **IP адрес:**
- https://whoer.net
- https://ipleak.net
2. **DNS утечки:**
- https://dnsleaktest.com
- https://www.dnsleaktest.org
3. **WebRTC утечки:**
- https://browserleaks.com/webrtc
---
## Рекомендации по безопасности
### ✅ Обязательно
1. **Всегда включайте Kill Switch** при работе с конфиденциальными данными
2. **Используйте защиту DNS** для предотвращения утечек
3. **Включите шифрование конфигов** для защиты учетных данных
4. **Регулярно проверяйте утечки** через меню безопасности
5. **Используйте надежные VPN серверы** с поддержкой современных протоколов
### ⚠️ Важно
1. **Не отключайте Kill Switch** во время активного VPN соединения
2. **Не делитесь конфигурационными файлами** - они содержат ваши ключи
3. **Проверяйте DNS** после каждого подключения
4. **Используйте HTTPS** сайты для дополнительной защиты
5. **Обновляйте клиент** для получения последних исправлений безопасности
### 🔴 Никогда
1. **Не запускайте VPN без Kill Switch** для критичных задач
2. **Не используйте публичные VPN** для конфиденциальных данных
3. **Не игнорируйте предупреждения** о утечках
4. **Не отключайте шифрование** без веской причины
---
## Устранение проблем
### Kill Switch не отключается
```bash
# Вручную удалите правила файрвола
netsh advfirewall firewall delete rule name=all dir=out
netsh advfirewall firewall delete rule name=all dir=in
```
### DNS не восстанавливаются
```bash
# Восстановите автоматическое получение DNS
netsh interface ip set dns "Ethernet" dhcp
netsh interface ip set dns "Wi-Fi" dhcp
ipconfig /flushdns
```
### Системный прокси не работает
1. Проверьте настройки: `Настройки → Сеть и Интернет → Прокси`
2. Убедитесь, что VPN подключен
3. Перезапустите браузер/приложение
4. Проверьте, что порт 10808 не занят
---
## Технические детали
### Алгоритмы шифрования
- **Алгоритм:** AES-256-GCM
- **Деривация ключа:** PBKDF2-SHA256
- **Итерации:** 100,000
- **Размер ключа:** 256 бит
- **Размер соли:** 256 бит
### Правила файрвола
Kill Switch создает следующие правила:
- `VPN_KillSwitch_Block_Out` - блокировка исходящего трафика
- `VPN_KillSwitch_Block_In` - блокировка входящего трафика
- `VPN_KillSwitch_Allow_Localhost` - разрешение локального трафика
- `VPN_KillSwitch_Allow_<IP>` - разрешение трафика к VPN серверу
### Изменения реестра
Системный прокси изменяет:
```
HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings
- ProxyEnable: 1
- ProxyServer: socks=127.0.0.1:10808
- ProxyOverride: <local>;localhost;127.*;10.*;172.16.*;172.31.*;192.168.*
```
---
## FAQ
**Q: Безопасно ли хранить конфигурации на диске?**
A: Да, при включенном шифровании конфигурации защищены AES-256 и привязаны к вашему компьютеру.
**Q: Что делать если Kill Switch заблокировал интернет?**
A: Запустите клиент и отключите Kill Switch через меню безопасности, или используйте команды из раздела "Устранение проблем".
**Q: Можно ли использовать VPN без системного прокси?**
A: Да, но тогда VPN будет работать только в приложениях с ручной настройкой SOCKS5 прокси.
**Q: Защищает ли Kill Switch от утечек WebRTC?**
A: Нет, для защиты от WebRTC утечек используйте расширения браузера или отключите WebRTC.
**Q: Как проверить, что VPN работает?**
A: Используйте встроенную проверку утечек или внешние сервисы типа whoer.net.
---
## Поддержка
При возникновении проблем с безопасностью:
1. Проверьте логи в папке `logs/`
2. Используйте встроенную проверку утечек
3. Убедитесь, что у вас права администратора
4. Проверьте настройки файрвола Windows
---
**Версия документа:** 1.0
**Дата обновления:** 2026-04-12

View File

@@ -1,68 +0,0 @@
# 🚀 НАЧНИТЕ ЗДЕСЬ
## ✅ CLI версия готова и работает!
```bash
vpn-client-cli.exe
```
**Все работает прямо сейчас:**
- WireGuard
- VLESS через Xray
- Подписки
- Тестирование серверов
- Полное логирование
---
## 🎨 Хотите GUI?
### Быстрая установка (15 минут)
**1. Установите GCC:**
- Скачайте: https://jmeubank.github.io/tdm-gcc/download/
- Установите `tdm64-gcc-10.3.0-2.exe`
- Отметьте "Add to PATH"
- Перезапустите терминал
**2. Соберите GUI:**
```bash
init_gui.bat
build_gui.bat
```
**3. Запустите:**
```bash
vpn-client-gui.exe
```
---
## 📚 Документация
- `STATUS.md` - Полный статус проекта
- `README_BUILD.md` - Инструкции по сборке
- `GUI_GUIDE.md` - Руководство по GUI
- `QUICK_START_GUI.md` - Быстрый старт
---
## 💡 Быстрый старт CLI
```bash
# 1. Запустите
vpn-client-cli.exe
# 2. Выберите VLESS (2)
# 3. Добавьте конфиг (2)
# Введите имя и VLESS URL
# 4. Подключитесь (4)
# 5. Используйте прокси: 127.0.0.1:10808
```
---
**Готово! Все работает! 🎉**

View File

@@ -1,260 +0,0 @@
# Статус проекта VPN Client
## ✅ Что готово и работает
### 1. CLI версия (100% готова)
-**Собрана**: `vpn-client-cli.exe` (6.5 MB)
-**Работает**: Полный функционал без зависимостей
-**Протоколы**: WireGuard + VLESS
-**Подписки**: Полная поддержка
-**Тестирование**: Ping серверов
-**Логирование**: Детальные логи
**Запуск:**
```bash
vpn-client-cli.exe
```
### 2. GUI код (100% готов)
-**Код написан**: `internal/gui/gui.go` (1000+ строк)
-**Интерфейс**: Современный Material Design
-**Функционал**: Все возможности CLI + визуализация
-**Статус**: Реальное время с автообновлением
-**Тестирование**: Визуальный прогресс
-**Документация**: Полная
**Требует для сборки:**
- GCC компилятор (для Fyne библиотеки)
### 3. Документация (100% готова)
-`README.md` - Основная документация
-`GUI_GUIDE.md` - Руководство по GUI (детальное)
-`QUICK_START_GUI.md` - Быстрый старт
-`BUILD_INSTRUCTIONS.md` - Инструкции по сборке
-`INSTALL_GCC_WINDOWS.md` - Установка GCC
-`CHANGELOG_GUI.md` - История изменений
-`EXAMPLES.md` - Примеры использования
-`README_BUILD.md` - Текущий статус сборки
### 4. Скрипты сборки
-`build_cli_only.bat` - Сборка CLI (работает!)
-`build_gui.bat` - Сборка GUI (требует GCC)
-`build_gui.sh` - Сборка GUI для Linux/macOS
-`init_gui.bat` - Инициализация зависимостей
-`init_gui.sh` - Инициализация для Linux/macOS
---
## 🎯 Текущая ситуация
### ✅ Работает прямо сейчас:
```
vpn_client_go/
├── vpn-client-cli.exe ✅ (6.5 MB, готов к использованию)
├── vpn-client.exe ✅ (старая версия, работает)
└── internal/gui/gui.go ✅ (код готов, требует GCC для сборки)
```
### 📋 Для сборки GUI нужно:
1. Установить GCC (TDM-GCC или MinGW-w64)
2. Запустить `init_gui.bat`
3. Запустить `build_gui.bat`
4. Получить `vpn-client-gui.exe`
---
## 🚀 Как использовать прямо сейчас
### Вариант 1: CLI версия (готова!)
```bash
# Запуск
vpn-client-cli.exe
# Меню:
# 1. WireGuard
# 2. VLESS
# 3. Подписки
# 4. Статус
# 5. Отключиться
```
**Все работает:**
- Добавление конфигов
- Подключение/отключение
- Тестирование серверов
- Управление подписками
- Статистика и логи
### Вариант 2: Собрать GUI (требует GCC)
**Шаг 1: Установить GCC**
```bash
# Скачайте TDM-GCC:
# https://jmeubank.github.io/tdm-gcc/download/
# Установите tdm64-gcc-10.3.0-2.exe
```
**Шаг 2: Собрать**
```bash
init_gui.bat
build_gui.bat
```
**Шаг 3: Запустить**
```bash
vpn-client-gui.exe
```
---
## 📊 Функционал
### WireGuard
- ✅ Добавление конфигов (вручную/из файла)
- ✅ Подключение/отключение
- ✅ Статистика трафика (прием/передача)
- ✅ Поддержка Windows/Linux/macOS
### VLESS (через Xray)
- ✅ Парсинг VLESS URL
- ✅ Поддержка Reality, TLS, WebSocket, gRPC, HTTP/2
- ✅ SOCKS5 прокси: 127.0.0.1:10808
- ✅ Тестирование серверов (ping)
- ✅ Детальное логирование
### Подписки
- ✅ Добавление подписок
- ✅ Автообновление конфигов
- ✅ Поддержка base64
- ✅ Парсинг VLESS, VMess, Trojan, Shadowsocks
- ✅ Массовое тестирование серверов
- ✅ Топ-10 лучших серверов
### Логирование
-`logs/vless.log` - основной лог
-`logs/vless_access.log` - IP адреса
-`logs/vless_error.log` - ошибки
-`logs/vless_traffic_*.log` - трафик
-`logs/wireguard.log` - WireGuard
-`logs/subscription.log` - подписки
---
## 🎨 GUI возможности (когда соберете)
### Интерфейс
- 🎨 Material Design стиль
- 📊 Панель статуса в реальном времени
- 🟢 Индикатор подключения (серый/зеленый)
- ⏱️ Таймер подключения (HH:MM:SS)
- 📈 Статистика трафика
- 🔄 Автообновление каждые 2 секунды
### Вкладки
1. **WireGuard**
- Список конфигов
- Добавить (вручную/из файла)
- Подключиться
- Удалить
2. **VLESS**
- Список конфигов с протоколами
- Добавить конфиг
- Тест (Ping)
- Подключиться
- Удалить
3. **Подписки**
- Список подписок
- Добавить подписку
- Обновить конфиги
- Показать конфиги
- Тест серверов (с прогресс-баром)
- Удалить
### Диалоги
- ✅ Формы ввода данных
- ✅ Подтверждение действий
- ✅ Прогресс-индикаторы
- ✅ Информационные сообщения
- ✅ Обработка ошибок
---
## 📦 Размеры
| Версия | Размер | Требует GCC |
|--------|--------|-------------|
| CLI | 6.5 MB | ❌ |
| GUI | ~28 MB | ✅ (для сборки) |
---
## 🔧 Системные требования
### CLI версия
- ✅ Windows 10+
- ✅ Linux (любой)
- ✅ macOS 10.13+
- ✅ RAM: 20-30 MB
- ✅ Нет зависимостей
### GUI версия
- ✅ Windows 10+ (с графической средой)
- ✅ Linux (X11/Wayland)
- ✅ macOS 10.13+
- ✅ RAM: 50-100 MB
- ✅ GCC (только для сборки)
---
## 📚 Полная документация
### Для пользователей
- `README_BUILD.md` - **НАЧНИТЕ ОТСЮДА**
- `QUICK_START_GUI.md` - Быстрый старт
- `GUI_GUIDE.md` - Полное руководство GUI
- `EXAMPLES.md` - Примеры использования
### Для разработчиков
- `BUILD_INSTRUCTIONS.md` - Детальная сборка
- `INSTALL_GCC_WINDOWS.md` - Установка GCC
- `CHANGELOG_GUI.md` - История изменений
---
## 💡 Рекомендации
### Если хотите использовать прямо сейчас:
```bash
vpn-client-cli.exe
```
Все работает, весь функционал доступен!
### Если хотите красивый GUI:
1. Установите GCC (10 минут)
2. Соберите GUI (5 минут)
3. Наслаждайтесь современным интерфейсом!
### Если не хотите возиться с GCC:
- Используйте CLI - он отличный!
- Или дождитесь готовых бинарников GUI
---
## ✨ Итог
**Проект полностью готов!**
- ✅ CLI версия работает
- ✅ GUI код написан и протестирован
- ✅ Документация полная
- ✅ Скрипты сборки готовы
- ⚠️ Для GUI нужен GCC (легко установить)
**Весь функционал доступен прямо сейчас через CLI!**
---
**Приятного использования! 🚀**

120
README.md
View File

@@ -4,20 +4,13 @@ VPN клиент на Golang с поддержкой VLESS протокола и
## 🚀 Возможности ## 🚀 Возможности
- ✅ Поддержка WireGuard * WireGuard (подключение + статистика)
- ✅ Поддержка VLESS протокола через Xray * VLESS через Xray-core
- ✅ Управление подписками * Поддержка подписок (auto-update конфигов)
- ✅ Автоматическое обновление конфигураций из подписок * Тестирование серверов (ping)
- ✅ Тестирование серверов (ping) * Детальное логирование
- ✅ Детальное логирование * Кроссплатформенность: Windows / Linux / macOS
- ✅ Статистика трафика для WireGuard * Один бинарник без внешних зависимостей (кроме WG/Xray)
- ✅ Кроссплатформенность (Windows, Linux, macOS)
-**Системный прокси для всего ПК**
- ⚠️ **Kill Switch (временно отключен)**
-**Защита от DNS утечек**
-**Шифрование конфигураций (AES-256-GCM)**
-**Защита файлов и директорий**
-**Проверка утечек безопасности**
## Требования ## Требования
@@ -27,59 +20,37 @@ VPN клиент на Golang с поддержкой VLESS протокола и
## Установка ## Установка
### Инициализация зависимостей ### Сборка из исходников
```bash ```bash
# Windows cd vpn_client_go
init_gui.bat go mod download
go build -o vpn-client main.go
# Linux/macOS
chmod +x init_gui.sh
./init_gui.sh
``` ```
### Сборка GUI версии ### Windows
```bash ```bash
# Windows
build_gui.bat
# Linux/macOS
chmod +x build_gui.sh
./build_gui.sh
```
### Сборка CLI версии (опционально)
```bash
# Windows
build.bat build.bat
```
# Linux/macOS ### Linux/macOS
```bash
chmod +x build.sh chmod +x build.sh
./build.sh ./build.sh
``` ```
## Использование ## Использование
### Запуск GUI (по умолчанию) ### Запуск
```bash ```bash
# Windows # Windows
vpn-client-gui.exe vpn-client.exe
# Linux/macOS # Linux/macOS
./vpn-client-gui ./vpn-client
```
### Запуск CLI режима
```bash
# Windows
vpn-client-gui.exe --cli
# Linux/macOS
./vpn-client-gui --cli
``` ```
## Основные возможности CLI ## Основные возможности CLI
@@ -150,58 +121,7 @@ vpn-client-gui.exe --cli
127.0.0.1:10808 127.0.0.1:10808
``` ```
**Системный прокси настраивается автоматически!** VPN будет работать для всех приложений Windows. Настройте браузер или систему на использование этого прокси, если клиент не настроил этого автоматически.
Для ручной настройки в браузере или других приложениях используйте указанный выше адрес.
## 🔒 Безопасность
Клиент включает комплексную систему безопасности:
### Системный прокси
Автоматически настраивается при подключении - VPN работает для всех приложений Windows.
### Kill Switch
⚠️ Временно отключен в этой версии из-за проблем с блокировкой интернета.
Будет доработан и добавлен в следующей версии.
### Защита DNS
Принудительно направляет все DNS запросы через VPN, предотвращая DNS утечки.
### Шифрование конфигураций
Все конфигурационные файлы шифруются с использованием AES-256-GCM и привязываются к вашему компьютеру.
### Проверка утечек
Встроенная система проверки DNS утечек и статуса защитных механизмов.
**Подробнее:** См. [Руководство по безопасности](Markdown/SECURITY_GUIDE.md)
## Отличия от Python версии
- ✅ Более быстрая работа
- ✅ Один исполняемый файл без зависимостей
- ✅ Меньшее потребление памяти
- ✅ Нативная кроссплатформенность
- ✅ Полная поддержка WireGuard
- ✅ Полная поддержка VLESS
-**Автоматическая настройка системного прокси**
-**Kill Switch и защита от утечек**
-**Шифрование конфигураций**
-**Комплексная система безопасности**
## Разработка
### Добавление новых функций
1. Создайте новый пакет в `internal/`
2. Реализуйте функциональность
3. Интегрируйте в CLI (`internal/cli/cli.go`)
### Тестирование
```bash
go test ./...
```
## Лицензия ## Лицензия

View File

@@ -1,17 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity
version="1.0.0.0"
processorArchitecture="*"
name="VPN.Client.CLI"
type="win32"
/>
<description>VPN Client CLI</description>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
</requestedPrivileges>
</security>
</trustInfo>
</assembly>

21
cmd/cli/main.go Normal file
View File

@@ -0,0 +1,21 @@
package main
import (
"fmt"
"os"
"vpn-client/internal/cli"
"vpn-client/internal/config"
)
func main() {
if err := config.Init(); err != nil {
fmt.Fprintf(os.Stderr, "config init error: %v\n", err)
os.Exit(1)
}
if err := cli.Run(); err != nil {
fmt.Fprintf(os.Stderr, "cli error: %v\n", err)
os.Exit(1)
}
}

13
go.mod
View File

@@ -1,14 +1,21 @@
module vpn-client module vpn-client
go 1.25.0 go 1.21
require ( require (
gioui.org v0.7.1
github.com/fatih/color v1.16.0 github.com/fatih/color v1.16.0
golang.org/x/crypto v0.31.0
golang.org/x/sys v0.42.0
) )
require ( require (
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2 // indirect
gioui.org/shader v1.0.8 // indirect
github.com/go-text/typesetting v0.1.1 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
golang.org/x/exp v0.0.0-20240707233637-46b078467d37 // indirect
golang.org/x/exp/shiny v0.0.0-20240707233637-46b078467d37 // indirect
golang.org/x/image v0.18.0 // indirect
golang.org/x/sys v0.22.0 // indirect
golang.org/x/text v0.16.0 // indirect
) )

27
go.sum
View File

@@ -1,13 +1,32 @@
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d h1:ARo7NCVvN2NdhLlJE9xAbKweuI9L6UgfTbYb0YwPacY=
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d/go.mod h1:OYVuxibdk9OSLX8vAqydtRPP87PyTFcT9uH3MlEGBQA=
gioui.org v0.7.1 h1:l7OVj47n1z8acaszQ6Wlu+Rxme+HqF3q8b+Fs68+x3w=
gioui.org v0.7.1/go.mod h1:5Kw/q7R1BWc5MKStuTNvhCgSrRqbfHc9Dzfjs4IGgZo=
gioui.org/cpu v0.0.0-20210808092351-bfe733dd3334/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2 h1:AGDDxsJE1RpcXTAxPG2B4jrwVUJGFDjINIPi1jtO6pc=
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
gioui.org/shader v1.0.8 h1:6ks0o/A+b0ne7RzEqRZK5f4Gboz2CfG+mVliciy6+qA=
gioui.org/shader v1.0.8/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/go-text/typesetting v0.1.1 h1:bGAesCuo85nXnEN5LmFMVGAGpGkCPtHrZLi//qD7EJo=
github.com/go-text/typesetting v0.1.1/go.mod h1:d22AnmeKq/on0HNv73UFriMKc4Ez6EqZAofLhAzpSzI=
github.com/go-text/typesetting-utils v0.0.0-20231211103740-d9332ae51f04 h1:zBx+p/W2aQYtNuyZNcTfinWvXBQwYtDfme051PR/lAY=
github.com/go-text/typesetting-utils v0.0.0-20231211103740-d9332ae51f04/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
golang.org/x/exp/shiny v0.0.0-20240707233637-46b078467d37 h1:SOSg7+sueresE4IbmmGM60GmlIys+zNX63d6/J4CMtU=
golang.org/x/exp/shiny v0.0.0-20240707233637-46b078467d37/go.mod h1:3F+MieQB7dRYLTmnncoFbb1crS5lfQoTfDgQy6K4N0o=
golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ=
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=

View File

@@ -1,160 +0,0 @@
; Скрипт Inno Setup для VPN Client (Go)
; Требуется Inno Setup 7
#define MyAppName "VPN Client Go"
#define MyAppVersion "1.0.0"
#define MyAppPublisher "VPN Client Team"
#define MyAppExeName "vpn-client-cli.exe"
[Setup]
AppId={{F8E7D6C5-B4A3-2109-8765-4321FEDCBA09}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
AppPublisher={#MyAppPublisher}
AppVerName={#MyAppName} {#MyAppVersion}
DefaultDirName={autopf}\VPNClientGo
DefaultGroupName={#MyAppName}
DisableProgramGroupPage=yes
OutputDir=installer_output
OutputBaseFilename=VPNClientGo-Setup-v{#MyAppVersion}
SetupIconFile=compiler:SetupClassicIcon.ico
UninstallDisplayIcon={app}\{#MyAppExeName}
Compression=lzma2/ultra64
SolidCompression=yes
LZMAUseSeparateProcess=yes
LZMANumBlockThreads=2
WizardStyle=modern
WizardSizePercent=100,100
DisableWelcomePage=no
MinVersion=10.0
ArchitecturesAllowed=x64compatible
ArchitecturesInstallIn64BitMode=x64compatible
PrivilegesRequired=admin
PrivilegesRequiredOverridesAllowed=commandline
[Languages]
Name: "russian"; MessagesFile: "compiler:Languages\Russian.isl"
Name: "english"; MessagesFile: "compiler:Default.isl"
[Tasks]
Name: "desktopicon"; Description: "Создать ярлык на рабочем столе"; GroupDescription: "Дополнительно:"; Flags: unchecked
[Files]
Source: "vpn-client-cli.exe"; DestDir: "{app}"; Flags: ignoreversion
[Dirs]
Name: "{app}\.vpn_client"; Permissions: users-full
Name: "{app}\logs"; Permissions: users-full
Name: "{app}\xray"; Permissions: users-full
Name: "{app}\v2ray"; Permissions: users-full
[Icons]
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Comment: "VPN Client"
Name: "{group}\Удалить {#MyAppName}"; Filename: "{uninstallexe}"
Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
[Run]
Filename: "{app}\{#MyAppExeName}"; Description: "Запустить {#MyAppName}"; Flags: nowait postinstall skipifsilent shellexec
[UninstallDelete]
Type: filesandordirs; Name: "{app}\.vpn_client"
Type: filesandordirs; Name: "{app}\logs"
Type: filesandordirs; Name: "{app}\xray"
Type: filesandordirs; Name: "{app}\v2ray"
[Messages]
russian.WelcomeLabel2=Программа установит [name/ver] на ваш компьютер.%n%nXray и V2Ray будут загружены во время установки (~20 МБ).
russian.FinishedLabel=Установка завершена.%n%nДля работы требуются права администратора.
english.WelcomeLabel2=This will install [name/ver] on your computer.%n%nXray and V2Ray will be downloaded during installation (~20 MB).
english.FinishedLabel=Installation complete.%n%nAdministrator privileges are required.
[Code]
function InitializeSetup(): Boolean;
begin
Result := True;
if not IsAdminLoggedOn then
begin
MsgBox('Требуются права администратора!', mbError, MB_OK);
Result := False;
end;
end;
function DownloadFile(const URL, FileName: String): Boolean;
var
ResultCode: Integer;
PSScript: String;
begin
Result := False;
PSScript := '[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; ' +
'Invoke-WebRequest -Uri ''' + URL + ''' -OutFile ''' + FileName + ''' -UseBasicParsing';
if Exec('powershell.exe', '-NoProfile -ExecutionPolicy Bypass -Command "' + PSScript + '"',
'', SW_HIDE, ewWaitUntilTerminated, ResultCode) then
begin
Result := (ResultCode = 0) and FileExists(FileName);
end;
end;
function ExtractZip(const ZipFile, DestDir: String): Boolean;
var
ResultCode: Integer;
begin
Result := False;
if not FileExists(ZipFile) then Exit;
if not DirExists(DestDir) then CreateDir(DestDir);
if Exec('powershell.exe',
'-NoProfile -ExecutionPolicy Bypass -Command "Expand-Archive -Path ''' + ZipFile + ''' -DestinationPath ''' + DestDir + ''' -Force"',
'', SW_HIDE, ewWaitUntilTerminated, ResultCode) then
begin
Result := (ResultCode = 0);
end;
end;
procedure CurStepChanged(CurStep: TSetupStep);
var
XrayZip, V2RayZip, XrayDir, V2RayDir: String;
StatusLabel: TNewStaticText;
begin
if CurStep = ssInstall then
begin
XrayZip := ExpandConstant('{tmp}\xray.zip');
V2RayZip := ExpandConstant('{tmp}\v2ray.zip');
XrayDir := ExpandConstant('{app}\xray');
V2RayDir := ExpandConstant('{app}\v2ray');
StatusLabel := TNewStaticText.Create(WizardForm);
StatusLabel.Parent := WizardForm.InstallingPage;
StatusLabel.Left := WizardForm.ProgressGauge.Left;
StatusLabel.Top := WizardForm.ProgressGauge.Top + WizardForm.ProgressGauge.Height + 20;
StatusLabel.Width := WizardForm.ProgressGauge.Width;
try
StatusLabel.Caption := 'Загрузка Xray...';
WizardForm.Update;
if DownloadFile('https://github.com/XTLS/Xray-core/releases/download/v24.12.18/Xray-windows-64.zip', XrayZip) then
begin
StatusLabel.Caption := 'Распаковка Xray...';
WizardForm.Update;
ExtractZip(XrayZip, XrayDir);
DeleteFile(XrayZip);
end;
StatusLabel.Caption := 'Загрузка V2Ray...';
WizardForm.Update;
if DownloadFile('https://github.com/v2fly/v2ray-core/releases/download/v5.22.0/v2ray-windows-64.zip', V2RayZip) then
begin
StatusLabel.Caption := 'Распаковка V2Ray...';
WizardForm.Update;
ExtractZip(V2RayZip, V2RayDir);
DeleteFile(V2RayZip);
end;
StatusLabel.Caption := 'Готово!';
finally
StatusLabel.Free;
end;
end;
end;

View File

@@ -1,29 +0,0 @@
// +build !windows
package admin
import (
"fmt"
"os"
)
// IsAdmin проверяет, запущено ли приложение с правами root
func IsAdmin() bool {
return os.Geteuid() == 0
}
// RequireAdmin проверяет права root и завершает программу, если их нет
func RequireAdmin() {
if !IsAdmin() {
fmt.Println("╔════════════════════════════════════════════════════════════╗")
fmt.Println("║ ⚠ ТРЕБУЮТСЯ ПРАВА ROOT ║")
fmt.Println("╚════════════════════════════════════════════════════════════╝")
fmt.Println()
fmt.Println("Это приложение требует прав root для работы с VPN.")
fmt.Println()
fmt.Println("Пожалуйста, запустите приложение с sudo:")
fmt.Println(" sudo ./vpn-client-cli")
fmt.Println()
os.Exit(1)
}
}

View File

@@ -1,57 +0,0 @@
// +build windows
package admin
import (
"fmt"
"os"
"golang.org/x/sys/windows"
)
// IsAdmin проверяет, запущено ли приложение с правами администратора
func IsAdmin() bool {
var sid *windows.SID
// Получаем SID группы администраторов
err := windows.AllocateAndInitializeSid(
&windows.SECURITY_NT_AUTHORITY,
2,
windows.SECURITY_BUILTIN_DOMAIN_RID,
windows.DOMAIN_ALIAS_RID_ADMINS,
0, 0, 0, 0, 0, 0,
&sid)
if err != nil {
return false
}
defer windows.FreeSid(sid)
// Получаем текущий токен процесса
token := windows.Token(0)
// Проверяем членство в группе
member, err := token.IsMember(sid)
if err != nil {
return false
}
return member
}
// RequireAdmin проверяет права администратора и завершает программу, если их нет
func RequireAdmin() {
if !IsAdmin() {
fmt.Println("╔════════════════════════════════════════════════════════════╗")
fmt.Println("║ ⚠ ТРЕБУЮТСЯ ПРАВА АДМИНИСТРАТОРА ║")
fmt.Println("╚════════════════════════════════════════════════════════════╝")
fmt.Println()
fmt.Println("Это приложение требует прав администратора для работы с VPN.")
fmt.Println()
fmt.Println("Пожалуйста, запустите приложение от имени администратора:")
fmt.Println(" 1. Щелкните правой кнопкой мыши на vpn-client-cli.exe")
fmt.Println(" 2. Выберите 'Запуск от имени администратора'")
fmt.Println()
fmt.Println("Нажмите Enter для выхода...")
fmt.Scanln()
os.Exit(1)
}
}

View File

@@ -5,11 +5,9 @@ import (
"fmt" "fmt"
"os" "os"
"os/exec" "os/exec"
"path/filepath"
"runtime" "runtime"
"strconv" "strconv"
"strings" "strings"
"time"
"vpn-client/internal/config" "vpn-client/internal/config"
"vpn-client/internal/subscription" "vpn-client/internal/subscription"
@@ -36,9 +34,6 @@ func Run() error {
choice := readInput("Выберите действие: ") choice := readInput("Выберите действие: ")
state, _ := vpn.GetStatus()
isConnected := state != nil && state.Connected
switch choice { switch choice {
case "1": case "1":
wireguardMenu() wireguardMenu()
@@ -52,45 +47,10 @@ func Run() error {
} }
pause() pause()
case "5": case "5":
if isConnected { if err := vpn.Disconnect(config.LogsDir); err != nil {
// Мониторинг в реальном времени fmt.Printf("%s %v\n", red("✗"), err)
if err := MonitorConnection(); err != nil {
fmt.Printf("%s %v\n", red("✗"), err)
pause()
}
} else {
// Отключение
if err := vpn.Disconnect(config.LogsDir); err != nil {
fmt.Printf("%s %v\n", red("✗"), err)
}
pause()
}
case "6":
if isConnected {
if err := vpn.Disconnect(config.LogsDir); err != nil {
fmt.Printf("%s %v\n", red("✗"), err)
}
pause()
} else {
// Настройки
settingsMenu()
}
case "7":
if isConnected {
// Настройки
settingsMenu()
} else {
// Безопасность
securityMenu()
}
case "8":
if isConnected {
// Безопасность
securityMenu()
} else {
fmt.Printf("%s Неверный выбор\n", red("✗"))
pause()
} }
pause()
case "0": case "0":
fmt.Println("До свидания!") fmt.Println("До свидания!")
return nil return nil
@@ -104,44 +64,25 @@ func Run() error {
func showMainMenu() { func showMainMenu() {
state, _ := vpn.GetStatus() state, _ := vpn.GetStatus()
// Получаем актуальный статус с временем statusIcon := "○"
statusLine := ShowQuickStatus() statusText := "Не подключено"
if state != nil && state.Connected {
// Показываем текущее ядро statusIcon = "✓"
settings, _ := config.LoadSettings() statusText = fmt.Sprintf("Подключено: %s", state.ConfigName)
coreName := "Xray"
if settings != nil && settings.CoreType == "v2ray" {
coreName = "V2Ray"
} }
fmt.Println("\n" + strings.Repeat("=", 60)) fmt.Println("\n" + strings.Repeat("=", 50))
fmt.Println(bold("VPN Клиент (Go)")) fmt.Println(bold("VPN Клиент (Go)"))
fmt.Println(strings.Repeat("=", 60)) fmt.Println(strings.Repeat("=", 50))
fmt.Printf("Статус: %s\n", statusLine) fmt.Printf("Статус: %s %s\n", statusIcon, statusText)
fmt.Printf("Ядро VLESS: %s\n", cyan(coreName)) fmt.Println(strings.Repeat("=", 50))
// Показываем текущее время
fmt.Printf("Время: %s\n", time.Now().Format("15:04:05"))
fmt.Println(strings.Repeat("=", 60))
fmt.Println("1. WireGuard") fmt.Println("1. WireGuard")
fmt.Println("2. VLESS") fmt.Println("2. VLESS")
fmt.Println("3. Управление подписками") fmt.Println("3. Управление подписками")
fmt.Println("4. Показать статус подключения") fmt.Println("4. Показать статус подключения")
fmt.Println("5. Отключиться от VPN")
if state != nil && state.Connected {
fmt.Println("5. " + green("Мониторинг в реальном времени"))
fmt.Println("6. Отключиться от VPN")
fmt.Println("7. " + yellow("Настройки (выбор ядра)"))
fmt.Println("8. " + cyan("🔒 Безопасность и защита"))
} else {
fmt.Println("5. Отключиться от VPN")
fmt.Println("6. " + yellow("Настройки (выбор ядра)"))
fmt.Println("7. " + cyan("🔒 Безопасность и защита"))
}
fmt.Println("0. Выход") fmt.Println("0. Выход")
fmt.Println(strings.Repeat("=", 60)) fmt.Println(strings.Repeat("=", 50))
} }
func vlessMenu() { func vlessMenu() {
@@ -155,7 +96,6 @@ func vlessMenu() {
fmt.Println("3. Удалить конфиг") fmt.Println("3. Удалить конфиг")
fmt.Println("4. Подключиться") fmt.Println("4. Подключиться")
fmt.Println("5. Тестировать конфиг (пинг)") fmt.Println("5. Тестировать конфиг (пинг)")
fmt.Println("6. " + yellow("Показать логи ошибок"))
fmt.Println("0. Назад") fmt.Println("0. Назад")
fmt.Println(strings.Repeat("=", 50)) fmt.Println(strings.Repeat("=", 50))
@@ -179,9 +119,6 @@ func vlessMenu() {
case "5": case "5":
testVLESSConfig() testVLESSConfig()
pause() pause()
case "6":
showVLESSLogs()
pause()
default: default:
fmt.Printf("%s Неверный выбор\n", red("✗")) fmt.Printf("%s Неверный выбор\n", red("✗"))
pause() pause()
@@ -309,28 +246,12 @@ func connectVLESS() {
configName := configs.VLESS[num-1].Name configName := configs.VLESS[num-1].Name
// Показываем текущее ядро fmt.Printf("\nПодключение к '%s' через Xray...\n", configName)
settings, _ := config.LoadSettings()
coreName := "Xray"
if settings != nil && settings.CoreType == "v2ray" {
coreName = "V2Ray"
}
fmt.Printf("\nПодключение к '%s' через %s...\n", configName, coreName) if err := vless.Connect(configName, config.LogsDir, config.XrayDir); err != nil {
if err := vless.Connect(configName, config.LogsDir); err != nil {
fmt.Printf("%s Ошибка подключения: %v\n", red("✗"), err) fmt.Printf("%s Ошибка подключения: %v\n", red("✗"), err)
return return
} }
// Предлагаем запустить мониторинг
fmt.Println("\n" + strings.Repeat("─", 60))
choice := readInput("Запустить мониторинг в реальном времени? (y/n): ")
if strings.ToLower(choice) == "y" || strings.ToLower(choice) == "д" {
if err := MonitorConnection(); err != nil {
fmt.Printf("%s %v\n", red("✗"), err)
}
}
} }
func testVLESSConfig() { func testVLESSConfig() {
@@ -940,115 +861,4 @@ func connectWireGuard() {
fmt.Printf("%s Ошибка подключения: %v\n", red("✗"), err) fmt.Printf("%s Ошибка подключения: %v\n", red("✗"), err)
return return
} }
// Предлагаем запустить мониторинг
fmt.Println("\n" + strings.Repeat("─", 60))
choice := readInput("Запустить мониторинг в реальном времени? (y/n): ")
if strings.ToLower(choice) == "y" || strings.ToLower(choice) == "д" {
if err := MonitorConnection(); err != nil {
fmt.Printf("%s %v\n", red("✗"), err)
}
}
}
func settingsMenu() {
clearScreen()
settings, err := config.LoadSettings()
if err != nil {
fmt.Printf("%s Ошибка загрузки настроек: %v\n", red("✗"), err)
pause()
return
}
currentCore := "Xray"
if settings.CoreType == "v2ray" {
currentCore = "V2Ray"
}
fmt.Println("\n" + strings.Repeat("=", 60))
fmt.Println(bold("Настройки"))
fmt.Println(strings.Repeat("=", 60))
fmt.Printf("Текущее ядро для VLESS: %s\n", cyan(currentCore))
fmt.Println(strings.Repeat("=", 60))
fmt.Println("\nВыберите ядро для подключения к VLESS:")
fmt.Println("1. Xray (рекомендуется)")
fmt.Println("2. V2Ray")
fmt.Println("0. Назад")
fmt.Println(strings.Repeat("=", 60))
choice := readInput("\nВыберите действие: ")
switch choice {
case "1":
settings.CoreType = "xray"
if err := config.SaveSettings(settings); err != nil {
fmt.Printf("%s Ошибка сохранения настроек: %v\n", red("✗"), err)
} else {
fmt.Printf("%s Ядро изменено на Xray\n", green("✓"))
}
pause()
case "2":
settings.CoreType = "v2ray"
if err := config.SaveSettings(settings); err != nil {
fmt.Printf("%s Ошибка сохранения настроек: %v\n", red("✗"), err)
} else {
fmt.Printf("%s Ядро изменено на V2Ray\n", green("✓"))
}
pause()
case "0":
return
default:
fmt.Printf("%s Неверный выбор\n", red("✗"))
pause()
}
}
func showVLESSLogs() {
fmt.Println("\n" + strings.Repeat("=", 60))
fmt.Println(bold("Логи VLESS/Xray/V2Ray"))
fmt.Println(strings.Repeat("=", 60))
errorLog := filepath.Join(config.LogsDir, "vless_error.log")
accessLog := filepath.Join(config.LogsDir, "vless_access.log")
// Показываем лог ошибок
fmt.Println("\n" + yellow("=== Лог ошибок (последние 30 строк) ==="))
if data, err := os.ReadFile(errorLog); err == nil && len(data) > 0 {
lines := strings.Split(string(data), "\n")
start := len(lines) - 30
if start < 0 {
start = 0
}
for i := start; i < len(lines); i++ {
if strings.TrimSpace(lines[i]) != "" {
fmt.Println(lines[i])
}
}
} else {
fmt.Println("Лог пуст или не найден")
}
fmt.Println("\n" + cyan("=== Лог доступа (последние 20 строк) ==="))
if data, err := os.ReadFile(accessLog); err == nil && len(data) > 0 {
lines := strings.Split(string(data), "\n")
start := len(lines) - 20
if start < 0 {
start = 0
}
for i := start; i < len(lines); i++ {
if strings.TrimSpace(lines[i]) != "" {
fmt.Println(lines[i])
}
}
} else {
fmt.Println("Лог пуст или не найден")
}
fmt.Println("\n" + strings.Repeat("=", 60))
fmt.Printf("Полные логи:\n")
fmt.Printf(" Ошибки: %s\n", errorLog)
fmt.Printf(" Доступ: %s\n", accessLog)
} }

View File

@@ -1,234 +0,0 @@
package cli
import (
"fmt"
"os"
"os/exec"
"runtime"
"strings"
"syscall"
"time"
"vpn-client/internal/config"
"vpn-client/internal/wireguard"
)
// MonitorConnection показывает статус подключения в реальном времени
func MonitorConnection() error {
// Проверяем, что есть активное подключение
state, err := config.LoadState()
if err != nil {
return fmt.Errorf("ошибка загрузки состояния: %w", err)
}
if !state.Connected {
fmt.Println("VPN не подключен")
return nil
}
fmt.Println("Нажмите 'q' или Ctrl+C для выхода из мониторинга\n")
time.Sleep(1 * time.Second)
// Создаем канал для остановки мониторинга
stopChan := make(chan bool, 1)
// Запускаем горутину для чтения клавиш
go func() {
for {
var input string
fmt.Scanln(&input)
if strings.ToLower(input) == "q" || strings.ToLower(input) == "й" {
stopChan <- true
return
}
}
}()
// Запускаем мониторинг
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
clearScreen()
displayRealtimeStatus(state)
fmt.Printf("\n%s Нажмите 'q' и Enter для выхода\n", cyan(""))
case <-stopChan:
fmt.Println("\n\nВыход из мониторинга...")
time.Sleep(500 * time.Millisecond)
return nil
}
}
}
func displayRealtimeStatus(state *config.ConnectionState) {
// Перезагружаем состояние для актуальных данных
currentState, err := config.LoadState()
if err != nil || !currentState.Connected {
fmt.Println("❌ Подключение потеряно")
return
}
// Заголовок
fmt.Println(strings.Repeat("═", 70))
fmt.Println(bold("📊 VPN МОНИТОРИНГ В РЕАЛЬНОМ ВРЕМЕНИ"))
fmt.Println(strings.Repeat("═", 70))
// Статус подключения
fmt.Printf("\n%s %s\n", green("●"), bold("ПОДКЛЮЧЕНО"))
fmt.Printf("Конфигурация: %s\n", cyan(currentState.ConfigName))
fmt.Printf("Тип: %s\n", currentState.ConfigType)
// Время подключения
if currentState.StartTime != "" {
startTime, err := time.Parse(time.RFC3339, currentState.StartTime)
if err == nil {
duration := time.Since(startTime)
hours := int(duration.Hours())
minutes := int(duration.Minutes()) % 60
seconds := int(duration.Seconds()) % 60
fmt.Printf("Время подключения: %s\n",
yellow(fmt.Sprintf("%02d:%02d:%02d", hours, minutes, seconds)))
}
}
// Текущее время
fmt.Printf("Текущее время: %s\n", time.Now().Format("15:04:05"))
fmt.Println(strings.Repeat("─", 70))
// Специфичная информация по типу подключения
if currentState.ConfigType == "vless" {
displayVLESSStats(currentState)
} else if currentState.ConfigType == "wireguard" {
displayWireGuardStats(currentState)
}
fmt.Println(strings.Repeat("═", 70))
fmt.Printf("\n%s Нажмите 'q' и Enter для выхода\n",
cyan(""))
}
func displayVLESSStats(state *config.ConnectionState) {
fmt.Println("\n" + bold("VLESS/Xray Прокси"))
fmt.Printf("Адрес прокси: %s\n", green("127.0.0.1:10808"))
fmt.Printf("Протокол: SOCKS5\n")
fmt.Printf("PID процесса: %d\n", state.ProcessPID)
if state.LogFile != "" {
fmt.Printf("\n%s Логи\n", bold("📝"))
fmt.Printf(" Трафик: %s\n", state.LogFile)
// Показываем размер лог-файла
if info, err := os.Stat(state.LogFile); err == nil {
size := info.Size()
sizeStr := formatBytes(size)
fmt.Printf(" Размер лога: %s\n", sizeStr)
}
}
// Проверяем, что процесс еще работает (улучшенная проверка для Windows)
if state.ProcessPID > 0 {
processRunning := checkProcessRunning(state.ProcessPID)
if !processRunning {
fmt.Printf("\n%s Процесс Xray не отвечает!\n", red("⚠"))
}
}
}
// checkProcessRunning проверяет, работает ли процесс
func checkProcessRunning(pid int) bool {
process, err := os.FindProcess(pid)
if err != nil {
return false
}
// На Windows FindProcess всегда успешен, нужна дополнительная проверка
// Пытаемся получить информацию о процессе через tasklist
if runtime.GOOS == "windows" {
cmd := exec.Command("tasklist", "/FI", fmt.Sprintf("PID eq %d", pid), "/NH")
output, err := cmd.Output()
if err != nil {
return false
}
// Если процесс существует, в выводе будет его PID
return strings.Contains(string(output), fmt.Sprintf("%d", pid))
}
// На Unix используем сигнал 0
err = process.Signal(syscall.Signal(0))
return err == nil
}
func displayWireGuardStats(state *config.ConnectionState) {
fmt.Println("\n" + bold("WireGuard Туннель"))
fmt.Printf("Интерфейс: %s\n", state.Interface)
// Получаем статистику WireGuard
stats, err := wireguard.GetStats(state.Interface)
if err != nil {
fmt.Printf("\n%s Ошибка получения статистики: %v\n", red("⚠"), err)
return
}
fmt.Printf("\n%s Статистика трафика\n", bold("📊"))
// Парсим и форматируем данные
if rx, ok := stats["rx"]; ok {
fmt.Printf(" %s Получено: %s\n", green("↓"), rx)
}
if tx, ok := stats["tx"]; ok {
fmt.Printf(" %s Отправлено: %s\n", yellow("↑"), tx)
}
// Дополнительная информация
if endpoint, ok := stats["endpoint"]; ok && endpoint != "" {
fmt.Printf("\n%s Сервер\n", bold("🌐"))
fmt.Printf(" Endpoint: %s\n", endpoint)
}
if handshake, ok := stats["latest_handshake"]; ok && handshake != "" {
fmt.Printf(" Последний handshake: %s\n", handshake)
}
}
func formatBytes(bytes int64) string {
const unit = 1024
if bytes < unit {
return fmt.Sprintf("%d B", bytes)
}
div, exp := int64(unit), 0
for n := bytes / unit; n >= unit; n /= unit {
div *= unit
exp++
}
return fmt.Sprintf("%.1f %cB", float64(bytes)/float64(div), "KMGTPE"[exp])
}
// ShowQuickStatus показывает краткий статус (для главного меню)
func ShowQuickStatus() string {
state, err := config.LoadState()
if err != nil || !state.Connected {
return fmt.Sprintf("%s Не подключено", red("○"))
}
// Вычисляем время подключения
var timeStr string
if state.StartTime != "" {
startTime, err := time.Parse(time.RFC3339, state.StartTime)
if err == nil {
duration := time.Since(startTime)
hours := int(duration.Hours())
minutes := int(duration.Minutes()) % 60
seconds := int(duration.Seconds()) % 60
timeStr = fmt.Sprintf(" [%02d:%02d:%02d]", hours, minutes, seconds)
}
}
return fmt.Sprintf("%s Подключено: %s%s",
green("●"),
cyan(state.ConfigName),
yellow(timeStr))
}

View File

@@ -1,323 +0,0 @@
package cli
import (
"fmt"
"strings"
"vpn-client/internal/config"
"vpn-client/internal/security"
)
// securityMenu отображает меню безопасности
func securityMenu() {
for {
clearScreen()
fmt.Println(bold("╔════════════════════════════════════════════════════════════╗"))
fmt.Println(bold("║ 🔒 БЕЗОПАСНОСТЬ И ЗАЩИТА ║"))
fmt.Println(bold("╚════════════════════════════════════════════════════════════╝"))
fmt.Println()
secManager := security.NewSecurityManager(config.ConfigDir, "default-password")
status := secManager.GetSecurityStatus()
// Показываем текущий статус
fmt.Println(cyan("Текущий статус безопасности:"))
fmt.Println(strings.Repeat("─", 60))
if status["kill_switch_enabled"].(bool) {
fmt.Printf(" Kill Switch: %s (отключен в этой версии)\n", yellow("⚠"))
} else {
fmt.Printf(" Kill Switch: %s (отключен в этой версии)\n", yellow("⚠"))
}
if status["dns_protection_enabled"].(bool) {
fmt.Printf(" Защита DNS: %s (активна)\n", green("✓"))
} else {
fmt.Printf(" Защита DNS: %s (неактивна)\n", red("✗"))
}
if status["encryption_enabled"].(bool) {
fmt.Printf(" Шифрование конфигов: %s (включено)\n", green("✓"))
} else {
fmt.Printf(" Шифрование конфигов: %s (выключено)\n", yellow("⚠"))
}
if status["config_dir_protected"].(bool) {
fmt.Printf(" Защита директории: %s (активна)\n", green("✓"))
} else {
fmt.Printf(" Защита директории: %s (неактивна)\n", red("✗"))
}
fmt.Println()
fmt.Println(strings.Repeat("─", 60))
fmt.Println()
fmt.Println("1. Настроить Kill Switch")
fmt.Println("2. Настроить защиту DNS")
fmt.Println("3. Управление шифрованием конфигов")
fmt.Println("4. Проверить утечки")
fmt.Println("5. Защитить директорию конфигураций")
fmt.Println("6. Показать текущие DNS серверы")
fmt.Println("0. Назад")
fmt.Println()
choice := readInput("Выберите действие: ")
switch choice {
case "1":
configureKillSwitch(secManager)
case "2":
configureDNSProtection(secManager)
case "3":
configureEncryption(secManager)
case "4":
checkForLeaks(secManager)
case "5":
protectConfigDirectory(secManager)
case "6":
showCurrentDNS()
case "0":
return
default:
fmt.Printf("%s Неверный выбор\n", red("✗"))
pause()
}
}
}
func configureKillSwitch(secManager *security.SecurityManager) {
clearScreen()
fmt.Println(bold("╔════════════════════════════════════════════════════════════╗"))
fmt.Println(bold("║ НАСТРОЙКА KILL SWITCH ║"))
fmt.Println(bold("╚════════════════════════════════════════════════════════════╝"))
fmt.Println()
fmt.Println(red("⚠️ KILL SWITCH ВРЕМЕННО ОТКЛЮЧЕН В ЭТОЙ ВЕРСИИ"))
fmt.Println()
fmt.Println("Kill Switch был отключен из-за проблем с блокировкой интернета.")
fmt.Println("Функция будет доработана и добавлена в следующей версии.")
fmt.Println()
fmt.Println("Для защиты используйте:")
fmt.Println(" • Защиту DNS (предотвращает DNS утечки)")
fmt.Println(" • Системный прокси (работает автоматически)")
fmt.Println(" • Шифрование конфигураций")
fmt.Println()
pause()
}
func configureDNSProtection(secManager *security.SecurityManager) {
clearScreen()
fmt.Println(bold("╔════════════════════════════════════════════════════════════╗"))
fmt.Println(bold("║ НАСТРОЙКА ЗАЩИТЫ DNS ║"))
fmt.Println(bold("╚════════════════════════════════════════════════════════════╝"))
fmt.Println()
fmt.Println("Защита DNS предотвращает утечку DNS запросов за пределы VPN.")
fmt.Println()
secConfig, err := secManager.LoadSecurityConfig()
if err != nil {
fmt.Printf("%s Ошибка загрузки конфигурации: %v\n", red("✗"), err)
pause()
return
}
fmt.Printf("Текущий статус: ")
if secConfig.DNSProtectionEnabled {
fmt.Println(green("Включена"))
} else {
fmt.Println(red("Отключена"))
}
fmt.Printf("\nТекущие VPN DNS серверы:\n")
for i, dns := range secConfig.VPNDNSServers {
fmt.Printf(" %d. %s\n", i+1, dns)
}
fmt.Println()
fmt.Println("1. Включить/отключить защиту DNS")
fmt.Println("2. Изменить DNS серверы")
fmt.Println("0. Назад")
fmt.Println()
choice := readInput("Выберите действие: ")
switch choice {
case "1":
secConfig.DNSProtectionEnabled = !secConfig.DNSProtectionEnabled
if err := secManager.SaveSecurityConfig(secConfig); err != nil {
fmt.Printf("%s Ошибка сохранения: %v\n", red("✗"), err)
} else {
if secConfig.DNSProtectionEnabled {
fmt.Printf("%s Защита DNS включена\n", green("✓"))
} else {
fmt.Printf("%s Защита DNS отключена\n", yellow("⚠"))
}
}
pause()
case "2":
fmt.Println("\nВведите DNS серверы (через запятую):")
fmt.Println("Например: 1.1.1.1,8.8.8.8")
fmt.Print("> ")
dnsInput := readInput("")
dnsServers := strings.Split(dnsInput, ",")
var cleanDNS []string
for _, dns := range dnsServers {
dns = strings.TrimSpace(dns)
if dns != "" {
cleanDNS = append(cleanDNS, dns)
}
}
if len(cleanDNS) > 0 {
secConfig.VPNDNSServers = cleanDNS
if err := secManager.SaveSecurityConfig(secConfig); err != nil {
fmt.Printf("%s Ошибка сохранения: %v\n", red("✗"), err)
} else {
fmt.Printf("%s DNS серверы обновлены\n", green("✓"))
}
} else {
fmt.Printf("%s Не указаны DNS серверы\n", red("✗"))
}
pause()
}
}
func configureEncryption(secManager *security.SecurityManager) {
clearScreen()
fmt.Println(bold("╔════════════════════════════════════════════════════════════╗"))
fmt.Println(bold("║ ШИФРОВАНИЕ КОНФИГУРАЦИЙ ║"))
fmt.Println(bold("╚════════════════════════════════════════════════════════════╝"))
fmt.Println()
fmt.Println("Шифрование защищает ваши конфигурации VPN от несанкционированного")
fmt.Println("доступа. Файлы шифруются с использованием AES-256-GCM.")
fmt.Println()
secConfig, err := secManager.LoadSecurityConfig()
if err != nil {
fmt.Printf("%s Ошибка загрузки конфигурации: %v\n", red("✗"), err)
pause()
return
}
fmt.Printf("Текущий статус: ")
if secConfig.EncryptionEnabled {
fmt.Println(green("Включено"))
} else {
fmt.Println(red("Отключено"))
}
fmt.Println()
fmt.Println("1. Включить шифрование")
fmt.Println("2. Отключить шифрование")
fmt.Println("0. Назад")
fmt.Println()
choice := readInput("Выберите действие: ")
switch choice {
case "1":
secConfig.EncryptionEnabled = true
if err := secManager.SaveSecurityConfig(secConfig); err != nil {
fmt.Printf("%s Ошибка сохранения: %v\n", red("✗"), err)
} else {
fmt.Printf("%s Шифрование включено\n", green("✓"))
fmt.Println("\nВнимание: Конфигурации будут зашифрованы автоматически")
}
pause()
case "2":
fmt.Println(yellow("\n⚠ Предупреждение: Отключение шифрования снизит безопасность!"))
confirm := readInput("Вы уверены? (yes/no): ")
if strings.ToLower(confirm) == "yes" {
secConfig.EncryptionEnabled = false
if err := secManager.SaveSecurityConfig(secConfig); err != nil {
fmt.Printf("%s Ошибка сохранения: %v\n", red("✗"), err)
} else {
fmt.Printf("%s Шифрование отключено\n", yellow("⚠"))
}
}
pause()
}
}
func checkForLeaks(secManager *security.SecurityManager) {
clearScreen()
fmt.Println(bold("╔════════════════════════════════════════════════════════════╗"))
fmt.Println(bold("║ ПРОВЕРКА УТЕЧЕК ║"))
fmt.Println(bold("╚════════════════════════════════════════════════════════════╝"))
fmt.Println()
fmt.Println("Проверка безопасности соединения...")
fmt.Println()
leaks, err := secManager.CheckForLeaks()
if err != nil {
fmt.Printf("%s Ошибка проверки: %v\n", red("✗"), err)
pause()
return
}
if len(leaks) == 0 {
fmt.Printf("%s Утечек не обнаружено!\n", green("✓"))
fmt.Println("\nВаше соединение безопасно:")
fmt.Println(" • DNS запросы защищены")
fmt.Println(" • Kill Switch активен")
fmt.Println(" • Трафик идет через VPN")
} else {
fmt.Printf("%s Обнаружены потенциальные утечки:\n\n", red("✗"))
for i, leak := range leaks {
fmt.Printf(" %d. %s\n", i+1, leak)
}
fmt.Println("\nРекомендации:")
fmt.Println(" • Проверьте настройки безопасности")
fmt.Println(" • Убедитесь, что VPN подключен")
fmt.Println(" • Включите Kill Switch и защиту DNS")
}
pause()
}
func protectConfigDirectory(secManager *security.SecurityManager) {
clearScreen()
fmt.Println(bold("╔════════════════════════════════════════════════════════════╗"))
fmt.Println(bold("║ ЗАЩИТА ДИРЕКТОРИИ КОНФИГУРАЦИЙ ║"))
fmt.Println(bold("╚════════════════════════════════════════════════════════════╝"))
fmt.Println()
fmt.Println("Установка строгих прав доступа к файлам конфигураций...")
fmt.Println()
if err := secManager.ProtectConfigDirectory(); err != nil {
fmt.Printf("%s Ошибка: %v\n", red("✗"), err)
} else {
fmt.Printf("%s Директория конфигураций защищена\n", green("✓"))
fmt.Println("\nТеперь только владелец может читать и изменять конфигурации")
}
pause()
}
func showCurrentDNS() {
clearScreen()
fmt.Println(bold("╔════════════════════════════════════════════════════════════╗"))
fmt.Println(bold("║ ТЕКУЩИЕ DNS СЕРВЕРЫ ║"))
fmt.Println(bold("╚════════════════════════════════════════════════════════════╝"))
fmt.Println()
dnsServers, err := security.GetCurrentDNS()
if err != nil {
fmt.Printf("%s Ошибка получения DNS: %v\n", red("✗"), err)
pause()
return
}
if len(dnsServers) == 0 {
fmt.Println(yellow("DNS серверы не обнаружены"))
} else {
fmt.Println("Активные DNS серверы:")
for i, dns := range dnsServers {
fmt.Printf(" %d. %s\n", i+1, dns)
}
}
pause()
}

View File

@@ -11,10 +11,8 @@ var (
SubscriptionsFile string SubscriptionsFile string
ConfigsFile string ConfigsFile string
StateFile string StateFile string
SettingsFile string
LogsDir string LogsDir string
XrayDir string XrayDir string
V2RayDir string
) )
type WireGuardConfig struct { type WireGuardConfig struct {
@@ -53,10 +51,6 @@ type ConnectionState struct {
LogFile string `json:"log_file"` LogFile string `json:"log_file"`
} }
type Settings struct {
CoreType string `json:"core_type"` // "xray" или "v2ray"
}
// Init инициализирует конфигурационные директории и файлы // Init инициализирует конфигурационные директории и файлы
func Init() error { func Init() error {
// Получаем рабочую директорию // Получаем рабочую директорию
@@ -69,10 +63,8 @@ func Init() error {
SubscriptionsFile = filepath.Join(ConfigDir, "subscriptions.json") SubscriptionsFile = filepath.Join(ConfigDir, "subscriptions.json")
ConfigsFile = filepath.Join(ConfigDir, "configs.json") ConfigsFile = filepath.Join(ConfigDir, "configs.json")
StateFile = filepath.Join(ConfigDir, "state.json") StateFile = filepath.Join(ConfigDir, "state.json")
SettingsFile = filepath.Join(ConfigDir, "settings.json")
LogsDir = filepath.Join(workDir, "logs") LogsDir = filepath.Join(workDir, "logs")
XrayDir = filepath.Join(workDir, "xray") XrayDir = filepath.Join(workDir, "xray")
V2RayDir = filepath.Join(workDir, "v2ray")
// Создаем директории // Создаем директории
if err := os.MkdirAll(ConfigDir, 0755); err != nil { if err := os.MkdirAll(ConfigDir, 0755); err != nil {
@@ -92,9 +84,6 @@ func Init() error {
if err := initFileIfNotExists(StateFile, ConnectionState{}); err != nil { if err := initFileIfNotExists(StateFile, ConnectionState{}); err != nil {
return err return err
} }
if err := initFileIfNotExists(SettingsFile, Settings{CoreType: "xray"}); err != nil {
return err
}
return nil return nil
} }
@@ -181,32 +170,3 @@ func SaveState(state *ConnectionState) error {
} }
return os.WriteFile(StateFile, data, 0644) return os.WriteFile(StateFile, data, 0644)
} }
// LoadSettings загружает настройки
func LoadSettings() (*Settings, error) {
data, err := os.ReadFile(SettingsFile)
if err != nil {
return nil, err
}
var settings Settings
if err := json.Unmarshal(data, &settings); err != nil {
return nil, err
}
// По умолчанию xray
if settings.CoreType == "" {
settings.CoreType = "xray"
}
return &settings, nil
}
// SaveSettings сохраняет настройки
func SaveSettings(settings *Settings) error {
data, err := json.MarshalIndent(settings, "", " ")
if err != nil {
return err
}
return os.WriteFile(SettingsFile, data, 0644)
}

View File

@@ -1,273 +0,0 @@
package downloader
import (
"archive/zip"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/fatih/color"
)
const (
XrayVersion = "24.12.18"
V2RayVersion = "5.22.0"
XrayDownloadURL = "https://github.com/XTLS/Xray-core/releases/download/v%s/Xray-windows-64.zip"
V2RayDownloadURL = "https://github.com/v2fly/v2ray-core/releases/download/v%s/v2ray-windows-64.zip"
)
var (
green = color.New(color.FgGreen).SprintFunc()
yellow = color.New(color.FgYellow).SprintFunc()
cyan = color.New(color.FgCyan).SprintFunc()
)
// DownloadCore загружает Xray или V2Ray
func DownloadCore(coreDir, coreName string) error {
var downloadURL, version, exeName string
if coreName == "xray" {
downloadURL = fmt.Sprintf(XrayDownloadURL, XrayVersion)
version = XrayVersion
exeName = "xray.exe"
} else if coreName == "v2ray" {
downloadURL = fmt.Sprintf(V2RayDownloadURL, V2RayVersion)
version = V2RayVersion
exeName = "v2ray.exe"
} else {
return fmt.Errorf("неизвестный тип ядра: %s", coreName)
}
// Проверяем, существует ли уже
coreExe := filepath.Join(coreDir, exeName)
if _, err := os.Stat(coreExe); err == nil {
return nil
}
fmt.Println("\n" + strings.Repeat("=", 60))
fmt.Printf("%s %s не найден. Начинаю загрузку...\n", yellow("⚠"), strings.ToUpper(coreName))
fmt.Println(strings.Repeat("=", 60))
// Создаем директорию
if err := os.MkdirAll(coreDir, 0755); err != nil {
return fmt.Errorf("ошибка создания директории: %w", err)
}
fmt.Printf("\nВерсия: %s\n", cyan(version))
fmt.Printf("URL: %s\n\n", downloadURL)
// Загружаем файл
zipPath := filepath.Join(coreDir, coreName+".zip")
if err := downloadFile(zipPath, downloadURL); err != nil {
return fmt.Errorf("ошибка загрузки: %w", err)
}
fmt.Printf("\n%s Загрузка завершена\n", green("✓"))
fmt.Printf("%s Распаковка архива...\n", yellow("⚙"))
// Распаковываем
if err := unzip(zipPath, coreDir); err != nil {
os.Remove(zipPath)
return fmt.Errorf("ошибка распаковки: %w", err)
}
os.Remove(zipPath)
fmt.Printf("%s %s успешно установлен в %s\n", green("✓"), strings.ToUpper(coreName), coreDir)
fmt.Println(strings.Repeat("=", 60))
return nil
}
// DownloadXray загружает Xray
func DownloadXray(xrayDir string) error {
return DownloadCore(xrayDir, "xray")
}
// DownloadV2Ray загружает V2Ray
func DownloadV2Ray(v2rayDir string) error {
return DownloadCore(v2rayDir, "v2ray")
}
// downloadFile загружает файл с прогресс-баром
func downloadFile(filepath string, url string) error {
out, err := os.Create(filepath)
if err != nil {
return err
}
defer out.Close()
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("ошибка загрузки: HTTP %d", resp.StatusCode)
}
size := resp.ContentLength
counter := &WriteCounter{Total: size}
_, err = io.Copy(out, io.TeeReader(resp.Body, counter))
if err != nil {
return err
}
fmt.Println()
return nil
}
// WriteCounter считает загруженные байты
type WriteCounter struct {
Total int64
Downloaded int64
}
func (wc *WriteCounter) Write(p []byte) (int, error) {
n := len(p)
wc.Downloaded += int64(n)
wc.PrintProgress()
return n, nil
}
func (wc *WriteCounter) PrintProgress() {
fmt.Printf("\r%s", strings.Repeat(" ", 60))
percent := float64(wc.Downloaded) / float64(wc.Total) * 100
downloaded := formatBytes(wc.Downloaded)
total := formatBytes(wc.Total)
fmt.Printf("\rЗагрузка: %s / %s (%.1f%%)", downloaded, total, percent)
}
func formatBytes(bytes int64) string {
const unit = 1024
if bytes < unit {
return fmt.Sprintf("%d B", bytes)
}
div, exp := int64(unit), 0
for n := bytes / unit; n >= unit; n /= unit {
div *= unit
exp++
}
return fmt.Sprintf("%.1f %cB", float64(bytes)/float64(div), "KMGTPE"[exp])
}
// unzip распаковывает zip архив
func unzip(src, dest string) error {
r, err := zip.OpenReader(src)
if err != nil {
return err
}
defer r.Close()
for _, f := range r.File {
fpath := filepath.Join(dest, f.Name)
if !strings.HasPrefix(fpath, filepath.Clean(dest)+string(os.PathSeparator)) {
return fmt.Errorf("недопустимый путь файла: %s", fpath)
}
if f.FileInfo().IsDir() {
os.MkdirAll(fpath, os.ModePerm)
continue
}
if err := os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
return err
}
outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
if err != nil {
return err
}
rc, err := f.Open()
if err != nil {
outFile.Close()
return err
}
_, err = io.Copy(outFile, rc)
outFile.Close()
rc.Close()
if err != nil {
return err
}
}
return nil
}
// CheckAndDownloadXray проверяет и загружает Xray
func CheckAndDownloadXray(xrayDir string) error {
if runtime.GOOS != "windows" {
return nil
}
xrayExe := filepath.Join(xrayDir, "xray.exe")
if _, err := os.Stat(xrayExe); os.IsNotExist(err) {
return DownloadXray(xrayDir)
}
return nil
}
// CheckAndDownloadV2Ray проверяет и загружает V2Ray
func CheckAndDownloadV2Ray(v2rayDir string) error {
if runtime.GOOS != "windows" {
return nil
}
v2rayExe := filepath.Join(v2rayDir, "v2ray.exe")
if _, err := os.Stat(v2rayExe); os.IsNotExist(err) {
return DownloadV2Ray(v2rayDir)
}
return nil
}
// CheckAndDownloadBoth проверяет и загружает оба ядра
func CheckAndDownloadBoth(xrayDir, v2rayDir string) error {
if runtime.GOOS != "windows" {
return nil
}
// Проверяем Xray
xrayExe := filepath.Join(xrayDir, "xray.exe")
xrayExists := true
if _, err := os.Stat(xrayExe); os.IsNotExist(err) {
xrayExists = false
}
// Проверяем V2Ray
v2rayExe := filepath.Join(v2rayDir, "v2ray.exe")
v2rayExists := true
if _, err := os.Stat(v2rayExe); os.IsNotExist(err) {
v2rayExists = false
}
// Если оба есть, ничего не делаем
if xrayExists && v2rayExists {
return nil
}
// Загружаем недостающие
if !xrayExists {
if err := DownloadXray(xrayDir); err != nil {
return err
}
}
if !v2rayExists {
if err := DownloadV2Ray(v2rayDir); err != nil {
return err
}
}
return nil
}

235
internal/gui/advanced.go Normal file
View File

@@ -0,0 +1,235 @@
package gui
import (
"fmt"
"sort"
"strings"
"time"
"gioui.org/layout"
"gioui.org/unit"
"gioui.org/widget/material"
"vpn-client/internal/config"
"vpn-client/internal/vless"
"vpn-client/internal/wireguard"
)
func (u *ui) addWireGuardManual() {
name := strings.TrimSpace(u.wgNameEditor.Text())
configText := strings.TrimSpace(u.wgConfigEditor.Text())
if name == "" || configText == "" {
u.message = "WireGuard name and config text are required."
u.messageColor = dangerColor
return
}
if err := wireguard.AddConfig(name, configText); err != nil {
u.message = err.Error()
u.messageColor = dangerColor
return
}
u.wgNameEditor.SetText("")
u.wgConfigEditor.SetText("")
u.message = "WireGuard config added."
u.messageColor = successColor
u.refresh()
}
func (u *ui) addWireGuardFromFile() {
name := strings.TrimSpace(u.wgFileNameEditor.Text())
filePath := strings.TrimSpace(u.wgFilePathEditor.Text())
if name == "" || filePath == "" {
u.message = "WireGuard name and file path are required."
u.messageColor = dangerColor
return
}
if err := wireguard.AddConfigFromFile(name, filePath); err != nil {
u.message = err.Error()
u.messageColor = dangerColor
return
}
u.wgFileNameEditor.SetText("")
u.wgFilePathEditor.SetText("")
u.message = "WireGuard config imported from file."
u.messageColor = successColor
u.refresh()
}
func (u *ui) deleteWireGuardConfig(name string) {
if err := wireguard.DeleteConfig(name); err != nil {
u.message = err.Error()
u.messageColor = dangerColor
return
}
u.message = "WireGuard config removed."
u.messageColor = successColor
u.refresh()
}
func (u *ui) subscriptionConfigs(name string) []config.VLESSConfig {
items := make([]config.VLESSConfig, 0)
for _, cfg := range u.configs.VLESS {
if cfg.Subscription == name {
items = append(items, cfg)
}
}
return items
}
func (u *ui) subscriptionSummary(name string) string {
items := u.subscriptionConfigs(name)
if len(items) == 0 {
return "No imported configs yet."
}
preview := strings.TrimPrefix(items[0].Name, fmt.Sprintf("[%s] ", name))
if len(items) == 1 {
return fmt.Sprintf("1 imported config: %s", trimForPreview(preview, 42))
}
return fmt.Sprintf("%d imported configs. First: %s", len(items), trimForPreview(preview, 36))
}
func (u *ui) testSubscriptionConfigs(name string) error {
items := u.subscriptionConfigs(name)
if len(items) == 0 {
return fmt.Errorf("no imported configs found for %s", name)
}
type result struct {
name string
ping float64
ok bool
}
results := make([]result, 0, len(items))
for _, cfg := range items {
ok, ping, _ := vless.PingServer(cfg.URL, 3*time.Second)
results = append(results, result{
name: strings.TrimPrefix(cfg.Name, fmt.Sprintf("[%s] ", name)),
ping: ping,
ok: ok,
})
}
successful := results[:0]
for _, r := range results {
if r.ok {
successful = append(successful, r)
}
}
if len(successful) == 0 {
return fmt.Errorf("all %d configs from %s are unreachable", len(results), name)
}
sort.Slice(successful, func(i, j int) bool { return successful[i].ping < successful[j].ping })
best := successful[0]
u.message = fmt.Sprintf("%s: %d/%d reachable, best %s at %.0f ms.", name, len(successful), len(results), trimForPreview(best.name, 34), best.ping)
u.messageColor = successColor
return nil
}
func (u *ui) layoutWireGuard(gtx layout.Context) layout.Dimensions {
return layout.Flex{Axis: layout.Vertical}.Layout(
gtx,
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return layout.Flex{Spacing: layout.SpaceBetween}.Layout(
gtx,
layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
return u.card(gtx, panelColor, func(gtx layout.Context) layout.Dimensions {
return layout.UniformInset(unit.Dp(18)).Layout(gtx, func(gtx layout.Context) layout.Dimensions {
return layout.Flex{Axis: layout.Vertical}.Layout(
gtx,
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
lbl := material.H5(u.theme, "Add manually")
lbl.Color = textColor
return lbl.Layout(gtx)
}),
layout.Rigid(spacerH(10)),
layout.Rigid(func(gtx layout.Context) layout.Dimensions { return u.layoutEditor(gtx, &u.wgNameEditor, "Config name") }),
layout.Rigid(spacerH(10)),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return u.layoutEditor(gtx, &u.wgConfigEditor, "WireGuard config text")
}),
layout.Rigid(spacerH(12)),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return u.layoutButton(gtx, &u.addWGManualBtn, "Save manual config", accentColor)
}),
)
})
})
}),
layout.Rigid(spacerW(12)),
layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
return u.card(gtx, panelColor, func(gtx layout.Context) layout.Dimensions {
return layout.UniformInset(unit.Dp(18)).Layout(gtx, func(gtx layout.Context) layout.Dimensions {
return layout.Flex{Axis: layout.Vertical}.Layout(
gtx,
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
lbl := material.H5(u.theme, "Import from file")
lbl.Color = textColor
return lbl.Layout(gtx)
}),
layout.Rigid(spacerH(10)),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return u.layoutEditor(gtx, &u.wgFileNameEditor, "Config name")
}),
layout.Rigid(spacerH(10)),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return u.layoutEditor(gtx, &u.wgFilePathEditor, "Path to .conf file")
}),
layout.Rigid(spacerH(12)),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return u.layoutButton(gtx, &u.addWGFileBtn, "Import file", accentSoftColor)
}),
)
})
})
}),
)
}),
layout.Rigid(spacerH(16)),
layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
return u.card(gtx, panelColor, func(gtx layout.Context) layout.Dimensions {
return layout.UniformInset(unit.Dp(18)).Layout(gtx, func(gtx layout.Context) layout.Dimensions {
if len(u.configs.WireGuard) == 0 {
lbl := material.Body1(u.theme, "No WireGuard configs yet.")
lbl.Color = mutedColor
return lbl.Layout(gtx)
}
return u.wgList.Layout(gtx, len(u.configs.WireGuard), func(gtx layout.Context, i int) layout.Dimensions {
cfg := u.configs.WireGuard[i]
return layout.Inset{Bottom: unit.Dp(12)}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
return u.card(gtx, panelAltColor, func(gtx layout.Context) layout.Dimensions {
return layout.UniformInset(unit.Dp(16)).Layout(gtx, func(gtx layout.Context) layout.Dimensions {
return layout.Flex{Axis: layout.Vertical}.Layout(
gtx,
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
lbl := material.H6(u.theme, cfg.Name)
lbl.Color = textColor
return lbl.Layout(gtx)
}),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
meta := material.Body2(u.theme, trimForPreview(cfg.Config, 110))
meta.Color = mutedColor
return meta.Layout(gtx)
}),
layout.Rigid(spacerH(12)),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return layout.Flex{}.Layout(
gtx,
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return u.layoutButton(gtx, &u.wgConnectBtns[i], "Connect", accentColor)
}),
layout.Rigid(spacerW(8)),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return u.layoutButton(gtx, &u.wgDeleteBtns[i], "Delete", dangerColor)
}),
)
}),
)
})
})
})
})
})
})
}),
)
}

1121
internal/gui/app.go Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,39 +0,0 @@
package gui
import (
"fmt"
"os/exec"
"runtime"
"time"
)
func Run() error {
url := startServer()
time.Sleep(500 * time.Millisecond)
fmt.Println("🚀 VPN Client GUI запущен!")
fmt.Printf("📱 Откройте в браузере: %s\n", url)
fmt.Println("💡 Браузер откроется автоматически...")
fmt.Println("\n⚠ Не закрывайте это окно!")
fmt.Println(" Для выхода нажмите Ctrl+C\n")
openBrowser(url)
select {}
}
func openBrowser(url string) {
var err error
switch runtime.GOOS {
case "linux":
err = exec.Command("xdg-open", url).Start()
case "windows":
err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start()
case "darwin":
err = exec.Command("open", url).Start()
default:
err = fmt.Errorf("unsupported platform")
}
if err != nil {
fmt.Printf("Не удалось открыть браузер: %v\n", err)
}
}

View File

@@ -1,453 +0,0 @@
package gui
import (
"encoding/json"
"fmt"
"net/http"
"time"
"vpn-client/internal/config"
"vpn-client/internal/subscription"
"vpn-client/internal/vless"
"vpn-client/internal/vpn"
"vpn-client/internal/wireguard"
)
// handleIndex обрабатывает главную страницу
func handleIndex(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
fmt.Fprintf(w, `<!DOCTYPE html>
<html>
<head>
<title>VPN Client</title>
<meta charset="utf-8">
</head>
<body>
<h1>VPN Client GUI</h1>
<p>GUI интерфейс в разработке. Используйте CLI режим: <code>vpn-client-gui.exe --cli</code></p>
</body>
</html>`)
}
// Запуск HTTP сервера
func startServer() string {
addr := "127.0.0.1:8765"
http.HandleFunc("/", handleIndex)
http.HandleFunc("/api/status", handleStatus)
http.HandleFunc("/api/wireguard/list", handleWireGuardList)
http.HandleFunc("/api/wireguard/add", handleWireGuardAdd)
http.HandleFunc("/api/wireguard/delete", handleWireGuardDelete)
http.HandleFunc("/api/wireguard/connect", handleWireGuardConnect)
http.HandleFunc("/api/vless/list", handleVLESSList)
http.HandleFunc("/api/vless/add", handleVLESSAdd)
http.HandleFunc("/api/vless/delete", handleVLESSDelete)
http.HandleFunc("/api/vless/connect", handleVLESSConnect)
http.HandleFunc("/api/vless/test", handleVLESSTest)
http.HandleFunc("/api/subscriptions/list", handleSubscriptionsList)
http.HandleFunc("/api/subscriptions/add", handleSubscriptionsAdd)
http.HandleFunc("/api/subscriptions/delete", handleSubscriptionsDelete)
http.HandleFunc("/api/subscriptions/update", handleSubscriptionsUpdate)
http.HandleFunc("/api/subscriptions/show", handleSubscriptionsShow)
http.HandleFunc("/api/subscriptions/test", handleSubscriptionsTest)
http.HandleFunc("/api/disconnect", handleDisconnect)
go http.ListenAndServe(addr, nil)
return "http://" + addr
}
// API handlers
func handleStatus(w http.ResponseWriter, r *http.Request) {
state, err := vpn.GetStatus()
response := map[string]interface{}{
"connected": false,
"info": "",
}
if err == nil && state.Connected {
response["connected"] = true
info := fmt.Sprintf("Конфиг: %s | Тип: %s", state.ConfigName, state.ConfigType)
if state.StartTime != "" {
startTime, err := time.Parse(time.RFC3339, state.StartTime)
if err == nil {
duration := time.Since(startTime)
hours := int(duration.Hours())
minutes := int(duration.Minutes()) % 60
seconds := int(duration.Seconds()) % 60
info += fmt.Sprintf(" | Время: %02d:%02d:%02d", hours, minutes, seconds)
}
}
if state.ConfigType == "vless" {
info += " | Прокси: 127.0.0.1:10808"
} else if state.ConfigType == "wireguard" {
stats, err := wireguard.GetStats(state.Interface)
if err == nil {
info += fmt.Sprintf(" | ↓%s ↑%s", stats["rx"], stats["tx"])
}
}
response["info"] = info
}
json.NewEncoder(w).Encode(response)
}
func handleWireGuardList(w http.ResponseWriter, r *http.Request) {
configs, _ := config.LoadConfigs()
json.NewEncoder(w).Encode(configs.WireGuard)
}
func handleWireGuardAdd(w http.ResponseWriter, r *http.Request) {
var req struct {
Name string `json:"name"`
Config string `json:"config"`
}
json.NewDecoder(r.Body).Decode(&req)
err := wireguard.AddConfig(req.Name, req.Config)
json.NewEncoder(w).Encode(map[string]interface{}{
"success": err == nil,
"message": func() string {
if err != nil {
return err.Error()
}
return "Конфиг добавлен"
}(),
})
}
func handleWireGuardDelete(w http.ResponseWriter, r *http.Request) {
var req struct {
Index int `json:"index"`
}
json.NewDecoder(r.Body).Decode(&req)
configs, _ := config.LoadConfigs()
if req.Index >= 0 && req.Index < len(configs.WireGuard) {
name := configs.WireGuard[req.Index].Name
wireguard.DeleteConfig(name)
}
json.NewEncoder(w).Encode(map[string]bool{"success": true})
}
func handleWireGuardConnect(w http.ResponseWriter, r *http.Request) {
var req struct {
Index int `json:"index"`
}
json.NewDecoder(r.Body).Decode(&req)
configs, _ := config.LoadConfigs()
if req.Index >= 0 && req.Index < len(configs.WireGuard) {
name := configs.WireGuard[req.Index].Name
err := wireguard.Connect(name, config.LogsDir)
json.NewEncoder(w).Encode(map[string]interface{}{
"success": err == nil,
"message": func() string {
if err != nil {
return err.Error()
}
return "Подключено к " + name
}(),
})
return
}
json.NewEncoder(w).Encode(map[string]interface{}{
"success": false,
"message": "Конфиг не найден",
})
}
func handleVLESSList(w http.ResponseWriter, r *http.Request) {
configs, _ := config.LoadConfigs()
json.NewEncoder(w).Encode(configs.VLESS)
}
func handleVLESSAdd(w http.ResponseWriter, r *http.Request) {
var req struct {
Name string `json:"name"`
URL string `json:"url"`
}
json.NewDecoder(r.Body).Decode(&req)
configs, err := config.LoadConfigs()
if err == nil {
configs.VLESS = append(configs.VLESS, config.VLESSConfig{
Name: req.Name,
URL: req.URL,
Protocol: "VLESS",
})
config.SaveConfigs(configs)
}
json.NewEncoder(w).Encode(map[string]bool{"success": true})
}
func handleVLESSDelete(w http.ResponseWriter, r *http.Request) {
var req struct {
Index int `json:"index"`
}
json.NewDecoder(r.Body).Decode(&req)
configs, _ := config.LoadConfigs()
if req.Index >= 0 && req.Index < len(configs.VLESS) {
configs.VLESS = append(configs.VLESS[:req.Index], configs.VLESS[req.Index+1:]...)
config.SaveConfigs(configs)
}
json.NewEncoder(w).Encode(map[string]bool{"success": true})
}
func handleVLESSConnect(w http.ResponseWriter, r *http.Request) {
var req struct {
Index int `json:"index"`
}
json.NewDecoder(r.Body).Decode(&req)
configs, _ := config.LoadConfigs()
if req.Index >= 0 && req.Index < len(configs.VLESS) {
name := configs.VLESS[req.Index].Name
err := vless.Connect(name, config.LogsDir)
json.NewEncoder(w).Encode(map[string]interface{}{
"success": err == nil,
"message": func() string {
if err != nil {
return err.Error()
}
return "Подключено к " + name + "\n\nSOCKS5 прокси: 127.0.0.1:10808"
}(),
})
return
}
json.NewEncoder(w).Encode(map[string]interface{}{
"success": false,
"message": "Конфиг не найден",
})
}
func handleVLESSTest(w http.ResponseWriter, r *http.Request) {
var req struct {
Index int `json:"index"`
}
json.NewDecoder(r.Body).Decode(&req)
configs, _ := config.LoadConfigs()
if req.Index >= 0 && req.Index < len(configs.VLESS) {
cfg := configs.VLESS[req.Index]
success, ping, err := vless.PingServer(cfg.URL, 5*time.Second)
if err != nil || !success {
msg := "Сервер недоступен"
if err != nil {
msg += "\n\nОшибка: " + err.Error()
}
json.NewEncoder(w).Encode(map[string]string{"message": msg})
return
}
quality := "Плохо"
if ping < 50 {
quality = "Отлично"
} else if ping < 100 {
quality = "Хорошо"
} else if ping < 200 {
quality = "Средне"
}
msg := fmt.Sprintf("✓ Сервер доступен\n\nПинг: %.2f мс\nКачество: %s", ping, quality)
json.NewEncoder(w).Encode(map[string]string{"message": msg})
return
}
json.NewEncoder(w).Encode(map[string]string{"message": "Конфиг не найден"})
}
func handleSubscriptionsList(w http.ResponseWriter, r *http.Request) {
subs, _ := config.LoadSubscriptions()
json.NewEncoder(w).Encode(subs.Subscriptions)
}
func handleSubscriptionsAdd(w http.ResponseWriter, r *http.Request) {
var req struct {
Name string `json:"name"`
URL string `json:"url"`
}
json.NewDecoder(r.Body).Decode(&req)
subs, err := config.LoadSubscriptions()
if err == nil {
subs.Subscriptions = append(subs.Subscriptions, config.Subscription{
Name: req.Name,
URL: req.URL,
})
config.SaveSubscriptions(subs)
}
json.NewEncoder(w).Encode(map[string]bool{"success": true})
}
func handleSubscriptionsDelete(w http.ResponseWriter, r *http.Request) {
var req struct {
Index int `json:"index"`
}
json.NewDecoder(r.Body).Decode(&req)
subs, _ := config.LoadSubscriptions()
if req.Index >= 0 && req.Index < len(subs.Subscriptions) {
subs.Subscriptions = append(subs.Subscriptions[:req.Index], subs.Subscriptions[req.Index+1:]...)
config.SaveSubscriptions(subs)
}
json.NewEncoder(w).Encode(map[string]bool{"success": true})
}
func handleSubscriptionsUpdate(w http.ResponseWriter, r *http.Request) {
var req struct {
Index int `json:"index"`
}
json.NewDecoder(r.Body).Decode(&req)
subs, _ := config.LoadSubscriptions()
if req.Index >= 0 && req.Index < len(subs.Subscriptions) {
name := subs.Subscriptions[req.Index].Name
err := subscription.UpdateSubscription(name, config.LogsDir)
json.NewEncoder(w).Encode(map[string]interface{}{
"success": err == nil,
"message": func() string {
if err != nil {
return err.Error()
}
return "Конфиги обновлены из подписки"
}(),
})
return
}
json.NewEncoder(w).Encode(map[string]interface{}{
"success": false,
"message": "Подписка не найдена",
})
}
func handleSubscriptionsShow(w http.ResponseWriter, r *http.Request) {
var req struct {
Index int `json:"index"`
}
json.NewDecoder(r.Body).Decode(&req)
subs, _ := config.LoadSubscriptions()
if req.Index >= 0 && req.Index < len(subs.Subscriptions) {
subName := subs.Subscriptions[req.Index].Name
configs, _ := config.LoadConfigs()
var subConfigs []config.VLESSConfig
for _, cfg := range configs.VLESS {
if cfg.Subscription == subName {
subConfigs = append(subConfigs, cfg)
}
}
if len(subConfigs) == 0 {
json.NewEncoder(w).Encode(map[string]string{
"message": "Нет конфигов из этой подписки.\n\nСначала обновите конфиги из подписки.",
})
return
}
msg := fmt.Sprintf("Конфиги из '%s':\n\n", subName)
for i, cfg := range subConfigs {
protocol := cfg.Protocol
if protocol == "" {
protocol = "Unknown"
}
msg += fmt.Sprintf("%d. [%s] %s\n", i+1, protocol, cfg.Name)
}
json.NewEncoder(w).Encode(map[string]string{"message": msg})
return
}
json.NewEncoder(w).Encode(map[string]string{"message": "Подписка не найдена"})
}
func handleSubscriptionsTest(w http.ResponseWriter, r *http.Request) {
var req struct {
Index int `json:"index"`
}
json.NewDecoder(r.Body).Decode(&req)
subs, _ := config.LoadSubscriptions()
if req.Index >= 0 && req.Index < len(subs.Subscriptions) {
subName := subs.Subscriptions[req.Index].Name
configs, _ := config.LoadConfigs()
var subConfigs []config.VLESSConfig
for _, cfg := range configs.VLESS {
if cfg.Subscription == subName {
subConfigs = append(subConfigs, cfg)
}
}
if len(subConfigs) == 0 {
json.NewEncoder(w).Encode(map[string]string{"message": "Нет конфигов из этой подписки"})
return
}
// Тестирование (упрощенная версия - первые 10)
msg := fmt.Sprintf("Тестирование серверов из '%s'...\n\n", subName)
tested := 0
for i, cfg := range subConfigs {
if i >= 10 {
break
}
tested++
success, ping, _ := vless.PingServer(cfg.URL, 3*time.Second)
if success {
msg += fmt.Sprintf("✓ %s - %.2f мс\n", cfg.Name, ping)
} else {
msg += fmt.Sprintf("✗ %s - недоступен\n", cfg.Name)
}
}
if len(subConfigs) > 10 {
msg += fmt.Sprintf("\n(Показано %d из %d серверов)", tested, len(subConfigs))
}
json.NewEncoder(w).Encode(map[string]string{"message": msg})
return
}
json.NewEncoder(w).Encode(map[string]string{"message": "Подписка не найдена"})
}
func handleDisconnect(w http.ResponseWriter, r *http.Request) {
state, err := vpn.GetStatus()
if err != nil || !state.Connected {
json.NewEncoder(w).Encode(map[string]interface{}{
"success": false,
"message": "VPN не подключен",
})
return
}
err = vpn.Disconnect(config.LogsDir)
json.NewEncoder(w).Encode(map[string]interface{}{
"success": err == nil,
"message": func() string {
if err != nil {
return err.Error()
}
return "Отключено от VPN"
}(),
})
}

View File

@@ -1,22 +0,0 @@
// +build !windows
package proxy
import (
"fmt"
)
// EnableSystemProxy включает системный прокси (заглушка для Unix)
func EnableSystemProxy(proxyAddr string) error {
return fmt.Errorf("автоматическая настройка системного прокси не поддерживается на этой платформе")
}
// DisableSystemProxy отключает системный прокси (заглушка для Unix)
func DisableSystemProxy() error {
return nil
}
// GetSystemProxyStatus проверяет статус системного прокси (заглушка для Unix)
func GetSystemProxyStatus() (bool, string, error) {
return false, "", nil
}

View File

@@ -1,209 +0,0 @@
package proxy
import (
"fmt"
"os/exec"
"strings"
"syscall"
)
var (
wininet = syscall.NewLazyDLL("wininet.dll")
internetSetOptionW = wininet.NewProc("InternetSetOptionW")
INTERNET_OPTION_SETTINGS_CHANGED = 39
INTERNET_OPTION_REFRESH = 37
)
// EnableSystemProxy включает системный прокси в Windows для ВСЕХ приложений
func EnableSystemProxy(proxyAddr string) error {
// Включаем прокси через реестр
cmd := exec.Command("reg", "add",
"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings",
"/v", "ProxyEnable",
"/t", "REG_DWORD",
"/d", "1",
"/f")
if err := cmd.Run(); err != nil {
return fmt.Errorf("ошибка включения прокси: %w", err)
}
// Устанавливаем адрес прокси (используем socks= для SOCKS5)
cmd = exec.Command("reg", "add",
"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings",
"/v", "ProxyServer",
"/t", "REG_SZ",
"/d", fmt.Sprintf("socks=%s", proxyAddr),
"/f")
if err := cmd.Run(); err != nil {
return fmt.Errorf("ошибка установки адреса прокси: %w", err)
}
// Отключаем прокси для локальных адресов
cmd = exec.Command("reg", "add",
"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings",
"/v", "ProxyOverride",
"/t", "REG_SZ",
"/d", "<local>;localhost;127.*;10.*;172.16.*;172.31.*;192.168.*",
"/f")
cmd.Run() // Игнорируем ошибку
// Применяем изменения немедленно через WinINet API
notifySystemProxyChange()
// Обновляем настройки Internet Explorer
refreshIESettings()
// Уведомляем систему о изменении настроек сети
notifyNetworkChange()
return nil
}
// DisableSystemProxy отключает системный прокси в Windows
func DisableSystemProxy() error {
// Отключаем прокси
cmd := exec.Command("reg", "add",
"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings",
"/v", "ProxyEnable",
"/t", "REG_DWORD",
"/d", "0",
"/f")
if err := cmd.Run(); err != nil {
return fmt.Errorf("ошибка отключения прокси: %w", err)
}
// Очищаем адрес прокси
cmd = exec.Command("reg", "delete",
"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings",
"/v", "ProxyServer",
"/f")
cmd.Run() // Игнорируем ошибку, если ключ не существует
// Обновляем настройки
refreshIESettings()
return nil
}
// GetSystemProxyStatus проверяет статус системного прокси
func GetSystemProxyStatus() (bool, string, error) {
// Проверяем, включен ли прокси
cmd := exec.Command("reg", "query",
"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings",
"/v", "ProxyEnable")
output, err := cmd.Output()
if err != nil {
return false, "", nil
}
enabled := strings.Contains(string(output), "0x1")
// Получаем адрес прокси
cmd = exec.Command("reg", "query",
"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings",
"/v", "ProxyServer")
output, err = cmd.Output()
proxyAddr := ""
if err == nil {
lines := strings.Split(string(output), "\n")
for _, line := range lines {
if strings.Contains(line, "ProxyServer") {
parts := strings.Fields(line)
if len(parts) >= 3 {
proxyAddr = parts[len(parts)-1]
}
}
}
}
return enabled, proxyAddr, nil
}
func refreshIESettings() {
// Используем rundll32 для обновления настроек Internet Explorer
cmd := exec.Command("rundll32.exe", "inetcpl.cpl,ClearMyTracksByProcess", "8")
cmd.Run()
}
// notifySystemProxyChange уведомляет систему об изменении прокси через WinINet API
func notifySystemProxyChange() {
// Уведомляем об изменении настроек
internetSetOptionW.Call(
0,
uintptr(INTERNET_OPTION_SETTINGS_CHANGED),
0,
0,
)
// Обновляем настройки
internetSetOptionW.Call(
0,
uintptr(INTERNET_OPTION_REFRESH),
0,
0,
)
}
// notifyNetworkChange уведомляет систему об изменении сетевых настроек
func notifyNetworkChange() {
// Используем netsh для сброса кэша DNS и обновления настроек
exec.Command("ipconfig", "/flushdns").Run()
// Перезапускаем сетевые службы для применения изменений
exec.Command("net", "stop", "WinHttpAutoProxySvc").Run()
exec.Command("net", "start", "WinHttpAutoProxySvc").Run()
}
// EnableSystemProxyHTTP включает HTTP прокси (для совместимости с некоторыми приложениями)
func EnableSystemProxyHTTP(httpProxyAddr string) error {
// Устанавливаем HTTP прокси
cmd := exec.Command("reg", "add",
"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings",
"/v", "ProxyServer",
"/t", "REG_SZ",
"/d", fmt.Sprintf("http=%s;https=%s", httpProxyAddr, httpProxyAddr),
"/f")
if err := cmd.Run(); err != nil {
return fmt.Errorf("ошибка установки HTTP прокси: %w", err)
}
notifySystemProxyChange()
return nil
}
// SetProxyForAllUsers устанавливает прокси для всех пользователей (требует прав администратора)
func SetProxyForAllUsers(proxyAddr string) error {
// Устанавливаем в HKLM для всех пользователей
cmd := exec.Command("reg", "add",
"HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings",
"/v", "ProxyEnable",
"/t", "REG_DWORD",
"/d", "1",
"/f")
if err := cmd.Run(); err != nil {
return fmt.Errorf("требуются права администратора: %w", err)
}
cmd = exec.Command("reg", "add",
"HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings",
"/v", "ProxyServer",
"/t", "REG_SZ",
"/d", fmt.Sprintf("socks=%s", proxyAddr),
"/f")
if err := cmd.Run(); err != nil {
return fmt.Errorf("ошибка установки прокси для всех пользователей: %w", err)
}
notifySystemProxyChange()
return nil
}

View File

@@ -1,240 +0,0 @@
package security
import (
"fmt"
"os/exec"
"runtime"
"strings"
)
// DNSProtection управляет защитой от DNS утечек
type DNSProtection struct {
originalDNS []string
enabled bool
}
// NewDNSProtection создает новый объект защиты DNS
func NewDNSProtection() *DNSProtection {
return &DNSProtection{
originalDNS: []string{},
enabled: false,
}
}
// Enable включает защиту от DNS утечек
func (dp *DNSProtection) Enable(vpnDNS []string) error {
if runtime.GOOS != "windows" {
return dp.enableUnix(vpnDNS)
}
return dp.enableWindows(vpnDNS)
}
// Disable отключает защиту и восстанавливает оригинальные DNS
func (dp *DNSProtection) Disable() error {
if runtime.GOOS != "windows" {
return dp.disableUnix()
}
return dp.disableWindows()
}
// enableWindows включает защиту DNS на Windows
func (dp *DNSProtection) enableWindows(vpnDNS []string) error {
// Получаем список активных сетевых интерфейсов
cmd := exec.Command("netsh", "interface", "show", "interface")
output, err := cmd.Output()
if err != nil {
return fmt.Errorf("ошибка получения списка интерфейсов: %w", err)
}
// Парсим вывод для получения имен интерфейсов
lines := strings.Split(string(output), "\n")
var activeInterfaces []string
for _, line := range lines {
if strings.Contains(line, "Connected") || strings.Contains(line, "Подключено") {
fields := strings.Fields(line)
if len(fields) >= 4 {
// Имя интерфейса обычно последнее поле
interfaceName := strings.Join(fields[3:], " ")
activeInterfaces = append(activeInterfaces, interfaceName)
}
}
}
// Сохраняем текущие DNS для каждого интерфейса
for _, iface := range activeInterfaces {
cmd = exec.Command("netsh", "interface", "ip", "show", "dns", iface)
output, err := cmd.Output()
if err == nil {
dp.originalDNS = append(dp.originalDNS, string(output))
}
}
// Устанавливаем VPN DNS для всех активных интерфейсов
for _, iface := range activeInterfaces {
// Устанавливаем первичный DNS
if len(vpnDNS) > 0 {
cmd = exec.Command("netsh", "interface", "ip", "set", "dns",
iface, "static", vpnDNS[0])
if err := cmd.Run(); err != nil {
fmt.Printf("Предупреждение: не удалось установить DNS для %s: %v\n", iface, err)
}
}
// Добавляем вторичные DNS
for i := 1; i < len(vpnDNS); i++ {
cmd = exec.Command("netsh", "interface", "ip", "add", "dns",
iface, vpnDNS[i], fmt.Sprintf("index=%d", i+1))
cmd.Run() // Игнорируем ошибки для дополнительных DNS
}
}
// Очищаем DNS кэш
exec.Command("ipconfig", "/flushdns").Run()
dp.enabled = true
return nil
}
// disableWindows отключает защиту DNS на Windows
func (dp *DNSProtection) disableWindows() error {
if !dp.enabled {
return nil
}
// Получаем список активных интерфейсов
cmd := exec.Command("netsh", "interface", "show", "interface")
output, err := cmd.Output()
if err != nil {
return fmt.Errorf("ошибка получения списка интерфейсов: %w", err)
}
lines := strings.Split(string(output), "\n")
var activeInterfaces []string
for _, line := range lines {
if strings.Contains(line, "Connected") || strings.Contains(line, "Подключено") {
fields := strings.Fields(line)
if len(fields) >= 4 {
interfaceName := strings.Join(fields[3:], " ")
activeInterfaces = append(activeInterfaces, interfaceName)
}
}
}
// Восстанавливаем автоматическое получение DNS
for _, iface := range activeInterfaces {
cmd = exec.Command("netsh", "interface", "ip", "set", "dns",
iface, "dhcp")
cmd.Run() // Игнорируем ошибки
}
// Очищаем DNS кэш
exec.Command("ipconfig", "/flushdns").Run()
dp.enabled = false
dp.originalDNS = []string{}
return nil
}
// enableUnix включает защиту DNS на Unix системах
func (dp *DNSProtection) enableUnix(vpnDNS []string) error {
// Сохраняем оригинальный resolv.conf
cmd := exec.Command("cat", "/etc/resolv.conf")
output, err := cmd.Output()
if err != nil {
return fmt.Errorf("ошибка чтения resolv.conf: %w", err)
}
dp.originalDNS = strings.Split(string(output), "\n")
// Создаем новый resolv.conf с VPN DNS
var newResolv strings.Builder
for _, dns := range vpnDNS {
newResolv.WriteString(fmt.Sprintf("nameserver %s\n", dns))
}
// Записываем новый resolv.conf
cmd = exec.Command("sh", "-c", fmt.Sprintf("echo '%s' > /etc/resolv.conf", newResolv.String()))
if err := cmd.Run(); err != nil {
return fmt.Errorf("ошибка записи resolv.conf: %w", err)
}
dp.enabled = true
return nil
}
// disableUnix отключает защиту DNS на Unix системах
func (dp *DNSProtection) disableUnix() error {
if !dp.enabled || len(dp.originalDNS) == 0 {
return nil
}
// Восстанавливаем оригинальный resolv.conf
content := strings.Join(dp.originalDNS, "\n")
cmd := exec.Command("sh", "-c", fmt.Sprintf("echo '%s' > /etc/resolv.conf", content))
if err := cmd.Run(); err != nil {
return fmt.Errorf("ошибка восстановления resolv.conf: %w", err)
}
dp.enabled = false
dp.originalDNS = []string{}
return nil
}
// IsEnabled проверяет, включена ли защита DNS
func (dp *DNSProtection) IsEnabled() bool {
return dp.enabled
}
// GetCurrentDNS возвращает текущие DNS серверы
func GetCurrentDNS() ([]string, error) {
if runtime.GOOS == "windows" {
return getCurrentDNSWindows()
}
return getCurrentDNSUnix()
}
func getCurrentDNSWindows() ([]string, error) {
cmd := exec.Command("nslookup", "localhost")
output, err := cmd.Output()
if err != nil {
return nil, err
}
var dnsServers []string
lines := strings.Split(string(output), "\n")
for _, line := range lines {
if strings.Contains(line, "Address:") {
parts := strings.Split(line, ":")
if len(parts) >= 2 {
dns := strings.TrimSpace(parts[1])
if dns != "" && dns != "127.0.0.1" {
dnsServers = append(dnsServers, dns)
}
}
}
}
return dnsServers, nil
}
func getCurrentDNSUnix() ([]string, error) {
cmd := exec.Command("cat", "/etc/resolv.conf")
output, err := cmd.Output()
if err != nil {
return nil, err
}
var dnsServers []string
lines := strings.Split(string(output), "\n")
for _, line := range lines {
if strings.HasPrefix(strings.TrimSpace(line), "nameserver") {
parts := strings.Fields(line)
if len(parts) >= 2 {
dnsServers = append(dnsServers, parts[1])
}
}
}
return dnsServers, nil
}

View File

@@ -1,212 +0,0 @@
package security
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"fmt"
"io"
"os"
"path/filepath"
"golang.org/x/crypto/pbkdf2"
)
const (
saltSize = 32
iterations = 100000
keySize = 32
)
// GetMachineKey генерирует ключ на основе уникальных характеристик машины
func GetMachineKey() ([]byte, error) {
// Используем hostname и другие системные параметры
hostname, err := os.Hostname()
if err != nil {
return nil, err
}
// Добавляем путь к исполняемому файлу для уникальности
exePath, err := os.Executable()
if err != nil {
return nil, err
}
// Комбинируем для создания уникального ключа
combined := hostname + exePath
hash := sha256.Sum256([]byte(combined))
return hash[:], nil
}
// Encrypt шифрует данные с использованием AES-256-GCM
func Encrypt(plaintext []byte, password string) (string, error) {
// Генерируем соль
salt := make([]byte, saltSize)
if _, err := io.ReadFull(rand.Reader, salt); err != nil {
return "", err
}
// Получаем машинный ключ
machineKey, err := GetMachineKey()
if err != nil {
return "", err
}
// Комбинируем пароль с машинным ключом
combinedPassword := append([]byte(password), machineKey...)
// Генерируем ключ шифрования
key := pbkdf2.Key(combinedPassword, salt, iterations, keySize, sha256.New)
// Создаем AES cipher
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}
// Используем GCM для аутентифицированного шифрования
gcm, err := cipher.NewGCM(block)
if err != nil {
return "", err
}
// Генерируем nonce
nonce := make([]byte, gcm.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return "", err
}
// Шифруем
ciphertext := gcm.Seal(nonce, nonce, plaintext, nil)
// Комбинируем соль и зашифрованные данные
result := append(salt, ciphertext...)
return base64.StdEncoding.EncodeToString(result), nil
}
// Decrypt расшифровывает данные
func Decrypt(encryptedData string, password string) ([]byte, error) {
// Декодируем из base64
data, err := base64.StdEncoding.DecodeString(encryptedData)
if err != nil {
return nil, err
}
if len(data) < saltSize {
return nil, fmt.Errorf("неверный формат зашифрованных данных")
}
// Извлекаем соль
salt := data[:saltSize]
ciphertext := data[saltSize:]
// Получаем машинный ключ
machineKey, err := GetMachineKey()
if err != nil {
return nil, err
}
// Комбинируем пароль с машинным ключом
combinedPassword := append([]byte(password), machineKey...)
// Генерируем ключ
key := pbkdf2.Key(combinedPassword, salt, iterations, keySize, sha256.New)
// Создаем AES cipher
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
// Используем GCM
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
nonceSize := gcm.NonceSize()
if len(ciphertext) < nonceSize {
return nil, fmt.Errorf("неверный размер зашифрованных данных")
}
nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
// Расшифровываем
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
return nil, fmt.Errorf("ошибка расшифровки: неверный пароль или поврежденные данные")
}
return plaintext, nil
}
// SecureDelete безопасно удаляет файл (перезаписывает случайными данными)
func SecureDelete(filepath string) error {
// Получаем размер файла
info, err := os.Stat(filepath)
if err != nil {
return err
}
size := info.Size()
// Открываем файл для записи
file, err := os.OpenFile(filepath, os.O_WRONLY, 0)
if err != nil {
return err
}
defer file.Close()
// Перезаписываем случайными данными 3 раза
for i := 0; i < 3; i++ {
randomData := make([]byte, size)
if _, err := rand.Read(randomData); err != nil {
return err
}
if _, err := file.WriteAt(randomData, 0); err != nil {
return err
}
if err := file.Sync(); err != nil {
return err
}
}
// Удаляем файл
return os.Remove(filepath)
}
// SetFilePermissions устанавливает строгие права доступа к файлу
func SetFilePermissions(path string) error {
// Только владелец может читать и писать
return os.Chmod(path, 0600)
}
// ProtectDirectory защищает директорию и все файлы в ней
func ProtectDirectory(dirPath string) error {
// Устанавливаем права на директорию
if err := os.Chmod(dirPath, 0700); err != nil {
return err
}
// Защищаем все файлы в директории
entries, err := os.ReadDir(dirPath)
if err != nil {
return err
}
for _, entry := range entries {
if !entry.IsDir() {
filePath := filepath.Join(dirPath, entry.Name())
if err := SetFilePermissions(filePath); err != nil {
return err
}
}
}
return nil
}

View File

@@ -1,190 +0,0 @@
package security
import (
"fmt"
"os/exec"
"runtime"
"strings"
)
// KillSwitch управляет блокировкой интернета при разрыве VPN
type KillSwitch struct {
enabled bool
originalRules []string
}
// NewKillSwitch создает новый Kill Switch
func NewKillSwitch() *KillSwitch {
return &KillSwitch{
enabled: false,
originalRules: []string{},
}
}
// Enable включает Kill Switch (блокирует весь трафик кроме VPN)
func (ks *KillSwitch) Enable(vpnInterface string, allowedIPs []string) error {
if runtime.GOOS != "windows" {
return ks.enableUnix(vpnInterface, allowedIPs)
}
return ks.enableWindows(vpnInterface, allowedIPs)
}
// Disable отключает Kill Switch (восстанавливает нормальный трафик)
func (ks *KillSwitch) Disable() error {
if runtime.GOOS != "windows" {
return ks.disableUnix()
}
return ks.disableWindows()
}
// enableWindows включает Kill Switch на Windows через Windows Firewall
func (ks *KillSwitch) enableWindows(vpnInterface string, allowedIPs []string) error {
// Создаем правило блокировки всего исходящего трафика
cmd := exec.Command("netsh", "advfirewall", "firewall", "add", "rule",
"name=VPN_KillSwitch_Block_Out",
"dir=out",
"action=block",
"enable=yes")
if err := cmd.Run(); err != nil {
return fmt.Errorf("ошибка создания правила блокировки: %w", err)
}
// Создаем правило блокировки всего входящего трафика
cmd = exec.Command("netsh", "advfirewall", "firewall", "add", "rule",
"name=VPN_KillSwitch_Block_In",
"dir=in",
"action=block",
"enable=yes")
if err := cmd.Run(); err != nil {
return fmt.Errorf("ошибка создания правила блокировки: %w", err)
}
// Разрешаем локальный трафик (127.0.0.1)
cmd = exec.Command("netsh", "advfirewall", "firewall", "add", "rule",
"name=VPN_KillSwitch_Allow_Localhost",
"dir=out",
"action=allow",
"remoteip=127.0.0.1",
"enable=yes")
cmd.Run() // Игнорируем ошибку
// Разрешаем трафик к VPN серверам
for _, ip := range allowedIPs {
cmd = exec.Command("netsh", "advfirewall", "firewall", "add", "rule",
fmt.Sprintf("name=VPN_KillSwitch_Allow_%s", strings.ReplaceAll(ip, ".", "_")),
"dir=out",
"action=allow",
fmt.Sprintf("remoteip=%s", ip),
"enable=yes")
cmd.Run() // Игнорируем ошибки для отдельных IP
}
ks.enabled = true
return nil
}
// disableWindows отключает Kill Switch на Windows
func (ks *KillSwitch) disableWindows() error {
if !ks.enabled {
return nil
}
// Удаляем все правила Kill Switch
rules := []string{
"VPN_KillSwitch_Block_Out",
"VPN_KillSwitch_Block_In",
"VPN_KillSwitch_Allow_Localhost",
}
for _, rule := range rules {
cmd := exec.Command("netsh", "advfirewall", "firewall", "delete", "rule",
fmt.Sprintf("name=%s", rule))
cmd.Run() // Игнорируем ошибки
}
// Удаляем правила для разрешенных IP (пытаемся удалить все возможные)
// Получаем список всех правил с префиксом VPN_KillSwitch_Allow_
listCmd := exec.Command("netsh", "advfirewall", "firewall", "show", "rule", "name=all")
output, _ := listCmd.Output()
lines := strings.Split(string(output), "\n")
for _, line := range lines {
if strings.Contains(line, "VPN_KillSwitch_Allow_") {
parts := strings.Split(line, ":")
if len(parts) >= 2 {
ruleName := strings.TrimSpace(parts[1])
deleteCmd := exec.Command("netsh", "advfirewall", "firewall", "delete", "rule",
fmt.Sprintf("name=%s", ruleName))
deleteCmd.Run()
}
}
}
ks.enabled = false
return nil
}
// enableUnix включает Kill Switch на Unix системах через iptables
func (ks *KillSwitch) enableUnix(vpnInterface string, allowedIPs []string) error {
// Сохраняем текущие правила
cmd := exec.Command("iptables-save")
output, err := cmd.Output()
if err != nil {
return fmt.Errorf("ошибка сохранения правил iptables: %w", err)
}
ks.originalRules = strings.Split(string(output), "\n")
// Блокируем весь исходящий трафик по умолчанию
cmd = exec.Command("iptables", "-P", "OUTPUT", "DROP")
if err := cmd.Run(); err != nil {
return fmt.Errorf("ошибка установки политики OUTPUT: %w", err)
}
// Разрешаем локальный трафик
cmd = exec.Command("iptables", "-A", "OUTPUT", "-o", "lo", "-j", "ACCEPT")
cmd.Run()
// Разрешаем трафик через VPN интерфейс
if vpnInterface != "" {
cmd = exec.Command("iptables", "-A", "OUTPUT", "-o", vpnInterface, "-j", "ACCEPT")
cmd.Run()
}
// Разрешаем трафик к VPN серверам
for _, ip := range allowedIPs {
cmd = exec.Command("iptables", "-A", "OUTPUT", "-d", ip, "-j", "ACCEPT")
cmd.Run()
}
ks.enabled = true
return nil
}
// disableUnix отключает Kill Switch на Unix системах
func (ks *KillSwitch) disableUnix() error {
if !ks.enabled {
return nil
}
// Восстанавливаем политику по умолчанию
cmd := exec.Command("iptables", "-P", "OUTPUT", "ACCEPT")
if err := cmd.Run(); err != nil {
return fmt.Errorf("ошибка восстановления политики: %w", err)
}
// Очищаем цепочку OUTPUT
cmd = exec.Command("iptables", "-F", "OUTPUT")
cmd.Run()
ks.enabled = false
return nil
}
// IsEnabled проверяет, включен ли Kill Switch
func (ks *KillSwitch) IsEnabled() bool {
return ks.enabled
}

View File

@@ -1,237 +0,0 @@
package security
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
)
// SecurityManager управляет всеми аспектами безопасности VPN
type SecurityManager struct {
killSwitch *KillSwitch
dnsProtection *DNSProtection
configDir string
password string
}
// SecurityConfig хранит настройки безопасности
type SecurityConfig struct {
KillSwitchEnabled bool `json:"kill_switch_enabled"`
DNSProtectionEnabled bool `json:"dns_protection_enabled"`
EncryptionEnabled bool `json:"encryption_enabled"`
VPNDNSServers []string `json:"vpn_dns_servers"`
AllowedVPNIPs []string `json:"allowed_vpn_ips"`
}
// NewSecurityManager создает новый менеджер безопасности
func NewSecurityManager(configDir string, password string) *SecurityManager {
return &SecurityManager{
killSwitch: NewKillSwitch(),
dnsProtection: NewDNSProtection(),
configDir: configDir,
password: password,
}
}
// LoadSecurityConfig загружает конфигурацию безопасности
func (sm *SecurityManager) LoadSecurityConfig() (*SecurityConfig, error) {
configPath := filepath.Join(sm.configDir, "security.json")
// Если файл не существует, создаем дефолтную конфигурацию с ОТКЛЮЧЕННЫМ Kill Switch
if _, err := os.Stat(configPath); os.IsNotExist(err) {
defaultConfig := &SecurityConfig{
KillSwitchEnabled: false, // ОТКЛЮЧЕНО ПО УМОЛЧАНИЮ!
DNSProtectionEnabled: true,
EncryptionEnabled: true,
VPNDNSServers: []string{"1.1.1.1", "8.8.8.8"},
AllowedVPNIPs: []string{},
}
sm.SaveSecurityConfig(defaultConfig)
return defaultConfig, nil
}
data, err := os.ReadFile(configPath)
if err != nil {
return nil, fmt.Errorf("ошибка чтения конфигурации безопасности: %w", err)
}
var config SecurityConfig
if err := json.Unmarshal(data, &config); err != nil {
return nil, fmt.Errorf("ошибка парсинга конфигурации: %w", err)
}
return &config, nil
}
// SaveSecurityConfig сохраняет конфигурацию безопасности
func (sm *SecurityManager) SaveSecurityConfig(config *SecurityConfig) error {
configPath := filepath.Join(sm.configDir, "security.json")
data, err := json.MarshalIndent(config, "", " ")
if err != nil {
return fmt.Errorf("ошибка сериализации конфигурации: %w", err)
}
if err := os.WriteFile(configPath, data, 0600); err != nil {
return fmt.Errorf("ошибка сохранения конфигурации: %w", err)
}
return nil
}
// EnableProtection включает все защитные механизмы
func (sm *SecurityManager) EnableProtection(vpnInterface string, vpnServerIP string) error {
config, err := sm.LoadSecurityConfig()
if err != nil {
return err
}
// KILL SWITCH ПОЛНОСТЬЮ ОТКЛЮЧЕН - НЕ ИСПОЛЬЗУЕТСЯ!
// Оставляем только защиту DNS
// Включаем защиту DNS
if config.DNSProtectionEnabled {
if err := sm.dnsProtection.Enable(config.VPNDNSServers); err != nil {
fmt.Printf("⚠ Предупреждение: не удалось включить защиту DNS: %v\n", err)
} else {
fmt.Println("✓ Защита DNS включена (предотвращение DNS утечек)")
}
}
return nil
}
// DisableProtection отключает все защитные механизмы
func (sm *SecurityManager) DisableProtection() error {
var errors []error
// KILL SWITCH НЕ ИСПОЛЬЗУЕТСЯ - пропускаем
// Отключаем защиту DNS
if sm.dnsProtection.IsEnabled() {
if err := sm.dnsProtection.Disable(); err != nil {
errors = append(errors, fmt.Errorf("ошибка отключения защиты DNS: %w", err))
} else {
fmt.Println("✓ Защита DNS отключена")
}
}
if len(errors) > 0 {
return fmt.Errorf("ошибки при отключении защиты: %v", errors)
}
return nil
}
// EncryptConfigFile шифрует файл конфигурации
func (sm *SecurityManager) EncryptConfigFile(filePath string) error {
// Читаем файл
data, err := os.ReadFile(filePath)
if err != nil {
return fmt.Errorf("ошибка чтения файла: %w", err)
}
// Шифруем
encrypted, err := Encrypt(data, sm.password)
if err != nil {
return fmt.Errorf("ошибка шифрования: %w", err)
}
// Сохраняем зашифрованный файл
encryptedPath := filePath + ".encrypted"
if err := os.WriteFile(encryptedPath, []byte(encrypted), 0600); err != nil {
return fmt.Errorf("ошибка сохранения зашифрованного файла: %w", err)
}
// Безопасно удаляем оригинальный файл
if err := SecureDelete(filePath); err != nil {
return fmt.Errorf("ошибка удаления оригинального файла: %w", err)
}
// Переименовываем зашифрованный файл
if err := os.Rename(encryptedPath, filePath); err != nil {
return fmt.Errorf("ошибка переименования файла: %w", err)
}
return nil
}
// DecryptConfigFile расшифровывает файл конфигурации
func (sm *SecurityManager) DecryptConfigFile(filePath string) ([]byte, error) {
// Читаем зашифрованный файл
encryptedData, err := os.ReadFile(filePath)
if err != nil {
return nil, fmt.Errorf("ошибка чтения файла: %w", err)
}
// Расшифровываем
decrypted, err := Decrypt(string(encryptedData), sm.password)
if err != nil {
return nil, fmt.Errorf("ошибка расшифровки: %w", err)
}
return decrypted, nil
}
// ProtectConfigDirectory защищает директорию с конфигурациями
func (sm *SecurityManager) ProtectConfigDirectory() error {
if err := ProtectDirectory(sm.configDir); err != nil {
return fmt.Errorf("ошибка защиты директории: %w", err)
}
fmt.Printf("✓ Директория конфигураций защищена: %s\n", sm.configDir)
return nil
}
// GetSecurityStatus возвращает текущий статус безопасности
func (sm *SecurityManager) GetSecurityStatus() map[string]interface{} {
config, _ := sm.LoadSecurityConfig()
currentDNS, _ := GetCurrentDNS()
return map[string]interface{}{
"kill_switch_enabled": sm.killSwitch.IsEnabled(),
"dns_protection_enabled": sm.dnsProtection.IsEnabled(),
"encryption_enabled": config.EncryptionEnabled,
"current_dns_servers": currentDNS,
"configured_vpn_dns": config.VPNDNSServers,
"config_dir_protected": true,
}
}
// CheckForLeaks проверяет наличие утечек
func (sm *SecurityManager) CheckForLeaks() ([]string, error) {
var leaks []string
// Проверяем DNS утечки
currentDNS, err := GetCurrentDNS()
if err == nil {
config, _ := sm.LoadSecurityConfig()
if config.DNSProtectionEnabled {
// Проверяем, что используются только VPN DNS
for _, dns := range currentDNS {
isVPNDNS := false
for _, vpnDNS := range config.VPNDNSServers {
if dns == vpnDNS {
isVPNDNS = true
break
}
}
if !isVPNDNS {
leaks = append(leaks, fmt.Sprintf("DNS утечка: используется %s вместо VPN DNS", dns))
}
}
}
}
// Проверяем Kill Switch
if !sm.killSwitch.IsEnabled() {
config, _ := sm.LoadSecurityConfig()
if config.KillSwitchEnabled {
leaks = append(leaks, "Kill Switch не активен, возможна утечка трафика при разрыве VPN")
}
}
return leaks, nil
}

View File

@@ -16,17 +16,16 @@ import (
"vpn-client/internal/config" "vpn-client/internal/config"
"vpn-client/internal/logger" "vpn-client/internal/logger"
"vpn-client/internal/proxy" "vpn-client/internal/wireguard"
"vpn-client/internal/security"
) )
// XrayConfig представляет конфигурацию Xray // XrayConfig представляет конфигурацию Xray
type XrayConfig struct { type XrayConfig struct {
Log LogConfig `json:"log"` Log LogConfig `json:"log"`
Inbounds []InboundConfig `json:"inbounds"` Inbounds []InboundConfig `json:"inbounds"`
Outbounds []OutboundConfig `json:"outbounds"` Outbounds []OutboundConfig `json:"outbounds"`
Stats interface{} `json:"stats"` Stats interface{} `json:"stats"`
Policy PolicyConfig `json:"policy"` Policy PolicyConfig `json:"policy"`
} }
type LogConfig struct { type LogConfig struct {
@@ -37,11 +36,11 @@ type LogConfig struct {
} }
type InboundConfig struct { type InboundConfig struct {
Port int `json:"port"` Port int `json:"port"`
Protocol string `json:"protocol"` Protocol string `json:"protocol"`
Settings InboundSettings `json:"settings"` Settings InboundSettings `json:"settings"`
Sniffing SniffingConfig `json:"sniffing"` Sniffing SniffingConfig `json:"sniffing"`
Tag string `json:"tag"` Tag string `json:"tag"`
} }
type InboundSettings struct { type InboundSettings struct {
@@ -56,8 +55,8 @@ type SniffingConfig struct {
} }
type OutboundConfig struct { type OutboundConfig struct {
Protocol string `json:"protocol"` Protocol string `json:"protocol"`
Tag string `json:"tag"` Tag string `json:"tag"`
Settings OutboundSettings `json:"settings"` Settings OutboundSettings `json:"settings"`
StreamSettings StreamSettings `json:"streamSettings"` StreamSettings StreamSettings `json:"streamSettings"`
} }
@@ -79,13 +78,13 @@ type UserConfig struct {
} }
type StreamSettings struct { type StreamSettings struct {
Network string `json:"network"` Network string `json:"network"`
Security string `json:"security,omitempty"` Security string `json:"security,omitempty"`
TLSSettings *TLSSettings `json:"tlsSettings,omitempty"` TLSSettings *TLSSettings `json:"tlsSettings,omitempty"`
RealitySettings *RealitySettings `json:"realitySettings,omitempty"` RealitySettings *RealitySettings `json:"realitySettings,omitempty"`
WSSettings *WSSettings `json:"wsSettings,omitempty"` WSSettings *WSSettings `json:"wsSettings,omitempty"`
GRPCSettings *GRPCSettings `json:"grpcSettings,omitempty"` GRPCSettings *GRPCSettings `json:"grpcSettings,omitempty"`
HTTPSettings *HTTPSettings `json:"httpSettings,omitempty"` HTTPSettings *HTTPSettings `json:"httpSettings,omitempty"`
} }
type TLSSettings struct { type TLSSettings struct {
@@ -334,35 +333,11 @@ func getParam(params map[string]string, key, defaultValue string) string {
} }
// Connect подключается к VLESS серверу // Connect подключается к VLESS серверу
func Connect(configName string, logsDir string) error { func Connect(configName string, logsDir, xrayDir string) error {
// Загружаем настройки if err := disconnectExistingConnection(logsDir); err != nil {
settings, err := config.LoadSettings() return err
if err != nil {
return fmt.Errorf("ошибка загрузки настроек: %w", err)
} }
// Определяем директорию и имя исполняемого файла
var coreDir, coreExe, coreName string
if settings.CoreType == "v2ray" {
coreDir = config.V2RayDir
coreName = "V2Ray"
if runtime.GOOS == "windows" {
coreExe = "v2ray.exe"
} else {
coreExe = "v2ray"
}
} else {
coreDir = config.XrayDir
coreName = "Xray"
if runtime.GOOS == "windows" {
coreExe = "xray.exe"
} else {
coreExe = "xray"
}
}
corePath := filepath.Join(coreDir, coreExe)
// Загружаем конфигурации // Загружаем конфигурации
configs, err := config.LoadConfigs() configs, err := config.LoadConfigs()
if err != nil { if err != nil {
@@ -382,15 +357,15 @@ func Connect(configName string, logsDir string) error {
return fmt.Errorf("конфиг '%s' не найден", configName) return fmt.Errorf("конфиг '%s' не найден", configName)
} }
// Создаем конфигурацию // Создаем конфигурацию Xray
coreConfig, err := ParseVLESSURL(vlessConfig.URL, logsDir) xrayConfig, err := ParseVLESSURL(vlessConfig.URL, logsDir)
if err != nil { if err != nil {
return fmt.Errorf("ошибка парсинга VLESS URL: %w", err) return fmt.Errorf("ошибка парсинга VLESS URL: %w", err)
} }
// Сохраняем конфигурацию // Сохраняем конфигурацию
configPath := filepath.Join(config.ConfigDir, "xray_config.json") configPath := filepath.Join(config.ConfigDir, "xray_config.json")
data, err := json.MarshalIndent(coreConfig, "", " ") data, err := json.MarshalIndent(xrayConfig, "", " ")
if err != nil { if err != nil {
return fmt.Errorf("ошибка сериализации конфига: %w", err) return fmt.Errorf("ошибка сериализации конфига: %w", err)
} }
@@ -399,9 +374,15 @@ func Connect(configName string, logsDir string) error {
return fmt.Errorf("ошибка сохранения конфига: %w", err) return fmt.Errorf("ошибка сохранения конфига: %w", err)
} }
// Проверяем наличие исполняемого файла // Путь к xray
if _, err := os.Stat(corePath); os.IsNotExist(err) { xrayExe := "xray"
return fmt.Errorf("%s не найден в %s\n\nПожалуйста, перезапустите приложение для автоматической загрузки %s", coreName, coreDir, coreName) if runtime.GOOS == "windows" {
xrayExe = "xray.exe"
}
xrayPath := filepath.Join(xrayDir, xrayExe)
if xrayPath, err = ensureXrayBinary(xrayDir); err != nil {
return err
} }
// Создаем лог-файл трафика // Создаем лог-файл трафика
@@ -414,70 +395,27 @@ func Connect(configName string, logsDir string) error {
// Записываем заголовок // Записываем заголовок
fmt.Fprintf(logFile, "=== VPN Подключение начато: %s ===\n", time.Now().Format("2006-01-02 15:04:05")) fmt.Fprintf(logFile, "=== VPN Подключение начато: %s ===\n", time.Now().Format("2006-01-02 15:04:05"))
fmt.Fprintf(logFile, "Конфиг: %s\n", configName) fmt.Fprintf(logFile, "Конфиг: %s\n", configName)
fmt.Fprintf(logFile, "Клиент: %s\n", coreName) fmt.Fprintf(logFile, "Клиент: Xray\n")
fmt.Fprintf(logFile, "Прокси: 127.0.0.1:10808\n") fmt.Fprintf(logFile, "Прокси: 127.0.0.1:10808\n")
fmt.Fprintf(logFile, "%s\n\n", strings.Repeat("=", 60)) fmt.Fprintf(logFile, "%s\n\n", strings.Repeat("=", 60))
// Запускаем ядро // Запускаем xray
cmd := exec.Command(corePath, "run", "-c", configPath) cmd := exec.Command(xrayPath, "run", "-c", configPath)
cmd.Stdout = logFile cmd.Stdout = logFile
cmd.Stderr = logFile cmd.Stderr = logFile
// На Windows создаем процесс в новой группе, чтобы Ctrl+C не убивал его
if runtime.GOOS == "windows" {
cmd.SysProcAttr = &syscall.SysProcAttr{
CreationFlags: syscall.CREATE_NEW_PROCESS_GROUP,
}
}
if err := cmd.Start(); err != nil { if err := cmd.Start(); err != nil {
logFile.Close() logFile.Close()
return fmt.Errorf("ошибка запуска %s: %w", coreName, err) return fmt.Errorf("ошибка запуска xray: %w", err)
} }
// Ждем немного для проверки запуска // Ждем немного для проверки запуска
time.Sleep(2 * time.Second) time.Sleep(3 * time.Second)
// Проверяем, что процесс еще работает (для Windows используем другой метод) // Проверяем, что процесс еще работает
processRunning := true if err := cmd.Process.Signal(os.Signal(nil)); err != nil {
if runtime.GOOS == "windows" {
// На Windows просто проверяем, что процесс существует
process, err := os.FindProcess(cmd.Process.Pid)
if err != nil {
processRunning = false
} else {
// Пытаемся отправить сигнал 0 (проверка существования)
err = process.Signal(syscall.Signal(0))
// На Windows это всегда возвращает ошибку, поэтому просто считаем что процесс работает
processRunning = true
}
} else {
// На Unix используем стандартную проверку
err := cmd.Process.Signal(syscall.Signal(0))
processRunning = (err == nil)
}
if !processRunning {
logFile.Close() logFile.Close()
return fmt.Errorf("процесс xray завершился с ошибкой")
// Читаем логи для диагностики
errorLog := filepath.Join(logsDir, "vless_error.log")
if errorData, readErr := os.ReadFile(errorLog); readErr == nil && len(errorData) > 0 {
lines := strings.Split(string(errorData), "\n")
errorLines := []string{}
for i := len(lines) - 1; i >= 0 && len(errorLines) < 10; i-- {
if strings.TrimSpace(lines[i]) != "" {
errorLines = append([]string{lines[i]}, errorLines...)
}
}
if len(errorLines) > 0 {
return fmt.Errorf("процесс %s завершился с ошибкой.\n\nПоследние строки из лога ошибок:\n%s\n\nПолный лог: %s",
coreName, strings.Join(errorLines, "\n"), errorLog)
}
}
return fmt.Errorf("процесс %s завершился с ошибкой.\nПроверьте логи в: %s", coreName, errorLog)
} }
// Сохраняем состояние // Сохраняем состояние
@@ -486,7 +424,7 @@ func Connect(configName string, logsDir string) error {
ConfigName: configName, ConfigName: configName,
ConfigType: "vless", ConfigType: "vless",
StartTime: time.Now().Format(time.RFC3339), StartTime: time.Now().Format(time.RFC3339),
Interface: strings.ToLower(coreName), Interface: "xray",
ProcessPID: cmd.Process.Pid, ProcessPID: cmd.Process.Pid,
LogFile: trafficLog, LogFile: trafficLog,
} }
@@ -499,58 +437,10 @@ func Connect(configName string, logsDir string) error {
// Логируем успешное подключение // Логируем успешное подключение
logPath := logger.GetLogPath(logsDir, "vless") logPath := logger.GetLogPath(logsDir, "vless")
logger.LogMessage(logPath, fmt.Sprintf("Успешно подключено к '%s' через %s (PID: %d, Лог: %s)", configName, coreName, cmd.Process.Pid, trafficLog)) logger.LogMessage(logPath, fmt.Sprintf("Успешно подключено к '%s' (PID: %d, Лог: %s)", configName, cmd.Process.Pid, trafficLog))
fmt.Printf("✓ Подключено к '%s' через %s\n", configName, coreName) fmt.Printf("✓ Подключено к '%s'\n", configName)
fmt.Printf("SOCKS5 прокси: 127.0.0.1:10808\n") fmt.Printf("SOCKS5 прокси: 127.0.0.1:10808\n")
// Автоматически настраиваем системный прокси
fmt.Println("\n" + strings.Repeat("─", 60))
fmt.Println("Настройка системного прокси для работы VPN по всему ПК...")
if err := proxy.EnableSystemProxy("127.0.0.1:10808"); err != nil {
fmt.Printf("⚠ Не удалось настроить системный прокси: %v\n", err)
fmt.Println("Вы можете настроить его вручную в настройках Windows")
} else {
fmt.Println("✓ Системный прокси настроен - VPN работает для всех приложений")
}
// Включаем защитные механизмы (БЕЗ Kill Switch!)
fmt.Println("\nНастройка защитных механизмов...")
secManager := security.NewSecurityManager(config.ConfigDir, "default-password")
secConfig, _ := secManager.LoadSecurityConfig()
// KILL SWITCH ПОЛНОСТЬЮ ОТКЛЮЧЕН!
// Включаем только защиту DNS если она включена
if secConfig != nil && secConfig.DNSProtectionEnabled {
dnsProtection := security.NewDNSProtection()
if err := dnsProtection.Enable(secConfig.VPNDNSServers); err != nil {
fmt.Printf("⚠ Предупреждение: не удалось включить защиту DNS: %v\n", err)
} else {
fmt.Println("✓ Защита DNS включена")
}
} else {
fmt.Println(" Защита DNS отключена")
}
// Защищаем директорию конфигураций
if err := secManager.ProtectConfigDirectory(); err != nil {
fmt.Printf("⚠ Предупреждение: не удалось защитить директорию конфигураций: %v\n", err)
}
// Проверяем утечки
fmt.Println("\nПроверка безопасности...")
leaks, err := secManager.CheckForLeaks()
if err == nil && len(leaks) == 0 {
fmt.Println("✓ Утечек не обнаружено - соединение безопасно")
} else if len(leaks) > 0 {
fmt.Println("⚠ Обнаружены потенциальные утечки:")
for _, leak := range leaks {
fmt.Printf(" - %s\n", leak)
}
}
fmt.Printf("\nЛоги трафика:\n") fmt.Printf("\nЛоги трафика:\n")
fmt.Printf(" - Основной: %s\n", trafficLog) fmt.Printf(" - Основной: %s\n", trafficLog)
fmt.Printf(" - Доступ (IP): %s\n", filepath.Join(logsDir, "vless_access.log")) fmt.Printf(" - Доступ (IP): %s\n", filepath.Join(logsDir, "vless_access.log"))
@@ -559,35 +449,38 @@ func Connect(configName string, logsDir string) error {
return nil return nil
} }
// extractServerIP извлекает IP адрес сервера из VLESS URL func disconnectExistingConnection(logsDir string) error {
func extractServerIP(vlessURL string) string { state, err := config.LoadState()
urlStr := strings.TrimPrefix(vlessURL, "vless://") if err != nil {
return fmt.Errorf("ошибка загрузки состояния подключения: %w", err)
if idx := strings.Index(urlStr, "#"); idx != -1 {
urlStr = urlStr[:idx]
} }
if idx := strings.Index(urlStr, "?"); idx != -1 { if state == nil || !state.Connected {
urlStr = urlStr[:idx] return nil
} }
parts := strings.Split(urlStr, "@") switch state.ConfigType {
if len(parts) != 2 { case "wireguard":
return "" if state.Interface != "" {
if err := wireguard.Disconnect(state.Interface, logsDir); err != nil {
return fmt.Errorf("ошибка отключения текущего WireGuard: %w", err)
}
}
case "vless":
if state.ProcessPID > 0 {
process, err := os.FindProcess(state.ProcessPID)
if err == nil {
if runtime.GOOS == "windows" {
_ = process.Kill()
} else {
_ = process.Signal(syscall.SIGTERM)
}
time.Sleep(1 * time.Second)
}
}
} }
serverPort := parts[1] return config.SaveState(&config.ConnectionState{})
var server string
if strings.Contains(serverPort, "[") {
endIdx := strings.Index(serverPort, "]")
server = serverPort[1:endIdx]
} else {
lastColon := strings.LastIndex(serverPort, ":")
server = serverPort[:lastColon]
}
return server
} }
// PingServer проверяет доступность VLESS сервера // PingServer проверяет доступность VLESS сервера

View File

@@ -0,0 +1,224 @@
package vless
import (
"archive/zip"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"runtime"
"strings"
"time"
)
const xrayLatestReleaseAPI = "https://api.github.com/repos/XTLS/Xray-core/releases/latest"
type xrayRelease struct {
TagName string `json:"tag_name"`
Assets []xrayReleaseAsset `json:"assets"`
}
type xrayReleaseAsset struct {
Name string `json:"name"`
BrowserDownloadURL string `json:"browser_download_url"`
}
func ensureXrayBinary(xrayDir string) (string, error) {
exeName := "xray"
if runtime.GOOS == "windows" {
exeName = "xray.exe"
}
xrayPath := filepath.Join(xrayDir, exeName)
if _, err := os.Stat(xrayPath); err == nil {
return xrayPath, nil
}
if err := os.MkdirAll(xrayDir, 0755); err != nil {
return "", fmt.Errorf("ошибка создания папки xray: %w", err)
}
asset, err := fetchLatestXrayAsset()
if err != nil {
return "", err
}
archivePath, err := downloadXrayArchive(asset.BrowserDownloadURL, xrayDir, asset.Name)
if err != nil {
return "", err
}
defer os.Remove(archivePath)
if err := unzipXrayArchive(archivePath, xrayDir); err != nil {
return "", err
}
if runtime.GOOS != "windows" {
_ = os.Chmod(xrayPath, 0755)
}
if _, err := os.Stat(xrayPath); err != nil {
return "", fmt.Errorf("xray не найден после распаковки в %s", xrayDir)
}
return xrayPath, nil
}
func fetchLatestXrayAsset() (*xrayReleaseAsset, error) {
client := &http.Client{Timeout: 45 * time.Second}
req, err := http.NewRequest(http.MethodGet, xrayLatestReleaseAPI, nil)
if err != nil {
return nil, fmt.Errorf("ошибка подготовки запроса релиза Xray: %w", err)
}
req.Header.Set("User-Agent", "go-vpn-client")
req.Header.Set("Accept", "application/vnd.github+json")
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("ошибка получения релиза Xray: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(io.LimitReader(resp.Body, 1024))
return nil, fmt.Errorf("ошибка API Xray: %d %s", resp.StatusCode, strings.TrimSpace(string(body)))
}
var release xrayRelease
if err := json.NewDecoder(resp.Body).Decode(&release); err != nil {
return nil, fmt.Errorf("ошибка разбора релиза Xray: %w", err)
}
for _, asset := range release.Assets {
if matchesXrayAsset(asset.Name) {
return &asset, nil
}
}
return nil, fmt.Errorf("не найден подходящий архив Xray для %s/%s в релизе %s", runtime.GOOS, runtime.GOARCH, release.TagName)
}
func matchesXrayAsset(name string) bool {
lower := strings.ToLower(name)
if !strings.HasSuffix(lower, ".zip") || strings.Contains(lower, ".dgst") {
return false
}
osToken := map[string]string{
"windows": "windows",
"linux": "linux",
"darwin": "macos",
}[runtime.GOOS]
if osToken == "" || !strings.Contains(lower, osToken) {
return false
}
archTokens := map[string][]string{
"amd64": {"64", "amd64", "x64"},
"386": {"32", "386", "x86"},
"arm64": {"arm64", "aarch64"},
"arm": {"arm32", "armv7", "arm"},
}[runtime.GOARCH]
if len(archTokens) == 0 {
return false
}
for _, token := range archTokens {
if strings.Contains(lower, token) {
return true
}
}
return false
}
func downloadXrayArchive(downloadURL, xrayDir, name string) (string, error) {
client := &http.Client{Timeout: 2 * time.Minute}
req, err := http.NewRequest(http.MethodGet, downloadURL, nil)
if err != nil {
return "", fmt.Errorf("ошибка подготовки загрузки Xray: %w", err)
}
req.Header.Set("User-Agent", "go-vpn-client")
resp, err := client.Do(req)
if err != nil {
return "", fmt.Errorf("ошибка загрузки Xray: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("ошибка загрузки Xray: HTTP %d", resp.StatusCode)
}
archivePath := filepath.Join(xrayDir, name)
file, err := os.Create(archivePath)
if err != nil {
return "", fmt.Errorf("ошибка создания архива Xray: %w", err)
}
defer file.Close()
if _, err := io.Copy(file, resp.Body); err != nil {
return "", fmt.Errorf("ошибка сохранения архива Xray: %w", err)
}
return archivePath, nil
}
func unzipXrayArchive(archivePath, targetDir string) error {
reader, err := zip.OpenReader(archivePath)
if err != nil {
return fmt.Errorf("ошибка открытия архива Xray: %w", err)
}
defer reader.Close()
for _, file := range reader.File {
if err := extractZipEntry(file, targetDir); err != nil {
return err
}
}
return nil
}
func extractZipEntry(file *zip.File, targetDir string) error {
cleanName := filepath.Clean(file.Name)
targetPath := filepath.Join(targetDir, cleanName)
absTargetDir, err := filepath.Abs(targetDir)
if err != nil {
return fmt.Errorf("ошибка определения папки Xray: %w", err)
}
absTargetPath, err := filepath.Abs(targetPath)
if err != nil {
return fmt.Errorf("ошибка определения пути Xray: %w", err)
}
if !strings.HasPrefix(absTargetPath, absTargetDir) {
return fmt.Errorf("небезопасный путь в архиве Xray: %s", file.Name)
}
if file.FileInfo().IsDir() {
return os.MkdirAll(absTargetPath, 0755)
}
if err := os.MkdirAll(filepath.Dir(absTargetPath), 0755); err != nil {
return fmt.Errorf("ошибка создания папки Xray: %w", err)
}
in, err := file.Open()
if err != nil {
return fmt.Errorf("ошибка чтения файла из архива Xray: %w", err)
}
defer in.Close()
out, err := os.OpenFile(absTargetPath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, file.Mode())
if err != nil {
return fmt.Errorf("ошибка создания файла Xray: %w", err)
}
defer out.Close()
if _, err := io.Copy(out, in); err != nil {
return fmt.Errorf("ошибка распаковки Xray: %w", err)
}
return nil
}

View File

@@ -9,8 +9,6 @@ import (
"vpn-client/internal/config" "vpn-client/internal/config"
"vpn-client/internal/logger" "vpn-client/internal/logger"
"vpn-client/internal/proxy"
"vpn-client/internal/security"
"vpn-client/internal/wireguard" "vpn-client/internal/wireguard"
) )
@@ -28,17 +26,6 @@ func Disconnect(logsDir string) error {
fmt.Printf("Отключение от '%s'...\n", state.ConfigName) fmt.Printf("Отключение от '%s'...\n", state.ConfigName)
// Отключаем защитные механизмы (только DNS, Kill Switch не используется)
fmt.Println("\nОтключение защитных механизмов...")
dnsProtection := security.NewDNSProtection()
if dnsProtection.IsEnabled() {
if err := dnsProtection.Disable(); err != nil {
fmt.Printf("%s Предупреждение при отключении DNS: %v\n", "⚠", err)
} else {
fmt.Println("✓ Защита DNS отключена")
}
}
// Логируем отключение // Логируем отключение
var logFile string var logFile string
if state.ConfigType == "wireguard" { if state.ConfigType == "wireguard" {
@@ -97,16 +84,6 @@ func Disconnect(logsDir string) error {
return fmt.Errorf("ошибка сохранения состояния: %w", err) return fmt.Errorf("ошибка сохранения состояния: %w", err)
} }
// Отключаем системный прокси если был VLESS
if state.ConfigType == "vless" {
if err := proxy.DisableSystemProxy(); err != nil {
fmt.Printf("%s Не удалось отключить системный прокси: %v\n", "⚠", err)
fmt.Println("Отключите его вручную в настройках Windows")
} else {
fmt.Println("✓ Системный прокси отключен")
}
}
fmt.Println("✓ Отключено от VPN") fmt.Println("✓ Отключено от VPN")
return nil return nil
} }

39
main.go
View File

@@ -1,33 +1,32 @@
package main package main
import ( import (
"fmt" "log"
"os" "os"
"vpn-client/internal/cli" "gioui.org/app"
"vpn-client/internal/config" "vpn-client/internal/config"
"vpn-client/internal/gui" "vpn-client/internal/gui"
) )
func main() { func main() {
// Инициализация конфигурации go func() {
if err := config.Init(); err != nil { if err := config.Init(); err != nil {
fmt.Fprintf(os.Stderr, "Ошибка инициализации: %v\n", err) log.Printf("config init failed: %v", err)
os.Exit(1) os.Exit(1)
} }
// Проверяем аргументы командной строки window := new(app.Window)
if len(os.Args) > 1 && os.Args[1] == "--cli" { window.Option(
// Запуск CLI режима app.Title("Go VPN Client"),
if err := cli.Run(); err != nil { )
fmt.Fprintf(os.Stderr, "Ошибка: %v\n", err)
os.Exit(1) if err := gui.Run(window); err != nil {
log.Printf("gui failed: %v", err)
} }
} else {
// Запуск GUI режима (по умолчанию) os.Exit(0)
if err := gui.Run(); err != nil { }()
fmt.Fprintf(os.Stderr, "Ошибка: %v\n", err)
os.Exit(1) app.Main()
}
}
} }

View File

@@ -1,42 +0,0 @@
package main
import (
"fmt"
"os"
"vpn-client/internal/admin"
"vpn-client/internal/cli"
"vpn-client/internal/config"
"vpn-client/internal/downloader"
)
func main() {
// Проверка прав администратора
admin.RequireAdmin()
// Инициализация конфигурации
if err := config.Init(); err != nil {
fmt.Fprintf(os.Stderr, "Ошибка инициализации: %v\n", err)
os.Exit(1)
}
// Проверка и загрузка Xray и V2Ray
fmt.Println("Проверка наличия Xray и V2Ray...")
if err := downloader.CheckAndDownloadBoth(config.XrayDir, config.V2RayDir); err != nil {
fmt.Fprintf(os.Stderr, "Ошибка загрузки ядер: %v\n", err)
fmt.Println("\nНажмите Enter для выхода...")
fmt.Scanln()
os.Exit(1)
}
// Запуск CLI
if err := cli.Run(); err != nil {
fmt.Fprintf(os.Stderr, "Ошибка: %v\n", err)
fmt.Println("\nНажмите Enter для выхода...")
fmt.Scanln()
os.Exit(1)
}
// Нормальный выход
os.Exit(0)
}

View File

@@ -1,3 +0,0 @@
{
"manifest": "admin.manifest"
}

View File

@@ -1,144 +0,0 @@
// Config file of V2Ray. This file follows standard JSON format, with comments support.
// Uncomment entries below to satisfy your needs. Also read our manual for more detail at
// https://www.v2fly.org/
{
"log": {
// By default, V2Ray writes access log to stdout.
// "access": "/path/to/access/log/file",
// By default, V2Ray write error log to stdout.
// "error": "/path/to/error/log/file",
// Log level, one of "debug", "info", "warning", "error", "none"
"loglevel": "warning"
},
// List of inbound proxy configurations.
"inbounds": [{
// Port to listen on. You may need root access if the value is less than 1024.
"port": 1080,
// IP address to listen on. Change to "0.0.0.0" to listen on all network interfaces.
"listen": "127.0.0.1",
// Tag of the inbound proxy. May be used for routing.
"tag": "socks-inbound",
// Protocol name of inbound proxy.
"protocol": "socks",
// Settings of the protocol. Varies based on protocol.
"settings": {
"auth": "noauth",
"udp": false,
"ip": "127.0.0.1"
},
// Enable sniffing on TCP connection.
"sniffing": {
"enabled": true,
// Target domain will be overriden to the one carried by the connection, if the connection is HTTP or HTTPS.
"destOverride": ["http", "tls"]
}
}],
// List of outbound proxy configurations.
"outbounds": [{
// Protocol name of the outbound proxy.
"protocol": "freedom",
// Settings of the protocol. Varies based on protocol.
"settings": {},
// Tag of the outbound. May be used for routing.
"tag": "direct"
},{
"protocol": "blackhole",
"settings": {},
"tag": "blocked"
}],
// Transport is for global transport settings. If you have multiple transports with same settings
// (say mKCP), you may put it here, instead of in each individual inbound/outbounds.
//"transport": {},
// Routing controls how traffic from inbounds are sent to outbounds.
"routing": {
"domainStrategy": "IPOnDemand",
"rules":[
{
// Blocks access to private IPs. Remove this if you want to access your router.
"type": "field",
"ip": ["geoip:private"],
"outboundTag": "blocked"
},
{
// Blocks major ads.
"type": "field",
"domain": ["geosite:category-ads"],
"outboundTag": "blocked"
}
]
},
// Dns settings for domain resolution.
"dns": {
// Static hosts, similar to hosts file.
"hosts": {
// Match v2fly.org to another domain on CloudFlare. This domain will be used when querying IPs for v2fly.org.
"domain:v2fly.org": "www.vicemc.net",
// The following settings help to eliminate DNS poisoning in mainland China.
// It is safe to comment these out if this is not the case for you.
"domain:github.io": "pages.github.com",
"domain:wikipedia.org": "www.wikimedia.org",
"domain:shadowsocks.org": "electronicsrealm.com"
},
"servers": [
"1.1.1.1",
{
"address": "114.114.114.114",
"port": 53,
// List of domains that use this DNS first.
"domains": [
"geosite:cn"
]
},
"8.8.8.8",
"localhost"
]
},
// Policy controls some internal behavior of how V2Ray handles connections.
// It may be on connection level by user levels in 'levels', or global settings in 'system.'
"policy": {
// Connection policys by user levels
"levels": {
"0": {
"uplinkOnly": 0,
"downlinkOnly": 0
}
},
"system": {
"statsInboundUplink": false,
"statsInboundDownlink": false,
"statsOutboundUplink": false,
"statsOutboundDownlink": false
}
},
// Stats enables internal stats counter.
// This setting can be used together with Policy and Api.
//"stats":{},
// Api enables gRPC APIs for external programs to communicate with V2Ray instance.
//"api": {
//"tag": "api",
//"services": [
// "HandlerService",
// "LoggerService",
// "StatsService"
//]
//},
// You may add other entries to the configuration, but they will not be recognized by V2Ray.
"other": {}
}

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@@ -1,25 +0,0 @@
{
"log": {
"loglevel": "warning"
},
"inbounds": [{
"port": 1080,
"listen": "127.0.0.1",
"protocol": "socks",
"settings": {
"auth": "noauth",
"udp": false,
"ip": "127.0.0.1"
}
}],
"outbounds": [{
"protocol": "freedom",
"settings": {},
"tag": "direct"
}],
"policy": {
"levels": {
"0": {"uplinkOnly": 0}
}
}
}

View File

@@ -1,32 +0,0 @@
{
"inbounds": [{
"port": 10086,
"protocol": "vmess",
"settings": {
"clients": [
{
"id": "23ad6b10-8d1a-40f7-8ad0-e3e35cd38297",
"level": 1,
"alterId": 64
}
]
}
}],
"outbounds": [{
"protocol": "freedom",
"settings": {}
},{
"protocol": "blackhole",
"settings": {},
"tag": "blocked"
}],
"routing": {
"rules": [
{
"type": "field",
"ip": ["geoip:private"],
"outboundTag": "blocked"
}
]
}
}

View File

@@ -1,373 +0,0 @@
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.

View File

@@ -1,147 +0,0 @@
# Project X
[Project X](https://github.com/XTLS) originates from XTLS protocol, providing a set of network tools such as [Xray-core](https://github.com/XTLS/Xray-core) and [REALITY](https://github.com/XTLS/REALITY).
[README](https://github.com/XTLS/Xray-core#readme) is open, so feel free to submit your project [here](https://github.com/XTLS/Xray-core/pulls).
## Donation & NFTs
- **ETH/USDT/USDC: `0xDc3Fe44F0f25D13CACb1C4896CD0D321df3146Ee`**
- **Project X NFT: [Announcement of NFTs by Project X](https://github.com/XTLS/Xray-core/discussions/3633)**
- **REALITY NFT: [XHTTP: Beyond REALITY](https://github.com/XTLS/Xray-core/discussions/4113)**
## License
[Mozilla Public License Version 2.0](https://github.com/XTLS/Xray-core/blob/main/LICENSE)
## Documentation
[Project X Official Website](https://xtls.github.io)
## Telegram
[Project X](https://t.me/projectXray)
[Project X Channel](https://t.me/projectXtls)
[Project VLESS](https://t.me/projectVless) (non-Chinese)
## Installation
- Linux Script
- [XTLS/Xray-install](https://github.com/XTLS/Xray-install) (**Official**)
- [tempest](https://github.com/team-cloudchaser/tempest) (supports [`systemd`](https://systemd.io) and [OpenRC](https://github.com/OpenRC/openrc); Linux-only)
- Docker
- [ghcr.io/xtls/xray-core](https://ghcr.io/xtls/xray-core) (**Official**)
- [teddysun/xray](https://hub.docker.com/r/teddysun/xray)
- [wulabing/xray_docker](https://github.com/wulabing/xray_docker)
- Web Panel - **WARNING: Please DO NOT USE plain HTTP panels like 3X-UI**, as they are believed to be bribed by Iran GFW for supporting plain HTTP by default and refused to change (https://github.com/XTLS/Xray-core/pull/3884#issuecomment-2439595331), which has already put many users' data security in danger in the past few years. **If you are already using 3X-UI, please switch to the following panels, which are verified to support HTTPS and SSH port forwarding only:**
- [Marzban](https://github.com/Gozargah/Marzban)
- [Xray-UI](https://github.com/qist/xray-ui)
- [Hiddify](https://github.com/hiddify/Hiddify-Manager)
- One Click
- [Xray-REALITY](https://github.com/zxcvos/Xray-script), [xray-reality](https://github.com/sajjaddg/xray-reality), [reality-ezpz](https://github.com/aleskxyz/reality-ezpz)
- [Xray_bash_onekey](https://github.com/hello-yunshu/Xray_bash_onekey), [XTool](https://github.com/LordPenguin666/XTool)
- [v2ray-agent](https://github.com/mack-a/v2ray-agent), [Xray_onekey](https://github.com/wulabing/Xray_onekey), [ProxySU](https://github.com/proxysu/ProxySU)
- Magisk
- [Xray4Magisk](https://github.com/Asterisk4Magisk/Xray4Magisk)
- [Xray_For_Magisk](https://github.com/E7KMbb/Xray_For_Magisk)
- Homebrew
- `brew install xray`
## Usage
- Example
- [VLESS-XTLS-uTLS-REALITY](https://github.com/XTLS/REALITY#readme)
- [VLESS-TCP-XTLS-Vision](https://github.com/XTLS/Xray-examples/tree/main/VLESS-TCP-XTLS-Vision)
- [All-in-One-fallbacks-Nginx](https://github.com/XTLS/Xray-examples/tree/main/All-in-One-fallbacks-Nginx)
- Xray-examples
- [XTLS/Xray-examples](https://github.com/XTLS/Xray-examples)
- [chika0801/Xray-examples](https://github.com/chika0801/Xray-examples)
- [lxhao61/integrated-examples](https://github.com/lxhao61/integrated-examples)
- Tutorial
- [XTLS Vision](https://github.com/chika0801/Xray-install)
- [REALITY (English)](https://cscot.pages.dev/2023/03/02/Xray-REALITY-tutorial/)
- [XTLS-Iran-Reality (English)](https://github.com/SasukeFreestyle/XTLS-Iran-Reality)
- [Xray REALITY with 'steal oneself' (English)](https://computerscot.github.io/vless-xtls-utls-reality-steal-oneself.html)
- [Xray with WireGuard inbound (English)](https://g800.pages.dev/wireguard)
## GUI Clients
- OpenWrt
- [PassWall](https://github.com/xiaorouji/openwrt-passwall), [PassWall 2](https://github.com/xiaorouji/openwrt-passwall2)
- [ShadowSocksR Plus+](https://github.com/fw876/helloworld)
- [luci-app-xray](https://github.com/yichya/luci-app-xray) ([openwrt-xray](https://github.com/yichya/openwrt-xray))
- Windows
- [v2rayN](https://github.com/2dust/v2rayN)
- [Furious](https://github.com/LorenEteval/Furious)
- [Invisible Man - Xray](https://github.com/InvisibleManVPN/InvisibleMan-XRayClient)
- Android
- [v2rayNG](https://github.com/2dust/v2rayNG)
- [X-flutter](https://github.com/XTLS/X-flutter)
- [SaeedDev94/Xray](https://github.com/SaeedDev94/Xray)
- iOS & macOS arm64
- [FoXray](https://apps.apple.com/app/foxray/id6448898396)
- [Streisand](https://apps.apple.com/app/streisand/id6450534064)
- macOS arm64 & x64
- [V2rayU](https://github.com/yanue/V2rayU)
- [V2RayXS](https://github.com/tzmax/V2RayXS)
- [Furious](https://github.com/LorenEteval/Furious)
- [FoXray](https://apps.apple.com/app/foxray/id6448898396)
- Linux
- [v2rayA](https://github.com/v2rayA/v2rayA)
- [Furious](https://github.com/LorenEteval/Furious)
## Others that support VLESS, XTLS, REALITY, XUDP, PLUX...
- iOS & macOS arm64
- [Shadowrocket](https://apps.apple.com/app/shadowrocket/id932747118)
- Xray Tools
- [xray-knife](https://github.com/lilendian0x00/xray-knife)
- Xray Wrapper
- [XTLS/libXray](https://github.com/XTLS/libXray)
- [xtlsapi](https://github.com/hiddify/xtlsapi)
- [AndroidLibXrayLite](https://github.com/2dust/AndroidLibXrayLite)
- [Xray-core-python](https://github.com/LorenEteval/Xray-core-python)
- [xray-api](https://github.com/XVGuardian/xray-api)
- [XrayR](https://github.com/XrayR-project/XrayR)
- [XrayR-release](https://github.com/XrayR-project/XrayR-release)
- [XrayR-V2Board](https://github.com/missuo/XrayR-V2Board)
- [Clash.Meta](https://github.com/MetaCubeX/Clash.Meta)
- [clashN](https://github.com/2dust/clashN)
- [Clash Meta for Android](https://github.com/MetaCubeX/ClashMetaForAndroid)
- [sing-box](https://github.com/SagerNet/sing-box)
## Contributing
[Code of Conduct](https://github.com/XTLS/Xray-core/blob/main/CODE_OF_CONDUCT.md)
## Credits
- [Xray-core v1.0.0](https://github.com/XTLS/Xray-core/releases/tag/v1.0.0) was forked from [v2fly-core 9a03cc5](https://github.com/v2fly/v2ray-core/commit/9a03cc5c98d04cc28320fcee26dbc236b3291256), and we have made & accumulated a huge number of enhancements over time, check [the release notes for each version](https://github.com/XTLS/Xray-core/releases).
- For third-party projects used in [Xray-core](https://github.com/XTLS/Xray-core), check your local or [the latest go.mod](https://github.com/XTLS/Xray-core/blob/main/go.mod).
## Compilation
### Windows (PowerShell)
```powershell
$env:CGO_ENABLED=0
go build -o xray.exe -trimpath -ldflags "-s -w -buildid=" ./main
```
### Linux / macOS
```bash
CGO_ENABLED=0 go build -o xray -trimpath -ldflags "-s -w -buildid=" ./main
```
### Reproducible Releases
```bash
make
```
## Stargazers over time
[![Stargazers over time](https://starchart.cc/XTLS/Xray-core.svg)](https://starchart.cc/XTLS/Xray-core)

Binary file not shown.

File diff suppressed because one or more lines are too long