Files
2026-09-15 15:24:03 -04:00

121 lines
5.9 KiB
Bash
Executable File

#!/bin/bash
# =====================================================================
# Everything the autoinstall does to a base image, for an image that was
# installed by hand instead.
#
# run INSIDE the Ubuntu guest, as root, during a maintenance boot:
# sudo bash by-hand.sh
#
# WHY THIS EXISTS
#
# user-data builds an image unattended and this file is its late-commands
# written out longhand. An image installed interactively - which is a
# perfectly reasonable way to build one, and how this host's first Ubuntu
# image was made - misses every one of them, and most of the misses are
# silent: the machine works, and something about it is wrong once a
# session is running and nobody is watching.
#
# Idempotent on purpose. It is meant to be run again after the operator
# installs something else, without having to remember what it already did.
# =====================================================================
set -uo pipefail
[ "$(id -u)" -eq 0 ] || { echo "run this with sudo" >&2; exit 1; }
say() { printf '\n== %s\n' "$*"; }
user=$(id -nu 1000 2>/dev/null || echo "")
say "the control channel"
# THE ONE THING WITHOUT WHICH NOTHING WORKS.
# It is how every command reaches this machine. No agent, no session -
# and the failure looks exactly like a machine that did not boot.
apt-get update -qq
apt-get install -y -qq qemu-guest-agent
systemctl enable --now qemu-guest-agent
say "what scripts under test tend to reach for"
apt-get install -y -qq python3 python3-pip python3-venv git curl jq unzip ca-certificates
say "the project share"
# The session mounts this itself over the guest channel, so the fstab
# line is belt and braces - it makes the mount happen on a boot nobody
# is driving, and `nofail` is what keeps a machine with no share from
# dropping into an emergency shell where nothing can reach it.
mkdir -p /mnt/project
grep -q '^project /mnt/project virtiofs' /etc/fstab \
|| printf 'project /mnt/project virtiofs rw,nofail,x-systemd.device-timeout=5 0 0\n' >> /etc/fstab
say "two minutes of every boot"
# MEASURED on this image: systemd-networkd-wait-online 2min 0.072s, with
# graphical.target reached at 2min 3s. The desktop is rendered by
# NetworkManager, so networkd has no links to wait for and sits out its
# entire timeout. The guest agent answers at multi-user, so a CLI session
# never noticed - which is why this stayed invisible until a GUI session
# spent two minutes photographing a black screen.
systemctl mask systemd-networkd-wait-online.service NetworkManager-wait-online.service
say "a desktop with nobody at the keyboard"
if [ -n "$user" ]; then
mkdir -p /etc/gdm3
cat > /etc/gdm3/custom.conf <<EOF
[daemon]
AutomaticLoginEnable=true
AutomaticLogin=$user
EOF
# MEASURED ON 26.04: `WaylandEnable=false` used to be here, with an
# xorg.conf.d file pinning the mode, because the agent clicks at
# absolute coordinates and a screen that changes size makes every
# learned position wrong.
#
# This release has no Xorg to fall back to - xserver-xorg-core is not
# installed and /usr/share/xsessions is empty - so that line does not
# choose X, it leaves GDM with no session to log in to at all, and an
# autologin into nothing looks exactly like a machine that failed to
# boot a desktop.
#
# Wayland costs nothing here: the agent's eyes and hands are QMP
# `screendump` and `input-send-event`, which are the virtual hardware
# and know nothing about the display server. The mode comes from the
# virtio-vga device instead of from a config file, and it is stable
# for the same reason the netdev is - the qemu argv is a constant.
rm -f /etc/X11/xorg.conf.d/10-owuivm-resolution.conf
# Every session IS the first one - the disk is restored from the same
# frozen base every time - so the first-run wizard would open a modal
# dialog over the desktop in front of the agent, every single run.
install -d -o 1000 -g 1000 "/home/$user/.config"
echo yes > "/home/$user/.config/gnome-initial-setup-done"
chown 1000:1000 "/home/$user/.config/gnome-initial-setup-done"
else
echo " no uid 1000, skipping the desktop settings"
fi
say "sources of background change"
# Every session starts from this frozen disk. Anything that updates
# itself in the background makes two runs of the same script differ, and
# the difference shows up as a script that is flaky rather than as a
# machine that changed underneath it.
systemctl disable --now unattended-upgrades.service 2>/dev/null
systemctl disable --now apt-daily.timer apt-daily-upgrade.timer 2>/dev/null
# SNAPD IS HELD, NOT DISABLED, AND THAT IS A DELIBERATE DIFFERENCE.
#
# The autoinstall disables it outright. On a desktop image that also
# takes Firefox away, because Firefox IS a snap here - and a GUI session
# whose agent cannot open a browser has lost something real. What was
# actually wanted is "stop changing underneath me", and holding refreshes
# says exactly that while leaving the snaps that are installed working.
snap refresh --hold >/dev/null 2>&1 && echo " snap refreshes held" \
|| echo " could not hold snap refreshes (old snapd?); check 'snap refresh --hold'"
# A remote desktop server inside the sandbox is a listening socket with
# no route to it and no reason to exist; the operator's console comes
# from QEMU's own VNC.
systemctl disable --now gnome-remote-desktop.service 2>/dev/null
[ -d /etc/cloud ] && touch /etc/cloud/cloud-init.disabled
say "what this machine looks like now"
printf ' guest agent : %s\n' "$(systemctl is-active qemu-guest-agent)"
printf ' autologin : %s\n' "$(grep -h '^AutomaticLogin=' /etc/gdm3/custom.conf 2>/dev/null || echo 'not set')"
printf ' fstab : %s\n' "$(grep -h '^project ' /etc/fstab 2>/dev/null || echo 'no share line')"
printf ' resolver : %s\n' "$(awk '/^nameserver/{printf "%s ", $2}' /etc/resolv.conf)"
printf ' internet : %s\n' "$(curl -sS -o /dev/null -w '%{http_code}' --max-time 10 https://example.com/ 2>&1 || echo unreachable)"
echo
echo "Shut the machine down from Tool Server VM Maintenance, then press Freeze."