From 77db30125a6d44cb4f53e4640ffd7068cb78723c Mon Sep 17 00:00:00 2001 From: Olliver Schinagl Date: Tue, 28 Oct 2025 11:31:54 +0100 Subject: [PATCH 1/2] Container: Add initial container for meshtastic-cli Just a quick set of files to enable the build of (tagged) containers. Both alpine and debian containers are available (~200MiB/~1.2GiB) allowing us to use meshtastic cli with a quick docker run, instead of having to build/install stuff locally. Signed-off-by: Olliver Schinagl --- .dockerignore | 10 ++++ .github/workflows/container-build.yaml | 77 ++++++++++++++++++++++++++ Containerfile | 1 + Containerfile.alpine | 24 ++++++++ Containerfile.debian | 24 ++++++++ Dockerfile | 1 + bin/container-entrypoint.sh | 24 ++++++++ 7 files changed, 161 insertions(+) create mode 100644 .dockerignore create mode 100644 .github/workflows/container-build.yaml create mode 120000 Containerfile create mode 100644 Containerfile.alpine create mode 100644 Containerfile.debian create mode 120000 Dockerfile create mode 100755 bin/container-entrypoint.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..e8bf254 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,10 @@ +* +!Containerfile* +!Dockerfile +!README.md +!bin/container-entrypoint.sh +!examples/ +!extra/ +!meshtastic/ +!poetry.lock +!pyproject.toml diff --git a/.github/workflows/container-build.yaml b/.github/workflows/container-build.yaml new file mode 100644 index 0000000..bae372d --- /dev/null +++ b/.github/workflows/container-build.yaml @@ -0,0 +1,77 @@ +name: Create and publish Container image + +on: + push: + branches: + - master + tags: + - '*.*.*' + pull_request: + branches: + - master + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + build-and-push-image: + runs-on: ubuntu-latest + continue-on-error: true + permissions: + contents: read + packages: write + strategy: + matrix: + include: + - container: Containerfile.debian + autotag: false + suffix: -debian + - container: Containerfile.alpine + autotag: ${{ github.ref == 'refs/heads/master' && 'true' || 'auto' }} + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Login to Container registry + uses: docker/login-action@v2 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Docker meta + id: meta + uses: docker/metadata-action@v4 + with: + images: | + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=branch + type=ref,event=pr + type=edge + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + flavor: | + latest=${{ matrix.autotag }} + suffix=${{ matrix.suffix }} + + - name: Build and push + uses: docker/build-push-action@v4 + with: + platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v7,linux/arm/v6 + context: . + file: ${{ matrix.container }} + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/Containerfile b/Containerfile new file mode 120000 index 0000000..83ac7f4 --- /dev/null +++ b/Containerfile @@ -0,0 +1 @@ +Containerfile.alpine \ No newline at end of file diff --git a/Containerfile.alpine b/Containerfile.alpine new file mode 100644 index 0000000..0cf2004 --- /dev/null +++ b/Containerfile.alpine @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# Copyright (C) 2025 Olliver Schinagl + +ARG TARGET_VERSION="3.11-alpine" +ARG TARGET_ARCH="library" + +FROM docker.io/${TARGET_ARCH}/python:${TARGET_VERSION} + +WORKDIR /usr/local/app + +COPY . /usr/local/app + +RUN _poetry_venv_dir="$(mktemp -d -p "${TMPDIR:-/tmp}" 'poetry_venv.XXXXXX')" && \ + python -m 'venv' "${_poetry_venv_dir}" && \ + "${_poetry_venv_dir}/bin/pip" install 'poetry' && \ + "${_poetry_venv_dir}/bin/poetry" config --local virtualenvs.create false && \ + "${_poetry_venv_dir}/bin/poetry" install && \ + rm -f -r "${_poetry_venv_dir}" && \ + rm -f -r "/usr/local/app" + +COPY "./bin/container-entrypoint.sh" "/init" + +ENTRYPOINT [ "/init" ] diff --git a/Containerfile.debian b/Containerfile.debian new file mode 100644 index 0000000..561c890 --- /dev/null +++ b/Containerfile.debian @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# Copyright (C) 2025 Olliver Schinagl + +ARG TARGET_VERSION="3.11" +ARG TARGET_ARCH="library" + +FROM docker.io/${TARGET_ARCH}/python:${TARGET_VERSION} + +WORKDIR /usr/local/app + +COPY . /usr/local/app + +RUN _poetry_venv_dir="$(mktemp -d -p "${TMPDIR:-/tmp}" 'poetry_venv.XXXXXX')" && \ + python -m 'venv' "${_poetry_venv_dir}" && \ + "${_poetry_venv_dir}/bin/pip" install 'poetry' && \ + "${_poetry_venv_dir}/bin/poetry" config --local virtualenvs.create false && \ + "${_poetry_venv_dir}/bin/poetry" install --no-directory && \ + rm -f -r "${_poetry_venv_dir}" && \ + rm -f -r "/usr/local/app" + +COPY "./bin/container-entrypoint.sh" "/init" + +ENTRYPOINT [ "/init" ] diff --git a/Dockerfile b/Dockerfile new file mode 120000 index 0000000..5240dc0 --- /dev/null +++ b/Dockerfile @@ -0,0 +1 @@ +Containerfile \ No newline at end of file diff --git a/bin/container-entrypoint.sh b/bin/container-entrypoint.sh new file mode 100755 index 0000000..4aeb841 --- /dev/null +++ b/bin/container-entrypoint.sh @@ -0,0 +1,24 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-3.0-or-later +# +# Copyright (C) 2025 Olliver Schinagl +# +# A beginning user should be able to docker run image bash (or sh) without +# needing to learn about --entrypoint +# https://github.com/docker-library/official-images#consistency + +set -eu + +bin='meshtastic' + +# run command if it is not starting with a "-" and is an executable in PATH +if [ "${#}" -le 0 ] || \ + [ "${1#-}" != "${1}" ] || \ + [ -d "${1}" ] || \ + ! command -v "${1}" > '/dev/null' 2>&1; then + entrypoint='true' +fi + +exec ${entrypoint:+${bin:?}} "${@}" + +exit 0 From b16441378e54073e6c8a6670940070e92ba2dcbc Mon Sep 17 00:00:00 2001 From: Ian McEwen Date: Sun, 31 May 2026 15:45:02 -0700 Subject: [PATCH 2/2] Harden a bit, update some sections, add a README section --- .github/workflows/container-build.yaml | 16 ++++++++-------- Containerfile.alpine | 16 +++++++++++----- Containerfile.debian | 15 ++++++++++----- README.md | 18 ++++++++++++++++++ 4 files changed, 47 insertions(+), 18 deletions(-) diff --git a/.github/workflows/container-build.yaml b/.github/workflows/container-build.yaml index bae372d..1ae6659 100644 --- a/.github/workflows/container-build.yaml +++ b/.github/workflows/container-build.yaml @@ -17,7 +17,6 @@ env: jobs: build-and-push-image: runs-on: ubuntu-latest - continue-on-error: true permissions: contents: read packages: write @@ -32,18 +31,19 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up QEMU - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 - name: Login to Container registry - uses: docker/login-action@v2 + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} @@ -51,7 +51,7 @@ jobs: - name: Docker meta id: meta - uses: docker/metadata-action@v4 + uses: docker/metadata-action@v5 with: images: | ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} @@ -67,9 +67,9 @@ jobs: suffix=${{ matrix.suffix }} - name: Build and push - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v6 with: - platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v7,linux/arm/v6 + platforms: linux/amd64,linux/arm64,linux/arm/v7 context: . file: ${{ matrix.container }} push: ${{ github.event_name != 'pull_request' }} diff --git a/Containerfile.alpine b/Containerfile.alpine index 0cf2004..1a9d312 100644 --- a/Containerfile.alpine +++ b/Containerfile.alpine @@ -7,18 +7,24 @@ ARG TARGET_ARCH="library" FROM docker.io/${TARGET_ARCH}/python:${TARGET_VERSION} -WORKDIR /usr/local/app +WORKDIR /tmp/build -COPY . /usr/local/app +COPY . /tmp/build RUN _poetry_venv_dir="$(mktemp -d -p "${TMPDIR:-/tmp}" 'poetry_venv.XXXXXX')" && \ python -m 'venv' "${_poetry_venv_dir}" && \ - "${_poetry_venv_dir}/bin/pip" install 'poetry' && \ + "${_poetry_venv_dir}/bin/pip" install --no-cache-dir 'poetry' && \ "${_poetry_venv_dir}/bin/poetry" config --local virtualenvs.create false && \ - "${_poetry_venv_dir}/bin/poetry" install && \ + "${_poetry_venv_dir}/bin/poetry" install --without dev --extras cli --extras tunnel --no-interaction --no-ansi && \ + addgroup -S meshtastic && \ + adduser -S -G meshtastic -h /home/meshtastic meshtastic && \ rm -f -r "${_poetry_venv_dir}" && \ - rm -f -r "/usr/local/app" + rm -f -r "/tmp/build" COPY "./bin/container-entrypoint.sh" "/init" +RUN chmod 0755 /init + +WORKDIR /home/meshtastic +USER meshtastic ENTRYPOINT [ "/init" ] diff --git a/Containerfile.debian b/Containerfile.debian index 561c890..50aa8cb 100644 --- a/Containerfile.debian +++ b/Containerfile.debian @@ -7,18 +7,23 @@ ARG TARGET_ARCH="library" FROM docker.io/${TARGET_ARCH}/python:${TARGET_VERSION} -WORKDIR /usr/local/app +WORKDIR /tmp/build -COPY . /usr/local/app +COPY . /tmp/build RUN _poetry_venv_dir="$(mktemp -d -p "${TMPDIR:-/tmp}" 'poetry_venv.XXXXXX')" && \ python -m 'venv' "${_poetry_venv_dir}" && \ - "${_poetry_venv_dir}/bin/pip" install 'poetry' && \ + "${_poetry_venv_dir}/bin/pip" install --no-cache-dir 'poetry' && \ "${_poetry_venv_dir}/bin/poetry" config --local virtualenvs.create false && \ - "${_poetry_venv_dir}/bin/poetry" install --no-directory && \ + "${_poetry_venv_dir}/bin/poetry" install --without dev --extras cli --extras tunnel --no-interaction --no-ansi && \ + useradd --system --create-home --home-dir /home/meshtastic meshtastic && \ rm -f -r "${_poetry_venv_dir}" && \ - rm -f -r "/usr/local/app" + rm -f -r "/tmp/build" COPY "./bin/container-entrypoint.sh" "/init" +RUN chmod 0755 /init + +WORKDIR /home/meshtastic +USER meshtastic ENTRYPOINT [ "/init" ] diff --git a/README.md b/README.md index 494c519..f82fc27 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,24 @@ This small library (and example application) provides an easy API for sending an It also provides access to any of the operations/data available in the device user interface or the Android application. Events are delivered using a publish-subscribe model, and you can subscribe to only the message types you are interested in. +## Container usage + +Container images are published to GHCR for this repository. The container entrypoint defaults to running `meshtastic`, +so CLI flags can be passed directly: + +```bash +docker run --rm ghcr.io/meshtastic/python --help +``` + +To run another command, pass it explicitly (for example, a shell): + +```bash +docker run --rm -it --entrypoint /bin/sh ghcr.io/meshtastic/python +``` + +The container runs as a non-root user by default. When talking to local hardware, pass the serial device through +explicitly (for example `--device /dev/ttyUSB0:/dev/ttyUSB0`) and ensure host device permissions allow access. + ## Call for Contributors This library and CLI has gone without a consistent maintainer for a while, and there's many improvements that could be made. We're all volunteers here and help is extremely appreciated, whether in implementing your own needs or helping maintain the library and CLI in general.