5c42c558c4
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018N9CD3ebPKm1b32bPpBknY
362 lines
21 KiB
Markdown
362 lines
21 KiB
Markdown
---
|
||
phase: quick-260914-ku1
|
||
plan: 01
|
||
subsystem: infra
|
||
tags: [ci, gitea-actions, docker, build-args, next-public-env, nestjs, health, versionsstempel, compose, handbuch]
|
||
status: complete
|
||
|
||
requires:
|
||
- phase: quick-260914-ku1 planning (1cd4212)
|
||
provides: Plan mit Erlaubnisliste, gemessenen Bezugszahlen und Threat-Register
|
||
provides:
|
||
- "GET /health/version liefert { name, version, channel, commit, buildTime } aus APP_* (Vorgaben dev/dev), Startzeile `Tessera API <version> (<channel>) <commit>`"
|
||
- "VersionResponse in packages/shared; apps/web/src/lib/app-version.ts als importierbare Quelle (appVersion + loadApiVersion) fuer Abzeichen und kommenden Fehler-melden-Knopf"
|
||
- "AppVersionBadge unten in der Seitenleiste (Desktop und mobile Schublade, nicht eingeklappt) mit Tooltip Commit/API-Version"
|
||
- "Beide Dockerfiles nehmen APP_VERSION/APP_CHANNEL/APP_COMMIT/APP_BUILD_TIME als Build-Args; Web bettet NEXT_PUBLIC_APP_* zur Bauzeit ein"
|
||
- ".gitea/scripts/publish-images.sh entscheidet Kanal/Etiketten aus GITHUB_REF (main -> beta+latest, v* -> live+vX.Y.Z, sonst nichts), --print-plan ohne Docker"
|
||
- "ci.yml loest auf main, live und Tags v* aus; publish mit fetch-depth 0 und Skriptaufruf"
|
||
- "docker-compose.prod.yml mit ${IMAGE_TAG:-beta} fuer web und api"
|
||
- "Betriebshandbuch Kapitel 9 (Kanaele, IMAGE_TAG je Server, Freigabe, Hotfix ohne Datenbankaenderung, Versionskontrolle, neuer Live-Server); ci-cd-setup auf gemessenen Stand"
|
||
affects: [fehler-melden-knopf, erstfreigabe-v1.0.0, live-server-einrichtung, deploy]
|
||
|
||
actuals:
|
||
tokens: 41545
|
||
tasks: 3
|
||
commits: 3
|
||
plan_head_before: 1cd4212df08cb0910e6c5c02abf6926534951935
|
||
|
||
tech-stack:
|
||
added: []
|
||
patterns:
|
||
- "Versionsstempel-Kette: CI-Skript -> --build-arg -> globales ARG + ARG-Wiederholung je Stufe -> ENV (runner) bzw. NEXT_PUBLIC_* vor pnpm build (web-builder)"
|
||
- "Kanalentscheidung im POSIX-Skript statt in Workflow-if-Ausdruecken, lokal per --print-plan pruefbar"
|
||
- "process.env.NEXT_PUBLIC_* nur mit vollem Literalnamen lesen (Bauzeit-Einbettung durch Next.js)"
|
||
- "||-Vorgaben fuer Compose-Leerstring-Semantik (leer == ungesetzt)"
|
||
|
||
key-files:
|
||
created:
|
||
- apps/api/src/health/app-version.ts
|
||
- apps/api/src/health/health.controller.spec.ts
|
||
- apps/web/src/lib/app-version.ts
|
||
- apps/web/src/lib/app-version.test.ts
|
||
- apps/web/src/components/layout/app-version-badge.tsx
|
||
- apps/web/src/components/layout/app-version-badge.test.tsx
|
||
- .gitea/scripts/publish-images.sh
|
||
modified:
|
||
- packages/shared/src/index.ts
|
||
- apps/api/src/health/health.controller.ts
|
||
- apps/api/src/main.ts
|
||
- apps/web/src/components/layout/sidebar.tsx
|
||
- apps/web/src/components/layout/sidebar.test.tsx
|
||
- apps/web/src/messages/de.json
|
||
- apps/web/src/messages/en.json
|
||
- apps/web/Dockerfile
|
||
- apps/api/Dockerfile
|
||
- .gitea/workflows/ci.yml
|
||
- docker-compose.prod.yml
|
||
- docs/anleitung-betrieb.md
|
||
- docs/ci-cd-setup.md
|
||
|
||
key-decisions:
|
||
- "Kanal ist die Primaerdarstellung (IMAGE_TAG, APP_CHANNEL); `latest` nur noch Alias von `beta`, damit alpha ohne Handgriff weiterlaeuft"
|
||
- "Zweig `live` ohne Tag wird geprueft, aber nicht veroeffentlicht — nur ein Tag darf das live-Etikett belegen (T-KU1-07)"
|
||
- "package.json-Versionen bleiben 0.0.1; die Wahrheit der Version ist der Git-Tag (git describe)"
|
||
- "GET /health/version bleibt @Public (T-KU1-03), per Spec-Test gepinnt"
|
||
- "apps/web spiegelt den Antworttyp lokal (ApiVersionInfo) statt @tessera/shared zu importieren — Lockfile und Docker-deps-Stufe bleiben unangetastet"
|
||
- "Commits direkt auf main (Projektkonvention branching_strategy: none; das Kanalmodell setzt main = Beta voraus)"
|
||
|
||
patterns-established:
|
||
- "ARG-Sichtbarkeit in Multi-Stage-Dockerfiles: globales ARG mit Vorgabe + ARG NAME (ohne Wert) in jeder nutzenden Stufe"
|
||
- "Memoisiertes Modul-Promise fuer einmalige API-Abfragen je Seitenladung, still bei Fehler"
|
||
|
||
requirements-completed: [QUICK-260914-KU1]
|
||
|
||
coverage:
|
||
- id: D1
|
||
description: "GET /health/version aus APP_* mit Vorgaben, Compose-Leerstring-Semantik, Startzeile, @Public gepinnt"
|
||
requirement: QUICK-260914-KU1
|
||
verification:
|
||
- kind: unit
|
||
ref: "apps/api/src/health/health.controller.spec.ts (6 Tests)"
|
||
status: pass
|
||
- kind: integration
|
||
ref: "docker run tessera-ku1-api:args node -e formatAppVersionLine() -> Tessera API v9.9.9-test (live) abc1234"
|
||
status: pass
|
||
- id: D2
|
||
description: "Web-Quelle app-version.ts (appVersion, loadApiVersion memoisiert/still) und AppVersionBadge in der Seitenleiste"
|
||
requirement: QUICK-260914-KU1
|
||
verification:
|
||
- kind: unit
|
||
ref: "apps/web/src/lib/app-version.test.ts (5), app-version-badge.test.tsx (4), sidebar.test.tsx#renders the version badge below the navigation"
|
||
status: pass
|
||
- kind: integration
|
||
ref: "grep -rl v9.9.9-test /app/apps/web/.next/static | wc -l -> 1 (Bauzeit-Einbettung)"
|
||
status: pass
|
||
- id: D3
|
||
description: "CI-Trigger je Kanal, publish-images.sh, Build-Args in beiden Dockerfiles, IMAGE_TAG in Compose"
|
||
requirement: QUICK-260914-KU1
|
||
verification:
|
||
- kind: automated_ui
|
||
ref: "js-yaml-Strukturpruefung, --print-plan in drei Lagen, docker compose config --images mit/ohne IMAGE_TAG"
|
||
status: pass
|
||
- kind: e2e
|
||
ref: "Gitea-Actions-Lauf 297 (success) und CI-gebaute :beta-Abbilder mit APP_VERSION=ea6aa99 APP_CHANNEL=beta"
|
||
status: pass
|
||
- id: D4
|
||
description: "Betriebshandbuch Kapitel 9 und ci-cd-setup auf gemessenen Stand"
|
||
requirement: QUICK-260914-KU1
|
||
verification:
|
||
- kind: other
|
||
ref: "grep-Gates (Kapitelueberschrift 1, IMAGE_TAG 11, Hotfix-Regel 1, veraltete Aussagen 0, Skriptname 4, Umlaute in ci-cd-setup 0)"
|
||
status: pass
|
||
|
||
metrics:
|
||
duration: "22 min (13:28Z bis 13:50Z, davon ca. 7,5 min lokale Docker-Bauten und 5,3 min CI-Lauf)"
|
||
completed: "2026-09-14"
|
||
---
|
||
|
||
# Quick 260914-ku1 Plan 01: Zwei Auslieferungskanaele (Beta auf main, Live per Tag) mit Versionsstempel durch alle Schichten — Summary
|
||
|
||
Versionsstempel `APP_VERSION/APP_CHANNEL/APP_COMMIT/APP_BUILD_TIME` fliesst vom CI-Skript ueber Build-Args in beide Abbilder, aus `GET /health/version` und dem Startlog der API und als `v<Version> · <Kanal>` unten in der Seitenleiste; `main` veroeffentlicht `beta`+`latest`, ein Tag `v*` veroeffentlicht `live`+`vX.Y.Z`; `docker-compose.prod.yml` waehlt den Kanal ueber `${IMAGE_TAG:-beta}`; das Betriebshandbuch erklaert Kanaele, Freigabe, Hotfix (ohne Datenbankaenderung) und den neuen Live-Server. Der echte CI-Weg ist einmal durchlaufen: Lauf 297 hat `:beta`-Abbilder mit `ea6aa99 beta` gebaut.
|
||
|
||
## Ausgangslage und Bezugspunkt
|
||
|
||
Alle Gates gegen `6c19451` (Code unangetastet seit Planung; HEAD bei Start `1cd4212`, Arbeitsbaum sauber, `main == origin/main`).
|
||
|
||
Baseline vor jeder Aenderung (erneut gemessen, identisch mit der Planung):
|
||
|
||
| Suite | Befehl | Ergebnis |
|
||
|---|---|---|
|
||
| API | `pnpm -C apps/api exec vitest run` | `Test Files 64 passed (64)` / `Tests 1054 passed (1054)`, Exit 0 |
|
||
| Web | `pnpm -C apps/web exec vitest run` | `Test Files 38 passed (38)` / `Tests 233 passed (233)`, Exit 0 |
|
||
|
||
## Task 1 — Versionsstempel durch alle Schichten (TDD)
|
||
|
||
### RED-Lauf (Schritt A, vor jedem Produktionscode)
|
||
|
||
`pnpm -C apps/api exec vitest run src/health/health.controller.spec.ts` (Exit 1):
|
||
|
||
```
|
||
Error: Cannot find module './app-version' imported from '.../apps/api/src/health/health.controller.spec.ts'
|
||
Test Files 1 failed (1)
|
||
Tests no tests
|
||
```
|
||
|
||
`pnpm -C apps/web exec vitest run src/lib/app-version.test.ts src/components/layout/app-version-badge.test.tsx src/components/layout/sidebar.test.tsx` (Exit 1):
|
||
|
||
```
|
||
❯ src/components/layout/app-version-badge.test.tsx (0 test) Failed to resolve import "./app-version-badge"
|
||
❯ src/lib/app-version.test.ts (0 test) Failed to resolve import "./app-version"
|
||
❯ src/components/layout/sidebar.test.tsx (6 tests | 1 failed)
|
||
× renders the version badge below the navigation Unable to find an element by: [data-testid="app-version-badge"]
|
||
Test Files 3 failed (3)
|
||
Tests 1 failed | 5 passed (6)
|
||
```
|
||
|
||
### GREEN und Gate (Schritt B/C)
|
||
|
||
Verify-Block von Task 1, gemessen:
|
||
|
||
| Pruefung | Erwartet | Gemessen |
|
||
|---|---|---|
|
||
| API-Spec `health.controller.spec.ts` | `Tests 6 passed (6)` | `Tests 6 passed (6)` |
|
||
| Web drei Specs | `Test Files 3 passed (3)` / `Tests 15 passed (15)` | `Test Files 3 passed (3)` / `Tests 15 passed (15)` |
|
||
| Plan-Checker-Zusatz: obige drei + `umlaut-guard.spec.ts` + `tenderRadar-parity.spec.ts` in EINEM Aufruf | gruen | `Test Files 5 passed (5)` / `Tests 21 passed (21)` |
|
||
| `grep -c npm_package_version health.controller.ts` | 0 | 0 |
|
||
| `grep -c "formatAppVersionLine()" main.ts` | 1 | 1 |
|
||
| `grep -c process.env.NEXT_PUBLIC_APP_VERSION app-version.ts` | 1 | 1 |
|
||
| `grep -c "<AppVersionBadge />" sidebar.tsx` | 1 | 1 |
|
||
| Node-Zeile Uebersetzungen | `Beta Live Entwicklung \| Development` | `Beta Live Entwicklung \| Development` |
|
||
| `tsc --noEmit` shared / api / web | 0 / 0 / 0 | 0 / 0 / 0 |
|
||
| API-Suite voll | 65 / 1060 | `Test Files 65 passed (65)` / `Tests 1060 passed (1060)` |
|
||
| Web-Suite voll | 40 / 243 | `Test Files 40 passed (40)` / `Tests 243 passed (243)` |
|
||
|
||
Commit `cdb571c` — `git show --stat HEAD` zeigt 13 Dateien (471+/8-).
|
||
|
||
## Task 2 — Build-Args, Veroeffentlichungs-Skript, CI-Trigger, IMAGE_TAG
|
||
|
||
### Gates ohne Docker (Schritt F), gemessen
|
||
|
||
```
|
||
branches=["main","live"] tags=["v*"] jobs=quality,test,publish fetchDepth=0 script=true needs=test
|
||
main_plan=4 tag_plan=4 live_plan=0 stamp=1
|
||
compose_beta=2 compose_live=2 latest-in-compose=0
|
||
ARG APP_VERSION=dev: api 1 / web 1; NEXT_PUBLIC_APP_VERSION=$APP_VERSION: 1; ENV APP_VERSION=$APP_VERSION: web 1 / api 1; EXEC=0
|
||
git describe --tags --always -> cdb571c (kein Tag vorhanden, Bau vor dem ersten Tag scheitert nicht)
|
||
```
|
||
|
||
`--print-plan` in den drei Lagen (woertlich):
|
||
|
||
```
|
||
GITHUB_REF=refs/heads/main:
|
||
Tessera cdb571c (beta) cdb571c 2026-09-14T13:33:40Z -> Etiketten: beta latest
|
||
push localhost:3002/schalli/tessera-ctl/web:beta
|
||
push localhost:3002/schalli/tessera-ctl/web:latest
|
||
push localhost:3002/schalli/tessera-ctl/api:beta
|
||
push localhost:3002/schalli/tessera-ctl/api:latest
|
||
|
||
GITHUB_REF=refs/tags/v1.2.3:
|
||
Tessera cdb571c (live) cdb571c 2026-09-14T13:33:40Z -> Etiketten: live v1.2.3
|
||
push localhost:3002/schalli/tessera-ctl/web:live
|
||
push localhost:3002/schalli/tessera-ctl/web:v1.2.3
|
||
push localhost:3002/schalli/tessera-ctl/api:live
|
||
push localhost:3002/schalli/tessera-ctl/api:v1.2.3
|
||
|
||
GITHUB_REF=refs/heads/live:
|
||
Kein Veroeffentlichungs-Anlass fuer 'refs/heads/live' (nur main und Tags v*): nichts zu tun.
|
||
```
|
||
|
||
`docker compose -f docker-compose.prod.yml config --images` ohne Variable: `web:beta`, `api:beta`, `postgres:16-alpine`; mit `IMAGE_TAG=live`: `api:live`, `postgres:16-alpine`, `web:live`.
|
||
|
||
### Falsifizierung Bauzeit-Einbettung (Schritt E)
|
||
|
||
Baudauer (warmer deps-Cache): api:args 111 s, web:args 129 s, api:noargs 113 s, web:noargs 99 s — zusammen 7 min 32 s, alle Exit 0.
|
||
|
||
| # | Befehl (Kurzform) | Erwartet | Gemessen |
|
||
|---|---|---|---|
|
||
| 1 | `api:args` node `APP_VERSION APP_CHANNEL APP_COMMIT APP_BUILD_TIME` | `v9.9.9-test live abc1234 2026-09-14T00:00:00Z` | `v9.9.9-test live abc1234 2026-09-14T00:00:00Z` |
|
||
| 2 | `api:args` node `require("/app/apps/api/dist/health/app-version").formatAppVersionLine()` | `Tessera API v9.9.9-test (live) abc1234` | `Tessera API v9.9.9-test (live) abc1234` |
|
||
| 3 | `web:args` sh `grep -rl v9.9.9-test /app/apps/web/.next/static \| wc -l` | >= 1 | `1` |
|
||
| 4 | `web:args` node `APP_VERSION` | `v9.9.9-test` | `v9.9.9-test` |
|
||
| 5 | `api:noargs` node `APP_VERSION APP_CHANNEL` / `web:noargs` node `APP_VERSION` | `dev dev` / `dev` | `dev dev` / `dev` |
|
||
| 6 | `web:noargs` Bundle-Grep auf `v9.9.9-test` | `0` | `0` |
|
||
| 6b | `api:noargs` dist-Zeile (Zusatz) | `Tessera API dev (dev)` | `Tessera API dev (dev)` |
|
||
|
||
Aufgeraeumt: `docker rmi` der vier `tessera-ku1-*`-Etiketten und `tessera-web-plancheck:baseline` (alle „Untagged", `docker images` zeigt keine mehr).
|
||
|
||
Commit `9731501` — 5 Dateien (115+/13-).
|
||
|
||
## Task 3 — Handbuch, ci-cd-setup, Push, CI-Beobachtung
|
||
|
||
Precondition: `curl localhost:3002/api/v1/version` -> `{"version":"1.26.2"}`; `docker ps | grep -c ^gitea-runner$` -> 1.
|
||
|
||
Gates, gemessen:
|
||
|
||
| Pruefung | Erwartet | Gemessen |
|
||
|---|---|---|
|
||
| `grep -c "^## 9. Zwei Kanäle: Live und Beta"` | 1 | 1 |
|
||
| `grep -c IMAGE_TAG anleitung-betrieb.md` | >= 6 | 11 |
|
||
| `grep -c "Keine Datenbankänderung als Hotfix"` | 1 | 1 |
|
||
| `grep -c "Kein Registry-Push\|build-deploy" ci-cd-setup.md` | 0 | 0 |
|
||
| `grep -c publish-images.sh ci-cd-setup.md` | >= 2 | 4 |
|
||
| `grep -c '[äöüÄÖÜß]' ci-cd-setup.md` | 0 | 0 |
|
||
| `git diff --stat 6c19451 -- . ':!.planning'` | `GIT_EXIT=0`, `20 files changed` | `GIT_EXIT=0`, `20 files changed, 851 insertions(+), 49 deletions(-)` |
|
||
| Unangetastet-Stichprobe (prisma, biome.json, .env*, package.json, lockfile) | `U_EXIT=0`, `U_EMPTY=0` | `U_EXIT=0`, `U_EMPTY=0` |
|
||
| `git status -sb` nach Push | kein `[ahead` | `## main...origin/main` |
|
||
|
||
Commit `ea6aa99` — 2 Dateien (265+/28-). `git push` -> `6c19451..ea6aa99 main -> main`.
|
||
|
||
### CI-Lauf nach dem Push
|
||
|
||
| Feld | Wert |
|
||
|---|---|
|
||
| Lauf-ID | 297 (event `push`, ref `main`, `head_sha` = `ea6aa99…`) |
|
||
| status / conclusion | `completed` / `success` |
|
||
| started_at / completed_at | 2026-09-14T15:44:23+02:00 / 2026-09-14T15:49:41+02:00 — **5 min 18 s** (Planung: 4-6 min mit Build-Args) |
|
||
| `api:beta` node `APP_VERSION APP_CHANNEL APP_COMMIT APP_BUILD_TIME` | `ea6aa99 beta ea6aa99 2026-09-14T13:46:12Z` (`git rev-parse --short ea6aa99` = `ea6aa99`) |
|
||
| `web:beta` node `APP_VERSION APP_CHANNEL` | `ea6aa99 beta` |
|
||
| `web:beta` Bundle-Grep auf `ea6aa99` | `1` (Bauzeit-Einbettung ueber den echten CI-Weg) |
|
||
| `docker image inspect Created` web:beta / web:latest | beide `2026-09-14T15:47:54.520283239+02:00` (ein Bau, zwei Etiketten, nach dem Push) |
|
||
| `docker image inspect Created` api:beta / api:latest | beide `2026-09-14T15:48:40.191081238+02:00` |
|
||
| Registry (Gitea-API `/packages/schalli?type=container`) | `tessera-ctl/web` `beta` 15:47:59, `latest` 15:48:00; `tessera-ctl/api` `beta` und `latest` 15:49:34 |
|
||
|
||
Kein Fehlversuch, ein einziger Push, ein einziger Lauf.
|
||
|
||
## Abschluss-Verifikation (nach Task 3)
|
||
|
||
- API `Test Files 65 passed (65)` / `Tests 1060 passed (1060)`, Exit 0
|
||
- Web `Test Files 40 passed (40)` / `Tests 243 passed (243)`, Exit 0
|
||
- `tsc --noEmit`: `TSC_packages/shared=0`, `TSC_apps/api=0`, `TSC_apps/web=0`
|
||
- `git fetch -q && git status -sb | head -1` -> `## main...origin/main`
|
||
- `git ls-remote --heads origin live | wc -l` -> 0, `git tag | wc -l` -> 0 (Zweig `live` und Tag `v1.0.0` wie geplant NICHT angelegt)
|
||
|
||
`git status --porcelain` (vor dem Schreiben dieses SUMMARY): leer.
|
||
|
||
`git log --oneline 1cd4212..HEAD`:
|
||
|
||
```
|
||
ea6aa99 docs(quick-260914-ku1): Betriebshandbuch — Zwei Kanäle Live und Beta, Freigabe, Hotfix ohne Datenbankänderung, neuer Live-Server; ci-cd-setup auf gemessenen Stand
|
||
9731501 ci(quick-260914-ku1): zwei Kanaele — main -> beta+latest, Tag v* -> live+vX.Y.Z, Versionsstempel als Build-Args in beide Dockerfiles, IMAGE_TAG in docker-compose.prod.yml
|
||
cdb571c feat(quick-260914-ku1): Versionsstempel — GET /health/version aus APP_*, VersionResponse, app-version.ts und Abzeichen v<Version> · <Kanal> in der Seitenleiste
|
||
```
|
||
|
||
`commits: 3` gemessen aus `git rev-list --count 1cd4212..HEAD`; `actuals.tokens` = 166183 Zeichen ueber die 20 geaenderten Dateien / 4 = 41545 (der reine Diff waere 49515 Zeichen = 12378).
|
||
|
||
## Deviations from Plan
|
||
|
||
### Auto-fixed Issues
|
||
|
||
None — plan executed exactly as written. Alle Zahlen des Plans (65/1060, 40/243, 20 Dateien, 13/5/2 Dateien je Commit, Grep-Werte) wurden exakt getroffen; keine Erwartung musste angepasst werden.
|
||
|
||
### Prozess-Abweichung (dokumentiert, keine Code-Abweichung)
|
||
|
||
**Commits auf `main`.** Die Executor-Vorschrift verlangt eigentlich einen Nicht-Standard-Zweig. Dieses Projekt arbeitet per `branching_strategy: none` seit jeher direkt auf `main`, der Auftrag verlangt ausdruecklich `git push` auf `main`, und das Kanalmodell dieses Plans definiert `main` = Beta — ein Seitenzweig haette den Plan nicht erfuellen koennen (die Pipeline haette nichts gebaut). Kein `git update-ref`, kein Force-Push, kein Eingriff in `.planning/config.json`.
|
||
|
||
## Nebenbefunde
|
||
|
||
- **`apps/web/src/components/layout/sidebar-footer.tsx` ist seit `ba02b25` (2026-06-26) toter Code**: wird nirgends gerendert, einziger Treffer ausserhalb der Datei ist der Mock in `sidebar.test.tsx`. Unangetastet gelassen (nicht in der Erlaubnisliste); Kandidat fuer einen Aufraeum-Quick-Task.
|
||
- **`.env.prod.example` bewusst nicht angefasst** (Regel: keine `.env*`-Dateien). Die Zeile `IMAGE_TAG` steht nur im Handbuch (Kapitel 3 Tabelle, Kapitel 9 mit ausdruecklichem Hinweis, dass die Vorlage sie noch nicht enthaelt).
|
||
- Der Web-Bau laeuft in der CI jetzt bei jedem Lauf durch die builder-Stufe (NEXT_PUBLIC_APP_COMMIT aendert sich je Commit) — gemessen 5 min 18 s statt unter 2 min zuvor; im ci-cd-setup vermerkt.
|
||
- Handbuch Kapitel 6/7 nennen weiterhin `/opt/tessera/docker-compose.yml` fuer die Volume-Reparatur; Kapitel 9 nennt die tatsaechlich benutzte Datei `/opt/tessera/docker-compose.prod.yml` (`.env` setzt `COMPOSE_FILE`). Die aelteren Stellen wurden nicht umgeschrieben (nicht Teil des Plans).
|
||
|
||
## Threat Flags
|
||
|
||
Keine neue Angriffsflaeche ausserhalb des Threat-Registers des Plans: `GET /health/version` war bereits @Public (T-KU1-03, akzeptiert und per Spec-Test 6 gepinnt); das Skript kennt kein Secret; das Push-Token wurde in Task 3 nur in einer Shell-Variablen benutzt (Ausgabe der Push-URL im Log mit `***` maskiert).
|
||
|
||
## Known Stubs
|
||
|
||
Keine. Alle neuen Werte sind an echte Quellen gebunden (Umgebung, `/health/version`); ohne Build-Args greifen die bewussten Vorgaben `dev`.
|
||
|
||
## Was bewusst offen bleibt
|
||
|
||
- **Zweig `live` und Tag `v1.0.0` sind NICHT angelegt** — Rezept steht im Handbuch Kapitel 9 („Erstfreigabe v1.0.0"); erfolgt nach dem Fehler-melden-Knopf (eigener Quick-Task, der `apps/web/src/lib/app-version.ts` importiert).
|
||
- **Server nicht angefasst**: alpha (`/opt/tessera/.env` und `docker-compose.prod.yml`) und der neue Live-Server werden vom User eingerichtet, siehe „Handgriffe" unten. Bis dahin zieht alpha weiter `:latest` = dasselbe Beta-Abbild.
|
||
- `.env.prod.example` ohne `IMAGE_TAG`-Zeile (Regel `.env*`); ein spaeterer Quick-Task darf sie ergaenzen.
|
||
- `latest` bleibt als Alias von `beta`, bis alpha auf `IMAGE_TAG=beta` umgestellt ist; danach kann das Etikett aus dem Skript entfallen.
|
||
- Tag-Schutz `v*` und Branch-Schutz `live` in Gitea (T-KU1-04) — heute nicht noetig (nur `schalli` hat Schreibrecht), im ci-cd-setup als Empfehlung fuer den Fall weiterer Konten.
|
||
- Human-Check (end-of-phase, nicht blockierend): lokal `docker compose up -d --build web api` und unten in der Seitenleiste `dev · Entwicklung` sehen, Tooltip `API dev (dev)`; eingeklappt verschwindet die Zeile.
|
||
|
||
## Handgriffe fuer den User
|
||
|
||
Aus dem Handbuch Kapitel 9 („Die eine Zeile je Server" und „Den neuen Live-Server einrichten"):
|
||
|
||
**Auf alpha (Beta), einmalig:**
|
||
|
||
1. In `/opt/tessera/.env` die Zeile `IMAGE_TAG=beta` eintragen.
|
||
2. Sicherung der Compose-Datei:
|
||
```bash
|
||
cd /opt/tessera
|
||
cp docker-compose.prod.yml docker-compose.prod.yml.bak.$(date +%Y%m%d)
|
||
```
|
||
3. In `/opt/tessera/docker-compose.prod.yml` die zwei `image:`-Zeilen aendern (nur das Ende der Zeile):
|
||
```yaml
|
||
image: git.vicolab.de/schalli/tessera-ctl/web:${IMAGE_TAG:-beta}
|
||
image: git.vicolab.de/schalli/tessera-ctl/api:${IMAGE_TAG:-beta}
|
||
```
|
||
4. Dann wie in Kapitel 4:
|
||
```bash
|
||
docker compose -f docker-compose.prod.yml pull
|
||
docker compose -f docker-compose.prod.yml up -d --force-recreate api web
|
||
```
|
||
5. Kontrolle: unten in der Seitenleiste steht `<Kurzkennung> · Beta`; `curl -s http://localhost:3001/health/version` zeigt `"channel":"beta"`.
|
||
|
||
**Auf dem neuen Live-Server (tessera.ctl.de):** Kapitel 2 des Handbuchs vollstaendig, mit diesen Abweichungen:
|
||
|
||
- `IMAGE_TAG=live` in der `.env` (Pflicht — ohne die Zeile zieht der Server die Beta).
|
||
- Eigene, neu erzeugte Geheimnisse (`JWT_SECRET`, `TESSERA_ENCRYPTION_KEY`, `DB_PASSWORD`, Admin-Passwort); nichts von alpha uebernehmen.
|
||
- Eigene, leere Datenbank; die alpha-Datenbank wird NICHT kopiert (falls doch gewuenscht: Kapitel 6 UND derselbe `TESSERA_ENCRYPTION_KEY`).
|
||
- `APP_URL=https://tessera.ctl.de`.
|
||
- Der erste `pull` holt `:live` — dieses Etikett gibt es erst nach der Erstfreigabe v1.0.0. Also erst freigeben (Claude: `git checkout -b live main`, Tag `v1.0.0`, Push), dann installieren; oder fuer einen Probelauf voruebergehend `IMAGE_TAG=beta`, danach auf `live` umstellen und `pull` + `up -d --force-recreate api web` wiederholen.
|
||
|
||
**Bei jeder Freigabe danach** (User sagt „Version X freigeben", Claude pusht Zweig und Tag), auf dem Live-Server:
|
||
|
||
```bash
|
||
docker compose -f docker-compose.prod.yml pull
|
||
docker compose -f docker-compose.prod.yml up -d --force-recreate api web
|
||
```
|
||
|
||
## Self-Check: PASSED
|
||
|
||
- Dateien vorhanden: alle 7 neu erstellten Dateien (`app-version.ts` api/web, beide Specs/Tests, Badge + Test, `publish-images.sh`) — FOUND.
|
||
- Commits vorhanden: `cdb571c`, `9731501`, `ea6aa99` — FOUND (`git log --oneline 1cd4212..HEAD`), alle auf `origin/main`.
|