From fee82d88c9765b8fef9dbb676c1b8a46a3350c37 Mon Sep 17 00:00:00 2001 From: Raphael Maenle Date: Tue, 9 Jun 2026 23:23:34 +0200 Subject: [PATCH] ci: self-contained secs_server image for secs4j interop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The harness previously bound the source tree into a compose service and built inside it. That breaks under docker-in-docker (gitea-act, GitHub Actions runners with /var/run/docker.sock mounted) because bind-mount sources resolve against the *host* daemon's filesystem, not the runner container's. Now Dockerfile.server bakes a Release secs_server into its own image, and secs4j_validate.sh wires server and harness together on a dedicated bridge — no volumes needed. Co-Authored-By: Claude Opus 4.7 --- .dockerignore | 1 + interop/Dockerfile.server | 45 +++++++++++++++++++++++++++ interop/secs4j_validate.sh | 62 ++++++++++++++++++++++++-------------- 3 files changed, 85 insertions(+), 23 deletions(-) create mode 100644 interop/Dockerfile.server diff --git a/.dockerignore b/.dockerignore index b512bcb..0b3c8f1 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,4 +1,5 @@ build/ +build-*/ .git/ .gitignore *.md diff --git a/interop/Dockerfile.server b/interop/Dockerfile.server new file mode 100644 index 0000000..cc43bed --- /dev/null +++ b/interop/Dockerfile.server @@ -0,0 +1,45 @@ +# Self-contained secs_server image for the secs4j interop harness. +# +# The dev-time `server` compose service bind-mounts the source tree +# read-write at /app and lets the in-container `builder` step compile +# there. That works on a developer laptop but breaks under docker- +# in-docker (gitea-act / GitHub Actions runners with a mounted +# /var/run/docker.sock): the bind-mount source resolves against the +# *host* daemon's filesystem, not the runner container's, so /app +# mounts empty and the build / launch silently fails. +# +# This image bakes the source + Release binary in at build time so +# `docker run` of it needs no volumes at all — same pattern already +# used by interop/secs4j/Dockerfile for the Java harness. +FROM ubuntu:24.04 AS build + +ENV DEBIAN_FRONTEND=noninteractive +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential cmake ninja-build \ + libasio-dev libyaml-cpp-dev \ + python3 python3-yaml \ + git ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /src +COPY . /src +RUN cmake -S /src -B /src/build -G Ninja -DCMAKE_BUILD_TYPE=Release \ + && cmake --build /src/build --target secs_server + + +FROM ubuntu:24.04 + +ENV DEBIAN_FRONTEND=noninteractive +RUN apt-get update && apt-get install -y --no-install-recommends \ + libyaml-cpp0.8 \ + && rm -rf /var/lib/apt/lists/* + +# WORKDIR must be the directory holding `data/` because secs_server's +# default --config / --state-table paths are relative ("data/..."). +WORKDIR /app +COPY --from=build /src/build/secs_server /usr/local/bin/secs_server +COPY data /app/data + +EXPOSE 5000 +ENTRYPOINT ["/usr/local/bin/secs_server"] +CMD ["--port", "5000", "--device", "0"] diff --git a/interop/secs4j_validate.sh b/interop/secs4j_validate.sh index 2609b7c..a8055bb 100755 --- a/interop/secs4j_validate.sh +++ b/interop/secs4j_validate.sh @@ -1,9 +1,13 @@ #!/usr/bin/env bash # Orchestrate the secs4java8 interop harness against our C++ server. # -# Boots secs_server on the docker bridge, then runs the Java harness -# in its own container against it. Wired into CI via a separate -# job (see .gitea/workflows/ci.yml). +# Builds a self-contained C++ server image (interop/Dockerfile.server) +# and the Java harness image (interop/secs4j/Dockerfile), spins both +# up on a dedicated docker network, and runs the harness against the +# server. No bind mounts — important under docker-in-docker (gitea- +# act / GitHub Actions runners with a mounted /var/run/docker.sock), +# where bind-mount sources don't resolve against the daemon's host +# filesystem. Wired into CI via .gitea/workflows/ci.yml. # # Usage: bash interop/secs4j_validate.sh # Exit codes: @@ -12,35 +16,47 @@ # 2 — script / orchestration error set -euo pipefail -PORT=${PORT:-5099} - cd "$(dirname "$0")/.." -# Ensure the secs4j-interop image is built (idempotent). The image -# COPYs Secs4jHostHarness.java in and compiles it at build time, so -# no bind mount is needed at run time — important because docker- -# in-docker (CI runners with a mounted /var/run/docker.sock) can't -# bind-mount paths from the container's filesystem to the daemon's. -echo "ensuring secs4j-interop image is built..." -docker build -t secsgem-secs4j-interop -f interop/secs4j/Dockerfile interop/secs4j \ - >/dev/null 2>&1 +NET=secs4j-interop-net +SERVER_NAME=secs4j-interop-server -# Start the C++ server in background on the compose bridge network so -# the secs4j container can DNS-resolve it. -echo "starting secs_server (compose)..." -docker compose up -d server >/dev/null 2>&1 -trap 'docker compose stop server >/dev/null 2>&1 || true' EXIT +cleanup() { + docker rm -f "$SERVER_NAME" >/dev/null 2>&1 || true + docker network rm "$NET" >/dev/null 2>&1 || true +} +trap cleanup EXIT + +# Build both images (idempotent — docker reuses layers). Errors are +# NOT swallowed: an early failure here is the most informative signal +# we get when something breaks in CI. +echo "building secs_server image..." +docker build -t secsgem-secs4j-server -f interop/Dockerfile.server . + +echo "building secs4j harness image..." +docker build -t secsgem-secs4j-interop -f interop/secs4j/Dockerfile interop/secs4j + +# Fresh network each run so we don't collide with a leftover from a +# previous failed invocation. +docker network rm "$NET" >/dev/null 2>&1 || true +docker network create "$NET" >/dev/null + +echo "starting secs_server..." +# --name doubles as the DNS hostname on the user-defined network, so +# the harness reaches it as "secs4j-interop-server:5000". +docker run -d --rm \ + --name "$SERVER_NAME" \ + --network "$NET" \ + secsgem-secs4j-server >/dev/null # Give the server a moment to bind. sleep 1 -# Run the harness on the same compose network as the server, against -# the compose-defined hostname "server" on port 5000 (the demo -# server's default). echo "running Secs4jHostHarness..." docker run --rm \ - --network secs-gem_secs \ + --network "$NET" \ secsgem-secs4j-interop \ - java -cp /opt/secs4java8/Export.jar:/work Secs4jHostHarness server 5000 + java -cp /opt/secs4java8/Export.jar:/work Secs4jHostHarness \ + "$SERVER_NAME" 5000 # trap handles teardown.