diff --git a/README.md b/README.md index 443685fc..2d76c4f0 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](/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 diff --git a/mikrotik-nat-sync/README.md b/mikrotik-nat-sync/README.md new file mode 100644 index 00000000..75ebceeb --- /dev/null +++ b/mikrotik-nat-sync/README.md @@ -0,0 +1,34 @@ + +# 🌐 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** + +--- + +## πŸ‡ΊπŸ‡Έ 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. 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. + +Developed with AI Assistance (Gemini) 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_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 settings are not configured!'); + 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) { + // CHECK: if port is in forbidden list + if (in_array((string)$srv->port, $forbidden_ports)) { + $this->warn("Port {$srv->port} for server {$srv->name} is FORBIDDEN. Skipping."); + 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 Error: ' . $response->body()); + return; + } + + $existing_rules = []; + $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:')) { + $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"); + Http::withBasicAuth($mk_user, $mk_pass)->delete("$url/$id"); + } + } + + // 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']}"); + + $payload = [ + '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']})" + ]; + + Http::withBasicAuth($mk_user, $mk_pass)->put($url, $payload); + } + } + $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..4944513c --- /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_NAT_SYNC_INTERVAL', 'everyFiveMinutes'); + + $schedule->command('mikrotik:sync') + ->{$interval}() + ->withoutOverlapping(); + }); + } + + public function getSettingsForm(): array + { + return [ + TextInput::make('mk_ip') + ->label('MikroTik IP') + ->default(env('MIKROTIK_NAT_SYNC_IP')) + ->required(), + TextInput::make('mk_port') + ->label('REST API Port') + ->default(env('MIKROTIK_NAT_SYNC_PORT', '9080')) + ->required(), + TextInput::make('mk_user') + ->label('Username') + ->default(env('MIKROTIK_NAT_SYNC_USER')) + ->required(), + TextInput::make('mk_pass') + ->label('Password') + ->password() + ->revealable() + ->default(env('MIKROTIK_NAT_SYNC_PASSWORD')), + TextInput::make('mk_interface') + ->label('WAN Interface') + ->default(env('MIKROTIK_NAT_SYNC_INTERFACE', 'ether1')) + ->required(), + TextInput::make('mk_forbidden_ports') + ->label('Forbidden Ports (comma separated)') + ->placeholder('22, 80, 443, 3306') + ->default(env('MIKROTIK_NAT_SYNC_FORBIDDEN_PORTS')), + Select::make('mk_interval') + ->label('Sync Interval') + ->options([ + 'everyMinute' => 'Every Minute', + 'everyFiveMinutes' => 'Every 5 Minutes', + 'everyTenMinutes' => 'Every 10 Minutes', + 'hourly' => 'Hourly', + ]) + ->default(env('MIKROTIK_NAT_SYNC_INTERVAL', 'everyFiveMinutes')) + ->required(), + ]; + } + + public function saveSettings(array $data): void + { + $this->writeToEnvironment([ + '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('Settings saved successfully') + ->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 @@ +