Dissolve plan.md into docs/, move open work to Gitea issues
This commit is contained in:
1 parent
aa7dd4525d
commit
f7fad64bcb
9 files changed
+142
-508
No files matched your search
@@ -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
@@ -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.
|
||||
@@ -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.
|
||||
Reference in new issue
Block a user