Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 112 additions & 0 deletions build-docker.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
#!/usr/bin/env bash
# build-docker.sh - build a MeshCore Linux firmware in an arm64 Debian container.
#
# The linux_repeater target compiles natively against libgpiod/bluez/libuv and
# only builds on Linux, so this runs the build inside a linux/arm64 container
# (matching a Raspberry Pi). On Apple Silicon this is native and fast; on Intel
# it emulates aarch64 via QEMU (works, but slow).
#
# Usage:
# ./build-docker.sh # builds env "linux_repeater"
# FIRMWARE_VERSION=1.0 ./build-docker.sh
# BASE_IMAGE=debian:trixie ./build-docker.sh linux_repeater # libgpiod v2
#
# Output: <build-dir>/<env>/meshcored (written into this repo via the mount)
# PlatformIO's packages are cached in a "mc_pio_cache*" Docker volume so repeat
# builds skip the re-download.
#
# BASE_IMAGE selects the container's libgpiod major version: bookworm ships
# libgpiod 1.x, trixie ships 2.x. Default is bookworm, so existing behaviour
# is unchanged unless BASE_IMAGE is set.
#
# PlatformIO's dependency scanner doesn't track system headers like
# /usr/include/gpiod.h, so reusing one build dir across libgpiod major
# versions can silently relink objects compiled against the other version's
# headers. To avoid that, the package cache and build dir are namespaced by
# BASE_IMAGE: the default (bookworm) keeps the original untagged paths
# (cache volume "mc_pio_cache", build dir ".pio/build") so existing caches
# and tooling keep working; any other BASE_IMAGE gets its own
# "mc_pio_cache_<tag>" volume and ".pio/build-<tag>" dir, e.g. trixie builds
# land in ".pio/build-trixie/linux_repeater/meshcored".
set -euo pipefail

ENV_NAME="${1:-linux_repeater}"
FIRMWARE_VERSION="${FIRMWARE_VERSION:-dev}"
IMAGE="${BASE_IMAGE:-debian:bookworm}"

if [ "${IMAGE}" = "debian:bookworm" ]; then
CACHE_VOL="mc_pio_cache"
BUILD_DIR=".pio/build"
else
# Derive a filesystem/volume-name-safe tag from the image reference. Strip
# to the part after the last ':' (the tag; also correct for a
# registry:port/name:tag reference, since ## takes the longest match), then
# drop any remaining path component (e.g. a tagless "myregistry.io/debian"
# reference, where the whole string would otherwise land here), then
# replace anything that isn't safe in a Docker volume name or directory
# name with '_'.
TAG="${IMAGE##*:}"
TAG="${TAG##*/}"
TAG="${TAG//[^A-Za-z0-9._-]/_}"
CACHE_VOL="mc_pio_cache_${TAG}"
BUILD_DIR=".pio/build-${TAG}"
fi

REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

if ! command -v docker >/dev/null 2>&1; then
echo "build-docker.sh: docker not found on PATH" >&2
exit 1
fi

echo ">> building env '${ENV_NAME}' (version '${FIRMWARE_VERSION}') in ${IMAGE} [linux/arm64] -> ${BUILD_DIR}"

# No -it: a TTY is not needed for a batch build, and requesting one makes the
# script fail outright when stdin is not a terminal (CI, agents, `| tee`).
#
# The container itself still runs as root -- apt-get needs it, and there is no
# host-matching user account inside the image for `--user "$(id -u):$(id -g)"`
# to resolve against without extra setup. Instead, chown back to the invoking
# user's uid/gid the specific paths THIS run's `pio run -e "${ENV_NAME}"`
# writes into the bind-mounted repo: its build output, the shared
# project.checksum PlatformIO keeps at the top of BUILD_DIR, and its
# per-env libdeps/ cache. That is deliberately narrower than "chown -R .pio":
# .pio/build and .pio/libdeps are shared across every env ever built in this
# checkout, including other envs built natively on the host (e.g. an MCU
# cross-build via `pio run -e <mcu-env>`), and those pre-existing host-owned
# trees can contain files (git-cloned lib_deps' own .git/objects/pack/*,
# which git itself writes read-only) that a recursive chown over the whole
# tree would try to touch. On Docker Desktop's bind-mount layer that fails
# outright with EPERM even though the container is root, which would abort
# this script under `set -e` *after* a successful build -- confirmed with a
# minimal repro (see task-5-report.md). Scoping to this run's own paths
# avoids ever touching another env's tree; `|| echo ... WARNING` on top of
# that means even an unexpected chown failure inside the scoped paths is
# reported but never turns a successful build into a script failure.
docker run --rm \
--platform linux/arm64 \
-v "${REPO_DIR}":/src -w /src \
-v "${CACHE_VOL}":/root/.platformio \
-e "FIRMWARE_VERSION=${FIRMWARE_VERSION}" \
-e "ENV_NAME=${ENV_NAME}" \
-e "PLATFORMIO_BUILD_DIR=${BUILD_DIR}" \
-e "HOST_UID=$(id -u)" \
-e "HOST_GID=$(id -g)" \
"${IMAGE}" bash -c '
set -e
apt-get update
apt-get install -y --no-install-recommends \
build-essential git python3 python3-venv \
pkg-config libgpiod-dev libi2c-dev libbluetooth-dev libuv1-dev
python3 -m venv /pio
. /pio/bin/activate
pip install --quiet --upgrade platformio
pio run -e "${ENV_NAME}"
chown -R "${HOST_UID}:${HOST_GID}" \
"${PLATFORMIO_BUILD_DIR}/${ENV_NAME}" \
"${PLATFORMIO_BUILD_DIR}/project.checksum" \
".pio/libdeps/${ENV_NAME}" \
|| echo ">> WARNING: could not reclaim ownership of every build artifact for ${HOST_UID}:${HOST_GID} (see chown errors above) -- the build itself still succeeded" >&2
'

