#cloud-config
# =====================================================================
# Unattended install for the Ubuntu base image.
#
# Read with vm/images/ubuntu/build.sh, which repacks the stock server ISO
# with `autoinstall` on the kernel command line and hands this file over
# on a second volume labelled CIDATA.
#
# Everything here exists to make the guest behave identically on every
# session. A base image that drifts is a base image that makes a failing
# script look like a failing agent.
# =====================================================================
autoinstall:
  version: 1
  interactive-sections: []

  locale: en_US.UTF-8
  keyboard:
    layout: us

  # STATIC, not DHCP, and this is load-bearing.
  #
  # SLIRP hands out 10.0.2.3 as the DNS server, which is a forwarder
  # inside the qemu process that relays to THIS HOST's resolver. The
  # sandbox's egress filter denies 127.0.0.0/8, so that relay is dead by
  # design - and correctly so, since the host's resolver is a LAN service.
  # A guest that took DHCP would therefore get a nameserver that never
  # answers, and every agent session would open with a confusing "no
  # internet". Pinning a public resolver here makes the guest's DNS a
  # thing the guest owns, which is what the isolation model assumes.
  network:
    version: 2
    ethernets:
      primary:
        match:
          name: "en*"
        addresses: [10.0.2.15/24]
        routes:
          - to: default
            via: 10.0.2.2
        nameservers:
          addresses: [1.1.1.1, 9.9.9.9]

  storage:
    layout:
      name: direct

  identity:
    hostname: owuivm
    realname: VM User
    username: vmuser
    # The password is "vmuser", and it is documented on purpose.
    #
    # There is no network path to this machine - no hostfwd, no sshd, no
    # bridge - so it guards a console nobody but the operator can reach,
    # through a VNC socket that exists only in maintenance mode. It is
    # written down rather than hidden because hiding it would invite
    # treating it as a secret, which it is not.
    #
    # It is still a KNOWN password, and this file is published, so if you
    # ever give this machine a listening socket, change it first:
    #
    #   openssl passwd -6 'your-password'
    #
    # and replace the hash below, before running build.sh.
    password: "$6$owuivm$eqPoBTdzXEA8WHYyXtQOlQfw4uOYGw2N4vC.Hl9KVdKOFuKQIEc0zvyVmy6t5XgVLhKr5Q9sdaWLOWWjBCaRZ1"

  # No sshd. The agent reaches this machine over virtio-serial, which
  # needs no listening socket and keeps working when a script breaks the
  # guest's networking - which is exactly when the agent needs it most.
  ssh:
    install-server: false

  packages:
    - qemu-guest-agent
    - ubuntu-desktop-minimal
    - python3
    - python3-pip
    - python3-venv
    - git
    - curl
    - ca-certificates
    - jq
    - unzip

  # Updates during a session would make two runs of the same script
  # differ for reasons that have nothing to do with the script.
  package_update: false
  package_upgrade: false

  late-commands:
    # The guest agent is the whole control channel. Without it the
    # machine boots and is unreachable, which looks identical to a
    # machine that failed to boot.
    - curtin in-target -- systemctl enable qemu-guest-agent.service

    # Debian and its derivatives have historically shipped
    # /etc/default/qemu-guest-agent restricting guest-exec and
    # guest-file-*. Those two ARE the control channel here, so the file
    # is written explicitly rather than left to the package default.
    - |
      cat > /target/etc/default/qemu-guest-agent <<'EOF'
      # BLACKLIST_RPC is deliberately empty: guest-exec and guest-file-*
      # are how this machine is driven. There is no network path in, so
      # the RPC surface is reachable only from the host process that owns
      # the VM.
      DAEMON_ARGS=""
      EOF

    # GUI mode drives whatever is on the screen, so something has to be
    # on it. Auto-login means a session exists from boot with no human,
    # and it is the same arrangement the Windows image uses so both
    # behave the same way.
    - |
      mkdir -p /target/etc/gdm3
      cat > /target/etc/gdm3/custom.conf <<'EOF'
      [daemon]
      AutomaticLoginEnable=true
      AutomaticLogin=vmuser
      EOF

    # NO `WaylandEnable=false`, AND NO xorg.conf, ON PURPOSE.
    #
    # Both were here. The reasoning was sound - the agent clicks at
    # absolute coordinates, so the screen must be the same size every
    # session - and the mechanism stopped existing: 26.04 ships no Xorg
    # (xserver-xorg-core is not installed, /usr/share/xsessions is
    # empty), so turning Wayland off leaves GDM with nothing to log in
    # to. Measured on this host's image before it was frozen.
    #
    # The size is fixed anyway, one layer down: it comes from the
    # virtio-vga device in a qemu argv that is a constant, not from a
    # display server`s config file.

    # NETPLAN IS WRITTEN HERE, BY HAND, AND NOT LEFT TO THE INSTALLER.
    #
    # Subiquity does not write /etc/netplan itself - it hands the network
    # config to cloud-init, which renders it on FIRST BOOT. This image
    # disables cloud-init a few lines below (it must not re-run on every
    # boot of a disk restored from a frozen base thousands of times), so
    # the netplan was never written at all and the guest silently fell
    # back to DHCP.
    #
    # That is not a cosmetic difference. SLIRP's DHCP hands out 10.0.2.3
    # as the nameserver, which is a forwarder inside the qemu process
    # that relays to THIS HOST's resolver - and the sandbox's egress
    # filter denies 127.0.0.0/8, correctly, because the host's resolver
    # is a LAN service. Measured: the guest came up with working TCP to
    # 1.1.1.1 and no name resolution whatsoever, which reads as "the
    # internet is broken" rather than "DNS is misconfigured".
    - |
      cat > /target/etc/netplan/00-owuivm.yaml <<'EOF'
      network:
        version: 2
        ethernets:
          primary:
            match:
              name: "en*"
            dhcp4: false
            dhcp6: false
            addresses: [10.0.2.15/24]
            routes:
              - to: default
                via: 10.0.2.2
            nameservers:
              addresses: [1.1.1.1, 9.9.9.9]
      EOF
      chmod 0600 /target/etc/netplan/00-owuivm.yaml

    # The project share. `nofail` because a session may legitimately run
    # without one, and a missing share must not drop the machine into an
    # emergency shell where nothing can reach it.
    - |
      mkdir -p /target/mnt/project
      printf 'project /mnt/project virtiofs rw,nofail,x-systemd.device-timeout=5 0 0\n' >> /target/etc/fstab

    # MEASURED: this one service cost two minutes of every boot.
    #
    # `systemd-analyze blame` on the first build: systemd-networkd-wait-online
    # 2min 0.072s, and graphical.target reached after 2min 3s. The desktop is
    # rendered by NetworkManager here (netplan's default renderer for a
    # desktop install), so networkd has no links to wait for and sits out its
    # full timeout before failing. The guest agent answers at multi-user, so a
    # CLI session was unaffected and fast - which is exactly why this was
    # invisible until a GUI session took a screenshot of a black screen for
    # two minutes.
    - curtin in-target -- systemctl mask systemd-networkd-wait-online.service
    - curtin in-target -- systemctl mask NetworkManager-wait-online.service

    # The first-run wizard opens a modal dialog over the desktop on every
    # session, because every session is the first one - the disk is restored
    # from the same frozen base every time. An agent driving the screen would
    # have to dismiss it before it could do anything, every single time.
    - |
      mkdir -p /target/home/vmuser/.config
      echo yes > /target/home/vmuser/.config/gnome-initial-setup-done
      chown -R 1000:1000 /target/home/vmuser/.config

    # A remote desktop server inside the sandbox is a listening socket with
    # no way to reach it and no reason to exist. The operator's console comes
    # from QEMU's own VNC in maintenance mode, not from inside the guest.
    - curtin in-target -- systemctl disable gnome-remote-desktop.service || true

    # Three sources of background change, all of which would make two
    # runs of the same script differ.
    - curtin in-target -- systemctl disable unattended-upgrades.service || true
    - curtin in-target -- systemctl disable apt-daily.timer apt-daily-upgrade.timer || true
    - curtin in-target -- systemctl disable snapd.service snapd.socket snapd.seeded.service || true

    # cloud-init has done its job; leaving it enabled means it re-runs on
    # every boot of a disk that is restored from the same frozen base
    # thousands of times.
    - touch /target/etc/cloud/cloud-init.disabled
