diff --git a/Dockerfile b/Dockerfile index 806b8c9..d5aa7fd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,7 @@ -# Build/version info, injected via --build-arg (defaults keep un-built/dev runs working). -ARG APP_VERSION=dev +# Build/version info. APP_VERSION comes from the committed VERSION file (always correct, +# even for a plain `docker compose build`); GIT_SHA/BUILD_DATE are best-effort build-args +# the deploy scripts pass. An explicit APP_VERSION build-arg still overrides the file. +ARG APP_VERSION= ARG GIT_SHA=unknown ARG BUILD_DATE= @@ -8,24 +10,25 @@ FROM node:20-alpine AS frontend ARG APP_VERSION ARG GIT_SHA ARG BUILD_DATE -# Vite inlines VITE_*-prefixed env into the bundle at build time. -ENV VITE_APP_VERSION=$APP_VERSION VITE_GIT_SHA=$GIT_SHA VITE_BUILD_DATE=$BUILD_DATE WORKDIR /fe COPY frontend/package.json ./ RUN npm install COPY frontend/ ./ -RUN npm run build +COPY VERSION ./.version +# Vite inlines VITE_*-prefixed env at build time; version falls back to the VERSION file. +RUN VITE_APP_VERSION="${APP_VERSION:-$(cat ./.version)}" \ + VITE_GIT_SHA="$GIT_SHA" \ + VITE_BUILD_DATE="$BUILD_DATE" \ + npm run build # Stage 2: backend runtime, serving the built SPA FROM python:3.12-slim -ARG APP_VERSION ARG GIT_SHA ARG BUILD_DATE ENV PYTHONDONTWRITEBYTECODE=1 \ PYTHONUNBUFFERED=1 \ PIP_NO_CACHE_DIR=1 \ PIP_DISABLE_PIP_VERSION_CHECK=1 \ - APP_VERSION=$APP_VERSION \ GIT_SHA=$GIT_SHA \ BUILD_DATE=$BUILD_DATE @@ -35,6 +38,8 @@ COPY backend/requirements.txt . RUN pip install --upgrade pip && pip install -r requirements.txt COPY backend/ . +# Read at runtime by config.py as the app_version (a committed single source of truth). +COPY VERSION ./VERSION COPY --from=frontend /fe/dist ./app/static_spa RUN adduser --disabled-password --gecos "" appuser && chown -R appuser /app diff --git a/backend/app/config.py b/backend/app/config.py index 0f796ab..3e7db21 100644 --- a/backend/app/config.py +++ b/backend/app/config.py @@ -1,3 +1,5 @@ +from pathlib import Path + from pydantic import model_validator from pydantic_settings import BaseSettings, SettingsConfigDict @@ -5,13 +7,26 @@ from pydantic_settings import BaseSettings, SettingsConfigDict _DEFAULT_SECRET_KEY = "change-me-session-key" +def _version_from_file() -> str: + """The committed VERSION file (copied into the image at /app/VERSION) is the single + source of truth for the app version; falls back to 'dev' for un-built/local runs.""" + for p in (Path(__file__).resolve().parents[1] / "VERSION", Path("VERSION")): + try: + v = p.read_text(encoding="utf-8").strip() + if v: + return v + except OSError: + continue + return "dev" + + class Settings(BaseSettings): model_config = SettingsConfigDict(env_file=".env", extra="ignore") app_name: str = "Siftlode" - # Build/version info, injected at image build time (Docker build-args -> env). Defaults - # apply for un-built/dev runs; the About dialog and version banner surface these. - app_version: str = "dev" + # Version from the committed VERSION file (an explicit APP_VERSION env still overrides); + # git_sha/build_date are injected at image build time. Surfaced by /api/version. + app_version: str = _version_from_file() git_sha: str = "unknown" build_date: str = ""