echo ">> done: ${BUILD_DIR}/${ENV_NAME}/meshcored"
10 changes: 10 additions & 0 deletions platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,16 @@ build_flags =
-std=c++17
-I variants/linux
-I /usr/include
; force-include the libc string header: ArduinoCore-API and the ardulinux core
; (String.cpp, Print.h, vfs_api.cpp, pgmspace.h, etc.) use
; strlen/memcpy/memset/strdup/... without including them, which newer
; GCC/libstdc++ no longer provide via transitive includes. Absolute path is
; required: a bare "-include string.h" resolves to the Arduino patched_api
; shim on the include path, not the C library header. string.h (not <cstring>)
; keeps this valid for both the .c and .cpp sources. Do NOT add stdlib.h/stdio.h
; here: they pull in <sys/select.h>, whose select() collides with the static
; select() in lib/ed25519/ge.c.
-include /usr/include/string.h

[sensor_base]
build_flags =
Expand Down
3 changes: 3 additions & 0 deletions src/helpers/ConfigSerializer.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
#include "ConfigSerializer.h"

#include <stdlib.h> // atoi/atol/atof -- reaches these via Arduino.h on MCU
// targets, but not in the host `native` test build

bool ConfigSerializer::saveSerial(Stream& s) {
Context context(&s, OP::WRITE);
_context = &context; // set the context for structure() call
Expand Down
18 changes: 18 additions & 0 deletions variants/linux/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,24 @@ Alternatively, build directly with PlatformIO (no version metadata):
FIRMWARE_VERSION=dev pio run -e linux_repeater
```

**Container cross-build.** `build-docker.sh` builds for arm64 inside a Debian
container, which is how to build from a non-Linux machine (or without
installing the toolchain on the host). `BASE_IMAGE` picks the libgpiod major
version the binary is built against, and each base gets its own build
directory so the two cannot be mixed:

```sh
./build-docker.sh linux_repeater
# bookworm (libgpiod 1.x) -> .pio/build/linux_repeater/meshcored

BASE_IMAGE=debian:trixie ./build-docker.sh linux_repeater
# trixie (libgpiod 2.x) -> .pio/build-trixie/linux_repeater/meshcored
```

PlatformIO's package cache lives in a Docker volume, so repeat builds skip the
download; on Apple Silicon the container is native and a warm build takes
about a minute.

## Setup

### 1. Install the binary
Expand Down
2 changes: 1 addition & 1 deletion variants/linux/meshcored.service
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# /var/lib/systemd/system/meshcored.service
# /etc/systemd/system/meshcored.service
[Unit]
Description=Meshcore Daemon (meshcored)
After=network.target
Expand Down
6 changes: 0 additions & 6 deletions variants/linux/platformio.ini
Original file line number Diff line number Diff line change
@@ -1,16 +1,10 @@
[env:linux]
extends = linux_base
build_flags = ${linux_base.build_flags}
!pkg-config --cflags --libs libbsd-overlay --silence-errors || :

[env:linux_repeater]
extends = linux_base
build_flags =
${linux_base.build_flags}
-D RADIO_CLASS=LinuxSX1262
-D WRAPPER_CLASS=LinuxSX1262Wrapper
-D USE_CUSTOM_SX1262_WRAPPER
-D SKIP_CONFIG_OVERWRITE=1
-D MAX_NEIGHBOURS=100
-D LORA_TX_POWER=22
-D MESH_DEBUG=1
Expand Down
Loading