Virtual Machine Setup Guide

We’ll be using Arch Linux virtual machines (VMs) in CS 326. Arch Linux is a simple distribution that gives us the opportunity to configure our virtual machines exactly as we want. For your reference, the Arch Linux Wiki has a wealth of information on configuring the system, swapping out components, and installing a wide variety of software.

In this lab, you’ll learn how to:

If you’d like to go beyond the steps outlined on this page, check out the helpful Arch Linux Installation Guide.

Virtual Machine Setup

One of our goals in this class is to get more familiar with Linux (and Unix) systems, particularly with the command line interface (CLI). Instead of giving each student a Linux workstation to use over the course of the semester, we’ll use virtual machines. After all, virtualized infrastructure has revolutionized the computing landscape; rather than maintaining large datacenters with thousands of power-hungry servers, companies can start and stop virtual machines as needed to scale their infrastructure in or out as required.

KVM is a virtualization module that allows Linux to act as a hypervisor, or software component that supervises virtual machines. In VM terminology, you have two types of systems:

We will use our VM host gojira to create a VM guest for each student in this lab.

Cloud Computing

What we’re about to do is very similar to creating an Amazon AWS, Google Cloud, Azure, etc.-based virtual machine. The big difference is that instead of using a pre-made template image of a VM, we’re starting from scratch.

After we build the VM, your interactions with it will be like any other Unix-based machine, including setting up SSH keys, managing packages, etc.

Logging into the VM Host

Dust off your USF CS credentials and ssh into our jump host, stargate.cs.usfca.edu. Once logged in you can proceed to ssh to our VM host, gojira:

# Get to the CS network:
$ ssh mmalensek@stargate.cs.usfca.edu

# Log in to the VM host:
$ ssh mmalensek@gojira

# Let's see who else is logged in:
$ finger
Login      Name               Tty      Idle  Login Time   
afedosov   Alex Fedosov       pts/0    4:04  Jan  9 12:20 
mmalensek  Matthew Malensek   pts/1          Jan  9 17:31 
# (This output will probably be a bit more exciting once classes start)

IMPORTANT: You will be using ssh extensively in this class, so you need to set up passwordless ssh if you haven’t already. In other words, you should be able to type ssh stargate.cs.usfca.edu followed by ssh gojira without entering a password.

All of our VMs will be hosted on a single server (gojira), which has 64 hardware threads (32 cores) and 128 GB of RAM. You will be able to log into the VM host to see all the VMs running.

Creating a new Virtual Machine

Before you start running commands, take a step back and determine the following:

Virtualization functionality is provided by the libvirt toolkit. We’ll use the virt-install command to set up our VM. Note that in the following example, the backslash (\) denotes a command continuation, a way of breaking one very long command line into multiple lines. You will need to tweak the command a bit to customize it for your own VM; see the explanation below for details.

TIP: if you’re about to paste the following into a text editor to modify, make sure it is in plain text mode! If it isn’t, it’ll make replacements with special characters that won’t work in your terminal.

$ virt-install \
    --name=VMNAME \
    --cpu host --vcpus=2 \
    --memory=1500 \
    --disk=/raid/$(whoami)/VMNAME.qcow2,size=32 \
    --cdrom=/home2/iso/archlinux-XXXX.iso \
    --net model=virtio,bridge=virbr0,mac='52:54:00:CA:FE:VMIDHEX' \
    --boot useserial=on \
    --console pty,target_type=serial \
    --graphics none \
    --os-type=linux \
    --os-variant=archlinux \
    --noautoconsole

Be sure to update:

You can leave the rest of the command line flags as shown above. They do the following:

If you would like to check out all the command line flags available for the virt-install utility, take a look at the man pages: man virt-install.

As long as virt-install succeeded, you should be ready to set up your OS. To verify, run virsh list:

# Note that without the --all flag, only running VMs will be shown.
# If your VM gets shut down later, you'll want to remember to pass
# this flag so you can check its status.
$ virsh list --all
 Id    Name                           State
 ----------------------------------------------------
 1     magical-unicorn                running

Great, it’s running! Let’s log in to the console:

virsh console VMNAME

