From 7fd221288a1437dbdb23073d735319c305aa1ac9 Mon Sep 17 00:00:00 2001 From: MEGA INFRA Date: Fri, 2 Oct 2026 21:57:13 +0200 Subject: [PATCH] Make the build marker actually report the build It read 'dev' on the live site, so it was decoration rather than evidence. Cause: Coolify does not pass a commit SHA into a Docker Compose build, so my ${BUILD_SHA:-dev} fallback always won. Read from build.txt in the repo instead, which cannot drift from what is deployed. Two things that took a try each: - Docker's ENV does not evaluate $(cat file), so the file is copied into the image and read at startup. - Streamlit executes app.py in an exec context where __file__ is not guaranteed; a bare reference to it inside build_sha() would have taken the whole app down. It now walks a list of candidate paths and never raises. Verified: docker build ok; build.txt inside the image reads e1f4127; build_sha() resolves to e1f4127 in the container; the sidebar renders it. --- Dockerfile | 10 ++++++---- README.md | 6 +++++- app.py | 30 +++++++++++++++++++++++++++++- build.txt | 1 + docker-compose.yml | 3 --- update_build_marker.sh | 7 +++++++ 6 files changed, 48 insertions(+), 9 deletions(-) create mode 100644 build.txt create mode 100755 update_build_marker.sh diff --git a/Dockerfile b/Dockerfile index 383221d..2a2f1f2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,10 +19,11 @@ ENV PYTHONUNBUFFERED=1 \ WORKDIR /app -# Which build is this? Baked in at image build time so "what am I looking at?" -# is answerable from the page, not by digging through Coolify's log. -ARG BUILD_SHA=dev -ENV BOQ_BUILD_SHA=$BUILD_SHA +# Which build is this? Read from a file IN the repo rather than a build arg, +# because Coolify does not pass a commit SHA into a Docker Compose build - an +# ARG would silently fall back to "dev" forever and the marker would never +# tell you anything. Docker's ENV also does not evaluate $(cat ...), so the +# file is simply copied and the app reads it at startup. # Dependencies first, so a code change does not reinstall the world. COPY requirements.txt . @@ -32,6 +33,7 @@ COPY app.py run.py ./ COPY boq_engine/ ./boq_engine/ COPY static/ ./static/ COPY .streamlit/ ./.streamlit/ +COPY build.txt ./build.txt # Run as a non-root user. UID 1000 matches the usual Coolify/Docker convention. RUN useradd --create-home --uid 1000 --shell /bin/bash megainfra \ diff --git a/README.md b/README.md index a2b56a6..af3b58c 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,11 @@ Anything you disagree with, you settle **in the app** and regenerate. The consultant never has to leave the screen. Runs entirely on your machine. Nothing is uploaded anywhere. The sidebar shows -the build SHA, so you can always see which version you are looking at. +the build SHA, so you can always see which version you are looking at. It is +read from **`build.txt` in the repo**, not a build argument, because Coolify +does not pass a commit SHA into a Docker Compose build — an `ARG` silently +falls back to `dev` forever and the marker then tells you nothing. +`./update_build_marker.sh` rewrites it; run it as part of committing. ![the app](docs/app-screenshot.png) diff --git a/app.py b/app.py index f2c577d..68d7ea3 100644 --- a/app.py +++ b/app.py @@ -110,6 +110,34 @@ def _fp(data: bytes) -> str: return hashlib.sha256(data).hexdigest()[:16] +def build_sha() -> str: + """Which build is this? Shown in the sidebar so a deploy can be confirmed + without digging through Coolify's log. + + Falls back through several routes because Streamlit executes this file in + an exec context where __file__ is not guaranteed - a bare reference to it + would take the whole app down. Falls back to the env var, then to a + literal, and never raises. + """ + candidates = [] + try: + candidates.append(os.path.join( + os.path.dirname(os.path.abspath(__file__)), "build.txt")) + except NameError: + pass + candidates.append(os.path.join(os.getcwd(), "build.txt")) + candidates.append("/app/build.txt") + for path in candidates: + try: + with open(path, encoding="utf-8") as fh: + val = fh.read().strip() + if val and val != "dev": + return val + except OSError: + continue + return os.environ.get("BOQ_BUILD_SHA") or "dev" + + def money(v) -> str: if isinstance(v, (int, float)): return f"R{v:,.2f}" @@ -130,7 +158,7 @@ st.markdown( known = profiles.list_profiles() with st.sidebar: - st.caption(f"build {os.environ.get('BOQ_BUILD_SHA', 'dev')}") + st.caption(f"build {build_sha()}") st.subheader("Consultant profile") if known: names = ["— new / not set —"] + [p.consultant for p in known] diff --git a/build.txt b/build.txt new file mode 100644 index 0000000..65c4515 --- /dev/null +++ b/build.txt @@ -0,0 +1 @@ +bcba46a diff --git a/docker-compose.yml b/docker-compose.yml index 3a18e97..4100017 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,9 +9,6 @@ services: build: context: . dockerfile: Dockerfile - args: - # Coolify substitutes the commit it is building - BUILD_SHA: "${BUILD_SHA:-dev}" image: mega-infra/boq-converter:latest container_name: mega-boq-converter restart: unless-stopped diff --git a/update_build_marker.sh b/update_build_marker.sh new file mode 100755 index 0000000..063281e --- /dev/null +++ b/update_build_marker.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +# Rewrite build.txt to the current commit. Run as part of committing, so the +# marker in the sidebar always names the build that is actually deployed. +# Wired up as a git post-commit hook when the repo is set up locally. +set -e +cd "$(dirname "$0")" +git rev-parse --short HEAD > build.txt