Skip to main content

It's easier than you think - Amateur Radio License

ยท 2 min read
David Bezuidenhout
Maintainer of Linuxgeek.za.net blog

I am serious. It is easier than you think it is, to get an amateur radio license (for those interested)

Earlier this year, I signed up to write the radio amateur radio exam (RAE).

To ensure I am successful, I also joined the RAE course of one of the clubs. I reached out to both BARK and CTARC. Both clubs responded really quick to be honest, however because BARK provided the classes in Afrikaans and lucky also cost a lot less for the course I chose them. This included a year membership to the club, so BONUS! The classes were online on zoom, every Tueday night 7-9pm. Schedule is on their website: https://bark.org.za/kursus-rae/ This got me through the handbook's work (over 300 pages). Then after that, I just practiced the flash cards on https://www.weprepare.co.za and that is what allowed me to get the below score.

  • On 11th May 2024 did the HF assessment with the club. Met a bunch of people and it was great outing.
  • On the 18th May wrote the exam.
  • On the 25th May received the exam result.

I definitely didn't expect to get an A for this. The work in the handbook seemed freakishly complex, although very interesting to refresh what I learned in school and then some more that is applicable to the hobby. That said, the exam was definitely easier than I expected. Compared to some of the questions in the handbook, definitely doable for most citizens.

  • On the 26th May, I was out to a park in my suburb, to ensure I get line of sight to test contacting on a repeater, on which the club bulletin was on.

Using my little Quansheng UV-K5 hand held radio, reached a repeater that is 41.70km away from me.

Off to the start of new a fun hobby!

Tasmotify a Bneta/Qualitel Smart plug

ยท 3 min read
David Bezuidenhout
Maintainer of Linuxgeek.za.net blog

Let's start.

๐Ÿ”ฉ Componentsโ€‹

You will need the following components:

This is the plugโ€‹

๐Ÿ”ฉ Flashing Tasmotaโ€‹

First up, let's program our new controller. I am using my ESP8266 development board here because it works, however I am using the pins as you can do with any other usb ttl device.

Soldering before we can program:

The programmer:

Using the esptool from Github, this is the command I used after downloading the Tasmota firmware binary.

esptool.py --port /dev/cu.usbserial-A9UD533R write_flash 0x00000 Downloads/tasmota.bin

Configurationโ€‹

I used the following template to configure the device for Tasmota:

{"NAME":"BNETA WifiPlug","GPIO":[0,288,0,32,2688,2656,0,0,2624,544,224,0,0,0],"FLAG":0,"BASE":18}

Calibrationโ€‹

The power metering also needs to be calibrated. The Tasmota documentation cover power monitoring calibration in detail, however I found that using a greasemonkey script in Tampermonkey to really fine tune this perfectly and basically, automates the calibration process apart from a few stemps, which I will quickly outline.

  1. Install tamper monkey in Chrome.
  2. Install the greasemonkey script in Tampermonkey
  3. Modify this script to whatever you will use. For example my bulb is rated 70w at 240v. So I had to put this into the script. The calibration is done based on this and the readings it get, real time.
  4. Have a way to read your current voltage from your power provider as accurate as possible. I read it from my solar inverter.
  5. Run these commands on the tasmota device in the console
    VoltageSet 233.3
    PowerSet 70.0
    Backlog VoltRes 3; WattRes 3; CurrentSet 300.171526586620926

ps. VoltageSet is the voltage you read from your meter. So sometimes I type in 235.2 or 237.8 depending on whatever eskom is pushing into the house or if you on solar what the inverter is pushing into your house. The other commands are also in the script telling you what to do. pps. You need to work out the CurrentSet according to whatever you are using. There is a formula in the script.

  1. I then click on the calibrate button and wait for it to do it's thing. Usually done within less than 2-3 minutes.

Have fun and enjoy automating!

Tasmotify a CBI Astute Isolator

ยท 4 min read
David Bezuidenhout
Maintainer of Linuxgeek.za.net blog

Some on their smart home journey, may have arrived at the time where they want to manage high loads remotely, such as a geyser. Typically, at least in South Africa, geysers come standard with a 3kW element which uses 13 amps. However in my own home, the geyser for the main bedroom was equipped with a 4kW element, which meant a whopping 18 amps! Most smart switches are rated 16 amp for the power metering switches or 10 amp for the non-metering options.

