mirror of
https://github.com/zvx-echo6/meshai.git
synced 2026-08-26 17:31:34 +00:00
* docs(readme): reconcile root README as the single source of truth /README.md (renders on GitHub) and work/README.md (packaged by work/pyproject.toml's readme = "README.md") had cross-drifted: each had two of four correct lines. Root had the correct `cd meshai/work` path and work/-prefixed curl URLs; work/README.md had the correct gemini-3.1-flash-lite model (google retired the gemini-2.x lite tier on this project's API key). Pull the one missing fix (model name) into root/README.md. Verified via diff that these were the only 4 lines (8 diff lines) that ever differed between the two files. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * build(work): eliminate work/README.md as a second source of truth The hand-maintained duplicate is what caused the cross-drift fixed in the previous commit. Replace work/README.md with a symlink to ../README.md so there is exactly one file to edit. This required unhooking work/'s packaging from a physical README.md: - setuptools' pyproject reader hard-rejects readme paths outside the project dir (`_assert_local` in setuptools/config/expand.py) — so `readme = "../README.md"` is not an option, tested and confirmed. - The symlink resolves fine for local packaging (pip install -e ., python -m build --sdist/--wheel all tested passing, PKG-INFO correctly carries the root content through the symlink). - It does NOT resolve for the Docker image build: work/Dockerfile's `COPY README.md .` and both work/docker-compose.yml (context: .) and .github/workflows/docker-publish.yml (context: work) pin the build context to work/, which does not contain the symlink's target. Confirmed with an isolated repro: Docker COPY on a symlink whose target is outside the build context fails with "too many links". Repointing the build context at the repo root would touch every COPY path in the Dockerfile plus CI — out of scope here and not worth it for a README. Chose the minimal fix instead: drop `readme = "README.md"` from work/pyproject.toml (meshai isn't published to PyPI — no publish workflow exists, only GHCR image publishing — so there's no long_description to lose in practice) and drop the now-unnecessary `COPY README.md .` from work/Dockerfile. Confirmed a dangling same-named symlink left in the build context, never COPYed, does not break context transfer. Tested end-to-end: a full `docker build -f work/Dockerfile work` against the real Dockerfile succeeded (frontend build, apt deps, pip install -e ., fastembed model fetch), and the resulting image imports meshai and reports correct `pip show` metadata with no README involved. Note for docs/onboarding-via-gui (PR #147) and chore/untrack-dashboard-static: both edited work/README.md, the file GitHub never rendered. That target is gone; their Quick-start content needs to be re-applied to root README.md when those branches rebase. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
98 lines
3.1 KiB
Docker
98 lines
3.1 KiB
Docker
# 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
|
|
|
|
# ── Stage 1: Build frontend ──
|
|
FROM node:20-alpine AS frontend
|
|
WORKDIR /build
|
|
COPY dashboard-frontend/ ./dashboard-frontend/
|
|
WORKDIR /build/dashboard-frontend
|
|
RUN npm ci && npm run build
|
|
# Output lands at /build/meshai/dashboard/static/ (via vite outDir)
|
|
|
|
# ── Stage 2: Python runtime ──
|
|
FROM python:3.11-slim-bookworm
|
|
|
|
LABEL maintainer="K7ZVX <matt@echo6.co>"
|
|
LABEL description="MeshAI - LLM-powered Meshtastic assistant"
|
|
LABEL version="0.1.0"
|
|
LABEL org.opencontainers.image.source=https://github.com/zvx-echo6/meshai
|
|
LABEL org.opencontainers.image.description="MeshAI - LLM-powered Meshtastic assistant"
|
|
LABEL org.opencontainers.image.licenses=MIT
|
|
|
|
# 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 \
|
|
# For serial communication
|
|
udev \
|
|
# For health checks
|
|
curl \
|
|
# For process management
|
|
procps \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
# Create non-root user
|
|
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
|
|
|
|
WORKDIR /app
|
|
|
|
# Copy requirements first for layer caching
|
|
COPY requirements.txt .
|
|
RUN pip install --no-cache-dir -r requirements.txt
|
|
# Pre-download embedding model for hybrid search
|
|
RUN python3 -c "from fastembed import TextEmbedding; TextEmbedding('BAAI/bge-small-en-v1.5')"
|
|
|
|
# Copy application code
|
|
COPY --chown=meshai:meshai meshai/ ./meshai/
|
|
# Overwrite with freshly built frontend assets from stage 1
|
|
COPY --from=frontend --chown=meshai:meshai /build/meshai/dashboard/static/ ./meshai/dashboard/static/
|
|
COPY --chown=meshai:meshai pyproject.toml .
|
|
# Reference only: docker-entrypoint.sh writes its own minimal default to
|
|
# /data/config.yaml on first boot and does NOT read this file. It's shipped
|
|
# for local/pip installs and anyone who wants the legacy single-file schema
|
|
# on hand inside the container (e.g. via `docker compose exec`).
|
|
COPY --chown=meshai:meshai config.example.yaml .
|
|
COPY --chown=meshai:meshai docker-entrypoint.sh .
|
|
|
|
# Install the package
|
|
RUN pip install --no-cache-dir -e .
|
|
|
|
# Switch to non-root user
|
|
USER meshai
|
|
|
|
# Data volume mount point
|
|
VOLUME ["/data"]
|
|
|
|
# Expose dashboard port
|
|
EXPOSE 8080
|
|
|
|
# Health check - verify bot process is alive via PID file
|
|
HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \
|
|
CMD test -f /tmp/meshai.pid && kill -0 $(cat /tmp/meshai.pid) 2>/dev/null && [ "$(cat /tmp/meshai.link 2>/dev/null)" = up ] || exit 1
|
|
|
|
# Entrypoint writes default config on first run, then starts the bot
|
|
ENTRYPOINT ["/app/docker-entrypoint.sh"]
|