feat(version): /api/version + build-time version/commit stamping

Add a VERSION file (0.1.0) and inject APP_VERSION/GIT_SHA/BUILD_DATE as Docker
build-args (both stages; Vite inlines them into the SPA). New public GET
/api/version returns app_version, git_sha, build_date and the Alembic head as the
database revision. deploy.sh and the localdev build pass the args.
This commit is contained in:
npeter83 2026-06-15 00:06:57 +02:00
parent 5dd3109286
commit a93ab30fb2
7 changed files with 56 additions and 2 deletions

View file

@ -1,5 +1,15 @@
# Build/version info, injected via --build-arg (defaults keep un-built/dev runs working).
ARG APP_VERSION=dev
ARG GIT_SHA=unknown
ARG BUILD_DATE=
# Stage 1: build the frontend SPA # Stage 1: build the frontend SPA
FROM node:20-alpine AS frontend 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 WORKDIR /fe
COPY frontend/package.json ./ COPY frontend/package.json ./
RUN npm install RUN npm install
@ -8,10 +18,16 @@ RUN npm run build
# Stage 2: backend runtime, serving the built SPA # Stage 2: backend runtime, serving the built SPA
FROM python:3.12-slim FROM python:3.12-slim
ARG APP_VERSION
ARG GIT_SHA
ARG BUILD_DATE
ENV PYTHONDONTWRITEBYTECODE=1 \ ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \ PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=1 \ PIP_NO_CACHE_DIR=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1 PIP_DISABLE_PIP_VERSION_CHECK=1 \
APP_VERSION=$APP_VERSION \
GIT_SHA=$GIT_SHA \
BUILD_DATE=$BUILD_DATE
WORKDIR /app WORKDIR /app

1
VERSION Normal file
View file

@ -0,0 +1 @@
0.1.0

View file

@ -9,6 +9,11 @@ class Settings(BaseSettings):
model_config = SettingsConfigDict(env_file=".env", extra="ignore") model_config = SettingsConfigDict(env_file=".env", extra="ignore")
app_name: str = "Siftlode" 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"
git_sha: str = "unknown"
build_date: str = ""
database_url: str = "postgresql+psycopg://subfeed:subfeed@db:5432/subfeed" database_url: str = "postgresql+psycopg://subfeed:subfeed@db:5432/subfeed"

View file

@ -26,7 +26,7 @@ from starlette.middleware.sessions import SessionMiddleware
from app import auth from app import auth
from app.config import settings from app.config import settings
from app.routes import admin, channels, feed, health, me, quota, sync, tags from app.routes import admin, channels, feed, health, me, quota, sync, tags, version
from app.scheduler import shutdown_scheduler, start_scheduler from app.scheduler import shutdown_scheduler, start_scheduler
@ -68,6 +68,7 @@ app.include_router(me.router)
app.include_router(channels.router) app.include_router(channels.router)
app.include_router(admin.router) app.include_router(admin.router)
app.include_router(quota.router) app.include_router(quota.router)
app.include_router(version.router)
# The built SPA (populated by the Docker frontend build stage). # The built SPA (populated by the Docker frontend build stage).
STATIC_DIR = Path(__file__).parent / "static_spa" STATIC_DIR = Path(__file__).parent / "static_spa"

View file

@ -0,0 +1,23 @@
from fastapi import APIRouter, Depends
from sqlalchemy import text
from sqlalchemy.orm import Session
from app.config import settings
from app.db import get_db
router = APIRouter(prefix="/api", tags=["version"])
@router.get("/version")
def get_version(db: Session = Depends(get_db)) -> dict:
"""Build + schema version, for the About dialog and the 'new version' banner.
Intentionally public (no auth) and cheap: just env-baked build info plus the current
Alembic migration head (the 'database version'). app_version/git_sha/build_date are
injected at image build time (see the Dockerfile build-args)."""
db_revision = db.execute(text("SELECT version_num FROM alembic_version")).scalar()
return {
"app_version": settings.app_version,
"git_sha": settings.git_sha,
"build_date": settings.build_date or None,
"db_revision": db_revision,
}

View file

@ -21,6 +21,10 @@ services:
build: build:
context: . context: .
dockerfile: Dockerfile dockerfile: Dockerfile
args:
APP_VERSION: ${APP_VERSION:-dev}
GIT_SHA: ${GIT_SHA:-unknown}
BUILD_DATE: ${BUILD_DATE:-}
env_file: .env env_file: .env
environment: environment:
# Exactly one scheduler may write to the shared DB; the server owns it. # Exactly one scheduler may write to the shared DB; the server owns it.

View file

@ -18,6 +18,10 @@ services:
build: build:
context: . context: .
dockerfile: Dockerfile dockerfile: Dockerfile
args:
APP_VERSION: ${APP_VERSION:-dev}
GIT_SHA: ${GIT_SHA:-unknown}
BUILD_DATE: ${BUILD_DATE:-}
env_file: .env env_file: .env
environment: environment:
DATABASE_URL: postgresql+psycopg://${POSTGRES_USER:-subfeed}:${POSTGRES_PASSWORD:-subfeed}@db:5432/${POSTGRES_DB:-subfeed} DATABASE_URL: postgresql+psycopg://${POSTGRES_USER:-subfeed}:${POSTGRES_PASSWORD:-subfeed}@db:5432/${POSTGRES_DB:-subfeed}