mirror of
https://github.com/gadgethd/ukmesh.git
synced 2026-09-01 17:08:17 +00:00
1047 lines
39 KiB
YAML
1047 lines
39 KiB
YAML
name: ${COMPOSE_PROJECT_NAME:-meshcore-analytics}
|
|
|
|
x-anubis-base: &anubis-base
|
|
image: ghcr.io/techarohq/anubis@sha256:5cf4c0d6c7ea01a356c6685a779971bdd28d6a5a4a6719201f73a9c4ae108db0
|
|
restart: always
|
|
user: "1000:1000"
|
|
read_only: true
|
|
cap_drop:
|
|
- ALL
|
|
security_opt:
|
|
- no-new-privileges:true
|
|
tmpfs:
|
|
- /tmp:rw,noexec,nosuid,size=16m,mode=1777
|
|
logging: &json-log-limits
|
|
driver: journald
|
|
options:
|
|
tag: "{{.Name}}"
|
|
labels: "com.docker.compose.service,com.docker.compose.project"
|
|
volumes:
|
|
- ./anubis/botPolicy.yaml:/data/botPolicy.yaml:ro
|
|
|
|
x-internal-hosts: &internal-hosts
|
|
{} # Docker's internal DNS handles service name resolution
|
|
|
|
x-application-hardening: &application-hardening
|
|
read_only: true
|
|
cap_drop:
|
|
- ALL
|
|
security_opt:
|
|
- no-new-privileges:true
|
|
tmpfs:
|
|
- /tmp:rw,noexec,nosuid,size=64m,mode=1777
|
|
|
|
services:
|
|
# Apply additive, versioned migrations once before the application starts.
|
|
# Base schema creation remains in the database init hook for fresh volumes;
|
|
# this service keeps existing volumes current while backend startup stays fast.
|
|
db-migrate:
|
|
<<: [*internal-hosts, *application-hardening]
|
|
image: ${BACKEND_IMAGE:-meshcore-analytics-backend:local}
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile.backend
|
|
args:
|
|
SOURCE_REVISION: ${SOURCE_REVISION:-local}
|
|
restart: "no"
|
|
logging: *json-log-limits
|
|
mem_limit: "256m"
|
|
memswap_limit: "256m"
|
|
command: ["node", "dist/tools/migrate.js"]
|
|
environment:
|
|
DATABASE_URL: postgresql://${POSTGRES_USER:-meshcore}:${POSTGRES_PASSWORD}@timescaledb:5432/${POSTGRES_DB:-meshcore}
|
|
DATABASE_STATEMENT_TIMEOUT_MS: 0
|
|
NODE_ENV: production
|
|
depends_on:
|
|
timescaledb:
|
|
condition: service_healthy
|
|
backend:
|
|
<<: *internal-hosts
|
|
image: ${BACKEND_IMAGE:-meshcore-analytics-backend:local}
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile.backend
|
|
args:
|
|
SOURCE_REVISION: ${SOURCE_REVISION:-local}
|
|
restart: always
|
|
logging: *json-log-limits
|
|
mem_limit: "1g"
|
|
memswap_limit: "1g"
|
|
environment:
|
|
DATABASE_URL: postgresql://${POSTGRES_USER:-meshcore}:${POSTGRES_PASSWORD}@timescaledb:5432/${POSTGRES_DB:-meshcore}
|
|
DATABASE_APPLICATION_NAME: meshcore-backend
|
|
OWNER_DATABASE_APPLICATION_NAME: meshcore-backend-owner-auth
|
|
OWNER_DATABASE_ADMIN_APPLICATION_NAME: meshcore-backend-owner-auth-admin
|
|
MQTT_BROKER_URL: ${MQTT_BROKER_URL:-ws://mosquitto:9001}
|
|
MQTT_USERNAME: ${MQTT_USERNAME:-backend}
|
|
MQTT_PASSWORD: ${MQTT_PASSWORD}
|
|
MQTT_CLIENT_ID: ${MQTT_CLIENT_ID:-meshcore-analytics-ingest}
|
|
REDIS_URL: ${REDIS_URL:-redis://redis:6379}
|
|
REDIS_PASSWORD: ${REDIS_PASSWORD:?REDIS_PASSWORD required}
|
|
JWT_SECRET: ${JWT_SECRET:?JWT_SECRET required}
|
|
PORT: ${PORT:-3000}
|
|
METRICS_PORT: ${METRICS_PORT:-9091}
|
|
MESHCORE_CHANNEL_SECRETS: ${MESHCORE_CHANNEL_SECRETS:-}
|
|
OWNER_DATABASE_URL: ${OWNER_DATABASE_URL:-postgresql://${POSTGRES_USER:-meshcore}:${POSTGRES_PASSWORD}@timescaledb:5432/${OWNER_POSTGRES_DB:-meshcore_owner_auth}}
|
|
OWNER_MQTT_USERNAME_MAP: ${OWNER_MQTT_USERNAME_MAP:-}
|
|
OWNER_AUTHORIZATION_MODE: ${OWNER_AUTHORIZATION_MODE:-shadow}
|
|
OWNER_ACL_MODE: ${OWNER_ACL_MODE:-shadow}
|
|
OWNER_ACL_UNMANAGED_USERS: ${OWNER_ACL_UNMANAGED_USERS:-backend,test,test2}
|
|
OWNER_ACL_ALLOW_EMPTY_USERS: ${OWNER_ACL_ALLOW_EMPTY_USERS:-}
|
|
OWNER_ACL_SHADOW_PATH: /tmp/meshcore-owner-acl.shadow
|
|
MOSQUITTO_ACL_PATH: /mosquitto/config/acl
|
|
OWNER_ACL_RELOAD_URL: http://mosquitto-reloader:8080/reload
|
|
OWNER_ACL_RELOAD_TOKEN: ${JWT_SECRET:?JWT_SECRET required}
|
|
OWNER_COOKIE_SECRET: ${OWNER_COOKIE_SECRET:-}
|
|
OPERATOR_SITE_TOKEN: ${OPERATOR_SITE_TOKEN:?OPERATOR_SITE_TOKEN required}
|
|
TRUSTED_PROXY_PEERS: ${TRUSTED_PROXY_PEERS:-172.30.0.10,172.30.0.11,172.30.0.12}
|
|
ALLOWED_ORIGINS: ${ALLOWED_ORIGINS:-http://localhost:3003,http://localhost:3004,https://ukmesh.com,https://www.ukmesh.com,https://app.ukmesh.com,https://test.ukmesh.com}
|
|
API_RATE_LIMIT_MAX: ${API_RATE_LIMIT_MAX:-120}
|
|
RADIO_BOT_URL: ${RADIO_BOT_URL:-}
|
|
PUBLIC_FEATURE_INFERRED_NODES_ENABLED: ${PUBLIC_FEATURE_INFERRED_NODES_ENABLED:-true}
|
|
PUBLIC_FEATURE_PACKET_ARCS_ENABLED: ${PUBLIC_FEATURE_PACKET_ARCS_ENABLED:-true}
|
|
PUBLIC_FEATURE_HEATMAP_ENABLED: ${PUBLIC_FEATURE_HEATMAP_ENABLED:-false}
|
|
PUBLIC_FEATURE_CONFIG_TTL_SECONDS: ${PUBLIC_FEATURE_CONFIG_TTL_SECONDS:-30}
|
|
# 15 min: the ukmesh chart snapshot regen runs multibyte analytics that
|
|
# exceed 5 min on the 2vCPU VPS (was 300000; raised 2026-08-08)
|
|
DATABASE_STATEMENT_TIMEOUT_MS: 900000
|
|
DATABASE_POOL_MAX: ${DATABASE_POOL_MAX:-16}
|
|
STATS_DB_QUERY_CONCURRENCY: ${STATS_DB_QUERY_CONCURRENCY:-2}
|
|
STATS_AGGREGATE_READS_ENABLED: ${STATS_AGGREGATE_READS_ENABLED:-false}
|
|
STATS_AGGREGATE_SHADOW_ENABLED: ${STATS_AGGREGATE_SHADOW_ENABLED:-false}
|
|
WARMUP_NETWORKS: ${WARMUP_NETWORKS:-ukmesh}
|
|
DATABASE_SKIP_SCHEMA_INIT: "true"
|
|
WS_INITIAL_STATE_ENABLED: "1"
|
|
WS_INITIAL_STATE_DB_TIMEOUT_MS: ${WS_INITIAL_STATE_DB_TIMEOUT_MS:-30000}
|
|
WS_INITIAL_STATE_MAX_BYTES: ${WS_INITIAL_STATE_MAX_BYTES:-16777216}
|
|
MQTT_MAX_PAYLOAD_BYTES: ${MQTT_MAX_PAYLOAD_BYTES:-65536}
|
|
MQTT_INGEST_CONCURRENCY: ${MQTT_INGEST_CONCURRENCY:-8}
|
|
# The 30s lifecycle deadline reserves 25s for MQTT disconnect and queue drain.
|
|
MQTT_INGEST_QUEUE_MAX: ${MQTT_INGEST_QUEUE_MAX:-20000}
|
|
PATH_RESOLVE_WORKERS: ${PATH_RESOLVE_WORKERS:-2}
|
|
PATH_RESOLVE_BACKGROUND_QUEUE_MAX: ${PATH_RESOLVE_BACKGROUND_QUEUE_MAX:-128}
|
|
PATH_RESOLVE_INTERACTIVE_QUEUE_MAX: ${PATH_RESOLVE_INTERACTIVE_QUEUE_MAX:-32}
|
|
PATH_RESOLVE_END_TO_END_TIMEOUT_MS: ${PATH_RESOLVE_END_TO_END_TIMEOUT_MS:-15000}
|
|
PATH_RESOLVE_UNIQUE_INFLIGHT_MAX: ${PATH_RESOLVE_UNIQUE_INFLIGHT_MAX:-34}
|
|
PATH_RESOLVE_OBSERVER_VARIANTS_PER_PACKET_MAX: ${PATH_RESOLVE_OBSERVER_VARIANTS_PER_PACKET_MAX:-4}
|
|
PATH_SINGLE_HISTORY_HOURS: ${PATH_SINGLE_HISTORY_HOURS:-168}
|
|
PATH_SINGLE_MAX_OBSERVERS: ${PATH_SINGLE_MAX_OBSERVERS:-128}
|
|
PATH_MULTI_HISTORY_WINDOW_HOURS: ${PATH_MULTI_HISTORY_WINDOW_HOURS:-168}
|
|
PATH_MULTI_MAX_SCAN_ROWS: ${PATH_MULTI_MAX_SCAN_ROWS:-2048}
|
|
PATH_MULTI_MAX_OBSERVERS: ${PATH_MULTI_MAX_OBSERVERS:-128}
|
|
LAZY_PATH_HISTORY_WINDOW_HOURS: ${LAZY_PATH_HISTORY_WINDOW_HOURS:-168}
|
|
LAZY_PATH_MAX_OBSERVATIONS: ${LAZY_PATH_MAX_OBSERVATIONS:-2048}
|
|
LAZY_PATH_MAX_OBSERVERS: ${LAZY_PATH_MAX_OBSERVERS:-128}
|
|
LAZY_PATH_MAX_UNIQUE_HASHES: ${LAZY_PATH_MAX_UNIQUE_HASHES:-64}
|
|
LAZY_PATH_MAX_CANDIDATE_NODES: ${LAZY_PATH_MAX_CANDIDATE_NODES:-2048}
|
|
SPAM_MESSAGE_MAX_CANDIDATE_ROWS: ${SPAM_MESSAGE_MAX_CANDIDATE_ROWS:-10000}
|
|
SPAM_MESSAGE_MAX_MESSAGES: ${SPAM_MESSAGE_MAX_MESSAGES:-2000}
|
|
SPAM_MESSAGE_MAX_OBSERVERS: ${SPAM_MESSAGE_MAX_OBSERVERS:-32}
|
|
SPAM_MESSAGE_MAX_NORMALIZED_CHARS: ${SPAM_MESSAGE_MAX_NORMALIZED_CHARS:-512}
|
|
SPAM_MESSAGE_MAX_INCIDENTS: ${SPAM_MESSAGE_MAX_INCIDENTS:-100}
|
|
SPAM_MESSAGE_MAX_CANDIDATE_CLUSTERS: ${SPAM_MESSAGE_MAX_CANDIDATE_CLUSTERS:-64}
|
|
SPAM_MESSAGE_MAX_EVIDENCE_MEMBERS: ${SPAM_MESSAGE_MAX_EVIDENCE_MEMBERS:-256}
|
|
SPAM_MESSAGE_ANALYSIS_BUDGET_MS: ${SPAM_MESSAGE_ANALYSIS_BUDGET_MS:-5000}
|
|
SPAM_MESSAGE_DB_TIMEOUT_MS: ${SPAM_MESSAGE_DB_TIMEOUT_MS:-15000}
|
|
STATS_UNIQUE_INFLIGHT_MAX: ${STATS_UNIQUE_INFLIGHT_MAX:-32}
|
|
LINK_QUEUE_V3_PRODUCER_ENABLED: ${LINK_QUEUE_V3_PRODUCER_ENABLED:-1}
|
|
LINK_QUEUE_V3_MAX_JOBS: ${LINK_QUEUE_V3_MAX_JOBS:-5000}
|
|
LINK_QUEUE_V3_MAX_BYTES: ${LINK_QUEUE_V3_MAX_BYTES:-67108864}
|
|
LINK_QUEUE_V3_MAX_PAYLOAD_BYTES: ${LINK_QUEUE_V3_MAX_PAYLOAD_BYTES:-32768}
|
|
LINK_QUEUE_V3_DEAD_MAX_JOBS: ${LINK_QUEUE_V3_DEAD_MAX_JOBS:-1000}
|
|
LINK_QUEUE_V3_DEAD_MAX_BYTES: ${LINK_QUEUE_V3_DEAD_MAX_BYTES:-33554432}
|
|
LINK_QUEUE_V3_DEAD_RETENTION_MS: ${LINK_QUEUE_V3_DEAD_RETENTION_MS:-2592000000}
|
|
MQTT_AUDIT_MAX_OBSERVATIONS_PER_SCAN: ${MQTT_AUDIT_MAX_OBSERVATIONS_PER_SCAN:-4096}
|
|
MQTT_AUDIT_MAX_NODES_PER_USER: ${MQTT_AUDIT_MAX_NODES_PER_USER:-256}
|
|
MQTT_AUDIT_RETENTION_DAYS: ${MQTT_AUDIT_RETENTION_DAYS:-30}
|
|
LINK_REBUILD_LEASE_MS: ${LINK_REBUILD_LEASE_MS:-120000}
|
|
WS_MAX_PAYLOAD_BYTES: ${WS_MAX_PAYLOAD_BYTES:-65536}
|
|
WS_MAX_QUEUE_BYTES: ${WS_MAX_QUEUE_BYTES:-8388608}
|
|
WS_MAX_BUFFERED_BYTES: ${WS_MAX_BUFFERED_BYTES:-33554432}
|
|
MAINTENANCE_ACTIVE: ${MAINTENANCE_ACTIVE:-0}
|
|
MAINTENANCE_MESSAGE: ${MAINTENANCE_MESSAGE:-}
|
|
HEALTH_VOLUME_PATHS: database=/health-volumes/database=${HEALTH_DATABASE_BUDGET_BYTES:-322122547200},redis=/health-volumes/redis=${HEALTH_REDIS_BUDGET_BYTES:-4294967296},terrain=/health-volumes/terrain=${HEALTH_TERRAIN_BUDGET_BYTES:-53687091200},logs=/health-volumes/logs=${HEALTH_LOG_BUDGET_BYTES:-21474836480}
|
|
DATA_LIFECYCLE_RETENTION_ENABLED: ${DATA_LIFECYCLE_RETENTION_ENABLED:-false}
|
|
DATA_LIFECYCLE_COMPRESSION_ENABLED: ${DATA_LIFECYCLE_COMPRESSION_ENABLED:-false}
|
|
DATA_LIFECYCLE_RETENTION_TARGETS: ${DATA_LIFECYCLE_RETENTION_TARGETS:-}
|
|
DATA_LIFECYCLE_RESTORE_RECEIPT_PATH: /backup-receipts/latest.json
|
|
DATA_LIFECYCLE_RESTORE_RECEIPT_SIGNATURE_PATH: /backup-receipts/latest.json.sig
|
|
DATA_LIFECYCLE_RECEIPT_VERIFY_KEY_PATH: /backup-receipts/verify.pem
|
|
DATA_LIFECYCLE_RECEIPT_MAX_AGE_DAYS: ${DATA_LIFECYCLE_RECEIPT_MAX_AGE_DAYS:-7}
|
|
SYNTHETIC_SUCCESS_TTL_MS: ${SYNTHETIC_SUCCESS_TTL_MS:-960000}
|
|
NODE_ENV: production
|
|
depends_on:
|
|
db-migrate:
|
|
condition: service_completed_successfully
|
|
ports:
|
|
- "127.0.0.1:3000:3000"
|
|
volumes:
|
|
- mosquitto_log:/mosquitto/log:ro
|
|
- ./mosquitto:/mosquitto/config
|
|
- timescaledb_data:/health-volumes/database:ro
|
|
- redis_data:/health-volumes/redis:ro
|
|
- srtm_data:/health-volumes/terrain:ro
|
|
- loki_data:/health-volumes/logs:ro
|
|
- ${BACKUP_RECEIPT_HOST_DIR:-./backup-receipts}:/backup-receipts:ro
|
|
group_add:
|
|
- "1883"
|
|
read_only: true
|
|
tmpfs:
|
|
- /tmp:rw,noexec,nosuid,size=64m
|
|
cap_drop:
|
|
- ALL
|
|
security_opt:
|
|
- no-new-privileges:true
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:3000/healthz >/dev/null"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 6
|
|
start_period: 30s
|
|
|
|
path-learning-worker:
|
|
<<: [*internal-hosts, *application-hardening]
|
|
image: ${BACKEND_IMAGE:-meshcore-analytics-backend:local}
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile.backend
|
|
args:
|
|
SOURCE_REVISION: ${SOURCE_REVISION:-local}
|
|
restart: always
|
|
logging: *json-log-limits
|
|
mem_limit: "512m"
|
|
memswap_limit: "512m"
|
|
command: ["node", "dist/workers/path-learning.js"]
|
|
environment:
|
|
DATABASE_URL: postgresql://${POSTGRES_USER:-meshcore}:${POSTGRES_PASSWORD}@timescaledb:5432/${POSTGRES_DB:-meshcore}
|
|
DATABASE_APPLICATION_NAME: meshcore-path-learning
|
|
REDIS_URL: ${REDIS_URL:-redis://redis:6379}
|
|
REDIS_PASSWORD: ${REDIS_PASSWORD:?REDIS_PASSWORD required}
|
|
JWT_SECRET: ${JWT_SECRET:?JWT_SECRET required}
|
|
DATABASE_STATEMENT_TIMEOUT_MS: 300000
|
|
DATABASE_SKIP_SCHEMA_INIT: "true"
|
|
PATH_LEARNING_MAX_TRAINING_PACKETS: ${PATH_LEARNING_MAX_TRAINING_PACKETS:-120000}
|
|
PATH_LEARNING_MAX_NODES: ${PATH_LEARNING_MAX_NODES:-50000}
|
|
PATH_LEARNING_MAX_LINKS: ${PATH_LEARNING_MAX_LINKS:-200000}
|
|
PATH_LEARNING_MAX_AGGREGATE_KEYS: ${PATH_LEARNING_MAX_AGGREGATE_KEYS:-1000000}
|
|
PATH_LEARNING_RUN_DEADLINE_MS: ${PATH_LEARNING_RUN_DEADLINE_MS:-2700000}
|
|
PATH_LEARNING_INTERVAL_MS: ${PATH_LEARNING_INTERVAL_MS:-3600000}
|
|
NODE_ENV: production
|
|
depends_on:
|
|
db-migrate:
|
|
condition: service_completed_successfully
|
|
|
|
health-worker:
|
|
<<: [*internal-hosts, *application-hardening]
|
|
image: ${BACKEND_IMAGE:-meshcore-analytics-backend:local}
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile.backend
|
|
args:
|
|
SOURCE_REVISION: ${SOURCE_REVISION:-local}
|
|
restart: always
|
|
logging: *json-log-limits
|
|
mem_limit: "512m"
|
|
memswap_limit: "512m"
|
|
command: ["node", "dist/workers/health.js"]
|
|
environment:
|
|
DATABASE_URL: postgresql://${POSTGRES_USER:-meshcore}:${POSTGRES_PASSWORD}@timescaledb:5432/${POSTGRES_DB:-meshcore}
|
|
DATABASE_APPLICATION_NAME: meshcore-health-worker
|
|
REDIS_URL: ${REDIS_URL:-redis://redis:6379}
|
|
REDIS_PASSWORD: ${REDIS_PASSWORD:?REDIS_PASSWORD required}
|
|
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}
|
|
HEALTH_VOLUME_PATHS: database=/health-volumes/database=${HEALTH_DATABASE_BUDGET_BYTES:-322122547200},redis=/health-volumes/redis=${HEALTH_REDIS_BUDGET_BYTES:-4294967296},terrain=/health-volumes/terrain=${HEALTH_TERRAIN_BUDGET_BYTES:-53687091200},logs=/health-volumes/logs=${HEALTH_LOG_BUDGET_BYTES:-21474836480}
|
|
DATA_LIFECYCLE_RETENTION_ENABLED: "true"
|
|
DATA_LIFECYCLE_RETENTION_TARGETS: ${DATA_LIFECYCLE_RETENTION_TARGETS:-worker_health_snapshots,operational_check_results}
|
|
NODE_ENV: production
|
|
depends_on:
|
|
db-migrate:
|
|
condition: service_completed_successfully
|
|
volumes:
|
|
- timescaledb_data:/health-volumes/database:ro
|
|
- redis_data:/health-volumes/redis:ro
|
|
- srtm_data:/health-volumes/terrain:ro
|
|
- loki_data:/health-volumes/logs:ro
|
|
|
|
synthetic-monitor:
|
|
<<: [*internal-hosts, *application-hardening]
|
|
image: ${BACKEND_IMAGE:-meshcore-analytics-backend:local}
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile.backend
|
|
args:
|
|
SOURCE_REVISION: ${SOURCE_REVISION:-local}
|
|
restart: always
|
|
logging: *json-log-limits
|
|
mem_limit: "256m"
|
|
memswap_limit: "256m"
|
|
command: ["node", "dist/workers/synthetic-monitor.js"]
|
|
environment:
|
|
DATABASE_URL: postgresql://${POSTGRES_USER:-meshcore}:${POSTGRES_PASSWORD}@timescaledb:5432/${POSTGRES_DB:-meshcore}
|
|
DATABASE_APPLICATION_NAME: meshcore-synthetic-monitor
|
|
DATABASE_SKIP_SCHEMA_INIT: "true"
|
|
SYNTHETIC_BASE_URL: http://backend:3000
|
|
SYNTHETIC_WS_URL: ws://backend:3000
|
|
SYNTHETIC_INTERVAL_MS: ${SYNTHETIC_INTERVAL_MS:-60000}
|
|
SYNTHETIC_FULL_INTERVAL_MS: ${SYNTHETIC_FULL_INTERVAL_MS:-720000}
|
|
SYNTHETIC_TIMEOUT_MS: ${SYNTHETIC_TIMEOUT_MS:-10000}
|
|
SYNTHETIC_INITIAL_STATE_TIMEOUT_MS: ${SYNTHETIC_INITIAL_STATE_TIMEOUT_MS:-35000}
|
|
SYNTHETIC_FAILURE_THRESHOLD: ${SYNTHETIC_FAILURE_THRESHOLD:-3}
|
|
ALERT_WEBHOOK_URL: ${ALERT_WEBHOOK_URL:-http://alert-receiver:8080/alerts}
|
|
NODE_ENV: production
|
|
depends_on:
|
|
db-migrate:
|
|
condition: service_completed_successfully
|
|
backend:
|
|
condition: service_healthy
|
|
alert-receiver:
|
|
condition: service_healthy
|
|
|
|
link-backfill-worker:
|
|
<<: [*internal-hosts, *application-hardening]
|
|
image: ${BACKEND_IMAGE:-meshcore-analytics-backend:local}
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile.backend
|
|
args:
|
|
SOURCE_REVISION: ${SOURCE_REVISION:-local}
|
|
restart: on-failure:3
|
|
logging: *json-log-limits
|
|
mem_limit: "512m"
|
|
memswap_limit: "512m"
|
|
command: ["node", "dist/workers/link-backfill.js"]
|
|
environment:
|
|
DATABASE_URL: postgresql://${POSTGRES_USER:-meshcore}:${POSTGRES_PASSWORD}@timescaledb:5432/${POSTGRES_DB:-meshcore}
|
|
MQTT_BROKER_URL: ${MQTT_BROKER_URL:-ws://mosquitto:9001}
|
|
REDIS_URL: ${REDIS_URL:-redis://redis:6379}
|
|
REDIS_PASSWORD: ${REDIS_PASSWORD:?REDIS_PASSWORD required}
|
|
MESHCORE_CHANNEL_SECRETS: ${MESHCORE_CHANNEL_SECRETS:-}
|
|
DATABASE_SKIP_SCHEMA_INIT: "true"
|
|
NODE_ENV: production
|
|
depends_on:
|
|
db-migrate:
|
|
condition: service_completed_successfully
|
|
backend:
|
|
condition: service_healthy
|
|
link-worker:
|
|
condition: service_healthy
|
|
|
|
link-worker:
|
|
<<: [*internal-hosts, *application-hardening]
|
|
image: ${RF_WORKER_IMAGE:-meshcore-analytics-rf-worker:local}
|
|
build:
|
|
context: ./viewshed-worker
|
|
dockerfile: Dockerfile
|
|
args:
|
|
SOURCE_REVISION: ${SOURCE_REVISION:-local}
|
|
restart: always
|
|
logging: *json-log-limits
|
|
mem_limit: "2g"
|
|
memswap_limit: "2g"
|
|
cpus: 2.0
|
|
environment:
|
|
DATABASE_URL: postgresql://${POSTGRES_USER:-meshcore}:${POSTGRES_PASSWORD}@timescaledb:5432/${POSTGRES_DB:-meshcore}
|
|
DATABASE_APPLICATION_NAME: meshcore-link-worker
|
|
REDIS_URL: ${REDIS_URL:-redis://redis:6379}
|
|
REDIS_PASSWORD: ${REDIS_PASSWORD:?REDIS_PASSWORD required}
|
|
RADIO_BOT_URL: ${LINK_WORKER_RADIO_BOT_URL:-}
|
|
SRTM_DIR: /data/srtm
|
|
WORKER_MODE: link
|
|
NUM_WORKERS: '1'
|
|
LINK_QUEUE_V3_MAX_JOBS: ${LINK_QUEUE_V3_MAX_JOBS:-5000}
|
|
LINK_QUEUE_V3_MAX_BYTES: ${LINK_QUEUE_V3_MAX_BYTES:-67108864}
|
|
LINK_QUEUE_V3_MAX_PAYLOAD_BYTES: ${LINK_QUEUE_V3_MAX_PAYLOAD_BYTES:-32768}
|
|
LINK_QUEUE_V3_MAX_ATTEMPTS: ${LINK_QUEUE_V3_MAX_ATTEMPTS:-5}
|
|
LINK_QUEUE_V3_LEASE_MS: ${LINK_QUEUE_V3_LEASE_MS:-120000}
|
|
LINK_QUEUE_V3_DEAD_MAX_JOBS: ${LINK_QUEUE_V3_DEAD_MAX_JOBS:-1000}
|
|
LINK_QUEUE_V3_DEAD_MAX_BYTES: ${LINK_QUEUE_V3_DEAD_MAX_BYTES:-33554432}
|
|
LINK_QUEUE_V3_DEAD_RETENTION_MS: ${LINK_QUEUE_V3_DEAD_RETENTION_MS:-2592000000}
|
|
SRTM_MAX_LINK_TILES: ${SRTM_MAX_LINK_TILES:-64}
|
|
SRTM_CACHE_MAX_BYTES: ${SRTM_CACHE_MAX_BYTES:-21474836480}
|
|
volumes:
|
|
- srtm_data:/data/srtm
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "python3 -c \"import os,redis; r=redis.Redis.from_url(os.environ['REDIS_URL'],password=os.environ.get('REDIS_PASSWORD')); raise SystemExit(0 if r.exists('meshcore:link:v3:worker_heartbeat') else 1)\""]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 12
|
|
start_period: 20s
|
|
depends_on:
|
|
db-migrate:
|
|
condition: service_completed_successfully
|
|
|
|
# Canonical whole-region RF calculator. HopReach owns propagation and
|
|
# raster output; UK Mesh supplies only the internal data adapter, versioned
|
|
# boundary, orchestration profile, and native map consumer.
|
|
hopreach:
|
|
<<: *internal-hosts
|
|
image: ${HOPREACH_IMAGE:-meshcore-analytics-hopreach:local}
|
|
build:
|
|
context: ./third_party/hopreach
|
|
dockerfile: Dockerfile
|
|
args:
|
|
VERSION: v0.1.32-ukmesh.3
|
|
SOURCE_REVISION: ${SOURCE_REVISION:-local}
|
|
restart: always
|
|
logging: *json-log-limits
|
|
cpus: ${HOPREACH_CPUS:-4.0}
|
|
mem_limit: ${HOPREACH_MEMORY_LIMIT:-8g}
|
|
memswap_limit: ${HOPREACH_MEMORY_LIMIT:-8g}
|
|
environment:
|
|
HOPREACH_CONFIG: /config/config.yaml
|
|
GOMAXPROCS: ${HOPREACH_CPU_WORKERS:-4}
|
|
volumes:
|
|
- rf_coverage_data:/data
|
|
- ./rf-coverage/config.ukmesh.yaml:/config/config.yaml:ro
|
|
- ./rf-coverage/boundaries/uk-operational-v1.geojson:/config/uk-operational-v1.geojson:ro
|
|
depends_on:
|
|
backend:
|
|
condition: service_healthy
|
|
|
|
app-ukmesh:
|
|
<<: [*internal-hosts, *application-hardening]
|
|
image: ${APP_IMAGE:-meshcore-analytics-app:local}
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile.app
|
|
args:
|
|
SOURCE_REVISION: ${SOURCE_REVISION:-local}
|
|
VITE_APP_HOSTNAME: app.ukmesh.com
|
|
VITE_BUILD_TARGET: app
|
|
VITE_NETWORK: ukmesh
|
|
VITE_SITE: ukmesh
|
|
VITE_RF_COVERAGE_ENABLED: ${RF_COVERAGE_ENABLED:-true}
|
|
restart: always
|
|
logging: *json-log-limits
|
|
mem_limit: "128m"
|
|
memswap_limit: "128m"
|
|
depends_on:
|
|
backend:
|
|
condition: service_healthy
|
|
ports:
|
|
- "127.0.0.1:3003:8080"
|
|
networks:
|
|
default:
|
|
ipv4_address: 172.30.0.10
|
|
volumes:
|
|
- ${TERRAIN_TILES_PATH:-./terrain-tiles}:/terrain-tiles:ro
|
|
- rf_coverage_data:/rf-coverage-data:ro
|
|
tmpfs:
|
|
- /tmp:rw,noexec,nosuid,size=16m,mode=1777
|
|
- /var/cache/nginx:rw,noexec,nosuid,size=32m,uid=101,gid=101,mode=0755
|
|
- /var/run:rw,noexec,nosuid,size=4m,uid=101,gid=101,mode=0755
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:8080/ >/dev/null && wget -qO- http://127.0.0.1:8080/readyz >/dev/null"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 6
|
|
start_period: 15s
|
|
|
|
website-ukmesh:
|
|
<<: [*internal-hosts, *application-hardening]
|
|
image: ${WEBSITE_IMAGE:-meshcore-analytics-website:local}
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile.website
|
|
args:
|
|
SOURCE_REVISION: ${SOURCE_REVISION:-local}
|
|
VITE_APP_HOSTNAME: app.ukmesh.com
|
|
VITE_BUILD_TARGET: public-website
|
|
VITE_SITE: ukmesh
|
|
restart: always
|
|
logging: *json-log-limits
|
|
mem_limit: "128m"
|
|
memswap_limit: "128m"
|
|
ports:
|
|
- "127.0.0.1:3004:8080"
|
|
networks:
|
|
default:
|
|
ipv4_address: 172.30.0.11
|
|
depends_on:
|
|
backend:
|
|
condition: service_healthy
|
|
volumes:
|
|
- ${TERRAIN_TILES_PATH:-./terrain-tiles}:/terrain-tiles:ro
|
|
tmpfs:
|
|
- /tmp:rw,noexec,nosuid,size=16m,mode=1777
|
|
- /var/cache/nginx:rw,noexec,nosuid,size=32m,uid=101,gid=101,mode=0755
|
|
- /var/run:rw,noexec,nosuid,size=4m,uid=101,gid=101,mode=0755
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:8080/ >/dev/null && wget -qO- http://127.0.0.1:8080/readyz >/dev/null"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 6
|
|
start_period: 15s
|
|
|
|
mesh-health-check-data-init:
|
|
<<: *application-hardening
|
|
image: ${HEALTHCHECK_IMAGE:-meshcore-analytics-health-check:local}
|
|
user: "0:0"
|
|
entrypoint: ["/bin/sh", "-ec"]
|
|
command:
|
|
- |
|
|
touch /app/data/.meshcore-initialized
|
|
chown -R node:node /app/data
|
|
find /app/data -xdev -type f -exec chmod 0640 {} +
|
|
chmod 0750 /app/data
|
|
restart: "no"
|
|
network_mode: "none"
|
|
mem_limit: "64m"
|
|
memswap_limit: "64m"
|
|
cap_add:
|
|
- CHOWN
|
|
- DAC_OVERRIDE
|
|
- FOWNER
|
|
volumes:
|
|
- mesh_health_check_data:/app/data
|
|
|
|
mesh-health-check:
|
|
<<: [*internal-hosts, *application-hardening]
|
|
image: ${HEALTHCHECK_IMAGE:-meshcore-analytics-health-check:local}
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile.mesh-health-check
|
|
args:
|
|
SOURCE_REVISION: ${SOURCE_REVISION:-local}
|
|
MESH_HEALTH_CHECK_REF: ${HEALTHCHECK_SOURCE_REF:?HEALTHCHECK_SOURCE_REF full commit required}
|
|
restart: always
|
|
logging: *json-log-limits
|
|
mem_limit: "512m"
|
|
memswap_limit: "512m"
|
|
environment:
|
|
PORT: "3090"
|
|
APP_TITLE: ${HEALTHCHECK_APP_TITLE:-UKMesh Health Check}
|
|
APP_EYEBROW: ${HEALTHCHECK_APP_EYEBROW:-MeshCore Observer Coverage}
|
|
APP_HEADLINE: ${HEALTHCHECK_APP_HEADLINE:-Check your UK mesh reach.}
|
|
APP_DESCRIPTION: ${HEALTHCHECK_APP_DESCRIPTION:-Generate a test code, send it to the configured channel, and watch observer coverage build in real time.}
|
|
EXTERNAL_LINK_URL: ${HEALTHCHECK_EXTERNAL_LINK_URL:-https://ukmesh.com}
|
|
EXTERNAL_LINK_LABEL: ${HEALTHCHECK_EXTERNAL_LINK_LABEL:-UKMesh}
|
|
LOG_LEVEL: ${HEALTHCHECK_LOG_LEVEL:-info}
|
|
TRUST_PROXY: "1"
|
|
MQTT_HOST: mosquitto
|
|
MQTT_PORT: "9001"
|
|
MQTT_USERNAME: ${MQTT_USERNAME:-backend}
|
|
MQTT_PASSWORD: ${MQTT_PASSWORD}
|
|
MQTT_TOPIC: ${HEALTHCHECK_MQTT_TOPIC:-meshcore/+/+/packets,meshcore/+/+/status,meshcore/+/+/internal}
|
|
MQTT_TRANSPORT: websockets
|
|
MQTT_WS_PATH: /
|
|
MQTT_TLS: "false"
|
|
DASH_BROKER_HOST: ${HEALTHCHECK_DASH_BROKER_HOST:-mqtt.ukmesh.com}
|
|
TEST_CHANNEL_NAME: ${HEALTHCHECK_TEST_CHANNEL_NAME:-ukmeshtest}
|
|
TEST_CHANNEL_SECRET: ${HEALTHCHECK_TEST_CHANNEL_SECRET:-}
|
|
TEST_CHANNEL_SECRET_SOURCE_NAME: ${HEALTHCHECK_TEST_CHANNEL_SECRET_SOURCE_NAME:-test}
|
|
TEST_CHANNEL_HASH: ${HEALTHCHECK_TEST_CHANNEL_HASH:-}
|
|
MESHCORE_CHANNEL_SECRETS: ${MESHCORE_CHANNEL_SECRETS:-}
|
|
SESSION_TTL_SECONDS: ${HEALTHCHECK_SESSION_TTL_SECONDS:-600}
|
|
RESULT_RETENTION_SECONDS: ${HEALTHCHECK_RESULT_RETENTION_SECONDS:-604800}
|
|
MAX_USES_PER_CODE: ${HEALTHCHECK_MAX_USES_PER_CODE:-3}
|
|
KNOWN_OBSERVERS: ${HEALTHCHECK_KNOWN_OBSERVERS:-}
|
|
OBSERVER_ACTIVE_WINDOW_SECONDS: ${HEALTHCHECK_OBSERVER_ACTIVE_WINDOW_SECONDS:-900}
|
|
OBSERVER_RETENTION_SECONDS: ${HEALTHCHECK_OBSERVER_RETENTION_SECONDS:-14400}
|
|
OBSERVERS_FILE: /app/data/observer.json
|
|
RESULTS_FILE: /app/data/session-results.json
|
|
REGIONS_FILE: ${HEALTHCHECK_REGIONS_FILE:-regions/uk.geojson}
|
|
TURNSTILE_ENABLED: ${HEALTHCHECK_TURNSTILE_ENABLED:-false}
|
|
TURNSTILE_SITE_KEY: ${HEALTHCHECK_TURNSTILE_SITE_KEY:-}
|
|
TURNSTILE_SECRET_KEY: ${HEALTHCHECK_TURNSTILE_SECRET_KEY:-}
|
|
depends_on:
|
|
mesh-health-check-data-init:
|
|
condition: service_completed_successfully
|
|
ports:
|
|
- "127.0.0.1:3090:3090"
|
|
volumes:
|
|
- mesh_health_check_data:/app/data
|
|
- ./healthcheck-overrides/index.html:/app/public/index.html:ro
|
|
- ./healthcheck-overrides/share.html:/app/public/share.html:ro
|
|
- ./healthcheck-overrides/sw.js:/app/public/sw.js:ro
|
|
- ./healthcheck-overrides/ukmesh-theme.css:/app/public/ukmesh-theme.css:ro
|
|
healthcheck:
|
|
# The upstream app has no /healthz route; its SPA fallback returns 200
|
|
# there even when the API is unavailable. Probe a real JSON endpoint.
|
|
test: ["CMD-SHELL", "node -e \"fetch('http://127.0.0.1:3090/api/bootstrap').then(r=>process.exit(r.ok && (r.headers.get('content-type') || '').includes('application/json') ? 0 : 1)).catch(()=>process.exit(1))\""]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 6
|
|
start_period: 30s
|
|
|
|
website-dev:
|
|
<<: [*internal-hosts, *application-hardening]
|
|
image: ${WEBSITE_DEV_IMAGE:-meshcore-analytics-website-dev:local}
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile.website
|
|
args:
|
|
SOURCE_REVISION: ${SOURCE_REVISION:-local}
|
|
VITE_APP_HOSTNAME: ${VITE_DEV_APP_HOSTNAME:-test.ukmesh.com}
|
|
VITE_BUILD_TARGET: dev-website
|
|
VITE_NETWORK: test
|
|
VITE_SITE: dev
|
|
VITE_OBSERVER_ID: ${VITE_DEV_OBSERVER_ID:-}
|
|
VITE_SITE_DISPLAY_NAME: ${VITE_DEV_SITE_DISPLAY_NAME:-UK Mesh Test}
|
|
VITE_SITE_FOOTER_NAME: ${VITE_DEV_SITE_FOOTER_NAME:-UK Mesh Test}
|
|
VITE_SITE_APP_URL: ${VITE_DEV_SITE_APP_URL:-https://test.ukmesh.com}
|
|
VITE_SITE_HOME_URL: ${VITE_DEV_SITE_HOME_URL:-https://test.ukmesh.com}
|
|
restart: always
|
|
profiles: [dev]
|
|
logging: *json-log-limits
|
|
mem_limit: "128m"
|
|
memswap_limit: "128m"
|
|
ports:
|
|
- "127.0.0.1:3006:8080"
|
|
networks:
|
|
default:
|
|
ipv4_address: 172.30.0.12
|
|
depends_on:
|
|
backend:
|
|
condition: service_healthy
|
|
volumes:
|
|
- ${TERRAIN_TILES_PATH:-./terrain-tiles}:/terrain-tiles:ro
|
|
tmpfs:
|
|
- /tmp:rw,noexec,nosuid,size=16m,mode=1777
|
|
- /var/cache/nginx:rw,noexec,nosuid,size=32m,uid=101,gid=101,mode=0755
|
|
- /var/run:rw,noexec,nosuid,size=4m,uid=101,gid=101,mode=0755
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:8080/ >/dev/null"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 6
|
|
start_period: 15s
|
|
|
|
# ── Anubis bot protection ─────────────────────────────────────────────────────
|
|
# One sidecar per public HTTP service. Cloudflare Tunnel routes to these
|
|
# containers (port 8923) instead of the origin services directly.
|
|
# MQTT (mosquitto:9001) is intentionally excluded — non-browser clients.
|
|
# Dev services are intentionally excluded — low value.
|
|
|
|
anubis-website-ukmesh:
|
|
<<: [*anubis-base, *internal-hosts]
|
|
mem_limit: "128m"
|
|
memswap_limit: "128m"
|
|
environment:
|
|
ED25519_PRIVATE_KEY_HEX: "${ANUBIS_ED25519_PRIVATE_KEY_HEX:?ANUBIS_ED25519_PRIVATE_KEY_HEX required}"
|
|
BIND: ":8923"
|
|
TARGET: "http://website-ukmesh:8080"
|
|
COOKIE_DOMAIN: "ukmesh.com"
|
|
COOKIE_SECURE: "true"
|
|
COOKIE_SAME_SITE: "Lax"
|
|
COOKIE_EXPIRATION_TIME: "168h"
|
|
POLICY_FNAME: "/data/botPolicy.yaml"
|
|
SLOG_LEVEL: "INFO"
|
|
CUSTOM_REAL_IP_HEADER: "CF-Connecting-IP"
|
|
depends_on:
|
|
website-ukmesh:
|
|
condition: service_healthy
|
|
networks:
|
|
default:
|
|
ipv4_address: 172.30.0.20
|
|
|
|
anubis-app-ukmesh:
|
|
<<: [*anubis-base, *internal-hosts]
|
|
mem_limit: "128m"
|
|
memswap_limit: "128m"
|
|
environment:
|
|
ED25519_PRIVATE_KEY_HEX: "${ANUBIS_ED25519_PRIVATE_KEY_HEX:?ANUBIS_ED25519_PRIVATE_KEY_HEX required}"
|
|
BIND: ":8923"
|
|
TARGET: "http://app-ukmesh:8080"
|
|
COOKIE_DOMAIN: "ukmesh.com"
|
|
COOKIE_SECURE: "true"
|
|
COOKIE_SAME_SITE: "Lax"
|
|
COOKIE_EXPIRATION_TIME: "168h"
|
|
POLICY_FNAME: "/data/botPolicy.yaml"
|
|
SLOG_LEVEL: "INFO"
|
|
CUSTOM_REAL_IP_HEADER: "CF-Connecting-IP"
|
|
depends_on:
|
|
app-ukmesh:
|
|
condition: service_healthy
|
|
networks:
|
|
default:
|
|
ipv4_address: 172.30.0.21
|
|
|
|
anubis-health-check:
|
|
<<: [*anubis-base, *internal-hosts]
|
|
mem_limit: "128m"
|
|
memswap_limit: "128m"
|
|
environment:
|
|
ED25519_PRIVATE_KEY_HEX: "${ANUBIS_ED25519_PRIVATE_KEY_HEX:?ANUBIS_ED25519_PRIVATE_KEY_HEX required}"
|
|
BIND: ":8923"
|
|
TARGET: "http://mesh-health-check:3090"
|
|
COOKIE_DOMAIN: "ukmesh.com"
|
|
COOKIE_SECURE: "true"
|
|
COOKIE_SAME_SITE: "Lax"
|
|
COOKIE_EXPIRATION_TIME: "168h"
|
|
POLICY_FNAME: "/data/botPolicy.yaml"
|
|
SLOG_LEVEL: "INFO"
|
|
CUSTOM_REAL_IP_HEADER: "CF-Connecting-IP"
|
|
depends_on:
|
|
mesh-health-check:
|
|
condition: service_healthy
|
|
networks:
|
|
default:
|
|
ipv4_address: 172.30.0.22
|
|
# Cloudflare Tunnel currently routes healthcheck.ukmesh.com to
|
|
# http://anubis-mesh-health-check:8923. Keep this alias unless the
|
|
# tunnel ingress config is changed at Cloudflare too.
|
|
aliases:
|
|
- anubis-mesh-health-check
|
|
|
|
cloudflared:
|
|
image: cloudflare/cloudflared@sha256:e39ee8da81ad5e05d77f38d2f51c60ca51bf2a8450ac3abab50c17fdb91d91bf
|
|
restart: always
|
|
logging: *json-log-limits
|
|
mem_limit: "256m"
|
|
memswap_limit: "256m"
|
|
user: "65532:65532"
|
|
read_only: true
|
|
cap_drop:
|
|
- ALL
|
|
security_opt:
|
|
- no-new-privileges:true
|
|
tmpfs:
|
|
- /tmp:rw,noexec,nosuid,size=16m,mode=1777
|
|
command: tunnel run
|
|
profiles: [tunnel]
|
|
networks:
|
|
default:
|
|
ipv4_address: 172.30.0.30
|
|
meshcore-beacon-ukmesh: {}
|
|
environment:
|
|
TUNNEL_TOKEN: ${CLOUDFLARE_TUNNEL_TOKEN}
|
|
depends_on:
|
|
backend:
|
|
condition: service_healthy
|
|
anubis-website-ukmesh:
|
|
condition: service_started
|
|
anubis-app-ukmesh:
|
|
condition: service_started
|
|
anubis-health-check:
|
|
condition: service_started
|
|
|
|
loki:
|
|
<<: *internal-hosts
|
|
image: grafana/loki@sha256:5fe9fa99e9a747297cdf0239a5b25d192d8f668bd6505b09beef4dffcab5aac2
|
|
restart: always
|
|
logging: *json-log-limits
|
|
mem_limit: "512m"
|
|
memswap_limit: "512m"
|
|
command: -config.file=/etc/loki/loki.yaml
|
|
read_only: true
|
|
cap_drop:
|
|
- ALL
|
|
security_opt:
|
|
- no-new-privileges:true
|
|
tmpfs:
|
|
- /tmp:rw,noexec,nosuid,size=32m
|
|
volumes:
|
|
- ./logging/loki.yaml:/etc/loki/loki.yaml:ro
|
|
- loki_data:/loki
|
|
healthcheck:
|
|
test: ["CMD", "/busybox/wget", "-q", "-O", "/dev/null", "http://127.0.0.1:3100/ready"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 5
|
|
start_period: 20s
|
|
|
|
alloy:
|
|
<<: *internal-hosts
|
|
image: grafana/alloy@sha256:491b0578c04983fd54fe99b587b6fab4404dc46d0dc16677bd6b00cc1140b308
|
|
restart: always
|
|
logging:
|
|
driver: json-file
|
|
options:
|
|
max-size: "10m"
|
|
max-file: "3"
|
|
mem_limit: "256m"
|
|
memswap_limit: "256m"
|
|
user: "473:473"
|
|
group_add:
|
|
- "${JOURNAL_GID:-999}"
|
|
read_only: true
|
|
cap_drop:
|
|
- ALL
|
|
security_opt:
|
|
- no-new-privileges:true
|
|
command:
|
|
- run
|
|
- --server.http.listen-addr=0.0.0.0:12345
|
|
- --storage.path=/var/lib/alloy/data
|
|
- /etc/alloy/config.alloy
|
|
volumes:
|
|
- ./logging/alloy/config.alloy:/etc/alloy/config.alloy:ro
|
|
- /var/log/journal:/var/log/journal:ro
|
|
- /run/log/journal:/run/log/journal:ro
|
|
- /etc/machine-id:/etc/machine-id:ro
|
|
- alloy_data:/var/lib/alloy
|
|
healthcheck:
|
|
test: ["CMD", "/bin/alloy", "validate", "/etc/alloy/config.alloy"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 5
|
|
start_period: 10s
|
|
depends_on:
|
|
loki:
|
|
condition: service_healthy
|
|
|
|
prometheus:
|
|
<<: *internal-hosts
|
|
image: prom/prometheus@sha256:63805ebb8d2b3920190daf1cb14a60871b16fd38bed42b857a3182bc621f4996
|
|
restart: always
|
|
logging: *json-log-limits
|
|
mem_limit: "256m"
|
|
memswap_limit: "256m"
|
|
command:
|
|
- --config.file=/etc/prometheus/prometheus.yml
|
|
- --storage.tsdb.retention.time=15d
|
|
- --storage.tsdb.retention.size=8GB
|
|
read_only: true
|
|
cap_drop:
|
|
- ALL
|
|
security_opt:
|
|
- no-new-privileges:true
|
|
tmpfs:
|
|
- /tmp:rw,noexec,nosuid,size=32m
|
|
volumes:
|
|
- ./logging/prometheus.yml:/etc/prometheus/prometheus.yml:ro
|
|
- ./logging/rules:/etc/prometheus/rules:ro
|
|
- prometheus_data:/prometheus
|
|
ports:
|
|
- "127.0.0.1:9090:9090"
|
|
healthcheck:
|
|
test: ["CMD", "/bin/wget", "-q", "-O", "/dev/null", "http://127.0.0.1:9090/-/ready"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 5
|
|
start_period: 20s
|
|
depends_on:
|
|
backend:
|
|
condition: service_healthy
|
|
alertmanager:
|
|
condition: service_healthy
|
|
postgres-exporter:
|
|
condition: service_healthy
|
|
redis-exporter:
|
|
condition: service_healthy
|
|
node-exporter:
|
|
condition: service_healthy
|
|
blackbox-exporter:
|
|
condition: service_healthy
|
|
|
|
grafana:
|
|
<<: *internal-hosts
|
|
image: grafana/grafana@sha256:0a2874cf39c6487093c682215f7c7903ed8646d78ae5f911af945d2dfcc0a447
|
|
restart: always
|
|
logging: *json-log-limits
|
|
mem_limit: "512m"
|
|
memswap_limit: "512m"
|
|
read_only: true
|
|
cap_drop:
|
|
- ALL
|
|
security_opt:
|
|
- no-new-privileges:true
|
|
tmpfs:
|
|
- /tmp:rw,noexec,nosuid,size=64m
|
|
environment:
|
|
GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_ADMIN_PASSWORD:?GRAFANA_ADMIN_PASSWORD required}
|
|
GF_USERS_ALLOW_SIGN_UP: "false"
|
|
GF_AUTH_ANONYMOUS_ENABLED: "false"
|
|
volumes:
|
|
- ./logging/grafana/provisioning:/etc/grafana/provisioning:ro
|
|
- ./logging/grafana/dashboards:/etc/grafana/dashboards:ro
|
|
- grafana_data:/var/lib/grafana
|
|
ports:
|
|
- "127.0.0.1:3010:3000"
|
|
healthcheck:
|
|
test: ["CMD", "/usr/bin/wget", "-q", "-O", "/dev/null", "http://127.0.0.1:3000/api/health"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 5
|
|
start_period: 30s
|
|
depends_on:
|
|
loki:
|
|
condition: service_healthy
|
|
prometheus:
|
|
condition: service_healthy
|
|
|
|
alert-receiver:
|
|
<<: *internal-hosts
|
|
image: ${BACKEND_IMAGE:-meshcore-analytics-backend:local}
|
|
build:
|
|
context: .
|
|
dockerfile: Dockerfile.backend
|
|
args:
|
|
SOURCE_REVISION: ${SOURCE_REVISION:-local}
|
|
command: ["node", "dist/workers/alert-receiver.js"]
|
|
restart: always
|
|
logging: *json-log-limits
|
|
mem_limit: "128m"
|
|
memswap_limit: "128m"
|
|
read_only: true
|
|
cap_drop:
|
|
- ALL
|
|
security_opt:
|
|
- no-new-privileges:true
|
|
tmpfs:
|
|
- /tmp:rw,noexec,nosuid,size=16m
|
|
environment:
|
|
DATABASE_APPLICATION_NAME: meshcore-alert-receiver
|
|
ALERT_FORWARD_URL: ${ALERT_FORWARD_URL:-}
|
|
ALERT_RECEIVER_PATH: /var/lib/meshcore-alerts/alerts.jsonl
|
|
NODE_ENV: production
|
|
volumes:
|
|
- alert_receiver_data:/var/lib/meshcore-alerts
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:8080/healthz >/dev/null"]
|
|
interval: 30s
|
|
timeout: 5s
|
|
retries: 5
|
|
start_period: 10s
|
|
|
|
alertmanager:
|
|
<<: *internal-hosts
|
|
image: prom/alertmanager@sha256:27c475db5fb156cab31d5c18a4251ac7ed567746a2483ff264516437a39b15ba
|
|
restart: always
|
|
logging: *json-log-limits
|
|
mem_limit: "128m"
|
|
memswap_limit: "128m"
|
|
command:
|
|
- --config.file=/etc/alertmanager/alertmanager.yml
|
|
- --storage.path=/alertmanager
|
|
- --data.retention=120h
|
|
read_only: true
|
|
cap_drop:
|
|
- ALL
|
|
security_opt:
|
|
- no-new-privileges:true
|
|
tmpfs:
|
|
- /tmp:rw,noexec,nosuid,size=16m
|
|
volumes:
|
|
- ./logging/alertmanager.yml:/etc/alertmanager/alertmanager.yml:ro
|
|
- alertmanager_data:/alertmanager
|
|
healthcheck:
|
|
test: ["CMD", "/bin/wget", "-q", "-O", "/dev/null", "http://127.0.0.1:9093/-/ready"]
|
|
interval: 30s
|
|
timeout: 5s
|
|
retries: 5
|
|
start_period: 10s
|
|
depends_on:
|
|
alert-receiver:
|
|
condition: service_healthy
|
|
|
|
postgres-exporter:
|
|
<<: *internal-hosts
|
|
image: quay.io/prometheuscommunity/postgres-exporter@sha256:fb96c4413985d4b23ab02b19022b3d70a86c8e0a62f41ab15ebb6f4673781a5d
|
|
restart: always
|
|
logging: *json-log-limits
|
|
mem_limit: "128m"
|
|
memswap_limit: "128m"
|
|
read_only: true
|
|
cap_drop:
|
|
- ALL
|
|
security_opt:
|
|
- no-new-privileges:true
|
|
environment:
|
|
DATA_SOURCE_NAME: postgresql://${POSTGRES_USER:-meshcore}:${POSTGRES_PASSWORD}@timescaledb:5432/${POSTGRES_DB:-meshcore}?sslmode=disable
|
|
healthcheck:
|
|
test: ["CMD", "/bin/postgres_exporter", "--version"]
|
|
interval: 30s
|
|
timeout: 5s
|
|
retries: 5
|
|
redis-exporter:
|
|
<<: *internal-hosts
|
|
image: oliver006/redis_exporter@sha256:4c8000eb3525e0f6ed1327499861e272fcce358a9e7a87dd9db74c44607e6d8e
|
|
restart: always
|
|
logging: *json-log-limits
|
|
mem_limit: "128m"
|
|
memswap_limit: "128m"
|
|
read_only: true
|
|
cap_drop:
|
|
- ALL
|
|
security_opt:
|
|
- no-new-privileges:true
|
|
environment:
|
|
REDIS_ADDR: redis://redis:6379
|
|
REDIS_PASSWORD: ${REDIS_PASSWORD:?REDIS_PASSWORD required}
|
|
REDIS_EXPORTER_CHECK_SINGLE_KEYS: meshcore:link:v3:worker_heartbeat
|
|
healthcheck:
|
|
test: ["CMD", "/redis_exporter", "-version"]
|
|
interval: 30s
|
|
timeout: 5s
|
|
retries: 5
|
|
node-exporter:
|
|
<<: *internal-hosts
|
|
image: prom/node-exporter@sha256:d00a542e409ee618a4edc67da14dd48c5da66726bbd5537ab2af9c1dfc442c8a
|
|
restart: always
|
|
logging: *json-log-limits
|
|
mem_limit: "128m"
|
|
memswap_limit: "128m"
|
|
pid: host
|
|
command:
|
|
- --path.rootfs=/host
|
|
- --path.procfs=/host/proc
|
|
- --path.sysfs=/host/sys
|
|
- --collector.filesystem.mount-points-exclude=^/(dev|proc|sys|var/lib/docker/.+)($|/)
|
|
read_only: true
|
|
cap_drop:
|
|
- ALL
|
|
security_opt:
|
|
- no-new-privileges:true
|
|
volumes:
|
|
- /:/host:ro,rslave
|
|
healthcheck:
|
|
test: ["CMD", "/bin/node_exporter", "--version"]
|
|
interval: 30s
|
|
timeout: 5s
|
|
retries: 5
|
|
|
|
blackbox-exporter:
|
|
<<: *internal-hosts
|
|
image: prom/blackbox-exporter@sha256:a50c4c0eda297baa1678cd4dc4712a67fdea713b832d43ce7fcc5f9bea05094d
|
|
restart: always
|
|
logging: *json-log-limits
|
|
mem_limit: "128m"
|
|
memswap_limit: "128m"
|
|
command: ["--config.file=/etc/blackbox_exporter/config.yml"]
|
|
read_only: true
|
|
cap_drop:
|
|
- ALL
|
|
security_opt:
|
|
- no-new-privileges:true
|
|
volumes:
|
|
- ./logging/blackbox.yml:/etc/blackbox_exporter/config.yml:ro
|
|
healthcheck:
|
|
test: ["CMD", "/bin/blackbox_exporter", "--version"]
|
|
interval: 30s
|
|
timeout: 5s
|
|
retries: 5
|
|
|
|
networks:
|
|
default:
|
|
ipam:
|
|
config:
|
|
# Fixed proxy addresses make the backend's immediate-peer trust exact.
|
|
# Keep Docker's dynamic allocator away from those reserved low
|
|
# addresses when the full stack starts services concurrently.
|
|
- subnet: 172.30.0.0/24
|
|
ip_range: 172.30.0.128/25
|
|
meshcore-beacon-ukmesh:
|
|
external: true
|
|
name: meshcore-beacon-ukmesh_default
|
|
|
|
volumes:
|
|
timescaledb_data:
|
|
mosquitto_data:
|
|
mosquitto_log:
|
|
redis_data:
|
|
srtm_data:
|
|
loki_data:
|
|
grafana_data:
|
|
prometheus_data:
|
|
alloy_data:
|
|
alert_receiver_data:
|
|
alertmanager_data:
|
|
mesh_health_check_data:
|
|
rf_coverage_data:
|