A prefix-mode container's /112 block index was sha256(username)[0:4], so the
suffix of its address was a global constant for a given name: "alice" got the
same block on every host running this panel. An address therefore identified
its owner to anyone who knew the scheme and could enumerate names — the
opposite of what the random container hostname is for.
The index now lives in users.ipv6_index (schema v19) and is random:
- accounts that predate the column are seeded with the value they have been
using, so no address moves and no container is touched;
- vps add picks a free index and retries internally, with the UNIQUE index on
the column as the backstop, so a name can no longer be refused for a reason
as opaque as "your address is taken" (the old checkIPv6BlockCollision). A
block containing the bridge gateway is skipped the same way.
- deleting an account and recreating the same name yields a fresh random index,
and so does a transfer: the value is per-host and stays out of the metadata.
Add launches a container before creating its account row, so within that window
the block can no longer be looked up by name: WireIPv6 and
ConfigureContainerIPv6 now take the block/address the caller already picked.
Blocks are also only meaningful in prefix mode, which the accessors now say.
Verified on a live prefix-mode host: a user created before the change kept her
address (panel and inside the container), a new user came up on a random block
end to end (guest address, ndppd rule, Incus route), deleting and recreating a
name produced a new random block, and a V4-only host still creates users with
no index at all.
Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
A browser reaching this panel is often a long way away, so the session is now
built to survive the link rather than be torn down by it.
Liveness first, because the old numbers were wrong: pings every 20s with a 60s
deadline. The previous setting was a single 15-minute timer, and since the
browser answers pings automatically it never fired for an idle user — the one
thing it did do was take fifteen minutes to notice a peer that had vanished.
- the shell outlives the socket. When the client goes away the PTY keeps running
for 120s with its output landing in a 256 KiB ring (with a notice if anything
is dropped). A client that comes back with its token resumes the same shell, so
a tunnel, a cell handover or a sleeping laptop no longer costs a running build.
The token travels in the first frame rather than the URL, is bound to the user,
and lives in sessionStorage — reloading resumes, closing the window lets go.
- the page has its own heartbeat: a browser cannot see protocol-level pings, so
the server also sends a small JSON one and the page reconnects with exponential
backoff and jitter after 45s of silence, up to eight attempts.
- a handshake that neither completes nor fails would otherwise stall the retry
loop for good, so an attempt that has not opened in 15s is abandoned.
- at the per-user cap the client is offered its slots back instead of being
refused, which is the only way to reclaim a window the user can no longer see.
Where there is room, the count is just a notice.
Three bugs the browser testing turned up, all of them real:
- Pty writes had no deadline, so a wedged Incus connection blocked the handler
forever — resume hung after any pause long enough to break that socket.
- a chunk whose write failed was neither delivered nor buffered.
- a session stayed resumable between Close and the end of its drain.
An operator can now turn the whole thing off with `vps config set panel.web_ssh
false`: the button is not rendered and the page, the script and the connection
all refuse. It defaults on, including for a config written before the setting
existed.
- add a "User groups" section: name-derived grouping, name validation, the
shared panel password and accent color, and the container switcher
- describe the switcher labels and the per-container sticky-notes tag
- note the accent color is group-wide in the color section
- list multi-container users in both READMEs
Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
Add docs/ipv6-ndp-responder.md: why prefix mode replaced the distro ndppd
daemon with a project-owned responder (some providers drop link-local-source
NAs), the data-plane correctness guarantees, the A/B design hardening, what
changed on dev-ndp, and an 8-point checklist for wide-scale testing. Link it
from docs/README.md.
The panel now talks to Incus over its Unix socket REST API
(/var/lib/incus/unix.socket, group incus-admin) exclusively:
- internal/lx: exec moved from the `lxc` CLI to the API websocket
transport (Exec/ExecSH/RunInitScript); EnsureImage pulls a
remote-qualified fallback (images:debian/13) before Launch since the
API cannot auto-fetch it inside the create call; per-container df
probe in mgr/batch.go uses the same API path.
- cfg: config key renamed lxd.* -> incus.* (image, image_fallback,
pool, bridge, socket); DefaultSocket is the Debian-package path.
- mgr: container disk probe and image handling updated; comment sweep
from LXD to Incus.
- deps: add gorilla/websocket for the exec transport.
Co-authored-by: CommandCodeBot <noreply@commandcode.ai>