10 Commits
v1.2 ... GUI

44 changed files with 1782 additions and 48543 deletions

3
.gitignore vendored
View File

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

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,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!**
---
**Приятного использования! 🚀**

View File

@@ -4,14 +4,13 @@ VPN клиент на Golang с поддержкой VLESS протокола и
## 🚀 Возможности
- ✅ Поддержка WireGuard
- ✅ Поддержка VLESS протокола через Xray
- ✅ Управление подписками
- ✅ Автоматическое обновление конфигураций из подписок
- ✅ Тестирование серверов (ping)
- ✅ Детальное логирование
- ✅ Статистика трафика для WireGuard
- ✅ Кроссплатформенность (Windows, Linux, macOS)
* WireGuard (подключение + статистика)
* VLESS через Xray-core
* Поддержка подписок (auto-update конфигов)
* Тестирование серверов (ping)
* Детальное логирование
* Кроссплатформенность: Windows / Linux / macOS
* Один бинарник без внешних зависимостей (кроме WG/Xray)
## Требования
@@ -21,59 +20,37 @@ VPN клиент на Golang с поддержкой VLESS протокола и
## Установка
### Инициализация зависимостей
### Сборка из исходников
```bash
# Windows
init_gui.bat
# Linux/macOS
chmod +x init_gui.sh
./init_gui.sh
cd vpn_client_go
go mod download
go build -o vpn-client main.go
```
### Сборка GUI версии
### Windows
```bash
# Windows
build_gui.bat
# Linux/macOS
chmod +x build_gui.sh
./build_gui.sh
```
### Сборка CLI версии (опционально)
```bash
# Windows
build.bat
```
# Linux/macOS
### Linux/macOS
```bash
chmod +x build.sh
./build.sh
```
## Использование
### Запуск GUI (по умолчанию)
### Запуск
```bash
# Windows
vpn-client-gui.exe
vpn-client.exe
# Linux/macOS
./vpn-client-gui
```
### Запуск CLI режима
```bash
# Windows
vpn-client-gui.exe --cli
# Linux/macOS
./vpn-client-gui --cli
./vpn-client
```
## Основные возможности CLI
@@ -144,32 +121,7 @@ vpn-client-gui.exe --cli
127.0.0.1:10808
```
Настройте браузер или систему на использование этого прокси.
## Отличия от Python версии
- ✅ Более быстрая работа
- ✅ Один исполняемый файл без зависимостей
- ✅ Меньшее потребление памяти
- ✅ Нативная кроссплатформенность
- ✅ Полная поддержка WireGuard
- ✅ Полная поддержка VLESS
- ⚠️ Нет GUI версии
- ⚠️ Нет автоматической настройки системного прокси (пока)
## Разработка
### Добавление новых функций
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)
}
}

18
go.mod
View File

@@ -1,11 +1,21 @@
module vpn-client
go 1.25.0
require github.com/fatih/color v1.16.0
go 1.21
require (
gioui.org v0.7.1
github.com/fatih/color v1.16.0
)
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-isatty v0.0.20 // indirect
golang.org/x/sys v0.42.0 // 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/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/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.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w=
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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
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,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"
"os"
"os/exec"
"path/filepath"
"runtime"
"strconv"
"strings"
"time"
"vpn-client/internal/config"
"vpn-client/internal/subscription"
@@ -36,9 +34,6 @@ func Run() error {
choice := readInput("Выберите действие: ")
state, _ := vpn.GetStatus()
isConnected := state != nil && state.Connected
switch choice {
case "1":
wireguardMenu()
@@ -52,37 +47,10 @@ func Run() error {
}
pause()
case "5":
if isConnected {
// Мониторинг в реальном времени
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 {
fmt.Printf("%s Неверный выбор\n", red("✗"))
pause()
if err := vpn.Disconnect(config.LogsDir); err != nil {
fmt.Printf("%s %v\n", red("✗"), err)
}
pause()
case "0":
fmt.Println("До свидания!")
return nil
@@ -96,42 +64,25 @@ func Run() error {
func showMainMenu() {
state, _ := vpn.GetStatus()
// Получаем актуальный статус с временем
statusLine := ShowQuickStatus()
// Показываем текущее ядро
settings, _ := config.LoadSettings()
coreName := "Xray"
if settings != nil && settings.CoreType == "v2ray" {
coreName = "V2Ray"
statusIcon := "○"
statusText := "Не подключено"
if state != nil && state.Connected {
statusIcon = "✓"
statusText = fmt.Sprintf("Подключено: %s", state.ConfigName)
}
fmt.Println("\n" + strings.Repeat("=", 60))
fmt.Println("\n" + strings.Repeat("=", 50))
fmt.Println(bold("VPN Клиент (Go)"))
fmt.Println(strings.Repeat("=", 60))
fmt.Printf("Статус: %s\n", statusLine)
fmt.Printf("Ядро VLESS: %s\n", cyan(coreName))
// Показываем текущее время
fmt.Printf("Время: %s\n", time.Now().Format("15:04:05"))
fmt.Println(strings.Repeat("=", 60))
fmt.Println(strings.Repeat("=", 50))
fmt.Printf("Статус: %s %s\n", statusIcon, statusText)
fmt.Println(strings.Repeat("=", 50))
fmt.Println("1. WireGuard")
fmt.Println("2. VLESS")
fmt.Println("3. Управление подписками")
fmt.Println("4. Показать статус подключения")
if state != nil && state.Connected {
fmt.Println("5. " + green("Мониторинг в реальном времени"))
fmt.Println("6. Отключиться от VPN")
fmt.Println("7. " + yellow("Настройки (выбор ядра)"))
} else {
fmt.Println("5. Отключиться от VPN")
fmt.Println("6. " + yellow("Настройки (выбор ядра)"))
}
fmt.Println("5. Отключиться от VPN")
fmt.Println("0. Выход")
fmt.Println(strings.Repeat("=", 60))
fmt.Println(strings.Repeat("=", 50))
}
func vlessMenu() {
@@ -145,7 +96,6 @@ func vlessMenu() {
fmt.Println("3. Удалить конфиг")
fmt.Println("4. Подключиться")
fmt.Println("5. Тестировать конфиг (пинг)")
fmt.Println("6. " + yellow("Показать логи ошибок"))
fmt.Println("0. Назад")
fmt.Println(strings.Repeat("=", 50))
@@ -169,9 +119,6 @@ func vlessMenu() {
case "5":
testVLESSConfig()
pause()
case "6":
showVLESSLogs()
pause()
default:
fmt.Printf("%s Неверный выбор\n", red("✗"))
pause()
@@ -299,28 +246,12 @@ func connectVLESS() {
configName := configs.VLESS[num-1].Name
// Показываем текущее ядро
settings, _ := config.LoadSettings()
coreName := "Xray"
if settings != nil && settings.CoreType == "v2ray" {
coreName = "V2Ray"
}
fmt.Printf("\nПодключение к '%s' через Xray...\n", configName)
fmt.Printf("\nПодключение к '%s' через %s...\n", configName, coreName)
if err := vless.Connect(configName, config.LogsDir); err != nil {
if err := vless.Connect(configName, config.LogsDir, config.XrayDir); err != nil {
fmt.Printf("%s Ошибка подключения: %v\n", red("✗"), err)
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() {
@@ -930,115 +861,4 @@ func connectWireGuard() {
fmt.Printf("%s Ошибка подключения: %v\n", red("✗"), err)
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

@@ -11,10 +11,8 @@ var (
SubscriptionsFile string
ConfigsFile string
StateFile string
SettingsFile string
LogsDir string
XrayDir string
V2RayDir string
)
type WireGuardConfig struct {
@@ -53,10 +51,6 @@ type ConnectionState struct {
LogFile string `json:"log_file"`
}
type Settings struct {
CoreType string `json:"core_type"` // "xray" или "v2ray"
}
// Init инициализирует конфигурационные директории и файлы
func Init() error {
// Получаем рабочую директорию
@@ -69,10 +63,8 @@ func Init() error {
SubscriptionsFile = filepath.Join(ConfigDir, "subscriptions.json")
ConfigsFile = filepath.Join(ConfigDir, "configs.json")
StateFile = filepath.Join(ConfigDir, "state.json")
SettingsFile = filepath.Join(ConfigDir, "settings.json")
LogsDir = filepath.Join(workDir, "logs")
XrayDir = filepath.Join(workDir, "xray")
V2RayDir = filepath.Join(workDir, "v2ray")
// Создаем директории
if err := os.MkdirAll(ConfigDir, 0755); err != nil {
@@ -92,9 +84,6 @@ func Init() error {
if err := initFileIfNotExists(StateFile, ConnectionState{}); err != nil {
return err
}
if err := initFileIfNotExists(SettingsFile, Settings{CoreType: "xray"}); err != nil {
return err
}
return nil
}
@@ -181,32 +170,3 @@ func SaveState(state *ConnectionState) error {
}
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,437 +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"
)
// Запуск 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, config.XrayDir)
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,111 +0,0 @@
// +build windows
package proxy
import (
"fmt"
"os/exec"
"strings"
)
// 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)
}
// Устанавливаем адрес прокси
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)
}
// Обновляем настройки Internet Explorer (применяет изменения)
refreshIESettings()
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()
}

View File

@@ -16,16 +16,16 @@ import (
"vpn-client/internal/config"
"vpn-client/internal/logger"
"vpn-client/internal/proxy"
"vpn-client/internal/wireguard"
)
// XrayConfig представляет конфигурацию Xray
type XrayConfig struct {
Log LogConfig `json:"log"`
Inbounds []InboundConfig `json:"inbounds"`
Log LogConfig `json:"log"`
Inbounds []InboundConfig `json:"inbounds"`
Outbounds []OutboundConfig `json:"outbounds"`
Stats interface{} `json:"stats"`
Policy PolicyConfig `json:"policy"`
Stats interface{} `json:"stats"`
Policy PolicyConfig `json:"policy"`
}
type LogConfig struct {
@@ -36,11 +36,11 @@ type LogConfig struct {
}
type InboundConfig struct {
Port int `json:"port"`
Protocol string `json:"protocol"`
Settings InboundSettings `json:"settings"`
Sniffing SniffingConfig `json:"sniffing"`
Tag string `json:"tag"`
Port int `json:"port"`
Protocol string `json:"protocol"`
Settings InboundSettings `json:"settings"`
Sniffing SniffingConfig `json:"sniffing"`
Tag string `json:"tag"`
}
type InboundSettings struct {
@@ -55,8 +55,8 @@ type SniffingConfig struct {
}
type OutboundConfig struct {
Protocol string `json:"protocol"`
Tag string `json:"tag"`
Protocol string `json:"protocol"`
Tag string `json:"tag"`
Settings OutboundSettings `json:"settings"`
StreamSettings StreamSettings `json:"streamSettings"`
}
@@ -78,13 +78,13 @@ type UserConfig struct {
}
type StreamSettings struct {
Network string `json:"network"`
Security string `json:"security,omitempty"`
TLSSettings *TLSSettings `json:"tlsSettings,omitempty"`
RealitySettings *RealitySettings `json:"realitySettings,omitempty"`
WSSettings *WSSettings `json:"wsSettings,omitempty"`
GRPCSettings *GRPCSettings `json:"grpcSettings,omitempty"`
HTTPSettings *HTTPSettings `json:"httpSettings,omitempty"`
Network string `json:"network"`
Security string `json:"security,omitempty"`
TLSSettings *TLSSettings `json:"tlsSettings,omitempty"`
RealitySettings *RealitySettings `json:"realitySettings,omitempty"`
WSSettings *WSSettings `json:"wsSettings,omitempty"`
GRPCSettings *GRPCSettings `json:"grpcSettings,omitempty"`
HTTPSettings *HTTPSettings `json:"httpSettings,omitempty"`
}
type TLSSettings struct {
@@ -333,35 +333,11 @@ func getParam(params map[string]string, key, defaultValue string) string {
}
// Connect подключается к VLESS серверу
func Connect(configName string, logsDir string) error {
// Загружаем настройки
settings, err := config.LoadSettings()
if err != nil {
return fmt.Errorf("ошибка загрузки настроек: %w", err)
func Connect(configName string, logsDir, xrayDir string) error {
if err := disconnectExistingConnection(logsDir); err != nil {
return 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()
if err != nil {
@@ -381,15 +357,15 @@ func Connect(configName string, logsDir string) error {
return fmt.Errorf("конфиг '%s' не найден", configName)
}
// Создаем конфигурацию
coreConfig, err := ParseVLESSURL(vlessConfig.URL, logsDir)
// Создаем конфигурацию Xray
xrayConfig, err := ParseVLESSURL(vlessConfig.URL, logsDir)
if err != nil {
return fmt.Errorf("ошибка парсинга VLESS URL: %w", err)
}
// Сохраняем конфигурацию
configPath := filepath.Join(config.ConfigDir, "xray_config.json")
data, err := json.MarshalIndent(coreConfig, "", " ")
data, err := json.MarshalIndent(xrayConfig, "", " ")
if err != nil {
return fmt.Errorf("ошибка сериализации конфига: %w", err)
}
@@ -398,9 +374,15 @@ func Connect(configName string, logsDir string) error {
return fmt.Errorf("ошибка сохранения конфига: %w", err)
}
// Проверяем наличие исполняемого файла
if _, err := os.Stat(corePath); os.IsNotExist(err) {
return fmt.Errorf("%s не найден в %s\n\nПожалуйста, перезапустите приложение для автоматической загрузки %s", coreName, coreDir, coreName)
// Путь к xray
xrayExe := "xray"
if runtime.GOOS == "windows" {
xrayExe = "xray.exe"
}
xrayPath := filepath.Join(xrayDir, xrayExe)
if xrayPath, err = ensureXrayBinary(xrayDir); err != nil {
return err
}
// Создаем лог-файл трафика
@@ -413,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, "Конфиг: %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, "%s\n\n", strings.Repeat("=", 60))
// Запускаем ядро
cmd := exec.Command(corePath, "run", "-c", configPath)
// Запускаем xray
cmd := exec.Command(xrayPath, "run", "-c", configPath)
cmd.Stdout = 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 {
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 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 {
// Проверяем, что процесс еще работает
if err := cmd.Process.Signal(os.Signal(nil)); err != nil {
logFile.Close()
// Читаем логи для диагностики
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)
return fmt.Errorf("процесс xray завершился с ошибкой")
}
// Сохраняем состояние
@@ -485,7 +424,7 @@ func Connect(configName string, logsDir string) error {
ConfigName: configName,
ConfigType: "vless",
StartTime: time.Now().Format(time.RFC3339),
Interface: strings.ToLower(coreName),
Interface: "xray",
ProcessPID: cmd.Process.Pid,
LogFile: trafficLog,
}
@@ -498,37 +437,10 @@ func Connect(configName string, logsDir string) error {
// Логируем успешное подключение
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.Println("\n" + strings.Repeat("─", 60))
fmt.Println("Настроить системный прокси Windows?")
fmt.Println("Это позволит всем приложениям использовать VPN.")
fmt.Print("(y/n): ")
var response string
fmt.Scanln(&response)
if strings.ToLower(response) == "y" || strings.ToLower(response) == "д" {
if err := proxy.EnableSystemProxy("127.0.0.1:10808"); err != nil {
fmt.Printf("⚠ Не удалось настроить системный прокси: %v\n", err)
fmt.Println("Вы можете настроить его вручную в настройках Windows")
} else {
fmt.Println("✓ Системный прокси настроен")
}
} else {
fmt.Println("\nДля использования VPN настройте прокси вручную:")
fmt.Println(" 1. Откройте Настройки Windows → Сеть и Интернет → Прокси")
fmt.Println(" 2. Включите 'Использовать прокси-сервер'")
fmt.Println(" 3. Адрес: 127.0.0.1, Порт: 10808")
fmt.Println("\nИли настройте SOCKS5 прокси в браузере:")
fmt.Println(" Firefox: Настройки → Основные → Параметры сети")
fmt.Println(" Chrome: Настройки → Система → Открыть настройки прокси")
}
fmt.Printf("\nЛоги трафика:\n")
fmt.Printf(" - Основной: %s\n", trafficLog)
fmt.Printf(" - Доступ (IP): %s\n", filepath.Join(logsDir, "vless_access.log"))
@@ -537,28 +449,62 @@ func Connect(configName string, logsDir string) error {
return nil
}
func disconnectExistingConnection(logsDir string) error {
state, err := config.LoadState()
if err != nil {
return fmt.Errorf("ошибка загрузки состояния подключения: %w", err)
}
if state == nil || !state.Connected {
return nil
}
switch state.ConfigType {
case "wireguard":
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)
}
}
}
return config.SaveState(&config.ConnectionState{})
}
// PingServer проверяет доступность VLESS сервера
func PingServer(vlessURL string, timeout time.Duration) (bool, float64, error) {
// Парсим URL для получения адреса сервера
urlStr := strings.TrimPrefix(vlessURL, "vless://")
if idx := strings.Index(urlStr, "#"); idx != -1 {
urlStr = urlStr[:idx]
}
if idx := strings.Index(urlStr, "?"); idx != -1 {
urlStr = urlStr[:idx]
}
parts := strings.Split(urlStr, "@")
if len(parts) != 2 {
return false, 0, fmt.Errorf("неверный формат URL")
}
serverPort := parts[1]
var server string
var port string
if strings.Contains(serverPort, "[") {
endIdx := strings.Index(serverPort, "]")
server = serverPort[1:endIdx]
@@ -568,20 +514,20 @@ func PingServer(vlessURL string, timeout time.Duration) (bool, float64, error) {
server = serverPort[:lastColon]
port = serverPort[lastColon+1:]
}
if port == "" {
port = "443"
}
// Измеряем время подключения
start := time.Now()
conn, err := net.DialTimeout("tcp", net.JoinHostPort(server, port), timeout)
elapsed := time.Since(start)
if err != nil {
return false, 0, err
}
conn.Close()
return true, float64(elapsed.Milliseconds()), nil
}

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,7 +9,6 @@ import (
"vpn-client/internal/config"
"vpn-client/internal/logger"
"vpn-client/internal/proxy"
"vpn-client/internal/wireguard"
)
@@ -85,16 +84,6 @@ func Disconnect(logsDir string) error {
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")
return nil
}

39
main.go
View File

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