niels / Blog / #classicpress,#migration


Update: all content has been migrated. Will keep this post around to test theme and plugins.

While content is being migrated, you can find the old blog at

Testing my new Markdown editor plug-in for ClassicPress.

Does it work?

  1. Yes
  2. No
  3. Not sure

More test.

echo "code test";

testing, 1, 2, 3. 4.

niels / Software / #linux,#ubuntu

Preferring IPv4 over IPv6

As explained in my previous post, I get my IPv6 subnet through a tunnelbroker. This is a great way to use IPv6 when your Internet provider does not yet support it.

There’s a downside though: the tunnel adds overhead in terms of both latency and a lower MTU. It also introduces an additional point where congestion or failure can occur.


Most Linux apps use the getaddrinfo function to obtain the IP address for a hostname. It is this function that causes your apps to prefer IPv6 over IPv4.

Fortunately it comes with a configuration file, which we can modify to have it prefer IPv4 over IPv6.

Open the configuration file by entering:

sudo vi /etc/gai.conf

(replace vi with your preferred editor)

Then scroll down to the following section:

#precedence  ::1/128       50
#precedence  ::/0          40
#precedence  2002::/16     30
#precedence ::/96          20
#precedence ::ffff:0:0/96  10
#    For sites which prefer IPv4 connections change the last line to
#precedence ::ffff:0:0/96  100

Uncomment that last line, so it looks like this:

#precedence  ::1/128       50
#precedence  ::/0          40
#precedence  2002::/16     30
#precedence ::/96          20
#precedence ::ffff:0:0/96  10
#    For sites which prefer IPv4 connections change the last line to
precedence ::ffff:0:0/96  100

Save the file.

That’s it! Most apps will now prefer IPv4 over IPv6.

niels / Hardware / #linux,#router and UDM Pro

I’m using IPv4 and IPv6 ranges supplied by (Available in The Netherlands only.)

Unfortunately the UDM Pro has no UI for setting up the required tunnels. I had to write a script, which I’m sharing below.

My Use Case

The script configures dnsmasq to distribute the IPv6 subnet using SLAAC. If you prefer, you could comment this section out and use the DHCPv6 server in the UI instead.

It also omits the traditional use of an IPv4 network-, gateway- and broadcast- address, allowing us to use all 8 IP addresses for clients or servers.


The instructions assume you have udm-utilities installed.

Disable IPv6 in UI

To prevent unexpected behavior created by the UI, we disable IPv6 on both WAN and LAN interfaces.


Open the Network application and navigate to Internet. Now edit your WAN interface to disable its IPv6 Connection.


Open the Network application and navigate to Settings > Networks. Edit the relevant network,
scroll down to IPv6, and switch it to Disable.

niels / Blog / #linux

Tweaking Elementary OS

Elementary OS has been my favorite desktop distribution for the past 2 years or so. Its out-of-the-box experience is very close to my preferred setup. Nothing is perfect though. Below are the tweaks I apply when installing Elementary OS 6.


The default Elementary OS install uses an encrypted ext4 partition. If you want to use encrypted btrfs, you’re out of luck. The Custom Install will let you use btrfs, but not encrypted btrfs. My work-around is very simple, if a bit tedious.

Default install

First run the default install like you would normally do. This erases your disk and sets you up with an encrypted ext4 partition.

Open Encryption

Boot the installer once more. This time, choose a Custom Install. Choose to manage your disks, which fires up gparted. Use gparted to Open Encryption on the encrypted partition. (You could do this on the command-line, but this is quick and easy.)

Convert to btrfs

Exit gparted and go backwards in the installer. Now choose to enter the Demo mode and open a Terminal.

Enter the following command to convert the ext4 root to btrfs:

sudo btrfs-convert /dev/mapper/data-root

This takes only a minute or so on a clean install.

Update /etc/fstab

Before rebooting, we’ll need to update /etc/fstab. To access the file, we first mount our newly converted btrfs filesystem:

sudo mount /dev/mapper/data-root /mnt

We then determine the new blkid of the btrfs filesystem:

sudo blkid /dev/mapper/data-root

It will show you two ID’s, you need the first one:

sudo blkid /dev/mapper/data-root 
[sudo] password for niels:         
/dev/mapper/data-root: UUID="dba6ca21-79e0-49c9-b889-c37d2ccb446a" UUID_SUB="27c5452a-7878-4357-8f95-596a08cab55b" TYPE="btrfs"

Now use your favorite text editor to update /etc/fstab:

PARTUUID=48e067c9-0a5e-4ad7-acb6-2313973188d6  /boot/efi  vfat  umask=0077  0  0
UUID=da66e7aa-9162-4550-b527-514a045759b0  /boot  ext4  noatime,errors=remount-ro  0  0
UUID=dba6ca21-79e0-49c9-b889-c37d2ccb446a  /  btrfs  defaults,noatime,autodefrag,compress  0  0
/dev/mapper/data-swap  none  swap  defaults  0  0

Two things have been updated:

  1. The UUID, as obtained with blkid.
  2. The options changed from noatime,errors=remount-ro to defaults,noatime,autodefrag,compress


You should now be able to reboot into your Elementary OS install.

niels / Blog / #linux

Magic Trackpad 2 with Xorg

Not quite happy with the solution described in my original post, I took another look at the problem and found:

  1. Xorg defaults to the libinput driver (just like Wayland), not the synaptics driver.
  2. The libinput driver complained about a parse error in /usr/share/libinput/50-system-lenovo.quirks.

I ignored the parse error the first time around because I do not have any lenovo input devices on this computer and assumed the parse error would not affect other devices. However, a parse error in 1 quirks file, does cause all quirks files to be ignored.

Deleting the faulty quirks file resolved the issue. My Magic Trackpad 2 now functions properly with Xorg.

Original post

I’ve been using an Apple Magic Trackpad 2~~ with my Ubuntu desktop for the past two years or so.
When using Wayland this works perfectly out-of-the-box.

Not so much with Xorg. It detects the trackpad and loads the driver, but the cursor won’t move unless I press the trackpad at the same time.

Creating /etc/X11/xorg.conf.d/10-touchpad.conf
fixed this:

Section "InputClass"
    Option "FingerLow" "2"
    Option "FingerHigh" "2"

If you have more – potentially conflicting – input devices you’ll want to expand that to:

Section "InputClass"
    Identifier "Apple Magic Trackpad"
    MatchIsTouchpad "on"
    MatchUSBID "05ac:0265"
    Driver "synaptics"
    Option "FingerLow" "2"
    Option "FingerHigh" "2"

Use lsusb to check that the USBID is correct for your trackpad:

❯ lsusb
Bus 001 Device 005: ID 05ac:0265 Apple, Inc. Magic Trackpad 2

niels / Blog / #jigsaw,#php


Whoops. I accidentally wiped the VM running my WordPress blog.

Rather than restore the WordPress blog I decided to go with Jigsaw this time. Jigsaw is one of many static site generators, or as the cool kids say, a way to use Jamstack.


What makes a Jamstack site more than just a regular static website is its use of
Javascript and API services to add dynamic features to your website.

The JAM in Jamstack stands for Javascript, API and Markup

I’m not a fan of microservice architectures like Jamstack as it tends to add complexity, cost and a dependency on potentially unreliable 3rd-parties.

That said, I don’t need much functionality on my blog. This is JAMstack with very little J and no A. I may add comments at some point, but only if I can self-host them.


The founding developer of WordPress, Matt Mullenweg, has this to say about it:

JAMstack is a regression for the vast majority of the people adopting it

Matt is biased, of course, but I agree with his arguments. Read them in more detail at

niels / Hardware / #laptop,#legion,#lenovo,#ubuntu

Ubuntu on the Legion 5 Pro (2021)

Heads up: I’m not a gamer. A combination of specs, pricing, availability and urgency led me to purchase this laptop for productivity reasons. My run-through may not cover all aspects needed for gaming.

As my work depends on this laptop, I did purchase the additional on-site support.

