#!/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 < "/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."