feat: fehlgeschlagener Build reisst die laufende App nicht mehr mit
Some checks are pending
Playwright Tests / test (push) Waiting to run
Test and SonarQube / quality (push) Waiting to run

next build lief direkt auf .next -- also auf dem Verzeichnis, aus dem die
laufende Instanz bedient wird. Brach der Build ab, fand PM2 server.js nicht
mehr (MODULE_NOT_FOUND) und geriet in eine Neustartschleife mit ueber 790
Versuchen. Am 07.09. war die Seite dadurch rund eine Stunde unbrauchbar.

Neuer Ablauf:
1. Build nach .next-build, .next bleibt unangetastet
2. statische Dateien und .env in den neuen Baum
3. Smoke-Test: der neue Build wird auf Port 3999 gestartet und muss auf
   /auth/login antworten -- ein Build kann durchlaufen und trotzdem nicht
   starten
4. erst danach umschalten, alter Stand bleibt als .next-previous liegen
5. PM2 neu starten

Scheitert etwas davon, raeumt der ERR-Trap .next-build weg und die laufende
App bleibt unveraendert. Die Meldung "die laufende App bleibt unberuehrt"
stimmt damit endlich -- vorher war sie schlicht falsch.

Geprueft wurde vorher, dass der umbenannte Build ueberhaupt startet: der
standalone-Ausgabebaum traegt den distDir-Namen intern, server.js findet
seine Dateien aber relativ zu sich selbst. Testweise gestartet, Login-Seite
und JS-Dateien lieferten 200.

Beide Faelle durchgespielt: Abbruch liess .next samt Markierung unberuehrt
und raeumte .next-build weg; Erfolg schaltete um, legte .next-previous an
und bestand den Smoke-Test.

v1.120.0

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
mARTin-B78 2026-09-07 22:33:04 +02:00
parent bea9723a6f
commit 162c56d0d3
7 changed files with 141 additions and 22 deletions

4
.gitignore vendored
View File

@ -60,3 +60,7 @@ INFRASTRUCTURE_REQUIREMENTS.md
# feste Namen, weshalb .env.production.save durchs Raster fiel
.env*
!.env.example
# Build-Arbeitsverzeichnisse von scripts/update-app.sh
.next-build/
.next-previous/

View File

@ -2,6 +2,31 @@
All notable changes to this project are documented in this file.
## [1.120.0] - 2026-09-07
### Changed
- **Ein fehlgeschlagener Build reisst die laufende App nicht mehr mit.** `next build` lief
bisher direkt auf `.next` — also auf dem Verzeichnis, aus dem die laufende Instanz bedient
wird. Brach der Build ab, fand PM2 `server.js` nicht mehr (`MODULE_NOT_FOUND`) und geriet
in eine Neustartschleife; am 07.09. war die Seite deshalb rund eine Stunde unbrauchbar.
Der neue Ablauf in `scripts/update-app.sh`:
1. Build nach `.next-build` — `.next` bleibt dabei unangetastet
2. Statische Dateien und `.env` in den neuen Baum kopieren
3. **Smoke-Test:** Der neue Build wird auf Port 3999 gestartet und muss auf
`/auth/login` antworten. Ein Build kann durchlaufen und trotzdem nicht starten.
4. Erst danach umschalten; der alte Stand bleibt als `.next-previous` liegen
5. PM2 neu starten
Scheitert irgendetwas davon, wird `.next-build` weggeräumt und die laufende App bleibt
unverändert. Zum Zurückrollen genügt `mv .next-previous .next` plus PM2-Neustart.
- `next.config.js`: `distDir` kommt jetzt aus `NEXT_DIST_DIR` und fällt auf `.next` zurück.
Beide Fälle sind praktisch getestet: Ein abgebrochener Lauf liess `.next` samt Markierung
unberührt und räumte `.next-build` weg; ein erfolgreicher schaltete sauber um, legte
`.next-previous` an und bestand den Smoke-Test.
## [1.119.4] - 2026-09-07
### Fixed

29
TODO.md
View File

