utils/vmbox/guest/provision-desktop.sh
Krzysztof kuhy Rudnicki cb3c985ee6 vmbox: 3-4x faster runs, a read-only host package cache, and vm scp
`vm run` took 41s cold / 30s warm to run `true`. Both were vmbox waiting on
itself. Now 12s / 0s, with all five shutdown verdicts re-tested unchanged.

- _run_settle waited up to 30s for the kernel's "reboot: Power down" marker
  UNCONDITIONALLY, including on the majority of runs that leave the guest up,
  where that marker is never going to appear. It now settles only when the
  guest has actually stopped, or when ssh returned 255 (what a successful
  poweroff looks like from the host).

- The ssh readiness polls used a 5s ConnectTimeout. QEMU's user-mode
  networking ACCEPTS the forwarded connection while the guest still has
  nothing on :22, so each probe blocked for the full timeout instead of
  failing fast: a guest reachable at ~10s was reported up at ~45s. Fixed in
  both the sandbox and build paths, via an env override -- ssh honours the
  FIRST value for an option, so a second -o could not have worked.

Making runs fast exposed a race: sshd now answers BEFORE tty1's autologin has
started Xorg, so the X environment could be missing on the first run after a
boot. `VMBOX_X_WAIT=<seconds>` waits for the session; it defaults to 0 so an
ordinary run never pays for a graphical session it is not using.

Build: the host's /var/cache/pacman/pkg is offered to the build guest
read-only (`readonly=on` AND an `ro` mount, because it is the real host cache)
as an extra CacheDir after the guest's own writable one, so a package the host
lacks still downloads rather than failing the build. This removes the 396 MiB
the build used to fetch. Host cache verified byte-for-byte identical across
four builds: same file count, same size, same mtime.

Build time itself moved only 103.8s -> 100.3s. Per-phase timings (also added
here) show why: 47s of it is the build guest's cloud-init first boot, and that
is NOT datasource probing -- `cloud-init analyze blame` totals 2.27s. Left
unexplained rather than guessed at; SESSION_RESULTS.md records the evidence.

Also: `vm scp`, which lib/ssh.sh could always do but the CLI never exposed,
and provision.sh split at the 250-line cap.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdyMBGCrGVGDWtJ9xe48f3
2026-08-22 23:17:21 +02:00

110 lines
4.7 KiB
Bash
Executable File

