From b52431616e861add7fb574d2636f1809a24231e0 Mon Sep 17 00:00:00 2001 From: Jack Kingsman Date: Tue, 31 Mar 2026 20:47:10 -0700 Subject: [PATCH] Be more sane with by-id aliases --- README.md | 12 ++++--- docker-compose.example.yml | 12 ++++--- scripts/setup/install_docker.sh | 59 +++++++++++++++++++++++++-------- 3 files changed, 60 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 9fc8976..ccd51a4 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,8 @@ Source checkouts expect a normal frontend build in `frontend/dist`. Local Docker builds are architecture-native by default. On Apple Silicon Macs and ARM64 Linux hosts such as Raspberry Pi, `docker compose build` / `docker compose up --build` will produce an ARM64 image unless you override the platform. +For serial-device passthrough, use rootful Docker. In practice that usually means starting the stack with `sudo docker compose ...` unless your Docker daemon is already configured for rootful access via your user/group. Rootless Docker has been observed to fail on serial-device mappings even when the compose file itself is correct. + Create a local `docker-compose.yml` in one of two ways: 1. Copy the example file and edit it by hand: @@ -128,7 +130,7 @@ The guided Docker flow can collect BLE settings, but BLE access from Docker stil Then customize the local compose file for your transport and launch: ```bash -docker compose up # -d for background once you validate it's working +sudo docker compose up # add -d for background once you validate it's working ``` The database is stored in `./data/` (bind-mounted), so the container shares the same database as the native app. @@ -136,8 +138,8 @@ The database is stored in `./data/` (bind-mounted), so the container shares the To rebuild after pulling updates: ```bash -docker compose pull -docker compose up -d +sudo docker compose pull +sudo docker compose up -d ``` The example file and setup script default to the published Docker Hub image. To build locally from your checkout instead, replace: @@ -155,7 +157,7 @@ build: . Then run: ```bash -docker compose up -d --build +sudo docker compose up -d --build ``` The container runs as root by default for maximum serial passthrough compatibility across host setups. On Linux, if you switch between native and Docker runs, `./data` can end up root-owned. If you do not need that serial compatibility behavior, you can enable the optional `user: "${UID:-1000}:${GID:-1000}"` line in `docker-compose.yml` to keep ownership aligned with your host user. @@ -163,7 +165,7 @@ The container runs as root by default for maximum serial passthrough compatibili To stop: ```bash -docker compose down +sudo docker compose down ``` ## Standard Environment Variables diff --git a/docker-compose.example.yml b/docker-compose.example.yml index 4a8c6b6..2cfce92 100644 --- a/docker-compose.example.yml +++ b/docker-compose.example.yml @@ -12,12 +12,14 @@ services: volumes: - ./data:/app/data - ################################################ - # Map your radio by stable device ID if available. # - ################################################ + ##################################################################### + # Map your radio by stable device ID if available. # + # If your by-id path contains ':' characters, Docker Compose cannot # + # represent it here directly; use a colon-free host alias instead. # + # (e.g. /dev/ttyUSB0) # + ##################################################################### devices: - - source: /dev/serial/by-id/your-meshcore-radio - target: /dev/meshcore-radio + - /dev/serial/by-id/your-meshcore-radio:/dev/meshcore-radio environment: MESHCORE_DATABASE_PATH: data/meshcore.db diff --git a/scripts/setup/install_docker.sh b/scripts/setup/install_docker.sh index 0e68cb4..8f65c79 100755 --- a/scripts/setup/install_docker.sh +++ b/scripts/setup/install_docker.sh @@ -32,6 +32,7 @@ SNAKEOIL_KEY_CONTAINER_PATH="/app/certs/$SNAKEOIL_KEY_BASENAME" IMAGE_MODE="image" TRANSPORT_MODE="serial" SERIAL_HOST_PATH="/dev/ttyACM0" +SERIAL_COMPOSE_HOST_PATH="/dev/ttyACM0" SERIAL_CONTAINER_PATH="/dev/meshcore-radio" TCP_HOST="" TCP_PORT="4000" @@ -98,6 +99,35 @@ yaml_quote() { printf "'%s'" "$value" } +normalize_serial_host_path_for_compose() { + local selected_path="$1" + local resolved_path="" + + if [[ "$selected_path" != *:* ]]; then + SERIAL_COMPOSE_HOST_PATH="$selected_path" + return 0 + fi + + resolved_path="$(readlink -f "$selected_path" 2>/dev/null || true)" + if [ -z "$resolved_path" ]; then + echo -e "${RED}Error:${NC} the selected serial path contains ':' and could not be resolved to a raw /dev/tty-style device path." + echo "Selected path: $selected_path" + echo "Please enter the raw serial device path instead (for example /dev/ttyACM0)." + exit 1 + fi + + if [[ "$resolved_path" == *:* ]]; then + echo -e "${RED}Error:${NC} the selected serial path still resolves to a path containing ':', which Docker Compose cannot use here." + echo "Selected path: $selected_path" + echo "Resolved path: $resolved_path" + echo "Please enter the raw serial device path instead (for example /dev/ttyACM0)." + exit 1 + fi + + echo -e "${YELLOW}Note:${NC} the selected serial path contains ':', so Docker Compose will use the resolved raw device path instead: ${resolved_path}" + SERIAL_COMPOSE_HOST_PATH="$resolved_path" +} + detect_primary_local_ip() { local ip="" local iface="" @@ -275,7 +305,8 @@ case "$TRANSPORT_CHOICE" in fi fi - echo -e "${GREEN}Serial passthrough: ${SERIAL_HOST_PATH} -> ${SERIAL_CONTAINER_PATH}${NC}" + normalize_serial_host_path_for_compose "$SERIAL_HOST_PATH" + echo -e "${GREEN}Serial passthrough: ${SERIAL_COMPOSE_HOST_PATH} -> ${SERIAL_CONTAINER_PATH}${NC}" ;; 2) TRANSPORT_MODE="tcp" @@ -419,8 +450,7 @@ mkdir -p "$REPO_DIR/data" fi if [ "$TRANSPORT_MODE" = "serial" ]; then echo " devices:" - echo " - source: $(yaml_quote "$SERIAL_HOST_PATH")" - echo " target: $(yaml_quote "$SERIAL_CONTAINER_PATH")" + echo " - ${SERIAL_COMPOSE_HOST_PATH}:${SERIAL_CONTAINER_PATH}" fi if [[ "$ENABLE_SNAKEOIL_TLS" =~ ^[Yy]$ ]]; then echo " command:" @@ -462,15 +492,18 @@ echo -e "${GREEN}Generated ${COMPOSE_FILE}.${NC}" echo echo -e "${BOLD}Docker commands${NC}" if [ "$IMAGE_MODE" = "build" ]; then - echo " docker compose up -d --build # build the local image and start RemoteTerm in the background" + echo " sudo docker compose up -d --build # build the local image and start RemoteTerm in the background" else - echo " docker compose up -d # start RemoteTerm in the background" + echo " sudo docker compose up -d # start RemoteTerm in the background" fi -echo " docker compose logs -f # follow the container logs live" +echo " sudo docker compose logs -f # follow the container logs live" echo -echo " docker compose down # stop and remove the running container" -echo " docker compose restart # restart the container without changing the image" -echo " docker compose pull && docker compose up -d # upgrade to the latest published image and restart" +echo " sudo docker compose down # stop and remove the running container" +echo " sudo docker compose restart # restart the container without changing the image" +echo " sudo docker compose pull && sudo docker compose up -d # upgrade to the latest published image and restart" +echo +echo -e "${YELLOW}Note:${NC} serial passthrough generally needs ${BOLD}rootful Docker${NC}." +echo "If Docker is running rootless on this host, serial-device mappings may fail even with a valid compose file." if [ "$TRANSPORT_MODE" = "ble" ] || [ "$BLE_MANUAL_WARNING" = true ]; then echo echo -e "${RED}BLE requires more than the generated env vars.${NC}" @@ -480,9 +513,9 @@ echo echo -e "${GREEN}Your new docker file is ready at ${COMPOSE_FILE}.${NC}" echo -e "${GREEN}Feel free to edit it by hand as desired, or:${NC}" echo -echo -e "${PURPLE}┌──────────────────────────────────────────────┐${NC}" -echo -e "${PURPLE}│ Run ${GREEN}${BOLD}docker compose up -d${NC}${PURPLE} to get started. │${NC}" -echo -e "${PURPLE}└──────────────────────────────────────────────┘${NC}" +echo -e "${PURPLE}┌───────────────────────────────────────────────┐${NC}" +echo -e "${PURPLE}│ Run ${GREEN}${BOLD}sudo docker compose up -d${NC}${PURPLE} to get started. │${NC}" +echo -e "${PURPLE}└───────────────────────────────────────────────┘${NC}" if [[ "$ENABLE_SNAKEOIL_TLS" =~ ^[Yy]$ ]]; then echo echo -e "After the container starts, open ${CYAN}https://${LOCAL_ACCESS_IP}:8000${NC}." @@ -492,4 +525,4 @@ else echo -e "After the container starts, open ${CYAN}http://${LOCAL_ACCESS_IP}:8000${NC}." fi echo "If the interface does not appear, follow the logs with:" -echo " docker compose logs -f" +echo " sudo docker compose logs -f"