Add plan.md and CLAUDE.md
Architecture decisions with rationale (plan.md) and repo-specific build/run commands plus style rules (CLAUDE.md). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
8b172e2c3c
commit
2298051bf4
2 files changed
+496
No files matched your search
@@ -0,0 +1,430 @@
|
||||
# elternbeirat-igmh.de — Neuaufbau
|
||||
|
||||
Ablösung der bisherigen WordPress-Seite durch eine eigene .NET-Anwendung auf
|
||||
eigener Infrastruktur.
|
||||
|
||||
Stand: 2026-09-20 · Verantwortlich: Tom (Thomas Leininger)
|
||||
|
||||
---
|
||||
|
||||
## 1. Ausgangslage
|
||||
|
||||
- Die Domain `elternbeirat-igmh.de` lag bei Maik Palm (ausscheidendes
|
||||
Elternbeirat-Mitglied) im Paket „STRATO Hosting Basic", Auftragsnummer 9157927.
|
||||
- Domaininhaber-Wechsel und Domainumzug sind beidseitig unterschrieben
|
||||
(19.09.2026), die Einreichung bei Strato steht noch aus.
|
||||
- Ziel-Paket: Toms „STRATO Mail Plus" (Auftragsnummer 8844576) — Domain + E-Mail,
|
||||
**kein Webspace**.
|
||||
- **E-Mail bleibt bei Strato.** Nur die Website zieht auf eigene Hardware.
|
||||
- **Die bisherige Website ist aktuell offline.** Der Inhaltsbestand ist damit der
|
||||
zeitkritischste offene Punkt (→ Abschnitt 9).
|
||||
|
||||
---
|
||||
|
||||
## 2. Ziele und Nicht-Ziele
|
||||
|
||||
**Ziele**
|
||||
|
||||
- Öffentliche Informationsseite des Elternbeirats: wer, wann, welche Protokolle,
|
||||
wie erreichbar.
|
||||
- Betrieb auf eigener Infrastruktur (Unraid), ohne fremden Hoster.
|
||||
- Inhalte versionierbar und ohne Datenbank — ein `git clone` ist das vollständige
|
||||
Backup.
|
||||
- Wartungsarm: keine Plugin-Updates, keine PHP-Sicherheitslücken, kein CMS-Login
|
||||
als Angriffsfläche.
|
||||
|
||||
**Nicht-Ziele (bewusst)**
|
||||
|
||||
- Kein CMS mit Web-Editor in Stufe 1. Falls andere Beiratsmitglieder später selbst
|
||||
redaktionell arbeiten sollen, ist das ein eigener Ausbauschritt (→ Abschnitt 11).
|
||||
- Keine Benutzerkonten, kein Login, kein Mitgliederbereich.
|
||||
- Keine Datenbank.
|
||||
- Keine externen Einbindungen (Fonts, Analytics, Maps, Social Widgets) — aus
|
||||
Datenschutzgründen, siehe Abschnitt 10.
|
||||
|
||||
---
|
||||
|
||||
## 3. Architekturentscheidungen
|
||||
|
||||
### AE-1: Blazor mit Static Server-Side Rendering, nicht WebAssembly
|
||||
|
||||
**Entscheidung:** Blazor Web App mit Interaktivitätsmodus *None* (reines statisches
|
||||
SSR). Zielframework .NET 10 (LTS).
|
||||
|
||||
**Begründung:** Blazor WASM lädt mehrere MB Runtime vor dem ersten sichtbaren
|
||||
Buchstaben, liefert Suchmaschinen und Link-Vorschauen (WhatsApp, Signal, Messenger
|
||||
— der Hauptverbreitungsweg bei Elternschaften) eine leere Shell und bringt auf
|
||||
einer reinen Informationsseite keinerlei Gegenwert. Static SSR liefert fertiges
|
||||
HTML, braucht kein JavaScript und kostet im Container rund 60 MB RAM.
|
||||
|
||||
**Konsequenz:** Interaktivität ist später pro Komponente nachrüstbar
|
||||
(`@rendermode InteractiveServer` an genau der einen Komponente), ohne die
|
||||
Architektur zu ändern.
|
||||
|
||||
**Verworfene Alternativen:**
|
||||
|
||||
| Alternative | Warum nicht |
|
||||
|---|---|
|
||||
| Blazor WASM | Payload, SEO, Link-Vorschauen, kein Nutzen |
|
||||
| ASP.NET Core MVC/Razor Pages | Funktioniert genauso, aber Razor Components sind das modernere Modell |
|
||||
| Statiq.Web (C#-SSG) → nginx | Ops-technisch am schlanksten (nichts zu patchen), aber jede Textänderung erzwingt einen Build-Lauf. Bleibt als Rückfallebene. |
|
||||
| Astro/Hugo | Ausgereifteres SSG-Ökosystem, aber fremdes Terrain |
|
||||
|
||||
### AE-2: Inhalte als Dateien im Repo, nicht in einer Datenbank
|
||||
|
||||
**Entscheidung:** Seiteninhalte als Markdown mit YAML-Frontmatter, Termine als
|
||||
strukturiertes YAML, Dokumente (Protokolle, Satzung) als PDF unter `wwwroot`.
|
||||
|
||||
**Begründung:** Kein DB-Backup, kein Migrationsschema, keine Konsistenzprobleme
|
||||
zwischen Dateien und Datenbank. Änderungen sind Commits und damit nachvollziehbar
|
||||
und rückrollbar. Für eine Seite mit ~10 Unterseiten und ein paar Terminen pro Jahr
|
||||
ist alles andere Overhead.
|
||||
|
||||
**Konsequenz:** Textänderungen erfordern einen Commit und ein Redeploy. Das ist bei
|
||||
erwarteten fünf Änderungen im Jahr akzeptabel — und der Grund, warum Abschnitt 11
|
||||
den Ausbau zum Web-Editor als eigene Stufe führt.
|
||||
|
||||
### AE-3: Inhalte werden ins Image gebacken, nicht als Volume gemountet
|
||||
|
||||
**Entscheidung:** `Content/` und `wwwroot/` sind Teil des Images.
|
||||
|
||||
**Begründung:** Gemountete Inhalte aus dem Appdata-Share ließen sich zwar direkt
|
||||
am NAS editieren, aber genau dann driften Repo und Live-Stand auseinander — und
|
||||
die Eigenschaft „Backup = `git clone`" aus AE-2 wäre wertlos.
|
||||
|
||||
**Konsequenz:** Kein Schnell-Fix am Live-System. Tippfehler werden korrekt über
|
||||
einen Commit behoben.
|
||||
|
||||
### AE-4: TLS und Zertifikate ausschließlich im Nginx Proxy Manager
|
||||
|
||||
**Entscheidung:** Der Container spricht nur HTTP auf Port 8080 und hat **kein**
|
||||
Port-Mapping nach außen. NPM terminiert TLS und ist der einzige Weg zum Container.
|
||||
|
||||
**Begründung:** Entspricht dem bereits etablierten Muster im Home-Lab
|
||||
(`cloud.anticarnist.de`). Zertifikatsverwaltung bleibt an einer Stelle.
|
||||
|
||||
**Konsequenz (wichtig):** In `Program.cs` **kein** `UseHttpsRedirection()` und
|
||||
**kein** `UseHsts()` — sonst Redirect-Schleife hinter dem Proxy. HSTS setzt NPM.
|
||||
|
||||
---
|
||||
|
||||
## 4. Projekt anlegen (Rider)
|
||||
|
||||
Template **Blazor Web App** mit diesen Optionen:
|
||||
|
||||
| Option | Wert |
|
||||
|---|---|
|
||||
| Framework | .NET 10.0 |
|
||||
| Authentication | None |
|
||||
| Interactive render mode | **None** |
|
||||
| Include sample pages | aus |
|
||||
| Configure for HTTPS | an (nur für lokale Entwicklung relevant) |
|
||||
| Do not use top-level statements | egal |
|
||||
| Enlist in .NET Aspire orchestration | **aus** |
|
||||
|
||||
Projektname: `Elternbeirat.Web`, Solution: `Elternbeirat`.
|
||||
|
||||
Wichtig ist allein *Interactive render mode = None* — damit erzeugt das Template
|
||||
kein `.Client`-Projekt und kein WebAssembly-Bundle.
|
||||
|
||||
**Angelegt und bestätigt (2026-09-20):** Blazor Web App, `net10.0`, Interactive
|
||||
render mode `None`, Auth `None`, Sample pages aus, Docker-Optionen im Dialog aus
|
||||
(Dockerfile schreiben wir selbst, siehe Abschnitt 7). Git-Repository beim
|
||||
Anlegen mit erzeugt. Solution liegt direkt unter
|
||||
`RiderProjects\Elternbeirat\`, das Projekt in `Elternbeirat.Web\` darunter —
|
||||
**kein** `src/`-Zwischenverzeichnis, anders als ursprünglich in Abschnitt 5
|
||||
skizziert.
|
||||
|
||||
NuGet-Pakete, die dazukommen:
|
||||
|
||||
- `Markdig` — Markdown-Rendering
|
||||
- `YamlDotNet` — Frontmatter und `termine.yml`
|
||||
|
||||
---
|
||||
|
||||
## 5. Repo-Struktur
|
||||
|
||||
```
|
||||
Elternbeirat/ ← Repo-Root, = Solution-Verzeichnis
|
||||
├── Elternbeirat.sln
|
||||
├── plan.md ← dieses Dokument
|
||||
├── CLAUDE.md ← minimal: Trigger, nicht-offensichtliche Kommandos
|
||||
├── docs/
|
||||
│ ├── deployment.md ← Unraid, NPM, Registry, Rollback
|
||||
│ ├── dns.md ← Strato, DynDNS, Mail-Records
|
||||
│ ├── inhalte-pflegen.md ← Anleitung für den Nicht-Alltagsfall
|
||||
│ ├── inhalte-migration.md ← Übernahme aus dem alten WordPress
|
||||
│ └── recht.md ← Impressum, Datenschutz, Fotos
|
||||
├── Elternbeirat.Web/
|
||||
│ ├── Elternbeirat.Web.csproj
|
||||
│ ├── Components/
|
||||
│ │ ├── App.razor
|
||||
│ │ ├── Routes.razor
|
||||
│ │ ├── Layout/ MainLayout, NavMenu, Footer
|
||||
│ │ └── Pages/ Start, UeberUns, Termine, Protokolle,
|
||||
│ │ News, NewsBeitrag, Kontakt,
|
||||
│ │ Impressum, Datenschutz, Fehler404
|
||||
│ ├── Content/ ← Inhalte, kein Code
|
||||
│ │ ├── seiten/*.md
|
||||
│ │ ├── news/2026-09-20-titel.md
|
||||
│ │ └── termine.yml
|
||||
│ ├── Services/
|
||||
│ │ ├── MarkdownContentService.cs
|
||||
│ │ ├── TermineService.cs
|
||||
│ │ └── IcsWriter.cs
|
||||
│ ├── wwwroot/
|
||||
│ │ ├── css/site.css ← eigenes CSS, keine CDN-Einbindung
|
||||
│ │ ├── img/
|
||||
│ │ └── dokumente/ ← Protokolle, Satzung (PDF)
|
||||
│ └── Program.cs
|
||||
├── Elternbeirat.Web.Tests/ ← Smoke-Tests: jede Route liefert 200
|
||||
├── Dockerfile
|
||||
├── compose.yaml
|
||||
├── .dockerignore
|
||||
└── .gitea/workflows/deploy.yml
|
||||
```
|
||||
|
||||
`CLAUDE.md` bleibt bewusst kurz (Build-/Run-Kommandos, Stilregeln, Verweis auf
|
||||
`docs/`). Die Details liegen in `docs/` und werden nur bei Bedarf gelesen.
|
||||
|
||||
---
|
||||
|
||||
## 6. Inhaltsmodell
|
||||
|
||||
**Seite** (`Content/seiten/ueber-uns.md`):
|
||||
|
||||
```markdown
|
||||
---
|
||||
titel: Über uns
|
||||
slug: ueber-uns
|
||||
reihenfolge: 20
|
||||
beschreibung: Wer im Elternbeirat der IGMH mitarbeitet und wie wir arbeiten.
|
||||
---
|
||||
|
||||
## Der Elternbeirat
|
||||
|
||||
Fließtext …
|
||||
```
|
||||
|
||||
**Termin** (`Content/termine.yml`):
|
||||
|
||||
```yaml
|
||||
- titel: Elternbeiratssitzung
|
||||
beginn: 2026-10-14T19:30:00
|
||||
ende: 2026-10-14T21:00:00
|
||||
ort: IGMH, Raum A103
|
||||
oeffentlich: true
|
||||
notiz: Gäste willkommen
|
||||
```
|
||||
|
||||
**News-Beitrag** (`Content/news/2026-09-20-neue-website.md`) — wie Seite, plus
|
||||
`datum` und `autor`.
|
||||
|
||||
Die Services lesen beim Start alles ein, cachen es im Speicher und stellen es
|
||||
typisiert bereit. In Development zusätzlich ein `FileSystemWatcher`, damit
|
||||
Textänderungen ohne Neustart sichtbar werden.
|
||||
|
||||
**Zusatznutzen ohne Mehraufwand:** ein ICS-Endpoint unter `/termine.ics`, der die
|
||||
öffentlichen Termine ausliefert. Eltern abonnieren den Kalender einmal im Handy
|
||||
und sehen jede Sitzung automatisch. Das ist der eine Punkt, an dem die Eigenbau-
|
||||
Lösung die alte WordPress-Seite spürbar schlägt.
|
||||
|
||||
---
|
||||
|
||||
## 7. Build und Deployment
|
||||
|
||||
### Stufe 1 — Handbetrieb (Start hier)
|
||||
|
||||
```bash
|
||||
docker compose build
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
Für fünf Deployments im Jahr vollkommen ausreichend. CI vorab zu bauen wäre
|
||||
Selbstzweck.
|
||||
|
||||
### Stufe 2 — Gitea Actions (Runner ist vorhanden)
|
||||
|
||||
`.gitea/workflows/deploy.yml` auf Push nach `main`:
|
||||
|
||||
1. `actions/checkout`
|
||||
2. Login an der Gitea-eigenen Container-Registry
|
||||
3. `docker/build-push-action` → `gitea.<domain>/tom/elternbeirat:latest` **und** `:${{ gitea.sha }}`
|
||||
|
||||
Zwei Stolpersteine, die erfahrungsgemäß Zeit kosten:
|
||||
|
||||
- Der `act_runner` im Docker-Modus braucht Zugriff auf einen Docker-Socket oder
|
||||
einen DinD-Service, sonst schlägt `build-push-action` fehl.
|
||||
- Die Gitea-Registry braucht ein Paket-Token mit Schreibrecht (`write:package`),
|
||||
nicht das normale Login-Passwort.
|
||||
|
||||
**Immer auch den SHA-Tag pushen.** `:latest` allein macht Rollback unmöglich.
|
||||
|
||||
### Redeploy auf Unraid
|
||||
|
||||
Watchtower, aber **label-scoped** — sonst aktualisiert er ungefragt den ganzen
|
||||
Home-Lab-Bestand:
|
||||
|
||||
```yaml
|
||||
# im Watchtower-Container
|
||||
WATCHTOWER_LABEL_ENABLE: "true"
|
||||
```
|
||||
|
||||
```yaml
|
||||
# im eb-web-Service
|
||||
labels:
|
||||
com.centurylinklabs.watchtower.enable: "true"
|
||||
```
|
||||
|
||||
Alternativ: manuell im Unraid-Docker-Tab „Update" drücken. Bei dieser
|
||||
Änderungsfrequenz völlig legitim.
|
||||
|
||||
### Dockerfile (Skizze)
|
||||
|
||||
```dockerfile
|
||||
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
|
||||
WORKDIR /src
|
||||
COPY Elternbeirat.Web/Elternbeirat.Web.csproj Elternbeirat.Web/
|
||||
RUN dotnet restore Elternbeirat.Web/Elternbeirat.Web.csproj
|
||||
COPY . .
|
||||
RUN dotnet publish Elternbeirat.Web/Elternbeirat.Web.csproj -c Release -o /app
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:10.0-noble-chiseled AS runtime
|
||||
WORKDIR /app
|
||||
COPY --from=build /app .
|
||||
EXPOSE 8080
|
||||
ENTRYPOINT ["dotnet", "Elternbeirat.Web.dll"]
|
||||
```
|
||||
|
||||
Das chiseled-Image läuft ab .NET 8 standardmäßig als non-root (UID 1654) und
|
||||
hört auf Port 8080. **Es enthält keine Shell** — ein `HEALTHCHECK` mit `curl`
|
||||
funktioniert dort nicht. Entweder das normale `aspnet:10.0-noble` verwenden oder
|
||||
die Überwachung NPM bzw. Uptime Kuma überlassen.
|
||||
|
||||
---
|
||||
|
||||
## 8. Hosting auf Unraid
|
||||
|
||||
```yaml
|
||||
# compose.yaml
|
||||
services:
|
||||
eb-web:
|
||||
image: gitea.<domain>/tom/elternbeirat:latest
|
||||
container_name: eb-web
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
ASPNETCORE_URLS: http://+:8080
|
||||
TZ: Europe/Berlin
|
||||
networks: [npm]
|
||||
labels:
|
||||
com.centurylinklabs.watchtower.enable: "true"
|
||||
|
||||
networks:
|
||||
npm:
|
||||
external: true
|
||||
```
|
||||
|
||||
Kein `ports:`-Block. Der Container ist ausschließlich über das NPM-Docker-Netz
|
||||
erreichbar.
|
||||
|
||||
**NPM Proxy Host:**
|
||||
|
||||
| Feld | Wert |
|
||||
|---|---|
|
||||
| Domain Names | `elternbeirat-igmh.de`, `www.elternbeirat-igmh.de` |
|
||||
| Scheme | `http` |
|
||||
| Forward Hostname | `eb-web` |
|
||||
| Forward Port | `8080` |
|
||||
| Block Common Exploits | an |
|
||||
| Websockets Support | aus (wird bei statischem SSR nicht gebraucht) |
|
||||
| SSL | Let's Encrypt, Force SSL, HTTP/2, HSTS |
|
||||
|
||||
**In `Program.cs` nicht vergessen:**
|
||||
|
||||
```csharp
|
||||
app.UseForwardedHeaders(new ForwardedHeadersOptions
|
||||
{
|
||||
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto,
|
||||
// NPM ist der einzige Weg zum Container (kein Port-Mapping),
|
||||
// daher ist das Leeren der Allowlists hier vertretbar:
|
||||
KnownNetworks = { }, KnownProxies = { }
|
||||
});
|
||||
```
|
||||
|
||||
Ohne das sieht die App jede Anfrage als HTTP und mit der Proxy-IP statt der
|
||||
Client-IP — relevant für korrekte absolute URLs und für die Logs.
|
||||
|
||||
---
|
||||
|
||||
## 9. Inhalte — zeitkritisch
|
||||
|
||||
Die alte Seite ist offline, Maiks Strato-Vertrag läuft aber noch. Solange er läuft,
|
||||
ist der Webspace erreichbar; nach der Kündigung ist der Bestand endgültig weg.
|
||||
|
||||
**Reihenfolge der Rettungsversuche:**
|
||||
|
||||
1. Prüfen, was beim Termin mit Maik tatsächlich gesichert wurde (Dateien? MySQL-Dump?
|
||||
Beides?). Ein vollständiger Dump wäre der Idealfall — daraus lassen sich Texte,
|
||||
Seitenstruktur, Medien und PDFs sauber extrahieren.
|
||||
2. Falls nur Dateien vorliegen: `wp-content/uploads` enthält Bilder und PDFs, die
|
||||
Texte liegen aber in der Datenbank. Dann Schritt 3.
|
||||
3. Wayback Machine auf Snapshots von `elternbeirat-igmh.de` prüfen.
|
||||
4. Falls nichts davon greift: Maik bitten, vor Vertragsende noch einen Export zu
|
||||
ziehen — oder Inhalte aus den Protokollen und von der Schulseite neu aufbauen.
|
||||
|
||||
**Diese Frage blockiert den Inhaltsteil, nicht den Technikteil.** Das Gerüst lässt
|
||||
sich mit Platzhaltern bauen und später befüllen.
|
||||
|
||||
---
|
||||
|
||||
## 10. Rechtliches
|
||||
|
||||
Kein Rechtsrat — aber die Punkte, an denen Schulseiten regelmäßig auffallen:
|
||||
|
||||
- **Impressum (§ 5 DDG):** Hat der Elternbeirat keine eigene Rechtsform, steht der
|
||||
Betreiber persönlich mit Name und ladungsfähiger Anschrift im Impressum. Das ist
|
||||
eine Entscheidung, keine Formalie — die Privatadresse wird damit öffentlich.
|
||||
Alternative: Anschrift der Schule, aber nur mit deren ausdrücklichem Einverständnis
|
||||
und wenn die Schule Mitbetreiberin ist.
|
||||
- **Datenschutzerklärung:** Mit dem Umzug ist Tom Verantwortlicher im Sinne der DSGVO.
|
||||
Server-Logs mit IP-Adressen benennen, Rechtsgrundlage und Löschfrist festlegen.
|
||||
- **Keine externen Ressourcen.** Google Fonts, Maps, YouTube-Embeds und CDN-Skripte
|
||||
übertragen die IP der Besucher an Dritte. Fonts werden selbst ausgeliefert.
|
||||
- **Fotos von Kindern:** nur mit Einwilligung der Erziehungsberechtigten — bei
|
||||
Schulseiten der mit Abstand häufigste Fehler. Im Zweifel keine Personenfotos.
|
||||
- Hosting am privaten Anschluss bedeutet: Die öffentliche IP des Privatanschlusses
|
||||
steht im DNS einer Schulseite. Bewusste Entscheidung, kein Nebeneffekt.
|
||||
|
||||
---
|
||||
|
||||
## 11. Offene Punkte
|
||||
|
||||
| # | Punkt | Status |
|
||||
|---|---|---|
|
||||
| 1 | Was wurde vom alten WordPress gesichert? | **offen, zeitkritisch** |
|
||||
| 2 | Kann „STRATO Mail Plus" DynDNS? Laut Strato-FAQ ab „PowerWeb Basic 2013 bzw. STRATO Domain" — ob Mail Plus dazuzählt, ist unklar. Beim Support mit anfragen, solange der Umzugsvorgang läuft. | offen |
|
||||
| 3 | DynDNS-Updater: Fritzbox (kennt als Exposed-Host-Vorschaltgerät die öffentliche IP) oder ddclient-Container auf Unraid? | offen |
|
||||
| 4 | Sollen andere Beiratsmitglieder Inhalte selbst pflegen können? Falls ja: eigener Ausbauschritt (Decap CMS auf Git-Basis oder kleines Admin-UI). | offen |
|
||||
| 5 | Kontaktformular gewünscht? Würde Server-Interaktivität und Spam-Schutz erfordern — `mailto:` ist die aufwandsfreie Alternative. | offen |
|
||||
| 6 | Wer springt ein, wenn Tom nicht verfügbar ist? Eine Seite, die nur einer deployen kann, ist eine Abhängigkeit, die der Beirat kennen sollte. | offen |
|
||||
| 7 | Formulare bei Strato einreichen (unterschrieben, liegt bereit) | offen |
|
||||
|
||||
---
|
||||
|
||||
## 12. Umsetzungsreihenfolge
|
||||
|
||||
| # | Schritt | Abhängig von |
|
||||
|---|---|---|
|
||||
| 1 | Strato-Formulare einreichen, Domainumzug anstoßen | — |
|
||||
| 2 | Inhaltslage klären (Abschnitt 9) | — |
|
||||
| 3 | Blazor-Projekt in Rider anlegen ✅ (2026-09-20) → nach Gitea pushen | — |
|
||||
| 4 | Content-Pipeline: Markdig, YamlDotNet, Services, ICS-Endpoint | 3 |
|
||||
| 5 | Layout, Navigation, Seiten mit Platzhaltern | 4 |
|
||||
| 6 | Dockerfile, compose.yaml, lokal gegen NPM testen | 5 |
|
||||
| 7 | Echte Inhalte einpflegen | 2, 5 |
|
||||
| 8 | Impressum und Datenschutzerklärung | 7 |
|
||||
| 9 | DNS umstellen, NPM Proxy Host, Let's Encrypt | 1, 6 |
|
||||
| 10 | Gitea Actions + Registry + Watchtower | 6, 9 |
|
||||
|
||||
Schritte 1 und 2 laufen unabhängig vom Code und sollten sofort starten —
|
||||
Schritt 2 ist der einzige, bei dem Warten echten Schaden anrichtet.
|
||||
Reference in new issue
Block a user