Installing UEFI ZFS Root on Ubuntu 19.04

There is a newer version of this guide for Ubuntu 19.10.


As rumors of Ubuntu 19.04 including ZFS installer proved to be a bit premature, I guess it’s time for a slight adjustment to my previous ZFS instructions.

Again, all this is just a derivation on ZFS-on-Linux project’s instruction for older version.

As before, we first need to get into root prompt:

sudo -i

Followed by getting a few basic packages ready:

apt-add-repository universe
apt update
apt install --yes debootstrap gdisk zfs-initramfs

Disk setup is quite simple with only two partitions:

sgdisk --zap-all                      /dev/disk/by-id/^^ata_disk^^

sgdisk -n3:1M:+511M -t3:8300 -c3:Boot /dev/disk/by-id/^^ata_disk^^
sgdisk -n2:0:+128M  -t2:EF00 -c2:EFI  /dev/disk/by-id/^^ata_disk^^
sgdisk -n1:0:0      -t1:8300 -c1:Data /dev/disk/by-id/^^ata_disk^^

sgdisk --print                        /dev/disk/by-id/^^ata_disk^^

I believe full disk encryption should be a no-brainer so of course we set up LUKS:

cryptsetup luksFormat -q --cipher aes-xts-plain64 --key-size 512 \
    --pbkdf pbkdf2 --hash sha256 /dev/disk/by-id/^^ata_disk^^-part1
cryptsetup luksOpen /dev/disk/by-id/^^ata_disk^^-part1 system

Creating ZFS stays the same as before:

zpool create -o ashift=12 -O atime=off -O canmount=off -O compression=lz4 \
      -O normalization=formD -O xattr=sa -O mountpoint=none system /dev/mapper/system
zfs create -o canmount=noauto -o mountpoint=/mnt/system/ system/root
zfs mount system/root

Getting basic installation on our disks follows next:

debootstrap disco /mnt/system/
zfs set devices=off system
zfs list

And then we setup EFI boot partition:

yes | mkfs.ext4 /dev/disk/by-id/^^ata_disk^^-part3
mount /dev/disk/by-id/^^ata_disk^^-part3 /mnt/system/boot/

mkdir /mnt/system/boot/efi
mkfs.msdos -F 32 -n EFI /dev/disk/by-id/^^ata_disk^^-part2
mount /dev/disk/by-id/^^ata_disk^^-part2 /mnt/system/boot/efi

We need to ensure boot partition auto-mounts:

echo PARTUUID=$(blkid -s PARTUUID -o value /dev/disk/by-id/^^ata_disk^^-part3) \
    /boot ext4 noatime,nofail,x-systemd.device-timeout=5s 0 1 >> /mnt/system/etc/fstab
echo PARTUUID=$(blkid -s PARTUUID -o value /dev/disk/by-id/^^ata_disk^^-part2) \
    /boot/efi vfat noatime,nofail,x-systemd.device-timeout=5s 0 1 >> /mnt/system/etc/fstab
cat /mnt/system/etc/fstab

Before we start using anything, we should prepare a few necessary files:

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

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

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

With chroot we can get the first taste of our new system:

mount --rbind --make-rslave /dev  /mnt/system/dev
mount --rbind --make-rslave /proc /mnt/system/proc
mount --rbind --make-rslave /sys  /mnt/system/sys
chroot /mnt/system/ /bin/bash --login

Now we can update our software:

apt update

Immediately followed with locale and time zone setup:

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 install Linux image and basic ZFS boot packages:

apt install --yes --no-install-recommends linux-image-generic
apt install --yes zfs-initramfs

Since we’re dealing with encrypted data, our cryptsetup should be also auto mounted:

apt install --yes cryptsetup keyutils

echo "system UUID=$(blkid -s UUID -o value /dev/disk/by-id/^^ata_disk^^-part1) \
    none luks,discard,initramfs,keyscript=decrypt_keyctl" >> /etc/crypttab

