From 47245152a2b43a9599423992bc326df43bd112c6 Mon Sep 17 00:00:00 2001 From: Florian Wilde Date: Mon, 27 Jul 2026 00:14:31 +0200 Subject: [PATCH] docs: Anleitung fuer den Serverbetrieb und Update-Skript DEPLOYMENT.md fuehrt Schritt fuer Schritt vom leeren Server zur Instanz hinter TLS: Docker, Konfiguration, Reverse Proxy mit Caddy oder nginx, Firewall, Sicherungen per Cron. Dazu, was an Haertung schon eingebaut ist - und was der Betreiber selbst beisteuern muss. scripts/update.sh macht die Schritte in der richtigen Reihenfolge: pruefen, holen, sichern, bauen, starten, Gesundheit pruefen. Erst bauen und dann umschalten, damit ein gescheiterter Bau die laufende Fassung nicht mitnimmt; die Sicherung liegt vor jeder Schemaaenderung. Antwortet /api/health nach einer Minute nicht, zeigt es die letzten Logzeilen und nennt den Weg zurueck. Es weigert sich zu laufen, solange APP_SECRET leer oder noch der Entwicklungswert ist - mit diesem Wert liegen die Git-Tokens faktisch offen. Co-Authored-By: Claude Opus 5 (1M context) --- CLAUDE.md | 2 +- DEPLOYMENT.md | 342 +++++++++++++++++++++++++++++++++++++++++++ README.md | 3 +- ROADMAP.md | 1 + package.json | 2 +- scripts/update.sh | 163 +++++++++++++++++++++ src/lib/changelog.ts | 45 ++++++ 7 files changed, 555 insertions(+), 3 deletions(-) create mode 100644 DEPLOYMENT.md create mode 100644 scripts/update.sh diff --git a/CLAUDE.md b/CLAUDE.md index 963bad8..b0ea386 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,7 +4,7 @@ Dashboard für Vibe-Coding-Projekte. Next.js 15 (App Router), TypeScript, Tailwind 4, Prisma, PostgreSQL. Oberfläche und Texte sind **deutsch**. Funktionsumfang: [FEATURES.md](FEATURES.md) · Geplantes: [ROADMAP.md](ROADMAP.md) · -Betrieb: [README.md](README.md) +Betrieb: [README.md](README.md) · Server: [DEPLOYMENT.md](DEPLOYMENT.md) ## Womit man arbeitet diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md new file mode 100644 index 0000000..e3b01b8 --- /dev/null +++ b/DEPLOYMENT.md @@ -0,0 +1,342 @@ +# Nebula auf einem Server betreiben + +Schritt für Schritt: von der leeren Maschine bis zur laufenden Instanz hinter +TLS — und wie Updates danach ablaufen. + +Für Installation zum Ausprobieren und die Entwicklungsvarianten siehe +[README.md](README.md). Hier geht es um den Dauerbetrieb. + +--- + +## Was du brauchst + +| | | +| --- | --- | +| Server | Linux mit Docker Engine ≥ 24 und Compose v2 | +| Arbeitsspeicher | 1 GB reichen; beim Bauen kurzzeitig ~2 GB | +| Plattenplatz | ~2 GB für Abbilder, dazu die Datenbank | +| Domain | Eine (Unter-)Domain, die auf den Server zeigt | +| TLS | Pflicht, wenn du Passkeys willst — siehe Schritt 6 | + +> **Warum TLS Pflicht ist:** Passkeys verlangen einen sicheren Kontext. +> Über `http://` an einer LAN-IP funktionieren sie nicht. Passwort und +> Zwei-Faktor gehen auch ohne, aber dann läuft dein Passwort im Klartext +> übers Netz — betreibe Nebula nicht ohne TLS. + +--- + +## 1 · Docker installieren + +```bash +curl -fsSL https://get.docker.com | sh +sudo usermod -aG docker "$USER" +newgrp docker # oder ab- und wieder anmelden + +docker --version && docker compose version +``` + +## 2 · Nebula holen + +```bash +sudo mkdir -p /opt/nebula && sudo chown "$USER" /opt/nebula +git clone https://git.example.com/dein-name/nebula.git /opt/nebula +cd /opt/nebula +``` + +## 3 · Konfigurieren + +```bash +node scripts/setup.mjs # legt .env an und würfelt ein APP_SECRET +``` + +Ohne Node auf dem Server: `cp .env.example .env` und das Geheimnis von Hand +setzen — + +```bash +openssl rand -base64 48 +``` + +Dann die `.env` öffnen und mindestens diese vier Werte anpassen: + +```env +# Die Adresse, unter der du Nebula im Browser aufrufst. +# Bestimmt die WebAuthn-rpID — passt sie nicht, scheitern Passkeys. +APP_URL="https://nebula.deine-domain.de" + +# Verschlüsselt die Git-Tokens. Lang, zufällig, niemals teilen. +APP_SECRET="…dein-langer-zufallswert…" + +# Das Datenbankpasswort. Die Voreinstellung „nebula" ist für Entwicklung. +POSTGRES_PASSWORD="…noch-ein-langer-zufallswert…" + +# Nur lokal lauschen — nach außen geht es über den Reverse Proxy (Schritt 6). +APP_PORT=127.0.0.1:3000 +``` + +> **`APP_SECRET` später zu ändern** macht gespeicherte Git-Tokens unlesbar. +> Sie müssen dann neu hinterlegt werden. Konten, Passwörter und Passkeys +> bleiben unberührt. +> +> **`APP_URL` später zu ändern** macht bestehende **Passkeys ungültig**, weil +> sie an die rpID gebunden sind. Passwörter funktionieren weiter. + +### Datenbankport schließen + +`docker-compose.yml` bildet Postgres standardmäßig auf `5432` ab, damit man +in der Entwicklung von außen drankommt. Auf einem Server hat die Datenbank +nichts im Internet zu suchen: + +```env +POSTGRES_PORT=127.0.0.1:5432 +``` + +Noch besser: die `ports:`-Zeile des `db`-Dienstes ganz entfernen. Die +Anwendung erreicht die Datenbank über das interne Docker-Netz, nicht über +den Host. + +## 4 · Starten + +```bash +docker compose --profile full up -d --build +``` + +Der erste Bau dauert ein paar Minuten. Der Container spielt beim Start +selbst die Migrationen ein und wartet dabei auf die Datenbank. + +```bash +docker compose --profile full logs -f app # mitlesen, Strg+C beendet nur das Lesen +curl -s localhost:3000/api/health # {"status":"ok","database":"up",…} +``` + +## 5 · Ersten Zugang anlegen + +Ruf `https://nebula.deine-domain.de/setup` auf (nach Schritt 6) oder +vorübergehend über einen SSH-Tunnel: + +```bash +ssh -L 3000:127.0.0.1:3000 benutzer@server +# dann lokal http://localhost:3000/setup öffnen +``` + +Dort legst du das Administratorkonto an und wählst die Betriebsart. +**Danach ist `/setup` dauerhaft gesperrt.** + +Anschließend gleich unter *Mein Konto*: + +1. **Passkey hinzufügen** — geht erst, wenn TLS steht +2. **Zwei-Faktor einrichten** und die Wiederherstellungscodes wegschreiben + +## 6 · Reverse Proxy mit TLS + +Nebula bringt keinen eigenen TLS-Abschluss mit. Beispiel mit **Caddy**, das +Zertifikate von selbst besorgt und erneuert: + +```caddyfile +# /etc/caddy/Caddyfile +nebula.deine-domain.de { + reverse_proxy 127.0.0.1:3000 +} +``` + +```bash +sudo systemctl reload caddy +``` + +Das genügt. Caddy setzt `X-Forwarded-*` von selbst, holt das Zertifikat bei +Let's Encrypt und leitet `http` auf `https` um. + +
+Dasselbe mit nginx + +```nginx +server { + listen 443 ssl http2; + server_name nebula.deine-domain.de; + + ssl_certificate /etc/letsencrypt/live/nebula.deine-domain.de/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/nebula.deine-domain.de/privkey.pem; + + location / { + proxy_pass http://127.0.0.1:3000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +} +``` + +Zertifikat mit `certbot --nginx -d nebula.deine-domain.de`. +
+ +> Steht `APP_URL` auf `https://…`, setzt Nebula das `Secure`-Flag auf die +> Sitzungscookies. Läuft der Proxy auf `https`, die `APP_URL` aber noch auf +> `http`, funktioniert die Anmeldung — nur ohne dieses Flag. Also beides +> gleichziehen. + +## 7 · Firewall + +```bash +sudo ufw allow 22/tcp +sudo ufw allow 80,443/tcp +sudo ufw enable +``` + +Port 3000 und 5432 bleiben **zu**. Beide sind nach Schritt 3 ohnehin nur an +`127.0.0.1` gebunden; die Firewall ist der zweite Riegel. + +## 8 · Sicherungen automatisieren + +`scripts/backup.sh` legt einen `pg_dump` unter `backups/` ab. Täglich per +Cron: + +```bash +crontab -e +``` + +```cron +# Jede Nacht um 3:15 sichern, 30 Tage aufheben +15 3 * * * cd /opt/nebula && ./scripts/backup.sh >> /var/log/nebula-backup.log 2>&1 +30 3 * * * find /opt/nebula/backups -name '*.sql.gz' -mtime +30 -delete +``` + +Zurückspielen: + +```bash +./scripts/restore.sh backups/nebula-2026-07-27-03-15-00.sql.gz +``` + +> Die Sicherungen liegen auf **demselben Server**. Das hilft gegen einen +> verpatzten Import, nicht gegen einen Plattenausfall. Zieh sie regelmäßig +> woandershin — `rsync`, `restic`, was du ohnehin nutzt. + +--- + +## Updates + +Dafür gibt es `scripts/update.sh`. Es macht alles in der richtigen +Reihenfolge und bricht ab, sobald ein Schritt scheitert: + +```bash +cd /opt/nebula +./scripts/update.sh +``` + +Was es tut: + +| Schritt | | +| --- | --- | +| **1 Prüfen** | docker, compose, `.env` da? Ist `APP_SECRET` gesetzt und nicht mehr der Entwicklungswert? | +| **2 Holen** | `git pull --ff-only`; zeigt die neuen Commits und fragt nach | +| **3 Sichern** | `pg_dump` **vor** jeder Schemaänderung | +| **4 Bauen** | Erst bauen, dann umschalten — ein gescheiterter Bau lässt die alte Fassung laufen | +| **5 Starten** | Container neu; die Migrationen laufen im Entrypoint | +| **6 Prüfen** | Bis zu 60 Sekunden auf `/api/health` warten | + +Scheitert die Gesundheitsprüfung, zeigt das Skript die letzten Logzeilen und +nennt den Weg zurück. + +```bash +./scripts/update.sh --yes # ohne Rückfrage, für Cron +./scripts/update.sh --ref v1.4.0 # auf eine bestimmte Fassung +./scripts/update.sh --no-pull # nur neu bauen, ohne git pull +``` + +### Wenn doch etwas schiefgeht + +```bash +# Zurück auf den vorigen Stand (die Kennung nennt das Skript beim Abbruch) +git checkout +./scripts/update.sh --no-pull --yes + +# Nur wenn eine Migration die Daten beschädigt hat: +./scripts/restore.sh backups/.sql.gz +``` + +> **Migrationen werden nicht zurückgerollt.** Eine Migration, die eine Spalte +> entfernt, ist nicht umkehrbar — deshalb sichert das Skript vorher. Steht +> ein Update mit Datenmigration an, lies vorher den Änderungsverlauf +> (Versionsnummer in der Fußzeile). + +--- + +## Betrieb + +### Zustand ansehen + +```bash +docker compose --profile full ps +docker compose --profile full logs -f app +curl -s localhost:3000/api/health +``` + +### Neu starten + +```bash +docker compose --profile full restart app +``` + +### Speicher- und Plattenverbrauch + +```bash +docker stats --no-stream nebula-app nebula-db +docker system df +docker image prune -f # alte Abbilder nach mehreren Updates +``` + +### Automatischer Abgleich + +Standardmäßig **aus**. Einzuschalten unter *Admin-Einstellungen → +Automatischer Abgleich*: Abstand, Repositories je Durchlauf, ob fertige +Projekte ausgelassen werden. Der Zeitgeber läuft im Serverprozess selbst — +kein zusätzlicher Container, kein Cron. + +Wer lieber von außen taktet, lässt ihn aus und ruft +`POST /api/admin/sync/run` auf. + +--- + +## Härtung, die schon eingebaut ist + +Damit du weißt, worum du dich **nicht** kümmern musst: + +- Der Container läuft als **unprivilegierter Benutzer** (`nextjs`, UID 1001), + nicht als root. +- **Content-Security-Policy** mit Nonce, `strict-dynamic` und ohne + `unsafe-inline` für Skripte. +- **CSRF-Prüfung** über `Sec-Fetch-Site`/`Origin` bei jedem schreibenden + Zugriff. +- **SSRF-Schutz** bei allen ausgehenden Aufrufen: jede Adresse wird + aufgelöst und gegen Loopback, Link-Local und Metadaten-Endpunkte geprüft, + Weiterleitungen eingeschlossen. +- **Ratenbegrenzung** auf Anmeldung, ausgehende Abrufe und schreibende + Zugriffe; Kontosperre nach acht Fehlversuchen. +- **Git-Tokens** liegen AES-256-GCM-verschlüsselt in der Datenbank und + verlassen den Server nie — auch nicht im Export. +- Der **Bildoptimierer** von Next ist abgeschaltet: Nebula benutzt ihn nicht, + und `/_next/image` wäre sonst eine Angriffsfläche ohne Gegenwert. + +### Was du beisteuern musst + +- **TLS** über den Reverse Proxy +- **`APP_SECRET`** lang und zufällig +- **Datenbankport** nicht nach außen öffnen +- **Sicherungen** wegschaffen +- **Systemupdates**: `unattended-upgrades` für das Betriebssystem, + `./scripts/update.sh` für Nebula + +--- + +## Mehrbenutzerbetrieb + +Nebula kann beides. Im Einzelbetrieb gibt es genau ein Konto und keine +Registrierung. Umschalten unter *Admin-Einstellungen*. + +Wichtig zu wissen: **Projekte sind zwischen Konten hart getrennt.** Auch ein +Administrator sieht fremde Projekte nicht — er verwaltet Konten, keine +Inhalte. Wer gemeinsam an einem Projekt arbeiten will, ist mit Nebula falsch +beraten; das ist [bewusst nicht](ROADMAP.md) vorgesehen. + +Die Selbstregistrierung ist standardmäßig aus. Lass sie aus, wenn die +Instanz aus dem Internet erreichbar ist und du nicht willst, dass sich +Fremde Konten anlegen. diff --git a/README.md b/README.md index 33a6737..6090ef7 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,8 @@ Alle Daten liegen in **deiner lokalen PostgreSQL**. Es gibt keinen Cloud-Dienst Access Tokens werden verschlüsselt abgelegt. Eine vollständige Auflistung aller Funktionen steht in **[FEATURES.md](FEATURES.md)**, -geplante Erweiterungen in **[ROADMAP.md](ROADMAP.md)**. +geplante Erweiterungen in **[ROADMAP.md](ROADMAP.md)**. Für den Dauerbetrieb auf einem +Server — TLS, Firewall, Sicherungen und Updates — gibt es **[DEPLOYMENT.md](DEPLOYMENT.md)**. --- diff --git a/ROADMAP.md b/ROADMAP.md index b0e6592..1bdac23 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -110,6 +110,7 @@ Damit klar ist, was **nicht** kommt und warum: | **v1.2.0** Commit-Heatmap über ein Jahr, Projektvorlagen | ✓ | | **v1.3.0** Wiederkehrende Aufgaben, Barrierefreiheit nach WCAG 2.1 AA | ✓ | | **v1.4.0** README-Vorschau auf der Projektseite | ✓ | +| **v1.5.0** Sicherheitsdurchsicht, Server-Anleitung und Update-Skript | ✓ | Zur Entscheidung beim Export: Git-Tokens gehen **nicht** mit. Die Verbindung wird nur beschrieben, das Token ist nach dem Import neu zu hinterlegen. Snapshots bleiben diff --git a/package.json b/package.json index b46b8e5..6190e68 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nebula", - "version": "1.4.0", + "version": "1.5.0", "private": true, "description": "Nebula – Dashboard fuer Vibe-Coding Projekte mit Git-Anbindung (GitHub, GitLab, Gitea)", "scripts": { diff --git a/scripts/update.sh b/scripts/update.sh new file mode 100644 index 0000000..9599f9d --- /dev/null +++ b/scripts/update.sh @@ -0,0 +1,163 @@ +#!/usr/bin/env bash +# ───────────────────────────────────────────────────────────── +# Nebula auf einem Server aktualisieren. +# +# Holt den neuen Stand, sichert die Datenbank, baut das Abbild, spielt die +# Migrationen ein und startet neu. Bricht ab, sobald ein Schritt scheitert – +# und sagt dann, wie zurueckzukommen ist. +# +# Aufruf: ./scripts/update.sh +# ./scripts/update.sh --no-pull (nur neu bauen und starten) +# ./scripts/update.sh --ref v1.4.0 (auf einen Stand festlegen) +# ./scripts/update.sh --yes (ohne Rueckfrage) +# +# Rueckgabe 0 heisst: die neue Fassung laeuft und antwortet gesund. +# ───────────────────────────────────────────────────────────── +set -euo pipefail + +root="$(cd "$(dirname "$0")/.." && pwd)" +cd "$root" + +pull=1 +ref="" +zusage=0 + +while [ $# -gt 0 ]; do + case "$1" in + --no-pull) pull=0 ;; + --ref) ref="${2:-}"; shift ;; + --yes|-y) zusage=1 ;; + -h|--help) sed -n '3,15p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;; + *) echo "Unbekannte Option: $1" >&2; exit 2 ;; + esac + shift +done + +# ── Farben, aber nur wenn ein Terminal zusieht ─────────────── +if [ -t 1 ]; then + rot=$'\033[31m'; gruen=$'\033[32m'; gelb=$'\033[33m'; fett=$'\033[1m'; aus=$'\033[0m' +else + rot=""; gruen=""; gelb=""; fett=""; aus="" +fi + +schritt() { printf '\n%s▸ %s%s\n' "$fett" "$1" "$aus"; } +ok() { printf ' %s✓%s %s\n' "$gruen" "$aus" "$1"; } +warnung() { printf ' %s!%s %s\n' "$gelb" "$aus" "$1"; } +abbruch() { printf '\n%s✗ %s%s\n' "$rot" "$1" "$aus" >&2; exit 1; } + +# ── Voraussetzungen ────────────────────────────────────────── +schritt "Voraussetzungen" + +command -v docker >/dev/null || abbruch "docker nicht gefunden." +docker compose version >/dev/null 2>&1 || abbruch "docker compose (v2) nicht gefunden." +[ -f "$root/.env" ] || abbruch "Keine .env in $root. Zuerst 'node scripts/setup.mjs' ausfuehren." +[ -f "$root/docker-compose.yml" ] || abbruch "Kein docker-compose.yml in $root." +ok "docker, compose und .env vorhanden" + +# APP_SECRET muss gesetzt sein – sonst liegen die Git-Tokens faktisch offen. +if grep -qE '^\s*APP_SECRET\s*=\s*("?)(nebula-dev-secret[^"]*|)\1\s*$' "$root/.env"; then + abbruch "APP_SECRET ist leer oder noch der Entwicklungswert. Bitte in der .env setzen." +fi +ok "APP_SECRET gesetzt" + +vorher="$(git rev-parse --short HEAD 2>/dev/null || echo "unbekannt")" +version_vorher="$(node -p "require('./package.json').version" 2>/dev/null || echo "?")" +printf ' Aktuell: v%s (%s)\n' "$version_vorher" "$vorher" + +# ── Neuen Stand holen ──────────────────────────────────────── +if [ "$pull" -eq 1 ]; then + schritt "Neuen Stand holen" + + if [ -n "$(git status --porcelain)" ]; then + abbruch "Das Arbeitsverzeichnis ist nicht sauber. Bitte erst committen oder verwerfen." + fi + + git fetch --all --tags --quiet + if [ -n "$ref" ]; then + git checkout --quiet "$ref" + ok "Auf $ref gewechselt" + else + zweig="$(git rev-parse --abbrev-ref HEAD)" + git pull --ff-only --quiet origin "$zweig" + ok "$zweig aktualisiert" + fi + + nachher="$(git rev-parse --short HEAD)" + version_nachher="$(node -p "require('./package.json').version" 2>/dev/null || echo "?")" + + if [ "$vorher" = "$nachher" ]; then + warnung "Kein neuer Stand – es wird trotzdem neu gebaut." + else + printf ' Neu: v%s (%s)\n' "$version_nachher" "$nachher" + printf '\n Was dazukam:\n' + git --no-pager log --oneline --no-decorate "$vorher..$nachher" 2>/dev/null | sed 's/^/ /' || true + fi +else + warnung "Ohne --pull: der vorhandene Stand wird neu gebaut." +fi + +# ── Rueckfrage ─────────────────────────────────────────────── +if [ "$zusage" -eq 0 ] && [ -t 0 ]; then + printf '\n%sWeiter? Die Datenbank wird vorher gesichert. [j/N] %s' "$fett" "$aus" + read -r antwort + case "$antwort" in [jJyY]*) ;; *) abbruch "Abgebrochen." ;; esac +fi + +# ── Datenbank sichern ──────────────────────────────────────── +schritt "Datenbank sichern" + +# Die Datenbank muss laufen, sonst gibt es nichts zu sichern. +docker compose up -d db >/dev/null +sicherung="" +if [ -x "$root/scripts/backup.sh" ]; then + ausgabe="$("$root/scripts/backup.sh" 2>&1)" || abbruch "Sicherung fehlgeschlagen:\n$ausgabe" + sicherung="$(printf '%s' "$ausgabe" | grep -oE '[^ ]*backups/[^ ]*\.sql[^ ]*' | tail -n 1 || true)" + ok "Gesichert${sicherung:+: $sicherung}" +else + warnung "scripts/backup.sh fehlt – es wird ohne Sicherung fortgefahren." +fi + +# ── Bauen ──────────────────────────────────────────────────── +schritt "Abbild bauen" +# Erst bauen, dann umschalten: ein Fehlschlag laesst die alte Fassung laufen. +docker compose --profile full build app || abbruch "Der Bau ist gescheitert. Die laufende Fassung ist unveraendert." +ok "Abbild gebaut" + +# ── Starten ────────────────────────────────────────────────── +schritt "Starten" +# Die Migrationen laufen im entrypoint.sh, bevor der Server hochkommt. +docker compose --profile full up -d || abbruch "Der Start ist gescheitert." +ok "Container gestartet" + +# ── Gesundheitspruefung ────────────────────────────────────── +schritt "Gesundheitspruefung" + +port="$(sed -n 's/^[[:space:]]*APP_PORT[[:space:]]*=[[:space:]]*"\{0,1\}\([^"#]*\)"\{0,1\}[[:space:]]*$/\1/p' "$root/.env" | tail -n 1)" +port="${port:-3000}" + +gesund=0 +for versuch in $(seq 1 30); do + if curl -fsS "http://127.0.0.1:${port}/api/health" >/dev/null 2>&1; then + gesund=1 + break + fi + sleep 2 +done + +if [ "$gesund" -ne 1 ]; then + printf '\n%sLetzte Zeilen aus dem Log:%s\n' "$fett" "$aus" + docker compose --profile full logs --tail 40 app || true + printf '\n%sZurueck zur vorigen Fassung:%s\n' "$fett" "$aus" + printf ' git checkout %s && ./scripts/update.sh --no-pull --yes\n' "$vorher" + [ -n "$sicherung" ] && printf ' Datenbank notfalls: ./scripts/restore.sh %s\n' "$sicherung" + abbruch "Die Anwendung antwortet nach 60 Sekunden nicht gesund." +fi + +antwort_json="$(curl -fsS "http://127.0.0.1:${port}/api/health")" +ok "Gesund: $antwort_json" + +# ── Fertig ─────────────────────────────────────────────────── +version_jetzt="$(node -p "require('./package.json').version" 2>/dev/null || echo "?")" +printf '\n%s%s✓ Nebula v%s laeuft auf Port %s%s\n' "$gruen" "$fett" "$version_jetzt" "$port" "$aus" +[ -n "$sicherung" ] && printf ' Sicherung vor dem Update: %s\n' "$sicherung" +printf ' Log mitlesen: docker compose --profile full logs -f app\n\n' diff --git a/src/lib/changelog.ts b/src/lib/changelog.ts index 783efbb..9b6525f 100644 --- a/src/lib/changelog.ts +++ b/src/lib/changelog.ts @@ -41,6 +41,51 @@ export const KIND_META: Record }; export const CHANGELOG: Release[] = [ + { + version: "1.5.0", + date: "2026-07-27", + headline: "Sicherheitsdurchsicht und eine Anleitung für den Server.", + changes: [ + { + kind: "behoben", + text: + "Ein Einmalkennwort ließ sich innerhalb seiner Gültigkeit mehrfach verwenden. " + + "Das eingelöste Zeitfenster wird jetzt gemerkt — RFC 6238 verlangt das " + + "ausdrücklich.", + }, + { + kind: "behoben", + text: + "Der Bildoptimierer von Next war ohne Anmeldung erreichbar und reichte Bilder " + + "durch libvips, für das es offene Schwachstellen gibt. Nebula benutzt ihn " + + "nirgends — jetzt ist er abgeschaltet.", + }, + { + kind: "neu", + text: + "DEPLOYMENT.md: Schritt für Schritt vom leeren Server bis zur Instanz hinter " + + "TLS, mit Firewall, Sicherungen und Betrieb.", + }, + { + kind: "neu", + text: + "scripts/update.sh rollt ein Update aus: prüfen, holen, sichern, bauen, " + + "starten, Gesundheit prüfen — und nennt bei einem Fehlschlag den Weg zurück.", + }, + { + kind: "besser", + text: + "Umsortieren im Brett und das Anlegen von Vorlagen sind jetzt ebenfalls " + + "ratenbegrenzt.", + }, + { + kind: "besser", + text: + "Die Bereinigung des Markdowns ist mit elf Tests festgehalten: Skripte, " + + "Ereignis-Attribute, javascript: und data: kommen nicht durch.", + }, + ], + }, { version: "1.4.0", date: "2026-07-26",