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.
This commit is contained in:
MEGA INFRA 2026-10-02 21:57:13 +02:00
parent e1f41275ad
commit 7fd221288a
6 changed files with 48 additions and 9 deletions

View file

@ -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 \

View file

@ -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)

30
app.py
View file

@ -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]

1
build.txt Normal file
View file

@ -0,0 +1 @@
bcba46a

View file

@ -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

7
update_build_marker.sh Executable file
View file

@ -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