Create Ubuntu 20.04 autoinstall ISO

Create Ubuntu 20.04 autoinstall ISO

Step-by-step guide to create the autoinstall ISO for subiquity based Ubuntu Server

Summary

Autoinstallation of an ISO lets us pre-configure the answers to all the configuration questions ahead of time so that the OS installation can happen without user intervention. Ubuntu Server version 18.04 LTS uses the debian-installer (d-i) for the installation process. This includes support for 'preseeding' to create unattended (automated) installations of ubuntu. However with Ubuntu 20.04, the debian installer was replaced by the new 'subiquity server installer'.

This article outlines the steps to follow to create an unattended ISO for Ubuntu 20.04.

Process overview

The three major steps involved in getting an autoinstall ISO for Ubuntu 20.04 are:

  • Download the AMD64 live server ISO from official Ubuntu website
  • Create a 'user-data' file with all the auto-install parameters in place.
  • Modify isolinux/txt.cfg and boot/grub/grub.cfg so that the created ISO will pick the auto-install script during the installation process.

Let's get started with preparing our environment.

Download dependent packages

# Install 7z (optional)
sudo apt-get install p7zip-full

# Install xorriso
sudo apt-get install -y xorriso

# Install isolinux
sudo apt-get install isolinux

Step 1: Download and extract the ISO

The first step is to download the live server ISO from official Ubuntu website releases.ubuntu.com/20.04 eg. releases.ubuntu.com/20.04/ubuntu-20.04.2-li..

After downloading the ISO, we can either choose to extract the ISO using 7z or can mount the ISO and copy the files.

# METHOD 1: USING 7z TO EXTRACT THE ISO
# Download ISO Installer:
wget https://ubuntu.volia.net/ubuntu-releases/20.04.2/ubuntu-20.04.2-live-server-amd64.iso

# Create ISO distribution dirrectory:
mkdir -p iso/nocloud/

# Extract ISO using 7z:
7z x ubuntu-20.04.2-live-server-amd64.iso -x'![BOOT]' -oiso
# METHOD 2: MOUNT THE ISO & COPY THE FILES
# mount the ISO to a local folder
mount -o loop <path to iso> <path to mount it to>

# copy files from mount folder to a new folder
cp -rT <path to mount it to> <new path where modifications are going to happen>

# change folder ownership to root
chown root:root <new path where modifications are going to happen>

Step 2: Create user-data and meta-data files with auto-install data

We will now create a user-data and a meta-data file inside the nocloud folder

# Create empty meta-data file:
touch iso/nocloud/meta-data

# Copy user-data file:
touch iso/nocloud/user-data

When the manual installation is done for Ubuntu 20.04, we can find the cloud-init user-data YAML for this particular configuration in the following file:

/var/log/installer/autoinstall-user-data

We can now use the above file to build the autoinstall script we want to use for our ISO. According to the documentation, this is a minimum viable configuration for the user-data file. We will now add the auto-install data to the 'user-data' file we created in iso/nocloud path:

#cloud-config
autoinstall:
version: 1
identity:
hostname: tpc-e7-08
password: "$6$BzPDXLjjzuA.w21j$STxZ0aB3LbwkBuaPgrdoJBNF1zhFj7duEr0gANKLGVdwGP/wJnDAgPCw1FZGSq9i4GczEt4J4gdsCkdWiH6.Z0"
username: deploy

The password in the sample above is the hash of "password". The above script performs a basic install + apt upgrade of the new system with a disk configuration based on an LVM layout. The complete schema of all the available options for the autoinstall script can be found here: ubuntu.com/server/docs/install/autoinstall-..

The password hash can be calculated the following way in Ubuntu

# generate the password hash
pwhash=$(echo "password" | mkpasswd -s -m sha-512)

We will now go ahead and see how to add more information to the user-data file to add more configurations.

Injecting a public SSH key for the user

ssh:
  allow-pw: yes
  install-server: true
  authorized-keys:
    - ssh-rsa <public_key>
  • allow-pw: specifies whether to allow SSH using password or not.
  • install-server: Whether to install OpenSSH server in the target system.
  • authorized-keys: A list of SSH public keys to install in the initial user’s account

Configure apt during installation

apt:
  geoip: true
  preserve_sources_list: false
  primary:
    - arches: [amd64, i386]
  uri: http://archive.ubuntu.com/ubuntu
    - arches: [default]
  uri: http://ports.ubuntu.com/ubuntu-ports

Early commands

A list of shell commands to invoke as soon as the installer starts, in particular before probing for block and network devices

early-commands:
  - systemctl stop ssh

