Files
felhom.eu/documentation/audits/release-2026-10-30/p3-cloudflare/PLAN.md
T

84 lines
6.9 KiB
Markdown

# D35 / R-1228 — the website and the installer on Cloudflare (plan, 2026-10-30)
Operator ruling D35 = YES (2026-10-23): the static website and the installer are served from Cloudflare like the ISO;
the hub and Gitea stay on DooPlex (R-1228's option B). This plan was written before anything on Cloudflare changed.
## 1. What is served today (read from the source, 2026-10-30)
| Address | Served by | What a box, the ISO or a person fetches |
|---|---|---|
| `felhom.eu`, `www.felhom.eu` (CNAME → the home line, not proxied) | DooPlex: nginx + git-sync of `website/` at `main` (`manifests/webpage.yaml`) | the 19 Hungarian and 11 English pages, `sitemap.xml`, `robots.txt`, `assets/` |
| `felhom.eu/scripts/…` | DooPlex: a second git-sync of `scripts/` at the tag `installer-v1.36.1` | **`/scripts/felhom-host-install.sh`** — the only `/scripts/` file anything fetches: the ISO's first boot (`scripts/iso/felhom-bootstrap.sh:168`, `FELHOM_INSTALL_URL` default) and the hub's day-0 command (`customer_unified.html`). sha256 `e18cdd17…aa1f1` = the tag's |
| `felhom.eu/api/contact` | DooPlex: `contact-mailer` (`manifests/contact-mailer.yaml`, Resend) | the contact form's POST (`kapcsolat.html`, `en/contact.html`) — **the one dynamic part** |
| `stats.felhom.eu` | DooPlex: Umami | the analytics script every page loads (a separate name — unchanged) |
| `iso.felhom.eu` | Cloudflare R2 (proxied CNAME) | the installer ISO — already off DooPlex |
| `hub.felhom.eu`, `gitea.dooplex.hu` | DooPlex | the box's claim and reports; the agent download — **stay on DooPlex (D35)** |
Inside the installer every other address is the hub, Gitea (the agent package) and `download.proxmox.com` — nothing
else from `felhom.eu`.
## 2. Where each goes — Cloudflare Pages, one project `felhom-site`
**Pick: Cloudflare Pages, not R2.** Fewest moving parts: Pages serves clean URLs (`/kapcsolat` → `kapcsolat.html`),
the site's `404.html` with status 404, directory indexes (`/en/`) and compression with no configuration — exactly what
nginx's `try_files $uri $uri.html $uri/` does today. R2 behind a custom domain serves objects by exact key only: no
clean URLs, no index, no 404 page, so it would need a Worker in front — one more piece to own.
- `/` ← `website/` from `origin/main`; `/scripts/` ← `scripts/` from the installer tag named in `manifests/webpage.yaml`
(the same two refs, the same files as DooPlex — 487 files, 25 MB).
- **The contact form cannot be static.** It stays on DooPlex (`contact-mailer`, which already answers CORS for
`https://felhom.eu`). After the switch `felhom.eu/api/contact` would reach Cloudflare, not DooPlex, so the form
needs its own name on DooPlex — e.g. `api.felhom.eu` → the same ingress, and the form's `formEndpoint` pointing there.
That is a DooPlex ingress change and a DNS name: **it waits for the operator's word** (the switch STOP). Without it the
form shows an error on Cloudflare; the page's `mailto:info@felhom.eu` link still works.
- `stats.felhom.eu`, `files.felhom.eu`, `hub.felhom.eu`, `ntfy.felhom.eu`: unchanged.
## 3. How a release reaches it
`scripts/publish-site-cloudflare.sh` (run under `sec run CF_PAGES_TOKEN --`): stages both trees from git, REFUSES when
the staged installer is not the tag's byte for byte, when a must-have page is missing, or when the manifest names more
than one tag; `--dry-run` stages and prints without uploading. The upload is wrangler in a pinned node container; the
token reaches it as an environment variable name only.
- **A website edit:** push to `main` (DooPlex picks it up as before) AND run the script (Cloudflare). After the switch
only the script matters.
- **An installer release:** cut `installer-v<X>`, bump BOTH `--ref`s in `manifests/webpage.yaml` (as today), then run
the script — it reads the tag from the manifest, so the two copies cannot differ.
## 4. What stays on DooPlex
The hub (`hub.felhom.eu`), Gitea and the registry (`gitea.dooplex.hu` — the agent package a new install downloads),
the contact mailer, Umami, FileBrowser, ntfy. **A new install still needs DooPlex for:** the hub's claim and its first
report (`hub.felhom.eu`), and the agent package from Gitea. It no longer needs DooPlex to fetch the installer.
## 5. The switch (only after the operator's yes) and the way back
Switch: in the Pages project add the custom domains `felhom.eu` and `www.felhom.eu`; Cloudflare replaces the two
CNAMEs to the home line with proxied records to `felhom-site.pages.dev`. Way back (7 days, DooPlex's webpage keeps
running): set the two records back to `CNAME dooplex.hopto.org`, not proxied (the values in `dns-before.json`), and
remove the two custom domains from the project. DooPlex's Let's Encrypt certificate for felhom.eu is valid until its
next renewal; a renewal after the switch fails (HTTP-01 reaches Cloudflare), which is the reason the way back is 7 days
and not open-ended.
## 6. Result (2026-10-30, after the plan)
- **Test address:** Pages project `felhom-site` + `preview.felhom.eu` (proxied CNAME). `sitecheck.sh`: 30 of 30 (every
page by its clean URL, the sitemap, robots, an asset, a missing page = 404, the installer's sha256 = the tag's).
Control: the same check against DooPlex's felhom.eu before the switch, 30 of 30.
- **Install proof:** throwaway VM 3461 on demo-hp (scratch NVMe, never the thin pool) from the public ISO 1.30.0
(sha256 = `iso.felhom.eu`'s). Booted with the cable unplugged, `FELHOM_INSTALL_URL` pointed at the test address, then
plugged in: the first boot reached the hub and registered as an unclaimed appliance (pairing mode — the installer is
fetched after a bind, which is a session-only operator act). The installer fetched from the test address (sha256 =
the tag's) passed `--preflight-only` (customer Tester4, read-only `GET /config`). After the switch the same preflight
with the installer fetched from `felhom.eu` passed again. The full install was not run: it needs the operator's bind
and would create Tester4's tunnel and off-site account. VM 3461 destroyed; the hub's unclaimed entry (uuid
`d3529d2d…`) is the operator's to discard.
- **The contact form:** `api.felhom.eu` (DNS to the home line, not proxied; ingress `contact-mailer-api`, only
`/api/contact`, 20 requests a minute per client; certificate issued). Preflight from `https://felhom.eu` 204; one test
message 200, and it arrived at info@felhom.eu (read through the mailbox).
- **Switch:** 10:02:35 UTC (operator yes): `felhom.eu` and `www` → `felhom-site.pages.dev`, proxied; the project's two
custom domains active at 10:03:47 (about 70 s of 522 in between). After: 30 of 30 on both names (one 522 on the first
pass, then 30 of 30 twice), `server: cloudflare`, the installer's sha256 = `installer-v1.36.1`.
- **Undo (until 2026-11-06):** `switch-undo.txt` holds the two record ids; set both back to `CNAME dooplex.hopto.org`,
`proxied: false`, and remove the two custom domains from `felhom-site`. DooPlex's webpage still serves both names.