Title: How to run a NixOS VM as an OpenBSD guest
Author: Solène
Date: 08 May 2021
Tags: openbsd nixos
Description: 

# Introduction

This guide is to help people installing the NixOS Linux distribution as
a virtual machine guest hosted on OpenBSD VMM hypervisor.

# Preparation

Some operations are required on the host but specifics instructions
will be needed on the guest as well.

## Create the disk

We will create a qcow2 disk, this format allows not using all the
reserved space upon creation, size will grow as the virtual disk will
be filled with data.

```shell command
vmctl create -s 20G nixos.qcow2
```

## Configure vmd

We have to configure the hypervisor to run the VM.  I've chose to
define a new MAC address for the VM interface to avoid collision with
the host MAC.

```configuration file /etc/vm.conf
vm "nixos" {
       memory 2G
       disk "/home/virt/nixos.qcow2"
       cdrom "/home/virt/latest-nixos-minimal-x86_64-linux.iso"
       interface { lladdr "aa:bb:cc:dd:ee:ff"  switch "uplink" }
       owner solene
       disable
}

switch "uplink" {
        interface bridge0
}
```
vm.conf man page
## Configure network

We need to create a bridge in which I will add my computer network
interface "em0" to it.  Virtual machines will be attached to this
bridge and will be seen from the network.

```network configuration
echo "add em0" > /etc/hostname.bridge0
sh /etc/netstart bridge0
```

## Start vmd

We want to enable and then start vmd to use the virtual machine.

```rcctl instructions
rcctl enable vmd
rcctl start vmd
```

## NixOS and serial console

When you are ready to start the VM, type "vmctl start -c nixos", you
will get automatically attached to the serial console, be sure to read
the whole chapter because you will have a time frame of approximately
10 seconds before it boots automatically (if you don't type anything).

If you see the grub display with letters displayed more than once, this
is perfectly fine.  We have to tell the kernel to enable the console
output and the desired speed.

On the first grub choice, press "tab" and append this text to the
command line: "console=ttyS0,115200" (without the quotes).  Press Enter
to validate and boot, you should see the boot sequence.

For me it took a long time on starting sshd, keep waiting, that will
continue after less than a few minutes.

## Installation

There is an excellent installation guide for NixOS in their official
documentation.
Official installation guide
I had issues with DHCP so I've set the network manually, my network is
in 192.168.1.0/24 and my router 192.168.1.254 is offering DNS too.

```
systemctl stop NetworkManager
ifconfig enp0s2 192.168.1.151/24 up
route add -net default gw 192.168.1.254
echo "nameserver 192.168.1.254" >> /etc/resolv.conf
```

The installation process can be summarized with theses instructions:

```installation instructions
sudo -i
parted /dev/vda -- mklabel msdos
parted /dev/vda -- mkpart primary 1MiB -1GiB # use every space for root except 1 GB for swap
parted /dev/vda -- mkpart primary linux-swap -1GiB 100%
mkfs.xfs -L nixos /dev/vda1
mkswap -L swap /dev/vda2
mount /dev/disk/by-label/nixos /mnt
swapon /dev/vda2
nixos-generate-config --root /mnt
nano /mnt/etc/nixos/configuration.nix
nixos-install
shutdown now
```

Here is my configuration.nix file on my VM guest, it's the most basic I
could want and I stripped all the comments from the base example
generated before install.

```example configuration file
{ config, pkgs, ... }:

{
  imports =
    [ # Include the results of the hardware scan.
      ./hardware-configuration.nix
    ];

  boot.loader.grub.enable = true;
  boot.loader.grub.version = 2;
  boot.loader.grub.extraConfig = ''
    serial --unit=0 --speed=115200 --word=8 --parity=no --stop=1
    terminal_input --append serial
    terminal_output --append serial
  '';

  networking.hostName = "my-little-vm";
  networking.useDHCP = false;

  # COMMENT THIS LINE IF YOU DON'T WANT DHCP
  # networking.interfaces.enp0s2.useDHCP = true;


  # BEGIN ADDITION
  # all of these variables were added or uncommented
  boot.loader.grub.device = "/dev/vda";

  # required for serial console to work!
  boot.kernelParams = [
    "console=ttyS0,115200n8"
  ];

  systemd.services."serial-getty@ttyS0" = {
    enable = true;
    wantedBy = [ "getty.target" ]; # to start at boot
    serviceConfig.Restart = "always"; # restart when session is closed
  };

  # use what you want
  time.timeZone = "Europe/Paris";

  # BEGIN NETWORK
  # define network here
  networking.interfaces.enp0s2.ipv4.addresses = [ {
        address = "192.168.1.151";
        prefixLength = 24;
  } ];
  networking.defaultGateway = "192.168.1.254";
  networking.nameservers = [ "192.168.1.254" ];
  # END NETWORK

  # enable SSH and allow X11 Forwarding to work
  services.openssh.enable = true;
  services.openssh.forwardX11 = true;

  # Declare a user that can use sudo
  users.users.solene = {
    isNormalUser = true;
    extraGroups = [ "wheel" ];
  };

  # declare the list of packages you want installed globally
  environment.systemPackages = with pkgs; [
     wget vim
  ];

  # firewall configuration, only allow inbound TCP 22
  networking.firewall.allowedTCPPorts = [ 22 ];
  networking.firewall.enable = true;
  # END ADDITION

  # DONT TOUCH THIS EVER EVEN WHEN UPGRADING
  system.stateVersion = "20.09"; # Did you read the comment?

}
```

Edit /etc/vm.conf to comment the cdrom line and reload vmd service.  If
you want the virtual machine to automatically start with vmd, you can
remove the "disable" keyword.

Once your virtual machine is started again with "vmctl start nixos",
you should be able to connect to ssh to it.  If you forgot to add
users, you will have to access the VM console with "vmctl console", log
as root, modify the configuration file, type "nixos-rebuild switch" to
apply changes, and then "passwd user" to define the user password.  You
can set a public key when declaring a user if you prefer (I recommend).

# Install packages

There are three ways to install packages on NixOS: globally, per-user
or for a single run.

- globally: edit /etc/nixos/configuration.nix and add your packages
names to the variable "environment.systemPackages" and then rebuild the
system
- per-user: type "nix-env -i nixos.firefox" to install Firefox for that
user
- for single run: type "nix-shell -p firefox" to create a shell with
Firefox available in it

Note that the single run doesn't mean the package will disappear, it's
most likely... not "hooked" into your PATH so you can't use it.  This
is mostly useful when you make development and you need specific
libraries to build a project and you don't always want them available
for your user.

# Conclusion

While I never used a Linux system as a guest in OpenBSD it may be
useful to run Linux specific software occasionally.  With X forwarding,
you can run Linux GUI programs that you couldn't run on OpenBSD, even
if it's not really smooth it may be enough for some situations.

I chose NixOS because it's a Linux distribution I like and it's quite
easy to use in the regards it has only one configuration file to manage
the whole system.