Packages to install

packages:
  - ntp
  - systemd
  - curl
  - build-essential

Keyboard config

keyboard:
  layout: us
  toggle: ''
  variant: ''

Network configuration

network:
  version: 2
  renderer: networkd
  ethernets:
    eno3: {}
    eno2:
      dhcp4: true
      dhcp-identifier: mac
      nameservers:
        search: <name server search domains>
        addresses: <dns IP>
  bridges:
    admin:
      dhcp4: yes
      interfaces:
        - eno3
    br0:
      dhcp4: yes
      gateway4: xx.xx.xx.xx
      interfaces:
        - eno2

In this example here, we are configuring ethernet ports and creating bridges as part of the auto-install. Other possible network configuration examples can be seen here: netplan.io/examples

To assign IP statically instead of using dhcp, we can provide the config values like this:

network:
  version: 2
  renderer: networkd
  ethernets:
    eno2:
    dhcp4: false
    addresses:
      - yy.yy.yy.yy/24
    gateway4: xx.xx.xx.xx
    nameservers:
      addresses: <DNS IP>
      search: <nameserver search domains>

ntp settings

ntp:
  enabled: true
  ntp_client: chrony
  pools: [0.us.pool.ntp.org, 1.us.pool.ntp.org]
  servers: [ntp.ubuntu.com]

user-data

This section can be used to pass extra user-data to the autoinstall file

user-data:
  disable_root: false
  timezone: America/Toronto
  package_update: true
  package_upgrade: true

late-commands

Shell commands to run after the install has completed successfully and any updates and packages installed, just before the system reboots. In this example, the umount statement is unmounting the /cdrom/ before reboot.

late-commands:
  - service ntp restart
  - echo 'root ALL=(ALL) NOPASSWD:ALL' > /target/etc/sudoers.d/root
  - curtin in-target --target=/target -- echo "Please wait...will reboot automatically"
  - curtin in-target --target=/target -- umount -l -r -f /cdrom/
  - reboot

Interactive sections

If we want, we can keep some sections whose data is provided by the autoinstall script while we can choose to keep some section interactive

interactive-sections:
  - network
  - storage

Storage layout

The following config creates root partition with 4GB and doesn't create swap.

storage:
  layout:
    name: lvm

The following config creates root partition with full space available on disk and also creates swap

storage:
  layout:
    name: direct

To create a custom layout, we can specify the config by looking at the data in /var/log/installer/autoinstall-user-data Here's a sample code snippet:

storage:
  config:
  - grub_device: true
    id: disk-sda
    path: /dev/sda
    ptable: gpt
    type: disk
    wipe: superblock-recursive
  - device: disk-sda
    flag: bios_grub
    id: partition-0
    number: 1
    size: 1048576
    type: partition
  - device: disk-sda
    id: partition-1
    number: 2
    size: -1
    type: partition
    wipe: superblock
  - fstype: ext4
    id: format-0
    type: format
    volume: partition-1
  - device: format-0
    id: mount-0
    path: /
    type: mount

The autofill option for disk config can be specified in two ways:

  • a negative size can be used for the final partition to indicate that the partition should use all the remaining space. eg. size: -1
  • configure a logical volume to fill a volume group with the property eg. size: 100%

Step 3: Update the boot flags with cloud-init autoinstall

Once we have the use-data file updated with all the auto-install data, we can go ahead and update grub.cfg and isolinux/txt.cfg to pick the user-data in nocloud folder for autoinstall

# Update boot flags with cloud-init autoinstall:
## Should look similar to this: initrd=/casper/initrd quiet autoinstall ds=nocloud;s=/cdrom/nocloud/ ---
sed -i 's|---|autoinstall ds=nocloud\\\;s=/cdrom/nocloud/ ---|g' iso/boot/grub/grub.cfg
sed -i 's|---|autoinstall ds=nocloud;s=/cdrom/nocloud/ ---|g' iso/isolinux/txt.cfg

Create the ISO

Now that we have the auto-install script and updated the grub with this information, we can now create the ISO

xorriso -as mkisofs -r -V Ubuntu\ custom\ amd64 -o ubuntu-20.04.2-live-server-amd64-autoinstall.iso -J -l -b isolinux/isolinux.bin -c isolinux/boot.cat -no-emul-boot -boot-load-size 4 -boot-info-table -eltorito-alt-boot -e boot/grub/efi.img -no-emul-boot -isohybrid-gpt-basdat -isohybrid-apm-hfsplus -isohybrid-mbr /usr/lib/ISOLINUX/isohdpfx.bin iso/boot iso