Headless VirtualBox on Ubuntu Server 20.04

As expected, first we need to install VirtualBox.

sudo wget -q https://www.virtualbox.org/download/oracle_vbox_2016.asc -O- | sudo apt-key add -
echo "deb [arch=amd64] https://download.virtualbox.org/virtualbox/debian focal contrib" \
| sudo tee /etc/apt/sources.list.d/virtualbox.list
sudo apt update
sudo apt-get install --yes virtualbox-6.1
sudo systemctl status vboxdrv

sudo usermod -aG vboxusers $USER

Since our server is headless, we will need a remote acces. This means installing Oracle’s extension pack with RDP support. You should be good installing this on your home system but you will need license for production deployments.

VBOXVER=`vboxmanage -v | cut -dr -f1`
wget -P /tmp \
vboxmanage extpack install /tmp/Oracle_VM_VirtualBox_Extension_Pack-$VBOXVER.vbox-extpack

With installation ready, we can approach creating VM. I’ll give an example of the Ubuntu Desktop but really anything goes.

mkdir -p /srv/virtualbox

vboxmanage createvm \
--ostype Ubuntu_64 \
--basefolder "/srv/virtualbox" \
--register \
--name "Test"

vboxmanage modifyvm "Test" \
--memory 1024 \
--nic1 nat \
--vrde on --vrdeport 33890

vboxmanage createhd \
--filename "/srv/virtualbox/Test/Test.vdi" \
--format VDI --size 10240

vboxmanage storagectl "Test" \
--name "SATA" \
--add sata

vboxmanage storageattach "Test" \
--storagectl SATA --port 0 --type hdd \
--medium "/srv/virtualbox/Test/Test.vdi"

vboxmanage storageattach "Test" \
--storagectl SATA --port 15 --type dvddrive \
--medium /tmp/ubuntu-20.04-desktop-amd64.iso

If you are using firewall, make sure to allow port through. For example, iptables would need the following adjustment.

iptables -A INPUT -p tcp --dport 33890 -j ACCEPT

With this, we’re finally ready to start the virtual machine.

vboxmanage startvm "Test" --type headless

To connect to it just use any Remote Desktop application.

PS: Authentication for RDP is highly recommended and you should probably check documentation to see what works best for you.

Printing Large Objects on 3D Printer

3D printers for me are often solution in search of a problem. This is especially true in the lower price bracket where often you can spend significant amounts of time and material trying to get a perfect print. But boy, they are a lot of fun.

And when it comes to wasting time, I found getting acceptable print for objects with large footprints is really a drag. And looking up what worked for others is not as straightforward as giving a recipe since setup will depend on the printer, filament, slicer, and bunch of other small variables. I will share here what works for me 90% of the time on Ender 3 Pro with MatterHackers Build PLA and using Cura as a slicer.

When it comes to Cura, I love Standard Quality setting. While Ender 3 can perform well with the higher quality for small items, quite often printing with less than 0.2 mm extrusion is finicky and requires quite a lot of care. With 0.2 mm you won’t necessary get the best it can offer but it usually won’t cause any issues either.

Having a heated bed is pretty much mandatory for relaxed printing. I just set Build Plate Temperature to 60 °C for PLA. There is actually some room to go higher but going too wild will often make bottom layers unevenly shrink as they cool down.

Extrusion temperature depends on the filament and every manufacturer has a preferred range. For MatterHackers Build PLA that range is 180-220 °C. I set Printing Temperature smack in the middle to 205 °C. I set Printing Temperature Initial Layer a bit higher to 215 °C as it really helps with the initial adhesion.

While fan is awesome I find it cools stuff way too fast at the full speed. I just set Fan Speed to 50% and that seems to work nicely. Of course Initial Fan Speed is left at 0%.

For bigger objects I always change Build Plate Adhesion Type to use Raft. While smaller objects work just fine with Skirt, I often left large print overnight only to find them messed-up in the morning because edges started lifting off. You can also avoid this by adjusting temperature, using better surface, or some type of adhesion. However, I prefer the raft to any of those alternatives as it works even when the other settings are a bit off.

I also like to increase Initial Layer Height to 0.4 mm as it helps with removing model from the raft but that comes at a cost of slightly rougher bottom layer. I find that a worthwhile exchange. If PLA is misbehaving and I get “stringy” bottom, I might also increase Initial Layer Line Width to 150% or 200% but mostly I leave it at 100%.

