Change Guard shipped covering only 4 routes (service toggle, bond create/delete, bridge vlan-filtering). The most recent real incident — a VLAN change that removed a switch from management — went through PUT /:id/ports/:name/vlan, which was NOT guarded. Neither were the bridge-VLAN CRUD routes, IP address changes, or route changes, all of which can sever the manager's own path. Guarded routes go from 4 to 12, adding: - port.vlan PUT /:id/ports/:name/vlan (PVID + tagged/untagged membership) - vlan.add POST /:id/vlans - vlan.update PUT /:id/vlans/:vlanDbId - vlan.delete DELETE /:id/vlans/:vlanDbId - ip.add POST /:id/ip-addresses - ip.remove DELETE /:id/ip-addresses/:addrId - route.add POST /:id/routing - route.remove DELETE /:id/routing/:routeId Each now takes a device-side restore point and arms a self-restore before applying, disarming only once the device is proven still reachable. Two correctness details while converting: - vlan.delete removes the local DB row in afterConfirmed, so a change that auto-reverts no longer leaves the UI claiming a VLAN was deleted when the device restored it. - The VLAN CRUD routes previously returned a bare array, which would have been spread into the guard response object. They now return a message; the frontend ignores these bodies and re-queries, so no client change is needed. Verified live against the production 100G switch: a guarded VLAN add and delete both returned protected/confirmed, were recorded in device_change_guards, and the switch was left in its original state. 133 backend tests pass; full CI preflight green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
589 lines
34 KiB
Markdown
589 lines
34 KiB
Markdown
# MikroTik Manager
|
||
|
||
A self-hosted, full-stack network management platform for MikroTik devices. Monitor, configure, and manage your entire MikroTik infrastructure — routers, switches, and wireless access points — from a single web interface.
|
||
|
||

|
||

|
||

|
||

|
||

