diff --git a/.gitea/scripts/publish-images.sh b/.gitea/scripts/publish-images.sh new file mode 100755 index 0000000..356e60f --- /dev/null +++ b/.gitea/scripts/publish-images.sh @@ -0,0 +1,68 @@ +#!/bin/sh +# publish-images.sh -- Abbilder je Auslieferungskanal bauen und veroeffentlichen +# (quick-260914-ku1). +# +# Kanalmodell: +# refs/heads/main -> Kanal beta, Etiketten beta + latest (latest = Alias, entfaellt spaeter) +# refs/tags/v* -> Kanal live, Etiketten live + vX.Y.Z +# alles andere -> nichts zu tun (Exit 0, kein Bau, kein Push) +# +# Der Zweig `live` OHNE Tag wird von der Pipeline geprueft, aber nicht veroeffentlicht: +# auf `live` ist jeder auslieferbare Stand ein Tag. Ein ungetaggter Merge darf das +# `live`-Etikett nicht ueberschreiben, sonst waere der Tag nicht mehr die Wahrheit. +# +# Die Entscheidung haengt allein an GITHUB_REF, damit sie lokal ohne Runner pruefbar ist: +# GITHUB_REF=refs/tags/v1.2.3 sh .gitea/scripts/publish-images.sh --print-plan +# +# Versionsstempel: APP_VERSION aus `git describe --tags --always` (ohne Tag: kurzer SHA), +# APP_COMMIT, APP_BUILD_TIME -- als --build-arg in beide Dockerfiles. Braucht im Checkout +# die volle Historie samt Tags (fetch-depth: 0 im Workflow). +# +# Dieses Skript kennt kein Secret und gibt keines aus; der Registry-Login bleibt im Workflow. +set -eu + +REGISTRY="${REGISTRY:-localhost:3002/schalli/tessera-ctl}" +REF="${GITHUB_REF:-}" + +case "$REF" in + refs/tags/v*) + APP_CHANNEL=live + TAGS="live ${REF#refs/tags/}" + ;; + refs/heads/main) + APP_CHANNEL=beta + TAGS="beta latest" + ;; + *) + echo "Kein Veroeffentlichungs-Anlass fuer '$REF' (nur main und Tags v*): nichts zu tun." + exit 0 + ;; +esac + +APP_VERSION="$(git describe --tags --always)" +APP_COMMIT="$(git rev-parse --short HEAD)" +APP_BUILD_TIME="$(date -u +%Y-%m-%dT%H:%M:%SZ)" + +echo "Tessera $APP_VERSION ($APP_CHANNEL) $APP_COMMIT $APP_BUILD_TIME -> Etiketten: $TAGS" + +if [ "${1:-}" = "--print-plan" ]; then + for IMG in web api; do + for TAG in $TAGS; do + echo "push $REGISTRY/$IMG:$TAG" + done + done + exit 0 +fi + +for IMG in web api; do + docker build -t "$REGISTRY/$IMG:$APP_CHANNEL" \ + --build-arg APP_VERSION="$APP_VERSION" \ + --build-arg APP_CHANNEL="$APP_CHANNEL" \ + --build-arg APP_COMMIT="$APP_COMMIT" \ + --build-arg APP_BUILD_TIME="$APP_BUILD_TIME" \ + -f "apps/$IMG/Dockerfile" . + for TAG in $TAGS; do + docker tag "$REGISTRY/$IMG:$APP_CHANNEL" "$REGISTRY/$IMG:$TAG" + docker push "$REGISTRY/$IMG:$TAG" + done +done diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index bcb8bee..2d2fd7a 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -1,8 +1,12 @@ +# Kanalmodell (quick-260914-ku1): main -> Kanal beta (Etiketten beta + latest); +# Tag v* -> Kanal live (Etiketten live + vX.Y.Z); Zweig live ohne Tag wird nur geprueft. +# Die Entscheidung trifft .gitea/scripts/publish-images.sh anhand GITHUB_REF. name: Tessera CI/CD on: push: - branches: [main] + branches: [main, live] + tags: ['v*'] jobs: quality: @@ -52,18 +56,13 @@ jobs: runs-on: ubuntu-latest needs: test steps: + # Ohne volle Historie und Tags liefert `git describe` nichts -- Pflicht fuer den Stempel. - uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Log in to Gitea Container Registry run: echo "${{ secrets.REGISTRY_TOKEN }}" | docker login localhost:3002 -u ${{ gitea.actor }} --password-stdin - - name: Build web image - run: docker build -t localhost:3002/schalli/tessera-ctl/web:latest -f apps/web/Dockerfile . - - - name: Build api image - run: docker build -t localhost:3002/schalli/tessera-ctl/api:latest -f apps/api/Dockerfile . - - - name: Push images - run: | - docker push localhost:3002/schalli/tessera-ctl/web:latest - docker push localhost:3002/schalli/tessera-ctl/api:latest + - name: Versionsstempel berechnen, Abbilder bauen und veroeffentlichen + run: sh .gitea/scripts/publish-images.sh diff --git a/apps/api/Dockerfile b/apps/api/Dockerfile index 0d32e38..592f314 100644 --- a/apps/api/Dockerfile +++ b/apps/api/Dockerfile @@ -1,3 +1,11 @@ +# Versionsstempel (quick-260914-ku1): die Werte setzt .gitea/scripts/publish-images.sh +# per --build-arg; lokal greifen die Vorgaben (dev). Ein globales ARG liefert nur die +# Vorgabe -- jede nutzende Stufe wiederholt deshalb `ARG NAME` ohne Wert. +ARG APP_VERSION=dev +ARG APP_CHANNEL=dev +ARG APP_COMMIT= +ARG APP_BUILD_TIME= + FROM node:24-alpine AS base RUN corepack enable && corepack prepare pnpm@9 --activate @@ -20,6 +28,11 @@ RUN pnpm --filter=@tessera/api build FROM base AS runner WORKDIR /app ENV NODE_ENV=production +ARG APP_VERSION +ARG APP_CHANNEL +ARG APP_COMMIT +ARG APP_BUILD_TIME +ENV APP_VERSION=$APP_VERSION APP_CHANNEL=$APP_CHANNEL APP_COMMIT=$APP_COMMIT APP_BUILD_TIME=$APP_BUILD_TIME RUN addgroup --system --gid 1001 nestjs && \ adduser --system --uid 1001 nestjs && \ mkdir -p /app/user-files && \ diff --git a/apps/web/Dockerfile b/apps/web/Dockerfile index 2454dba..f2163ff 100644 --- a/apps/web/Dockerfile +++ b/apps/web/Dockerfile @@ -1,3 +1,11 @@ +# Versionsstempel (quick-260914-ku1): die Werte setzt .gitea/scripts/publish-images.sh +# per --build-arg; lokal greifen die Vorgaben (dev). Ein globales ARG liefert nur die +# Vorgabe -- jede nutzende Stufe wiederholt deshalb `ARG NAME` ohne Wert. +ARG APP_VERSION=dev +ARG APP_CHANNEL=dev +ARG APP_COMMIT= +ARG APP_BUILD_TIME= + FROM node:24-alpine AS base RUN corepack enable && corepack prepare pnpm@9 --activate @@ -15,11 +23,22 @@ COPY apps/web/ ./apps/web/ COPY packages/shared/ ./packages/shared/ COPY tsconfig.base.json ./ ENV NEXT_PUBLIC_API_URL=/api-proxy +# Muss VOR dem Build stehen: Next.js bettet NEXT_PUBLIC_* zur Bauzeit ins Bundle ein. +# So spaet wie moeglich, damit die COPY-Schichten darueber im Cache bleiben. +ARG APP_VERSION +ARG APP_CHANNEL +ARG APP_COMMIT +ENV NEXT_PUBLIC_APP_VERSION=$APP_VERSION NEXT_PUBLIC_APP_CHANNEL=$APP_CHANNEL NEXT_PUBLIC_APP_COMMIT=$APP_COMMIT RUN pnpm --filter=@tessera/web build FROM node:24-alpine AS runner WORKDIR /app ENV NODE_ENV=production +ARG APP_VERSION +ARG APP_CHANNEL +ARG APP_COMMIT +ARG APP_BUILD_TIME +ENV APP_VERSION=$APP_VERSION APP_CHANNEL=$APP_CHANNEL APP_COMMIT=$APP_COMMIT APP_BUILD_TIME=$APP_BUILD_TIME RUN addgroup --system --gid 1001 nodejs && \ adduser --system --uid 1001 nextjs COPY --from=builder /app/apps/web/public ./apps/web/public diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index b20f383..394ec28 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -1,6 +1,8 @@ services: web: - image: git.vicolab.de/schalli/tessera-ctl/web:latest + # IMAGE_TAG in .env selects the delivery channel: beta (alpha, all new + # changes) or live (released versions only). Defaults to beta. + image: git.vicolab.de/schalli/tessera-ctl/web:${IMAGE_TAG:-beta} restart: unless-stopped ports: - "3000:3000" @@ -20,7 +22,8 @@ services: condition: service_healthy api: - image: git.vicolab.de/schalli/tessera-ctl/api:latest + # Same channel as web, see IMAGE_TAG above. + image: git.vicolab.de/schalli/tessera-ctl/api:${IMAGE_TAG:-beta} restart: unless-stopped ports: - "3001:3001"