You will see the boot splash screen (it might be nearly unreadable because we’re using an ancient serial console to view the VM’s screen). Hit enter to start the boot process, and after a few seconds you should see a login: prompt. Enter root as the username and you should be dropped into a root shell, ready to configure the system.

NOTE: if you don’t see anything when you connect to the console, try hitting enter a couple times and then wait.

Installing the OS

At this point, we’ve booted up a Live CD version of Arch Linux. The live CD contains all the standard Unix utilities we need to bootstrap and install the operating system by hand onto our VM’s hard disk. Let’s verify the VM is on the network with the ping command:

root@archiso ~ # ping google.com
PING google.com (216.58.194.206): 56 data bytes
64 bytes from 216.58.194.206: icmp_seq=0 ttl=57 time=11.457 ms
64 bytes from 216.58.194.206: icmp_seq=1 ttl=57 time=4.017 ms
^C

# (As usual, Ctrl + C quits the program).

Now we need to verify that the IP address our VM received from the DHCP server is correct. To do this, run:

root@archiso ~ # ip address | grep '192\.168\.122'
# The command will output something like this:
    inet 192.168.122.200/24 brd 192.168.122.255 scope global ens3

The last number of the IP address MUST be your 326 ID number!. In this case, mine is 200. If this is wrong, then you need to confirm you configured the MAC address correctly in the steps above.

Okay, so our IP is correct and we’re online. We have a live CD running and want to set up a new installation of Linux on our VM’s hard disk. Let’s get started!

Partitioning the Disk

The hard disk our VM starts out with is the same as one that has just been purchased and plugged in, i.e., it has absolutely nothing on it. On Unix systems, devices are represented as special files stored in /dev/. We can see the devices available on our VM with ls:

root@archiso ~ # ls /dev
autofs           initctl             null      tty3   tty24  tty45  ttyS2    vcsu
block            input               port      tty4   tty25  tty46  ttyS3    vcsu1
bsg              kmsg                ppp       tty5   tty26  tty47  udmabuf  vcsu2
btrfs-control    lightnvm            psaux     tty6   tty27  tty48  uhid     vcsu3
bus              log                 ptmx      tty7   tty28  tty49  uinput   vcsu4
cdrom            loop0               pts       tty8   tty29  tty50  urandom  vcsu5
char             loop1               random    tty9   tty30  tty51  userio   vcsu6
console          loop2               rfkill    tty10  tty31  tty52  vcs      vda
core             loop3               rtc       tty11  tty32  tty53  vcs1     vfio
cpu_dma_latency  loop4               rtc0      tty12  tty33  tty54  vcs2     vga_arbiter
cuse             loop5               shm       tty13  tty34  tty55  vcs3     vhci
disk             loop6               snapshot  tty14  tty35  tty56  vcs4     vhost-net
dri              loop7               snd       tty15  tty36  tty57  vcs5     vhost-vsock
fb0              loop-control        sr0       tty16  tty37  tty58  vcs6     virtio-ports
fd               mapper              stderr    tty17  tty38  tty59  vcsa     vport1p1
full             mem                 stdin     tty18  tty39  tty60  vcsa1    zero
fuse             memory_bandwidth    stdout    tty19  tty40  tty61  vcsa2
hidraw0          mqueue              tty       tty20  tty41  tty62  vcsa3
hpet             net                 tty0      tty21  tty42  tty63  vcsa4
hugepages        network_latency     tty1      tty22  tty43  ttyS0  vcsa5
hwrng            network_throughput  tty2      tty23  tty44  ttyS1  vcsa6

But which one of these is our hard disk? We can view all the disk partitions on our system by inspecting /proc/partitions or using the lsblk command. Another option is fdisk -l to list all the block devices available:

# The /proc/partitions file is a virtual file that gets populated with
# partition data. We'll learn more about /proc later on.

root@archiso ~ # cat /proc/partitions
major minor  #blocks  name

 254        0   33554432 vda
  11        0     616448 sr0
   7        0     500948 loop0

