diff --git a/docker-dev/Dockerfile b/docker-dev/Dockerfile index e7c068977..cb846ebaf 100644 --- a/docker-dev/Dockerfile +++ b/docker-dev/Dockerfile @@ -12,8 +12,16 @@ FROM --platform=linux/amd64 mcr.microsoft.com/dotnet/sdk:10.0 AS build WORKDIR /src COPY . . RUN dotnet restore ZB.MOM.WW.OtOpcUa.slnx +# Commit stamp (#893). .dockerignore keeps .git/ out of the build context, so SourceLink has no +# repository to read and the informational version would be a bare "1.0.0". The compose `build.args` +# (fed by docker-dev/compose.sh from the host's `git rev-parse HEAD`, plus "-dirty" for an uncommitted +# tree) hand it to the SDK as SourceRevisionId, which yields "1.0.0+" in +# AssemblyInformationalVersion (and so Telemetry's service.version). Left empty, the build still +# succeeds as "1.0.0". Declared after restore so a new commit does not bust the restore layer. +ARG SOURCE_REVISION= RUN dotnet publish src/Server/ZB.MOM.WW.OtOpcUa.Host/ZB.MOM.WW.OtOpcUa.Host.csproj \ - -c Release -o /app --no-restore + -c Release -o /app --no-restore \ + ${SOURCE_REVISION:+-p:SourceRevisionId=${SOURCE_REVISION}} FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime WORKDIR /app diff --git a/docker-dev/README.md b/docker-dev/README.md index cae3aeaf4..2ed7059d9 100644 --- a/docker-dev/README.md +++ b/docker-dev/README.md @@ -87,7 +87,7 @@ The DriverHost actor doesn't spawn drivers from raw DriverInstance rows on its o ```bash # from the repo root -docker compose -f docker-dev/docker-compose.yml up -d --build +docker-dev/compose.sh up -d --build # the one-shot migrator + cluster-seed bootstrap the DB; watch the seed finish: docker compose -f docker-dev/docker-compose.yml logs -f cluster-seed # ^C once it prints "[cluster-seed] done." @@ -96,6 +96,8 @@ open http://localhost:9200 # Admin UI (Traefik → central-1 or open http://localhost:8089 # Traefik dashboard ``` +`docker-dev/compose.sh` is `docker compose -f docker-dev/docker-compose.yml "$@"` with `SOURCE_REVISION` exported from `git rev-parse HEAD` (suffixed `-dirty` for an uncommitted tree; an explicit `SOURCE_REVISION=...` wins). `.git/` stays out of the build context (`.dockerignore`), so that build arg is the only way the in-image publish learns the commit: with it the Host stamps `1.0.0+` as its informational version (Telemetry's `service.version`); a plain `docker compose ... --build` still works and stamps a bare `1.0.0` (#893). Check a built image with `docker run --rm --entrypoint sh otopcua-host:dev -c "grep -ao '1\.0\.0[+0-9a-f-]*' OtOpcUa.Host.dll"`. + The first build takes a few minutes (.NET SDK image + restore + publish). **No manual schema step is needed** — on a fresh SQL volume the one-shot `migrator` service applies the EF migrations (the host nodes deliberately don't auto-migrate, since production owns schema changes), then `cluster-seed` populates the cluster/namespace/driver rows. `cluster-seed` and the host nodes wait for the migrator via `service_completed_successfully`, so nothing races an in-progress migration. A plain `docker compose ... up -d` on an existing volume is a fast no-op for both — the named SQL volume keeps the schema + rows across restarts; only `down -v` wipes them, after which the next `up` re-migrates + re-seeds automatically. ## Auth (dev only) diff --git a/docker-dev/compose.sh b/docker-dev/compose.sh new file mode 100755 index 000000000..0c8c136a6 --- /dev/null +++ b/docker-dev/compose.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +# +# docker compose wrapper for the docker-dev rig that stamps the git commit into the images (#893). +# +# .dockerignore keeps .git/ out of the build context, so the in-image `dotnet publish` cannot read the +# commit itself and AssemblyInformationalVersion would be a bare "1.0.0". This exports SOURCE_REVISION +# (git rev-parse HEAD, suffixed "-dirty" for an uncommitted tree) for the compose `build.args`, which +# the Dockerfile hands to the SDK as SourceRevisionId -> "1.0.0+". An explicit SOURCE_REVISION in +# the environment wins; outside a git checkout it stays empty and the images stamp a bare "1.0.0". +# +# Usage (any docker compose arguments; the base compose file is always passed first): +# docker-dev/compose.sh up -d --build +# docker-dev/compose.sh -f docker-dev/docker-compose.questdb.yml up -d --build central-1 central-2 +# docker-dev/compose.sh build central-1 +# +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" + +SOURCE_REVISION="${SOURCE_REVISION:-}" +if [ -z "$SOURCE_REVISION" ] && git -C "$REPO_ROOT" rev-parse --git-dir >/dev/null 2>&1; then + SOURCE_REVISION="$(git -C "$REPO_ROOT" rev-parse HEAD)" + if [ -n "$(git -C "$REPO_ROOT" status --porcelain --untracked-files=no)" ]; then + SOURCE_REVISION="${SOURCE_REVISION}-dirty" + fi +fi +export SOURCE_REVISION +echo "Source revision: ${SOURCE_REVISION:-}" >&2 + +exec docker compose -f "$SCRIPT_DIR/docker-compose.yml" "$@" diff --git a/docker-dev/docker-compose.yml b/docker-dev/docker-compose.yml index d22fe517d..262ad4b06 100644 --- a/docker-dev/docker-compose.yml +++ b/docker-dev/docker-compose.yml @@ -113,7 +113,9 @@ # exercises. # # Usage: -# docker compose -f docker-dev/docker-compose.yml up -d --build +# docker-dev/compose.sh up -d --build # = docker compose -f docker-dev/docker-compose.yml ..., plus +# # SOURCE_REVISION= so the images stamp the commit (#893) +# docker compose -f docker-dev/docker-compose.yml up -d --build # also works; images stamp a bare 1.0.0 # open http://localhost:9200 # central Blazor admin UI # open http://localhost:8089 # Traefik dashboard (8080 is the sister scadalink stack) # @@ -250,6 +252,9 @@ services: context: .. dockerfile: docker-dev/Dockerfile target: migrator + args: + # Commit stamp for the image's informational version (#893); docker-dev/compose.sh exports it. + SOURCE_REVISION: ${SOURCE_REVISION:-} image: otopcua-migrator:dev depends_on: sql: @@ -291,6 +296,9 @@ services: context: .. dockerfile: docker-dev/Dockerfile target: runtime + args: + # Commit stamp for the image's informational version (#893); docker-dev/compose.sh exports it. + SOURCE_REVISION: ${SOURCE_REVISION:-} image: otopcua-host:dev # Per-node memory bounds. The full single-mesh stack (6 host nodes) OOM-killed # central-1 on a loaded host. Each host node measured ~357 MiB idle-solo and