CBI-electric (Circuit Breaker Industries) released a a few "smart" switches that can handle 30 amp. The one I have here is the CBI Astute Smart Isolator or ASI for short.

These are all built into the Tuya Smart IoT framework, which I am not a fan off. I prefer where possible using open-source software, including on the IoT devices in my house. In this guide, I will go over the process of converting one to just that!

Let's start.

๐Ÿ”ฉ Componentsโ€‹

You will need the following components:

You can use the ESP-12F as well, but then you will require 2x 10k resistor in addition to the ESP-12F. The recommendation is a 10k 0805 SMD resistor and a 10k 1/4w leaded resistor.

๐Ÿ”ฉ Flashing Tasmotaโ€‹

First up, let's program our new controller. I am using my ESP8266 development board here. This makes the process convenient and easy.

ESP Programmer

Using the esptool from Github, this is the command I used after downloading the Tasmota firmware binary.

esptool.py --port /dev/cu.usbserial-A9UD533R write_flash 0x00000 Downloads/tasmota.bin

Taking the CBI ASI apartโ€‹

Brand new ASI removed from packaging:

First cover removed and we can see the 30A relay:

Further removal will reveal the WR3E micro controller:

Desolderingโ€‹

I start by putting the board into something to hold it well in place while I work with heat.

My eye sight is also not what it used to be for something up close so I need some help.

Once the WR3E is removed, this is what it looks like.

I then again have some help to keep the ESP-12S in place while I add solder back on.

Testingโ€‹

Initial quick testing, only to make sure it turns on and look happy.

Aaand time to put everything back together.

Configurationโ€‹

I used the following template to configure the device for Tasmota.

Smart Isolator:

{"NAME":"CBI Astute","GPIO":[2624,320,0,0,0,224,0,0,2720,32,2656,0,0,0],"FLAG":0,"BASE":6}

Smart Switch:

{"NAME":"CBI Astute","GPIO":[131,56,0,0,0,21,0,0,134,17,132,0,0],"FLAG":0,"BASE":6}

Calibrationโ€‹

The power metering also needs to be calibrated. The Tasmota documentation cover power monitoring calibration in detail, however I found that using a greasemonkey script in Tampermonkey to really fine tune this perfectly and basically, automates the calibration process apart from a few stemps, which I will quickly outline.

  1. Install tamper monkey in Chrome.
  2. Install the greasemonkey script in Tampermonkey
  3. Modify this script to whatever you will use. For example my bulb is rated 70w at 240v. So I had to put this into the script. The calibration is done based on this and the readings it get, real time.
  4. Have a way to read your current voltage from your power provider as accurate as possible. I read it from my solar inverter.
  5. Run these commands on the tasmota device in the console
    VoltageSet 233.3
    PowerSet 70.0
    Backlog VoltRes 3; WattRes 3; CurrentSet 300.171526586620926

ps. VoltageSet is the voltage you read from your meter. So sometimes I type in 235.2 or 237.8 depending on whatever eskom is pushing into the house or if you on solar what the inverter is pushing into your house. The other commands are also in the script telling you what to do. pps. You need to work out the CurrentSet according to whatever you are using. There is a formula in the script.

  1. I then click on the calibrate button and wait for it to do it's thing. Usually done within less than 2-3 minutes.

Have fun and enjoy automating!

Nodered with docker-compose & CloudFlare

ยท 8 min read
David Bezuidenhout
Maintainer of Linuxgeek.za.net blog

Node-REDโ€‹

A visual tool for wiring the Internet of Things.

Overviewโ€‹

I am planning on writing a series of different posts on using Node-RED and so this is the first post of the series. It will have a small twist, in that I will be setting it up using docker-compose with a Let's Encrypt certificate using Traefik for the reverse proxy and lastly, CloudFlare for DNS.

As a bonus, I will also add Watchtower to automate updates of Traefik and [Node-Red.]

Goalsโ€‹

After finishing the instructions in this blog post, you will have:

  • Used docker-compose
  • Node-RED installed without exposing the ports using docker
  • Traefik installed as reverse-proxy
  • Ability to have multiple apps behind unique subdomains on your domain.tld using Let's Encrypt certificates

