Manually Installing Ubuntu 21.04 on Surface Go

Now, one can install Ubuntu perfectly well onto Surface Go 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, host, 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. Please note you can actually reduce size of boot partition but that might get you in trouble if you start playing with low latency kernel. Some extra space will help here.

blkdiscard $DISK

sgdisk --zap-all $DISK
sgdisk -n1:1M:+63M -t1:EF00 -c1:EFI $DISK
sgdisk -n2:0:+640M -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 256 \
--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 update ; apt install --yes debootstrap

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

debootstrap $(basename `ls -d /cdrom/dists/*/`) /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 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.

mem_sleep_default=deep\"/" /etc/default/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.

add-apt-repository universe
apt update ; 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.

adduser --disabled-password --gecos '' $USER
usermod -a -G adm,cdrom,dip,lpadmin,plugdev,sudo $USER
echo "$USER ALL=NOPASSWD:ALL" > /etc/sudoers.d/$USER
passwd $USER

Before finishing it up, I like to install Surface Go WiFi and backlight tracer packages. This will allow for usage of wireless once we boot into installed system and for remembering light level between plugged/unplugged states.

wget -O /tmp/surface-go-wifi_amd64.deb \
apt install --yes /tmp/surface-go-wifi_amd64.deb

wget -O /tmp/backlight-tracer_amd64.deb \
apt install --yes /tmp/backlight-tracer_amd64.deb

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


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.


Once booted I like to setup suspend to react on power button and and to disable automatic brightness changes.

gsettings set org.gnome.settings-daemon.plugins.power button-power 'suspend'
gsettings set org.gnome.settings-daemon.plugins.power power-button-action 'suspend'
gsettings set org.gnome.settings-daemon.plugins.power ambient-enabled 'false'
gsettings set org.gnome.mutter experimental-features "['x11-randr-fractional-scaling']"

My preferred scale factor is 150% (instead of default 200%) but you’ll need to change that in settings manually.

Own Pwned

For a while now ‘;–have i been pwned? has been providing two services. One more known is informing people of data breaches. One slightly less known is their API. My personal favorite are their password search interface. So, I was really sad to see when Troy started charging for it.

While I understand Troy’s reasons, I used this API in freeware application. And yes, I could “swallow” $3.50 this service cost but I wasn’t willing to. My freeware hobby is already costing me enough. :)

Fortunately, Troy is allowing download of password hashes so one could easily make API on their own server. So, over a weekend I did. In my OwnPwned GitHub repository there’s everything you might need to create your own verification service. But there are some differences.

First of all, this is not a substitution for ‘;–have i been pwned? API as due to dependency on the data from it, it will ALWAYS be one step behind. Also, I haven’t implemented full API as I only needed the password verification portion. Even for password verification portion, I trimmed all extra data (e.g. password breach count) and focused only on passwords themselves.

To make use of the project, you first need to download the latest password dump (ordered by hash). Once you unpack that file, you would use PwnedRepack to convert this to a binary file. I found this step necessary for both speed (as you can use binary search) and for size (as it brought 25 GB file to slightly more manageable but still huge 12 GB).

With file in hand, there are two ways to search data. The first one would be PwnedServe application that will simply expose interface on localhost. Second way forward it serving PwnedPhp on Apache server. Either way, you can do k-anonymity search over a range using the first 5 hexadecimal characters of password’s SHA-1 hash.

Something like this /range/12345/.

And yes, code is not optimized and probably will never be due to the lack of free time on my side. But it does solve my issue. Your mileage may vary.

PS: Please note, Tray Hunt has opensourced some elements of HIBP with more to come. If you need fully-featured interface that’s probably what you should keep eye on.

HexDump’s Illegal Seek

After I upgraded to Ubuntu 21.04, my TmpUsb script suddenly started reporting the following hexdump: stdin: Illegal seek.

Line causing issue was the one determining partition serial number:

dd if=/dev/sda bs=512 skip=1 count=1 | hexdump -s39 -n4 -e '4/1 "%02X"'

It seems that hexdump got a bit too stricter with its input parameters and now disallows skipping bytes in fifo stream. I haven’t investigated much but my guess is that skipping 39 bytes probably messes with its internal buffer. In any case, dd has no such issues so the same code can be done without skipping in hexdump.

dd if=/dev/sda bs=1 skip=551 count=4 | hexdump -n4 -e '4/1 "%02X"'`

The best part is that this is compatible with older versions too.

Segmentation Fault Using Threads With Static Compile

I was playing a bit with threads in C++ and all was going well. For the final compile I wanted a static binary (don’t judge me, I have my reasons ;)). Compile passed as expected but executing program resulted in Segmentation fault (core dumped) error:

g++ -pthread -static test.cpp
Segmentation fault (core dumped)

Well, these were definitely not the droids I was looking for.

Strangely enough, issue was with my thread.join() statement. Something that shouldn’t cause any issues.

It took me some time (in addition to helpful GCC ticket and StackOverflow answer) to finally come to the following compilation line:

g++ -static -pthread -D_GLIBCXX_GTHREAD_USE_WEAK=0 -std=c++0x \
-Wl,--whole-archive -lpthread -Wl,--no-whole-archive test.cpp

And now my static binary worked happily ever after.