Wren Linux

wren-linux / tools / try-install-iso.sh

#!/bin/bash
# Tries an install image from prep/make-install-iso.sh in a throwaway VM, the
# way a real laptop would start it: UEFI firmware, the image plugged in as a USB
# stick, and an empty 20 GB disk. Nothing on this computer is touched; the VM's
# files are in ~/.cache/wren/iso-test (left out of backups).
#
#   bash tools/try-install-iso.sh                 install from the newest built image
#   bash tools/try-install-iso.sh --iso FILE      ...from FILE
#   bash tools/try-install-iso.sh --two-disks     with a second empty disk: the
#                                                 installer should then ask which
#   bash tools/try-install-iso.sh --no-net        no network in the VM: everything
#                                                 the installer asks should still
#                                                 be answerable (this laptop's
#                                                 wifi-only case)
#   bash tools/try-install-iso.sh --boot          start the installed system again
#   bash tools/try-install-iso.sh --clean         delete the VM's files, to start over
#                                                 (no software is removed)
#   bash tools/try-install-iso.sh --remove        remove the packages this script
#                                                 installed (not ones you had, or that
#                                                 other Wren Linux scripts need)
#   bash tools/try-install-iso.sh --print ...     only show the QEMU commands
#
# It needs QEMU (qemu-system-x86, qemu-utils, and qemu-system-gui for its
# window), the UEFI firmware (ovmf) and
# access to /dev/kvm, and offers to install the packages (sudo) if they are
# missing. Run it as your normal user, in your sway session (the VM is a window).
# shellcheck disable=SC2054  # QEMU's options are comma-separated on purpose
. "$(dirname -- "${BASH_SOURCE[0]}")/../lib.sh"
not_root

DIR=${XDG_CACHE_HOME:-$HOME/.cache}/wren/iso-test
OVMF=${WREN_OVMF_DIR:-/usr/share/OVMF}   # (a test points this elsewhere)
CODE=$OVMF/OVMF_CODE_4M.fd
VARS_TEMPLATE=$OVMF/OVMF_VARS_4M.fd
DISK=$DIR/disk.qcow2
DISK2=$DIR/disk2.qcow2
VARS=$DIR/vars.fd

mode=install iso='' two=false print=false no_net=false
while [ $# -gt 0 ]; do
    case $1 in
        --iso) iso=${2:-}; shift 2 ;;
        --two-disks) two=true; shift ;;
        --no-net) no_net=true; shift ;;
        --boot) mode=boot; shift ;;
        --clean) mode=clean; shift ;;
        --remove) mode=remove; shift ;;
        --print) print=true; shift ;;
        -h | --help) sed -n '2,25p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
        *) die "Unknown option $1. Try: bash tools/try-install-iso.sh --help" ;;
    esac
done

if [ "$mode" = clean ]; then
    rm -rf "$DIR"
    note "Deleted $DIR. No software was removed."
    exit 0
fi
if [ "$mode" = remove ]; then
    if ! is_installed "$(apt_marker)"; then
        note "This script installed no packages, so there is nothing to remove."
        exit 0
    fi
    mapfile -t mine < <(marker_depends "$(apt_marker)")
    note "This script installed: ${mine[*]}."
    note "Removed are only those that nothing else needs: QEMU stays if Whonix, claude-vm or"
    note "mockvps-kvm use it, and so does anything you had before."
    ask "Remove them?" n && apt_purge
    exit 0
fi

if [ -z "$iso" ]; then
    iso=$(find "$OFFLINE_DIR/install-iso" -maxdepth 1 -name '*-wren.iso' -printf '%T@ %p\n' 2>/dev/null | sort -rn | head -1 | cut -d' ' -f2-)
    [ -n "$iso" ] || $print || [ "$mode" = boot ] ||
        die "No built image in $OFFLINE_DIR/install-iso. Make one: bash prep/make-install-iso.sh"
fi
[ "$mode" = boot ] || [ -f "$iso" ] || $print || die "No image $iso."

# The machine: KVM, UEFI (its settings in a copy of its own), virtio network,
# in a window (asked for by name: without it QEMU falls back to a hidden VNC
# screen, and the installer waits where you can't see it)
base=(qemu-system-x86_64 -enable-kvm -cpu host -machine q35 -m 2048 -display gtk
    -drive "if=pflash,format=raw,readonly=on,file=$CODE"
    -drive "if=pflash,format=raw,file=$VARS")
# a wired look by default; --no-net takes the network out (a wifi-only laptop)
nic=(-nic user,model=virtio-net-pci)
$no_net && nic=("-nic" "none")
install_cmd=("${base[@]}" "${nic[@]}"
    -drive "if=none,id=disk,file=$DISK" -device virtio-blk-pci,drive=disk,bootindex=1)
$two && install_cmd+=(-drive "if=none,id=disk2,file=$DISK2" -device virtio-blk-pci,drive=disk2)
# the image as a USB stick, read-only, booted first; when the installer
# restarts at the end, QEMU stops instead (else the stick starts it again)
install_cmd+=(-drive "if=none,id=stick,format=raw,readonly=on,file=$iso"
    -device qemu-xhci -device usb-storage,drive=stick,bootindex=0 -no-reboot)