Requirementsโ€‹

  • Linux Host for Docker and Node-RED - Can be a VPS or Raspberry Pi ect.
  • CloudFlare account with API key
  • Domain with DNS hosted by CloudFlare
  • You will need to point a *.domain.tld record to your Linux host. The reason for this is, we will be using subdomains to point to different services running in docker. In this example, I will be pointing nodered.domain.tld to my nodered container for easy access.

Preperationโ€‹

Usually I have a separate volume for my data mounted on /data and on this volume I will create a folder named docker with all my files in.

We will create 2 files /data/docker to start with.

First file will contain our authentication and keys: .env

PUID=1001
PGID=1001
TZ=Africa/Johannesburg
USERDIR=/data

DOMAINNAME=domain.tld
CLOUDFLARE_EMAIL=cloudflare@domain.tld
CLOUDFLARE_API_KEY=abcdefghiklmnopqrstvwxyz01234567897e8c6

HTTP_USERNAME=tinuva
HTTP_PASSWORD=$apr1$bJ77aaaaaaaaaaaaaaaaaaaaaaaaaaax/vO0
API_HTTP_USERNAME=nodered
API_HTTP_PASSWORD=$apr1$bbbbbbbbbbbbbbbbbbbbbbbbbnElz47L1

To do:

  1. Replace domain.tld with your domain that is hosted with CloudFlare
  2. Set your timezone
  3. Create a .htpass file with HTPASSWD Generator then place the username and passwords into the above fields. Initially we will only use the HTTP_USERNAME and HTTP_PASSWORD. The API versions will be used at a later stage when we create an API endpoint using Node-RED

Next we will create a docker-compose.yml file/

version: '2.1'

services:
nodered:
image: nodered/node-red-docker:slim-v10
container_name: nodered
volumes:
- ${USERDIR}/docker/nodered/data:/data
- /etc/localtime:/etc/localtime
restart: always
mem_limit: 100m
cpu_shares: 50
labels:
- "traefik.enable=true"
- "traefik.backend=nodered"
- "traefik.frontend.redirect.entryPoint=https"
- "traefik.port=1880"
- "traefik.protocol=http"
- "traefik.docker.network=docker_traefik_proxy"
- "traefik.ifttt.frontend.rule=Host:nodered.${DOMAINNAME};Path:/ifttt"
- "traefik.ifttt.frontend.auth.basic.users=${API_HTTP_USERNAME}:${API_HTTP_PASSWORD}"
- "traefik.web.frontend.rule=Host:nodered.${DOMAINNAME}"
- "traefik.web.frontend.auth.basic.users=${HTTP_USERNAME}:${HTTP_PASSWORD}"
- com.centurylinklabs.watchtower.enable=true

traefik:
hostname: traefik
image: traefik:latest
container_name: traefik
restart: always
domainname: ${DOMAINNAME}
cpu_shares: 30
mem_limit: 250m
ports:
- 80:80
- 443:443
environment:
- CLOUDFLARE_EMAIL=${CLOUDFLARE_EMAIL}
- CLOUDFLARE_API_KEY=${CLOUDFLARE_API_KEY}
labels:
- "traefik.enable=true"
- "traefik.backend=traefik"
- "traefik.frontend.rule=Host:traefik.${DOMAINNAME}"
- "traefik.frontend.redirect.entryPoint=https"
- "traefik.port=8080"
- "traefik.docker.network=docker_traefik_proxy"
- "traefik.frontend.auth.basic.users=${HTTP_USERNAME}:${HTTP_PASSWORD}"
- com.centurylinklabs.watchtower.enable=true
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ${USERDIR}/docker/traefik:/etc/traefik
- ${USERDIR}/docker/shared:/shared

watchtower:
image: v2tec/watchtower:latest
container_name: watchtower
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /etc/localtime:/etc/localtime:ro
command: --schedule "0 30 5 * * *" --cleanup --label-enable
restart: always
mem_limit: 50m
cpu_shares: 25

We also need to create folders and set ownership to match the above.

mkdir /data/docker/shared
mkdir -p /data/docker/nodered/data
mkdir -p /data/docker/traefik/acme

touch /data/docker/traefik/rules.toml
touch /data/docker/traefik/acme/acme.json
chmod 600 /data/docker/traefik/acme/acme.json

