# Using virt-install to create unattended virtual machine install

## Introduction

[Virt-manager](https://en.wikipedia.org/wiki/Virt-manager) is a great tool to replace [VirtualBox](https://en.wikipedia.org/wiki/Virtualbox) or [VMware Workstation](https://en.wikipedia.org/wiki/VMware_Workstation) on Linux, when [GNOME boxes](https://en.wikipedia.org/wiki/GNOME_Boxes) is not enough.

I use virtualization on a daily basis and often need to set-up new virtual machines (VM). Virt-manager has a wizard to create new VM, where you specify the ISO, select all you need, but then we have to go through the installation process.

All that takes time, so to speed up, instead of going through the GUI, we'll use `virt-install`(install `virt-install` package if needed) to create an unattended installation of a [debian](https://www.debian.org/) server with just a serial console.

## Unattended VM creation

We first need to create a password file for `root` and one for the user. They are in plaintext, and the password must be on the first line:

```plaintext
echo "toor" > root-pwd.txt
echo "resu" > user-pwd.txt
```

Now let's do the installation:

```plaintext
virt-install --install debian12 \
    --graphics none \
    --console pty,target_type=serial \
    --noautoconsole \
    --extra-args="console=ttyS0,115200n8" \
    --unattended profile=jeos,admin-password-file=root-pwd.txt,user-login=user,user-password-file=user-pwd.txt
```

Let's go through the options:

* `--install debian12`: Use debian 12. To list all the possible options, use `osinfo-query os`. What we need is the `shortcode`.
    
* `--graphics none`: No graphics (We can add later on) but since this is a server, we'll just use SSH
    
* `--console pty,target_type=serial`: Add a [PTY](https://en.wikipedia.org/wiki/Pseudoterminal) console to the virtual machine
    
* `--noautoconsole`: Don't automatically try to connect to the guest console
    
* `--extra-args="console=ttyS0,115200n8"`: Arguments when starting the kernel to output to the serial console
    
* `--unattended profile=jeos,admin-password-file=root-pwd.txt,user-login=user,user-password-file=user-pwd.txt`: Make an unattended installation. If not specified, we'll have to install ourselves.
    
    * `profile=jeos`: Use the [JeOS](https://en.wikipedia.org/wiki/Just_enough_operating_system) profile (instead of default `desktop`)
        
    * `admin-password-file=password.txt`: Use the password in the `password.txt` file we created just before for `root`.
        
    * `user-login=user,user-password-file=user-pwd.txt`: Create a user named `user`with the password provided in the file. By default [OpenSSH](https://www.openssh.com/) doesn't allow `root` to connect, so if we don't have a user, we won't be able to connect via SSH.
        

It will output something similar to:

```plaintext
Using default --name debian12
Using debian12 default --memory 2048
Using debian12 default --disk size=20

Starting install...
Retrieving 'linux'                                          | 7.6 MB  00:14 ... 
Retrieving 'initrd.gz'                                      |  39 MB  01:05 ... 
Allocating 'virtinst-pm9z6mm7-linux'                        |    0 B  00:00 ... 
Transferring 'virtinst-pm9z6mm7-linux'                      |    0 B  00:00 ... 
Allocating 'virtinst-c_yhjtv8-initrd.gz'                    |    0 B  00:00 ... 
Transferring 'virtinst-c_yhjtv8-initrd.gz'                  |    0 B  00:00 ... 
Allocating 'debian12.qcow2'                                 |    0 B  00:00 ... 
Creating domain...                                          |    0 B  00:00     

Domain is still running. Installation may be in progress.
You can reconnect to the console to complete the installation process.
```

We haven't specified the name of the VM (`--name`), the network (`--network`) to use, the location for the disk or its size (`--disk`), as well the RAM (`--memory`).

It defaulted to `debian12` for the name. For the RAM and disk size, it uses defaults from its database. As for the network, it will use the `default` adapter, and it will store the disk in the `default`[storage pool](https://libvirt.org/storage.html), which is in `/var/lib/libvirt/images`.

It starts by downloading the necessary files to start the installation, then runs it in the background as requested.

## Monitoring installation progress

While not necessary, we can look at the console to see the progress:

```plaintext
sudo virsh console debian12
```

We can also continue with `virt-manager` instead.

It should complete in a few minutes, then it will poweroff the system.

It's a basic server with SSH, so we'll still need to adjust a few things such as setting the timezone, adding users, and providing SSH key for authentication.

## Accessing the system via SSH

It's useful to have console access, but SSH is more practical.

### List running domains

We can list the running virtual machines (aka ***domains***) with `sudo virsh list`:

```plaintext
user@fedora:~$ sudo virsh list
 Id   Name      State
-------------------------
 1    dev       running
 2    freebsd   running
 3    win11     running
 4    spotify   running
```

`debian12` is not present in the list, so it must be in another state. We can add the `--all` parameter to the command to list all the VM in any state:

```plaintext
user@ubuntu:~$ sudo virsh list --all
 Id   Name                State
------------------------------------
 1    dev                 running
 2    freebsd             running
 3    win11               running
 4    spotify             running
 -    AI                  shut off
 -    debian12            shut off
 -    Kali                shut off
 -    storage             shut off
```

It's shut off at this time.

### Start the domain

```plaintext
user@ubuntu:~$ sudo virsh start debian12
Domain 'debian12' started
```

Let's give it a few seconds to boot so that it can get an IP address.

### Obtaining VM IP address

Our VM got attached to the `default` network. We'll query the DHCP leases to figure out what IP address it got:

```plaintext
 Expiry Time           MAC address         Protocol   IP address           Hostname   Client ID or DUID
------------------------------------------------------------------------------------------------------------------------------------------------
 2024-05-25 12:06:08   52:54:00:de:18:bb   ipv4       192.168.100.168/24   -          01:52:54:00:12:18:aa
 2024-05-25 12:25:07   52:54:00:de:98:bb   ipv4       192.168.100.169/24   debian12   ff:00:12:18:aa:00:01:00:01:2d:e4:67:8b:52:54:00:12:18:aa
 2024-05-25 12:19:29   52:54:00:a8:63:74   ipv4       192.168.100.181/24   pc         01:52:54:00:a8:68:75
 2024-05-25 12:16:25   52:54:00:c4:28:fe   ipv4       192.168.100.16/24    -          01:52:54:00:b5:28:ce
 2024-05-25 12:16:56   52:54:00:32:05:09   ipv4       192.168.100.189/24   -          01:52:54:00:c2:83:63
```

We can easily identify our `debian12`, it got 192.168.100.169. Now we're ready to SSH into it.

## A few things

The big gotcha is that not all distributions are supported. We can list the ones that support the `--unattended` option using the following script (on Ubuntu, we need the `osinfo-db-tools` package):

```bash
#!/bin/bash

echo "Checking install scripts for each OS..."

# List all OSes and their short IDs
os_list=$(osinfo-query os)

# Loop through each OS and check if it has an install script
while IFS= read -r line; do
    # Extract the short ID and the full name of the OS
    short_id=$(echo "$line" | awk '{print $1}')
    name=$(echo "$line" | awk -F '|' '{print $2}')

    # Check if the OS has an install script
    if osinfo-install-script "$short_id" &> /dev/null; then
        echo "$name ($short_id) has an install script."
    fi
done <<< "$os_list"
```

The database (containing the unattended installation scripts, URL locations, etc) gets updated once in a while, but we can also update it manually. Using the `nightly` option to get the most recent development version (`--latest` is a lesser adventurous option), it looks like this:

```bash
osinfo-db-import --nightly
```

A bit more than a month after the release of Ubuntu 24.04 and Fedora 40, only Fedora 40 support was added.

Furthermore, while a number of distributions have unattended installation, they don't always have an install tree URL. As an example, for Ubuntu, we have to go back all the way to 20.04 for the install tree URL to be in the database. Anything more recent than 20.04 doesn't have one available. However, if we have an install tree and provide its path (`--location`), it should be able to do the unattended installation.
