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:
273
internal/downloader/downloader.go
Normal file
273
internal/downloader/downloader.go
Normal file
@@ -0,0 +1,273 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user