feat(cli): add settings menu and VLESS log viewer with core selection
- Add settings menu to switch between Xray and V2Ray cores for VLESS connections - Implement core type persistence in configuration with LoadSettings/SaveSettings - Add VLESS error and access log viewer showing last 30 and 20 lines respectively - Display current core type and system time in main menu - Update VLESS connection to use selected core dynamically - Refactor monitor.go to accept 'q' key input for graceful exit instead of signal handling - Add proxy platform-specific implementations (proxy_unix.go, proxy_windows.go) - Add downloader module for managing binary resources - Include V2Ray and Xray configuration files and geodata (geoip.dat, geosite.dat) - Update CLI imports to include path/filepath and time packages - Improve user experience with core selection visibility and log diagnostics
This commit is contained in:
@@ -11,10 +11,12 @@ import (
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"vpn-client/internal/config"
|
||||
"vpn-client/internal/logger"
|
||||
"vpn-client/internal/proxy"
|
||||
)
|
||||
|
||||
// XrayConfig представляет конфигурацию Xray
|
||||
@@ -331,7 +333,35 @@ func getParam(params map[string]string, key, defaultValue string) string {
|
||||
}
|
||||
|
||||
// Connect подключается к VLESS серверу
|
||||
func Connect(configName string, logsDir, xrayDir string) error {
|
||||
func Connect(configName string, logsDir string) error {
|
||||
// Загружаем настройки
|
||||
settings, err := config.LoadSettings()
|
||||
if err != nil {
|
||||
return fmt.Errorf("ошибка загрузки настроек: %w", err)
|
||||
}
|
||||
|
||||
// Определяем директорию и имя исполняемого файла
|
||||
var coreDir, coreExe, coreName string
|
||||
if settings.CoreType == "v2ray" {
|
||||
coreDir = config.V2RayDir
|
||||
coreName = "V2Ray"
|
||||
if runtime.GOOS == "windows" {
|
||||
coreExe = "v2ray.exe"
|
||||
} else {
|
||||
coreExe = "v2ray"
|
||||
}
|
||||
} else {
|
||||
coreDir = config.XrayDir
|
||||
coreName = "Xray"
|
||||
if runtime.GOOS == "windows" {
|
||||
coreExe = "xray.exe"
|
||||
} else {
|
||||
coreExe = "xray"
|
||||
}
|
||||
}
|
||||
|
||||
corePath := filepath.Join(coreDir, coreExe)
|
||||
|
||||
// Загружаем конфигурации
|
||||
configs, err := config.LoadConfigs()
|
||||
if err != nil {
|
||||
@@ -351,15 +381,15 @@ func Connect(configName string, logsDir, xrayDir string) error {
|
||||
return fmt.Errorf("конфиг '%s' не найден", configName)
|
||||
}
|
||||
|
||||
// Создаем конфигурацию Xray
|
||||
xrayConfig, err := ParseVLESSURL(vlessConfig.URL, logsDir)
|
||||
// Создаем конфигурацию
|
||||
coreConfig, 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(xrayConfig, "", " ")
|
||||
data, err := json.MarshalIndent(coreConfig, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("ошибка сериализации конфига: %w", err)
|
||||
}
|
||||
@@ -368,15 +398,9 @@ func Connect(configName string, logsDir, xrayDir string) error {
|
||||
return fmt.Errorf("ошибка сохранения конфига: %w", err)
|
||||
}
|
||||
|
||||
// Путь к xray
|
||||
xrayExe := "xray"
|
||||
if runtime.GOOS == "windows" {
|
||||
xrayExe = "xray.exe"
|
||||
}
|
||||
xrayPath := filepath.Join(xrayDir, xrayExe)
|
||||
|
||||
if _, err := os.Stat(xrayPath); os.IsNotExist(err) {
|
||||
return fmt.Errorf("xray не найден в %s", xrayDir)
|
||||
// Проверяем наличие исполняемого файла
|
||||
if _, err := os.Stat(corePath); os.IsNotExist(err) {
|
||||
return fmt.Errorf("%s не найден в %s\n\nПожалуйста, перезапустите приложение для автоматической загрузки %s", coreName, coreDir, coreName)
|
||||
}
|
||||
|
||||
// Создаем лог-файл трафика
|
||||
@@ -389,27 +413,70 @@ func Connect(configName string, logsDir, xrayDir 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, "Клиент: Xray\n")
|
||||
fmt.Fprintf(logFile, "Клиент: %s\n", coreName)
|
||||
fmt.Fprintf(logFile, "Прокси: 127.0.0.1:10808\n")
|
||||
fmt.Fprintf(logFile, "%s\n\n", strings.Repeat("=", 60))
|
||||
|
||||
// Запускаем xray
|
||||
cmd := exec.Command(xrayPath, "run", "-c", configPath)
|
||||
// Запускаем ядро
|
||||
cmd := exec.Command(corePath, "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("ошибка запуска xray: %w", err)
|
||||
return fmt.Errorf("ошибка запуска %s: %w", coreName, err)
|
||||
}
|
||||
|
||||
// Ждем немного для проверки запуска
|
||||
time.Sleep(3 * time.Second)
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
// Проверяем, что процесс еще работает
|
||||
if err := cmd.Process.Signal(os.Signal(nil)); err != nil {
|
||||
// Проверяем, что процесс еще работает (для 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 {
|
||||
logFile.Close()
|
||||
return fmt.Errorf("процесс xray завершился с ошибкой")
|
||||
|
||||
// Читаем логи для диагностики
|
||||
errorLog := filepath.Join(logsDir, "vless_error.log")
|
||||
if errorData, readErr := os.ReadFile(errorLog); readErr == nil && len(errorData) > 0 {
|
||||
lines := strings.Split(string(errorData), "\n")
|
||||
errorLines := []string{}
|
||||
for i := len(lines) - 1; i >= 0 && len(errorLines) < 10; i-- {
|
||||
if strings.TrimSpace(lines[i]) != "" {
|
||||
errorLines = append([]string{lines[i]}, errorLines...)
|
||||
}
|
||||
}
|
||||
|
||||
if len(errorLines) > 0 {
|
||||
return fmt.Errorf("процесс %s завершился с ошибкой.\n\nПоследние строки из лога ошибок:\n%s\n\nПолный лог: %s",
|
||||
coreName, strings.Join(errorLines, "\n"), errorLog)
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("процесс %s завершился с ошибкой.\nПроверьте логи в: %s", coreName, errorLog)
|
||||
}
|
||||
|
||||
// Сохраняем состояние
|
||||
@@ -418,7 +485,7 @@ func Connect(configName string, logsDir, xrayDir string) error {
|
||||
ConfigName: configName,
|
||||
ConfigType: "vless",
|
||||
StartTime: time.Now().Format(time.RFC3339),
|
||||
Interface: "xray",
|
||||
Interface: strings.ToLower(coreName),
|
||||
ProcessPID: cmd.Process.Pid,
|
||||
LogFile: trafficLog,
|
||||
}
|
||||
@@ -431,10 +498,37 @@ func Connect(configName string, logsDir, xrayDir string) error {
|
||||
|
||||
// Логируем успешное подключение
|
||||
logPath := logger.GetLogPath(logsDir, "vless")
|
||||
logger.LogMessage(logPath, fmt.Sprintf("Успешно подключено к '%s' (PID: %d, Лог: %s)", configName, cmd.Process.Pid, trafficLog))
|
||||
logger.LogMessage(logPath, fmt.Sprintf("Успешно подключено к '%s' через %s (PID: %d, Лог: %s)", configName, coreName, cmd.Process.Pid, trafficLog))
|
||||
|
||||
fmt.Printf("✓ Подключено к '%s'\n", configName)
|
||||
fmt.Printf("✓ Подключено к '%s' через %s\n", configName, coreName)
|
||||
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"))
|
||||
|
||||
Reference in New Issue
Block a user