feat: Add comprehensive Docker support (#122)

* feat: Add comprehensive Docker support

- Add multi-container Docker setup with web app and data ingestor
- Create production-ready Dockerfiles with multi-stage builds
- Add Docker Compose configurations for dev, prod, and custom environments
- Implement CI/CD pipeline with GitHub Actions for automated builds
- Add comprehensive Docker documentation and setup guides
- Include security scanning and multi-platform builds
- Support for Meshtastic device integration via serial access
- Persistent data storage with named volumes
- Health checks and monitoring capabilities

Addresses GitHub issue #120: Dockerize the project for easier community adoption

Files added:
- web/Dockerfile: Ruby web application container
- data/Dockerfile: Python data ingestor container
- data/requirements.txt: Python dependencies
- docker-compose.yml: Base Docker Compose configuration
- docker-compose.dev.yml: Development environment overrides
- docker-compose.prod.yml: Production environment overrides
- .env.example: Environment configuration template
- .dockerignore: Docker build context optimization
- .github/workflows/docker.yml: CI/CD pipeline
- DOCKER.md: Comprehensive Docker documentation

This implementation transforms PotatoMesh from a complex manual setup
to a single-command deployment: docker-compose up -d

* feat: Add Docker support with multi-architecture builds

- Add web/Dockerfile with Ruby 3.4 Alpine base
- Add data/Dockerfile with Python 3.13 Alpine base
- Use Alpine's SQLite3 packages for cross-platform compatibility
- Support AMD64, ARM64, ARMv7, and Windows architectures
- Multi-stage builds for optimized production images
- Non-root user security and proper file permissions

* feat: Add Docker Compose configurations for different environments

- docker-compose.yml: Production setup with GHCR images
- docker-compose.dev.yml: Development setup with local builds
- docker-compose.raspberry-pi.yml: Pi-optimized with resource limits
- Support for all architectures (AMD64, ARM64, ARMv7)
- Proper volume mounts and network configuration
- Environment variable configuration for different deployments

* feat: Add GitHub Actions workflows for Docker CI/CD

- docker.yml: Multi-architecture build and push to GHCR
- test-raspberry-pi-hardware.yml: ARM64 testing with QEMU
- Support for manual workflow dispatch with version input
- Build and test all Docker variants (AMD64, ARM64, ARMv7, Windows)
- Automated publishing to GitHub Container Registry
- Comprehensive testing for Raspberry Pi deployments

* feat: Add Docker documentation and configuration tools

- docs/DOCKER.md: Comprehensive Docker setup and usage guide
- configure.sh: Interactive configuration script for deployment
- Platform-specific setup instructions (macOS, Linux, Windows)
- Raspberry Pi optimization guidelines
- Environment variable configuration
- Troubleshooting and best practices

* docs: Update README with comprehensive Docker support

- Add Docker Quick Start section with published images
- Add comprehensive table of all available GHCR images
- Include architecture-specific pull commands
- Update manual installation instructions
- Add platform-specific deployment examples
- Document all supported architectures and use cases

* chore: Update dependencies and project configuration

- Update data/requirements.txt for Python 3.13 compatibility
- Add v0.3.0 changelog entry documenting Docker support
- Update .gitignore for Docker-related files
- Prepare project for Docker deployment

* feat: Update web interface for Denver Mesh Network

- Update default configuration to center on Denver, Colorado
- Set SITE_NAME to 'Denver Mesh Network'
- Configure 915MHz frequency for US region
- Update map center coordinates (39.7392, -104.9903)
- Set appropriate node distance and Matrix room settings

* Update Docker configuration and documentation

- Remove Raspberry Pi specific Docker files and workflows
- Update Docker workflow configuration
- Consolidate Docker documentation
- Add AGENTS.md for opencode integration
- Update README with current project status

* cleanup: workflow/readme

* Update README.md

Co-authored-by: l5y <220195275+l5yth@users.noreply.github.com>

* Add .env.example and simplify documentation

- Add comprehensive .env.example with all environment variables
- Update web Dockerfile to use Berlin coordinates instead of Denver
- Simplify README Docker quick start with helpful comments
- Greatly simplify DOCKER.md with only essential information

* cleanup: readme

* Remove Stadia API key references

- Remove STADIA_API_KEY from docker-compose.yml environment variables
- Remove Stadia Maps configuration section from configure.sh
- Remove Stadia API key references from .env.example
- Simplify configuration to use basic OpenStreetMap tiles only

* quickfix

* cleanup: remove example usage from docker gh action output

---------

Co-authored-by: l5y <220195275+l5yth@users.noreply.github.com>
This commit is contained in:
Taylor Rose
2025-09-20 13:04:19 -06:00
committed by GitHub
parent 608d1e0396
commit 812d3c851f
15 changed files with 852 additions and 1 deletions

76
.dockerignore Normal file
View File

@@ -0,0 +1,76 @@
# Git
.git
.gitignore
# Documentation
README.md
CHANGELOG.md
*.md
# Docker files
docker-compose*.yml
.dockerignore
# Environment files
.env*
!.env.example
# Logs
*.log
logs/
# Runtime data
*.pid
*.seed
*.pid.lock
# Coverage directory used by tools like istanbul
coverage/
# nyc test coverage
.nyc_output
# Dependency directories
node_modules/
vendor/
# Optional npm cache directory
.npm
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
# IDE files
.vscode/
.idea/
*.swp
*.swo
*~
# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
# Test files
tests/
spec/
test_*
*_test.py
*_spec.rb
# Development files
ai_docs/

71
.env.example Normal file
View File

@@ -0,0 +1,71 @@
# PotatoMesh Environment Configuration
# Copy this file to .env and customize for your setup
# =============================================================================
# REQUIRED SETTINGS
# =============================================================================
# API authentication token (required for ingestor communication)
# Generate a secure token: openssl rand -hex 32
API_TOKEN=your-secure-api-token-here
# Meshtastic device path (required for ingestor)
# Common paths:
# - Linux: /dev/ttyACM0, /dev/ttyUSB0
# - macOS: /dev/cu.usbserial-*
# - Windows (WSL): /dev/ttyS*
MESH_SERIAL=/dev/ttyACM0
# =============================================================================
# SITE CUSTOMIZATION
# =============================================================================
# Your mesh network name
SITE_NAME=My Meshtastic Network
# Default Meshtastic channel
DEFAULT_CHANNEL=#MediumFast
# Default frequency for your region
# Common frequencies: 868MHz (Europe), 915MHz (US), 433MHz (Worldwide)
DEFAULT_FREQUENCY=868MHz
# Map center coordinates (latitude, longitude)
# Berlin, Germany: 52.502889, 13.404194
# Denver, Colorado: 39.7392, -104.9903
# London, UK: 51.5074, -0.1278
MAP_CENTER_LAT=52.502889
MAP_CENTER_LON=13.404194
# Maximum distance to show nodes (kilometers)
MAX_NODE_DISTANCE_KM=50
# =============================================================================
# OPTIONAL INTEGRATIONS
# =============================================================================
# Matrix chat room for your community (optional)
# Format: !roomid:matrix.org
MATRIX_ROOM='#meshtastic-berlin:matrix.org'
# =============================================================================
# ADVANCED SETTINGS
# =============================================================================
# Debug mode (0=off, 1=on)
DEBUG=0
# Meshtastic snapshot interval (seconds)
MESH_SNAPSHOT_SECS=60
# Meshtastic channel index (0=primary, 1=secondary, etc.)
MESH_CHANNEL_INDEX=0
# Database settings
DB_BUSY_TIMEOUT_MS=5000
DB_BUSY_MAX_RETRIES=5
DB_BUSY_RETRY_DELAY=0.05
# Application settings
MAX_JSON_BODY_BYTES=1048576

18
.github/workflows/README.md vendored Normal file
View File

@@ -0,0 +1,18 @@
# GitHub Actions Workflows
## Workflows
- **`docker.yml`** - Build and push Docker images to GHCR
- **`codeql.yml`** - Security scanning
- **`python.yml`** - Python testing
- **`ruby.yml`** - Ruby testing
## Usage
```bash
# Build locally
docker-compose build
# Deploy
docker-compose up -d
```

167
.github/workflows/docker.yml vendored Normal file
View File

@@ -0,0 +1,167 @@
name: Build and Push Docker Images
on:
push:
tags: [ 'v*' ]
workflow_dispatch:
inputs:
version:
description: 'Version to publish (e.g., 1.0.0)'
required: true
default: '1.0.0'
publish_all_variants:
description: 'Publish all Docker image variants (latest tag)'
type: boolean
default: false
env:
REGISTRY: ghcr.io
IMAGE_PREFIX: l5yth/potato-mesh
jobs:
build-and-push:
runs-on: ubuntu-latest
if: (startsWith(github.ref, 'refs/tags/v') && github.event_name == 'push') || github.event_name == 'workflow_dispatch'
environment: production
permissions:
contents: read
packages: write
strategy:
matrix:
service: [web, ingestor]
architecture:
- { name: linux-amd64, platform: linux/amd64, label: "Linux x86_64" }
- { name: windows-amd64, platform: windows/amd64, label: "Windows x86_64" }
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract version from tag or input
id: version
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
VERSION="${{ github.event.inputs.version }}"
else
VERSION=${GITHUB_REF#refs/tags/v}
fi
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "Published version: $VERSION"
- name: Build and push ${{ matrix.service }} for ${{ matrix.architecture.name }}
uses: docker/build-push-action@v5
with:
context: .
file: ./${{ matrix.service == 'web' && 'web/Dockerfile' || 'data/Dockerfile' }}
target: production
platforms: ${{ matrix.architecture.platform }}
push: true
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-${{ matrix.service }}-${{ matrix.architecture.name }}:latest
${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-${{ matrix.service }}-${{ matrix.architecture.name }}:${{ steps.version.outputs.version }}
labels: |
org.opencontainers.image.source=https://github.com/${{ github.repository }}
org.opencontainers.image.description=PotatoMesh ${{ matrix.service == 'web' && 'Web Application' || 'Python Ingestor' }} for ${{ matrix.architecture.label }}
org.opencontainers.image.licenses=Apache-2.0
org.opencontainers.image.version=${{ steps.version.outputs.version }}
org.opencontainers.image.created=${{ github.event.head_commit.timestamp }}
org.opencontainers.image.revision=${{ github.sha }}
org.opencontainers.image.title=PotatoMesh ${{ matrix.service == 'web' && 'Web' || 'Ingestor' }} (${{ matrix.architecture.label }})
org.opencontainers.image.vendor=PotatoMesh
org.opencontainers.image.architecture=${{ matrix.architecture.name }}
org.opencontainers.image.os=linux
org.opencontainers.image.arch=${{ matrix.architecture.name }}
cache-from: type=gha,scope=${{ matrix.service }}-${{ matrix.architecture.name }}
cache-to: type=gha,mode=max,scope=${{ matrix.service }}-${{ matrix.architecture.name }}
test-images:
runs-on: ubuntu-latest
needs: build-and-push
if: startsWith(github.ref, 'refs/tags/v') && github.event_name == 'push'
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract version from tag
id: version
run: |
VERSION=${GITHUB_REF#refs/tags/v}
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Test web application (Linux AMD64)
run: |
docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-web-linux-amd64:${{ steps.version.outputs.version }}
docker run --rm -d --name web-test -p 41447:41447 \
-e API_TOKEN=test-token \
-e DEBUG=1 \
${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-web-linux-amd64:${{ steps.version.outputs.version }}
sleep 10
curl -f http://localhost:41447/ || exit 1
docker stop web-test
- name: Test ingestor (Linux AMD64)
run: |
docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-ingestor-linux-amd64:${{ steps.version.outputs.version }}
docker run --rm --name ingestor-test \
-e POTATOMESH_INSTANCE=http://localhost:41447 \
-e API_TOKEN=test-token \
-e DEBUG=1 \
${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-ingestor-linux-amd64:${{ steps.version.outputs.version }} &
sleep 5
docker stop ingestor-test || true
publish-summary:
runs-on: ubuntu-latest
needs: [build-and-push, test-images]
if: always() && startsWith(github.ref, 'refs/tags/v') && github.event_name == 'push'
steps:
- name: Extract version from tag
id: version
run: |
VERSION=${GITHUB_REF#refs/tags/v}
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Publish release summary
run: |
echo "## 🚀 PotatoMesh Images Published to GHCR" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Version:** ${{ steps.version.outputs.version }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Published Images:**" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
# Web images
echo "### 🌐 Web Application" >> $GITHUB_STEP_SUMMARY
echo "- \`${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-web-linux-amd64:latest\` - Linux x86_64" >> $GITHUB_STEP_SUMMARY
echo "- \`${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-web-windows-amd64:latest\` - Windows x86_64" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
# Ingestor images
echo "### 📡 Ingestor Service" >> $GITHUB_STEP_SUMMARY
echo "- \`${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-ingestor-linux-amd64:latest\` - Linux x86_64" >> $GITHUB_STEP_SUMMARY
echo "- \`${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}-ingestor-windows-amd64:latest\` - Windows x86_64" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY

5
.gitignore vendored
View File

@@ -11,7 +11,7 @@
/tmp/ /tmp/
# Used by dotenv library to load environment variables. # Used by dotenv library to load environment variables.
# .env .env
# Ignore Byebug command history file. # Ignore Byebug command history file.
.byebug_history .byebug_history
@@ -62,3 +62,6 @@ coverage/
coverage.xml coverage.xml
htmlcov/ htmlcov/
reports/ reports/
# AI planning and documentation
ai_docs/

View File

@@ -1,5 +1,9 @@
# CHANGELOG # CHANGELOG
## v0.3.0
* Add comprehensive Docker support with multi-architecture builds and automated CI/CD by @trose in <https://github.com/l5yth/potato-mesh/pull/122>
## v0.2.0 ## v0.2.0
* Update readme for 0.2 by @l5yth in <https://github.com/l5yth/potato-mesh/pull/118> * Update readme for 0.2 by @l5yth in <https://github.com/l5yth/potato-mesh/pull/118>

88
DOCKER.md Normal file
View File

@@ -0,0 +1,88 @@
# PotatoMesh Docker Setup
## Quick Start
```bash
./configure.sh
docker-compose up -d
docker-compose logs -f
```
Access at `http://localhost:41447`
## Configuration
Edit `.env` file or run `./configure.sh` to set:
- `API_TOKEN` - Required for ingestor authentication
- `MESH_SERIAL` - Your Meshtastic device path (e.g., `/dev/ttyACM0`)
- `SITE_NAME` - Your mesh network name
- `MAP_CENTER_LAT/LON` - Map center coordinates
## Device Setup
**Find your device:**
```bash
# Linux
ls /dev/ttyACM* /dev/ttyUSB*
# macOS
ls /dev/cu.usbserial-*
# Windows
ls /dev/ttyS*
```
**Set permissions (Linux/macOS):**
```bash
sudo chmod 666 /dev/ttyACM0
# Or add user to dialout group
sudo usermod -a -G dialout $USER
```
## Common Commands
```bash
# Start services
docker-compose up -d
# View logs
docker-compose logs -f
# Stop services
docker-compose down
# Stop and remove data
docker-compose down -v
# Update images
docker-compose pull && docker-compose up -d
```
## Troubleshooting
**Device access issues:**
```bash
# Check device exists and permissions
ls -la /dev/ttyACM0
# Fix permissions
sudo chmod 666 /dev/ttyACM0
```
**Port conflicts:**
```bash
# Find what's using port 41447
sudo lsof -i :41447
```
**Container issues:**
```bash
# Check logs
docker-compose logs
# Restart services
docker-compose restart
```
For more Docker help, see [Docker Compose documentation](https://docs.docker.com/compose/).

View File

@@ -18,6 +18,14 @@ Live demo for Berlin #MediumFast: [potatomesh.net](https://potatomesh.net)
![screenshot of the second version](./scrot-0.2.png) ![screenshot of the second version](./scrot-0.2.png)
## 🐳 Quick Start with Docker
```bash
./configure.sh # Configure your setup
docker-compose up -d # Start services
docker-compose logs -f # View logs
```
## Web App ## Web App
Requires Ruby for the Sinatra web app and SQLite3 for the app's database. Requires Ruby for the Sinatra web app and SQLite3 for the app's database.

155
configure.sh Executable file
View File

@@ -0,0 +1,155 @@
#!/bin/bash
# PotatoMesh Configuration Script
# This script helps you configure your PotatoMesh instance with your local settings
set -e
echo "🥔 PotatoMesh Configuration"
echo "=========================="
echo ""
# Check if .env exists, if not create from .env.example
if [ ! -f .env ]; then
if [ -f .env.example ]; then
echo "📋 Creating .env file from .env.example..."
cp .env.example .env
else
echo "📋 Creating new .env file..."
touch .env
fi
fi
echo "🔧 Let's configure your PotatoMesh instance!"
echo ""
# Function to read input with default
read_with_default() {
local prompt="$1"
local default="$2"
local var_name="$3"
if [ -n "$default" ]; then
read -p "$prompt [$default]: " input
input=${input:-$default}
else
read -p "$prompt: " input
fi
eval "$var_name='$input'"
}
# Function to update .env file
update_env() {
local key="$1"
local value="$2"
if grep -q "^$key=" .env; then
# Update existing value
sed -i.bak "s/^$key=.*/$key=$value/" .env
else
# Add new value
echo "$key=$value" >> .env
fi
}
# Get current values from .env if they exist
SITE_NAME=$(grep "^SITE_NAME=" .env 2>/dev/null | cut -d'=' -f2- | tr -d '"' || echo "My Meshtastic Network")
DEFAULT_CHANNEL=$(grep "^DEFAULT_CHANNEL=" .env 2>/dev/null | cut -d'=' -f2- | tr -d '"' || echo "#MediumFast")
DEFAULT_FREQUENCY=$(grep "^DEFAULT_FREQUENCY=" .env 2>/dev/null | cut -d'=' -f2- | tr -d '"' || echo "868MHz")
MAP_CENTER_LAT=$(grep "^MAP_CENTER_LAT=" .env 2>/dev/null | cut -d'=' -f2- | tr -d '"' || echo "52.502889")
MAP_CENTER_LON=$(grep "^MAP_CENTER_LON=" .env 2>/dev/null | cut -d'=' -f2- | tr -d '"' || echo "13.404194")
MAX_NODE_DISTANCE_KM=$(grep "^MAX_NODE_DISTANCE_KM=" .env 2>/dev/null | cut -d'=' -f2- | tr -d '"' || echo "50")
MATRIX_ROOM=$(grep "^MATRIX_ROOM=" .env 2>/dev/null | cut -d'=' -f2- | tr -d '"' || echo "")
API_TOKEN=$(grep "^API_TOKEN=" .env 2>/dev/null | cut -d'=' -f2- | tr -d '"' || echo "")
echo "📍 Location Settings"
echo "-------------------"
read_with_default "Site Name (your mesh network name)" "$SITE_NAME" SITE_NAME
read_with_default "Map Center Latitude" "$MAP_CENTER_LAT" MAP_CENTER_LAT
read_with_default "Map Center Longitude" "$MAP_CENTER_LON" MAP_CENTER_LON
read_with_default "Max Node Distance (km)" "$MAX_NODE_DISTANCE_KM" MAX_NODE_DISTANCE_KM
echo ""
echo "📡 Meshtastic Settings"
echo "---------------------"
read_with_default "Default Channel" "$DEFAULT_CHANNEL" DEFAULT_CHANNEL
read_with_default "Default Frequency (868MHz, 915MHz, etc.)" "$DEFAULT_FREQUENCY" DEFAULT_FREQUENCY
echo ""
echo "💬 Optional Settings"
echo "-------------------"
read_with_default "Matrix Room (optional, e.g., #meshtastic-berlin:matrix.org)" "$MATRIX_ROOM" MATRIX_ROOM
echo ""
echo "🔐 Security Settings"
echo "-------------------"
echo "The API token is used for secure communication between the web app and ingestor."
echo "You can provide your own custom token or let us generate a secure one for you."
echo ""
if [ -z "$API_TOKEN" ]; then
echo "No existing API token found. Generating a secure token..."
API_TOKEN=$(openssl rand -hex 32 2>/dev/null || python3 -c "import secrets; print(secrets.token_hex(32))" 2>/dev/null || echo "your-secure-api-token-here")
echo "✅ Generated secure API token: ${API_TOKEN:0:8}..."
echo ""
read -p "Use this generated token? (Y/n): " use_generated
if [[ "$use_generated" =~ ^[Nn]$ ]]; then
read -p "Enter your custom API token: " API_TOKEN
fi
else
echo "Existing API token found: ${API_TOKEN:0:8}..."
read -p "Keep existing token? (Y/n): " keep_existing
if [[ "$keep_existing" =~ ^[Nn]$ ]]; then
read -p "Enter new API token (or press Enter to generate): " new_token
if [ -n "$new_token" ]; then
API_TOKEN="$new_token"
else
echo "Generating new secure token..."
API_TOKEN=$(openssl rand -hex 32 2>/dev/null || python3 -c "import secrets; print(secrets.token_hex(32))" 2>/dev/null || echo "your-secure-api-token-here")
echo "✅ Generated new API token: ${API_TOKEN:0:8}..."
fi
fi
fi
echo ""
echo "📝 Updating .env file..."
# Update .env file
update_env "SITE_NAME" "\"$SITE_NAME\""
update_env "DEFAULT_CHANNEL" "\"$DEFAULT_CHANNEL\""
update_env "DEFAULT_FREQUENCY" "\"$DEFAULT_FREQUENCY\""
update_env "MAP_CENTER_LAT" "$MAP_CENTER_LAT"
update_env "MAP_CENTER_LON" "$MAP_CENTER_LON"
update_env "MAX_NODE_DISTANCE_KM" "$MAX_NODE_DISTANCE_KM"
update_env "MATRIX_ROOM" "\"$MATRIX_ROOM\""
update_env "API_TOKEN" "$API_TOKEN"
# Add other common settings if they don't exist
if ! grep -q "^MESH_SERIAL=" .env; then
echo "MESH_SERIAL=/dev/ttyACM0" >> .env
fi
if ! grep -q "^DEBUG=" .env; then
echo "DEBUG=0" >> .env
fi
# Clean up backup file
rm -f .env.bak
echo ""
echo "✅ Configuration complete!"
echo ""
echo "📋 Your settings:"
echo " Site Name: $SITE_NAME"
echo " Map Center: $MAP_CENTER_LAT, $MAP_CENTER_LON"
echo " Max Distance: ${MAX_NODE_DISTANCE_KM}km"
echo " Channel: $DEFAULT_CHANNEL"
echo " Frequency: $DEFAULT_FREQUENCY"
echo " Matrix Room: ${MATRIX_ROOM:-'Not set'}"
echo " API Token: ${API_TOKEN:0:8}..."
echo ""
echo "🚀 You can now start PotatoMesh with:"
echo " docker-compose up -d"
echo ""
echo "📖 For more configuration options, see the README.md"

55
data/Dockerfile Normal file
View File

@@ -0,0 +1,55 @@
# Multi-stage build for PotatoMesh Data Ingestor
FROM python:3.13-alpine AS builder
# Install build dependencies
RUN apk add --no-cache \
gcc \
musl-dev \
linux-headers \
build-base
# Set working directory
WORKDIR /app
# Copy requirements and install Python dependencies
COPY data/requirements.txt ./
RUN pip install --no-cache-dir --user -r requirements.txt
# Production stage
FROM python:3.13-alpine AS production
# Install runtime dependencies
RUN apk add --no-cache \
tzdata \
curl
# Create non-root user and add to dialout group for serial access
RUN addgroup -g 1000 -S potatomesh && \
adduser -u 1000 -S potatomesh -G potatomesh && \
adduser potatomesh dialout
# Set working directory
WORKDIR /app
# Copy installed Python packages from builder stage
COPY --from=builder /root/.local /home/potatomesh/.local
# Copy application code
COPY --chown=potatomesh:potatomesh data/ .
# Switch to non-root user
USER potatomesh
# Add local Python packages to PATH
ENV PATH=/home/potatomesh/.local/bin:$PATH
# Default environment variables (can be overridden by host)
ENV MESH_SERIAL=/dev/ttyACM0 \
MESH_SNAPSHOT_SECS=60 \
MESH_CHANNEL_INDEX=0 \
DEBUG=0 \
POTATOMESH_INSTANCE="" \
API_TOKEN=""
# Start the mesh daemon
CMD ["python", "mesh.py"]

7
data/requirements.txt Normal file
View File

@@ -0,0 +1,7 @@
# Production dependencies
meshtastic>=2.0.0
protobuf>=4.21.12
# Development dependencies (optional)
black>=23.0.0
pytest>=7.0.0

21
docker-compose.dev.yml Normal file
View File

@@ -0,0 +1,21 @@
version: '3.8'
# Development overrides for docker-compose.yml
services:
web:
environment:
- DEBUG=1
volumes:
- ./web:/app
- ./data:/data # Mount data directory for SQL files
- /app/vendor/bundle # Exclude vendor directory from volume mount
ports:
- "41447:41447"
- "9292:9292" # Additional port for development tools
ingestor:
environment:
- DEBUG=1
volumes:
- ./data:/app
- /app/.local # Exclude Python packages from volume mount

33
docker-compose.prod.yml Normal file
View File

@@ -0,0 +1,33 @@
version: '3.8'
# Production overrides for docker-compose.yml
services:
web:
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
restart: always
deploy:
resources:
limits:
memory: 256M
cpus: '0.25'
reservations:
memory: 128M
cpus: '0.1'

71
docker-compose.yml Normal file
View File

@@ -0,0 +1,71 @@
version: '3.8'
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'
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
networks:
- potatomesh-network
depends_on:
- web
restart: unless-stopped
deploy:
resources:
limits:
memory: 256M
cpus: '0.25'
reservations:
memory: 128M
cpus: '0.1'
volumes:
potatomesh_data:
driver: local
potatomesh_logs:
driver: local

74
web/Dockerfile Normal file
View File

@@ -0,0 +1,74 @@
# Main application builder stage
FROM ruby:3.4-alpine AS builder
# Install build dependencies and SQLite3
RUN apk add --no-cache \
build-base \
sqlite-dev \
linux-headers \
pkgconfig
# Set working directory
WORKDIR /app
# Copy Gemfile and install dependencies
COPY web/Gemfile web/Gemfile.lock* ./
# Install gems with SQLite3 support
RUN bundle config set --local without 'development test' && \
bundle install --jobs=4 --retry=3
# Production stage
FROM ruby:3.4-alpine AS production
# Install runtime dependencies
RUN apk add --no-cache \
sqlite \
tzdata \
curl
# Create non-root user
RUN addgroup -g 1000 -S potatomesh && \
adduser -u 1000 -S potatomesh -G potatomesh
# Set working directory
WORKDIR /app
# Copy installed gems from builder stage
COPY --from=builder /usr/local/bundle /usr/local/bundle
# Copy application code (exclude Dockerfile from web directory)
COPY --chown=potatomesh:potatomesh web/app.rb web/app.sh web/Gemfile web/Gemfile.lock* web/public/ web/spec/ ./
COPY --chown=potatomesh:potatomesh web/views/ ./views/
# Copy SQL schema files from data directory
COPY --chown=potatomesh:potatomesh data/*.sql /data/
# Create data directory for SQLite database
RUN mkdir -p /app/data && \
chown -R potatomesh:potatomesh /app/data
# Switch to non-root user
USER potatomesh
# Expose port
EXPOSE 41447
# Default environment variables (can be overridden by host)
ENV APP_ENV=production \
MESH_DB=/app/data/mesh.db \
DB_BUSY_TIMEOUT_MS=5000 \
DB_BUSY_MAX_RETRIES=5 \
DB_BUSY_RETRY_DELAY=0.05 \
MAX_JSON_BODY_BYTES=1048576 \
SITE_NAME="Berlin Mesh Network" \
DEFAULT_CHANNEL="#MediumFast" \
DEFAULT_FREQUENCY="868MHz" \
MAP_CENTER_LAT=52.502889 \
MAP_CENTER_LON=13.404194 \
MAX_NODE_DISTANCE_KM=50 \
MATRIX_ROOM="" \
DEBUG=0
# Start the application
CMD ["ruby", "app.rb", "-p", "41447", "-o", "0.0.0.0"]