boot_cmd=("${base[@]}" "${nic[@]}" -drive "if=none,id=disk,file=$DISK" -device virtio-blk-pci,drive=disk,bootindex=1)
$two && boot_cmd+=(-drive "if=none,id=disk2,file=$DISK2" -device virtio-blk-pci,drive=disk2)

if $print; then
    [ "$mode" = boot ] || { echo "# the installer:"; printf '%q ' "${install_cmd[@]}" | sed 's/\\,/,/g'; echo; }
    echo "# the installed system:"
    printf '%q ' "${boot_cmd[@]}" | sed 's/\\,/,/g'
    echo
    exit 0
fi

# ---- what it needs ----
need=()
command -v qemu-system-x86_64 >/dev/null || need+=(qemu-system-x86)
command -v qemu-img >/dev/null || need+=(qemu-utils)
# the window: a package of its own, which QEMU only recommends (and other
# Wren Linux VMs, run without a window, don't install)
is_installed qemu-system-gui || need+=(qemu-system-gui)
[ -f "$CODE" ] && [ -f "$VARS_TEMPLATE" ] || need+=(ovmf)
if [ ${#need[@]} -gt 0 ]; then
    note "Missing: ${need[*]}."
    ask "Install them (needs your sudo password)? bash tools/try-install-iso.sh --remove removes them again." y ||
        die "Nothing was started. Install them yourself: sudo apt install ${need[*]}"
    apt_install "${need[@]}"
fi
[ -r /dev/kvm ] && [ -w /dev/kvm ] ||
    die "No access to /dev/kvm. Add yourself to the kvm group (sudo adduser $(id -un) kvm), log out and back in, and run this again. (No KVM in the firmware settings? Turn on VT-x / AMD-V.)"
[ -n "${WAYLAND_DISPLAY:-}${DISPLAY:-}" ] || warn "No graphical session: QEMU's window needs one (run this inside sway)."

mkdir -p "$DIR"
if [ "$mode" = boot ]; then
    [ -s "$DISK" ] || die "Nothing installed yet. Run: bash tools/try-install-iso.sh"
else
    if [ -s "$DISK" ]; then
        ask "A test disk from before is there. Start over with an empty one?" y || die "Kept it. Start the installed system with: bash tools/try-install-iso.sh --boot"
        rm -f "$DISK" "$DISK2"
    fi
    qemu-img create -q -f qcow2 "$DISK" 20G
    $two && qemu-img create -q -f qcow2 "$DISK2" 20G
    # the firmware's settings start fresh with the disk
    cp "$VARS_TEMPLATE" "$VARS"
    chmod u+w "$VARS"
fi
[ -f "$VARS" ] || { cp "$VARS_TEMPLATE" "$VARS"; chmod u+w "$VARS"; }

if [ "$mode" = install ]; then
    say "The installer, from $(basename -- "$iso") as a USB stick"
    cat <<EOF
    What to check in the window:
      1. The menu's first entry, highlighted: "Automatic install with Wren Linux".
         Press Enter.
      2. It asks only for the disk encryption passphrase (twice) and your
         user's password (twice). Use throwaway ones.
EOF
    if $no_net; then
        cat <<'EOF'
      -- With --no-net there are NO network questions at all: no wifi prompt,
         no "network autoconfiguration failed" (the install uses the stick
         alone; wifi.sh asks for the wifi after the install).
EOF
    fi
    if $two; then
        echo "      3. With two disks it should ASK which disk (vda or vdb), never the stick."
    else
        echo "      3. \"Write the changes to disks?\" names /dev/vda (the 20 GB disk), not the stick."
    fi
    cat <<EOF
         If it names anything else, choose No.
      4. "Erasing data" fills the disk with random data: a few minutes.
      5. When it is done it restarts: the window closes by itself, and this
         script goes on to the installed system.
    To stop earlier: close the window, or Ctrl+C here.

EOF
    # It stops by itself when the installer restarts at the end (-no-reboot)
    "${install_cmd[@]}" || die "QEMU stopped before the install was done (an error above, or Ctrl+C). Run this again to start over."
    ask "Start the installed system now?" y || { note "Later: bash tools/try-install-iso.sh --boot"; exit 0; }
fi

say "The installed system"
cat <<'EOF'
    What to check:
      - It asks for the disk passphrase at start, then shows a login.
      - You can log in with your user; sudo true works with your password.
      - Wren Linux is the package, root's, in /usr/share/wren, and not in your home:
        dpkg -s wren-linux says "install ok installed", ls -ld /usr/share/wren
        shows root, and there is no ~/wren.
      - "Set it up now?" at the first login runs /usr/share/wren/start.sh; after it,
        ~/Documents/wren-docs has the docs, and wrenctl --dir says /usr/share/wren.
EOF
if $no_net; then
    cat <<'EOF'
      - It was installed without a network, so also: the setup puts deb.debian.org
        into /etc/apt/sources.list, and `sudo apt-get update` works after the wifi
        is on (bash /usr/share/wren/wifi.sh, with the packages saved in
        ~/.local/share/wren/offline).
EOF
fi
cat <<'EOF'
    Keys: your own sway takes Super (Super+Enter, Super+d...) before the VM
    sees it. Press Ctrl+Alt+G in the window to give the VM the keyboard, and
    again to take it back; or click wren in the VM's bar, then Terminal.
    Close the window to stop it. Start over: bash tools/try-install-iso.sh --clean

EOF
"${boot_cmd[@]}" || warn "QEMU ended with an error (see above)."