Files
wgServer/internal/wireguard/sync.go
2025-10-16 16:27:36 +07:00

255 lines
8.6 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 wireguard
import (
"fmt"
"log"
"os"
"os/exec"
"strconv"
"strings"
"wg-panel/internal/database"
)
// SyncWireGuardWithDatabase синхронизирует WireGuard с базой данных
// База данных - единственный источник истины
func SyncWireGuardWithDatabase(db *database.Database) error {
log.Println("🔄 Синхронизация WireGuard с базой данных...")
log.Println("📋 База данных - единственный источник истины")
// Получаем список всех интерфейсов WireGuard
cmd := exec.Command("wg", "show", "interfaces")
output, err := cmd.Output()
var activeInterfaces []string
if err == nil && len(output) > 0 {
activeInterfaces = strings.Fields(strings.TrimSpace(string(output)))
}
// Создаем карту интерфейсов из БД
dbInterfaces := make(map[string]*database.Server)
for i := range db.Servers {
dbInterfaces[db.Servers[i].Interface] = &db.Servers[i]
}
// Удаляем все интерфейсы которых НЕТ в БД
for _, iface := range activeInterfaces {
if _, exists := dbInterfaces[iface]; !exists {
log.Printf(" ❌ Интерфейс %s не найден в БД - удаление...", iface)
if err := removeInterface(iface); err != nil {
log.Printf(" ⚠️ Ошибка удаления интерфейса: %v", err)
} else {
log.Printf(" ✅ Интерфейс %s удален", iface)
}
}
}
// Также удаляем конфиг файлы которых нет в БД (но интерфейс не запущен)
cmd = exec.Command("sh", "-c", "ls /etc/wireguard/*.conf 2>/dev/null | xargs -n1 basename | sed 's/.conf$//'")
output, err = cmd.Output()
if err == nil && len(output) > 0 {
configInterfaces := strings.Fields(strings.TrimSpace(string(output)))
for _, iface := range configInterfaces {
if _, exists := dbInterfaces[iface]; !exists {
configPath := fmt.Sprintf("/etc/wireguard/%s.conf", iface)
log.Printf(" 🗑️ Удаление конфига %s (не в БД)...", configPath)
if err := os.Remove(configPath); err != nil {
log.Printf(" ⚠️ Ошибка удаления конфига: %v", err)
} else {
log.Printf(" ✅ Конфиг удален")
}
}
}
}
// Синхронизируем каждый сервер из БД
for i := range db.Servers {
server := &db.Servers[i]
log.Printf(" 🔧 Синхронизация сервера %s (%s)...", server.Name, server.Interface)
// Проверяем запущен ли интерфейс
isRunning := false
for _, iface := range activeInterfaces {
if iface == server.Interface {
isRunning = true
break
}
}
if server.Enabled {
// Сервер должен быть запущен
if isRunning {
// Если уже запущен - перезапускаем (чтобы применить правила iptables после очистки)
log.Printf(" 🔄 Интерфейс уже запущен, перезапускаю для применения правил...")
if err := stopInterface(server.Interface); err != nil {
log.Printf(" ⚠️ Ошибка остановки: %v", err)
}
}
log.Printf(" 🚀 Запуск интерфейса %s...", server.Interface)
if err := startInterface(server, db); err != nil {
log.Printf(" ❌ Ошибка запуска: %v", err)
server.Enabled = false
continue
}
// Синхронизируем peers
log.Printf(" 🧹 Очистка всех peers...")
if err := clearAllPeers(server.Interface); err != nil {
log.Printf(" ⚠️ Ошибка очистки: %v", err)
}
log.Printf(" 📤 Загрузка peers из БД...")
loadedCount := 0
for j := range db.Clients {
client := &db.Clients[j]
if client.ServerID == server.ID && client.Enabled {
if err := addPeerToWireGuard(server, *client); err != nil {
log.Printf(" ⚠️ Ошибка добавления %s: %v", client.Name, err)
} else {
loadedCount++
// Применяем пробросы портов
if len(client.PortForwards) > 0 {
log.Printf(" 🔀 Применяю %d пробросов портов для %s...", len(client.PortForwards), client.Name)
ApplyAllPortForwards(client)
}
}
}
}
log.Printf(" ✅ Загружено %d peers", loadedCount)
} else {
// Сервер должен быть остановлен
if isRunning {
log.Printf(" 🛑 Остановка интерфейса %s...", server.Interface)
if err := stopInterface(server.Interface); err != nil {
log.Printf(" ⚠️ Ошибка остановки: %v", err)
} else {
log.Printf(" ✅ Интерфейс остановлен")
}
}
}
}
database.SaveDatabase(db)
log.Println("✅ Синхронизация завершена")
return nil
}
// removeInterface удаляет интерфейс WireGuard
func removeInterface(iface string) error {
// Останавливаем интерфейс
cmd := exec.Command("wg-quick", "down", iface)
cmd.Run() // Игнорируем ошибку если уже остановлен
// Удаляем конфиг файл
configPath := fmt.Sprintf("/etc/wireguard/%s.conf", iface)
return os.Remove(configPath)
}
// stopInterface останавливает интерфейс WireGuard
func stopInterface(iface string) error {
cmd := exec.Command("wg-quick", "down", iface)
return cmd.Run()
}
// startInterface запускает интерфейс WireGuard
func startInterface(server *database.Server, db *database.Database) error {
log.Printf(" 📝 Обновляю конфиг файл...")
// Убеждаемся что конфиг файл существует
if err := UpdateServerConfig(server, db); err != nil {
return fmt.Errorf("ошибка создания конфига: %v", err)
}
log.Printf(" 🔧 Включаю IP forwarding...")
// Включаем IP forwarding
if err := database.EnableIPForwarding(); err != nil {
log.Printf(" ⚠️ Ошибка IP forwarding: %v", err)
}
log.Printf(" 🚀 Запускаю wg-quick up %s...", server.Interface)
// Запускаем интерфейс
cmd := exec.Command("wg-quick", "up", server.Interface)
output, err := cmd.CombinedOutput()
if err != nil {
log.Printf(" ❌ Вывод wg-quick: %s", string(output))
return fmt.Errorf("wg-quick up failed: %v, output: %s", err, string(output))
}
log.Printf(" ✅ Интерфейс запущен успешно")
// Применяем правила iptables вручную (не полагаемся на PostUp)
if err := ApplyWireGuardIPTablesRules(server); err != nil {
log.Printf(" ⚠️ Ошибка применения правил: %v", err)
}
return nil
}
// clearAllPeers очищает все peers из интерфейса WireGuard
func clearAllPeers(iface string) error {
// Получаем список всех peers
cmd := exec.Command("wg", "show", iface, "peers")
output, err := cmd.Output()
if err != nil {
return err
}
peers := strings.Split(strings.TrimSpace(string(output)), "\n")
removedCount := 0
for _, peer := range peers {
peer = strings.TrimSpace(peer)
if peer == "" {
continue
}
// Удаляем peer
cmd := exec.Command("wg", "set", iface, "peer", peer, "remove")
if err := cmd.Run(); err != nil {
log.Printf(" ⚠️ Не удалось удалить peer %s: %v", peer[:16]+"...", err)
} else {
removedCount++
}
}
if removedCount > 0 {
log.Printf(" 🗑️ Удалено %d peers", removedCount)
}
return nil
}
// updateNextClientIP обновляет счетчик следующего IP для клиентов
func updateNextClientIP(server *database.Server, db *database.Database) {
// Парсим адрес сервера
parts := strings.Split(server.Address, "/")
if len(parts) != 2 {
server.NextClientIP = 2
return
}
ipParts := strings.Split(parts[0], ".")
if len(ipParts) != 4 {
server.NextClientIP = 2
return
}
// Находим максимальный IP среди клиентов этого сервера
maxIP := 1
for _, client := range db.Clients {
if client.ServerID != server.ID {
continue
}
clientIPParts := strings.Split(client.Address, ".")
if len(clientIPParts) == 4 {
lastOctet, err := strconv.Atoi(clientIPParts[3])
if err == nil && lastOctet > maxIP {
maxIP = lastOctet
}
}
}
server.NextClientIP = maxIP + 1
}