Using ditto on macOS

Recently I was investigating why this command returned a not authorised response:

spctl --verbose --assess --type execute --v ${fileName}.app

Turns out because I zipped the .app file like this:

zip -r zipped-file ${fileName}.app

This causes spctl to no longer find the correct notarized details.

Using ditto is a better solution:

ditto -c -k --sequesterRsrc --keepParent ${fileName}.app

Oh and by the way, do not use jar xf to unzip the file, instead use plain unzip or ditto if you don’t want spctl to complain.


TCP MSS clamping with iptables for IPSec tunnel

When routing traffic through a (IPSec) tunnel, an endpoint might need to do mss clamping if you are experiencing MTU issues.

For example, you are using a site-to-site VPN network, with a specific gateway as endpoint. When browsing websites through the tunnel, some websites might not load properly.

An example, using iptables to fix this problem:

iptables -A FORWARD -s -o ens4 -p tcp -m tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss 1360

This will set the mss to 1360 for traffic coming from on interface ens4.

The 1360 value depends on the situation, 1360 bytes is the overhead created by IPsec encapsulation


Multiple default gateways on Linux

Suppose you have a Linux machine doing IP forwarding (net.ipv4.ip_forward=1).

Depending on the incoming traffic, you might want to forward the packets to different gateways.

With just one gateway, you can simply add (or replace) the default gateway:
ip route add default via x.x.x.x

If you want to set a default gateway for a specific (incoming) IP range, you can add a custom routing table, using iproute2:

  • echo 200 custom >> /etc/iproute2/rt_tables
  • ip rule add from table custom
  • ip route add default via y.y.y.y table custom
  • ip route flush cache


Electron with custom Chromium build

I was looking into a way to customise the Chromium code in an Electron app. As it turns out, it’s not as difficult as it might sound, though it requires some patience (mainly because building Chromium takes a lot of time, RAM and CPU).

To get started, make sure you have installed depot_tools from Google.
It’s a good idea to provision a git cache as well:

$ export GIT_CACHE_PATH="${HOME}/.git_cache"
$ mkdir -p "${GIT_CACHE_PATH}"

Now, you can fork electron and add your Chromium patches.
It’s important to make sure you deal with whitespace and newlines as well. Electron has a couple of scripts that will generate the patch file for you.

Next, let’s configure the build:

$ mkdir electron && cd electron
$ gclient config --name "src/electron" --unmanaged[your-fork-name]/electron
$ gclient sync --with_branch_heads --with_tags

Once that completes successfully, you can indicate the build config you want to use. In our case, let’s use the release config:

$ gn gen out/Release --args="import(\"//electron/build/args/\") $GN_EXTRA_ARGS"

$ ninja -C out/Release electron

This will take a while to build, depending on your CPU, RAM and disk.

When ninja finally completes, you might want to build a package of Electron:

$ ninja -C out/Release electron:electron_dist_zip

You now have a zip file, which you can use with for example @electron-forge. Make sure to specify the correct config in your package.json:

"config": { "forge": { "packagerConfig": { "electronZipDir": "../custom-electron" } }

The zip files should be named similar to these:


Now you can build your Electron app with the custom Chromium build.


VMWare Fusion – modify DHCP

If you are running VMWare Fusion, chances are you might have created your own custom network adapter.

In case you’re running an (authoritative) DHCP server in this subnet, you might see interference with VMWare Fusion’s own DHCP server.

You can easily disable the Fusion DHCP server by following these steps (no Fusion restart required):

  • set DHCP no for your adapter with sudo nano /Library/Preferences/VMware\ Fusion/networking
  • apply the new settings with:

sudo /Applications/VMware\ --configure
sudo /Applications/VMware\ --stop
sudo /Applications/VMware\ --start

You might also need to disable the macOS bootp process:
sudo /bin/launchctl unload -w /System/Library/LaunchDaemons/bootps.plist


Automated Browser Testing with Puppeteer

If you are interested in browser automation, you probably have heard of Puppeteer.

Puppeteer is a NodeJS library, which connects with Chromium browsers through the DevTools protocol.

Puppeteer will send the same messages back and forth just like the Chrome DevTools do. By doing that, it allows Puppeteer to control and interact with the Chrome browser.

There’s some advantages to using this method instead of using Selenium (WebDriver):

  • It is faster, because of the DevTools protocol which is natively supported. And because it’s using WebSockets instead of HTTP requests (which WebDriver uses).
  • The default mode is headless, which means no UI is visible. If you are automating your browser, chances are you don’t really need to see the browser. If you are doing UI tests, you might want to see the browser, in which case Puppeteer has a ‘headful‘ mode as well.
  • Regular updates. Puppeteer is maintained by Google. This means it will definitely keep up with Chrome and any new features.

Ready to get started? I can recommend reading the article Puppeteer Testing which will guide you through setting up and configuring Puppeteer and a test framework such as Jest, WebDriverIO or PyTest.

In case you’re looking for an alternative solution, I can recommend Playwright. It offers the same set of features, uses the same technology under the hood and has broader browser support.

Happy Testing!


Removing ‘System Volume Information’ from a NTFS Volume

There’s a quick and easy way to remove the ‘System Volume Information’ folder from a NTFS disk. Run these commands in an elevated shell:

D: (or whichever volume letter you are using)
takeown /r /f "System Volume Information"
rd /s /q "System Volume Information"

Streaming MySQL backup

This week I needed to backup a Percona MySQL server.
One solution for this, is to stop the MySQL server, create a mysqldump, and transfer it to your backup location.

However, depending on your tables and data size, this might not be the best solution. Especially if the database you want to backup is a live database with active users.

The solution for me was to use xtrabackup (innobackupex) from Percona to stream the database in tar format over SSH to another server:

innobackupex --stream=tar . | ssh user@x.x.x.x "cat - > /mnt/backup/backup.tar"

Once this is done, the other server needs to unpack the tar and prepare the backup:

xtrabackup --prepare --target-dir=/var/lib/mysql

At the end of this command, you should see an OK message.
If all went well, you can now do:

chown -R mysql:mysql /var/lib/mysql

and restart the MySQL server. The binlog position will be included in the output of the xtrabackup --prepare so you can easily set up master/slave syncing.

Finally, I created a cronjob on the MySQL Slave server which will take a daily backup with xtrabackup and upload to a 3rd party secure storage.


image-orientation CSS property

The other day I was investigating an issue with Chrome 83 and a MJPEG stream embedded in an <img />

The MJPEG stream was streaming an iPhone screen to the end user. When the user decided to rotate the screen (switching between portrait and landscape), the MJPEG stream was updated accordingly when viewing the stream inside a separate tab, but it was not showing correctly when embedded in a HTML image tag.

Turns out that since Chrome 81, the browser will look at the EXIF data to decide the correct orientation. The issue I was experiencing was happening because the EXIF data did not update after each rotation.

The solution was to use image-orientation and apply this CSS rule to the image tag. Once that was in place, the MJPEG stream was showing correctly after each rotation.

More information is available in this Chromium ticket.


WireGuard: an alternative to OpenVPN

This week I’ve been experimenting with WireGuard, which is a relatively new alternative to OpenVPN. It claims to be faster and more secure than other VPN products, partly because its codebase is very small compared to other VPN products.

WireGuard is easy to configure. It is compatible with many Linux distro’s, including Ubuntu. For my testing purposes, I’ve set up a new Ubuntu 18.04 LTS VM with Hardware Enablement.

First, make sure you’ve installed WireGuard correctly:
apt-get install wireguard

You should now be able to use wg and wg-quick
Let’s create a public and private key, which we’ll be using to set up a secure connection:

wg genkey | tee privatekey | wg pubkey > publickey

On the server VM, create a new configuration file /etc/wireguard/wg0.conf
Add the private key you just generated in the PrivateKey section.

This should contain configuration like this:

PrivateKey = <private key>
Address =
ListenPort = 51820
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth1 -j MASQUERADE; ip6tables -A FORWARD -i wg0 -j ACCEPT; ip6tables -t nat -A POSTROUTING -o eth1 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth1 -j MASQUERADE; ip6tables -D FORWARD -i wg0 -j ACCEPT; ip6tables -t nat -D POSTROUTING -o eth1 -j MASQUERADE
SaveConfig = false

PublicKey = <public key>
AllowedIPs =

The next thing you’ll need to do is add the Peers that will be able to connect to this server. Simply create another VM (Windows, Linux or MacOS) and follow the same steps:

  • install WireGuard
  • generate private and public key
  • create a new /etc/wireguard/wg0.conf configuration file
PrivateKey = <private key>
Address =

PublicKey = <public key of the server>
Endpoint = <ip4-of-server>:51820
AllowedIPs =, ::/0 # Forward all traffic to server

The AllowedIPs instructs WireGuard to forward all traffic through the tunnel.

Finally, you can start up WireGuard on both the server and client:

wg-quick up /etc/wireguard/wg0.conf

Now both VMs should be connected and able to ping each other.
You can check the status of the connection with:

wg show

I saw a notable increase in throughput compared to OpenVPN. Try it out yourself and let me know in the comments.