Ubuntu headless VM

Published on 5 Apr 2018

I have been using Xubuntu 17.10 for a few months. I like it because it does not consume a lot of RAM (as opposed to running Gnome 3 or KDE 5 Plasma). With 700 MB allocated memory, it runs snappy enough. Lately, I kept on checking the amount of RAM I had left on the VM, and seeing that 400 MB (sometimes more) being used by the desktop environment (XFCE), the thought of having 400 MB wasted on DE alone did not sit well with my minimalist philosophy. After all, I was using Xubuntu just because I wanted to run a number of applications that would run really well on a linux system (e.g. Emacs with Spacemacs kit, Imagemagick, etc).

What about just running headless without all the fun stuff?

And I did give that thought a try. Today, I do not regret that decision. I downloaded Ubuntu 17.10 headless server, ran the installation, enabled ssh daemon, and started tinkering for the best setup. Oh by the way, I am using Oracle VM VirtualBox Manager, not Windows Hyper-V.

I divide this note into three main sections, namely:

  1. Network Interfaces. I prefer using NAT plus Host-only Adapter setup instead of Bridged Adapter.
  2. Terminal on Browser with wetty, because most consoles on Windows suck.
  3. Forwarding X11 session. The fun part.

If you were to ask why I did not just go with Ubuntu WSL (Windows 10), the answers are (1) it is painfully slow for some reasons, (2) WSL runs on an older Ubuntu version, (3) I felt like I did not have much control over it. Why I did not go with Arch linux? Well, Arch is too bleedy. I just want to have a peaceful (and somewhat boring) life.

Network Interfaces

In the past I went with bridged adapter exclusively with Arya Hendriks (the codename for my previous Xubuntu 17.10 VM installation), but that setup had a network problem to which I already attempted a successful workaround (which I wrote here). Somehow I could not use bridged adapter with Fiona Hendriks (the codename for my current Ubuntu 17.10 headless installation). I resorted to a different setup: 2 interfaces. The first interface is NAT (connects the VM to the outside world) and the second interface is Host-Only (for host to access the VM).

This setup requires 2 separate configuration steps: on the VirtualBox itself and telling the guest OS to use both interfaces.

Configuring VirtualBox. There are 2 parts to this: (a) configuring the adapter with VirtualBox’s Host Network Manager (shortcut: ctrl + w), and (b) configuring the virtual machine so that it can use both adapters, one is NAT and the other one is Host-only Adapter.

USH_NetworksPt1 Part 1: Configure gateway and DHCP server (GIF).

Under the Host Network Manager, choose the IP for gateway or leave the default value (192.168.56.1). Since I planned to let my VM to have a static IP under the Host-only Adapter, I turned off the DHCP server for this network.

USH_NetworksPt2 Part 2: Let our VM to have 2 adapters (GIF).

As for my VM network settings, I enabled Adapter 1 (for NAT) and Adapter 2 (for Host-only Adapter).

That is all here and the VM was ready to be booted up.