root@archiso ~ # lsblk
NAME   MAJ:MIN RM   SIZE RO TYPE MOUNTPOINT
loop0    7:0    0 489.2M  1 loop /run/archiso/sfs/airootfs
sr0     11:0    1   602M  0 rom  /run/archiso/bootmnt
vda    254:0    0    32G  0 disk

root@archiso ~ # fdisk -l
Disk /dev/vda: 32 GiB, 17179869184 bytes, 33554432 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes


Disk /dev/loop0: 489.2 MiB, 512970752 bytes, 1001896 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes

One easy way to identify our hard disk is its size: we configured the VM with a 32 GB disk, so the 32 GB disk at /dev/vda is the one we want.

Partitioning the disk takes a block device and splits it into multiple sub-devices. So for instance, we could take our 32 GB disk and split it into 1, 5, and 10 GB pieces. We could also create three partitions of 1 GB and leave 29 GB of unused space on the disk… but that wouldn’t be very useful! The point is, you can divide the disk however you’d like and you don’t necessarily need to use the entire thing.

In our configuration, we’ll have two partitions: 200 MB for the boot partition, and the rest of the disk for our root partition. The boot partition contains the code for the bootloader, which takes care of actually starting our OS for us.

Let’s partition this disk! Here’s the steps:

  1. Pass the block device file to the fdisk utility
  2. Create a new partition table (the place where partition configuration information is stored)
  3. Create a new primary partition (enter n, then p). This will be partition 1.
  4. Configure the partition to start at the beginning of the disk (use the default value) and then extend by 200 MB (enter +200M).
  5. Create another primary partition (defaults are fine here, just press enter for each prompt).
  6. Print the partition table with p to verify your work
  7. Toggle the boot flag for the first partition
  8. Write the changes to the disk

Easy, right? Okay, here we go. But be careful. If you make a mistake, you can always quit fdisk and then run it again to start over from scratch. In some of the lines below there is no input (it’s blank) – that means we’re accepting the defaults.

root@archiso ~ # fdisk /dev/vda
Welcome to fdisk (util-linux 2.33).
Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.

Device does not contain a recognized partition table.
Created a new DOS disklabel with disk identifier 0xc22fbe10.

Command (m for help): o
Created a new DOS disklabel with disk identifier 0xdd504842.

Command (m for help): n
Partition type
   p   primary (0 primary, 0 extended, 4 free)
   e   extended (container for logical partitions)
Select (default p): p
Partition number (1-4, default 1): 1
First sector (2048-67108863, default 2048):
Last sector, +/-sectors or +/-size{K,M,G,T,P}: +200M

Created a new partition 1 of type 'Linux' and of size 200 MiB.

Command (m for help): n
Partition type
   p   primary (1 primary, 0 extended, 3 free)
   e   extended (container for logical partitions)
Select (default p): p
Partition number (2-4, default 2): 2
First sector (411648-67108863, default 411648):
Last sector, +/-sectors or +/-size{K,M,G,T,P}:

Created a new partition 2 of type 'Linux' and of size 31.8 GiB.

# *****************
# Okay, great, we've made it through steps 1-5. You should use the help menu
# ('m') to figure out how to:
#
#   - Set the boot flag on partition 1
#   - Write the changes to the disk
#
# After setting the boot flag, print the partition table with 'p'. Your output
# should look like this (note the * for 'Boot'):
# *****************

Command (m for help): p
Disk /dev/vda: 32 GiB, 34359738368 bytes, 67108864 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0xe0619161

Device     Boot  Start      End  Sectors  Size Id Type
/dev/vda1  *      2048   411647   409600  200M 83 Linux
/dev/vda2       411648 67108863 66697216 31.8G 83 Linux

Command (m for help): 

# Reminder: be sure to write your changes after setting the flag.

Note: many modern systems (especially those with uEFI) use the GPT partitioning scheme instead of MBR. To do this, you can use the gdisk utility instead of fdisk – it operates similarly but uses the more modern GPT partition format.

Assuming the partitioning process went well, you should be able to see the partitions with lsblk:

root@archiso ~ # lsblk
NAME   MAJ:MIN RM   SIZE RO TYPE MOUNTPOINT
loop0    7:0    0 489.2M  1 loop /run/archiso/sfs/airootfs
sr0     11:0    1   602M  0 rom  /run/archiso/bootmnt
vda    254:0    0    16G  0 disk
├─vda1 254:1    0   200M  0 part
└─vda2 254:2    0  31.8G  0 part

