setup: Ubuntu GNOME-on-Xorg desktop + setup.sh hardening

setup-desktop.sh now installs ubuntu-desktop + gdm3 + x11vnc and forces the
Xorg session (WaylandEnable=false) with auto-login — x11vnc can only mirror an
Xorg :0, not Wayland. vnc-manager.ts resolves the X authority from the GDM
per-session path (/run/user/<uid>/gdm/Xauthority) with a ~/.Xauthority fallback.

setup.sh fixes:
- desktop step gates on `dpkg -s ubuntu-desktop` (was the decommissioned
  officer-vnc service, which never matched so setup-desktop re-ran every time)
- remove Pi (install, --list-models validation, verification check)
- export GOPATH before the cliamp build so `go install` lands where it's checked
  even when Go was already present this run
- write PUBLIC_BUILD_ENV=production and quote all .env values
- guard the interactive .env block behind a TTY check so non-interactive runs
  skip cleanly instead of aborting on read EOF under set -e
- restart systemd-logind only when a key actually changed
- sed prefix-strip instead of `tr -d` (which deletes characters, not a prefix)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-25 15:32:28 +00:00
co-authored by Claude Opus 4.8
parent 5d077a4a54
commit 1e789b4c44
4 changed files with 113 additions and 266 deletions
+17 -6
View File
@@ -11,9 +11,19 @@ const MIRROR_DISPLAY_NUM = 0;
const MIRROR_PORT = 5900;
const READY_TIMEOUT_MS = 5000;
// x11vnc reads :0's cookie from the logged-in user's own .Xauthority, so no root is needed.
// While the greeter owns :0 the cookie lives in lightdm's file instead and mirroring fails.
const XAUTHORITY = join(process.env.HOME ?? '', '.Xauthority');
// x11vnc reads :0's cookie from the logged-in user's X authority, so no root is needed. Where that
// cookie lives depends on the display manager: GDM (Ubuntu GNOME on Xorg) keeps it in the per-session
// dir /run/user/<uid>/gdm/Xauthority; LightDM (or a manual startx) uses the classic ~/.Xauthority. Try
// the GDM path first, then fall back. While the greeter owns :0 the user's cookie doesn't exist yet and
// mirroring fails until someone is logged in.
function resolveXauthority(): string | null {
const uid = typeof process.getuid === 'function' ? process.getuid() : null;
const candidates = [
uid != null ? `/run/user/${uid}/gdm/Xauthority` : null,
join(process.env.HOME ?? '', '.Xauthority'),
].filter((p): p is string => Boolean(p));
return candidates.find((p) => existsSync(p)) ?? null;
}
type MirrorSession = {
email: string;
@@ -99,8 +109,9 @@ export async function startSession(params: VncStartParams): Promise<{ port: numb
const homeDir = getHomeDirForRole(params.email, params.role);
const passwdFile = await ensureVncPassword(homeDir);
if (!existsSync(XAUTHORITY)) {
throw new Error(`No X authority at ${XAUTHORITY} — nobody is logged in on ${MIRROR_DISPLAY}`);
const xauthority = resolveXauthority();
if (!xauthority) {
throw new Error(`No X authority found (GDM or ~/.Xauthority) — nobody is logged in on ${MIRROR_DISPLAY}`);
}
const proc = Bun.spawn({
@@ -109,7 +120,7 @@ export async function startSession(params: VncStartParams): Promise<{ port: numb
'-display',
MIRROR_DISPLAY,
'-auth',
XAUTHORITY,
xauthority,
'-rfbport',
String(MIRROR_PORT),
'-rfbauth',