Dissolve plan.md into docs/, move open work to Gitea issues

This commit is contained in:
tleininger committed 2026-09-22 13:39:27 +02:00
1 parent aa7dd4525d
commit f7fad64bcb
9 files changed
+142 -508

No files matched your search

+72
View File
@@ -0,0 +1,72 @@
# Architecture decisions
Why the site is built the way it is. Read this before questioning one of these
decisions — each records the reasoning, not just the choice. Operational how-to
lives in `docs/deployment.md`; open work lives in `tasks/`.
> **Note on scope.** These decisions describe stage 1: a static information site
> with content as files. The move to editor-based content maintenance
> (`tasks/001-redaktion-ohne-entwickler.md`) deliberately revisits AE-2 and AE-3 —
> when that lands, update this file.
## AE-1: Blazor with static server-side rendering, not WebAssembly
**Decision:** Blazor Web App with interactivity mode *None* (pure static SSR).
Target framework .NET 10 (LTS).
**Rationale:** Blazor WASM loads several MB of runtime before the first visible
letter, serves search engines and link previews (WhatsApp, Signal, Messenger — the
main distribution channel among parents) an empty shell, and provides no value on a
pure information site. Static SSR delivers finished HTML, needs no JavaScript, and
costs around 60 MB of RAM in the container.
**Consequence:** Interactivity can be added later per component
(`@rendermode InteractiveServer` on exactly that one component), without changing the
architecture. Never global.
## AE-2: Content as files in the repo, not in a database
**Decision:** Page content as Markdown with YAML frontmatter, events as structured
YAML (`Content/events.yml`), documents as PDF under `wwwroot`.
**Rationale:** No DB backup, no migration schema, no consistency problems between
files and database. Changes are commits and are therefore traceable and reversible.
For a site with ~10 subpages and a few events per year, anything else is overhead.
**Consequence:** Text changes require a commit and a redeploy. With an expected five
changes a year that is acceptable — and the reason the editor expansion
(`tasks/001`) is treated as a separate stage.
## AE-3: Content is baked into the image, not mounted as a volume
**Decision:** `Content/` and `wwwroot/` are part of the image.
**Rationale:** Mounted content from the appdata share could be edited directly on the
NAS, but that is exactly when the repo and the live state drift apart — and the
"backup = `git clone`" property from AE-2 would be worthless.
**Consequence:** No quick fix on the live system. Typos are corrected properly via a
commit. Note: the content is read once at startup and cached — there is **no**
`FileSystemWatcher` (a change needs a restart / `dotnet watch`).
## AE-4: TLS and certificates exclusively in the Nginx Proxy Manager
**Decision:** The container speaks only HTTP on port 8080 and has **no** port mapping
to the outside. NPM terminates TLS and is the only path to the container.
**Rationale:** Matches the pattern already established in the home lab
(`cloud.anticarnist.de`). Certificate management stays in one place.
**Consequence (important):** In `Program.cs`, **no** `UseHttpsRedirection()` and
**no** `UseHsts()` — otherwise a redirect loop behind the proxy. NPM sets HSTS.
`UseForwardedHeaders` with emptied `KnownNetworks`/`KnownProxies` is correct because
the container has no port mapping and is reachable only through NPM.
## Rejected alternatives (for AE-1)
| Alternative | Why not |
|---|---|
| Blazor WASM | Payload, SEO, link previews, no benefit |
| ASP.NET Core MVC/Razor Pages | Works too, but Razor Components are the more modern model |
| Statiq.Web (C# SSG) → nginx | Leanest to operate, but every text change forces a build. Kept as a fallback. |
| Astro/Hugo | More mature SSG ecosystem, but unfamiliar territory |
+5 -26
View File
@@ -187,33 +187,12 @@ Requires two repository secrets (Gitea → repo → **Settings** → **Actions**
- `REGISTRY_TOKEN` — a Gitea access token with **`package: write`**, *not* the
login password.
The workflow currently only builds and pushes — it does **not** run the tests
yet, and it does **not** deploy. Those open steps (tests in the workflow,
auto-deploy with a health gate via Watchtower, encrypting the registry token on
Unraid) are tracked in `tasks/002-deployment-automatisieren.md`.
Two known pitfalls: the `act_runner` needs Docker socket access to build, and it
must offer the `ubuntu-latest` label the workflow asks for. To check the runner:
Gitea → repo (or site admin) → **Settings** → **Actions** → **Runners** — it
should be listed as **online** with a label set that includes `ubuntu-latest`.
### Auto-deploy via Watchtower (open — needs a health gate first)
Automatically rolling out `:latest` the moment it lands in the registry is
tempting, but **not** something to switch on blindly: a build can be green and
still serve a broken page (a bad content file, a runtime-only culture crash like
the de-DE one). Auto-deploy without a gate would push that live **unnoticed**.
So before turning this on, decide the gate:
- The container must prove itself **healthy** before it replaces the running one
— but the chiseled image has no shell, so a `HEALTHCHECK` with `curl`/`sh` does
not work inside it. The check has to come from outside (e.g. an external probe
hitting a known route, or a compose-level check from a sidecar).
- Watchtower must be **label-scoped** to *only* the `eb-web` container, otherwise
it updates the entire home-lab inventory.
- Keep a fast **rollback**: pin `:<sha>` in the Unraid compose and bring it back
up (see "Rollback" above).
Until that gate exists, deployment stays manual on purpose.
### Encrypt the registry token on Unraid (open)
Currently the token sits in plain text in `/root/.docker/config.json`. Set up a
credential helper later, so that the plain-text warning from `docker login`
disappears.
+36
View File
@@ -0,0 +1,36 @@
# Legal — imprint, privacy, photos
Not legal advice — but the points where school sites regularly get flagged. The
actual imprint and privacy texts live in `Content/pages/impressum.md` and
`Content/pages/datenschutz.md`; filling them in with released, real data is tracked
in `tasks/003-echte-inhalte-vor-go-live.md`.
## Imprint (§ 5 DDG)
If the Elternbeirat has no legal form of its own, the operator is listed personally
in the imprint with name and a valid postal address for service. This is a decision,
not a formality — the private address becomes public. Alternative: the school's
address, but only with its explicit consent and if the school is a co-operator.
## Privacy policy
With the move, Tom becomes the controller in the sense of the GDPR. Name the server
logs with IP addresses, define the legal basis and the deletion period.
## No external resources
Google Fonts, Maps, YouTube embeds, and CDN scripts transmit visitors' IPs to third
parties. Fonts are served by ourselves. This is the reason behind the "no external
resources" rule in `CLAUDE.md` — data protection, not taste.
## Photos of children
Only with the consent of the legal guardians — by far the most common mistake on
school sites. When in doubt, no photos of people. Note: the old WordPress backup
contained no own images anyway (only URLs in the database), so any photo used is a
fresh, consciously released one.
## Hosting on a private connection
The public IP of the private connection appears in the DNS of a school site. A
deliberate decision, not a side effect.