setup-desktop: configure the headless display

x11vnc mirrors :0, but with no monitor attached the connector has no EDID and no
CRTC, so GNOME renders nothing and the remote desktop is black. Reproduced the
hard way on this box: the desktop only worked because the session had started
while a screen was plugged in, and survived exactly until the next restart.

The step captures a connected monitor's EDID, installs it as
drm.edid_firmware with video=<connector>:1920x1080e so the connector reports
permanently attached, and adds a login-time hook to raise the resolution — the
replayed EDID's *preferred* mode is the captured panel's native one, which can be
tiny, and GNOME picks preferred. monitors.xml is the documented override but its
monitor matching did not take.

The EDID can only be captured from a screen that is plugged in, so a headless run
skips with instructions rather than pretending to succeed. Re-running is safe:
GRUB is left alone once the argument is present, and the mode setter no-ops when
the mode is already right.

officer-set-display.sh discovers the output and the largest mode within a cap
rather than hardcoding either, since setup runs before X exists and cannot know
them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
brunorezio
2026-07-26 06:02:10 +01:00
co-authored by Claude Opus 5
parent f0a0166ecd
commit 592cc72f85
2 changed files with 108 additions and 5 deletions
+38
View File
@@ -0,0 +1,38 @@
#!/usr/bin/env bash
# Pick the largest sane mode on whichever output is connected, at session start.
#
# With no monitor attached the connector's EDID comes from drm.edid_firmware (see the kernel command
# line). That EDID advertises plenty of modes but its *preferred* one is the captured screen's native
# resolution, which may be tiny — GNOME picks preferred, so an 800x480 panel yields an 800x480
# desktop. monitors.xml is the documented override but its monitor matching proved unreliable here,
# so set the mode directly. No-ops when the mode is already right, so it is safe to run repeatedly.
set -eu
MAX_WIDTH="${OFFICER_MAX_WIDTH:-1920}"
# The session's X server may not be up yet when autostart fires.
for _ in $(seq 1 20); do
xrandr --query >/dev/null 2>&1 && break
sleep 0.5
done
xrandr --query >/dev/null 2>&1 || exit 0
output=$(xrandr --query | awk '/ connected/ { print $1; exit }')
[ -n "$output" ] || exit 0
# Modes are listed under the output but are not reliably sorted, so pick the widest that fits.
mode=$(
xrandr --query \
| sed -n "/^${output} connected/,/^[^ ]/p" \
| awk '/^[[:space:]]+[0-9]+x[0-9]+/ { print $1 }' \
| awk -F x -v max="$MAX_WIDTH" '
{ w = $1 + 0; h = $2 + 0 }
w <= max && (w > bw || (w == bw && h > bh)) { bw = w; bh = h; best = $0 }
END { if (best) print best }'
)
[ -n "$mode" ] || exit 0
current=$(xrandr --query | awk -v o="$output" '$1 == o { print $3 }' | cut -d+ -f1)
[ "$current" = "$mode" ] && exit 0
xrandr --output "$output" --mode "$mode" || true
+70 -5
View File
@@ -15,7 +15,7 @@ echo ""
DESKTOP_USER="$(whoami)" DESKTOP_USER="$(whoami)"
# --- Step 1: Install GNOME desktop + GDM + x11vnc --- # --- Step 1: Install GNOME desktop + GDM + x11vnc ---
echo "[1/5] Installing ubuntu-desktop, GDM, x11vnc..." echo "[1/6] Installing ubuntu-desktop, GDM, x11vnc..."
sudo apt update -qq sudo apt update -qq
sudo DEBIAN_FRONTEND=noninteractive apt install -y -qq \ sudo DEBIAN_FRONTEND=noninteractive apt install -y -qq \
ubuntu-desktop \ ubuntu-desktop \
@@ -29,7 +29,7 @@ echo " Done."
# created and the desktop could not authenticate. # created and the desktop could not authenticate.
# --- Step 2: Force GDM onto Xorg + enable auto-login (x11vnc cannot mirror Wayland) --- # --- Step 2: Force GDM onto Xorg + enable auto-login (x11vnc cannot mirror Wayland) ---
echo "[2/5] Forcing Xorg session and auto-login in GDM..." echo "[2/6] Forcing Xorg session and auto-login in GDM..."
GDM_CONF=/etc/gdm3/custom.conf GDM_CONF=/etc/gdm3/custom.conf
sudo mkdir -p /etc/gdm3 sudo mkdir -p /etc/gdm3
[ -f "$GDM_CONF" ] || echo "[daemon]" | sudo tee "$GDM_CONF" > /dev/null [ -f "$GDM_CONF" ] || echo "[daemon]" | sudo tee "$GDM_CONF" > /dev/null
@@ -52,7 +52,7 @@ set_gdm_key AutomaticLogin "$DESKTOP_USER"
echo " Xorg forced (WaylandEnable=false), auto-login as $DESKTOP_USER." echo " Xorg forced (WaylandEnable=false), auto-login as $DESKTOP_USER."
# --- Step 3: Make GDM the default display manager --- # --- Step 3: Make GDM the default display manager ---
echo "[3/5] Setting GDM as the default display manager..." echo "[3/6] Setting GDM as the default display manager..."
echo "/usr/sbin/gdm3" | sudo tee /etc/X11/default-display-manager > /dev/null echo "/usr/sbin/gdm3" | sudo tee /etc/X11/default-display-manager > /dev/null
sudo systemctl enable gdm3 >/dev/null 2>&1 || sudo systemctl enable gdm >/dev/null 2>&1 || true sudo systemctl enable gdm3 >/dev/null 2>&1 || sudo systemctl enable gdm >/dev/null 2>&1 || true
sudo systemctl set-default graphical.target >/dev/null 2>&1 || true sudo systemctl set-default graphical.target >/dev/null 2>&1 || true
@@ -61,7 +61,7 @@ sudo systemctl disable lightdm >/dev/null 2>&1 || true
echo " Done (takes effect on next reboot)." echo " Done (takes effect on next reboot)."
# --- Step 4: Install Brave browser (native .deb, not snap) --- # --- Step 4: Install Brave browser (native .deb, not snap) ---
echo "[4/5] Installing Brave browser..." echo "[4/6] Installing Brave browser..."
if ! command -v brave-browser-stable > /dev/null 2>&1; then if ! command -v brave-browser-stable > /dev/null 2>&1; then
sudo curl -fsSLo /usr/share/keyrings/brave-browser-archive-keyring.gpg \ sudo curl -fsSLo /usr/share/keyrings/brave-browser-archive-keyring.gpg \
https://brave-browser-apt-release.s3.brave.com/brave-browser-archive-keyring.gpg https://brave-browser-apt-release.s3.brave.com/brave-browser-archive-keyring.gpg
@@ -81,7 +81,7 @@ echo '--password-store=basic' | sudo tee /etc/brave/brave-flags.conf > /dev/null
echo " Done." echo " Done."
# --- Step 5: Remove GNOME Keyring + set default browser (prevents password prompts on login) --- # --- Step 5: Remove GNOME Keyring + set default browser (prevents password prompts on login) ---
echo "[5/5] Removing GNOME Keyring and setting default browser..." echo "[5/6] Removing GNOME Keyring and setting default browser..."
sudo apt remove -y --purge gnome-keyring > /dev/null 2>&1 || true sudo apt remove -y --purge gnome-keyring > /dev/null 2>&1 || true
rm -rf ~/.local/share/keyrings rm -rf ~/.local/share/keyrings
if command -v brave-browser-stable > /dev/null 2>&1; then if command -v brave-browser-stable > /dev/null 2>&1; then
@@ -91,6 +91,71 @@ else
echo " No supported browser found, skipping default." echo " No supported browser found, skipping default."
fi fi
# --- Step 6: Headless display (keep a desktop when no monitor is attached) ---
echo "[6/6] Configuring the headless display..."
# x11vnc mirrors :0, but with nothing plugged in the connector has no EDID and no CRTC, so GNOME
# renders nothing and the mirror is black. Replaying a real monitor's EDID makes the connector look
# permanently attached. The EDID has to be captured from a screen that is plugged in *now* — there is
# nothing to copy otherwise — so this step is skipped on a headless run and can be re-run later.
EDID_DIR=/lib/firmware/edid
EDID_FILE="$EDID_DIR/officer-screen.bin"
CONNECTED_SYSFS=""
for c in /sys/class/drm/card*-*/status; do
[ "$(cat "$c" 2>/dev/null)" = "connected" ] || continue
CONNECTED_SYSFS="$(basename "$(dirname "$c")")"
break
done
if [ -z "$CONNECTED_SYSFS" ] && [ ! -f "$EDID_FILE" ]; then
echo " ! No display connected and no EDID saved — skipping."
echo " Plug a monitor in and re-run this script to capture one, otherwise the remote"
echo " desktop will be black whenever nothing is attached."
else
# cardN-HDMI-A-1 -> HDMI-A-1, which is the name the kernel parameters use (card numbering can
# change between boots; the connector name does not).
if [ -n "$CONNECTED_SYSFS" ]; then
CONNECTOR="${CONNECTED_SYSFS#*-}"
sudo mkdir -p "$EDID_DIR"
sudo cp "/sys/class/drm/$CONNECTED_SYSFS/edid" "$EDID_FILE"
echo " Captured EDID from $CONNECTOR ($(stat -c%s "$EDID_FILE") bytes)."
echo "$CONNECTOR" | sudo tee "$EDID_DIR/officer-connector" > /dev/null
else
CONNECTOR="$(cat "$EDID_DIR/officer-connector" 2>/dev/null || echo HDMI-A-1)"
echo " Reusing the saved EDID for $CONNECTOR."
fi
# drm.edid_firmware makes the connector report that EDID; the trailing "e" on video= forces it
# enabled with nothing attached. amdgpu is not in the initramfs, so /lib/firmware is readable by
# the time the driver loads and no initramfs rebuild is needed.
KERNEL_ARGS="drm.edid_firmware=$CONNECTOR:edid/officer-screen.bin video=$CONNECTOR:1920x1080e"
if grep -q "drm.edid_firmware=" /etc/default/grub; then
echo " GRUB already carries a forced EDID — leaving it alone."
else
sudo cp /etc/default/grub "/etc/default/grub.bak-$(date +%Y%m%d%H%M%S)"
CURRENT=$(grep "^GRUB_CMDLINE_LINUX_DEFAULT=" /etc/default/grub | sed 's/^[^"]*"//; s/"$//')
sudo sed -i "s|^GRUB_CMDLINE_LINUX_DEFAULT=.*|GRUB_CMDLINE_LINUX_DEFAULT=\"${CURRENT:+$CURRENT }$KERNEL_ARGS\"|" /etc/default/grub
sudo update-grub > /dev/null 2>&1
echo " Added the forced EDID to the kernel command line (takes effect on reboot)."
fi
# The EDID's preferred mode is whatever the captured panel was, which may be small. GNOME picks
# preferred, so raise it at session start.
install -d "$HOME/.local/bin" "$HOME/.config/autostart"
install -m 755 "$(dirname "$0")/officer-set-display.sh" "$HOME/.local/bin/officer-set-display.sh"
cat > "$HOME/.config/autostart/officer-set-display.desktop" <<AUTOSTART
[Desktop Entry]
Type=Application
Name=Officer display mode
Comment=Raise the headless display to its largest usable mode at login
Exec=$HOME/.local/bin/officer-set-display.sh
X-GNOME-Autostart-enabled=true
NoDisplay=true
AUTOSTART
echo " Installed the login-time mode setter."
fi
echo " Done."
# --- Cleanup old systemd service if it exists --- # --- Cleanup old systemd service if it exists ---
if systemctl list-unit-files officer-vnc.service &>/dev/null; then if systemctl list-unit-files officer-vnc.service &>/dev/null; then
echo "" echo ""