From 328e1b4bedcb9cabe8dab296d3ce8578510c27f0 Mon Sep 17 00:00:00 2001 From: avalon0077 <43924346+avalon0077@users.noreply.github.com> Date: Sat, 14 Feb 2026 17:12:33 +0200 Subject: [PATCH 01/11] Add MikroTik NAT Sync to README Add MikroTik NAT Sync plugin to the list --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 443685fc..fa6daaac 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ npm i -g yarn - [Legal Pages](/legal-pages) - Adds legal pages (Imprint, Privacy Policy, ToS) to the panel - [McLogCleaner](/mclogcleaner) - Delete old logs with ease - [MCLogs Uploader](/mclogs-uploader) - Upload console logs to mclo.gs +- [MikroTik NAT Sync](https://github.com/avalon0077/mikrotik-nat-sync) - Automatically synchronize Pelican allocations with MikroTik NAT rules via REST API. - [Minecraft Modrinth](/minecraft-modrinth) - Download Minecraft mods & plugins from Modrinth - [PasteFox Share](/pastefox-share) - Share console logs via pastefox.com - [Player Counter](/player-counter) - Show connected players count for game servers From c3eaad72b9e095a5ccacfe098a19ee21320b8efc Mon Sep 17 00:00:00 2001 From: avalon0077 <43924346+avalon0077@users.noreply.github.com> Date: Sat, 14 Feb 2026 17:28:58 +0200 Subject: [PATCH 02/11] Add plugin.json for MikroTik NAT Sync --- mikrotik-nat-sync/plugin.json | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 mikrotik-nat-sync/plugin.json diff --git a/mikrotik-nat-sync/plugin.json b/mikrotik-nat-sync/plugin.json new file mode 100644 index 00000000..937d3e22 --- /dev/null +++ b/mikrotik-nat-sync/plugin.json @@ -0,0 +1,19 @@ +{ + "id": "mikrotik-nat-sync", + "name": "MikroTik NAT Sync", + "author": "avalon", + "version": "1.0.0", + "description": null, + "category": "plugin", + "url": null, + "update_url": null, + "namespace": "Avalon\\MikroTikNATSync", + "class": "MikroTikNATSyncPlugin", + "panels": null, + "panel_version": "1.0.0-beta32", + "composer_packages": null, + "meta": { + "status": "enabled", + "status_message": null + } +} From 8c1f2354893774078cc616f81049b7403aa384b8 Mon Sep 17 00:00:00 2001 From: avalon0077 <43924346+avalon0077@users.noreply.github.com> Date: Sat, 14 Feb 2026 17:30:42 +0200 Subject: [PATCH 03/11] Add README for MikroTik NAT Sync plugin Added README.md for MikroTik NAT Sync plugin with features, installation instructions, and configuration details. --- mikrotik-nat-sync/README.md | 76 +++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 mikrotik-nat-sync/README.md diff --git a/mikrotik-nat-sync/README.md b/mikrotik-nat-sync/README.md new file mode 100644 index 00000000..24b23b6c --- /dev/null +++ b/mikrotik-nat-sync/README.md @@ -0,0 +1,76 @@ + +# 🌐 MikroTik NAT Sync for Pelican Panel + +![License](https://img.shields.io/badge/license-MIT-blue.svg) +![Platform](https://img.shields.io/badge/platform-Pelican%20Panel-orange.svg) +![AI](https://img.shields.io/badge/Created%20with-AI%20Gemini-brightgreen.svg) + +**MikroTik NAT Sync** — це плагін для автоматизації прокидання портів (Port Forwarding) між панеллю Pelican та маршрутизаторами MikroTik через REST API. + +--- + +## 🇺🇸 English + +### 🚀 Features +* **Full Automation**: Automatically creates/removes DST-NAT rules based on Pelican allocations. +* **Security First**: Define a "Forbidden Ports" list to protect sensitive services (SSH, SFTP, etc.). +* **Smart Tags**: Manages only its own rules using the `Pelican:` comment tag. +* **Easy Setup**: Configure everything (IP, credentials, intervals) directly in the Admin UI. + +### 🛠 MikroTik Configuration +Enable the REST API on your router to allow communication: +```Bash +/ip service set www-ssl disabled=no port=9443 +``` +Note: We recommend creating a dedicated user with specific firewall permissions. + +### 📦 Installation / Встановлення + +**Method 1: Via Web Interface (Easiest)** +1. Copy the direct link to the plugin ZIP archive: + `https://github.com/avalon0077/mikrotik-nat-sync/archive/refs/heads/main.zip` +2. In your Pelican Admin Panel, go to **Plugins** -> **Import**. +3. Paste the URL or upload the downloaded ZIP file. +4. Click **Install** and configure via the Gear icon. + +**Method 2: Manual (CLI)** +1. Download and extract the archive to `/var/www/pelican/plugins/mikrotik-nat-sync`. +2. Make sure the folder name is exactly `mikrotik-nat-sync`. +3. Head to the **Plugins** page and click **Install**. + +--- + +## 🇺🇦 Українською + +### 🚀 Можливості +* **Повна автоматизація**: Автоматично керує правилами DST-NAT на основі активних алокацій. + +* **Безпека**: Список "Заборонених портів" для захисту системних сервісів. + +* **Розумні теги**: Керує лише своїми правилами через коментар Pelican:. + +* **Зручне налаштування**: Налаштуйте IP, логін, пароль та інтервали прямо в адмінці. + +### 🛠 Налаштування MikroTik +Увімкніть REST API для можливості віддаленого керування: + +```Bash +/ip service set www-ssl disabled=no port=9443 +``` +Порада: Створіть окремого користувача з правами на роботу лише з Firewall. + +### 📦 Installation / Встановлення + +**Спосіб 1: Через веб-інтерфейс (Найпростіший)** +1. Скопіюйте пряме посилання на ZIP-архів плагіна: + `https://github.com/avalon0077/mikrotik-nat-sync/archive/refs/heads/main.zip` +2. В адмін-панелі Pelican перейдіть у **Plugins** -> **Import**. +3. Вставте посилання або завантажте попередньо скачаний ZIP-файл. +4. Натисніть **Install** та налаштуйте через іконку шестерні. + +**Спосіб 2: Вручну (через консоль)** +1. Скачайте та розпакуйте архів у папку `/var/www/pelican/plugins/mikrotik-nat-sync`. +2. Переконайтеся, що папка називається саме `mikrotik-nat-sync`. +3. Перейдіть на сторінку **Plugins** та натисніть **Install**. + +>Developed with AI Assistance (Gemini) Розроблено за допомогою ШІ (Gemini) From a06ba19c905adb20895b02c1c1d79b203709c485 Mon Sep 17 00:00:00 2001 From: avalon0077 <43924346+avalon0077@users.noreply.github.com> Date: Sat, 14 Feb 2026 17:33:09 +0200 Subject: [PATCH 04/11] Fix link for MikroTik NAT Sync in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fa6daaac..2d76c4f0 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ npm i -g yarn - [Legal Pages](/legal-pages) - Adds legal pages (Imprint, Privacy Policy, ToS) to the panel - [McLogCleaner](/mclogcleaner) - Delete old logs with ease - [MCLogs Uploader](/mclogs-uploader) - Upload console logs to mclo.gs -- [MikroTik NAT Sync](https://github.com/avalon0077/mikrotik-nat-sync) - Automatically synchronize Pelican allocations with MikroTik NAT rules via REST API. +- [MikroTik NAT Sync](/mikrotik-nat-sync) - Automatically synchronize Pelican allocations with MikroTik NAT rules via REST API. - [Minecraft Modrinth](/minecraft-modrinth) - Download Minecraft mods & plugins from Modrinth - [PasteFox Share](/pastefox-share) - Share console logs via pastefox.com - [Player Counter](/player-counter) - Show connected players count for game servers From 19ff9c8c583cab6d52bfc6e1b42b6eb92eaf5193 Mon Sep 17 00:00:00 2001 From: avalon0077 <43924346+avalon0077@users.noreply.github.com> Date: Sat, 14 Feb 2026 17:40:27 +0200 Subject: [PATCH 05/11] Add configuration file for MikroTik NAT Sync --- mikrotik-nat-sync/config /mikrotik-nat-sync.php | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 mikrotik-nat-sync/config /mikrotik-nat-sync.php diff --git a/mikrotik-nat-sync/config /mikrotik-nat-sync.php b/mikrotik-nat-sync/config /mikrotik-nat-sync.php new file mode 100644 index 00000000..15085c78 --- /dev/null +++ b/mikrotik-nat-sync/config /mikrotik-nat-sync.php @@ -0,0 +1,5 @@ + Date: Sat, 14 Feb 2026 17:41:41 +0200 Subject: [PATCH 06/11] Add MikroTikNATSyncPlugin implementation --- .../src /MikroTikNATSyncPlugin.php | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 mikrotik-nat-sync/src /MikroTikNATSyncPlugin.php diff --git a/mikrotik-nat-sync/src /MikroTikNATSyncPlugin.php b/mikrotik-nat-sync/src /MikroTikNATSyncPlugin.php new file mode 100644 index 00000000..97628bf5 --- /dev/null +++ b/mikrotik-nat-sync/src /MikroTikNATSyncPlugin.php @@ -0,0 +1,104 @@ +runningInConsole()) { + $this->commands([ + \Avalon\MikroTikNATSync\Console\Commands\SyncMikrotikCommand::class, + ]); + } + + app()->booted(function () { + $schedule = app(Schedule::class); + $interval = env('MIKROTIK_SYNC_INTERVAL', 'everyFiveMinutes'); + + $schedule->command('mikrotik:sync') + ->{$interval}() + ->withoutOverlapping(); + }); + } + + public function getSettingsForm(): array + { + return [ + TextInput::make('mk_ip') + ->label('IP MikroTik') + ->default(env('MIKROTIK_IP')) + ->required(), + TextInput::make('mk_port') + ->label('Порт REST API') + ->default(env('MIKROTIK_PORT', '9080')) + ->required(), + TextInput::make('mk_user') + ->label('Користувач') + ->default(env('MIKROTIK_USER')) + ->required(), + TextInput::make('mk_pass') + ->label('Пароль') + ->password() + ->revealable() + ->default(env('MIKROTIK_PASS')), + TextInput::make('mk_interface') + ->label('Вхідний інтерфейс (WAN)') + ->default(env('MIKROTIK_INTERFACE', 'ether1')) + ->required(), + TextInput::make('mk_forbidden_ports') + ->label('Заборонені порти (через кому)') + ->placeholder('22, 80, 443, 3306') + ->default(env('MIKROTIK_FORBIDDEN_PORTS')), + Select::make('mk_interval') + ->label('Інтервал синхронізації') + ->options([ + 'everyMinute' => 'Щохвилини', + 'everyFiveMinutes' => 'Кожні 5 хвилин', + 'everyTenMinutes' => 'Кожні 10 хвилин', + 'hourly' => 'Щогодини', + ]) + ->default(env('MIKROTIK_SYNC_INTERVAL', 'everyFiveMinutes')) + ->required(), + ]; + } + + public function saveSettings(array $data): void + { + $this->writeToEnvironment([ + 'MIKROTIK_IP' => $data['mk_ip'], + 'MIKROTIK_PORT' => $data['mk_port'], + 'MIKROTIK_USER' => $data['mk_user'], + 'MIKROTIK_PASS' => $data['mk_pass'], + 'MIKROTIK_INTERFACE' => $data['mk_interface'], + 'MIKROTIK_SYNC_INTERVAL' => $data['mk_interval'], + 'MIKROTIK_FORBIDDEN_PORTS' => $data['mk_forbidden_ports'], + ]); + + Notification::make() + ->title('Налаштування збережено') + ->success() + ->send(); + } +} From e11e9db5d5b5fa136a0bfc9947280ea0834d0947 Mon Sep 17 00:00:00 2001 From: avalon0077 <43924346+avalon0077@users.noreply.github.com> Date: Sat, 14 Feb 2026 17:42:39 +0200 Subject: [PATCH 07/11] Create SyncMikrotikCommand for NAT synchronization This command synchronizes NAT rules from MikroTik while checking for forbidden ports. --- .../Console/Commands /SyncMikrotikCommand.php | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 mikrotik-nat-sync/src /Console/Commands /SyncMikrotikCommand.php diff --git a/mikrotik-nat-sync/src /Console/Commands /SyncMikrotikCommand.php b/mikrotik-nat-sync/src /Console/Commands /SyncMikrotikCommand.php new file mode 100644 index 00000000..2838b75a --- /dev/null +++ b/mikrotik-nat-sync/src /Console/Commands /SyncMikrotikCommand.php @@ -0,0 +1,100 @@ +info('Starting MikroTik Sync...'); + + $mk_ip = str_replace(['http://', 'https://'], '', env('MIKROTIK_IP')); + $mk_port = env('MIKROTIK_PORT', '9080'); + $mk_user = env('MIKROTIK_USER'); + $mk_pass = env('MIKROTIK_PASS'); + $mk_interface = env('MIKROTIK_INTERFACE', 'ether1'); + + // Отримуємо список заборонених портів та перетворюємо в масив + $forbidden_string = env('MIKROTIK_FORBIDDEN_PORTS', ''); + $forbidden_ports = array_map('trim', explode(',', $forbidden_string)); + + if (!$mk_ip || !$mk_user || !$mk_pass) { + $this->error('Налаштування MikroTik не заповнені!'); + return; + } + + if (str_contains($mk_ip, ':')) { + $url = "http://" . $mk_ip . "/rest/ip/firewall/nat"; + } else { + $url = "http://" . $mk_ip . ":" . $mk_port . "/rest/ip/firewall/nat"; + } + + $active_servers = DB::table('servers') + ->join('allocations', 'allocations.server_id', '=', 'servers.id') + ->select('servers.uuid', 'servers.name', 'allocations.ip', 'allocations.port') + ->get(); + + $whitelist = []; + foreach ($active_servers as $srv) { + // ПЕРЕВІРКА: чи порт не в списку заборонених + if (in_array((string)$srv->port, $forbidden_ports)) { + $this->warn("Порт {$srv->port} для сервера {$srv->name} ЗАБОРОНЕНИЙ. Пропускаємо."); + continue; + } + + $target_ip = ($srv->ip == '0.0.0.0') ? '192.168.70.231' : $srv->ip; + $whitelist[$srv->port . '-tcp'] = ['ip' => $target_ip, 'name' => $srv->name, 'uuid' => $srv->uuid]; + $whitelist[$srv->port . '-udp'] = ['ip' => $target_ip, 'name' => $srv->name, 'uuid' => $srv->uuid]; + } + + try { + $response = Http::withBasicAuth($mk_user, $mk_pass)->timeout(10)->get($url); + if (!$response->successful()) { + $this->error('Помилка API: ' . $response->body()); + return; + } + + $existing_rules = []; + foreach ($response->json() as $rule) { + if (isset($rule['comment']) && str_contains($rule['comment'], 'Pelican:')) { + $key = ($rule['dst-port'] ?? '') . '-' . ($rule['protocol'] ?? 'tcp'); + $existing_rules[$key] = $rule['.id']; + } + } + + foreach ($existing_rules as $key => $id) { + if (!isset($whitelist[$key])) { + $this->warn("Deleting rule: $key"); + Http::withBasicAuth($mk_user, $mk_pass)->delete("$url/$id"); + } + } + + foreach ($whitelist as $key => $info) { + if (!isset($existing_rules[$key])) { + [$port, $proto] = explode('-', $key); + $this->info("Adding rule: $key for {$info['name']}"); + Http::withBasicAuth($mk_user, $mk_pass)->put($url, [ + 'chain' => 'dstnat', + 'action' => 'dst-nat', + 'to-addresses' => $info['ip'], + 'to-ports' => (string)$port, + 'protocol' => $proto, + 'dst-port' => (string)$port, + 'in-interface' => $mk_interface, + 'comment' => "Pelican: {$info['name']} ({$info['uuid']})" + ]); + } + } + $this->info('Sync Complete.'); + } catch (\Exception $e) { + $this->error('Error: ' . $e->getMessage()); + } + } +} From 308d646ad87f5626e6310a73ea54ede544150574 Mon Sep 17 00:00:00 2001 From: avalon0077 <43924346+avalon0077@users.noreply.github.com> Date: Sat, 14 Feb 2026 17:43:22 +0200 Subject: [PATCH 08/11] Add MikroTikNATSyncPluginProvider service provider --- .../MikroTikNATSyncPluginProvider.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 mikrotik-nat-sync/src /Providers /MikroTikNATSyncPluginProvider.php diff --git a/mikrotik-nat-sync/src /Providers /MikroTikNATSyncPluginProvider.php b/mikrotik-nat-sync/src /Providers /MikroTikNATSyncPluginProvider.php new file mode 100644 index 00000000..ba409a00 --- /dev/null +++ b/mikrotik-nat-sync/src /Providers /MikroTikNATSyncPluginProvider.php @@ -0,0 +1,18 @@ + Date: Sat, 14 Feb 2026 17:59:34 +0200 Subject: [PATCH 09/11] Update mikrotik-nat-sync plugin files --- .../config/mikrotik-nat-sync.php | 5 + mikrotik-nat-sync/plugin.json | 2 +- .../Console/Commands/SyncMikrotikCommand.php | 100 +++++++++++++++++ .../src/MikroTikNATSyncPlugin.php | 104 ++++++++++++++++++ .../MikroTikNATSyncPluginProvider.php | 18 +++ 5 files changed, 228 insertions(+), 1 deletion(-) create mode 100644 mikrotik-nat-sync/config/mikrotik-nat-sync.php create mode 100644 mikrotik-nat-sync/src/Console/Commands/SyncMikrotikCommand.php create mode 100644 mikrotik-nat-sync/src/MikroTikNATSyncPlugin.php create mode 100644 mikrotik-nat-sync/src/Providers/MikroTikNATSyncPluginProvider.php diff --git a/mikrotik-nat-sync/config/mikrotik-nat-sync.php b/mikrotik-nat-sync/config/mikrotik-nat-sync.php new file mode 100644 index 00000000..15085c78 --- /dev/null +++ b/mikrotik-nat-sync/config/mikrotik-nat-sync.php @@ -0,0 +1,5 @@ +info('Starting MikroTik Sync...'); + + $mk_ip = str_replace(['http://', 'https://'], '', env('MIKROTIK_IP')); + $mk_port = env('MIKROTIK_PORT', '9080'); + $mk_user = env('MIKROTIK_USER'); + $mk_pass = env('MIKROTIK_PASS'); + $mk_interface = env('MIKROTIK_INTERFACE', 'ether1'); + + // Отримуємо список заборонених портів та перетворюємо в масив + $forbidden_string = env('MIKROTIK_FORBIDDEN_PORTS', ''); + $forbidden_ports = array_map('trim', explode(',', $forbidden_string)); + + if (!$mk_ip || !$mk_user || !$mk_pass) { + $this->error('Налаштування MikroTik не заповнені!'); + return; + } + + if (str_contains($mk_ip, ':')) { + $url = "http://" . $mk_ip . "/rest/ip/firewall/nat"; + } else { + $url = "http://" . $mk_ip . ":" . $mk_port . "/rest/ip/firewall/nat"; + } + + $active_servers = DB::table('servers') + ->join('allocations', 'allocations.server_id', '=', 'servers.id') + ->select('servers.uuid', 'servers.name', 'allocations.ip', 'allocations.port') + ->get(); + + $whitelist = []; + foreach ($active_servers as $srv) { + // ПЕРЕВІРКА: чи порт не в списку заборонених + if (in_array((string)$srv->port, $forbidden_ports)) { + $this->warn("Порт {$srv->port} для сервера {$srv->name} ЗАБОРОНЕНИЙ. Пропускаємо."); + continue; + } + + $target_ip = ($srv->ip == '0.0.0.0') ? '192.168.70.231' : $srv->ip; + $whitelist[$srv->port . '-tcp'] = ['ip' => $target_ip, 'name' => $srv->name, 'uuid' => $srv->uuid]; + $whitelist[$srv->port . '-udp'] = ['ip' => $target_ip, 'name' => $srv->name, 'uuid' => $srv->uuid]; + } + + try { + $response = Http::withBasicAuth($mk_user, $mk_pass)->timeout(10)->get($url); + if (!$response->successful()) { + $this->error('Помилка API: ' . $response->body()); + return; + } + + $existing_rules = []; + foreach ($response->json() as $rule) { + if (isset($rule['comment']) && str_contains($rule['comment'], 'Pelican:')) { + $key = ($rule['dst-port'] ?? '') . '-' . ($rule['protocol'] ?? 'tcp'); + $existing_rules[$key] = $rule['.id']; + } + } + + foreach ($existing_rules as $key => $id) { + if (!isset($whitelist[$key])) { + $this->warn("Deleting rule: $key"); + Http::withBasicAuth($mk_user, $mk_pass)->delete("$url/$id"); + } + } + + foreach ($whitelist as $key => $info) { + if (!isset($existing_rules[$key])) { + [$port, $proto] = explode('-', $key); + $this->info("Adding rule: $key for {$info['name']}"); + Http::withBasicAuth($mk_user, $mk_pass)->put($url, [ + 'chain' => 'dstnat', + 'action' => 'dst-nat', + 'to-addresses' => $info['ip'], + 'to-ports' => (string)$port, + 'protocol' => $proto, + 'dst-port' => (string)$port, + 'in-interface' => $mk_interface, + 'comment' => "Pelican: {$info['name']} ({$info['uuid']})" + ]); + } + } + $this->info('Sync Complete.'); + } catch (\Exception $e) { + $this->error('Error: ' . $e->getMessage()); + } + } +} diff --git a/mikrotik-nat-sync/src/MikroTikNATSyncPlugin.php b/mikrotik-nat-sync/src/MikroTikNATSyncPlugin.php new file mode 100644 index 00000000..97628bf5 --- /dev/null +++ b/mikrotik-nat-sync/src/MikroTikNATSyncPlugin.php @@ -0,0 +1,104 @@ +runningInConsole()) { + $this->commands([ + \Avalon\MikroTikNATSync\Console\Commands\SyncMikrotikCommand::class, + ]); + } + + app()->booted(function () { + $schedule = app(Schedule::class); + $interval = env('MIKROTIK_SYNC_INTERVAL', 'everyFiveMinutes'); + + $schedule->command('mikrotik:sync') + ->{$interval}() + ->withoutOverlapping(); + }); + } + + public function getSettingsForm(): array + { + return [ + TextInput::make('mk_ip') + ->label('IP MikroTik') + ->default(env('MIKROTIK_IP')) + ->required(), + TextInput::make('mk_port') + ->label('Порт REST API') + ->default(env('MIKROTIK_PORT', '9080')) + ->required(), + TextInput::make('mk_user') + ->label('Користувач') + ->default(env('MIKROTIK_USER')) + ->required(), + TextInput::make('mk_pass') + ->label('Пароль') + ->password() + ->revealable() + ->default(env('MIKROTIK_PASS')), + TextInput::make('mk_interface') + ->label('Вхідний інтерфейс (WAN)') + ->default(env('MIKROTIK_INTERFACE', 'ether1')) + ->required(), + TextInput::make('mk_forbidden_ports') + ->label('Заборонені порти (через кому)') + ->placeholder('22, 80, 443, 3306') + ->default(env('MIKROTIK_FORBIDDEN_PORTS')), + Select::make('mk_interval') + ->label('Інтервал синхронізації') + ->options([ + 'everyMinute' => 'Щохвилини', + 'everyFiveMinutes' => 'Кожні 5 хвилин', + 'everyTenMinutes' => 'Кожні 10 хвилин', + 'hourly' => 'Щогодини', + ]) + ->default(env('MIKROTIK_SYNC_INTERVAL', 'everyFiveMinutes')) + ->required(), + ]; + } + + public function saveSettings(array $data): void + { + $this->writeToEnvironment([ + 'MIKROTIK_IP' => $data['mk_ip'], + 'MIKROTIK_PORT' => $data['mk_port'], + 'MIKROTIK_USER' => $data['mk_user'], + 'MIKROTIK_PASS' => $data['mk_pass'], + 'MIKROTIK_INTERFACE' => $data['mk_interface'], + 'MIKROTIK_SYNC_INTERVAL' => $data['mk_interval'], + 'MIKROTIK_FORBIDDEN_PORTS' => $data['mk_forbidden_ports'], + ]); + + Notification::make() + ->title('Налаштування збережено') + ->success() + ->send(); + } +} diff --git a/mikrotik-nat-sync/src/Providers/MikroTikNATSyncPluginProvider.php b/mikrotik-nat-sync/src/Providers/MikroTikNATSyncPluginProvider.php new file mode 100644 index 00000000..ba409a00 --- /dev/null +++ b/mikrotik-nat-sync/src/Providers/MikroTikNATSyncPluginProvider.php @@ -0,0 +1,18 @@ + Date: Sat, 14 Feb 2026 18:05:38 +0200 Subject: [PATCH 10/11] Remove duplicate folders with trailing spaces --- .../config /mikrotik-nat-sync.php | 5 - .../Console/Commands /SyncMikrotikCommand.php | 100 ----------------- .../src /MikroTikNATSyncPlugin.php | 104 ------------------ .../MikroTikNATSyncPluginProvider.php | 18 --- 4 files changed, 227 deletions(-) delete mode 100644 mikrotik-nat-sync/config /mikrotik-nat-sync.php delete mode 100644 mikrotik-nat-sync/src /Console/Commands /SyncMikrotikCommand.php delete mode 100644 mikrotik-nat-sync/src /MikroTikNATSyncPlugin.php delete mode 100644 mikrotik-nat-sync/src /Providers /MikroTikNATSyncPluginProvider.php diff --git a/mikrotik-nat-sync/config /mikrotik-nat-sync.php b/mikrotik-nat-sync/config /mikrotik-nat-sync.php deleted file mode 100644 index 15085c78..00000000 --- a/mikrotik-nat-sync/config /mikrotik-nat-sync.php +++ /dev/null @@ -1,5 +0,0 @@ -info('Starting MikroTik Sync...'); - - $mk_ip = str_replace(['http://', 'https://'], '', env('MIKROTIK_IP')); - $mk_port = env('MIKROTIK_PORT', '9080'); - $mk_user = env('MIKROTIK_USER'); - $mk_pass = env('MIKROTIK_PASS'); - $mk_interface = env('MIKROTIK_INTERFACE', 'ether1'); - - // Отримуємо список заборонених портів та перетворюємо в масив - $forbidden_string = env('MIKROTIK_FORBIDDEN_PORTS', ''); - $forbidden_ports = array_map('trim', explode(',', $forbidden_string)); - - if (!$mk_ip || !$mk_user || !$mk_pass) { - $this->error('Налаштування MikroTik не заповнені!'); - return; - } - - if (str_contains($mk_ip, ':')) { - $url = "http://" . $mk_ip . "/rest/ip/firewall/nat"; - } else { - $url = "http://" . $mk_ip . ":" . $mk_port . "/rest/ip/firewall/nat"; - } - - $active_servers = DB::table('servers') - ->join('allocations', 'allocations.server_id', '=', 'servers.id') - ->select('servers.uuid', 'servers.name', 'allocations.ip', 'allocations.port') - ->get(); - - $whitelist = []; - foreach ($active_servers as $srv) { - // ПЕРЕВІРКА: чи порт не в списку заборонених - if (in_array((string)$srv->port, $forbidden_ports)) { - $this->warn("Порт {$srv->port} для сервера {$srv->name} ЗАБОРОНЕНИЙ. Пропускаємо."); - continue; - } - - $target_ip = ($srv->ip == '0.0.0.0') ? '192.168.70.231' : $srv->ip; - $whitelist[$srv->port . '-tcp'] = ['ip' => $target_ip, 'name' => $srv->name, 'uuid' => $srv->uuid]; - $whitelist[$srv->port . '-udp'] = ['ip' => $target_ip, 'name' => $srv->name, 'uuid' => $srv->uuid]; - } - - try { - $response = Http::withBasicAuth($mk_user, $mk_pass)->timeout(10)->get($url); - if (!$response->successful()) { - $this->error('Помилка API: ' . $response->body()); - return; - } - - $existing_rules = []; - foreach ($response->json() as $rule) { - if (isset($rule['comment']) && str_contains($rule['comment'], 'Pelican:')) { - $key = ($rule['dst-port'] ?? '') . '-' . ($rule['protocol'] ?? 'tcp'); - $existing_rules[$key] = $rule['.id']; - } - } - - foreach ($existing_rules as $key => $id) { - if (!isset($whitelist[$key])) { - $this->warn("Deleting rule: $key"); - Http::withBasicAuth($mk_user, $mk_pass)->delete("$url/$id"); - } - } - - foreach ($whitelist as $key => $info) { - if (!isset($existing_rules[$key])) { - [$port, $proto] = explode('-', $key); - $this->info("Adding rule: $key for {$info['name']}"); - Http::withBasicAuth($mk_user, $mk_pass)->put($url, [ - 'chain' => 'dstnat', - 'action' => 'dst-nat', - 'to-addresses' => $info['ip'], - 'to-ports' => (string)$port, - 'protocol' => $proto, - 'dst-port' => (string)$port, - 'in-interface' => $mk_interface, - 'comment' => "Pelican: {$info['name']} ({$info['uuid']})" - ]); - } - } - $this->info('Sync Complete.'); - } catch (\Exception $e) { - $this->error('Error: ' . $e->getMessage()); - } - } -} diff --git a/mikrotik-nat-sync/src /MikroTikNATSyncPlugin.php b/mikrotik-nat-sync/src /MikroTikNATSyncPlugin.php deleted file mode 100644 index 97628bf5..00000000 --- a/mikrotik-nat-sync/src /MikroTikNATSyncPlugin.php +++ /dev/null @@ -1,104 +0,0 @@ -runningInConsole()) { - $this->commands([ - \Avalon\MikroTikNATSync\Console\Commands\SyncMikrotikCommand::class, - ]); - } - - app()->booted(function () { - $schedule = app(Schedule::class); - $interval = env('MIKROTIK_SYNC_INTERVAL', 'everyFiveMinutes'); - - $schedule->command('mikrotik:sync') - ->{$interval}() - ->withoutOverlapping(); - }); - } - - public function getSettingsForm(): array - { - return [ - TextInput::make('mk_ip') - ->label('IP MikroTik') - ->default(env('MIKROTIK_IP')) - ->required(), - TextInput::make('mk_port') - ->label('Порт REST API') - ->default(env('MIKROTIK_PORT', '9080')) - ->required(), - TextInput::make('mk_user') - ->label('Користувач') - ->default(env('MIKROTIK_USER')) - ->required(), - TextInput::make('mk_pass') - ->label('Пароль') - ->password() - ->revealable() - ->default(env('MIKROTIK_PASS')), - TextInput::make('mk_interface') - ->label('Вхідний інтерфейс (WAN)') - ->default(env('MIKROTIK_INTERFACE', 'ether1')) - ->required(), - TextInput::make('mk_forbidden_ports') - ->label('Заборонені порти (через кому)') - ->placeholder('22, 80, 443, 3306') - ->default(env('MIKROTIK_FORBIDDEN_PORTS')), - Select::make('mk_interval') - ->label('Інтервал синхронізації') - ->options([ - 'everyMinute' => 'Щохвилини', - 'everyFiveMinutes' => 'Кожні 5 хвилин', - 'everyTenMinutes' => 'Кожні 10 хвилин', - 'hourly' => 'Щогодини', - ]) - ->default(env('MIKROTIK_SYNC_INTERVAL', 'everyFiveMinutes')) - ->required(), - ]; - } - - public function saveSettings(array $data): void - { - $this->writeToEnvironment([ - 'MIKROTIK_IP' => $data['mk_ip'], - 'MIKROTIK_PORT' => $data['mk_port'], - 'MIKROTIK_USER' => $data['mk_user'], - 'MIKROTIK_PASS' => $data['mk_pass'], - 'MIKROTIK_INTERFACE' => $data['mk_interface'], - 'MIKROTIK_SYNC_INTERVAL' => $data['mk_interval'], - 'MIKROTIK_FORBIDDEN_PORTS' => $data['mk_forbidden_ports'], - ]); - - Notification::make() - ->title('Налаштування збережено') - ->success() - ->send(); - } -} diff --git a/mikrotik-nat-sync/src /Providers /MikroTikNATSyncPluginProvider.php b/mikrotik-nat-sync/src /Providers /MikroTikNATSyncPluginProvider.php deleted file mode 100644 index ba409a00..00000000 --- a/mikrotik-nat-sync/src /Providers /MikroTikNATSyncPluginProvider.php +++ /dev/null @@ -1,18 +0,0 @@ - Date: Sat, 14 Feb 2026 18:36:32 +0200 Subject: [PATCH 11/11] Sync code from standalone repo: Fix review issues (English, ENV, cleanup) --- mikrotik-nat-sync/README.md | 54 ++---------------- mikrotik-nat-sync/plugin.json | 8 +-- .../Console/Commands/SyncMikrotikCommand.php | 55 +++++++++++++------ .../src/MikroTikNATSyncPlugin.php | 54 +++++++++--------- 4 files changed, 72 insertions(+), 99 deletions(-) diff --git a/mikrotik-nat-sync/README.md b/mikrotik-nat-sync/README.md index 24b23b6c..75ebceeb 100644 --- a/mikrotik-nat-sync/README.md +++ b/mikrotik-nat-sync/README.md @@ -5,7 +5,7 @@ ![Platform](https://img.shields.io/badge/platform-Pelican%20Panel-orange.svg) ![AI](https://img.shields.io/badge/Created%20with-AI%20Gemini-brightgreen.svg) -**MikroTik NAT Sync** — це плагін для автоматизації прокидання портів (Port Forwarding) між панеллю Pelican та маршрутизаторами MikroTik через REST API. +**MikroTik NAT Sync** --- @@ -24,53 +24,11 @@ Enable the REST API on your router to allow communication: ``` Note: We recommend creating a dedicated user with specific firewall permissions. -### 📦 Installation / Встановлення +### 📦 Installation **Method 1: Via Web Interface (Easiest)** -1. Copy the direct link to the plugin ZIP archive: - `https://github.com/avalon0077/mikrotik-nat-sync/archive/refs/heads/main.zip` -2. In your Pelican Admin Panel, go to **Plugins** -> **Import**. -3. Paste the URL or upload the downloaded ZIP file. -4. Click **Install** and configure via the Gear icon. +1. In your Pelican Admin Panel, go to **Plugins** -> **Import**. +2. Paste the URL or upload the downloaded ZIP file. +3. Click **Install** and configure via the Gear icon. -**Method 2: Manual (CLI)** -1. Download and extract the archive to `/var/www/pelican/plugins/mikrotik-nat-sync`. -2. Make sure the folder name is exactly `mikrotik-nat-sync`. -3. Head to the **Plugins** page and click **Install**. - ---- - -## 🇺🇦 Українською - -### 🚀 Можливості -* **Повна автоматизація**: Автоматично керує правилами DST-NAT на основі активних алокацій. - -* **Безпека**: Список "Заборонених портів" для захисту системних сервісів. - -* **Розумні теги**: Керує лише своїми правилами через коментар Pelican:. - -* **Зручне налаштування**: Налаштуйте IP, логін, пароль та інтервали прямо в адмінці. - -### 🛠 Налаштування MikroTik -Увімкніть REST API для можливості віддаленого керування: - -```Bash -/ip service set www-ssl disabled=no port=9443 -``` -Порада: Створіть окремого користувача з правами на роботу лише з Firewall. - -### 📦 Installation / Встановлення - -**Спосіб 1: Через веб-інтерфейс (Найпростіший)** -1. Скопіюйте пряме посилання на ZIP-архів плагіна: - `https://github.com/avalon0077/mikrotik-nat-sync/archive/refs/heads/main.zip` -2. В адмін-панелі Pelican перейдіть у **Plugins** -> **Import**. -3. Вставте посилання або завантажте попередньо скачаний ZIP-файл. -4. Натисніть **Install** та налаштуйте через іконку шестерні. - -**Спосіб 2: Вручну (через консоль)** -1. Скачайте та розпакуйте архів у папку `/var/www/pelican/plugins/mikrotik-nat-sync`. -2. Переконайтеся, що папка називається саме `mikrotik-nat-sync`. -3. Перейдіть на сторінку **Plugins** та натисніть **Install**. - ->Developed with AI Assistance (Gemini) Розроблено за допомогою ШІ (Gemini) +Developed with AI Assistance (Gemini) diff --git a/mikrotik-nat-sync/plugin.json b/mikrotik-nat-sync/plugin.json index c1a5d603..c7d0a7b8 100644 --- a/mikrotik-nat-sync/plugin.json +++ b/mikrotik-nat-sync/plugin.json @@ -11,9 +11,5 @@ "class": "MikroTikNATSyncPlugin", "panels": null, "panel_version": "1.0.0-beta32", - "composer_packages": null, - "meta": { - "status": "enabled", - "status_message": null - } -} \ No newline at end of file + "composer_packages": null +} diff --git a/mikrotik-nat-sync/src/Console/Commands/SyncMikrotikCommand.php b/mikrotik-nat-sync/src/Console/Commands/SyncMikrotikCommand.php index 2838b75a..a7d40f55 100644 --- a/mikrotik-nat-sync/src/Console/Commands/SyncMikrotikCommand.php +++ b/mikrotik-nat-sync/src/Console/Commands/SyncMikrotikCommand.php @@ -9,24 +9,24 @@ class SyncMikrotikCommand extends Command { protected $signature = 'mikrotik:sync'; - protected $description = 'Синхронізація правил NAT з MikroTik з перевіркою заборонених портів'; + protected $description = 'Synchronize NAT rules with MikroTik checking for forbidden ports'; public function handle() { $this->info('Starting MikroTik Sync...'); - $mk_ip = str_replace(['http://', 'https://'], '', env('MIKROTIK_IP')); - $mk_port = env('MIKROTIK_PORT', '9080'); - $mk_user = env('MIKROTIK_USER'); - $mk_pass = env('MIKROTIK_PASS'); - $mk_interface = env('MIKROTIK_INTERFACE', 'ether1'); - - // Отримуємо список заборонених портів та перетворюємо в масив - $forbidden_string = env('MIKROTIK_FORBIDDEN_PORTS', ''); + $mk_ip = str_replace(['http://', 'https://'], '', env('MIKROTIK_NAT_SYNC_IP')); + $mk_port = env('MIKROTIK_NAT_SYNC_PORT', '9080'); + $mk_user = env('MIKROTIK_NAT_SYNC_USER'); + $mk_pass = env('MIKROTIK_NAT_SYNC_PASSWORD'); + $mk_interface = env('MIKROTIK_NAT_SYNC_INTERFACE', 'ether1'); + + // Get forbidden ports list and convert to array + $forbidden_string = env('MIKROTIK_NAT_SYNC_FORBIDDEN_PORTS', ''); $forbidden_ports = array_map('trim', explode(',', $forbidden_string)); if (!$mk_ip || !$mk_user || !$mk_pass) { - $this->error('Налаштування MikroTik не заповнені!'); + $this->error('MikroTik settings are not configured!'); return; } @@ -43,9 +43,9 @@ public function handle() $whitelist = []; foreach ($active_servers as $srv) { - // ПЕРЕВІРКА: чи порт не в списку заборонених + // CHECK: if port is in forbidden list if (in_array((string)$srv->port, $forbidden_ports)) { - $this->warn("Порт {$srv->port} для сервера {$srv->name} ЗАБОРОНЕНИЙ. Пропускаємо."); + $this->warn("Port {$srv->port} for server {$srv->name} is FORBIDDEN. Skipping."); continue; } @@ -57,18 +57,33 @@ public function handle() try { $response = Http::withBasicAuth($mk_user, $mk_pass)->timeout(10)->get($url); if (!$response->successful()) { - $this->error('Помилка API: ' . $response->body()); + $this->error('API Error: ' . $response->body()); return; } $existing_rules = []; - foreach ($response->json() as $rule) { + $rules_data = $response->json(); + + // Safety check if response is array + if (!is_array($rules_data)) { + $this->error('Invalid response format from MikroTik.'); + return; + } + + foreach ($rules_data as $rule) { if (isset($rule['comment']) && str_contains($rule['comment'], 'Pelican:')) { - $key = ($rule['dst-port'] ?? '') . '-' . ($rule['protocol'] ?? 'tcp'); - $existing_rules[$key] = $rule['.id']; + $dst_port = $rule['dst-port'] ?? ''; + $protocol = $rule['protocol'] ?? 'tcp'; + $key = $dst_port . '-' . $protocol; + + // We need rule ID to delete it later if needed + if (isset($rule['.id'])) { + $existing_rules[$key] = $rule['.id']; + } } } + // Remove old rules that are not in whitelist foreach ($existing_rules as $key => $id) { if (!isset($whitelist[$key])) { $this->warn("Deleting rule: $key"); @@ -76,11 +91,13 @@ public function handle() } } + // Add new rules foreach ($whitelist as $key => $info) { if (!isset($existing_rules[$key])) { [$port, $proto] = explode('-', $key); $this->info("Adding rule: $key for {$info['name']}"); - Http::withBasicAuth($mk_user, $mk_pass)->put($url, [ + + $payload = [ 'chain' => 'dstnat', 'action' => 'dst-nat', 'to-addresses' => $info['ip'], @@ -89,7 +106,9 @@ public function handle() 'dst-port' => (string)$port, 'in-interface' => $mk_interface, 'comment' => "Pelican: {$info['name']} ({$info['uuid']})" - ]); + ]; + + Http::withBasicAuth($mk_user, $mk_pass)->put($url, $payload); } } $this->info('Sync Complete.'); diff --git a/mikrotik-nat-sync/src/MikroTikNATSyncPlugin.php b/mikrotik-nat-sync/src/MikroTikNATSyncPlugin.php index 97628bf5..4944513c 100644 --- a/mikrotik-nat-sync/src/MikroTikNATSyncPlugin.php +++ b/mikrotik-nat-sync/src/MikroTikNATSyncPlugin.php @@ -35,7 +35,7 @@ public function boot(Panel $panel): void app()->booted(function () { $schedule = app(Schedule::class); - $interval = env('MIKROTIK_SYNC_INTERVAL', 'everyFiveMinutes'); + $interval = env('MIKROTIK_NAT_SYNC_INTERVAL', 'everyFiveMinutes'); $schedule->command('mikrotik:sync') ->{$interval}() @@ -47,39 +47,39 @@ public function getSettingsForm(): array { return [ TextInput::make('mk_ip') - ->label('IP MikroTik') - ->default(env('MIKROTIK_IP')) + ->label('MikroTik IP') + ->default(env('MIKROTIK_NAT_SYNC_IP')) ->required(), TextInput::make('mk_port') - ->label('Порт REST API') - ->default(env('MIKROTIK_PORT', '9080')) + ->label('REST API Port') + ->default(env('MIKROTIK_NAT_SYNC_PORT', '9080')) ->required(), TextInput::make('mk_user') - ->label('Користувач') - ->default(env('MIKROTIK_USER')) + ->label('Username') + ->default(env('MIKROTIK_NAT_SYNC_USER')) ->required(), TextInput::make('mk_pass') - ->label('Пароль') + ->label('Password') ->password() ->revealable() - ->default(env('MIKROTIK_PASS')), + ->default(env('MIKROTIK_NAT_SYNC_PASSWORD')), TextInput::make('mk_interface') - ->label('Вхідний інтерфейс (WAN)') - ->default(env('MIKROTIK_INTERFACE', 'ether1')) + ->label('WAN Interface') + ->default(env('MIKROTIK_NAT_SYNC_INTERFACE', 'ether1')) ->required(), TextInput::make('mk_forbidden_ports') - ->label('Заборонені порти (через кому)') + ->label('Forbidden Ports (comma separated)') ->placeholder('22, 80, 443, 3306') - ->default(env('MIKROTIK_FORBIDDEN_PORTS')), + ->default(env('MIKROTIK_NAT_SYNC_FORBIDDEN_PORTS')), Select::make('mk_interval') - ->label('Інтервал синхронізації') + ->label('Sync Interval') ->options([ - 'everyMinute' => 'Щохвилини', - 'everyFiveMinutes' => 'Кожні 5 хвилин', - 'everyTenMinutes' => 'Кожні 10 хвилин', - 'hourly' => 'Щогодини', + 'everyMinute' => 'Every Minute', + 'everyFiveMinutes' => 'Every 5 Minutes', + 'everyTenMinutes' => 'Every 10 Minutes', + 'hourly' => 'Hourly', ]) - ->default(env('MIKROTIK_SYNC_INTERVAL', 'everyFiveMinutes')) + ->default(env('MIKROTIK_NAT_SYNC_INTERVAL', 'everyFiveMinutes')) ->required(), ]; } @@ -87,17 +87,17 @@ public function getSettingsForm(): array public function saveSettings(array $data): void { $this->writeToEnvironment([ - 'MIKROTIK_IP' => $data['mk_ip'], - 'MIKROTIK_PORT' => $data['mk_port'], - 'MIKROTIK_USER' => $data['mk_user'], - 'MIKROTIK_PASS' => $data['mk_pass'], - 'MIKROTIK_INTERFACE' => $data['mk_interface'], - 'MIKROTIK_SYNC_INTERVAL' => $data['mk_interval'], - 'MIKROTIK_FORBIDDEN_PORTS' => $data['mk_forbidden_ports'], + 'MIKROTIK_NAT_SYNC_IP' => $data['mk_ip'], + 'MIKROTIK_NAT_SYNC_PORT' => $data['mk_port'], + 'MIKROTIK_NAT_SYNC_USER' => $data['mk_user'], + 'MIKROTIK_NAT_SYNC_PASSWORD' => $data['mk_pass'], + 'MIKROTIK_NAT_SYNC_INTERFACE' => $data['mk_interface'], + 'MIKROTIK_NAT_SYNC_INTERVAL' => $data['mk_interval'], + 'MIKROTIK_NAT_SYNC_FORBIDDEN_PORTS' => $data['mk_forbidden_ports'], ]); Notification::make() - ->title('Налаштування збережено') + ->title('Settings saved successfully') ->success() ->send(); }