Self-hosted web application for managing MikroTik device fleets. Monitor, configure, upgrade, and backup your devices from a single dashboard with real-time WebSocket updates.
- Real-time dashboard — device status cards with WebSocket live updates (60s polling, configurable per device)
- Metric & SFP optical graphs (v1.42.0+, CPU/memory v1.53.1+) — per-device history charts for CPU load, memory usage, voltage, temperature, fan speed and any other
/system/healthsensor a model exposes (standalone card, auto-scaled axis, 1h/6h/24h/7d/30d). CPU and memory ride along on the regular poll, so they cost no extra device I/O and are graphed on every transport, SNMP-polled devices included. Fibre ports also get SFP optical graphs — Rx/Tx power (dBm), module temperature and supply voltage — via a Traffic/Optical switch on the traffic card. Sampled every 5 minutes, kept 90 days by default (METRICS_RETENTION_DAYS, adjustable in Settings); useful even without an SNMP monitoring system. Works for offline devices, and on models with no sensors at all - Metric threshold alerts (v1.58.0+) — fire a webhook when a device's CPU load, memory usage or temperature crosses a threshold, and an optional recovery webhook when it drops back below. Thresholds set globally in Settings → Metric alerts (CPU/memory default 90%, temperature off by default) or overridden per device (
0disables a metric for that device). An alert only fires after the value stays over the limit for a few consecutive samples (default 3), so brief spikes stay quiet - SNMP monitoring — lightweight SNMPv2c polling (CPU, memory, uptime, temperature, voltage)
- Three connection methods — SSH, REST API, or SNMP-only per device
- SNMP as supplementary — SSH/REST devices can also use SNMP for faster status checks
- SNMP-first monitoring (v1.46.0+) — optionally poll routine status and traffic over SNMP (IF-MIB 64-bit counters), opening SSH/REST only on demand for actions and detail tabs — keeps long-lived SSH sessions off routers that don't like them. Global toggle or per-device (Default / SNMP / SSH); off by default, never forced
- Site grouping — organize devices by physical location
- Device tags — assign tags with autocomplete, filter by multiple tags (Shift+click)
- Bulk editing — select multiple devices, change connection parameters in one action
- Enable/disable — disabled devices skip monitoring, dimmed in UI
- Import from scan — discover and add devices from network scan results
- Export / import between instances (v1.45.0+) — export selected devices (by site or individually) to a passphrase-encrypted
.mikrbundleand import them into another mikr instance, credentials included; ideal for seeding demo/staging from production
- Bulk CLI commands — execute on multiple devices with live WebSocket output
- Run-as credential override — optionally run a bulk command under your own RouterOS login for that one execution; credentials are never stored or logged
- Tag-based target selection — toggle all devices carrying a tag on/off with one click, alongside Select All/Online and manual picking
- RouterOS upgrades — check for updates + upgrade with real-time progress
- Active Tasks tray — running upgrades stay visible from any page and survive a reload; each task keeps its own log (v1.54.0+)
- Firmware upgrades — write firmware + automatic reboot
- Config backup & export — save device configurations to database. (v1.55.0+) Every export is verified complete before it is stored, and backups taken earlier are checked on upgrade — any that do not hold a whole config are marked incomplete so they are never mistaken for a usable restore point
- Side-by-side diff — compare any two backups visually
- Backup scheduling — automated exports with time-of-day selection and flexible intervals (2h to 7d)
- Site-wide backup schedules (v1.41.0+) — set one schedule per site; every device in the site inherits it and devices added later are included automatically. Per-device overrides are kept; opt a new device out from the Add Device form
- Built-in syslog receiver (UDP + TCP) — the Manager ships both UDP and TCP listeners on port
5514that ingest MikroTik syslog messages, persist them to SQLite, and stream live to the UI over WebSocket. TCP is useful when UDP is blocked by firewalls or when you want guaranteed delivery — RouterOS 7.x supportsRemote Log Protocol: TCPnatively. - Native RouterOS format — topics and messages appear exactly as in
/log print(works out of the box withremote-log-format=default; also accepts<PRI>-prefixed andbsd-syslog=yesRFC3164 forms) - Unified Logs page — all devices in one stream with colored severity stripe, filters (device, severity, topic, text search), time range selector (
All time/Last 1h/6h/24h/7d), Load older pagination, pause and auto-scroll - Per-device Logs tab — focused view on the device detail page for troubleshooting a specific router
- Multi-IP device correlation — logs are matched to the right device by any interface IP (collected each monitor poll), so the syslog source IP doesn't need to equal the management IP and
src-address=pinning is optional - Per-device retention override — raise the row cap on chatty core/border routers, lower it on quiet APs, so a log-storm on one device can't evict logs from the rest of the fleet
- Setup Guide modal — one click generates a copy-paste MikroTik CLI snippet and an optional Command Template to apply the config to every device at once
- On-device syslog + RouterOS
dstnat(if running mikr inside a MikroTik Container App) — see the full install guide at mikr.app/install.html#container - Webhooks — fire an HTTP notification on device online/offline/not-accessible/rebooted, upgrades, backups, new RouterOS releases, CVE and vendor-patch alerts, LTE data thresholds, and metric thresholds (CPU/memory/temperature). Ready-made JSON, ntfy and Discord formats, optional HMAC-SHA256 signing, and per-event custom message templates
- Webhooks to any REST API (v1.52.0+) — the Custom format adds your own headers, body template and HTTP method (POST/GET/PUT), so a webhook can drive an SMS gateway, ticketing system or anything else with a REST endpoint. Write the body your target expects (e.g.
{"to":["+48123456789"],"text":"{{summary}}"}); placeholders work in the URL too, for APIs that take query parameters. Substituted values are escaped for yourContent-Type, so quotes in device names or error text can't malform the request. Header values are encrypted at rest like device passwords and are never returned to the browser - Prometheus / Grafana export (v1.59.0+) — an opt-in
/metricsendpoint in Prometheus text-exposition format for scraping fleet metrics into a time-series database for long-term graphing. Exposes device up/down, CPU, memory, uptime, temperature, voltage, power, and per-interface receive/transmit rates (mikr_interface_rx_bits_per_second/_tx_bits_per_second) — labelled by device, host, site and interface. Enable and set an optional scrape token under Settings → Prometheus export (orMETRICS_ENABLED/METRICS_TOKEN). Reads from the manager's own database, so a scrape adds no load on your routers. InfluxDB users can pull it in via a Telegrafprometheusinput — see docs/prometheus-telegraf.md; a starter Grafana dashboard is in docs/grafana-dashboard.json
- IP range scanning — CIDR, dash ranges, single IP (probes SSH + HTTPS + HTTP)
- Neighbor discovery — MNDP / LLDP / CDP with clickable links to managed devices
- MAC→IPv4 cross-reference — resolves link-local IPv6 neighbors to real addresses
- Full interface discovery — all types: ethernet, SFP, bridge, VLAN, bonding, wireless, WireGuard, EoIP, GRE, PPPoE
- Grouped interface view — Ethernet/Physical, CAPsMAN/WiFi, Bridge/VLAN/Bond, Tunnels/VPN, categorized with chips
- Physical port map (redesigned v1.48.0+) — a compact faceplate: each port is a slot with a link-speed LED bar and an activity light, micro-badges for PoE watts (⚡), SFP Rx power (dBm) and the connected neighbour, and a live-traffic sparkline. Copper and SFP ports are drawn as separate blocks (fibre ports as distinct SFP cages); wide 24/48-port switches scale the whole two-row faceplate to fit the width (no scroll), and a non-clipping hover tooltip shows full per-port detail
- PoE indicators — lightning bolt icon with power, voltage, current in tooltip
- DHCP leases — view all leases with IP, MAC, hostname, status badges, and expiry time
- Wireless clients — connected clients with signal strength, TX/RX rates, uptime, and IP from DHCP
- Wi-Fi password change — set new WPA2/WPA3 passphrases from the Wi-Fi tab for modern RouterOS 7 Wi-Fi (
/interface wifi, incl. CAPsMAN); profile-aware, write-only (current password never shown), admin-only, audited (v1.49.1+) - Auto-refresh — DHCP and wireless tables update every 30s while visible, disconnected clients disappear automatically
- IPsec tunnels — configured peers with established/not established state, traffic counters, uptime; grouped as an expandable Peer → Policies tree with per-peer established/total policy counts (v1.43.0+)
- WireGuard peers — endpoint, last handshake (color-coded by recency), TX/RX counters; Add client wizard (v1.50.0+) picks the next free tunnel IP (v4 + v6), generates the keypair, writes the peer and returns a QR + downloadable
.conf— private key shown once, never stored (admin only) - LTE monitoring — per-modem signal (RSRP/RSRQ/SINR/RSSI with bars), operator, band/cell info, session uptime, optional cell-tower location (CellMapper / OpenCelliD), and modem firmware upgrade
- LTE SMS inbox (v1.44.0+) — read SMS received by an LTE modem (time, sender, text) under the device LTE tab, with a sender/text filter and unread tracking; messages are stored in mikr so the history survives the modem auto-erasing its own inbox
- LTE data-usage counter (v1.47.0+) — set a monthly data limit (GB) per LTE device and mikr accumulates the LTE transfer for the calendar month, showing a used/limit bar on the LTE tab and firing a webhook at a warning % (default 80%) and at 100% — once each per cycle. Counts LTE interfaces only; the figure is an estimate accumulated from polling (not the carrier balance). Optional per-device Owner phone is carried in the alert payload for SMS bridging via webhooks
- BGP / OSPF status (v1.38.0+) — a Routing tab on devices running BGP or OSPF: live BGP sessions with remote AS, state, uptime and received prefix count, plus OSPF neighbors with state, adjacency time and area. Surfaces details RouterOS doesn't expose over SNMP (per-peer prefix counts, OSPF neighbors); auto-refreshes, shown only where the protocol is active
- BGP history graphs (v1.39.0+) — each BGP peer keeps a 5-minute history of its received prefix count and session up/down state, charted under the BGP table (auto-scaled Y axis, downtime strip, 6h/24h/7d/30d ranges). Click any BGP row to chart that peer
- IP services — see all MikroTik services (SSH, API, WWW, Winbox, FTP) as colored pills, toggle enable/disable with safety checks
- Route counting — per-protocol breakdown (static, connected, BGP, OSPF, RIP, etc.)
- Network topology map — interactive per-site map built from MNDP/LLDP neighbour discovery: managed devices as draggable cards (status, model, CPU/RAM, IP), physical links styled by speed / bond / wireless, and unknown neighbours as dashed nodes. More reliable link discovery, an empty-state that explains why no links were found, and IPs shown on unmanaged nodes — resolved from LLDP or, when discovery advertises none, from the ARP tables of your managed devices (ARP fallback v1.60.0+)
- Mobile-friendly layout (v1.36.0+) — manage the fleet from a phone or tablet in the field: slide-in drawer menu, tables that reflow into cards or scroll horizontally, full-width bottom-sheet modals, a readable stacked log viewer, and touch-sized controls. Desktop layout unchanged; Topology stays desktop-only
- HTTPS / TLS — optional HTTPS server on port 3443; auto-generated self-signed cert or bring your own; HTTP and HTTPS run in parallel; WebSocket (WSS) works automatically over HTTPS
- Role-based access — superadmin / admin / operator / viewer
- Per-site access control (v1.33.0+) — scope any non-superadmin user to specific sites; the role decides what they can do, the assigned sites decide where. Scoped users only see and act on their sites everywhere (dashboard, devices, logs, backups, live status, bulk commands/upgrades), enforced server-side. Superadmin manages users and grants site access; unrestricted users keep full-fleet access. (v1.40.0+) Access can be assigned both ways — per user, or per site straight from the Add/Edit Site form (tick which scoped users get the site, instead of editing each user)
- JWT authentication — access token (15min) + refresh token (7d)
- RouterOS CVE alerting — each device's RouterOS version is matched daily against the public NVD vulnerability feed. Dedicated Security page with severity/site filters, a per-device Security Advisories card, a
N CVEbadge on device cards, and a High/Critical dashboard banner. Each entry shows the CVSS score/vector, summary, advisory link, and the version that fixes it. Scoped to RouterOS 7; optionalcve-alertwebhook. Informational only — never blocks upgrades or commands - Vendor patch-gap detection (v1.51.0+) — a second, independent security signal that catches what CVE databases structurally cannot. mikr reads MikroTik's own release changelogs for the branches you actually run and flags any device below a release that fixed something security-related, using MikroTik's
!)marker plus any named CVE id. Kept deliberately separate from the CVE feed, because the two make different claims: a CVE says "this device is vulnerable" (NVD supplies an affected range), a patch gap says only "a security release exists on your branch and you're below it" — a changelog names the fix version, never the affected range, so mikr never infers one from the other and never guesses across branches. Security lines are snapshotted on first sight and re-checked daily: when the vendor edits or removes one after publication, mikr keeps the original text and shows the edit. Security → Vendor patches tab, device-page card,security-patch-availablewebhook - Auto-block / Simple IDS — detects repeated failed logins from the syslog stream, aggregates them per source IP across the whole fleet, and past a threshold (default 5 / 10 min) blocks the IP by maintaining a
mikr-blocklistfirewall address-list with a native 72h timeout. Per-device opt-in; Audit mode (list candidates, block by hand) or Auto-block (block at threshold); whitelist (manual CIDRs + auto private-range + hard-whitelisted server IP). Address-list only — you add one drop rule that references the list; mikr never touches your firewall chains. Candidates / Blocked / Whitelist tabs on the Security page - GeoIP rule generator — pick countries and generate an idempotent RouterOS firewall script sourced from ipdeny.com: Blocklist (drop the selected countries; inbound/outbound,
raworfilter) or Allowlist (keep only the selected countries, drop the rest — auto-adds an established/private/always-allow safety block so it can't lock you out); IPv4/IPv6, optional weekly auto-refresh; saved as a Script command template and installed via the existing Deploy flow - Two-factor authentication (TOTP) — opt-in per user, RFC 6238 compatible with Google Authenticator / Authy / 1Password / Microsoft Authenticator; AES-256-GCM-encrypted secrets; 8 single-use backup codes; admin reset for lost devices
- Passkey / WebAuthn / FIDO2 sign-in — additive second factor alongside TOTP: Touch ID, Face ID, Windows Hello, Apple/Google passkey, YubiKey. Multiple named passkeys per account with last-used info, admin reset, counter-regression check against cloned authenticators. Requires HTTPS on a real domain (WebAuthn spec forbids IP RP IDs) — LAN-IP installs see an explicit "Unavailable" notice rather than silent failure
- Encrypted passwords — AES-256-GCM for stored device credentials
- Dark / Light theme — toggle in sidebar, persisted in localStorage
- Settings UI for runtime knobs (v1.30.0+) — admin Settings → System configuration: 21 knobs across 11 groups (CVE feed enable/key/interval, activity-log retention, monitor poll cadence and concurrency, traffic and syslog retention, backup and upgrade scheduler intervals, JWT access/refresh expiry, WebAuthn RP ID / name / origins, log level, default SSH / REST API port for new devices). Edit live in the browser, no
docker-composeedit or container restart, secrets encrypted at rest with AES-256-GCM. Sticky left subnav, one card per group, "from env" badge on any field locked by an environment variable. - Environment variables always win — anything you set in
.env/ docker-compose stays the source of truth and renders as read-only in the UI with a "from env" badge. GitOps-friendly: existing compose-first deployments don't change behaviour after the upgrade. Bootstrap values (PORT,HOST,JWT_SECRET,ENCRYPTION_KEY,TLS_*,SYSLOG_PORT) stay env-only by design — they're either consumed before settings are loaded, or moving them would invalidate every stored secret on change. - Optional
.rscmirror to/data/exports(v1.30.0+, closes #28) — toggle in Settings → External export. After each successful backup, also writes the RouterOS script export to/data/exports/<site>/<device>.rsc— one file per device, overwritten on each new backup. Mount/data/exportson a separate host volume for disaster recovery, point a git checkout at it for a free change-history (commit after each write), or rclone it to a NAS / cloud for 3-2-1 backups. Mikr never deletes from this volume; cleanup is yours (git rm / shell).
Full install guide, including how to deploy on a MikroTik Container App and configure the RouterOS
dstnatrule for UDP 5514 (syslog): see mikr.app/install.html.
# Pull image
docker pull ghcr.io/hreskiv/mikr:latest
# Create project directory
mkdir -p /opt/mikr/data && cd /opt/mikr
# Create docker-compose.yml
cat > docker-compose.yml << 'EOF'
services:
mikr:
image: ghcr.io/hreskiv/mikr:latest
container_name: mikr-manager
restart: unless-stopped
ports:
- "3000:3000"
- "3443:3443" # HTTPS (optional, requires TLS_ENABLED=true)
- "5514:5514/udp" # Syslog UDP (optional — omit if not using the Logs page)
- "5514:5514/tcp" # Syslog TCP (optional — useful when UDP is blocked)
volumes:
- ./data:/app/data
environment:
- PORT=3000
- HOST=0.0.0.0
- STORAGE_ADAPTER=sqlite
# JWT_SECRET and ENCRYPTION_KEY are auto-generated on first start and saved
# to ./data/.secrets.json (mode 0600). Back up the data/ directory!
# Override here if you want to set your own:
# - JWT_SECRET=$(openssl rand -hex 48)
# - ENCRYPTION_KEY=$(openssl rand -hex 32)
EOF
# Start
docker compose up -d
# Create default admin user (first run only)
docker exec mikr-manager node scripts/seed.jsOpen http://<host>:3000, login: admin / admin
Production: Change the default password immediately. Create a dedicated MikroTik user group with only the required policies instead of using
adminwith full access:/user/group/add name=manager-group policy=ssh,reboot,read,write,sensitive,rest-api,policy,!local,!telnet,!ftp,!test,!winbox,!password,!web,!sniff,api,!romon /user/add name=mikr group=manager-group password=YOUR_PASSWORDThis limits the blast radius if the manager is compromised.
docker pull ghcr.io/hreskiv/mikr:latest
mkdir -p /opt/mikr/data
docker run -d \
--name mikr-manager \
--restart unless-stopped \
-p 3000:3000 \
-p 5514:5514/udp \
-p 5514:5514/tcp \
-v /opt/mikr/data:/app/data \
-e PORT=3000 \
-e HOST=0.0.0.0 \
-e STORAGE_ADAPTER=sqlite \
ghcr.io/hreskiv/mikr:latest
# JWT_SECRET and ENCRYPTION_KEY are auto-generated and persisted to
# /opt/mikr/data/.secrets.json on first start. To use your own values,
# pass: -e JWT_SECRET=$(openssl rand -hex 48) -e ENCRYPTION_KEY=$(openssl rand -hex 32)
docker exec mikr-manager node scripts/seed.jsAll variables below are optional with sensible defaults. From v1.30.0, most of them are also editable from Admin → Settings → System configuration without a compose edit or restart — when set as an env var here, they take precedence over the UI value and show as locked with a "from env" badge. Bootstrap values (marked bootstrap below) stay env-only.
| Variable | Required | Default | Description |
|---|---|---|---|
JWT_SECRET |
No | auto-generated | Auto-generated on first start, persisted to data/.secrets.json. Override by setting env. Generate your own: openssl rand -hex 48 |
ENCRYPTION_KEY |
No | auto-generated | 64 hex chars (32 bytes). Same auto-gen + persist as JWT_SECRET. Generate your own: openssl rand -hex 32 |
PORT |
No | 3000 |
Server port |
HOST |
No | 0.0.0.0 |
Bind address |
STORAGE_ADAPTER |
No | sqlite |
Storage backend |
LOG_LEVEL |
No | info |
Log level |
MONITOR_INTERVAL_MS |
No | 60000 |
Device polling interval (ms) |
MONITOR_CONCURRENCY |
No | 10 |
Parallel device checks |
TLS_ENABLED |
No | false |
Enable HTTPS server |
HTTPS_PORT |
No | 3443 |
HTTPS server port |
TLS_CERT_PATH |
No | auto-generated | Path to custom TLS certificate (PEM) |
TLS_KEY_PATH |
No | auto-generated | Path to custom TLS private key (PEM) |
SYSLOG_ENABLED |
No | true |
Enable the built-in UDP syslog receiver |
SYSLOG_PORT |
No | 5514 |
UDP port for the syslog listener |
SYSLOG_TCP_ENABLED |
No | true |
Enable the built-in TCP syslog receiver (parallel to UDP) |
SYSLOG_TCP_PORT |
No | 5514 |
TCP port for the syslog listener (defaults to SYSLOG_PORT) |
SYSLOG_RETENTION_DAYS |
No | 7 |
Drop log rows older than this many days |
SYSLOG_MAX_ROWS_PER_DEVICE |
No | 10000 |
Global per-device row cap (overridable per device in the UI) |
CVE_ENABLED |
No | true |
Enable RouterOS CVE alerting (daily NVD feed match) |
NVD_API_KEY |
No | (none) | Optional NVD API key — raises the fetch rate limit (not required) |
CVE_CHECK_INTERVAL_MS |
No | 86400000 |
How often to refresh the NVD feed (default 24h) |
IDS_ENABLED |
No | false |
Enable Auto-block / Simple IDS (block source IPs of repeated failed logins; per-device opt-in, tune the rest in Settings) |
EXPORT_RSC_ENABLED |
No | false |
Mirror the latest .rsc export to /data/exports/<site>/<device>.rsc after each backup (v1.30.0+). Mount /data/exports to a host volume for DR / git / rclone. |
ACTIVITY_LOG_RETENTION_DAYS |
No | 90 |
Drop audit log rows older than this many days |
JWT_ACCESS_EXPIRY |
No | 15m |
Access token lifetime (jsonwebtoken duration: 15m, 1h, 30s) |
JWT_REFRESH_EXPIRY |
No | 7d |
Refresh token lifetime |
WEBAUTHN_RP_ID |
No | (none) | Registrable domain for passkey sign-in (e.g. mikr.example.com). Must be a real domain, not an IP. |
WEBAUTHN_RP_NAME |
No | MikroTik Manager |
Display name shown by the authenticator during passkey registration |
WEBAUTHN_ORIGINS |
No | (derived from WEBAUTHN_RP_ID) |
Comma-separated list of allowed origins for passkey ceremonies |
DEFAULT_SSH_PORT |
No | 22 |
Pre-filled SSH port for new devices (per-device override always wins) |
DEFAULT_API_PORT |
No | 443 |
Pre-filled REST API port for new devices |
Important: If you rely on auto-generated secrets, back up data/.secrets.json alongside the SQLite database. Losing it invalidates all sessions and makes stored device passwords unrecoverable. ENCRYPTION_KEY must be exactly 64 hex characters — if changed after devices are added, existing encrypted passwords won't decrypt.
| Capability | SSH | REST API | SNMP |
|---|---|---|---|
| Status monitoring | ✓ | ✓ | ✓ |
| CLI commands | ✓ | ✓ | — |
| RouterOS upgrades | ✓ | ✓ | — |
| Config backup | ✓ | ✓ | — |
| Interface details | ✓ | ✓ | ✓ |
| Self-signed TLS | N/A | ✓ | N/A |
| Session overhead | 1 SSH conn | HTTPS per request | UDP per poll |
- SSH — non-interactive exec, best compatibility with all RouterOS versions
- REST API — available on RouterOS 7.1+, supports HTTPS and HTTP
- SNMP — monitoring only (no commands, upgrades, or backups). Useful for devices where SSH/REST isn't available
Devices can use SSH or REST as primary method, with SNMP as an optional supplementary source for faster status checks. The device detail page detects available methods from actual service data and allows quick switching between them — the SSH / REST / SNMP switch stays available in every state, including SNMP-only and offline devices, so you can always change a device's method back (v1.48.2+).
All features are available on every tier — the only difference is the device limit.
| Community | License 30 | License 50 | License Unlimited | |
|---|---|---|---|---|
| Devices | up to 10 | up to 30 | up to 50 | Unlimited |
| Price | €0 | €99 (one-time) | €149 (one-time) | €399 (one-time) |
| Updates | — | €39/year (optional) | €49/year (optional) | €99/year (optional) |
| All features | ✓ | ✓ | ✓ | ✓ |
- Perpetual license — the software works forever on the purchased version
- Update subscription — grants access to new versions (optional, not required)
- Self-hosted — your data stays on your server, offline license activation
- No account required, no telemetry, no usage tracking — none of your fleet/device/config data ever leaves the server
- The only outbound traffic is optional read-only public-feed checks (RouterOS CVE feed from NVD, RouterOS release changelogs and latest version from MikroTik, Manager update from GitHub Releases) — GET-only, carry no data, individually disable-able, and degrade gracefully offline
SQLite database stored in ./data/mikr.db. Persists across container updates.
# Backup
cp data/mikr.db mikr-backup.db
# Restore
cp mikr-backup.db data/mikr.db
docker restart mikr-managerdocker compose logs -f # View logs
docker compose restart # Restart
docker compose down # Stop
docker compose up -d # Start / update- Backend: Node.js 22, Express.js, SQLite (better-sqlite3)
- Frontend: Vanilla JS SPA (no framework), CSS3
- Real-time: WebSocket for live status, command output, upgrade progress
- Auth: JWT + bcrypt, role-based (superadmin / admin / operator / viewer) with per-site scoping
- MikroTik: SSH (node-ssh) + REST API (https module) + SNMP (net-snmp)
- Encryption: AES-256-GCM for stored device passwords
- Container: Docker, node:22-alpine (multi-stage build)