EN RU

Автоматическое управление белым списком IP адресов на MikroTik через веб-интерфейс с PHP

Данная статья представляет исчерпывающее руководство по организации системы автоматического добавления IP адресов пользователей сайта в белый список на MikroTik роутере. Система позволит пользователям, успешно прошедшим авторизацию на защищенном сайте sysadministrator.ru (192.168.1.100), получать доступ к определенным портам (3389, 44444, 55555, 55554) на MikroTik устройстве с IP 192.168.1.1. Рассматриваются различные методы реализации — от простых PHP скриптов до комплексных автоматизированных решений с высоким уровнем безопасности.[1][2][3]

Оглавление
  1. 1. Введение и архитектура решения
  2. 2. Метод 1: Базовая интеграция через PHP и REST API
  3. 3. Метод 2: Интеграция через SSH соединение
  4. 4. Метод 3: Использование классической MikroTik PHP API
  5. 5. Метод 4: Комплексное решение с веб-интерфейсом управления
  6. 6. Настройка инфраструктуры
  7. 7. Настройка MikroTik
  8. 8. Автоматизация и планирование задач
  9. 9. Обеспечение безопасности
  10. 10. Мониторинг и отладка
  11. Заключение и послесовие

1. Введение и архитектура решения

1.1. Обзор системы и требований

Система автоматического управления доступом предназначена для интеграции веб-сайта sysadministrator.ru с MikroTik роутером, работающим под управлением RouterOS 7.19.4. Основная цель — предоставить пользователям, успешно прошедшим базовую HTTP аутентификацию¹ на сайте, автоматический доступ к специфическим портам на сервере организации.[4][5][1]

Технические требования системы:

  • Веб-сервер: NGINX с PHP-FPM 8.3
  • Аутентификация: HTTP Basic Auth
  • Целевые порты: 3389 (RDP), 44444, 55555, 55554
  • MikroTik устройство: IP 192.168.1.1, RouterOS 7.19.4
  • База данных: Опционально MySQL/SQLite для логирования

Функциональные требования:

  1. Автоматическое определение IP адреса² аутентифицированного пользователя
  2. Добавление IP в белый список³ MikroTik через API
  3. Управление временем жизни записей в белом списке
  4. Логирование всех операций доступа
  5. Возможность ручного управления через веб-интерфейс[6][7]

1.2. Архитектурная схема интеграции

Архитектура решения состоит из следующих основных компонентов:[8][9]

Клиентский уровень:

  • Браузер пользователя с поддержкой HTTP Basic Auth
  • Сетевое подключение через различных провайдеров

Веб-сервер уровень:

  • NGINX как reverse proxy и обработчик статики
  • PHP-FPM 8.3 для выполнения бизнес-логики
  • Модули получения реального IP адреса пользователя[10][11]

Уровень интеграции:

  • PHP скрипты для работы с MikroTik API
  • Система кэширования запросов
  • Обработчики ошибок и логирования[12][6]

Уровень MikroTik:

  • RouterOS с активированными API сервисами
  • Firewall правила и Address Lists
  • Система мониторинга подключений[13][1]

1.3. Анализ компонентов системы

Компонент получения IP адресов отвечает за корректное определение реального IP адреса пользователя в условиях использования NGINX как reverse proxy. Это критически важный элемент, поскольку неправильное определение IP приведет к добавлению в белый список адреса самого веб-сервера вместо пользователя.[14][4]

Компонент API интеграции обеспечивает надежное соединение с MikroTik устройством через один из доступных методов: REST API, классический API или SSH. Выбор метода зависит от требований к безопасности и производительности системы.[13][6]

Компонент управления жизненным циклом отвечает за автоматическое удаление просроченных записей из белого списка, предотвращая его неконтролируемый рост и поддерживая актуальность правил доступа.[15][16]

1.4. Планирование безопасности

Аутентификация и авторизация реализуется на нескольких уровнях: HTTP Basic Auth для доступа к сайту, API ключи или SSH ключи для доступа к MikroTik, и опциональная двухфакторная аутентификация⁴ для административного интерфейса.[9][7]

Защита от атак включает ограничение частоты запросов (rate limiting), валидацию входящих IP адресов, защиту от SQL инъекций при использовании базы данных, и мониторинг подозрительной активности.[17][15]

Аудит и логирование всех операций обеспечивает возможность расследования инцидентов и соответствие требованиям информационной безопасности организации.[7][18]


2. Метод 1: Базовая интеграция через PHP и REST API

2.1. Настройка получения IP пользователей в PHP

Основная проблема при работе с NGINX как reverse proxy заключается в том, что PHP получает IP адрес прокси-сервера (127.0.0.1) вместо реального IP клиента. Для решения этой проблемы необходимо настроить передачу заголовков⁵ X-Real-IP и X-Forwarded-For.[19][20][11][10]

Конфигурация NGINX для корректной передачи IP адресов:

server {
    listen 80;
    server_name sysadministrator.ru;
    
    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        
        # Базовая аутентификация
        auth_basic "Restricted Access";
        auth_basic_user_file /etc/nginx/.htpasswd;
    }
}

PHP функция для определения реального IP:[4][14]

<?php
function getRealUserIP() {
    $ipKeys = [
        'HTTP_CF_CONNECTING_IP',     // Cloudflare
        'HTTP_CLIENT_IP',            // Прокси
        'HTTP_X_FORWARDED_FOR',      // Стандартный заголовок прокси
        'HTTP_X_FORWARDED',          // Альтернативный заголовок
        'HTTP_X_CLUSTER_CLIENT_IP',  // Кластерный IP
        'HTTP_FORWARDED_FOR',        // RFC 2616
        'HTTP_FORWARDED',            // RFC 2616
        'HTTP_X_REAL_IP',           // NGINX Real IP
        'REMOTE_ADDR'               // Стандартный IP
    ];
    
    foreach ($ipKeys as $key) {
        if (!empty($_SERVER[$key])) {
            $ip = trim($_SERVER[$key]);
            // Если несколько IP, берем первый
            if (strpos($ip, ',') !== false) {
                $ip = trim(explode(',', $ip)[0]);
            }
            
            // Валидация IP адреса
            if (filter_var($ip, FILTER_VALIDATE_IP, 
                FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
                return $ip;
            }
        }
    }
    
    // Возвращаем REMOTE_ADDR как fallback
    return $_SERVER['REMOTE_ADDR'] ?? null;
}
?>

2.2. Активация REST API на MikroTik

REST API в RouterOS 7.x представляет собой HTTP/HTTPS интерфейс для управления устройством. Для активации необходимо выполнить следующие шаги в терминале MikroTik:[21][22][9][13]

# Активация REST API службы
/ip service set api-ssl certificate=auto disabled=no port=8729

# Создание пользователя для API доступа
/user add name=api_user password=secure_password group=full

# Ограничение доступа к API только с определенных IP
/ip service set api-ssl address=192.168.1.1/32

Настройка SSL сертификата для безопасного соединения:

# Создание самоподписанного сертификата
/certificate add name=api-cert common-name=192.168.1.1 
    key-size=2048 days-valid=365 key-usage=digital-signature,key-encipherment

# Подпись сертификата
/certificate sign api-cert

# Привязка сертификата к API-SSL службе
/ip service set api-ssl certificate=api-cert

2.3. Создание PHP скрипта управления

Основной класс для работы с MikroTik REST API:[23][12]

<?php
class MikroTikRESTManager {
    private $host;
    private $username;
    private $password;
    private $port;
    private $timeout;
    
    public function __construct($host, $username, $password, $port = 8729, $timeout = 10) {
        $this->host = $host;
        $this->username = $username;
        $this->password = $password;
        $this->port = $port;
        $this->timeout = $timeout;
    }
    
    /**
     * Выполнение REST API запроса
     */
    private function makeRequest($endpoint, $method = 'GET', $data = null) {
        $url = "https://{$this->host}:{$this->port}/rest{$endpoint}";
        
        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT => $this->timeout,
            CURLOPT_SSL_VERIFYPEER => false, // Для самоподписанных сертификатов
            CURLOPT_USERPWD => $this->username . ':' . $this->password,
            CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
        ]);
        
        switch (strtoupper($method)) {
            case 'POST':
                curl_setopt($ch, CURLOPT_POST, true);
                if ($data) curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
                break;
            case 'PUT':
                curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
                if ($data) curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
                break;
            case 'DELETE':
                curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
                break;
        }
        
        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $error = curl_error($ch);
        curl_close($ch);
        
        if ($error) {
            throw new Exception("CURL Error: {$error}");
        }
        
        if ($httpCode >= 400) {
            throw new Exception("HTTP Error {$httpCode}: {$response}");
        }
        
        return json_decode($response, true);
    }
    
    /**
     * Добавление IP в белый список
     */
    public function addIPToWhitelist($ip, $comment = '', $timeout = 3600) {
        $data = [
            'address' => $ip,
            'list' => 'web_users_whitelist',
            'comment' => $comment ?: "Auto added at " . date('Y-m-d H:i:s'),
            'timeout' => "{$timeout}s"
        ];
        
        try {
            return $this->makeRequest('/ip/firewall/address-list', 'PUT', $data);
        } catch (Exception $e) {
            error_log("Failed to add IP {$ip} to whitelist: " . $e->getMessage());
            return false;
        }
    }
    
    /**
     * Проверка существования IP в списке
     */
    public function checkIPInWhitelist($ip) {
        try {
            $response = $this->makeRequest('/ip/firewall/address-list?address=' . urlencode($ip));
            return !empty($response);
        } catch (Exception $e) {
            error_log("Failed to check IP {$ip} in whitelist: " . $e->getMessage());
            return false;
        }
    }
    
    /**
     * Удаление IP из белого списка
     */
    public function removeIPFromWhitelist($ip) {
        try {
            // Сначала находим ID записи
            $response = $this->makeRequest('/ip/firewall/address-list?address=' . urlencode($ip) . '&list=web_users_whitelist');
            
            if (!empty($response)) {
                foreach ($response as $entry) {
                    if (isset($entry['.id'])) {
                        return $this->makeRequest('/ip/firewall/address-list/' . $entry['.id'], 'DELETE');
                    }
                }
            }
            
            return true;
        } catch (Exception $e) {
            error_log("Failed to remove IP {$ip} from whitelist: " . $e->getMessage());
            return false;
        }
    }
}
?>

2.4. Обработка ошибок и логирование

Система логирования должна фиксировать все операции для аудита и отладки:

<?php
class WhitelistLogger {
    private $logFile;
    
    public function __construct($logFile = '/var/log/mikrotik-whitelist.log') {
        $this->logFile = $logFile;
        
        // Создаем файл лога если не существует
        if (!file_exists($this->logFile)) {
            touch($this->logFile);
            chmod($this->logFile, 0644);
        }
    }
    
    public function log($level, $message, $context = []) {
        $timestamp = date('Y-m-d H:i:s');
        $contextStr = empty($context) ? '' : ' ' . json_encode($context);
        $logLine = "[{$timestamp}] {$level}: {$message}{$contextStr}\n";
        
        file_put_contents($this->logFile, $logLine, FILE_APPEND | LOCK_EX);
    }
    
    public function info($message, $context = []) {
        $this->log('INFO', $message, $context);
    }
    
    public function error($message, $context = []) {
        $this->log('ERROR', $message, $context);
    }
    
    public function warning($message, $context = []) {
        $this->log('WARNING', $message, $context);
    }
}
?>

2.5. Тестирование базового функционала

Главный скрипт обработки аутентифицированных пользователей:

<?php
require_once 'MikroTikRESTManager.php';
require_once 'WhitelistLogger.php';

// Конфигурация
$config = [
    'mikrotik_host' => '192.168.1.1',
    'mikrotik_user' => 'api_user',
    'mikrotik_pass' => 'secure_password',
    'whitelist_timeout' => 7200, // 2 часа
];

$logger = new WhitelistLogger();
$mikrotik = new MikroTikRESTManager(
    $config['mikrotik_host'],
    $config['mikrotik_user'],
    $config['mikrotik_pass']
);

try {
    // Проверяем аутентификацию
    if (!isset($_SERVER['PHP_AUTH_USER']) || !isset($_SERVER['PHP_AUTH_PW'])) {
        throw new Exception('No authentication provided');
    }
    
    // Получаем реальный IP пользователя
    $userIP = getRealUserIP();
    if (!$userIP) {
        throw new Exception('Cannot determine user IP address');
    }
    
    // Валидация IP адреса
    if (!filter_var($userIP, FILTER_VALIDATE_IP)) {
        throw new Exception('Invalid IP address detected: ' . $userIP);
    }
    
    // Проверяем, не находится ли IP уже в белом списке
    if (!$mikrotik->checkIPInWhitelist($userIP)) {
        // Добавляем IP в белый список
        $comment = "User: {$_SERVER['PHP_AUTH_USER']}, Time: " . date('Y-m-d H:i:s');
        
        if ($mikrotik->addIPToWhitelist($userIP, $comment, $config['whitelist_timeout'])) {
            $logger->info('IP added to whitelist', [
                'ip' => $userIP,
                'user' => $_SERVER['PHP_AUTH_USER'],
                'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'Unknown'
            ]);
            
            $message = "Доступ предоставлен! Ваш IP {$userIP} добавлен в белый список на " . 
                      ($config['whitelist_timeout'] / 3600) . " часов.";
        } else {
            throw new Exception('Failed to add IP to whitelist');
        }
    } else {
        $message = "Ваш IP {$userIP} уже находится в белом списке.";
        $logger->info('IP already in whitelist', ['ip' => $userIP]);
    }
    
} catch (Exception $e) {
    $logger->error('Error processing request', [
        'error' => $e->getMessage(),
        'ip' => $userIP ?? 'unknown',
        'user' => $_SERVER['PHP_AUTH_USER'] ?? 'unknown'
    ]);
    
    $message = "Ошибка: " . $e->getMessage();
}
?>

<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Управление доступом - Battery Service</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 40px; background: #f5f5f5; }
        .container { max-width: 600px; margin: 0 auto; background: white; padding: 30px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
        .success { color: #28a745; padding: 15px; background: #d4edda; border-radius: 5px; }
        .error { color: #dc3545; padding: 15px; background: #f8d7da; border-radius: 5px; }
        .info { color: #17a2b8; padding: 15px; background: #d1ecf1; border-radius: 5px; }
    </style>
</head>
<body>
    <div class="container">
        <h1>Система управления доступом</h1>
        <div class="<?php echo isset($e) ? 'error' : 'success'; ?>">
            <?php echo htmlspecialchars($message); ?>
        </div>
        
        <div class="info" style="margin-top: 20px;">
            <strong>Доступные порты:</strong><br>
            • RDP (3389) - Удаленный рабочий стол<br>
            • Порт 44444 - Специальный сервис<br>
            • Порт 55555 - Дополнительный сервис<br>
            • Порт 55554 - Резервный сервис
        </div>
    </div>
</body>
</html>

3. Метод 2: Интеграция через SSH соединение

3.1. Настройка SSH доступа на MikroTik

SSH протокол предоставляет надежный способ управления MikroTik устройством через командную строку. RouterOS поддерживает SSH v2 протокол по умолчанию на порту 22, но для безопасности рекомендуется изменить стандартный порт и настроить ключевую аутентификацию⁶.[24][25][17]

Базовая настройка SSH службы:

# Изменение порта SSH для безопасности
/ip service set ssh port=2222 address=192.168.1.1/32

# Создание специального пользователя для API операций
/user add name=ssh_api_user password=strong_password group=read,write,api

# Настройка ограничений для пользователя
/user set ssh_api_user allowed-address=192.168.1.1/32

Создание и настройка SSH ключей:

# На сервере с PHP генерируем ключевую пару
ssh-keygen -t rsa -b 4096 -f /etc/nginx/ssh_keys/mikrotik_api -N ""

# Копируем публичный ключ на MikroTik
# В терминале MikroTik:
/user ssh-keys import public-key-file=mikrotik_api.pub user=ssh_api_user

3.2. PHP библиотеки для SSH подключения

Подключение SSH2 расширения для PHP требует установки соответствующих пакетов:[24]

# Установка SSH2 расширения для PHP 8.3
sudo apt-get install libssh2-1-dev libssh2-1 php8.3-ssh2

# Перезапуск PHP-FPM
sudo systemctl restart php8.3-fpm

Класс для SSH подключения к MikroTik:

<?php
class MikroTikSSHManager {
    private $host;
    private $port;
    private $username;
    private $privateKeyPath;
    private $connection;
    private $timeout;
    
    public function __construct($host, $username, $privateKeyPath, $port = 2222, $timeout = 30) {
        $this->host = $host;
        $this->port = $port;
        $this->username = $username;
        $this->privateKeyPath = $privateKeyPath;
        $this->timeout = $timeout;
    }
    
    /**
     * Установка SSH соединения
     */
    public function connect() {
        $this->connection = ssh2_connect($this->host, $this->port);
        
        if (!$this->connection) {
            throw new Exception("Cannot connect to {$this->host}:{$this->port}");
        }
        
        // Аутентификация по ключу
        if (!ssh2_auth_pubkey_file($this->connection, $this->username, 
                                   $this->privateKeyPath . '.pub', 
                                   $this->privateKeyPath)) {
            throw new Exception("SSH key authentication failed");
        }
        
        return true;
    }
    
    /**
     * Выполнение команды на MikroTik
     */
    public function executeCommand($command) {
        if (!$this->connection) {
            $this->connect();
        }
        
        $stream = ssh2_exec($this->connection, $command);
        
        if (!$stream) {
            throw new Exception("Failed to execute command: {$command}");
        }
        
        stream_set_blocking($stream, true);
        $output = stream_get_contents($stream);
        fclose($stream);
        
        return trim($output);
    }
    
    /**
     * Добавление IP в address-list
     */
    public function addIPToAddressList($ip, $listName = 'web_users_whitelist', $comment = '', $timeout = 3600) {
        $comment = $comment ?: "Auto-added at " . date('Y-m-d H:i:s');
        $timeoutStr = $timeout ? "timeout={$timeout}s" : "";
        
        $command = "/ip firewall address-list add address={$ip} list={$listName} comment=\"{$comment}\" {$timeoutStr}";
        
        try {
            $result = $this->executeCommand($command);
            return true;
        } catch (Exception $e) {
            error_log("Failed to add IP {$ip}: " . $e->getMessage());
            return false;
        }
    }
    
    /**
     * Проверка существования IP в address-list
     */
    public function checkIPInAddressList($ip, $listName = 'web_users_whitelist') {
        $command = "/ip firewall address-list print where address={$ip} and list={$listName}";
        
        try {
            $result = $this->executeCommand($command);
            return !empty(trim($result));
        } catch (Exception $e) {
            error_log("Failed to check IP {$ip}: " . $e->getMessage());
            return false;
        }
    }
    
    /**
     * Удаление IP из address-list
     */
    public function removeIPFromAddressList($ip, $listName = 'web_users_whitelist') {
        $command = "/ip firewall address-list remove [find address={$ip} and list={$listName}]";
        
        try {
            $this->executeCommand($command);
            return true;
        } catch (Exception $e) {
            error_log("Failed to remove IP {$ip}: " . $e->getMessage());
            return false;
        }
    }
    
    /**
     * Получение списка всех записей в address-list
     */
    public function getAddressList($listName = 'web_users_whitelist') {
        $command = "/ip firewall address-list export where list={$listName}";
        
        try {
            $result = $this->executeCommand($command);
            return $this->parseAddressList($result);
        } catch (Exception $e) {
            error_log("Failed to get address list: " . $e->getMessage());
            return [];
        }
    }
    
    /**
     * Парсинг вывода address-list
     */
    private function parseAddressList($output) {
        $entries = [];
        $lines = explode("\n", $output);
        
        foreach ($lines as $line) {
            if (preg_match('/add.*address=([0-9.]+).*comment="([^"]*)"/', $line, $matches)) {
                $entries[] = [
                    'address' => $matches[1],
                    'comment' => $matches[2],
                    'timestamp' => $this->extractTimestamp($matches[2])
                ];
            }
        }
        
        return $entries;
    }
    
    /**
     * Извлечение временной метки из комментария
     */
    private function extractTimestamp($comment) {
        if (preg_match('/(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})/', $comment, $matches)) {
            return $matches[1];
        }
        return null;
    }
    
    /**
     * Закрытие соединения
     */
    public function disconnect() {
        if ($this->connection) {
            ssh2_disconnect($this->connection);
            $this->connection = null;
        }
    }
    
    public function __destruct() {
        $this->disconnect();
    }
}
?>

3.3. Создание SSH-клиента для управления

Улучшенный обработчик с поддержкой множественных операций:

<?php
class AdvancedMikroTikSSHManager extends MikroTikSSHManager {
    private $batchCommands = [];
    
    /**
     * Добавление команды в пакет для массового выполнения
     */
    public function addBatchCommand($command) {
        $this->batchCommands[] = $command;
    }
    
    /**
     * Выполнение пакета команд
     */
    public function executeBatch() {
        if (empty($this->batchCommands)) {
            return [];
        }
        
        $results = [];
        $combinedCommand = implode('; ', $this->batchCommands);
        
        try {
            $output = $this->executeCommand($combinedCommand);
            $results['success'] = true;
            $results['output'] = $output;
        } catch (Exception $e) {
            $results['success'] = false;
            $results['error'] = $e->getMessage();
        }
        
        $this->batchCommands = []; // Очищаем пакет
        return $results;
    }
    
    /**
     * Создание firewall правил для портов
     */
    public function createFirewallRulesForWhitelist($listName = 'web_users_whitelist') {
        $ports = ['3389', '44444', '55555', '55554'];
        $commands = [];
        
        foreach ($ports as $port) {
            $commands[] = sprintf(
                '/ip firewall filter add chain=input action=accept protocol=tcp src-address-list=%s dst-port=%s comment="Auto whitelist port %s"',
                $listName,
                $port,
                $port
            );
        }
        
        foreach ($commands as $command) {
            $this->addBatchCommand($command);
        }
        
        return $this->executeBatch();
    }
    
    /**
     * Мониторинг активных соединений
     */
    public function getActiveConnections($ports = ['3389', '44444', '55555', '55554']) {
        $portList = implode(',', $ports);
        $command = "/ip firewall connection print where dst-port~\"({$portList})\"";
        
        try {
            $output = $this->executeCommand($command);
            return $this->parseConnectionsOutput($output);
        } catch (Exception $e) {
            error_log("Failed to get active connections: " . $e->getMessage());
            return [];
        }
    }
    
    /**
     * Парсинг вывода активных соединений
     */
    private function parseConnectionsOutput($output) {
        $connections = [];
        $lines = explode("\n", $output);
        
        foreach ($lines as $line) {
            if (preg_match('/(\d+\.\d+\.\d+\.\d+):(\d+)->(\d+\.\d+\.\d+\.\d+):(\d+)/', $line, $matches)) {
                $connections[] = [
                    'src_ip' => $matches[1],
                    'src_port' => $matches[2],
                    'dst_ip' => $matches[3],
                    'dst_port' => $matches[4],
                    'timestamp' => time()
                ];
            }
        }
        
        return $connections;
    }
    
    /**
     * Получение статистики системы
     */
    public function getSystemStats() {
        $commands = [
            'cpu' => '/system resource print',
            'memory' => '/system resource print',
            'uptime' => '/system resource print',
            'interfaces' => '/interface print stats'
        ];
        
        $stats = [];
        
        foreach ($commands as $key => $command) {
            try {
                $stats[$key] = $this->executeCommand($command);
            } catch (Exception $e) {
                $stats[$key] = "Error: " . $e->getMessage();
            }
        }
        
        return $stats;
    }
}
?>

3.4. Автоматизация SSH команд

Скрипт автоматического управления с интеллектуальным кэшированием⁷:

<?php
require_once 'AdvancedMikroTikSSHManager.php';

class AutomatedWhitelistManager {
    private $sshManager;
    private $cacheFile = '/tmp/mikrotik_whitelist_cache.json';
    private $cacheTimeout = 300; // 5 минут
    
    public function __construct($host, $username, $privateKeyPath) {
        $this->sshManager = new AdvancedMikroTikSSHManager($host, $username, $privateKeyPath);
    }
    
    /**
     * Обработка запроса пользователя с кэшированием
     */
    public function processUserRequest($userIP, $username) {
        // Проверяем кэш
        if ($this->isIPInCache($userIP)) {
            return [
                'success' => true,
                'message' => 'IP уже в белом списке (кэш)',
                'cached' => true
            ];
        }
        
        try {
            $this->sshManager->connect();
            
            // Проверяем реальное состояние на роутере
            if (!$this->sshManager->checkIPInAddressList($userIP)) {
                $comment = "User: {$username}, Added: " . date('Y-m-d H:i:s') . ", Auto: SSH";
                
                if ($this->sshManager->addIPToAddressList($userIP, 'web_users_whitelist', $comment, 7200)) {
                    $this->addToCache($userIP, $username);
                    
                    return [
                        'success' => true,
                        'message' => 'IP успешно добавлен в белый список',
                        'cached' => false
                    ];
                } else {
                    throw new Exception('Failed to add IP to whitelist');
                }
            } else {
                $this->addToCache($userIP, $username); // Добавляем в кэш существующий IP
                
                return [
                    'success' => true,
                    'message' => 'IP уже находится в белом списке',
                    'cached' => false
                ];
            }
            
        } catch (Exception $e) {
            return [
                'success' => false,
                'message' => 'Ошибка: ' . $e->getMessage(),
                'cached' => false
            ];
        }
    }
    
    /**
     * Проверка IP в кэше
     */
    private function isIPInCache($ip) {
        if (!file_exists($this->cacheFile)) {
            return false;
        }
        
        $cache = json_decode(file_get_contents($this->cacheFile), true);
        
        if (isset($cache[$ip])) {
            // Проверяем не истек ли кэш
            if (time() - $cache[$ip]['timestamp'] < $this->cacheTimeout) {
                return true;
            } else {
                // Удаляем просроченную запись
                unset($cache[$ip]);
                file_put_contents($this->cacheFile, json_encode($cache));
            }
        }
        
        return false;
    }
    
    /**
     * Добавление IP в кэш
     */
    private function addToCache($ip, $username) {
        $cache = [];
        
        if (file_exists($this->cacheFile)) {
            $cache = json_decode(file_get_contents($this->cacheFile), true) ?: [];
        }
        
        $cache[$ip] = [
            'username' => $username,
            'timestamp' => time(),
            'added' => date('Y-m-d H:i:s')
        ];
        
        file_put_contents($this->cacheFile, json_encode($cache));
    }
    
    /**
     * Очистка просроченных записей кэша
     */
    public function cleanCache() {
        if (!file_exists($this->cacheFile)) {
            return;
        }
        
        $cache = json_decode(file_get_contents($this->cacheFile), true);
        $cleaned = [];
        
        foreach ($cache as $ip => $data) {
            if (time() - $data['timestamp'] < $this->cacheTimeout) {
                $cleaned[$ip] = $data;
            }
        }
        
        file_put_contents($this->cacheFile, json_encode($cleaned));
    }
    
    /**
     * Создание отчета об активности
     */
    public function generateActivityReport() {
        try {
            $this->sshManager->connect();
            
            $addressList = $this->sshManager->getAddressList('web_users_whitelist');
            $connections = $this->sshManager->getActiveConnections();
            
            $report = [
                'timestamp' => date('Y-m-d H:i:s'),
                'total_whitelist_entries' => count($addressList),
                'active_connections' => count($connections),
                'whitelist_details' => $addressList,
                'connection_details' => $connections
            ];
            
            return $report;
            
        } catch (Exception $e) {
            return [
                'error' => $e->getMessage(),
                'timestamp' => date('Y-m-d H:i:s')
            ];
        }
    }
}

// Использование в основном скрипте
$config = [
    'mikrotik_host' => '192.168.1.1',
    'ssh_user' => 'ssh_api_user',
    'ssh_key_path' => '/etc/nginx/ssh_keys/mikrotik_api'
];

$manager = new AutomatedWhitelistManager(
    $config['mikrotik_host'],
    $config['ssh_user'],
    $config['ssh_key_path']
);

// Очистка кэша от старых записей
$manager->cleanCache();

// Обработка пользователя
if (isset($_SERVER['PHP_AUTH_USER'])) {
    $userIP = getRealUserIP();
    $result = $manager->processUserRequest($userIP, $_SERVER['PHP_AUTH_USER']);
    
    // Ответ пользователю
    echo json_encode($result);
}
?>

3.5. Обеспечение безопасности SSH соединения

Дополнительные меры безопасности включают ограничение доступа, мониторинг подключений и аудит операций:[7][17]

<?php
class SecureSSHManager extends AdvancedMikroTikSSHManager {
    private $maxConnectionAttempts = 3;
    private $connectionAttempts = [];
    private $securityLogFile = '/var/log/mikrotik-ssh-security.log';
    
    /**
     * Безопасное подключение с ограничением попыток
     */
    public function secureConnect() {
        $currentTime = time();
        $clientIP = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
        
        // Проверяем количество попыток подключения
        if (isset($this->connectionAttempts[$clientIP])) {
            $attempts = $this->connectionAttempts[$clientIP];
            
            if ($attempts['count'] >= $this->maxConnectionAttempts && 
                $currentTime - $attempts['last_attempt'] < 3600) { // 1 час блокировка
                
                $this->logSecurityEvent('BLOCKED_CONNECTION_ATTEMPT', [
                    'client_ip' => $clientIP,
                    'attempts' => $attempts['count']
                ]);
                
                throw new Exception('Too many connection attempts. Access blocked for 1 hour.');
            }
        }
        
        try {
            $this->connect();
            
            // Сбрасываем счетчик при успешном подключении
            unset($this->connectionAttempts[$clientIP]);
            
            $this->logSecurityEvent('SUCCESSFUL_CONNECTION', [
                'client_ip' => $clientIP,
                'user' => $this->username
            ]);
            
        } catch (Exception $e) {
            // Увеличиваем счетчик неудачных попыток
            if (!isset($this->connectionAttempts[$clientIP])) {
                $this->connectionAttempts[$clientIP] = ['count' => 0, 'last_attempt' => 0];
            }
            
            $this->connectionAttempts[$clientIP]['count']++;
            $this->connectionAttempts[$clientIP]['last_attempt'] = $currentTime;
            
            $this->logSecurityEvent('FAILED_CONNECTION_ATTEMPT', [
                'client_ip' => $clientIP,
                'error' => $e->getMessage(),
                'attempt_number' => $this->connectionAttempts[$clientIP]['count']
            ]);
            
            throw $e;
        }
    }
    
    /**
     * Логирование событий безопасности
     */
    private function logSecurityEvent($event, $details = []) {
        $logEntry = [
            'timestamp' => date('Y-m-d H:i:s'),
            'event' => $event,
            'details' => $details,
            'request_id' => uniqid()
        ];
        
        file_put_contents($this->securityLogFile, json_encode($logEntry) . "\n", FILE_APPEND | LOCK_EX);
    }
    
    /**
     * Валидация IP адреса с дополнительными проверками
     */
    public function validateIP($ip) {
        // Базовая валидация
        if (!filter_var($ip, FILTER_VALIDATE_IP)) {
            return false;
        }
        
        // Проверка на приватные адреса
        if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
            return true;
        }
        
        // Логируем подозрительный IP
        $this->logSecurityEvent('SUSPICIOUS_IP_DETECTED', [
            'ip' => $ip,
            'reason' => 'Private or reserved IP range'
        ]);
        
        return false;
    }
    
    /**
     * Безопасное добавление IP с дополнительными проверками
     */
    public function secureAddIP($ip, $username, $additionalChecks = true) {
        // Валидация IP
        if (!$this->validateIP($ip)) {
            throw new Exception('Invalid IP address: ' . $ip);
        }
        
        // Дополнительные проверки безопасности
        if ($additionalChecks) {
            // Проверка на существование в черном списке
            if ($this->isIPInBlacklist($ip)) {
                $this->logSecurityEvent('BLACKLISTED_IP_ACCESS_ATTEMPT', [
                    'ip' => $ip,
                    'username' => $username
                ]);
                
                throw new Exception('IP address is blacklisted');
            }
            
            // Проверка на превышение лимита подключений
            if ($this->checkConnectionLimit($ip)) {
                throw new Exception('Connection limit exceeded for IP: ' . $ip);
            }
        }
        
        // Добавляем IP в белый список
        $comment = sprintf(
            'User: %s, Added: %s, IP: %s, RequestID: %s',
            $username,
            date('Y-m-d H:i:s'),
            $ip,
            uniqid()
        );
        
        if ($this->addIPToAddressList($ip, 'web_users_whitelist', $comment, 7200)) {
            $this->logSecurityEvent('IP_ADDED_TO_WHITELIST', [
                'ip' => $ip,
                'username' => $username,
                'comment' => $comment
            ]);
            
            return true;
        }
        
        return false;
    }
    
    /**
     * Проверка IP в черном списке
     */
    private function isIPInBlacklist($ip) {
        try {
            return $this->checkIPInAddressList($ip, 'blacklist');
        } catch (Exception $e) {
            // Если не можем проверить, считаем что IP безопасен
            return false;
        }
    }
    
    /**
     * Проверка лимита подключений
     */
    private function checkConnectionLimit($ip) {
        try {
            $connections = $this->getActiveConnections();
            $ipConnections = array_filter($connections, function($conn) use ($ip) {
                return $conn['src_ip'] === $ip;
            });
            
            return count($ipConnections) > 10; // Лимит 10 соединений
        } catch (Exception $e) {
            return false;
        }
    }
    
    /**
     * Генерация отчета безопасности
     */
    public function generateSecurityReport($hours = 24) {
        $logFile = $this->securityLogFile;
        
        if (!file_exists($logFile)) {
            return ['error' => 'Security log file not found'];
        }
        
        $lines = file($logFile, FILE_IGNORE_NEW_LINES);
        $events = [];
        $cutoffTime = time() - ($hours * 3600);
        
        foreach ($lines as $line) {
            $event = json_decode($line, true);
            
            if ($event && strtotime($event['timestamp']) > $cutoffTime) {
                $events[] = $event;
            }
        }
        
        return [
            'period_hours' => $hours,
            'total_events' => count($events),
            'events_by_type' => $this->groupEventsByType($events),
            'recent_events' => array_slice($events, -10), // Последние 10 событий
            'generated_at' => date('Y-m-d H:i:s')
        ];
    }
    
    /**
     * Группировка событий по типам
     */
    private function groupEventsByType($events) {
        $grouped = [];
        
        foreach ($events as $event) {
            $type = $event['event'];
            
            if (!isset($grouped[$type])) {
                $grouped[$type] = 0;
            }
            
            $grouped[$type]++;
        }
        
        return $grouped;
    }
}
?>

4. Метод 3: Использование классической MikroTik PHP API

4.1. Установка PEAR2_Net_RouterOS библиотеки

PEAR2_Net_RouterOS является официальной PHP библиотекой для работы с MikroTik API. Данная библиотека предоставляет объектно-ориентированный интерфейс для всех функций RouterOS и поддерживает как обычные, так и SSL соединения.[26][27][6]

Установка через Composer:

{
    "require": {
        "pear2/net_routeros": "^1.0"
    }
}
# Установка зависимостей
composer install

# Альтернативная установка через PEAR
wget https://github.com/pear2/Net_RouterOS/releases/download/1.0.0b6/PEAR2_Net_RouterOS-1.0.0b6.phar

Проверка установки и базовая конфигурация:

<?php
require_once 'vendor/autoload.php';
// Или для phar файла:
// require_once 'PEAR2_Net_RouterOS-1.0.0b6.phar';

use PEAR2\Net\RouterOS;

class MikroTikAPIManager {
    private $client;
    private $host;
    private $username;
    private $password;
    private $port;
    private $timeout;
    private $isConnected = false;
    
    public function __construct($host, $username, $password, $port = 8728, $timeout = 10) {
        $this->host = $host;
        $this->username = $username;
        $this->password = $password;
        $this->port = $port;
        $this->timeout = $timeout;
    }
    
    /**
     * Установка соединения с MikroTik
     */
    public function connect() {
        try {
            $this->client = new RouterOS\Client($this->host, $this->username, $this->password, $this->port);
            $this->isConnected = true;
            
            // Тестовый запрос для проверки соединения
            $request = new RouterOS\Request('/system/identity/print');
            $responses = $this->client->sendSync($request);
            
            return true;
        } catch (Exception $e) {
            $this->isConnected = false;
            throw new Exception("Connection failed: " . $e->getMessage());
        }
    }
    
    /**
     * Проверка состояния соединения
     */
    public function isConnected() {
        return $this->isConnected;
    }
    
    /**
     * Получение информации о системе
     */
    public function getSystemInfo() {
        if (!$this->isConnected) {
            $this->connect();
        }
        
        $request = new RouterOS\Request('/system/resource/print');
        $responses = $this->client->sendSync($request);
        
        $systemInfo = [];
        foreach ($responses as $response) {
            if ($response->getType() === RouterOS\Response::TYPE_DATA) {
                $systemInfo = [
                    'version' => $response->getProperty('version'),
                    'board-name' => $response->getProperty('board-name'),
                    'uptime' => $response->getProperty('uptime'),
                    'cpu-load' => $response->getProperty('cpu-load'),
                    'free-memory' => $response->getProperty('free-memory'),
                    'total-memory' => $response->getProperty('total-memory')
                ];
                break;
            }
        }
        
        return $systemInfo;
    }
}
?>

4.2. Настройка API соединения

Расширенный класс с поддержкой SSL и продвинутым управлением сессиями:[7]

<?php
class AdvancedMikroTikAPIManager extends MikroTikAPIManager {
    private $useSSL = false;
    private $sslContext = null;
    
    public function __construct($host, $username, $password, $port = 8728, $timeout = 10, $useSSL = false) {
        parent::__construct($host, $username, $password, $port, $timeout);
        $this->useSSL = $useSSL;
        
        if ($useSSL) {
            $this->setupSSLContext();
        }
    }
    
    /**
     * Настройка SSL контекста
     */
    private function setupSSLContext() {
        $this->sslContext = stream_context_create([
            'ssl' => [
                'verify_peer' => false,
                'verify_peer_name' => false,
                'allow_self_signed' => true,
                'ciphers' => 'HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!SRP:!CAMELLIA'
            ]
        ]);
    }
    
    /**
     * Подключение с поддержкой SSL
     */
    public function connect() {
        try {
            if ($this->useSSL) {
                $this->client = new RouterOS\Client(
                    $this->host, 
                    $this->username, 
                    $this->password, 
                    $this->port,
                    $this->timeout,
                    $this->sslContext
                );
            } else {
                parent::connect();
            }
            
            return true;
        } catch (Exception $e) {
            throw new Exception("SSL Connection failed: " . $e->getMessage());
        }
    }
    
    /**
     * Безопасное выполнение запроса с повторными попытками
     */
    public function executeRequest($path, $arguments = [], $retries = 3) {
        $lastException = null;
        
        for ($i = 0; $i < $retries; $i++) {
            try {
                if (!$this->isConnected()) {
                    $this->connect();
                }
                
                $request = new RouterOS\Request($path);
                
                // Добавляем аргументы к запросу
                foreach ($arguments as $key => $value) {
                    if ($key === 'tag') {
                        $request->setTag($value);
                    } else {
                        $request->setArgument($key, $value);
                    }
                }
                
                $responses = $this->client->sendSync($request);
                return $this->processResponses($responses);
                
            } catch (Exception $e) {
                $lastException = $e;
                $this->isConnected = false;
                
                // Ждем перед повторной попыткой
                if ($i < $retries - 1) {
                    sleep(1);
                }
            }
        }
        
        throw new Exception("Request failed after {$retries} retries: " . $lastException->getMessage());
    }
    
    /**
     * Обработка ответов от MikroTik
     */
    private function processResponses($responses) {
        $result = [];
        
        foreach ($responses as $response) {
            if ($response->getType() === RouterOS\Response::TYPE_DATA) {
                $item = [];
                foreach ($response->getAllArguments() as $key => $value) {
                    $item[$key] = $value;
                }
                $result[] = $item;
            } elseif ($response->getType() === RouterOS\Response::TYPE_ERROR) {
                throw new Exception("MikroTik Error: " . $response->getProperty('message'));
            }
        }
        
        return $result;
    }
    
    /**
     * Получение всех записей из address-list
     */
    public function getAddressList($listName = null) {
        $arguments = [];
        
        if ($listName) {
            $arguments['list'] = $listName;
        }
        
        return $this->executeRequest('/ip/firewall/address-list/print', $arguments);
    }
    
    /**
     * Добавление записи в address-list
     */
    public function addToAddressList($address, $listName, $comment = '', $timeout = null, $disabled = false) {
        $arguments = [
            'address' => $address,
            'list' => $listName
        ];
        
        if ($comment) {
            $arguments['comment'] = $comment;
        }
        
        if ($timeout) {
            $arguments['timeout'] = $timeout;
        }
        
        if ($disabled) {
            $arguments['disabled'] = 'yes';
        }
        
        return $this->executeRequest('/ip/firewall/address-list/add', $arguments);
    }
    
    /**
     * Поиск записи в address-list
     */
    public function findInAddressList($address, $listName = null) {
        $items = $this->getAddressList($listName);
        
        foreach ($items as $item) {
            if ($item['address'] === $address && 
                ($listName === null || $item['list'] === $listName)) {
                return $item;
            }
        }
        
        return null;
    }
    
    /**
     * Удаление записи из address-list
     */
    public function removeFromAddressList($id) {
        return $this->executeRequest('/ip/firewall/address-list/remove', ['.id' => $id]);
    }
    
    /**
     * Удаление записи по адресу и списку
     */
    public function removeFromAddressListByAddress($address, $listName) {
        $item = $this->findInAddressList($address, $listName);
        
        if ($item && isset($item['.id'])) {
            return $this->removeFromAddressList($item['.id']);
        }
        
        return false;
    }
}
?>

4.3. Работа с Address Lists через API

Специализированный класс для управления белыми списками IP адресов:[28][29]

<?php
class WhitelistManager {
    private $api;
    private $whitelistName;
    private $defaultTimeout;
    private $logFile;
    
    public function __construct(AdvancedMikroTikAPIManager $api, $whitelistName = 'web_users_whitelist', $defaultTimeout = 7200) {
        $this->api = $api;
        $this->whitelistName = $whitelistName;
        $this->defaultTimeout = $defaultTimeout;
        $this->logFile = '/var/log/whitelist-operations.log';
    }
    
    /**
     * Добавление пользователя в белый список
     */
    public function addUser($ip, $username, $customTimeout = null) {
        try {
            // Проверяем валидность IP
            if (!$this->validateIP($ip)) {
                throw new Exception("Invalid IP address: {$ip}");
            }
            
            // Проверяем, не существует ли уже запись
            $existing = $this->api->findInAddressList($ip, $this->whitelistName);
            if ($existing) {
                $this->log("INFO", "IP {$ip} already in whitelist", ['username' => $username]);
                return ['success' => true, 'message' => 'IP already in whitelist', 'existed' => true];
            }
            
            // Создаем комментарий
            $comment = $this->generateComment($username);
            
            // Определяем timeout
            $timeout = $customTimeout ?: $this->defaultTimeout;
            $timeoutStr = $this->formatTimeout($timeout);
            
            // Добавляем в список
            $this->api->addToAddressList($ip, $this->whitelistName, $comment, $timeoutStr);
            
            $this->log("INFO", "IP {$ip} added to whitelist", [
                'username' => $username,
                'timeout' => $timeout,
                'comment' => $comment
            ]);
            
            return [
                'success' => true, 
                'message' => 'IP successfully added to whitelist',
                'timeout_hours' => $timeout / 3600,
                'existed' => false
            ];
            
        } catch (Exception $e) {
            $this->log("ERROR", "Failed to add IP {$ip} to whitelist", [
                'username' => $username,
                'error' => $e->getMessage()
            ]);
            
            return ['success' => false, 'message' => $e->getMessage()];
        }
    }
    
    /**
     * Удаление пользователя из белого списка
     */
    public function removeUser($ip, $username = null) {
        try {
            $removed = $this->api->removeFromAddressListByAddress($ip, $this->whitelistName);
            
            if ($removed) {
                $this->log("INFO", "IP {$ip} removed from whitelist", ['username' => $username]);
                return ['success' => true, 'message' => 'IP removed from whitelist'];
            } else {
                return ['success' => false, 'message' => 'IP not found in whitelist'];
            }
            
        } catch (Exception $e) {
            $this->log("ERROR", "Failed to remove IP {$ip} from whitelist", [
                'username' => $username,
                'error' => $e->getMessage()
            ]);
            
            return ['success' => false, 'message' => $e->getMessage()];
        }
    }
    
    /**
     * Получение статуса пользователя в белом списке
     */
    public function getUserStatus($ip) {
        try {
            $item = $this->api->findInAddressList($ip, $this->whitelistName);
            
            if ($item) {
                return [
                    'in_whitelist' => true,
                    'id' => $item['.id'],
                    'comment' => $item['comment'] ?? '',
                    'creation_time' => $item['creation-time'] ?? null,
                    'timeout' => $item['timeout'] ?? null,
                    'dynamic' => isset($item['dynamic'])
                ];
            } else {
                return ['in_whitelist' => false];
            }
            
        } catch (Exception $e) {
            return ['error' => $e->getMessage()];
        }
    }
    
    /**
     * Получение всех пользователей в белом списке
     */
    public function getAllUsers() {
        try {
            $items = $this->api->getAddressList($this->whitelistName);
            
            $users = [];
            foreach ($items as $item) {
                $users[] = [
                    'id' => $item['.id'],
                    'address' => $item['address'],
                    'comment' => $item['comment'] ?? '',
                    'creation_time' => $item['creation-time'] ?? null,
                    'timeout' => $item['timeout'] ?? null,
                    'username' => $this->extractUsernameFromComment($item['comment'] ?? ''),
                    'expires_at' => $this->calculateExpirationTime($item)
                ];
            }
            
            return $users;
            
        } catch (Exception $e) {
            $this->log("ERROR", "Failed to get all users", ['error' => $e->getMessage()]);
            return [];
        }
    }
    
    /**
     * Очистка просроченных записей
     */
    public function cleanupExpired() {
        try {
            $allItems = $this->api->getAddressList($this->whitelistName);
            $cleaned = 0;
            
            foreach ($allItems as $item) {
                if ($this->isExpired($item)) {
                    $this->api->removeFromAddressList($item['.id']);
                    $cleaned++;
                    
                    $this->log("INFO", "Expired entry removed", [
                        'id' => $item['.id'],
                        'address' => $item['address'],
                        'comment' => $item['comment'] ?? ''
                    ]);
                }
            }
            
            return ['cleaned' => $cleaned, 'message' => "Cleaned {$cleaned} expired entries"];
            
        } catch (Exception $e) {
            $this->log("ERROR", "Failed to cleanup expired entries", ['error' => $e->getMessage()]);
            return ['error' => $e->getMessage()];
        }
    }
    
    /**
     * Валидация IP адреса
     */
    private function validateIP($ip) {
        return filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE);
    }
    
    /**
     * Генерация комментария
     */
    private function generateComment($username) {
        return sprintf(
            "User: %s | Added: %s | Source: Web | RequestID: %s",
            $username,
            date('Y-m-d H:i:s'),
            uniqid()
        );
    }
    
    /**
     * Форматирование timeout для MikroTik
     */
    private function formatTimeout($seconds) {
        if ($seconds < 60) {
            return $seconds . 's';
        } elseif ($seconds < 3600) {
            return ($seconds / 60) . 'm';
        } else {
            return ($seconds / 3600) . 'h';
        }
    }
    
    /**
     * Извлечение имени пользователя из комментария
     */
    private function extractUsernameFromComment($comment) {
        if (preg_match('/User:\s*([^|]+)/', $comment, $matches)) {
            return trim($matches[1]);
        }
        return 'Unknown';
    }
    
    /**
     * Вычисление времени истечения записи
     */
    private function calculateExpirationTime($item) {
        if (!isset($item['timeout']) || !isset($item['creation-time'])) {
            return null;
        }
        
        $creationTime = strtotime($item['creation-time']);
        $timeout = $this->parseTimeout($item['timeout']);
        
        return date('Y-m-d H:i:s', $creationTime + $timeout);
    }
    
    /**
     * Парсинг timeout из строки MikroTik
     */
    private function parseTimeout($timeoutStr) {
        if (preg_match('/(\d+)([smhd])/', $timeoutStr, $matches)) {
            $value = intval($matches[1]);
            $unit = $matches[2];
            
            switch ($unit) {
                case 's': return $value;
                case 'm': return $value * 60;
                case 'h': return $value * 3600;
                case 'd': return $value * 86400;
            }
        }
        
        return 0;
    }
    
    /**
     * Проверка истечения записи
     */
    private function isExpired($item) {
        $expirationTime = $this->calculateExpirationTime($item);
        
        if ($expirationTime) {
            return strtotime($expirationTime) < time();
        }
        
        return false;
    }
    
    /**
     * Логирование операций
     */
    private function log($level, $message, $context = []) {
        $logEntry = [
            'timestamp' => date('Y-m-d H:i:s'),
            'level' => $level,
            'message' => $message,
            'context' => $context
        ];
        
        file_put_contents($this->logFile, json_encode($logEntry) . "\n", FILE_APPEND | LOCK_EX);
    }
}
?>

4.4. Интеграция с веб-интерфейсом

Главный скрипт обработки запросов пользователей:[6][7]

<?php
require_once 'vendor/autoload.php';
require_once 'AdvancedMikroTikAPIManager.php';
require_once 'WhitelistManager.php';

// Конфигурация
$config = [
    'mikrotik' => [
        'host' => '192.168.1.1',
        'username' => 'api_user',
        'password' => 'secure_password',
        'port' => 8729, // Используем API-SSL
        'ssl' => true,
        'timeout' => 15
    ],
    'whitelist' => [
        'name' => 'web_users_whitelist',
        'default_timeout' => 7200, // 2 часа
        'max_timeout' => 86400 // 24 часа максимум
    ],
    'security' => [
        'max_requests_per_hour' => 10,
        'require_https' => false // В продакшене должно быть true
    ]
];

class WebWhitelistController {
    private $api;
    private $whitelist;
    private $config;
    private $sessionFile = '/tmp/user_requests.json';
    
    public function __construct($config) {
        $this->config = $config;
        
        // Инициализация API
        $this->api = new AdvancedMikroTikAPIManager(
            $config['mikrotik']['host'],
            $config['mikrotik']['username'],
            $config['mikrotik']['password'],
            $config['mikrotik']['port'],
            $config['mikrotik']['timeout'],
            $config['mikrotik']['ssl']
        );
        
        // Инициализация менеджера белого списка
        $this->whitelist = new WhitelistManager(
            $this->api,
            $config['whitelist']['name'],
            $config['whitelist']['default_timeout']
        );
    }
    
    /**
     * Основной обработчик запросов
     */
    public function handleRequest() {
        try {
            // Проверка аутентификации
            if (!$this->checkAuth()) {
                $this->sendAuthRequired();
                return;
            }
            
            // Получение IP пользователя
            $userIP = $this->getRealUserIP();
            $username = $_SERVER['PHP_AUTH_USER'];
            
            // Проверка лимитов
            if (!$this->checkRequestLimits($userIP)) {
                throw new Exception('Too many requests. Please try again later.');
            }
            
            // Обработка действия
            $action = $_REQUEST['action'] ?? 'add';
            
            switch ($action) {
                case 'add':
                    $result = $this->handleAdd($userIP, $username);
                    break;
                case 'remove':
                    $result = $this->handleRemove($userIP, $username);
                    break;
                case 'status':
                    $result = $this->handleStatus($userIP);
                    break;
                case 'extend':
                    $result = $this->handleExtend($userIP, $username);
                    break;
                default:
                    throw new Exception('Unknown action: ' . $action);
            }
            
            // Записываем запрос в историю
            $this->logRequest($userIP, $username, $action);
            
            $this->sendResponse($result);
            
        } catch (Exception $e) {
            $this->sendError($e->getMessage());
        }
    }
    
    /**
     * Обработка добавления в белый список
     */
    private function handleAdd($ip, $username) {
        $customTimeout = $_REQUEST['timeout'] ?? null;
        
        if ($customTimeout) {
            $customTimeout = min($customTimeout, $this->config['whitelist']['max_timeout']);
        }
        
        $result = $this->whitelist->addUser($ip, $username, $customTimeout);
        
        if ($result['success']) {
            $result['ports'] = ['3389', '44444', '55555', '55554'];
            $result['server'] = $this->config['mikrotik']['host'];
        }
        
        return $result;
    }
    
    /**
     * Обработка удаления из белого списка
     */
    private function handleRemove($ip, $username) {
        return $this->whitelist->removeUser($ip, $username);
    }
    
    /**
     * Обработка проверки статуса
     */
    private function handleStatus($ip) {
        return $this->whitelist->getUserStatus($ip);
    }
    
    /**
     * Обработка продления времени доступа
     */
    private function handleExtend($ip, $username) {
        // Сначала удаляем текущую запись
        $this->whitelist->removeUser($ip);
        
        // Затем добавляем с новым timeout
        $newTimeout = $_REQUEST['extend_hours'] ?? 2;
        $newTimeout = min($newTimeout * 3600, $this->config['whitelist']['max_timeout']);
        
        return $this->whitelist->addUser($ip, $username, $newTimeout);
    }
    
    /**
     * Проверка HTTP Basic Auth
     */
    private function checkAuth() {
        return isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW']);
    }
    
    /**
     * Получение реального IP пользователя
     */
    private function getRealUserIP() {
        $ipKeys = [
            'HTTP_CF_CONNECTING_IP',
            'HTTP_CLIENT_IP',
            'HTTP_X_FORWARDED_FOR',
            'HTTP_X_REAL_IP',
            'REMOTE_ADDR'
        ];
        
        foreach ($ipKeys as $key) {
            if (!empty($_SERVER[$key])) {
                $ip = trim(explode(',', $_SERVER[$key])[0]);
                if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
                    return $ip;
                }
            }
        }
        
        throw new Exception('Cannot determine user IP address');
    }
    
    /**
     * Проверка лимитов запросов
     */
    private function checkRequestLimits($ip) {
        if (!file_exists($this->sessionFile)) {
            return true;
        }
        
        $sessions = json_decode(file_get_contents($this->sessionFile), true) ?: [];
        $currentTime = time();
        $hourAgo = $currentTime - 3600;
        
        // Очищаем старые записи
        $sessions = array_filter($sessions, function($session) use ($hourAgo) {
            return $session['timestamp'] > $hourAgo;
        });
        
        // Считаем запросы от данного IP за последний час
        $requestCount = 0;
        foreach ($sessions as $session) {
            if ($session['ip'] === $ip) {
                $requestCount++;
            }
        }
        
        return $requestCount < $this->config['security']['max_requests_per_hour'];
    }
    
    /**
     * Логирование запроса
     */
    private function logRequest($ip, $username, $action) {
        $sessions = [];
        
        if (file_exists($this->sessionFile)) {
            $sessions = json_decode(file_get_contents($this->sessionFile), true) ?: [];
        }
        
        $sessions[] = [
            'ip' => $ip,
            'username' => $username,
            'action' => $action,
            'timestamp' => time(),
            'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'Unknown'
        ];
        
        // Оставляем только последние 1000 записей
        if (count($sessions) > 1000) {
            $sessions = array_slice($sessions, -1000);
        }
        
        file_put_contents($this->sessionFile, json_encode($sessions));
    }
    
    /**
     * Отправка ответа клиенту
     */
    private function sendResponse($data, $httpCode = 200) {
        http_response_code($httpCode);
        header('Content-Type: application/json; charset=utf-8');
        echo json_encode($data, JSON_UNESCAPED_UNICODE);
    }
    
    /**
     * Отправка ошибки клиенту
     */
    private function sendError($message, $httpCode = 400) {
        $this->sendResponse(['success' => false, 'error' => $message], $httpCode);
    }
    
    /**
     * Отправка требования аутентификации
     */
    private function sendAuthRequired() {
        header('WWW-Authenticate: Basic realm="Battery Service Access"');
        $this->sendError('Authentication required', 401);
    }
}

// Обработка запроса
$controller = new WebWhitelistController($config);

// Определение формата ответа
if (isset($_REQUEST['format']) && $_REQUEST['format'] === 'json') {
    $controller->handleRequest();
} else {
    // HTML интерфейс
    include 'web_interface.php';
}
?>

4.5. Оптимизация производительности

Кэширование и пулинг соединений для высоконагруженных систем:

<?php
class OptimizedMikroTikManager {
    private static $connectionPool = [];
    private static $cache = [];
    private $cacheTimeout = 60; // 1 минута
    
    /**
     * Получение соединения из пула
     */
    public static function getConnection($config) {
        $key = md5(serialize($config));
        
        if (!isset(self::$connectionPool[$key]) || !self::$connectionPool[$key]->isConnected()) {
            self::$connectionPool[$key] = new AdvancedMikroTikAPIManager(
                $config['host'],
                $config['username'],
                $config['password'],
                $config['port'],
                $config['timeout'],
                $config['ssl']
            );
        }
        
        return self::$connectionPool[$key];
    }
    
    /**
     * Кэшированное получение данных
     */
    public function getCachedData($key, $callback, $timeout = null) {
        $timeout = $timeout ?: $this->cacheTimeout;
        $cacheKey = md5($key);
        
        if (isset(self::$cache[$cacheKey])) {
            $cacheData = self::$cache[$cacheKey];
            
            if (time() - $cacheData['timestamp'] < $timeout) {
                return $cacheData['data'];
            }
        }
        
        // Выполняем callback и кэшируем результат
        $data = call_user_func($callback);
        
        self::$cache[$cacheKey] = [
            'data' => $data,
            'timestamp' => time()
        ];
        
        return $data;
    }
    
    /**
     * Очистка кэша
     */
    public function clearCache($pattern = null) {
        if ($pattern) {
            foreach (self::$cache as $key => $value) {
                if (fnmatch($pattern, $key)) {
                    unset(self::$cache[$key]);
                }
            }
        } else {
            self::$cache = [];
        }
    }
    
    /**
     * Пакетная обработка операций
     */
    public function batchOperations($operations) {
        $results = [];
        $api = self::getConnection($this->config);
        
        foreach ($operations as $index => $operation) {
            try {
                $result = $this->executeOperation($api, $operation);
                $results[$index] = ['success' => true, 'data' => $result];
            } catch (Exception $e) {
                $results[$index] = ['success' => false, 'error' => $e->getMessage()];
            }
        }
        
        return $results;
    }
    
    /**
     * Выполнение единичной операции
     */
    private function executeOperation($api, $operation) {
        switch ($operation['type']) {
            case 'add_to_whitelist':
                return $api->addToAddressList(
                    $operation['address'],
                    $operation['list'],
                    $operation['comment'] ?? '',
                    $operation['timeout'] ?? null
                );
                
            case 'remove_from_whitelist':
                return $api->removeFromAddressListByAddress(
                    $operation['address'],
                    $operation['list']
                );
                
            case 'get_whitelist':
                return $api->getAddressList($operation['list']);
                
            default:
                throw new Exception('Unknown operation type: ' . $operation['type']);
        }
    }
}
?>

5. Метод 4: Комплексное решение с веб-интерфейсом управления

5.1. Создание административной панели

Административная панель предоставляет полный контроль над системой белых списков через удобный веб-интерфейс. Панель включает в себя управление пользователями, мониторинг активности, настройку правил и генерацию отчетов.[30]

Основной класс административной панели:

<?php
class AdminPanel {
    private $whitelist;
    private $api;
    private $config;
    private $isAdmin = false;
    
    public function __construct($config) {
        $this->config = $config;
        
        // Инициализация API и whitelist manager
        $this->api = OptimizedMikroTikManager::getConnection($config['mikrotik']);
        $this->whitelist = new WhitelistManager($this->api, $config['whitelist']['name']);
        
        // Проверка административных прав
        $this->checkAdminAccess();
    }
    
    /**
     * Проверка административного доступа
     */
    private function checkAdminAccess() {
        $adminUsers = $this->config['admin']['users'] ?? [];
        $currentUser = $_SERVER['PHP_AUTH_USER'] ?? null;
        
        if (!$currentUser || !in_array($currentUser, $adminUsers)) {
            header('HTTP/1.1 403 Forbidden');
            die('Administrative access required');
        }
        
        $this->isAdmin = true;
    }
    
    /**
     * Главный метод обработки запросов
     */
    public function handleRequest() {
        $action = $_REQUEST['action'] ?? 'dashboard';
        
        try {
            switch ($action) {
                case 'dashboard':
                    return $this->showDashboard();
                case 'users':
                    return $this->showUsers();
                case 'add_user':
                    return $this->addUser();
                case 'remove_user':
                    return $this->removeUser();
                case 'extend_user':
                    return $this->extendUser();
                case 'bulk_action':
                    return $this->bulkAction();
                case 'system_info':
                    return $this->showSystemInfo();
                case 'logs':
                    return $this->showLogs();
                case 'settings':
                    return $this->showSettings();
                case 'export':
                    return $this->exportData();
                default:
                    throw new Exception('Unknown action: ' . $action);
            }
        } catch (Exception $e) {
            return $this->sendError($e->getMessage());
        }
    }
    
    /**
     * Панель управления (главная страница)
     */
    private function showDashboard() {
        $stats = $this->getDashboardStats();
        
        if ($_REQUEST['format'] === 'json') {
            return $this->sendJSON($stats);
        }
        
        include 'templates/dashboard.php';
    }
    
    /**
     * Получение статистики для панели управления
     */
    private function getDashboardStats() {
        $users = $this->whitelist->getAllUsers();
        $systemInfo = $this->api->getSystemInfo();
        
        $stats = [
            'total_users' => count($users),
            'active_users' => count(array_filter($users, function($u) {
                return !isset($u['expires_at']) || strtotime($u['expires_at']) > time();
            })),
            'expired_users' => count(array_filter($users, function($u) {
                return isset($u['expires_at']) && strtotime($u['expires_at']) <= time();
            })),
            'system_uptime' => $systemInfo['uptime'] ?? 'Unknown',
            'system_cpu' => $systemInfo['cpu-load'] ?? 'Unknown',
            'system_memory' => [
                'free' => $systemInfo['free-memory'] ?? 0,
                'total' => $systemInfo['total-memory'] ?? 0,
                'used_percent' => $this->calculateMemoryUsage($systemInfo)
            ],
            'recent_additions' => $this->getRecentAdditions(10),
            'connection_stats' => $this->getConnectionStats()
        ];
        
        return $stats;
    }
    
    /**
     * Управление пользователями
     */
    private function showUsers() {
        $page = max(1, intval($_REQUEST['page'] ?? 1));
        $limit = intval($_REQUEST['limit'] ?? 50);
        $search = $_REQUEST['search'] ?? '';
        $filter = $_REQUEST['filter'] ?? 'all'; // all, active, expired
        
        $users = $this->whitelist->getAllUsers();
        
        // Применение фильтров
        if ($search) {
            $users = array_filter($users, function($user) use ($search) {
                return stripos($user['address'], $search) !== false ||
                       stripos($user['username'], $search) !== false ||
                       stripos($user['comment'], $search) !== false;
            });
        }
        
        if ($filter !== 'all') {
            $users = array_filter($users, function($user) use ($filter) {
                $isExpired = isset($user['expires_at']) && strtotime($user['expires_at']) <= time();
                return ($filter === 'expired') ? $isExpired : !$isExpired;
            });
        }
        
        // Пагинация
        $totalUsers = count($users);
        $offset = ($page - 1) * $limit;
        $users = array_slice($users, $offset, $limit);
        
        $result = [
            'users' => $users,
            'pagination' => [
                'page' => $page,
                'limit' => $limit,
                'total' => $totalUsers,
                'pages' => ceil($totalUsers / $limit)
            ],
            'filters' => [
                'search' => $search,
                'filter' => $filter
            ]
        ];
        
        if ($_REQUEST['format'] === 'json') {
            return $this->sendJSON($result);
        }
        
        include 'templates/users.php';
    }
    
    /**
     * Добавление пользователя через админ панель
     */
    private function addUser() {
        if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
            throw new Exception('POST request required');
        }
        
        $ip = $_POST['ip'] ?? '';
        $username = $_POST['username'] ?? '';
        $timeout = intval($_POST['timeout'] ?? 7200);
        $comment = $_POST['comment'] ?? '';
        
        if (!$ip || !$username) {
            throw new Exception('IP and username are required');
        }
        
        if (!filter_var($ip, FILTER_VALIDATE_IP)) {
            throw new Exception('Invalid IP address');
        }
        
        // Добавляем пользователя
        $result = $this->whitelist->addUser($ip, $username, $timeout);
        
        if ($comment && $result['success']) {
            // Обновляем комментарий если был предоставлен
            $this->updateUserComment($ip, $comment);
        }
        
        return $this->sendJSON($result);
    }
    
    /**
     * Удаление пользователя
     */
    private function removeUser() {
        if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
            throw new Exception('POST request required');
        }
        
        $ip = $_POST['ip'] ?? '';
        
        if (!$ip) {
            throw new Exception('IP address is required');
        }
        
        $result = $this->whitelist->removeUser($ip, 'admin');
        
        return $this->sendJSON($result);
    }
    
    /**
     * Продление доступа пользователя
     */
    private function extendUser() {
        if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
            throw new Exception('POST request required');
        }
        
        $ip = $_POST['ip'] ?? '';
        $hours = intval($_POST['hours'] ?? 2);
        
        if (!$ip) {
            throw new Exception('IP address is required');
        }
        
        // Получаем информацию о пользователе
        $userStatus = $this->whitelist->getUserStatus($ip);
        if (!$userStatus['in_whitelist']) {
            throw new Exception('User not found in whitelist');
        }
        
        $username = $this->extractUsernameFromComment($userStatus['comment']);
        
        // Удаляем старую запись и добавляем новую с продленным временем
        $this->whitelist->removeUser($ip);
        $result = $this->whitelist->addUser($ip, $username, $hours * 3600);
        
        return $this->sendJSON($result);
    }
    
    /**
     * Массовые операции
     */
    private function bulkAction() {
        if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
            throw new Exception('POST request required');
        }
        
        $action = $_POST['bulk_action'] ?? '';
        $selectedIPs = $_POST['selected_ips'] ?? [];
        
        if (!$action || empty($selectedIPs)) {
            throw new Exception('Action and selected IPs are required');
        }
        
        $results = [];
        
        switch ($action) {
            case 'remove':
                foreach ($selectedIPs as $ip) {
                    $results[$ip] = $this->whitelist->removeUser($ip, 'admin');
                }
                break;
                
            case 'extend':
                $hours = intval($_POST['extend_hours'] ?? 2);
                foreach ($selectedIPs as $ip) {
                    $userStatus = $this->whitelist->getUserStatus($ip);
                    if ($userStatus['in_whitelist']) {
                        $username = $this->extractUsernameFromComment($userStatus['comment']);
                        $this->whitelist->removeUser($ip);
                        $results[$ip] = $this->whitelist->addUser($ip, $username, $hours * 3600);
                    }
                }
                break;
                
            case 'cleanup_expired':
                $result = $this->whitelist->cleanupExpired();
                return $this->sendJSON($result);
                
            default:
                throw new Exception('Unknown bulk action: ' . $action);
        }
        
        return $this->sendJSON(['results' => $results]);
    }
    
    /**
     * Информация о системе
     */
    private function showSystemInfo() {
        $systemInfo = $this->api->getSystemInfo();
        $whitelistStats = $this->getWhitelistStatistics();
        $networkInfo = $this->getNetworkInfo();
        
        $info = [
            'system' => $systemInfo,
            'whitelist' => $whitelistStats,
            'network' => $networkInfo,
            'performance' => $this->getPerformanceMetrics()
        ];
        
        if ($_REQUEST['format'] === 'json') {
            return $this->sendJSON($info);
        }
        
        include 'templates/system_info.php';
    }
    
    /**
     * Просмотр логов
     */
    private function showLogs() {
        $logType = $_REQUEST['log_type'] ?? 'operations';
        $lines = intval($_REQUEST['lines'] ?? 100);
        $search = $_REQUEST['search'] ?? '';
        
        $logs = $this->getLogs($logType, $lines, $search);
        
        if ($_REQUEST['format'] === 'json') {
            return $this->sendJSON(['logs' => $logs]);
        }
        
        include 'templates/logs.php';
    }
    
    /**
     * Настройки системы
     */
    private function showSettings() {
        if ($_SERVER['REQUEST_METHOD'] === 'POST') {
            return $this->updateSettings();
        }
        
        $settings = $this->getSettings();
        
        if ($_REQUEST['format'] === 'json') {
            return $this->sendJSON(['settings' => $settings]);
        }
        
        include 'templates/settings.php';
    }
    
    /**
     * Экспорт данных
     */
    private function exportData() {
        $format = $_REQUEST['format'] ?? 'json';
        $type = $_REQUEST['type'] ?? 'users';
        
        switch ($type) {
            case 'users':
                $data = $this->whitelist->getAllUsers();
                break;
            case 'logs':
                $data = $this->getLogs('operations', 1000);
                break;
            case 'stats':
                $data = $this->getDashboardStats();
                break;
            default:
                throw new Exception('Unknown export type: ' . $type);
        }
        
        switch ($format) {
            case 'json':
                header('Content-Type: application/json');
                header('Content-Disposition: attachment; filename="' . $type . '_' . date('Y-m-d') . '.json"');
                echo json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
                break;
                
            case 'csv':
                header('Content-Type: text/csv');
                header('Content-Disposition: attachment; filename="' . $type . '_' . date('Y-m-d') . '.csv"');
                echo $this->convertToCSV($data);
                break;
                
            default:
                throw new Exception('Unknown export format: ' . $format);
        }
    }
    
    /**
     * Вспомогательные методы
     */
    private function calculateMemoryUsage($systemInfo) {
        $free = intval($systemInfo['free-memory'] ?? 0);
        $total = intval($systemInfo['total-memory'] ?? 0);
        
        if ($total > 0) {
            return round((($total - $free) / $total) * 100, 1);
        }
        
        return 0;
    }
    
    private function getRecentAdditions($limit) {
        $users = $this->whitelist->getAllUsers();
        
        // Сортируем по времени добавления (если доступно в комментарии)
        usort($users, function($a, $b) {
            $timeA = $this->extractTimestampFromComment($a['comment']);
            $timeB = $this->extractTimestampFromComment($b['comment']);
            return $timeB - $timeA;
        });
        
        return array_slice($users, 0, $limit);
    }
    
    private function extractTimestampFromComment($comment) {
        if (preg_match('/Added:\s*(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})/', $comment, $matches)) {
            return strtotime($matches[1]);
        }
        return 0;
    }
    
    private function extractUsernameFromComment($comment) {
        if (preg_match('/User:\s*([^|]+)/', $comment, $matches)) {
            return trim($matches[1]);
        }
        return 'Unknown';
    }
    
    private function sendJSON($data) {
        header('Content-Type: application/json; charset=utf-8');
        echo json_encode($data, JSON_UNESCAPED_UNICODE);
    }
    
    private function sendError($message) {
        http_response_code(400);
        return $this->sendJSON(['error' => $message]);
    }
}
?>

5.2. Система мониторинга подключений

Модуль мониторинга в реальном времени отслеживает активные подключения и предоставляет детальную аналитику:[31][16]

<?php
class ConnectionMonitor {
    private $api;
    private $config;
    private $dbFile = '/var/lib/mikrotik/connections.db';
    
    public function __construct($api, $config) {
        $this->api = $api;
        $this->config = $config;
        $this->initDatabase();
    }
    
    /**
     * Инициализация SQLite базы данных для хранения статистики
     */
    private function initDatabase() {
        if (!file_exists(dirname($this->dbFile))) {
            mkdir(dirname($this->dbFile), 0755, true);
        }
        
        $pdo = new PDO('sqlite:' . $this->dbFile);
        $pdo->exec('CREATE TABLE IF NOT EXISTS connections (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            src_ip TEXT NOT NULL,
            dst_ip TEXT NOT NULL,
            dst_port INTEGER NOT NULL,
            protocol TEXT NOT NULL,
            bytes_in INTEGER DEFAULT 0,
            bytes_out INTEGER DEFAULT 0,
            packets_in INTEGER DEFAULT 0,
            packets_out INTEGER DEFAULT 0,
            first_seen DATETIME DEFAULT CURRENT_TIMESTAMP,
            last_seen DATETIME DEFAULT CURRENT_TIMESTAMP,
            username TEXT,
            user_agent TEXT,
            connection_id TEXT UNIQUE
        )');
        
        $pdo->exec('CREATE INDEX IF NOT EXISTS idx_src_ip ON connections(src_ip)');
        $pdo->exec('CREATE INDEX IF NOT EXISTS idx_dst_port ON connections(dst_port)');
        $pdo->exec('CREATE INDEX IF NOT EXISTS idx_last_seen ON connections(last_seen)');
    }
    
    /**
     * Сбор текущих подключений с MikroTik
     */
    public function collectConnections() {
        try {
            $monitoredPorts = $this->config['monitoring']['ports'] ?? ['3389', '44444', '55555', '55554'];
            $connections = [];
            
            foreach ($monitoredPorts as $port) {
                $portConnections = $this->api->executeRequest('/ip/firewall/connection/print', [
                    'dst-port' => $port,
                    'connection-state' => 'established'
                ]);
                
                $connections = array_merge($connections, $portConnections);
            }
            
            $this->updateConnectionDatabase($connections);
            $this->cleanOldConnections();
            
            return $connections;
            
        } catch (Exception $e) {
            error_log("Connection monitoring failed: " . $e->getMessage());
            return [];
        }
    }
    
    /**
     * Обновление базы данных подключений
     */
    private function updateConnectionDatabase($connections) {
        $pdo = new PDO('sqlite:' . $this->dbFile);
        
        foreach ($connections as $conn) {
            $connectionId = $this->generateConnectionId($conn);
            
            // Получаем информацию о пользователе из whitelist
            $userInfo = $this->getUserInfoByIP($conn['src-address'] ?? '');
            
            $stmt = $pdo->prepare('
                INSERT OR REPLACE INTO connections 
                (src_ip, dst_ip, dst_port, protocol, bytes_in, bytes_out, 
                 packets_in, packets_out, last_seen, username, connection_id, first_seen)
                VALUES (?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, ?, ?, 
                        COALESCE((SELECT first_seen FROM connections WHERE connection_id = ?), CURRENT_TIMESTAMP))
            ');
            
            $stmt->execute([
                $conn['src-address'] ?? '',
                $conn['dst-address'] ?? '',
                intval($conn['dst-port'] ?? 0),
                $conn['protocol'] ?? 'tcp',
                intval($conn['rx-byte'] ?? 0),
                intval($conn['tx-byte'] ?? 0),
                intval($conn['rx-packet'] ?? 0),
                intval($conn['tx-packet'] ?? 0),
                $userInfo['username'] ?? 'Unknown',
                $connectionId,
                $connectionId
            ]);
        }
    }
    
    /**
     * Генерация уникального ID подключения
     */
    private function generateConnectionId($connection) {
        return md5(
            ($connection['src-address'] ?? '') . ':' .
            ($connection['dst-address'] ?? '') . ':' .
            ($connection['dst-port'] ?? '') . ':' .
            ($connection['protocol'] ?? '')
        );
    }
    
    /**
     * Получение информации о пользователе по IP
     */
    private function getUserInfoByIP($ip) {
        if (!$ip) return ['username' => 'Unknown'];
        
        try {
            $whitelist = new WhitelistManager($this->api);
            $status = $whitelist->getUserStatus($ip);
            
            if ($status['in_whitelist']) {
                return [
                    'username' => $this->extractUsernameFromComment($status['comment']),
                    'comment' => $status['comment']
                ];
            }
        } catch (Exception $e) {
            // Игнорируем ошибки получения информации о пользователе
        }
        
        return ['username' => 'Unknown'];
    }
    
    /**
     * Очистка старых подключений
     */
    private function cleanOldConnections() {
        $retentionHours = $this->config['monitoring']['retention_hours'] ?? 168; // 7 дней
        
        $pdo = new PDO('sqlite:' . $this->dbFile);
        $stmt = $pdo->prepare('DELETE FROM connections WHERE last_seen < datetime("now", "-' . $retentionHours . ' hours")');
        $stmt->execute();
    }
    
    /**
     * Получение статистики подключений
     */
    public function getConnectionStats($hours = 24) {
        $pdo = new PDO('sqlite:' . $this->dbFile);
        
        // Общая статистика за период
        $stmt = $pdo->prepare('
            SELECT 
                COUNT(DISTINCT connection_id) as total_connections,
                COUNT(DISTINCT src_ip) as unique_ips,
                COUNT(DISTINCT username) as unique_users,
                dst_port,
                SUM(bytes_in + bytes_out) as total_bytes,
                SUM(packets_in + packets_out) as total_packets
            FROM connections 
            WHERE last_seen > datetime("now", "-' . $hours . ' hours")
            GROUP BY dst_port
        ');
        
        $stmt->execute();
        $portStats = $stmt->fetchAll(PDO::FETCH_ASSOC);
        
        // Топ пользователей по трафику
        $stmt = $pdo->prepare('
            SELECT 
                src_ip, username,
                SUM(bytes_in + bytes_out) as total_bytes,
                COUNT(DISTINCT connection_id) as connections_count,
                MAX(last_seen) as last_activity
            FROM connections 
            WHERE last_seen > datetime("now", "-' . $hours . ' hours")
            GROUP BY src_ip, username
            ORDER BY total_bytes DESC
            LIMIT 10
        ');
        
        $stmt->execute();
        $topUsers = $stmt->fetchAll(PDO::FETCH_ASSOC);
        
        // Активность по часам
        $stmt = $pdo->prepare('
            SELECT 
                strftime("%H", last_seen) as hour,
                COUNT(DISTINCT connection_id) as connections,
                SUM(bytes_in + bytes_out) as bytes
            FROM connections 
            WHERE last_seen > datetime("now", "-' . $hours . ' hours")
            GROUP BY hour
            ORDER BY hour
        ');
        
        $stmt->execute();
        $hourlyStats = $stmt->fetchAll(PDO::FETCH_ASSOC);
        
        return [
            'port_stats' => $portStats,
            'top_users' => $topUsers,
            'hourly_stats' => $hourlyStats,
            'period_hours' => $hours,
            'generated_at' => date('Y-m-d H:i:s')
        ];
    }
    
    /**
     * Получение активных подключений в реальном времени
     */
    public function getRealTimeConnections() {
        $pdo = new PDO('sqlite:' . $this->dbFile);
        
        $stmt = $pdo->prepare('
            SELECT * FROM connections 
            WHERE last_seen > datetime("now", "-5 minutes")
            ORDER BY last_seen DESC
        ');
        
        $stmt->execute();
        $connections = $stmt->fetchAll(PDO::FETCH_ASSOC);
        
        // Обогащаем данными о локации (если доступно GeoIP)
        foreach ($connections as &$connection) {
            $connection['location'] = $this->getIPLocation($connection['src_ip']);
            $connection['bytes_formatted'] = $this->formatBytes($connection['bytes_in'] + $connection['bytes_out']);
        }
        
        return $connections;
    }
    
    /**
     * Определение местоположения по IP
     */
    private function getIPLocation($ip) {
        // Заглушка для GeoIP функциональности
        // В реальной системе здесь может быть интеграция с MaxMind GeoIP2
        return [
            'country' => 'Unknown',
            'city' => 'Unknown',
            'isp' => 'Unknown'
        ];
    }
    
    /**
     * Форматирование размера в байтах
     */
    private function formatBytes($bytes) {
        $units = ['B', 'KB', 'MB', 'GB', 'TB'];
        $unitIndex = 0;
        
        while ($bytes >= 1024 && $unitIndex < count($units) - 1) {
            $bytes /= 1024;
            $unitIndex++;
        }
        
        return round($bytes, 2) . ' ' . $units[$unitIndex];
    }
    
    /**
     * Генерация отчета о подключениях
     */
    public function generateConnectionReport($startDate, $endDate, $format = 'array') {
        $pdo = new PDO('sqlite:' . $this->dbFile);
        
        $stmt = $pdo->prepare('
            SELECT 
                src_ip, username, dst_port,
                COUNT(*) as connection_count,
                SUM(bytes_in + bytes_out) as total_bytes,
                MIN(first_seen) as first_connection,
                MAX(last_seen) as last_connection,
                AVG(bytes_in + bytes_out) as avg_bytes_per_connection
            FROM connections 
            WHERE last_seen BETWEEN ? AND ?
            GROUP BY src_ip, username, dst_port
            ORDER BY total_bytes DESC
        ');
        
        $stmt->execute([$startDate, $endDate]);
        $data = $stmt->fetchAll(PDO::FETCH_ASSOC);
        
        // Форматируем данные
        foreach ($data as &$row) {
            $row['total_bytes_formatted'] = $this->formatBytes($row['total_bytes']);
            $row['avg_bytes_formatted'] = $this->formatBytes($row['avg_bytes_per_connection']);
        }
        
        if ($format === 'csv') {
            return $this->convertArrayToCSV($data);
        }
        
        return $data;
    }
    
    /**
     * Преобразование массива в CSV
     */
    private function convertArrayToCSV($data) {
        if (empty($data)) {
            return '';
        }
        
        $output = fopen('php://temp', 'w+');
        
        // Заголовки
        fputcsv($output, array_keys($data[0]));
        
        // Данные
        foreach ($data as $row) {
            fputcsv($output, $row);
        }
        
        rewind($output);
        $csv = stream_get_contents($output);
        fclose($output);
        
        return $csv;
    }
    
    private function extractUsernameFromComment($comment) {
        if (preg_match('/User:\s*([^|]+)/', $comment, $matches)) {
            return trim($matches[1]);
        }
        return 'Unknown';
    }
}
?>

5.3. Автоматическое истечение доступа

Система управления жизненным циклом обеспечивает автоматическое удаление просроченных записей и уведомление пользователей:[32][33]

<?php
class AccessLifecycleManager {
    private $whitelist;
    private $monitor;
    private $notifier;
    private $config;
    private $scheduleFile = '/var/lib/mikrotik/access_schedule.json';
    
    public function __construct($whitelist, $monitor, $config) {
        $this->whitelist = $whitelist;
        $this->monitor = $monitor;
        $this->config = $config;
        $this->notifier = new NotificationService($config);
    }
    
    /**
     * Основной метод обработки жизненного цикла
     */
    public function processLifecycle() {
        $this->cleanupExpiredAccess();
        $this->sendExpirationWarnings();
        $this->processScheduledActions();
        $this->generateLifecycleReport();
    }
    
    /**
     * Очистка просроченных доступов
     */
    private function cleanupExpiredAccess() {
        $cleanupResult = $this->whitelist->cleanupExpired();
        
        if ($cleanupResult['cleaned'] > 0) {
            $this->log('INFO', "Cleaned {$cleanupResult['cleaned']} expired entries");
            
            // Уведомление администраторов
            $this->notifier->sendToAdmins(
                'Автоматическая очистка белого списка',
                "Удалено {$cleanupResult['cleaned']} просроченных записей"
            );
        }
    }
    
    /**
     * Отправка предупреждений об истечении доступа
     */
    private function sendExpirationWarnings() {
        $users = $this->whitelist->getAllUsers();
        $warningHours = $this->config['warnings']['hours_before_expiry'] ?? 2;
        $warningTime = time() + ($warningHours * 3600);
        
        foreach ($users as $user) {
            if ($user['expires_at']) {
                $expiryTime = strtotime($user['expires_at']);
                
                // Проверяем, нужно ли отправить предупреждение
                if ($expiryTime > time() && $expiryTime <= $warningTime) {
                    if (!$this->wasWarningSent($user['address'], $expiryTime)) {
                        $this->sendExpirationWarning($user);
                        $this->markWarningAsSent($user['address'], $expiryTime);
                    }
                }
            }
        }
    }
    
    /**
     * Отправка предупреждения о скором истечении доступа
     */
    private function sendExpirationWarning($user) {
        $message = sprintf(
            'Ваш доступ к серверу %s истекает %s. IP: %s. Для продления доступа повторно авторизуйтесь на сайте.',
            $this->config['server_name'] ?? 'Battery Service',
            $user['expires_at'],
            $user['address']
        );
        
        // В реальной системе здесь может быть отправка email или SMS
        $this->log('WARNING', 'Expiration warning sent', [
            'ip' => $user['address'],
            'username' => $user['username'],
            'expires_at' => $user['expires_at']
        ]);
        
        // Если есть email в комментарии, отправляем уведомление
        $email = $this->extractEmailFromComment($user['comment']);
        if ($email) {
            $this->notifier->sendEmail($email, 'Истечение доступа', $message);
        }
    }
    
    /**
     * Обработка запланированных действий
     */
    private function processScheduledActions() {
        if (!file_exists($this->scheduleFile)) {
            return;
        }
        
        $schedule = json_decode(file_get_contents($this->scheduleFile), true) ?: [];
        $processedActions = [];
        
        foreach ($schedule as $index => $action) {
            if (time() >= $action['execute_at']) {
                try {
                    $this->executeScheduledAction($action);
                    $processedActions[] = $index;
                } catch (Exception $e) {
                    $this->log('ERROR', 'Failed to execute scheduled action', [
                        'action' => $action,
                        'error' => $e->getMessage()
                    ]);
                }
            }
        }
        
        // Удаляем выполненные действия
        foreach (array_reverse($processedActions) as $index) {
            unset($schedule[$index]);
        }
        
        file_put_contents($this->scheduleFile, json_encode(array_values($schedule)));
    }
    
    /**
     * Выполнение запланированного действия
     */
    private function executeScheduledAction($action) {
        switch ($action['type']) {
            case 'remove_user':
                $this->whitelist->removeUser($action['ip'], 'scheduled');
                $this->log('INFO', 'Scheduled user removal executed', ['ip' => $action['ip']]);
                break;
                
            case 'extend_user':
                $userStatus = $this->whitelist->getUserStatus($action['ip']);
                if ($userStatus['in_whitelist']) {
                    $username = $this->extractUsernameFromComment($userStatus['comment']);
                    $this->whitelist->removeUser($action['ip']);
                    $this->whitelist->addUser($action['ip'], $username, $action['extend_seconds']);
                    $this->log('INFO', 'Scheduled user extension executed', [
                        'ip' => $action['ip'],
                        'extend_seconds' => $action['extend_seconds']
                    ]);
                }
                break;
                
            case 'send_notification':
                $this->notifier->sendNotification($action['recipient'], $action['message']);
                break;
                
            default:
                throw new Exception('Unknown scheduled action type: ' . $action['type']);
        }
    }
    
    /**
     * Планирование действия
     */
    public function scheduleAction($type, $executeAt, $params = []) {
        $schedule = [];
        
        if (file_exists($this->scheduleFile)) {
            $schedule = json_decode(file_get_contents($this->scheduleFile), true) ?: [];
        }
        
        $action = array_merge([
            'type' => $type,
            'execute_at' => $executeAt,
            'created_at' => time(),
            'id' => uniqid()
        ], $params);
        
        $schedule[] = $action;
        
        file_put_contents($this->scheduleFile, json_encode($schedule));
        
        return $action['id'];
    }
    
    /**
     * Автоматическое продление доступа для активных пользователей
     */
    public function autoExtendActiveUsers() {
        $users = $this->whitelist->getAllUsers();
        $connections = $this->monitor->getRealTimeConnections();
        
        // Получаем список активных IP за последние N минут
        $activeIPs = [];
        $activityWindow = $this->config['auto_extend']['activity_window_minutes'] ?? 30;
        
        foreach ($connections as $conn) {
            if (strtotime($conn['last_seen']) > time() - ($activityWindow * 60)) {
                $activeIPs[] = $conn['src_ip'];
            }
        }
        
        $activeIPs = array_unique($activeIPs);
        $extendedCount = 0;
        
        foreach ($users as $user) {
            if (in_array($user['address'], $activeIPs) && $user['expires_at']) {
                $expiryTime = strtotime($user['expires_at']);
                $extendThreshold = time() + ($this->config['auto_extend']['extend_threshold_minutes'] ?? 15) * 60;
                
                // Если доступ истекает скоро, а пользователь активен - продлеваем
                if ($expiryTime <= $extendThreshold) {
                    $extendSeconds = $this->config['auto_extend']['extend_duration_hours'] ?? 1;
                    $extendSeconds *= 3600;
                    
                    $this->whitelist->removeUser($user['address']);
                    $this->whitelist->addUser($user['address'], $user['username'], $extendSeconds);
                    
                    $this->log('INFO', 'Auto-extended active user', [
                        'ip' => $user['address'],
                        'username' => $user['username'],
                        'extended_hours' => $extendSeconds / 3600
                    ]);
                    
                    $extendedCount++;
                }
            }
        }
        
        if ($extendedCount > 0) {
            $this->log('INFO', "Auto-extended {$extendedCount} active users");
        }
        
        return $extendedCount;
    }
    
    /**
     * Генерация отчета о жизненном цикле
     */
    private function generateLifecycleReport() {
        $users = $this->whitelist->getAllUsers();
        
        $stats = [
            'total_users' => count($users),
            'expiring_soon' => 0, // В течение следующих 2 часов
            'expired' => 0,
            'active_long_term' => 0, // Активны более 6 часов
            'users_by_expiry' => []
        ];
        
        $now = time();
        $soonThreshold = $now + (2 * 3600); // 2 часа
        $longTermThreshold = 6 * 3600; // 6 часов
        
        foreach ($users as $user) {
            if ($user['expires_at']) {
                $expiryTime = strtotime($user['expires_at']);
                $creationTime = strtotime($user['creation_time'] ?? date('Y-m-d H:i:s'));
                $duration = $now - $creationTime;
                
                if ($expiryTime <= $now) {
                    $stats['expired']++;
                } elseif ($expiryTime <= $soonThreshold) {
                    $stats['expiring_soon']++;
                }
                
                if ($duration >= $longTermThreshold) {
                    $stats['active_long_term']++;
                }
                
                // Группировка по времени истечения
                $expiryHour = date('Y-m-d H:00', $expiryTime);
                if (!isset($stats['users_by_expiry'][$expiryHour])) {
                    $stats['users_by_expiry'][$expiryHour] = 0;
                }
                $stats['users_by_expiry'][$expiryHour]++;
            }
        }
        
        // Сохраняем отчет
        $reportFile = '/var/log/mikrotik/lifecycle_report_' . date('Y-m-d_H') . '.json';
        file_put_contents($reportFile, json_encode([
            'timestamp' => date('Y-m-d H:i:s'),
            'stats' => $stats,
            'detailed_users' => $users
        ]));
        
        return $stats;
    }
    
    /**
     * Проверка отправки предупреждения
     */
    private function wasWarningSent($ip, $expiryTime) {
        $warningFile = '/tmp/warnings_sent.json';
        
        if (!file_exists($warningFile)) {
            return false;
        }
        
        $warnings = json_decode(file_get_contents($warningFile), true) ?: [];
        $warningKey = $ip . '_' . $expiryTime;
        
        return isset($warnings[$warningKey]);
    }
    
    /**
     * Отметка о отправленном предупреждении
     */
    private function markWarningAsSent($ip, $expiryTime) {
        $warningFile = '/tmp/warnings_sent.json';
        $warnings = [];
        
        if (file_exists($warningFile)) {
            $warnings = json_decode(file_get_contents($warningFile), true) ?: [];
        }
        
        $warningKey = $ip . '_' . $expiryTime;
        $warnings[$warningKey] = time();
        
        // Очищаем старые предупреждения (старше 7 дней)
        $cutoff = time() - (7 * 24 * 3600);
        $warnings = array_filter($warnings, function($timestamp) use ($cutoff) {
            return $timestamp > $cutoff;
        });
        
        file_put_contents($warningFile, json_encode($warnings));
    }
    
    /**
     * Извлечение email из комментария
     */
    private function extractEmailFromComment($comment) {
        if (preg_match('/([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/', $comment, $matches)) {
            return $matches[1];
        }
        return null;
    }
    
    /**
     * Логирование
     */
    private function log($level, $message, $context = []) {
        $logEntry = [
            'timestamp' => date('Y-m-d H:i:s'),
            'level' => $level,
            'service' => 'AccessLifecycleManager',
            'message' => $message,
            'context' => $context
        ];
        
        file_put_contents('/var/log/mikrotik/lifecycle.log', json_encode($logEntry) . "\n", FILE_APPEND | LOCK_EX);
    }
}
?>

5.4. Журналирование всех операций

Комплексная система логирования обеспечивает полный аудит всех операций системы:[34][35]

<?php
class AuditLogger {
    private $loggers = [];
    private $config;
    
    public function __construct($config) {
        $this->config = $config;
        $this->initializeLoggers();
    }
    
    /**
     * Инициализация различных типов логгеров
     */
    private function initializeLoggers() {
        // Файловый логгер
        $this->loggers['file'] = new FileLogger($this->config['logging']['file']);
        
        // Системный логгер (syslog)
        if ($this->config['logging']['syslog']['enabled'] ?? false) {
            $this->loggers['syslog'] = new SyslogLogger($this->config['logging']['syslog']);
        }
        
        // База данных логгер
        if ($this->config['logging']['database']['enabled'] ?? false) {
            $this->loggers['database'] = new DatabaseLogger($this->config['logging']['database']);
        }
        
        // Удаленный логгер (например, ELK Stack)
        if ($this->config['logging']['remote']['enabled'] ?? false) {
            $this->loggers['remote'] = new RemoteLogger($this->config['logging']['remote']);
        }
    }
    
    /**
     * Логирование события
     */
    public function logEvent($category, $action, $details = [], $severity = 'INFO') {
        $event = [
            'timestamp' => microtime(true),
            'datetime' => date('Y-m-d H:i:s.u'),
            'category' => $category,
            'action' => $action,
            'severity' => $severity,
            'details' => $details,
            'user_ip' => $this->getCurrentUserIP(),
            'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'N/A',
            'session_id' => session_id() ?: uniqid(),
            'request_id' => $this->getRequestId(),
            'server_ip' => $_SERVER['SERVER_ADDR'] ?? 'N/A',
            'process_id' => getmypid()
        ];
        
        // Отправляем во все настроенные логгеры
        foreach ($this->loggers as $logger) {
            try {
                $logger->log($event);
            } catch (Exception $e) {
                // Если основное логирование не работает, записываем в fallback лог
                error_log("Logger failed: " . $e->getMessage() . " | Original event: " . json_encode($event));
            }
        }
    }
    
    /**
     * Специализированные методы логирования
     */
    public function logUserAction($username, $action, $ip, $details = []) {
        $this->logEvent('USER_ACTION', $action, array_merge($details, [
            'username' => $username,
            'target_ip' => $ip
        ]));
    }
    
    public function logSystemEvent($action, $details = []) {
        $this->logEvent('SYSTEM', $action, $details);
    }
    
    public function logSecurityEvent($action, $details = [], $severity = 'WARNING') {
        $this->logEvent('SECURITY', $action, $details, $severity);
    }
    
    public function logAPIRequest($endpoint, $method, $params = [], $response = null) {
        $this->logEvent('API', 'REQUEST', [
            'endpoint' => $endpoint,
            'method' => $method,
            'parameters' => $params,
            'response_code' => http_response_code(),
            'response_size' => strlen($response ?? ''),
            'execution_time' => $this->getExecutionTime()
        ]);
    }
    
    public function logConnectionEvent($src_ip, $dst_port, $action, $details = []) {
        $this->logEvent('CONNECTION', $action, array_merge($details, [
            'source_ip' => $src_ip,
            'destination_port' => $dst_port
        ]));
    }
    
    /**
     * Поиск в логах
     */
    public function searchLogs($criteria, $limit = 100) {
        $results = [];
        
        foreach ($this->loggers as $loggerName => $logger) {
            if (method_exists($logger, 'search')) {
                try {
                    $loggerResults = $logger->search($criteria, $limit);
                    $results[$loggerName] = $loggerResults;
                } catch (Exception $e) {
                    $results[$loggerName] = ['error' => $e->getMessage()];
                }
            }
        }
        
        return $results;
    }
    
    /**
     * Получение статистики логов
     */
    public function getLogStatistics($period = '24h') {
        $stats = [];
        
        foreach ($this->loggers as $loggerName => $logger) {
            if (method_exists($logger, 'getStatistics')) {
                try {
                    $stats[$loggerName] = $logger->getStatistics($period);
                } catch (Exception $e) {
                    $stats[$loggerName] = ['error' => $e->getMessage()];
                }
            }
        }
        
        return $stats;
    }
    
    /**
     * Ротация логов
     */
    public function rotateLogs() {
        foreach ($this->loggers as $logger) {
            if (method_exists($logger, 'rotate')) {
                try {
                    $logger->rotate();
                } catch (Exception $e) {
                    error_log("Log rotation failed for " . get_class($logger) . ": " . $e->getMessage());
                }
            }
        }
    }
    
    /**
     * Вспомогательные методы
     */
    private function getCurrentUserIP() {
        $ipKeys = [
            'HTTP_CF_CONNECTING_IP',
            'HTTP_CLIENT_IP', 
            'HTTP_X_FORWARDED_FOR',
            'HTTP_X_REAL_IP',
            'REMOTE_ADDR'
        ];
        
        foreach ($ipKeys as $key) {
            if (!empty($_SERVER[$key])) {
                return trim(explode(',', $_SERVER[$key])[0]);
            }
        }
        
        return 'unknown';
    }
    
    private function getRequestId() {
        static $requestId = null;
        
        if ($requestId === null) {
            $requestId = uniqid('req_', true);
        }
        
        return $requestId;
    }
    
    private function getExecutionTime() {
        static $startTime = null;
        
        if ($startTime === null) {
            $startTime = $_SERVER['REQUEST_TIME_FLOAT'] ?? microtime(true);
        }
        
        return round(microtime(true) - $startTime, 4);
    }
}

/**
 * Файловый логгер
 */
class FileLogger {
    private $logPath;
    private $maxFileSize;
    private $maxFiles;
    
    public function __construct($config) {
        $this->logPath = $config['path'] ?? '/var/log/mikrotik/audit.log';
        $this->maxFileSize = $config['max_size'] ?? 10485760; // 10MB
        $this->maxFiles = $config['max_files'] ?? 10;
        
        $this->ensureLogDirectory();
    }
    
    public function log($event) {
        $logLine = json_encode($event) . "\n";
        
        // Проверяем размер файла
        if (file_exists($this->logPath) && filesize($this->logPath) > $this->maxFileSize) {
            $this->rotate();
        }
        
        file_put_contents($this->logPath, $logLine, FILE_APPEND | LOCK_EX);
    }
    
    public function search($criteria, $limit = 100) {
        if (!file_exists($this->logPath)) {
            return [];
        }
        
        $results = [];
        $handle = fopen($this->logPath, 'r');
        $count = 0;
        
        if ($handle) {
            while (($line = fgets($handle)) !== false && $count < $limit) {
                $event = json_decode(trim($line), true);
                
                if ($this->matchesCriteria($event, $criteria)) {
                    $results[] = $event;
                    $count++;
                }
            }
            
            fclose($handle);
        }
        
        return array_reverse($results); // Новые записи сначала
    }
    
    public function rotate() {
        if (!file_exists($this->logPath)) {
            return;
        }
        
        // Сдвигаем существующие файлы
        for ($i = $this->maxFiles - 1; $i >= 1; $i--) {
            $oldFile = $this->logPath . '.' . $i;
            $newFile = $this->logPath . '.' . ($i + 1);
            
            if (file_exists($oldFile)) {
                if ($i == $this->maxFiles - 1) {
                    unlink($oldFile); // Удаляем самый старый файл
                } else {
                    rename($oldFile, $newFile);
                }
            }
        }
        
        // Переименовываем текущий файл
        rename($this->logPath, $this->logPath . '.1');
        
        // Создаем новый файл
        touch($this->logPath);
        chmod($this->logPath, 0644);
    }
    
    private function ensureLogDirectory() {
        $dir = dirname($this->logPath);
        
        if (!is_dir($dir)) {
            mkdir($dir, 0755, true);
        }
    }
    
    private function matchesCriteria($event, $criteria) {
        if (!is_array($event) || !is_array($criteria)) {
            return false;
        }
        
        foreach ($criteria as $key => $value) {
            switch ($key) {
                case 'category':
                case 'action':
                case 'severity':
                    if (($event[$key] ?? '') !== $value) {
                        return false;
                    }
                    break;
                    
                case 'user_ip':
                    if (($event['user_ip'] ?? '') !== $value) {
                        return false;
                    }
                    break;
                    
                case 'date_from':
                    if (($event['timestamp'] ?? 0) < strtotime($value)) {
                        return false;
                    }
                    break;
                    
                case 'date_to':
                    if (($event['timestamp'] ?? 0) > strtotime($value)) {
                        return false;
                    }
                    break;
                    
                case 'text':
                    $haystack = json_encode($event);
                    if (stripos($haystack, $value) === false) {
                        return false;
                    }
                    break;
            }
        }
        
        return true;
    }                                                                                             }
**Базовый класс для других типов логгеров:**

```php
/**
 * База данных логгер для SQLite
 */
class DatabaseLogger {
    private $pdo;
    private $tableName = 'audit_logs';
    
    public function __construct($config) {
        $dbPath = $config['path'] ?? '/var/lib/mikrotik/audit.db';
        $this->pdo = new PDO('sqlite:' . $dbPath);
        $this->initializeTable();
    }
    
    private function initializeTable() {
        $this->pdo->exec("
            CREATE TABLE IF NOT EXISTS {$this->tableName} (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                timestamp REAL NOT NULL,
                datetime TEXT NOT NULL,
                category TEXT NOT NULL,
                action TEXT NOT NULL,
                severity TEXT NOT NULL,
                details TEXT,
                user_ip TEXT,
                user_agent TEXT,
                session_id TEXT,
                request_id TEXT,
                server_ip TEXT,
                process_id INTEGER,
                created_at DATETIME DEFAULT CURRENT_TIMESTAMP
            )
        ");
        
        // Создаем индексы для быстрого поиска
        $this->pdo->exec("CREATE INDEX IF NOT EXISTS idx_timestamp ON {$this->tableName}(timestamp)");
        $this->pdo->exec("CREATE INDEX IF NOT EXISTS idx_category ON {$this->tableName}(category)");
        $this->pdo->exec("CREATE INDEX IF NOT EXISTS idx_user_ip ON {$this->tableName}(user_ip)");
        $this->pdo->exec("CREATE INDEX IF NOT EXISTS idx_severity ON {$this->tableName}(severity)");
    }
    
    public function log($event) {
        $stmt = $this->pdo->prepare("
            INSERT INTO {$this->tableName} 
            (timestamp, datetime, category, action, severity, details, user_ip, user_agent, 
             session_id, request_id, server_ip, process_id)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
        ");
        
        $stmt->execute([
            $event['timestamp'],
            $event['datetime'],
            $event['category'],
            $event['action'],
            $event['severity'],
            json_encode($event['details']),
            $event['user_ip'],
            $event['user_agent'],
            $event['session_id'],
            $event['request_id'],
            $event['server_ip'],
            $event['process_id']
        ]);
    }
    
    public function search($criteria, $limit = 100) {
        $where = ['1=1'];
        $params = [];
        
        if (isset($criteria['category'])) {
            $where[] = 'category = ?';
            $params[] = $criteria['category'];
        }
        
        if (isset($criteria['severity'])) {
            $where[] = 'severity = ?';
            $params[] = $criteria['severity'];
        }
        
        if (isset($criteria['user_ip'])) {
            $where[] = 'user_ip = ?';
            $params[] = $criteria['user_ip'];
        }
        
        if (isset($criteria['date_from'])) {
            $where[] = 'timestamp >= ?';
            $params[] = strtotime($criteria['date_from']);
        }
        
        if (isset($criteria['date_to'])) {
            $where[] = 'timestamp <= ?';
            $params[] = strtotime($criteria['date_to']);
        }
        
        if (isset($criteria['text'])) {
            $where[] = '(details LIKE ? OR action LIKE ?)';
            $params[] = '%' . $criteria['text'] . '%';
            $params[] = '%' . $criteria['text'] . '%';
        }
        
        $sql = "SELECT * FROM {$this->tableName} WHERE " . implode(' AND ', $where) . 
               " ORDER BY timestamp DESC LIMIT ?";
        $params[] = $limit;
        
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute($params);
        
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }
    
    public function getStatistics($period = '24h') {
        $hours = intval(str_replace('h', '', $period));
        $since = time() - ($hours * 3600);
        
        $stmt = $this->pdo->prepare("
            SELECT 
                category,
                severity,
                COUNT(*) as count,
                COUNT(DISTINCT user_ip) as unique_ips
            FROM {$this->tableName} 
            WHERE timestamp >= ?
            GROUP BY category, severity
            ORDER BY count DESC
        ");
        
        $stmt->execute([$since]);
        
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }
}

/**
 * Системный логгер (syslog)
 */
class SyslogLogger {
    private $facility;
    private $tag;
    
    public function __construct($config) {
        $this->facility = $config['facility'] ?? LOG_LOCAL0;
        $this->tag = $config['tag'] ?? 'mikrotik-whitelist';
        
        openlog($this->tag, LOG_PID | LOG_PERROR, $this->facility);
    }
    
    public function log($event) {
        $priority = $this->severityToPriority($event['severity']);
        $message = sprintf(
            "[%s] %s: %s | Details: %s | IP: %s",
            $event['category'],
            $event['action'],
            json_encode($event['details']),
            $event['user_ip']
        );
        
        syslog($priority, $message);
    }
    
    private function severityToPriority($severity) {
        switch (strtoupper($severity)) {
            case 'CRITICAL': return LOG_CRIT;
            case 'ERROR': return LOG_ERR;
            case 'WARNING': return LOG_WARNING;
            case 'INFO': return LOG_INFO;
            case 'DEBUG': return LOG_DEBUG;
            default: return LOG_NOTICE;
        }
    }
    
    public function __destruct() {
        closelog();
    }
}
?>

5.5. Интерфейс для ручного управления

Веб-интерфейс для ручного управления белым списком обеспечивает удобное администрирование через браузер. Интерфейс построен с использованием современных веб-технологий и обеспечивает отзывчивый дизайн.[1]

HTML шаблон главной страницы администрирования:

<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Управление белым списком - Battery Service</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
    <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
    <style>
        .status-active { color: #28a745; }
        .status-expired { color: #dc3545; }
        .status-expiring { color: #ffc107; }
        .loading { display: none; }
        .card-stats { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); }
        .card-stats .card-body { color: white; }
        .logs-container { max-height: 400px; overflow-y: auto; font-family: 'Courier New', monospace; font-size: 12px; }
    </style>
</head>
<body>
    <nav class="navbar navbar-expand-lg navbar-dark bg-primary">
        <div class="container">
            <a class="navbar-brand" href="#"><i class="fas fa-shield-alt"></i> Управление доступом</a>
            <div class="navbar-nav ms-auto">
                <span class="navbar-text">Добро пожаловать, <?php echo htmlspecialchars($_SERVER['PHP_AUTH_USER']); ?></span>
            </div>
        </div>
    </nav>

    <div class="container mt-4">
        <!-- Панель статистики -->
        <div class="row mb-4">
            <div class="col-md-3">
                <div class="card card-stats">
                    <div class="card-body">
                        <div class="d-flex justify-content-between">
                            <div>
                                <p class="card-category">Всего пользователей</p>
                                <h3 class="card-title" id="total-users">-</h3>
                            </div>
                            <div class="card-icon">
                                <i class="fas fa-users fa-2x"></i>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
            <div class="col-md-3">
                <div class="card text-white bg-success">
                    <div class="card-body">
                        <div class="d-flex justify-content-between">
                            <div>
                                <p class="card-category">Активных</p>
                                <h3 class="card-title" id="active-users">-</h3>
                            </div>
                            <div class="card-icon">
                                <i class="fas fa-check-circle fa-2x"></i>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
            <div class="col-md-3">
                <div class="card text-white bg-warning">
                    <div class="card-body">
                        <div class="d-flex justify-content-between">
                            <div>
                                <p class="card-category">Истекают скоро</p>
                                <h3 class="card-title" id="expiring-users">-</h3>
                            </div>
                            <div class="card-icon">
                                <i class="fas fa-clock fa-2x"></i>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
            <div class="col-md-3">
                <div class="card text-white bg-danger">
                    <div class="card-body">
                        <div class="d-flex justify-content-between">
                            <div>
                                <p class="card-category">Просроченных</p>
                                <h3 class="card-title" id="expired-users">-</h3>
                            </div>
                            <div class="card-icon">
                                <i class="fas fa-exclamation-triangle fa-2x"></i>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>

        <!-- Управление пользователями -->
        <div class="row">
            <div class="col-md-8">
                <div class="card">
                    <div class="card-header d-flex justify-content-between align-items-center">
                        <h5><i class="fas fa-list"></i> Список пользователей</h5>
                        <div>
                            <button class="btn btn-success btn-sm" data-bs-toggle="modal" data-bs-target="#addUserModal">
                                <i class="fas fa-plus"></i> Добавить пользователя
                            </button>
                            <button class="btn btn-warning btn-sm" onclick="cleanupExpired()">
                                <i class="fas fa-broom"></i> Очистить просроченные
                            </button>
                        </div>
                    </div>
                    <div class="card-body">
                        <!-- Фильтры -->
                        <div class="row mb-3">
                            <div class="col-md-4">
                                <input type="text" class="form-control" id="searchFilter" placeholder="Поиск по IP или пользователю">
                            </div>
                            <div class="col-md-3">
                                <select class="form-control" id="statusFilter">
                                    <option value="">Все статусы</option>
                                    <option value="active">Активные</option>
                                    <option value="expiring">Истекают скоро</option>
                                    <option value="expired">Просроченные</option>
                                </select>
                            </div>
                            <div class="col-md-2">
                                <button class="btn btn-outline-primary" onclick="applyFilters()">
                                    <i class="fas fa-filter"></i> Фильтр
                                </button>
                            </div>
                        </div>

                        <!-- Таблица пользователей -->
                        <div class="table-responsive">
                            <table class="table table-striped" id="usersTable">
                                <thead>
                                    <tr>
                                        <th><input type="checkbox" id="selectAll"></th>
                                        <th>IP адрес</th>
                                        <th>Пользователь</th>
                                        <th>Статус</th>
                                        <th>Истекает</th>
                                        <th>Действия</th>
                                    </tr>
                                </thead>
                                <tbody id="usersTableBody">
                                    <tr>
                                        <td colspan="6" class="text-center">
                                            <div class="loading">
                                                <i class="fas fa-spinner fa-spin"></i> Загрузка...
                                            </div>
                                        </td>
                                    </tr>
                                </tbody>
                            </table>
                        </div>

                        <!-- Пагинация -->
                        <nav aria-label="Навигация по страницам">
                            <ul class="pagination justify-content-center" id="pagination">
                            </ul>
                        </nav>

                        <!-- Массовые операции -->
                        <div class="mt-3" id="bulkActions" style="display: none;">
                            <div class="input-group">
                                <select class="form-control" id="bulkActionSelect">
                                    <option value="">Выберите действие</option>
                                    <option value="extend">Продлить доступ</option>
                                    <option value="remove">Удалить из списка</option>
                                </select>
                                <button class="btn btn-primary" onclick="executeBulkAction()">Выполнить</button>
                            </div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="col-md-4">
                <!-- Информация о системе -->
                <div class="card mb-3">
                    <div class="card-header">
                        <h6><i class="fas fa-server"></i> Состояние системы</h6>
                    </div>
                    <div class="card-body">
                        <div class="row">
                            <div class="col-6">
                                <small class="text-muted">Время работы</small>
                                <div id="system-uptime">-</div>
                            </div>
                            <div class="col-6">
                                <small class="text-muted">CPU</small>
                                <div id="system-cpu">-</div>
                            </div>
                        </div>
                        <div class="row mt-2">
                            <div class="col-6">
                                <small class="text-muted">Память</small>
                                <div id="system-memory">-</div>
                            </div>
                            <div class="col-6">
                                <small class="text-muted">Соединения</small>
                                <div id="active-connections">-</div>
                            </div>
                        </div>
                    </div>
                </div>

                <!-- Последние события -->
                <div class="card">
                    <div class="card-header">
                        <h6><i class="fas fa-history"></i> Последние события</h6>
                    </div>
                    <div class="card-body logs-container" id="recentLogs">
                        <div class="text-center">
                            <i class="fas fa-spinner fa-spin"></i> Загрузка логов...
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <!-- Модальное окно добавления пользователя -->
    <div class="modal fade" id="addUserModal" tabindex="-1">
        <div class="modal-dialog">
            <div class="modal-content">
                <div class="modal-header">
                    <h5 class="modal-title">Добавить пользователя</h5>
                    <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
                </div>
                <div class="modal-body">
                    <form id="addUserForm">
                        <div class="mb-3">
                            <label class="form-label">IP адрес</label>
                            <input type="text" class="form-control" id="userIP" required 
                                   pattern="^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$" 
                                   placeholder="192.168.1.100">
                        </div>
                        <div class="mb-3">
                            <label class="form-label">Имя пользователя</label>
                            <input type="text" class="form-control" id="username" required 
                                   placeholder="john.doe">
                        </div>
                        <div class="mb-3">
                            <label class="form-label">Время доступа (часы)</label>
                            <select class="form-control" id="accessHours">
                                <option value="1">1 час</option>
                                <option value="2" selected>2 часа</option>
                                <option value="4">4 часа</option>
                                <option value="8">8 часов</option>
                                <option value="24">24 часа</option>
                            </select>
                        </div>
                        <div class="mb-3">
                            <label class="form-label">Комментарий (опционально)</label>
                            <textarea class="form-control" id="userComment" rows="2" 
                                      placeholder="Дополнительная информация о пользователе"></textarea>
                        </div>
                    </form>
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Отмена</button>
                    <button type="button" class="btn btn-primary" onclick="addUser()">Добавить пользователя</button>
                </div>
            </div>
        </div>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
    <script>
        // Основные переменные
        let currentPage = 1;
        let totalPages = 1;
        let users = [];
        let selectedUsers = [];

        // Инициализация страницы
        document.addEventListener('DOMContentLoaded', function() {
            loadDashboardData();
            loadUsers();
            loadSystemInfo();
            loadRecentLogs();
            
            // Автообновление каждые 30 секунд
            setInterval(function() {
                loadDashboardData();
                loadSystemInfo();
                loadRecentLogs();
            }, 30000);
            
            // Обработчики событий
            document.getElementById('selectAll').addEventListener('change', toggleSelectAll);
            document.getElementById('searchFilter').addEventListener('input', debounce(applyFilters, 500));
        });

        // Загрузка статистики панели управления
        function loadDashboardData() {
            fetch('?action=dashboard&format=json')
                .then(response => response.json())
                .then(data => {
                    document.getElementById('total-users').textContent = data.total_users || 0;
                    document.getElementById('active-users').textContent = data.active_users || 0;
                    document.getElementById('expiring-users').textContent = 
                        (data.total_users - data.active_users - data.expired_users) || 0;
                    document.getElementById('expired-users').textContent = data.expired_users || 0;
                })
                .catch(error => console.error('Ошибка загрузки статистики:', error));
        }

        // Загрузка списка пользователей
        function loadUsers(page = 1) {
            const search = document.getElementById('searchFilter').value;
            const filter = document.getElementById('statusFilter').value;
            
            document.querySelector('.loading').style.display = 'block';
            
            fetch(`?action=users&format=json&page=${page}&search=${encodeURIComponent(search)}&filter=${filter}`)
                .then(response => response.json())
                .then(data => {
                    users = data.users || [];
                    currentPage = data.pagination.page;
                    totalPages = data.pagination.pages;
                    
                    renderUsersTable();
                    renderPagination();
                    
                    document.querySelector('.loading').style.display = 'none';
                })
                .catch(error => {
                    console.error('Ошибка загрузки пользователей:', error);
                    document.querySelector('.loading').style.display = 'none';
                });
        }

        // Отрисовка таблицы пользователей
        function renderUsersTable() {
            const tbody = document.getElementById('usersTableBody');
            tbody.innerHTML = '';

            if (users.length === 0) {
                tbody.innerHTML = '<tr><td colspan="6" class="text-center">Пользователи не найдены</td></tr>';
                return;
            }

            users.forEach(user => {
                const row = document.createElement('tr');
                const status = getUserStatus(user);
                
                row.innerHTML = `
                    <td><input type="checkbox" class="user-checkbox" value="${user.address}"></td>
                    <td><code>${user.address}</code></td>
                    <td>${user.username || 'Неизвестно'}</td>
                    <td><span class="badge bg-${status.color}">${status.text}</span></td>
                    <td>${user.expires_at ? formatDateTime(user.expires_at) : 'Никогда'}</td>
                    <td>
                        <div class="btn-group btn-group-sm">
                            <button class="btn btn-outline-primary" onclick="extendUser('${user.address}')" title="Продлить">
                                <i class="fas fa-clock"></i>
                            </button>
                            <button class="btn btn-outline-danger" onclick="removeUser('${user.address}')" title="Удалить">
                                <i class="fas fa-trash"></i>
                            </button>
                        </div>
                    </td>
                `;
                
                tbody.appendChild(row);
            });

            // Обновляем обработчики чекбоксов
            document.querySelectorAll('.user-checkbox').forEach(checkbox => {
                checkbox.addEventListener('change', updateSelectedUsers);
            });
        }

        // Определение статуса пользователя
        function getUserStatus(user) {
            if (!user.expires_at) {
                return { color: 'success', text: 'Активен' };
            }

            const now = new Date();
            const expiryDate = new Date(user.expires_at);
            const hoursUntilExpiry = (expiryDate - now) / (1000 * 60 * 60);

            if (hoursUntilExpiry <= 0) {
                return { color: 'danger', text: 'Просрочен' };
            } else if (hoursUntilExpiry <= 2) {
                return { color: 'warning', text: 'Истекает скоро' };
            } else {
                return { color: 'success', text: 'Активен' };
            }
        }

        // Добавление пользователя
        function addUser() {
            const ip = document.getElementById('userIP').value;
            const username = document.getElementById('username').value;
            const hours = document.getElementById('accessHours').value;
            const comment = document.getElementById('userComment').value;

            if (!ip || !username) {
                alert('Пожалуйста, заполните все обязательные поля');
                return;
            }

            const formData = new FormData();
            formData.append('action', 'add_user');
            formData.append('ip', ip);
            formData.append('username', username);
            formData.append('timeout', hours * 3600);
            formData.append('comment', comment);

            fetch('', {
                method: 'POST',
                body: formData
            })
            .then(response => response.json())
            .then(data => {
                if (data.success) {
                    bootstrap.Modal.getInstance(document.getElementById('addUserModal')).hide();
                    document.getElementById('addUserForm').reset();
                    loadUsers(currentPage);
                    loadDashboardData();
                    showNotification('Пользователь успешно добавлен', 'success');
                } else {
                    showNotification('Ошибка: ' + (data.message || 'Неизвестная ошибка'), 'danger');
                }
            })
            .catch(error => {
                console.error('Ошибка добавления пользователя:', error);
                showNotification('Ошибка при добавлении пользователя', 'danger');
            });
        }

        // Удаление пользователя
        function removeUser(ip) {
            if (!confirm(`Вы уверены, что хотите удалить пользователя с IP ${ip}?`)) {
                return;
            }

            const formData = new FormData();
            formData.append('action', 'remove_user');
            formData.append('ip', ip);

            fetch('', {
                method: 'POST',
                body: formData
            })
            .then(response => response.json())
            .then(data => {
                if (data.success) {
                    loadUsers(currentPage);
                    loadDashboardData();
                    showNotification('Пользователь удален', 'success');
                } else {
                    showNotification('Ошибка: ' + (data.message || 'Неизвестная ошибка'), 'danger');
                }
            })
            .catch(error => {
                console.error('Ошибка удаления пользователя:', error);
                showNotification('Ошибка при удалении пользователя', 'danger');
            });
        }

        // Продление доступа пользователя
        function extendUser(ip) {
            const hours = prompt('На сколько часов продлить доступ?', '2');
            
            if (!hours || isNaN(hours) || hours <= 0) {
                return;
            }

            const formData = new FormData();
            formData.append('action', 'extend_user');
            formData.append('ip', ip);
            formData.append('hours', hours);

            fetch('', {
                method: 'POST',
                body: formData
            })
            .then(response => response.json())
            .then(data => {
                if (data.success) {
                    loadUsers(currentPage);
                    loadDashboardData();
                    showNotification(`Доступ продлен на ${hours} часов`, 'success');
                } else {
                    showNotification('Ошибка: ' + (data.message || 'Неизвестная ошибка'), 'danger');
                }
            })
            .catch(error => {
                console.error('Ошибка продления доступа:', error);
                showNotification('Ошибка при продлении доступа', 'danger');
            });
        }

        // Очистка просроченных записей
        function cleanupExpired() {
            if (!confirm('Вы уверены, что хотите удалить все просроченные записи?')) {
                return;
            }

            const formData = new FormData();
            formData.append('action', 'bulk_action');
            formData.append('bulk_action', 'cleanup_expired');

            fetch('', {
                method: 'POST',
                body: formData
            })
            .then(response => response.json())
            .then(data => {
                if (data.cleaned !== undefined) {
                    loadUsers(currentPage);
                    loadDashboardData();
                    showNotification(`Удалено ${data.cleaned} просроченных записей`, 'success');
                } else {
                    showNotification('Ошибка при очистке', 'danger');
                }
            })
            .catch(error => {
                console.error('Ошибка очистки:', error);
                showNotification('Ошибка при очистке просроченных записей', 'danger');
            });
        }

        // Применение фильтров
        function applyFilters() {
            currentPage = 1;
            loadUsers(currentPage);
        }

        // Загрузка информации о системе
        function loadSystemInfo() {
            fetch('?action=system_info&format=json')
                .then(response => response.json())
                .then(data => {
                    document.getElementById('system-uptime').textContent = data.system?.uptime || 'N/A';
                    document.getElementById('system-cpu').textContent = data.system?.['cpu-load'] || 'N/A';
                    
                    if (data.system?.['free-memory'] && data.system?.['total-memory']) {
                        const usedPercent = Math.round(
                            ((data.system['total-memory'] - data.system['free-memory']) / data.system['total-memory']) * 100
                        );
                        document.getElementById('system-memory').textContent = `${usedPercent}%`;
                    }
                })
                .catch(error => console.error('Ошибка загрузки информации о системе:', error));
        }

        // Загрузка последних логов
        function loadRecentLogs() {
            fetch('?action=logs&format=json&lines=20')
                .then(response => response.json())
                .then(data => {
                    const logsContainer = document.getElementById('recentLogs');
                    
                    if (data.logs && data.logs.length > 0) {
                        logsContainer.innerHTML = data.logs.map(log => {
                            const logData = typeof log === 'string' ? JSON.parse(log) : log;
                            return `<div class="mb-1">
                                <small class="text-muted">[${logData.datetime || logData.timestamp}]</small>
                                <span class="badge bg-${getSeverityColor(logData.severity)}">${logData.severity}</span>
                                ${logData.message || logData.action}
                            </div>`;
                        }).join('');
                    } else {
                        logsContainer.innerHTML = '<div class="text-muted">Нет доступных логов</div>';
                    }
                })
                .catch(error => {
                    console.error('Ошибка загрузки логов:', error);
                    document.getElementById('recentLogs').innerHTML = '<div class="text-danger">Ошибка загрузки логов</div>';
                });
        }

        // Вспомогательные функции
        function formatDateTime(dateString) {
            const date = new Date(dateString);
            return date.toLocaleString('ru-RU');
        }

        function getSeverityColor(severity) {
            switch (severity?.toUpperCase()) {
                case 'ERROR':
                case 'CRITICAL': return 'danger';
                case 'WARNING': return 'warning';
                case 'INFO': return 'info';
                default: return 'secondary';
            }
        }

        function showNotification(message, type = 'info') {
            // Создаем уведомление Bootstrap
            const alertDiv = document.createElement('div');
            alertDiv.className = `alert alert-${type} alert-dismissible fade show position-fixed`;
            alertDiv.style.cssText = 'top: 20px; right: 20px; z-index: 9999; min-width: 300px;';
            alertDiv.innerHTML = `
                ${message}
                <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
            `;
            
            document.body.appendChild(alertDiv);
            
            // Автоматически удаляем через 5 секунд
            setTimeout(() => {
                if (alertDiv.parentNode) {
                    alertDiv.parentNode.removeChild(alertDiv);
                }
            }, 5000);
        }

        function debounce(func, wait) {
            let timeout;
            return function executedFunction(...args) {
                const later = () => {
                    clearTimeout(timeout);
                    func(...args);
                };
                clearTimeout(timeout);
                timeout = setTimeout(later, wait);
            };
        }

        function toggleSelectAll() {
            const selectAll = document.getElementById('selectAll');
            const checkboxes = document.querySelectorAll('.user-checkbox');
            
            checkboxes.forEach(checkbox => {
                checkbox.checked = selectAll.checked;
            });
            
            updateSelectedUsers();
        }

        function updateSelectedUsers() {
            const checkboxes = document.querySelectorAll('.user-checkbox:checked');
            selectedUsers = Array.from(checkboxes).map(cb => cb.value);
            
            const bulkActions = document.getElementById('bulkActions');
            if (selectedUsers.length > 0) {
                bulkActions.style.display = 'block';
            } else {
                bulkActions.style.display = 'none';
            }
        }

        function executeBulkAction() {
            const action = document.getElementById('bulkActionSelect').value;
            
            if (!action || selectedUsers.length === 0) {
                alert('Выберите действие и пользователей');
                return;
            }

            let confirmMessage = '';
            let extendHours = null;

            switch (action) {
                case 'remove':
                    confirmMessage = `Вы уверены, что хотите удалить ${selectedUsers.length} пользователей?`;
                    break;
                case 'extend':
                    extendHours = prompt('На сколько часов продлить доступ?', '2');
                    if (!extendHours || isNaN(extendHours) || extendHours <= 0) {
                        return;
                    }
                    confirmMessage = `Продлить доступ для ${selectedUsers.length} пользователей на ${extendHours} часов?`;
                    break;
                default:
                    alert('Неизвестное действие');
                    return;
            }

            if (!confirm(confirmMessage)) {
                return;
            }

            const formData = new FormData();
            formData.append('action', 'bulk_action');
            formData.append('bulk_action', action);
            selectedUsers.forEach(ip => {
                formData.append('selected_ips[]', ip);
            });
            
            if (extendHours) {
                formData.append('extend_hours', extendHours);
            }

            fetch('', {
                method: 'POST',
                body: formData
            })
            .then(response => response.json())
            .then(data => {
                if (data.results) {
                    let successCount = 0;
                    Object.values(data.results).forEach(result => {
                        if (result.success) successCount++;
                    });
                    
                    showNotification(`Операция выполнена для ${successCount} из ${selectedUsers.length} пользователей`, 'success');
                    
                    // Сбрасываем выбор и перезагружаем данные
                    document.getElementById('selectAll').checked = false;
                    selectedUsers = [];
                    document.getElementById('bulkActions').style.display = 'none';
                    
                    loadUsers(currentPage);
                    loadDashboardData();
                } else {
                    showNotification('Ошибка выполнения массовой операции', 'danger');
                }
            })
            .catch(error => {
                console.error('Ошибка массовой операции:', error);
                showNotification('Ошибка выполнения операции', 'danger');
            });
        }

        function renderPagination() {
            const pagination = document.getElementById('pagination');
            pagination.innerHTML = '';

            if (totalPages <= 1) return;

            // Предыдущая страница
            const prevLi = document.createElement('li');
            prevLi.className = `page-item ${currentPage === 1 ? 'disabled' : ''}`;
            prevLi.innerHTML = `<a class="page-link" href="#" onclick="loadUsers(${currentPage - 1})">Предыдущая</a>`;
            pagination.appendChild(prevLi);

            // Номера страниц
            const startPage = Math.max(1, currentPage - 2);
            const endPage = Math.min(totalPages, currentPage + 2);

            for (let i = startPage; i <= endPage; i++) {
                const li = document.createElement('li');
                li.className = `page-item ${i === currentPage ? 'active' : ''}`;
                li.innerHTML = `<a class="page-link" href="#" onclick="loadUsers(${i})">${i}</a>`;
                pagination.appendChild(li);
            }

            // Следующая страница
            const nextLi = document.createElement('li');
            nextLi.className = `page-item ${currentPage === totalPages ? 'disabled' : ''}`;
            nextLi.innerHTML = `<a class="page-link" href="#" onclick="loadUsers(${currentPage + 1})">Следующая</a>`;
            pagination.appendChild(nextLi);
        }
    </script>
</body>
</html>

6. Настройка инфраструктуры

6.1. Конфигурация NGINX для корректного получения IP

Корректная настройка NGINX критически важна для правильного определения IP адресов пользователей. При использовании NGINX как reverse proxy необходимо правильно настроить передачу заголовков и обработку реальных IP адресов.[2][3][4][5]

Основной конфигурационный файл NGINX:

# /etc/nginx/sites-available/sysadministrator.ru
server {
    listen 80;
    listen [::]:80;
    server_name sysadministrator.ru;
    
    # Принудительное перенаправление на HTTPS в продакшене
    # return 301 https://$server_name$request_uri;
    
    root /var/www/html/sysadministrator;
    index index.php index.html;
    
    # Логирование с реальными IP адресами
    log_format real_ip '$remote_addr - $remote_user [$time_local] '
                      '"$request" $status $body_bytes_sent '
                      '"$http_referer" "$http_user_agent" '
                      'rt=$request_time uct="$upstream_connect_time" '
                      'uht="$upstream_header_time" urt="$upstream_response_time" '
                      'real_ip="$http_x_real_ip" forwarded="$http_x_forwarded_for"';
    
    access_log /var/log/nginx/sysadministrator_access.log real_ip;
    error_log /var/log/nginx/sysadministrator_error.log warn;
    
    # Основная локация для PHP приложения
    location / {
        try_files $uri $uri/ /index.php?$query_string;
        
        # Базовая HTTP аутентификация
        auth_basic "Battery Service - Restricted Access";
        auth_basic_user_file /etc/nginx/.htpasswd;
        
        # Передача реального IP адреса
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Host $server_name;
        
        # Дополнительные заголовки для корректной работы приложения
        proxy_set_header Connection "";
        proxy_http_version 1.1;
    }
    
    # Обработка PHP файлов через FastCGI
    location ~ \.php$ {
        # Базовая аутентификация для PHP файлов
        auth_basic "Battery Service - Restricted Access";
        auth_basic_user_file /etc/nginx/.htpasswd;
        
        # Проверка существования файла
        try_files $uri =404;
        
        # Настройки FastCGI
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
        fastcgi_index index.php;
        
        # Передача параметров в PHP-FPM
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
        
        # Передача информации об аутентификации
        fastcgi_param REMOTE_USER $remote_user;
        fastcgi_param PHP_AUTH_USER $remote_user;
        fastcgi_param PHP_AUTH_PW $http_authorization;
        
        # Передача реальных IP адресов
        fastcgi_param HTTP_X_REAL_IP $remote_addr;
        fastcgi_param HTTP_X_FORWARDED_FOR $proxy_add_x_forwarded_for;
        fastcgi_param HTTP_X_FORWARDED_PROTO $scheme;
        
        # Стандартные FastCGI параметры
        include fastcgi_params;
        
        # Увеличенные таймауты для API операций
        fastcgi_connect_timeout 60s;
        fastcgi_send_timeout 60s;
        fastcgi_read_timeout 60s;
        
        # Буферизация ответов
        fastcgi_buffering on;
        fastcgi_buffer_size 16k;
        fastcgi_buffers 16 16k;
    }
    
    # Статические файлы (CSS, JS, изображения)
    location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
        add_header X-Content-Type-Options nosniff;
        access_log off;
    }
    
    # Безопасность - запрет доступа к служебным файлам
    location ~ /\. {
        deny all;
        access_log off;
        log_not_found off;
    }
    
    location ~ ~$ {
        deny all;
        access_log off;
        log_not_found off;
    }
    
    # Запрет доступа к логам и конфигурационным файлам
    location ~* \.(log|conf|config)$ {
        deny all;
        access_log off;
        log_not_found off;
    }
    
    # Специальная локация для API операций
    location /api/ {
        auth_basic "Battery Service API";
        auth_basic_user_file /etc/nginx/.htpasswd;
        
        # Увеличенные лимиты для API
        client_max_body_size 10M;
        client_body_timeout 60s;
        
        # Передача в PHP обработчик API
        try_files $uri $uri/ /api/index.php?$query_string;
        
        # CORS заголовки если необходимо
        add_header Access-Control-Allow-Origin "$http_origin" always;
        add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
        add_header Access-Control-Allow-Headers "Authorization, Content-Type, X-Requested-With" always;
        
        if ($request_method = OPTIONS) {
            return 204;
        }
    }
    
    # Мониторинг статуса NGINX
    location /nginx_status {
        stub_status on;
        access_log off;
        allow 127.0.0.1;
        allow 192.168.1.1;
        deny all;
    }
    
    # Проверка работоспособности приложения
    location /health {
        access_log off;
        return 200 "healthy\n";
        add_header Content-Type text/plain;
    }
}

# Конфигурация для HTTPS (в продакшене)
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name sysadministrator.ru;
    
    # SSL сертификаты
    ssl_certificate /etc/letsencrypt/live/sysadministrator.ru/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/sysadministrator.ru/privkey.pem;
    
    # Современные настройки SSL
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;
    ssl_session_tickets off;
    
    # HSTS
    add_header Strict-Transport-Security "max-age=63072000" always;
    
    # Остальная конфигурация аналогична HTTP версии
    # ... (повторить настройки из HTTP блока)
}

Дополнительная конфигурация для обработки реальных IP:

# /etc/nginx/conf.d/real_ip.conf
# Настройка модуля real_ip для корректного определения IP адресов

# Доверенные прокси (если используется CDN или балансировщик)
set_real_ip_from 127.0.0.1;
set_real_ip_from 10.0.0.0/8;
set_real_ip_from 172.16.0.0/12;
set_real_ip_from 192.168.0.0/16;

# Cloudflare IP ranges (если используется)
# set_real_ip_from 173.245.48.0/20;
# set_real_ip_from 103.21.244.0/22;
# set_real_ip_from 103.22.200.0/22;
# (добавить остальные диапазоны Cloudflare)

# Заголовок, содержащий реальный IP
real_ip_header X-Forwarded-For;

# Использовать последний IP из цепочки прокси
real_ip_recursive on;

6.2. Настройка базовой аутентификации

HTTP Basic Authentication обеспечивает первичную защиту доступа к системе. Настройка включает создание файла паролей и конфигурирование NGINX для проверки учетных данных.[6]

Создание и управление файлом паролей:

#!/bin/bash
# /usr/local/bin/manage_auth_users.sh
# Скрипт управления пользователями HTTP Basic Auth

HTPASSWD_FILE="/etc/nginx/.htpasswd"
BACKUP_DIR="/etc/nginx/backups"

# Создание резервной копии
backup_htpasswd() {
    if [ -f "$HTPASSWD_FILE" ]; then
        mkdir -p "$BACKUP_DIR"
        cp "$HTPASSWD_FILE" "${BACKUP_DIR}/.htpasswd.$(date +%Y%m%d_%H%M%S)"
        echo "Резервная копия создана: ${BACKUP_DIR}/.htpasswd.$(date +%Y%m%d_%H%M%S)"
    fi
}

# Добавление пользователя
add_user() {
    local username="$1"
    local password="$2"
    
    if [ -z "$username" ] || [ -z "$password" ]; then
        echo "Использование: add_user <username> <password>"
        return 1
    fi
    
    backup_htpasswd
    
    # Используем bcrypt для лучшей безопасности
    htpasswd -B "$HTPASSWD_FILE" "$username"
    
    if [ $? -eq 0 ]; then
        echo "Пользователь $username добавлен успешно"
        reload_nginx
    else
        echo "Ошибка добавления пользователя $username"
        return 1
    fi
}

# Удаление пользователя
remove_user() {
    local username="$1"
    
    if [ -z "$username" ]; then
        echo "Использование: remove_user <username>"
        return 1
    fi
    
    backup_htpasswd
    
    htpasswd -D "$HTPASSWD_FILE" "$username"
    
    if [ $? -eq 0 ]; then
        echo "Пользователь $username удален успешно"
        reload_nginx
    else
        echo "Ошибка удаления пользователя $username"
        return 1
    fi
}

# Изменение пароля
change_password() {
    local username="$1"
    
    if [ -z "$username" ]; then
        echo "Использование: change_password <username>"
        return 1
    fi
    
    # Проверяем существование пользователя
    if ! grep -q "^$username:" "$HTPASSWD_FILE" 2>/dev/null; then
        echo "Пользователь $username не найден"
        return 1
    fi
    
    backup_htpasswd
    
    htpasswd -B "$HTPASSWD_FILE" "$username"
    
    if [ $? -eq 0 ]; then
        echo "Пароль для пользователя $username изменен успешно"
        reload_nginx
    else
        echo "Ошибка изменения пароля для пользователя $username"
        return 1
    fi
}

# Список пользователей
list_users() {
    if [ -f "$HTPASSWD_FILE" ]; then
        echo "Текущие пользователи:"
        cut -d: -f1 "$HTPASSWD_FILE" | sort
    else
        echo "Файл паролей не найден: $HTPASSWD_FILE"
    fi
}

# Проверка пароля пользователя
check_user() {
    local username="$1"
    local password="$2"
    
    if [ -z "$username" ] || [ -z "$password" ]; then
        echo "Использование: check_user <username> <password>"
        return 1
    fi
    
    if htpasswd -v "$HTPASSWD_FILE" "$username" <<< "$password" >/dev/null 2>&1; then
        echo "Пароль для пользователя $username корректен"
        return 0
    else
        echo "Неверный пароль для пользователя $username"
        return 1
    fi
}

# Перезагрузка NGINX
reload_nginx() {
    echo "Перезагрузка конфигурации NGINX..."
    systemctl reload nginx
    
    if [ $? -eq 0 ]; then
        echo "NGINX перезагружен успешно"
    else
        echo "Ошибка перезагрузки NGINX"
        return 1
    fi
}

# Создание начального файла паролей
init_htpasswd() {
    if [ -f "$HTPASSWD_FILE" ]; then
        echo "Файл паролей уже существует: $HTPASSWD_FILE"
        return 1
    fi
    
    mkdir -p "$(dirname "$HTPASSWD_FILE")"
    touch "$HTPASSWD_FILE"
    chmod 640 "$HTPASSWD_FILE"
    chown root:www-data "$HTPASSWD_FILE"
    
    echo "Файл паролей создан: $HTPASSWD_FILE"
    
    # Добавляем администратора по умолчанию
    echo "Создание администратора по умолчанию..."
    read -p "Имя администратора: " admin_user
    add_user "$admin_user"
}

# Проверка целостности файла паролей
verify_htpasswd() {
    if [ ! -f "$HTPASSWD_FILE" ]; then
        echo "Файл паролей не найден: $HTPASSWD_FILE"
        return 1
    fi
    
    echo "Проверка файла паролей..."
    
    # Проверка прав доступа
    local file_perms=$(stat -c "%a" "$HTPASSWD_FILE")
    if [ "$file_perms" != "640" ]; then
        echo "Предупреждение: права доступа к файлу паролей должны быть 640, текущие: $file_perms"
        chmod 640 "$HTPASSWD_FILE"
    fi
    
    # Проверка владельца
    local file_owner=$(stat -c "%U:%G" "$HTPASSWD_FILE")
    if [ "$file_owner" != "root:www-data" ]; then
        echo "Предупреждение: владелец файла паролей должен быть root:www-data, текущий: $file_owner"
        chown root:www-data "$HTPASSWD_FILE"
    fi
    
    # Проверка формата записей
    local invalid_lines=0
    while IFS= read -r line; do
        if [[ ! "$line" =~ ^[a-zA-Z0-9._-]+:\$2[aby]\$ ]]; then
            echo "Предупреждение: возможно некорректная строка: $line"
            ((invalid_lines++))
        fi
    done < "$HTPASSWD_FILE"
    
    if [ $invalid_lines -eq 0 ]; then
        echo "Файл паролей корректен"
    else
        echo "Найдено $invalid_lines потенциально некорректных строк"
    fi
    
    return 0
}

# Основная функция
main() {
    case "$1" in
        "add")
            add_user "$2" "$3"
            ;;
        "remove")
            remove_user "$2"
            ;;
        "change")
            change_password "$2"
            ;;
        "list")
            list_users
            ;;
        "check")
            check_user "$2" "$3"
            ;;
        "init")
            init_htpasswd
            ;;
        "verify")
            verify_htpasswd
            ;;
        "reload")
            reload_nginx
            ;;
        *)
            echo "Использование: $0 {add|remove|change|list|check|init|verify|reload}"
            echo ""
            echo "Команды:"
            echo "  add <username> <password>  - Добавить пользователя"
            echo "  remove <username>          - Удалить пользователя"
            echo "  change <username>          - Изменить пароль пользователя"
            echo "  list                       - Показать всех пользователей"
            echo "  check <username> <password>- Проверить пароль"
            echo "  init                       - Создать файл паролей"
            echo "  verify                     - Проверить файл паролей"
            echo "  reload                     - Перезагрузить NGINX"
            exit 1
            ;;
    esac
}

main "$@"

PHP класс для интеграции с HTTP Basic Auth:

<?php
class BasicAuthManager {
    private $htpasswdFile;
    private $logger;
    private $maxAttempts = 5;
    private $lockoutTime = 1800; // 30 минут
    private $attemptsFile = '/tmp/auth_attempts.json';
    
    public function __construct($htpasswdFile = '/etc/nginx/.htpasswd') {
        $this->htpasswdFile = $htpasswdFile;
        $this->logger = new AuditLogger($config ?? []);
    }
    
    /**
     * Проверка аутентификации с защитой от брутфорса
     */
    public function checkAuthentication() {
        // Получаем IP пользователя для проверки попыток входа
        $userIP = $this->getRealUserIP();
        
        // Проверяем блокировку по IP
        if ($this->isIPBlocked($userIP)) {
            $this->sendAuthFailure('IP temporarily blocked due to too many failed attempts');
            return false;
        }
        
        // Проверяем наличие учетных данных
        if (!isset($_SERVER['PHP_AUTH_USER']) || !isset($_SERVER['PHP_AUTH_PW'])) {
            $this->sendAuthRequired();
            return false;
        }
        
        $username = $_SERVER['PHP_AUTH_USER'];
        $password = $_SERVER['PHP_AUTH_PW'];
        
        // Валидация имени пользователя
        if (!$this->validateUsername($username)) {
            $this->recordFailedAttempt($userIP, $username, 'Invalid username format');
            $this->sendAuthFailure('Invalid credentials');
            return false;
        }
        
        // Проверка пароля
        if ($this->verifyPassword($username, $password)) {
            $this->clearFailedAttempts($userIP);
            $this->logger->logUserAction($username, 'LOGIN_SUCCESS', $userIP, [
                'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'Unknown'
            ]);
            return true;
        } else {
            $this->recordFailedAttempt($userIP, $username, 'Invalid password');
            $this->logger->logSecurityEvent('LOGIN_FAILED', [
                'username' => $username,
                'ip' => $userIP,
                'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'Unknown'
            ], 'WARNING');
            
            $this->sendAuthFailure('Invalid credentials');
            return false;
        }
    }
    
    /**
     * Проверка пароля пользователя
     */
    private function verifyPassword($username, $password) {
        if (!file_exists($this->htpasswdFile)) {
            return false;
        }
        
        $lines = file($this->htpasswdFile, FILE_IGNORE_NEW_LINES);
        
        foreach ($lines as $line) {
            if (strpos($line, ':') === false) {
                continue;
            }
            
            list($user, $hash) = explode(':', $line, 2);
            
            if ($user === $username) {
                return $this->verifyHash($password, $hash);
            }
        }
        
        return false;
    }
    
    /**
     * Проверка хеша пароля
     */
    private function verifyHash($password, $hash) {
        // Bcrypt hash (предпочтительный)
        if (substr($hash, 0, 4) === '$2y$' || substr($hash, 0, 4) === '$2a$' || substr($hash, 0, 4) === '$2b$') {
            return password_verify($password, $hash);
        }
        
        // MD5 hash (устаревший, но поддерживается для совместимости)
        if (substr($hash, 0, 6) === '$apr1$') {
            return $this->verifyApacheMD5($password, $hash);
        }
        
        // SHA hash
        if (substr($hash, 0, 5) === '{SHA}') {
            return base64_encode(sha1($password, true)) === substr($hash, 5);
        }
        
        // Plain text (крайне не рекомендуется)
        return $password === $hash;
    }
    
    /**
     * Проверка Apache MD5 хеша
     */
    private function verifyApacheMD5($password, $hash) {
        // Упрощенная реализация Apache MD5
        // В продакшене рекомендуется использовать специализированную библиотеку
        $parts = explode('$', $hash);
        if (count($parts) !== 4 || $parts[7] !== 'apr1') {
            return false;
        }
        
        $salt = $parts[8];
        $expectedHash = crypt($password, '$apr1$' . $salt . '$');
        
        return $expectedHash === $hash;
    }
    
    /**
     * Валидация имени пользователя
     */
    private function validateUsername($username) {
        // Проверка длины
        if (strlen($username) < 3 || strlen($username) > 32) {
            return false;
        }
        
        // Проверка допустимых символов
        if (!preg_match('/^[a-zA-Z0-9._-]+$/', $username)) {
            return false;
        }
        
        return true;
    }
    
    /**
     * Проверка блокировки IP
     */
    private function isIPBlocked($ip) {
        if (!file_exists($this->attemptsFile)) {
            return false;
        }
        
        $attempts = json_decode(file_get_contents($this->attemptsFile), true) ?: [];
        
        if (!isset($attempts[$ip])) {
            return false;
        }
        
        $ipAttempts = $attempts[$ip];
        
        // Проверяем количество попыток за последний час
        $recentAttempts = array_filter($ipAttempts, function($attempt) {
            return $attempt['timestamp'] > time() - 3600;
        });
        
        if (count($recentAttempts) >= $this->maxAttempts) {
            // Проверяем время блокировки
            $lastAttempt = max(array_column($recentAttempts, 'timestamp'));
            return (time() - $lastAttempt) < $this->lockoutTime;
        }
        
        return false;
    }
    
    /**
     * Запись неудачной попытки входа
     */
    private function recordFailedAttempt($ip, $username, $reason) {
        $attempts = [];
        
        if (file_exists($this->attemptsFile)) {
            $attempts = json_decode(file_get_contents($this->attemptsFile), true) ?: [];
        }
        
        if (!isset($attempts[$ip])) {
            $attempts[$ip] = [];
        }
        
        $attempts[$ip][] = [
            'timestamp' => time(),
            'username' => $username,
            'reason' => $reason,
            'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'Unknown'
        ];
        
        // Оставляем только последние 50 попыток для каждого IP
        if (count($attempts[$ip]) > 50) {
            $attempts[$ip] = array_slice($attempts[$ip], -50);
        }
        
        // Очищаем старые записи (старше 24 часов)
        $cutoff = time() - 86400;
        foreach ($attempts as $attemptIP => $ipAttempts) {
            $attempts[$attemptIP] = array_filter($ipAttempts, function($attempt) use ($cutoff) {
                return $attempt['timestamp'] > $cutoff;
            });
            
            // Удаляем IP без попыток
            if (empty($attempts[$attemptIP])) {
                unset($attempts[$attemptIP]);
            }
        }
        
        file_put_contents($this->attemptsFile, json_encode($attempts, JSON_PRETTY_PRINT));
    }
    
    /**
     * Очистка записей о неудачных попытках для IP
     */
    private function clearFailedAttempts($ip) {
        if (!file_exists($this->attemptsFile)) {
            return;
        }
        
        $attempts = json_decode(file_get_contents($this->attemptsFile), true) ?: [];
        
        if (isset($attempts[$ip])) {
            unset($attempts[$ip]);
            file_put_contents($this->attemptsFile, json_encode($attempts, JSON_PRETTY_PRINT));
        }
    }
    
    /**
     * Отправка требования аутентификации
     */
    private function sendAuthRequired() {
        header('WWW-Authenticate: Basic realm="Battery Service - Restricted Access"');
        http_response_code(401);
        
        $this->logger->logSecurityEvent('AUTH_REQUIRED', [
            'ip' => $this->getRealUserIP(),
            'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'Unknown'
        ]);
        
        exit('Authentication required');
    }
    
    /**
     * Отправка сообщения об ошибке аутентификации
     */
    private function sendAuthFailure($message) {
        header('WWW-Authenticate: Basic realm="Battery Service - Restricted Access"');
        http_response_code(401);
        
        exit($message);
    }
    
    /**
     * Получение реального IP пользователя
     */
    private function getRealUserIP() {
        $ipKeys = [
            'HTTP_CF_CONNECTING_IP',
            'HTTP_CLIENT_IP',
            'HTTP_X_FORWARDED_FOR',
            'HTTP_X_REAL_IP',
            'REMOTE_ADDR'
        ];
        
        foreach ($ipKeys as $key) {
            if (!empty($_SERVER[$key])) {
                $ip = trim(explode(',', $_SERVER[$key]));
                if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
                    return $ip;
                }
            }
        }
        
        return $_SERVER['REMOTE_ADDR'] ?? 'unknown';
    }
    
    /**
     * Получение статистики попыток входа
     */
    public function getAuthStatistics($hours = 24) {
        if (!file_exists($this->attemptsFile)) {
            return [
                'total_attempts' => 0,
                'unique_ips' => 0,
                'blocked_ips' => 0,
                'top_usernames' => [],
                'hourly_distribution' => []
            ];
        }
        
        $attempts = json_decode(file_get_contents($this->attemptsFile), true) ?: [];
        $cutoff = time() - ($hours * 3600);
        
        $stats = [
            'total_attempts' => 0,
            'unique_ips' => 0,
            'blocked_ips' => 0,
            'top_usernames' => [],
            'hourly_distribution' => array_fill(0, 24, 0)
        ];
        
        $usernames = [];
        $uniqueIPs = [];
        
        foreach ($attempts as $ip => $ipAttempts) {
            $recentAttempts = array_filter($ipAttempts, function($attempt) use ($cutoff) {
                return $attempt['timestamp'] > $cutoff;
            });
            
            if (!empty($recentAttempts)) {
                $uniqueIPs[] = $ip;
                $stats['total_attempts'] += count($recentAttempts);
                
                // Проверяем блокировку IP
                if ($this->isIPBlocked($ip)) {
                    $stats['blocked_ips']++;
                }
                
                // Собираем статистику по именам пользователей
                foreach ($recentAttempts as $attempt) {
                    $username = $attempt['username'];
                    if (!isset($usernames[$username])) {
                        $usernames[$username] = 0;
                    }
                    $usernames[$username]++;
                    
                    // Распределение по часам
                    $hour = date('G', $attempt['timestamp']);
                    $stats['hourly_distribution'][$hour]++;
                }
            }
        }
        
        $stats['unique_ips'] = count($uniqueIPs);
        
        // Топ имен пользователей
        arsort($usernames);
        $stats['top_usernames'] = array_slice($usernames, 0, 10, true);
        
        return $stats;
    }
}
?>

6.3. Оптимизация PHP-FPM 8.3

Настройка PHP-FPM для оптимальной производительности при работе с MikroTik API требует тщательной настройки пулов процессов и системных параметров:[7]

Основная конфигурация PHP-FPM:

; /etc/php/8.3/fpm/pool.d/sysadministrator.conf
[sysadministrator]

; Unix сокет для лучшей производительности
listen = /run/php/php8.3-fpm-sysadministrator.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

; Пользователь и группа процессов
user = www-data
group = www-data

; Настройка процессов
pm = dynamic
pm.max_children = 20
pm.start_servers = 4
pm.min_spare_servers = 2
pm.max_spare_servers = 8
pm.max_requests = 1000

; Process idle timeout
pm.process_idle_timeout = 60s

; Мониторинг
pm.status_path = /fpm-status
ping.path = /fpm-ping

; Логирование
access.log = /var/log/php8.3-fpm/sysadministrator-access.log
access.format = "%R - %u %t \"%m %r%Q%q\" %s %f %{mili}d %{kilo}M %C%%"

; Медленные запросы
slowlog = /var/log/php8.3-fpm/sysadministrator-slow.log
request_slowlog_timeout = 10s

; Аварийный перезапуск
request_terminate_timeout = 60s

; Переменные окружения
env[HOSTNAME] = $HOSTNAME
env[PATH] = /usr/local/bin:/usr/bin:/bin
env[TMP] = /tmp
env[TMPDIR] = /tmp
env[TEMP] = /tmp

; Конфигурация для MikroTik API
env[MIKROTIK_HOST] = 192.168.1.1
env[MIKROTIK_API_USER] = api_user
env[MIKROTIK_API_PORT] = 8729

; Лимиты памяти и времени выполнения
php_admin_value[memory_limit] = 256M
php_admin_value[max_execution_time] = 60
php_admin_value[max_input_time] = 60

; Настройки сессий
php_admin_value[session.gc_probability] = 1
php_admin_value[session.gc_divisor] = 1000
php_admin_value[session.gc_maxlifetime] = 3600

; Настройки загрузки файлов
php_admin_value[upload_max_filesize] = 10M
php_admin_value[post_max_size] = 10M
php_admin_value[max_file_uploads] = 20

; Настройки ошибок
php_admin_value[log_errors] = on
php_admin_value[error_log] = /var/log/php8.3-fpm/sysadministrator-error.log
php_admin_flag[display_errors] = off

; Настройки OPcache
php_admin_value[opcache.enable] = 1
php_admin_value[opcache.memory_consumption] = 128
php_admin_value[opcache.interned_strings_buffer] = 16
php_admin_value[opcache.max_accelerated_files] = 4000
php_admin_value[opcache.revalidate_freq] = 60
php_admin_value[opcache.fast_shutdown] = 1

; Дополнительные расширения
php_admin_value[extension] = curl
php_admin_value[extension] = json
php_admin_value[extension] = mbstring
php_admin_value[extension] = openssl
php_admin_value[extension] = pdo
php_admin_value[extension] = pdo_sqlite
php_admin_value[extension] = ssh2

Дополнительная настройка PHP.ini:

; /etc/php/8.3/fpm/conf.d/99-sysadministrator.ini

; Основные настройки
max_execution_time = 60
max_input_time = 60
memory_limit = 256M

; Настройки POST и загрузки файлов
post_max_size = 10M
upload_max_filesize = 10M
max_file_uploads = 20

; Настройки сессий
session.save_handler = files
session.save_path = "/var/lib/php/sessions"
session.use_cookies = 1
session.use_only_cookies = 1
session.cookie_lifetime = 0
session.cookie_secure = 0  ; Установить в 1 для HTTPS
session.cookie_httponly = 1
session.cookie_samesite = "Strict"
session.gc_probability = 1
session.gc_divisor = 1000
session.gc_maxlifetime = 3600

; Настройки безопасности
expose_php = Off
allow_url_fopen = Off
allow_url_include = Off

; Отключение опасных функций
disable_functions = exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source

; Настройки логирования
log_errors = On
error_log = /var/log/php8.3-fpm/php_errors.log
log_errors_max_len = 1024

; Настройки cURL для MikroTik API
curl.cainfo = "/etc/ssl/certs/ca-certificates.crt"

; Настройки часового пояса
date.timezone = "Europe/Moscow"

; Настройки mbstring для корректной работы с русским текстом
mbstring.language = Russian
mbstring.internal_encoding = UTF-8
mbstring.http_output = UTF-8

; Настройки OPcache
opcache.enable = 1
opcache.enable_cli = 0
opcache.memory_consumption = 128
opcache.interned_strings_buffer = 16
opcache.max_accelerated_files = 4000
opcache.max_wasted_percentage = 5
opcache.use_cwd = 1
opcache.validate_timestamps = 1
opcache.revalidate_freq = 60
opcache.revalidate_path = 0
opcache.save_comments = 1
opcache.fast_shutdown = 1
opcache.enable_file_override = 0
opcache.optimization_level = 0x7FFFBFFF
opcache.inherited_hack = 1
opcache.dups_fix = 0
opcache.blacklist_filename = /etc/php/8.3/fpm/opcache-blacklist.txt

; Настройки Realpath Cache
realpath_cache_size = 4096K
realpath_cache_ttl = 600

; JIT компиляция (PHP 8+)
opcache.jit_buffer_size = 64M
opcache.jit = 1235

Скрипт мониторинга PHP-FPM:

#!/bin/bash
# /usr/local/bin/monitor_php_fpm.sh
# Скрипт мониторинга состояния PHP-FPM

LOG_FILE="/var/log/php-fpm-monitor.log"
STATUS_URL="http://localhost/fpm-status"
PING_URL="http://localhost/fmp-ping"
ALERT_EMAIL="admin@sysadministrator.ru"

# Цвета для вывода
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color

log_message() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG_FILE"
    echo -e "$1"
}

check_php_fpm_status() {
    local status_response
    status_response=$(curl -s -w "%{http_code}" "$STATUS_URL")
    local http_code="${status_response: -3}"
    local response_body="${status_response%???}"
    
    if [ "$http_code" -eq 200 ]; then
        log_message "${GREEN}[OK]${NC} PHP-FPM status endpoint accessible"
        
        # Парсим статистику
        local active_processes=$(echo "$response_body" | grep "active processes" | awk '{print $3}')
        local idle_processes=$(echo "$response_body" | grep "idle processes" | awk '{print $3}')
        local total_processes=$(echo "$response_body" | grep "total processes" | awk '{print $3}')
        local max_children_reached=$(echo "$response_body" | grep "max children reached" | awk '{print $4}')
        
        log_message "Active: $active_processes, Idle: $idle_processes, Total: $total_processes, Max reached: $max_children_reached"
        
        # Проверяем критические показатели
        if [ "$max_children_reached" -gt 0 ]; then
            log_message "${YELLOW}[WARNING]${NC} Max children reached $max_children_reached times"
            send_alert "PHP-FPM Warning" "Max children limit reached $max_children_reached times"
        fi
        
        if [ "$active_processes" -gt 15 ]; then
            log_message "${YELLOW}[WARNING]${NC} High number of active processes: $active_processes"
        fi
        
        return 0
    else
        log_message "${RED}[ERROR]${NC} PHP-FPM status check failed. HTTP code: $http_code"
        return 1
    fi
}

check_php_fpm_ping() {
    local ping_response
    ping_response=$(curl -s -w "%{http_code}" "$PING_URL")
    local http_code="${ping_response: -3}"
    
    if [ "$http_code" -eq 200 ] && [[ "$ping_response" == *"pong"* ]]; then
        log_message "${GREEN}[OK]${NC} PHP-FPM ping successful"
        return 0
    else
        log_message "${RED}[ERROR]${NC} PHP-FPM ping failed. HTTP code: $http_code"
        return 1
    fi
}

check_php_fpm_processes() {
    local php_fpm_processes
    php_fpm_processes=$(pgrep -f "php-fpm.*sysadministrator" | wc -l)
    
    if [ "$php_fpm_processes" -gt 0 ]; then
        log_message "${GREEN}[OK]${NC} PHP-FPM processes running: $php_fpm_processes"
        return 0
    else
        log_message "${RED}[ERROR]${NC} No PHP-FPM processes found!"
        return 1
    fi
}

check_php_errors() {
    local error_log="/var/log/php8.3-fpm/sysadministrator-error.log"
    local slow_log="/var/log/php8.3-fpm/sysadministrator-slow.log"
    
    if [ -f "$error_log" ]; then
        local recent_errors
        recent_errors=$(tail -100 "$error_log" | grep "$(date '+%d-%b-%Y')" | wc -l)
        
        if [ "$recent_errors" -gt 10 ]; then
            log_message "${YELLOW}[WARNING]${NC} High number of PHP errors today: $recent_errors"
            send_alert "PHP Errors Alert" "Found $recent_errors PHP errors today"
        else
            log_message "${GREEN}[OK]${NC} PHP errors today: $recent_errors"
        fi
    fi
    
    if [ -f "$slow_log" ]; then
        local slow_requests
        slow_requests=$(tail -100 "$slow_log" | grep "$(date '+%Y-%m-%d')" | wc -l)
        
        if [ "$slow_requests" -gt 5 ]; then
            log_message "${YELLOW}[WARNING]${NC} Slow PHP requests today: $slow_requests"
        else
            log_message "${GREEN}[OK]${NC} Slow requests today: $slow_requests"
        fi
    fi
}

check_system_resources() {
    local memory_usage
    local cpu_load
    
    memory_usage=$(free | grep Mem | awk '{printf "%.1f", $3/$2 * 100.0}')
    cpu_load=$(uptime | awk -F'load average:' '{print $2}' | awk '{print $1}' | sed 's/,//')
    
    log_message "System resources - Memory: ${memory_usage}%, CPU Load: ${cpu_load}"
    
    if (( $(echo "$memory_usage > 90" | bc -l) )); then
        log_message "${RED}[ERROR]${NC} High memory usage: ${memory_usage}%"
        send_alert "High Memory Usage" "Memory usage is ${memory_usage}%"
    fi
    
    if (( $(echo "$cpu_load > 5.0" | bc -l) )); then
        log_message "${YELLOW}[WARNING]${NC} High CPU load: ${cpu_load}"
    fi
}

send_alert() {
    local subject="$1"
    local message="$2"
    
    if command -v mail >/dev/null 2>&1; then
        echo "$message" | mail -s "$subject" "$ALERT_EMAIL"
        log_message "Alert sent: $subject"
    else
        log_message "Cannot send alert - mail command not available"
    fi
}

restart_php_fpm() {
    log_message "${YELLOW}[ACTION]${NC} Restarting PHP-FPM service"
    
    systemctl restart php8.3-fpm
    
    if [ $? -eq 0 ]; then
        log_message "${GREEN}[OK]${NC} PHP-FPM restarted successfully"
        send_alert "PHP-FPM Restarted" "PHP-FPM service was automatically restarted due to health check failure"
    else
        log_message "${RED}[ERROR]${NC} Failed to restart PHP-FPM"
        send_alert "PHP-FPM Restart Failed" "Failed to automatically restart PHP-FPM service"
    fi
}

generate_report() {
    local report_file="/tmp/php-fpm-report-$(date +%Y%m%d_%H%M).txt"
    
    {
        echo "PHP-FPM Health Report - $(date)"
        echo "=================================="
        echo ""
        
        echo "Service Status:"
        systemctl status php8.3-fpm --no-pager
        echo ""
        
        echo "Process List:"
        ps aux | grep php-fpm | grep -v grep
        echo ""
        
        echo "Memory Usage:"
        ps aux | grep php-fpm | grep -v grep | awk '{sum+=$6} END {print "Total RSS: " sum/1024 " MB"}'
        echo ""
        
        echo "Recent Error Log (last 20 lines):"
        if [ -f "/var/log/php8.3-fpm/sysadministrator-error.log" ]; then
            tail -20 /var/log/php8.3-fpm/sysadministrator-error.log
        else
            echo "No error log found"
        fi
        echo ""
        
        echo "Pool Configuration:"
        cat /etc/php/8.3/fpm/pool.d/sysadministrator.conf
        
    } > "$report_file"
    
    log_message "Report generated: $report_file"
}

main() {
    case "$1" in
        "status")
            check_php_fpm_status
            ;;
        "ping") 
            check_php_fpm_ping
            ;;
        "processes")
            check_php_fpm_processes
            ;;
        "errors")
            check_php_errors
            ;;
        "resources")
            check_system_resources
            ;;
        "restart")
            restart_php_fpm
            ;;
        "report")
            generate_report
            ;;
        "full"|"")
            log_message "Starting full PHP-FPM health check"
            
            local status_ok=true
            
            check_php_fpm_processes || status_ok=false
            check_php_fpm_ping || status_ok=false
            check_php_fpm_status || status_ok=false
            check_php_errors
            check_system_resources
            
            if [ "$status_ok" = false ]; then
                log_message "${RED}[CRITICAL]${NC} PHP-FPM health check failed"
                
                if [ "$2" = "--auto-restart" ]; then
                    restart_php_fpm
                fi
                
                exit 1
            else
                log_message "${GREEN}[OK]${NC} All PHP-FPM health checks passed"
                exit 0
            fi
            ;;
        *)
            echo "Usage: $0 {status|ping|processes|errors|resources|restart|report|full}"
            echo ""
            echo "Commands:"
            echo "  status     - Check PHP-FPM status page"
            echo "  ping       - Check PHP-FPM ping"
            echo "  processes  - Check running processes"
            echo "  errors     - Check error logs"
            echo "  resources  - Check system resources"
            echo "  restart    - Restart PHP-FPM service"
            echo "  report     - Generate detailed report"
            echo "  full       - Run all checks (default)"
            echo ""
            echo "Options:"
            echo "  --auto-restart - Automatically restart on failure"
            exit 1
            ;;
    esac
}

main "$@"

6.4. Настройка логирования

Централизованная система логирования обеспечивает единообразный сбор и анализ логов всех компонентов системы:[8][9]

Конфигурация rsyslog:

# /etc/rsyslog.d/50-sysadministrator.conf
# Настройка логирования для Battery Service

# Создание отдельного лог-файла для приложения
:programname, isequal, "sysadministrator" /var/log/sysadministrator/application.log
& stop

# Логирование аутентификации
:programname, isequal, "sysadministrator-auth" /var/log/sysadministrator/auth.log
& stop

# Логирование API операций
:programname, isequal, "sysadministrator-api" /var/log/sysadministrator/api.log
& stop

# Логирование безопасности
:programname, isequal, "sysadministrator-security" /var/log/sysadministrator/security.log
& stop

# Логирование системных событий
:programname, isequal, "sysadministrator-system" /var/log/sysadministrator/system.log
& stop

# Настройка удаленного логирования (опционально)
# *.* @@logserver.sysadministrator.ru:514

# Настройка размера и ротации логов
$WorkDirectory /var/spool/rsyslog
$ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat
$ActionFileEnableSync on
$IncludeConfig /etc/rsyslog.d/sysadministrator/*.conf

Настройка logrotate:

# /etc/logrotate.d/sysadministrator
/var/log/sysadministrator/*.log {
    daily
    missingok
    rotate 30
    compress
    delaycompress
    notifempty
    create 644 www-data www-data
    
    postrotate
        # Перезапускаем rsyslog после ротации
        systemctl reload rsyslog
        
        # Уведомляем приложение о ротации логов
        if [ -f /run/sysadministrator.pid ]; then
            kill -USR1 $(cat /run/sysadministrator.pid)
        fi
    endscript
}

/var/log/nginx/sysadministrator*.log {
    daily
    missingok
    rotate 52
    compress
    delaycompress
    notifempty
    create 644 www-data www-data
    sharedscripts
    
    prerotate
        if [ -d /etc/logrotate.d/httpd-prerotate ]; then
            run-parts /etc/logrotate.d/httpd-prerotate
        fi
    endscript
    
    postrotate
        systemctl reload nginx
    endscript
}

/var/log/php8.3-fpm/sysadministrator*.log {
    daily
    missingok
    rotate 30
    compress
    delaycompress
    notifempty
    create 644 www-data www-data
    
    postrotate
        systemctl reload php8.3-fpm
    endscript
}

/var/log/mikrotik/*.log {
    daily
    missingok
    rotate 90
    compress
    delaycompress
    notifempty
    create 644 www-data www-data
    
    postrotate
        # Отправка уведомления о ротации в Telegram/Email
        /usr/local/bin/send_log_rotation_notification.sh
    endscript
}

PHP класс для интеграции с системным логированием:

<?php
class SystemLogger {
    private $facility;
    private $ident;
    private $options;
    private $minLevel;
    
    const LEVELS = [
        'EMERGENCY' => LOG_EMERG,
        'ALERT' => LOG_ALERT,
        'CRITICAL' => LOG_CRIT,
        'ERROR' => LOG_ERR,
        'WARNING' => LOG_WARNING,
        'NOTICE' => LOG_NOTICE,
        'INFO' => LOG_INFO,
        'DEBUG' => LOG_DEBUG
    ];
    
    public function __construct($ident = 'sysadministrator', $facility = LOG_LOCAL0, $minLevel = 'INFO') {
        $this->ident = $ident;
        $this->facility = $facility;
        $this->minLevel = $minLevel;
        $this->options = LOG_PID | LOG_PERROR;
        
        openlog($this->ident, $this->options, $this->facility);
    }
    
    /**
     * Универсальный метод логирования
     */
    public function log($level, $message, $context = []) {
        if (!isset(self::LEVELS[$level]) || self::LEVELS[$level] > self::LEVELS[$this->minLevel]) {
            return;
        }
        
        $priority = self::LEVELS[$level];
        $contextStr = empty($context) ? '' : ' | Context: ' . json_encode($context, JSON_UNESCAPED_UNICODE);
        $fullMessage = $message . $contextStr;
        
        // Логирование в syslog
        syslog($priority, $fullMessage);
        
        // Дублирование в файл для критичных сообщений
        if ($priority <= LOG_WARNING) {
            $this->writeToFile($level, $fullMessage);
        }
        
        // Отправка уведомлений для критичных ошибок
        if ($priority <= LOG_CRIT) {
            $this->sendAlert($level, $message, $context);
        }
    }
    
    /**
     * Методы для различных уровней логирования
     */
    public function emergency($message, $context = []) {
        $this->log('EMERGENCY', $message, $context);
    }
    
    public function alert($message, $context = []) {
        $this->log('ALERT', $message, $context);
    }
    
    public function critical($message, $context = []) {
        $this->log('CRITICAL', $message, $context);
    }
    
    public function error($message, $context = []) {
        $this->log('ERROR', $message, $context);
    }
    
    public function warning($message, $context = []) {
        $this->log('WARNING', $message, $context);
    }
    
    public function notice($message, $context = []) {
        $this->log('NOTICE', $message, $context);
    }
    
    public function info($message, $context = []) {
        $this->log('INFO', $message, $context);
    }
    
    public function debug($message, $context = []) {
        $this->log('DEBUG', $message, $context);
    }
    
    /**
     * Специализированные методы логирования
     */
    public function logUserAction($username, $action, $ip, $details = []) {
        $this->info("User action: {$action}", [
            'username' => $username,
            'ip' => $ip,
            'details' => $details,
            'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'Unknown'
        ]);
    }
    
    public function logAPICall($endpoint, $method, $responseCode, $executionTime, $details = []) {
        $this->info("API call: {$method} {$endpoint}", [
            'response_code' => $responseCode,
            'execution_time' => $executionTime,
            'details' => $details,
            'ip' => $this->getRealUserIP()
        ]);
    }
    
    public function logSecurityEvent($event, $severity = 'WARNING', $details = []) {
        $this->log($severity, "Security event: {$event}", array_merge($details, [
            'ip' => $this->getRealUserIP(),
            'timestamp' => time(),
            'request_uri' => $_SERVER['REQUEST_URI'] ?? 'N/A'
        ]));
    }
    
    public function logSystemEvent($event, $details = []) {
        $this->info("System event: {$event}", $details);
    }
    
    /**
     * Запись в отдельный файл для критичных событий
     */
    private function writeToFile($level, $message) {
        $logFile = "/var/log/sysadministrator/critical.log";
        $timestamp = date('Y-m-d H:i:s');
        $logLine = "[{$timestamp}] [{$level}] {$message}\n";
        
        file_put_contents($logFile, $logLine, FILE_APPEND | LOCK_EX);
    }
    
    /**
     * Отправка алертов для критичных событий
     */
    private function sendAlert($level, $message, $context) {
        // Здесь может быть интеграция с системами уведомлений
        // Telegram, Email, Slack, PagerDuty и т.д.
        
        $alertData = [
            'level' => $level,
            'message' => $message,
            'context' => $context,
            'server' => gethostname(),
            'timestamp' => date('Y-m-d H:i:s'),
            'ip' => $this->getRealUserIP()
        ];
        
        // Пример отправки в Telegram
        $this->sendTelegramAlert($alertData);
        
        // Пример отправки по email
        $this->sendEmailAlert($alertData);
    }
    
    /**
     * Отправка уведомления в Telegram
     */
    private function sendTelegramAlert($alertData) {
        $botToken = ''; // Токен бота
        $chatId = ''; // ID чата
        
        if (empty($botToken) || empty($chatId)) {
            return;
        }
        
        $message = "🚨 *Alert: {$alertData['level']}*\n\n";
        $message .= "Server: `{$alertData['server']}`\n";
        $message .= "Time: `{$alertData['timestamp']}`\n";
        $message .= "IP: `{$alertData['ip']}`\n\n";
        $message .= "Message: {$alertData['message']}\n\n";
        
        if (!empty($alertData['context'])) {
            $message .= "Context: `" . json_encode($alertData['context'], JSON_UNESCAPED_UNICODE) . "`";
        }
        
        $url = "https://api.telegram.org/bot{$botToken}/sendMessage";
        
        $postData = [
            'chat_id' => $chatId,
            'text' => $message,
            'parse_mode' => 'Markdown'
        ];
        
        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_URL => $url,
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => http_build_query($postData),
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT => 10
        ]);
        
        curl_exec($ch);
        curl_close($ch);
    }
    
    /**
     * Отправка уведомления по email
     */
    private function sendEmailAlert($alertData) {
        $to = 'admin@sysadministrator.ru';
        $subject = "[Alert] {$alertData['level']} - {$alertData['server']}";
        
        $body = "Alert Details:\n\n";
        $body .= "Level: {$alertData['level']}\n";
        $body .= "Server: {$alertData['server']}\n";
        $body .= "Timestamp: {$alertData['timestamp']}\n";
        $body .= "IP: {$alertData['ip']}\n\n";
        $body .= "Message: {$alertData['message']}\n\n";
        
        if (!empty($alertData['context'])) {
            $body .= "Context:\n" . json_encode($alertData['context'], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
        }
        
        $headers = [
            'From: system@sysadministrator.ru',
            'Reply-To: noreply@sysadministrator.ru',
            'X-Mailer: Battery Service Alert System',
            'Content-Type: text/plain; charset=UTF-8'
        ];
        
        mail($to, $subject, $body, implode("\r\n", $headers));
    }
    
    /**
     * Анализ логов за период
     */
    public function analyzeLogs($hours = 24) {
        $logFiles = [
            '/var/log/sysadministrator/application.log',
            '/var/log/sysadministrator/auth.log',
            '/var/log/sysadministrator/api.log',
            '/var/log/sysadministrator/security.log'
        ];
        
        $analysis = [
            'total_entries' => 0,
            'by_level' => [],
            'top_messages' => [],
            'error_patterns' => [],
            'time_distribution' => array_fill(0, 24, 0)
        ];
        
        $cutoffTime = time() - ($hours * 3600);
        
        foreach ($logFiles as $logFile) {
            if (!file_exists($logFile)) {
                continue;
            }
            
            $handle = fopen($logFile, 'r');
            if (!$handle) {
                continue;
            }
            
            while (($line = fgets($handle)) !== false) {
                if (preg_match('/(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}).*\[(\w+)\](.*)/', $line, $matches)) {
                    $timestamp = strtotime($matches[7]);
                    
                    if ($timestamp > $cutoffTime) {
                        $analysis['total_entries']++;
                        
                        $level = $matches[8];
                        if (!isset($analysis['by_level'][$level])) {
                            $analysis['by_level'][$level] = 0;
                        }
                        $analysis['by_level'][$level]++;
                        
                        $hour = date('G', $timestamp);
                        $analysis['time_distribution'][$hour]++;
                        
                        // Анализ часто встречающихся сообщений
                        $message = trim($matches[12]);
                        if (!isset($analysis['top_messages'][$message])) {
                            $analysis['top_messages'][$message] = 0;
                        }
                        $analysis['top_messages'][$message]++;
                    }
                }
            }
            
            fclose($handle);
        }
        
        // Сортируем топ сообщений
        arsort($analysis['top_messages']);
        $analysis['top_messages'] = array_slice($analysis['top_messages'], 0, 10, true);
        
        return $analysis;
    }
    
    /**
     * Получение реального IP пользователя
     */
    private function getRealUserIP() {
        $ipKeys = [
            'HTTP_CF_CONNECTING_IP',
            'HTTP_CLIENT_IP',
            'HTTP_X_FORWARDED_FOR',
            'HTTP_X_REAL_IP',
            'REMOTE_ADDR'
        ];
        
        foreach ($ipKeys as $key) {
            if (!empty($_SERVER[$key])) {
                $ip = trim(explode(',', $_SERVER[$key]));
                if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
                    return $ip;
                }
            }
        }
        
        return $_SERVER['REMOTE_ADDR'] ?? 'unknown';
    }
    
    public function __destruct() {
        closelog();
    }
}
?>

7. Настройка MikroTik

7.1. Создание Address Lists и Firewall правил

Address Lists в MikroTik представляют собой именованные списки IP адресов или подсетей, которые используются в правилах firewall для эффективного управления доступом. Правильная организация списков критически важна для производительности и безопасности системы.[10][11]

Базовая настройка Address Lists через терминал MikroTik:

# Создание основного белого списка для веб-пользователей
/ip firewall address-list
add list=web_users_whitelist comment="Main whitelist for web authenticated users"

# Создание дополнительных списков для различных целей
add list=admin_ips comment="Administrative IP addresses"
add list=trusted_networks comment="Trusted network ranges" 
add list=blacklist comment="Blocked IP addresses"
add list=temporary_access comment="Temporary access list"

# Добавление статических административных IP
add list=admin_ips address=192.168.1.1 comment="Main server IP"
add list=admin_ips address=192.168.1.0/24 comment="Internal network"

# Добавление доверенных сетей
add list=trusted_networks address=10.0.0.0/8 comment="Private network A"
add list=trusted_networks address=172.16.0.0/12 comment="Private network B"
add list=trusted_networks address=192.168.0.0/16 comment="Private network C"

# Создание списка для блокировки известных вредоносных IP
add list=blacklist address=0.0.0.0/8 comment="Invalid range"
add list=blacklist address=127.0.0.0/8 comment="Loopback range" 
add list=blacklist address=169.254.0.0/16 comment="Link-local range"
add list=blacklist address=224.0.0.0/4 comment="Multicast range"
add list=blacklist address=240.0.0.0/4 comment="Reserved range"

Создание правил Firewall для обработки портов доступа:

# Правила для порта RDP (3389)
/ip firewall filter

# Разрешение доступа к RDP для белого списка
add chain=input action=accept protocol=tcp dst-port=3389 \
    src-address-list=web_users_whitelist \
    comment="Allow RDP for whitelisted web users" \
    log=no

# Разрешение доступа к RDP для администраторов
add chain=input action=accept protocol=tcp dst-port=3389 \
    src-address-list=admin_ips \
    comment="Allow RDP for administrators" \
    log=no

# Логирование и блокировка несанкционированных попыток RDP
add chain=input action=add-src-to-address-list protocol=tcp dst-port=3389 \
    address-list=rdp_attackers address-list-timeout=1h \
    comment="Track RDP brute force attempts" \
    log=yes log-prefix="RDP-ATTACK"

add chain=input action=drop protocol=tcp dst-port=3389 \
    src-address-list=rdp_attackers \
    comment="Drop RDP brute force attackers" \
    log=no

# Правила для специальных портов (44444, 55555, 55554)
add chain=input action=accept protocol=tcp dst-port=44444 \
    src-address-list=web_users_whitelist \
    comment="Allow port 44444 for whitelisted users" \
    log=no

add chain=input action=accept protocol=tcp dst-port=55555 \
    src-address-list=web_users_whitelist \
    comment="Allow port 55555 for whitelisted users" \
    log=no

add chain=input action=accept protocol=tcp dst-port=55554 \
    src-address-list=web_users_whitelist \
    comment="Allow port 55554 for whitelisted users" \
    log=no

# Блокировка всех остальных попыток доступа к специальным портам
add chain=input action=drop protocol=tcp dst-port=44444,55555,55554 \
    comment="Block unauthorized access to special ports" \
    log=yes log-prefix="SPECIAL-PORT-BLOCK"

# Общие правила безопасности
add chain=input action=drop src-address-list=blacklist \
    comment="Drop blacklisted IPs" \
    log=yes log-prefix="BLACKLIST-DROP"

# Разрешение established и related соединений
add chain=input action=accept connection-state=established,related \
    comment="Allow established and related" \
    log=no

# Разрешение loopback трафика
add chain=input action=accept in-interface=lo \
    comment="Allow loopback" \
    log=no

# Базовые службы для администрирования
add chain=input action=accept protocol=tcp dst-port=22 \
    src-address-list=admin_ips \
    comment="SSH for administrators" \
    log=no

add chain=input action=accept protocol=tcp dst-port=8291 \
    src-address-list=admin_ips \
    comment="Winbox for administrators" \
    log=no

add chain=input action=accept protocol=tcp dst-port=8729 \
    src-address-list=admin_ips \
    comment="API-SSL for administrators" \
    log=no

# Защита от port scan
add chain=input action=add-src-to-address-list protocol=tcp \
    tcp-flags=fin,!syn,!rst,!psh,!ack,!urg \
    address-list=port_scanners address-list-timeout=2w \
    comment="NMAP FIN Stealth scan"

add chain=input action=add-src-to-address-list protocol=tcp \
    tcp-flags=fin,syn \
    address-list=port_scanners address-list-timeout=2w \
    comment="SYN/FIN scan"

add chain=input action=add-src-to-address-list protocol=tcp \
    tcp-flags=syn,rst \
    address-list=port_scanners address-list-timeout=2w \
    comment="SYN/RST scan"

add chain=input action=drop src-address-list=port_scanners \
    comment="Drop port scanners" \
    log=yes log-prefix="PORT-SCAN-DROP"

# Финальное правило - блокировка всего остального
add chain=input action=drop \
    comment="Drop all other input" \
    log=yes log-prefix="INPUT-DROP"

Скрипт для автоматического создания и обновления правил:

#!/bin/bash
# /system/script/add name=setup_whitelist_rules source="

# Функция проверки существования address-list
:local checkList do={
    :local listName $1
    :local listExists false
    
    /ip firewall address-list
    :foreach entry in=[find] do={
        :if ([get $entry list] = $listName) do={
            :set listExists true
        }
    }
    
    :return $listExists
}

# Функция создания address-list если не существует
:local createListIfNotExists do={
    :local listName $1
    :local comment $2
    
    :if (![$checkList $listName]) do={
        /ip firewall address-list add list=$listName comment=$comment
        :log info "Created address-list: $listName"
    } else={
        :log info "Address-list already exists: $listName"
    }
}

# Создание всех необходимых списков
$createListIfNotExists "web_users_whitelist" "Main whitelist for web authenticated users"
$createListIfNotExists "admin_ips" "Administrative IP addresses"  
$createListIfNotExists "trusted_networks" "Trusted network ranges"
$createListIfNotExists "blacklist" "Blocked IP addresses"
$createListIfNotExists "rdp_attackers" "RDP brute force attackers"
$createListIfNotExists "port_scanners" "Port scan attackers"
$createListIfNotExists "temporary_access" "Temporary access list"

# Функция проверки существования firewall правила
:local checkFirewallRule do={
    :local chain $1
    :local comment $2
    :local ruleExists false
    
    /ip firewall filter
    :foreach entry in=[find chain=$chain] do={
        :if ([get $entry comment] = $comment) do={
            :set ruleExists true
        }
    }
    
    :return $ruleExists
}

# Массив правил firewall для создания
:local firewallRules {
    {"chain"="input"; "action"="accept"; "protocol"="tcp"; "dst-port"="3389"; 
     "src-address-list"="web_users_whitelist"; "comment"="Allow RDP for whitelisted web users"};
    {"chain"="input"; "action"="accept"; "protocol"="tcp"; "dst-port"="3389"; 
     "src-address-list"="admin_ips"; "comment"="Allow RDP for administrators"};
    {"chain"="input"; "action"="accept"; "protocol"="tcp"; "dst-port"="44444"; 
     "src-address-list"="web_users_whitelist"; "comment"="Allow port 44444 for whitelisted users"};
    {"chain"="input"; "action"="accept"; "protocol"="tcp"; "dst-port"="55555"; 
     "src-address-list"="web_users_whitelist"; "comment"="Allow port 55555 for whitelisted users"};
    {"chain"="input"; "action"="accept"; "protocol"="tcp"; "dst-port"="55554"; 
     "src-address-list"="web_users_whitelist"; "comment"="Allow port 55554 for whitelisted users"};
    {"chain"="input"; "action"="drop"; "protocol"="tcp"; "dst-port"="44444,55555,55554"; 
     "comment"="Block unauthorized access to special ports"; "log"="yes"; "log-prefix"="SPECIAL-PORT-BLOCK"};
    {"chain"="input"; "action"="drop"; "src-address-list"="blacklist"; 
     "comment"="Drop blacklisted IPs"; "log"="yes"; "log-prefix"="BLACKLIST-DROP"}
}

# Создание правил firewall
:foreach rule in=$firewallRules do={
    :local comment ($rule->"comment")
    
    :if (![$checkFirewallRule ($rule->"chain") $comment]) do={
        /ip firewall filter add \
            chain=($rule->"chain") \
            action=($rule->"action") \
            protocol=($rule->"protocol") \
            dst-port=($rule->"dst-port") \
            src-address-list=($rule->"src-address-list") \
            comment=$comment \
            log=($rule->"log") \
            log-prefix=($rule->"log-prefix")
            
        :log info "Created firewall rule: $comment"
    } else={
        :log info "Firewall rule already exists: $comment"
    }
}

# Добавление базовых административных IP
:local adminIPs {"192.168.1.1"; "192.168.1.0/24"}

:foreach adminIP in=$adminIPs do={
    :local ipExists false
    
    /ip firewall address-list
    :foreach entry in=[find list="admin_ips"] do={
        :if ([get $entry address] = $adminIP) do={
            :set ipExists true
        }
    }
    
    :if (!$ipExists) do={
        /ip firewall address-list add list=admin_ips address=$adminIP comment="Auto-added admin IP"
        :log info "Added admin IP: $adminIP"
    }
}

:log info "Whitelist firewall setup completed successfully"

# "

7.2. Конфигурация портов доступа

Детальная настройка портов включает конфигурацию служб, правил NAT при необходимости, и мониторинг подключений:[12][13]

# Настройка служб и портов
/ip service

# Отключение ненужных служб для безопасности
set telnet disabled=yes
set ftp disabled=yes  
set www disabled=yes
set api disabled=yes

# Настройка SSH (изменение стандартного порта)
set ssh address=192.168.1.1/32,192.168.1.0/24 port=2222 disabled=no

# Настройка Winbox
set winbox address=192.168.1.1/32,192.168.1.0/24 port=8291 disabled=no

# Настройка API-SSL
set api-ssl certificate=api-cert address=192.168.1.1/32 port=8729 disabled=no

# Настройка HTTPS для веб-интерфейса (если нужен)
set www-ssl certificate=api-cert address=192.168.1.1/32 port=8443 disabled=no

# Создание правил NAT для перенаправления портов (если сервер за NAT)
/ip firewall nat

# Перенаправление RDP на внутренний сервер
add chain=dstnat action=dst-nat protocol=tcp dst-port=3389 \
    src-address-list=web_users_whitelist \
    to-addresses=192.168.1.100 to-ports=3389 \
    comment="Forward RDP to internal server"

# Перенаправление специальных портов
add chain=dstnat action=dst-nat protocol=tcp dst-port=44444 \
    src-address-list=web_users_whitelist \
    to-addresses=192.168.1.100 to-ports=44444 \
    comment="Forward port 44444 to internal server"

add chain=dstnat action=dst-nat protocol=tcp dst-port=55555 \
    src-address-list=web_users_whitelist \
    to-addresses=192.168.1.100 to-ports=55555 \
    comment="Forward port 55555 to internal server"

add chain=dstnat action=dst-nat protocol=tcp dst-port=55554 \
    src-address-list=web_users_whitelist \
    to-addresses=192.168.1.100 to-ports=55554 \
    comment="Forward port 55554 to internal server"

# Правила SRCNAT для обеспечения возврата трафика
add chain=srcnat action=src-nat protocol=tcp dst-address=192.168.1.100 \
    dst-port=3389,44444,55555,55554 \
    to-addresses=192.168.1.1 \
    comment="SRCNAT for forwarded ports"
    # Отправка уведомления администратору
    /tool e-mail send to="admin@sysadministrator.ru" \
        subject="MikroTik Alert: High Connection Count" \
        body="Detected $totalConnections active connections to monitored ports"
}

# Проверка на атаки брутфорса
:local attackerCount 0
/ip firewall address-list
:foreach entry in=[find list="rdp_attackers"] do={
    :set attackerCount ($attackerCount + 1)
}

:if ($attackerCount > 10) do={
    $logMessage "warning" "High number of RDP attackers detected: $attackerCount"
}

# Анализ трафика по портам за последний час
:local currentTime [/system clock get time]
:local trafficStats {}

/ip accounting
:foreach account in=[find] do={
    :local srcAddress [get $account src-address]
    :local dstPort [get $account dst-port]
    :local bytes [get $account bytes]
    
    :if ($dstPort in $monitoredPorts) do={
        :if (!(any $trafficStats where ($.srcAddress = $srcAddress and $.dstPort = $dstPort))) do={
            :set trafficStats ($trafficStats, {srcAddress=$srcAddress; dstPort=$dstPort; bytes=$bytes})
        }
    }
}

# Проверка состояния служб
:local services {"ssh"; "winbox"; "api-ssl"}
:foreach service in=$services do={
    /ip service
    :local serviceStatus [get [find name=$service] disabled]
    
    :if ($serviceStatus = "true") do={
        $logMessage "warning" "Service $service is disabled"
    } else={
        $logMessage "info" "Service $service is active"
    }
}

# Проверка доступности внутренних серверов
:local internalServers {"192.168.1.100"}
:foreach server in=$internalServers do={
    :local pingResult [/ping $server count=1]
    
    :if ($pingResult = 0) do={
        $logMessage "error" "Internal server $server is not responding"
        
        # Отправка критического уведомления
        /tool e-mail send to="admin@sysadministrator.ru" \
            subject="Critical: Internal Server Down" \
            body="Server $server is not responding to ping"
    } else={
        $logMessage "info" "Internal server $server is responding"
    }
}

$logMessage "info" "Connection monitoring completed"

# "

Автоматическое управление портами через скрипты:

# /system/script/add name=port_management source="

# Функция открытия порта для IP
:local openPortForIP do={
    :local ipAddress $1
    :local portNumber $2
    :local timeoutHours $3
    :local comment $4
    
    # Добавляем IP в белый список
    /ip firewall address-list
    add list=web_users_whitelist address=$ipAddress \
        timeout=($timeoutHours . "h") \
        comment=$comment
    
    :log info "Opened port $portNumber for IP $ipAddress for $timeoutHours hours"
}

# Функция закрытия порта для IP
:local closePortForIP do={
    :local ipAddress $1
    
    /ip firewall address-list
    :foreach entry in=[find list="web_users_whitelist" address=$ipAddress] do={
        remove $entry
        :log info "Removed IP $ipAddress from whitelist"
    }
}

# Функция проверки статуса доступа
:local checkIPAccess do={
    :local ipAddress $1
    :local hasAccess false
    
    /ip firewall address-list
    :foreach entry in=[find list="web_users_whitelist" address=$ipAddress] do={
        :set hasAccess true
    }
    
    :return $hasAccess
}

# Функция получения статистики портов
:local getPortStatistics do={
    :local stats {}
    :local monitoredPorts {3389; 44444; 55555; 55554}
    
    :foreach port in=$monitoredPorts do={
        :local connections 0
        :local bytesTotal 0
        
        # Подсчет активных соединений
        /ip firewall connection
        :foreach conn in=[find dst-port=$port] do={
            :set connections ($connections + 1)
        }
        
        # Получение статистики трафика
        /interface 
        :local interfaceBytes [get [find default-name="ether1"] rx-byte]
        
        :set ($stats->$port) {connections=$connections; bytes=$interfaceBytes}
    }
    
    :return $stats
}

# Основная логика управления портами
:global portCommand
:global targetIP
:global targetPort
:global timeoutHours
:global comment

:if ($portCommand = "open") do={
    $openPortForIP $targetIP $targetPort $timeoutHours $comment
} else={
    :if ($portCommand = "close") do={
        $closePortForIP $targetIP
    } else={
        :if ($portCommand = "check") do={
            :local access [$checkIPAccess $targetIP]
            :log info "IP $targetIP access status: $access"
        } else={
            :if ($portCommand = "stats") do={
                :local statistics [$getPortStatistics]
                :log info "Port statistics retrieved"
            }
        }
    }
}

# "

7.3. Настройка API сервисов

API сервисы MikroTik обеспечивают программный интерфейс для управления устройством. Правильная настройка API критически важна для безопасности и производительности системы:[1][2]

# Базовая настройка API сервисов
/ip service

# Отключение небезопасного API
set api disabled=yes

# Настройка API-SSL с сертификатом
set api-ssl certificate=api-ssl-cert address=192.168.1.1/32 port=8729 disabled=no

# Создание SSL сертификата для API
/certificate
add name=api-ssl-cert common-name=192.168.1.1 subject-alt-name=DNS:api.sysadministrator.ru \
    key-size=2048 days-valid=365 key-usage=digital-signature,key-encipherment \
    trusted=yes

# Подпись сертификата
sign api-ssl-cert

# Создание специального пользователя для API
/user
add name=api_service password="$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 32)" \
    group=api comment="Service user for web application API access"

# Настройка группы с ограниченными правами
/user group
add name=api policy=api,read,write,policy,test,password,sensitive \
    comment="Limited API access group"

# Применение группы к пользователю API
/user set api_service group=api

# Ограничение доступа к API по IP адресам
/user set api_service allowed-address=192.168.1.1/32

# Настройка дополнительных ограничений
/user set api_service disabled=no

# Создание правил firewall для защиты API
/ip firewall filter

# Разрешение API доступа только с определенных IP
add chain=input action=accept protocol=tcp dst-port=8729 \
    src-address-list=api_allowed_ips \
    comment="Allow API access from authorized IPs" \
    place-before=0

# Логирование попыток несанкционированного доступа к API
add chain=input action=log protocol=tcp dst-port=8729 \
    log-prefix="API-UNAUTHORIZED" \
    comment="Log unauthorized API access attempts"

# Блокировка остальных попыток доступа к API
add chain=input action=drop protocol=tcp dst-port=8729 \
    comment="Drop unauthorized API access"

# Создание списка разрешенных IP для API
/ip firewall address-list
add list=api_allowed_ips address=192.168.1.1 comment="Main web server"
add list=api_allowed_ips address=192.168.1.10 comment="Backup server"

Расширенная конфигурация API с мониторингом:

# /system/script/add name=api_security_monitor source="

# Функция мониторинга API подключений
:local monitorAPIConnections do={
    :local apiConnections 0
    :local suspiciousIPs {}
    :local currentTime [/system clock get time]
    
    # Подсчет активных API соединений
    /ip firewall connection
    :foreach conn in=[find dst-port=8729] do={
        :set apiConnections ($apiConnections + 1)
        :local srcIP [get $conn src-address]
        
        # Проверка IP в списке разрешенных
        :local ipAllowed false
        /ip firewall address-list
        :foreach entry in=[find list="api_allowed_ips"] do={
            :if ([get $entry address] = $srcIP) do={
                :set ipAllowed true
            }
        }
        
        # Добавляем подозрительные IP в список
        :if (!$ipAllowed) do={
            :set suspiciousIPs ($suspiciousIPs, $srcIP)
        }
    }
    
    :log info "API_MONITOR: Active API connections: $apiConnections"
    
    # Обработка подозрительных IP
    :if ([:len $suspiciousIPs] > 0) do={
        :foreach suspiciousIP in=$suspiciousIPs do={
            # Добавляем в blacklist
            /ip firewall address-list
            add list=blacklist address=$suspiciousIP \
                timeout=24h comment="Unauthorized API access attempt"
                
            :log warning "API_MONITOR: Blocked unauthorized API access from $suspiciousIP"
            
            # Отправка уведомления
            /tool e-mail send to="security@sysadministrator.ru" \
                subject="Security Alert: Unauthorized API Access" \
                body="Unauthorized API access attempt from $suspiciousIP at $currentTime"
        }
    }
    
    :return $apiConnections
}

# Функция проверки нагрузки на API
:local checkAPILoad do={
    :local apiLoad 0
    :local maxAPILoad 10
    
    # Получаем количество активных соединений
    :set apiLoad [$monitorAPIConnections]
    
    :if ($apiLoad > $maxAPILoad) do={
        :log warning "API_MONITOR: High API load detected: $apiLoad connections"
        
        # Временное ограничение новых соединений
        /ip firewall filter
        add chain=input action=drop protocol=tcp dst-port=8729 \
            connection-limit=5,32 \
            comment="Temporary API connection limit" \
            timeout=5m
            
        return false
    }
    
    return true
}

# Функция аудита API пользователей
:local auditAPIUsers do={
    :local activeUsers {}
    :local inactiveUsers {}
    :local currentTime [/system clock get date]
    
    /user
    :foreach user in=[find group="api"] do={
        :local username [get $user name]
        :local lastLoggedIn [get $user last-logged-in]
        :local disabled [get $user disabled]
        
        :if ($disabled = "false") do={
            :if ($lastLoggedIn != "") do={
                :set activeUsers ($activeUsers, $username)
            } else={
                :set inactiveUsers ($inactiveUsers, $username)
            }
        }
    }
    
    :log info "API_AUDIT: Active users: $activeUsers"
    :log info "API_AUDIT: Inactive users: $inactiveUsers"
    
    # Уведомление о неактивных пользователях
    :if ([:len $inactiveUsers] > 0) do={
        :log warning "API_AUDIT: Found inactive API users: $inactiveUsers"
    }
}

# Функция проверки сертификатов
:local checkAPICertificates do={
    :local expiringSoon {}
    :local currentDate [/system clock get date]
    
    /certificate
    :foreach cert in=[find] do={
        :local certName [get $cert name]
        :local validTo [get $cert valid-to]
        :local daysToExpiry [get $cert days-valid]
        
        # Проверяем сертификаты API
        :if ($certName ~ "api") do={
            :if ($daysToExpiry < 30) do={
                :set expiringSoon ($expiringSoon, $certName)
                :log warning "API_CERT: Certificate $certName expires in $daysToExpiry days"
            }
        }
    }
    
    :if ([:len $expiringSoon] > 0) do={
        /tool e-mail send to="admin@sysadministrator.ru" \
            subject="Certificate Expiry Warning" \
            body="The following API certificates expire soon: $expiringSoon"
    }
}

# Основной мониторинг
:local apiStatus [$checkAPILoad]
:if ($apiStatus) do={
    $auditAPIUsers
    $checkAPICertificates
    :log info "API_MONITOR: Security check completed successfully"
} else={
    :log error "API_MONITOR: High load detected, security measures activated"
}

# "

REST API конфигурация для современных интеграций:

# Настройка REST API (RouterOS 7.x)
/ip service
set www-ssl certificate=api-ssl-cert address=192.168.1.1/32 port=8443 disabled=no

# Создание пользователя для REST API
/user
add name=rest_api_user password="RestAPIPassword123!" \
    group=api comment="REST API service user"

# Настройка CORS для веб-приложений
/ip service set www-ssl cors=yes

# Создание скрипта для управления через REST API
# /system/script/add name=rest_api_handler source="

# Функция обработки REST запросов
:local handleRESTRequest do={
    :local method $1
    :local endpoint $2
    :local data $3
    
    :log info "REST_API: $method request to $endpoint"
    
    # Обработка различных endpoints
    :if ($endpoint = "/whitelist/add") do={
        :local ipAddress ($data->"ip")
        :local comment ($data->"comment")
        :local timeout ($data->"timeout")
        
        /ip firewall address-list
        add list=web_users_whitelist address=$ipAddress \
            comment=$comment timeout=$timeout
            
        :return "{\"status\":\"success\",\"message\":\"IP added to whitelist\"}"
    }
    
    :if ($endpoint = "/whitelist/remove") do={
        :local ipAddress ($data->"ip")
        
        /ip firewall address-list
        :foreach entry in=[find list="web_users_whitelist" address=$ipAddress] do={
            remove $entry
        }
        
        :return "{\"status\":\"success\",\"message\":\"IP removed from whitelist\"}"
    }
    
    :if ($endpoint = "/whitelist/list") do={
        :local whitelistData "["
        :local first true
        
        /ip firewall address-list
        :foreach entry in=[find list="web_users_whitelist"] do={
            :if (!$first) do={ :set whitelistData ($whitelistData . ",") }
            :set first false
            
            :local ip [get $entry address]
            :local comment [get $entry comment]
            :local timeout [get $entry timeout]
            
            :set whitelistData ($whitelistData . "{\"ip\":\"$ip\",\"comment\":\"$comment\",\"timeout\":\"$timeout\"}")
        }
        
        :set whitelistData ($whitelistData . "]")
        :return "{\"status\":\"success\",\"data\":$whitelistData}"
    }
    
    :return "{\"status\":\"error\",\"message\":\"Unknown endpoint\"}"
}

# Глобальные переменные для REST обработки
:global restMethod
:global restEndpoint  
:global restData

:local result [$handleRESTRequest $restMethod $restEndpoint $restData]
:put $result

# "

7.4. Правила безопасности

Комплексные правила безопасности обеспечивают защиту MikroTik устройства от различных типов атак и несанкционированного доступа:[3][4]

# Базовые правила безопасности MikroTik
/ip firewall filter

# === ЗАЩИТА ОТ DDoS АТАК ===

# Ограничение количества новых соединений
add chain=input action=add-src-to-address-list protocol=tcp \
    connection-limit=20,32 address-list=ddos_attackers \
    address-list-timeout=1d comment="DDoS protection - connection limit"

# Блокировка DDoS атакующих
add chain=input action=drop src-address-list=ddos_attackers \
    comment="Drop DDoS attackers"

# Защита от SYN flood
add chain=input action=accept protocol=tcp connection-state=established \
    comment="Allow established TCP"

add chain=input action=drop protocol=tcp tcp-flags=syn \
    connection-limit=10,32 \
    comment="SYN flood protection"

# === ЗАЩИТА ОТ BRUTEFORCE ===

# SSH брутфорс защита
add chain=input action=add-src-to-address-list protocol=tcp dst-port=22 \
    src-address-list=ssh_blacklist address-list=ssh_blacklist \
    address-list-timeout=1d comment="SSH blacklist chain"

add chain=input action=add-src-to-address-list protocol=tcp dst-port=22 \
    connection-state=new src-address-list=!admin_ips \
    address-list=ssh_stage1 address-list-timeout=1m \
    comment="SSH stage 1"

add chain=input action=add-src-to-address-list protocol=tcp dst-port=22 \
    connection-state=new src-address-list=ssh_stage1 \
    address-list=ssh_stage2 address-list-timeout=1m \
    comment="SSH stage 2"

add chain=input action=add-src-to-address-list protocol=tcp dst-port=22 \
    connection-state=new src-address-list=ssh_stage2 \
    address-list=ssh_blacklist address-list-timeout=1d \
    comment="SSH blacklist final"

add chain=input action=drop src-address-list=ssh_blacklist \
    comment="Drop SSH brute force"

# RDP брутфорс защита
add chain=input action=add-src-to-address-list protocol=tcp dst-port=3389 \
    connection-state=new src-address-list=!web_users_whitelist \
    src-address-list=!admin_ips \
    address-list=rdp_stage1 address-list-timeout=1m \
    comment="RDP stage 1"

add chain=input action=add-src-to-address-list protocol=tcp dst-port=3389 \
    connection-state=new src-address-list=rdp_stage1 \
    address-list=rdp_stage2 address-list-timeout=1m \
    comment="RDP stage 2"

add chain=input action=add-src-to-address-list protocol=tcp dst-port=3389 \
    connection-state=new src-address-list=rdp_stage2 \
    address-list=rdp_blacklist address-list-timeout=1d \
    comment="RDP blacklist final"

# API брутфорс защита  
add chain=input action=add-src-to-address-list protocol=tcp dst-port=8729 \
    connection-state=new src-address-list=!api_allowed_ips \
    address-list=api_attackers address-list-timeout=1d \
    comment="API brute force protection"

# === ЗАЩИТА ОТ PORT SCANNING ===

# Детекция NMAP сканирования
add chain=input action=add-src-to-address-list protocol=tcp \
    tcp-flags=fin,!syn,!rst,!psh,!ack,!urg \
    address-list=port_scanners address-list-timeout=2w \
    comment="NMAP FIN stealth scan"

add chain=input action=add-src-to-address-list protocol=tcp \
    tcp-flags=fin,syn \
    address-list=port_scanners address-list-timeout=2w \
    comment="SYN/FIN scan"

add chain=input action=add-src-to-address-list protocol=tcp \
    tcp-flags=syn,rst \
    address-list=port_scanners address-list-timeout=2w \
    comment="SYN/RST scan"

add chain=input action=add-src-to-address-list protocol=tcp \
    tcp-flags=fin,psh,urg,!syn,!rst,!ack \
    address-list=port_scanners address-list-timeout=2w \
    comment="XMAS scan"

add chain=input action=add-src-to-address-list protocol=tcp \
    tcp-flags=!fin,!syn,!rst,!psh,!ack,!urg \
    address-list=port_scanners address-list-timeout=2w \
    comment="NULL scan"

# Блокировка port scanners
add chain=input action=drop src-address-list=port_scanners \
    comment="Drop port scanners" log=yes log-prefix="PORT-SCAN"

# === ЗАЩИТА ОТ ICMP FLOOD ===
add chain=input action=accept protocol=icmp icmp-options=8:0-255 \
    limit=5,10:packet comment="Allow limited ICMP ping"

add chain=input action=drop protocol=icmp \
    comment="Drop excess ICMP"

# === ЗАЩИТА ОТ INVALID ПАКЕТОВ ===
add chain=input action=drop connection-state=invalid \
    comment="Drop invalid connections"

# === ГЕОГРАФИЧЕСКАЯ ФИЛЬТРАЦИЯ ===
# (Требует установки GeoIP пакета)
# add chain=input action=drop src-address-list=geoip-c_CN \
#     comment="Block China" log=yes log-prefix="GEO-BLOCK-CN"

# === ВРЕМЕННЫЕ ОГРАНИЧЕНИЯ ===
# Ограничение доступа в нерабочее время
add chain=input action=drop protocol=tcp dst-port=3389,44444,55555,55554 \
    time=22:00-06:00,sat,sun src-address-list=!admin_ips \
    comment="Block access outside business hours"

# === ЗАЩИТА ВНУТРЕННЕЙ СЕТИ ===
add chain=forward action=fasttrack-connection connection-state=established,related \
    comment="FastTrack established,related"

add chain=forward action=accept connection-state=established,related \
    comment="Accept established,related"

add chain=forward action=drop connection-state=invalid \
    comment="Drop invalid forward"

# Защита от межсетевого сканирования
add chain=forward action=add-src-to-address-list protocol=tcp \
    tcp-flags=syn connection-limit=20,32 \
    address-list=network_scanners address-list-timeout=1d \
    comment="Detect network scanning"

add chain=forward action=drop src-address-list=network_scanners \
    comment="Drop network scanners"

Автоматизированная система безопасности:

# /system/script/add name=security_automation source="

# Функция анализа угроз
:local analyzeThreat do={
    :local attackType $1
    :local sourceIP $2
    :local severity $3
    
    :log warning "SECURITY: $attackType detected from $sourceIP (severity: $severity)"
    
    # Определение ответных мер
    :if ($severity = "high") do={
        # Блокируем IP на длительный срок
        /ip firewall address-list
        add list=high_threat_ips address=$sourceIP \
            timeout=30d comment="High threat IP - $attackType"
            
        # Немедленное уведомление
        /tool e-mail send to="security@sysadministrator.ru" \
            subject="HIGH THREAT DETECTED" \
            body="High severity $attackType detected from $sourceIP"
            
    } else={
        :if ($severity = "medium") do={
            # Блокируем на средний срок
            /ip firewall address-list
            add list=medium_threat_ips address=$sourceIP \
                timeout=7d comment="Medium threat IP - $attackType"
        }
    }
}

# Функция проверки аномальной активности
:local checkAnomalousActivity do={
    :local currentTime [/system clock get time]
    :local alertThreshold 100
    
    # Подсчет заблокированных IP за последний час
    :local recentBlocks 0
    /log
    :foreach entry in=[find message~".*DROP.*" time>($currentTime - 3600)] do={
        :set recentBlocks ($recentBlocks + 1)
    }
    
    :if ($recentBlocks > $alertThreshold) do={
        :log error "SECURITY: Anomalous activity detected - $recentBlocks blocks in last hour"
        
        # Активация режима повышенной безопасности
        /ip firewall filter
        add chain=input action=drop protocol=tcp \
            connection-state=new connection-limit=5,32 \
            comment="Emergency DDoS protection" \
            timeout=1h
            
        /tool e-mail send to="admin@sysadministrator.ru" \
            subject="EMERGENCY: High Attack Volume" \
            body="Emergency DDoS protection activated due to $recentBlocks attacks"
    }
}

# Функция автоматической очистки старых записей
:local cleanupOldEntries do={
    :local cleaned 0
    
    # Очистка просроченных address-list записей
    /ip firewall address-list
    :foreach entry in=[find] do={
        :local timeout [get $entry timeout]
        :local creationTime [get $entry creation-time]
        
        # Если запись просрочена, удаляем
        :if ($timeout != "" and $creationTime != "") do={
            # Логика проверки истечения времени
            # (упрощенная проверка)
            :set cleaned ($cleaned + 1)
        }
    }
    
    :log info "SECURITY: Cleaned $cleaned old address-list entries"
}

# Функция мониторинга системных ресурсов
:local monitorSystemResources do={
    /system resource
    :local cpuLoad [get cpu-load]
    :local freeMemory [get free-memory]
    :local totalMemory [get total-memory]
    :local memoryUsage (($totalMemory - $freeMemory) * 100 / $totalMemory)
    
    :if ($cpuLoad > 80) do={
        :log warning "SECURITY: High CPU load detected: $cpuLoad%"
        
        # Временное ограничение новых соединений
        /ip firewall filter
        add chain=input action=drop connection-state=new \
            connection-limit=10,32 \
            comment="High CPU protection" \
            timeout=10m
    }
    
    :if ($memoryUsage > 90) do={
        :log warning "SECURITY: High memory usage detected: $memoryUsage%"
        
        # Очистка соединений
        /ip firewall connection
        :foreach conn in=[find] do={
            :local connState [get $conn connection-state]
            :if ($connState = "time-wait") do={
                remove $conn
            }
        }
    }
}

# Функция проверки целостности конфигурации
:local checkConfigIntegrity do={
    :local criticalRules {
        "Allow established and related";
        "Drop blacklisted IPs";
        "Allow RDP for whitelisted web users";
        "Drop all other input"
    }
    
    :local missingRules {}
    
    :foreach rule in=$criticalRules do={
        :local ruleFound false
        
        /ip firewall filter
        :foreach entry in=[find] do={
            :local comment [get $entry comment]
            :if ($comment = $rule) do={
                :set ruleFound true
            }
        }
        
        :if (!$ruleFound) do={
            :set missingRules ($missingRules, $rule)
        }
    }
    
    :if ([:len $missingRules] > 0) do={
        :log error "SECURITY: Critical firewall rules missing: $missingRules"
        
        /tool e-mail send to="admin@sysadministrator.ru" \
            subject="CRITICAL: Missing Firewall Rules" \
            body="The following critical firewall rules are missing: $missingRules"
    }
}

# Главная функция автоматизации безопасности
:local mainSecurityCheck do={
    $checkAnomalousActivity
    $cleanupOldEntries
    $monitorSystemResources
    $checkConfigIntegrity
    
    :log info "SECURITY: Automated security check completed"
}

# Запуск основной проверки
$mainSecurityCheck

# "

8. Автоматизация и планирование задач

8.1. Настройка Cron для автоматического обновления

Система cron обеспечивает автоматическое выполнение задач по расписанию. Правильная настройка cron критически важна для поддержания системы в актуальном состоянии:[5][6]

# /etc/crontab - Основной файл cron для системы

# Переменные окружения
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=admin@sysadministrator.ru
HOME=/root

# Автоматическое обновление белого списка каждые 5 минут
*/5 * * * * www-data /usr/local/bin/update_whitelist.php >> /var/log/whitelist_updates.log 2>&1

# Очистка просроченных записей каждые 30 минут
*/30 * * * * www-data /usr/local/bin/cleanup_expired.php >> /var/log/whitelist_cleanup.log 2>&1

# Мониторинг соединений каждые 10 минут
*/10 * * * * root /usr/local/bin/monitor_connections.sh >> /var/log/connection_monitor.log 2>&1

# Проверка работоспособности PHP-FPM каждые 15 минут
*/15 * * * * root /usr/local/bin/monitor_php_fpm.sh full >> /var/log/php_fpm_monitor.log 2>&1

# Синхронизация с MikroTik каждый час
0 * * * * www-data /usr/local/bin/sync_mikrotik.php >> /var/log/mikrotik_sync.log 2>&1

# Генерация отчетов безопасности каждые 6 часов
0 */6 * * * root /usr/local/bin/security_report.sh >> /var/log/security_reports.log 2>&1

# Резервное копирование конфигураций ежедневно в 2:00
0 2 * * * root /usr/local/bin/backup_configs.sh >> /var/log/backup.log 2>&1

# Ротация логов еженедельно по воскресеньям в 3:00
0 3 * * 0 root /usr/sbin/logrotate -f /etc/logrotate.conf >> /var/log/logrotate.log 2>&1

# Проверка обновлений системы ежедневно в 4:00
0 4 * * * root /usr/local/bin/check_system_updates.sh >> /var/log/system_updates.log 2>&1

# Очистка временных файлов еженедельно
0 5 * * 1 root /usr/local/bin/cleanup_temp_files.sh >> /var/log/cleanup.log 2>&1

# Проверка сертификатов SSL ежемесячно
0 6 1 * * root /usr/local/bin/check_ssl_certs.sh >> /var/log/ssl_check.log 2>&1

Скрипт автоматического обновления белого списка:

#!/usr/bin/env php
<?php
// /usr/local/bin/update_whitelist.php
// Автоматическое обновление белого списка

require_once '/var/www/html/sysadministrator/vendor/autoload.php';
require_once '/var/www/html/sysadministrator/config/config.php';
require_once '/var/www/html/sysadministrator/classes/WhitelistManager.php';
require_once '/var/www/html/sysadministrator/classes/SystemLogger.php';

class AutomaticWhitelistUpdater {
    private $config;
    private $logger;
    private $whitelistManager;
    private $lockFile = '/tmp/whitelist_update.lock';
    
    public function __construct() {
        global $config;
        $this->config = $config;
        $this->logger = new SystemLogger('whitelist-updater');
        
        // Инициализация менеджера белого списка
        $api = OptimizedMikroTikManager::getConnection($config['mikrotik']);
        $this->whitelistManager = new WhitelistManager($api, $config['whitelist']['name']);
    }
    
    /**
     * Основной метод обновления
     */
    public function run() {
        // Проверка блокировки для предотвращения множественного запуска
        if (!$this->acquireLock()) {
            $this->logger->warning('Another update process is running, skipping');
            return false;
        }
        
        try {
            $this->logger->info('Starting automatic whitelist update');
            
            $stats = [
                'processed' => 0,
                'added' => 0,
                'updated' => 0,
                'errors' => 0,
                'start_time' => microtime(true)
            ];
            
            // Обработка очереди запросов на добавление
            $stats = $this->processAdditionQueue($stats);
            
            // Проверка и обновление существующих записей
            $stats = $this->updateExistingEntries($stats);
            
            // Синхронизация с базой данных
            $stats = $this->syncWithDatabase($stats);
            
            // Проверка соответствия политикам безопасности
            $stats = $this->enforceSecurityPolicies($stats);
            
            $stats['execution_time'] = microtime(true) - $stats['start_time'];
            
            $this->logger->info('Whitelist update completed', $stats);
            
            // Отправка статистики если есть значительные изменения
            if ($stats['added'] > 10 || $stats['errors'] > 5) {
                $this->sendUpdateReport($stats);
            }
            
            return true;
            
        } catch (Exception $e) {
            $this->logger->error('Whitelist update failed', [
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString()
            ]);
            
            $this->sendErrorNotification($e);
            return false;
            
        } finally {
            $this->releaseLock();
        }
    }
    
    /**
     * Обработка очереди запросов на добавление
     */
    private function processAdditionQueue($stats) {
        $queueFile = '/tmp/whitelist_queue.json';
        
        if (!file_exists($queueFile)) {
            return $stats;
        }
        
        $queue = json_decode(file_get_contents($queueFile), true) ?: [];
        $processedQueue = [];
        
        foreach ($queue as $request) {
            $stats['processed']++;
            
            try {
                // Валидация запроса
                if (!$this->validateAdditionRequest($request)) {
                    $stats['errors']++;
                    continue;
                }
                
                // Проверка существования IP в белом списке
                $status = $this->whitelistManager->getUserStatus($request['ip']);
                
                if (!$status['in_whitelist']) {
                    // Добавление нового IP
                    $result = $this->whitelistManager->addUser(
                        $request['ip'],
                        $request['username'],
                        $request['timeout'] ?? 7200
                    );
                    
                    if ($result['success']) {
                        $stats['added']++;
                        $this->logger->info('Added IP to whitelist', [
                            'ip' => $request['ip'],
                            'username' => $request['username']
                        ]);
                    } else {
                        $stats['errors']++;
                        $this->logger->error('Failed to add IP to whitelist', [
                            'ip' => $request['ip'],
                            'error' => $result['message']
                        ]);
                    }
                } else {
                    // IP уже в списке, возможно нужно обновить timeout
                    if (isset($request['extend']) && $request['extend']) {
                        $this->whitelistManager->removeUser($request['ip']);
                        $result = $this->whitelistManager->addUser(
                            $request['ip'],
                            $request['username'],
                            $request['timeout'] ?? 7200
                        );
                        
                        if ($result['success']) {
                            $stats['updated']++;
                        } else {
                            $stats['errors']++;
                        }
                    }
                }
                
            } catch (Exception $e) {
                $stats['errors']++;
                $this->logger->error('Error processing queue item', [
                    'request' => $request,
                    'error' => $e->getMessage()
                ]);
            }
        }
        
        // Сохраняем обновленную очередь
        file_put_contents($queueFile, json_encode($processedQueue));
        
        return $stats;
    }
    
    /**
     * Обновление существующих записей
     */
    private function updateExistingEntries($stats) {
        $users = $this->whitelistManager->getAllUsers();
        $currentTime = time();
        
        foreach ($users as $user) {
            $stats['processed']++;
            
            try {
                // Проверка истечения времени доступа
                if (isset($user['expires_at']) && $user['expires_at']) {
                    $expiryTime = strtotime($user['expires_at']);
                    
                    // Если доступ истек более часа назад, удаляем
                    if ($expiryTime < $currentTime - 3600) {
                        $this->whitelistManager->removeUser($user['address']);
                        $stats['updated']++;
                        
                        $this->logger->info('Removed expired user', [
                            'ip' => $user['address'],
                            'username' => $user['username'],
                            'expired_at' => $user['expires_at']
                        ]);
                    }
                    // Если доступ истекает в течение 30 минут, отправляем предупреждение
                    elseif ($expiryTime < $currentTime + 1800) {
                        $this->sendExpirationWarning($user);
                    }
                }
                
            } catch (Exception $e) {
                $stats['errors']++;
                $this->logger->error('Error updating user entry', [
                    'user' => $user,
                    'error' => $e->getMessage()
                ]);
            }
        }
        
        return $stats;
    }
    
    /**
     * Синхронизация с базой данных
     */
    private function syncWithDatabase($stats) {
        $dbFile = '/var/lib/mikrotik/whitelist.db';
        
        if (!file_exists($dbFile)) {
            return $stats;
        }
        
        try {
            $pdo = new PDO('sqlite:' . $dbFile);
            
            // Получаем записи из MikroTik
            $mikrotikUsers = $this->whitelistManager->getAllUsers();
            
            // Получаем записи из базы данных
            $stmt = $pdo->prepare('SELECT * FROM whitelist_entries WHERE active = 1');
            $stmt->execute();
            $dbUsers = $stmt->fetchAll(PDO::FETCH_ASSOC);
            
            // Сравниваем и синхронизируем
            foreach ($dbUsers as $dbUser) {
                $found = false;
                
                foreach ($mikrotikUsers as $mtUser) {
                    if ($mtUser['address'] === $dbUser['ip_address']) {
                        $found = true;
                        break;
                    }
                }
                
                // Если запись есть в БД, но нет в MikroTik - добавляем
                if (!$found && strtotime($dbUser['expires_at']) > time()) {
                    $result = $this->whitelistManager->addUser(
                        $dbUser['ip_address'],
                        $dbUser['username'],
                        strtotime($dbUser['expires_at']) - time()
                    );
                    
                    if ($result['success']) {
                        $stats['added']++;
                        $this->logger->info('Restored user from database', [
                            'ip' => $dbUser['ip_address'],
                            'username' => $dbUser['username']
                        ]);
                    }
                }
            }
            
        } catch (Exception $e) {
            $stats['errors']++;
            $this->logger->error('Database sync failed', [
                'error' => $e->getMessage()
            ]);
        }
        
        return $stats;
    }
    
    /**
     * Применение политик безопасности
     */
    private function enforceSecurityPolicies($stats) {
        $users = $this->whitelistManager->getAllUsers();
        $maxUsersPerIP = $this->config['security']['max_users_per_ip'] ?? 3;
        $maxTotalUsers = $this->config['security']['max_total_users'] ?? 100;
        
        // Проверка общего количества пользователей
        if (count($users) > $maxTotalUsers) {
            $this->logger->warning('Maximum user limit exceeded', [
                'current_users' => count($users),
                'max_users' => $maxTotalUsers
            ]);
            
            // Удаляем самые старые записи
            usort($users, function($a, $b) {
                return strtotime($a['creation_time'] ?? '1970-01-01') - 
                       strtotime($b['creation_time'] ?? '1970-01-01');
            });
            
            $toRemove = count($users) - $maxTotalUsers;
            for ($i = 0; $i < $toRemove; $i++) {
                $this->whitelistManager->removeUser($users[$i]['address']);
                $stats['updated']++;
            }
        }
        
        // Проверка подозрительных IP адресов
        $suspiciousIPs = $this->detectSuspiciousIPs($users);
        
        foreach ($suspiciousIPs as $suspiciousIP) {
            $this->whitelistManager->removeUser($suspiciousIP);
            $stats['updated']++;
            
            $this->logger->warning('Removed suspicious IP', [
                'ip' => $suspiciousIP,
                'reason' => 'Security policy violation'
            ]);
        }
        
        return $stats;
    }
    
    /**
     * Валидация запроса на добавление
     */
    private function validateAdditionRequest($request) {
        // Проверка обязательных полей
        if (!isset($request['ip']) || !isset($request['username'])) {
            return false;
        }
        
        // Валидация IP адреса
        if (!filter_var($request['ip'], FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
            return false;
        }
        
        // Проверка имени пользователя
        if (!preg_match('/^[a-zA-Z0-9._-]{3,32}$/', $request['username'])) {
            return false;
        }
        
        // Проверка timeout
        if (isset($request['timeout'])) {
            $timeout = intval($request['timeout']);
            if ($timeout < 300 || $timeout > 86400) { // От 5 минут до 24 часов
                return false;
            }
        }
        
        return true;
    }
    
    /**
     * Обнаружение подозрительных IP адресов
     */
    private function detectSuspiciousIPs($users) {
        $suspicious = [];
        $ipCounts = [];
        
        // Подсчет записей по IP
        foreach ($users as $user) {
            $ip = $user['address'];
            if (!isset($ipCounts[$ip])) {
                $ipCounts[$ip] = 0;
            }
            $ipCounts[$ip]++;
        }
        
        // Поиск IP с множественными записями
        foreach ($ipCounts as $ip => $count) {
            if ($count > 3) {
                $suspicious[] = $ip;
            }
        }
        
        return $suspicious;
    }
    
    /**
     * Отправка предупреждения об истечении
     */
    private function sendExpirationWarning($user) {
        $warningsFile = '/tmp/expiration_warnings.json';
        $warnings = [];
        
        if (file_exists($warningsFile)) {
            $warnings = json_decode(file_get_contents($warningsFile), true) ?: [];
        }
        
        $warningKey = $user['address'] . '_' . $user['expires_at'];
        
        // Если предупреждение уже отправлено, пропускаем
        if (isset($warnings[$warningKey])) {
            return;
        }
        
        // Отправляем предупреждение (здесь может быть интеграция с email/SMS/Telegram)
        $this->logger->info('Sent expiration warning', [
            'ip' => $user['address'],
            'username' => $user['username'],
            'expires_at' => $user['expires_at']
        ]);
        
        // Записываем отправленное предупреждение
        $warnings[$warningKey] = time();
        file_put_contents($warningsFile, json_encode($warnings));
    }
    
    /**
     * Отправка отчета об обновлении
     */
    private function sendUpdateReport($stats) {
        $subject = 'Whitelist Update Report - ' . date('Y-m-d H:i:s');
        $body = "Whitelist update completed with the following statistics:\n\n";
        $body .= "Processed entries: {$stats['processed']}\n";
        $body .= "Added entries: {$stats['added']}\n";
        $body .= "Updated entries: {$stats['updated']}\n";
        $body .= "Errors: {$stats['errors']}\n";
        $body .= "Execution time: " . round($stats['execution_time'], 2) . " seconds\n";
        
        // Отправка email (здесь должна быть реальная реализация)
        mail('admin@sysadministrator.ru', $subject, $body);
    }
    
    /**
     * Отправка уведомления об ошибке
     */
    private function sendErrorNotification($exception) {
        $subject = 'ERROR: Whitelist Update Failed';
        $body = "Whitelist update failed with the following error:\n\n";
        $body .= "Error: " . $exception->getMessage() . "\n";
        $body .= "File: " . $exception->getFile() . "\n";
        $body .= "Line: " . $exception->getLine() . "\n\n";
        $body .= "Stack trace:\n" . $exception->getTraceAsString();
        
        mail('admin@sysadministrator.ru', $subject, $body);
    }
    
    /**
     * Получение блокировки
     */
    private function acquireLock() {
        if (file_exists($this->lockFile)) {
            $lockTime = filemtime($this->lockFile);
            
            // Если lock файл старше 30 минут, считаем его устаревшим
            if (time() - $lockTime > 1800) {
                unlink($this->lockFile);
            } else {
                return false;
            }
        }
        
        return touch($this->lockFile);
    }
    
    /**
     * Освобождение блокировки
     */
    private function releaseLock() {
        if (file_exists($this->lockFile)) {
            unlink($this->lockFile);
        }
    }
}

// Запуск обновления
$updater = new AutomaticWhitelistUpdater();
$success = $updater->run();

exit($success ? 0 : 1);
?>

8.2. Скрипты очистки просроченных записей

Автоматическая очистка предотвращает накопление устаревших записей и поддерживает оптимальную производительность системы:

#!/usr/bin/env php
<?php
// /usr/local/bin/cleanup_expired.php
// Скрипт очистки просроченных записей

require_once '/var/www/html/sysadministrator/vendor/autoload.php';
require_once '/var/www/html/sysadministrator/config/config.php';
require_once '/var/www/html/sysadministrator/classes/WhitelistManager.php';
require_once '/var/www/html/sysadministrator/classes/SystemLogger.php';

class ExpiredEntriesCleanup {
    private $config;
    private $logger;
    private $whitelistManager;
    private $dbConnection;
    private $lockFile = '/tmp/cleanup_expired.lock';
    
    public function __construct() {
        global $config;
        $this->config = $config;
        $this->logger = new SystemLogger('cleanup-expired');
        
        // Инициализация подключений
        $api = OptimizedMikroTikManager::getConnection($config['mikrotik']);
        $this->whitelistManager = new WhitelistManager($api, $config['whitelist']['name']);
        
        // Подключение к базе данных для расширенного логирования
        if (isset($config['database']['enabled']) && $config['database']['enabled']) {
            $this->initDatabaseConnection();
        }
    }
    
    /**
     * Основной метод очистки
     */
    public function run() {
        if (!$this->acquireLock()) {
            $this->logger->warning('Another cleanup process is running, skipping');
            return false;
        }
        
        try {
            $this->logger->info('Starting expired entries cleanup');
            
            $stats = [
                'start_time' => microtime(true),
                'mikrotik_removed' => 0,
                'database_removed' => 0,
                'cache_cleared' => 0,
                'logs_cleaned' => 0,
                'temp_files_removed' => 0,
                'errors' => 0
            ];
            
            // Очистка MikroTik записей
            $stats = $this->cleanupMikroTikEntries($stats);
            
            // Очистка базы данных
            $stats = $this->cleanupDatabaseEntries($stats);
            
            // Очистка кэш файлов
            $stats = $this->cleanupCacheFiles($stats);
            
            // Очистка старых логов
            $stats = $this->cleanupOldLogs($stats);
            
            // Очистка временных файлов
            $stats = $this->cleanupTempFiles($stats);
            
            // Очистка сессий
            $stats = $this->cleanupSessions($stats);
            
            $stats['execution_time'] = microtime(true) - $stats['start_time'];
            
            $this->logger->info('Cleanup completed successfully', $stats);
            
            // Сохранение статистики для мониторинга
            $this->saveCleanupStats($stats);
            
            return true;
            
        } catch (Exception $e) {
            $this->logger->error('Cleanup failed', [
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString()
            ]);
            
            return false;
            
        } finally {
            $this->releaseLock();
        }
    }
    
    /**
     * Очистка записей MikroTik
     */
    private function cleanupMikroTikEntries($stats) {
        try {
            $result = $this->whitelistManager->cleanupExpired();
            $stats['mikrotik_removed'] = $result['cleaned'] ?? 0;
            
            $this->logger->info('MikroTik cleanup completed', [
                'removed_entries' => $stats['mikrotik_removed']
            ]);
            
        } catch (Exception $e) {
            $stats['errors']++;
            $this->logger->error('MikroTik cleanup failed', [
                'error' => $e->getMessage()
            ]);
        }
        
        return $stats;
    }
    
    /**
     * Очистка записей базы данных
     */
    private function cleanupDatabaseEntries($stats) {
        if (!$this->dbConnection) {
            return $stats;
        }
        
        try {
            // Очистка просроченных записей whitelist
            $stmt = $this->dbConnection->prepare('
                DELETE FROM whitelist_entries 
                WHERE expires_at < datetime("now") 
                AND expires_at IS NOT NULL
            ');
            $stmt->execute();
            $stats['database_removed'] += $stmt->rowCount();
            
            // Очистка старых логов соединений
            $stmt = $this->dbConnection->prepare('
                DELETE FROM connections 
                WHERE last_seen < datetime("now", "-7 days")
            ');
            $stmt->execute();
            $stats['database_removed'] += $stmt->rowCount();
            
            // Очистка старых попыток аутентификации
            $stmt = $this->dbConnection->prepare('
                DELETE FROM auth_attempts 
                WHERE timestamp < datetime("now", "-30 days")
            ');
            $stmt->execute();
            $stats['database_removed'] += $stmt->rowCount();
            
            // Очистка статистики старше 90 дней
            $stmt = $this->dbConnection->prepare('
                DELETE FROM statistics 
                WHERE created_at < datetime("now", "-90 days")
            ');
            $stmt->execute();
            $stats['database_removed'] += $stmt->rowCount();
            
            // Оптимизация базы данных
            $this->dbConnection->exec('VACUUM');
            $this->dbConnection->exec('ANALYZE');
            
            $this->logger->info('Database cleanup completed', [
                'removed_entries' => $stats['database_removed']
            ]);
            
        } catch (Exception $e) {
            $stats['errors']++;
            $this->logger->error('Database cleanup failed', [
                'error' => $e->getMessage()
            ]);
        }
        
        return $stats;
    }
    
    /**
     * Очистка кэш файлов
     */
    private function cleanupCacheFiles($stats) {
        $cacheDirectories = [
            '/tmp',
            '/var/cache/nginx',
            '/var/cache/php-fpm',
            '/var/lib/php/sessions'
        ];
        
        $maxAge = 3600 * 24; // 24 часа
        
        foreach ($cacheDirectories as $dir) {
            if (!is_dir($dir)) {
                continue;
            }
            
            try {
                $iterator = new RecursiveIteratorIterator(
                    new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS),
                    RecursiveIteratorIterator::CHILD_FIRST
                );
                
                foreach ($iterator as $file) {
                    if ($file->isFile()) {
                        $fileAge = time() - $file->getMTime();
                        
                        // Удаляем файлы старше максимального возраста
                        if ($fileAge > $maxAge) {
                            // Проверяем, что файл можно удалять
                            if ($this->isSafeToDelete($file->getPathname())) {
                                unlink($file->getPathname());
                                $stats['cache_cleared']++;
                            }
                        }
                    }
                }
                
            } catch (Exception $e) {
                $stats['errors']++;
                $this->logger->error('Cache cleanup failed for directory', [
                    'directory' => $dir,
                    'error' => $e->getMessage()
                ]);
            }
        }
        
        // Очистка специфичных кэш файлов приложения
        $appCacheFiles = [
            '/tmp/mikrotik_whitelist_cache.json',
            '/tmp/user_requests.json',
            '/tmp/connection_stats.json',
            '/tmp/auth_attempts.json'
        ];
        
        foreach ($appCacheFiles as $cacheFile) {
            if (file_exists($cacheFile)) {
                $fileAge = time() - filemtime($cacheFile);
                
                if ($fileAge > 3600) { // 1 час для app cache
                    unlink($cacheFile);
                    $stats['cache_cleared']++;
                }
            }
        }
        
        $this->logger->info('Cache cleanup completed', [
            'files_removed' => $stats['cache_cleared']
        ]);
        
        return $stats;
    }
    
    /**
     * Очистка старых логов
     */
    private function cleanupOldLogs($stats) {
        $logDirectories = [
            '/var/log/sysadministrator',
            '/var/log/mikrotik',
            '/var/log/nginx',
            '/var/log/php8.3-fpm'
        ];
        
        $retentionDays = $this->config['cleanup']['log_retention_days'] ?? 30;
        $maxAge = $retentionDays * 24 * 3600;
        
        foreach ($logDirectories as $logDir) {
            if (!is_dir($logDir)) {
                continue;
            }
            
            try {
                $files = glob($logDir . '/*.log.*');
                
                foreach ($files as $logFile) {
                    $fileAge = time() - filemtime($logFile);
                    
                    if ($fileAge > $maxAge) {
                        // Архивируем перед удалением если файл большой
                        if (filesize($logFile) > 10485760) { // 10MB
                            $this->archiveLogFile($logFile);
                        }
                        
                        unlink($logFile);
                        $stats['logs_cleaned']++;
                    }
                }
                
            } catch (Exception $e) {
                $stats['errors']++;
                $this->logger->error('Log cleanup failed for directory', [
                    'directory' => $logDir,
                    'error' => $e->getMessage()
                ]);
            }
        }
        
        $this->logger->info('Log cleanup completed', [
            'files_removed' => $stats['logs_cleaned']
        ]);
        
        return $stats;
    }
    
    /**
     * Очистка временных файлов
     */
    private function cleanupTempFiles($stats) {
        $tempPatterns = [
            '/tmp/php*',
            '/tmp/sess_*',
            '/tmp/*.tmp',
            '/tmp/mikrotik_*',
            '/tmp/whitelist_*',
            '/var/tmp/*'
        ];
        
        foreach ($tempPatterns as $pattern) {
            try {
                $files = glob($pattern);
                
                foreach ($files as $tempFile) {
                    if (is_file($tempFile)) {
                        $fileAge = time() - filemtime($tempFile);
                        
                        // Удаляем файлы старше 2 часов
                        if ($fileAge > 7200) {
                            unlink($tempFile);
                            $stats['temp_files_removed']++;
                        }
                    }
                }
                
            } catch (Exception $e) {
                $stats['errors']++;
                $this->logger->error('Temp files cleanup failed for pattern', [
                    'pattern' => $pattern,
                    'error' => $e->getMessage()
                ]);
            }
        }
        
        $this->logger->info('Temp files cleanup completed', [
            'files_removed' => $stats['temp_files_removed']
        ]);
        
        return $stats;
    }
    
    /**
     * Очистка сессий
     */
    private function cleanupSessions($stats) {
        $sessionPath = ini_get('session.save_path') ?: '/var/lib/php/sessions';
        
        if (!is_dir($sessionPath)) {
            return $stats;
        }
        
        try {
            $maxLifetime = ini_get('session.gc_maxlifetime') ?: 1440;
            $files = glob($sessionPath . '/sess_*');
            $removedSessions = 0;
            
            foreach ($files as $sessionFile) {
                $fileAge = time() - filemtime($sessionFile);
                
                if ($fileAge > $maxLifetime) {
                    unlink($sessionFile);
                    $removedSessions++;
                }
            }
            
            $stats['cache_cleared'] += $removedSessions;
            
            $this->logger->info('Session cleanup completed', [
                'sessions_removed' => $removedSessions
            ]);
            
        } catch (Exception $e) {
            $stats['errors']++;
            $this->logger->error('Session cleanup failed', [
                'error' => $e->getMessage()
            ]);
        }
        
        return $stats;
    }
    
    /**
     * Проверка безопасности удаления файла
     */
    private function isSafeToDelete($filepath) {
        // Не удаляем системные файлы
        $protectedPatterns = [
            '/etc/',
            '/usr/',
            '/var/lib/dpkg',
            '/var/lib/apt',
            '/run/',
            '/proc/',
            '/sys/'
        ];
        
        foreach ($protectedPatterns as $pattern) {
            if (strpos($filepath, $pattern) === 0) {
                return false;
            }
        }
        
        // Не удаляем активные lock файлы
        if (strpos($filepath, '.lock') !== false) {
            $lockAge = time() - filemtime($filepath);
            if ($lockAge < 1800) { // 30 минут
                return false;
            }
        }
        
        return true;
    }
    
    /**
     * Архивирование лог файла
     */
    private function archiveLogFile($logFile) {
        $archiveDir = dirname($logFile) . '/archive';
        
        if (!is_dir($archiveDir)) {
            mkdir($archiveDir, 0755, true);
        }
        
        $archiveName = $archiveDir . '/' . basename($logFile) . '.gz';
        
        $input = fopen($logFile, 'rb');
        $output = gzopen($archiveName, 'wb9');
        
        while (!feof($input)) {
            gzwrite($output, fread($input, 4096));
        }
        
        fclose($input);
        gzclose($output);
        
        $this->logger->info('Archived log file', [
            'original' => $logFile,
            'archive' => $archiveName
        ]);
    }
    
    /**
     * Сохранение статистики очистки
     */
    private function saveCleanupStats($stats) {
        $statsFile = '/var/log/cleanup_statistics.json';
        $history = [];
        
        if (file_exists($statsFile)) {
            $history = json_decode(file_get_contents($statsFile), true) ?: [];
        }
        
        // Добавляем текущую статистику
        $stats['timestamp'] = date('Y-m-d H:i:s');
        $history[] = $stats;
        
        // Оставляем только последние 100 записей
        if (count($history) > 100) {
            $history = array_slice($history, -100);
        }
        
        file_put_contents($statsFile, json_encode($history, JSON_PRETTY_PRINT));
    }
    
    /**
     * Инициализация подключения к базе данных
     */
    private function initDatabaseConnection() {
        try {
            $dbPath = $this->config['database']['path'] ?? '/var/lib/mikrotik/main.db';
            $this->dbConnection = new PDO('sqlite:' . $dbPath);
            $this->dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
            
        } catch (Exception $e) {
            $this->logger->error('Database connection failed', [
                'error' => $e->getMessage()
            ]);
        }
    }
    
    /**
     * Получение блокировки
     */
    private function acquireLock() {
        if (file_exists($this->lockFile)) {
            $lockTime = filemtime($this->lockFile);
            
            if (time() - $lockTime > 3600) { // 1 час
                unlink($this->lockFile);
            } else {
                return false;
            }
        }
        
        return touch($this->lockFile);
    }
    
    /**
     * Освобождение блокировки
     */
    private function releaseLock() {
        if (file_exists($this->lockFile)) {
            unlink($this->lockFile);
        }
    }
}

// Запуск очистки
$cleanup = new ExpiredEntriesCleanup();
$success = $cleanup->run();

exit($success ? 0 : 1);
?>

8.3. Мониторинг работоспособности системы

Комплексная система мониторинга обеспечивает непрерывный контроль работоспособности всех компонентов:

#!/bin/bash
# /usr/local/bin/monitor_connections.sh
# Скрипт мониторинга соединений и работоспособности системы

# Конфигурация
LOG_FILE="/var/log/connection_monitor.log"
ALERT_EMAIL="admin@sysadministrator.ru"
MIKROTIK_HOST="192.168.1.1"
MIKROTIK_USER="monitor_user"
MIKROTIK_PORT="2222"
STATUS_FILE="/tmp/system_status.json"
ALERT_THRESHOLD_HIGH=50
ALERT_THRESHOLD_CRITICAL=100

# Цвета для вывода
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'

log_message() {
    local level="$1"
    local message="$2"
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    
    echo "[$timestamp] [$level] $message" >> "$LOG_FILE"
    echo -e "[$timestamp] [$level] $message"
}

# Функция отправки уведомлений
send_alert() {
    local subject="$1"
    local message="$2"
    local priority="$3"
    
    # Email уведомление
    if command -v mail >/dev/null 2>&1; then
        echo "$message" | mail -s "$subject" "$ALERT_EMAIL"
    fi
    
    # Логирование критических событий
    if [ "$priority" = "CRITICAL" ]; then
        logger -p local0.crit "sysadministrator: $subject - $message"
    fi
    
    log_message "ALERT" "$subject: $message"
}

# Проверка подключения к MikroTik
check_mikrotik_connectivity() {
    log_message "INFO" "Checking MikroTik connectivity"
    
    local status="OK"
    local response_time=""
    local error_message=""
    
    # Проверка ping
    if ping -c 3 -W 5 "$MIKROTIK_HOST" >/dev/null 2>&1; then
        response_time=$(ping -c 1 -W 5 "$MIKROTIK_HOST" 2>/dev/null | grep "time=" | awk -F'time=' '{print $2}' | awk '{print $1}')
        log_message "INFO" "MikroTik ping successful (${response_time}ms)"
    else
        status="CRITICAL"
        error_message="MikroTik host not responding to ping"
        log_message "ERROR" "$error_message"
        send_alert "MikroTik Connectivity Critical" "$error_message" "CRITICAL"
    fi
    
    # Проверка SSH порта
    if [ "$status" = "OK" ]; then
        if timeout 10 bash -c "</dev/tcp/$MIKROTIK_HOST/$MIKROTIK_PORT"; then
            log_message "INFO" "MikroTik SSH port accessible"
        else
            status="WARNING"
            error_message="MikroTik SSH port not accessible"
            log_message "WARNING" "$error_message"
        fi
    fi
    
    # Проверка API порта
    if [ "$status" = "OK" ]; then
        if timeout 10 bash -c "</dev/tcp/$MIKROTIK_HOST/8729"; then
            log_message "INFO" "MikroTik API port accessible"
        else
            status="WARNING"
            error_message="MikroTik API port not accessible"
            log_message "WARNING" "$error_message"
        fi
    fi
    
    echo "$status|$response_time|$error_message"
}

# Мониторинг активных соединений
monitor_active_connections() {
    log_message "INFO" "Monitoring active connections"
    
    local total_connections=0
    local rdp_connections=0
    local special_port_connections=0
    local connection_details=""
    
    # Подсчет соединений по портам
    rdp_connections=$(netstat -tn 2>/dev/null | grep ":3389 " | grep "ESTABLISHED" | wc -l)
    special_port_connections=$(netstat -tn 2>/dev/null | grep -E ":(44444|55555|55554) " | grep "ESTABLISHED" | wc -l)
    total_connections=$((rdp_connections + special_port_connections))
    
    log_message "INFO" "Active connections: Total=$total_connections, RDP=$rdp_connections, Special=$special_port_connections"
    
    # Проверка превышения порогов
    if [ $total_connections -gt $ALERT_THRESHOLD_CRITICAL ]; then
        send_alert "Critical Connection Load" "Critical number of connections detected: $total_connections" "CRITICAL"
    elif [ $total_connections -gt $ALERT_THRESHOLD_HIGH ]; then
        send_alert "High Connection Load" "High number of connections detected: $total_connections" "WARNING"
    fi
    
    # Детальный анализ соединений
    if [ $total_connections -gt 20 ]; then
        connection_details=$(netstat -tn 2>/dev/null | grep -E ":(3389|44444|55555|55554) " | grep "ESTABLISHED" | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -nr | head -10)
        log_message "INFO" "Top connection sources: $connection_details"
    fi
    
    echo "$total_connections|$rdp_connections|$special_port_connections|$connection_details"
}

# Проверка работоспособности веб-сервера
check_web_server() {
    log_message "INFO" "Checking web server status"
    
    local nginx_status="OK"
    local phpfpm_status="OK"
    local response_code=""
    local response_time=""
    local error_message=""
    
    # Проверка NGINX
    if systemctl is-active --quiet nginx; then
        log_message "INFO" "NGINX service is active"
        
        # Проверка HTTP ответа
        local http_response=$(curl -s -w "%{http_code}|%{time_total}" -o /dev/null "http://localhost/health" 2>/dev/null)
        response_code=$(echo "$http_response" | cut -d'|' -f1)
        response_time=$(echo "$http_response" | cut -d'|' -f2)
        
        if [ "$response_code" = "200" ]; then
            log_message "INFO" "Web server responding correctly (${response_time}s)"
        else
            nginx_status="ERROR"
            error_message="Web server returned HTTP $response_code"
            log_message "ERROR" "$error_message"
        fi
    else
        nginx_status="CRITICAL"
        error_message="NGINX service is not running"
        log_message "ERROR" "$error_message"
        send_alert "Web Server Critical" "$error_message" "CRITICAL"
    fi
    
    # Проверка PHP-FPM
    if systemctl is-active --quiet php8.3-fpm; then
        log_message "INFO" "PHP-FPM service is active"
        
        # Проверка статуса PHP-FPM
        local fpm_status_response=$(curl -s "http://localhost/fpm-status" 2>/dev/null)
        if echo "$fpm_status_response" | grep -q "accepted conn"; then
            log_message "INFO" "PHP-FPM status page accessible"
        else
            phpfpm_status="WARNING"
            log_message "WARNING" "PHP-FPM status page not accessible"
        fi
    else
        phpfpm_status="CRITICAL"
        error_message="PHP-FPM service is not running"
        log_message "ERROR" "$error_message"
        send_alert "PHP-FPM Critical" "$error_message" "CRITICAL"
    fi
    
    echo "$nginx_status|$phpfpm_status|$response_code|$response_time|$error_message"
}

# Мониторинг системных ресурсов
monitor_system_resources() {
    log_message "INFO" "Monitoring system resources"
    
    local cpu_usage=""
    local memory_usage=""
    local disk_usage=""
    local load_average=""
    local status="OK"
    local warnings=""
    
    # CPU использование
    cpu_usage=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | cut -d'%' -f1)
    load_average=$(uptime | awk -F'load average:' '{print $2}' | awk '{print $1}' | sed 's/,//')
    
    # Память
    local memory_info=$(free | grep Mem)
    local total_memory=$(echo $memory_info | awk '{print $2}')
    local used_memory=$(echo $memory_info | awk '{print $3}')
    memory_usage=$(echo "scale=1; $used_memory * 100 / $total_memory" | bc)
    
    # Дисковое пространство
    disk_usage=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
    
    log_message "INFO" "Resources: CPU=${cpu_usage}%, Memory=${memory_usage}%, Disk=${disk_usage}%, Load=${load_average}"
    
    # Проверка критических значений
    if (( $(echo "$cpu_usage > 90" | bc -l) )); then
        status="CRITICAL"
        warnings="$warnings CPU usage critical: ${cpu_usage}%;"
    elif (( $(echo "$cpu_usage > 70" | bc -l) )); then
        status="WARNING"
        warnings="$warnings CPU usage high: ${cpu_usage}%;"
    fi
    
    if (( $(echo "$memory_usage > 95" | bc -l) )); then
        status="CRITICAL"
        warnings="$warnings Memory usage critical: ${memory_usage}%;"
    elif (( $(echo "$memory_usage > 80" | bc -l) )); then
        status="WARNING"
        warnings="$warnings Memory usage high: ${memory_usage}%;"
    fi
    
    if [ "$disk_usage" -gt 95 ]; then
        status="CRITICAL"
        warnings="$warnings Disk usage critical: ${disk_usage}%;"
    elif [ "$disk_usage" -gt 80 ]; then
        status="WARNING"
        warnings="$warnings Disk usage high: ${disk_usage}%;"
    fi
    
    if (( $(echo "$load_average > 8.0" | bc -l) )); then
        status="WARNING"
        warnings="$warnings High system load: ${load_average};"
    fi
    
    # Отправка уведомлений при проблемах
    if [ "$status" = "CRITICAL" ]; then
        send_alert "System Resources Critical" "$warnings" "CRITICAL"
    elif [ "$status" = "WARNING" ]; then
        send_alert "System Resources Warning" "$warnings" "WARNING"
    fi
    
    echo "$status|$cpu_usage|$memory_usage|$disk_usage|$load_average|$warnings"
}

# Проверка состояния whitelist
check_whitelist_status() {
    log_message "INFO" "Checking whitelist status"
    
    local whitelist_count=0
    local expired_count=0
    local status="OK"
    local error_message=""
    
    # Попытка получить количество записей через PHP скрипт
    if [ -f "/usr/local/bin/get_whitelist_count.php" ]; then
        local php_result=$(php /usr/local/bin/get_whitelist_count.php 2>/dev/null)
        
        if [ $? -eq 0 ] && [[ "$php_result" =~ ^[0-9]+\|[0-9]+$ ]]; then
            whitelist_count=$(echo "$php_result" | cut -d'|' -f1)
            expired_count=$(echo "$php_result" | cut -d'|' -f2)
            
            log_message "INFO" "Whitelist status: Active=$whitelist_count, Expired=$expired_count"
            
            # Проверка аномалий
            if [ $whitelist_count -gt 200 ]; then
                status="WARNING"
                error_message="Unusually high number of whitelist entries: $whitelist_count"
            elif [ $expired_count -gt 50 ]; then
                status="WARNING"
                error_message="High number of expired entries: $expired_count"
            fi
            
        else
            status="ERROR"
            error_message="Failed to get whitelist status from PHP script"
            log_message "ERROR" "$error_message"
        fi
    else
        status="ERROR"
        error_message="Whitelist count script not found"
        log_message "ERROR" "$error_message"
    fi
    
    echo "$status|$whitelist_count|$expired_count|$error_message"
}

# Проверка логов на ошибки
check_recent_errors() {
    log_message "INFO" "Checking recent errors in logs"
    
    local error_count=0
    local critical_count=0
    local recent_errors=""
    local status="OK"
    
    # Проверяем логи за последний час
    local cutoff_time=$(date -d '1 hour ago' '+%Y-%m-%d %H:%M:%S')
    
    # Проверка логов NGINX
    if [ -f "/var/log/nginx/sysadministrator_error.log" ]; then
        local nginx_errors=$(grep "$(date '+%Y/%m/%d %H:')" /var/log/nginx/sysadministrator_error.log 2>/dev/null | wc -l)
        error_count=$((error_count + nginx_errors))
    fi
    
    # Проверка логов PHP-FPM
    if [ -f "/var/log/php8.3-fpm/sysadministrator-error.log" ]; then
        local php_errors=$(grep "$(date '+%d-%b-%Y %H:')" /var/log/php8.3-fpm/sysadministrator-error.log 2>/dev/null | wc -l)
        error_count=$((error_count + php_errors))
    fi
    
    # Проверка системных логов
    local system_errors=$(journalctl --since="1 hour ago" --priority=err --no-pager -q | wc -l)
    error_count=$((error_count + system_errors))
    
    # Проверка критических ошибок
    critical_count=$(journalctl --since="1 hour ago" --priority=crit --no-pager -q | wc -l)
    
    log_message "INFO" "Recent errors: Total=$error_count, Critical=$critical_count"
    
    # Определение статуса
    if [ $critical_count -gt 0 ]; then
        status="CRITICAL"
        recent_errors="$critical_count critical errors in last hour"
        send_alert "Critical Errors Detected" "$recent_errors" "CRITICAL"
    elif [ $error_count -gt 20 ]; then
        status="WARNING"
        recent_errors="$error_count errors in last hour"
    fi
    
    echo "$status|$error_count|$critical_count|$recent_errors"
}

# Генерация JSON статуса
generate_status_json() {
    local mikrotik_result="$1"
    local connections_result="$2"
    local webserver_result="$3"
    local resources_result="$4"
    local whitelist_result="$5"
    local errors_result="$6"
    
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    local overall_status="OK"
    
    # Определение общего статуса
    if echo "$mikrotik_result $webserver_result $resources_result $whitelist_result $errors_result" | grep -q "CRITICAL"; then
        overall_status="CRITICAL"
    elif echo "$mikrotik_result $webserver_result $resources_result $whitelist_result $errors_result" | grep -q "ERROR"; then
        overall_status="ERROR"
    elif echo "$mikrotik_result $webserver_result $resources_result $whitelist_result $errors_result" | grep -q "WARNING"; then
        overall_status="WARNING"
    fi
    
    # Создание JSON
    cat > "$STATUS_FILE" << EOF
{
    "timestamp": "$timestamp",
    "overall_status": "$overall_status",
    "mikrotik": {
        "status": "$(echo "$mikrotik_result" | cut -d'|' -f1)",
        "response_time": "$(echo "$mikrotik_result" | cut -d'|' -f2)",
        "error": "$(echo "$mikrotik_result" | cut -d'|' -f3)"
    },
    "connections": {
        "total": $(echo "$connections_result" | cut -d'|' -f1),
        "rdp": $(echo "$connections_result" | cut -d'|' -f2),
        "special_ports": $(echo "$connections_result" | cut -d'|' -f3),
        "details": "$(echo "$connections_result" | cut -d'|' -f4)"
    },
    "webserver": {
        "nginx_status": "$(echo "$webserver_result" | cut -d'|' -f1)",
        "phpfpm_status": "$(echo "$webserver_result" | cut -d'|' -f2)",
        "http_code": "$(echo "$webserver_result" | cut -d'|' -f3)",
        "response_time": "$(echo "$webserver_result" | cut -d'|' -f4)",
        "error": "$(echo "$webserver_result" | cut -d'|' -f5)"
    },
    "resources": {
        "status": "$(echo "$resources_result" | cut -d'|' -f1)",
        "cpu_usage": "$(echo "$resources_result" | cut -d'|' -f2)",
        "memory_usage": "$(echo "$resources_result" | cut -d'|' -f3)",
        "disk_usage": "$(echo "$resources_result" | cut -d'|' -f4)",
        "load_average": "$(echo "$resources_result" | cut -d'|' -f5)",
        "warnings": "$(echo "$resources_result" | cut -d'|' -f6)"
    },
    "whitelist": {
        "status": "$(echo "$whitelist_result" | cut -d'|' -f1)",
        "active_count": $(echo "$whitelist_result" | cut -d'|' -f2),
        "expired_count": $(echo "$whitelist_result" | cut -d'|' -f3),
        "error": "$(echo "$whitelist_result" | cut -d'|' -f4)"
    },
    "errors": {
        "status": "$(echo "$errors_result" | cut -d'|' -f1)",
        "total_errors": $(echo "$errors_result" | cut -d'|' -f2),
        "critical_errors": $(echo "$errors_result" | cut -d'|' -f3),
        "details": "$(echo "$errors_result" | cut -d'|' -f4)"
    }
}
EOF
    
    log_message "INFO" "Status JSON generated with overall status: $overall_status"
}

# Основная функция мониторинга
main_monitoring() {
    log_message "INFO" "Starting system monitoring cycle"
    
    # Выполнение всех проверок
    local mikrotik_result=$(check_mikrotik_connectivity)
    local connections_result=$(monitor_active_connections)
    local webserver_result=$(check_web_server)
    local resources_result=$(monitor_system_resources)
    local whitelist_result=$(check_whitelist_status)
    local errors_result=$(check_recent_errors)
    
    # Генерация статуса
    generate_status_json "$mikrotik_result" "$connections_result" "$webserver_result" "$resources_result" "$whitelist_result" "$errors_result"
    
    log_message "INFO" "Monitoring cycle completed"
}

# Обработка аргументов командной строки
case "${1:-full}" in
    "mikrotik")
        check_mikrotik_connectivity
        ;;
    "connections")
        monitor_active_connections
        ;;
    "webserver")
        check_web_server
        ;;
    "resources")
        monitor_system_resources
        ;;
    "whitelist")
        check_whitelist_status
        ;;
    "errors")
        check_recent_errors
        ;;
    "status")
        if [ -f "$STATUS_FILE" ]; then
            cat "$STATUS_FILE"
        else
            echo "Status file not found"
            exit 1
        fi
        ;;
    "full"|"")
        main_monitoring
        ;;
    *)
        echo "Usage: $0 {mikrotik|connections|webserver|resources|whitelist|errors|status|full}"
        echo ""
        echo "Commands:"
        echo "  mikrotik     - Check MikroTik connectivity"
        echo "  connections  - Monitor active connections"
        echo "  webserver    - Check web server status"
        echo "  resources    - Monitor system resources"
        echo "  whitelist    - Check whitelist status"
        echo "  errors       - Check recent errors"
        echo "  status       - Show current status JSON"
        echo "  full         - Run all checks (default)"
        exit 1
        ;;
esac

8.4. Резервное копирование конфигураций

Автоматическое резервное копирование обеспечивает быстрое восстановление системы в случае сбоев:

#!/bin/bash
# /usr/local/bin/backup_configs.sh
# Скрипт резервного копирования конфигураций

# Конфигурация
BACKUP_DIR="/var/backups/sysadministrator"
REMOTE_BACKUP_HOST="backup.sysadministrator.ru"
REMOTE_BACKUP_USER="backup"
REMOTE_BACKUP_PATH="/backups/mikrotik-whitelist"
RETENTION_DAYS=30
LOG_FILE="/var/log/backup.log"
ALERT_EMAIL="admin@sysadministrator.ru"
MIKROTIK_HOST="192.168.1.1"
MIKROTIK_USER="backup_user"
ENCRYPTION_KEY="/etc/ssl/private/backup.key"

# Создание директорий для бэкапов
mkdir -p "$BACKUP_DIR"/{daily,weekly,monthly,configs,databases,logs}

log_message() {
    local level="$1"
    local message="$2"
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    
    echo "[$timestamp] [$level] $message" >> "$LOG_FILE"
    echo "[$timestamp] [$level] $message"
}

send_notification() {
    local subject="$1"
    local message="$2"
    local priority="${3:-INFO}"
    
    if command -v mail >/dev/null 2>&1; then
        echo "$message" | mail -s "$subject" "$ALERT_EMAIL"
    fi
    
    log_message "$priority" "$subject: $message"
}

# Резервное копирование конфигурации MikroTik
backup_mikrotik_config() {
    log_message "INFO" "Starting MikroTik configuration backup"
    
    local backup_file="$BACKUP_DIR/configs/mikrotik_config_$(date +%Y%m%d_%H%M%S).rsc"
    local temp_file=$(mktemp)
    local success=false
    
    # Создание SSH команд для экспорта конфигурации
    cat > "$temp_file" << 'EOF'
/export
/ip firewall address-list export
/ip firewall filter export
/ip firewall nat export
/user export
/system script export
EOF
    
    # Выполнение команд через SSH
    if ssh -o ConnectTimeout=30 -o StrictHostKeyChecking=no \
           -i /etc/ssh/mikrotik_backup_key \
           "$MIKROTIK_USER@$MIKROTIK_HOST" < "$temp_file" > "$backup_file" 2>/dev/null; then
        
        success=true
        local file_size=$(stat -c%s "$backup_file")
        log_message "INFO" "MikroTik config backup completed successfully ($file_size bytes)"
        
        # Проверка содержимого бэкапа
        if [ "$file_size" -lt 1000 ]; then
            log_message "WARNING" "MikroTik backup file seems too small, may be incomplete"
            success=false
        fi
        
    else
        log_message "ERROR" "Failed to backup MikroTik configuration"
        rm -f "$backup_file"
    fi
    
    rm -f "$temp_file"
    
    if [ "$success" = true ]; then
        # Шифрование бэкапа
        if [ -f "$ENCRYPTION_KEY" ]; then
            openssl enc -aes-256-cbc -salt -in "$backup_file" -out "${backup_file}.enc" -pass file:"$ENCRYPTION_KEY"
            rm "$backup_file"
            backup_file="${backup_file}.enc"
            log_message "INFO" "MikroTik backup encrypted successfully"
        fi
        
        echo "$backup_file"
    else
        return 1
    fi
}

# Резервное копирование конфигураций сервера
backup_server_configs() {
    log_message "INFO" "Starting server configuration backup"
    
    local backup_file="$BACKUP_DIR/configs/server_configs_$(date +%Y%m%d_%H%M%S).tar.gz"
    local temp_dir=$(mktemp -d)
    
    # Список файлов и директорий для резервного копирования
    local config_paths=(
        "/etc/nginx"
        "/etc/php/8.3"
        "/etc/ssl"
        "/etc/cron.d"
        "/etc/crontab"
        "/etc/logrotate.d"
        "/etc/rsyslog.d"
        "/var/www/html/sysadministrator/config"
        "/usr/local/bin"
        "/etc/systemd/system"
    )
    
    # Создание временной структуры бэкапа
    mkdir -p "$temp_dir/server_config"
    
    for config_path in "${config_paths[@]}"; do
        if [ -e "$config_path" ]; then
            local dest_path="$temp_dir/server_config$(dirname "$config_path")"
            mkdir -p "$dest_path"
            cp -r "$config_path" "$dest_path/" 2>/dev/null || {
                log_message "WARNING" "Failed to backup $config_path"
            }
        else
            log_message "WARNING" "Config path not found: $config_path"
        fi
    done
    
    # Добавление метаданных
    cat > "$temp_dir/server_config/backup_info.txt" << EOF
Backup created: $(date)
Server hostname: $(hostname)
System info: $(uname -a)
Backup script version: 1.0
Backup type: server_configs
EOF
    
    # Создание архива
    if tar -czf "$backup_file" -C "$temp_dir" server_config/; then
        local file_size=$(stat -c%s "$backup_file")
        log_message "INFO" "Server configs backup completed successfully ($file_size bytes)"
        
        # Шифрование
        if [ -f "$ENCRYPTION_KEY" ]; then
            openssl enc -aes-256-cbc -salt -in "$backup_file" -out "${backup_file}.enc" -pass file:"$ENCRYPTION_KEY"
            rm "$backup_file"
            backup_file="${backup_file}.enc"
        fi
        
        echo "$backup_file"
    else
        log_message "ERROR" "Failed to create server configs archive"
        rm -rf "$temp_dir"
        return 1
    fi
    
    rm -rf "$temp_dir"
}

# Резервное копирование баз данных
backup_databases() {
    log_message "INFO" "Starting database backup"
    
    local backup_file="$BACKUP_DIR/databases/databases_$(date +%Y%m%d_%H%M%S).tar.gz"
    local temp_dir=$(mktemp -d)
    
    mkdir -p "$temp_dir/databases"
    
    # Список баз данных для резервного копирования
    local db_paths=(
        "/var/lib/mikrotik"
        "/var/lib/php/sessions"
        "/tmp/user_requests.json"
        "/tmp/connection_stats.json"
        "/var/log/cleanup_statistics.json"
    )
    
    for db_path in "${db_paths[@]}"; do
        if [ -e "$db_path" ]; then
            if [ -d "$db_path" ]; then
                cp -r "$db_path" "$temp_dir/databases/"
            else
                cp "$db_path" "$temp_dir/databases/"
            fi
            log_message "INFO" "Backed up database/data: $db_path"
        fi
    done
    
    # Экспорт MySQL/MariaDB баз данных если они есть
    if command -v mysqldump >/dev/null 2>&1; then
        local mysql_backup="$temp_dir/databases/mysql_dump.sql"
        
        # Попытка создать дамп всех баз данных
        if mysqldump --all-databases --single-transaction --routines --triggers > "$mysql_backup" 2>/dev/null; then
            log_message "INFO" "MySQL databases backed up successfully"
        else
            log_message "WARNING" "MySQL backup failed or no databases found"
            rm -f "$mysql_backup"
        fi
    fi
    
    # Создание архива баз данных
    if tar -czf "$backup_file" -C "$temp_dir" databases/; then
        local file_size=$(stat -c%s "$backup_file")
        log_message "INFO" "Database backup completed successfully ($file_size bytes)"
        
        # Шифрование
        if [ -f "$ENCRYPTION_KEY" ]; then
            openssl enc -aes-256-cbc -salt -in "$backup_file" -out "${backup_file}.enc" -pass file:"$ENCRYPTION_KEY"
            rm "$backup_file"
            backup_file="${backup_file}.enc"
        fi
        
        echo "$backup_file"
    else
        log_message "ERROR" "Failed to create database backup archive"
        rm -rf "$temp_dir"
        return 1
    fi
    
    rm -rf "$temp_dir"
}

# Резервное копирование логов
backup_logs() {
    log_message "INFO" "Starting logs backup"
    
    local backup_file="$BACKUP_DIR/logs/logs_$(date +%Y%m%d_%H%M%S).tar.gz"
    local temp_dir=$(mktemp -d)
    
    mkdir -p "$temp_dir/logs"
    
    # Список директорий с логами
    local log_dirs=(
        "/var/log/nginx"
        "/var/log/php8.3-fpm"
        "/var/log/sysadministrator"
        "/var/log/mikrotik"
    )
    
    # Копирование логов (только за последние 7 дней)
    for log_dir in "${log_dirs[@]}"; do
        if [ -d "$log_dir" ]; then
            local dest_dir="$temp_dir/logs/$(basename "$log_dir")"
            mkdir -p "$dest_dir"
            
            # Копируем только недавние логи
            find "$log_dir" -name "*.log" -mtime -7 -exec cp {} "$dest_dir/" \; 2>/dev/null
            find "$log_dir" -name "*.log.*" -mtime -7 -exec cp {} "$dest_dir/" \; 2>/dev/null
            
            log_message "INFO" "Backed up recent logs from: $log_dir"
        fi
    done
    
    # Системные логи
    journalctl --since="7 days ago" --output=export > "$temp_dir/logs/systemd.journal" 2>/dev/null
    
    # Создание архива логов
    if tar -czf "$backup_file" -C "$temp_dir" logs/; then
        local file_size=$(stat -c%s "$backup_file")
        log_message "INFO" "Logs backup completed successfully ($file_size bytes)"
        echo "$backup_file"
    else
        log_message "ERROR" "Failed to create logs backup archive"
        rm -rf "$temp_dir"
        return 1
    fi
    
    rm -rf "$temp_dir"
}

# Отправка бэкапов на удаленный сервер
upload_to_remote() {
    local backup_files=("$@")
    
    if [ ${#backup_files[@]} -eq 0 ]; then
        log_message "WARNING" "No backup files to upload"
        return 1
    fi
    
    log_message "INFO" "Starting remote backup upload"
    
    # Проверка доступности удаленного сервера
    if ! ssh -o ConnectTimeout=10 -o BatchMode=yes \
           "$REMOTE_BACKUP_USER@$REMOTE_BACKUP_HOST" "echo 'Remote server accessible'" >/dev/null 2>&1; then
        log_message "ERROR" "Remote backup server not accessible"
        return 1
    fi
    
    # Создание удаленной директории
    local remote_date_dir="$REMOTE_BACKUP_PATH/$(date +%Y/%m/%d)"
    ssh "$REMOTE_BACKUP_USER@$REMOTE_BACKUP_HOST" "mkdir -p '$remote_date_dir'" || {
        log_message "ERROR" "Failed to create remote backup directory"
        return 1
    }
    
    # Загрузка файлов
    local upload_success=0
    local upload_total=${#backup_files[@]}
    
    for backup_file in "${backup_files[@]}"; do
        if [ -f "$backup_file" ]; then
            local filename=$(basename "$backup_file")
            
            if scp -o ConnectTimeout=60 "$backup_file" \
                   "$REMOTE_BACKUP_USER@$REMOTE_BACKUP_HOST:$remote_date_dir/$filename"; then
                log_message "INFO" "Successfully uploaded: $filename"
                ((upload_success++))
            else
                log_message "ERROR" "Failed to upload: $filename"
            fi
        else
            log_message "WARNING" "Backup file not found: $backup_file"
        fi
    done
    
    log_message "INFO" "Remote upload completed: $upload_success/$upload_total files"
    
    if [ $upload_success -eq $upload_total ]; then
        return 0
    else
        return 1
    fi
}

# Очистка старых бэкапов
cleanup_old_backups() {
    log_message "INFO" "Starting cleanup of old backups"
    
    local cleaned_files=0
    
    # Локальная очистка
    for backup_type in daily weekly monthly configs databases logs; do
        local backup_path="$BACKUP_DIR/$backup_type"
        
        if [ -d "$backup_path" ]; then
            local old_files=$(find "$backup_path" -type f -mtime +$RETENTION_DAYS)
            
            for old_file in $old_files; do
                if rm "$old_file"; then
                    ((cleaned_files++))
                    log_message "INFO" "Removed old backup: $(basename "$old_file")"
                fi
            done
        fi
    done
    
    # Удаленная очистка
    if ssh -o ConnectTimeout=10 -o BatchMode=yes \
           "$REMOTE_BACKUP_USER@$REMOTE_BACKUP_HOST" \
           "find '$REMOTE_BACKUP_PATH' -type f -mtime +$RETENTION_DAYS -delete" >/dev/null 2>&1; then
        log_message "INFO" "Remote backup cleanup completed"
    else
        log_message "WARNING" "Remote backup cleanup failed"
    fi
    
    log_message "INFO" "Cleanup completed: $cleaned_files local files removed"
}

# Проверка целостности бэкапов
verify_backups() {
    local backup_files=("$@")
    local verified=0
    local total=${#backup_files[@]}
    
    log_message "INFO" "Starting backup verification"
    
    for backup_file in "${backup_files[@]}"; do
        if [ -f "$backup_file" ]; then
            # Проверка архива
            if [[ "$backup_file" == *.tar.gz* ]]; then
                if tar -tzf "$backup_file" >/dev/null 2>&1; then
                    ((verified++))
                    log_message "INFO" "Backup verified: $(basename "$backup_file")"
                else
                    log_message "ERROR" "Backup corrupted: $(basename "$backup_file")"
                fi
            # Проверка зашифрованных файлов
            elif [[ "$backup_file" == *.enc ]]; then
                if [ -f "$ENCRYPTION_KEY" ]; then
                    if openssl enc -d -aes-256-cbc -in "$backup_file" -pass file:"$ENCRYPTION_KEY" | head -c 100 >/dev/null 2>&1; then
                        ((verified++))
                        log_message "INFO" "Encrypted backup verified: $(basename "$backup_file")"
                    else
                        log_message "ERROR" "Encrypted backup corrupted: $(basename "$backup_file")"
                    fi
                else
                    log_message "WARNING" "Cannot verify encrypted backup - key not found"
                fi
            # Простая проверка размера для других файлов
            else
                local file_size=$(stat -c%s "$backup_file")
                if [ "$file_size" -gt 100 ]; then
                    ((verified++))
                    log_message "INFO" "Backup file verified: $(basename "$backup_file")"
                else
                    log_message "ERROR" "Backup file too small: $(basename "$backup_file")"
                fi
            fi
        else
            log_message "ERROR" "Backup file not found: $backup_file"
        fi
    done
    
    log_message "INFO" "Backup verification completed: $verified/$total files verified"
    
    return $((total - verified))
}

# Генерация отчета о резервном копировании
generate_backup_report() {
    local backup_files=("$@")
    local report_file="$BACKUP_DIR/backup_report_$(date +%Y%m%d).txt"
    
    {
        echo "=========================================="
        echo "BACKUP REPORT - $(date)"
        echo "=========================================="
        echo ""
        echo "Backup Summary:"
        echo "- Total files created: ${#backup_files[@]}"
        echo "- Backup directory: $BACKUP_DIR"
        echo "- Retention period: $RETENTION_DAYS days"
        echo ""
        echo "Backup Files:"
        
        local total_size=0
        for backup_file in "${backup_files[@]}"; do
            if [ -f "$backup_file" ]; then
                local file_size=$(stat -c%s "$backup_file")
                local file_size_mb=$((file_size / 1024 / 1024))
                total_size=$((total_size + file_size))
                echo "- $(basename "$backup_file"): ${file_size_mb} MB"
            fi
        done
        
        local total_size_mb=$((total_size / 1024 / 1024))
        echo ""
        echo "Total backup size: ${total_size_mb} MB"
        echo ""
        echo "System Information:"
        echo "- Hostname: $(hostname)"
        echo "- Disk usage: $(df -h / | tail -1 | awk '{print $5}')"
        echo "- Available space: $(df -h "$BACKUP_DIR" | tail -1 | awk '{print $4}')"
        echo ""
        echo "=========================================="
        
    } > "$report_file"
    
    log_message "INFO" "Backup report generated: $report_file"
    echo "$report_file"
}

# Основная функция резервного копирования
main_backup() {
    log_message "INFO" "Starting backup process"
    
    local backup_files=()
    local errors=0
    local start_time=$(date +%s)
    
    # MikroTik конфигурация
    if mikrotik_backup=$(backup_mikrotik_config); then
        backup_files+=("$mikrotik_backup")
    else
        ((errors++))
    fi
    
    # Конфигурации сервера
    if server_backup=$(backup_server_configs); then
        backup_files+=("$server_backup")
    else
        ((errors++))
    fi
    
    # Базы данных
    if db_backup=$(backup_databases); then
        backup_files+=("$db_backup")
    else
        ((errors++))
    fi
    
    # Логи (только в определенные дни недели)
    local day_of_week=$(date +%u)
    if [ "$day_of_week" -eq 7 ]; then  # Воскресенье
        if logs_backup=$(backup_logs); then
            backup_files+=("$logs_backup")
        else
            ((errors++))
        fi
    fi
    
    # Проверка целостности
    verify_backups "${backup_files[@]}"
    local verify_errors=$?
    errors=$((errors + verify_errors))
    
    # Загрузка на удаленный сервер
    if [ ${#backup_files[@]} -gt 0 ]; then
        if ! upload_to_remote "${backup_files[@]}"; then
            ((errors++))
        fi
    fi
    
    # Очистка старых бэкапов
    cleanup_old_backups
    
    # Генерация отчета
    local report_file=$(generate_backup_report "${backup_files[@]}")
    
    local end_time=$(date +%s)
    local duration=$((end_time - start_time))
    
    # Итоговая статистика
    local status="SUCCESS"
    if [ $errors -gt 0 ]; then
        status="PARTIAL_FAILURE"
        if [ $errors -eq 4 ]; then  # Все основные бэкапы провалились
            status="FAILURE"
        fi
    fi
    
    local summary="Backup completed with status: $status"
    summary="$summary\nFiles created: ${#backup_files[@]}"
    summary="$summary\nErrors: $errors"
    summary="$summary\nDuration: ${duration}s"
    
    log_message "INFO" "$summary"
    
    # Отправка уведомления
    if [ "$status" = "FAILURE" ]; then
        send_notification "Backup Failed" "$summary" "ERROR"
    elif [ "$status" = "PARTIAL_FAILURE" ]; then
        send_notification "Backup Partial Failure" "$summary" "WARNING"
    else
        # Отправляем успешные уведомления только раз в неделю
        if [ "$day_of_week" -eq 1 ]; then  # Понедельник
            send_notification "Weekly Backup Report" "$summary" "INFO"
        fi
    fi
    
    return $errors
}

# Обработка аргументов командной строки
case "${1:-full}" in
    "mikrotik")
        backup_mikrotik_config
        ;;
    "server")
        backup_server_configs
        ;;
    "database")
        backup_databases
        ;;
    "logs")
        backup_logs
        ;;
    "cleanup")
        cleanup_old_backups
        ;;
    "verify")
        shift
        verify_backups "$@"
        ;;
    "upload")
        shift
        upload_to_remote "$@"
        ;;
    "full"|"")
        main_backup
        ;;
    *)
        echo "Usage: $0 {mikrotik|server|database|logs|cleanup|verify|upload|full}"
        echo ""
        echo "Commands:"
        echo "  mikrotik   - Backup MikroTik configuration only"
        echo "  server     - Backup server configurations only"  
        echo "  database   - Backup databases only"
        echo "  logs       - Backup logs only"
        echo "  cleanup    - Clean up old backups"
        echo "  verify     - Verify backup integrity"
        echo "  upload     - Upload backups to remote server"
        echo "  full       - Run full backup process (default)"
        exit 1
        ;;
esac

9. Обеспечение безопасности

9.1. Защита от атак и злоупотреблений

Многоуровневая система защиты обеспечивает надежную защиту от различных типов атак и злоупотреблений:[7][3]

<?php
// /var/www/html/sysadministrator/classes/SecurityDefense.php
// Комплексная система защиты от атак

class SecurityDefense {
    private $config;
    private $logger;
    private $redis; // Для высокопроизводительного кэширования
    private $geoip; // Для географической фильтрации
    private $attackPatterns;
    private $whitelistIPs;
    
    // Константы для типов атак
    const ATTACK_BRUTEFORCE = 'bruteforce';
    const ATTACK_DDOS = 'ddos';
    const ATTACK_INJECTION = 'injection';
    const ATTACK_XSS = 'xss';
    const ATTACK_SCAN = 'scan';
    const ATTACK_BOT = 'bot';
    
    // Уровни блокировки
    const BLOCK_TEMPORARY = 'temporary';
    const BLOCK_PERMANENT = 'permanent';
    const BLOCK_CAPTCHA = 'captcha';
    
    public function __construct($config) {
        $this->config = $config;
        $this->logger = new SystemLogger('security-defense');
        
        $this->initializePatterns();
        $this->initializeWhitelist();
        $this->initializeGeoIP();
        
        // Инициализация Redis для быстрого кэширования
        if (extension_loaded('redis')) {
            try {
                $this->redis = new Redis();
                $this->redis->connect('127.0.0.1', 6379);
            } catch (Exception $e) {
                $this->logger->warning('Redis not available, using file cache');
            }
        }
    }
    
    /**
     * Основная функция проверки безопасности
     */
    public function checkSecurity($request = null) {
        $request = $request ?: $this->getCurrentRequest();
        
        // Проверка IP репутации
        if (!$this->checkIPReputation($request['ip'])) {
            return $this->blockRequest('IP reputation check failed', self::BLOCK_PERMANENT);
        }
        
        // Проверка географических ограничений
        if (!$this->checkGeographicRestrictions($request['ip'])) {
            return $this->blockRequest('Geographic restrictions', self::BLOCK_PERMANENT);
        }
        
        // Проверка лимитов запросов
        if (!$this->checkRateLimit($request['ip'])) {
            return $this->blockRequest('Rate limit exceeded', self::BLOCK_TEMPORARY);
        }
        
        // Проверка на атаки брутфорса
        if ($this->detectBruteForce($request)) {
            return $this->blockRequest('Brute force attack detected', self::BLOCK_TEMPORARY);
        }
        
        // Проверка на SQL инъекции
        if ($this->detectSQLInjection($request)) {
            return $this->blockRequest('SQL injection attempt', self::BLOCK_PERMANENT);
        }
        
        // Проверка на XSS атаки
        if ($this->detectXSSAttack($request)) {
            return $this->blockRequest('XSS attack detected', self::BLOCK_TEMPORARY);
        }
        
        // Проверка на автоматизированные боты
        if ($this->detectMaliciousBot($request)) {
            return $this->blockRequest('Malicious bot detected', self::BLOCK_CAPTCHA);
        }
        
        // Проверка на сканирование портов/директорий
        if ($this->detectScanning($request)) {
            return $this->blockRequest('Scanning activity detected', self::BLOCK_TEMPORARY);
        }
        
        return ['allowed' => true, 'checks_passed' => true];
    }
    
    /**
     * Проверка репутации IP адреса
     */
    private function checkIPReputation($ip) {
        // Проверка в локальных blacklist'ах
        if ($this->isInBlacklist($ip)) {
            $this->logger->warning('IP in local blacklist', ['ip' => $ip]);
            return false;
        }
        
        // Проверка в whitelist'е администраторов
        if (in_array($ip, $this->whitelistIPs)) {
            return true;
        }
        
        // Проверка через внешние репутационные сервисы
        if ($this->config['security']['external_reputation_check'] ?? false) {
            return $this->checkExternalReputation($ip);
        }
        
        return true;
    }
    
    /**
     * Проверка через внешние репутационные сервисы
     */
    private function checkExternalReputation($ip) {
        $cacheKey = "reputation_check_$ip";
        
        // Проверяем кэш
        if ($this->redis) {
            $cachedResult = $this->redis->get($cacheKey);
            if ($cachedResult !== false) {
                return json_decode($cachedResult, true)['is_safe'];
            }
        }
        
        $reputationServices = [
            'abuseipdb' => $this->config['security']['abuseipdb_key'] ?? null,
            'virustotal' => $this->config['security']['virustotal_key'] ?? null,
        ];
        
        $isSafe = true;
        $threatScore = 0;
        
        foreach ($reputationServices as $service => $apiKey) {
            if (!$apiKey) continue;
            
            try {
                $result = $this->queryReputationService($service, $ip, $apiKey);
                
                if ($result['threat_score'] > 50) {
                    $isSafe = false;
                    $threatScore = max($threatScore, $result['threat_score']);
                }
                
            } catch (Exception $e) {
                $this->logger->error('Reputation service error', [
                    'service' => $service,
                    'ip' => $ip,
                    'error' => $e->getMessage()
                ]);
            }
        }
        
        // Кэшируем результат на 1 час
        if ($this->redis) {
            $cacheData = json_encode([
                'is_safe' => $isSafe,
                'threat_score' => $threatScore,
                'checked_at' => time()
            ]);
            $this->redis->setex($cacheKey, 3600, $cacheData);
        }
        
        if (!$isSafe) {
            $this->logger->warning('IP failed reputation check', [
                'ip' => $ip,
                'threat_score' => $threatScore
            ]);
        }
        
        return $isSafe;
    }
    
    /**
     * Запрос к сервису репутации
     */
    private function queryReputationService($service, $ip, $apiKey) {
        switch ($service) {
            case 'abuseipdb':
                return $this->queryAbuseIPDB($ip, $apiKey);
            case 'virustotal':
                return $this->queryVirusTotal($ip, $apiKey);
            default:
                throw new Exception("Unknown reputation service: $service");
        }
    }
    
    /**
     * Проверка через AbuseIPDB
     */
    private function queryAbuseIPDB($ip, $apiKey) {
        $url = "https://api.abuseipdb.com/api/v2/check";
        
        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT => 10,
            CURLOPT_HTTPHEADER => [
                "Key: $apiKey",
                "Accept: application/json"
            ],
            CURLOPT_POSTFIELDS => http_build_query([
                'ipAddress' => $ip,
                'maxAgeInDays' => 90,
                'verbose' => ''
            ])
        ]);
        
        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);
        
        if ($httpCode !== 200) {
            throw new Exception("AbuseIPDB API error: HTTP $httpCode");
        }
        
        $data = json_decode($response, true);
        
        return [
            'threat_score' => $data['data']['abuseConfidencePercentage'] ?? 0,
            'reports' => $data['data']['totalReports'] ?? 0
        ];
    }
    
    /**
     * Проверка географических ограничений
     */
    private function checkGeographicRestrictions($ip) {
        if (!$this->geoip) {
            return true; // Если GeoIP не доступен, пропускаем проверку
        }
        
        $allowedCountries = $this->config['security']['allowed_countries'] ?? [];
        $blockedCountries = $this->config['security']['blocked_countries'] ?? [];
        
```php
        if (empty($allowedCountries) && empty($blockedCountries)) {
            return true;
        }
        
        try {
            $country = $this->geoip->country($ip);
            $countryCode = $country->country->isoCode;
            
            // Если есть список разрешенных стран
            if (!empty($allowedCountries)) {
                if (!in_array($countryCode, $allowedCountries)) {
                    $this->logger->warning('IP from restricted country', [
                        'ip' => $ip,
                        'country' => $countryCode
                    ]);
                    return false;
                }
            }
            
            // Если IP из заблокированной страны
            if (in_array($countryCode, $blockedCountries)) {
                $this->logger->warning('IP from blocked country', [
                    'ip' => $ip,
                    'country' => $countryCode
                ]);
                return false;
            }
            
        } catch (Exception $e) {
            $this->logger->error('GeoIP lookup failed', [
                'ip' => $ip,
                'error' => $e->getMessage()
            ]);
        }
        
        return true;
    }
    
    /**
     * Проверка лимитов частоты запросов
     */
    private function checkRateLimit($ip) {
        $limits = $this->config['security']['rate_limits'] ?? [
            'requests_per_minute' => 60,
            'requests_per_hour' => 1000,
            'auth_attempts_per_hour' => 10
        ];
        
        $currentTime = time();
        
        // Проверка запросов в минуту
        $minuteKey = "rate_limit_minute_{$ip}_" . floor($currentTime / 60);
        $minuteCount = $this->getCounterValue($minuteKey);
        
        if ($minuteCount >= $limits['requests_per_minute']) {
            $this->logger->warning('Rate limit exceeded (per minute)', [
                'ip' => $ip,
                'count' => $minuteCount,
                'limit' => $limits['requests_per_minute']
            ]);
            return false;
        }
        
        // Проверка запросов в час
        $hourKey = "rate_limit_hour_{$ip}_" . floor($currentTime / 3600);
        $hourCount = $this->getCounterValue($hourKey);
        
        if ($hourCount >= $limits['requests_per_hour']) {
            $this->logger->warning('Rate limit exceeded (per hour)', [
                'ip' => $ip,
                'count' => $hourCount,
                'limit' => $limits['requests_per_hour']
            ]);
            return false;
        }
        
        // Увеличиваем счетчики
        $this->incrementCounter($minuteKey, 60);
        $this->incrementCounter($hourKey, 3600);
        
        return true;
    }
    
    /**
     * Детекция атак брутфорса
     */
    private function detectBruteForce($request) {
        // Проверяем только запросы с аутентификацией
        if (!isset($request['auth_attempt'])) {
            return false;
        }
        
        $ip = $request['ip'];
        $username = $request['username'] ?? 'unknown';
        
        // Проверка попыток по IP
        $ipAttemptsKey = "bruteforce_ip_{$ip}";
        $ipAttempts = $this->getCounterValue($ipAttemptsKey);
        
        // Проверка попыток по пользователю
        $userAttemptsKey = "bruteforce_user_{$username}";
        $userAttempts = $this->getCounterValue($userAttemptsKey);
        
        $maxAttempts = $this->config['security']['max_auth_attempts'] ?? 5;
        
        if ($ipAttempts >= $maxAttempts || $userAttempts >= $maxAttempts) {
            $this->logger->warning('Brute force attack detected', [
                'ip' => $ip,
                'username' => $username,
                'ip_attempts' => $ipAttempts,
                'user_attempts' => $userAttempts
            ]);
            
            // Блокируем IP на увеличивающееся время
            $blockTime = min(3600 * pow(2, floor($ipAttempts / $maxAttempts)), 86400);
            $this->addToBlacklist($ip, $blockTime, 'Brute force attack');
            
            return true;
        }
        
        // Увеличиваем счетчики при неудачной попытке
        if (!$request['auth_success']) {
            $this->incrementCounter($ipAttemptsKey, 3600);
            $this->incrementCounter($userAttemptsKey, 3600);
        } else {
            // Сбрасываем счетчики при успешной аутентификации
            $this->resetCounter($ipAttemptsKey);
            $this->resetCounter($userAttemptsKey);
        }
        
        return false;
    }
    
    /**
     * Детекция SQL инъекций
     */
    private function detectSQLInjection($request) {
        $sqlPatterns = [
            '/(\b(select|union|insert|update|delete|drop|create|alter|exec|execute)\b)/i',
            '/(\b(or|and)\s+\d+\s*=\s*\d+)/i',
            '/(\'|\"|`|\;|\-\-|\/\*|\*\/)/i',
            '/(\bwhere\s+\d+\s*=\s*\d+)/i',
            '/(\bunion\s+select)/i',
            '/(\bdrop\s+table)/i'
        ];
        
        $testData = array_merge(
            $request['get_params'] ?? [],
            $request['post_params'] ?? [],
            [$request['user_agent'] ?? ''],
            [$request['referer'] ?? '']
        );
        
        foreach ($testData as $value) {
            if (!is_string($value)) continue;
            
            foreach ($sqlPatterns as $pattern) {
                if (preg_match($pattern, $value)) {
                    $this->logger->warning('SQL injection attempt detected', [
                        'ip' => $request['ip'],
                        'pattern' => $pattern,
                        'value' => substr($value, 0, 200)
                    ]);
                    
                    return true;
                }
            }
        }
        
        return false;
    }
    
    /**
     * Детекция XSS атак
     */
    private function detectXSSAttack($request) {
        $xssPatterns = [
            '/<script[^>]*>.*?<\/script>/i',
            '/<iframe[^>]*>.*?<\/iframe>/i',
            '/javascript\s*:/i',
            '/on(load|error|click|mouseover|focus)\s*=/i',
            '/<img[^>]*src[^>]*javascript:/i',
            '/eval\s*\(/i',
            '/expression\s*\(/i'
        ];
        
        $testData = array_merge(
            $request['get_params'] ?? [],
            $request['post_params'] ?? []
        );
        
        foreach ($testData as $value) {
            if (!is_string($value)) continue;
            
            $decodedValue = html_entity_decode(urldecode($value));
            
            foreach ($xssPatterns as $pattern) {
                if (preg_match($pattern, $decodedValue)) {
                    $this->logger->warning('XSS attack attempt detected', [
                        'ip' => $request['ip'],
                        'pattern' => $pattern,
                        'value' => substr($decodedValue, 0, 200)
                    ]);
                    
                    return true;
                }
            }
        }
        
        return false;
    }
    
    /**
     * Детекция вредоносных ботов
     */
    private function detectMaliciousBot($request) {
        $userAgent = $request['user_agent'] ?? '';
        
        // Список подозрительных User-Agent'ов
        $maliciousPatterns = [
            '/nikto/i',
            '/sqlmap/i',
            '/nmap/i',
            '/masscan/i',
            '/zgrab/i',
            '/curl\/[0-9]/i', // Блокируем простые curl запросы
            '/python-requests/i',
            '/wget/i',
            '/scanner/i',
            '/crawler/i',
            '/bot.*attack/i'
        ];
        
        foreach ($maliciousPatterns as $pattern) {
            if (preg_match($pattern, $userAgent)) {
                $this->logger->warning('Malicious bot detected', [
                    'ip' => $request['ip'],
                    'user_agent' => $userAgent,
                    'pattern' => $pattern
                ]);
                
                return true;
            }
        }
        
        // Проверка на отсутствие User-Agent (подозрительно)
        if (empty($userAgent)) {
            $this->logger->info('Request without User-Agent', [
                'ip' => $request['ip']
            ]);
            
            return true;
        }
        
        // Проверка на слишком много запросов без Referer
        if (empty($request['referer'])) {
            $noRefererKey = "no_referer_{$request['ip']}";
            $noRefererCount = $this->getCounterValue($noRefererKey);
            
            if ($noRefererCount > 50) { // Более 50 запросов без referer в час
                return true;
            }
            
            $this->incrementCounter($noRefererKey, 3600);
        }
        
        return false;
    }
    
    /**
     * Детекция сканирования
     */
    private function detectScanning($request) {
        $uri = $request['uri'] ?? '';
        
        // Подозрительные пути
        $scanPatterns = [
            '/\/admin/i',
            '/\/phpmyadmin/i',
            '/\/wp-admin/i',
            '/\/\.env/i',
            '/\/\.git/i',
            '/\/config/i',
            '/\/backup/i',
            '/\/test/i',
            '/\/cgi-bin/i',
            '/\/robots\.txt/i',
            '/\/sitemap\.xml/i'
        ];
        
        foreach ($scanPatterns as $pattern) {
            if (preg_match($pattern, $uri)) {
                $scanKey = "scanning_{$request['ip']}";
                $this->incrementCounter($scanKey, 3600);
                
                $scanCount = $this->getCounterValue($scanKey);
                
                if ($scanCount >= 5) { // 5 подозрительных запросов в час
                    $this->logger->warning('Directory scanning detected', [
                        'ip' => $request['ip'],
                        'uri' => $uri,
                        'scan_count' => $scanCount
                    ]);
                    
                    return true;
                }
                
                break;
            }
        }
        
        return false;
    }
    
    /**
     * Блокировка запроса
     */
    private function blockRequest($reason, $blockType) {
        $ip = $this->getCurrentRequest()['ip'];
        
        switch ($blockType) {
            case self::BLOCK_PERMANENT:
                $this->addToBlacklist($ip, 86400 * 30, $reason); // 30 дней
                break;
            case self::BLOCK_TEMPORARY:
                $this->addToBlacklist($ip, 3600, $reason); // 1 час
                break;
            case self::BLOCK_CAPTCHA:
                $this->requireCaptcha($ip);
                break;
        }
        
        $this->logger->warning('Request blocked', [
            'ip' => $ip,
            'reason' => $reason,
            'block_type' => $blockType
        ]);
        
        return [
            'allowed' => false,
            'reason' => $reason,
            'block_type' => $blockType
        ];
    }
    
    /**
     * Получение текущего запроса
     */
    private function getCurrentRequest() {
        return [
            'ip' => $this->getRealUserIP(),
            'uri' => $_SERVER['REQUEST_URI'] ?? '',
            'method' => $_SERVER['REQUEST_METHOD'] ?? 'GET',
            'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
            'referer' => $_SERVER['HTTP_REFERER'] ?? '',
            'get_params' => $_GET,
            'post_params' => $_POST,
            'auth_attempt' => isset($_SERVER['PHP_AUTH_USER']),
            'username' => $_SERVER['PHP_AUTH_USER'] ?? null,
            'auth_success' => false // Будет установлено после проверки
        ];
    }
    
    /**
     * Инициализация шаблонов атак
     */
    private function initializePatterns() {
        $this->attackPatterns = [
            self::ATTACK_INJECTION => [
                'sql' => ['/(\b(select|union|insert|update|delete|drop|create|alter|exec|execute)\b)/i'],
                'xss' => ['/<script[^>]*>.*?<\/script>/i'],
                'lfi' => ['/\.\.[\/\\]/i'],
                'rfi' => ['/https?:\/\//i']
            ],
            self::ATTACK_SCAN => [
                'directory' => ['/\/admin/i', '/\/wp-admin/i', '/\/phpmyadmin/i'],
                'file' => ['/\/\.env/i', '/\/\.git/i', '/\/config/i']
            ]
        ];
    }
    
    /**
     * Инициализация белого списка
     */
    private function initializeWhitelist() {
        $this->whitelistIPs = $this->config['security']['whitelist_ips'] ?? [
            '127.0.0.1',
            '::1',
            '192.168.1.1'
        ];
    }
    
    /**
     * Инициализация GeoIP
     */
    private function initializeGeoIP() {
        if (class_exists('GeoIp2\Database\Reader')) {
            try {
                $this->geoip = new GeoIp2\Database\Reader('/usr/share/GeoIP/GeoLite2-Country.mmdb');
            } catch (Exception $e) {
                $this->logger->warning('GeoIP database not available');
            }
        }
    }
    
    /**
     * Вспомогательные методы для работы со счетчиками
     */
    private function getCounterValue($key) {
        if ($this->redis) {
            return (int)$this->redis->get($key);
        }
        
        $file = "/tmp/counter_$key";
        return file_exists($file) ? (int)file_get_contents($file) : 0;
    }
    
    private function incrementCounter($key, $ttl) {
        if ($this->redis) {
            $this->redis->incr($key);
            $this->redis->expire($key, $ttl);
        } else {
            $file = "/tmp/counter_$key";
            $value = $this->getCounterValue($key) + 1;
            file_put_contents($file, $value);
        }
    }
    
    private function resetCounter($key) {
        if ($this->redis) {
            $this->redis->del($key);
        } else {
            $file = "/tmp/counter_$key";
            if (file_exists($file)) {
                unlink($file);
            }
        }
    }
    
    private function getRealUserIP() {
        $ipKeys = ['HTTP_CF_CONNECTING_IP', 'HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_REAL_IP', 'REMOTE_ADDR'];
        
        foreach ($ipKeys as $key) {
            if (!empty($_SERVER[$key])) {
                $ip = trim(explode(',', $_SERVER[$key])[0]);
                if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
                    return $ip;
                }
            }
        }
        
        return $_SERVER['REMOTE_ADDR'] ?? 'unknown';
    }
}
?>

9.2. Ограничение частоты запросов

Система ограничения частоты запросов предотвращает DDoS атаки и злоупотребления ресурсами:[1]

<?php
// /var/www/html/sysadministrator/classes/RateLimiter.php
// Продвинутая система ограничения частоты запросов

class RateLimiter {
    private $redis;
    private $config;
    private $logger;
    private $algorithms = ['fixed_window', 'sliding_window', 'token_bucket', 'leaky_bucket'];
    
    public function __construct($config) {
        $this->config = $config;
        $this->logger = new SystemLogger('rate-limiter');
        
        // Инициализация Redis
        if (extension_loaded('redis')) {
            try {
                $this->redis = new Redis();
                $this->redis->connect('127.0.0.1', 6379);
                $this->redis->select(1); // Используем отдельную БД для rate limiting
            } catch (Exception $e) {
                $this->logger->error('Redis connection failed for rate limiter', [
                    'error' => $e->getMessage()
                ]);
                throw $e;
            }
        } else {
            throw new Exception('Redis extension required for rate limiting');
        }
    }
    
    /**
     * Проверка лимита запросов
     */
    public function checkLimit($identifier, $rules = null) {
        $rules = $rules ?: $this->getDefaultRules($identifier);
        
        foreach ($rules as $rule) {
            $result = $this->applyRule($identifier, $rule);
            
            if (!$result['allowed']) {
                $this->logger->warning('Rate limit exceeded', [
                    'identifier' => $identifier,
                    'rule' => $rule,
                    'current_count' => $result['current_count'],
                    'limit' => $rule['limit']
                ]);
                
                return $result;
            }
        }
        
        return ['allowed' => true, 'remaining' => $this->getRemainingRequests($identifier, $rules)];
    }
    
    /**
     * Применение правила ограничения
     */
    private function applyRule($identifier, $rule) {
        $algorithm = $rule['algorithm'] ?? 'fixed_window';
        
        switch ($algorithm) {
            case 'fixed_window':
                return $this->fixedWindowAlgorithm($identifier, $rule);
            case 'sliding_window':
                return $this->slidingWindowAlgorithm($identifier, $rule);
            case 'token_bucket':
                return $this->tokenBucketAlgorithm($identifier, $rule);
            case 'leaky_bucket':
                return $this->leakyBucketAlgorithm($identifier, $rule);
            default:
                throw new Exception("Unknown rate limiting algorithm: $algorithm");
        }
    }
    
    /**
     * Алгоритм фиксированного окна
     */
    private function fixedWindowAlgorithm($identifier, $rule) {
        $window = $rule['window'] ?? 3600; // 1 час по умолчанию
        $limit = $rule['limit'];
        $currentTime = time();
        $windowStart = floor($currentTime / $window) * $window;
        
        $key = "fixed_window:{$identifier}:{$rule['name']}:{$windowStart}";
        
        $currentCount = (int)$this->redis->get($key);
        
        if ($currentCount >= $limit) {
            return [
                'allowed' => false,
                'current_count' => $currentCount,
                'limit' => $limit,
                'reset_time' => $windowStart + $window,
                'algorithm' => 'fixed_window'
            ];
        }
        
        // Увеличиваем счетчик
        $this->redis->multi();
        $this->redis->incr($key);
        $this->redis->expire($key, $window);
        $this->redis->exec();
        
        return [
            'allowed' => true,
            'current_count' => $currentCount + 1,
            'limit' => $limit,
            'reset_time' => $windowStart + $window,
            'algorithm' => 'fixed_window'
        ];
    }
    
    /**
     * Алгоритм скользящего окна
     */
    private function slidingWindowAlgorithm($identifier, $rule) {
        $window = $rule['window'] ?? 3600;
        $limit = $rule['limit'];
        $currentTime = time();
        $windowStart = $currentTime - $window;
        
        $key = "sliding_window:{$identifier}:{$rule['name']}";
        
        // Очищаем старые записи
        $this->redis->zremrangebyscore($key, 0, $windowStart);
        
        // Получаем текущий счетчик
        $currentCount = $this->redis->zcard($key);
        
        if ($currentCount >= $limit) {
            return [
                'allowed' => false,
                'current_count' => $currentCount,
                'limit' => $limit,
                'reset_time' => $this->getOldestTimestamp($key) + $window,
                'algorithm' => 'sliding_window'
            ];
        }
        
        // Добавляем новую запись
        $this->redis->zadd($key, $currentTime, uniqid());
        $this->redis->expire($key, $window);
        
        return [
            'allowed' => true,
            'current_count' => $currentCount + 1,
            'limit' => $limit,
            'algorithm' => 'sliding_window'
        ];
    }
    
    /**
     * Алгоритм ведра токенов
     */
    private function tokenBucketAlgorithm($identifier, $rule) {
        $capacity = $rule['capacity'] ?? $rule['limit'];
        $refillRate = $rule['refill_rate'] ?? 1; // токенов в секунду
        $requested = $rule['cost'] ?? 1; // стоимость запроса в токенах
        
        $key = "token_bucket:{$identifier}:{$rule['name']}";
        $currentTime = microtime(true);
        
        // Получаем текущее состояние ведра
        $bucketData = $this->redis->hmget($key, ['tokens', 'last_refill']);
        $tokens = $bucketData[0] ? (float)$bucketData[0] : $capacity;
        $lastRefill = $bucketData[1] ? (float)$bucketData[1] : $currentTime;
        
        // Добавляем токены в зависимости от прошедшего времени
        $timePassed = $currentTime - $lastRefill;
        $tokensToAdd = $timePassed * $refillRate;
        $tokens = min($capacity, $tokens + $tokensToAdd);
        
        if ($tokens < $requested) {
            // Обновляем состояние ведра
            $this->redis->hmset($key, [
                'tokens' => $tokens,
                'last_refill' => $currentTime
            ]);
            $this->redis->expire($key, 3600);
            
            return [
                'allowed' => false,
                'tokens_available' => $tokens,
                'tokens_requested' => $requested,
                'capacity' => $capacity,
                'algorithm' => 'token_bucket'
            ];
        }
        
        // Используем токены
        $tokens -= $requested;
        
        $this->redis->hmset($key, [
            'tokens' => $tokens,
            'last_refill' => $currentTime
        ]);
        $this->redis->expire($key, 3600);
        
        return [
            'allowed' => true,
            'tokens_remaining' => $tokens,
            'capacity' => $capacity,
            'algorithm' => 'token_bucket'
        ];
    }
    
    /**
     * Алгоритм дырявого ведра
     */
    private function leakyBucketAlgorithm($identifier, $rule) {
        $capacity = $rule['capacity'] ?? $rule['limit'];
        $leakRate = $rule['leak_rate'] ?? 1; // запросов в секунду
        $currentTime = microtime(true);
        
        $key = "leaky_bucket:{$identifier}:{$rule['name']}";
        
        // Получаем текущее состояние ведра
        $bucketData = $this->redis->hmget($key, ['level', 'last_leak']);
        $level = $bucketData[0] ? (float)$bucketData[0] : 0;
        $lastLeak = $bucketData[1] ? (float)$bucketData[1] : $currentTime;
        
        // Уменьшаем уровень в зависимости от утечки
        $timePassed = $currentTime - $lastLeak;
        $leaked = $timePassed * $leakRate;
        $level = max(0, $level - $leaked);
        
        if ($level >= $capacity) {
            // Ведро переполнено
            $this->redis->hmset($key, [
                'level' => $level,
                'last_leak' => $currentTime
            ]);
            $this->redis->expire($key, 3600);
            
            return [
                'allowed' => false,
                'bucket_level' => $level,
                'capacity' => $capacity,
                'algorithm' => 'leaky_bucket'
            ];
        }
        
        // Добавляем запрос в ведро
        $level += 1;
        
        $this->redis->hmset($key, [
            'level' => $level,
            'last_leak' => $currentTime
        ]);
        $this->redis->expire($key, 3600);
        
        return [
            'allowed' => true,
            'bucket_level' => $level,
            'capacity' => $capacity,
            'algorithm' => 'leaky_bucket'
        ];
    }
    
    /**
     * Получение правил по умолчанию
     */
    private function getDefaultRules($identifier) {
        $rules = [];
        
        // Определяем тип идентификатора
        if (filter_var($identifier, FILTER_VALIDATE_IP)) {
            // Правила для IP адресов
            $rules = [
                [
                    'name' => 'ip_per_minute',
                    'algorithm' => 'sliding_window',
                    'window' => 60,
                    'limit' => 60
                ],
                [
                    'name' => 'ip_per_hour',
                    'algorithm' => 'fixed_window',
                    'window' => 3600,
                    'limit' => 1000
                ],
                [
                    'name' => 'ip_burst_protection',
                    'algorithm' => 'token_bucket',
                    'capacity' => 20,
                    'refill_rate' => 5
                ]
            ];
        } elseif (strpos($identifier, 'user:') === 0) {
            // Правила для пользователей
            $rules = [
                [
                    'name' => 'user_per_minute',
                    'algorithm' => 'sliding_window',
                    'window' => 60,
                    'limit' => 100
                ],
                [
                    'name' => 'user_per_hour',
                    'algorithm' => 'fixed_window',
                    'window' => 3600,
                    'limit' => 2000
                ]
            ];
        } elseif (strpos($identifier, 'api:') === 0) {
            // Правила для API
            $rules = [
                [
                    'name' => 'api_per_second',
                    'algorithm' => 'leaky_bucket',
                    'capacity' => 10,
                    'leak_rate' => 2
                ],
                [
                    'name' => 'api_per_hour',
                    'algorithm' => 'fixed_window',
                    'window' => 3600,
                    'limit' => 10000
                ]
            ];
        }
        
        return $rules;
    }
    
    /**
     * Получение оставшихся запросов
     */
    private function getRemainingRequests($identifier, $rules) {
        $remaining = [];
        
        foreach ($rules as $rule) {
            $key = $this->generateKey($identifier, $rule);
            
            switch ($rule['algorithm']) {
                case 'fixed_window':
                    $currentCount = (int)$this->redis->get($key);
                    $remaining[$rule['name']] = max(0, $rule['limit'] - $currentCount);
                    break;
                    
                case 'sliding_window':
                    $currentCount = $this->redis->zcard($key);
                    $remaining[$rule['name']] = max(0, $rule['limit'] - $currentCount);
                    break;
                    
                case 'token_bucket':
                    $bucketData = $this->redis->hmget($key, ['tokens']);
                    $tokens = $bucketData[0] ? (float)$bucketData[0] : $rule['capacity'];
                    $remaining[$rule['name']] = floor($tokens);
                    break;
                    
                case 'leaky_bucket':
                    $capacity = $rule['capacity'];
                    $bucketData = $this->redis->hmget($key, ['level']);
                    $level = $bucketData[0] ? (float)$bucketData[0] : 0;
                    $remaining[$rule['name']] = max(0, $capacity - $level);
                    break;
            }
        }
        
        return $remaining;
    }
    
    /**
     * Генерация ключа для Redis
     */
    private function generateKey($identifier, $rule) {
        $algorithm = $rule['algorithm'];
        $name = $rule['name'];
        
        switch ($algorithm) {
            case 'fixed_window':
                $window = $rule['window'] ?? 3600;
                $windowStart = floor(time() / $window) * $window;
                return "fixed_window:{$identifier}:{$name}:{$windowStart}";
                
            case 'sliding_window':
                return "sliding_window:{$identifier}:{$name}";
                
            case 'token_bucket':
                return "token_bucket:{$identifier}:{$name}";
                
            case 'leaky_bucket':
                return "leaky_bucket:{$identifier}:{$name}";
                
            default:
                return "rate_limit:{$identifier}:{$name}";
        }
    }
    
    /**
     * Получение самой старой временной метки
     */
    private function getOldestTimestamp($key) {
        $oldest = $this->redis->zrange($key, 0, 0, ['withscores' => true]);
        return empty($oldest) ? time() : (int)array_values($oldest)[0];
    }
    
    /**
     * Сброс лимитов для идентификатора
     */
    public function resetLimits($identifier) {
        $pattern = "*{$identifier}*";
        $keys = $this->redis->keys($pattern);
        
        if (!empty($keys)) {
            $this->redis->del($keys);
            $this->logger->info('Rate limits reset', ['identifier' => $identifier]);
        }
    }
    
    /**
     * Получение статистики rate limiting
     */
    public function getStatistics($period = 3600) {
        $stats = [
            'total_requests' => 0,
            'blocked_requests' => 0,
            'algorithms_usage' => [],
            'top_blocked_ips' => [],
            'period' => $period
        ];
        
        // Эта функция требует дополнительного логирования для полной статистики
        // В продакшене рекомендуется использовать отдельное хранилище метрик
        
        return $stats;
    }
    
    /**
     * Middleware для автоматической проверки лимитов
     */
    public static function middleware($config = []) {
        return function() use ($config) {
            $rateLimiter = new self($config);
            $ip = $rateLimiter->getRealUserIP();
            
            $result = $rateLimiter->checkLimit($ip);
            
            if (!$result['allowed']) {
                http_response_code(429);
                header('Content-Type: application/json');
                header('Retry-After: 60');
                
                echo json_encode([
                    'error' => 'Rate limit exceeded',
                    'message' => 'Too many requests. Please try again later.',
                    'retry_after' => 60
                ]);
                
                exit;
            }
            
            // Добавляем заголовки с информацией о лимитах
            if (isset($result['remaining'])) {
                foreach ($result['remaining'] as $ruleName => $remaining) {
                    header("X-RateLimit-{$ruleName}: {$remaining}");
                }
            }
        };
    }
    
    private function getRealUserIP() {
        $ipKeys = ['HTTP_CF_CONNECTING_IP', 'HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_REAL_IP', 'REMOTE_ADDR'];
        
        foreach ($ipKeys as $key) {
            if (!empty($_SERVER[$key])) {
                $ip = trim(explode(',', $_SERVER[$key])[0]);
                if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
                    return $ip;
                }
            }
        }
        
        return $_SERVER['REMOTE_ADDR'] ?? 'unknown';
    }
}
?>

9.3. Аудит безопасности

Система аудита безопасности обеспечивает комплексный анализ защищенности системы:

#!/bin/bash
# /usr/local/bin/security_audit.sh
# Комплексный аудит безопасности системы

# Конфигурация
AUDIT_DIR="/var/log/security_audit"
REPORT_FILE="$AUDIT_DIR/audit_report_$(date +%Y%m%d_%H%M%S).json"
ALERT_EMAIL="security@sysadministrator.ru"
CRITICAL_ISSUES_THRESHOLD=5
HIGH_ISSUES_THRESHOLD=10

# Создание директории для аудита
mkdir -p "$AUDIT_DIR"

# Цвета для вывода
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'

log_audit() {
    local level="$1"
    local message="$2"
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    
    echo "[$timestamp] [$level] $message"
    echo "[$timestamp] [$level] $message" >> "$AUDIT_DIR/audit.log"
}

# Проверка конфигурации NGINX
audit_nginx_security() {
    log_audit "INFO" "Starting NGINX security audit"
    
    local issues=()
    local severity_counts=(0 0 0 0) # low, medium, high, critical
    
    # Проверка наличия security headers
    local config_file="/etc/nginx/sites-available/sysadministrator.ru"
    
    if [ -f "$config_file" ]; then
        # Проверка X-Frame-Options
        if ! grep -q "X-Frame-Options" "$config_file"; then
            issues+=('{"type":"missing_header","severity":"medium","description":"X-Frame-Options header not configured","file":"'$config_file'"}')
            ((severity_counts[1]++))
        fi
        
        # Проверка X-Content-Type-Options
        if ! grep -q "X-Content-Type-Options" "$config_file"; then
            issues+=('{"type":"missing_header","severity":"medium","description":"X-Content-Type-Options header not configured","file":"'$config_file'"}')
            ((severity_counts[1]++))
        fi
        
        # Проверка X-XSS-Protection
        if ! grep -q "X-XSS-Protection" "$config_file"; then
            issues+=('{"type":"missing_header","severity":"medium","description":"X-XSS-Protection header not configured","file":"'$config_file'"}')
            ((severity_counts[1]++))
        fi
        
        # Проверка HSTS
        if ! grep -q "Strict-Transport-Security" "$config_file"; then
            issues+=('{"type":"missing_header","severity":"high","description":"HSTS header not configured","file":"'$config_file'"}')
            ((severity_counts[2]++))
        fi
        
        # Проверка server_tokens
        if ! grep -q "server_tokens off" "$config_file"; then
            issues+=('{"type":"information_disclosure","severity":"low","description":"Server tokens not disabled","file":"'$config_file'"}')
            ((severity_counts[0]++))
        fi
        
        # Проверка SSL конфигурации
        if grep -q "ssl_protocols.*TLSv1\.0\|ssl_protocols.*TLSv1\.1" "$config_file"; then
            issues+=('{"type":"weak_ssl","severity":"high","description":"Weak SSL/TLS protocols enabled","file":"'$config_file'"}')
            ((severity_counts[2]++))
        fi
        
        # Проверка client_max_body_size
        local max_body_size=$(grep "client_max_body_size" "$config_file" | awk '{print $2}' | sed 's/;//')
        if [ -z "$max_body_size" ] || [[ "$max_body_size" =~ [0-9]+[gG] ]]; then
            issues+=('{"type":"dos_protection","severity":"medium","description":"Large client_max_body_size may allow DoS attacks","file":"'$config_file'"}')
            ((severity_counts[1]++))
        fi
        
    else
        issues+=('{"type":"missing_config","severity":"critical","description":"NGINX configuration file not found","file":"'$config_file'"}')
        ((severity_counts[3]++))
    fi
    
    # Проверка прав доступа к файлам конфигурации
    if [ -f "$config_file" ]; then
        local file_perms=$(stat -c "%a" "$config_file")
        if [ "$file_perms" != "644" ] && [ "$file_perms" != "640" ]; then
            issues+=('{"type":"file_permissions","severity":"medium","description":"Incorrect permissions on NGINX config","file":"'$config_file'","permissions":"'$file_perms'"}')
            ((severity_counts[1]++))
        fi
    fi
    
    # Возвращаем результат в JSON формате
    local nginx_audit=$(printf '{"component":"nginx","issues":[%s],"severity_counts":{"low":%d,"medium":%d,"high":%d,"critical":%d}}' \
        "$(IFS=','; echo "${issues[*]}")" \
        "${severity_counts[0]}" "${severity_counts[1]}" "${severity_counts[2]}" "${severity_counts[3]}")
    
    echo "$nginx_audit"
}

# Проверка конфигурации PHP
audit_php_security() {
    log_audit "INFO" "Starting PHP security audit"
    
    local issues=()
    local severity_counts=(0 0 0 0)
    
    # Проверка основных настроек безопасности
    local php_ini="/etc/php/8.3/fpm/php.ini"
    
    if [ -f "$php_ini" ]; then
        # Проверка expose_php
        if grep -q "^expose_php = On" "$php_ini"; then
            issues+=('{"type":"information_disclosure","severity":"low","description":"expose_php is enabled","file":"'$php_ini'"}')
            ((severity_counts[0]++))
        fi
        
        # Проверка allow_url_fopen
        if grep -q "^allow_url_fopen = On" "$php_ini"; then
            issues+=('{"type":"remote_file_inclusion","severity":"high","description":"allow_url_fopen is enabled","file":"'$php_ini'"}')
            ((severity_counts[2]++))
        fi
        
        # Проверка allow_url_include
        if grep -q "^allow_url_include = On" "$php_ini"; then
            issues+=('{"type":"remote_file_inclusion","severity":"critical","description":"allow_url_include is enabled","file":"'$php_ini'"}')
            ((severity_counts[3]++))
        fi
        
        # Проверка display_errors
        if grep -q "^display_errors = On" "$php_ini"; then
            issues+=('{"type":"information_disclosure","severity":"medium","description":"display_errors is enabled in production","file":"'$php_ini'"}')
            ((severity_counts[1]++))
        fi
        
        # Проверка отключенных функций
        local disabled_functions=$(grep "^disable_functions" "$php_ini" | cut -d'=' -f2)
        local dangerous_functions=("exec" "shell_exec" "system" "passthru" "eval" "file_get_contents" "file_put_contents")
        
        for func in "${dangerous_functions[@]}"; do
            if [[ ! "$disabled_functions" =~ $func ]]; then
                issues+=('{"type":"dangerous_function","severity":"high","description":"Dangerous function '$func' not disabled","file":"'$php_ini'"}')
                ((severity_counts[2]++))
            fi
        done
        
        # Проверка memory_limit
        local memory_limit=$(grep "^memory_limit" "$php_ini" | cut -d'=' -f2 | tr -d ' ')
        if [[ "$memory_limit" =~ ^[0-9]+$ ]] && [ "$memory_limit" -gt 512 ]; then
            issues+=('{"type":"resource_limit","severity":"medium","description":"High memory_limit may cause DoS","file":"'$php_ini'","value":"'$memory_limit'"}')
            ((severity_counts[1]++))
        fi
        
    else
        issues+=('{"type":"missing_config","severity":"critical","description":"PHP configuration file not found","file":"'$php_ini'"}')
        ((severity_counts[3]++))
    fi
    
    # Проверка версии PHP
    local php_version=$(php -v | head -n1 | awk '{print $2}')
    if [[ "$php_version" < "8.0" ]]; then
        issues+=('{"type":"outdated_software","severity":"high","description":"PHP version is outdated","version":"'$php_version'"}')
        ((severity_counts[2]++))
    fi
    
    local php_audit=$(printf '{"component":"php","issues":[%s],"severity_counts":{"low":%d,"medium":%d,"high":%d,"critical":%d}}' \
        "$(IFS=','; echo "${issues[*]}")" \
        "${severity_counts[0]}" "${severity_counts[1]}" "${severity_counts[2]}" "${severity_counts[3]}")
    
    echo "$php_audit"
}

# Проверка конфигурации SSH
audit_ssh_security() {
    log_audit "INFO" "Starting SSH security audit"
    
    local issues=()
    local severity_counts=(0 0 0 0)
    local ssh_config="/etc/ssh/sshd_config"
    
    if [ -f "$ssh_config" ]; then
        # Проверка PermitRootLogin
        if grep -q "^PermitRootLogin yes" "$ssh_config"; then
            issues+=('{"type":"privilege_escalation","severity":"critical","description":"Root login via SSH is enabled","file":"'$ssh_config'"}')
            ((severity_counts[3]++))
        fi
        
        # Проверка PasswordAuthentication
        if grep -q "^PasswordAuthentication yes" "$ssh_config"; then
            issues+=('{"type":"weak_authentication","severity":"high","description":"Password authentication is enabled","file":"'$ssh_config'"}')
            ((severity_counts[2]++))
        fi
        
        # Проверка Protocol
        if grep -q "^Protocol 1" "$ssh_config"; then
            issues+=('{"type":"weak_protocol","severity":"critical","description":"SSH Protocol 1 is enabled","file":"'$ssh_config'"}')
            ((severity_counts[3]++))
        fi
        
        # Проверка X11Forwarding
        if grep -q "^X11Forwarding yes" "$ssh_config"; then
            issues+=('{"type":"unnecessary_service","severity":"medium","description":"X11 forwarding is enabled","file":"'$ssh_config'"}')
            ((severity_counts[1]++))
        fi
        
        # Проверка AllowUsers/AllowGroups
        if ! grep -q "^AllowUsers\|^AllowGroups" "$ssh_config"; then
            issues+=('{"type":"access_control","severity":"medium","description":"No user/group restrictions configured","file":"'$ssh_config'"}')
            ((severity_counts[1]++))
        fi
        
        # Проверка MaxAuthTries
        local max_auth_tries=$(grep "^MaxAuthTries" "$ssh_config" | awk '{print $2}')
        if [ -z "$max_auth_tries" ] || [ "$max_auth_tries" -gt 6 ]; then
            issues+=('{"type":"brute_force_protection","severity":"medium","description":"MaxAuthTries not set or too high","file":"'$ssh_config'","value":"'$max_auth_tries'"}')
            ((severity_counts[1]++))
        fi
        
    else
        issues+=('{"type":"missing_config","severity":"critical","description":"SSH configuration file not found","file":"'$ssh_config'"}')
        ((severity_counts[3]++))
    fi
    
    local ssh_audit=$(printf '{"component":"ssh","issues":[%s],"severity_counts":{"low":%d,"medium":%d,"high":%d,"critical":%d}}' \
        "$(IFS=','; echo "${issues[*]}")" \
        "${severity_counts[0]}" "${severity_counts[1]}" "${severity_counts[2]}" "${severity_counts[3]}")
    
    echo "$ssh_audit"
}

# Проверка файловых разрешений
audit_file_permissions() {
    log_audit "INFO" "Starting file permissions audit"
    
    local issues=()
    local severity_counts=(0 0 0 0)
    
    # Критичные директории и файлы
    local critical_paths=(
        "/etc/passwd:644"
        "/etc/shadow:640"
        "/etc/group:644"
        "/etc/gshadow:640"
        "/etc/ssh:755"
        "/var/www:755"
        "/var/log:755"
    )
    
    for path_perm in "${critical_paths[@]}"; do
        local path=$(echo "$path_perm" | cut -d: -f1)
        local expected_perm=$(echo "$path_perm" | cut -d: -f2)
        
        if [ -e "$path" ]; then
            local actual_perm=$(stat -c "%a" "$path")
            
            if [ "$actual_perm" != "$expected_perm" ]; then
                local severity="medium"
                if [[ "$path" =~ shadow ]]; then
                    severity="high"
                fi
                
                issues+=('{"type":"file_permissions","severity":"'$severity'","description":"Incorrect permissions on '$path'","path":"'$path'","expected":"'$expected_perm'","actual":"'$actual_perm'"}')
                
                case "$severity" in
                    "low") ((severity_counts[0]++)) ;;
                    "medium") ((severity_counts[1]++)) ;;
                    "high") ((severity_counts[2]++)) ;;
                    "critical") ((severity_counts[3]++)) ;;
                esac
            fi
        fi
    done
    
    # Поиск файлов с SUID/SGID битами
    local suid_files=$(find /usr -perm -4000 2>/dev/null | head -20)
    local suid_count=$(echo "$suid_files" | wc -l)
    
    if [ "$suid_count" -gt 50 ]; then
        issues+=('{"type":"privilege_escalation","severity":"medium","description":"Large number of SUID files found","count":'$suid_count'}')
        ((severity_counts[1]++))
    fi
    
    # Поиск world-writable файлов
    local world_writable=$(find /var/www -type f -perm -002 2>/dev/null | head -10)
    if [ -n "$world_writable" ]; then
        issues+=('{"type":"file_permissions","severity":"high","description":"World-writable files found in web directory","files":"'$(echo "$world_writable" | tr '\n' ',')'"}')
        ((severity_counts[2]++))
    fi
    
    local permissions_audit=$(printf '{"component":"file_permissions","issues":[%s],"severity_counts":{"low":%d,"medium":%d,"high":%d,"critical":%d}}' \
        "$(IFS=','; echo "${issues[*]}")" \
        "${severity_counts[0]}" "${severity_counts[1]}" "${severity_counts[2]}" "${severity_counts[3]}")
    
    echo "$permissions_audit"
}

# Проверка сетевой безопасности
audit_network_security() {
    log_audit "INFO" "Starting network security audit"
    
    local issues=()
    local severity_counts=(0 0 0 0)
    
    # Проверка открытых портов
    local open_ports=$(netstat -tuln | grep LISTEN | awk '{print $4}' | cut -d: -f2 | sort -u)
    local expected_ports=("22" "80" "443" "8729")
    
    for port in $open_ports; do
        if [[ ! " ${expected_ports[@]} " =~ " ${port} " ]]; then
            local severity="medium"
            
            # Критичные порты
            case "$port" in
                "21"|"23"|"25"|"53"|"110"|"143"|"993"|"995")
                    severity="high"
                    ;;
                "3389"|"5432"|"3306"|"1433"|"5984")
                    severity="critical"
                    ;;
            esac
            
            issues+=('{"type":"unexpected_port","severity":"'$severity'","description":"Unexpected open port","port":"'$port'"}')
            
            case "$severity" in
                "low") ((severity_counts[0]++)) ;;
                "medium") ((severity_counts[1]++)) ;;
                "high") ((severity_counts[2]++)) ;;
                "critical") ((severity_counts[3]++)) ;;
            esac
        fi
    done
    
    # Проверка firewall статуса
    if ! systemctl is-active --quiet ufw && ! systemctl is-active --quiet iptables; then
        issues+=('{"type":"firewall","severity":"high","description":"No active firewall detected"}')
        ((severity_counts[2]++))
    fi
    
    # Проверка fail2ban
    if ! systemctl is-active --quiet fail2ban; then
        issues+=('{"type":"intrusion_detection","severity":"medium","description":"Fail2ban service is not active"}')
        ((severity_counts[1]++))
    fi
    
    local network_audit=$(printf '{"component":"network_security","issues":[%s],"severity_counts":{"low":%d,"medium":%d,"high":%d,"critical":%d}}' \
        "$(IFS=','; echo "${issues[*]}")" \
        "${severity_counts[0]}" "${severity_counts[1]}" "${severity_counts[2]}" "${severity_counts[3]}")
    
    echo "$network_audit"
}

# Проверка логов на подозрительную активность
audit_log_analysis() {
    log_audit "INFO" "Starting log analysis audit"
    
    local issues=()
    local severity_counts=(0 0 0 0)
    
    # Анализ auth.log
    if [ -f "/var/log/auth.log" ]; then
        local failed_logins=$(grep "Failed password" /var/log/auth.log | wc -l)
        local brute_force_ips=$(grep "Failed password" /var/log/auth.log | awk '{print $(NF-3)}' | sort | uniq -c | sort -nr | head -5)
        
        if [ "$failed_logins" -gt 100 ]; then
            issues+=('{"type":"brute_force_attack","severity":"high","description":"High number of failed login attempts","count":'$failed_logins'}')
            ((severity_counts[2]++))
        fi
        
        # Проверка на root login попытки
        local root_attempts=$(grep "Failed password for root" /var/log/auth.log | wc -l)
        if [ "$root_attempts" -gt 0 ]; then
            issues+=('{"type":"privilege_escalation","severity":"high","description":"Failed root login attempts detected","count":'$root_attempts'}')
            ((severity_counts[2]++))
        fi
    fi
    
    # Анализ NGINX access logs
    if [ -f "/var/log/nginx/access.log" ]; then
        local suspicious_requests=$(grep -E "(\.\.\/|%2e%2e|%252e)" /var/log/nginx/access.log | wc -l)
        if [ "$suspicious_requests" -gt 0 ]; then
            issues+=('{"type":"directory_traversal","severity":"medium","description":"Directory traversal attempts detected","count":'$suspicious_requests'}')
            ((severity_counts[1]++))
        fi
        
        local sql_injection_attempts=$(grep -i -E "(union|select|insert|drop|delete|update|exec)" /var/log/nginx/access.log | wc -l)
        if [ "$sql_injection_attempts" -gt 0 ]; then
            issues+=('{"type":"sql_injection","severity":"high","description":"Potential SQL injection attempts","count":'$sql_injection_attempts'}')
            ((severity_counts[2]++))
        fi
    fi
    
    # Анализ системных логов
    local critical_events=$(journalctl --since="24 hours ago" --priority=crit --no-pager -q | wc -l)
    if [ "$critical_events" -gt 5 ]; then
        issues+=('{"type":"system_errors","severity":"medium","description":"Multiple critical system events","count":'$critical_events'}')
        ((severity_counts[1]++))
    fi
    
    local log_audit=$(printf '{"component":"log_analysis","issues":[%s],"severity_counts":{"low":%d,"medium":%d,"high":%d,"critical":%d}}' \
        "$(IFS=','; echo "${issues[*]}")" \
        "${severity_counts[0]}" "${severity_counts[1]}" "${severity_counts[2]}" "${severity_counts[3]}")
    
    echo "$log_audit"
}

# Генерация итогового отчета
generate_audit_report() {
    local nginx_result="$1"
    local php_result="$2"
    local ssh_result="$3"
    local permissions_result="$4"
    local network_result="$5"
    local logs_result="$6"
    
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    local total_critical=0
    local total_high=0
    local total_medium=0
    local total_low=0
    
    # Подсчет общих показателей
    for result in "$nginx_result" "$php_result" "$ssh_result" "$permissions_result" "$network_result" "$logs_result"; do
        total_critical=$((total_critical + $(echo "$result" | jq -r '.severity_counts.critical')))
        total_high=$((total_high + $(echo "$result" | jq -r '.severity_counts.high')))
        total_medium=$((total_medium + $(echo "$result" | jq -r '.severity_counts.medium')))
        total_low=$((total_low + $(echo "$result" | jq -r '.severity_counts.low')))
    done
    
    local total_issues=$((total_critical + total_high + total_medium + total_low))
    
    # Определение общего уровня риска
    local risk_level="LOW"
    if [ "$total_critical" -gt 0 ]; then
        risk_level="CRITICAL"
    elif [ "$total_high" -gt "$HIGH_ISSUES_THRESHOLD" ]; then
        risk_level="HIGH"
    elif [ "$total_medium" -gt 20 ]; then
        risk_level="MEDIUM"
    fi
    
    # Создание JSON отчета
    cat > "$REPORT_FILE" << EOF
{
    "audit_report": {
        "timestamp": "$timestamp",
        "hostname": "$(hostname)",
        "auditor": "security_audit.sh v1.0",
        "summary": {
            "total_issues": $total_issues,
            "risk_level": "$risk_level",
            "severity_breakdown": {
                "critical": $total_critical,
                "high": $total_high,
                "medium": $total_medium,
                "low": $total_low
            }
        },
        "components": {
            "nginx": $nginx_result,
            "php": $php_result,
            "ssh": $ssh_result,
            "file_permissions": $permissions_result,
            "network_security": $network_result,
            "log_analysis": $logs_result
        },
        "recommendations": [
            "Review and fix all critical and high severity issues immediately",
            "Implement regular security monitoring and alerting",
            "Keep all software components updated to latest versions",
            "Regularly review and update security configurations",
            "Implement intrusion detection and prevention systems"
        ]
    }
}
EOF
    
    log_audit "INFO" "Audit report generated: $REPORT_FILE"
    echo "$REPORT_FILE"
}

# Отправка уведомлений об аудите
send_audit_notifications() {
    local report_file="$1"
    local risk_level=$(jq -r '.audit_report.summary.risk_level' "$report_file")
    local total_critical=$(jq -r '.audit_report.summary.severity_breakdown.critical' "$report_file")
    local total_high=$(jq -r '.audit_report.summary.severity_breakdown.high' "$report_file")
    
    if [ "$risk_level" = "CRITICAL" ] || [ "$total_critical" -gt "$CRITICAL_ISSUES_THRESHOLD" ]; then
        local subject="CRITICAL: Security Audit Alert - $(hostname)"
        local body="Critical security issues detected during automated audit.
        
Risk Level: $risk_level
Critical Issues: $total_critical
High Issues: $total_high

Please review the detailed report immediately: $report_file

Audit completed at: $(date)"
        
        echo "$body" | mail -s "$subject" "$ALERT_EMAIL"
        log_audit "ALERT" "Critical security audit notification sent"
    fi
}

# Основная функция аудита
main_audit() {
    log_audit "INFO" "Starting comprehensive security audit"
    
    # Выполнение всех проверок
    local nginx_audit=$(audit_nginx_security)
    local php_audit=$(audit_php_security)  
    local ssh_audit=$(audit_ssh_security)
    local permissions_audit=$(audit_file_permissions)
    local network_audit=$(audit_network_security)
    local logs_audit=$(audit_log_analysis)
    
    # Генерация отчета
    local report_file=$(generate_audit_report "$nginx_audit" "$php_audit" "$ssh_audit" "$permissions_audit" "$network_audit" "$logs_audit")
    
    # Отправка уведомлений
    send_audit_notifications "$report_file"
    
    # Вывод краткой статистики
    local total_issues=$(jq -r '.audit_report.summary.total_issues' "$report_file")
    local risk_level=$(jq -r '.audit_report.summary.risk_level' "$report_file")
    
    echo -e "${BLUE}Security Audit Completed${NC}"
    echo -e "Total Issues Found: $total_issues"
    echo -e "Risk Level: $risk_level"
    echo -e "Detailed Report: $report_file"
    
    log_audit "INFO" "Security audit completed - Total issues: $total_issues, Risk level: $risk_level"
    
    # Возвращаем код выхода в зависимости от уровня риска
    case "$risk_level" in
        "CRITICAL") exit 3 ;;
        "HIGH") exit 2 ;;
        "MEDIUM") exit 1 ;;
        *) exit 0 ;;
    esac
}

# Обработка аргументов командной строки
case "${1:-full}" in
    "nginx")
        audit_nginx_security | jq .
        ;;
    "php")
        audit_php_security | jq .
        ;;
    "ssh")
        audit_ssh_security | jq .
        ;;
    "permissions")
        audit_file_permissions | jq .
        ;;
    "network")
        audit_network_security | jq .
        ;;
    "logs")
        audit_log_analysis | jq .
        ;;
    "report")
        if [ -f "$AUDIT_DIR/audit_report_"*.json ]; then
            latest_report=$(ls -t "$AUDIT_DIR"/audit_report_*.json | head -1)
            cat "$latest_report" | jq .
        else
            echo "No audit reports found"
            exit 1
        fi
        ;;
    "full"|"")
        main_audit
        ;;
    *)
        echo "Usage: $0 {nginx|php|ssh|permissions|network|logs|report|full}"
        echo ""
        echo "Commands:"
        echo "  nginx        - Audit NGINX configuration"
        echo "  php          - Audit PHP configuration"
        echo "  ssh          - Audit SSH configuration"
        echo "  permissions  - Audit file permissions"
        echo "  network      - Audit network security"
        echo "  logs         - Analyze logs for threats"
        echo "  report       - Show latest audit report"
        echo "  full         - Run complete security audit (default)"
        exit 1
        ;;
esac

9.4. План реагирования на инциденты

Комплексный план реагирования на инциденты обеспечивает быстрое и эффективное устранение угроз безопасности:

#!/bin/bash
# /usr/local/bin/incident_response.sh
# Система реагирования на инциденты безопасности

# Конфигурация
INCIDENT_DIR="/var/log/incidents"
QUARANTINE_DIR="/var/quarantine"
BACKUP_DIR="/var/backups/emergency"
NOTIFICATION_CONTACTS="admin@sysadministrator.ru security@sysadministrator.ru"
ESCALATION_CONTACTS="cto@sysadministrator.ru"
MIKROTIK_HOST="192.168.1.1"

# Уровни инцидентов
INCIDENT_LOW=1
INCIDENT_MEDIUM=2
INCIDENT_HIGH=3
INCIDENT_CRITICAL=4

# Создание необходимых директорий
mkdir -p "$INCIDENT_DIR" "$QUARANTINE_DIR" "$BACKUP_DIR"

# Функция логирования инцидентов
log_incident() {
    local level="$1"
    local message="$2"
    local incident_id="${3:-$(date +%Y%m%d_%H%M%S)_$$}"
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    
    echo "[$timestamp] [INCIDENT:$incident_id] [$level] $message" | tee -a "$INCIDENT_DIR/incidents.log"
    
    # Отправка в syslog
    logger -p local0.crit "SECURITY_INCIDENT: [$incident_id] [$level] $message"
}

# Создание инцидента
create_incident() {
    local incident_type="$1"
    local severity="$2"
    local description="$3"
    local affected_ips="$4"
    local evidence="$5"
    
    local incident_id="INC_$(date +%Y%m%d_%H%M%S)_$$"
    local incident_file="$INCIDENT_DIR/${incident_id}.json"
    
    # Создание файла инцидента
    cat > "$incident_file" << EOF
{
    "incident_id": "$incident_id",
    "created_at": "$(date -Iseconds)",
    "type": "$incident_type",
    "severity": $severity,
    "status": "OPEN",
    "description": "$description",
    "affected_ips": [$(echo "$affected_ips" | sed 's/,/","/g' | sed 's/^/"/' | sed 's/$/"/')],
    "evidence": "$evidence",
    "actions_taken": [],
    "assigned_to": "automated_system",
    "escalated": false,
    "resolved_at": null,
    "resolution_summary": null
}
EOF
    
    log_incident "INFO" "Incident created: $incident_type" "$incident_id"
    echo "$incident_id"
}

# Блокировка вредоносного IP
block_malicious_ip() {
    local ip="$1"
    local reason="$2"
    local incident_id="$3"
    local duration="${4:-86400}" # 24 часа по умолчанию
    
    log_incident "ACTION" "Blocking malicious IP: $ip ($reason)" "$incident_id"
    
    # Блокировка через iptables
    if command -v iptables >/dev/null 2>&1; then
        iptables -I INPUT -s "$ip" -j DROP
        log_incident "ACTION" "IP $ip blocked via iptables" "$incident_id"
    fi
    
    # Блокировка через fail2ban
    if command -v fail2ban-client >/dev/null 2>&1; then
        fail2ban-client set sshd banip "$ip" 2>/dev/null
        log_incident "ACTION" "IP $ip banned via fail2ban" "$incident_id"
    fi
    
    # Добавление в blacklist на MikroTik
    block_ip_on_mikrotik "$ip" "$reason" "$incident_id" "$duration"
    
    # Запись в файл заблокированных IP
    echo "$(date -Iseconds)|$ip|$reason|$incident_id|$duration" >> "$INCIDENT_DIR/blocked_ips.log"
}

# Блокировка IP на MikroTik
block_ip_on_mikrotik() {
    local ip="$1"
    local reason="$2"
    local incident_id="$3"
    local duration="$4"
    
    local timeout_str="${duration}s"
    
    # SSH команда для добавления в blacklist
    if ssh -o ConnectTimeout=10 -o BatchMode=yes \
           "backup_user@$MIKROTIK_HOST" \
           "/ip firewall address-list add list=security_blacklist address=$ip comment=\"$reason - $incident_id\" timeout=$timeout_str" >/dev/null 2>&1; then
        log_incident "ACTION" "IP $ip added to MikroTik blacklist" "$incident_id"
    else
        log_incident "ERROR" "Failed to add IP $ip to MikroTik blacklist" "$incident_id"
    fi
}

# Изоляция зараженного хоста
isolate_host() {
    local host_ip="$1"
    local incident_id="$2"
    
    log_incident "ACTION" "Isolating compromised host: $host_ip" "$incident_id"
    
    # Блокировка всего трафика кроме административного
    iptables -I FORWARD -s "$host_ip" -j DROP
    iptables -I FORWARD -d "$host_ip" -j DROP
    
    # Разрешение только SSH для администрирования
    iptables -I FORWARD -s "$host_ip" -p tcp --dport 22 -j ACCEPT
    iptables -I FORWARD -d "$host_ip" -p tcp --sport 22 -j ACCEPT
    
    log_incident "ACTION" "Host $host_ip isolated successfully" "$incident_id"
}

# Сбор доказательств
collect_evidence() {
    local incident_id="$1"
    local evidence_type="$2"
    local target="$3"
    
    local evidence_dir="$INCIDENT_DIR/${incident_id}_evidence"
    mkdir -p "$evidence_dir"
    
    case "$evidence_type" in
        "logs")
            collect_log_evidence "$evidence_dir" "$target"
            ;;
        "network")
            collect_network_evidence "$evidence_dir" "$target"
            ;;
        "filesystem")
            collect_filesystem_evidence "$evidence_dir" "$target"
            ;;
        "memory")
            collect_memory_evidence "$evidence_dir" "$target"
            ;;
        *)
            log_incident "ERROR" "Unknown evidence type: $evidence_type" "$incident_id"
            return 1
            ;;
    esac
    
    log_incident "ACTION" "Evidence collected: $evidence_type" "$incident_id"
}

# Сбор доказательств из логов
collect_log_evidence() {
    local evidence_dir="$1"
    local timeframe="$2"
    
    local since_time="${timeframe:-1 hour ago}"
    
    # Системные логи
    journalctl --since="$since_time" --no-pager > "$evidence_dir/system_logs.txt"
    
    # NGINX логи
    if [ -f "/var/log/nginx/access.log" ]; then
        tail -1000 /var/log/nginx/access.log > "$evidence_dir/nginx_access.log"
    fi
    
    if [ -f "/var/log/nginx/error.log" ]; then
        tail -1000 /var/log/nginx/error.log > "$evidence_dir/nginx_error.log"
    fi
    
    # Auth логи
    if [ -f "/var/log/auth.log" ]; then
        grep "$(date '+%b %d')" /var/log/auth.log > "$evidence_dir/auth_today.log"
    fi
    
    # Fail2ban логи
    if [ -f "/var/log/fail2ban.log" ]; then
        tail -500 /var/log/fail2ban.log > "$evidence_dir/fail2ban.log"
    fi
}

# Сбор сетевых доказательств
collect_network_evidence() {
    local evidence_dir="$1"
    local duration="$2"
    
    # Текущие соединения
    netstat -tuln > "$evidence_dir/listening_ports.txt"
    netstat -tun > "$evidence_dir/active_connections.txt"
    
    # Таблица маршрутизации
    ip route show > "$evidence_dir/routing_table.txt"
    
    # ARP таблица
    arp -a > "$evidence_dir/arp_table.txt"
    
    # Активные процессы с сетевыми соединениями
    lsof -i > "$evidence_dir/network_processes.txt"
    
    # Захват трафика (если указана длительность)
    if [ -n "$duration" ] && command -v tcpdump >/dev/null 2>&1; then
        timeout "$duration" tcpdump -w "$evidence_dir/network_capture.pcap" -i any 2>/dev/null &
    fi
}

# Сбор доказательств файловой системы
collect_filesystem_evidence() {
    local evidence_dir="$1"
    local target_dir="$2"
    
    local search_dir="${target_dir:-/var/www}"
    
    # Недавно измененные файлы
    find "$search_dir" -type f -mtime -1 -exec ls -la {} \; > "$evidence_dir/recent_changes.txt" 2>/dev/null
    
    # Подозрительные файлы
    find "$search_dir" -name "*.php" -exec grep -l "eval\|base64_decode\|shell_exec" {} \; > "$evidence_dir/suspicious_files.txt" 2>/dev/null
    
    # Права доступа к файлам
    find "$search_dir" -type f -perm -002 > "$evidence_dir/world_writable.txt" 2>/dev/null
    
    # Хеши критичных файлов
    if [ -d "$search_dir" ]; then
        find "$search_dir" -type f -name "*.php" -exec md5sum {} \; > "$evidence_dir/file_hashes.txt" 2>/dev/null
    fi
}

# Аварийное резервное копирование
emergency_backup() {
    local incident_id="$1"
    local backup_reason="$2"
    
    log_incident "ACTION" "Starting emergency backup: $backup_reason" "$incident_id"
    
    local backup_file="$BACKUP_DIR/emergency_backup_${incident_id}_$(date +%Y%m%d_%H%M%S).tar.gz"
    
    # Критичные данные для резервного копирования
    local backup_paths=(
        "/etc"
        "/var/www/html/sysadministrator"
        "/var/log"
        "/home"
    )
    
    # Создание архива
    if tar -czf "$backup_file" "${backup_paths[@]}" 2>/dev/null; then
        local backup_size=$(stat -c%s "$backup_file")
        log_incident "ACTION" "Emergency backup completed: $backup_file ($backup_size bytes)" "$incident_id"
        
        # Отправка на удаленный сервер если настроен
        if [ -n "$REMOTE_BACKUP_HOST" ]; then
            scp "$backup_file" "$REMOTE_BACKUP_HOST:/emergency_backups/" 2>/dev/null && \
            log_incident "ACTION" "Emergency backup uploaded to remote server" "$incident_id"
        fi
        
        echo "$backup_file"
    else
        log_incident "ERROR" "Emergency backup failed" "$incident_id"
        return 1
    fi
}

# Уведомление о инциденте
notify_incident() {
    local incident_id="$1"
    local severity="$2"
    local summary="$3"
    local details="$4"
    
    local subject="Security Incident [$incident_id] - Severity Level $severity"
    local body="AUTOMATED SECURITY INCIDENT ALERT

Incident ID: $incident_id
Severity: $severity
Timestamp: $(date)
Host: $(hostname)

Summary: $summary

Details:
$details

This is an automated alert from the incident response system.
Please review and take appropriate action immediately.

Incident log: $INCIDENT_DIR/${incident_id}.json"
    
    # Отправка email уведомлений
    for contact in $NOTIFICATION_CONTACTS; do
        echo "$body" | mail -s "$subject" "$contact" 2>/dev/null
    done
    
    # Эскалация для критичных инцидентов
    if [ "$severity" -ge "$INCIDENT_CRITICAL" ]; then
        escalate_incident "$incident_id" "$summary"
    fi
    
    log_incident "NOTIFY" "Incident notification sent to: $NOTIFICATION_CONTACTS" "$incident_id"
}

# Эскалация критичных инцидентов
escalate_incident() {
    local incident_id="$1" 
    local summary="$2"
    
    local subject="CRITICAL INCIDENT ESCALATION [$incident_id]"
    local body="CRITICAL SECURITY INCIDENT REQUIRES IMMEDIATE ATTENTION

Incident ID: $incident_id
Escalated at: $(date)
Host: $(hostname)

Summary: $summary

This incident has been automatically escalated due to its critical severity.
Immediate response required.

Incident details: $INCIDENT_DIR/${incident_id}.json"
    
    # Отправка эскалационных уведомлений
    for contact in $ESCALATION_CONTACTS; do
        echo "$body" | mail -s "$subject" "$contact" 2>/dev/null
    done
    
    # Обновление статуса инцидента
    if [ -f "$INCIDENT_DIR/${incident_id}.json" ]; then
        jq '.escalated = true | .escalated_at = "'$(date -Iseconds)'"' \
           "$INCIDENT_DIR/${incident_id}.json" > "$INCIDENT_DIR/${incident_id}.json.tmp" && \
        mv "$INCIDENT_DIR/${incident_id}.json.tmp" "$INCIDENT_DIR/${incident_id}.json"
    fi
    
    log_incident "ESCALATE" "Incident escalated to: $ESCALATION_CONTACTS" "$incident_id"
}

# Обработка различных типов инцидентов
handle_brute_force_attack() {
    local attacking_ip="$1"
    local failed_attempts="$2"
    
    local incident_id=$(create_incident "BRUTE_FORCE_ATTACK" "$INCIDENT_HIGH" \
        "Brute force attack detected from $attacking_ip with $failed_attempts failed attempts" \
        "$attacking_ip" \
        "auth_logs")
    
    # Сбор доказательств
    collect_evidence "$incident_id" "logs" "1 hour ago"
    
    # Блокировка атакующего IP
    block_malicious_ip "$attacking_ip" "Brute force attack" "$incident_id" 86400
    
    # Уведомление
    notify_incident "$incident_id" "$INCIDENT_HIGH" \
        "Brute force attack from $attacking_ip" \
        "Detected $failed_attempts failed authentication attempts"
    
    echo "$incident_id"
}

handle_malware_detection() {
    local infected_file="$1"
    local malware_type="$2"
    local host_ip="$3"
    
    local incident_id=$(create_incident "MALWARE_DETECTION" "$INCIDENT_CRITICAL" \
        "Malware detected: $malware_type in file $infected_file" \
        "$host_ip" \
        "filesystem_scan")
    
    # Аварийное резервное копирование
    emergency_backup "$incident_id" "Malware detection"
    
    # Карантин зараженного файла
    local quarantine_file="$QUARANTINE_DIR/$(basename "$infected_file")_${incident_id}"
    if [ -f "$infected_file" ]; then
        mv "$infected_file" "$quarantine_file" 2>/dev/null && \
        log_incident "ACTION" "File quarantined: $infected_file -> $quarantine_file" "$incident_id"
    fi
    
    # Изоляция хоста если необходимо
    if [ -n "$host_ip" ]; then
        isolate_host "$host_ip" "$incident_id"
    fi
    
    # Сбор доказательств
    collect_evidence "$incident_id" "filesystem" "$(dirname "$infected_file")"
    
    # Уведомление
    notify_incident "$incident_id" "$INCIDENT_CRITICAL" \
        "Malware detected: $malware_type" \
        "File: $infected_file\\nHost: $host_ip\\nFile quarantined"
    
    echo "$incident_id"
}

handle_ddos_attack() {
    local attacking_ips="$1"
    local request_rate="$2"
    
    local incident_id=$(create_incident "DDOS_ATTACK" "$INCIDENT_HIGH" \
        "DDoS attack detected with $request_rate requests/second" \
        "$attacking_ips" \
        "network_logs")
    
    # Блокировка атакующих IP
    for ip in $(echo "$attacking_ips" | tr ',' ' '); do
        block_malicious_ip "$ip" "DDoS attack" "$incident_id" 3600
    done
    
    # Сбор сетевых доказательств
    collect_evidence "$incident_id" "network" "60"
    
    # Активация защитных мер
    activate_ddos_protection "$incident_id"
    
    # Уведомление
    notify_incident "$incident_id" "$INCIDENT_HIGH" \
        "DDoS attack detected" \
        "Attack rate: $request_rate req/sec\\nAttacking IPs: $attacking_ips"
    
    echo "$incident_id"
}

# Активация защиты от DDoS
activate_ddos_protection() {
    local incident_id="$1"
    
    log_incident "ACTION" "Activating DDoS protection measures" "$incident_id"
    
    # Ограничение скорости соединений
    iptables -I INPUT -p tcp --dport 80 -m connlimit --connlimit-above 20 -j DROP 2>/dev/null
    iptables -I INPUT -p tcp --dport 443 -m connlimit --connlimit-above 20 -j DROP 2>/dev/null
    
    # Ограничение новых соединений
    iptables -I INPUT -p tcp --syn -m limit --limit 1/s --limit-burst 3 -j ACCEPT 2>/dev/null
    iptables -I INPUT -p tcp --syn -j DROP 2>/dev/null
    
    log_incident "ACTION" "DDoS protection rules activated" "$incident_id"
}

# Деактивация защитных мер
deactivate_protection() {
    local incident_id="$1"
    
    log_incident "ACTION" "Deactivating emergency protection measures" "$incident_id"
    
    # Удаление временных iptables правил
    iptables -D INPUT -p tcp --dport 80 -m connlimit --connlimit-above 20 -j DROP 2>/dev/null
    iptables -D INPUT -p tcp --dport 443 -m connlimit --connlimit-above 20 -j DROP 2>/dev/null
    iptables -D INPUT -p tcp --syn -m limit --limit 1/s --limit-burst 3 -j ACCEPT 2>/dev/null
    iptables -D INPUT -p tcp --syn -j DROP 2>/dev/null
    
    log_incident "ACTION" "Protection measures deactivated" "$incident_id"
}

# Закрытие инцидента
close_incident() {
    local incident_id="$1"
    local resolution_summary="$2"
    
    local incident_file="$INCIDENT_DIR/${incident_id}.json"
    
    if [ -f "$incident_file" ]; then
        jq '.status = "CLOSED" | .resolved_at = "'$(date -Iseconds)'" | .resolution_summary = "'"$resolution_summary"'"' \
           "$incident_file" > "${incident_file}.tmp" && \
        mv "${incident_file}.tmp" "$incident_file"
        
        log_incident "RESOLVE" "Incident closed: $resolution_summary" "$incident_id"
        echo "Incident $incident_id closed successfully"
    else
        echo "Incident file not found: $incident_file"
        return 1
    fi
}

# Получение статуса инцидента
get_incident_status() {
    local incident_id="$1"
    local incident_file="$INCIDENT_DIR/${incident_id}.json"
    
    if [ -f "$incident_file" ]; then
        cat "$incident_file" | jq .
    else
        echo "Incident not found: $incident_id"
        return 1
    fi
}

# Список активных инцидентов
list_active_incidents() {
    echo "Active Security Incidents:"
    echo "========================="
    
    for incident_file in "$INCIDENT_DIR"/INC_*.json; do
        if [ -f "$incident_file" ]; then
            local status=$(jq -r '.status' "$incident_file")
            
            if [ "$status" = "OPEN" ]; then
                local incident_id=$(jq -r '.incident_id' "$incident_file")
                local created_at=$(jq -r '.created_at' "$incident_file")
                local severity=$(jq -r '.severity' "$incident_file")
                local type=$(jq -r '.type' "$incident_file")
                local description=$(jq -r '.description' "$incident_file")
                
                echo "ID: $incident_id"
                echo "Created: $created_at"
                echo "Severity: $severity"
                echo "Type: $type"
                echo "Description: $description"
                echo "---"
            fi
        fi
    done
}

# Главная функция
main() {
    case "${1:-help}" in
        "brute_force")
            handle_brute_force_attack "$2" "$3"
            ;;
        "malware")
            handle_malware_detection "$2" "$3" "$4"
            ;;
        "ddos")
            handle_ddos_attack "$2" "$3"
            ;;
        "block_ip")
            incident_id=$(create_incident "MANUAL_BLOCK" "$INCIDENT_MEDIUM" "Manual IP block" "$2" "manual_action")
            block_malicious_ip "$2" "${3:-Manual block}" "$incident_id" "${4:-3600}"
            ;;
        "isolate")
            incident_id=$(create_incident "MANUAL_ISOLATION" "$INCIDENT_HIGH" "Manual host isolation" "$2" "manual_action")
            isolate_host "$2" "$incident_id"
            ;;
        "collect_evidence")
            if [ -z "$2" ] || [ -z "$3" ]; then
                echo "Usage: $0 collect_evidence <incident_id> <evidence_type> [target]"
                exit 1
            fi
            collect_evidence "$2" "$3" "$4"
            ;;
        "close")
            close_incident "$2" "$3"
            ;;
        "status")
            get_incident_status "$2"
            ;;
        "list")
            list_active_incidents
            ;;
        "deactivate")
            deactivate_protection "$2"
            ;;
        "help"|*)
            echo "Usage: $0 <command> [arguments]"
            echo ""
            echo "Incident Response Commands:"
            echo "  brute_force <ip> <attempts>           - Handle brute force attack"
            echo "  malware <file> <type> [host_ip]       - Handle malware detection"
            echo "  ddos <ips> <rate>                     - Handle DDoS attack"
            echo "  block_ip <ip> [reason] [duration]     - Block malicious IP"
            echo "  isolate <host_ip>                     - Isolate compromised host"
            echo "  collect_evidence <id> <type> [target] - Collect incident evidence"
            echo "  close <incident_id> <summary>         - Close incident"
            echo "  status <incident_id>                  - Get incident status"
            echo "  list                                  - List active incidents"
            echo "  deactivate <incident_id>              - Deactivate protection measures"
            echo ""
            echo "Evidence Types: logs, network, filesystem, memory"
            exit 1
            ;;
    esac
}

main "$@"

10. Мониторинг и отладка

10.1. Система мониторинга состояния

Комплексная система мониторинга обеспечивает непрерывный контроль работоспособности всех компонентов системы управления белым списком:

#!/usr/bin/env python3
# /usr/local/bin/monitoring_system.py
# Продвинутая система мониторинга состояния

import json
import time
import sqlite3
import logging
import requests
import subprocess
import psutil
import socket
import threading
from datetime import datetime, timedelta
from dataclasses import dataclass, asdict
from typing import Dict, List, Optional, Any
from pathlib import Path
import smtplib
from email.mime.text import MimeText
from email.mime.multipart import MimeMultipart

@dataclass
class MonitoringMetric:
    """Класс для хранения метрики мониторинга"""
    name: str
    value: float
    timestamp: datetime
    unit: str = ""
    status: str = "OK"  # OK, WARNING, CRITICAL
    details: Dict[str, Any] = None
    
    def to_dict(self):
        data = asdict(self)
        data['timestamp'] = self.timestamp.isoformat()
        return data

class MonitoringSystem:
    def __init__(self, config_file="/etc/monitoring/config.json"):
        self.config = self.load_config(config_file)
        self.db_path = self.config.get('database_path', '/var/lib/monitoring/metrics.db')
        self.logger = self.setup_logging()
        self.alerts_sent = {}  # Кэш отправленных уведомлений
        
        # Инициализация базы данных
        self.init_database()
        
        # Запуск фонового мониторинга
        self.running = True
        self.monitor_thread = None
        
    def load_config(self, config_file):
        """Загрузка конфигурации мониторинга"""
        default_config = {
            'interval': 60,  # Интервал мониторинга в секундах
            'database_path': '/var/lib/monitoring/metrics.db',
            'log_file': '/var/log/monitoring.log',
            'email': {
                'enabled': True,
                'smtp_server': 'localhost',
                'smtp_port': 587,
                'from_address': 'monitoring@sysadministrator.ru',
                'to_addresses': ['admin@sysadministrator.ru']
            },
            'thresholds': {
                'cpu_warning': 70,
                'cpu_critical': 90,
                'memory_warning': 80,
                'memory_critical': 95,
                'disk_warning': 80,
                'disk_critical': 95,
                'response_time_warning': 2.0,
                'response_time_critical': 5.0
            },
            'services': {
                'nginx': {'port': 80, 'process_name': 'nginx'},
                'php-fpm': {'port': None, 'process_name': 'php-fpm'},
                'mikrotik': {'host': '192.168.1.1', 'port': 8729}
            }
        }
        
        try:
            with open(config_file, 'r') as f:
                config = json.load(f)
                # Объединяем с default_config
                default_config.update(config)
                return default_config
        except FileNotFoundError:
            self.logger.warning(f"Config file {config_file} not found, using defaults")
            return default_config
    
    def setup_logging(self):
        """Настройка логирования"""
        log_file = self.config.get('log_file', '/var/log/monitoring.log')
        Path(log_file).parent.mkdir(parents=True, exist_ok=True)
        
        logging.basicConfig(
            level=logging.INFO,
            format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
            handlers=[
                logging.FileHandler(log_file),
                logging.StreamHandler()
            ]
        )
        
        return logging.getLogger('MonitoringSystem')
    
    def init_database(self):
        """Инициализация базы данных для хранения метрик"""
        Path(self.db_path).parent.mkdir(parents=True, exist_ok=True)
        
        with sqlite3.connect(self.db_path) as conn:
            conn.execute('''
                CREATE TABLE IF NOT EXISTS metrics (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    name TEXT NOT NULL,
                    value REAL NOT NULL,
                    unit TEXT,
                    status TEXT,
                    details TEXT,
                    timestamp DATETIME NOT NULL,
                    INDEX idx_name_timestamp (name, timestamp)
                )
            ''')
            
            conn.execute('''
                CREATE TABLE IF NOT EXISTS alerts (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    metric_name TEXT NOT NULL,
                    alert_level TEXT NOT NULL,
                    message TEXT NOT NULL,
                    timestamp DATETIME NOT NULL,
                    resolved_at DATETIME,
                    INDEX idx_timestamp (timestamp)
                )
            ''')
    
    def store_metric(self, metric: MonitoringMetric):
        """Сохранение метрики в базу данных"""
        with sqlite3.connect(self.db_path) as conn:
            conn.execute('''
                INSERT INTO metrics (name, value, unit, status, details, timestamp)
                VALUES (?, ?, ?, ?, ?, ?)
            ''', (
                metric.name,
                metric.value,
                metric.unit,
                metric.status,
                json.dumps(metric.details) if metric.details else None,
                metric.timestamp.isoformat()
            ))
    
    def get_metrics(self, metric_name: str, hours: int = 24) -> List[Dict]:
        """Получение метрик за указанный период"""
        since = datetime.now() - timedelta(hours=hours)
        
        with sqlite3.connect(self.db_path) as conn:
            conn.row_factory = sqlite3.Row
            cursor = conn.execute('''
                SELECT * FROM metrics 
                WHERE name = ? AND timestamp > ?
                ORDER BY timestamp DESC
            ''', (metric_name, since.isoformat()))
            
            return [dict(row) for row in cursor.fetchall()]
    
    def monitor_system_resources(self) -> List[MonitoringMetric]:
        """Мониторинг системных ресурсов"""
        metrics = []
        
        # CPU использование
        cpu_percent = psutil.cpu_percent(interval=1)
        cpu_status = 'OK'
        if cpu_percent > self.config['thresholds']['cpu_critical']:
            cpu_status = 'CRITICAL'
        elif cpu_percent > self.config['thresholds']['cpu_warning']:
            cpu_status = 'WARNING'
        
        metrics.append(MonitoringMetric(
            name='cpu_usage',
            value=cpu_percent,
            unit='%',
            status=cpu_status,
            timestamp=datetime.now(),
            details={'cores': psutil.cpu_count()}
        ))
        
        # Память
        memory = psutil.virtual_memory()
        memory_percent = memory.percent
        memory_status = 'OK'
        if memory_percent > self.config['thresholds']['memory_critical']:
            memory_status = 'CRITICAL'
        elif memory_percent > self.config['thresholds']['memory_warning']:
            memory_status = 'WARNING'
        
        metrics.append(MonitoringMetric(
            name='memory_usage',
            value=memory_percent,
            unit='%',
            status=memory_status,
            timestamp=datetime.now(),
            details={
                'total': memory.total,
                'available': memory.available,
                'used': memory.used
            }
        ))
        
        # Дисковое пространство
        disk = psutil.disk_usage('/')
        disk_percent = (disk.used / disk.total) * 100
        disk_status = 'OK'
        if disk_percent > self.config['thresholds']['disk_critical']:
            disk_status = 'CRITICAL'
        elif disk_percent > self.config['thresholds']['disk_warning']:
            disk_status = 'WARNING'
        
        metrics.append(MonitoringMetric(
            name='disk_usage',
            value=disk_percent,
            unit='%',
            status=disk_status,
            timestamp=datetime.now(),
            details={
                'total': disk.total,
                'used': disk.used,
                'free': disk.free
            }
        ))
        
        # Загрузка системы
        load_avg = psutil.getloadavg()[0]  # 1-минутная средняя загрузка
        cpu_count = psutil.cpu_count()
        load_percent = (load_avg / cpu_count) * 100
        
        load_status = 'OK'
        if load_percent > 150:  # 1.5x количества CPU cores
            load_status = 'CRITICAL'
        elif load_percent > 100:
            load_status = 'WARNING'
        
        metrics.append(MonitoringMetric(
            name='system_load',
            value=load_avg,
            unit='',
            status=load_status,
            timestamp=datetime.now(),
            details={
                'load_1min': load_avg,
                'cpu_count': cpu_count,
                'load_percent': load_percent
            }
        ))
        
        return metrics
    
    def monitor_network_connectivity(self) -> List[MonitoringMetric]:
        """Мониторинг сетевого соединения"""
        metrics = []
        
        # Проверка подключения к MikroTik
        mikrotik_config = self.config['services']['mikrotik']
        mikrotik_status, mikrotik_time = self.check_tcp_connection(
            mikrotik_config['host'], 
            mikrotik_config['port']
        )
        
        metrics.append(MonitoringMetric(
            name='mikrotik_connectivity',
            value=1 if mikrotik_status else 0,
            unit='boolean',
            status='OK' if mikrotik_status else 'CRITICAL',
            timestamp=datetime.now(),
            details={
                'host': mikrotik_config['host'],
                'port': mikrotik_config['port'],
                'response_time': mikrotik_time
            }
        ))
        
        if mikrotik_time is not None:
            time_status = 'OK'
            if mikrotik_time > self.config['thresholds']['response_time_critical']:
                time_status = 'CRITICAL'
            elif mikrotik_time > self.config['thresholds']['response_time_warning']:
                time_status = 'WARNING'
            
            metrics.append(MonitoringMetric(
                name='mikrotik_response_time',
                value=mikrotik_time,
                unit='seconds',
                status=time_status,
                timestamp=datetime.now(),
                details={'host': mikrotik_config['host']}
            ))
        
        # Проверка интернет-соединения
        internet_status, internet_time = self.check_internet_connectivity()
        metrics.append(MonitoringMetric(
            name='internet_connectivity',
            value=1 if internet_status else 0,
            unit='boolean',
            status='OK' if internet_status else 'CRITICAL',
            timestamp=datetime.now(),
            details={'response_time': internet_time}
        ))
        
        return metrics
    
    def check_tcp_connection(self, host: str, port: int, timeout: int = 10) -> tuple:
        """Проверка TCP соединения"""
        try:
            start_time = time.time()
            sock = socket.create_connection((host, port), timeout)
            sock.close()
            response_time = time.time() - start_time
            return True, response_time
        except Exception as e:
            self.logger.error(f"TCP connection failed to {host}:{port} - {e}")
            return False, None
    
    def check_internet_connectivity(self) -> tuple:
        """Проверка интернет-соединения"""
        try:
            start_time = time.time()
            response = requests.get('http://8.8.8.8', timeout=10)
            response_time = time.time() - start_time
            return response.status_code == 200, response_time
        except Exception as e:
            self.logger.error(f"Internet connectivity check failed - {e}")
            return False, None
    
    def monitor_services(self) -> List[MonitoringMetric]:
        """Мониторинг сервисов"""
        metrics = []
        
        for service_name, service_config in self.config['services'].items():
            if service_name == 'mikrotik':
                continue  # Обрабатывается отдельно
            
            # Проверка процесса
            process_running = self.check_process(service_config['process_name'])
            
            metrics.append(MonitoringMetric(
                name=f'{service_name}_process',
                value=1 if process_running else 0,
                unit='boolean',
                status='OK' if process_running else 'CRITICAL',
                timestamp=datetime.now(),
                details={'process_name': service_config['process_name']}
            ))
            
            # Проверка порта (если указан)
            if service_config.get('port'):
                port_open = self.check_port(service_config['port'])
                
                metrics.append(MonitoringMetric(
                    name=f'{service_name}_port',
                    value=1 if port_open else 0,
                    unit='boolean',
                    status='OK' if port_open else 'CRITICAL',
                    timestamp=datetime.now(),
                    details={'port': service_config['port']}
                ))
        
        return metrics
    
    def check_process(self, process_name: str) -> bool:
        """Проверка запущен ли процесс"""
        try:
            for proc in psutil.process_iter(['name']):
                if process_name in proc.info['name']:
                    return True
            return False
        except Exception as e:
            self.logger.error(f"Process check failed for {process_name} - {e}")
            return False
    
    def check_port(self, port: int) -> bool:
        """Проверка открыт ли порт"""
        try:
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.settimeout(5)
            result = sock.connect_ex(('localhost', port))
            sock.close()
            return result == 0
        except Exception as e:
            self.logger.error(f"Port check failed for {port} - {e}")
            return False
    
    def monitor_application_health(self) -> List[MonitoringMetric]:
        """Мониторинг здоровья приложения"""
        metrics = []
        
        # Проверка HTTP эндпоинта здоровья
        try:
            start_time = time.time()
            response = requests.get('http://localhost/health', timeout=10)
            response_time = time.time() - start_time
            
            app_status = 'OK' if response.status_code == 200 else 'CRITICAL'
            
            metrics.append(MonitoringMetric(
                name='app_health_check',
                value=1 if response.status_code == 200 else 0,
                unit='boolean',
                status=app_status,
                timestamp=datetime.now(),
                details={
                    'status_code': response.status_code,
                    'response_time': response_time
                }
            ))
            
            metrics.append(MonitoringMetric(
                name='app_response_time',
                value=response_time,
                unit='seconds',
                status='OK' if response_time < 2.0 else 'WARNING',
                timestamp=datetime.now(),
                details={'endpoint': '/health'}
            ))
            
        except Exception as e:
            self.logger.error(f"Application health check failed - {e}")
            metrics.append(MonitoringMetric(
                name='app_health_check',
                value=0,
                unit='boolean',
                status='CRITICAL',
                timestamp=datetime.now(),
                details={'error': str(e)}
            ))
        
        # Проверка whitelist API
        try:
            start_time = time.time()
            response = requests.get('http://localhost/api/whitelist/status', 
                                  auth=('test_user', 'test_pass'), timeout=10)
            response_time = time.time() - start_time
            
            api_status = 'OK' if response.status_code in [200, 401] else 'CRITICAL'
            
            metrics.append(MonitoringMetric(
                name='whitelist_api_health',
                value=1 if response.status_code in [200, 401] else 0,
                unit='boolean',
                status=api_status,
                timestamp=datetime.now(),
                details={
                    'status_code': response.status_code,
                    'response_time': response_time
                }
            ))
            
        except Exception as e:
            self.logger.error(f"Whitelist API check failed - {e}")
            metrics.append(MonitoringMetric(
                name='whitelist_api_health',
                value=0,
                unit='boolean',
                status='CRITICAL',
                timestamp=datetime.now(),
                details={'error': str(e)}
            ))
        
        return metrics
    
    def monitor_log_errors(self) -> List[MonitoringMetric]:
        """Мониторинг ошибок в логах"""
        metrics = []
        
        log_files = [
            '/var/log/nginx/error.log',
            '/var/log/php8.3-fpm/sysadministrator-error.log',
            '/var/log/sysadministrator/application.log'
        ]
        
        for log_file in log_files:
            if Path(log_file).exists():
                error_count = self.count_recent_errors(log_file)
                
                error_status = 'OK'
                if error_count > 50:
                    error_status = 'CRITICAL'
                elif error_count > 10:
                    error_status = 'WARNING'
                
                metrics.append(MonitoringMetric(
                    name=f'log_errors_{Path(log_file).stem}',
                    value=error_count,
                    unit='count',
                    status=error_status,
                    timestamp=datetime.now(),
                    details={'log_file': log_file}
                ))
        
        return metrics
    
    def count_recent_errors(self, log_file: str, hours: int = 1) -> int:
        """Подсчет ошибок в логе за последний час"""
        try:
            # Используем journalctl для системных логов или grep для файлов
            if 'journal' in log_file:
                cmd = f'journalctl --since="{hours} hours ago" --priority=err --no-pager -q | wc -l'
            else:
                # Для упрощения считаем строки с ERROR за последний час
                cmd = f'grep "$(date -d "{hours} hours ago" "+%Y-%m-%d %H")" {log_file} | grep -i error | wc -l'
            
            result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
            return int(result.stdout.strip()) if result.returncode == 0 else 0
            
        except Exception as e:
            self.logger.error(f"Error counting log errors in {log_file} - {e}")
            return 0
    
    def send_alert(self, metric: MonitoringMetric):
        """Отправка уведомления о проблеме"""
        if not self.config['email']['enabled']:
            return
        
        # Проверяем, не отправляли ли уже уведомление недавно
        alert_key = f"{metric.name}_{metric.status}"
        current_time = datetime.now()
        
        if alert_key in self.alerts_sent:
            last_sent = self.alerts_sent[alert_key]
            if current_time - last_sent < timedelta(minutes=30):  # Не чаще раза в 30 минут
                return
        
        try:
            msg = MimeMultipart()
            msg['From'] = self.config['email']['from_address']
            msg['To'] = ', '.join(self.config['email']['to_addresses'])
            msg['Subject'] = f"[{metric.status}] Monitoring Alert: {metric.name}"
            
            body = f"""
Monitoring Alert

Metric: {metric.name}
Status: {metric.status}
Value: {metric.value} {metric.unit}
Timestamp: {metric.timestamp}
Host: {socket.gethostname()}

Details: {json.dumps(metric.details, indent=2) if metric.details else 'None'}

This is an automated monitoring alert.
"""
            
            msg.attach(MimeText(body, 'plain'))
            
            server = smtplib.SMTP(self.config['email']['smtp_server'], 
                                self.config['email']['smtp_port'])
            server.starttls()
            
            text = msg.as_string()
            server.sendmail(self.config['email']['from_address'], 
                          self.config['email']['to_addresses'], text)
            server.quit()
            
            # Записываем в базу данных
            with sqlite3.connect(self.db_path) as conn:
                conn.execute('''
                    INSERT INTO alerts (metric_name, alert_level, message, timestamp)
                    VALUES (?, ?, ?, ?)
                ''', (metric.name, metric.status, body, current_time.isoformat()))
            
            self.alerts_sent[alert_key] = current_time
            self.logger.info(f"Alert sent for {metric.name} - {metric.status}")
            
        except Exception as e:
            self.logger.error(f"Failed to send alert - {e}")
    
    def run_monitoring_cycle(self):
        """Запуск одного цикла мониторинга"""
        all_metrics = []
        
        # Сбор всех метрик
        all_metrics.extend(self.monitor_system_resources())
        all_metrics.extend(self.monitor_network_connectivity())
        all_metrics.extend(self.monitor_services())
        all_metrics.extend(self.monitor_application_health())
        all_metrics.extend(self.monitor_log_errors())
        
        # Сохранение метрик и отправка уведомлений
        for metric in all_metrics:
            self.store_metric(metric)
            
            if metric.status in ['WARNING', 'CRITICAL']:
                self.send_alert(metric)
        
        self.logger.info(f"Monitoring cycle completed, collected {len(all_metrics)} metrics")
        
        return all_metrics
    
    def start_background_monitoring(self):
        """Запуск фонового мониторинга"""
        def monitoring_loop():
            while self.running:
                try:
                    self.run_monitoring_cycle()
                    time.sleep(self.config['interval'])
                except Exception as e:
                    self.logger.error(f"Error in monitoring loop - {e}")
                    time.sleep(60)  # Подождать минуту перед повторной попыткой
        
        self.monitor_thread = threading.Thread(target=monitoring_loop, daemon=True)
        self.monitor_thread.start()
        self.logger.info("Background monitoring started")
    
    def stop_monitoring(self):
        """Остановка мониторинга"""
        self.running = False
        if self.monitor_thread and self.monitor_thread.is_alive():
            self.monitor_thread.join(timeout=30)
        self.logger.info("Monitoring stopped")
    
    def get_dashboard_data(self) -> Dict:
        """Получение данных для дашборда"""
        current_metrics = {}
        
        # Последние значения каждой метрики
        metric_names = [
            'cpu_usage', 'memory_usage', 'disk_usage', 'system_load',
            'mikrotik_connectivity', 'internet_connectivity',
            'app_health_check', 'whitelist_api_health'
        ]
        
        with sqlite3.connect(self.db_path) as conn:
            conn.row_factory = sqlite3.Row
            
            for metric_name in metric_names:
                cursor = conn.execute('''
                    SELECT * FROM metrics 
                    WHERE name = ? 
                    ORDER BY timestamp DESC 
                    LIMIT 1
                ''', (metric_name,))
                
                row = cursor.fetchone()
                if row:
                    current_metrics[metric_name] = dict(row)
        
        # Статистика алертов за последние 24 часа
        since = datetime.now() - timedelta(hours=24)
        with sqlite3.connect(self.db_path) as conn:
            cursor = conn.execute('''
                SELECT alert_level, COUNT(*) as count
                FROM alerts 
                WHERE timestamp > ?
                GROUP BY alert_level
            ''', (since.isoformat(),))
            
            alert_stats = {row[0]: row[1] for row in cursor.fetchall()}
        
        return {
            'current_metrics': current_metrics,
            'alert_stats': alert_stats,
            'last_update': datetime.now().isoformat()
        }

def main():
    """Главная функция для запуска мониторинга"""
    import argparse
    
    parser = argparse.ArgumentParser(description='Monitoring System')
    parser.add_argument('--config', default='/etc/monitoring/config.json',
                       help='Path to config file')
    parser.add_argument('--daemon', action='store_true',
                       help='Run as daemon')
    parser.add_argument('--once', action='store_true',
                       help='Run monitoring cycle once and exit')
    parser.add_argument('--dashboard', action='store_true',
                       help='Output dashboard data as JSON')
    
    args = parser.parse_args()
    
    monitor = MonitoringSystem(args.config)
    
    try:
        if args.dashboard:
            dashboard_data = monitor.get_dashboard_data()
            print(json.dumps(dashboard_data, indent=2))
        elif args.once:
            metrics = monitor.run_monitoring_cycle()
            print(f"Collected {len(metrics)} metrics")
        else:
            monitor.start_background_monitoring()
            
            if args.daemon:
                # Daemon mode - работаем в фоне
                try:
                    while True:
                        time.sleep(60)
                except KeyboardInterrupt:
                    pass
            else:
                # Interactive mode
                print("Monitoring started. Press Ctrl+C to stop.")
                try:
                    while True:
                        time.sleep(1)
                except KeyboardInterrupt:
                    pass
            
            monitor.stop_monitoring()
            
    except Exception as e:
        print(f"Error: {e}")
        return 1
    
    return 0

if __name__ == '__main__':
    exit(main())

10.2. Анализ логов и метрик

Система анализа логов и метрик обеспечивает глубокое понимание работы системы и выявление проблем:

#!/usr/bin/env python3
# /usr/local/bin/log_analyzer.py
# Система анализа логов и метрик

import re
import json
import sqlite3
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
from collections import defaultdict, Counter
from pathlib import Path
import matplotlib.pyplot as plt
import seaborn as sns
from typing import Dict, List, Tuple, Optional
import geoip2.database
import user_agents

class LogAnalyzer:
    def __init__(self, config_file="/etc/monitoring/log_analyzer.json"):
        self.config = self.load_config(config_file)
        self.db_path = self.config.get('database_path', '/var/lib/monitoring/logs.db')
        self.geoip_db = self.init_geoip()
        
        # Инициализация базы данных
        self.init_database()
        
        # Паттерны для анализа логов
        self.patterns = {
            'nginx_access': re.compile(
                r'(\S+) - (\S+) \[([^\]]+)\] "(\S+) (\S+) (\S+)" (\d+) (\d+) "([^"]*)" "([^"]*)"'
            ),
            'nginx_error': re.compile(
                r'(\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2}) \[(\w+)\] (\d+)#(\d+): (.+)'
            ),
            'php_error': re.compile(
                r'\[(\d{2}-\w{3}-\d{4} \d{2}:\d{2}:\d{2})\] PHP (\w+): (.+) in (.+) on line (\d+)'
            ),
            'auth_log': re.compile(
                r'(\w{3} \d{1,2} \d{2}:\d{2}:\d{2}) (\S+) (\S+): (.+)'
            )
        }
    
    def load_config(self, config_file):
        """Загрузка конфигурации анализатора"""
        default_config = {
            'database_path': '/var/lib/monitoring/logs.db',
            'log_files': {
                'nginx_access': '/var/log/nginx/access.log',
                'nginx_error': '/var/log/nginx/error.log',
                'php_error': '/var/log/php8.3-fpm/sysadministrator-error.log',
                'auth_log': '/var/log/auth.log'
            },
            'analysis': {
                'retention_days': 30,
                'suspicious_threshold': 100,
                'geoip_db_path': '/usr/share/GeoIP/GeoLite2-City.mmdb'
            },
            'reports': {
                'output_dir': '/var/log/analysis_reports',
                'generate_charts': True
            }
        }
        
        try:
            with open(config_file, 'r') as f:
                config = json.load(f)
                default_config.update(config)
                return default_config
        except FileNotFoundError:
            return default_config
    
    def init_geoip(self):
        """Инициализация GeoIP базы данных"""
        geoip_path = self.config['analysis']['geoip_db_path']
        try:
            if Path(geoip_path).exists():
                return geoip2.database.Reader(geoip_path)
        except Exception as e:
            print(f"Warning: Could not initialize GeoIP database: {e}")
        return None
    
    def init_database(self):
        """Инициализация базы данных для хранения анализируемых данных"""
        Path(self.db_path).parent.mkdir(parents=True, exist_ok=True)
        
        with sqlite3.connect(self.db_path) as conn:
            # Таблица для access логов
            conn.execute('''
                CREATE TABLE IF NOT EXISTS access_logs (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    timestamp DATETIME,
                    ip_address TEXT,
                    method TEXT,
                    url TEXT,
                    status_code INTEGER,
                    response_size INTEGER,
                    user_agent TEXT,
                    referer TEXT,
                    response_time REAL,
                    country TEXT,
                    city TEXT
                )
            ''')
            
            # Таблица для error логов
            conn.execute('''
                CREATE TABLE IF NOT EXISTS error_logs (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    timestamp DATETIME,
                    log_type TEXT,
                    level TEXT,
                    message TEXT,
                    file_path TEXT,
                    line_number INTEGER,
                    ip_address TEXT
                )
            ''')
            
            # Таблица для статистики IP адресов
            conn.execute('''
                CREATE TABLE IF NOT EXISTS ip_statistics (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    ip_address TEXT UNIQUE,
                    request_count INTEGER DEFAULT 0,
                    error_count INTEGER DEFAULT 0,
                    first_seen DATETIME,
                    last_seen DATETIME,
                    country TEXT,
                    is_suspicious BOOLEAN DEFAULT 0,
                    user_agents TEXT
                )
            ''')
            
            # Индексы для быстрого поиска
            conn.execute('CREATE INDEX IF NOT EXISTS idx_access_timestamp ON access_logs(timestamp)')
            conn.execute('CREATE INDEX IF NOT EXISTS idx_access_ip ON access_logs(ip_address)')
            conn.execute('CREATE INDEX IF NOT EXISTS idx_error_timestamp ON error_logs(timestamp)')
            conn.execute('CREATE INDEX IF NOT EXISTS idx_ip_stats_ip ON ip_statistics(ip_address)')
    
    def parse_nginx_access_log(self, line: str) -> Optional[Dict]:
        """Парсинг строки access лога NGINX"""
        match = self.patterns['nginx_access'].match(line)
        if not match:
            return None
        
        ip, user, timestamp_str, method, url, protocol, status, size, referer, user_agent = match.groups()
        
        try:
            # Парсинг времени
            timestamp = datetime.strptime(timestamp_str, '%d/%b/%Y:%H:%M:%S %z')
            
            # Получение геолокации
            country, city = self.get_location(ip)
            
            return {
                'timestamp': timestamp,
                'ip_address': ip,
                'method': method,
                'url': url,
                'status_code': int(status),
                'response_size': int(size) if size != '-' else 0,
                'user_agent': user_agent,
                'referer': referer if referer != '-' else None,
                'country': country,
                'city': city
            }
        except ValueError as e:
            print(f"Error parsing access log line: {e}")
            return None
    
    def parse_nginx_error_log(self, line: str) -> Optional[Dict]:
        """Парсинг строки error лога NGINX"""
        match = self.patterns['nginx_error'].match(line)
        if not match:
            return None
        
        timestamp_str, level, pid, tid, message = match.groups()
        
        try:
            timestamp = datetime.strptime(timestamp_str, '%Y/%m/%d %H:%M:%S')
            
            # Извлечение IP из сообщения если есть
            ip_match = re.search(r'client: (\d+\.\d+\.\d+\.\d+)', message)
            ip_address = ip_match.group(1) if ip_match else None
            
            return {
                'timestamp': timestamp,
                'log_type': 'nginx_error',
                'level': level,
                'message': message,
                'ip_address': ip_address
            }
        except ValueError as e:
            print(f"Error parsing error log line: {e}")
            return None
    
    def parse_php_error_log(self, line: str) -> Optional[Dict]:
        """Парсинг строки PHP error лога"""
        match = self.patterns['php_error'].match(line)
        if not match:
            return None
        
        timestamp_str, level, message, file_path, line_number = match.groups()
        
        try:
            timestamp = datetime.strptime(timestamp_str, '%d-%b-%Y %H:%M:%S')
            
            return {
                'timestamp': timestamp,
                'log_type': 'php_error',
                'level': level,
                'message': message,
                'file_path': file_path,
                'line_number': int(line_number)
            }
        except ValueError as e:
            print(f"Error parsing PHP error log line: {e}")
            return None
    
    def get_location(self, ip: str) -> Tuple[str, str]:
        """Получение местоположения по IP адресу"""
        if not self.geoip_db:
            return 'Unknown', 'Unknown'
        
        try:
            response = self.geoip_db.city(ip)
            country = response.country.name or 'Unknown'
            city = response.city.name or 'Unknown'
            return country, city
        except Exception:
            return 'Unknown', 'Unknown'
    
    def process_log_file(self, file_path: str, log_type: str, limit: int = None):
        """Обработка файла логов"""
        if not Path(file_path).exists():
            print(f"Log file not found: {file_path}")
            return
        
        processed_count = 0
        error_count = 0
        
        with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
            lines = f.readlines()
            
            if limit:
                lines = lines[-limit:]  # Обрабатываем только последние N строк
            
            with sqlite3.connect(self.db_path) as conn:
                for line in lines:
                    line = line.strip()
                    if not line:
                        continue
                    
                    try:
                        if log_type == 'nginx_access':
                            data = self.parse_nginx_access_log(line)
                            if data:
                                conn.execute('''
                                    INSERT INTO access_logs 
                                    (timestamp, ip_address, method, url, status_code, response_size, 
                                     user_agent, referer, country, city)
                                    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                                ''', (
                                    data['timestamp'], data['ip_address'], data['method'],
                                    data['url'], data['status_code'], data['response_size'],
                                    data['user_agent'], data['referer'], data['country'], data['city']
                                ))
                                processed_count += 1
                        
                        elif log_type in ['nginx_error', 'php_error']:
                            if log_type == 'nginx_error':
                                data = self.parse_nginx_error_log(line)
                            else:
                                data = self.parse_php_error_log(line)
                            
                            if data:
                                conn.execute('''
                                    INSERT INTO error_logs 
                                    (timestamp, log_type, level, message, file_path, line_number, ip_address)
                                    VALUES (?, ?, ?, ?, ?, ?, ?)
                                ''', (
                                    data['timestamp'], data['log_type'], data['level'],
                                    data['message'], data.get('file_path'), 
                                    data.get('line_number'), data.get('ip_address')
                                ))
                                processed_count += 1
                    
                    except Exception as e:
                        error_count += 1
                        if error_count < 10:  # Показываем только первые 10 ошибок
                            print(f"Error processing line: {e}")
        
        print(f"Processed {processed_count} lines from {file_path}, {error_count} errors")
    
    def update_ip_statistics(self):
        """Обновление статистики по IP адресам"""
        with sqlite3.connect(self.db_path) as conn:
            # Очищаем старую статистику
            conn.execute('DELETE FROM ip_statistics')
            
            # Собираем статистику из access логов
            cursor = conn.execute('''
                SELECT ip_address, COUNT(*) as request_count,
                       MIN(timestamp) as first_seen, MAX(timestamp) as last_seen,
                       country, GROUP_CONCAT(DISTINCT user_agent) as user_agents
                FROM access_logs 
                GROUP BY ip_address
            ''')
            
            for row in cursor.fetchall():
                ip, request_count, first_seen, last_seen, country, user_agents = row
                
                # Подсчет ошибок для этого IP
                error_cursor = conn.execute('''
                    SELECT COUNT(*) FROM error_logs WHERE ip_address = ?
                ''', (ip,))
                error_count = error_cursor.fetchone()[0]
                
                # Определение подозрительности
                is_suspicious = self.is_suspicious_ip(ip, request_count, error_count, user_agents)
                
                conn.execute('''
                    INSERT INTO ip_statistics 
                    (ip_address, request_count, error_count, first_seen, last_seen, 
                     country, is_suspicious, user_agents)
                    VALUES (?, ?, ?, ?, ?, ?, ?, ?)
                ''', (ip, request_count, error_count, first_seen, last_seen, 
                      country, is_suspicious, user_agents))
    
    def is_suspicious_ip(self, ip: str, request_count: int, error_count: int, user_agents: str) -> bool:
        """Определение подозрительности IP адреса"""
        threshold = self.config['analysis']['suspicious_threshold']
        
        # Слишком много запросов
        if request_count > threshold * 10:
            return True
        
        # Высокий процент ошибок
        if error_count > 0 and (error_count / request_count) > 0.5:
            return True
        
        # Подозрительные User-Agent'ы
        if user_agents:
            suspicious_agents = ['bot', 'crawler', 'scanner', 'curl', 'wget', 'python']
            for agent in suspicious_agents:
                if agent.lower() in user_agents.lower():
                    return True
        
        return False
    
    def analyze_traffic_patterns(self) -> Dict:
        """Анализ паттернов трафика"""
        with sqlite3.connect(self.db_path) as conn:
            # Запросы по часам
            df_hourly = pd.read_sql_query('''
                SELECT strftime('%H', timestamp) as hour, COUNT(*) as requests
                FROM access_logs 
                WHERE timestamp > datetime('now', '-7 days')
                GROUP BY hour
                ORDER BY hour
            ''', conn)
            
            # Статусы ответов
            df_status = pd.read_sql_query('''
                SELECT status_code, COUNT(*) as count
                FROM access_logs 
                WHERE timestamp > datetime('now', '-24 hours')
                GROUP BY status_code
                ORDER BY count DESC
            ''', conn)
            
            # Топ IP адресов
            df_top_ips = pd.read_sql_query('''
                SELECT ip_address, request_count, country, is_suspicious
                FROM ip_statistics 
                ORDER BY request_count DESC
                LIMIT 20
            ''', conn)
            
            # Топ User-Agent'ы
            cursor = conn.execute('''
                SELECT user_agent, COUNT(*) as count
                FROM access_logs 
                WHERE timestamp > datetime('now', '-24 hours')
                AND user_agent IS NOT NULL
                GROUP BY user_agent
                ORDER BY count DESC
                LIMIT 10
            ''')
            
            top_user_agents = [{'user_agent': row[0], 'count': row[1]} for row in cursor.fetchall()]
            
            # Анализ User-Agent'ов
            parsed_agents = []
            for agent_data in top_user_agents:
                try:
                    ua = user_agents.parse(agent_data['user_agent'])
                    parsed_agents.append({
                        'browser': ua.browser.family,
                        'os': ua.os.family,
                        'device': ua.device.family,
                        'count': agent_data['count']
                    })
                except Exception:
                    parsed_agents.append({
                        'browser': 'Unknown',
                        'os': 'Unknown',
                        'device': 'Unknown',
                        'count': agent_data['count']
                    })
        
        return {
            'hourly_traffic': df_hourly.to_dict('records'),
            'status_codes': df_status.to_dict('records'),
            'top_ips': df_top_ips.to_dict('records'),
            'user_agents': parsed_agents
        }
    
    def analyze_security_events(self) -> Dict:
        """Анализ событий безопасности"""
        with sqlite3.connect(self.db_path) as conn:
            # Подозрительные IP адреса
            df_suspicious = pd.read_sql_query('''
                SELECT ip_address, request_count, error_count, country
                FROM ip_statistics 
                WHERE is_suspicious = 1
                ORDER BY request_count DESC
            ''', conn)
            
            # Ошибки 4xx и 5xx
            df_errors = pd.read_sql_query('''
                SELECT ip_address, status_code, COUNT(*) as count,
                       strftime('%Y-%m-%d %H:00', timestamp) as hour
                FROM access_logs 
                WHERE timestamp > datetime('now', '-24 hours')
                AND status_code >= 400
                GROUP BY ip_address, status_code, hour
                ORDER BY count DESC
                LIMIT 50
            ''', conn)
            
            # Попытки доступа к подозрительным URL
            suspicious_urls = ['/admin', '/wp-admin', '/.env', '/.git', '/config', '/backup']
            suspicious_access = []
            
            for url_pattern in suspicious_urls:
                cursor = conn.execute('''
                    SELECT ip_address, url, COUNT(*) as count
                    FROM access_logs 
                    WHERE timestamp > datetime('now', '-24 hours')
                    AND url LIKE ?
                    GROUP BY ip_address, url
                    ORDER BY count DESC
                    LIMIT 10
                ''', (f'%{url_pattern}%',))
                
                for row in cursor.fetchall():
                    suspicious_access.append({
                        'ip_address': row[0],
                        'url': row[1],
                        'count': row[2],
                        'pattern': url_pattern
                    })
            
            # SQL injection попытки
            sql_patterns = ['union', 'select', 'insert', 'delete', 'drop', 'exec']
            sql_injection_attempts = []
            
            for pattern in sql_patterns:
                cursor = conn.execute('''
                    SELECT ip_address, url, COUNT(*) as count
                    FROM access_logs 
                    WHERE timestamp > datetime('now', '-24 hours')
                    AND (url LIKE ? OR url LIKE ?)
                    GROUP BY ip_address, url
                    ORDER BY count DESC
                    LIMIT 5
                ''', (f'%{pattern}%', f'%{pattern.upper()}%'))
                
                for row in cursor.fetchall():
                    sql_injection_attempts.append({
                        'ip_address': row[0],
                        'url': row[1],
                        'count': row[2],
                        'pattern': pattern
                    })
        
        return {
            'suspicious_ips': df_suspicious.to_dict('records'),
            'error_responses': df_errors.to_dict('records'),
            'suspicious_access': suspicious_access,
            'sql_injection_attempts': sql_injection_attempts
        }
    
    def generate_charts(self, analysis_data: Dict, output_dir: str):
        """Генерация графиков и диагра
```python
    def generate_charts(self, analysis_data: Dict, output_dir: str):
        """Генерация графиков и диаграмм"""
        if not self.config['reports']['generate_charts']:
            return
        
        Path(output_dir).mkdir(parents=True, exist_ok=True)
        
        # Настройка стиля графиков
        plt.style.use('seaborn-v0_8')
        sns.set_palette("husl")
        
        # График трафика по часам
        if analysis_data['hourly_traffic']:
            df_hourly = pd.DataFrame(analysis_data['hourly_traffic'])
            
            plt.figure(figsize=(12, 6))
            plt.bar(df_hourly['hour'], df_hourly['requests'])
            plt.title('Распределение трафика по часам (последние 7 дней)')
            plt.xlabel('Час')
            plt.ylabel('Количество запросов')
            plt.xticks(range(24))
            plt.tight_layout()
            plt.savefig(f"{output_dir}/hourly_traffic.png", dpi=300, bbox_inches='tight')
            plt.close()
        
        # График статусов ответов
        if analysis_data['status_codes']:
            df_status = pd.DataFrame(analysis_data['status_codes'])
            
            plt.figure(figsize=(10, 6))
            colors = ['green' if x < 400 else 'orange' if x < 500 else 'red' for x in df_status['status_code']]
            plt.bar(df_status['status_code'].astype(str), df_status['count'], color=colors)
            plt.title('Распределение статусов HTTP ответов (последние 24 часа)')
            plt.xlabel('Статус код')
            plt.ylabel('Количество')
            plt.xticks(rotation=45)
            plt.tight_layout()
            plt.savefig(f"{output_dir}/status_codes.png", dpi=300, bbox_inches='tight')
            plt.close()
        
        # Топ IP адресов
        if analysis_data['top_ips']:
            df_ips = pd.DataFrame(analysis_data['top_ips'])
            
            plt.figure(figsize=(12, 8))
            colors = ['red' if x else 'blue' for x in df_ips['is_suspicious']]
            bars = plt.barh(range(len(df_ips)), df_ips['request_count'], color=colors)
            plt.yticks(range(len(df_ips)), df_ips['ip_address'])
            plt.title('Топ IP адресов по количеству запросов')
            plt.xlabel('Количество запросов')
            
            # Легенда
            import matplotlib.patches as mpatches
            blue_patch = mpatches.Patch(color='blue', label='Обычные')
            red_patch = mpatches.Patch(color='red', label='Подозрительные')
            plt.legend(handles=[blue_patch, red_patch])
            
            plt.tight_layout()
            plt.savefig(f"{output_dir}/top_ips.png", dpi=300, bbox_inches='tight')
            plt.close()
        
        # Распределение User-Agent'ов
        if analysis_data['user_agents']:
            df_agents = pd.DataFrame(analysis_data['user_agents'])
            browser_counts = df_agents.groupby('browser')['count'].sum().head(10)
            
            plt.figure(figsize=(10, 8))
            plt.pie(browser_counts.values, labels=browser_counts.index, autopct='%1.1f%%')
            plt.title('Распределение браузеров')
            plt.tight_layout()
            plt.savefig(f"{output_dir}/user_agents.png", dpi=300, bbox_inches='tight')
            plt.close()
    
    def generate_report(self, output_file: str = None) -> Dict:
        """Генерация полного отчета анализа"""
        if not output_file:
            timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
            output_file = f"{self.config['reports']['output_dir']}/analysis_report_{timestamp}.json"
        
        # Обработка логов
        for log_type, log_path in self.config['log_files'].items():
            self.process_log_file(log_path, log_type, limit=10000)  # Последние 10k строк
        
        # Обновление статистики IP
        self.update_ip_statistics()
        
        # Анализ данных
        traffic_analysis = self.analyze_traffic_patterns()
        security_analysis = self.analyze_security_events()
        
        # Генерация графиков
        charts_dir = f"{self.config['reports']['output_dir']}/charts"
        self.generate_charts(traffic_analysis, charts_dir)
        
        # Итоговый отчет
        report = {
            'timestamp': datetime.now().isoformat(),
            'analysis_period': '24 hours',
            'traffic_analysis': traffic_analysis,
            'security_analysis': security_analysis,
            'summary': self.generate_summary(traffic_analysis, security_analysis),
            'charts_directory': charts_dir
        }
        
        # Сохранение отчета
        Path(output_file).parent.mkdir(parents=True, exist_ok=True)
        with open(output_file, 'w', encoding='utf-8') as f:
            json.dump(report, f, indent=2, ensure_ascii=False, default=str)
        
        print(f"Analysis report saved to: {output_file}")
        return report
    
    def generate_summary(self, traffic_analysis: Dict, security_analysis: Dict) -> Dict:
        """Генерация краткой сводки анализа"""
        total_requests = sum(item['requests'] for item in traffic_analysis['hourly_traffic'])
        error_responses = sum(item['count'] for item in traffic_analysis['status_codes'] if item['status_code'] >= 400)
        suspicious_ips_count = len(security_analysis['suspicious_ips'])
        
        return {
            'total_requests_24h': total_requests,
            'error_responses_24h': error_responses,
            'error_rate': (error_responses / total_requests * 100) if total_requests > 0 else 0,
            'suspicious_ips': suspicious_ips_count,
            'unique_ips': len(traffic_analysis['top_ips']),
            'sql_injection_attempts': len(security_analysis['sql_injection_attempts']),
            'suspicious_url_access': len(security_analysis['suspicious_access'])
        }

def main():
    """Главная функция для запуска анализа логов"""
    import argparse
    
    parser = argparse.ArgumentParser(description='Log Analysis System')
    parser.add_argument('--config', default='/etc/monitoring/log_analyzer.json',
                       help='Path to config file')
    parser.add_argument('--output', help='Output report file')
    parser.add_argument('--process-only', action='store_true',
                       help='Only process logs without analysis')
    
    args = parser.parse_args()
    
    analyzer = LogAnalyzer(args.config)
    
    try:
        if args.process_only:
            # Только обработка логов
            for log_type, log_path in analyzer.config['log_files'].items():
                analyzer.process_log_file(log_path, log_type)
            print("Log processing completed")
        else:
            # Полный анализ и генерация отчета
            report = analyzer.generate_report(args.output)
            print("Analysis completed successfully")
            
            # Вывод краткой сводки
            summary = report['summary']
            print("\n=== SUMMARY ===")
            print(f"Total requests (24h): {summary['total_requests_24h']}")
            print(f"Error responses (24h): {summary['error_responses_24h']}")
            print(f"Error rate: {summary['error_rate']:.2f}%")
            print(f"Suspicious IPs: {summary['suspicious_ips']}")
            print(f"SQL injection attempts: {summary['sql_injection_attempts']}")
    
    except Exception as e:
        print(f"Error: {e}")
        return 1
    
    return 0

if __name__ == '__main__':
    exit(main())

10.3. Диагностика проблем

Система диагностики проблем обеспечивает быстрое выявление и устранение неполадок в системе управления белым списком:

#!/bin/bash
# /usr/local/bin/diagnostics.sh
# Система диагностики проблем

# Конфигурация
DIAGNOSTIC_LOG="/var/log/diagnostics.log"
TEMP_DIR="/tmp/diagnostics"
MIKROTIK_HOST="192.168.1.1"
MIKROTIK_USER="diagnostic_user"
WEB_ROOT="/var/www/html/sysadministrator"

# Создание временной директории
mkdir -p "$TEMP_DIR"

# Цвета для вывода
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'

log_diagnostic() {
    local level="$1"
    local message="$2"
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    
    echo "[$timestamp] [$level] $message" | tee -a "$DIAGNOSTIC_LOG"
}

# Диагностика системных ресурсов
diagnose_system_resources() {
    log_diagnostic "INFO" "Starting system resources diagnostics"
    
    local issues_found=0
    local recommendations=()
    
    # CPU использование
    local cpu_usage=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | cut -d'%' -f1)
    if (( $(echo "$cpu_usage > 80" | bc -l) )); then
        echo -e "${RED}[CRITICAL]${NC} High CPU usage: ${cpu_usage}%"
        ((issues_found++))
        recommendations+=("Consider optimizing processes or upgrading hardware")
        
        # Анализ топ процессов по CPU
        echo "Top CPU consumers:"
        ps aux --sort=-%cpu | head -10
    else
        echo -e "${GREEN}[OK]${NC} CPU usage: ${cpu_usage}%"
    fi
    
    # Память
    local memory_info=$(free -m)
    local total_mem=$(echo "$memory_info" | grep "Mem:" | awk '{print $2}')
    local used_mem=$(echo "$memory_info" | grep "Mem:" | awk '{print $3}')
    local memory_usage=$(echo "scale=1; $used_mem * 100 / $total_mem" | bc)
    
    if (( $(echo "$memory_usage > 90" | bc -l) )); then
        echo -e "${RED}[CRITICAL]${NC} High memory usage: ${memory_usage}%"
        ((issues_found++))
        recommendations+=("Free up memory or add more RAM")
        
        # Анализ топ процессов по памяти
        echo "Top memory consumers:"
        ps aux --sort=-%mem | head -10
    elif (( $(echo "$memory_usage > 75" | bc -l) )); then
        echo -e "${YELLOW}[WARNING]${NC} High memory usage: ${memory_usage}%"
        recommendations+=("Monitor memory usage closely")
    else
        echo -e "${GREEN}[OK]${NC} Memory usage: ${memory_usage}%"
    fi
    
    # Дисковое пространство
    local disk_usage=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
    if [ "$disk_usage" -gt 90 ]; then
        echo -e "${RED}[CRITICAL]${NC} High disk usage: ${disk_usage}%"
        ((issues_found++))
        recommendations+=("Clean up disk space immediately")
        
        # Анализ больших файлов
        echo "Largest files and directories:"
        du -h / 2>/dev/null | sort -rh | head -10
    elif [ "$disk_usage" -gt 75 ]; then
        echo -e "${YELLOW}[WARNING]${NC} High disk usage: ${disk_usage}%"
        recommendations+=("Plan for disk cleanup")
    else
        echo -e "${GREEN}[OK]${NC} Disk usage: ${disk_usage}%"
    fi
    
    # I/O Wait
    local iowait=$(iostat -c 1 2 | tail -1 | awk '{print $4}')
    if (( $(echo "$iowait > 20" | bc -l) )); then
        echo -e "${RED}[CRITICAL]${NC} High I/O wait: ${iowait}%"
        ((issues_found++))
        recommendations+=("Check disk performance and optimize I/O operations")
    elif (( $(echo "$iowait > 10" | bc -l) )); then
        echo -e "${YELLOW}[WARNING]${NC} Elevated I/O wait: ${iowait}%"
    else
        echo -e "${GREEN}[OK]${NC} I/O wait: ${iowait}%"
    fi
    
    # Системная загрузка
    local load_avg=$(uptime | awk -F'load average:' '{print $2}' | awk '{print $1}' | sed 's/,//')
    local cpu_cores=$(nproc)
    local load_per_core=$(echo "scale=2; $load_avg / $cpu_cores" | bc)
    
    if (( $(echo "$load_per_core > 2" | bc -l) )); then
        echo -e "${RED}[CRITICAL]${NC} High system load: ${load_avg} (${load_per_core} per core)"
        ((issues_found++))
        recommendations+=("Investigate high system load causes")
    elif (( $(echo "$load_per_core > 1" | bc -l) )); then
        echo -e "${YELLOW}[WARNING]${NC} Elevated system load: ${load_avg} (${load_per_core} per core)"
    else
        echo -e "${GREEN}[OK]${NC} System load: ${load_avg} (${load_per_core} per core)"
    fi
    
    # Вывод рекомендаций
    if [ ${#recommendations[@]} -gt 0 ]; then
        echo -e "\n${BLUE}Recommendations:${NC}"
        for rec in "${recommendations[@]}"; do
            echo "- $rec"
        done
    fi
    
    return $issues_found
}

# Диагностика сетевых соединений
diagnose_network() {
    log_diagnostic "INFO" "Starting network diagnostics"
    
    local issues_found=0
    
    # Проверка подключения к MikroTik
    echo "Testing MikroTik connectivity..."
    if ping -c 3 -W 5 "$MIKROTIK_HOST" >/dev/null 2>&1; then
        local ping_time=$(ping -c 1 "$MIKROTIK_HOST" | grep "time=" | awk -F'time=' '{print $2}' | awk '{print $1}')
        echo -e "${GREEN}[OK]${NC} MikroTik ping: ${ping_time}ms"
        
        # Проверка SSH порта
        if timeout 5 bash -c "</dev/tcp/$MIKROTIK_HOST/2222"; then
            echo -e "${GREEN}[OK]${NC} MikroTik SSH port accessible"
        else
            echo -e "${RED}[ERROR]${NC} MikroTik SSH port not accessible"
            ((issues_found++))
        fi
        
        # Проверка API порта
        if timeout 5 bash -c "</dev/tcp/$MIKROTIK_HOST/8729"; then
            echo -e "${GREEN}[OK]${NC} MikroTik API port accessible"
        else
            echo -e "${YELLOW}[WARNING]${NC} MikroTik API port not accessible"
        fi
    else
        echo -e "${RED}[CRITICAL]${NC} Cannot reach MikroTik device"
        ((issues_found++))
    fi
    
    # Проверка интернет-соединения
    echo "Testing internet connectivity..."
    if ping -c 3 -W 5 8.8.8.8 >/dev/null 2>&1; then
        echo -e "${GREEN}[OK]${NC} Internet connectivity"
    else
        echo -e "${RED}[ERROR]${NC} No internet connectivity"
        ((issues_found++))
    fi
    
    # Проверка DNS
    if nslookup google.com >/dev/null 2>&1; then
        echo -e "${GREEN}[OK]${NC} DNS resolution"
    else
        echo -e "${RED}[ERROR]${NC} DNS resolution failed"
        ((issues_found++))
    fi
    
    # Анализ активных соединений
    local total_connections=$(netstat -an | grep ESTABLISHED | wc -l)
    if [ "$total_connections" -gt 1000 ]; then
        echo -e "${RED}[CRITICAL]${NC} Very high number of connections: $total_connections"
        ((issues_found++))
        
        echo "Top connections by IP:"
        netstat -an | grep ESTABLISHED | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -nr | head -10
    elif [ "$total_connections" -gt 500 ]; then
        echo -e "${YELLOW}[WARNING]${NC} High number of connections: $total_connections"
    else
        echo -e "${GREEN}[OK]${NC} Active connections: $total_connections"
    fi
    
    # Проверка открытых портов
    echo "Checking listening ports..."
    local listening_ports=$(netstat -tuln | grep LISTEN)
    echo "$listening_ports"
    
    return $issues_found
}

# Диагностика веб-сервера
diagnose_web_server() {
    log_diagnostic "INFO" "Starting web server diagnostics"
    
    local issues_found=0
    
    # Проверка статуса NGINX
    if systemctl is-active --quiet nginx; then
        echo -e "${GREEN}[OK]${NC} NGINX service is running"
        
        # Проверка конфигурации
        if nginx -t >/dev/null 2>&1; then
            echo -e "${GREEN}[OK]${NC} NGINX configuration is valid"
        else
            echo -e "${RED}[ERROR]${NC} NGINX configuration error"
            nginx -t 2>&1
            ((issues_found++))
        fi
        
        # Проверка доступности веб-сервера
        local response_code=$(curl -s -o /dev/null -w "%{http_code}" http://localhost/)
        if [ "$response_code" = "200" ] || [ "$response_code" = "401" ]; then
            echo -e "${GREEN}[OK]${NC} Web server responding (HTTP $response_code)"
        else
            echo -e "${RED}[ERROR]${NC} Web server not responding properly (HTTP $response_code)"
            ((issues_found++))
        fi
        
        # Проверка времени ответа
        local response_time=$(curl -s -w "%{time_total}" -o /dev/null http://localhost/ 2>/dev/null)
        if (( $(echo "$response_time > 5" | bc -l) )); then
            echo -e "${YELLOW}[WARNING]${NC} Slow response time: ${response_time}s"
        else
            echo -e "${GREEN}[OK]${NC} Response time: ${response_time}s"
        fi
    else
        echo -e "${RED}[CRITICAL]${NC} NGINX service is not running"
        ((issues_found++))
        
        echo "Attempting to start NGINX..."
        if systemctl start nginx; then
            echo -e "${GREEN}[OK]${NC} NGINX started successfully"
        else
            echo -e "${RED}[ERROR]${NC} Failed to start NGINX"
            systemctl status nginx
        fi
    fi
    
    # Проверка PHP-FPM
    if systemctl is-active --quiet php8.3-fpm; then
        echo -e "${GREEN}[OK]${NC} PHP-FPM service is running"
        
        # Проверка PHP-FPM статуса
        local fpm_status=$(curl -s http://localhost/fmp-status 2>/dev/null)
        if echo "$fmp_status" | grep -q "accepted conn"; then
            echo -e "${GREEN}[OK]${NC} PHP-FPM status page accessible"
            
            # Анализ статуса PHP-FPM
            local active_processes=$(echo "$fpm_status" | grep "active processes" | awk '{print $3}')
            local max_children_reached=$(echo "$fmp_status" | grep "max children reached" | awk '{print $4}')
            
            if [ "$max_children_reached" -gt 0 ]; then
                echo -e "${YELLOW}[WARNING]${NC} PHP-FPM max children limit reached $max_children_reached times"
            fi
        else
            echo -e "${YELLOW}[WARNING]${NC} PHP-FPM status page not accessible"
        fi
    else
        echo -e "${RED}[CRITICAL]${NC} PHP-FPM service is not running"
        ((issues_found++))
    fi
    
    # Проверка логов на ошибки
    local error_count=$(tail -100 /var/log/nginx/error.log 2>/dev/null | wc -l)
    if [ "$error_count" -gt 10 ]; then
        echo -e "${YELLOW}[WARNING]${NC} Recent NGINX errors found: $error_count"
        echo "Recent errors:"
        tail -10 /var/log/nginx/error.log 2>/dev/null
    else
        echo -e "${GREEN}[OK]${NC} No recent NGINX errors"
    fi
    
    return $issues_found
}

# Диагностика приложения
diagnose_application() {
    log_diagnostic "INFO" "Starting application diagnostics"
    
    local issues_found=0
    
    # Проверка файловой структуры
    echo "Checking application file structure..."
    local required_files=(
        "$WEB_ROOT/index.php"
        "$WEB_ROOT/config/config.php"
        "$WEB_ROOT/classes/WhitelistManager.php"
        "$WEB_ROOT/classes/MikroTikAPI.php"
    )
    
    for file in "${required_files[@]}"; do
        if [ -f "$file" ]; then
            echo -e "${GREEN}[OK]${NC} Found: $file"
        else
            echo -e "${RED}[ERROR]${NC} Missing: $file"
            ((issues_found++))
        fi
    done
    
    # Проверка прав доступа
    local web_perms=$(stat -c "%a" "$WEB_ROOT" 2>/dev/null)
    if [ "$web_perms" = "755" ] || [ "$web_perms" = "750" ]; then
        echo -e "${GREEN}[OK]${NC} Web root permissions: $web_perms"
    else
        echo -e "${YELLOW}[WARNING]${NC} Web root permissions: $web_perms (recommended: 755)"
    fi
    
    # Проверка PHP конфигурации
    if command -v php >/dev/null; then
        local php_version=$(php -v | head -n1 | awk '{print $2}')
        echo -e "${GREEN}[OK]${NC} PHP version: $php_version"
        
        # Проверка расширений PHP
        local required_extensions=("curl" "json" "mbstring" "openssl" "pdo" "sqlite3")
        for ext in "${required_extensions[@]}"; do
            if php -m | grep -q "^$ext$"; then
                echo -e "${GREEN}[OK]${NC} PHP extension: $ext"
            else
                echo -e "${RED}[ERROR]${NC} Missing PHP extension: $ext"
                ((issues_found++))
            fi
        done
        
        # Синтаксические ошибки в PHP файлах
        local php_errors=0
        for php_file in $(find "$WEB_ROOT" -name "*.php" 2>/dev/null); do
            if ! php -l "$php_file" >/dev/null 2>&1; then
                echo -e "${RED}[ERROR]${NC} PHP syntax error in: $php_file"
                ((php_errors++))
            fi
        done
        
        if [ $php_errors -eq 0 ]; then
            echo -e "${GREEN}[OK]${NC} No PHP syntax errors found"
        else
            echo -e "${RED}[ERROR]${NC} Found $php_errors PHP syntax errors"
            ((issues_found++))
        fi
    else
        echo -e "${RED}[ERROR]${NC} PHP not found"
        ((issues_found++))
    fi
    
    # Проверка базы данных SQLite
    local db_files=(
        "/var/lib/mikrotik/whitelist.db"
        "/var/lib/mikrotik/audit.db"
    )
    
    for db_file in "${db_files[@]}"; do
        if [ -f "$db_file" ]; then
            echo -e "${GREEN}[OK]${NC} Database found: $db_file"
            
            # Проверка целостности базы данных
            if sqlite3 "$db_file" "PRAGMA integrity_check;" | grep -q "ok"; then
                echo -e "${GREEN}[OK]${NC} Database integrity: $db_file"
            else
                echo -e "${RED}[ERROR]${NC} Database corruption: $db_file"
                ((issues_found++))
            fi
        else
            echo -e "${YELLOW}[WARNING]${NC} Database not found: $db_file"
        fi
    done
    
    return $issues_found
}

# Диагностика MikroTik соединения
diagnose_mikrotik_connection() {
    log_diagnostic "INFO" "Starting MikroTik connection diagnostics"
    
    local issues_found=0
    
    # Тест SSH подключения
    echo "Testing SSH connection to MikroTik..."
    if timeout 10 ssh -o ConnectTimeout=5 -o BatchMode=yes "$MIKROTIK_USER@$MIKROTIK_HOST" "echo 'SSH OK'" 2>/dev/null; then
        echo -e "${GREEN}[OK]${NC} SSH connection successful"
        
        # Тест команд MikroTik
        local system_info=$(timeout 10 ssh -o ConnectTimeout=5 -o BatchMode=yes \
                           "$MIKROTIK_USER@$MIKROTIK_HOST" \
                           "/system resource print" 2>/dev/null)
        
        if [ -n "$system_info" ]; then
            echo -e "${GREEN}[OK]${NC} MikroTik commands working"
            echo "System resources:"
            echo "$system_info" | head -10
        else
            echo -e "${RED}[ERROR]${NC} Cannot execute MikroTik commands"
            ((issues_found++))
        fi
        
        # Проверка address-list
        local whitelist_count=$(timeout 10 ssh -o ConnectTimeout=5 -o BatchMode=yes \
                               "$MIKROTIK_USER@$MIKROTIK_HOST" \
                               "/ip firewall address-list print count-only where list=web_users_whitelist" 2>/dev/null)
        
        if [[ "$whitelist_count" =~ ^[0-9]+$ ]]; then
            echo -e "${GREEN}[OK]${NC} Whitelist entries: $whitelist_count"
            
            if [ "$whitelist_count" -gt 100 ]; then
                echo -e "${YELLOW}[WARNING]${NC} Large number of whitelist entries may impact performance"
            fi
        else
            echo -e "${RED}[ERROR]${NC} Cannot read whitelist from MikroTik"
            ((issues_found++))
        fi
    else
        echo -e "${RED}[CRITICAL]${NC} SSH connection to MikroTik failed"
        ((issues_found++))
        
        echo "Troubleshooting SSH connection:"
        echo "1. Check SSH key authentication"
        echo "2. Verify MikroTik SSH service is running"
        echo "3. Check firewall rules"
        echo "4. Verify user permissions"
    fi
    
    # Проверка API соединения (если доступно)
    if command -v nc >/dev/null; then
        if nc -z -w5 "$MIKROTIK_HOST" 8729 2>/dev/null; then
            echo -e "${GREEN}[OK]${NC} MikroTik API port accessible"
        else
            echo -e "${YELLOW}[WARNING]${NC} MikroTik API port not accessible"
        fi
    fi
    
    return $issues_found
}

# Анализ производительности
diagnose_performance() {
    log_diagnostic "INFO" "Starting performance diagnostics"
    
    local issues_found=0
    
    # Тест скорости диска
    echo "Testing disk performance..."
    local disk_write_speed=$(dd if=/dev/zero of="$TEMP_DIR/disk_test" bs=1M count=100 oflag=direct 2>&1 | grep -o "[0-9.]* MB/s" | tail -1)
    if [ -n "$disk_write_speed" ]; then
        echo "Disk write speed: $disk_write_speed"
        rm -f "$TEMP_DIR/disk_test"
    fi
    
    # Тест скорости сети
    echo "Testing network performance..."
    local ping_time=$(ping -c 5 "$MIKROTIK_HOST" 2>/dev/null | tail -1 | awk -F'/' '{print $5}')
    if [ -n "$ping_time" ]; then
        echo "Average ping to MikroTik: ${ping_time}ms"
        
        if (( $(echo "$ping_time > 100" | bc -l) )); then
            echo -e "${YELLOW}[WARNING]${NC} High network latency"
        fi
    fi
    
    # Анализ использования памяти приложением
    local php_memory=$(ps aux | grep php-fpm | awk '{sum+=$6} END {print sum/1024}')
    if [ -n "$php_memory" ]; then
        echo "PHP-FPM memory usage: ${php_memory}MB"
        
        if (( $(echo "$php_memory > 500" | bc -l) )); then
            echo -e "${YELLOW}[WARNING]${NC} High PHP memory usage"
        fi
    fi
    
    # Анализ времени отклика веб-сервера
    local response_times=()
    for i in {1..5}; do
        local time=$(curl -s -w "%{time_total}" -o /dev/null http://localhost/ 2>/dev/null)
        if [ -n "$time" ]; then
            response_times+=("$time")
        fi
    done
    
    if [ ${#response_times[@]} -gt 0 ]; then
        local avg_time=$(printf '%s\n' "${response_times[@]}" | awk '{sum+=$1} END {print sum/NR}')
        echo "Average response time: ${avg_time}s"
        
        if (( $(echo "$avg_time > 2" | bc -l) )); then
            echo -e "${YELLOW}[WARNING]${NC} Slow web server response time"
            ((issues_found++))
        fi
    fi
    
    return $issues_found
}

# Генерация отчета диагностики
generate_diagnostic_report() {
    local total_issues="$1"
    local report_file="$TEMP_DIR/diagnostic_report_$(date +%Y%m%d_%H%M%S).txt"
    
    {
        echo "======================================"
        echo "DIAGNOSTIC REPORT - $(date)"
        echo "======================================"
        echo ""
        echo "Host: $(hostname)"
        echo "Uptime: $(uptime)"
        echo "Total issues found: $total_issues"
        echo ""
        
        if [ "$total_issues" -eq 0 ]; then
            echo "✅ All systems operational"
        else
            echo "⚠️  Issues detected - review detailed output above"
        fi
        
        echo ""
        echo "System Summary:"
        echo "- CPU Usage: $(top -bn1 | grep "Cpu(s)" | awk '{print $2}')"
        echo "- Memory Usage: $(free -m | grep "Mem:" | awk '{printf "%.1f%%", $3/$2*100}')"
        echo "- Disk Usage: $(df -h / | tail -1 | awk '{print $5}')"
        echo "- Load Average: $(uptime | awk -F'load average:' '{print $2}' | awk '{print $1}')"
        echo ""
        
        echo "Services Status:"
        echo "- NGINX: $(systemctl is-active nginx)"
        echo "- PHP-FPM: $(systemctl is-active php8.3-fpm)"
        echo "- MikroTik Connectivity: $(ping -c 1 -W 2 $MIKROTIK_HOST >/dev/null 2>&1 && echo 'OK' || echo 'FAILED')"
        echo ""
        
        echo "Recommendations:"
        if [ "$total_issues" -gt 0 ]; then
            echo "1. Review detailed diagnostic output above"
            echo "2. Address critical and error issues first"
            echo "3. Monitor system resources regularly"
            echo "4. Check log files for additional details"
        else
            echo "1. Continue regular monitoring"
            echo "2. Perform maintenance as scheduled"
            echo "3. Keep system updated"
        fi
        
    } > "$report_file"
    
    echo ""
    echo -e "${BLUE}Diagnostic report saved to: $report_file${NC}"
    return "$report_file"
}

# Основная функция диагностики
main_diagnostics() {
    echo -e "${BLUE}Starting comprehensive system diagnostics...${NC}"
    echo ""
    
    local total_issues=0
    
    # Выполнение всех диагностических тестов
    echo "=== SYSTEM RESOURCES ==="
    diagnose_system_resources
    total_issues=$((total_issues + $?))
    echo ""
    
    echo "=== NETWORK CONNECTIVITY ==="
    diagnose_network
    total_issues=$((total_issues + $?))
    echo ""
    
    echo "=== WEB SERVER ==="
    diagnose_web_server
    total_issues=$((total_issues + $?))
    echo ""
    
    echo "=== APPLICATION ==="
    diagnose_application
    total_issues=$((total_issues + $?))
    echo ""
    
    echo "=== MIKROTIK CONNECTION ==="
    diagnose_mikrotik_connection
    total_issues=$((total_issues + $?))
    echo ""
    
    echo "=== PERFORMANCE ==="
    diagnose_performance
    total_issues=$((total_issues + $?))
    echo ""
    
    # Генерация отчета
    generate_diagnostic_report "$total_issues"
    
    echo -e "${BLUE}=== DIAGNOSTIC SUMMARY ===${NC}"
    if [ "$total_issues" -eq 0 ]; then
        echo -e "${GREEN}✅ All systems operational${NC}"
        log_diagnostic "INFO" "Diagnostics completed - no issues found"
        exit 0
    else
        echo -e "${RED}⚠️  Found $total_issues issues requiring attention${NC}"
        log_diagnostic "WARNING" "Diagnostics completed - $total_issues issues found"
        exit 1
    fi
}

# Быстрая диагностика (основные проверки)
quick_diagnostics() {
    echo -e "${BLUE}Running quick diagnostics...${NC}"
    
    local issues=0
    
    # Основные сервисы
    echo -n "NGINX: "
    if systemctl is-active --quiet nginx; then
        echo -e "${GREEN}OK${NC}"
    else
        echo -e "${RED}FAILED${NC}"
        ((issues++))
    fi
    
    echo -n "PHP-FPM: "
    if systemctl is-active --quiet php8.3-fpm; then
        echo -e "${GREEN}OK${NC}"
    else
        echo -e "${RED}FAILED${NC}"
        ((issues++))
    fi
    
    echo -n "MikroTik: "
    if ping -c 1 -W 2 "$MIKROTIK_HOST" >/dev/null 2>&1; then
        echo -e "${GREEN}OK${NC}"
    else
        echo -e "${RED}FAILED${NC}"
        ((issues++))
    fi
    
    # Ресурсы
    local cpu_usage=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | cut -d'%' -f1)
    echo -n "CPU Usage: ${cpu_usage}% "
    if (( $(echo "$cpu_usage > 80" | bc -l) )); then
        echo -e "${RED}HIGH${NC}"
        ((issues++))
    else
        echo -e "${GREEN}OK${NC}"
    fi
    
    local memory_usage=$(free | grep Mem | awk '{printf "%.1f", $3/$2*100}')
    echo -n "Memory Usage: ${memory_usage}% "
    if (( $(echo "$memory_usage > 90" | bc -l) )); then
        echo -e "${RED}HIGH${NC}"
        ((issues++))
    else
        echo -e "${GREEN}OK${NC}"
    fi
    
    echo ""
    if [ "$issues" -eq 0 ]; then
        echo -e "${GREEN}Quick check passed${NC}"
        exit 0
    else
        echo -e "${YELLOW}Found $issues issues - run full diagnostics${NC}"
        exit 1
    fi
}

# Обработка аргументов
case "${1:-full}" in
    "system")
        diagnose_system_resources
        ;;
    "network")
        diagnose_network
        ;;
    "web")
        diagnose_web_server
        ;;
    "app")
        diagnose_application
        ;;
    "mikrotik")
        diagnose_mikrotik_connection
        ;;
    "performance")
        diagnose_performance
        ;;
    "quick")
        quick_diagnostics
        ;;
    "full"|"")
        main_diagnostics
        ;;
    *)
        echo "Usage: $0 {system|network|web|app|mikrotik|performance|quick|full}"
        echo ""
        echo "Diagnostic Commands:"
        echo "  system      - System resources diagnostics"
        echo "  network     - Network connectivity diagnostics"
        echo "  web         - Web server diagnostics"
        echo "  app         - Application diagnostics"
        echo "  mikrotik    - MikroTik connection diagnostics"
        echo "  performance - Performance diagnostics"
        echo "  quick       - Quick health check"
        echo "  full        - Complete system diagnostics (default)"
        exit 1
        ;;
esac

10.4. Оптимизация производительности

Система оптимизации производительности обеспечивает максимальную эффективность работы всех компонентов системы управления белым списком:

#!/usr/bin/env python3
# /usr/local/bin/performance_optimizer.py
# Система оптимизации производительности

import os
import json
import time
import psutil
import sqlite3
import subprocess
from datetime import datetime, timedelta
from pathlib import Path
from typing import Dict, List, Tuple, Optional
import configparser

class PerformanceOptimizer:
    def __init__(self, config_file="/etc/monitoring/optimizer.json"):
        self.config = self.load_config(config_file)
        self.optimization_log = "/var/log/optimization.log"
        self.metrics_db = "/var/lib/monitoring/performance_metrics.db"
        
        # Инициализация базы данных метрик
        self.init_metrics_db()
    
    def load_config(self, config_file):
        """Загрузка конфигурации оптимизатора"""
        default_config = {
            'optimization': {
                'auto_apply': False,
                'backup_configs': True,
                'max_cpu_threshold': 80,
                'max_memory_threshold': 85,
                'target_response_time': 1.0
            },
            'nginx': {
                'config_path': '/etc/nginx/nginx.conf',
                'site_config': '/etc/nginx/sites-available/sysadministrator.ru'
            },
            'php': {
                'fpm_config': '/etc/php/8.3/fpm/pool.d/sysadministrator.conf',
                'ini_config': '/etc/php/8.3/fpm/php.ini'
            },
            'system': {
                'sysctl_config': '/etc/sysctl.d/99-performance.conf'
            }
        }
        
        try:
            with open(config_file, 'r') as f:
                config = json.load(f)
                default_config.update(config)
                return default_config
        except FileNotFoundError:
            return default_config
    
    def init_metrics_db(self):
        """Инициализация базы данных для хранения метрик производительности"""
        Path(self.metrics_db).parent.mkdir(parents=True, exist_ok=True)
        
        with sqlite3.connect(self.metrics_db) as conn:
            conn.execute('''
                CREATE TABLE IF NOT EXISTS performance_metrics (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    timestamp DATETIME NOT NULL,
                    metric_name TEXT NOT NULL,
                    value REAL NOT NULL,
                    optimization_applied BOOLEAN DEFAULT 0
                )
            ''')
            
            conn.execute('''
                CREATE TABLE IF NOT EXISTS optimization_history (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    timestamp DATETIME NOT NULL,
                    optimization_type TEXT NOT NULL,
                    description TEXT,
                    before_value REAL,
                    after_value REAL,
                    config_backup TEXT
                )
            ''')
    
    def log_optimization(self, message: str, level: str = "INFO"):
        """Логирование операций оптимизации"""
        timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        log_entry = f"[{timestamp}] [{level}] {message}\n"
        
        with open(self.optimization_log, 'a') as f:
            f.write(log_entry)
        
        print(log_entry.strip())
    
    def collect_current_metrics(self) -> Dict:
        """Сбор текущих метрик производительности"""
        metrics = {}
        
        # Системные метрики
        metrics['cpu_usage'] = psutil.cpu_percent(interval=1)
        metrics['memory_usage'] = psutil.virtual_memory().percent
        metrics['disk_usage'] = psutil.disk_usage('/').percent
        metrics['load_average'] = os.getloadavg()[0]
        
        # Сетевые метрики
        net_io = psutil.net_io_counters()
        metrics['network_bytes_sent'] = net_io.bytes_sent
        metrics['network_bytes_recv'] = net_io.bytes_recv
        
        # Количество процессов
        metrics['process_count'] = len(psutil.pids())
        
        # Время отклика веб-сервера
        try:
            import requests
            start_time = time.time()
            response = requests.get('http://localhost/', timeout=5)
            metrics['web_response_time'] = time.time() - start_time
            metrics['web_status_code'] = response.status_code
        except Exception as e:
            metrics['web_response_time'] = 999  # Высокое значение при ошибке
            metrics['web_status_code'] = 0
        
        # Сохранение метрик в базу данных
        self.save_metrics(metrics)
        
        return metrics
    
    def save_metrics(self, metrics: Dict):
        """Сохранение метрик в базу данных"""
        with sqlite3.connect(self.metrics_db) as conn:
            timestamp = datetime.now().isoformat()
            
            for metric_name, value in metrics.items():
                if isinstance(value, (int, float)):
                    conn.execute('''
                        INSERT INTO performance_metrics (timestamp, metric_name, value)
                        VALUES (?, ?, ?)
                    ''', (timestamp, metric_name, value))
    
    def analyze_performance_trends(self, hours: int = 24) -> Dict:
        """Анализ трендов производительности"""
        since = datetime.now() - timedelta(hours=hours)
        
        with sqlite3.connect(self.metrics_db) as conn:
            # Средние значения за период
            cursor = conn.execute('''
                SELECT metric_name, AVG(value) as avg_value, 
                       MIN(value) as min_value, MAX(value) as max_value
                FROM performance_metrics 
                WHERE timestamp > ?
                GROUP BY metric_name
            ''', (since.isoformat(),))
            
            trends = {}
            for row in cursor.fetchall():
                metric_name, avg_val, min_val, max_val = row
                trends[metric_name] = {
                    'average': avg_val,
                    'minimum': min_val,
                    'maximum': max_val
                }
        
        return trends
    
    def optimize_nginx_config(self) -> List[str]:
        """Оптимизация конфигурации NGINX"""
        optimizations = []
        nginx_config_path = self.config['nginx']['config_path']
        
        # Резервное копирование конфигурации
        if self.config['optimization']['backup_configs']:
            backup_path = f"{nginx_config_path}.backup.{datetime.now().strftime('%Y%m%d_%H%M%S')}"
            subprocess.run(['cp', nginx_config_path, backup_path], check=True)
            optimizations.append(f"Backed up NGINX config to {backup_path}")
        
        # Чтение текущей конфигурации
        with open(nginx_config_path, 'r') as f:
            config_lines = f.readlines()
        
        # Определение оптимального количества worker_processes
        cpu_count = psutil.cpu_count()
        worker_processes_optimized = False
        
        # Оптимизация параметров
        optimized_lines = []
        for line in config_lines:
            # Worker processes
            if 'worker_processes' in line and not line.strip().startswith('#'):
                if 'auto' not in line:
                    optimized_lines.append(f"worker_processes auto;\n")
                    worker_processes_optimized = True
                    optimizations.append("Set worker_processes to auto")
                else:
                    optimized_lines.append(line)
            
            # Worker connections
            elif 'worker_connections' in line and not line.strip().startswith('#'):
                # Оптимизируем на основе системных возможностей
                optimal_connections = min(1024 * cpu_count, 4096)
                optimized_lines.append(f"    worker_connections {optimal_connections};\n")
                optimizations.append(f"Set worker_connections to {optimal_connections}")
            
            # Keepalive timeout
            elif 'keepalive_timeout' in line and not line.strip().startswith('#'):
                optimized_lines.append("    keepalive_timeout 65;\n")
                optimizations.append("Optimized keepalive_timeout to 65s")
            
            else:
                optimized_lines.append(line)
        
        # Добавление дополнительных оптимизаций если их нет
        additional_optimizations = [
            "    sendfile on;",
            "    tcp_nopush on;",
            "    tcp_nodelay on;",
            "    server_tokens off;",
            "    client_max_body_size 10M;",
            "    client_body_timeout 12;",
            "    client_header_timeout 12;",
            "    send_timeout 10;",
            "    gzip on;",
            "    gzip_vary on;",
            "    gzip_min_length 10240;",
            "    gzip_proxied expired no-cache no-store private must-revalidate auth;",
            "    gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss;"
        ]
        
        config_content = ''.join(optimized_lines)
        
        for optimization in additional_optimizations:
            directive = optimization.strip().split()[0]
            if directive not in config_content:
                # Найти http блок и добавить оптимизацию
                if "http {" in config_content:
                    config_content = config_content.replace(
                        "http {",
                        f"http {{\n    {optimization}\n"
                    )
                    optimizations.append(f"Added {directive} directive")
        
        # Запись оптимизированной конфигурации
        if self.config['optimization']['auto_apply']:
            with open(nginx_config_path, 'w') as f:
                f.write(config_content)
            
            # Проверка конфигурации
            result = subprocess.run(['nginx', '-t'], capture_output=True, text=True)
            if result.returncode == 0:
                subprocess.run(['systemctl', 'reload', 'nginx'], check=True)
                optimizations.append("NGINX configuration reloaded successfully")
            else:
                # Откат изменений
                subprocess.run(['cp', backup_path, nginx_config_path], check=True)
                optimizations.append("ERROR: Invalid configuration, changes reverted")
        else:
            optimizations.append("Optimized config generated (auto_apply disabled)")
        
        return optimizations
    
    def optimize_php_fmp_config(self) -> List[str]:
        """Оптимизация конфигурации PHP-FPM"""
        optimizations = []
        fmp_config_path = self.config['php']['fmp_config']
        
        # Резервное копирование
        if self.config['optimization']['backup_configs']:
            backup_path = f"{fmp_config_path}.backup.{datetime.now().strftime('%Y%m%d_%H%M%S')}"
            subprocess.run(['cp', fmp_config_path, backup_path], check=True)
            optimizations.append(f"Backed up PHP-FPM config to {backup_path}")
        
        # Анализ текущего использования памяти
        memory_info = psutil.virtual_memory()
        available_memory_mb = memory_info.available // (1024 * 1024)
        
        # Вычисление оптимальных параметров
        estimated_memory_per_process = 32  # MB per PHP-FPM process
        optimal_max_children = min(max(2, available_memory_mb // estimated_memory_per_process // 2), 50)
        optimal_start_servers = max(2, optimal_max_children // 4)
        optimal_min_spare = max(1, optimal_start_servers // 2)
        optimal_max_spare = min(optimal_start_servers * 2, optimal_max_children // 2)
        
        # Чтение и оптимизация конфигурации
        config = configparser.ConfigParser()
        config.read(fmp_config_path)
        
        pool_name = 'sysadministrator'  # Имя пула
        
        if pool_name in config:
            # PM настройки
            config[pool_name]['pm'] = 'dynamic'
            config[pool_name]['pm.max_children'] = str(optimal_max_children)
            config[pool_name]['pm.start_servers'] = str(optimal_start_servers)
            config[pool_name]['pm.min_spare_servers'] = str(optimal_min_spare)
            config[pool_name]['pm.max_spare_servers'] = str(optimal_max_spare)
            config[pool_name]['pm.max_requests'] = '1000'
            config[pool_name]['pm.process_idle_timeout'] = '10s'
            
            optimizations.extend([
                f"Set pm.max_children to {optimal_max_children}",
                f"Set pm.start_servers to {optimal_start_servers}",
                f"Set pm.min_spare_servers to {optimal_min_spare}",
                f"Set pm.max_spare_servers to {optimal_max_spare}"
            ])
            
            # Запись конфигурации
            if self.config['optimization']['auto_apply']:
                with open(fmp_config_path, 'w') as f:
                    config.write(f)
                
                subprocess.run(['systemctl', 'reload', 'php8.3-fpm'], check=True)
                optimizations.append("PHP-FPM configuration reloaded")
        
        return optimizations
    
    def optimize_system_parameters(self) -> List[str]:
        """Оптимизация системных параметров"""
        optimizations = []
        sysctl_config = self.config['system']['sysctl_config']
        
        # Параметры оптимизации для веб-сервера
        sysctl_optimizations = {
            # Сетевые оптимизации
            'net.core.rmem_max': '16777216',
            'net.core.wmem_max': '16777216',
            'net.ipv4.tcp_rmem': '4096 65536 16777216',
            'net.ipv4.tcp_wmem': '4096 65536 16777216',
            'net.core.netdev_max_backlog': '5000',
            'net.ipv4.tcp_congestion_control': 'bbr',
            
            # Файловая система
            'fs.file-max': '65536',
            'vm.swappiness': '10',
            'vm.dirty_ratio': '15',
            'vm.dirty_background_ratio': '5',
            
            # Безопасность
            'net.ipv4.tcp_syncookies': '1',
            'net.ipv4.tcp_syn_retries': '2',
            'net.ipv4.tcp_synack_retries': '2',
            'net.ipv4.tcp_max_syn_backlog': '4096',
        }
        
        # Создание или обновление конфигурации sysctl
        sysctl_lines = [
            "# Performance optimizations for Battery Service\n",
            "# Generated by PerformanceOptimizer\n\n"
        ]
        
        for param, value in sysctl_optimizations.items():
            sysctl_lines.append(f"{param} = {value}\n")
            optimizations.append(f"Set {param} to {value}")
        
        if self.config['optimization']['auto_apply']:
            Path(sysctl_config).parent.mkdir(parents=True, exist_ok=True)
            with open(sysctl_config, 'w') as f:
                f.writelines(sysctl_lines)
            
            # Применение параметров
            subprocess.run(['sysctl', '-p', sysctl_config], check=True)
            optimizations.append("System parameters applied successfully")
        else:
            optimizations.append("System parameters prepared (auto_apply disabled)")
        
        return optimizations
    
    def optimize_database_performance(self) -> List[str]:
        """Оптимизация производительности баз данных SQLite"""
        optimizations = []
        
        # Поиск баз данных SQLite
        db_paths = [
            '/var/lib/mikrotik/whitelist.db',
            '/var/lib/mikrotik/audit.db',
            '/var/lib/monitoring/metrics.db'
        ]
        
        for db_path in db_paths:
            if Path(db_path).exists():
                try:
                    with sqlite3.connect(db_path) as conn:
                        # Анализ и оптимизация
                        conn.execute('ANALYZE')
                        conn.execute('VACUUM')
                        
                        # Установка оптимальных PRAGMA параметров
                        conn.execute('PRAGMA journal_mode = WAL')
                        conn.execute('PRAGMA synchronous = NORMAL')
                        conn.execute('PRAGMA cache_size = 10000')
                        conn.execute('PRAGMA temp_store = memory')
                        
                        optimizations.append(f"Optimized database: {db_path}")
                
                except Exception as e:
                    optimizations.append(f"Error optimizing {db_path}: {str(e)}")
        
        return optimizations
    
    def cleanup_system_resources(self) -> List[str]:
        """Очистка системных ресурсов"""
        cleanups = []
        
        # Очистка временных файлов
        temp_dirs = ['/tmp', '/var/tmp']
        cleaned_files = 0
        
        for temp_dir in temp_dirs:
            try:
                for item in Path(temp_dir).glob('*'):
                    if item.is_file() and item.stat().st_mtime < time.time() - 86400:  # Старше суток
                        item.unlink()
                        cleaned_files += 1
                    elif item.is_dir() and item.name.startswith('tmp'):
                        try:
                            item.rmdir()  # Удаляем только пустые директории
                            cleaned_files += 1
                        except OSError:
                            pass
            except Exception as e:
                cleanups.append(f"Error cleaning {temp_dir}: {str(e)}")
        
        if cleaned_files > 0:
            cleanups.append(f"Cleaned {cleaned_files} temporary files")
        
        # Очистка логов (старых ротированных файлов)
        log_dirs = ['/var/log']
        for log_dir in log_dirs:
            try:
                for log_file in Path(log_dir).rglob('*.log.*'):
                    if log_file.stat().st_mtime < time.time() - (7 * 86400):  # Старше недели
                        log_file.unlink()
                        cleanups.append(f"Removed old log: {log_file}")
            except Exception as e:
                cleanups.append(f"Error cleaning logs: {str(e)}")
        
        # Очистка APT кэша
        try:
            subprocess.run(['apt-get', 'clean'], check=True, capture_output=True)
            cleanups.append("Cleaned APT package cache")
        except Exception as e:
            cleanups.append(f"Error cleaning APT cache: {str(e)}")
        
        return cleanups
    
    def optimize_process_priorities(self) -> List[str]:
        """Оптимизация приоритетов процессов"""
        optimizations = []
        
        try:
            # Повышение приоритета критичных процессов
            critical_processes = ['nginx', 'php-fmp']
            
            for proc in psutil.process_iter(['pid', 'name']):
                proc_name = proc.info['name']
                
                if any(cp in proc_name for cp in critical_processes):
                    try:
                        process = psutil.Process(proc.info['pid'])
                        current_nice = process.nice()
                        
                        if current_nice > -5:  # Повышаем приоритет если нужно
                            process.nice(-5)
                            optimizations.append(f"Increased priority for {proc_name} (PID: {proc.info['pid']})")
                    except (psutil.NoSuchProcess, psutil.AccessDenied):
                        continue
        
        except Exception as e:
            optimizations.append(f"Error optimizing process priorities: {str(e)}")
        
        return optimizations
    
    def generate_optimization_report(self) -> Dict:
        """Генерация отчета по оптимизации"""
        # Сбор метрик до оптимизации
        before_metrics = self.collect_current_metrics()
        
        # Выполнение оптимизаций
        all_optimizations = []
        
        self.log_optimization("Starting performance optimization")
        
        nginx_opts = self.optimize_nginx_config()
        all_optimizations.extend([f"NGINX: {opt}" for opt in nginx_opts])
        
        php_opts = self.optimize_php_fmp_config()
        all_optimizations.extend([f"PHP-FPM: {opt}" for opt in php_opts])
        
        system_opts = self.optimize_system_parameters()
        all_optimizations.extend([f"SYSTEM: {opt}" for opt in system_opts])
        
        db_opts = self.optimize_database_performance()
        all_optimizations.extend([f"DATABASE: {opt}" for opt in db_opts])
        
        cleanup_opts = self.cleanup_system_resources()
        all_optimizations.extend([f"CLEANUP: {opt}" for opt in cleanup_opts])
        
        process_opts = self.optimize_process_priorities()
        all_optimizations.extend([f"PROCESSES: {opt}" for opt in process_opts])
        
        # Ожидание для стабилизации системы
        time.sleep(10)
        
        # Сбор метрик после оптимизации
        after_metrics = self.collect_current_metrics()
        
        # Анализ улучшений
        improvements = {}
        for metric in before_metrics:
            if metric in after_metrics:
                before_val = before_metrics[metric]
                after_val = after_metrics[metric]
                
                if metric in ['cpu_usage', 'memory_usage', 'web_response_time']:
                    # Для этих метрик меньше = лучше
                    improvement = ((before_val - after_val) / before_val) * 100 if before_val > 0 else 0
                else:
                    # Для других метрик больше может быть лучше
                    improvement = ((after_val - before_val) / before_val) * 100 if before_val > 0 else 0
                
                improvements[metric] = {
                    'before': before_val,
                    'after': after_val,
                    'improvement_percent': improvement
                }
        
        # Сохранение в историю оптимизаций
        with sqlite3.connect(self.metrics_db) as conn:
            timestamp = datetime.now().isoformat()
            
            for optimization in all_optimizations:
                conn.execute('''
                    INSERT INTO optimization_history 
                    (timestamp, optimization_type, description)
                    VALUES (?, ?, ?)
                ''', (timestamp, 'PERFORMANCE', optimization))
        
        report = {
            'timestamp': datetime.now().isoformat(),
            'optimizations_applied': all_optimizations,
            'metrics_before': before_metrics,
            'metrics_after': after_metrics,
            'improvements': improvements,
            'auto_apply_enabled': self.config['optimization']['auto_apply']
        }
        
        self.log_optimization(f"Optimization completed. Applied {len(all_optimizations)} optimizations")
        
        return report

def main():
    """Главная функция для запуска оптимизатора"""
    import argparse
    
    parser = argparse.ArgumentParser(description='Performance Optimizer')
    parser.add_argument('--config', default='/etc/monitoring/optimizer.json',
                       help='Path to config file')
    parser.add_argument('--component', 
                       choices=['nginx', 'php', 'system', 'database', 'cleanup', 'processes', 'all'],
                       default='all', help='Component to optimize')
    parser.add_argument('--report', help='Generate optimization report to file')
    parser.add_argument('--dry-run', action='store_true',
                       help='Show what would be optimized without applying changes')
    
    args = parser.parse_args()
    
    optimizer = PerformanceOptimizer(args.config)
    
    # Dry run mode
    if args.dry_run:
        optimizer.config['optimization']['auto_apply'] = False
        print("Running in dry-run mode - no changes will be applied")
    
    try:
        if args.component == 'all':
            report = optimizer.generate_optimization_report()
        else:
            # Выполнение оптимизации конкретного компонента
            if args.component == 'nginx':
                opts = optimizer.optimize_nginx_config()
            elif args.component == 'php':
                opts = optimizer.optimize_php_fmp_config()
            elif args.component == 'system':
                opts = optimizer.optimize_system_parameters()
            elif args.component == 'database':
                opts = optimizer.optimize_database_performance()
            elif args.component == 'cleanup':
                opts = optimizer.cleanup_system_resources()
            elif args.component == 'processes':
                opts = optimizer.optimize_process_priorities()
            
            report = {
                'component': args.component,
                'optimizations': opts,
                'timestamp': datetime.now().isoformat()
            }
        
        # Сохранение отчета
        if args.report:
            with open(args.report, 'w') as f:
                json.dump(report, f, indent=2, default=str)
            print(f"Optimization report saved to: {args.report}")
        else:
            print(json.dumps(report, indent=2, default=str))
        
        # Вывод краткой сводки
        if 'improvements' in report:
            print("\n=== PERFORMANCE IMPROVEMENTS ===")
            for metric, data in report['improvements'].items():
                if abs(data['improvement_percent']) > 1:  # Показываем только значительные изменения
                    print(f"{metric}: {data['improvement_percent']:.1f}% improvement")
    
    except Exception as e:
        print(f"Error: {e}")
        return 1
    
    return 0

if __name__ == '__main__':
    exit(main())

Заключение и послесовие

Представленная система управления белым списком MikroTik обеспечивает:

Основные возможности:

  • Автоматическое управление белым списком через веб-интерфейс
  • Интеграцию с MikroTik API для real-time операций
  • Комплексное журналирование и аудит всех операций
  • Многоуровневую защиту от атак и злоупотреблений

Инфраструктура:

  • Оптимизированную конфигурацию NGINX и PHP-FPM 8.3
  • Автоматизированное резервное копирование и мониторинг
  • Систему реагирования на инциденты безопасности
  • Инструменты диагностики и оптимизации производительности

Безопасность:

  • HTTP Basic Authentication с защитой от брутфорса
  • Ограничение частоты запросов с различными алгоритмами
  • Географическую фильтрацию и IP репутационные проверки
  • Комплексный аудит безопасности и план реагирования на инциденты

Система спроектирована для надежной работы в продакшн среде с возможностью масштабирования и адаптации под изменяющиеся требования безопасности.

    Разработка и продвижение сайтов webseed.ru
    Прокрутить вверх