cat /etc/crypttab

Now we get grub started:

apt install --yes grub-efi-amd64

And update our boot environment again (seeing errors is nothing unusual):

update-initramfs -u -k all

And then we finalize our grup setup:

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

Finally we get the rest of desktop system:

apt-get install --yes ubuntu-desktop samba linux-headers-generic
apt dist-upgrade --yes

We can omit creation of the swap dataset but I always find it handy:

zfs create -V 4G -b $(getconf PAGESIZE) -o compression=off -o logbias=throughput \
    -o sync=always -o primarycache=metadata -o secondarycache=none system/swap
mkswap -f /dev/zvol/system/swap
echo "/dev/zvol/system/swap none swap defaults 0 0" >> /etc/fstab
echo RESUME=none > /etc/initramfs-tools/conf.d/resume

If one is so inclined, /home directory can get a separate dataset too:

rmdir /home
zfs create -o mountpoint=/home system/home

Only remaining thing before restart is to create user:

adduser ^^user^^
usermod -a -G adm,cdrom,dip,lpadmin,plugdev,sambashare,sudo ^^user^^
chown -R ^^user^^:^^user^^ /home/^^user^^

As install is ready, we can exit our chroot environment and reboot unmount our new environment. If unmount fails, just repeat it until it doesn’t. :)

exit
umount -R /mnt/system

Finally we can correct root’s mount point and reboot:

zfs set mountpoint=/ system/root
reboot

Assuming nothing went wrong, your UEFI system is now ready.


[2019-10-27: Added --make-rslave]


PS: There are versions of this guide using the native ZFS encryption for other Ubuntu versions: 21.10 and 20.04

PPS: For LUKS-based ZFS setup, check the following posts: 20.04, 19.10, and 18.10.

Using Visual Studio Code for .NET Framework Projects in C#

As Visual Studio Code is the only completely free member of Visual Studio family now that Visual Studio Express is gone (and no, Community edition doesn’t count as a proper replacement), I wanted to have some of my projects debuggable from within it. While .NET Core works out of box, it takes a bit of effort to setup a full .NET Framework project.

First step is, of course, hunt on Stack Overflow. If you are lucky you might find a guide how to do it that actually works perfectly once you do a few modifications to your project. However, depending on your project, you might not need to go that far.

To start, destination computer will require something that can build your projects. Since installing Visual Studio makes the whole exercise a bit pointless, go and download Build Tools for Visual Studio 2019.

To your Visual Studio Code workspace add familiar .vscode directory and add tasks.json that will build your project. Based on a few of my .NET Framework projects, I found the following common denominator to do wonders (of course, change name of solution and exact path to your source and binaries):

{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "build",
      "dependsOn": [ "clean" ],
      "type": "process",
      "windows": {
        "command": "C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\MSBuild\\Current\\Bin\\amd64\\msbuild.exe",
        "args": [
          "-p:Configuration=Debug;DebugType=portable;PlatformTarget=x64",
          "^^Solution.sln^^"
        ]
      },
      "options": { "cwd": "${workspaceFolder}/^^Source^^/" },
      "group": "build"
    },
    {
      "label": "clean",
      "type": "shell",
      "windows": {
        "command": "DEL",
        "args": [ "^^.\\Binaries\\*^^" ]
      }
    }
  ]
}

Here you can already see the trick - we’re adding parameters as argument to MSBuild instead of modifying the project file. I found that most projects (definitely all I tried) are only missing portable debug type and x64 as a platform target (I am still sucker for Any CPU). If it doesn’t work for you, go and check the original article to see if something else is missing.

And the last step is placing launch.json into .vscode directory (again, adjust names):

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": ".NET Framework Launch",
      "type": "clr",
      "request": "launch",
      "preLaunchTask": "build",
      "program": "^^Program.exe^^",
      "args": [],
      "cwd": "${workspaceFolder}/^^Binaries^^/",
      "stopAtEntry": false
    }
  ]
}

