Harden runtime and monitoring configuration

This commit is contained in:
gadgethd
2026-07-28 23:17:39 +01:00
parent 24d49198c6
commit 8404a72fbc
7 changed files with 48 additions and 5 deletions
+6
View File
@@ -32,8 +32,14 @@ MQTT_MAX_PAYLOAD_BYTES=65536
MQTT_INGEST_CONCURRENCY=8
MQTT_INGEST_QUEUE_MAX=1000
# Archive and remove repeater visibility after its own observer MQTT feed has
# been silent for one month. Credentials and packet history are preserved.
STALE_MQTT_OBSERVER_CLEANUP_DAYS=30
STALE_MQTT_OBSERVER_CLEANUP_INTERVAL_MS=21600000
# Bound expensive statistics scans so they cannot exhaust the OLTP pool.
STATS_DB_QUERY_CONCURRENCY=2
WARMUP_NETWORKS=ukmesh
# ── Redis ─────────────────────────────────────────────────────────────────────
# Docker Compose protects the internal Redis service with this password. The
+1
View File
@@ -31,6 +31,7 @@ COPY --from=backend-builder /build/backend/dist ./dist
COPY --from=backend-builder /build/backend/src/db/schema ./dist/db/schema
COPY --from=backend-builder /build/backend/src/db/migrations ./dist/db/migrations
COPY --from=backend-builder /build/backend/src/db/owner-auth.sql ./dist/db/owner-auth.sql
COPY --from=backend-builder /build/backend/src/backend-site/template.html ./dist/backend-site/template.html
# Copy frontend build into static dir served by backend
COPY --from=frontend-builder /build/frontend/dist ./public
+2 -2
View File
@@ -2,14 +2,14 @@
FROM node:20-alpine AS backend-builder
WORKDIR /build/backend
COPY backend/package.json backend/package-lock.json ./
RUN npm ci
RUN npm ci --audit
COPY backend/ ./
RUN npm run build
FROM node:20-alpine AS runtime
WORKDIR /app
COPY backend/package.json backend/package-lock.json ./
RUN npm ci --omit=dev && npm cache clean --force
RUN npm ci --omit=dev --audit && npm cache clean --force
COPY --from=backend-builder /build/backend/dist ./dist
COPY --from=backend-builder /build/backend/src/db/schema ./dist/db/schema
COPY --from=backend-builder /build/backend/src/db/migrations ./dist/db/migrations
+27 -1
View File
@@ -1,3 +1,5 @@
name: ${COMPOSE_PROJECT_NAME:-meshcore-analytics}
x-anubis-base: &anubis-base
image: ghcr.io/techarohq/anubis:v1.25.0
restart: always
@@ -203,6 +205,9 @@ services:
volumes:
- mosquitto_log:/mosquitto/log:ro
- ./mosquitto:/mosquitto/config
# The backend currently needs Docker API access for the owner ACL
# reconciler. Production deployments should prefer docker-socket-proxy
# with only the required endpoints; use :ro when mutation is not needed.
- /var/run/docker.sock:/var/run/docker.sock
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:3000/healthz >/dev/null"]
@@ -298,6 +303,8 @@ services:
JWT_SECRET: ${JWT_SECRET:?JWT_SECRET required}
DATABASE_STATEMENT_TIMEOUT_MS: 300000
DATABASE_SKIP_SCHEMA_INIT: "true"
STALE_MQTT_OBSERVER_CLEANUP_DAYS: ${STALE_MQTT_OBSERVER_CLEANUP_DAYS:-30}
STALE_MQTT_OBSERVER_CLEANUP_INTERVAL_MS: ${STALE_MQTT_OBSERVER_CLEANUP_INTERVAL_MS:-21600000}
NODE_ENV: production
depends_on:
timescaledb:
@@ -338,7 +345,7 @@ services:
build:
context: .
dockerfile: Dockerfile.backend
restart: "no"
restart: on-failure:3
logging: *json-log-limits
mem_limit: "512m"
memswap_limit: "512m"
@@ -754,6 +761,23 @@ services:
depends_on:
- loki
prometheus:
<<: *internal-hosts
image: prom/prometheus:v3.5.0
restart: always
logging: *json-log-limits
mem_limit: "256m"
memswap_limit: "256m"
command: ["--config.file=/etc/prometheus/prometheus.yml", "--storage.tsdb.retention.time=15d"]
volumes:
- ./logging/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- prometheus_data:/prometheus
ports:
- "127.0.0.1:9090:9090"
depends_on:
backend:
condition: service_healthy
grafana:
<<: *internal-hosts
image: grafana/grafana:11.5.0
@@ -773,6 +797,7 @@ services:
- "127.0.0.1:3010:3000"
depends_on:
- loki
- prometheus
networks:
default:
@@ -792,4 +817,5 @@ volumes:
srtm_data:
loki_data:
grafana_data:
prometheus_data:
mesh_health_check_data:
+5
View File
@@ -9,6 +9,11 @@ map "$mesh_trusted_edge:$http_cf_connecting_ip" $mesh_client_ip {
~^1:([0-9A-Fa-f:.]+)$ $1;
}
map $uri $mesh_permissions_policy {
default "geolocation=(), camera=(), microphone=()";
~^/(feed|repeater)?/?$ "geolocation=(self), camera=(), microphone=()";
}
server {
listen 80;
root /usr/share/nginx/html;
+2 -2
View File
@@ -2,7 +2,7 @@ add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; prelo
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), camera=(), microphone=()" always;
add_header Permissions-Policy "$mesh_permissions_policy" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;
add_header Cross-Origin-Resource-Policy "same-origin" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https:; connect-src 'self' wss: https:; font-src 'self' data:; worker-src 'self' blob:; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; form-action 'self'" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https://basemaps.cartocdn.com https://*.basemaps.cartocdn.com https://openstreetmap.org https://*.openstreetmap.org; connect-src 'self' wss: https:; font-src 'self' data:; worker-src 'self' blob:; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; form-action 'self'" always;
+5
View File
@@ -9,6 +9,11 @@ map "$mesh_trusted_edge:$http_cf_connecting_ip" $mesh_client_ip {
~^1:([0-9A-Fa-f:.]+)$ $1;
}
map $uri $mesh_permissions_policy {
default "geolocation=(), camera=(), microphone=()";
~^/(feed|repeater)?/?$ "geolocation=(self), camera=(), microphone=()";
}
server {
listen 80;
root /usr/share/nginx/html;