Keyboard Shortcuts

If the RGB show on the keyboard annoys you, use Fn+SPACE to select a less annoying mode for now.
Also: Fn+L to toggle the Legion logo, Fn+Q to toggle thermal profile. These shortcuts work on both Windows and Linux.


I expect support to want to deal with Windows only, so I left Windows installed.

Even if you decide to remove Windows, I recommend you set it up first and use it to apply any BIOS or firmware updates prior to installing Ubuntu. (As it turns out, my laptop already shipped with the latest BIOS version.)

The factory Windows install uses about 60GB, so I shrunk the Windows-SSD down to 75GB. You can use the Disk Management tool to do so.


Shutdown the laptop and restart it while pressing F2. This gets you into the BIOS. I made the following changes:

  • Graphic Device: Dynamic Graphics (you will need to use nomodeset in GRUB if you don’t do this)
  • Boot > PXE Boot to LAN: Disabled;

That’s it. I left Secure Boot enabled. Ubuntu and Ubuntu based Linux distributions have no problem with it.


Install Ubuntu. How to install Ubuntu has been well documented, I won’t repeat it here.

You’ll notice that while the brightness function keys appear to work they do not actually change the brightness. You can fix this by editing /etc/default/grub and adding the amdgpu.backlight parameter:


Run update-grub, reboot, and things will work.

Battery Conservation Mode

Battery conservation mode prevents your laptop battery from charging fully and keeps it around 60% instead. This should aid the longevity of the battery.

For convenience I created a systemd unit called /etc/systemd/system/battery-conservation-mode.service
with the following content:

Description=Battery Conservation Mode

ExecStart=/usr/bin/bash -c 'echo 1 > "/sys/devices/pci0000:00/0000:00:14.3/PNP0C09:00/VPC2004:00/conservation_mode"'
ExecStop=/usr/bin/bash -c 'echo 0 > "/sys/devices/pci0000:00/0000:00:14.3/PNP0C09:00/VPC2004:00/conservation_mode"'


Which we then activate using:

systemctl daemon-reload 
systemctl start battery-conservation-mode 
systemctl stop battery-conservation-mode 
systemctl enable battery-conservation-mode 

Function Lock

Maybe you never use the F1, F2,.. keys and just want them to function them as permanent media keys (for volume control, etc.) instead. You can do this by enabling the Fn Lock:

echo 1 > "/sys/devices/pci0000:00/0000:00:14.3/PNP0C09:00/VPC2004:00/fn_lock"

Just like the battery conservation mode, you could put this in a systemd unit to enable it automatically at start-up.

File System

Ubuntu comes with periodic SSD trimming out of the box. No need to enable that yourself. If you use encrypted partitions like I do, Ubuntu also enabled discard in crypttab automatically. Some changes I made to fstab:

  • Added the ssd option for my btrfs file systems.
  • Added the noatime option to both btrfs and ext4 file systems.

niels / Software / #linux,#snap,#wayland,#zsh

Snap apps with Wayland and Zsh

There’s are two longstanding issues using snap apps in Ubuntu. Two issues that still exist in Ubuntu 20.04 beta.

Snap apps don’t show up in Gnome’s Activities view when using Wayland instead of Xorg.
Snap apps cannot be started from the command-line when using zsh instead of bash.
Looking into this, I ran into numerous discussions on both topics. Most notably:


The zsh work-around mentioned in these and other discussions works very well. Just add the following line to your .zshrc. (Adding it to /etc/zsh/zshrc should work as well.)

emulate sh -c 'source /etc/profile.d/'

When using bash the scripts in /etc/profile.d/ are sourced automatically. Zsh does not bother with them, unless we instruct it to.


Unfortunately the work-arounds for Wayland did not work for me. They failed to convince Wayland and Gnome to look in snap’s applications folder.

The following line links snap’s applications folder into the Ubuntu’s main applications folder.

sudo ln -s /var/lib/snapd/desktop/applications/ /usr/share/applications/snap

Unlike all the other work-arounds out there, there’s no need to logout and log back in. This works instantly.