From larger objects I expect a bit more of structural stability so I change Infill Pattern to Gyroid with Infill Density of 40%. I usually don’t go higher but, if I don’t need print to be sturdy or object is a bit smaller, I might go as low as 10%.

Some models might require supports and here I found Cura settings way too conservative. I always increase Support Speed to 50 mm (matching my print speed) and I lower Support Density to 10% so removal is easier. With Ender 3 Pro you can quite often go more aggressive but I found 50 mm works so well with whatever I throw at it that I don’t bother going higher.

As matter of preference I set Combing Mode to Off as I prefer “rougher” look of the final layer. I also set Z Hop When Retracted as it seems to work better with thin walls.

All these settings, while not perfect for any particular print, fail me so rarely that I have them set as a default and change them only if there is something special I am going for.

Manually Installing Ubuntu 20.04 on Surface Go

I love ZFS but it definitelly doesn’t fit every situation. One situation it doesn’t fit is Surface Go. Not only device is low on RAM but it’s also low on disk space. And ZFS really hates when it doesn’t have enough disk space.

Now, one can install Ubuntu perfectly well without any shenanigans. Just follow a guide on how to boot install USB and you’re golden. But I like my installations to be a bit special. :)

After booting into Ubuntu desktop installation one needs a root prompt. All further commands are going to need root credentials anyhow.

sudo -i

The very first step should be setting up a few variables – disk, pool, host name, and user name. This way we can use them going forward and avoid accidental mistakes. Just make sure to replace these values with ones appropriate for your system.


Disk setup is really minimal .

blkdiscard $DISK

sgdisk --zap-all $DISK

sgdisk -n1:1M:+63M -t1:EF00 -c1:EFI $DISK
sgdisk -n2:0:+448M -t2:8300 -c2:Boot $DISK
sgdisk -n3:0:0 -t3:8309 -c3:Ubuntu $DISK

sgdisk --print $DISK

I usually encrypt just the root partition as having boot partition unencrypted does offer advantages and having standard kernels exposed is not much of a security issue.

cryptsetup luksFormat -q --cipher aes-xts-plain64 --key-size 512 \
--pbkdf pbkdf2 --hash sha256 $DISK-part3

Since crypt device name is displayed on every startup, for Surface Go I like to use host name here.

cryptsetup luksOpen $DISK-part3 $HOST

Now we can prepare all needed partitions.

yes | mkfs.ext4 /dev/mapper/$HOST
mkdir /mnt/install
mount /dev/mapper/$HOST /mnt/install/

yes | mkfs.ext4 $DISK-part2
mkdir /mnt/install/boot
mount $DISK-part2 /mnt/install/boot/

mkfs.msdos -F 32 -n EFI $DISK-part1
mkdir /mnt/install/boot/efi
mount $DISK-part1 /mnt/install/boot/efi

To start the fun we need debootstrap package.

apt install --yes debootstrap

And then we can get basic OS on the disk. This will take a while.

debootstrap focal /mnt/install/

Our newly copied system is lacking a few files and we should make sure they exist before proceeding.

