---
title: "Hands-off Ubuntu installs with autoinstall and PXE"
date: "2024-05-03"
path: "/221-hands-off-ubuntu-installs-with-autoinstall-and-pxe.html"
section: "/ramblings"
description: "Network-booting Ubuntu 24.04 and installing it unattended with a cloud-init autoinstall file served over PXE, plus a note on MAAS for doing it at scale."
---

If you build more than one identical machine, doing the installer by hand gets old fast. Ubuntu's autoinstall takes a YAML answer file and runs the whole installation with no keyboard: partitioning, user, packages, post-install commands, the lot. Pair it with a PXE boot so the machine pulls everything over the network, and a bare box becomes a finished install with one power-on. This is on Ubuntu 24.04.

## The provisioning host

One Linux box hands out the boot files. It needs a web server to serve the ISO and the autoinstall file, DHCP/TFTP for PXE, and `whois` for `mkpasswd` (which you need to hash the install password):

```bash
apt update
apt install nginx-light dnsmasq whois syslinux-common
```

Clear the default nginx page and drop the ISO and your config in the web root. Download the live-server ISO and keep it where nginx can serve it:

```bash
rm /var/www/html/index.nginx-debian.html
wget -P /var/www/html https://releases.ubuntu.com/noble/ubuntu-24.04-live-server-amd64.iso
```

Give the provisioning NIC a static address on the isolated build network. This is one of the few places a private range is the point, as it is a self-contained provisioning LAN:

```bash
ip addr add 192.168.123.1/24 dev eth0
```

## DHCP, TFTP and the boot menu

`dnsmasq` does DHCP and TFTP in one. Put this in `/etc/dnsmasq.d/pxe.conf`:

```text
interface=eth0,lo
bind-interfaces
dhcp-range=eth0,192.168.123.100,192.168.123.200
dhcp-boot=pxelinux.0
enable-tftp
tftp-root=/srv/tftp
```

Restart it:

```bash
/etc/init.d/dnsmasq restart
```

Now the PXE menu. This is where the magic flags live: `autoinstall` switches the installer into unattended mode, and the two URLs tell it where to fetch the answer file and the install source from your web server. Put this in `/srv/tftp/pxelinux.cfg/default`:

```text
DEFAULT install
LABEL install
 KERNEL vmlinuz
 INITRD initrd
 APPEND root=/dev/ram0 ramdisk_size=1500000 ip=dhcp autoinstall cloud-config-url=http://192.168.123.1/autoinstall.yaml url=http://192.168.123.1/ubuntu-24.04-live-server-amd64.iso
```

Copy the kernel, initrd and the PXE loader module into place. Mount the ISO to get the kernel and initrd:

```bash
mount -r /var/www/html/ubuntu-24.04-live-server-amd64.iso /mnt
cp /mnt/casper/{vmlinuz,initrd} /srv/tftp/
cp /usr/lib/syslinux/modules/bios/ldlinux.c32 /srv/tftp/
```

## Generate the password hash

Never put a plaintext password in the answer file, and never reuse a hash from someone else's example, including this one. Generate your own SHA-512 crypt hash:

```bash
mkpasswd --method=SHA-512
```

It prompts for the password and prints a hash that starts with `$6$`. That string goes into the `identity` block below.

## The autoinstall file

This is `/var/www/html/autoinstall.yaml`. It installs the desktop packages, creates one admin user from your generated hash, uses a plain (non-LVM) partition layout, and does a pile of post-install tidy-up. Replace the username, hostname and `password` with your own:

```yaml
#cloud-config
autoinstall:
  version: 1

  packages:
    - ubuntu-desktop

  identity:
    realname: 'Kiosk Admin'
    username: kioskadmin
    password: '$6$REPLACE_WITH_YOUR_MKPASSWD_HASH'
    hostname: kiosk

  storage:
    layout:
      name: direct

  early-commands:
    - echo 'linux-generic-hwe-24.04' > /run/kernel-meta-package

  late-commands:
    # Enable the boot splash
    - >-
      curtin in-target -- sed -i /etc/default/grub -e
      's/GRUB_CMDLINE_LINUX_DEFAULT=".*/GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"/'
    - curtin in-target -- update-grub

    # Let NetworkManager handle networking
    - rm /target/etc/netplan/00-installer-config*yaml
    - >-
      printf "network:\n  version: 2\n  renderer: NetworkManager"
      > /target/etc/netplan/01-network-manager-all.yaml

    # Strip server-only packages not wanted on a desktop/kiosk image
    - >-
      curtin in-target -- apt-get remove -y
      btrfs-progs cryptsetup* lvm2 xfsprogs
      ubuntu-server ubuntu-server-minimal
      byobu finalrd kpartx mdadm needrestart open-iscsi
      sg3-utils ssh-import-id sssd thin-provisioning-tools tmux
      sosreport open-vm-tools motd-news-config landscape-common

    # cloud-init finishes some setup on first boot, so keep it
    - curtin in-target -- apt-get install -y cloud-init
    - curtin in-target -- apt-get autoremove -y

    # Optional: run your own post-install script served from the same host
    - wget -O /target/postinstall.sh http://192.168.123.1/postinstall.sh
    - curtin in-target -- bash /postinstall.sh
    - rm /target/postinstall.sh
```

A machine PXE-booted against this installs itself end to end and reboots into a finished system. The `postinstall.sh` hook is where I do the per-image work, installing the application, applying the lockdown, so the YAML stays about the OS and the script stays about the product.

## At scale: MAAS

PXE plus a YAML file is perfect for a handful of machines. Once you are commissioning racks of them, Canonical's MAAS (Metal as a Service) is the grown-up version: it is PXE, DHCP, image management and an inventory with a web UI and an API, so machines are enlisted, commissioned and deployed on demand.

A single-node install is region + rack controller on one host, backed by PostgreSQL. Create the database (use your own credentials, not these):

```sql
CREATE USER "maas" WITH ENCRYPTED PASSWORD 'CHANGE_ME';
```

```bash
sudo -i -u postgres createdb -O "maas" maasdb
```

Initialise MAAS against it:

```bash
sudo maas init region+rack --database-uri "postgres://maas:CHANGE_ME@localhost/maasdb"
```

Then create the admin account and finish setup from the printed URL:

```bash
sudo maas createadmin
```

```text
http://192.168.123.1:5240/MAAS
```

For a fleet of identical kiosks the autoinstall file is still doing the real work; MAAS just takes over the booting, imaging and tracking so you are not hand-editing a TFTP menu every time.