#!/bin/bash
# ============================================================================
# Runs INSIDE the guest during `vm build`, as root, called by provision.sh.
#
# Everything that gives the sandbox a real graphical session: autologin on
# tty1, startx into i3, a minimal i3 config, and the X-environment discovery
# that lets `vm run` reach that session. Split out of provision.sh to keep
# both files under the 250-line cap.
# ============================================================================
set -euo pipefail
GUEST_USER="${1:-arch}"
# --- X11 autologin + i3 ---------------------------------------------------
# Locker tests need a real X session on tty1. Autologin then startx.
install -d -m 755 /etc/systemd/system/getty@tty1.service.d
cat > /etc/systemd/system/getty@tty1.service.d/autologin.conf <<UNIT
[Service]
ExecStart=
ExecStart=-/sbin/agetty --autologin ${GUEST_USER} --noclear %I \$TERM
UNIT
cat > "/home/$GUEST_USER/.xinitrc" <<'XINIT'
exec i3
XINIT
chown "$GUEST_USER:$GUEST_USER" "/home/$GUEST_USER/.xinitrc"
# startx on tty1 login only, so `vm ssh` sessions do not try to start X.
cat > "/home/$GUEST_USER/.bash_profile" <<'PROFILE'
[[ -f ~/.bashrc ]] && . ~/.bashrc
if [[ -z ${DISPLAY:-} && $(tty) == /dev/tty1 ]]; then
exec startx -- -keeptty >~/.xsession.log 2>&1
fi
PROFILE
chown "$GUEST_USER:$GUEST_USER" "/home/$GUEST_USER/.bash_profile"
# i3 without a config prompts a wizard that blocks the session; ship a minimal one.
install -d -m 755 "/home/$GUEST_USER/.config/i3"
cat > "/home/$GUEST_USER/.config/i3/config" <<'I3'
set $mod Mod4
font pango:monospace 10
bindsym $mod+Return exec xterm
bindsym $mod+d exec dmenu_run
bindsym $mod+Shift+q kill
# Marker window so screenshot tests have something deterministic to match.
exec --no-startup-id xterm -T vmbox-ready -e 'echo VMBOX READY; exec bash'
I3
chown -R "$GUEST_USER:$GUEST_USER" "/home/$GUEST_USER/.config"
# --- X session discovery for non-login commands ---------------------------
# The image ships a real X/i3 session on tty1 so locker and i3 tests can run.
# But `vm run` executes through `sh -c`, which reads no profile, so those
# commands land with no DISPLAY and no XAUTHORITY -- and every X tool then
# fails with "Could not determine i3 socket path" / "cannot open display".
# That looks exactly like "the sandbox has no X", which is wrong and cost a
# real installer run to diagnose.
#
# XAUTHORITY is the awkward half: startx generates /tmp/serverauth.XXXXXXXX
# with a random suffix on every boot, so it cannot be hardcoded. Resolve it
# from the running Xorg process instead, falling back to a glob.
cat > /etc/profile.d/vmbox-x11.sh <<'XENV'
# Point non-login shells at the guest's X session, if one is running.
# Sourced by vmbox's `vm run`; harmless when no X session exists.
if [ -z "${DISPLAY:-}" ]; then
# Wait briefly for the X session rather than reporting "no X".
#
# vmbox now returns control as soon as sshd answers (~11s), which is
# EARLIER than getty@tty1 autologin has run .bash_profile and started
# Xorg. Without this wait the first `vm run` after a boot silently gets
# no DISPLAY and every X tool fails as if the sandbox had no X server --
# the exact failure this file exists to prevent, reintroduced by making
# the rest of the tool faster. Bounded, and skipped entirely once Xorg
# is up, so a guest with no graphical session costs at most this wait.
# Default 0: a plain `vm run` must not pay for a graphical session it is
# not using. Set VMBOX_X_WAIT=<seconds> for X11/i3/locker work.
_vmbox_x_wait="${VMBOX_X_WAIT:-0}"
while [ "$_vmbox_x_wait" -gt 0 ]; do
pgrep -x Xorg >/dev/null 2>&1 && break
sleep 1
_vmbox_x_wait=$((_vmbox_x_wait - 1))
done
unset _vmbox_x_wait
if pgrep -x Xorg >/dev/null 2>&1; then
DISPLAY=":0"
export DISPLAY
fi
fi
if [ -n "${DISPLAY:-}" ] && [ -z "${XAUTHORITY:-}" ]; then
# Prefer the auth file the running Xorg was actually started with.
_vmbox_xauth="$(tr '\0' '\n' < /proc/"$(pgrep -x Xorg | head -1)"/cmdline 2>/dev/null \
| grep -A1 -x -- -auth | tail -1)"
if [ ! -f "${_vmbox_xauth:-}" ]; then
_vmbox_xauth="$(ls -1t /tmp/serverauth.* 2>/dev/null | head -1)"
fi
if [ -f "${_vmbox_xauth:-}" ]; then
XAUTHORITY="$_vmbox_xauth"
export XAUTHORITY
fi
unset _vmbox_xauth
fi
# i3 tools look here when I3SOCK is unset; setting it explicitly avoids a
# second round of X round-trips in tests that shell out repeatedly.
if [ -n "${DISPLAY:-}" ] && [ -z "${I3SOCK:-}" ] && command -v i3 >/dev/null 2>&1; then
I3SOCK="$(i3 --get-socketpath 2>/dev/null || true)"
[ -n "$I3SOCK" ] && export I3SOCK || unset I3SOCK
fi
XENV
chmod 644 /etc/profile.d/vmbox-x11.sh