echo $HOST > /mnt/install/etc/hostname
sed "s/ubuntu/$HOST/" /etc/hosts > /mnt/install/etc/hosts
sed '/cdrom/d' /etc/apt/sources.list > /mnt/install/etc/apt/sources.list
cp /etc/netplan/*.yaml /mnt/install/etc/netplan/

If you are installing via WiFi, you might as well copy your wireless credentials:

mkdir -p /mnt/install/etc/NetworkManager/system-connections/
cp /etc/NetworkManager/system-connections/* /mnt/install/etc/NetworkManager/system-connections/

Finally we’re ready to “chroot” into our new system.

mount --rbind /dev /mnt/install/dev
mount --rbind /proc /mnt/install/proc
mount --rbind /sys /mnt/install/sys
chroot /mnt/install \
bash --login

Let’s not forget to setup locale and time zone.

locale-gen --purge "en_US.UTF-8"
update-locale LANG=en_US.UTF-8 LANGUAGE=en_US
dpkg-reconfigure --frontend noninteractive locales

dpkg-reconfigure tzdata

Now we’re ready to onboard the latest Linux image.

apt update
apt install --yes --no-install-recommends linux-image-generic linux-headers-generic

Followed by boot environment packages.

apt install --yes initramfs-tools cryptsetup keyutils grub-efi-amd64-signed shim-signed tasksel

Since we’re dealing with encrypted data, we should auto mount it via crypttab. If there are multiple encrypted drives or partitions, keyscript really comes in handy to open them all with the same password. As it doesn’t have negative consequences, I just add it even for a single disk setup.

echo "$HOST UUID=$(blkid -s UUID -o value $DISK-part3) none \
luks,discard,initramfs,keyscript=decrypt_keyctl" >> /etc/crypttab
cat /etc/crypttab

To mount boot and EFI partition, we need to do some fstab setup too:

echo "UUID=$(blkid -s UUID -o value /dev/mapper/$HOST) \
/ ext4 noatime,nofail,x-systemd.device-timeout=5s 0 1" >> /etc/fstab
echo "PARTUUID=$(blkid -s PARTUUID -o value $DISK-part2) \
/boot ext4 noatime,nofail,x-systemd.device-timeout=5s 0 1" >> /etc/fstab
echo "PARTUUID=$(blkid -s PARTUUID -o value $DISK-part1) \
/boot/efi vfat noatime,nofail,x-systemd.device-timeout=5s 0 1" >> /etc/fstab
cat /etc/fstab

Now we get grub started and update our boot environment.

KERNEL=`ls /usr/lib/modules/ | cut -d/ -f1 | sed 's/linux-image-//'`
update-initramfs -u -k $KERNEL

Grub update is what makes EFI tick.

grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=Ubuntu \
--recheck --no-floppy

Finally we install out GUI environment. I personally like ubuntu-desktop-minimal but you can opt for ubuntu-desktop. In any case, it’ll take a considerable amount of time.

tasksel install ubuntu-desktop-minimal

Short package upgrade will not hurt.

apt dist-upgrade --yes

The only remaining task before restart is to create the user, assign a few extra groups to it, and make sure its home has correct owner.

sudo adduser --disabled-password --gecos '' $USER
usermod -a -G adm,cdrom,dip,lpadmin,plugdev,sudo $USER
passwd $USER

As install is ready, we can exit our chroot environment.


And unmount our disk:

umount /mnt/install/boot/efi
umount /mnt/install/boot
umount /mnt/install

After the reboot you should be able to enjoy your installation.


PS: If you are doing install on normal desktop, check similar ZFS-based installation guide.

On-screen Measurement Grid

When shopping for gloves it is really useful to know hand size. Normal person would find a measuring tape. Me? I decided to make a on-screen measurement grid.

First step is just figuring pixel dimensions in relation to my diagonal. A bit of Pythagorean later, the following proved to be what I needed:

var diagonal = 15.6;
var pixelWidth = 1920;
var pixelHeight = 1080;

var ratio = 16.0 / 9;
var height = diagonal / Math.Sqrt(ratio * ratio + 1);
var width = height * ratio; // not really needed

var pixelsPerInch = pixelHeight / height;

var inchWidth = pixelWidth / pixelsPerInch;
var inchHeight = pixelHeight / pixelsPerInch;

Notice here that I am using “witchcraft” units instead of millimetres as I normally would. Reason for that is simple – I was buying gloves on USA site and all measurements were in inches. My screen measurement was also in inches. With both these units being the same, it made no sense to convert into something else first.

Also notice I am only using height to determine pixel density thus making an assumption pixel is a perfect square. Unless you are dealing with something really strange, this assumption is perfectly good.

With these basic calculations done, it’s time to draw. Notice I have a few multiplications/divisions by 4 hear – the only reason for these is due to me finding inch-based grid way too coarse. A quarter-inch grid gives a bit more flexibility here.

using (var bmp = new Bitmap(pixelWidth, pixelHeight))
using (var g = Graphics.FromImage(bmp))
g.FillRectangle(Brushes.White, 0, 0, pixelWidth, pixelHeight);

for (var i = 0; i < (int)Math.Ceiling(inchWidth) * 4; i++) {
var pen = (i % 4 == 0) ? Pens.Black : Pens.LightBlue;
var x = (int)(i / 4.0 * pixelsPerInch);
g.DrawLine(pen, x, 0, x, pixelHeight);

for (var i = 0; i < (int)Math.Ceiling(inchHeight) * 4; i++)
var pen = (i % 4 == 0) ? Pens.Black : Pens.LightBlue;
var y = (int)(i / 4.0 * pixelsPerInch);
g.DrawLine(pen, 0, y, pixelWidth, y);

I made this image my background and voila! Now I can measure my hand without ever leaving my chair.