@ -286,6 +286,31 @@ Umgestellt auf `dataSources.query()`; die Suche filtert jetzt auf `data_source`
---
## Block K — Deploy-Sicherheit ✅
### K1. Build zerstört nicht mehr die laufende App ✅ *(v1.120.0)*
- [x] Build läuft nach `.next-build`, das laufende `.next` bleibt unberührt
- [x] Smoke-Test vor dem Umschalten — der neue Build muss einmal geantwortet haben
- [x] Umschalten erst danach, alter Stand bleibt als `.next-previous` liegen
- [x] Beide Fälle getestet: Fehlschlag lässt `.next` unangetastet, Erfolg schaltet sauber um
**Warum:** `next build` lief direkt auf `.next` — also auf dem Verzeichnis, aus dem die
laufende App bedient wird. Scheiterte der Build, war nicht nur das Update weg, sondern die
laufende Instanz gleich mit: PM2 fand `server.js` nicht mehr
(`MODULE_NOT_FOUND`) und geriet in eine Neustartschleife mit über 790 Versuchen.
Am 07.09. war die Seite dadurch rund eine Stunde unbrauchbar.
**Zurückrollen** (falls ein Deploy doch mal Mist ausliefert):
```bash
pm2 stop my-weekly-todo
cd /root/My-Weekly-ToDo-List
rm -rf .next && mv .next-previous .next
pm2 restart my-weekly-todo
```
---
## Block J — Build-Speicher auf dem Container
### J1. Container-RAM erhöhen *(empfohlen)*
@ -316,7 +341,7 @@ Deploy-Wegs, aber die sauberste Trennung.
## Block I — UI-Wünsche
### I1. Aktuellen Tag auch im Spaltenkopf hervorheben *(erledigt, bitte ansehen)*
### I1. Aktuellen Tag auch im Spaltenkopf hervorheben ✅ *(vom Nutzer bestätigt)*
- [x] Hervorhebungsfarbe zusätzlich hinter Wochentagsname und Datum gelegt
**Wunsch:** Der laufende Tag soll deutlicher auffallen. Aktuell ist nur der Spaltenkörper
@ -331,7 +356,7 @@ offenbar mit eigenem Hintergrund. Der Wochentagsname hat bereits eine eigene Reg
**Zu klären:** Der Kopf ist vermutlich `sticky` und braucht deshalb einen deckenden
Hintergrund — die Farbe muss also gesetzt, nicht nur geerbt werden.
### I2. Symbole in Kalenderterminen nach oben rechts *(erledigt, bitte ansehen)*
### I2. Symbole in Kalenderterminen nach oben rechts ✅ *(vom Nutzer bestätigt, Freiraum reicht)*
- [x] `.event-icons-row` nach oben rechts verschoben, Titel bekommt rechts Freiraum
**Wunsch:** Die kleinen Symbole (Wiederholung, Anbieter) sitzen unten rechts im Termin und

View File