This should be sufficient to get you debugging in a pinch.

Using InnoSetup to Convert LF to CRLF For Read-Only Files

My application setup was tasked with installing README.txt file. As Windows versions before 10 have trouble dealing with LF characters, I needed a way to convert line endings to CRLF.

I thought that was easy, I just needed to call AfterInstall:

Source: "..\README.md"; DestDir: "{app}"; DestName: "ReadMe.txt"; Attribs: readonly; AfterInstall: AdjustTextFile;

and then call a simple function:

[code]
procedure AdjustTextFile();
var
  path : String;
  data : String;
begin
  path := ExpandConstant(CurrentFileName)
  LoadStringFromFile(path, data);
  StringChangeEx(data, #10, #13#10, True);
  SaveStringToFile(path, data, False);
end;

However, this didn’t work. While code seemingly worked without an issue, it wouldn’t save to read-only file. Yep, our read-only flag was an issue.

While this meant I had to change my InnoSetup project to install file without read-only attribute set, nothing prevented me setting attribute (after I wrote corrected line endings) using a bit of interop magic:

[code]
function SetFileAttributes(lpFileName: string; dwFileAttributes: LongInt): Boolean;
  external 'SetFileAttributesA@kernel32.dll stdcall';

procedure AdjustTextFile();
var
  path : String;
  data : String;
begin
  path := ExpandConstant(CurrentFileName)
  LoadStringFromFile(path, data);
  StringChangeEx(data, #10, #13#10, True);
  SaveStringToFile(path, data, False);
  SetFileAttributes(path, 1);
end;

Why You Should Update GPS Firmware

Illustration

Assuming you survived the great GPS rollover, you might wonder if updating your GPS firmware makes any sense at all. It’s obvious you might want new maps. But firmware? Why do you need a new one if everything works just fine.

Real answer is that you probably need not bother. Again, assuming everything works fine, there is no navigation reason for firmware upgrade. Navigation will work equally well whether you have fresh firmware or one that’s a few years old.

However, if you care about time keeping, you will definitely need to update your GPS once in a while. And it’s not because of rollover - that issue is sorted now with the new 13-bit week counter and it’s up to your grandchildren to bother with that. Nope, it’s darn leap seconds.

Leap seconds were created to account for uneven rotation of this big rock we call Earth. As such they are currently beyond our capability to calculate in advance. Once in a while astronomers look upon the skies and decide if leap second is needed. One cannot know in advance when this will happen.

AS GPS time has no concept of a leap second, firmware is what adjusts GPS time to UTC and then later to time zones we all deal with. Guess what, if you have old firmware, your GPS will adjust wrongly and thus something you believe to be a correct time will be off by a second or two (or 27 if you were really lazy).

I know it’s not a breaking deal for a vast majority of population but I simply find it rather unnerving that you would intentionally make your GPS show the wrong time.

Update darn firmware to avoid having your existence out of sync with rest of the world. :)

Installing QT Creator on Ubuntu

Illustration

Those wanting to play with QT will probably end up installing QT Creator package. While, strictly speaking, you can make QT applications also without it, a lot of things get much simpler if you use it - most noticeable example being GUI design.

After download, installation is really simple:

chmod +x ~/Downloads/qt-unified-linux-x64-3.0.6-online.run
sudo ~/Downloads/qt-unified-linux-x64-3.0.6-online.run

However, I wouldn’t write this post if it was so easy.

Indeed, in Ubuntu, this will seemingly install everything until you try to make a project in QT Creator. There you’ll be stopped with “No valid kits found.” message. Cause of this is missing QT version selection as None will be only option on a clean system. To get offered more, a few additional packages are required:

sudo apt-get install --yes qt5-default qtdeclarative5-dev libgl1-mesa-dev

Once these packages are installed, you’ll be able to modify your Desktop kit definition and select correct version. Finally, you can finish creating the project and get onto coding.