Automated Installation of Ubuntu 18.04

When you try new things, you break old things. In order to avoid the fear of losing something important I figured out how to automate the reinstallation of my laptop.

Target Audience

This shall be a detailed guide to preseeding Ubuntu 18.04 Desktop and automatically configuring some software. You will heavily use the terminal while following in my footprints. Apart from executing bash commands, not programming knowledge is required.

If you don't have already a device running linux, I recommend doing as I did: Set up a normal installation USB stick with the official Ubuntu 18.04 LTS Desktop ISO. Because we will be modifying the bootable ISO, and I did not manage to do that correctly from Windows using balenaEtcher or Cygwin.

I have previously used linux on my raspberry pi and on compute clusters, and cygwin on windows, but am kinda new to ubuntu.

Goal

I'd like to be able to plug the USB Stick into my laptop, walk away, and when I return 2 hours later my ubuntu installation should be restored to a clean but perfectly usable state. My dual-boot windows partitions should remain unaffected. All programs already configured so that I don't need to reconfigure anything manually.

You could just make a disk image. But that requires way more storage space and is a bit harder to keep clean yet up-to-date regarding which programs it configures.

Since almost all of the documentation is either plainly wrong, outdated, or inexistent, here is a detailed guide.

Structure

Every Section will outline how you do something and possibly how you can automate that. I will provide relevant links often, but you should hopefully rarely need to consult them.

Installing Linux

If you have a windows partition, it's worth checking whether bitlocker is on. If it is, I recommend having a look at Chris Xiao's Guide (Last Updated April 2019). He also recommends to disable Secure Boot from the UEFI BIOS settings.

Even if you choose a distro that supports Secure Boot, it can potentially cause issues, especially when you’re installing extra drivers or manually upgrading kernel. Therefore, you may want to disable Secure Boot to make your life easier.

I won't cover flashing the ISO to a usb stick in detail. Just download the ISO, flash it on the stick using e.g. balena etcher (this will of course wipe any preexisting data on the stick) and boot your laptop from the stick.

Something Went Terribly Wrong (Optional)

If you only get a black screen telling you that something has gone seriously wrong, it may be because you aborted a previous installation after checking the box about using proprietary drivers. In that case, the installer remembers that it wants to start off a different file on the stick, which has not yet been created, and is hence slightly broken now. I followed this answer to resolve that problem:

Now when I try to run the installer from my bootable USB I get the following error


I booted an Ubuntu Live USB stick, went intomy hard disk drive's /boot/efi folder and renamed the file grubx64.efi to mmx64.efi

Reboot the machine and it should work.

Partitioning Manually

Choose something else as the installation type. That gives you more control and is safer than choosing to install alongside windows.

There are different reasonable choices that you can make for splitting your linux installation into partitions. One possibility I did not explore was using a separate home partition to make upgrading easier or to use at the same time in different systems. What I am using is this:

Preseeding

Partitioning Automatically

Preseeding means adding commands onto the stick so that the installer will have to ask less questions. However, this is very limited in regards to partitioning since the underlying tool partman only allows you to either wipe the whole hard disk (including the windows partition) or to only use the free space (so you would have to manually free the previously used partitions for the reinstall).

This 2017 thread claims to have a solution, but I have not tested it. Similarly, this blog post outlines how to customize the partitioning within those constraints.