chown -R 1001.1001 /data/docker/nodered
chown -R 1001.1001 /data/docker/traefik
chown 1001.1001 /data/docker/shared

Create Traefik's config file: /data/docker/traefik/traefik.toml

#debug = true

logLevel = "ERROR" #DEBUG, INFO, WARN, ERROR, FATAL, PANIC
InsecureSkipVerify = true
defaultEntryPoints = ["https", "http"]

# WEB interface of Traefik - it will show web page with overview of frontend and backend configurations
[api]
entryPoint = "traefik"
dashboard = true
address = ":8080"

# Force HTTPS
[entryPoints]
[entryPoints.http]
address = ":80"
[entryPoints.http.redirect]
entryPoint = "https"
[entryPoints.https]
address = ":443"
[entryPoints.https.tls]

[file]
watch = true
filename = "/etc/traefik/rules.toml"

# Let's encrypt configuration
[acme]
email = "email@domain.com" #any email id will work
storage="/etc/traefik/acme/acme.json"
entryPoint = "https"
acmeLogging=true
onDemand = false #create certificate when container is created
[acme.dnsChallenge]
provider = "cloudflare"
delayBeforeCheck = 300
[[acme.domains]]
main = "domain.tld"
[[acme.domains]]
main = "*.domain.tld"

# Connection to docker host system (docker.sock)
[docker]
endpoint = "unix:///var/run/docker.sock"
domain = "domain.tld"
watch = true
# This will hide all docker containers that don't have explicitly
# set label to "enable"
exposedbydefault = false

Replace/Configure:

  1. email@domain.com: with your email.
  2. domain.tld: with your private domain name.
  3. InsecureSkipVerify = true: I had to add at the beginning to allow some apps (eg. UniFi controller) be accessible through Traefik.
  4. provider = "cloudflare": Change to your DNS provider for DNS challenge.
  5. exposedbydefault = false: This will force you to use traefik.enable=true label in docker compose to put apps behind traefik.
  • If you want to use another DNS provider instead of CloudFlare, review the list of available providers on the Traefik documentation

Install Dockerโ€‹

Ok lets install docker.

I have a CentOS host which the following work:

sudo yum install -y yum-utils \
device-mapper-persistent-data \
lvm2

sudo yum-config-manager \
--add-repo \
https://download.docker.com/linux/centos/docker-ce.repo

sudo yum install docker-ce docker-ce-cli containerd.io

systemctl start docker
systemctl enable docker

Reference: https://docs.docker.com/install/linux/docker-ce/centos/

On Ubuntu the commands will be:

sudo apt-get update

sudo apt-get install \
apt-transport-https \
ca-certificates \
curl \
gnupg-agent \
software-properties-common

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

sudo add-apt-repository \
"deb [arch=amd64] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) \
stable"

sudo apt-get update

sudo apt-get install docker-ce docker-ce-cli containerd.io

Reference: https://docs.docker.com/install/linux/docker-ce/ubuntu/

Install docker-composeโ€‹

First confirm pip is installed:

On CentOS:

yum install python-pip

On Ubuntu:

apt install python-pip

Then install docker-compose:

pip install docker-compose

Start containersโ€‹

Starting the containers is as simple as:

cd /data/docker && docker-compose up -d --remove-orphans && docker-compose logs -f --tail 10

You should see output like the following:

[root@thor docker]# cd /data/docker && docker-compose up -d --remove-orphans && docker-compose logs -f --tail 10
Pulling traefik (traefik:latest)...
latest: Pulling from library/traefik
d572f7c8e983: Pull complete
d62b0f6adf29: Pull complete
Digest: sha256:02cfdb77b0cd82d973dffb3dafe498283f82399bd75b335797d7f0fe3ebeccb8
Status: Downloaded newer image for traefik:latest
Pulling watchtower (v2tec/watchtower:latest)...
latest: Pulling from v2tec/watchtower
a5415f98d52c: Pull complete
c3f7208ad77c: Pull complete
169c1e589d74: Pull complete
Digest: sha256:4cb6299fe87dcbfe0f13dcc5a11bf44bd9628a4dae0035fecb8cc2b88ff0fc79
Status: Downloaded newer image for v2tec/watchtower:latest
Pulling nodered (nodered/node-red-docker:slim-v10)...
slim-v10: Pulling from nodered/node-red-docker
e7c96db7181b: Pull complete
bbec46749066: Pull complete
89e5cf82282d: Pull complete
5de6895db72f: Pull complete
6c5493c0ae88: Pull complete
6ace8e934b90: Pull complete
e37b30d9d482: Pull complete
95734c8da825: Pull complete
cf430c1fac0c: Pull complete
Digest: sha256:42126aab7325a3133f47e0a89bb14a3985c8d4daeb8d8a3c0e19cfe9137e1c79
Status: Downloaded newer image for nodered/node-red-docker:slim-v10
Creating traefik ... done
Creating nodered ... done
Creating watchtower ... done
Attaching to nodered, watchtower, traefik
nodered |
nodered | > node-red-docker@1.0.0 start /usr/src/node-red
nodered | > node $NODE_OPTIONS node_modules/node-red/red.js -v $FLOWS "--userDir" "/data"
nodered |
watchtower | time="2019-07-22T17:12:26+02:00" level=info msg="First run: 2019-07-23 05:30:00 +0200 SAST"
nodered | 22 Jul 17:12:28 - [info]
nodered |
nodered | Welcome to Node-RED
nodered | ===================
nodered |
nodered | 22 Jul 17:12:28 - [info] Node-RED version: v0.20.7
nodered | 22 Jul 17:12:28 - [info] Node.js version: v10.16.0
nodered | 22 Jul 17:12:28 - [info] Linux 3.10.0-957.21.3.el7.x86_64 x64 LE
nodered | 22 Jul 17:12:28 - [info] Loading palette nodes
nodered | 22 Jul 17:12:30 - [warn] rpi-gpio : Raspberry Pi specific node set inactive
nodered | 22 Jul 17:12:30 - [warn] rpi-gpio : Cannot find Pi RPi.GPIO python library
nodered | 22 Jul 17:12:30 - [info] Settings file : /data/settings.js
nodered | 22 Jul 17:12:30 - [info] Context store : 'default' [module=memory]
nodered | 22 Jul 17:12:30 - [info] User directory : /data
nodered | 22 Jul 17:12:30 - [warn] Projects disabled : editorTheme.projects.enabled=false
nodered | 22 Jul 17:12:30 - [info] Flows file : /data/flows.json
nodered | 22 Jul 17:12:30 - [info] Creating new flow file
nodered | 22 Jul 17:12:30 - [warn]
nodered |
nodered | ---------------------------------------------------------------------
nodered | Your flow credentials file is encrypted using a system-generated key.
nodered |
nodered | If the system-generated key is lost for any reason, your credentials
nodered | file will not be recoverable, you will have to delete it and re-enter
nodered | your credentials.
nodered |
nodered | You should set your own key using the 'credentialSecret' option in
nodered | your settings file. Node-RED will then re-encrypt your credentials
nodered | file using your chosen key the next time you deploy a change.
nodered | ---------------------------------------------------------------------
nodered |
nodered | 22 Jul 17:12:30 - [info] Server now running at http://127.0.0.1:1880/
nodered | 22 Jul 17:12:30 - [info] Starting flows
nodered | 22 Jul 17:12:30 - [info] Started flows

Do review the logs for a short while, it is possible that traefik failed to generate a certificate if your domain already has a TXT record for SPF.

traefik       | time="2019-07-22T15:26:58Z" level=error msg="Unable to obtain ACME certificate for domains \"domain.tld\" : unable to generate a certificate for the domains [domain.tld]: acme: Error -> One or more domains had a problem:\n[domain.tld] [domain.tld] acme: error presenting token: cloudflare: failed to create TXT record: error from makeRequest: HTTP status 400: content \"{\\\"success\\\":false,\\\"errors\\\":[{\\\"code\\\":81057,\\\"message\\\":\\\"The record already exists.\\\"}],\\\"messages\\\":[],\\\"result\\\":null}\"\n"

Successโ€‹

If all good, you should now be able to access 2 services.

Both will require a password set earlier. Nodered by default doesn't have authentication, and traefik's dashboard also doesn't, but that doesn't mean we need to keep it open to the world.

Initially these may show up with a self-generated certificate while Traefik work to generate and authenticate a wild-card certificate for your domain. Once this is complete, you will have a certificate issued with: Issued by: Let's Encrypt Authority X3

