diff --git a/Dockerfile b/Dockerfile index 7bcd21f..90ac68c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,18 +1,54 @@ -FROM python:3.11-slim +# MeshAI Dockerfile +# LLM-powered Meshtastic assistant +# +# Build: docker build -t meshai . +# Run: docker run -d --name meshai \ +# --device=/dev/ttyUSB0 \ +# -p 7681:7681 \ +# -v meshai_data:/data \ +# meshai + +FROM python:3.11-slim-bookworm LABEL maintainer="K7ZVX " LABEL description="MeshAI - LLM-powered Meshtastic assistant" +LABEL version="0.1.0" + +# Build arguments +ARG UID=1000 +ARG GID=1000 + +# Environment variables +ENV PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 \ + PIP_NO_CACHE_DIR=1 \ + PIP_DISABLE_PIP_VERSION_CHECK=1 # Install system dependencies RUN apt-get update && apt-get install -y --no-install-recommends \ gcc \ libc6-dev \ - && rm -rf /var/lib/apt/lists/* + # For serial communication + udev \ + # For health checks + curl \ + # For process management + procps \ + && rm -rf /var/lib/apt/lists/* \ + # Install ttyd for web-based config interface + && curl -sL https://github.com/tsl0922/ttyd/releases/download/1.7.7/ttyd.x86_64 -o /usr/local/bin/ttyd \ + && chmod +x /usr/local/bin/ttyd # Create non-root user -RUN useradd -m -s /bin/bash meshai +RUN groupadd -g ${GID} meshai && \ + useradd -u ${UID} -g ${GID} -m -s /bin/bash meshai && \ + # Add to dialout group for serial access + usermod -aG dialout meshai + +# Create directories +RUN mkdir -p /app /data && \ + chown -R meshai:meshai /app /data -# Set working directory WORKDIR /app # Copy requirements first for layer caching @@ -20,22 +56,27 @@ COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # Copy application code -COPY meshai/ ./meshai/ -COPY pyproject.toml . -COPY README.md . +COPY --chown=meshai:meshai meshai/ ./meshai/ +COPY --chown=meshai:meshai pyproject.toml . +COPY --chown=meshai:meshai README.md . +COPY --chown=meshai:meshai config.example.yaml . +COPY --chown=meshai:meshai docker-entrypoint.sh . -# Install the package and fix permissions -RUN pip install --no-cache-dir -e . && \ - chown -R meshai:meshai /app - -# Create data directory for config and database -RUN mkdir -p /data && chown meshai:meshai /data +# Install the package +RUN pip install --no-cache-dir -e . # Switch to non-root user USER meshai -# Set working directory to data for config files -WORKDIR /data +# Data volume mount point +VOLUME ["/data"] -# Default command -CMD ["python", "-m", "meshai"] +# Expose ttyd web config port +EXPOSE 7681 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \ + CMD python -c "import sqlite3; sqlite3.connect('/data/conversations.db').execute('SELECT 1')" || exit 1 + +# Entrypoint handles config and ttyd +ENTRYPOINT ["/app/docker-entrypoint.sh"] diff --git a/docker-compose.yml b/docker-compose.yml index e30d720..921e8cf 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,19 +1,74 @@ +# MeshAI Docker Compose Configuration +# +# Usage: +# docker compose up -d # Start bot + web config +# docker compose logs -f # View logs +# +# Web config: http://localhost:7681 (TUI in browser) +# +# Config is stored in the meshai_data volume at /data/config.yaml +# +# For serial connection (USB), uncomment the devices section below +# For TCP connection, configure via web interface + services: meshai: - build: . + build: + context: . + dockerfile: Dockerfile + args: + UID: ${UID:-1000} + GID: ${GID:-1000} + image: meshai:latest container_name: meshai restart: unless-stopped - volumes: - # Config and database persistence - - ./data:/data - # For serial connection - uncomment and adjust device path - # - /dev/ttyUSB0:/dev/ttyUSB0 - # For serial connection - uncomment + + # Uncomment for USB serial connection to Meshtastic device # devices: # - /dev/ttyUSB0:/dev/ttyUSB0 - # privileged: true # May be needed for serial access + # - /dev/ttyACM0:/dev/ttyACM0 + + ports: + # Web-based config interface (ttyd) + - "7681:7681" + + volumes: + # Persistent data (database, config) + - meshai_data:/data + + # Run interactively for first-time setup wizard + stdin_open: true + tty: true + environment: # API key can be set here or in config.yaml - LLM_API_KEY=${LLM_API_KEY:-} - # For TCP connection, ensure network access to Meshtastic node - # network_mode: host # Uncomment if needed for local network access + + # Limit resources + deploy: + resources: + limits: + memory: 256M + reservations: + memory: 64M + + healthcheck: + test: ["CMD", "python", "-c", "import sqlite3; sqlite3.connect('/data/conversations.db').execute('SELECT 1')"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 15s + + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "3" + +volumes: + meshai_data: + name: meshai_data + +networks: + default: + name: meshai_network diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh new file mode 100755 index 0000000..fe83160 --- /dev/null +++ b/docker-entrypoint.sh @@ -0,0 +1,101 @@ +#!/bin/bash +# MeshAI Docker Entrypoint +# Runs ttyd for web config access and the bot + +export MESHAI_CONFIG="/data/config.yaml" +export TERM="${TERM:-xterm-256color}" + +# First run - no config exists, create defaults +if [ ! -f "$MESHAI_CONFIG" ]; then + mkdir -p /data + cat > "$MESHAI_CONFIG" << 'EOF' +# MeshAI Configuration +# Configure via http://localhost:7681 + +bot: + name: ai + owner: "" + respond_to_mentions: true + respond_to_dms: true + +connection: + type: tcp + serial_port: /dev/ttyUSB0 + tcp_host: localhost + tcp_port: 4403 + +channels: + mode: all + whitelist: + - 0 + +response: + delay_min: 2.2 + delay_max: 3.0 + max_length: 150 + max_messages: 2 + +history: + database: /data/conversations.db + max_messages_per_user: 20 + conversation_timeout: 86400 + +memory: + enabled: true + window_size: 4 + summarize_threshold: 8 + +llm: + backend: openai + api_key: "" + base_url: https://api.openai.com/v1 + model: gpt-4o-mini + system_prompt: >- + You are a helpful assistant on a Meshtastic mesh network. + Keep responses VERY brief - under 250 characters total. + Be concise but friendly. No markdown formatting. + +weather: + primary: openmeteo + fallback: llm + default_location: "" + openmeteo: + url: https://api.open-meteo.com/v1 + wttr: + url: https://wttr.in +EOF + echo "Default config created. Configure via http://localhost:7681" +fi + +# Start ttyd for web-based config access +echo "Starting web config interface on port 7681..." +ttyd -W -p 7681 \ + -t titleFixed="MeshAI Config" \ + -t 'theme={"background":"#0d1117","foreground":"#c9d1d9","cursor":"#58a6ff","selectionBackground":"#388bfd"}' \ + -t fontSize=14 \ + /bin/bash -c 'while true; do python3 -m meshai --config-file "$MESHAI_CONFIG" --config; sleep 1; done' & + +# Keep ttyd running even if bot fails +trap "kill %1 2>/dev/null; kill %2 2>/dev/null" EXIT + +# Restart watcher - monitors for restart signal from config tool +( + while true; do + if [ -f /tmp/meshai_restart ]; then + rm -f /tmp/meshai_restart + echo "Restart signal received, restarting bot..." + pkill -f "python.*meshai.*--config" --signal 0 2>/dev/null || true # Don't kill config tool + pkill -f "python -m meshai --config-file" || true + sleep 1 + fi + sleep 2 + done +) & + +# Start the bot in a loop - retry on failure +echo "Starting MeshAI..." +while true; do + python -m meshai --config-file "$MESHAI_CONFIG" || true + echo "Bot exited. Check config at http://localhost:7681. Retrying in 5s..." + sleep 5 +done