I hope my directions will be enough, but if not here are some links. Skip this paragraph until you need it. You can consult the official ubuntu documentation on preseeding and on customizing the LiveCD and a self-proclaimed beginner outlines hist process here. There is also a Step-By-Step guide for ubuntu 16.04 and the Ubuntu Help example-preseed.txt (which is specifically linked to as "for bionic" even though the file itself says it's for debian stretch.) and a third-pary preseed file that uses ubiquity. This QA contains valuable information on creating a customized ISO, as does this article and this guide. You could also consider running Ubuntu in qemu for testing purposes. This answer explains how you should specify the file path

Modifying the ISO

The Ubuntu Help claims

B.2.1. Loading the preconfiguration file

If you are using initrd preseeding, you only have to make sure a file named preseed.cfg is included in the root directory of the initrd. The installer will automatically check if this file is present and load it.

For the other preseeding methods you need to tell the installer what file to use when you boot it. This is normally done by editing the bootloader configuration file (e.g. parmfile.ubuntu) and adding the parameter to the end (however, the file is empty by default).

That documentation is pretty useless. There is no parmfile.ubuntu and while placing a preseed.cfg in the initrd (more on what "initrd" even is later) may work but is harder than necessary.

You have the option to get the preseed config file from the network, but that will bring its own issues: You'd have to specify some settings manually because they happen to be asked before the network is loaded (Or perhaps you can specify them in the boot arguments).

So here's what we do instead:

We write our preseed commands into a file that you name however you want. I named mine my.seed. We will unpack the official ubuntu iso, modify the contents, and repack it. I will explain this later in more detail, but the modified iso shall contain our preseed file, and information on where to find it.

The following commands are to be executed on any linux system. It must not necessarily be the machine you will in the end automatically install ubuntu on.

If something is unclear to you, perhaps I phrased it better in my answer on how to preseed wirieless authentication.

Place your preseed file somewhere within ~/Downloads/iso.new. You could choose any place. I created the directory iso.new/preseed and placed it there as iso.new/preseed/my.seed. Keep it as an empty file for now. We will iteratively work on it, so that you have an easier time debugging in the case of something going wrong.

Boot Arguments

There is a shitload of documentation around on how to tell the ubuntu installer where to check for your preseed file. Most of those don't work. Some suggest to use iso.new/isolinux/txt.cfg, which does indeed work. It is in the end included in iso.new/isolinux/isolinux.cfg, so I specified my boot arguments directly there.

Modify isolinux.cfg so that it looks like this:

The line label install declares a new block which is by default selected due to the earlier line default install. Anything that comes after menu label ^ is what will be displayed as the name. I use a version counter here so that I can see early whether I'm using the version of my iso that I thought I am using. But of course you will need to manually update that number.

The line append specifies the arguments that actually interest us. First of all, you need to specify auto. That enables the usage of a preseed file, which you specify with a leading file=/cdrom because your usb stick will actually be mounted as /cdrom.

There are several features of the Ubuntu Installer that combine to allow fairly simple command lines at the boot prompt to result in arbitrarily complex customized automatic installs.

This is enabled by using the Automated install boot choice, also called auto for some architectures or boot methods. In this section, auto is thus not a parameter, it means selecting that boot choice, and appending the following boot parameters on the boot prompt.

Source

boot=casper initrd=/casper/initrd is always there.

debug-ubiquity DEBCONF_DEBUG=5 increases the verbosity of the installer. That is useful for finding out why it's not working as expected. The log file can be found during the installation at /var/log/installer/debug.

And automatic-ubiquity is again a must.

automatic-ubiquity: Pass --automatic to ubiquity which enables automatic mode. Questions that have been marked seen will be skipped and pages of the interface that ask no questions (because they are all skipped) will not be shown. Implies only-ubiquity (see below).

Source

Installer Background Image

To see whether our modifications have any effect, let us modify the background image that is shown during the installation (but not on the installed system). The idea comes from here. I use the normal background turned blue - you can grab it here.

Store it in iso.new/preseed/warty-final-ubuntu-blue.png (or wherever you like).

To get it to display, it needs to be placed where it will be loaded from, before the installer actually is fully ready. To that end, we add this command to iso.new/preseed/my.seed:

Note that I prefixed both paths with /root. That is because the preseed/early_command is actually executed by casper (Source). In order to understand what that means, here's a short overview of filesystems involved in the installation process.

There are multiple filesystems.

  1. The filesystem you are running all these commands in
  2. The iso we mounted on /mnt/myiso and copied the contents of over to ~/Downloads/iso.new. It contains some information for the bootloader.
  3. A ~/Downloads/iso.new/casper/initrd file which contains an initial ram filesystem that will be booted into once you select "Install Ubuntu" in grub.
  4. A ~/Downloads/iso.new/casper/filesystem.squashfs file which contains a compressed filesystem. This filesystem will be chrooted into after some initial setup done in the initramfs. Once that is up, it's possible to log into a tty by pressing CtrlAltF2 (or F3) and enter ubuntu as the username. The squashfs contains scripts for ubiquity - the installer.

Source: My Answer

I advise you to keep the whole command on one line. It's technically allowed to have newlines in there, but not everywhere.

Write The Modified ISO To The Stick

You might have to install isohybrid and mkisofs. I'm not sure anymore but I think only the latter was preinstalled.

I do not understand what all of these flags do. Feel free to check out the mkisofs man page to learn more about them before reaching the conclusion that you don't care as long as it works™.

If you want to, you can test whether the iso is bootable in qemu. However, I did not really use this as I tested it on another laptop instead.

To test it on a real laptop, starting from the stick, you will need to write it to that stick first of course. dd is not forgiving when you do mistakes, so make sure you're writing to the correct device and that it is not mounted and that it is actually plugged in.

If you accidentally run this command before plugging the stick in, the unmount part will fail. If you run the dd command without the stick plugged in, it will create a file /dev/sda that you'll have to delete before being able to mount the stick again.

conv=fdatasync makes sure that the command only returns once it is done. This command will take a few minutes!

Boot From Stick

There are enough guides in the web for this. Plug the stick into your device, select it as boot device in bios, and then it should show you several options. One of them being titled the way you named it - and that should be autoselected anyway.

If you'd like to experiment with boot parameters, you can do that here as well.

Once your installer has started, the background image should be blue and it will ask you some questions which we will get rid of at a later point.

Isolinux.cfg Additions

UbiquityAutomation implies that you need to specify languages differently than with debconf d-i. That site was updated in 2019:

Furthermore, there are a few other components necessary for an automated installation with ubiquity:

This gist implies that even with ubiquity, it's still possible to set up the locale using the d-i commands... but it also specifies everything as boot arguments. I read in some official docs that you cannot have more than 8 arguments there, but I guess that no longer applies.

I set the installer to use a swiss german keyboard layout but the English language. Somehow, with this configuration, I ended up with some german logging messages, but I didn't mind because I change those settings in the final install anyway.

You can modify your iso.new/isolinux/isolinux.cfg to match your preferences. I advise you to recreate the stick and test whether it works as you're expecting it to every time you perform a few modifications.

Preseed Commands

Again, I advise you to iteratively add a few commands to test if they work instead of just dumping everything in at once - at least if you're doing something differently than me.

Background

We've been over this already.

Localization

This results in a system with the following output of locale && localectl. Except that I later modified the compose key.

Wireless Networking

We want our ubuntu installer to automatically connect to the Wifi Network. In older versions of ubuntu, this could have been set up using d-i netcfg. The latest version does no longer support that, since it uses ubiquity as the installer instead of the good old debian-installer.

Preseeding keys for the following installer components will not be used in Ubiquity, usually because they do not fit with Ubiquity's mode of operation:

Source: UbiquityAutomation

I have tried replacing the d-i commands with ubiquity as follows, but I'm pretty sure having that in my preseed file did little if anything at all. Still, it currently is in my working preseed file, so it doesn't hurt to have that there at least.

I have also tried using preseed/early_command, which does not work because it is executed too early.

Evan needs to add a "a bit later than early command" preseed variable so that testing scripts can run once the desktop is available.

Evan didn't. But we can. I'll give you a quick run-through on how to modify ubiquity itself so that it allows us to run our custom not-too-early commands. If you need more explanation, I highly recommend you to check out my answer on askubuntu - I have taken the following excerpts from there.

Open The Squashfs

Modify Ubiquity

Open ~/Downloads/squashy/squashfs-root/usr/bin/lib/ubiquity/ubiquity/plugin_manager.py (I'd say as root, but not sure if that's necessary) and add the import statement import subprocess after all the other imports at the start of the file. Then navigate to line 42 where the function def load_plugins(): should be defined. Here what I inserted at the start of it, though the print statements are only for the logs and can be skipped.

Set Up inb4.sh

It is now possible for us to run arbitrary bash commands at the start of the installation by entering them into ~/Downloads/iso.new/preseed/inb4.sh. Since I simply want to connect to a normal WPA2 network, here's how mine looks (make sure you use your own interface as ip link reports it instead of my wlp3s0b1):

Make inb4.sh executable

Just to be safe.

Pack Squashfs

Make sure you currently don't have a file called filesystem.squashfs in your squashy directory, because that will cause the next commands to create /dev_1 instead of /dev and such things...

This command will take about 10 minutes on my laptop - be patient.

Update Filesystem Size Metadata

Test

Now is a good time to test your work again. Here's a reminder in case you can't be bothered to scroll back up to the section Write The Modified ISO To The Stick

Hostname

Remember how the documentation states that netcfg preseed commands are no longer used? Well, that was a lie. To set up your device's hostname:

Mirrors

Account Setup

Time

Package Selection

Again, I find the documentation to be confusing at best and wrong at worst. But despite what they say, this does choose a minimal install for me:

Grub Alongside Windows

It is possible to specify a disk, but I let it choose automatically since I only have one hard drive anyway.

And if you have seen the official example with all their options... I have not made use of them and it works fine:

Further options which I did not use yet include the possibility to set a password for grub:

Finishing

There are some hints in the official example file. This first one does not automatically reboot for me.

I have not tried this second tip, but it could work.

Late Command

In ubiquity, that's now called success_command and you can in addition also specify a failure_command.

You can only specify one of each, and each can only be on one single line unless you use line continuation characters. Because that gets annoying, I have moved all my late commands to bash files and only have this at the end of my preseed file:

I will show you more about this in the next section.

Configuring Software

I have added two files to iso.new/preseed that contain my commands for installing and configuring applications. One is called l8r.sh (but you could choose your own name) and is responsible for the actual installation process. The other one is called l8r.bootstrap.sh and is responsible for setting up the environment and logging. Of course, both are executable and have a shebang.

l8r.bootstrap.sh

This file is run from my ubiquity/success_command instruction and hence from the installer's perspective - not in the target filesystem.

In the installer, /target represents the target filesystem and /cdrom represents the USB Stick.

l8r.sh

Anything that works in a root bash shell in the target system should also work from this file.

Consider this an example file. I am not showing you everything I'm doing, because I'm still trying things out. There might follow another post on this.

Anything else that you'd like to run automatically after the OS is installed successfully, you can add in here.

Note that I wrote this file so that it instantly stops on an error. If you don't like this, remove set -e.

You find the logs of the latest install in /var/log/lucidbrot.log when you're normalled logged into the targed system.

Comments

Any reactions, replies or comments are welcome at comments@mink.li.

If you have any addition, I'll be happy to edit this article.

TODO

These are things that I am still working on automating fully and that I would like to include in a future post.

Licence

You are free to quote me and to improve on my work and redistribute it. You need to attribute me. You may not use my work for commercial purposes.

In more formal terms: This article is distributed under the CC BY-NC-SA 4.0 Licence which you can find here