Manually Installing Ubuntu 20.04 on Surface Go

I love ZFS but it definitely 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=/dev/disk/by-id/^^ata_disk^^
HOST=^^desktop^^
USER=^^user^^

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 \
    /usr/bin/env DISK=$DISK HOST=$HOST USER=$USER \
    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.

update-grub
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.

exit

And unmount our disk:

umount /mnt/install/boot/efi
umount /mnt/install/boot
mount | tac | awk '/\/mnt/ {print $3}' | xargs -i{} umount -lf {}

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

reboot

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

On-screen Measurement Grid

Illustration

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);
        }
    }
    bmp.Save("Background.png");
}

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

Dark Mode for QT Application

With dark mode becoming quite fast the default setup for many, I figured having it in the new QText wouldn’t hurt. And with QT it’s easy - surprisingly so. Just start with a good style (i.e. Fusion) and adjust its palette. And that’s all it took.

qApp->setStyle(QStyleFactory::create("Fusion"));

QPalette newPalette;
newPalette.setColor(QPalette::Window,          QColor( 37,  37,  37));
newPalette.setColor(QPalette::WindowText,      QColor(212, 212, 212));
newPalette.setColor(QPalette::Base,            QColor( 60,  60,  60));
newPalette.setColor(QPalette::AlternateBase,   QColor( 45,  45,  45));
newPalette.setColor(QPalette::PlaceholderText, QColor(127, 127, 127));
newPalette.setColor(QPalette::Text,            QColor(212, 212, 212));
newPalette.setColor(QPalette::Button,          QColor( 45,  45,  45));
newPalette.setColor(QPalette::ButtonText,      QColor(212, 212, 212));
newPalette.setColor(QPalette::BrightText,      QColor(240, 240, 240));
newPalette.setColor(QPalette::Highlight,       QColor( 38,  79, 120));
newPalette.setColor(QPalette::HighlightedText, QColor(240, 240, 240));

newPalette.setColor(QPalette::Light,           QColor( 60,  60,  60));
newPalette.setColor(QPalette::Midlight,        QColor( 52,  52,  52));
newPalette.setColor(QPalette::Dark,            QColor( 30,  30,  30) );
newPalette.setColor(QPalette::Mid,             QColor( 37,  37,  37));
newPalette.setColor(QPalette::Shadow,          QColor( 0,    0,   0));

newPalette.setColor(QPalette::Disabled, QPalette::Text, QColor(127, 127, 127));

qApp->setPalette(newPalette);

PS: Ok, chances are that you will need another iconset but that’s the story for some other time.

Getting Rid of Aspeed Firmware Warning

Every time I would run update-initramfs, I would receive the following warning on my SuperMicro machine:

update-initramfs -u
 update-initramfs: Generating /boot/initrd.img-5.4.0-42-generic
 W: Possible missing firmware /lib/firmware/ast_dp501_fw.bin for module ast

This issue is well-known and harmless. It’s just a hardcoded value that’s checked by ASPEED driver and, if file is not present, results in this warning.

If you are tired of it and you just want to make driver happy, create an empty file at that spot:

touch /lib/firmware/ast_dp501_fw.bin

And now the warning is gone. :)

IPv6 Prefix Delegation with NetPlan

No. That’s the short of it.

Longer version starts with me setting up my new Ubuntu server and deciding to use netplan to setup network interfaces. Nothing too big. Just a few bonds here and there. And all seemed fine until I tried to setup email and noticed the following message in syslog:

postfix/smtp[49721]: connect to alt1.aspmx.l.google.com[209.85.146.27]:25: Connection timed out
postfix/smtp[49721]: connect to aspmx5.googlemail.com[2607:f8b0:4002:c08::1b]:25: Network is unreachable

Once I double-checked, I noticed syslog was right - I only had link-local address assigned to interface. Ok, so DHCPv6 only turns on DHCP and not prefix delegation I use for my network. Easy-peasy, that’s surely matter of just turning on the correct setting…

Or finding a matching bug. Yep, prefix delegation that’s perfectly valid and well supported IPv6 address allocation method is not supported.

But ticket did solve my problem. As advised, I simply reverted to use networkd (don’t forget to use systemctl enable systemd-networkd).


PS: Here are my networkd files that work with IPv6 PD.

[NetDev]
Name=bond0
MACAddress=00:25:90:ba:31:40
Kind=bond

[Bond]
Mode=802.3ad
LACPTransmitRate=fast
MIIMonitorSec=100ms
TransmitHashPolicy=layer2+3
[Match]
Name=bond0

[DHCP]
RouteMetric=100
UseMTU=true

[Network]
DHCP=yes
IPv6PrefixDelegation=yes
IPv6PrivacyExtensions=true
LinkLocalAddressing=ipv6
ConfigureWithoutCarrier=yes
[Match]
PermanentMACAddress=00:25:90:ba:31:40

[Network]
Bond=lan
[Match]
PermanentMACAddress=00:25:90:ba:31:41

[Network]
Bond=lan