package main import ( "fmt" "log" "net/http" "os" "os/exec" "strconv" "strings" "syscall" "wg-panel/internal/database" "wg-panel/internal/server" "wg-panel/internal/wireguard" ) const version = "1.0.0" func main() { // Обрабатываем аргументы командной строки if len(os.Args) > 1 { command := os.Args[1] // Команды доступные всегда switch command { case "install": installServer() return case "version", "-v", "--version": fmt.Printf("wg_serf version %s\n", version) os.Exit(0) case "help", "-h", "--help": showHelp() return case "serve": // Прямой запуск сервера (используется systemd) runServer() return } // Для остальных команд проверяем: // 1. Установлена ли служба // 2. Запущено через PATH (не ./wg_serf) if !isInstalled() { fmt.Println("") fmt.Println("❌ WG_SERF не установлен!") fmt.Println("") fmt.Println("Сначала установите:") fmt.Println(" sudo wg_serf install") fmt.Println("") fmt.Println("Или используйте install.sh:") fmt.Println(" curl -fsSL https://vserf.ru/download/wgserf/install.sh | sudo bash") fmt.Println("") os.Exit(1) } // Проверяем что команда запущена через PATH, а не напрямую exePath, _ := os.Executable() if strings.HasPrefix(exePath, "./") || strings.HasPrefix(exePath, "/root/") || strings.HasPrefix(exePath, "/home/") { // Запущено не через PATH if exePath != "/opt/wg_serf/wg_serf" { fmt.Println("") fmt.Println("⚠️ Команды управления работают только через PATH") fmt.Println("") fmt.Println("Используйте:") fmt.Printf(" wg_serf %s\n", command) fmt.Println("") fmt.Println("А не:") fmt.Printf(" %s %s\n", exePath, command) fmt.Println("") os.Exit(1) } } // Команды доступные только после установки switch command { case "start": startServer() case "stop": stopServer() case "restart": restartServer() case "status": showStatus() case "uninstall", "delete": deleteServer() default: fmt.Printf("Неизвестная команда: %s\n\n", command) showHelp() os.Exit(1) } return } // Без аргументов - интерактивный режим handleNoArgs() } func handleNoArgs() { // Проверяем запущен ли через PATH или напрямую exePath, _ := os.Executable() // Если не установлено - предлагаем установить if !isInstalled() { fmt.Println("") fmt.Println("╔══════════════════════════════════════════════════════════════╗") fmt.Println("║ WG_SERF - WireGuard Server Panel ║") fmt.Println("║ Version " + version + " ║") fmt.Println("╚══════════════════════════════════════════════════════════════╝") fmt.Println("") fmt.Println("❌ WG_SERF не установлен") fmt.Println("") if askYesNo("📥 Установить в систему? (yes/no): ") { installServer() } else { fmt.Println("") fmt.Println("Установка отменена. Установить позже:") fmt.Println(" sudo " + exePath + " install") fmt.Println("") } return } // Если установлено - показываем информацию config, _ := database.LoadConfig() pid, _ := readPIDFile() running := isRunning() fmt.Println("") fmt.Println("╔══════════════════════════════════════════════════════════════╗") fmt.Println("║ WG_SERF - WireGuard Server Panel ║") if running { // Динамическое форматирование для ровной рамки line := fmt.Sprintf("Version %s PID: %d", version, pid) padding := 62 - len(line) leftPad := padding / 2 rightPad := padding - leftPad fmt.Printf("║%s%s%s║\n", strings.Repeat(" ", leftPad), line, strings.Repeat(" ", rightPad)) } else { line := fmt.Sprintf("Version %s", version) padding := 62 - len(line) leftPad := padding / 2 rightPad := padding - leftPad fmt.Printf("║%s%s%s║\n", strings.Repeat(" ", leftPad), line, strings.Repeat(" ", rightPad)) } fmt.Println("╚══════════════════════════════════════════════════════════════╝") fmt.Println("") if running { fmt.Println("✅ Сервис установлен и работает") if config != nil { serverIP := database.GetLocalIP() if serverIP == "127.0.0.1" { serverIP = database.GetServerEndpoint() } fmt.Printf("🌐 Веб-панель: http://%s:%s\n", serverIP, config.Port) fmt.Printf("👤 Логин: %s\n", config.Username) fmt.Printf("🔒 Пароль: %s\n", config.Password) } } else { fmt.Println("⚠️ Сервис установлен, но не запущен") fmt.Println("") fmt.Println("Запустить: wg_serf start") } fmt.Println("") fmt.Println("📋 Доступные команды:") fmt.Println(" wg_serf status # Статус") fmt.Println(" wg_serf restart # Перезапустить") fmt.Println(" wg_serf stop # Остановить") fmt.Println(" wg_serf delete # Удалить") fmt.Println("") fmt.Println("📚 Справка: wg_serf help") fmt.Println("") } func isInstalled() bool { _, err := os.Stat("/etc/systemd/system/wg_serf.service") return err == nil } // askYesNo запрашивает подтверждение yes/no (толерантно к вводу) func askYesNo(prompt string) bool { fmt.Print(prompt) var response string fmt.Scanln(&response) // Убираем пробелы и переводим в нижний регистр response = strings.ToLower(strings.TrimSpace(response)) // Проверяем содержит ли yes if strings.Contains(response, "yes") || strings.Contains(response, "y") { return true } return false } func showHelp() { fmt.Println(` ╔══════════════════════════════════════════════════════════════╗ ║ WG_SERF - WireGuard Server Panel ║ ║ Version ` + version + ` ║ ╚══════════════════════════════════════════════════════════════╝ 📖 ИСПОЛЬЗОВАНИЕ: wg_serf <команда> 📋 КОМАНДЫ: install Установить wg_serf как службу (требуется сначала!) version Показать версию help Показать эту справку 📋 КОМАНДЫ ПОСЛЕ УСТАНОВКИ: start Запустить сервер stop Остановить сервер restart Перезапустить сервер status Показать статус сервера delete Удалить wg_serf полностью 🔧 ПРИМЕРЫ: sudo wg_serf install # Сначала установить wg_serf status # Проверить статус wg_serf restart # Перезапустить 📡 ВЕБ-ИНТЕРФЕЙС: После запуска откройте в браузере: http://your-server-ip:8080 Логин по умолчанию: admin / admin 💡 Совет: Для работы требуются root права (sudo)`) } func startServer() { // Проверяем не запущен ли уже if isRunning() { fmt.Println("❌ Сервер уже запущен!") os.Exit(1) } fmt.Println("🚀 Запуск WG_SERF...") // Пробуем запустить через systemctl (если установлена служба) cmd := exec.Command("systemctl", "start", "wg_serf") output, err := cmd.CombinedOutput() if err != nil { // Если systemd не доступен или служба не установлена - запускаем напрямую if strings.Contains(string(output), "Failed to connect") || strings.Contains(string(output), "not found") || strings.Contains(err.Error(), "executable file not found") { fmt.Println("⚠️ Служба не найдена, запускаю напрямую...") runServer() return } fmt.Println("❌ Ошибка запуска:", err) fmt.Println(string(output)) os.Exit(1) } fmt.Println("✅ Сервер запущен как служба") fmt.Println("📋 Проверить статус: wg_serf status") fmt.Println("📋 Просмотр логов: journalctl -u wg_serf -f") } func stopServer() { fmt.Println("🛑 Остановка сервера...") // Останавливаем через systemctl cmd := exec.Command("systemctl", "stop", "wg_serf") output, err := cmd.CombinedOutput() if err != nil { // Если systemd не доступен, пробуем через PID файл if strings.Contains(string(output), "Failed to connect") || strings.Contains(err.Error(), "executable file not found") { stopServerViaPID() return } fmt.Println("❌ Ошибка остановки:", err) os.Exit(1) } fmt.Println("✅ Сервер остановлен") } func restartServer() { fmt.Println("🔄 Перезапуск сервера...") // Перезапускаем через systemctl cmd := exec.Command("systemctl", "restart", "wg_serf") output, err := cmd.CombinedOutput() if err != nil { // Если systemd не доступен, делаем вручную if strings.Contains(string(output), "Failed to connect") || strings.Contains(err.Error(), "executable file not found") { stopServerViaPID() startServer() return } fmt.Println("❌ Ошибка перезапуска:", err) os.Exit(1) } fmt.Println("✅ Сервер перезапущен") } func stopServerViaPID() { pid, err := readPIDFile() if err != nil { fmt.Println("❌ Сервер не запущен или PID файл не найден") os.Exit(1) } process, err := os.FindProcess(pid) if err != nil { fmt.Println("❌ Процесс не найден") os.Exit(1) } if err := process.Signal(syscall.SIGTERM); err != nil { fmt.Println("❌ Ошибка остановки сервера:", err) os.Exit(1) } fmt.Println("✅ Сервер остановлен") } func showStatus() { if isRunning() { pid, _ := readPIDFile() config, err := database.LoadConfig() if err == nil { fmt.Printf("✅ Сервер работает (PID: %d)\n", pid) fmt.Printf("🌐 Веб-интерфейс: http://%s:%s\n", config.Address, config.Port) fmt.Printf("👤 Логин: %s\n", config.Username) } else { fmt.Printf("✅ Сервер работает (PID: %d)\n", pid) } } else { fmt.Println("❌ Сервер не запущен") os.Exit(1) } } func isRunning() bool { pid, err := readPIDFile() if err != nil { return false } process, err := os.FindProcess(pid) if err != nil { return false } // Проверяем существует ли процесс err = process.Signal(syscall.Signal(0)) return err == nil } func installServer() { fmt.Println("") fmt.Println("╔══════════════════════════════════════════════════════════════╗") fmt.Println("║ WG_SERF Installer ║") fmt.Println("╚══════════════════════════════════════════════════════════════╝") fmt.Println("") // Проверяем установлена ли уже служба if _, err := os.Stat("/etc/systemd/system/wg_serf.service"); err == nil { fmt.Println("✅ WG_SERF уже установлен как служба!") fmt.Println("") fmt.Println("📋 Доступные команды:") fmt.Println(" wg_serf status # Проверить статус") fmt.Println(" wg_serf restart # Перезапустить") fmt.Println(" wg_serf stop # Остановить") fmt.Println(" wg_serf delete # Удалить") fmt.Println("") return } // Проверяем и устанавливаем WireGuard fmt.Println("🔍 Проверка WireGuard...") if !database.CheckWireGuardInstalled() { fmt.Println("⚠️ WireGuard не установлен. Устанавливаю...") if err := installWireGuard(); err != nil { fmt.Println("❌ Не удалось установить WireGuard:", err) fmt.Println("") fmt.Println("Установите WireGuard вручную:") fmt.Println(" apt install wireguard # Debian/Ubuntu") fmt.Println(" dnf install wireguard-tools # Fedora") fmt.Println(" yum install wireguard-tools # CentOS") os.Exit(1) } fmt.Println("✅ WireGuard установлен") } else { fmt.Println("✅ WireGuard уже установлен") } // Проверяем что бинарник в правильном месте currentPath, err := os.Executable() if err != nil { fmt.Println("❌ Ошибка определения пути к бинарнику:", err) os.Exit(1) } targetPath := "/opt/wg_serf/wg_serf" if currentPath != targetPath { // Создаем директорию fmt.Println("📁 Создание /opt/wg_serf/...") os.MkdirAll("/opt/wg_serf", 0755) // Копируем бинарник fmt.Println("📦 Копирование бинарника...") input, err := os.ReadFile(currentPath) if err != nil { fmt.Println("❌ Ошибка чтения:", err) os.Exit(1) } if err := os.WriteFile(targetPath, input, 0755); err != nil { fmt.Println("❌ Ошибка записи:", err) os.Exit(1) } // Создаем symlink fmt.Println("🔗 Создание symlink...") os.Remove("/usr/local/bin/wg_serf") os.Symlink(targetPath, "/usr/local/bin/wg_serf") } // Создаем systemd service fmt.Println("⚙️ Создание systemd service...") serviceContent := `[Unit] Description=WG_SERF - WireGuard Server Panel After=network.target Wants=network-online.target [Service] Type=simple User=root WorkingDirectory=/opt/wg_serf ExecStart=/opt/wg_serf/wg_serf serve Restart=on-failure RestartSec=5s KillMode=mixed KillSignal=SIGTERM TimeoutStopSec=5s # Security NoNewPrivileges=false PrivateTmp=false [Install] WantedBy=multi-user.target ` if err := os.WriteFile("/etc/systemd/system/wg_serf.service", []byte(serviceContent), 0644); err != nil { fmt.Println("❌ Ошибка создания service:", err) os.Exit(1) } // Перезагрузка systemd fmt.Println("🔄 Перезагрузка systemd...") exec.Command("systemctl", "daemon-reload").Run() // Включение автозапуска fmt.Println("✅ Включение автозапуска...") exec.Command("systemctl", "enable", "wg_serf").Run() // Запуск fmt.Println("🚀 Запуск wg_serf...") cmd := exec.Command("systemctl", "start", "wg_serf") if err := cmd.Run(); err != nil { fmt.Println("❌ Ошибка запуска:", err) os.Exit(1) } fmt.Println("") // Получаем IP и конфиг serverIP := database.GetLocalIP() if serverIP == "127.0.0.1" { serverIP = database.GetServerEndpoint() } config, _ := database.LoadConfig() port := "8080" if config != nil { port = config.Port } fmt.Println("╔══════════════════════════════════════════════════════════════╗") fmt.Println("║ ✅ Установка завершена! ║") fmt.Println("╚══════════════════════════════════════════════════════════════╝") fmt.Println("") fmt.Printf("🌐 Откройте в браузере: http://%s:%s\n", serverIP, port) if config != nil { fmt.Printf("👤 Логин: %s\n", config.Username) fmt.Printf("🔒 Пароль: %s\n", config.Password) } else { fmt.Println("👤 Логин: admin") fmt.Println("🔒 Пароль: admin") } fmt.Println("") fmt.Println("📋 Команды:") fmt.Println(" wg_serf status # Проверить статус") fmt.Println(" wg_serf restart # Перезапустить") fmt.Println("") } func deleteServer() { fmt.Println("") fmt.Println("╔══════════════════════════════════════════════════════════════╗") fmt.Println("║ WG_SERF - Удаление ║") fmt.Println("╚══════════════════════════════════════════════════════════════╝") fmt.Println("") // Подтверждение if !askYesNo("⚠️ Вы уверены? Все данные будут удалены! (yes/no): ") { fmt.Println("❌ Удаление отменено") os.Exit(0) } // Остановка сервиса fmt.Println("🛑 Остановка wg_serf...") exec.Command("systemctl", "stop", "wg_serf").Run() // Отключение автозапуска fmt.Println("🔄 Отключение автозапуска...") exec.Command("systemctl", "disable", "wg_serf").Run() // Удаление service файла fmt.Println("🗑️ Удаление systemd service...") os.Remove("/etc/systemd/system/wg_serf.service") exec.Command("systemctl", "daemon-reload").Run() // Удаление symlink fmt.Println("🗑️ Удаление symlink...") os.Remove("/usr/local/bin/wg_serf") // Удаление директории fmt.Println("🗑️ Удаление /opt/wg_serf...") os.RemoveAll("/opt/wg_serf") fmt.Println("") fmt.Println("✅ Удаление завершено!") fmt.Println("") } func installWireGuard() error { // Определяем дистрибутив osRelease, err := os.ReadFile("/etc/os-release") if err != nil { return fmt.Errorf("не удалось определить дистрибутив") } osID := "" for _, line := range strings.Split(string(osRelease), "\n") { if strings.HasPrefix(line, "ID=") { osID = strings.Trim(strings.TrimPrefix(line, "ID="), "\"") break } } var cmd *exec.Cmd switch osID { case "ubuntu", "debian": fmt.Println("📦 Установка для Debian/Ubuntu...") exec.Command("apt", "update", "-qq").Run() cmd = exec.Command("apt", "install", "-y", "wireguard", "wireguard-tools") case "centos", "rhel": fmt.Println("📦 Установка для RHEL/CentOS...") exec.Command("yum", "install", "-y", "epel-release").Run() cmd = exec.Command("yum", "install", "-y", "wireguard-tools") case "fedora": fmt.Println("📦 Установка для Fedora...") cmd = exec.Command("dnf", "install", "-y", "wireguard-tools") case "arch", "manjaro": fmt.Println("📦 Установка для Arch Linux...") cmd = exec.Command("pacman", "-Sy", "--noconfirm", "wireguard-tools") default: return fmt.Errorf("дистрибутив %s не поддерживается", osID) } if output, err := cmd.CombinedOutput(); err != nil { return fmt.Errorf("ошибка установки: %v, output: %s", err, string(output)) } // Проверяем что установилось if !database.CheckWireGuardInstalled() { return fmt.Errorf("WireGuard не найден после установки") } return nil } func readPIDFile() (int, error) { data, err := os.ReadFile("/opt/wg_serf/wg_serf.pid") if err != nil { return 0, err } return strconv.Atoi(strings.TrimSpace(string(data))) } func runServer() { // Проверяем и завершаем старый процесс если запущен if err := database.CheckAndKillOldProcess(); err != nil { log.Fatal("Ошибка при завершении старого процесса:", err) } // Записываем PID текущего процесса if err := database.WritePIDFile(); err != nil { log.Fatal("Ошибка записи PID файла:", err) } defer database.RemovePIDFile() // Проверяем установлен ли WireGuard if !database.CheckWireGuardInstalled() { log.Fatal("WireGuard не установлен! Установите WireGuard перед запуском.") } // Загружаем конфигурацию config, err := database.LoadConfig() if err != nil { log.Fatal("Ошибка загрузки конфигурации:", err) } server.Config = config // Загружаем базу данных db, err := database.LoadDatabase() if err != nil { log.Println("Создаю новую базу данных...") db = &database.Database{ Servers: []database.Server{}, Clients: []database.Client{}, } database.SaveDatabase(db) } server.DB = db // Очищаем iptables (так как сервер только для WireGuard) if err := wireguard.CleanIPTables(); err != nil { log.Println("Предупреждение: ошибка очистки iptables:", err) } // Настраиваем базовые правила if err := wireguard.SetupBasicIPTables(); err != nil { log.Println("Предупреждение: ошибка настройки базовых правил:", err) } // Включаем IP forwarding if err := database.EnableIPForwarding(); err != nil { log.Println("Предупреждение: не удалось включить IP forwarding:", err) } // Синхронизируем WireGuard с базой данных (создаст правила для серверов из БД) if err := wireguard.SyncWireGuardWithDatabase(db); err != nil { log.Println("Предупреждение: ошибка синхронизации:", err) } // Настраиваем маршруты server.SetupRoutes() // Обновляем статистику каждые 5 секунд go wireguard.UpdateStatsLoop(db) addr := config.Address + ":" + config.Port log.Printf("🚀 Сервер запущен на http://%s\n", addr) log.Printf("👤 Логин: %s\n", config.Username) log.Printf("🔒 Пароль: %s\n", config.Password) log.Fatal(http.ListenAndServe(addr, nil)) }