@ -3,6 +3,10 @@ const { version } = require('./package.json');
const nextConfig = {
output: 'standalone',
// Ziel des Builds. update-app.sh baut nach .next-build, damit das laufende .next
// waehrend des Builds unberuehrt bleibt, und schaltet erst nach erfolgreichem
// Smoke-Test um. Ohne das zerstoert ein fehlgeschlagener Build die laufende App.
distDir: process.env.NEXT_DIST_DIR || '.next',
eslint: {
// Aus: Der Build meldet ESLint-Fehler. Warnungen blockieren ihn nicht, nur Errors.
//

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "my-weekly-todo-list",
"version": "1.119.4",
"version": "1.120.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "my-weekly-todo-list",
"version": "1.119.4",
"version": "1.120.0",
"license": "MIT",
"dependencies": {
"@auth/prisma-adapter": "^2.11.1",

View File

@ -1,6 +1,6 @@
{
"name": "my-weekly-todo-list",
"version": "1.119.4",
"version": "1.120.0",
"description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view",
"main": "index.js",
"scripts": {

View File

@ -34,7 +34,10 @@ main() {
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"; }
trap 'log "!!! Update FAILED at line $LINENO (exit $?) — the running build is untouched"' ERR
# Bei einem Abbruch den halbfertigen Build wegraeumen. Der Hinweis "die laufende App
# bleibt unberuehrt" stimmt seit v1.120.0 tatsaechlich -- vorher wurde direkt auf .next
# gebaut, ein Fehlschlag riss die laufende Instanz mit.
trap 'log "!!! Update FAILED at line $LINENO (exit $?) — die laufende App bleibt unberuehrt"; rm -rf "${APP_DIR:-.}/.next-build"' ERR
# Fail fast instead of hanging on a credential prompt when run without a terminal
# (systemd has no tty). The host authenticates via a read-only SSH deploy key.
@ -95,24 +98,82 @@ main() {
log "--- Regenerating Prisma client ---"
npx prisma generate
# Der Standard-Heap von Node richtet sich nach dem Arbeitsspeicher und lag auf diesem
# Container bei rund 1,5 GB -- zu wenig fuer `next build`. Ein erster Versuch mit 2048
# scheiterte ebenfalls: der Build starb punktgenau bei 2045 MB. Gemessener Bedarf sind
# rund 2,6 GB, deshalb 4 GB. Der Container hat 6 GB, das laesst genug Luft fuer den
# laufenden PM2-Prozess.
log "--- Building application ---"
NODE_OPTIONS="--max-old-space-size=4096" npm run build
# ── Build in ein Nebenverzeichnis ────────────────────────────────────────────────
#
# Frueher lief `next build` direkt auf .next -- also auf dem Verzeichnis, aus dem die
# laufende App bedient wird. Scheiterte der Build, war nicht nur das Update weg, sondern
# die laufende Instanz gleich mit: PM2 fand server.js nicht mehr und geriet in eine
# Neustartschleife. Genau das ist am 07.09. passiert, die Seite war rund eine Stunde
# unbrauchbar.
#
# Jetzt entsteht der neue Build unter .next-build. Das laufende .next wird dabei nicht
# angefasst. Erst wenn der neue Build gebaut UND probeweise gestartet werden konnte,
# wird umgeschaltet.
#
# Nodes Standard-Heap lag hier bei rund 1,5 GB und reichte nicht; ein Versuch mit 2048
# scheiterte punktgenau bei 2045 MB. Gemessener Bedarf sind rund 2,6 GB.
local BUILD_DIR=".next-build"
rm -rf "$BUILD_DIR"
# next.config.js sets output: 'standalone', and Next.js does NOT copy public/ or
# .next/static/ into .next/standalone — that is a documented manual step. Skipping it
# ships new server code whose HTML references new chunk hashes while the old chunks sit
# in the standalone tree: they 404, React never hydrates, and every page hangs on its
# server-rendered shell ("Loading session...") while the API still answers 200.
log "--- Building application (nach $BUILD_DIR) ---"
NEXT_DIST_DIR="$BUILD_DIR" NODE_OPTIONS="--max-old-space-size=4096" npm run build
# next.config.js setzt output: 'standalone', und Next.js kopiert public/ und static/
# NICHT selbst dorthin -- ein dokumentierter manueller Schritt. Wird er ausgelassen,
# verweist das neue HTML auf Chunks, die im standalone-Baum fehlen: sie liefern 404,
# React hydriert nie, und jede Seite haengt auf "Loading session...", waehrend die API
# weiter 200 antwortet.
#
# Der innere Ordner traegt den Namen des distDir -- deshalb .next-build/static und nicht
# .next/static. Beim Umschalten wandert er unveraendert mit; server.js findet ihn
# relativ zu sich selbst (geprueft).
log "--- Copying static assets into standalone output ---"
cp -r public .next/standalone/
mkdir -p .next/standalone/.next/static
cp -r .next/static .next/standalone/.next/
cp .env .next/standalone/ 2>/dev/null || true
cp -r public "$BUILD_DIR/standalone/"
mkdir -p "$BUILD_DIR/standalone/$BUILD_DIR/static"
cp -r "$BUILD_DIR/static/." "$BUILD_DIR/standalone/$BUILD_DIR/static/"
cp .env "$BUILD_DIR/standalone/" 2>/dev/null || true
# ── Smoke-Test auf einem freien Port ─────────────────────────────────────────────
#
# Ein Build kann durchlaufen und trotzdem nicht starten. Bevor der neue Stand live
# geht, muss er einmal wirklich geantwortet haben.
log "--- Smoke-Test des neuen Builds ---"
local SMOKE_PORT=3999
( cd "$BUILD_DIR/standalone" && PORT=$SMOKE_PORT node server.js > /tmp/smoke-test.log 2>&1 ) &
local SMOKE_PID=$!
local SMOKE_OK=0
for _ in $(seq 1 20); do
sleep 1
if curl -sf -o /dev/null --max-time 3 "http://127.0.0.1:$SMOKE_PORT/auth/login"; then
SMOKE_OK=1
break
fi
done
kill "$SMOKE_PID" 2>/dev/null || true
wait "$SMOKE_PID" 2>/dev/null || true
if [ "$SMOKE_OK" -ne 1 ]; then
log "!!! Smoke-Test fehlgeschlagen -- der neue Build antwortet nicht."
log " Die laufende App bleibt unveraendert. Ausgabe:"
tail -20 /tmp/smoke-test.log | sed 's/^/ /'
rm -rf "$BUILD_DIR"
return 1
fi
log " Smoke-Test bestanden."
# ── Umschalten ───────────────────────────────────────────────────────────────────
#
# Ab hier ist der neue Build erwiesenermassen lauffaehig. Der alte bleibt als
# .next-previous liegen; zum Zurueckrollen genuegt:
# pm2 stop my-weekly-todo
# rm -rf .next && mv .next-previous .next
# pm2 restart my-weekly-todo
log "--- Umschalten auf den neuen Build ---"
rm -rf .next-previous
if [ -d .next ]; then
mv .next .next-previous
fi
mv "$BUILD_DIR" .next
# At boot this script runs before pm2-root.service, so the PM2 daemon is not up yet and
# there is nothing to restart — pm2-root.service resurrects the app right after. Probing