Files
wgServer/main.go
2025-10-16 16:27:36 +07:00

667 lines
23 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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))
}