If something is wrong, you can repeat the process described above without starting everything over again.

In the past, Unix installations frequently had several different partitions: binary executables might be on one partition, user home directories on another, configurations on another, and so on. This has a number of advantages, including:

Many Linux distributions have recently started defaulting to single partition: /. This is simple to work with and modern hardware is not as limited as it was in the past, making a one-partition setup a reasonable choice.

In the following steps, we’ll need to remember the names of the two partitions we just created. We’ll identify them as follows:

Creating File Systems

Now that we’re done editing the partition table, we have two raw partitions of an empty disk. Our OS needs to know how to store and retrieve files from these partitions; to do this, we’ll create a file system on both.

In our case, we’ll have one file system on /boot and another on / (root). We’ll use the ext4 file system. Each file system has its own set of pros and cons; if you want to do some research and use a different one, feel free!

File systems are created with the mkfs.* family of commands. We’ll use mkfs.ext4:

# Create the file system for /boot:
root@archiso ~ # mkfs.ext4 BOOT-PART
mke2fs 1.45.6 (20-Mar-2020)
Discarding device blocks: done
Creating filesystem with 204800 1k blocks and 51200 inodes
Filesystem UUID: e1049950-cc2a-46ff-b81c-816999e7df46
Superblock backups stored on blocks:
        8193, 24577, 40961, 57345, 73729

Allocating group tables: done
Writing inode tables: done
Creating journal (4096 blocks): done
Writing superblocks and filesystem accounting information: done

# Create the file system for / (root):
root@archiso ~ # mkfs.ext4 ROOT-PART
mke2fs 1.45.6 (20-Mar-2020)
Discarding device blocks: done
Creating filesystem with 8337152 4k blocks and 2084880 inodes
Filesystem UUID: 353e3389-1b4d-46eb-82b4-12e792fa022a
Superblock backups stored on blocks:
        32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208,
        4096000, 7962624

Allocating group tables: done
Writing inode tables: done
Creating journal (32768 blocks): done
Writing superblocks and filesystem accounting information: done

Let’s verify this worked with blkid:

root@archiso ~ # blkid /dev/vda1
/dev/vda1: UUID="e1049950-cc2a-46ff-b81c-816999e7df46" BLOCK_SIZE="1024" TYPE="ext4" PARTUUID="e0619161-01"

root@archiso ~ # blkid /dev/vda2
/dev/vda2: UUID="353e3389-1b4d-46eb-82b4-12e792fa022a" BLOCK_SIZE="4096" TYPE="ext4" PARTUUID="e0619161-02"

Mounting the File Systems

Our partitions have file systems and are ready to go. Now we just need to install Linux. To modify the file system contents, we need to mount them on our live CD’s file system tree. Mounting a file system grafts it onto an existing file system tree. We’ll mount our root partition on /mnt (you can probably guess what /mnt is for…):

# Take the root FS we created and stick it on /mnt:
root@archiso ~ # mount ROOT-PART /mnt
# Create a /boot directory on the root FS:
root@archiso ~ # mkdir /mnt/boot
# Stick our boot partition inside the root partition:
root@archiso ~ # mount BOOT-PART /mnt/boot

# Now if we do an ls, all we get is:
root@archiso ~ # ls /mnt
boot    lost+found
# Pretty sad, huh? We need to fill that root directory with files!

# Let's take a look at what's mounted with df:
root@archiso ~ # df -H
Filesystem      Size  Used Avail Use% Mounted on
( ... some contents removed for brevity ... )
dev             494M     0  494M   0% /dev
airootfs        269M  7.5M  262M   3% /
tmpfs           517M     0  517M   0% /tmp
/dev/vda2        34G   51M   32G   1% /mnt
/dev/vda1       199M  1.6M  183M   1% /mnt/boot

# Great, they're both mounted!

Installing the Base System

Arch Linux has a nice tool called pacstrap that will bootstrap a system into a specified directory. We want it to bootstrap our new root partition:

