PLAN: the KVM tab shipped
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
823c72122c
commit
70fcf86283
@@ -320,6 +320,34 @@ something long in one, switch away and back, refresh — it's all still there.
|
|||||||
snapshot, the package is purged and the old cron line is gone, with the 515 snapshots it
|
snapshot, the package is purged and the old cron line is gone, with the 515 snapshots it
|
||||||
had taken left untouched and fully readable here — which is what adopting its naming
|
had taken left untouched and fully readable here — which is what adopting its naming
|
||||||
bought. See `hamlet/docs/hosts.md`.
|
bought. See `hamlet/docs/hosts.md`.
|
||||||
|
- **KVM tab** — **shipped 2026-09-24** (`internal/api/kvm.go`, `KVM.tsx`, `config.KVM`).
|
||||||
|
Paste a device's address; its own web page appears in the tab. **Nothing in it knows
|
||||||
|
anything about any particular KVM** — the first device here is a Sipeed NanoKVM on `grid`
|
||||||
|
at `10.0.0.217`, but none of that is in the code.
|
||||||
|
**Why it proxies rather than framing the device directly.** Ham Lab is served over TLS and
|
||||||
|
a KVM usually is not, so a bare `http://…` iframe is blocked as mixed content before it
|
||||||
|
loads. Serving the device from Ham Lab's own origin (`/kvm/<id>/…`) fixes that, puts it
|
||||||
|
behind **Ham Lab's** login rather than a second one, and makes the iframe same-origin.
|
||||||
|
**The general problem, and the answer.** A device UI asks for its assets by absolute path
|
||||||
|
— `/assets/app.js`, `/api/whatever` — which no path prefix can rewrite away, and rewriting
|
||||||
|
bodies per device would not be general. So the prefix is only the entry point: any request
|
||||||
|
Ham Lab does not serve itself is routed to whichever KVM the asking page belongs to, read
|
||||||
|
from the **Referer**, falling back to a **cookie** when there is none (a WebSocket upgrade).
|
||||||
|
Ham Lab's own routes always match first, so a device can never shadow the console, and the
|
||||||
|
fallback is gated on a live session. The device never receives Ham Lab's session cookie,
|
||||||
|
and its `X-Frame-Options`/CSP/HSTS are dropped on the way out since Ham Lab is the origin
|
||||||
|
now. Redirects to the device's root are rewritten back through the prefix.
|
||||||
|
**One catch found in testing:** Ham Lab's own `frame-ancestors 'none'` forbids framing even
|
||||||
|
from the same origin, so `/kvm/` paths send `'self'` instead. Without that the iframe was
|
||||||
|
refused before it loaded.
|
||||||
|
**The keyboard** needs less than it sounds: a page cannot forward keys into an iframe and
|
||||||
|
does not need to, since one with focus receives them directly. Fullscreen plus
|
||||||
|
`navigator.keyboard.lock()` is what takes Escape, Tab and Ctrl-W back from the browser;
|
||||||
|
that is Chromium-only, and the tab says which version you are getting rather than
|
||||||
|
pretending. The list is **hub-side** on purpose — a KVM earns its keep when the machine is
|
||||||
|
down, which is when that machine's own Ham Lab cannot answer — and a KVM need not belong to
|
||||||
|
a machine Ham Lab manages at all. Not done: holding the device's own login on the hub, so
|
||||||
|
its page does not ask for a second password.
|
||||||
- **Skins** — 13 since 2026-09-19 (`themes.ts`: a palette is one `mk(…)` line): Dark,
|
- **Skins** — 13 since 2026-09-19 (`themes.ts`: a palette is one `mk(…)` line): Dark,
|
||||||
Light, Cyberpunk, Retro, **HUD** (hairlines of cyan on near-black, corner ticks, thin
|
Light, Cyberpunk, Retro, **HUD** (hairlines of cyan on near-black, corner ticks, thin
|
||||||
type), Nord, Gruvbox, Catppuccin + Latte, Dracula, Tokyo Night, Solarized Dark + Light;
|
type), Nord, Gruvbox, Catppuccin + Latte, Dracula, Tokyo Night, Solarized Dark + Light;
|
||||||
@@ -585,39 +613,6 @@ something long in one, switch away and back, refresh — it's all still there.
|
|||||||
`internal/term`); snapshots and clones; and what it does on a host with no virtualisation
|
`internal/term`); snapshots and clones; and what it does on a host with no virtualisation
|
||||||
at all, which is every host here today. Worth scoping as its own plan before starting.
|
at all, which is every host here today. Worth scoping as its own plan before starting.
|
||||||
|
|
||||||
- **A KVM tab** — asked for 2026-09-24, not started. **Dead simple is the brief**: paste one
|
|
||||||
HTTP link, the tab shows whatever UI that KVM serves, and the keyboard goes to it. **This
|
|
||||||
is KVM the keyboard-video-mouse switch, not KVM the hypervisor** — the Virtualization item
|
|
||||||
above means the other one.
|
|
||||||
- **The device**: a **Sipeed NanoKVM** attached to `grid` at **`10.0.0.217`** (MAC
|
|
||||||
`48:da:35:6f:d0:31`), web UI on port 80. The only one so far, so store a list, not one URL.
|
|
||||||
- **Framing works.** Probed 2026-09-24: it sends **no `X-Frame-Options` and no
|
|
||||||
`frame-ancestors`**, so it does not refuse to be embedded. No fallback needed.
|
|
||||||
- **Proxy it through the daemon — that is what makes the simple version possible.** The
|
|
||||||
KVM is **HTTP only**, and Ham Lab is served over TLS, so a bare `http://…` iframe is
|
|
||||||
blocked as mixed content and the "just paste a link" idea dies there. Serving it from Ham
|
|
||||||
Lab's own origin (`/api/kvm/<id>/…`, on the `proxyNode` machinery, which already carries
|
|
||||||
WebSockets — exactly what the video and input streams are) fixes three things at once:
|
|
||||||
no mixed content, the KVM sits behind **Ham Lab's** login rather than a second one
|
|
||||||
(`/api/vm/info` answers 401, so it has its own, which the hub can hold once), and the
|
|
||||||
iframe becomes **same-origin**, which matters for the keyboard below.
|
|
||||||
- **The keyboard needs less than it sounds.** A parent page *cannot* capture keys and
|
|
||||||
forward them into a **cross-origin** iframe — the browser forbids synthetic events across
|
|
||||||
origins — but it does not need to: an iframe with focus receives keystrokes directly, and
|
|
||||||
the NanoKVM's own page already handles them. So "capture and give it to the iframe" is
|
|
||||||
really: click-to-focus, keep focus there, and stop the console's own shortcuts from
|
|
||||||
eating keys while it is focused. What is genuinely worth adding is **fullscreen** plus
|
|
||||||
`navigator.keyboard.lock()`, which is what stops the *browser* swallowing Escape, Tab and
|
|
||||||
Ctrl-W so they reach the guest — note it is Chromium-only, so Firefox and iOS Safari get
|
|
||||||
the plain version. Proxying makes the iframe same-origin anyway, so real forwarding stays
|
|
||||||
available if some KVM ever needs it.
|
|
||||||
- **Where the list lives is the one real design decision.** A KVM earns its keep when the
|
|
||||||
machine is *down* — no OS, a bad kernel, a BIOS screen — which is when that host's Ham Lab
|
|
||||||
cannot answer. So it is **hub-side** and must work for machines running no Ham Lab at all.
|
|
||||||
A `kvm_url` in each node's own `config.json` would fail in exactly the case it is for.
|
|
||||||
- **Do not give it an NPM host** without deciding to: `*.bigham.dev` is a wildcard to the
|
|
||||||
public IP, so that would put BIOS-level control of `grid` on the internet behind only the
|
|
||||||
KVM's own login. Behind Ham Lab's proxy it inherits Ham Lab's auth instead.
|
|
||||||
|
|
||||||
## Open questions
|
## Open questions
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user