Netplan with 2 Interfaces. The latest versions of Ubuntu (as of writing) makes it easier to configure network interfaces. Previously, one had to tinker with the /etc/network/interfaces configuration file and the directives kind of confusing. Starting with Ubuntu 17.10 (if I recall correctly), Ubuntu manages network interfaces with netplan that takes configuration from /etc/netplan/*.yaml. This is convenient because the YAML formatting is easier for mortal eyes.

Before that, let’s query how many interfaces we actually have. We should expect 2 interfaces with ifconfig -a command. On my setup, the enp0s3 was the NAT and it was already running. The second interface, enp0s8 had to be activated. To bring the enp0s8 adapter up, I ran:

$ sudo ifconfig enp0s8 192.168.56.101 netmask 255.255.255.0 up

Using ifconfig -a again would say that the interface enp0s8 now up and running. I had my default 01-netcfg.yaml inside my /etc/netplan directory for the network configuration. I simply added another entry for enp0s8 under the ethernets as shown below. Because I was assigning the enp0s8 with a static IP of 192.168.56.101, the dhcp4 directive was set to no.

# This file describes the network interfaces available on your system
# For more information, see netplan(5).
network:
  version: 2
  renderer: networkd
  ethernets:
    enp0s3:
      dhcp4: yes 
    enp0s8:
      dhcp4: no
      addresses: [192.168.56.101/24]

This configuration was applied with sudo netplan apply and the system was restarted. This new setting should persist.

Wetty Web TTY

Before I installed PuTTY (to connect to my VM through SSH on Windows 10 host), I had this idea to find a web-based TTY terminal that I could use. I had no specific reason other than the thought to use web browser because that would be convenient. So I found wetty, which is a node package that can be installed and run as a service pretty easily.

I cloned wetty’s git repository into ~/script/wetty, installed dependencies with npm install and then gave it a go with this command (-p means port):

$ node app.js -p 3000

Then I visited 192.168.56.101:3000 and it worked! To run wetty as a systemd service, I created a unit file.

File: /lib/systemd/system/wetty.service.

[Unit]
Description=Wetty Web TTY 
After=network.target

[Service]
Type=simple
User=fiona
ExecStart=/usr/bin/node /home/fiona/script/wetty/app.js -p 3000
Restart=on-failure

[Install]
WantedBy=multi-user.target

Started and enabled this unit at startup:

$ sudo systemctl start wetty.service
$ sudo systemctl enable wetty.service

Done!

X11 Forwarding

Perhaps this is the most exciting part of the setup. The main drive for me to implement the X11 forwarding so that I could forward Inkscape from Ubuntu to my macOS desktop by having XQuartz to draw the Inkscape window. Why would I do this? First, the Inkscape for macOS is inherently buggy. This setup alleviates some annoyance but it provides a better Inkscape experience (with a little bit ugly-looking interface).

Inkscape with XQuartz on macOS Inkscape with XQuartz on macOS.

Note: On macOS, X is handled by XQuartz. On Windows, there are a few options, notably Xming and VcXsrv. I went with VcXsrv and don’t ask me why. The setup for both is the same, so not much difference actually.

I would like to go over how I set up X11 forwarding on macOS. To summarize (before we go deeper), I employed 2 strategies: (1) execute the application within the VM directly from host terminal without SSH and (2) SSH-ing into the box first to execute the application. The latter is a lot cooler.

Execute from host’s terminal. Assuming the VM is up & running and the intended application has already been installed (e.g. Inkscape), it requires a simple SSH command to start forwarding. Ideally, XQuartz (or VcXsrv) is also running at this time.

ssh -fYC user@vm inkscape

The option -fYC is shorthand for:

  • -f, tells ssh to go to background just before command execution.
  • -Y, to enable trusted X11, which won’t be subjected to X11 security extension control, i.e. faster forwarding.
  • -C, which enables compression, i.e. faster.

To make it easier, we can make an alias for this. Say, call this alias vm

alias vm='ssh -fYC user@vm'

So, any application within the VM can be called with this vm command. For example, vm inkscape or vm mkvtoolnix-gui.

Execute after ssh. There could be a situation where I would like to ssh into my VM first before running a command. During the ssh connection initialization, ssh must be run explicitly with -Y argument. Then, run any application. XQuartz (or VcXsrv) must be running for this to work.

# Connect to VM via ssh.
ssh -Y user@vm

# Execute application, e.g. Inkscape
inkscape

However with this method, the Inkscape would output messages (that we can safely ignore) in the terminal session, which won’t allow you to issue any command afterward. We can use tmux terminal multiplexer as the workaround, but there is a cooler solution: create a bash function.

function run() { nohup "$@" > /dev/null 2>&1 & disown; }

Essentially, I made a function run() that would do these:

  • Run nohup which makes a process immune to hangup (HUP) signal (can close terminal now).
  • The output from nohup goes to /dev/null (a bottomless pit).
  • disown for the shell terminal to disown that process.

Since I am running ZSH as my shell (as opposed to BASH), I place this function inside my ~/.zshrc ZSH configuration file. After refreshing the config file with source command, any application can be executed with run command.

# Execute with run() command
run inkscape

However, not all application can be forwarded. Applications like web browsers might not work properly or run very slowly. Since I am only forwarding X11 from my local VM, I will not experience any network penalty.

Network penalty is a real issue if I would like to forward an X11 session from a VPS. This is limited mainly by (1) network latency and (2) the connection speed. The further away the physical distance between VPS and my computer, that would make an X11 session runs like a snail. One workaround for this is to use faster (and less secured) ciphers, e.g. arcfour, blowfish, etc. I will not cover those here, but I have good resources: here, here, and here.

That’s all for today. If there is any question, tweet to me @aixnr.