root@archiso ~ # pacstrap /mnt base linux linux-firmware
==> Creating install root at /mnt
==> Installing packages to /mnt
:: Synchronizing package databases...
 core                 135.0 KiB   7.75M/s 00:00 [#############] 100%
 extra                1694.1 KiB  61.3M/s 00:00 [#############] 100%
 community            4.8 MiB     76.6M/s 00:00 [#############] 100%

( ... lots of packages get installed ... )

# Afterward, we can see that /mnt is populated with system files. (run ls -l /mnt).
/mnt
├── bin -> usr/bin
├── boot
├── dev
├── etc
├── home
├── lib -> usr/lib
├── lib64 -> usr/lib
├── mnt
├── opt
├── proc
├── root
├── run
├── sbin -> usr/bin
├── srv
├── sys
├── tmp
├── usr
└── var

The arrows (->) here denote links or files/directories that point to another file/directory. Kind of like aliases or shortcuts in macOS/Windows.

Basic Configuration

The base system is ready, but we need to configure a few things before we can start using it. First, we need to create an fstab, which describes the file systems available to the OS. The fstab is how the OS will know what to mount automatically at boot. Note that your fstab will be a little different than the one shown below, since your hard disk partitions will have different UUIDs.

root@archiso ~ # genfstab -U /mnt >> /mnt/etc/fstab

# Let's check the contents of the fstab:
root@archiso ~ # cat /mnt/etc/fstab
# Static information about the filesystems.
# See fstab(5) for details.

# <file system> <dir> <type> <options> <dump> <pass>
# /dev/vda2
UUID=353e3389-1b4d-46eb-82b4-12e792fa022a       /               ext4            rw,relatime  0 1

# /dev/vda1
UUID=e1049950-cc2a-46ff-b81c-816999e7df46       /boot           ext4            rw,relatime  0 2

Once the fstab is ready, we can chroot into our new base system. chroot stands for ‘change root’ – it starts a new shell inside a specified directory as if it was the root directory of the system. So if we chroot into /mnt, the /mnt directory will now appear to be the root of the system! This is important because we can then run all the utilities we just installed to our new disk, and the changes they make will be written to the new system rather than the live CD’s environment.

Note that chroot is also useful from a security perspective – you can jail a process inside a chroot environment, and it won’t be able to modify the file system outside its virtual root directory. A more advanced version of chroot, called jails on BSD systems, allows for some basic containerization functionality. This might add a bit of context on where the term “jailbreaking” comes from (such as with iOS devices).

Enough talk! Let’s chroot in and start configuring!

# Notice how the shell prompt changes after doing the chroot. This is because
# we're running inside a "different" system with a different shell configuration.
root@archiso ~ # arch-chroot /mnt
[root@archiso /]#

We need a few more utilities installed on our new system. Let’s install them with pacman:

[root@archiso /]# pacman -Sy \
    base-devel clang dhcpcd gdb git grub inetutils \
    man man-pages nano openssh python vi vim wget

# Note: you can accept the default packages (just press enter) for the
# 'base-devel' meta-package as well as for the 'man' package.

( ... lots more packages install ... )

Make sure you perform this step! Some of the following configuration steps require these packages.

Our next order of business is to configure the time zone. We will create a link to specify that our local time zone is ‘Pacific’:

[root@archiso /]# ln -sfv /usr/share/zoneinfo/US/Pacific /etc/localtime
'/etc/localtime' -> '/usr/share/zoneinfo/US/Pacific'
[root@archiso /]# hwclock --systohc
[root@archiso /]# date
Tue Jan 12 13:50:12 PST 2021

If this doesn’t work (timezone stays UTC), try reinstalling the tzdata package with pacman -Sy tzdata.

Now we need to set up the locale. If we don’t do this, characters won’t show up correctly and we can have issues with date, time, currency formatting. Open up /etc/locale.gen in your editor (vi, nano, etc) and uncomment the en_US lines (around line 176). Here’s a snippet from the file with the correct lines uncommented:

#en_PH.UTF-8 UTF-8
#en_PH ISO-8859-1
#en_SC.UTF-8 UTF-8
#en_SG.UTF-8 UTF-8
#en_SG ISO-8859-1
en_US.UTF-8 UTF-8
en_US ISO-8859-1
#en_ZA.UTF-8 UTF-8
#en_ZA ISO-8859-1
#en_ZM UTF-8
#en_ZW.UTF-8 UTF-8
#en_ZW ISO-8859-1

Next, we’ll update the locale configuration and generate the locales:

[root@archiso /]# echo 'LANG=en_US.UTF-8' > /etc/locale.conf
[root@archiso /]# locale-gen
Generating locales...
  en_US.UTF-8... done
  en_US.ISO-8859-1... done
Generation complete.

Now we’ll create a user account for ourselves:

# It's a bad idea to always be logged in as root, so let's create our own user
# account with the useradd command. The -m flag creates a home directory for
# the user (/home/<username>).
# *** Remember: replace VMUSERNAME with your usual CS username!! ***
[root@archiso /]# useradd -m VMUSERNAME -g wheel

# Set the password for our new user
[root@archiso /]# passwd VMUSERNAME
(enter whatever your VMPASSWORD is at the prompt)

# To allow our user to use 'sudo', edit the sudoers file with 'visudo'.
# By default visudo will use the vim editor, so if you'd prefer something
# else then prefix with EDITOR as shown below:
[root@archiso /]# EDITOR=nano visudo

# Scroll down near the bottom of the the sudoers file (type 'G' in vim)
# and uncomment the 'wheel' option (just remove the # before %wheel)
# as shown here:
## Uncomment to allow members of group wheel to execute any command
%wheel ALL=(ALL) ALL

# Save and exit your editor.

# Let's test out the account. Since we're root, we can actually use the
# substitute user (su) command to switch over:
[root@archiso /]# su - VMUSERNAME
[mmalensek@archiso ~]$ 

# Now let's make sure we can use sudo:
[mmalensek@archiso ~]$ sudo ls /root
# (Nothing should display. If you get a permission denied error,
# something went wrong and you probably need to retrace your steps.)

# Time to create an ssh key for our user:
# (when inputs are blank I'm accepting the defaults --
#  in particular, I went with no passphrase (empty) here)
[mmalensek@archiso ~]$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/home/mmalensek/.ssh/id_rsa):
Created directory '/home/mmalensek/.ssh'.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/mmalensek/.ssh/id_rsa
Your public key has been saved in /home/mmalensek/.ssh/id_rsa.pub
The key fingerprint is: (... removed for brevity ...)
[mmalensek@archiso ~]$ 

# Great, it worked. Let's switch back to root. To do this, we exit from our
# current session back to the original login session.
[mmalensek@archiso ~]$ exit

Setting the Hostname

There are two hard problems in computer science: cache invalidation, naming things, and off-by-one errors. We have to tackle one of these here: naming your VM. The /etc/hostname file contains the hostname of the machine, so let’s create this file from our shell.

[root@archiso /]# echo "VMNAME" > /etc/hostname

# When I executed this command, I did the following:
# *** You should come up with your own name, though!!! ***
[root@archiso /]# echo "magical-unicorn" > /etc/hostname

This is a great illustration of why we need chroot: if we hadn’t changed root to our new system’s root partition, then we’d just be changing the hostname of the live CD with the command above.

Network Configuration

One of the last things we need to do is set up the network so we (1) assign an IP address to the VM on boot, and (2) access our VM remotely with ssh so we don’t need to use the console.

[root@archiso /]# systemctl enable dhcpcd
Created symlink /etc/systemd/system/multi-user.target.wants/dhcpcd.service → /usr/lib/systemd/system/dhcpcd.service.

[root@archiso /]# systemctl enable sshd
Created symlink /etc/systemd/system/multi-user.target.wants/sshd.service → /usr/lib/systemd/system/sshd.service.

Alright, we should be all set with the network now.

Bootloader

Traditionally, operating systems need a small utility called a bootloader to start them up. This helps enforce separation of concerns; the hardware BIOS doesn’t need to be able to understand all the various file systems that exist or know how to start things up; instead, it just launches the bootloader and lets it do the heavy lifting. On modern systems, such as those with uEFI, a bootloader isn’t strictly necessary, but it still gives us more flexibility. We will install the GRUB bootloader on our VMs, but there are many different options available. Please be careful while doing these next few steps. If you do them incorrectly, your VM may not boot back up!

# IMPORTANT: notice how we are installing the bootloader to the *device*
# itself (vda), NOT a partition. The boot code is independent from the
# partitions.
[root@archiso /]# grub-install --target=i386-pc /dev/vda
Installing for i386-pc platform.
Installation finished. No error reported.

Now we need to generate a config file for GRUB. To do this, we’ll set up some parameters and then have a tool called grub-mkconfig take care of the rest. Open up /etc/default/grub in your editor. First, add console=tty0 console=ttyS0,115200 to the GRUB_CMDLINE_LINUX_DEFAULT variable and then remove quiet from the string. On my machine, it looks something like this:

GRUB_CMDLINE_LINUX_DEFAULT="loglevel=3 console=tty0 console=ttyS0,115200"

Next, update the GRUB_TERMINAL_INPUT, GRUB_TERMINAL_OUTPUT, and GRUB_SERIAL_COMMAND variables like so (add them if they don’t already exist in the config file):

GRUB_TERMINAL_INPUT="serial" 
GRUB_TERMINAL_OUTPUT="serial" 
GRUB_SERIAL_COMMAND="serial --unit=0 --speed=115200"

Make sure the two variables are not commented out with #.

Now we are ready to generate the full configuration file:

[root@archiso boot]# grub-mkconfig -o /boot/grub/grub.cfg
Generating grub configuration file ...
Found linux image: /boot/vmlinuz-linux
Found initrd image: /boot/initramfs-linux.img
Found fallback initrd image(s) in /boot: initramfs-linux-fallback.img
done

WARNING: If the grub-mkconfig command above didn’t print the ‘Found linux’, ‘Found initrd’, etc. lines then ensure you installed all the packages with pacman above. If you continue beyond this point your system may not boot back up!

Now when the VM boots up, it will launch GRUB first. GRUB displays a nice menu that lets us select which OS to run (only one in this case), and we can also launch different versions of the Linux kernel from the menu.

Finishing Up

Okay, so I guess we really appreciate the GUI installers for Linux now! But hey, at least we’re done. Go ahead and reboot your VM with:

[root@archiso /]# exit
root@archiso ~ # shutdown -h now

Powering Back On

If your VM gets shut off for some reason (say, it doesn’t come back online after the last step), you will need to start it again. Log into gojira and then start your VM with virsh:

[gojira:~]$ virsh list --all
 Id    Name                           State
 ----------------------------------------------------
  8     magical-unicorn               shut off

[gojira:~]$ virsh start magical-unicorn
Domain magical-unicorn started

If everything went well, you should be able to ssh to the VM’s IP address from gojira. For instance, if my CS 326 ID is 200, I would do the following:

[mmalensek@gojira ~]$ ssh mmalensek@192.168.122.200

Finalizing Your VM Installation

The last piece of the puzzle required to finish the lab is to add the class ssh key to the root account of your VM. This will let us know that you finished the lab successfully. To do this, log into your VM:

[mmalensek@gojira ~]$ ssh mmalensek@192.168.122.200
[mmalensek@magical-unicorn ~]$ 

# Let's switch to root:
[mmalensek@magical-unicorn ~]$ sudo -i
Password:

[root@magical-unicorn ~]# wget 'https://bit.ly/38nahhv'
Saving to: ‘38nahhv’
2020-01-17 09:27:59 (31.4 MB/s) - ‘38nahhv’ saved [402/402]

# Then, after that:
[root@magical-unicorn ~]# mkdir .ssh
[root@magical-unicorn ~]# cat 38nahhv >> .ssh/authorized_keys
[root@magical-unicorn ~]# rm 38nahhv

This will let our grading script run to verify you completed the lab. Congratulations, you successfully configured your VM!

You’re Not Done Yet…

Your VM is up and running, but accessing it and using it is not very convenient (yet). Head to the working remotely page to set up your editing environment, file transfers, etc.