diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 1747dc4..48673fa 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -30,10 +30,15 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 + # QEMU is required for cross-platform builds (arm64, arm/v7). + # Without it, BuildKit cannot emulate non-native architectures. + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + with: + platforms: linux/amd64,linux/arm64,linux/arm/v7 + - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - with: - platforms: linux/amd64,linux/arm64 - name: Log in to Container Registry if: github.event_name != 'pull_request' @@ -67,16 +72,23 @@ jobs: fi - name: Build and push Docker image - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 with: context: . + # Push on all non-PR events; PRs only validate the build. push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} build-args: | MESHCORE_BOT_VERSION=${{ steps.version.outputs.version }} + # BuildKit layer cache via GitHub Actions cache backend. cache-from: type=gha cache-to: type=gha,mode=max - platforms: linux/amd64,linux/arm64 - # Build for both amd64 and arm64 architectures - # This enables the image to run on both x86_64 and ARM devices (Raspberry Pi, Orange Pi, etc.) + # Supported platforms: + # linux/amd64 — x86-64 servers / desktops + # linux/arm64 — Raspberry Pi 4/5 (64-bit OS), Apple M-series via Rosetta + # linux/arm/v7 — Raspberry Pi 3 and older (32-bit Raspbian / Raspberry Pi OS) + platforms: linux/amd64,linux/arm64,linux/arm/v7 + # Provenance attestations improve supply-chain transparency (SLSA level 1). + provenance: true + sbom: true diff --git a/Dockerfile b/Dockerfile index 8c8e1b9..a31c017 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,66 +1,72 @@ # Multi-stage build for meshcore-bot +# Supports: linux/amd64, linux/arm64 (RPi 4/5, 64-bit OS), linux/arm/v7 (RPi 3, 32-bit OS) + +# ── builder stage ────────────────────────────────────────────────────────── FROM python:3.11-slim AS builder -# Install build dependencies (with cache mount for apt) -RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ - --mount=type=cache,target=/var/lib/apt,sharing=locked \ +# TARGETPLATFORM is injected by BuildKit for each platform in the matrix. +# Useful for platform-specific build steps if needed in future. +ARG TARGETPLATFORM +ARG TARGETARCH + +# Install build dependencies. +# apt cache mounts are scoped per-architecture to avoid cross-contamination. +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=apt-$TARGETARCH \ + --mount=type=cache,target=/var/lib/apt,sharing=locked,id=apt-lib-$TARGETARCH \ apt-get update && apt-get install -y --no-install-recommends \ build-essential \ && rm -rf /var/lib/apt/lists/* -# Set working directory WORKDIR /build -# Copy dependency files COPY requirements.txt pyproject.toml ./ -# Install Python dependencies (with cache mount for pip) -RUN --mount=type=cache,target=/root/.cache/pip \ +# Pip cache is scoped per-architecture. +RUN --mount=type=cache,target=/root/.cache/pip,id=pip-$TARGETARCH \ pip install --user -r requirements.txt -# Final stage +# ── runtime stage ────────────────────────────────────────────────────────── FROM python:3.11-slim -# Install runtime dependencies (with cache mount for apt) -RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ - --mount=type=cache,target=/var/lib/apt,sharing=locked \ +ARG TARGETARCH + +# Runtime system packages. +# libbluetooth3 is available on amd64, arm64, and armhf (arm/v7). +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=apt-$TARGETARCH \ + --mount=type=cache,target=/var/lib/apt,sharing=locked,id=apt-lib-$TARGETARCH \ apt-get update && apt-get install -y --no-install-recommends \ - # For serial port access udev \ - # For BLE support (optional, but commonly needed) libbluetooth3 \ - # Cleanup && rm -rf /var/lib/apt/lists/* -# Create non-root user and add to dialout group for serial port access +# Non-root user with dialout group for serial port access. RUN useradd -m -u 1000 -G dialout,tty meshcore && \ mkdir -p /app /data/config /data/databases /data/logs /data/backups && \ chown -R meshcore:meshcore /app /data -# Copy Python dependencies from builder COPY --from=builder /root/.local /home/meshcore/.local -# Set working directory WORKDIR /app -# Version for web viewer footer (set at build time; e.g. --build-arg MESHCORE_BOT_VERSION=v1.2.3) +# Version label for web viewer footer (passed via --build-arg in CI). ARG MESHCORE_BOT_VERSION ENV MESHCORE_BOT_VERSION=${MESHCORE_BOT_VERSION} -# Copy application files COPY --chown=meshcore:meshcore . /app/ -# Set PATH to include user's local bin ENV PATH=/home/meshcore/.local/bin:$PATH \ PYTHONUNBUFFERED=1 \ PYTHONDONTWRITEBYTECODE=1 -# Switch to non-root user USER meshcore -# Health check: verify the main process (PID 1, the bot) is still running +# OCI image labels for supply-chain transparency. +LABEL org.opencontainers.image.title="meshcore-bot" \ + org.opencontainers.image.description="MeshCore Bot for mesh radio networks" \ + org.opencontainers.image.source="https://github.com/agessaman/meshcore-bot" + +# Health check: verify PID 1 (the bot process) is still alive. HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \ CMD ["sh", "-c", "kill -0 1"] -# Default command CMD ["python3", "meshcore_bot.py", "--config", "/data/config/config.ini"]