# syntax=docker/dockerfile:1 # Author: cirakg & claude ai # Summary: garmin-otp/dev image: Connect IQ build, test and headless-simulator toolchain (no SDK inside). # # garmin-otp/dev: the toolchain image for this project. # # Everything that builds, tests or runs the watch app happens in a container # made from this image (README section 15). The image deliberately does NOT # contain the Garmin Connect IQ SDK, the device files or your developer key: # * Garmin's licence does not allow redistributing the SDK, so `make sdk` # downloads it at runtime into the project's .cache/ folder, after you # accept the licence yourself. # * The developer key is generated by `make key` into .cache/keys/. # # Reproducibility: the base image is pinned by digest, Ubuntu packages come # from a dated snapshot of the archive, the SDK manager CLI is checked against # its release checksum, and Python packages install from a lock file with # hashes (docker/requirements.txt). ARG UBUNTU_IMAGE=ubuntu@sha256:0a9887931b0e38af2e3487ed39e29436b0535c4f3f52e57745baaf79d1898956 FROM ${UBUNTU_IMAGE} # Dated snapshot of the Ubuntu archive (https://snapshot.ubuntu.com). Changing # this date is the only way the Ubuntu package versions change. ARG UBUNTU_SNAPSHOT=20261001T000000Z # connect-iq-sdk-manager-cli, Apache-2.0: https://github.com/lindell/connect-iq-sdk-manager-cli ARG SDKMGR_VERSION=0.8.4 ARG SDKMGR_SHA256=5d57b8f9cfcd02d3b3f48e19d6daca0480e4c6f627926f15d8617ef8a6f57e0b # Your user and group IDs, so files written into the bind-mounted project # folder belong to you (see the user setup at the end). ARG UID=1000 ARG GID=1000 LABEL project=garmin-otp \ org.opencontainers.image.title="garmin-otp/dev" \ org.opencontainers.image.description="Connect IQ build, test and simulator toolchain (Garmin SDK not included)" ENV DEBIAN_FRONTEND=noninteractive \ LANG=C.UTF-8 \ LC_ALL=C.UTF-8 \ TZ=UTC # Step 1: CA certificates, so apt can reach the HTTPS-only snapshot mirror. # They come from the regular archive; apt verifies every package against the # archive's signing keys, so this bootstrap step is integrity-checked too. RUN apt-get update \ && apt-get install -y --no-install-recommends ca-certificates \ && rm -rf /var/lib/apt/lists/* # Step 2: switch apt to the dated snapshot and install the toolchain. # openjdk-17-jre-headless monkeyc (the Monkey C compiler runs on Java) # libwebkit2gtk-4.0-37, libgtk-3-0, the Connect IQ simulator (GTK + WebKitGTK 4.0, # libusb-1.0-0, libsm6, libsecret, which is why the base is Ubuntu 22.04: 24.04 # dbus-x11, fonts-dejavu-core no longer ships WebKitGTK 4.0) # xvfb, openbox virtual display and a window manager, so the # simulator runs headless and its window can be # found for screenshots # x11vnc, novnc, websockify optional live view in your browser (make sim) # xdotool, x11-utils, imagemagick scripted input and screenshots for UI tests # python3, python3-venv Python tools (test reports, reference encoders) # make, openssl, unzip, curl, jq, build helpers and developer-key generation # procps, file RUN printf '%s\n' \ "deb https://snapshot.ubuntu.com/ubuntu/${UBUNTU_SNAPSHOT} jammy main universe" \ "deb https://snapshot.ubuntu.com/ubuntu/${UBUNTU_SNAPSHOT} jammy-updates main universe" \ "deb https://snapshot.ubuntu.com/ubuntu/${UBUNTU_SNAPSHOT} jammy-security main universe" \ > /etc/apt/sources.list \ && apt-get update \ && apt-get install -y --no-install-recommends \ openjdk-17-jre-headless \ libwebkit2gtk-4.0-37 libgtk-3-0 libusb-1.0-0 libsm6 libsecret-1-0 dbus-x11 \ fonts-dejavu-core \ xvfb openbox x11vnc novnc websockify xdotool x11-utils imagemagick \ python3 python3-venv \ make openssl unzip curl jq procps file \ && rm -rf /var/lib/apt/lists/* # Step 3: the SDK manager CLI (downloads the SDK and device files for `make sdk`), # verified against the SHA-256 published with the release. RUN curl -fsSL -o /tmp/sdkmgr.tgz \ "https://github.com/lindell/connect-iq-sdk-manager-cli/releases/download/v${SDKMGR_VERSION}/connect-iq-sdk-manager-cli_${SDKMGR_VERSION}_Linux_x86_64.tar.gz" \ && echo "${SDKMGR_SHA256} /tmp/sdkmgr.tgz" | sha256sum --check --strict - \ && tar -xzf /tmp/sdkmgr.tgz -C /usr/local/bin connect-iq-sdk-manager \ && mkdir -p /usr/local/share/doc/connect-iq-sdk-manager \ && tar -xzf /tmp/sdkmgr.tgz -C /usr/local/share/doc/connect-iq-sdk-manager LICENSE \ && rm /tmp/sdkmgr.tgz # Step 4: Python tools in a virtual environment: exact versions, hashes, wheels only. COPY requirements.txt /opt/garmin-otp/requirements.txt RUN python3 -m venv /opt/venv \ && /opt/venv/bin/pip install --no-cache-dir --disable-pip-version-check \ --require-hashes --only-binary :all: -r /opt/garmin-otp/requirements.txt ENV PATH=/opt/venv/bin:${PATH} # Step 5: the container-side scripts are not baked in. They live in the # project (docker/scripts/, mounted at /work), so they are versioned with the # code and a script change never needs an image rebuild. ENV PATH=/work/docker/scripts:${PATH} # Step 6: an unprivileged user with your UID/GID. Containers also run with # `--user`, but Java takes its home directory from /etc/passwd, and the Garmin # tools keep the SDK and device files under ~/.Garmin. At runtime /home/dev is # replaced by the project's .cache/home folder, so all of that stays inside the # project and disappears with `make nuke`. RUN groupadd --non-unique --gid "${GID}" dev \ && useradd --non-unique --uid "${UID}" --gid "${GID}" --home-dir /home/dev \ --create-home --shell /bin/bash dev USER dev ENV HOME=/home/dev WORKDIR /work