niels / Blog / #wordpress

Cleaner Theme

Update 2021-10-31: I’m no-longer using WordPress or the theme mentioned below.

Changed to a nice clean WordPress theme that uses Tailwind CSS. Based on the wp-tailwind starter theme by freeshifter. (Thank you!)

Will share my changes on Github shortly.

niels / Code / #laravel,#nova,#php

Managing languages with Laravel Nova

The real title of this post should be Managing many languages with Laravel Nova. You’ll find out why, very soon.

Using Spatie’s Laravel Translatable and Nova Translatable packages managing multiple languages with Laravel Nova is easy. Follow their instructions and it probably takes you less than 20 minutes to make your model translatable. I will not repeat those steps here.

Many languages

What’s not so easy is using the Nova component to manage a larger number of languages. When you edit your model in Nova it shows each translatable field in every language that you configured. This gets messy (and slow) real quick.

Unfortunately there’s no good way to deal with so many fields in the current version of Nova. There are 3rd-party components that implement tabs etc., but nothing that could be considered the de-facto standard and long-term way of implementing this.

If Nova gains the ability to better structure a long form natively, we’d probably start leveraging that in a new major version of the package.
Spatie’s Github page for Nova Translatable

Spatie is probably right to wait for Nova to provide a native solution. Until then, we have the solution provided below. In fact, I believe the solution below will be preferred even when Nova solves this issue.

Ideally we see only two languages: the language we’re working on and the primary language of the website. (Assuming all content is initially created in a single primary language.) Turns out this is fairly easy to do in Laravel.

Locale switcher

Before we continue: I recommend you to install a browser add-on that allows you to switch locale and language quickly. I currently use Locale Switcher in a Chrome based browser.

The Locale Switcher helps us to see what our translators see in Nova as well as what our users see on the front-end. (If you’re translating your site, I bet you’ve installed this already.)


While Spatie’s packages do not require this, I prefer to be explicit and define which languages the site supports. In our config/app.php we find the locale definition and add one for locales:

| Application Locale Configuration
| The application locale determines the default locale that will be used
| by the translation service provider. You are free to set this value
| to any of the locales which will be supported by the application.

'locale' => 'en',
'locales' => [ 'en', 'zh', 'nl', 'ja' ],

In my case I’m allowing English, Chinese, Dutch and Japanese.

Next is our Nova resource. In my Post resource at app/Nova/Post.php I have two fields that I want to make translatable: Title and Content.


Which we make translatable by wrapping them:


So far so good. This is what you probably had already.
Unfortunately this results in every Nova user seeing the Title and Content field in all of the languages we are using. Four in my example, potentially dozens in your case.

We need to tell the Translatable field which languages we want to see exactly. In my case that’s English, the primary language of the website, as well as the native (or Locale Switcher) language of the user.

To be more precise: I want the primary language,
as well as the native language if the native language is one of the languages we support. We accomplish this by putting the two languages in an array which we then feed to the locales method on Translatable.

$locales = [ 'en' ];
if(app()->getLocale() != 'en' && in_array(app()->getLocale(), Config::get('app.locales')))
  $locales = [app()->getLocale(), 'en'];

If you have only one translatable model you can put this in its Nova resource. Otherwise, you may want to find a more suitable location.

Now we add the locales method to Translatable and end up with something like this:

* Get the fields displayed by the resource.
* @param  \Illuminate\Http\Request  $request
* @return array
  public function fields(Request $request)
  $locales = [ 'en' ];
  if(app()->getLocale() != 'en' && in_array(app()->getLocale(), Config::get('app.locales')))
  $locales = [app()->getLocale(), 'en'];

  return [


That’s it! Our user will now only see English + their native language. (If their native language is not English.)

You may be wondering: if the user submits only 2 languages when making changes, what happens to all the other languages in the database? Good news: nothing happens to them. They’re completely safe.


I hardcoded the primary language to English in my example. You can replace the three occurrences of ‘en’ with Config::get(‘app.locale’) to properly honour the configuration made in app.php.