v 0.0.7 b1 (оптимизация под разнве системы)
This commit is contained in:
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Binary file not shown.
@@ -275,10 +275,10 @@ func (db *DB) getChannel(id string) (*ChannelConfigDB, error) {
|
||||
&config.SampleRate, &config.Channels, &config.Format,
|
||||
&config.AutoRestart, &config.SMBFolder, &config.CreatedAt, &config.UpdatedAt)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
switch {
|
||||
case err == sql.ErrNoRows:
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
case err != nil:
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
@@ -275,3 +275,15 @@
|
||||
{"id":432,"timestamp":"2026-06-10T12:29:50.762449527+07:00","level":"INFO","category":"RECORDING","message":"Запись: НАЧАЛО | Канал: channel1 | Файл: 2026-06-10 12-29-50.wav"}
|
||||
{"id":433,"timestamp":"2026-06-10T12:31:09.804898834+07:00","level":"INFO","category":"RECORDING","message":"Запись: ЗАВЕРШЕНА | Канал: channel1 | Файл: 2026-06-10 12-29-50.wav | Длительность: 5м 0с | Размер: 2 MB"}
|
||||
{"id":434,"timestamp":"2026-06-10T12:31:09.807941618+07:00","level":"INFO","category":"SMB","message":"Добавлен в очередь повторных попыток | Файл: 2026-06-10 12-29-50.wav | Папка: 1-60рпропол-998-2026"}
|
||||
{"id":435,"timestamp":"2026-06-10T12:37:10.352531773+07:00","level":"INFO","category":"OPENVPN","message":"Остановка OpenVPN"}
|
||||
{"id":436,"timestamp":"2026-06-10T13:12:47.020233582+07:00","level":"INFO","category":"OPENVPN","message":"Восстановление подключения после перезагрузки"}
|
||||
{"id":437,"timestamp":"2026-06-10T13:12:47.031657155+07:00","level":"INFO","category":"VPN","message":"VPN Manager инициализирован | VPN по умолчанию: wireguard"}
|
||||
{"id":438,"timestamp":"2026-06-10T13:12:52.046931708+07:00","level":"INFO","category":"OPENVPN","message":"Запуск OpenVPN | Конфиг: /home/dnd/pfSense-UDP4-1194-DND-config.ovpn"}
|
||||
{"id":439,"timestamp":"2026-06-10T13:12:52.04969342+07:00","level":"INFO","category":"OPENVPN","message":"OpenVPN запущен, ожидание подключения..."}
|
||||
{"id":440,"timestamp":"2026-06-10T13:12:52.130010364+07:00","level":"INFO","category":"WIREGUARD","message":"Запуск WireGuard | Интерфейс: wg0"}
|
||||
{"id":441,"timestamp":"2026-06-10T13:12:52.507874042+07:00","level":"INFO","category":"WIREGUARD","message":"WireGuard запущен успешно"}
|
||||
{"id":442,"timestamp":"2026-06-10T13:13:02.05878545+07:00","level":"WARNING","category":"OPENVPN","message":"Подключение не установлено, проверьте логи"}
|
||||
{"id":443,"timestamp":"2026-06-10T13:14:01.404863738+07:00","level":"INFO","category":"RECORDING","message":"Запись: НАЧАЛО | Канал: channel1 | Файл: 2026-06-10 13-14-01.wav"}
|
||||
{"id":444,"timestamp":"2026-06-10T13:14:24.332423949+07:00","level":"INFO","category":"RECORDING","message":"Запись: ЗАВЕРШЕНА | Канал: channel1 | Файл: 2026-06-10 13-14-01.wav | Длительность: 5м 0с | Размер: 652 KB"}
|
||||
{"id":445,"timestamp":"2026-06-10T13:14:24.334883655+07:00","level":"INFO","category":"SMB","message":"Добавлен в очередь повторных попыток | Файл: 2026-06-10 13-14-01.wav | Папка: 1-60рпропол-998-2026"}
|
||||
{"id":446,"timestamp":"2026-06-10T13:17:29.85417718+07:00","level":"INFO","category":"OPENVPN","message":"Остановка OpenVPN"}
|
||||
|
||||
@@ -10,22 +10,25 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
|
||||
"audio-recorder/database"
|
||||
"audio-recorder/devices"
|
||||
"audio-recorder/logger"
|
||||
"audio-recorder/network"
|
||||
"audio-recorder/optimization"
|
||||
"audio-recorder/recorder"
|
||||
"audio-recorder/server"
|
||||
"audio-recorder/smb"
|
||||
"audio-recorder/vpn"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
//go:embed static
|
||||
var staticFiles embed.FS
|
||||
|
||||
func main() {
|
||||
// Оптимизация для архитектуры
|
||||
optimization.OptimizeGoroutines()
|
||||
|
||||
// Инициализация логгера
|
||||
logger.Init()
|
||||
|
||||
@@ -38,14 +41,9 @@ func main() {
|
||||
log.Printf("Warning: Failed to init default settings: %v", err)
|
||||
}
|
||||
|
||||
// Загружаем пользователей из БД (после создания таблиц)
|
||||
// Загружаем пользователей из БД
|
||||
loadUsersFromDB()
|
||||
|
||||
// Автоматическое монтирование дисков
|
||||
diskManager := network.GetDiskManager()
|
||||
if err := diskManager.AutoMountDisks(); err != nil {
|
||||
log.Printf("Warning: Auto-mount failed: %v", err)
|
||||
}
|
||||
// Получаем папку для записей из БД
|
||||
recordingsDir, err := db.GetSetting("recordings_dir")
|
||||
if err != nil || recordingsDir == "" {
|
||||
@@ -93,7 +91,7 @@ func main() {
|
||||
// Создаем рекордер
|
||||
rec := recorder.NewRecorder(recordingsDir)
|
||||
|
||||
// Проверяем, есть ли каналы в БД, если нет - создаем каналы по умолчанию
|
||||
// Проверяем, есть ли каналы в БД
|
||||
channels, err := db.GetAllChannels()
|
||||
if err != nil {
|
||||
log.Printf("Warning: Failed to get channels from DB: %v", err)
|
||||
@@ -102,36 +100,19 @@ func main() {
|
||||
if len(channels) == 0 {
|
||||
log.Println("No channels in database, creating default channels...")
|
||||
|
||||
// Создаем папки для каналов
|
||||
os.MkdirAll(filepath.Join(recordingsDir, "channel1"), 0755)
|
||||
os.MkdirAll(filepath.Join(recordingsDir, "channel2"), 0755)
|
||||
|
||||
// Канал 1
|
||||
channel1 := recorder.NewChannel(
|
||||
"channel1",
|
||||
"Канал 1",
|
||||
"default",
|
||||
60,
|
||||
8000,
|
||||
1,
|
||||
"economy",
|
||||
false,
|
||||
recordingsDir,
|
||||
"channel1", "Канал 1", "default",
|
||||
60, 8000, 1, "economy", false, recordingsDir,
|
||||
)
|
||||
channel1.SMBFolder = "channel1"
|
||||
rec.AddChannel(channel1)
|
||||
|
||||
// Канал 2
|
||||
channel2 := recorder.NewChannel(
|
||||
"channel2",
|
||||
"Канал 2",
|
||||
"default",
|
||||
60,
|
||||
8000,
|
||||
1,
|
||||
"economy",
|
||||
false,
|
||||
recordingsDir,
|
||||
"channel2", "Канал 2", "default",
|
||||
60, 8000, 1, "economy", false, recordingsDir,
|
||||
)
|
||||
channel2.SMBFolder = "channel2"
|
||||
rec.AddChannel(channel2)
|
||||
@@ -139,17 +120,12 @@ func main() {
|
||||
log.Println("Default channels created successfully")
|
||||
}
|
||||
|
||||
// Устанавливаем экземпляр рекордера в обработчики
|
||||
server.SetRecorder(rec)
|
||||
|
||||
// Устанавливаем экземпляры для обратных вызовов
|
||||
recorder.SetRecorderInstance(rec)
|
||||
recorder.SetDeviceManager(dm)
|
||||
|
||||
// Восстанавливаем состояние каналов после перезагрузки
|
||||
restoreChannelsState(rec, dm, db)
|
||||
|
||||
// Запускаем проверяльщик состояний
|
||||
stateChecker := recorder.GetStateChecker()
|
||||
stateChecker.Start()
|
||||
|
||||
@@ -174,7 +150,6 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
// Применяем настройки SMB
|
||||
smbConfig := smb.SMBConfig{
|
||||
Enabled: smbEnabledStr == "true",
|
||||
AutoStart: smbAutoStartStr == "true",
|
||||
@@ -191,7 +166,6 @@ func main() {
|
||||
log.Printf("Warning: Failed to update SMB config: %v", err)
|
||||
}
|
||||
|
||||
// Устанавливаем колбэк для отправки файлов на SMB
|
||||
recorder.SetRecordingCompletedCallback(func(filename string, filePath string, smbFolder string) {
|
||||
if smbClient.IsConnected() {
|
||||
log.Printf("SMB client connected, uploading...")
|
||||
@@ -212,7 +186,6 @@ func main() {
|
||||
}
|
||||
})
|
||||
|
||||
// Запускаем SMB клиент если включен
|
||||
if smbEnabledStr == "true" {
|
||||
log.Printf("Запуск SMB клиента...")
|
||||
if err := smbClient.Start(); err != nil {
|
||||
@@ -220,48 +193,31 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
// Запускаем обработчик очереди повторных отправок
|
||||
go smbClient.ProcessRetryQueue()
|
||||
|
||||
// ========== Инициализация VPN ==========
|
||||
// Настройка OpenVPN клиента
|
||||
// Инициализация VPN
|
||||
openvpn := vpn.GetOpenVPNClient()
|
||||
openvpn.RestoreState()
|
||||
|
||||
// Настройка WireGuard клиента
|
||||
wireguard := vpn.GetWireGuardClient()
|
||||
wireguard.LoadFromDB()
|
||||
|
||||
// Инициализация VPN Manager
|
||||
vpnManager := vpn.GetVPNManager()
|
||||
vpnManager.Init()
|
||||
|
||||
// Настраиваем обработку сигналов для graceful shutdown
|
||||
// Настраиваем обработку сигналов
|
||||
signalChan := make(chan os.Signal, 1)
|
||||
signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
|
||||
|
||||
go func() {
|
||||
<-signalChan
|
||||
log.Println("Получен сигнал завершения, сохраняем состояния каналов...")
|
||||
|
||||
// Останавливаем проверяльщик состояний
|
||||
stateChecker.Stop()
|
||||
|
||||
// Останавливаем SMB клиент
|
||||
smbClient.Stop()
|
||||
|
||||
// Останавливаем OpenVPN
|
||||
openvpn.Stop()
|
||||
|
||||
// Останавливаем WireGuard
|
||||
wireguard.Stop()
|
||||
|
||||
// Останавливаем все VPN
|
||||
vpnManager.StopAll()
|
||||
|
||||
// Сохраняем состояния всех каналов в БД
|
||||
rec.SaveAllStates()
|
||||
|
||||
log.Println("Завершаем работу...")
|
||||
os.Exit(0)
|
||||
}()
|
||||
@@ -275,18 +231,16 @@ func main() {
|
||||
log.Printf("📝 Логин: admin, Пароль: admin")
|
||||
log.Printf("📁 Папка записей: %s", recordingsDir)
|
||||
log.Printf("🔄 Проверка состояния каналов: каждые %d секунд", stateChecker.GetCheckInterval())
|
||||
log.Printf("🏗️ Архитектура: %s", optimization.GetArch())
|
||||
log.Printf("")
|
||||
|
||||
// Запускаем сервер с graceful shutdown
|
||||
if err := srv.Run(); err != nil {
|
||||
log.Fatal("Server error:", err)
|
||||
}
|
||||
}
|
||||
|
||||
// restoreChannelsState восстанавливает состояние каналов после перезагрузки
|
||||
func restoreChannelsState(rec *recorder.Recorder, dm *devices.DeviceManager, db *database.DB) {
|
||||
channels := rec.GetAllChannels()
|
||||
|
||||
log.Printf("=== Восстановление состояния каналов из БД ===")
|
||||
|
||||
for id, ch := range channels {
|
||||
@@ -305,11 +259,15 @@ func restoreChannelsState(rec *recorder.Recorder, dm *devices.DeviceManager, db
|
||||
return dm.TestDevice(device)
|
||||
}
|
||||
|
||||
// Используем отдельную горутину с копией переменных
|
||||
channelID := id
|
||||
channel := ch
|
||||
go func() {
|
||||
time.Sleep(2 * time.Second)
|
||||
// Для ARM добавляем задержку
|
||||
if optimization.IsARM() {
|
||||
time.Sleep(3 * time.Second)
|
||||
} else {
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
if err := channel.StartRecording(channel.OutputDir, testFunc); err != nil {
|
||||
log.Printf("Не удалось восстановить запись для канала %s: %v", channelID, err)
|
||||
db.SetChannelState(channelID, false)
|
||||
@@ -324,7 +282,6 @@ func restoreChannelsState(rec *recorder.Recorder, dm *devices.DeviceManager, db
|
||||
log.Printf("=== Восстановление состояний завершено ===")
|
||||
}
|
||||
|
||||
// loadUsersFromDB загружает пользователей из БД или создает admin
|
||||
func loadUsersFromDB() {
|
||||
db := database.GetDB()
|
||||
users, err := db.GetAllUsers()
|
||||
@@ -332,7 +289,6 @@ func loadUsersFromDB() {
|
||||
log.Printf("Error loading users from DB: %v", err)
|
||||
}
|
||||
|
||||
// Если нет пользователей в БД, создаем admin
|
||||
if len(users) == 0 {
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte("admin"), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
# Makefile
|
||||
.PHONY: build-all build-amd64 build-arm64 build-armhf build-386 clean help
|
||||
|
||||
APP_NAME = audio-recorder
|
||||
VERSION = $(shell git describe --tags --always --dirty 2>/dev/null || echo "dev")
|
||||
BUILD_TIME = $(shell date -u '+%Y-%m-%d_%H:%M:%S')
|
||||
LDFLAGS = -ldflags="-s -w -X main.Version=$(VERSION) -X main.BuildTime=$(BUILD_TIME)"
|
||||
|
||||
# Цвета для вывода
|
||||
GREEN = \033[0;32m
|
||||
RED = \033[0;31m
|
||||
NC = \033[0m
|
||||
|
||||
all: build-all
|
||||
|
||||
# AMD64 (x86_64) - для обычных ПК и серверов
|
||||
build-amd64:
|
||||
@echo "$(GREEN)Building for AMD64 (x86_64)...$(NC)"
|
||||
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build $(LDFLAGS) -o $(APP_NAME)-amd64
|
||||
@echo "$(GREEN)✅ Done: $(APP_NAME)-amd64$(NC)"
|
||||
@ls -lh $(APP_NAME)-amd64
|
||||
@file $(APP_NAME)-amd64
|
||||
|
||||
# ARM64 (aarch64) - для Raspberry Pi 4/5, Orange Pi Zero 2W, Apple Silicon
|
||||
build-arm64:
|
||||
@echo "$(GREEN)Building for ARM64 (aarch64)...$(NC)"
|
||||
GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build $(LDFLAGS) -o $(APP_NAME)-arm64
|
||||
@echo "$(GREEN)✅ Done: $(APP_NAME)-arm64$(NC)"
|
||||
@ls -lh $(APP_NAME)-arm64
|
||||
@file $(APP_NAME)-arm64
|
||||
|
||||
# ARMHF (armv7l) - для старых Raspberry Pi 2/3, Orange Pi PC
|
||||
build-armhf:
|
||||
@echo "$(GREEN)Building for ARMHF (armv7l)...$(NC)"
|
||||
GOOS=linux GOARCH=arm GOARM=7 CGO_ENABLED=0 go build $(LDFLAGS) -o $(APP_NAME)-armhf
|
||||
@echo "$(GREEN)✅ Done: $(APP_NAME)-armhf$(NC)"
|
||||
@ls -lh $(APP_NAME)-armhf
|
||||
@file $(APP_NAME)-armhf
|
||||
|
||||
# ARMv6 (armv6l) - для Raspberry Pi Zero, Raspberry Pi 1
|
||||
build-armv6:
|
||||
@echo "$(GREEN)Building for ARMv6 (armv6l)...$(NC)"
|
||||
GOOS=linux GOARCH=arm GOARM=6 CGO_ENABLED=0 go build $(LDFLAGS) -o $(APP_NAME)-armv6
|
||||
@echo "$(GREEN)✅ Done: $(APP_NAME)-armv6$(NC)"
|
||||
@ls -lh $(APP_NAME)-armv6
|
||||
|
||||
# 32-bit x86 - для старых 32-битных систем
|
||||
build-386:
|
||||
@echo "$(GREEN)Building for 386 (32-bit x86)...$(NC)"
|
||||
GOOS=linux GOARCH=386 CGO_ENABLED=0 go build $(LDFLAGS) -o $(APP_NAME)-386
|
||||
@echo "$(GREEN)✅ Done: $(APP_NAME)-386$(NC)"
|
||||
@ls -lh $(APP_NAME)-386
|
||||
|
||||
# Windows AMD64
|
||||
build-windows:
|
||||
@echo "$(GREEN)Building for Windows AMD64...$(NC)"
|
||||
GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build $(LDFLAGS) -o $(APP_NAME)-windows-amd64.exe
|
||||
@echo "$(GREEN)✅ Done: $(APP_NAME)-windows-amd64.exe$(NC)"
|
||||
@ls -lh $(APP_NAME)-windows-amd64.exe
|
||||
|
||||
# macOS AMD64 (Intel)
|
||||
build-darwin-amd64:
|
||||
@echo "$(GREEN)Building for macOS AMD64 (Intel)...$(NC)"
|
||||
GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go build $(LDFLAGS) -o $(APP_NAME)-darwin-amd64
|
||||
@echo "$(GREEN)✅ Done: $(APP_NAME)-darwin-amd64$(NC)"
|
||||
@ls -lh $(APP_NAME)-darwin-amd64
|
||||
|
||||
# macOS ARM64 (Apple Silicon M1/M2)
|
||||
build-darwin-arm64:
|
||||
@echo "$(GREEN)Building for macOS ARM64 (Apple Silicon)...$(NC)"
|
||||
GOOS=darwin GOARCH=arm64 CGO_ENABLED=0 go build $(LDFLAGS) -o $(APP_NAME)-darwin-arm64
|
||||
@echo "$(GREEN)✅ Done: $(APP_NAME)-darwin-arm64$(NC)"
|
||||
@ls -lh $(APP_NAME)-darwin-arm64
|
||||
|
||||
# Сборка всех Linux архитектур
|
||||
build-linux: build-amd64 build-arm64 build-armhf build-armv6 build-386
|
||||
@echo "$(GREEN)✅ All Linux builds complete!$(NC)"
|
||||
|
||||
# Сборка всех архитектур
|
||||
build-all: build-linux build-windows build-darwin-amd64 build-darwin-arm64
|
||||
@echo "$(GREEN)✅ All builds complete!$(NC)"
|
||||
|
||||
# Очистка
|
||||
clean:
|
||||
@echo "$(GREEN)Cleaning...$(NC)"
|
||||
rm -f $(APP_NAME)-*
|
||||
@echo "$(GREEN)✅ Clean complete$(NC)"
|
||||
|
||||
# Упаковка в архив для каждой архитектуры
|
||||
package:
|
||||
@echo "$(GREEN)Creating packages...$(NC)"
|
||||
mkdir -p packages
|
||||
for arch in amd64 arm64 armhf armv6 386; do \
|
||||
if [ -f "$(APP_NAME)-$$arch" ]; then \
|
||||
mkdir -p packages/$(APP_NAME)-$$arch; \
|
||||
cp $(APP_NAME)-$$arch packages/$(APP_NAME)-$$arch/; \
|
||||
cp -r static packages/$(APP_NAME)-$$arch/; \
|
||||
cd packages && tar -czf $(APP_NAME)-$$arch.tar.gz $(APP_NAME)-$$arch && cd ..; \
|
||||
rm -rf packages/$(APP_NAME)-$$arch; \
|
||||
echo "$(GREEN)✅ Created $(APP_NAME)-$$arch.tar.gz$(NC)"; \
|
||||
fi \
|
||||
done
|
||||
rm -rf packages
|
||||
@echo "$(GREEN)✅ Packaging complete!$(NC)"
|
||||
|
||||
# Помощь
|
||||
help:
|
||||
@echo "Available targets:"
|
||||
@echo " make build-amd64 - Build for Linux AMD64 (x86_64)"
|
||||
@echo " make build-arm64 - Build for Linux ARM64 (aarch64)"
|
||||
@echo " make build-armhf - Build for Linux ARMHF (armv7l)"
|
||||
@echo " make build-armv6 - Build for Linux ARMv6 (Raspberry Pi Zero)"
|
||||
@echo " make build-386 - Build for Linux 386 (32-bit x86)"
|
||||
@echo " make build-windows - Build for Windows AMD64"
|
||||
@echo " make build-darwin-amd64 - Build for macOS Intel"
|
||||
@echo " make build-darwin-arm64 - Build for macOS Apple Silicon"
|
||||
@echo " make build-linux - Build all Linux architectures"
|
||||
@echo " make build-all - Build all architectures"
|
||||
@echo " make clean - Remove all binaries"
|
||||
@echo " make package - Create tar.gz packages"
|
||||
@echo " make help - Show this help"
|
||||
+58
-33
@@ -1,4 +1,3 @@
|
||||
// network/disks.go
|
||||
package network
|
||||
|
||||
import (
|
||||
@@ -9,8 +8,10 @@ import (
|
||||
"os/exec"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"audio-recorder/database"
|
||||
"audio-recorder/optimization"
|
||||
)
|
||||
|
||||
type DiskManager struct {
|
||||
@@ -20,7 +21,7 @@ type DiskManager struct {
|
||||
type DiskInfo struct {
|
||||
Device string `json:"device"`
|
||||
Size string `json:"size"`
|
||||
Type string `json:"type"` // disk, part
|
||||
Type string `json:"type"`
|
||||
Filesystem string `json:"filesystem"`
|
||||
MountPoint string `json:"mount_point"`
|
||||
Free string `json:"free"`
|
||||
@@ -43,18 +44,27 @@ func GetDiskManager() *DiskManager {
|
||||
return diskInstance
|
||||
}
|
||||
|
||||
// GetDisks возвращает список всех дисков
|
||||
func commandExists(cmd string) bool {
|
||||
_, err := exec.LookPath(cmd)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (dm *DiskManager) GetDisks() ([]DiskInfo, error) {
|
||||
dm.mu.Lock()
|
||||
defer dm.mu.Unlock()
|
||||
|
||||
var disks []DiskInfo
|
||||
|
||||
// Получаем список дисков через lsblk в текстовом режиме
|
||||
if !commandExists("lsblk") {
|
||||
log.Printf("lsblk not found, skipping disk detection")
|
||||
return disks, nil
|
||||
}
|
||||
|
||||
textCmd := exec.Command("lsblk", "-o", "NAME,SIZE,TYPE,FSTYPE,MOUNTPOINT,LABEL", "-n")
|
||||
textOutput, err := textCmd.Output()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
log.Printf("Error running lsblk: %v", err)
|
||||
return disks, nil
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(strings.NewReader(string(textOutput)))
|
||||
@@ -87,13 +97,11 @@ func (dm *DiskManager) GetDisks() ([]DiskInfo, error) {
|
||||
disk.Label = fields[5]
|
||||
}
|
||||
|
||||
// Получаем свободное место если смонтирован
|
||||
if disk.MountPoint != "" {
|
||||
if disk.MountPoint != "" && commandExists("df") {
|
||||
free := dm.getFreeSpace(disk.MountPoint)
|
||||
disk.Free = free
|
||||
}
|
||||
|
||||
// Пропускаем системные диски
|
||||
if strings.HasPrefix(name, "loop") || name == "sr0" {
|
||||
continue
|
||||
}
|
||||
@@ -105,6 +113,10 @@ func (dm *DiskManager) GetDisks() ([]DiskInfo, error) {
|
||||
}
|
||||
|
||||
func (dm *DiskManager) getFreeSpace(mountPoint string) string {
|
||||
if !commandExists("df") {
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
cmd := exec.Command("df", "-h", mountPoint)
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
@@ -121,9 +133,7 @@ func (dm *DiskManager) getFreeSpace(mountPoint string) string {
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
// SaveMountConfig сохраняет настройки монтирования в БД
|
||||
func (dm *DiskManager) SaveMountConfig(device, mountPoint string, enabled bool) error {
|
||||
// Сохраняем как JSON
|
||||
key := "disk_mount_" + strings.ReplaceAll(device, "/", "_")
|
||||
value := fmt.Sprintf(`{"device":"%s","mount_point":"%s","enabled":%v}`, device, mountPoint, enabled)
|
||||
|
||||
@@ -131,11 +141,9 @@ func (dm *DiskManager) SaveMountConfig(device, mountPoint string, enabled bool)
|
||||
return db.SetSetting(key, value)
|
||||
}
|
||||
|
||||
// GetAllMountConfigsFromDB получает все настройки монтирования из БД
|
||||
func (dm *DiskManager) GetAllMountConfigsFromDB() ([]MountConfig, error) {
|
||||
db := database.GetDB()
|
||||
|
||||
// Получаем все settings
|
||||
settings, err := db.GetAllSettings()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -145,7 +153,6 @@ func (dm *DiskManager) GetAllMountConfigsFromDB() ([]MountConfig, error) {
|
||||
for key, value := range settings {
|
||||
if strings.HasPrefix(key, "disk_mount_") {
|
||||
var config MountConfig
|
||||
// Парсим JSON
|
||||
if err := parseMountConfig(value, &config); err == nil {
|
||||
configs = append(configs, config)
|
||||
}
|
||||
@@ -156,12 +163,9 @@ func (dm *DiskManager) GetAllMountConfigsFromDB() ([]MountConfig, error) {
|
||||
}
|
||||
|
||||
func parseMountConfig(value string, config *MountConfig) error {
|
||||
// Простой парсинг JSON (можно использовать json.Unmarshal)
|
||||
// Для простоты используем ручной парсинг
|
||||
var device, mountPoint string
|
||||
var enabled bool
|
||||
|
||||
// Удаляем фигурные скобки
|
||||
value = strings.Trim(value, "{}")
|
||||
parts := strings.Split(value, ",")
|
||||
for _, part := range parts {
|
||||
@@ -188,17 +192,20 @@ func parseMountConfig(value string, config *MountConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteMountConfig удаляет настройку монтирования
|
||||
func (dm *DiskManager) DeleteMountConfig(device string) error {
|
||||
db := database.GetDB()
|
||||
key := "disk_mount_" + strings.ReplaceAll(device, "/", "_")
|
||||
return db.SetSetting(key, "")
|
||||
}
|
||||
|
||||
// AutoMountDisks автоматически монтирует диски по сохраненным настройкам
|
||||
func (dm *DiskManager) AutoMountDisks() error {
|
||||
log.Println("Auto-mounting disks from saved configs...")
|
||||
|
||||
if !commandExists("mountpoint") || !commandExists("mount") {
|
||||
log.Printf("Mount commands not available, skipping auto-mount")
|
||||
return nil
|
||||
}
|
||||
|
||||
configs, err := dm.GetAllMountConfigsFromDB()
|
||||
if err != nil {
|
||||
log.Printf("Error getting mount configs: %v", err)
|
||||
@@ -210,50 +217,57 @@ func (dm *DiskManager) AutoMountDisks() error {
|
||||
continue
|
||||
}
|
||||
|
||||
// Проверяем, не смонтирован ли уже
|
||||
checkCmd := exec.Command("mountpoint", "-q", config.MountPoint)
|
||||
if checkCmd.Run() == nil {
|
||||
log.Printf("Already mounted: %s at %s", config.Device, config.MountPoint)
|
||||
continue
|
||||
}
|
||||
|
||||
// Проверяем, существует ли устройство
|
||||
if _, err := os.Stat(config.Device); os.IsNotExist(err) {
|
||||
log.Printf("Device %s not found, skipping", config.Device)
|
||||
continue
|
||||
}
|
||||
|
||||
// Монтируем
|
||||
log.Printf("Auto-mounting %s to %s", config.Device, config.MountPoint)
|
||||
if err := dm.MountDisk(config.Device, config.MountPoint); err != nil {
|
||||
log.Printf("Failed to auto-mount %s: %v", config.Device, err)
|
||||
} else {
|
||||
log.Printf("Successfully auto-mounted %s to %s", config.Device, config.MountPoint)
|
||||
}
|
||||
|
||||
// Для ARM добавляем задержку между монтированиями
|
||||
if optimization.IsARM() {
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MountDisk монтирует диск и сохраняет настройку
|
||||
func (dm *DiskManager) MountDisk(device, mountPoint string) error {
|
||||
log.Printf("Mounting %s to %s", device, mountPoint)
|
||||
|
||||
// Создаем точку монтирования если не существует
|
||||
if err := os.MkdirAll(mountPoint, 0755); err != nil {
|
||||
return fmt.Errorf("failed to create mount point: %v", err)
|
||||
}
|
||||
|
||||
// Проверяем, не смонтирован ли уже
|
||||
checkCmd := exec.Command("mountpoint", "-q", mountPoint)
|
||||
if checkCmd.Run() == nil {
|
||||
return fmt.Errorf("already mounted at %s", mountPoint)
|
||||
}
|
||||
|
||||
// Определяем тип файловой системы для правильных параметров монтирования
|
||||
fstype := "auto"
|
||||
|
||||
// Монтируем
|
||||
if commandExists("ntfs-3g") {
|
||||
checkFsCmd := exec.Command("blkid", "-o", "value", "-s", "TYPE", device)
|
||||
if fsOutput, err := checkFsCmd.Output(); err == nil {
|
||||
fsType := strings.TrimSpace(string(fsOutput))
|
||||
if fsType == "ntfs" {
|
||||
fstype = "ntfs-3g"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cmd := exec.Command("sudo", "mount", "-t", fstype, device, mountPoint)
|
||||
if output, err := cmd.CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("mount failed: %v, output: %s", err, output)
|
||||
@@ -263,7 +277,6 @@ func (dm *DiskManager) MountDisk(device, mountPoint string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// MountDiskWithSave монтирует диск и сохраняет настройку
|
||||
func (dm *DiskManager) MountDiskWithSave(device, mountPoint string, saveConfig bool) error {
|
||||
if err := dm.MountDisk(device, mountPoint); err != nil {
|
||||
return err
|
||||
@@ -278,7 +291,6 @@ func (dm *DiskManager) MountDiskWithSave(device, mountPoint string, saveConfig b
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmountDisk отмонтирует диск и удаляет настройку
|
||||
func (dm *DiskManager) UnmountDisk(mountPoint string, removeConfig bool, device string) error {
|
||||
log.Printf("Unmounting %s", mountPoint)
|
||||
|
||||
@@ -298,11 +310,27 @@ func (dm *DiskManager) UnmountDisk(mountPoint string, removeConfig bool, device
|
||||
return nil
|
||||
}
|
||||
|
||||
// FormatDisk форматирует диск
|
||||
func (dm *DiskManager) FormatDisk(device, filesystem, label string) error {
|
||||
log.Printf("Formatting %s with %s, label: %s", device, filesystem, label)
|
||||
|
||||
// Проверяем, не смонтирован ли диск
|
||||
var formatCmd string
|
||||
switch filesystem {
|
||||
case "ext4":
|
||||
formatCmd = "mkfs.ext4"
|
||||
case "ntfs":
|
||||
formatCmd = "mkfs.ntfs"
|
||||
case "fat32":
|
||||
formatCmd = "mkfs.vfat"
|
||||
case "exfat":
|
||||
formatCmd = "mkfs.exfat"
|
||||
default:
|
||||
return fmt.Errorf("unsupported filesystem: %s", filesystem)
|
||||
}
|
||||
|
||||
if !commandExists(formatCmd) {
|
||||
return fmt.Errorf("format command %s not available", formatCmd)
|
||||
}
|
||||
|
||||
checkCmd := exec.Command("findmnt", "-n", device)
|
||||
if checkCmd.Run() == nil {
|
||||
return fmt.Errorf("device is mounted, please unmount first")
|
||||
@@ -318,8 +346,6 @@ func (dm *DiskManager) FormatDisk(device, filesystem, label string) error {
|
||||
cmd = exec.Command("sudo", "mkfs.vfat", "-F", "32", "-n", label, device)
|
||||
case "exfat":
|
||||
cmd = exec.Command("sudo", "mkfs.exfat", "-n", label, device)
|
||||
default:
|
||||
return fmt.Errorf("unsupported filesystem: %s", filesystem)
|
||||
}
|
||||
|
||||
if output, err := cmd.CombinedOutput(); err != nil {
|
||||
@@ -330,7 +356,6 @@ func (dm *DiskManager) FormatDisk(device, filesystem, label string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetMountableDevices возвращает список немонтированных устройств
|
||||
func (dm *DiskManager) GetMountableDevices() ([]DiskInfo, error) {
|
||||
disks, err := dm.GetDisks()
|
||||
if err != nil {
|
||||
|
||||
+30
-52
@@ -2,20 +2,22 @@ package network
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"audio-recorder/optimization"
|
||||
)
|
||||
|
||||
// NetworkManager - менеджер сетевых интерфейсов
|
||||
type NetworkManager struct {
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// NetworkInterface - информация о сетевом интерфейсе
|
||||
type NetworkInterface struct {
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
@@ -28,16 +30,14 @@ type NetworkInterface struct {
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// IPConfig - конфигурация IP
|
||||
type IPConfig struct {
|
||||
Mode string `json:"mode"` // dhcp/static
|
||||
Mode string `json:"mode"`
|
||||
IP string `json:"ip"`
|
||||
Netmask string `json:"netmask"`
|
||||
Gateway string `json:"gateway"`
|
||||
DNS string `json:"dns"`
|
||||
}
|
||||
|
||||
// WiFiNetwork - информация о WiFi сети
|
||||
type WiFiNetwork struct {
|
||||
SSID string `json:"ssid"`
|
||||
Strength int `json:"strength"`
|
||||
@@ -47,7 +47,6 @@ type WiFiNetwork struct {
|
||||
var instance *NetworkManager
|
||||
var once sync.Once
|
||||
|
||||
// GetManager возвращает экземпляр менеджера сетей
|
||||
func GetManager() *NetworkManager {
|
||||
once.Do(func() {
|
||||
instance = &NetworkManager{}
|
||||
@@ -55,7 +54,6 @@ func GetManager() *NetworkManager {
|
||||
return instance
|
||||
}
|
||||
|
||||
// GetInterfaces возвращает список всех сетевых интерфейсов
|
||||
func (nm *NetworkManager) GetInterfaces() ([]NetworkInterface, error) {
|
||||
nm.mu.Lock()
|
||||
defer nm.mu.Unlock()
|
||||
@@ -88,7 +86,6 @@ func (nm *NetworkManager) GetInterfaces() ([]NetworkInterface, error) {
|
||||
Status: status,
|
||||
}
|
||||
|
||||
// Определяем тип интерфейса
|
||||
if strings.HasPrefix(name, "wl") || strings.HasPrefix(name, "wlan") {
|
||||
iface.Type = "wifi"
|
||||
} else if strings.HasPrefix(name, "en") || strings.HasPrefix(name, "eth") {
|
||||
@@ -97,13 +94,11 @@ func (nm *NetworkManager) GetInterfaces() ([]NetworkInterface, error) {
|
||||
iface.Type = "other"
|
||||
}
|
||||
|
||||
// Получаем MAC адрес
|
||||
macCmd := exec.Command("cat", "/sys/class/net/"+name+"/address")
|
||||
if macOutput, err := macCmd.Output(); err == nil {
|
||||
iface.MAC = strings.TrimSpace(string(macOutput))
|
||||
}
|
||||
|
||||
// Получаем IP адрес
|
||||
ipCmd := exec.Command("ip", "-4", "addr", "show", name)
|
||||
ipOutput, _ := ipCmd.Output()
|
||||
lines := strings.Split(string(ipOutput), "\n")
|
||||
@@ -120,7 +115,6 @@ func (nm *NetworkManager) GetInterfaces() ([]NetworkInterface, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Получаем шлюз
|
||||
routeCmd := exec.Command("ip", "route", "show", "default", "dev", name)
|
||||
if routeOutput, err := routeCmd.Output(); err == nil {
|
||||
routeParts := strings.Fields(string(routeOutput))
|
||||
@@ -132,7 +126,6 @@ func (nm *NetworkManager) GetInterfaces() ([]NetworkInterface, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Получаем DNS
|
||||
if dns, err := nm.getDNS(); err == nil {
|
||||
iface.DNS = dns
|
||||
}
|
||||
@@ -143,7 +136,6 @@ func (nm *NetworkManager) GetInterfaces() ([]NetworkInterface, error) {
|
||||
return interfaces, nil
|
||||
}
|
||||
|
||||
// getDNS получает DNS серверы из resolv.conf
|
||||
func (nm *NetworkManager) getDNS() (string, error) {
|
||||
file, err := os.Open("/etc/resolv.conf")
|
||||
if err != nil {
|
||||
@@ -166,7 +158,6 @@ func (nm *NetworkManager) getDNS() (string, error) {
|
||||
return strings.Join(dnsServers, ", "), nil
|
||||
}
|
||||
|
||||
// GetIPConfig получает текущую конфигурацию IP для интерфейса
|
||||
func (nm *NetworkManager) GetIPConfig(interfaceName string) (*IPConfig, error) {
|
||||
config := &IPConfig{Mode: "dhcp"}
|
||||
|
||||
@@ -176,7 +167,6 @@ func (nm *NetworkManager) GetIPConfig(interfaceName string) (*IPConfig, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Проверяем, есть ли указание на dynamic (DHCP)
|
||||
if strings.Contains(string(ipOutput), "dynamic") {
|
||||
config.Mode = "dhcp"
|
||||
} else {
|
||||
@@ -193,7 +183,6 @@ func (nm *NetworkManager) GetIPConfig(interfaceName string) (*IPConfig, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Получаем шлюз
|
||||
routeCmd := exec.Command("ip", "route", "show", "default", "dev", interfaceName)
|
||||
if routeOutput, err := routeCmd.Output(); err == nil {
|
||||
routeParts := strings.Fields(string(routeOutput))
|
||||
@@ -205,7 +194,6 @@ func (nm *NetworkManager) GetIPConfig(interfaceName string) (*IPConfig, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Получаем DNS
|
||||
if dns, err := nm.getDNS(); err == nil {
|
||||
config.DNS = dns
|
||||
}
|
||||
@@ -213,15 +201,17 @@ func (nm *NetworkManager) GetIPConfig(interfaceName string) (*IPConfig, error) {
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// SetDHCP устанавливает DHCP для интерфейса
|
||||
func (nm *NetworkManager) SetDHCP(interfaceName string) error {
|
||||
log.Printf("Setting DHCP for interface %s", interfaceName)
|
||||
|
||||
// Убиваем старые dhclient процессы
|
||||
exec.Command("sudo", "pkill", "-f", "dhclient.*"+interfaceName).Run()
|
||||
exec.Command("sudo", "dhclient", "-r", interfaceName).Run()
|
||||
|
||||
// Запускаем dhclient
|
||||
// Для ARM добавляем задержку
|
||||
if optimization.IsARM() {
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
|
||||
cmd := exec.Command("sudo", "dhclient", "-v", interfaceName)
|
||||
if output, err := cmd.CombinedOutput(); err != nil {
|
||||
log.Printf("DHCP failed: %v, output: %s", err, output)
|
||||
@@ -232,14 +222,11 @@ func (nm *NetworkManager) SetDHCP(interfaceName string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetStaticIP устанавливает статический IP для интерфейса
|
||||
func (nm *NetworkManager) SetStaticIP(interfaceName, ip, netmask, gateway, dns string) error {
|
||||
log.Printf("Setting static IP for interface %s: %s/%s, gateway %s", interfaceName, ip, netmask, gateway)
|
||||
|
||||
// Очищаем текущие IP адреса
|
||||
exec.Command("sudo", "ip", "addr", "flush", "dev", interfaceName).Run()
|
||||
|
||||
// Добавляем новый IP
|
||||
cidr := netmask
|
||||
if cidr == "" {
|
||||
cidr = "24"
|
||||
@@ -251,13 +238,9 @@ func (nm *NetworkManager) SetStaticIP(interfaceName, ip, netmask, gateway, dns s
|
||||
return err
|
||||
}
|
||||
|
||||
// Поднимаем интерфейс
|
||||
exec.Command("sudo", "ip", "link", "set", interfaceName, "up").Run()
|
||||
|
||||
// Удаляем старый шлюз
|
||||
exec.Command("sudo", "ip", "route", "del", "default", "dev", interfaceName).Run()
|
||||
|
||||
// Добавляем новый шлюз
|
||||
if gateway != "" {
|
||||
routeCmd := exec.Command("sudo", "ip", "route", "add", "default", "via", gateway, "dev", interfaceName)
|
||||
if output, err := routeCmd.CombinedOutput(); err != nil {
|
||||
@@ -267,7 +250,6 @@ func (nm *NetworkManager) SetStaticIP(interfaceName, ip, netmask, gateway, dns s
|
||||
}
|
||||
}
|
||||
|
||||
// Настраиваем DNS
|
||||
if dns != "" {
|
||||
if err := nm.setDNS(dns); err != nil {
|
||||
log.Printf("Warning: failed to set DNS: %v", err)
|
||||
@@ -278,7 +260,6 @@ func (nm *NetworkManager) SetStaticIP(interfaceName, ip, netmask, gateway, dns s
|
||||
return nil
|
||||
}
|
||||
|
||||
// setDNS устанавливает DNS серверы
|
||||
func (nm *NetworkManager) setDNS(dnsServers string) error {
|
||||
servers := strings.Split(dnsServers, ",")
|
||||
|
||||
@@ -299,34 +280,42 @@ func (nm *NetworkManager) setDNS(dnsServers string) error {
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
// RenewDHCP обновляет DHCP аренду
|
||||
func (nm *NetworkManager) RenewDHCP(interfaceName string) error {
|
||||
log.Printf("Renewing DHCP for interface %s", interfaceName)
|
||||
|
||||
exec.Command("sudo", "dhclient", "-r", interfaceName).Run()
|
||||
|
||||
// Для ARM добавляем задержку
|
||||
if optimization.IsARM() {
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
|
||||
return nm.SetDHCP(interfaceName)
|
||||
}
|
||||
|
||||
// SetInterfaceUp поднимает интерфейс
|
||||
func (nm *NetworkManager) SetInterfaceUp(name string) error {
|
||||
cmd := exec.Command("sudo", "ip", "link", "set", name, "up")
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
// SetInterfaceDown опускает интерфейс
|
||||
func (nm *NetworkManager) SetInterfaceDown(name string) error {
|
||||
cmd := exec.Command("sudo", "ip", "link", "set", name, "down")
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
// ScanWiFiNetworks сканирует доступные WiFi сети
|
||||
func (nm *NetworkManager) ScanWiFiNetworks(interfaceName string) ([]WiFiNetwork, error) {
|
||||
// Поднимаем интерфейс
|
||||
nm.SetInterfaceUp(interfaceName)
|
||||
|
||||
// Сканируем
|
||||
cmd := exec.Command("sudo", "iwlist", interfaceName, "scan")
|
||||
// Для ARM используем более короткий таймаут
|
||||
timeout := 30 * time.Second
|
||||
if optimization.IsARM() {
|
||||
timeout = 15 * time.Second
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
cmd := exec.CommandContext(ctx, "sudo", "iwlist", interfaceName, "scan")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
log.Printf("Error scanning WiFi: %v", err)
|
||||
@@ -358,7 +347,6 @@ func (nm *NetworkManager) ScanWiFiNetworks(interfaceName string) ([]WiFiNetwork,
|
||||
if idx2 := strings.Index(qualityPart, "/"); idx2 != -1 {
|
||||
var quality int
|
||||
fmt.Sscanf(qualityPart, "%d", &quality)
|
||||
// Нормализуем до 100% (максимальное значение обычно 70)
|
||||
currentNetwork.Strength = quality * 100 / 70
|
||||
if currentNetwork.Strength > 100 {
|
||||
currentNetwork.Strength = 100
|
||||
@@ -381,16 +369,13 @@ func (nm *NetworkManager) ScanWiFiNetworks(interfaceName string) ([]WiFiNetwork,
|
||||
return networks, nil
|
||||
}
|
||||
|
||||
// ConnectToWiFi подключается к WiFi сети
|
||||
func (nm *NetworkManager) ConnectToWiFi(interfaceName, ssid, password, country string) error {
|
||||
log.Printf("Connecting to WiFi: %s on %s", ssid, interfaceName)
|
||||
|
||||
// Устанавливаем страну
|
||||
if country != "" {
|
||||
exec.Command("sudo", "iw", "reg", "set", country).Run()
|
||||
}
|
||||
|
||||
// Создаем конфиг для wpa_supplicant
|
||||
configPath := "/tmp/wpa_supplicant.conf"
|
||||
var configContent string
|
||||
|
||||
@@ -413,44 +398,38 @@ network={
|
||||
return err
|
||||
}
|
||||
|
||||
// Убиваем старые процессы wpa_supplicant
|
||||
exec.Command("sudo", "killall", "wpa_supplicant").Run()
|
||||
|
||||
// Запускаем wpa_supplicant
|
||||
// Для ARM добавляем задержку
|
||||
if optimization.IsARM() {
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
|
||||
cmd := exec.Command("sudo", "wpa_supplicant", "-B", "-i", interfaceName, "-c", configPath)
|
||||
if err := cmd.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Получаем IP через DHCP
|
||||
exec.Command("sudo", "dhclient", "-r", interfaceName).Run()
|
||||
exec.Command("sudo", "dhclient", interfaceName).Run()
|
||||
|
||||
// Поднимаем интерфейс
|
||||
nm.SetInterfaceUp(interfaceName)
|
||||
|
||||
log.Printf("Connected to WiFi: %s", ssid)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DisconnectWiFi отключает WiFi
|
||||
func (nm *NetworkManager) DisconnectWiFi(interfaceName string) error {
|
||||
log.Printf("Disconnecting WiFi from %s", interfaceName)
|
||||
|
||||
// Убиваем wpa_supplicant
|
||||
exec.Command("sudo", "killall", "wpa_supplicant").Run()
|
||||
|
||||
// Очищаем IP
|
||||
exec.Command("sudo", "ip", "addr", "flush", "dev", interfaceName).Run()
|
||||
|
||||
// Опускаем интерфейс
|
||||
nm.SetInterfaceDown(interfaceName)
|
||||
|
||||
log.Printf("WiFi disconnected from %s", interfaceName)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetWiFiStatus возвращает статус WiFi подключения
|
||||
func (nm *NetworkManager) GetWiFiStatus(interfaceName string) (bool, string) {
|
||||
cmd := exec.Command("iwconfig", interfaceName)
|
||||
output, err := cmd.Output()
|
||||
@@ -462,7 +441,6 @@ func (nm *NetworkManager) GetWiFiStatus(interfaceName string) (bool, string) {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
// Извлекаем ESSID
|
||||
lines := strings.Split(string(output), "\n")
|
||||
for _, line := range lines {
|
||||
if strings.Contains(line, "ESSID:") && !strings.Contains(line, "off/any") {
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
package optimization
|
||||
|
||||
import (
|
||||
"log"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
arch string
|
||||
isARM bool
|
||||
isARM64 bool
|
||||
isARMHF bool
|
||||
isAMD64 bool
|
||||
optimizeOnce sync.Once
|
||||
)
|
||||
|
||||
func init() {
|
||||
detectArchitecture()
|
||||
}
|
||||
|
||||
func detectArchitecture() {
|
||||
arch = runtime.GOARCH
|
||||
isAMD64 = arch == "amd64"
|
||||
isARM64 = arch == "arm64"
|
||||
isARMHF = arch == "arm" || arch == "armv7l" || arch == "armv6l"
|
||||
isARM = isARM64 || isARMHF
|
||||
|
||||
log.Printf("Architecture detected: %s", arch)
|
||||
log.Printf("Optimizations: ARM=%v, ARM64=%v, ARMHF=%v, AMD64=%v", isARM, isARM64, isARMHF, isAMD64)
|
||||
}
|
||||
|
||||
// GetArch возвращает текущую архитектуру
|
||||
func GetArch() string {
|
||||
return arch
|
||||
}
|
||||
|
||||
// IsARM возвращает true для ARM архитектур
|
||||
func IsARM() bool {
|
||||
return isARM
|
||||
}
|
||||
|
||||
// IsARM64 возвращает true для ARM64
|
||||
func IsARM64() bool {
|
||||
return isARM64
|
||||
}
|
||||
|
||||
// IsARMHF возвращает true для ARMHF (32-bit ARM)
|
||||
func IsARMHF() bool {
|
||||
return isARMHF
|
||||
}
|
||||
|
||||
// IsAMD64 возвращает true для AMD64
|
||||
func IsAMD64() bool {
|
||||
return isAMD64
|
||||
}
|
||||
|
||||
// GetBufferSize возвращает оптимальный размер буфера для архитектуры
|
||||
func GetBufferSize(defaultSize, armSize, arm64Size, armhfSize int) int {
|
||||
switch {
|
||||
case IsARM64():
|
||||
return arm64Size
|
||||
case IsARMHF():
|
||||
return armhfSize
|
||||
case IsARM():
|
||||
return armSize
|
||||
default:
|
||||
return defaultSize
|
||||
}
|
||||
}
|
||||
|
||||
// GetTimeout возвращает оптимизированный таймаут
|
||||
func GetTimeout(defaultTimeout, armTimeout time.Duration) time.Duration {
|
||||
if IsARM() {
|
||||
return armTimeout
|
||||
}
|
||||
return defaultTimeout
|
||||
}
|
||||
|
||||
// GetInterval возвращает оптимизированный интервал
|
||||
func GetInterval(defaultInterval, armInterval time.Duration) time.Duration {
|
||||
if IsARM() {
|
||||
return armInterval
|
||||
}
|
||||
return defaultInterval
|
||||
}
|
||||
|
||||
// ShouldParallel возвращает true если можно использовать параллельное выполнение
|
||||
func ShouldParallel() bool {
|
||||
return !IsARM() // Только на AMD64 используем параллелизацию
|
||||
}
|
||||
|
||||
// GetCPULimit возвращает оптимальное количество CPU для архитектуры
|
||||
func GetCPULimit() int {
|
||||
switch {
|
||||
case IsARM64():
|
||||
return 2 // Orange Pi Zero 2W - ограничиваем для стабильности
|
||||
case IsARMHF():
|
||||
return 1 // Старые ARM устройства
|
||||
default:
|
||||
return runtime.NumCPU()
|
||||
}
|
||||
}
|
||||
|
||||
// GetLogLevel возвращает уровень логирования
|
||||
func GetLogLevel() string {
|
||||
if IsARM() {
|
||||
return "WARNING" // Меньше логов на ARM
|
||||
}
|
||||
return "INFO"
|
||||
}
|
||||
|
||||
// GetWorkerCount возвращает количество воркеров для архитектуры
|
||||
func GetWorkerCount(defaultCount, armCount int) int {
|
||||
if IsARM() {
|
||||
return armCount
|
||||
}
|
||||
return defaultCount
|
||||
}
|
||||
|
||||
// SleepForARM добавляет задержку для ARM устройств
|
||||
func SleepForARM(duration time.Duration) {
|
||||
if IsARM() {
|
||||
time.Sleep(duration)
|
||||
}
|
||||
}
|
||||
|
||||
// OptimizeGoroutines устанавливает оптимальное количество горутин
|
||||
func OptimizeGoroutines() {
|
||||
if IsARM() {
|
||||
// Ограничиваем количество горутин на ARM
|
||||
runtime.GOMAXPROCS(GetCPULimit())
|
||||
log.Printf("GOMAXPROCS set to %d for ARM optimization", GetCPULimit())
|
||||
}
|
||||
}
|
||||
+41
-12
@@ -15,6 +15,7 @@ import (
|
||||
|
||||
"audio-recorder/database"
|
||||
"audio-recorder/logger"
|
||||
"audio-recorder/optimization"
|
||||
)
|
||||
|
||||
type Channel struct {
|
||||
@@ -62,9 +63,34 @@ type ChannelConfig struct {
|
||||
SMBFolder string `json:"smb_folder"`
|
||||
}
|
||||
|
||||
// Константы для разных архитектур
|
||||
const (
|
||||
bufferSizeAMD64 = 4096
|
||||
bufferSizeARM64 = 2048
|
||||
bufferSizeARMHF = 1024
|
||||
)
|
||||
|
||||
var recorderInstance *Recorder
|
||||
var deviceManager interface{}
|
||||
|
||||
type RecordingCompletedCallback func(filename string, filePath string, smbFolder string)
|
||||
|
||||
var onRecordingCompleted RecordingCompletedCallback
|
||||
|
||||
func getOptimalBufferSize() int {
|
||||
return optimization.GetBufferSize(bufferSizeAMD64, bufferSizeARM64, bufferSizeARM64, bufferSizeARMHF)
|
||||
}
|
||||
|
||||
func getOptimalAudioBufferSize(sampleRate, channels int) int {
|
||||
size := sampleRate * channels * 2 * 2
|
||||
if optimization.IsARM() {
|
||||
if size > 32768 {
|
||||
size = 32768
|
||||
}
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
func SetRecorderInstance(rec *Recorder) {
|
||||
recorderInstance = rec
|
||||
}
|
||||
@@ -81,10 +107,6 @@ func GetDeviceManager() interface{} {
|
||||
return deviceManager
|
||||
}
|
||||
|
||||
type RecordingCompletedCallback func(filename string, filePath string, smbFolder string)
|
||||
|
||||
var onRecordingCompleted RecordingCompletedCallback
|
||||
|
||||
func SetRecordingCompletedCallback(callback RecordingCompletedCallback) {
|
||||
onRecordingCompleted = callback
|
||||
}
|
||||
@@ -93,6 +115,8 @@ func NewChannel(id, name, device string, duration, sampleRate, channels int, for
|
||||
log.Printf("NewChannel: id=%s, name=%s, device=%s, duration=%d, sampleRate=%d, channels=%d, format=%s, autoRestart=%v",
|
||||
id, name, device, duration, sampleRate, channels, format, autoRestart)
|
||||
|
||||
audioDataSize := getOptimalAudioBufferSize(sampleRate, channels)
|
||||
|
||||
return &Channel{
|
||||
ID: id,
|
||||
Name: name,
|
||||
@@ -105,7 +129,7 @@ func NewChannel(id, name, device string, duration, sampleRate, channels int, for
|
||||
SMBFolder: "",
|
||||
CreatedAt: time.Now(),
|
||||
OutputDir: outputDir,
|
||||
audioData: make([]byte, 0, sampleRate*channels*2),
|
||||
audioData: make([]byte, 0, audioDataSize),
|
||||
stopChan: make(chan struct{}),
|
||||
ScheduleEnabled: false,
|
||||
}
|
||||
@@ -203,8 +227,11 @@ func (ch *Channel) StartRecording(outputDir string, testDeviceFunc func(string)
|
||||
|
||||
ch.cmd = cmd
|
||||
|
||||
// Оптимизированный буфер для архитектуры
|
||||
bufferSize := getOptimalBufferSize()
|
||||
|
||||
go func() {
|
||||
buf := make([]byte, 1024)
|
||||
buf := make([]byte, 256)
|
||||
for {
|
||||
select {
|
||||
case <-ch.ctx.Done():
|
||||
@@ -223,9 +250,9 @@ func (ch *Channel) StartRecording(outputDir string, testDeviceFunc func(string)
|
||||
}
|
||||
}()
|
||||
|
||||
ch.audioData = make([]byte, 0, ch.SampleRate*ch.Channels*2*2)
|
||||
ch.audioData = make([]byte, 0, getOptimalAudioBufferSize(ch.SampleRate, ch.Channels))
|
||||
go func() {
|
||||
buffer := make([]byte, 4096)
|
||||
buffer := make([]byte, bufferSize)
|
||||
for {
|
||||
select {
|
||||
case <-ch.ctx.Done():
|
||||
@@ -237,7 +264,7 @@ func (ch *Channel) StartRecording(outputDir string, testDeviceFunc func(string)
|
||||
if n > 0 {
|
||||
ch.mu.Lock()
|
||||
ch.audioData = append(ch.audioData, buffer[:n]...)
|
||||
maxBufferSize := ch.SampleRate * ch.Channels * 2 * 2
|
||||
maxBufferSize := getOptimalAudioBufferSize(ch.SampleRate, ch.Channels)
|
||||
if len(ch.audioData) > maxBufferSize {
|
||||
ch.audioData = ch.audioData[len(ch.audioData)-maxBufferSize:]
|
||||
}
|
||||
@@ -310,7 +337,6 @@ func (ch *Channel) StopRecording() {
|
||||
ch.stopOnce.Do(func() {
|
||||
log.Printf("=== STOPPING RECORDING for channel %s ===", ch.ID)
|
||||
|
||||
// Сохраняем информацию о файле до остановки
|
||||
currentFile := ch.CurrentFile
|
||||
currentPath := ""
|
||||
smbFolder := ch.SMBFolder
|
||||
@@ -391,7 +417,6 @@ func (ch *Channel) StopRecording() {
|
||||
|
||||
ch.saveStateToDB()
|
||||
|
||||
// Проверяем существует ли файл
|
||||
fileExists := false
|
||||
if currentPath != "" {
|
||||
if _, err := os.Stat(currentPath); err == nil {
|
||||
@@ -402,7 +427,6 @@ func (ch *Channel) StopRecording() {
|
||||
log.Printf("DEBUG: fileExists=%v, currentPath=%s, smbFolder=%s, onRecordingCompleted=%v",
|
||||
fileExists, currentPath, smbFolder, onRecordingCompleted != nil)
|
||||
|
||||
// Отправляем файл на SMB если есть файл и папка SMB указана
|
||||
if fileExists && currentPath != "" && smbFolder != "" && onRecordingCompleted != nil {
|
||||
log.Printf("✅ Sending file to SMB: %s, folder: %s, path: %s", currentFile, smbFolder, currentPath)
|
||||
|
||||
@@ -591,6 +615,11 @@ func (ch *Channel) StartSchedule() {
|
||||
ch.ID, targetStart.Format("2006-01-02 15:04:05"), startDuration,
|
||||
targetEnd.Format("2006-01-02 15:04:05"), endDuration)
|
||||
|
||||
// Для ARM добавляем небольшую задержку перед стартом
|
||||
if optimization.IsARM() {
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
|
||||
time.AfterFunc(startDuration, func() {
|
||||
log.Printf("Schedule: Starting recording for channel %s", ch.ID)
|
||||
db := database.GetDB()
|
||||
|
||||
+49
-8
@@ -8,6 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
"audio-recorder/database"
|
||||
"audio-recorder/optimization"
|
||||
)
|
||||
|
||||
type Recorder struct {
|
||||
@@ -21,6 +22,11 @@ func NewRecorder(outputDir string) *Recorder {
|
||||
channels: make(map[string]*Channel),
|
||||
OutputDir: outputDir,
|
||||
}
|
||||
|
||||
if optimization.IsARM() {
|
||||
log.Println("ARM mode: using optimized channel loading")
|
||||
}
|
||||
|
||||
r.loadChannelsFromDB()
|
||||
return r
|
||||
}
|
||||
@@ -38,6 +44,12 @@ func (r *Recorder) loadChannelsFromDB() {
|
||||
return
|
||||
}
|
||||
|
||||
// Для ARM добавляем задержку между загрузкой каналов
|
||||
delay := 0
|
||||
if optimization.IsARM() {
|
||||
delay = 100 // миллисекунд
|
||||
}
|
||||
|
||||
for _, chConfig := range channels {
|
||||
channel := NewChannel(
|
||||
chConfig.ID, chConfig.Name, chConfig.Device,
|
||||
@@ -46,7 +58,6 @@ func (r *Recorder) loadChannelsFromDB() {
|
||||
)
|
||||
channel.SMBFolder = chConfig.SMBFolder
|
||||
|
||||
// Загружаем расписание
|
||||
schedule, _ := db.GetSchedule(chConfig.ID)
|
||||
if schedule != nil {
|
||||
channel.ScheduleEnabled = schedule.Enabled
|
||||
@@ -57,9 +68,12 @@ func (r *Recorder) loadChannelsFromDB() {
|
||||
channel.RepeatDaily = schedule.RepeatDaily
|
||||
|
||||
if schedule.Enabled {
|
||||
// Добавляем задержку перед стартом расписания для полной инициализации
|
||||
go func(ch *Channel) {
|
||||
time.Sleep(2 * time.Second)
|
||||
sleepTime := 2 * time.Second
|
||||
if optimization.IsARM() {
|
||||
sleepTime = 3 * time.Second
|
||||
}
|
||||
time.Sleep(sleepTime)
|
||||
ch.StartSchedule()
|
||||
}(channel)
|
||||
}
|
||||
@@ -68,9 +82,14 @@ func (r *Recorder) loadChannelsFromDB() {
|
||||
r.channels[channel.ID] = channel
|
||||
os.MkdirAll(filepath.Join(recordingsDir, channel.ID), 0755)
|
||||
log.Printf("Loaded channel %s from database", chConfig.ID)
|
||||
|
||||
if delay > 0 {
|
||||
time.Sleep(time.Duration(delay) * time.Millisecond)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AddChannel добавляет новый канал
|
||||
func (r *Recorder) AddChannel(channel *Channel) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
@@ -105,6 +124,7 @@ func (r *Recorder) AddChannel(channel *Channel) {
|
||||
db.SaveChannel(config)
|
||||
}
|
||||
|
||||
// GetChannel возвращает канал по ID
|
||||
func (r *Recorder) GetChannel(id string) (*Channel, bool) {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
@@ -112,6 +132,7 @@ func (r *Recorder) GetChannel(id string) (*Channel, bool) {
|
||||
return ch, exists
|
||||
}
|
||||
|
||||
// GetAllChannels возвращает все каналы
|
||||
func (r *Recorder) GetAllChannels() map[string]*Channel {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
@@ -122,6 +143,7 @@ func (r *Recorder) GetAllChannels() map[string]*Channel {
|
||||
return result
|
||||
}
|
||||
|
||||
// DeleteChannel удаляет канал
|
||||
func (r *Recorder) DeleteChannel(id string) bool {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
@@ -140,6 +162,7 @@ func (r *Recorder) DeleteChannel(id string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// UpdateChannel обновляет конфигурацию канала
|
||||
func (r *Recorder) UpdateChannel(id string, config ChannelConfig) bool {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
@@ -166,6 +189,7 @@ func (r *Recorder) UpdateChannel(id string, config ChannelConfig) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// GetChannelsList возвращает список каналов для API
|
||||
func (r *Recorder) GetChannelsList() []map[string]interface{} {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
@@ -204,12 +228,29 @@ func (r *Recorder) SaveAllStates() {
|
||||
|
||||
log.Printf("=== Сохранение состояний всех каналов ===")
|
||||
|
||||
for id, ch := range r.channels {
|
||||
if err := db.SetChannelState(id, ch.IsRecording); err != nil {
|
||||
log.Printf("Failed to save state for channel %s: %v", id, err)
|
||||
} else {
|
||||
log.Printf("✅ Saved state for channel %s: %v", id, ch.IsRecording)
|
||||
// На ARM используем последовательное сохранение
|
||||
if optimization.IsARM() {
|
||||
for id, ch := range r.channels {
|
||||
if err := db.SetChannelState(id, ch.IsRecording); err != nil {
|
||||
log.Printf("Failed to save state for channel %s: %v", id, err)
|
||||
} else {
|
||||
log.Printf("✅ Saved state for channel %s: %v", id, ch.IsRecording)
|
||||
}
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
} else {
|
||||
// На AMD64 можно параллельно
|
||||
var wg sync.WaitGroup
|
||||
for id, ch := range r.channels {
|
||||
wg.Add(1)
|
||||
go func(id string, ch *Channel) {
|
||||
defer wg.Done()
|
||||
if err := db.SetChannelState(id, ch.IsRecording); err != nil {
|
||||
log.Printf("Failed to save state for channel %s: %v", id, err)
|
||||
}
|
||||
}(id, ch)
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
log.Printf("=== Сохранение состояний завершено ===")
|
||||
|
||||
@@ -66,15 +66,12 @@ func (sc *StateChecker) run() {
|
||||
saveTicker := time.NewTicker(30 * time.Second)
|
||||
defer saveTicker.Stop()
|
||||
|
||||
for {
|
||||
for range ticker.C {
|
||||
select {
|
||||
case <-sc.stopChan:
|
||||
log.Println("State checker stopping...")
|
||||
return
|
||||
case <-ticker.C:
|
||||
default:
|
||||
sc.checkStates()
|
||||
case <-saveTicker.C:
|
||||
sc.forceSaveStates()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+48
-56
@@ -17,15 +17,16 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
|
||||
"audio-recorder/database"
|
||||
"audio-recorder/devices"
|
||||
"audio-recorder/logger"
|
||||
"audio-recorder/network"
|
||||
"audio-recorder/optimization"
|
||||
"audio-recorder/recorder"
|
||||
"audio-recorder/smb"
|
||||
"audio-recorder/vpn"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
// WebSocket upgrader с настройками безопасности
|
||||
@@ -1111,7 +1112,12 @@ func HandleLiveStream(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}()
|
||||
|
||||
buffer := make([]byte, 8192)
|
||||
// Оптимизированный буфер для ARM
|
||||
bufferSize := 8192
|
||||
if optimization.IsARM() {
|
||||
bufferSize = 4096
|
||||
}
|
||||
buffer := make([]byte, bufferSize)
|
||||
done := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
@@ -1828,7 +1834,6 @@ func HandleBrowseDirectory(w http.ResponseWriter, r *http.Request) {
|
||||
path = "/"
|
||||
}
|
||||
|
||||
// Разрешенные пути для просмотра
|
||||
allowedPrefixes := []string{"/etc/openvpn", "/etc/wireguard", "/home", "/root", "/opt", "/var", "/tmp"}
|
||||
|
||||
absPath, err := filepath.Abs(path)
|
||||
@@ -1979,7 +1984,7 @@ func HandleInterfaceControl(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
var req struct {
|
||||
Interface string `json:"interface"`
|
||||
Action string `json:"action"` // up/down
|
||||
Action string `json:"action"`
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
@@ -2008,8 +2013,6 @@ func HandleInterfaceControl(w http.ResponseWriter, r *http.Request) {
|
||||
sendJSONResponse(w, http.StatusOK, map[string]string{"status": "ok"})
|
||||
}
|
||||
|
||||
// ... весь код до этих функций остается без изменений ...
|
||||
|
||||
// HandleWiFiScan - сканирование WiFi сетей
|
||||
func HandleWiFiScan(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
@@ -2023,6 +2026,8 @@ func HandleWiFiScan(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Для ARM используем контекст с таймаутом
|
||||
|
||||
nm := network.GetManager()
|
||||
networks, err := nm.ScanWiFiNetworks(iface)
|
||||
if err != nil {
|
||||
@@ -2240,7 +2245,6 @@ func HandleTimeSettings(w http.ResponseWriter, r *http.Request) {
|
||||
timezone = "Europe/Moscow"
|
||||
}
|
||||
|
||||
// Получаем текущее системное время
|
||||
currentTime := time.Now()
|
||||
utcTime := time.Now().UTC()
|
||||
|
||||
@@ -2267,7 +2271,6 @@ func HandleTimeSettings(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Сохраняем настройки
|
||||
if req.TimeMode != "" {
|
||||
db.SetSetting("time_mode", req.TimeMode)
|
||||
}
|
||||
@@ -2290,7 +2293,6 @@ func HandleTimeSettings(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Устанавливаем часовой пояс
|
||||
if req.Timezone != "" {
|
||||
if err := setTimezone(req.Timezone); err != nil {
|
||||
log.Printf("Warning: failed to set timezone: %v", err)
|
||||
@@ -2338,13 +2340,11 @@ func getTimezoneOffset() string {
|
||||
func syncWithNTP(server string) error {
|
||||
log.Printf("Syncing time with NTP server: %s", server)
|
||||
|
||||
// Используем ntpdate
|
||||
cmd := exec.Command("sudo", "ntpdate", "-u", server)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
log.Printf("ntpdate failed: %v, output: %s", err, output)
|
||||
|
||||
// Пробуем timedatectl
|
||||
cmd2 := exec.Command("sudo", "timedatectl", "set-ntp", "true")
|
||||
if err2 := cmd2.Run(); err2 != nil {
|
||||
return fmt.Errorf("failed to sync time: %v", err)
|
||||
@@ -2358,7 +2358,6 @@ func syncWithNTP(server string) error {
|
||||
func setSystemTime(dateTime string, timezone string) error {
|
||||
log.Printf("Setting system time: %s with timezone: %s", dateTime, timezone)
|
||||
|
||||
// Парсим дату и время в местном времени
|
||||
loc, err := time.LoadLocation(timezone)
|
||||
if err != nil {
|
||||
loc = time.Local
|
||||
@@ -2369,10 +2368,8 @@ func setSystemTime(dateTime string, timezone string) error {
|
||||
return fmt.Errorf("invalid datetime format: %v", err)
|
||||
}
|
||||
|
||||
// Преобразуем в UTC для системных часов
|
||||
utcTime := t.UTC()
|
||||
|
||||
// Устанавливаем системное время в UTC
|
||||
dateStr := utcTime.Format("2006-01-02")
|
||||
timeStr := utcTime.Format("15:04:05")
|
||||
|
||||
@@ -2382,7 +2379,6 @@ func setSystemTime(dateTime string, timezone string) error {
|
||||
return fmt.Errorf("failed to set system time: %v", err)
|
||||
}
|
||||
|
||||
// Синхронизируем аппаратные часы
|
||||
exec.Command("sudo", "hwclock", "--systohc", "--utc").Run()
|
||||
|
||||
log.Printf("System time set to UTC: %s", utcTime.Format("2006-01-02 15:04:05"))
|
||||
@@ -2392,18 +2388,14 @@ func setSystemTime(dateTime string, timezone string) error {
|
||||
func setTimezone(timezone string) error {
|
||||
log.Printf("Setting timezone: %s", timezone)
|
||||
|
||||
// Проверяем существование файла зоны
|
||||
zoneFile := "/usr/share/zoneinfo/" + timezone
|
||||
if _, err := os.Stat(zoneFile); err != nil {
|
||||
return fmt.Errorf("timezone not found: %s", timezone)
|
||||
}
|
||||
|
||||
// Устанавливаем часовой пояс через timedatectl
|
||||
cmd := exec.Command("sudo", "timedatectl", "set-timezone", timezone)
|
||||
if output, err := cmd.CombinedOutput(); err != nil {
|
||||
log.Printf("timedatectl failed: %v, output: %s", err, output)
|
||||
|
||||
// Альтернативный способ через ln
|
||||
exec.Command("sudo", "ln", "-sf", zoneFile, "/etc/localtime").Run()
|
||||
}
|
||||
|
||||
@@ -2427,7 +2419,7 @@ func HandleGetDisks(w http.ResponseWriter, r *http.Request) {
|
||||
sendJSONResponse(w, http.StatusOK, disks)
|
||||
}
|
||||
|
||||
// HandleMountDisk - монтирование диска с сохранением настройки
|
||||
// HandleMountDisk - монтирование диска
|
||||
func HandleMountDisk(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
@@ -2466,7 +2458,7 @@ func HandleMountDisk(w http.ResponseWriter, r *http.Request) {
|
||||
sendJSONResponse(w, http.StatusOK, map[string]string{"status": "mounted"})
|
||||
}
|
||||
|
||||
// HandleUnmountDisk - отмонтирование диска с удалением настройки
|
||||
// HandleUnmountDisk - отмонтирование диска
|
||||
func HandleUnmountDisk(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
@@ -2499,40 +2491,6 @@ func HandleUnmountDisk(w http.ResponseWriter, r *http.Request) {
|
||||
sendJSONResponse(w, http.StatusOK, map[string]string{"status": "unmounted"})
|
||||
}
|
||||
|
||||
// HandleGetMountConfigs - получение сохраненных настроек монтирования
|
||||
func HandleGetMountConfigs(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
dm := network.GetDiskManager()
|
||||
configs, err := dm.GetAllMountConfigsFromDB()
|
||||
if err != nil {
|
||||
sendJSONError(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
sendJSONResponse(w, http.StatusOK, configs)
|
||||
}
|
||||
|
||||
// HandleAutoMount - принудительный запуск автомонтирования
|
||||
func HandleAutoMount(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
dm := network.GetDiskManager()
|
||||
err := dm.AutoMountDisks()
|
||||
if err != nil {
|
||||
sendJSONError(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
sendJSONResponse(w, http.StatusOK, map[string]string{"status": "auto_mount_completed"})
|
||||
}
|
||||
|
||||
// HandleFormatDisk - форматирование диска
|
||||
func HandleFormatDisk(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
@@ -2569,3 +2527,37 @@ func HandleFormatDisk(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
sendJSONResponse(w, http.StatusOK, map[string]string{"status": "formatted"})
|
||||
}
|
||||
|
||||
// HandleGetMountConfigs - получение сохраненных настроек монтирования
|
||||
func HandleGetMountConfigs(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
dm := network.GetDiskManager()
|
||||
configs, err := dm.GetAllMountConfigsFromDB()
|
||||
if err != nil {
|
||||
sendJSONError(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
sendJSONResponse(w, http.StatusOK, configs)
|
||||
}
|
||||
|
||||
// HandleAutoMount - принудительный запуск автомонтирования
|
||||
func HandleAutoMount(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
dm := network.GetDiskManager()
|
||||
err := dm.AutoMountDisks()
|
||||
if err != nil {
|
||||
sendJSONError(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
sendJSONResponse(w, http.StatusOK, map[string]string{"status": "auto_mount_completed"})
|
||||
}
|
||||
|
||||
+36
-3
@@ -1,9 +1,42 @@
|
||||
/* style.css - Dark Theme, белый текст, исправлены фоны */
|
||||
|
||||
/* CSS Reset для совместимости с разными браузерами */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
/* Для select элементов возвращаем стандартный вид */
|
||||
select {
|
||||
-webkit-appearance: menulist;
|
||||
-moz-appearance: menulist;
|
||||
appearance: menulist;
|
||||
}
|
||||
|
||||
/* Для input */
|
||||
input[type="text"],
|
||||
input[type="number"],
|
||||
input[type="password"],
|
||||
input[type="date"],
|
||||
input[type="time"] {
|
||||
-webkit-appearance: textfield;
|
||||
-moz-appearance: textfield;
|
||||
appearance: textfield;
|
||||
}
|
||||
|
||||
/* Для checkbox */
|
||||
input[type="checkbox"] {
|
||||
-webkit-appearance: checkbox;
|
||||
-moz-appearance: checkbox;
|
||||
appearance: checkbox;
|
||||
}
|
||||
|
||||
/* Для range slider */
|
||||
input[type="range"] {
|
||||
-webkit-appearance: slider-horizontal;
|
||||
-moz-appearance: slider-horizontal;
|
||||
appearance: slider-horizontal;
|
||||
}
|
||||
|
||||
body {
|
||||
|
||||
+55
-11
@@ -1,6 +1,7 @@
|
||||
package vpn
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
@@ -13,6 +14,7 @@ import (
|
||||
|
||||
"audio-recorder/database"
|
||||
"audio-recorder/logger"
|
||||
"audio-recorder/optimization"
|
||||
)
|
||||
|
||||
type OpenVPNClient struct {
|
||||
@@ -80,6 +82,11 @@ func (v *OpenVPNClient) Start() error {
|
||||
return fmt.Errorf("config file not found: %s", v.ConfigPath)
|
||||
}
|
||||
|
||||
// Для ARM добавляем задержку перед запуском
|
||||
if optimization.IsARM() {
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
|
||||
// Создаем auth файл с правильным форматированием
|
||||
var authFile string
|
||||
if v.AuthUser != "" && v.AuthPassword != "" {
|
||||
@@ -113,8 +120,15 @@ func (v *OpenVPNClient) Start() error {
|
||||
log.Printf("Starting OpenVPN with config: %s", v.ConfigPath)
|
||||
logger.Add("INFO", "OPENVPN", "Запуск OpenVPN | Конфиг: "+v.ConfigPath)
|
||||
|
||||
// Запускаем openvpn с sudo для прав root
|
||||
cmd := exec.Command("sudo", "openvpn", "--config", tempConfig)
|
||||
// Для ARM используем контекст с таймаутом
|
||||
var cmd *exec.Cmd
|
||||
if optimization.IsARM() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
cmd = exec.CommandContext(ctx, "sudo", "openvpn", "--config", tempConfig)
|
||||
} else {
|
||||
cmd = exec.Command("sudo", "openvpn", "--config", tempConfig)
|
||||
}
|
||||
|
||||
// Создаем pipes для логов
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
@@ -146,9 +160,14 @@ func (v *OpenVPNClient) Start() error {
|
||||
db := database.GetDB()
|
||||
db.SetSetting("openvpn_was_running", "true")
|
||||
|
||||
// Ждем установки подключения
|
||||
// Ждем установки подключения с разным таймаутом для разных архитектур
|
||||
timeout := 10 * time.Second
|
||||
if optimization.IsARM() {
|
||||
timeout = 15 * time.Second
|
||||
}
|
||||
|
||||
go func() {
|
||||
time.Sleep(10 * time.Second)
|
||||
time.Sleep(timeout)
|
||||
v.updateConnectionStatus()
|
||||
if !v.Connected {
|
||||
logger.Add("WARNING", "OPENVPN", "Подключение не установлено, проверьте логи")
|
||||
@@ -276,7 +295,11 @@ func (v *OpenVPNClient) parseOutput(line string) {
|
||||
}
|
||||
|
||||
func (v *OpenVPNClient) monitorConnection() {
|
||||
ticker := time.NewTicker(5 * time.Second)
|
||||
interval := 5 * time.Second
|
||||
if optimization.IsARM() {
|
||||
interval = 10 * time.Second
|
||||
}
|
||||
ticker := time.NewTicker(interval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
@@ -290,7 +313,11 @@ func (v *OpenVPNClient) monitorConnection() {
|
||||
}
|
||||
|
||||
func (v *OpenVPNClient) periodicHealthCheck() {
|
||||
ticker := time.NewTicker(30 * time.Second)
|
||||
interval := 30 * time.Second
|
||||
if optimization.IsARM() {
|
||||
interval = 45 * time.Second
|
||||
}
|
||||
ticker := time.NewTicker(interval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
@@ -305,10 +332,14 @@ func (v *OpenVPNClient) periodicHealthCheck() {
|
||||
if wasConnected && !v.Connected {
|
||||
logger.Add("WARNING", "OPENVPN", "Потеряно подключение, попытка переподключения")
|
||||
go func() {
|
||||
time.Sleep(5 * time.Second)
|
||||
sleepTime := 5 * time.Second
|
||||
if optimization.IsARM() {
|
||||
sleepTime = 8 * time.Second
|
||||
}
|
||||
time.Sleep(sleepTime)
|
||||
v.Reconnect()
|
||||
}()
|
||||
} else if v.Connected {
|
||||
} else if v.Connected && optimization.GetLogLevel() == "INFO" {
|
||||
log.Printf("OpenVPN health check: OK, IP: %s", v.ConnectionIP)
|
||||
}
|
||||
}
|
||||
@@ -328,7 +359,12 @@ func (v *OpenVPNClient) Reconnect() error {
|
||||
logger.Add("INFO", "OPENVPN", "Переподключение OpenVPN")
|
||||
|
||||
v.stopOpenVPN()
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
sleepTime := 3 * time.Second
|
||||
if optimization.IsARM() {
|
||||
sleepTime = 5 * time.Second
|
||||
}
|
||||
time.Sleep(sleepTime)
|
||||
|
||||
// Запускаем заново через Start
|
||||
v.mu.Unlock()
|
||||
@@ -490,7 +526,11 @@ func (v *OpenVPNClient) UpdateConfig(config OpenVPNConfig) error {
|
||||
go func() {
|
||||
v.Stop()
|
||||
if v.Enabled {
|
||||
time.Sleep(2 * time.Second)
|
||||
sleepTime := 2 * time.Second
|
||||
if optimization.IsARM() {
|
||||
sleepTime = 3 * time.Second
|
||||
}
|
||||
time.Sleep(sleepTime)
|
||||
v.Start()
|
||||
}
|
||||
}()
|
||||
@@ -537,7 +577,11 @@ func (v *OpenVPNClient) RestoreState() {
|
||||
log.Printf("Restoring OpenVPN connection...")
|
||||
logger.Add("INFO", "OPENVPN", "Восстановление подключения после перезагрузки")
|
||||
go func() {
|
||||
time.Sleep(5 * time.Second)
|
||||
sleepTime := 5 * time.Second
|
||||
if optimization.IsARM() {
|
||||
sleepTime = 8 * time.Second
|
||||
}
|
||||
time.Sleep(sleepTime)
|
||||
v.Start()
|
||||
}()
|
||||
}
|
||||
|
||||
+35
-63
@@ -12,14 +12,9 @@ import (
|
||||
|
||||
"audio-recorder/database"
|
||||
"audio-recorder/logger"
|
||||
"audio-recorder/optimization"
|
||||
)
|
||||
|
||||
type WireGuardConfig struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
ConfigPath string `json:"config_path"`
|
||||
Interface string `json:"interface"`
|
||||
}
|
||||
|
||||
type WireGuardClient struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Interface string `json:"interface"`
|
||||
@@ -38,6 +33,12 @@ type WireGuardClient struct {
|
||||
started bool
|
||||
}
|
||||
|
||||
type WireGuardConfig struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
ConfigPath string `json:"config_path"`
|
||||
Interface string `json:"interface"`
|
||||
}
|
||||
|
||||
var wgInstance *WireGuardClient
|
||||
var wgOnce sync.Once
|
||||
|
||||
@@ -97,17 +98,20 @@ func (w *WireGuardClient) Start() error {
|
||||
|
||||
w.stopWireGuard()
|
||||
|
||||
// Для ARM добавляем задержку
|
||||
if optimization.IsARM() {
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
|
||||
log.Printf("Starting WireGuard interface: %s", w.Interface)
|
||||
logger.Add("INFO", "WIREGUARD", "Запуск WireGuard | Интерфейс: "+w.Interface)
|
||||
|
||||
// Создаем конфиг для wg setconf (без Address)
|
||||
if err := w.createWgConfig(); err != nil {
|
||||
log.Printf("Failed to create wg config: %v", err)
|
||||
logger.Add("ERROR", "WIREGUARD", "Ошибка создания конфига: "+err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
// Ручной запуск
|
||||
if err := w.startManual(); err != nil {
|
||||
log.Printf("Failed to start WireGuard: %v", err)
|
||||
logger.Add("ERROR", "WIREGUARD", "Ошибка запуска: "+err.Error())
|
||||
@@ -139,7 +143,6 @@ func (w *WireGuardClient) createWgConfig() error {
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// Только параметры для wg setconf
|
||||
fmt.Fprintf(file, "[Interface]\n")
|
||||
fmt.Fprintf(file, "PrivateKey = %s\n", w.PrivateKey)
|
||||
|
||||
@@ -163,11 +166,11 @@ func (w *WireGuardClient) createWgConfig() error {
|
||||
func (w *WireGuardClient) startManual() error {
|
||||
log.Printf("=== Starting WireGuard manual setup ===")
|
||||
|
||||
// Удаляем старый интерфейс
|
||||
exec.Command("ip", "link", "delete", w.Interface).Run()
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
if optimization.IsARM() {
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
|
||||
// Создаем новый интерфейс
|
||||
log.Printf("Creating interface %s", w.Interface)
|
||||
createCmd := exec.Command("ip", "link", "add", w.Interface, "type", "wireguard")
|
||||
if output, err := createCmd.CombinedOutput(); err != nil {
|
||||
@@ -176,7 +179,6 @@ func (w *WireGuardClient) startManual() error {
|
||||
}
|
||||
log.Printf("Interface %s created successfully", w.Interface)
|
||||
|
||||
// Поднимаем интерфейс
|
||||
log.Printf("Bringing up interface %s", w.Interface)
|
||||
upCmd := exec.Command("ip", "link", "set", w.Interface, "up")
|
||||
if output, err := upCmd.CombinedOutput(); err != nil {
|
||||
@@ -185,7 +187,6 @@ func (w *WireGuardClient) startManual() error {
|
||||
}
|
||||
log.Printf("Interface %s is up", w.Interface)
|
||||
|
||||
// Применяем конфигурацию WireGuard
|
||||
configPath := "/etc/wireguard/" + w.Interface + ".conf"
|
||||
setconfCmd := exec.Command("wg", "setconf", w.Interface, configPath)
|
||||
if output, err := setconfCmd.CombinedOutput(); err != nil {
|
||||
@@ -194,7 +195,6 @@ func (w *WireGuardClient) startManual() error {
|
||||
}
|
||||
log.Printf("WireGuard config applied successfully")
|
||||
|
||||
// Добавляем IP адреса (разделяем по запятой)
|
||||
if w.Address != "" {
|
||||
addresses := strings.Split(w.Address, ",")
|
||||
for _, addr := range addresses {
|
||||
@@ -202,7 +202,6 @@ func (w *WireGuardClient) startManual() error {
|
||||
if addr == "" {
|
||||
continue
|
||||
}
|
||||
// Пропускаем IPv6 если есть
|
||||
if strings.Contains(addr, ":") {
|
||||
log.Printf("Skipping IPv6 address: %s", addr)
|
||||
continue
|
||||
@@ -217,7 +216,6 @@ func (w *WireGuardClient) startManual() error {
|
||||
}
|
||||
}
|
||||
|
||||
// Добавляем маршруты для AllowedIPs
|
||||
if w.PeerAllowedIPs != "" {
|
||||
allowedIPs := strings.Split(w.PeerAllowedIPs, ",")
|
||||
for _, ip := range allowedIPs {
|
||||
@@ -225,11 +223,9 @@ func (w *WireGuardClient) startManual() error {
|
||||
if ip == "" {
|
||||
continue
|
||||
}
|
||||
// Пропускаем IPv6
|
||||
if strings.Contains(ip, ":") {
|
||||
continue
|
||||
}
|
||||
// Пропускаем 0.0.0.0/0 (default route) - добавим отдельно
|
||||
if ip == "0.0.0.0/0" {
|
||||
continue
|
||||
}
|
||||
@@ -243,31 +239,27 @@ func (w *WireGuardClient) startManual() error {
|
||||
}
|
||||
}
|
||||
|
||||
// Настраиваем MTU
|
||||
if w.MTU > 0 {
|
||||
log.Printf("Setting MTU to %d on %s", w.MTU, w.Interface)
|
||||
mtuCmd := exec.Command("ip", "link", "set", "mtu", strconv.Itoa(w.MTU), "dev", w.Interface)
|
||||
mtuCmd.Run()
|
||||
}
|
||||
|
||||
// Показываем состояние WireGuard
|
||||
showCmd := exec.Command("wg", "show", w.Interface)
|
||||
showOutput, _ := showCmd.CombinedOutput()
|
||||
log.Printf("WireGuard state:\n%s", string(showOutput))
|
||||
|
||||
// Показываем маршруты
|
||||
routeCmd := exec.Command("ip", "route", "show", "dev", w.Interface)
|
||||
routeOutput, _ := routeCmd.CombinedOutput()
|
||||
log.Printf("Routes for %s:\n%s", w.Interface, string(routeOutput))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// testConnectivity проверяет реальную работоспособность VPN
|
||||
func (w *WireGuardClient) testConnectivity() bool {
|
||||
// Пытаемся пинговать стандартный IP сервера в VPN сети
|
||||
testIPs := []string{"10.8.0.1", "8.8.8.8", "1.1.1.1"}
|
||||
|
||||
// Для ARM уменьшаем количество проверок
|
||||
if optimization.IsARM() {
|
||||
testIPs = []string{"10.8.0.1", "8.8.8.8"}
|
||||
}
|
||||
|
||||
for _, testIP := range testIPs {
|
||||
cmd := exec.Command("ping", "-c", "1", "-W", "2", testIP)
|
||||
if err := cmd.Run(); err == nil {
|
||||
@@ -276,30 +268,11 @@ func (w *WireGuardClient) testConnectivity() bool {
|
||||
}
|
||||
}
|
||||
|
||||
// Пробуем альтернативные IP из AllowedIPs
|
||||
if w.PeerAllowedIPs != "" {
|
||||
ips := strings.Split(w.PeerAllowedIPs, ",")
|
||||
for _, ip := range ips {
|
||||
ip = strings.TrimSpace(ip)
|
||||
if strings.Contains(ip, "/") {
|
||||
ip = strings.Split(ip, "/")[0]
|
||||
}
|
||||
if strings.Contains(ip, ".") && !strings.Contains(ip, "0.0.0.0") && !strings.Contains(ip, "0.0.0") {
|
||||
cmd := exec.Command("ping", "-c", "1", "-W", "2", ip)
|
||||
if cmd.Run() == nil {
|
||||
log.Printf("Connectivity test OK: ping to %s successful", ip)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("Connectivity test FAILED: no ping responses")
|
||||
return false
|
||||
}
|
||||
|
||||
func (w *WireGuardClient) stopWireGuard() {
|
||||
// Удаляем маршруты
|
||||
if w.PeerAllowedIPs != "" {
|
||||
allowedIPs := strings.Split(w.PeerAllowedIPs, ",")
|
||||
for _, ip := range allowedIPs {
|
||||
@@ -313,7 +286,6 @@ func (w *WireGuardClient) stopWireGuard() {
|
||||
exec.Command("ip", "route", "del", ip, "dev", w.Interface).Run()
|
||||
}
|
||||
}
|
||||
// Удаляем интерфейс
|
||||
exec.Command("ip", "link", "delete", w.Interface).Run()
|
||||
w.started = false
|
||||
w.Connected = false
|
||||
@@ -321,7 +293,11 @@ func (w *WireGuardClient) stopWireGuard() {
|
||||
}
|
||||
|
||||
func (w *WireGuardClient) monitorConnection() {
|
||||
ticker := time.NewTicker(15 * time.Second)
|
||||
interval := 15 * time.Second
|
||||
if optimization.IsARM() {
|
||||
interval = 20 * time.Second
|
||||
}
|
||||
ticker := time.NewTicker(interval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
@@ -382,11 +358,9 @@ func (w *WireGuardClient) updateConnectionStatus() {
|
||||
defer w.mu.Unlock()
|
||||
|
||||
if err == nil && strings.Contains(string(output), "transfer:") {
|
||||
// WireGuard handshake есть, проверяем реальную связь
|
||||
if w.testConnectivity() {
|
||||
w.Connected = true
|
||||
} else {
|
||||
// Handshake есть, но трафик не проходит
|
||||
log.Printf("⚠️ WireGuard handshake exists but no connectivity")
|
||||
w.Connected = false
|
||||
}
|
||||
@@ -442,16 +416,6 @@ func (w *WireGuardClient) IsConnected() bool {
|
||||
return w.Connected && w.Enabled
|
||||
}
|
||||
|
||||
// IsReallyConnected проверяет реальное подключение (для VPN Manager)
|
||||
func (w *WireGuardClient) IsReallyConnected() bool {
|
||||
if !w.Enabled {
|
||||
return false
|
||||
}
|
||||
w.mu.RLock()
|
||||
defer w.mu.RUnlock()
|
||||
return w.Connected && w.testConnectivity()
|
||||
}
|
||||
|
||||
func (w *WireGuardClient) LoadFromDB() {
|
||||
db := database.GetDB()
|
||||
|
||||
@@ -486,7 +450,11 @@ func (w *WireGuardClient) LoadFromDB() {
|
||||
if w.Enabled && wasRunning == "true" && w.PrivateKey != "" && w.PeerPublicKey != "" {
|
||||
log.Printf("Restoring WireGuard connection...")
|
||||
go func() {
|
||||
time.Sleep(5 * time.Second)
|
||||
sleepTime := 5 * time.Second
|
||||
if optimization.IsARM() {
|
||||
sleepTime = 7 * time.Second
|
||||
}
|
||||
time.Sleep(sleepTime)
|
||||
w.Start()
|
||||
}()
|
||||
}
|
||||
@@ -554,7 +522,11 @@ func (w *WireGuardClient) UpdateConfig(interfaceName, privateKey, address, dns s
|
||||
go func() {
|
||||
w.Stop()
|
||||
if w.Enabled {
|
||||
time.Sleep(2 * time.Second)
|
||||
sleepTime := 2 * time.Second
|
||||
if optimization.IsARM() {
|
||||
sleepTime = 3 * time.Second
|
||||
}
|
||||
time.Sleep(sleepTime)
|
||||
w.Start()
|
||||
}
|
||||
}()
|
||||
|
||||
Reference in New Issue
Block a user