diff --git a/CHANGELOG.md b/CHANGELOG.md
index a68fcf0..9842d63 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,13 +11,15 @@ und dieses Projekt folgt [Semantic Versioning](https://semver.org/lang/de/).
- Betrieb per Docker Compose (postgres, migrate, app, worker) mit einem Image für alles, non-root (#019)
- LXC-Installer `deploy/install-lxc.sh`: legt auf dem Proxmox-Host einen Debian-13-Container an, installiert Node.js und PostgreSQL, richtet systemd-Dienste ein; mit Update-Pfad (#019)
- Migrationen im Betrieb ohne Prisma-CLI, kompatibel zu Prisma (#019)
-- Übersicht zeigt Nodes, CPUs und freien RAM je Server sowie die letzten Deployments (#018)
+- Übersicht zeigt Nodes, CPUs und freien RAM je Server (#018)
+- README mit Banner, Screenshots, Installation, ersten Schritten und Katalog-Anleitungen (#018)
- Marken-Dateien nach Vorgabe unter `public/brand/`: Symbol (dunkel/hell), kleines Icon, Avatar, Logo (dunkel/hell), Banner 1280×640, Favicons, Apple-Touch- und Manifest-Icons; erzeugt mit `pnpm marke` (#017)
- Web-App-Manifest; Favicon passt sich hellem und dunklem Browser an (#017)
### Geändert
### Behoben
+- Die Übersicht zeigte statt der letzten Deployments nur einen Platzhalter (#018)
### Entfernt
diff --git a/README.md b/README.md
index 09df022..dfeea8c 100644
--- a/README.md
+++ b/README.md
@@ -1,28 +1,80 @@
-# ProxDeploy
+
-**VMs und LXC-Container auf Proxmox VE ausrollen, per Web oder CLI.**
+**ProxDeploy** ist eine selbst gehostete Webanwendung mit einer CLI (`proxdeploy`). Damit wählt man
+einen von mehreren Proxmox-VE-Servern (8.x/9.x, Einzelhost oder Cluster) und rollt darauf vollständig
+unattended eine VM oder einen LXC-Container mit Debian oder Ubuntu aus – mit Profilen wie `base`,
+`docker` oder `zabbix-agent`. Distributionen und Profile sind YAML-Dateien im Katalog, kein Code.
-ProxDeploy ist eine selbst gehostete Webanwendung mit einer CLI (`proxdeploy`). Damit wählt man einen von
-mehreren Proxmox-VE-Servern (8.x/9.x, Einzelhost oder Cluster) und rollt darauf vollständig unattended
-eine VM oder einen LXC-Container mit Debian oder Ubuntu aus, inklusive Profilen wie `base`, `docker`
-oder `zabbix-agent`. Weitere Distributionen und Profile sind YAML-Dateien im Katalog, kein Code.
-
-> ProxDeploy ist ein unabhängiges Werkzeug und stammt nicht von Proxmox Server Solutions GmbH.
+> ProxDeploy ist ein unabhängiges Werkzeug und stammt nicht von der Proxmox Server Solutions GmbH.
> „Proxmox“ ist eine Marke der Proxmox Server Solutions GmbH.
-## Stand
+
-In Entwicklung. Version und Fortschritt stehen im [CHANGELOG](CHANGELOG.md), der Plan in
-[docs/architektur.md](docs/architektur.md), die Aufgaben in [docs/issues/](docs/issues/README.md).
+## Was es kann
-| Etappe | Inhalt | Stand |
+- **Assistent in neun Schritten** – Server, Typ, Distribution, Name, Ressourcen, Netzwerk (DHCP/statisch,
+ VLAN), Zugang (SSH-Schlüssel), Profile mit Variablen – und am Ende ein **Dry-Run** mit allen geplanten
+ Proxmox-Aufrufen und dem passenden CLI-Befehl.
+- **Live-Fortschritt** je Phase (Server-Sent Events), in Web und CLI mit denselben Texten; bei Fehlern
+ Ursache und Lösungshinweis, danach auf Wunsch **Aufräumen** des halb angelegten Gasts.
+- **Templates** aus offiziellen Cloud-Images (Prüfsumme geprüft, UEFI, Guest-Agent eingebaut), auf
+ Wunsch mit eingebackenen Profilen – ein Docker-Host ist dann in Sekunden statt Minuten fertig.
+- **Ohne SSH zum Node** per API und Guest-Agent; mit SSH zusätzlich LXC-Provisionierung und
+ cloud-init-Snippets. Was ein Server kann, zeigt der Verbindungstest.
+- **Verlauf** mit Filtern und Wiederholen, **Vorlagen** („Docker-Host klein“), hinterlegte
+ **SSH-Schlüssel**, **Rollen** Admin/Operator, **Audit-Log**, Anmeldung lokal oder per **OIDC**.
+- **CLI** als einzelnes Binary für Linux, macOS und Windows – mit Batch-Dateien für viele Gäste.
+
+| Übersicht | Assistent mit Dry-Run | Am Handy |
|---|---|---|
-| 1 · v0.1.0 | Server-Verwaltung, Fingerprint-Pinning, Verbindungstest mit Fähigkeitserkennung | ✓ fertig, auf PVE 9.2 geprüft |
-| 2 · v0.2.0 | Katalog, Template-Bau, Debian-13-VM, Live-Fortschritt, Assistent | ✓ fertig, auf PVE 9.2 geprüft |
-| 3 · v0.3.0 | LXC, Ubuntu, Profile, Vorlagen, Profile im Template, SSH-Schlüssel | ✓ fertig, auf PVE 9.2 geprüft (Bau-Adresse #021: Abnahme offen) |
-| 4 · v0.4.0 | CLI `proxdeploy` (Go), API-Tokens | ✓ fertig, auf PVE 9.2 geprüft |
-| 5 · v0.5.0 | Verlauf, Aufräumen, Audit-Log, Benutzer, OIDC | ✓ fertig (Aufräumen auf PVE 9.2 geprüft, OIDC gegen Mock-IdP) |
-| 6 · v0.6.0 | Branding, README mit Screenshots, Installer | ◐ als Nächstes |
+|  |  |  |
+
+Weitere Ansichten: [Verlauf](docs/bilder/verlauf.png) · [Server mit Verbindungstest](docs/bilder/server.png) · [Katalog](docs/bilder/katalog.png).
+Erzeugt mit `scripts/screenshots.ts` aus dem gebauten Stand.
+
+## Installation
+
+ProxDeploy braucht PostgreSQL und läuft als zwei Prozesse (app und worker). Es gehört hinter einen
+Reverse-Proxy mit TLS; dann `TRUST_PROXY=true` und `APP_URL` auf die öffentliche Adresse setzen.
+
+### Docker Compose
+
+```bash
+git clone https://git.wilde-server.de/florian.wilde/proxdeploy.git && cd proxdeploy
+cp .env.example .env
+# POSTGRES_PASSWORT=$(openssl rand -hex 24)
+# PROXDEPLOY_SCHLUESSEL=$(openssl rand -base64 32) ← sichern! ohne ihn sind Tokens und Schlüssel verloren
+# SITZUNG_GEHEIMNIS=$(openssl rand -base64 48)
+# APP_URL=https://proxdeploy.example
+# ADMIN_PASSWORT=… ← erster Admin, nur beim allerersten Start
+docker compose up -d --build
+```
+
+Compose startet `postgres`, spielt mit `migrate` die Datenbank ein und startet dann `app` (Port 3000 auf
+127.0.0.1) und `worker`. Aktualisieren: `git pull && docker compose up -d --build`.
+
+### In einem Debian-LXC auf dem Proxmox-Host
+
+```bash
+scripts/paket-bauen.sh 0.6.0 --tar # im Repository: proxdeploy-0.6.0.tar.gz
+scp proxdeploy-0.6.0.tar.gz deploy/install-lxc.sh deploy/im-container.sh root@pve01:/root/
+ssh root@pve01 ./install-lxc.sh --paket proxdeploy-0.6.0.tar.gz --url https://proxdeploy.example
+```
+
+Der Installer legt einen unprivilegierten Debian-13-Container an (DHCP auf `vmbr0`; `--ip`, `--gw`,
+`--vlan`, `--storage`, `--ctid` u. a. siehe `--help`), installiert Node.js 24 (Prüfsumme geprüft) und
+PostgreSQL, richtet systemd-Dienste ein und zeigt am Ende Adresse und das Passwort des ersten Admins.
+Aktualisieren mit `./install-lxc.sh --aktualisieren --paket proxdeploy-0.6.1.tar.gz` – die
+letzten drei Fassungen bleiben unter `/opt/proxdeploy/versionen/`.
+
+## Erste Schritte
+
+1. **Token in Proxmox:** eigenen Benutzer und die Rolle `ProxDeploy` anlegen, Token ohne
+ Privilege Separation – die Befehle für 8.x und 9.x stehen in [docs/proxmox-rechte.md](docs/proxmox-rechte.md).
+2. **Server anlegen:** URL, Token, Zertifikat bestätigen (Fingerprint-Pinning für selbst signierte
+ Zertifikate), „Verbindung testen“. Optional SSH-Zugang zum Node hinterlegen.
+3. **SSH-Schlüssel** unter Einstellungen hinterlegen – sie kommen automatisch in jeden Gast.
+4. **Ausrollen:** fehlt das Template, baut ProxDeploy es vorher (oder unter „Templates“).
## Was ProxDeploy auf einem Server kann
@@ -129,6 +181,28 @@ OIDC_GRUPPE_OPERATOR=proxdeploy-operators
- Gesperrte Konten bleiben gesperrt, auch per SSO. Getestet gegen einen Mock-IdP (`tests/oidc.test.ts`);
ein echter IdP folgt beim Feldtest.
+## Katalog erweitern
+
+- **Neue Distribution:** eine YAML-Datei unter `catalog/distros/` – Image-URL, Prüfsummen-Quelle,
+ Standardbenutzer, LXC-Template-Muster. Für eine neue Familie (z. B. `rhel` mit dnf) kommt ein
+ Adapter unter `src/engine/adapter/` dazu. → [docs/katalog.md](docs/katalog.md#neue-distribution-hinzufügen)
+- **Eigenes Profil:** in der Web-GUI unter Katalog → „Profil anlegen“ (YAML mit Live-Prüfung und
+ Skriptvorschau) oder als Datei unter `catalog/profiles/`. → [docs/katalog.md](docs/katalog.md#eigenes-profil-anlegen)
+
+## Stand
+
+Version und Änderungen stehen im [CHANGELOG](CHANGELOG.md), der Plan in
+[docs/architektur.md](docs/architektur.md), die Aufgaben in [docs/issues/](docs/issues/README.md).
+
+| Etappe | Inhalt | Stand |
+|---|---|---|
+| 1 · v0.1.0 | Server-Verwaltung, Fingerprint-Pinning, Verbindungstest mit Fähigkeitserkennung | ✓ fertig, auf PVE 9.2 geprüft |
+| 2 · v0.2.0 | Katalog, Template-Bau, Debian-13-VM, Live-Fortschritt, Assistent | ✓ fertig, auf PVE 9.2 geprüft |
+| 3 · v0.3.0 | LXC, Ubuntu, Profile, Vorlagen, Profile im Template, SSH-Schlüssel | ✓ fertig, auf PVE 9.2 geprüft (Bau-Adresse #021: Abnahme offen) |
+| 4 · v0.4.0 | CLI `proxdeploy` (Go), API-Tokens | ✓ fertig, auf PVE 9.2 geprüft |
+| 5 · v0.5.0 | Verlauf, Aufräumen, Audit-Log, Benutzer, OIDC | ✓ fertig (Aufräumen auf PVE 9.2 geprüft, OIDC gegen Mock-IdP) |
+| 6 · v0.6.0 | Branding, README mit Screenshots, Installer | ◐ als Nächstes |
+
## Entwicklung
Voraussetzungen: Node 24 (z. B. über nvm), pnpm 12 (corepack), Docker für PostgreSQL.
@@ -154,12 +228,6 @@ Go-Client nutzt die aus `cli/api/openapi.json` erzeugten Typen.
Konventionen (deutsche Bezeichner, Schichtung, Versionen) stehen in [CLAUDE.md](CLAUDE.md).
-## Betrieb
-
-Docker Compose (app, worker, postgres) und ein Installer für einen Debian-LXC auf Proxmox folgen mit
-Etappe 6. ProxDeploy ist für den Betrieb hinter einem Reverse-Proxy mit TLS gedacht
-(`TRUST_PROXY=true`, `APP_URL` auf die öffentliche Adresse).
-
## Lizenz
Noch nicht festgelegt.
diff --git a/docs/bilder/assistent-zusammenfassung.png b/docs/bilder/assistent-zusammenfassung.png
new file mode 100644
index 0000000..cb5f5f2
Binary files /dev/null and b/docs/bilder/assistent-zusammenfassung.png differ
diff --git a/docs/bilder/handy-live-ansicht.png b/docs/bilder/handy-live-ansicht.png
new file mode 100644
index 0000000..d69ff32
Binary files /dev/null and b/docs/bilder/handy-live-ansicht.png differ
diff --git a/docs/bilder/katalog.png b/docs/bilder/katalog.png
new file mode 100644
index 0000000..ef1db94
Binary files /dev/null and b/docs/bilder/katalog.png differ
diff --git a/docs/bilder/live-ansicht.png b/docs/bilder/live-ansicht.png
new file mode 100644
index 0000000..0f5f206
Binary files /dev/null and b/docs/bilder/live-ansicht.png differ
diff --git a/docs/bilder/server.png b/docs/bilder/server.png
new file mode 100644
index 0000000..df37bb2
Binary files /dev/null and b/docs/bilder/server.png differ
diff --git a/docs/bilder/uebersicht.png b/docs/bilder/uebersicht.png
new file mode 100644
index 0000000..fbadc2c
Binary files /dev/null and b/docs/bilder/uebersicht.png differ
diff --git a/docs/bilder/verlauf.png b/docs/bilder/verlauf.png
new file mode 100644
index 0000000..b2cd052
Binary files /dev/null and b/docs/bilder/verlauf.png differ
diff --git a/docs/issues/018-readme.md b/docs/issues/018-readme.md
index 54cc162..c15ec5c 100644
--- a/docs/issues/018-readme.md
+++ b/docs/issues/018-readme.md
@@ -1,5 +1,5 @@
# #018 README und Dokumentation
-**Status:** offen
+**Status:** erledigt
**Meilenstein:** v0.6.0
**Labels:** doku
**Geschätzt:** 4 h **Tatsächlich:** –
@@ -8,14 +8,16 @@
README mit Banner, Screenshots, Installation, CLI, Proxmox-Rechten, neuer Distribution, eigenem Profil.
## Aufgaben
-- [ ] Screenshots aus dem gebauten Stand
-- [ ] Hinweis: unabhängiges Tool, nicht von Proxmox Server Solutions
+- [x] Screenshots aus dem gebauten Stand (`scripts/screenshots.ts`, dunkel, 1440 px und 390 px, echte Daten von pve-demo)
+- [x] Hinweis: unabhängiges Tool, nicht von Proxmox Server Solutions
+- [x] Banner oben, Funktionen, Installation (Compose, LXC), Erste Schritte, CLI, OIDC, Katalog erweitern
## Akzeptanzkriterien
-- [ ] Anleitungen einmal Schritt für Schritt nachvollzogen
+- [x] Anleitungen einmal Schritt für Schritt nachvollzogen: Compose (eigene Env-Datei, frische DB), LXC-Installer auf pve-demo, „Erste Schritte“ in der frisch installierten Instanz (Server anlegen, testen, Token, CLI, Dry-Run, echter LXC). „Neue Distribution“ ist der Weg, auf dem Ubuntu 26.04 dazukam (#010); „Eigenes Profil“ prüft der Playwright-Ablauf `profil-editor`.
## Abhängigkeiten
#013
## Notizen / Entscheidungen
-–
+- Beim Erstellen der Screenshots fiel auf: die Übersicht zeigte seit Etappe 1 nur einen Platzhalter
+ („Noch keine Deployments“) – jetzt Server mit Nodes, CPUs und freiem RAM sowie die letzten Aufträge.
diff --git a/docs/issues/README.md b/docs/issues/README.md
index 674a35f..8ac4cdb 100644
--- a/docs/issues/README.md
+++ b/docs/issues/README.md
@@ -21,7 +21,7 @@ Details und Architektur: [../architektur.md](../architektur.md). Zeiten werden a
| [#015](015-rollen-audit.md) | Rollen, Benutzer, Tokens, Audit-Log | erledigt | v0.5.0 | 6 h | 0 h 10 min |
| [#016](016-oidc.md) | Anmeldung per OIDC | erledigt | v0.5.0 | 4 h | 0 h 08 min |
| [#017](017-branding.md) | Branding-Assets | erledigt | v0.6.0 | 4 h | – |
-| [#018](018-readme.md) | README und Dokumentation | offen | v0.6.0 | 4 h | – |
+| [#018](018-readme.md) | README und Dokumentation | erledigt | v0.6.0 | 4 h | – |
| [#019](019-installer.md) | Docker-Image und LXC-Installer | erledigt | v0.6.0 | 6 h | – |
| [#020](020-feldtest.md) | Feldtest 8.x und 9.x, Version 1.0.0 | offen | v1.0.0 | 6 h | – |
| [#021](021-template-bau-ohne-ssh.md) | Template-Bau ohne SSH (Bau-Adresse) | umgesetzt, Abnahme offen | v0.3.0 | 3 h | 0 h 19 min |
diff --git a/scripts/screenshots.ts b/scripts/screenshots.ts
new file mode 100644
index 0000000..5156093
--- /dev/null
+++ b/scripts/screenshots.ts
@@ -0,0 +1,91 @@
+/**
+ * Screenshots für das README aus dem gebauten Stand (`scripts/neu-starten.sh`, Port 3310), dunkles
+ * Theme, 1440 px und 390 px. Ergebnis: docs/bilder/*.png
+ *
+ * pnpm exec tsx scripts/screenshots.ts [auftrag-kurzid]
+ */
+import { mkdirSync } from "node:fs";
+import { chromium, type Page } from "@playwright/test";
+
+const BASIS = process.env["SCREENSHOT_URL"] ?? "http://127.0.0.1:3310";
+const BENUTZER = process.env["E2E_BENUTZER"] ?? "admin";
+const PASSWORT = process.env["E2E_PASSWORT"] ?? "proxdeploy-dev-admin";
+const ZIEL = "docs/bilder";
+
+async function anmelden(seite: Page) {
+ await seite.goto(`${BASIS}/anmelden`);
+ await seite.getByLabel("Benutzername").fill(BENUTZER);
+ await seite.getByLabel("Passwort").fill(PASSWORT);
+ await seite.getByRole("button", { name: "Anmelden" }).click();
+ await seite.waitForURL((url) => !url.pathname.startsWith("/anmelden"));
+}
+
+async function bild(seite: Page, name: string, ganz = false) {
+ await seite.waitForLoadState("networkidle");
+ // Blinkender Cursor und Spinner sollen nicht zufällig halb im Bild stehen.
+ await seite.addStyleTag({ content: "*{animation:none!important;transition:none!important}" });
+ await seite.screenshot({ path: `${ZIEL}/${name}.png`, fullPage: ganz });
+ console.log(`${ZIEL}/${name}.png`);
+}
+
+async function main() {
+ const auftrag = process.argv[2];
+ if (!auftrag) throw new Error("Kurz-ID eines erfolgreichen Auftrags angeben (für die Live-Ansicht).");
+ mkdirSync(ZIEL, { recursive: true });
+ const browser = await chromium.launch();
+ try {
+ const kontext = await browser.newContext({
+ viewport: { width: 1440, height: 900 },
+ deviceScaleFactor: 1,
+ });
+ await kontext.addCookies([{ name: "pd_theme", value: "dunkel", url: BASIS }]);
+ const seite = await kontext.newPage();
+ await anmelden(seite);
+
+ await seite.goto(`${BASIS}/`);
+ await bild(seite, "uebersicht");
+
+ await seite.goto(`${BASIS}/auftraege/${auftrag}`);
+ await bild(seite, "live-ansicht");
+
+ await seite.goto(`${BASIS}/auftraege`);
+ await bild(seite, "verlauf");
+
+ await seite.goto(`${BASIS}/server/demo`);
+ await bild(seite, "server", true);
+
+ await seite.goto(`${BASIS}/katalog`);
+ await bild(seite, "katalog");
+
+ // Assistent bis zur Zusammenfassung – mit echtem Dry-Run gegen den Server.
+ await seite.goto(`${BASIS}/ausrollen`);
+ await seite.waitForLoadState("networkidle");
+ const weiter = async (bis: number) => {
+ await seite.getByRole("button", { name: "Weiter" }).click();
+ await seite.getByText(`Schritt ${bis} von 9`).waitFor();
+ };
+ for (let s = 2; s <= 4; s++) await weiter(s);
+ await seite.getByLabel("Name (Hostname)").fill("web01");
+ for (let s = 5; s <= 7; s++) await weiter(s);
+ // Ohne hinterlegten Schlüssel einen angeben (öffentlicher Beispielschlüssel).
+ const feld = seite.locator("textarea#schluessel");
+ if ((await feld.inputValue()) === "")
+ await feld.fill(
+ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEFTdkCuqQ6mF7I8QzmkNaQpKj7S/I2+yuxWeYasJjLd florian@laptop",
+ );
+ for (let s = 8; s <= 9; s++) await weiter(s);
+ await seite.getByText("Geplante Proxmox-Aufrufe").waitFor({ timeout: 60_000 });
+ await bild(seite, "assistent-zusammenfassung");
+
+ const handy = await browser.newContext({ viewport: { width: 390, height: 844 }, deviceScaleFactor: 2 });
+ await handy.addCookies([{ name: "pd_theme", value: "dunkel", url: BASIS }]);
+ const klein = await handy.newPage();
+ await anmelden(klein);
+ await klein.goto(`${BASIS}/auftraege/${auftrag}`);
+ await bild(klein, "handy-live-ansicht");
+ } finally {
+ await browser.close();
+ }
+}
+
+await main();
diff --git a/src/app/(app)/page.tsx b/src/app/(app)/page.tsx
index 42fdbfd..9f8a3a6 100644
--- a/src/app/(app)/page.tsx
+++ b/src/app/(app)/page.tsx
@@ -1,16 +1,21 @@
import type { Metadata } from "next";
+import Link from "next/link";
import { Karte } from "@/components/karte";
import { Status } from "@/components/status";
-import Link from "next/link";
+import { ART_TEXT, STATUS_ANZEIGE } from "@/lib/auftrag-text";
+import { bytes, zeitpunkt } from "@/lib/formate";
import { seiteSchuetzen } from "@/server/auth/zugang";
+import { auftraegeSuchen } from "@/server/services/auftraege";
import { serverListe } from "@/server/services/server";
import { ServerStatus } from "./server/server-status";
export const metadata: Metadata = { title: "Übersicht" };
+/** Server mit Status und freien Ressourcen (aus dem letzten Verbindungstest), letzte Aufträge. */
export default async function Uebersicht() {
const benutzer = await seiteSchuetzen("lesen", "/");
- const server = await serverListe();
+ const [server, { eintraege }] = await Promise.all([serverListe(), auftraegeSuchen({ seite: 1 })]);
+ const letzte = eintraege.slice(0, 8);
return (
@@ -32,23 +37,73 @@ export default async function Uebersicht() {
Noch kein Proxmox-Server angelegt.
) : (