diff --git a/.env.example b/.env.example index 2f27ef7..ef8352f 100644 --- a/.env.example +++ b/.env.example @@ -56,6 +56,12 @@ MATRIX_ROOM='#meshtastic-berlin:matrix.org' # Debug mode (0=off, 1=on) DEBUG=0 +# Docker Compose networking profile +# Leave unset for Linux hosts (default host networking). +# Set to "bridge" on Docker Desktop (macOS/Windows) if host networking +# is unavailable. +# COMPOSE_PROFILES=bridge + # Meshtastic snapshot interval (seconds) MESH_SNAPSHOT_SECS=60 diff --git a/DOCKER.md b/DOCKER.md index cad3c2b..e386d50 100644 --- a/DOCKER.md +++ b/DOCKER.md @@ -8,6 +8,16 @@ docker-compose up -d docker-compose logs -f ``` +The default configuration attaches both services to the host network. This +avoids creating Docker bridge interfaces on platforms where that operation is +blocked. Access the dashboard at `http://127.0.0.1:41447` as soon as the +containers are running. On Docker Desktop (macOS/Windows) or when you prefer +traditional bridged networking, start Compose with the `bridge` profile: + +```bash +COMPOSE_PROFILES=bridge docker-compose up -d +``` + Access at `http://localhost:41447` ## Configuration diff --git a/README.md b/README.md index 72e7932..5880d40 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,16 @@ docker-compose up -d # Start services docker-compose logs -f # View logs ``` +PotatoMesh uses host networking by default so it can run on restricted +systems where Docker cannot create bridged interfaces. The web UI listens on +`http://127.0.0.1:41447` immediately without explicit port mappings. If you +are using Docker Desktop (macOS/Windows) or otherwise require bridged +networking, enable the Compose profile with: + +```bash +COMPOSE_PROFILES=bridge docker-compose up -d +``` + ## Web App Requires Ruby for the Sinatra web app and SQLite3 for the app's database. diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 85407b0..c7ade38 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -1,21 +1,34 @@ -version: '3.8' - # Development overrides for docker-compose.yml services: web: environment: - - DEBUG=1 + DEBUG: 1 volumes: - ./web:/app - - ./data:/data # Mount data directory for SQL files - - /app/vendor/bundle # Exclude vendor directory from volume mount + - ./data:/data + - /app/vendor/bundle + + web-bridge: + environment: + DEBUG: 1 + volumes: + - ./web:/app + - ./data:/data + - /app/vendor/bundle ports: - "41447:41447" - - "9292:9292" # Additional port for development tools + - "9292:9292" ingestor: environment: - - DEBUG=1 + DEBUG: 1 volumes: - ./data:/app - - /app/.local # Exclude Python packages from volume mount + - /app/.local + + ingestor-bridge: + environment: + DEBUG: 1 + volumes: + - ./data:/app + - /app/.local diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index b542d26..607a929 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -1,33 +1,29 @@ -version: '3.8' - # Production overrides for docker-compose.yml services: web: build: target: production environment: - - DEBUG=0 + DEBUG: 0 + restart: always + + web-bridge: + build: + target: production + environment: + DEBUG: 0 restart: always - deploy: - resources: - limits: - memory: 512M - cpus: '0.5' - reservations: - memory: 256M - cpus: '0.25' ingestor: build: target: production environment: - - DEBUG=0 + DEBUG: 0 + restart: always + + ingestor-bridge: + build: + target: production + environment: + DEBUG: 0 restart: always - deploy: - resources: - limits: - memory: 256M - cpus: '0.25' - reservations: - memory: 128M - cpus: '0.1' diff --git a/docker-compose.yml b/docker-compose.yml index 1de4a2f..3e2cab3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,67 +1,85 @@ -version: '3.8' +x-web-base: &web-base + image: ghcr.io/l5yth/potato-mesh-web-linux-amd64:latest + environment: + SITE_NAME: ${SITE_NAME:-My Meshtastic Network} + DEFAULT_CHANNEL: ${DEFAULT_CHANNEL:-#MediumFast} + DEFAULT_FREQUENCY: ${DEFAULT_FREQUENCY:-868MHz} + MAP_CENTER_LAT: ${MAP_CENTER_LAT:-52.502889} + MAP_CENTER_LON: ${MAP_CENTER_LON:-13.404194} + MAX_NODE_DISTANCE_KM: ${MAX_NODE_DISTANCE_KM:-50} + MATRIX_ROOM: ${MATRIX_ROOM:-} + API_TOKEN: ${API_TOKEN} + DEBUG: ${DEBUG:-0} + volumes: + - potatomesh_data:/app/data + - potatomesh_logs:/app/logs + restart: unless-stopped + deploy: + resources: + limits: + memory: 512M + cpus: '0.5' + reservations: + memory: 256M + cpus: '0.25' + +x-ingestor-base: &ingestor-base + image: ghcr.io/l5yth/potato-mesh-ingestor-linux-amd64:latest + environment: + MESH_SERIAL: ${MESH_SERIAL:-/dev/ttyACM0} + MESH_SNAPSHOT_SECS: ${MESH_SNAPSHOT_SECS:-60} + MESH_CHANNEL_INDEX: ${MESH_CHANNEL_INDEX:-0} + POTATOMESH_INSTANCE: ${POTATOMESH_INSTANCE:-http://web:41447} + API_TOKEN: ${API_TOKEN} + DEBUG: ${DEBUG:-0} + volumes: + - potatomesh_data:/app/data + - potatomesh_logs:/app/logs + devices: + - ${MESH_SERIAL:-/dev/ttyACM0}:${MESH_SERIAL:-/dev/ttyACM0} + privileged: false + restart: unless-stopped + deploy: + resources: + limits: + memory: 256M + cpus: '0.25' + reservations: + memory: 128M + cpus: '0.1' services: web: - image: ghcr.io/l5yth/potato-mesh-web-linux-amd64:latest - container_name: potatomesh-web - ports: - - "41447:41447" - environment: - - SITE_NAME=${SITE_NAME:-My Meshtastic Network} - - DEFAULT_CHANNEL=${DEFAULT_CHANNEL:-#MediumFast} - - DEFAULT_FREQUENCY=${DEFAULT_FREQUENCY:-868MHz} - - MAP_CENTER_LAT=${MAP_CENTER_LAT:-52.502889} - - MAP_CENTER_LON=${MAP_CENTER_LON:-13.404194} - - MAX_NODE_DISTANCE_KM=${MAX_NODE_DISTANCE_KM:-50} - - MATRIX_ROOM=${MATRIX_ROOM:-} - - API_TOKEN=${API_TOKEN} - - DEBUG=${DEBUG:-0} - volumes: - - potatomesh_data:/app/data - - potatomesh_logs:/app/logs - networks: - - potatomesh-network - restart: unless-stopped - deploy: - resources: - limits: - memory: 512M - cpus: '0.5' - reservations: - memory: 256M - cpus: '0.25' + <<: *web-base + network_mode: host ingestor: - image: ghcr.io/l5yth/potato-mesh-ingestor-linux-amd64:latest - container_name: potatomesh-ingestor - environment: - - MESH_SERIAL=${MESH_SERIAL:-/dev/ttyACM0} - - MESH_SNAPSHOT_SECS=${MESH_SNAPSHOT_SECS:-60} - - MESH_CHANNEL_INDEX=${MESH_CHANNEL_INDEX:-0} - - POTATOMESH_INSTANCE=${POTATOMESH_INSTANCE:-http://web:41447} - - API_TOKEN=${API_TOKEN} - - DEBUG=${DEBUG:-0} - volumes: - - potatomesh_data:/app/data - - potatomesh_logs:/app/logs - devices: - # Map Meshtastic serial device from host to container - # Common paths: /dev/ttyACM0, /dev/ttyUSB0, /dev/cu.usbserial-* - - ${MESH_SERIAL:-/dev/ttyACM0}:${MESH_SERIAL:-/dev/ttyACM0} - privileged: false + <<: *ingestor-base + network_mode: host + depends_on: + - web + extra_hosts: + - "web:127.0.0.1" + + web-bridge: + <<: *web-base + container_name: potatomesh-web-bridge + networks: + - potatomesh-network + ports: + - "41447:41447" + profiles: + - bridge + + ingestor-bridge: + <<: *ingestor-base + container_name: potatomesh-ingestor-bridge networks: - potatomesh-network depends_on: - - web - restart: unless-stopped - deploy: - resources: - limits: - memory: 256M - cpus: '0.25' - reservations: - memory: 128M - cpus: '0.1' + - web-bridge + profiles: + - bridge volumes: potatomesh_data: @@ -72,4 +90,3 @@ volumes: networks: potatomesh-network: driver: bridge - diff --git a/web/app.rb b/web/app.rb index a068f1e..9e190ff 100644 --- a/web/app.rb +++ b/web/app.rb @@ -25,6 +25,7 @@ require "fileutils" require "logger" require "rack/utils" require "open3" +require "time" DB_PATH = ENV.fetch("MESH_DB", File.join(__dir__, "../data/mesh.db")) DB_BUSY_TIMEOUT_MS = ENV.fetch("DB_BUSY_TIMEOUT_MS", "5000").to_i