|
||
|
||
---
|
||
|
||
## Screenshots
|
||
|
||
<p align="center">
|
||
<img src=".github/images/Login%20Page.png" alt="Login Page" width="100%" />
|
||
</p>
|
||
|
||
<br>
|
||
|
||
### Dashboard
|
||
|
||
<p align="center">
|
||
<img src=".github/images/Dashboard.png" alt="Dashboard" width="100%" />
|
||
</p>
|
||
|
||
### Device Management
|
||
|
||
<table>
|
||
<tr>
|
||
<td align="center">
|
||
<img src=".github/images/Device%20List.png" alt="Device List" /><br>
|
||
<sub><b>Device List</b></sub>
|
||
</td>
|
||
<td align="center">
|
||
<img src=".github/images/Device%20Overview.png" alt="Device Overview" /><br>
|
||
<sub><b>Device Overview</b></sub>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td align="center">
|
||
<img src=".github/images/Device%20Ports.png" alt="Switch Ports" /><br>
|
||
<sub><b>Switch Ports & Throughput</b></sub>
|
||
</td>
|
||
<td align="center">
|
||
<img src=".github/images/Device%20Hardware.png" alt="Hardware Monitor" /><br>
|
||
<sub><b>Hardware Monitor</b></sub>
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
|
||
### Wireless
|
||
|
||
<p align="center">
|
||
<img src=".github/images/Device%20Wireless%20Radio.png" alt="Wireless Radio Management" width="100%" />
|
||
</p>
|
||
|
||
### Client Tracking
|
||
|
||
<table>
|
||
<tr>
|
||
<td align="center">
|
||
<img src=".github/images/Clients.png" alt="Client List" /><br>
|
||
<sub><b>Client List</b></sub>
|
||
</td>
|
||
<td align="center">
|
||
<img src=".github/images/Client%20Details.png" alt="Client Details" /><br>
|
||
<sub><b>Client Detail View</b></sub>
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
|
||
### Network Topology
|
||
|
||
<p align="center">
|
||
<img src=".github/images/Topology.png" alt="Network Topology" width="100%" />
|
||
</p>
|
||
|
||
### Events & Backups
|
||
|
||
<table>
|
||
<tr>
|
||
<td align="center">
|
||
<img src=".github/images/Events.png" alt="Event Log" /><br>
|
||
<sub><b>Event Log</b></sub>
|
||
</td>
|
||
<td align="center">
|
||
<img src=".github/images/Backups.png" alt="Backup Management" /><br>
|
||
<sub><b>Backup Management</b></sub>
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
|
||
---
|
||
|
||
## Features
|
||
|
||
### Dashboard
|
||
- Live KPI cards: total devices, online/offline count, connected wireless clients, active alerts, fleet-wide 30-day availability %
|
||
- Device type distribution chart
|
||
- Firmware update notifications with per-device details
|
||
- Historical client count graph (1h → 30d range)
|
||
- **Operations view** — a second dashboard mode focused on running the network:
|
||
- **Things to handle** — a server-side insights engine that surfaces actionable issues across the fleet: offline devices, firmware/RouterBOOT updates, CPU/memory pressure, devices missing recent backups, connectivity flapping, and WiFi quality problems (weak-signal clients, co-channel overlap, high TX-retry radios), severity-ordered
|
||
- **Quick actions that actually run** — Run discovery, Back up all online devices, Sync config (pull `/export` + re-collect), and Open terminal (device picker → live in-browser SSH terminal), each with inline progress/result
|
||
- **Capacity & health** — per-device CPU/memory meters sorted by pressure
|
||
- **Security posture rollup** — fleet average hardening score, high-severity finding count, and the lowest-scoring devices, deep-linking to the Security Center
|
||
- **Recent activity feed** — merged config changes, user actions, and alerts
|
||
- **Anomaly insights** (Mist-style) — compares each device's last 30 minutes of client-count and CPU against its **same-hour-of-day 14-day baseline** and flags ≥2.5σ deviations, plus error-log burst detection — surfaced alongside rogue-AP alerts in Things to handle
|
||
|
||
### Device Management
|
||
- Add, edit, and delete MikroTik devices (routers, switches, wireless APs)
|
||
- Automatic polling: status, model, firmware version, RouterOS version
|
||
- Firmware update availability detection
|
||
- Per-device notes, rack location, and physical address with map support
|
||
- Device credential encryption at rest
|
||
- **Encrypted API (api-ssl)** — devices whose API runs on the SSL port (8729) are reached over TLS automatically; RouterOS's default self-signed certificates are accepted, so the credentials and session are encrypted in transit without any manual certificate trust
|
||
- **Bulk device add** — "Try All" discovered devices runs as a server-side background job (survives browser tab close) with live progress and cancel support
|
||
- CPU load and historical sparkline displayed correctly for all device types, including hardware switches that report 0% CPU via ASIC offloading
|
||
- **Device tags** — colored labels for organizing and filtering devices; full tag management in Settings
|
||
- **30-day availability tracking** — per-device uptime %, outage count, and longest outage duration recorded automatically; visible on the device Overview tab and the fleet dashboard
|
||
|
||
### Routers
|
||
- Routing table viewer
|
||
- Interface overview with IP assignments
|
||
- Router-specific settings and configuration
|
||
|
||
### Firewall & Security
|
||
A Meraki/UniFi-grade firewall experience built on the full RouterOS feature set:
|
||
- **Dedicated Security Center** — a top-level section with fleet-wide hardening scores, a per-device posture list, and a "Common Findings" rollup that aggregates identical issues across every device; "Manage" deep-links into a device's Security tab. Firewall/NAT, bandwidth, and connections are available on every managed RouterOS device (routers, switches, and APs)
|
||
- **Friendly rule builder** — Allow/Drop/Reject action chips, Any/Address/Address-List source & destination pickers, well-known port presets (HTTPS/HTTP/SSH/DNS/RDP/SMB), connection-state chips, per-rule logging, and a live plain-English preview of every rule
|
||
- **Address lists as reusable objects** — define `LAN`, `Trusted`, `Blocklist` etc. once and reference them from any rule (the Meraki/UniFi "groups" concept)
|
||
- **Rule reordering** — move rules up/down (order is decisive in RouterOS) with a single click
|
||
- **Hit counters & dead-rule insight** — per-rule packet/byte counters surface which rules are matching and flag zero-hit rules; one-click counter reset
|
||
- **NAT wizards** — guided Port Forward, Masquerade (internet sharing), and 1:1 NAT flows instead of raw fields, plus a Custom mode
|
||
- **Safe-apply lockout guard** — refuses to apply an unscoped `input`-chain drop/reject that would lock the platform/admin out of the device, requiring explicit confirmation
|
||
- **Security posture audit** — per-device hardening checklist (insecure services like telnet/ftp/www/api, missing input-chain firewall, SNMP exposure, outdated RouterOS/RouterBOOT firmware) with a score and one-click remediation, plus a management-services table
|
||
- **Bandwidth control** — simple-queue management with up/down caps per IP, subnet, or interface, and a one-click "Limit this client" action on the client detail page
|
||
- **Active connections viewer** — live connection-tracking table (source/destination, protocol, state, rate, bytes) with search
|
||
|
||
### Switches
|
||
- VLAN management (create, edit, delete VLANs)
|
||
- Per-port configuration and VLAN membership
|
||
- Switch overview with port status
|
||
- **Per-port connected clients** — selecting a port shows the clients actually connected to it (hostname, MAC, IP, VLAN, connection time) with links to each client's page. Crucially, it distinguishes *physically connected* clients from the raw MAC table: **uplink/trunk ports are auto-detected** (via an LLDP/MNDP topology neighbor, MACs spanning multiple VLANs, or a high MAC count) and show an "uplink" explainer instead of every MAC reachable through the port — with a one-click disclosure to view the full MAC table when you need it
|
||
- **Copy VLANs from another switch** — 3-step wizard that copies VLAN IDs and names from any other managed switch onto the current device, with manual per-VLAN port assignment (tagged/untagged) using a click-to-cycle interface chip grid, conflict detection with per-VLAN skip/overwrite control, and a review summary before any changes are applied
|
||
|
||
### Wireless
|
||
- Per-AP SSID management — create, edit, enable/disable, delete wireless interfaces
|
||
- **Bulk SSID deployment** — push an SSID configuration to all managed APs simultaneously
|
||
- Security profile management (WPA2/WPA3, PSK, EAP)
|
||
- Hardware radio information and band filtering (RouterOS 7 wifi package + legacy wlan package)
|
||
- Scheduled and on-demand **spectral scans** per radio
|
||
- Scheduled and on-demand **AP scans** (nearby access point discovery)
|
||
- Real-time radio monitoring
|
||
- Wireless client tracking with vendor lookup
|
||
- **Device fingerprinting** — every client is classified into a device category (server/NAS, computer, phone, TV, camera, printer, game console, voice assistant, smart-home, IoT, network gear…) from its OUI vendor and hostname, shown as an icon on the Clients list. The fingerprint is fully **overridable per client** on the client detail page, and the override persists across re-polls
|
||
- **RF Health** (fleet-wide on the Wireless page and per-AP on the Radios tab):
|
||
- **Channel usage map** across 2.4 / 5 / 6 GHz, computed from each radio's operating channel (live-resolved from `monitor` on the wifi package when channels are auto), highlighting in-use channels and **co-channel overlap**
|
||
- **AP deployment density** — connected clients plotted across the −90…−30 dBm signal scale, with a coverage-gap warning when too many clients connect at weak RSSI
|
||
- **AP radio TX retries** histogram (0%→35%+) with a band selector, derived from per-client CCQ (legacy `wireless` driver)
|
||
- **WiFi connectivity success** funnel (Association / Authentication / DHCP) derived from device logs
|
||
- **Rogue & neighbor AP detection** — cross-references stored AP-scan results against your own SSIDs and radio MACs. A foreign BSSID broadcasting one of *your* SSIDs is flagged as a **rogue / evil-twin** (and raised in Operations → Things to handle); everything else is a ranked neighbor-network inventory
|
||
|
||
### Guest WiFi (Hotspot)
|
||
A one-click captive-portal guest network on RouterOS Hotspot, under **Wireless → Guest WiFi**:
|
||
- **Guided setup wizard** — creates the whole guest network in one pass: a new **guest SSID** (a virtual AP on every physical radio), **VLAN segregation** (guest SSIDs tagged onto the bridge with an L3 VLAN interface for the portal — or a dedicated guest bridge when no VLAN is given), IP pool, DHCP, hotspot profile, portal server, a bandwidth-limited guest user profile, and an optional masquerade NAT rule. Idempotent, with honest warnings (e.g. bridge VLAN-filtering disabled). Can also target an existing interface.
|
||
- **Vouchers** — batch-generate access codes (`XXXX-XXXX`, no ambiguous characters) with validity hours, data caps, and a speed profile; **printable voucher sheets** (3-up cards with code, limits, and connection instructions) and a used/unused status table
|
||
- **Guests online** — live session table (code, IP, MAC, session time, down/up bytes) with one-click disconnect
|
||
- **Walled garden** — sites reachable before login, managed inline; per-server enable/disable
|
||
|
||
### Network Services
|
||
Each service supports multi-device management with conflict detection:
|
||
|
||
| Service | Capabilities |
|
||
|---|---|
|
||
| **DHCP** | IPv4 & IPv6 servers, address pools, static leases, live lease table |
|
||
| **DNS** | Upstream servers, static records (A/AAAA/CNAME/MX/NS/PTR/TXT/SRV), cache flush, DoH |
|
||
| **NTP** | Server (broadcast/manycast), client (unicast/multicast), sync status |
|
||
| **WireGuard** | Interface management, peer configuration, public key display, RX/TX stats |
|
||
| **Logging** | Syslog actions (remote/memory/disk/echo) and routing rules; single-device or push-to-all with per-entry coverage; enable/disable rules |
|
||
| **NetFlow** | One-toggle Traffic Flow (NetFlow v9/IPFIX) export per device, pointed at the built-in collector; live export status per device |
|
||
| **Discovery & SNMP** | Fleet-wide LLDP enable/disable and SNMP v1/v2c/v3 configuration with per-device status tables, scoped to all devices, routers only, or switches only |
|
||
|
||
### Traffic Analytics (NetFlow)
|
||
- **Built-in NetFlow/IPFIX collector** — receives Traffic Flow exports from your routers on UDP 2055 (no external tools needed); decoder supports both NetFlow v9 and IPFIX
|
||
- **Per-client usage accounting** — flows are attributed to known clients (IP → MAC) so every client shows real upload/download totals; "Data (today)" column on the Clients page and an App Traffic card on each client's detail page
|
||
- **Application breakdown** — flows are classified by protocol and port into readable categories (HTTPS, QUIC, DNS, SSH, Email, WireGuard, …) with fleet-wide and per-client views
|
||
- **Top talkers** — ranked client bandwidth usage over 1h/24h/7d/30d ranges on the dedicated Traffic page
|
||
- **Automatic deduplication** — a flow crossing two managed routers is exported twice; the collector keeps only the best exporter per client each window so totals are never double-counted
|
||
- **NAT-tolerant ingest** — when routers export from behind NAT or a VPN/Tailscale subnet router, packets arrive from an address that matches no managed device; the collector accepts these as per-source "unidentified" exporters (configurable) so flows are still attributed to clients instead of silently dropped, and the NetFlow page shows a banner naming the NAT'd source. Per-device export status/flow counters require un-NAT'd sources — exempt `udp/2055` from masquerade on the gateway to restore device identity (and exact cross-device dedup)
|
||
- **Fully UI-configurable** — enable the collector, set its address/port/version in the NetFlow page, then toggle export per device; the platform pushes `/ip traffic-flow` configuration to each router via the RouterOS API
|
||
- Configurable retention for detailed time-series (default 30 days) and daily rollups (default 365 days)
|
||
|
||
### Network Topology
|
||
- **LLDP-authoritative** — LLDP links are treated as ground truth; CDP/MNDP links to the same neighbor are automatically suppressed, eliminating spurious "Shared Segment" nodes
|
||
- Bidirectional LLDP pairs merged into a single canonical edge with both port names labeled
|
||
- Auto-discovered network map using LLDP, CDP, and MNDP neighbor data
|
||
- Interactive node graph with device type icons and protocol-priority link deduplication
|
||
- **Manual link drawing** — Connect Mode lets you drag between any two devices to draw a persistent connection for devices with no auto-discovered neighbors; connections are stored in the database and survive page reloads
|
||
- **Orphan node detection** — devices with no known connections are grouped in a dedicated row with an orange warning banner prompting Connect Mode usage
|
||
- Manual links render as purple dashed edges with a midpoint delete button
|
||
|
||
### Device Network Tools
|
||
Per-device diagnostic and testing tools accessible from the device detail Tools tab:
|
||
|
||
| Tool | Description |
|
||
|---|---|
|
||
| **Ping** | ICMP reachability test with RTT and loss metrics |
|
||
| **Traceroute** | Hop-by-hop path trace to any destination |
|
||
| **IP Scan** | ARP sweep of a subnet to discover live hosts |
|
||
| **Wake-on-LAN** | Send a magic packet from the MikroTik device to wake a host |
|
||
| **Packet Capture** | Start the RouterOS sniffer, capture for 5–60 seconds, download a `.pcap` file directly to your browser (opens in Wireshark). Requires SSH credentials on the device. |
|
||
| **Bandwidth Test** | Measure throughput between two devices. Select any managed device as the target — the bandwidth-test server is automatically enabled on the target before the test and disabled afterward. Manual IP mode available for non-managed targets. |
|
||
|
||
### Firmware Orchestration
|
||
Staged fleet-wide RouterOS upgrades, under the top-level **Firmware** section:
|
||
- **Fleet versions** — current RouterOS, latest-known version, and pending RouterBOOT upgrades per device; **Check all for updates** queries every online device live
|
||
- **Release notes in-app** — when an update is available, a "What's new" link on the device's firmware section (and the version on the Firmware page) opens MikroTik's official changelog for that exact version in a modal, with a link out to mikrotik.com
|
||
- **Staged rollouts in waves** — select updatable devices and assign each to a wave (wave 1 = **canary**); the orchestrator runs one rollout at a time, devices sequentially, through a verified pipeline: **pre-upgrade backup → install → ride out the reboot → verify it returned healthy on the new version → next device**
|
||
- **Halt on failure** stops the entire remaining rollout if any device fails, so a bad build never reaches the fleet; a reboot that comes back on the *old* version counts as a failure
|
||
- **Schedule** rollouts for a future time (pair with a maintenance window); live wave-grouped progress with animated per-device status, `from → to` versions, error detail, and **Cancel** (never interrupts an in-flight flash)
|
||
|
||
### Backups
|
||
- Trigger RouterOS backups on demand via SSH
|
||
- **Scheduled automatic backups** — pick a daily, weekly, or monthly schedule and time in Settings (no cron knowledge required); runs for all online devices
|
||
- Download and manage backup files from the UI
|
||
|
||
### Platform & Automation
|
||
Make the platform scriptable and integrable, under **Settings → Automation**:
|
||
- **Scoped API tokens** — issue `mtm_…` tokens with **read** or **write** scope and optional expiry for scripting and IaC; the token is shown once (only a SHA-256 hash is stored) and maps onto the role model — no token can perform admin actions or manage other tokens. Use with `Authorization: Bearer mtm_…` against the full REST API
|
||
- **Outbound webhooks** — subscribe URLs to any of 12 events (device up/down, log errors, high CPU/memory, cert expiry, device discovered, firmware update, config drift, firmware rollout completed/failed); deliveries are JSON POSTs **HMAC-SHA256 signed** (`X-MTM-Signature`) when a secret is set, with last-status tracking and a Send-test button. Fired through the same alert pipeline (respecting alert rules, cooldowns, and maintenance windows)
|
||
- **Scheduled email reports** — daily/weekly/monthly HTML fleet summaries (devices online, outages + downtime, error/warning counts, updates pending, backups taken, top clients by traffic) to any recipient list, using your Alerting SMTP settings; Send-now for instant delivery
|
||
|
||
### Configuration History
|
||
- Per-device config snapshots based on the device's full RouterOS `/export` — config-only, so counters and operational state never create noise — captured automatically whenever the configuration changes (deduplicated by content hash, so a snapshot is only stored on a real change)
|
||
- **Config History** tab on each device with a timeline of snapshots and a one-line summary of what changed (e.g. `+8 / −3 lines`)
|
||
- Side-by-side **line diff** between any two snapshots, rendered as readable RouterOS commands
|
||
- **Capture snapshot** button for an on-demand checkpoint, with honest feedback when nothing has changed since the last one
|
||
- **One-click rollback** — each snapshot links a restorable `.rsc` backup, so rolling back reuses the proven restore path
|
||
- The snapshot and its backup are one artifact: snapshot backups are clearly badged **Config Snapshot** on the Backups page, and deleting one (from either place) removes the other so they never drift apart
|
||
- Fires a `config_drift` alert (off by default) when a device's configuration changes
|
||
- Snapshot cadence and retention configurable via `config_snapshot_interval_min` / `config_snapshot_retention`
|
||
|
||
### Alerts
|
||
Configurable alert rules with cooldown periods:
|
||
- Device online / offline
|
||
- High CPU or memory usage (configurable threshold)
|
||
- SSL certificate expiry warning
|
||
- Firmware update available
|
||
- RouterOS log errors and warnings
|
||
- New device discovered
|
||
- Configuration changed (drift detection)
|
||
|
||
Alert delivery channels: **Email**, **Slack**, **Discord**, **Telegram**
|
||
|
||
### Maintenance Windows
|
||
- Schedule planned downtime windows per device or group of devices to suppress alerts automatically
|
||
- One-time or recurring windows (cron-based)
|
||
- Active window management with activate/deactivate controls
|
||
- Managed from Settings → Maintenance
|
||
|
||
### Audit Log
|
||
- Every write operation (create, update, delete, push) performed by an authenticated user is recorded automatically
|
||
- Log includes: user, timestamp, HTTP method, API path, entity type/ID, summary, IP address, and HTTP response status
|
||
- Filterable and paginated view in Settings → Audit Log
|
||
- Useful for multi-operator environments to track who changed what and when
|
||
|
||
### Configuration Templates
|
||
- Define reusable configuration sets (DNS servers, NTP servers, syslog host) and push them to one or more managed devices in a single operation
|
||
- Per-device result reporting (success / error per device)
|
||
- Managed from Settings → Config Templates
|
||
|
||
### Global Search
|
||
Instant search across devices, clients, and events from the top navigation bar.
|
||
|
||
### User Management & Access Control
|
||
- Role-based access: **Admin**, **Operator** (read/write), **Viewer** (read-only)
|
||
- Admin-only user creation and role assignment
|
||
- JWT authentication with secure session handling
|
||
- **Two-factor authentication (TOTP)** — per-user 2FA setup via QR code (compatible with Google Authenticator, Authy, etc.); login requires a 6-digit code after password when enabled; disable with password confirmation
|
||
- **Single sign-on (OIDC/SSO)** — authenticate against any standards-compliant OpenID Connect provider (Microsoft Entra ID, Okta, Google Workspace, Keycloak, Authentik, …). Auto-provisions or links users on first sign-in, maps IdP groups to platform roles, and coexists with local login as a break-glass fallback. Configured entirely in the UI — see [Single sign-on (OIDC)](#single-sign-on-oidc) below
|
||
- **Credential preset access control** — presets can be restricted to admins only (`allow_operator_use`); operators only see presets they are permitted to use when adding or updating devices
|
||
|
||
### TLS / HTTPS
|
||
- Automatic self-signed certificate generation on first run
|
||
- Upload a custom certificate and private key via the Settings UI
|
||
- nginx handles TLS termination and HTTP→HTTPS redirect
|
||
|
||
---
|
||
|
||
## Tech Stack
|
||
|
||
| Layer | Technology |
|
||
|---|---|
|
||
| **Frontend** | React 18, TypeScript, Vite, Tailwind CSS |
|
||
| **State / Data** | TanStack Query v5, React Router v6, Zustand |
|
||
| **Charts** | Recharts |
|
||
| **Topology** | @xyflow/react |
|
||
| **Maps** | Leaflet |
|
||
| **Terminal** | xterm.js |
|
||
| **Backend** | Node.js, Express, TypeScript |
|
||
| **Primary DB** | PostgreSQL 15 |
|
||
| **Time-series DB** | InfluxDB 2.7 |
|
||
| **Cache / Queue** | Redis 7, BullMQ |
|
||
| **Real-time** | Socket.IO |
|
||
| **Device comms** | RouterOS API (port 8728), SSH2 |
|
||
| **Proxy** | nginx (TLS termination, static file serving) |
|
||
| **Container** | Docker Compose |
|
||
|
||
---
|
||
|
||
## Requirements
|
||
|
||
- [Docker](https://docs.docker.com/get-docker/) and [Docker Compose](https://docs.docker.com/compose/install/) (v2+)
|
||
- MikroTik devices running **RouterOS 6.x or 7.x** with the API service enabled
|
||
- Network access from the host running this application to your MikroTik devices on port **8728** (or your configured API port)
|
||
|
||
---
|
||
|
||
## Quick Deploy (Pre-built Images)
|
||
|
||
No source code or build toolchain required — just Docker and Docker Compose.
|
||
|
||
### 1. Download the compose file
|
||
|
||
```bash
|
||
curl -O https://raw.githubusercontent.com/2GT-Media-Group-LLC/mikrotik-manager/main/docker-compose.ghcr.yml
|
||
```
|
||
|
||
### 2. Create your environment file
|
||
|
||
```bash
|
||
curl -O https://raw.githubusercontent.com/2GT-Media-Group-LLC/mikrotik-manager/main/.env.example
|
||
mv .env.example .env
|
||
```
|
||
|
||
Edit `.env` and set at minimum:
|
||
|
||
```env
|
||
JWT_SECRET=your_long_random_jwt_secret_here
|
||
ENCRYPTION_KEY=your_32_character_encryption_key_
|
||
CORS_ORIGIN=https://your-domain.example.com
|
||
```
|
||
|
||
### 3. Start the application
|
||
|
||
```bash
|
||
docker compose -f docker-compose.ghcr.yml up -d
|
||
```
|
||
|
||
Docker pulls the pre-built images from GitHub Container Registry and starts the stack. No compilation, no cloning.
|
||
|
||
### 4. Open the app
|
||
|
||
Navigate to **https://localhost** (or your server's IP/hostname) and log in with `admin` / `admin`.
|
||
|
||
> To update to the latest release: `docker compose -f docker-compose.ghcr.yml pull && docker compose -f docker-compose.ghcr.yml up -d`
|
||
|
||
---
|
||
|
||
## Quick Start (Build from Source)
|
||
|
||
For contributors or anyone who wants to build the images locally.
|
||
|
||
### 1. Clone the repository
|
||
|
||
```bash
|
||
git clone https://github.com/2GT-Media-Group-LLC/mikrotik-manager.git
|
||
cd mikrotik-manager
|
||
```
|
||
|
||
### 2. Configure environment variables
|
||
|
||
Copy the example environment file and edit it:
|
||
|
||
```bash
|
||
cp .env.example .env
|
||
```
|
||
|
||
At minimum, change these values in `.env`:
|
||
|
||
```env
|
||
# Required — use long, random strings
|
||
JWT_SECRET=your_long_random_jwt_secret_here
|
||
ENCRYPTION_KEY=your_32_character_encryption_key_
|
||
|
||
# Required for production deployments (set to your domain)
|
||
# CORS_ORIGIN=https://manager.example.com
|
||
|
||
# Optional — defaults work for a local install
|
||
DB_PASSWORD=mikrotik_secure_pw
|
||
INFLUXDB_TOKEN=mytoken123456789
|
||
```
|
||
|
||
> **Security note:** Never commit your `.env` file to version control. The `.gitignore` already excludes it.
|
||
|
||
### 3. Start the application
|
||
|
||
```bash
|
||
docker compose up -d
|
||
```
|
||
|
||
The first run will:
|
||
- Build the frontend (React → static files)
|
||
- Build the backend (TypeScript → Node.js)
|
||
- Initialize PostgreSQL with the database schema
|
||
- Initialize InfluxDB
|
||
- Generate a self-signed TLS certificate
|
||
|
||
### 4. Open the app
|
||
|
||
Navigate to **https://localhost** (or your server's IP/hostname).
|
||
|
||
Accept the browser's self-signed certificate warning, or upload a real certificate in **Settings → TLS Certificate**.
|
||
|
||
### 5. Log in
|
||
|
||
Default credentials on first run:
|
||
|
||
| Username | Password | Role |
|
||
|---|---|---|
|
||
| `admin` | `admin` | Admin |
|
||
|
||
**Change the default password immediately** in Settings → Users.
|
||
|
||
---
|
||
|
||
## Enabling the RouterOS API
|
||
|
||
Typically, API access is enabled on MikroTik devices. However if you can't connect via API, ensure the API service is enabled:
|
||
|
||
```
|
||
/ip service enable api
|
||
```
|
||
|
||
For API over SSL (port 8729):
|
||
```
|
||
/ip service enable api-ssl
|
||
```
|
||
|
||
The default API port is `8728`. You can configure a different port per device in the MikroTik Manager interface.
|
||
|
||
---
|
||
|
||
## Configuration Reference
|
||
|
||
All configuration is done via environment variables in `.env`:
|
||
|
||
| Variable | Default | Description |
|
||
|---|---|---|
|
||
| `JWT_SECRET` | *(auto-generated & persisted if unset)* | Secret for signing JWT tokens. Leave unset to have a strong one generated on first boot; set it to pin your own value. |
|
||
| `ENCRYPTION_KEY` | *(auto-generated & persisted if unset)* | Key for encrypting device passwords at rest. Leave unset to auto-generate; set it to pin your own value. |
|
||
| `CORS_ORIGIN` | *(localhost defaults set by Docker Compose)* | Comma-separated list of browser origins allowed to call the API (e.g. `https://manager.example.com`). **Required in production** — set this to your domain. |
|
||
| `DB_PASSWORD` | `mikrotik_secure_pw` | PostgreSQL password |
|
||
| `INFLUXDB_TOKEN` | `mytoken123456789` | InfluxDB admin token |
|
||
| `INFLUXDB_ORG` | `mikrotik-manager` | InfluxDB organization name |
|
||
| `INFLUXDB_BUCKET` | `metrics` | InfluxDB bucket for time-series data |
|
||
| `INFLUXDB_ADMIN_PASSWORD` | `admin_password_123` | InfluxDB admin UI password |
|
||
| `HTTP_PORT` | `80` | Host port for HTTP (redirects to HTTPS) |
|
||
| `HTTPS_PORT` | `443` | Host port for HTTPS |
|
||
|
||
### Secret management (self-healing)
|
||
|
||
`JWT_SECRET` (session signing) and `ENCRYPTION_KEY` (AES-256-GCM credential encryption, `backend/src/utils/crypto.ts`) are managed automatically:
|
||
|
||
- **If set to a strong value in the environment**, that value is used — you stay in control.
|
||
- **If unset or left at an old default**, the backend generates a strong secret **once**, persists it to the `app_data` volume (`SECRETS_DIR`, default `/app/data`), and reuses it on every boot. Secrets live outside the database so a DB dump alone can't reveal the key protecting the credentials in it.
|
||
|
||
Upgrades are non-breaking: on startup, existing ciphertext is decrypted via a **legacy-key fallback** (including previous defaults) and transparently **re-encrypted under the current key** by a background sweep. When rotating the JWT secret off a public default, sessions signed with that default are no longer accepted — users simply log in again once.
|
||
|
||
**Key rotation:** set a new `ENCRYPTION_KEY` (or delete the persisted secret to force regeneration) and restart. Old rows keep decrypting via the legacy fallback and are re-encrypted forward automatically. **If the persisted secret and any prior key are both lost**, ciphertext under that key cannot be recovered — re-enter device credentials or restore a backup.
|
||
|
||
### Single sign-on (OIDC)
|
||
|
||
Sign in against any standards-compliant OpenID Connect provider (Microsoft Entra ID, Okta, Google Workspace, Keycloak, Authentik, and others). It uses the Authorization Code flow with PKCE; the server validates the ID token (signature via JWKS, plus `iss`/`aud`/`exp`/`nonce`) and then issues the platform's normal session — so SSO users get the same experience as local users. **No environment variables or redeploy are required** — everything is configured in the UI and stored (with the client secret encrypted at rest) in the database.
|
||
|
||
**1. Register the app with your identity provider.** Create an OIDC/OAuth "web application" and note its **Issuer URL** (the base that serves `/.well-known/openid-configuration`), **Client ID**, and **Client Secret** (public clients with no secret are also supported via PKCE). For the **redirect / callback URI**, use the value shown in the settings panel:
|
||
|
||
```
|
||
https://<your-host>/api/auth/oidc/callback
|
||
```
|
||
|
||
**2. Configure it in the app.** As an admin, go to **Settings → SSO / OIDC** and fill in:
|
||
|
||
| Field | Purpose |
|
||
|---|---|
|
||
| **Issuer URL** | Your provider's base URL; **Test discovery** verifies it's reachable. |
|
||
| **Client ID / Client Secret** | From step 1. The secret is write-only and encrypted at rest; leave blank to keep the existing one. |
|
||
| **Scopes** | Default `openid profile email`. Add a groups scope if your IdP needs one to emit group membership. |
|
||
| **Username / Email / Groups claim** | Which ID-token claims to read. Defaults: `preferred_username`, `email`, `groups`. |
|
||
| **Group → role mapping** | Map IdP groups to **admin / operator / viewer** (e.g. `net-admins → admin`). Highest-privilege match wins. |
|
||
| **Default role** | Role for users whose groups don't match any mapping (default **viewer**). |
|
||
| **Auto-provision** | Create a local user automatically on first sign-in. |
|
||
| **Link by verified email** | Attach an SSO identity to an existing local account when the IdP's `email_verified` matches. |
|
||
| **Allowed email domains** | Optional allowlist restricting who may sign in. |
|
||
| **Button label** | Text on the login button (e.g. "Sign in with Okta"). |
|
||
|
||
Enable it and **Save**. A **Sign in with SSO** button appears on the login page.
|
||
|
||
**Behavior and safety:**
|
||
- **Provisioning / linking** — first sign-in creates a user (auto-provision) or, if `email_verified` matches an existing local account, links to it (no duplicate). SSO users have no local password.
|
||
- **Roles** are always resolved server-side from your group mapping; existing users are never silently demoted when a login carries no matching group.
|
||
- **Break-glass** — local username/password login stays available, so the seeded local admin can always get in even if the IdP is misconfigured or unreachable.
|
||
- **Behind a proxy / custom domain** — the redirect URI is derived from the request host; set **Public base URL** in the panel to pin it if your external URL differs from what the backend sees.
|
||
- SSO users are labeled with an **SSO** badge in **Users & Roles**.
|
||
|
||
---
|
||
|
||
## Updating
|
||
|
||
Pull the latest changes and rebuild:
|
||
|
||
```bash
|
||
git pull
|
||
docker compose up -d --build backend nginx
|
||
```
|
||
|
||
Database migrations run automatically on backend startup.
|
||
|
||
---
|
||
|
||
## Project Structure
|
||
|
||
```
|
||
mikrotik-manager/
|
||
├── frontend/ # React + TypeScript (Vite)
|
||
│ └── src/
|
||
│ ├── pages/ # Page components (one per route)
|
||
│ ├── components/ # Shared UI components
|
||
│ ├── services/ # API client (Axios)
|
||
│ ├── hooks/ # Custom React hooks
|
||
│ └── types/ # TypeScript type definitions
|
||
│
|
||
├── backend/ # Node.js + Express + TypeScript
|
||
│ └── src/
|
||
│ ├── routes/ # REST API route handlers
|
||
│ ├── services/ # Business logic (polling, alerts, backups)
|
||
│ │ └── mikrotik/ # RouterOS API client and device collector
|
||
│ ├── db/ # Database migrations
|
||
│ ├── config/ # DB, InfluxDB, Redis connections
|
||
│ ├── middleware/ # Auth, audit logging, error handling
|
||
│ └── utils/ # Helpers (crypto, OUI lookup, etc.)
|
||
│
|
||
├── nginx/ # Reverse proxy config and Dockerfile
|
||
├── docker-compose.yml
|
||
└── .env.example
|
||
```
|
||
|
||
---
|
||
|
||
## Contributing
|
||
|
||
Contributions are welcome! Please open an issue before submitting a pull request so we can discuss the approach.
|
||
|
||
1. Fork the repository
|
||
2. Create a feature branch: `git checkout -b feature/your-feature`
|
||
3. Commit your changes: `git commit -m "Add your feature"`
|
||
4. Push to the branch: `git push origin feature/your-feature`
|
||
5. Open a pull request
|
||
|
||
---
|
||
|
||
## License
|
||
|
||
This project is licensed under the **GNU Affero General Public License v3.0 (AGPLv3)** — see the [LICENSE](LICENSE) file for the full text.
|
||
|
||
### What this means
|
||
|
||
- You are free to use, modify, and distribute this software.
|
||
- If you run a modified version of this software as a network service (e.g., as a hosted web app), you **must** make your modified source code available to users of that service under the same AGPLv3 license.
|
||
- Any distributed copies or derivatives must also carry the AGPLv3 license.
|
||
|
||
This license was chosen to ensure that improvements made to this project — including those deployed as a service — remain open and available to the community.
|
||
|
||
---
|
||
|
||
## AI Assistance
|
||
|
||
This project was designed and built with the help of [Claude](https://claude.ai) by Anthropic. AI assistance was used throughout development — including architecture decisions, backend services, frontend components, the CI/CD pipeline, security configuration, and unit tests.
|
||
|
||
We believe in being transparent about how software is made. The code has also been reviewed and tested using AI and is maintained by the project authors.
|
||
|
||
---
|
||
|
||
## Disclaimer
|
||
|
||
This project is not affiliated with or endorsed by MikroTik. MikroTik and RouterOS are trademarks of SIA MikroTīkls. Use this software at your own risk. Always test configuration changes in a non-production environment first.
|