feat(ops): /api/version endpoint with git SHA + build time

Adds a public, no-auth endpoint at /api/version that returns:
  { commit, commitShort, buildTime, nodeEnv, cutover, nextVersion }

Build-time injection via GIT_COMMIT + BUILD_TIME ARG/ENV propagated
from compose build.args through Dockerfile builder + runner stages.
Excluded from middleware auth gating.

Deploy command (run on satra after git pull):
  GIT_COMMIT=$(git rev-parse HEAD) \
    BUILD_TIME=$(date -u +%FT%TZ) \
    docker compose build architools

Without these env vars, falls back to "unknown" so the build never
fails; only the endpoint shows reduced info.

Useful for: confirming what's actually deployed after CI, cross-app
deploy correlation (api.gis.ac, eterra.live, orchestrator), uptime
monitors.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude VM
2026-05-18 23:06:35 +03:00
parent 6054d083b5
commit 64bccdb4b0
4 changed files with 47 additions and 1 deletions
+11
View File
@@ -26,11 +26,16 @@ ARG NEXT_PUBLIC_APP_NAME=ArchiTools
ARG NEXT_PUBLIC_APP_URL=https://tools.beletage.ro
ARG NEXT_PUBLIC_MARTIN_URL=https://tools.beletage.ro/tiles
ARG NEXT_PUBLIC_PMTILES_URL=
# Version metadata baked at build time → /api/version
ARG GIT_COMMIT=unknown
ARG BUILD_TIME=unknown
ENV NEXT_PUBLIC_STORAGE_ADAPTER=${NEXT_PUBLIC_STORAGE_ADAPTER}
ENV NEXT_PUBLIC_APP_NAME=${NEXT_PUBLIC_APP_NAME}
ENV NEXT_PUBLIC_APP_URL=${NEXT_PUBLIC_APP_URL}
ENV NEXT_PUBLIC_MARTIN_URL=${NEXT_PUBLIC_MARTIN_URL}
ENV NEXT_PUBLIC_PMTILES_URL=${NEXT_PUBLIC_PMTILES_URL}
ENV GIT_COMMIT=${GIT_COMMIT}
ENV BUILD_TIME=${BUILD_TIME}
# Increase memory for Next.js build if VM has limited RAM
ENV NODE_OPTIONS="--max-old-space-size=2048"
@@ -40,6 +45,12 @@ RUN npm run build
FROM node:22-alpine AS runner
WORKDIR /app
# Re-declare build-time version args so they propagate into runner ENV
ARG GIT_COMMIT=unknown
ARG BUILD_TIME=unknown
ENV GIT_COMMIT=${GIT_COMMIT}
ENV BUILD_TIME=${BUILD_TIME}
ENV NODE_ENV=production
ENV TZ=Europe/Bucharest
+4
View File
@@ -8,6 +8,10 @@ services:
- NEXT_PUBLIC_APP_URL=${NEXT_PUBLIC_APP_URL:-https://tools.beletage.ro}
- NEXT_PUBLIC_MARTIN_URL=${NEXT_PUBLIC_MARTIN_URL}
- NEXT_PUBLIC_PMTILES_URL=${NEXT_PUBLIC_PMTILES_URL}
# Inject git SHA + build timestamp; deploy via:
# GIT_COMMIT=$(git rev-parse HEAD) BUILD_TIME=$(date -u +%FT%TZ) docker compose build
- GIT_COMMIT=${GIT_COMMIT:-unknown}
- BUILD_TIME=${BUILD_TIME:-unknown}
container_name: architools
restart: unless-stopped
ports:
+31
View File
@@ -0,0 +1,31 @@
import { NextResponse } from "next/server";
export const runtime = "nodejs";
export const dynamic = "force-dynamic";
/**
* Public version endpoint. Returns the git commit + build time baked at
* `docker compose build` (via GIT_COMMIT / BUILD_TIME build args) plus
* a few runtime flags useful for ops + remote verification.
*
* No auth. Excluded from middleware matcher.
*
* Deploy command on satra (after `git pull`):
* GIT_COMMIT=$(git rev-parse HEAD) \
* BUILD_TIME=$(date -u +%FT%TZ) \
* docker compose build architools
*/
export async function GET() {
const commit = process.env.GIT_COMMIT || "unknown";
return NextResponse.json({
commit,
commitShort: commit.slice(0, 7),
buildTime: process.env.BUILD_TIME || "unknown",
nodeEnv: process.env.NODE_ENV || "unknown",
cutover: {
useGisAcDefault: process.env.USE_GIS_AC === "1",
pilotUsers: (process.env.GIS_AC_PILOT_USERS || "").split(",").filter(Boolean).length,
},
nextVersion: "16.1.6",
});
}
+1 -1
View File
@@ -58,6 +58,6 @@ export const config = {
* - /favicon.ico, /robots.txt, /sitemap.xml
* - Files with extensions (images, fonts, etc.)
*/
"/((?!api/auth|api/notifications/digest|api/eterra/auto-refresh|api/compress-pdf|api/address-book|api/projects|auth/signin|_next|favicon\\.ico|robots\\.txt|sitemap\\.xml|.*\\..*).*)",
"/((?!api/auth|api/version|api/notifications/digest|api/eterra/auto-refresh|api/compress-pdf|api/address-book|api/projects|auth/signin|_next|favicon\\.ico|robots\\.txt|sitemap\\.xml|.*\\..*).*)",
],
};