VRRP between Linux and RouterOS / Mikrotik

ยท 4 min read
David Bezuidenhout
Maintainer of Linuxgeek.za.net blog

VRRPโ€‹

VRRP

The Virtual Router Redundancy Protocol (VRRP) is a computer networking protocol that provides for automatic assignment of available Internet Protocol (IP) routers to participating hosts. This increases the availability and reliability of routing paths via automatic default gateway selections on an IP subnetwork.

Reference: Wikipedia

Whyโ€‹

I couldn't find a guide on how to do this. There are plenty guides setting up VRRP on RouterOS between two Mikrotik devices, plenty for Cisco, plenty for Linux using Keepalived, and so on and so forth...

And so this guide...

OK but Why...โ€‹

Ok but why did I need this you may ask?

In my specific situation, I have a DNS server running Pi-hole which is a Linux network-level advertisement and Internet tracker blocking application which acts as a DNS sinkhole, intended for use on a private network.

What happen is, this runs on a big server, however here in sunny South Africa we at times have power outages, otherwise known as load-shedding or blackouts in other countries over the world. When this happen, the fileserver loses power, but small devices like the Mikrotik Router, Ubiquiti wifi and FTTH ONT stays online...except nothing can resolve DNS when everything is configured to query the Pi-hole server which is now down.

The solution was born, using VRRP to have a floating ip address, which all devices will be configured to use. When the Pi-hole server is online, the IP address will be on there, and block advertisements, and when it goes offline, the Mikrotik router will take over answering DNS queries. The failover happens very quickly, in less than a second.

RouterOSโ€‹

On the Mikrotik side, its fairy easy, I will make use of a screenshot to show what is needed.

WINBOX

  1. Create a new interface in the VRRP tab
  2. Select the LAN interface, bridge1 in my case
  3. Choose a VRID, 2 here
  4. Priority is 100 (as this will be my backup)
  5. Interval is 1.00 for 1 second
  6. Setup authentication. I wanted to use 'ah' but settled for 'simple' and you need version 2 for IPv4. Version 3 of VRRP is intended for IPv6.

Linuxโ€‹

On the Linux OS side, I make use of Keepalived which can do both virtual ip addresses using VRRP and also simple load-balancing. We will only use the former.

This is the /etc/keepalived/keepalived.conf to match up to the VRRP on the Mikrotik:

! Configuration File for keepalived

global_defs {
router_id LVS_DEVEL
vrrp_skip_check_adv_addr
}

vrrp_instance VI_1 {
state BACKUP
interface eth0
virtual_router_id 2
priority 200
advert_int 1
authentication {
auth_type PASS
auth_pass pihole77
}
virtual_ipaddress {
192.168.241.2/24
}
}
  1. Router id matches the Mikrotik
  2. Prioty is doubled up to 200 to make this the master
  3. advert_int is 1 for 1 second
  4. Authentication match up to the Mikrotik

Slow...โ€‹

There was one issue I ran in to. Pi-hole service takes a few minutes to start up, mostly combining my huge amount of blacklists from the internet. The problem with this is, if the ip address move over before this complete, I have downtime on the DNS.

The solution to this, Keepalived isn't set to auto start, but controlled through a very simplisting bash/cron script, that check if we can resolve dns locally on the Pi-hole server before starting up Keepalived.

#!/bin/bash

DNSCHECK=`dig localhost @192.168.241.186 +short`
#KEEPALIVE=`/etc/init.d/keepalived status | grep started`

if [ $DNSCHECK = '127.0.0.1' ]
then
echo "DNS IS UP!"
/etc/init.d/keepalived status | grep started > /dev/null
if [ $? -ne 0 ]; then
echo "Keepalive not running, lets start it"
/etc/init.d/keepalived zap > /dev/null
/etc/init.d/keepalived start > /dev/null
fi
else
echo "DNS IS DOWN!"
/etc/init.d/keepalived status | grep started > /dev/null
if [ $? -eq 0 ]; then
echo "Keepalive is running, lets stop it"
/etc/init.d/keepalived stop > /dev/null
fi
fi

ENDโ€‹

So there you have it. A little bit of work, but makes for a smooth transition between the Pi-hole server and the Mikrotik router. Let me know in the comments what you think.