From 0a9ed52429b4b3f2e2e1093113105d6db48819e8 Mon Sep 17 00:00:00 2001 From: tcsenpai Date: Tue, 26 May 2026 16:50:11 +0200 Subject: [PATCH] feat(docker): bake git provenance into image via build-args + export from docker-run MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes the gap left by PR #864/#865: src/utilities/nodeVersion.ts already reads process.env.GIT_COMMIT/GIT_BRANCH/GIT_DIRTY when present, but nothing on the docker path was supplying them. The result was that getNetworkInfo.nodeVersion came back with commit: null on every Docker- booted node, defeating the original "is this host running the binary I think it is?" diagnostic value. Three pieces wire it end-to-end: 1) Dockerfile (runtime stage): ARG GIT_COMMIT/GIT_BRANCH/GIT_DIRTY/ BUILT_AT, then re-export each via ENV so the running node sees them in process.env. Defaults are empty strings — a build without the args (manual `docker build .`) still produces a runnable image, nodeVersion just surfaces null for the missing fields. 2) docker-compose.yml (node service build block): args passes the four values straight through with `:-` defaults, so any caller that exported them (the wrapper below, CI, an operator's shell) gets them baked in; anyone who didn't gets an empty-string ARG and the same null-field fallback. 3) scripts/docker-run: detects the working tree's git status with defensive guards (no-repo, no-git-binary, dirty-vs-clean) and exports the four variables before any `docker compose build`/`up`. `git diff-index --quiet HEAD` exit code → "true"/"false" string the nodeVersion module already parses. BUILT_AT is the UTC ISO-8601 timestamp at invocation. Operator-visible effect: after `./scripts/docker-run --rebuild -d`, curl getNetworkInfo and the response carries the real commit SHA, branch name, dirty flag, and build timestamp the image was made from. That answers "did this host actually pick up the fix I merged?" with a one-liner. Manual repro paths still work: docker compose build --build-arg GIT_COMMIT=$(git rev-parse HEAD) GIT_COMMIT=abc123 docker compose build --- Dockerfile | 18 +++++++++++++++++- docker-compose.yml | 10 ++++++++++ scripts/docker-run | 18 ++++++++++++++++++ 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 4a4080c9..ff3b697b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -178,11 +178,27 @@ RUN chmod 0755 /app/scripts/docker-entrypoint.sh \ && chown demos:demos /app /app/data /app/logs /app/state \ && chmod 0755 /app /app/data /app/logs /app/state +# Build-time provenance. These ARGs are populated by the build driver +# (compose passes `git rev-parse HEAD` + `git rev-parse --abbrev-ref HEAD` +# + `git diff --quiet; echo $?` + an ISO timestamp). They land in the +# image as ENV so `process.env.GIT_COMMIT` etc. resolve from +# `src/utilities/nodeVersion.ts` without shipping `.git/` into the runtime +# layer. Missing values fall through to the module's null defaults — the +# node never panics on absence. +ARG GIT_COMMIT= +ARG GIT_BRANCH= +ARG GIT_DIRTY=false +ARG BUILT_AT= + # Sensible image-level defaults. Anything else (DATABASE_URL, EXPOSED_URL, # IDENTITY_FILE, PEER_LIST_FILE, etc.) must be supplied at runtime. ENV NODE_ENV=production \ RPC_PORT=53550 \ - METRICS_HOST=0.0.0.0 + METRICS_HOST=0.0.0.0 \ + GIT_COMMIT=$GIT_COMMIT \ + GIT_BRANCH=$GIT_BRANCH \ + GIT_DIRTY=$GIT_DIRTY \ + BUILT_AT=$BUILT_AT # Exposed services: # 53550 - RPC (HTTP/JSON-RPC) diff --git a/docker-compose.yml b/docker-compose.yml index f9f47d8b..6c12a239 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -102,6 +102,16 @@ services: build: context: . dockerfile: Dockerfile + # Build-time provenance. Compose interpolates these from the + # host's git + shell at `docker compose build` time. The wrapper + # script `./scripts/docker-run` (and CI) export them; manual + # invocations also work because every variable has a sensible + # `:-` default so an unset host falls back gracefully. + args: + GIT_COMMIT: ${GIT_COMMIT:-} + GIT_BRANCH: ${GIT_BRANCH:-} + GIT_DIRTY: ${GIT_DIRTY:-false} + BUILT_AT: ${BUILT_AT:-} container_name: demos-node restart: unless-stopped depends_on: diff --git a/scripts/docker-run b/scripts/docker-run index 5c493ba0..3c030690 100755 --- a/scripts/docker-run +++ b/scripts/docker-run @@ -129,6 +129,24 @@ fi COMPOSE_ARGS=(-f docker-compose.yml) PROFILES=() +# Export build-time git provenance so `docker compose build` can bake it +# into the image (consumed by src/utilities/nodeVersion.ts and surfaced +# via getNetworkInfo.nodeVersion). Every var has a safe empty default +# so a host without git, or a non-repo working tree, still builds. +# `git diff-index --quiet HEAD` exits 1 when dirty, 0 when clean; map +# to a "true"/"false" string the node module understands. +if git -C "$(dirname "${BASH_SOURCE[0]}")/.." rev-parse --git-dir >/dev/null 2>&1; then + GIT_REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + export GIT_COMMIT="$(git -C "$GIT_REPO_DIR" rev-parse HEAD 2>/dev/null || true)" + export GIT_BRANCH="$(git -C "$GIT_REPO_DIR" rev-parse --abbrev-ref HEAD 2>/dev/null || true)" + if git -C "$GIT_REPO_DIR" diff-index --quiet HEAD 2>/dev/null; then + export GIT_DIRTY="false" + else + export GIT_DIRTY="true" + fi +fi +export BUILT_AT="$(date -u +%Y-%m-%dT%H:%M:%SZ)" + if [[ "$USE_PROXY" == "true" ]]; then if [[ ! -f docker-compose.proxy.yml ]]; then echo "docker-compose.proxy.yml missing — cannot enable proxy mode." >&2