Files
MIKROTIK-RMM/docker-compose.yml
2GT-RichandClaude Opus 5 dd71271473 fix(poller): the health panel must not blank the page, and pass the tunables through (v0.23.20-beta)
Two faults reported against v0.23.19, one of which makes the other much worse.

**POLLER_CONCURRENCY was never passed into the container.** It is read from the
environment by the backend and documented in .env.example, but docker-compose
did not forward it, so setting it did nothing at all. The same was true of the
job timeout, interval and both retention limits. Every poller tunable shipped in
v0.23.17 was inert for anyone running the compose stack — including the one knob
the #114 reply told the reporter to reach for. All five are now forwarded.

**The health panel crashed on an unexpected payload**, taking the whole page
with it: `cap is undefined` from reading data.capacity without a guard. It also
rendered "Loading…" indefinitely on a request error, so a failing endpoint and a
slow one looked identical.

Both are the wrong failure mode for this screen in particular. It is the page an
operator opens precisely when monitoring already looks broken, and a blank white
panel at that moment is worse than no panel: it suggests the whole tool is dead
rather than that one query failed.

Every field is now defaulted, so a backend a version behind or one that answered
partially degrades the panel instead of destroying the page, and a failed request
renders what actually went wrong with a retry. Verified against the four shapes
that could produce the reported crash: capacity missing, an error body arriving
with a 2xx, an older backend without the status field, and an empty object.

The endpoint is hardened to match. Device count, timing and the stale-device
listing are each guarded independently, so a partial failure returns partial
numbers rather than a 500 — the same reasoning applied at the other end.

I could not reproduce the original crash: at concurrency 48 the endpoint returns
a complete payload here, there is no route shadowing, and the axios interceptor
rejects errors correctly rather than passing them through as data. The defensive
work stands on its own regardless of which of those shapes he actually hit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-01 13:14:26 -07:00

153 lines
4.7 KiB
YAML

version: '3.8'
services:
nginx:
build:
context: .
dockerfile: nginx/Dockerfile
ports:
- "${HTTP_PORT:-80}:80"
- "${HTTPS_PORT:-443}:443"
volumes:
- certs_data:/certs
depends_on:
backend:
condition: service_healthy
restart: unless-stopped
networks:
- app-network
backend:
build:
context: ./backend
dockerfile: Dockerfile
environment:
NODE_ENV: production
TZ: ${TZ:-UTC}
DATABASE_URL: postgresql://mikrotik:${DB_PASSWORD:-mikrotik_secure_pw}@postgres:5432/mikrotik_manager
REDIS_URL: redis://redis:6379
INFLUXDB_URL: http://influxdb:8086
INFLUXDB_TOKEN: ${INFLUXDB_TOKEN:-mytoken123456789}
INFLUXDB_ORG: ${INFLUXDB_ORG:-mikrotik-manager}
INFLUXDB_BUCKET: ${INFLUXDB_BUCKET:-metrics}
# Leave unset to auto-generate & persist strong secrets on first boot
# (stored in the app_data volume). Set them to pin your own values.
JWT_SECRET: ${JWT_SECRET:-}
ENCRYPTION_KEY: ${ENCRYPTION_KEY:-}
CORS_ORIGIN: ${CORS_ORIGIN:-https://localhost,http://localhost,https://127.0.0.1,http://127.0.0.1}
PORT: 3001
NETFLOW_BIND_PORT: 2055
# Poller tuning. These were documented in .env.example but never passed
# through, so setting them had no effect on the container at all.
POLLER_CONCURRENCY: ${POLLER_CONCURRENCY:-12}
POLLER_JOB_TIMEOUT_MS: ${POLLER_JOB_TIMEOUT_MS:-45000}
POLLER_INTERVAL_MS: ${POLLER_INTERVAL_MS:-30000}
POLLER_JOB_RETENTION_SEC: ${POLLER_JOB_RETENTION_SEC:-3600}
POLLER_JOB_RETENTION_COUNT: ${POLLER_JOB_RETENTION_COUNT:-5000}
ports:
# NetFlow/IPFIX collector — UDP goes straight to the backend (nginx not involved).
# Host port must match the netflow_collector_port app setting (default 2055).
- "${NETFLOW_PORT:-2055}:2055/udp"
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
influxdb:
condition: service_healthy
volumes:
- backups_data:/app/backups
- certs_data:/certs
- app_data:/app/data
cap_add:
- NET_RAW
restart: unless-stopped
networks:
- app-network
healthcheck:
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:3001/api/health"]
interval: 15s
timeout: 10s
retries: 5
start_period: 30s
postgres:
image: postgres:15-alpine
environment:
TZ: ${TZ:-UTC}
POSTGRES_DB: mikrotik_manager
POSTGRES_USER: mikrotik
POSTGRES_PASSWORD: ${DB_PASSWORD:-mikrotik_secure_pw}
volumes:
- postgres_data:/var/lib/postgresql/data
restart: unless-stopped
networks:
- app-network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U mikrotik -d mikrotik_manager"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
redis:
image: redis:7-alpine
# A memory ceiling so a runaway producer can never take the host down, with
# eviction disabled.
#
# volatile-lru looks tempting — it would evict only keys with an expiry, and
# BullMQ's job hashes have none. But its *control* keys do: job locks and
# per-queue stalled-check markers both carry TTLs, and losing one causes
# duplicate processing or a stalled-job storm. BullMQ warns about this on
# connect, and it is right. noeviction means Redis refuses writes when full
# rather than quietly corrupting queue state: the host stays up, and the
# failure is loud. Retention limits and the stale-pending sweep are what keep
# us away from the ceiling in the first place.
command: >
redis-server --appendonly yes
--maxmemory ${REDIS_MAXMEMORY:-512mb}
--maxmemory-policy noeviction
volumes:
- redis_data:/data
restart: unless-stopped
networks:
- app-network
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
influxdb:
image: influxdb:2.7
environment:
DOCKER_INFLUXDB_INIT_MODE: setup
DOCKER_INFLUXDB_INIT_USERNAME: admin
DOCKER_INFLUXDB_INIT_PASSWORD: ${INFLUXDB_ADMIN_PASSWORD:-admin_password_123}
DOCKER_INFLUXDB_INIT_ORG: ${INFLUXDB_ORG:-mikrotik-manager}
DOCKER_INFLUXDB_INIT_BUCKET: ${INFLUXDB_BUCKET:-metrics}
DOCKER_INFLUXDB_INIT_ADMIN_TOKEN: ${INFLUXDB_TOKEN:-mytoken123456789}
volumes:
- influxdb_data:/var/lib/influxdb2
restart: unless-stopped
networks:
- app-network
healthcheck:
test: ["CMD", "influx", "ping"]
interval: 15s
timeout: 10s
retries: 10
start_period: 30s
volumes:
postgres_data:
redis_data:
influxdb_data:
backups_data:
certs_data:
app_data:
networks:
app-network:
driver: bridge