Recent changes to this wiki:

Remove Feedburner email subscription option.
Feedburner is going into maintenance mode and shutting down email subscriptions.
diff --git a/sidebar.mdwn b/sidebar.mdwn
index fbe8589..48fefed 100644
--- a/sidebar.mdwn
+++ b/sidebar.mdwn
@@ -1,7 +1,4 @@
-# Subscribe to this blog
-
-<a href="https://feeding.cloud.geek.nz/index.rss"><img src="/feed-icon.png" height="32" width="32" align="left">Subscribe in a reader</a>
-[Subscribe by Email](https://www.feedburner.com/fb/a/emailverifySubmit?feedId=1839853&loc=en_US)
+<a href="/index.rss"><img src="/feed-icon.png" height="32" width="32" align="left">Subscribe to this blog</a>
 
 # About me
 

Add new restic corruption post.
diff --git a/posts/deleting-non-decryptable-restic-snapshots.mdwn b/posts/deleting-non-decryptable-restic-snapshots.mdwn
new file mode 100644
index 0000000..0e132d4
--- /dev/null
+++ b/posts/deleting-non-decryptable-restic-snapshots.mdwn
@@ -0,0 +1,137 @@
+[[!meta title="Deleting non-decryptable restic snapshots"]]
+[[!meta date="2021-04-12T20:20:00.000-08:00"]]
+[[!meta license="[Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/)"]]
+
+Due to what I suspect is disk corruption error due to a faulty RAM module or
+network interface on my [GnuBee](http://gnubee.org/), my
+[restic](https://restic.net/) backup failed with the following error:
+
+    $ restic check
+    using temporary cache in /var/tmp/restic-tmp/restic-check-cache-854484247
+    repository b0b0516c opened successfully, password is correct
+    created new cache in /var/tmp/restic-tmp/restic-check-cache-854484247
+    create exclusive lock for repository
+    load indexes
+    check all packs
+    check snapshots, trees and blobs
+    error for tree 4645312b:
+      decrypting blob 4645312b443338d57295550f2f4c135c34bda7b17865c4153c9b99d634ae641c failed: ciphertext verification failed
+    error for tree 2c3248ce:
+      decrypting blob 2c3248ce5dc7a4bc77f03f7475936041b6b03e0202439154a249cd28ef4018b6 failed: ciphertext verification failed
+    Fatal: repository contains errors
+
+I started by locating the snapshots which make use of these corrupt trees:
+
+    $ restic find --tree 4645312b
+    repository b0b0516c opened successfully, password is correct
+    Found tree 4645312b443338d57295550f2f4c135c34bda7b17865c4153c9b99d634ae641c
+     ... path /usr/include/boost/spirit/home/support/auxiliary
+     ... in snapshot 41e138c8 (2021-01-31 08:35:16)
+    Found tree 4645312b443338d57295550f2f4c135c34bda7b17865c4153c9b99d634ae641c
+     ... path /usr/include/boost/spirit/home/support/auxiliary
+     ... in snapshot e75876ed (2021-02-28 08:35:29)
+    
+    $ restic find --tree 2c3248ce
+    repository b0b0516c opened successfully, password is correct
+    Found tree 2c3248ce5dc7a4bc77f03f7475936041b6b03e0202439154a249cd28ef4018b6
+     ... path /usr/include/boost/spirit/home/support/char_encoding
+     ... in snapshot 41e138c8 (2021-01-31 08:35:16)
+    Found tree 2c3248ce5dc7a4bc77f03f7475936041b6b03e0202439154a249cd28ef4018b6
+     ... path /usr/include/boost/spirit/home/support/char_encoding
+     ... in snapshot e75876ed (2021-02-28 08:35:29)
+
+and then deleted them:
+
+    $ restic forget 41e138c8 e75876ed
+    repository b0b0516c opened successfully, password is correct
+    [0:00] 100.00%  2 / 2 files deleted
+
+    $ restic prune 
+    repository b0b0516c opened successfully, password is correct
+    counting files in repo
+    building new index for repo
+    [13:23] 100.00%  58964 / 58964 packs
+    repository contains 58964 packs (1417910 blobs) with 278.913 GiB
+    processed 1417910 blobs: 0 duplicate blobs, 0 B duplicate
+    load all snapshots
+    find data that is still in use for 20 snapshots
+    [1:15] 100.00%  20 / 20 snapshots
+    found 1364852 of 1417910 data blobs still in use, removing 53058 blobs
+    will remove 0 invalid files
+    will delete 942 packs and rewrite 1358 packs, this frees 6.741 GiB
+    [10:50] 31.96%  434 / 1358 packs rewritten
+    hash does not match id: want 9ec955794534be06356655cfee6abe73cb181f88bb86b0cd769cf8699f9f9e57, got 95d90aa48ffb18e6d149731a8542acd6eb0e4c26449a4d4c8266009697fd1904
+    github.com/restic/restic/internal/repository.Repack
+    	github.com/restic/restic/internal/repository/repack.go:37
+    main.pruneRepository
+    	github.com/restic/restic/cmd/restic/cmd_prune.go:242
+    main.runPrune
+    	github.com/restic/restic/cmd/restic/cmd_prune.go:62
+    main.glob..func19
+    	github.com/restic/restic/cmd/restic/cmd_prune.go:27
+    github.com/spf13/cobra.(*Command).execute
+    	github.com/spf13/cobra/command.go:852
+    github.com/spf13/cobra.(*Command).ExecuteC
+    	github.com/spf13/cobra/command.go:960
+    github.com/spf13/cobra.(*Command).Execute
+    	github.com/spf13/cobra/command.go:897
+    main.main
+    	github.com/restic/restic/cmd/restic/main.go:98
+    runtime.main
+    	runtime/proc.go:204
+    runtime.goexit
+    	runtime/asm_amd64.s:1374
+
+As you can see above, the `prune` command failed due to a corrupt pack and
+so I followed the [process I previously wrote
+about](https://feeding.cloud.geek.nz/posts/removing-corrupted-data-pack-restic-backup/)
+and identified the affected snapshots using:
+
+    $ restic find --pack 9ec955794534be06356655cfee6abe73cb181f88bb86b0cd769cf8699f9f9e57
+
+before deleting them with:
+
+    $ restic forget 031ab8f1 1672a9e1 1f23fb5b 2c58ea3a 331c7231 5e0e1936 735c6744 94f74bdb b11df023 dfa17ba8 e3f78133 eefbd0b0 fe88aeb5 
+    repository b0b0516c opened successfully, password is correct
+    [0:00] 100.00%  13 / 13 files deleted
+
+    $ restic prune
+    repository b0b0516c opened successfully, password is correct
+    counting files in repo
+    building new index for repo
+    [13:37] 100.00%  60020 / 60020 packs
+    repository contains 60020 packs (1548315 blobs) with 283.466 GiB
+    processed 1548315 blobs: 129812 duplicate blobs, 4.331 GiB duplicate
+    load all snapshots
+    find data that is still in use for 8 snapshots
+    [0:53] 100.00%  8 / 8 snapshots
+    found 1219895 of 1548315 data blobs still in use, removing 328420 blobs
+    will remove 0 invalid files
+    will delete 6232 packs and rewrite 1275 packs, this frees 36.302 GiB
+    [23:37] 100.00%  1275 / 1275 packs rewritten
+    counting files in repo
+    [11:45] 100.00%  52822 / 52822 packs
+    finding old index files
+    saved new indexes as [a31b0fc3 9f5aa9b5 db19be6f 4fd9f1d8 941e710b 528489d9 fb46b04a 6662cd78 4b3f5aad 0f6f3e07 26ae96b2 2de7b89f 78222bea 47e1a063 5abf5c2d d4b1d1c3 f8616415 3b0ebbaa]
+    remove 23 old index files
+    [0:00] 100.00%  23 / 23 files deleted
+    remove 7507 old packs
+    [0:08] 100.00%  7507 / 7507 files deleted
+    done
+
+And with 13 of my 21 snapshots deleted, the checks now pass:
+
+    $ restic check
+    using temporary cache in /var/tmp/restic-tmp/restic-check-cache-407999210
+    repository b0b0516c opened successfully, password is correct
+    created new cache in /var/tmp/restic-tmp/restic-check-cache-407999210
+    create exclusive lock for repository
+    load indexes
+    check all packs
+    check snapshots, trees and blobs
+    no errors were found
+
+This represents a significant amount of lost backup history, but at least
+it's not all of it.
+
+[[!tag gnubee]] [[!tag restic]] [[!tag debian]]

Mention an alternative approach.
diff --git a/posts/removing-corrupted-data-pack-restic-backup.mdwn b/posts/removing-corrupted-data-pack-restic-backup.mdwn
index a1fd591..638699c 100644
--- a/posts/removing-corrupted-data-pack-restic-backup.mdwn
+++ b/posts/removing-corrupted-data-pack-restic-backup.mdwn
@@ -86,4 +86,16 @@ removed due to the other error:
     [1:23] 100.00%  28868 / 28868 files deleted
     done
 
+## Recovering from a corrupt pack
+
+If dropping a single snapshot is not an option (for example, if the corrupt pack is used in
+**every** snapshot!) and you still have the original file, then you can try a
+[different approach](https://github.com/restic/restic/issues/2191#issuecomment-624890063):
+
+    restic rebuild-index
+    restic backup
+
+If you are using an older version of restic which doesn't [automatically heal repos](https://github.com/restic/restic/pull/2827),
+you may need to use the `--force` option to the `backup` command.
+
 [[!tag restic]] [[!tag debian]]

Use a separate temp directory for `restic check`.
The main /tmp directory may not be large enough for all of the backup data.
diff --git a/posts/backing-up-to-gnubee2.mdwn b/posts/backing-up-to-gnubee2.mdwn
index 1c29413..c1b94cb 100644
--- a/posts/backing-up-to-gnubee2.mdwn
+++ b/posts/backing-up-to-gnubee2.mdwn
@@ -240,7 +240,9 @@ to reuse on all of my computers:
     fi
     
     # Check the integrity of existing backups
-    RESTIC_PASSWORD=$PASSWORD restic --quiet -r $REMOTE_URL check || exit 1
+    CHECK_CACHE_DIR="$(mktemp -d /var/tmp/restic-check-XXXXXXXX)"
+    RESTIC_PASSWORD=$PASSWORD restic --quiet --cache-dir=$CHECK_CACHE_DIR -r $REMOTE_URL check || exit 1
+    rmdir "$CHECK_CACHE_DIR"
     
     # Dump list of Debian packages
     dpkg --get-selections > $PKG_FILE
@@ -259,6 +261,11 @@ I run it with the following cronjob in `/etc/cron.d/backups`:
 
 in a way that [doesn't impact the rest of the system too much](https://feeding.cloud.geek.nz/posts/three-wrappers-to-run-commands-without-impacting-the-rest-of-the-system/).
 
+I also put the following in my `/etc/rc.local` to cleanup any leftover temp
+directories for aborted backups:
+
+    rmdir --ignore-fail-on-non-empty /var/tmp/restic-check-*
+
 Finally, I printed a copy of each of my backup script, using
 [enscript](https://www.gnu.org/software/enscript/), to stash in a safe place:
 

Add unlock command to the backup script.
diff --git a/posts/backing-up-to-gnubee2.mdwn b/posts/backing-up-to-gnubee2.mdwn
index 3fcac7c..1c29413 100644
--- a/posts/backing-up-to-gnubee2.mdwn
+++ b/posts/backing-up-to-gnubee2.mdwn
@@ -228,6 +228,11 @@ to reuse on all of my computers:
         RESTIC_PASSWORD=$PASSWORD restic --quiet -r $REMOTE_URL prune
         exit 0
     
+    # Unlock the repository
+    elif [ "$1" = "--unlock" ]; then
+        RESTIC_PASSWORD=$PASSWORD restic -r $REMOTE_URL unlock
+        exit 0
+
     # Catch invalid arguments
     elif [ "$1" != "" ]; then
     	echo "Invalid argument: $1"

Comment moderation
diff --git a/posts/tweaking-referrer-for-privacy-in-firefox/comment_3_fb20b08ca7d1ff6bb0a878a20ea20678._comment b/posts/tweaking-referrer-for-privacy-in-firefox/comment_3_fb20b08ca7d1ff6bb0a878a20ea20678._comment
new file mode 100644
index 0000000..7ac2393
--- /dev/null
+++ b/posts/tweaking-referrer-for-privacy-in-firefox/comment_3_fb20b08ca7d1ff6bb0a878a20ea20678._comment
@@ -0,0 +1,8 @@
+[[!comment format=mdwn
+ ip="216.239.89.105"
+ claimedauthor="Francois Diebolt"
+ subject="network.http.referer.XOriginPolicy settings"
+ date="2021-04-04T16:44:35Z"
+ content="""
+I set both network.http.referer.XOriginPolicy and network.http.referer.XOriginTrimmingPolicy to 2, I'm not sure it's the combination you recommend. I encounter only one break with the login of bmo.com online banking.
+"""]]

creating tag page tags/efi
diff --git a/tags/efi.mdwn b/tags/efi.mdwn
new file mode 100644
index 0000000..ac39d40
--- /dev/null
+++ b/tags/efi.mdwn
@@ -0,0 +1,4 @@
+[[!meta title="pages tagged efi"]]
+
+[[!inline pages="tagged(efi)" actions="no" archive="yes"
+feedshow=10]]

Add EFI syncing.
diff --git a/posts/installing-ubuntu-bionic-on-encrypted-raid1.mdwn b/posts/installing-ubuntu-bionic-on-encrypted-raid1.mdwn
index 9b983b5..3c32592 100644
--- a/posts/installing-ubuntu-bionic-on-encrypted-raid1.mdwn
+++ b/posts/installing-ubuntu-bionic-on-encrypted-raid1.mdwn
@@ -183,4 +183,38 @@ setup:
 At this point, you have a working setup that will gracefully degrade to a
 one-drive RAID array should one of your drives fail.
 
-[[!tag debian]] [[!tag nzoss]] [[!tag ubuntu]] [[!tag raid]]
+## Keep the EFI partitions in sync
+
+Since the EFI partition is not RAIDed, I decided to setup a cron job to
+keep it in sync on the two drives.
+
+First of all, I made sure that both partitions were mounted by explicitly
+using their `PARTUUID` (use `blkid /dev/sda1` to look it up) in `/etc/fstab`:
+
+    PARTUUID=9a923b8a-6d41-473d-b4f3-b7488eedeace  /boot/efi  vfat  umask=0077,rw,x-gvfs-hide      0       1
+    PARTUUID=ef386bc2-e184-4397-86b4-88ecd4469a9b  /mnt/efi   vfat  umask=0077,rw,x-gvfs-hide      0       0
+
+and then creating the `/mnt/efi` directory and mounting everything:
+
+    mkdir -p /mnt/efi
+    mount -a
+
+Then put the following script in `cron.daily/efi-sync`:
+
+    #!/bin/sh
+    if [ ! -e /mnt/efi/backup.mnt ] ; then
+        echo "The backup drive is not mounted in /mnt/efi."
+        exit 1
+    fi
+    if [ ! -e /boot/efi/orig.mnt ] ; then
+        echo "The original drive is not the EFI partition"
+        exit 1
+    fi
+    rsync -aHx --delete --exclude=/efi/orig.mnt --exclude=/efi/backup.mnt /boot/efi /mnt/
+
+and adding the guard files:
+
+    touch /mnt/efi/backup.mnt
+    touch /boot/efi/orig.mnt
+
+[[!tag debian]] [[!tag ubuntu]] [[!tag raid]] [[!tag efi]]

Comment moderation
diff --git a/posts/fixing-turris-omnia-wifi-quality/comment_3_2b8e9b086389ebbac047e6f8dd0d0103._comment b/posts/fixing-turris-omnia-wifi-quality/comment_3_2b8e9b086389ebbac047e6f8dd0d0103._comment
new file mode 100644
index 0000000..09b362d
--- /dev/null
+++ b/posts/fixing-turris-omnia-wifi-quality/comment_3_2b8e9b086389ebbac047e6f8dd0d0103._comment
@@ -0,0 +1,12 @@
+[[!comment format=mdwn
+ username="francois@665656f0ba400877c9b12e8fbb086e45aa01f7c0"
+ nickname="francois"
+ subject="Re: First picture was after you fixed the connections?"
+ date="2021-03-31T21:00:35Z"
+ content="""
+> It looks to me like the first picture was taken after you fixed the connections. Is that right?
+
+Yes, it looks like all of the photos were taken after fixing the wiring.
+
+Francois
+"""]]

Comment moderation
diff --git a/posts/fixing-turris-omnia-wifi-quality/comment_2_2df77904342bdcfb05a2df36362830a3._comment b/posts/fixing-turris-omnia-wifi-quality/comment_2_2df77904342bdcfb05a2df36362830a3._comment
new file mode 100644
index 0000000..8b2b0c8
--- /dev/null
+++ b/posts/fixing-turris-omnia-wifi-quality/comment_2_2df77904342bdcfb05a2df36362830a3._comment
@@ -0,0 +1,8 @@
+[[!comment format=mdwn
+ ip="2600:6ce7:0:10::70"
+ claimedauthor="Alan Robertson"
+ subject="First picture was after you fixed the connections?"
+ date="2021-03-31T19:12:53Z"
+ content="""
+It looks to me like the first picture was taken after you fixed the connections. Is that right?
+"""]]

Fix audio between local devices.
diff --git a/posts/connecting-voip-phone-directly-to-asterisk-server.mdwn b/posts/connecting-voip-phone-directly-to-asterisk-server.mdwn
index 0b091f3..5563f38 100644
--- a/posts/connecting-voip-phone-directly-to-asterisk-server.mdwn
+++ b/posts/connecting-voip-phone-directly-to-asterisk-server.mdwn
@@ -75,4 +75,24 @@ via ssh:
     Host asterisk
         LocalForward 8081 192.168.2.3:80
 
+# Allowing calls between local SIP devices
+
+Because this local device is not connected to the local network
+(`192.168.1.0/24`), it's unable to negotiate a direct media connection to
+any other local (i.e. one connected to the same Asterisk server) SIP device.
+What this means is that while calls might get connected successfully, by
+default, there will not be any audio in a call.
+
+In order for the two local SIP devices to be able to hear one another, we
+must enforce that all media be routed via Asterisk instead of going directly
+from one device to the other. This can be done using the `directmedia`
+directive (formerly
+[`canreinvite`](https://www.voip-info.org/asterisk-sip-canreinvite/)) in
+`sip.conf`:
+
+    [1234]
+    directmedia=no
+
+where `1234` is the extension of the phone.
+
 [[!tag debian]] [[!tag asterisk]] [[!tag ubuntu]] [[!tag ntp]]

Add missing firewall rule.
diff --git a/posts/connecting-voip-phone-directly-to-asterisk-server.mdwn b/posts/connecting-voip-phone-directly-to-asterisk-server.mdwn
index 35dabb0..0b091f3 100644
--- a/posts/connecting-voip-phone-directly-to-asterisk-server.mdwn
+++ b/posts/connecting-voip-phone-directly-to-asterisk-server.mdwn
@@ -44,6 +44,7 @@ Finally, I opened the right ports on the server's firewall in
 `/etc/network/iptables.up.rules`:
 
     -A INPUT -s 192.168.2.3/32 -p udp --dport 5060 -j ACCEPT
+    -A INPUT -s 192.168.2.3/32 -p tcp --dport 5060 -j ACCEPT
     -A INPUT -s 192.168.2.3/32 -p udp --dport 10000:20000 -j ACCEPT
 
 # Network time synchronization
@@ -59,7 +60,7 @@ then I configured it to listen on the private network interface and allow access
 
 Finally, I opened the right firewall port by adding a new rule to `/etc/network/iptables.up.rules`:
 
-    -A INPUT -p udp -s 192.168.2.3 --dport 123 -j ACCEPT
+    -A INPUT -s 192.168.2.3 -p udp --dport 123 -j ACCEPT
 
 # Accessing the admin page
 

Remove Flattr button from the sidebar.
diff --git a/sidebar.mdwn b/sidebar.mdwn
index 5c09f50..fbe8589 100644
--- a/sidebar.mdwn
+++ b/sidebar.mdwn
@@ -3,8 +3,6 @@
 <a href="https://feeding.cloud.geek.nz/index.rss"><img src="/feed-icon.png" height="32" width="32" align="left">Subscribe in a reader</a>
 [Subscribe by Email](https://www.feedburner.com/fb/a/emailverifySubmit?feedId=1839853&loc=en_US)
 
-<br><a href="https://flattr.com/thing/311291/Feeding-the-Cloud"><img src="/flattr_button.png" border="0" alt="Flattr" width="93" height="20"></a>
-
 # About me
 
 <a href="https://fmarier.org"><img src="/avatar.jpg" width="80" height="80">

Comment moderation
diff --git a/posts/using-letsencrypt-cert-with-asterisk/comment_2_d7bf0d6d12a5138099fad1cac682d7a0._comment b/posts/using-letsencrypt-cert-with-asterisk/comment_2_d7bf0d6d12a5138099fad1cac682d7a0._comment
new file mode 100644
index 0000000..f8c2d43
--- /dev/null
+++ b/posts/using-letsencrypt-cert-with-asterisk/comment_2_d7bf0d6d12a5138099fad1cac682d7a0._comment
@@ -0,0 +1,8 @@
+[[!comment format=mdwn
+ ip="81.82.205.71"
+ claimedauthor="Vincent"
+ subject="use the deploy renewal hook for certbot"
+ date="2021-03-22T15:20:55Z"
+ content="""
+I agree with Steve Kemp's comment, and additionally you can use the 'deploy' renewal hook to copy the newly generated files to the asterisk location. That renewal hook is only executed if certbot has succesfully renewed the certificate.
+"""]]

Fix typos.
diff --git a/posts/setting-up-a-network-scanner-using-sane.mdwn b/posts/setting-up-a-network-scanner-using-sane.mdwn
index e1c7147..5b0ae82 100644
--- a/posts/setting-up-a-network-scanner-using-sane.mdwn
+++ b/posts/setting-up-a-network-scanner-using-sane.mdwn
@@ -49,9 +49,9 @@ and that its USB ID shows up in the SANE backend it needs:
 
 To do a test scan, simply run:
 
-    scanimage > test.ppm
+    scanimage > test.pnm
 
-and then take a look at the (greyscale) image it produced (`test.ppm`).
+and then take a look at the (greyscale) image it produced (`test.pnm`).
 
 # Letting normal users access the scanner
 
@@ -76,7 +76,7 @@ also see it in the output of `sane-find-scanner`.
 
 Finally, test the scanner as your normal user:
 
-    scanimage > test.ppm
+    scanimage > test.pnm
 
 to confirm that everything is working.
 
@@ -138,7 +138,7 @@ client computer:
 
 and successfully perform a test scan using this command:
 
-    scanimage > test.ppm
+    scanimage > test.pnm
 
 # Troubleshooting connection problems
 
diff --git a/posts/using-letsencrypt-cert-with-asterisk.mdwn b/posts/using-letsencrypt-cert-with-asterisk.mdwn
index fcd3350..e3ec903 100644
--- a/posts/using-letsencrypt-cert-with-asterisk.mdwn
+++ b/posts/using-letsencrypt-cert-with-asterisk.mdwn
@@ -15,7 +15,7 @@ I created a [Let's Encrypt](https://letsencrypt.org/) certificate using
     certbot certonly --standalone -d hostname.example.com
 
 To enable the `asterisk` user to load the certificate successfuly (it
-doesn't permission to access to the certificates under `/etc/letsencrypt/`),
+doesn't have permission to access the certificates under `/etc/letsencrypt/`),
 I copied it to the right directory:
 
     cp /etc/letsencrypt/live/hostname.example.com/privkey.pem /etc/asterisk/asterisk.key

Add a local NTP server.
diff --git a/posts/connecting-voip-phone-directly-to-asterisk-server.mdwn b/posts/connecting-voip-phone-directly-to-asterisk-server.mdwn
index 61280da..35dabb0 100644
--- a/posts/connecting-voip-phone-directly-to-asterisk-server.mdwn
+++ b/posts/connecting-voip-phone-directly-to-asterisk-server.mdwn
@@ -46,6 +46,21 @@ Finally, I opened the right ports on the server's firewall in
     -A INPUT -s 192.168.2.3/32 -p udp --dport 5060 -j ACCEPT
     -A INPUT -s 192.168.2.3/32 -p udp --dport 10000:20000 -j ACCEPT
 
+# Network time synchronization
+
+In order for the phone to update its clock automatically using [NTP](https://en.wikipedia.org/wiki/Network_Time_Protocol), I installed [chrony](https://chrony.tuxfamily.org/) on the Asterisk server:
+
+    apt install chrony
+
+then I configured it to listen on the private network interface and allow access from the VoIP phone by adding the following to `/etc/chrony/chrony.conf`:
+
+    bindaddress 192.168.2.2
+    allow 192.168.2.3
+
+Finally, I opened the right firewall port by adding a new rule to `/etc/network/iptables.up.rules`:
+
+    -A INPUT -p udp -s 192.168.2.3 --dport 123 -j ACCEPT
+
 # Accessing the admin page
 
 Now that the VoIP phone is no longer available on the local network, it's
@@ -59,4 +74,4 @@ via ssh:
     Host asterisk
         LocalForward 8081 192.168.2.3:80
 
-[[!tag debian]] [[!tag asterisk]] [[!tag nzoss]] [[!tag ubuntu]]
+[[!tag debian]] [[!tag asterisk]] [[!tag ubuntu]] [[!tag ntp]]

Comment moderation
diff --git a/posts/programming-anytone-d878uv-on-linux-using-windows10-and-virtualbox/comment_10_ca3af363fb42ab83915a1c7551e111d4._comment b/posts/programming-anytone-d878uv-on-linux-using-windows10-and-virtualbox/comment_10_ca3af363fb42ab83915a1c7551e111d4._comment
new file mode 100644
index 0000000..2729104
--- /dev/null
+++ b/posts/programming-anytone-d878uv-on-linux-using-windows10-and-virtualbox/comment_10_ca3af363fb42ab83915a1c7551e111d4._comment
@@ -0,0 +1,8 @@
+[[!comment format=mdwn
+ ip="2a02:587:4537:ee25:7868:c47:46a0:12cd"
+ claimedauthor="George Kapoulas"
+ subject="USB  port   in virtual box running windows 7 &  anytone878 cps"
+ date="2021-03-16T09:16:45Z"
+ content="""
+Sometimes I have to  manually select the port, but first I hve to istruct the virual machine to use the  port, then set the port  from the cps. Works fine  in  running  MS win 7   in virtula box  in LInux mint 20, enjoy
+"""]]

Add some clarifications suggested by Roberto U.
diff --git a/posts/encrypting-your-home-directory-using.mdwn b/posts/encrypting-your-home-directory-using.mdwn
index 44e3f99..b4ad3b6 100644
--- a/posts/encrypting-your-home-directory-using.mdwn
+++ b/posts/encrypting-your-home-directory-using.mdwn
@@ -3,7 +3,13 @@
 [[!meta license="[Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/)"]]
 Laptops are easily lost or stolen and in order to protect your emails, web passwords, encryption keys, etc., you should really think about encrypting (at least) your home directory.
 
-If you happen to have `/home` on a separate partition already (`/dev/sda5` in this example), then it's a really easy process:
+If you happen to have `/home` on a separate partition already (`/dev/sda5` in this example), then it's a really easy process.
+
+Do the following as the `root` user:
+
+0. Install the [`cryptsetup` package](https://packages.debian.org/stable/cryptsetup):
+
+       apt install cryptsetup
 
 1. Copy your home directory to a temporary directory on a different partition:
 
@@ -21,7 +27,7 @@ If you happen to have `/home` on a separate partition already (`/dev/sda5` in th
 
        chome    /dev/sda5    none    luks,timeout=30
 
-4. Set the home partition to this in `/etc/fstab`:
+4. Set the home partition to this in `/etc/fstab` (replacing the original home partition line):
 
        /dev/mapper/chome /home ext4 nodev,nosuid,noatime 0 2
 
@@ -31,6 +37,8 @@ If you happen to have `/home` on a separate partition already (`/dev/sda5` in th
        cp -a /homebackup/* /home
        rm -rf /homebackup
 
-That's it. Now to fully secure your laptop against theft, you should think about an [encrypted backup strategy](http://packages.debian.org/sid/duplicity) for your data...
+That's it. Next time you boot your laptop, you will be prompted for the passphrase you set in Step 2.
+
+Now to fully secure your laptop against theft, you should think about an [encrypted backup strategy](http://packages.debian.org/sid/duplicity) for your data...
 
 [[!tag debian]] [[!tag sysadmin]] [[!tag ubuntu]] [[!tag luks]] [[!tag ext4]]

Use Tor with Kodi.
diff --git a/posts/creating-kodi-media-pc-raspberry-pi.mdwn b/posts/creating-kodi-media-pc-raspberry-pi.mdwn
index 517c044..4c0ef3d 100644
--- a/posts/creating-kodi-media-pc-raspberry-pi.mdwn
+++ b/posts/creating-kodi-media-pc-raspberry-pi.mdwn
@@ -124,6 +124,16 @@ apps if needed:
     cp /etc/xdg/lxsession/LXDE-pi/autostart ~/.config/lxsession/LXDE-pi/
     echo "@kodi" >> ~/.config/lxsession/LXDE-pi/autostart
 
+In order to improve privacy while fetching metadata, I also installed Tor:
+
+    apt install tor
+
+and then set a proxy in the Kodi [*System | Internet access* settings](https://kodi.wiki/view/Settings/System/Internet_access#Proxy_type):
+
+- Proxy type: `SOCKS5 with remote DNS resolving`
+- Server: `localhost`
+- Port: `9050`
+
 ## Network File System
 
 In order to avoid having to have all media storage connected directly to the
@@ -177,4 +187,4 @@ Then create the mount points and mount everything:
     mount /kodi/movies
     mount /kodi/tv
 
-[[!tag raspberrypi]] [[!tag kodi]] [[!tag debian]] [[!tag nfs]]
+[[!tag raspberrypi]] [[!tag kodi]] [[!tag debian]] [[!tag nfs]] [[!tag tor]]

Fix typo in comment.
diff --git a/posts/recovering-from-unbootable-ubuntu-encrypted-lvm-root-partition/comment_7_6d782ed81d9fbdfbcc70fdf6da15fbed._comment b/posts/recovering-from-unbootable-ubuntu-encrypted-lvm-root-partition/comment_7_6d782ed81d9fbdfbcc70fdf6da15fbed._comment
index 45d5fa2..82d05c2 100644
--- a/posts/recovering-from-unbootable-ubuntu-encrypted-lvm-root-partition/comment_7_6d782ed81d9fbdfbcc70fdf6da15fbed._comment
+++ b/posts/recovering-from-unbootable-ubuntu-encrypted-lvm-root-partition/comment_7_6d782ed81d9fbdfbcc70fdf6da15fbed._comment
@@ -4,5 +4,5 @@
  subject="Boot to earlier kernel worked better"
  date="2020-02-05T02:19:59Z"
  content="""
-As William (William — 13:54, 06 May 2018) did, I booted to the preceding kernel version, and as I logged in, I saw LivePatch flash by saying it had just updated something. I used `apt` to update everything and restarted, my machine is now runnign like a Swiss watch!
+As William (William — 13:54, 06 May 2018) did, I booted to the preceding kernel version, and as I logged in, I saw LivePatch flash by saying it had just updated something. I used `apt` to update everything and restarted, my machine is now running like a Swiss watch!
 """]]

Fix typo in mount point.
diff --git a/posts/recovering-from-unbootable-ubuntu-encrypted-lvm-root-partition/comment_12_b5728229dac60e75e8ed8336fc56563e._comment b/posts/recovering-from-unbootable-ubuntu-encrypted-lvm-root-partition/comment_12_b5728229dac60e75e8ed8336fc56563e._comment
index b8227b0..2c81698 100644
--- a/posts/recovering-from-unbootable-ubuntu-encrypted-lvm-root-partition/comment_12_b5728229dac60e75e8ed8336fc56563e._comment
+++ b/posts/recovering-from-unbootable-ubuntu-encrypted-lvm-root-partition/comment_12_b5728229dac60e75e8ed8336fc56563e._comment
@@ -13,7 +13,7 @@ I had an additional problem and while running \"update-initramfs -c -k all\" I g
 
 Google couldn't answer anything about this error, but I finally figured it out - you just need to mount the sysfs (just like you mounted /proc and /dev). So just run this command:
 
-    mount -t sysfs sysfs /sys
+    mount -t sysfs sysfs /mnt/sys
 
 Now when you run update-initramfs again the error should be gone and you can boot to your Linux again!
 """]]

Comment moderation
diff --git a/posts/recovering-from-unbootable-ubuntu-encrypted-lvm-root-partition/comment_12_b5728229dac60e75e8ed8336fc56563e._comment b/posts/recovering-from-unbootable-ubuntu-encrypted-lvm-root-partition/comment_12_b5728229dac60e75e8ed8336fc56563e._comment
new file mode 100644
index 0000000..b8227b0
--- /dev/null
+++ b/posts/recovering-from-unbootable-ubuntu-encrypted-lvm-root-partition/comment_12_b5728229dac60e75e8ed8336fc56563e._comment
@@ -0,0 +1,19 @@
+[[!comment format=mdwn
+ ip="2001:7d0:8406:9e80:5810:b092:b8d9:11f9"
+ claimedauthor="Rene"
+ subject="Solution for: &quot;cryptsetup: ERROR: Couldn't find sysfs directory for 253:2&quot;"
+ date="2021-03-06T00:30:19Z"
+ content="""
+This is a superb guide, thanks!
+
+I had an additional problem and while running \"update-initramfs -c -k all\" I got the following error:
+
+    cryptsetup: ERROR: Couldn't find sysfs directory for 253:2
+
+
+Google couldn't answer anything about this error, but I finally figured it out - you just need to mount the sysfs (just like you mounted /proc and /dev). So just run this command:
+
+    mount -t sysfs sysfs /sys
+
+Now when you run update-initramfs again the error should be gone and you can boot to your Linux again!
+"""]]

Comment moderation
diff --git a/posts/recovering-from-unbootable-ubuntu-encrypted-lvm-root-partition/comment_11_941c33e1bd58ad5951b3d8dbba899edc._comment b/posts/recovering-from-unbootable-ubuntu-encrypted-lvm-root-partition/comment_11_941c33e1bd58ad5951b3d8dbba899edc._comment
new file mode 100644
index 0000000..332a9e1
--- /dev/null
+++ b/posts/recovering-from-unbootable-ubuntu-encrypted-lvm-root-partition/comment_11_941c33e1bd58ad5951b3d8dbba899edc._comment
@@ -0,0 +1,9 @@
+[[!comment format=mdwn
+ ip="85.167.103.197"
+ claimedauthor="Morgan"
+ url="morgansimonsen.com"
+ subject="LVM missing"
+ date="2021-03-03T21:47:24Z"
+ content="""
+My case was similar, but I had an additional issue. On my Ubuntu machine /usr/share/initramfs-tools/hooks/lvm2 was not set as executable. This caused it to be skipped when initramfs was built, leaving me with an initramfs without LVM. After making the file executable (chmod +x) and rebuilding initramfs, the system was bootable again.
+"""]]

Fix typo in post title.
diff --git a/posts/creating-kodi-media-pc-raspberry-pi.mdwn b/posts/creating-kodi-media-pc-raspberry-pi.mdwn
index 2c14dc5..517c044 100644
--- a/posts/creating-kodi-media-pc-raspberry-pi.mdwn
+++ b/posts/creating-kodi-media-pc-raspberry-pi.mdwn
@@ -1,4 +1,4 @@
-[[!meta title="Creating a Kodia media PC using a Raspberry Pi 4"]]
+[[!meta title="Creating a Kodi media PC using a Raspberry Pi 4"]]
 [[!meta date="2021-02-13T19:25:00.000-08:00"]]
 [[!meta license="[Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/)"]]
 

creating tag page tags/streamzap
diff --git a/tags/streamzap.mdwn b/tags/streamzap.mdwn
new file mode 100644
index 0000000..513a617
--- /dev/null
+++ b/tags/streamzap.mdwn
@@ -0,0 +1,4 @@
+[[!meta title="pages tagged streamzap"]]
+
+[[!inline pages="tagged(streamzap)" actions="no" archive="yes"
+feedshow=10]]

Add Streamzap in Kodi post.
diff --git a/posts/using-streamzap-remote-with-kodi.mdwn b/posts/using-streamzap-remote-with-kodi.mdwn
new file mode 100644
index 0000000..b5307d1
--- /dev/null
+++ b/posts/using-streamzap-remote-with-kodi.mdwn
@@ -0,0 +1,139 @@
+[[!meta title="Using a Streamzap remote control with Kodi"]]
+[[!meta date="2021-03-02T21:00:00.000-08:00"]]
+[[!meta license="[Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/)"]]
+
+After [installing Kodi on a Raspberry Pi 4](https://feeding.cloud.geek.nz/posts/creating-kodi-media-pc-raspberry-pi/), I found that my
+[Streamzap remote control](http://www.streamzap.com/consumer/pc_remote/index.php) worked for everything except the *Ok* and
+*Exit* buttons (which are supposed to get mapped to *Enter* and *Back*
+respectively).
+
+A very old set of instructions for this is [archived on the Kodi
+wiki](https://kodi.wiki/view/Archive:Set_up_Streamzap_PC_Remote_for_Linux)
+but here's a more modern version of it.
+
+## Root cause
+
+I finally tracked down the problem by enabling [debug
+logging](https://kodi.wiki/view/Log_file) in Kodi settings. I saw the
+following in `~/.kodi/temp/kodi.log` when presing the OK button:
+
+    DEBUG: Keyboard: scancode: 0x00, sym: 0x0000, unicode: 0x0000, modifier: 0x0
+    DEBUG: GetActionCode: Trying Hardy keycode for 0xf200
+    DEBUG: Previous line repeats 3 times.
+    DEBUG: HandleKey: long-0 (0x100f200, obc-16838913) pressed, action is
+    DEBUG: Keyboard: scancode: 0x00, sym: 0x0000, unicode: 0x0000, modifier: 0x0
+
+and this when pressing the Down button:
+
+    DEBUG: CLibInputKeyboard::ProcessKey - using delay: 500ms repeat: 125ms
+    DEBUG: Thread Timer start, auto delete: false
+    DEBUG: Keyboard: scancode: 0x6c, sym: 0x0112, unicode: 0x0000, modifier: 0x0
+    DEBUG: HandleKey: down (0xf081) pressed, action is Down
+    DEBUG: Thread Timer 2502349008 terminating
+    DEBUG: Keyboard: scancode: 0x6c, sym: 0x0112, unicode: 0x0000, modifier: 0x0
+
+This suggests that my Streamzap remote is recognized as a keyboard, which I
+can confirm using:
+
+    $ cat /proc/bus/input/devices 
+    I: Bus=0003 Vendor=0e9c Product=0000 Version=0100
+    N: Name="Streamzap PC Remote Infrared Receiver (0e9c:0000)"
+    P: Phys=usb-0000:01:00.0-1.2/input0
+    S: Sysfs=/devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb1/1-1/1-1.2/1-1.2:1.0/rc/rc0/input4
+    U: Uniq=
+    H: Handlers=kbd event0 
+    B: PROP=20
+    B: EV=100017
+    B: KEY=3ff 0 0 0 fc000 1 0 0 0 0 18000 4180 c0000801 9e1680 0 0 0
+    B: REL=3
+    B: MSC=10
+
+## Installing LIRC
+
+The [fix I found](https://github.com/graysky2/streamzap) is to
+put the following in `/etc/X11/xorg.conf.d/90-streamzap-disable.conf`:
+
+    Section "InputClass"
+    	Identifier "Ignore Streamzap IR"
+    	MatchProduct "Streamzap"
+    	MatchIsKeyboard "true"
+    	Option "Ignore" "true"
+    EndSection
+
+to prevent the remote from being used as a keyboard and to instead use it
+via [LIRC](https://lirc.org/), which can be installed like this:
+
+    apt install lirc lirc-compat-modules
+
+Put the following in `/etc/lirc/lirc_options`:
+
+    driver=default
+    device=/dev/lirc0
+
+and add the remote config for the Streamzap:
+
+    cd /etc/lirc/lircd.conf.d/
+    ln -s /usr/share/lirc/remotes/streamzap/lircd.conf.streamzap streamzap.conf
+
+### Testing
+
+Now you should be able to test the remote using:
+
+    mode2
+
+to see the undecoded infra-red signal, and:
+
+    irw
+
+to display the decoded key presses.
+
+## Kodi configuration
+
+Finally, as the `pi` user, put the [following config](https://raw.githubusercontent.com/graysky2/streamzap/master/kodi/Lircmap.xml) in `~/.kodi/userdata/Lircmap.xml`:
+
+    <lircmap>
+      <remote device="Streamzap_PC_Remote">
+        <power>KEY_POWER</power>
+        <play>KEY_PLAY</play>
+        <pause>KEY_PAUSE</pause>
+        <stop>KEY_STOP</stop>
+        <forward>KEY_FORWARD</forward>
+        <reverse>KEY_REWIND</reverse>
+        <left>KEY_LEFT</left>
+        <right>KEY_RIGHT</right>
+        <up>KEY_UP</up>
+        <down>KEY_DOWN</down>
+        <pageplus>KEY_CHANNELUP</pageplus>
+        <pageminus>KEY_CHANNELDOWN</pageminus>
+        <select>KEY_OK</select>
+        <back>KEY_EXIT</back>
+        <menu>KEY_MENU</menu>
+        <red>KEY_RED</red>
+        <green>KEY_GREEN</green>
+        <yellow>KEY_YELLOW</yellow>
+        <blue>KEY_BLUE</blue>
+        <skipplus>KEY_NEXT</skipplus>
+        <skipminus>KEY_PREVIOUS</skipminus>
+        <record>KEY_RECORD</record>
+        <volumeplus>KEY_VOLUMEUP</volumeplus>
+        <volumeminus>KEY_VOLUMEDOWN</volumeminus>
+        <mute>KEY_MUTE</mute>
+        <record>KEY_RECORD</record>
+        <one>KEY_1</one>
+        <two>KEY_2</two>
+        <three>KEY_3</three>
+        <four>KEY_4</four>
+        <five>KEY_5</five>
+        <six>KEY_6</six>
+        <seven>KEY_7</seven>
+        <eight>KEY_8</eight>
+        <nine>KEY_9</nine>
+        <zero>KEY_0</zero>
+      </remote>
+    </lircmap>
+
+In order for all of this to take effect, I simply rebooted the Pi:
+
+    sudo systemctl reboot
+
+[[!tag kodi]] [[!tag streamzap]] [[!tag lirc]]

Revert "Display the "Ubuntu" tag in the sidebar."
This reverts commit d436199d9039b2181fc6d753689d16110c30706c.
This tag is far too noisy and takes over the whole tag cloud.
diff --git a/sidebar.mdwn b/sidebar.mdwn
index 9935378..5c09f50 100644
--- a/sidebar.mdwn
+++ b/sidebar.mdwn
@@ -23,4 +23,4 @@
 [[Recent Comments|comments]]
 
 [[Tags]]:
-[[!pagestats pages="tags/* and !tags/debian and !tags/catalyst and !tags/mozilla and !tags/nzoss and !tags/sysadmin and !tags/mahara" among="posts/*"]]
+[[!pagestats pages="tags/* and !tags/debian and !tags/catalyst and !tags/mozilla and !tags/ubuntu and !tags/nzoss and !tags/sysadmin and !tags/mahara" among="posts/*"]]

Display the "Ubuntu" tag in the sidebar.
The blog aggregator I originally used it for is long gone.
diff --git a/sidebar.mdwn b/sidebar.mdwn
index 5c09f50..9935378 100644
--- a/sidebar.mdwn
+++ b/sidebar.mdwn
@@ -23,4 +23,4 @@
 [[Recent Comments|comments]]
 
 [[Tags]]:
-[[!pagestats pages="tags/* and !tags/debian and !tags/catalyst and !tags/mozilla and !tags/ubuntu and !tags/nzoss and !tags/sysadmin and !tags/mahara" among="posts/*"]]
+[[!pagestats pages="tags/* and !tags/debian and !tags/catalyst and !tags/mozilla and !tags/nzoss and !tags/sysadmin and !tags/mahara" among="posts/*"]]

Add more posts to the NetworkManager tag.
diff --git a/posts/sharing-wifi-connection-with-network-manager-hotspot.mdwn b/posts/sharing-wifi-connection-with-network-manager-hotspot.mdwn
index 82585ac..b0dcd5f 100644
--- a/posts/sharing-wifi-connection-with-network-manager-hotspot.mdwn
+++ b/posts/sharing-wifi-connection-with-network-manager-hotspot.mdwn
@@ -93,4 +93,4 @@ on my machine's local firewall:
     -A INPUT -d 10.42.0.255 -s 10.42.0.1 -j ACCEPT
     -A INPUT -d 10.42.0.1 -s 10.42.0.0/24 -j ACCEPT
 
-[[!tag nzoss]] [[!tag gnome]] [[!tag debian]] [[!tag wifi]]
+[[!tag gnome]] [[!tag debian]] [[!tag wifi]] [[!tag networkmanager]]
diff --git a/posts/upgrading-from-ubuntu-bionic-to-focal.mdwn b/posts/upgrading-from-ubuntu-bionic-to-focal.mdwn
index b828492..748a802 100644
--- a/posts/upgrading-from-ubuntu-bionic-to-focal.mdwn
+++ b/posts/upgrading-from-ubuntu-bionic-to-focal.mdwn
@@ -99,4 +99,4 @@ and then re-creating all initramfs:
 
 seemed to do the trick.
 
-[[!tag ubuntu]] [[!tag systemd]] [[!tag raid]]
+[[!tag ubuntu]] [[!tag systemd]] [[!tag raid]] [[!tag networkmanager]]

creating tag page tags/networkmanager
diff --git a/tags/networkmanager.mdwn b/tags/networkmanager.mdwn
new file mode 100644
index 0000000..7564660
--- /dev/null
+++ b/tags/networkmanager.mdwn
@@ -0,0 +1,4 @@
+[[!meta title="pages tagged networkmanager"]]
+
+[[!inline pages="tagged(networkmanager)" actions="no" archive="yes"
+feedshow=10]]

Add a new NetworkManager tag.
diff --git a/posts/using-iptables-with-network-manager.mdwn b/posts/using-iptables-with-network-manager.mdwn
index 030c598..43ebf47 100644
--- a/posts/using-iptables-with-network-manager.mdwn
+++ b/posts/using-iptables-with-network-manager.mdwn
@@ -63,4 +63,4 @@ Looking at `/var/log/iptables.log`, you'll be able to confirm that
 it is being called correctly for each network interface as they
 are started.
 
-[[!tag nzoss]] [[!tag debian]] [[!tag iptables]]
+[[!tag debian]] [[!tag iptables]] [[!tag networkmanager]]

Comment moderation
diff --git a/posts/creating-kodi-media-pc-raspberry-pi/comment_4_49f6f33218d532169f4c2e1eb730d5be._comment b/posts/creating-kodi-media-pc-raspberry-pi/comment_4_49f6f33218d532169f4c2e1eb730d5be._comment
new file mode 100644
index 0000000..0d2a9ea
--- /dev/null
+++ b/posts/creating-kodi-media-pc-raspberry-pi/comment_4_49f6f33218d532169f4c2e1eb730d5be._comment
@@ -0,0 +1,10 @@
+[[!comment format=mdwn
+ username="francois@665656f0ba400877c9b12e8fbb086e45aa01f7c0"
+ nickname="francois"
+ subject="Re: Debian for RaspberryPi"
+ date="2021-02-15T19:28:34Z"
+ content="""
+> Why did you install Raspbian, and not the pure Debian build for arm64? https://raspi.debian.net/
+
+Interesting. I was not aware there were images for stock Debian.
+"""]]

Comment moderation
diff --git a/posts/creating-kodi-media-pc-raspberry-pi/comment_3_568b0682ca4c9331a219756063470d75._comment b/posts/creating-kodi-media-pc-raspberry-pi/comment_3_568b0682ca4c9331a219756063470d75._comment
new file mode 100644
index 0000000..466bf2a
--- /dev/null
+++ b/posts/creating-kodi-media-pc-raspberry-pi/comment_3_568b0682ca4c9331a219756063470d75._comment
@@ -0,0 +1,8 @@
+[[!comment format=mdwn
+ ip="96.23.12.217"
+ claimedauthor="mgregoire"
+ subject="Debian for RaspberryPi"
+ date="2021-02-15T18:42:31Z"
+ content="""
+Why did you install Raspbian, and not the pure Debian build for arm64?  [[https://raspi.debian.net/]]
+"""]]

Comment moderation
diff --git a/posts/creating-kodi-media-pc-raspberry-pi/comment_2_2fabcd228593d113ea70ce386dbfc347._comment b/posts/creating-kodi-media-pc-raspberry-pi/comment_2_2fabcd228593d113ea70ce386dbfc347._comment
new file mode 100644
index 0000000..bdebfce
--- /dev/null
+++ b/posts/creating-kodi-media-pc-raspberry-pi/comment_2_2fabcd228593d113ea70ce386dbfc347._comment
@@ -0,0 +1,12 @@
+[[!comment format=mdwn
+ username="francois@665656f0ba400877c9b12e8fbb086e45aa01f7c0"
+ nickname="francois"
+ subject="Re: The &quot;pi&quot; user"
+ date="2021-02-14T20:33:58Z"
+ content="""
+> This may be a stupid question, but why don't you remove the \"pi\" user altogether?
+
+That's a fair question. The primary reason is that I would need to customize more things since that user is already setup for everything to just work.
+
+Once it's de-fanged (no sudo access, no ssh access, random password), it's probably not very dangerous.
+"""]]

Comment moderation
diff --git a/posts/creating-kodi-media-pc-raspberry-pi/comment_1_e40e667c1684fed5c3ed6791077b80c8._comment b/posts/creating-kodi-media-pc-raspberry-pi/comment_1_e40e667c1684fed5c3ed6791077b80c8._comment
new file mode 100644
index 0000000..f920aed
--- /dev/null
+++ b/posts/creating-kodi-media-pc-raspberry-pi/comment_1_e40e667c1684fed5c3ed6791077b80c8._comment
@@ -0,0 +1,8 @@
+[[!comment format=mdwn
+ ip="2001:780:107:b::b"
+ claimedauthor="Matthias Urlichs"
+ subject="The &quot;pi&quot; user"
+ date="2021-02-14T11:35:32Z"
+ content="""
+This may be a stupid question, but why don't you remove the \"pi\" user altogether?
+"""]]

creating tag page tags/kodi
diff --git a/tags/kodi.mdwn b/tags/kodi.mdwn
new file mode 100644
index 0000000..e2113c2
--- /dev/null
+++ b/tags/kodi.mdwn
@@ -0,0 +1,4 @@
+[[!meta title="pages tagged kodi"]]
+
+[[!inline pages="tagged(kodi)" actions="no" archive="yes"
+feedshow=10]]

creating tag page tags/raspberrypi
diff --git a/tags/raspberrypi.mdwn b/tags/raspberrypi.mdwn
new file mode 100644
index 0000000..8ba4ac8
--- /dev/null
+++ b/tags/raspberrypi.mdwn
@@ -0,0 +1,4 @@
+[[!meta title="pages tagged raspberrypi"]]
+
+[[!inline pages="tagged(raspberrypi)" actions="no" archive="yes"
+feedshow=10]]

creating tag page tags/nfs
diff --git a/tags/nfs.mdwn b/tags/nfs.mdwn
new file mode 100644
index 0000000..51171e4
--- /dev/null
+++ b/tags/nfs.mdwn
@@ -0,0 +1,4 @@
+[[!meta title="pages tagged nfs"]]
+
+[[!inline pages="tagged(nfs)" actions="no" archive="yes"
+feedshow=10]]

Add Kodi blog post.
diff --git a/posts/creating-kodi-media-pc-raspberry-pi.mdwn b/posts/creating-kodi-media-pc-raspberry-pi.mdwn
new file mode 100644
index 0000000..2c14dc5
--- /dev/null
+++ b/posts/creating-kodi-media-pc-raspberry-pi.mdwn
@@ -0,0 +1,180 @@
+[[!meta title="Creating a Kodia media PC using a Raspberry Pi 4"]]
+[[!meta date="2021-02-13T19:25:00.000-08:00"]]
+[[!meta license="[Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/)"]]
+
+Here's how I set up a media PC using [Kodi](https://kodi.tv/) (formerly XMBC) and a
+[Raspberry Pi 4](https://www.raspberrypi.org/products/raspberry-pi-4-model-b/).
+
+## Hardware
+
+The hardware is fairly straightforward, but here's what I ended up getting:
+
+- [Raspberry Pi 4 board](https://www.amazon.ca/gp/product/B089GNJNDS): I
+  went for the maximum amount of RAM (8 GB).
+- [SD-card](https://www.amazon.ca/gp/product/B07DKGP6RB): Since I'm not
+  going to store any media on here, 32 GB is plenty.
+- [HDMI to **micro**-HDMI cable](https://www.amazon.ca/gp/product/B00Z07JYLE)
+- [Case and power supply](https://www.amazon.ca/gp/product/B08CRDKX6T)
+
+You'll probably want to add a [remote
+control](https://kodi.wiki/view/Remote_controls) to that setup. I used an
+old [Streamzap](http://www.streamzap.com/consumer/pc_remote/index.php) I had
+lying around.
+
+## Installing the OS on the SD-card
+
+Plug the SD card into a computer using a USB adapter.
+
+Download the [imager](https://www.raspberrypi.org/software/) and use it to
+install **Raspbian** on the SDcard.
+
+Then you can simply plug the SD card into the Pi and boot.
+
+## System configuration
+
+Using `sudo raspi-config`, I changed the following:
+
+- Set hostname (*System Options*)
+- Wait for network at boot (*System Options*): [needed for NFS](https://raspberrypi.stackexchange.com/a/53147)
+- Disable screen blanking (*Display Options*)
+- Enable ssh (*Interface Options*)
+- Configure locale, timezone and keyboard (*Localisation Options*)
+- Set WiFi country (*Localisation Options*)
+
+Then I [enabled automatic updates](https://raspberrypi.stackexchange.com/a/102350):
+
+    apt install unattended-upgrades anacron
+
+    echo 'Unattended-Upgrade::Origins-Pattern {
+            "origin=Debian,codename=${distro_codename},label=Debian";
+            "origin=Debian,codename=${distro_codename},label=Debian-Security";
+            "origin=Raspbian,codename=${distro_codename},label=Raspbian";
+            "origin=Raspberry Pi Foundation,codename=${distro_codename},label=Raspberry Pi Foundation";
+    };' | sudo tee /etc/apt/apt.conf.d/51unattended-upgrades-raspbian
+
+
+### Headless setup
+
+Should you need to do the setup without a monitor, you can enable ssh by
+inserting the SD card into a computer and then creating an empty file called
+`ssh` in the boot partition.
+
+Plug it into your router and boot it up. Check the IP that it received by
+looking at the active DHCP leases in your router's admin panel.
+
+Then login:
+
+    ssh -o PreferredAuthentications=password -o PubkeyAuthentication=no pi@192.168.1.xxx
+
+using the default password of `raspberry`.
+
+## Hardening
+
+In order to secure the Pi, I followed most of the [steps I usually take when
+setting up a new Linux
+server](https://feeding.cloud.geek.nz/posts/usual-server-setup/).
+
+I created a new user account for admin and ssh access:
+
+    adduser francois
+    addgroup sshuser
+    adduser francois sshuser
+    adduser francois sudo
+
+and changed the `pi` user password to a random one:
+
+    pwgen -sy 32
+    sudo passwd pi
+
+before removing its admin permissions:
+
+    deluser pi adm
+    deluser pi sudo
+    deluser pi dialout
+    deluser pi cdrom
+    deluser pi lpadmin
+
+Finally, I enabled the [Uncomplicated Firewall](https://launchpad.net/ufw)
+by installing its package:
+
+    apt install ufw
+
+and only allowing ssh connections.
+
+After starting ufw using `systemctl start ufw.service`, you can check that
+it's configured as expected using `ufw status`. It should display the
+following:
+
+    Status: active
+    
+    To                         Action      From
+    --                         ------      ----
+    22/tcp                     ALLOW       Anywhere
+    22/tcp (v6)                ALLOW       Anywhere (v6)
+
+## Installing Kodi
+
+Kodi is very straightforward to install since it's now part of the Raspbian repositories:
+
+    apt install kodi
+
+To make it start at boot/login, while still being able to exit and use other
+apps if needed:
+
+    cp /etc/xdg/lxsession/LXDE-pi/autostart ~/.config/lxsession/LXDE-pi/
+    echo "@kodi" >> ~/.config/lxsession/LXDE-pi/autostart
+
+## Network File System
+
+In order to avoid having to have all media storage connected directly to the
+Pi via USB, I setup an
+[NFS](https://en.wikipedia.org/wiki/Network_File_System) share over my local
+network.
+
+First, give static IP allocations to the server and the Pi in your DHCP
+server, then add it to the `/etc/hosts` file on your NFS server:
+
+    192.168.1.3    pi
+
+Install the NFS server package:
+
+    apt instal nfs-kernel-server
+
+Setup the directories to share in `/etc/exports`:
+
+    /pub/movies    pi(ro,insecure,all_squash,subtree_check)
+    /pub/tv_shows  pi(ro,insecure,all_squash,subtree_check)
+
+Open the right ports on your firewall by putting this in `/etc/network/iptables.up.rules`:
+
+    -A INPUT -s 192.168.1.3 -p udp -j ACCEPT
+    -A INPUT -s 192.168.1.0/24 -p tcp --dport 111 -j ACCEPT
+    -A INPUT -s 192.168.1.0/24 -p udp --dport 111 -j ACCEPT
+    -A INPUT -s 192.168.1.0/24 -p udp --dport 123 -j ACCEPT
+    -A INPUT -s 192.168.1.0/24 -p tcp --dport 600:1124 -j ACCEPT
+    -A INPUT -s 192.168.1.0/24 -p udp --dport 600:1124 -j ACCEPT
+    -A INPUT -s 192.168.1.0/24 -p tcp --dport 2049 -j ACCEPT
+    -A INPUT -s 192.168.1.0/24 -p udp --dport 2049 -j ACCEPT
+
+Finally, apply all of these changes:
+
+    iptables-apply
+    systemctl restart nfs-kernel-server.service
+
+On the Pi, put the server's static IP in `/etc/hosts`:
+
+    192.168.1.2    fileserver
+
+and this in `/etc/fstab`:
+
+    fileserver:/data/movies  /kodi/movies  nfs  ro,bg,hard,noatime,async,nolock  0  0
+    fileserver:/data/tv      /kodi/tv      nfs  ro,bg,hard,noatime,async,nolock  0  0
+
+Then create the mount points and mount everything:
+
+    mkdir -p /kodi/movies
+    mkdir /kodi/tv
+    mount /kodi/movies
+    mount /kodi/tv
+
+[[!tag raspberrypi]] [[!tag kodi]] [[!tag debian]] [[!tag nfs]]
diff --git a/posts/upgrade-dmr-hotspot-pistar-4_1.mdwn b/posts/upgrade-dmr-hotspot-pistar-4_1.mdwn
index 4cd908f..ef746a5 100644
--- a/posts/upgrade-dmr-hotspot-pistar-4_1.mdwn
+++ b/posts/upgrade-dmr-hotspot-pistar-4_1.mdwn
@@ -96,4 +96,4 @@ As long as you didn't reuse the original SD card for this upgrade, rolling
 back to version 3.4.17 simply involves shutting down the pi and then
 swapping the new SD card for the old one and then starting it up again.
 
-[[!tag dmr]] [[!tag ham]] [[!tag pistar]]
+[[!tag dmr]] [[!tag ham]] [[!tag pistar]] [[!tag raspberrypi]]

Purge sendmail too in case that was installed instead of exim4.
For some reason, I saw sendmail installed on Raspbian instead of exim4.
diff --git a/posts/usual-server-setup.mdwn b/posts/usual-server-setup.mdwn
index 20bfe4b..73cb69b 100644
--- a/posts/usual-server-setup.mdwn
+++ b/posts/usual-server-setup.mdwn
@@ -322,7 +322,7 @@ and then run:
 # Mail
 
     apt install postfix libsasl2-modules
-    apt purge exim4-base exim4-daemon-light exim4-config
+    apt purge exim4-base exim4-daemon-light exim4-config sendmail sendmail-bin sendmail-base sendmail-cf
 
 Configuring mail properly is tricky but the following has worked for me.
 

Add missing apt-file call.
diff --git a/posts/usual-server-setup.mdwn b/posts/usual-server-setup.mdwn
index 632b7ae..20bfe4b 100644
--- a/posts/usual-server-setup.mdwn
+++ b/posts/usual-server-setup.mdwn
@@ -291,6 +291,7 @@ Also, [`command-not-found` won't work until you update the apt cache](https://bu
 
     apt update
     update-command-not-found
+    apt-file update
 
 # Apache configuration
 

Set the default editor as soon as possible.
diff --git a/posts/usual-server-setup.mdwn b/posts/usual-server-setup.mdwn
index a8412ea..632b7ae 100644
--- a/posts/usual-server-setup.mdwn
+++ b/posts/usual-server-setup.mdwn
@@ -26,6 +26,19 @@ Then I check the hard drives using:
 
     apt install etckeeper git sudo vim
 
+Since I use vim for all of my configuration file editing, I make it the
+default editor:
+
+    update-alternatives --config editor
+
+and I turn on syntax highlighting and visual beeping globally by adding the
+following to `/etc/vim/vimrc.local`:
+
+    syntax on
+    set background=dark
+    set visualbell
+    set nomodeline
+
 To keep track of the configuration changes I make in `/etc/`, I use etckeeper
 to keep that directory in a git repository and make the following changes to
 the default `/etc/etckeeper/etckeeper.conf`:
@@ -59,19 +72,6 @@ default debconf level to medium:
 
     dpkg-reconfigure debconf
 
-Since I use vim for all of my configuration file editing, I make it the
-default editor:
-
-    update-alternatives --config editor
-
-and I turn on syntax highlighting and visual beeping globally by adding the
-following to `/etc/vim/vimrc.local`:
-
-    syntax on
-    set background=dark
-    set visualbell
-    set nomodeline
-
 # ssh
 
     apt install openssh-server mosh fail2ban

creating tag page tags/wifi
diff --git a/tags/wifi.mdwn b/tags/wifi.mdwn
new file mode 100644
index 0000000..98f2e25
--- /dev/null
+++ b/tags/wifi.mdwn
@@ -0,0 +1,4 @@
+[[!meta title="pages tagged wifi"]]
+
+[[!inline pages="tagged(wifi)" actions="no" archive="yes"
+feedshow=10]]

Add a new wifi tag.
diff --git a/posts/encoding-wifi-access-point-passwords-qr-code.mdwn b/posts/encoding-wifi-access-point-passwords-qr-code.mdwn
index bffca9d..dbc91d1 100644
--- a/posts/encoding-wifi-access-point-passwords-qr-code.mdwn
+++ b/posts/encoding-wifi-access-point-passwords-qr-code.mdwn
@@ -53,4 +53,4 @@ If you can't do this locally for some reason, there is also an [in-browser
 QR code generator](https://qifi.org/) with [source code
 available](https://github.com/evgeni/qifi).
 
-[[!tag ios]] [[!tag android]] [[!tag security]] [[!tag debian]] [[!tag nzoss]]
+[[!tag ios]] [[!tag android]] [[!tag security]] [[!tag debian]] [[!tag nzoss]] [[!tag wifi]]
diff --git a/posts/fixing-turris-omnia-wifi-quality.mdwn b/posts/fixing-turris-omnia-wifi-quality.mdwn
index c4a9b91..70219ad 100644
--- a/posts/fixing-turris-omnia-wifi-quality.mdwn
+++ b/posts/fixing-turris-omnia-wifi-quality.mdwn
@@ -74,4 +74,4 @@ as I passed my advanced [amateur radio license
 exam](https://launchpad.net/canadian-ham-exam). I guess that was a good way
 to put the course material into practice!
 
-[[!tag nzoss]] [[!tag turris]]
+[[!tag nzoss]] [[!tag turris]] [[!tag wifi]]
diff --git a/posts/setting-wifi-regulatory-domain-linux-openwrt.mdwn b/posts/setting-wifi-regulatory-domain-linux-openwrt.mdwn
index e65b8ad..8c9f2bb 100644
--- a/posts/setting-wifi-regulatory-domain-linux-openwrt.mdwn
+++ b/posts/setting-wifi-regulatory-domain-linux-openwrt.mdwn
@@ -59,4 +59,4 @@ if you have the hardware, otherwise
 [WiFi Analyzer](https://f-droid.org/repository/browse/?fdid=com.vrem.wifianalyzer)
 is a good choice on Android.
 
-[[!tag debian]] [[!tag openwrt]] [[!tag gargoyle]]
+[[!tag debian]] [[!tag openwrt]] [[!tag gargoyle]] [[!tag wifi]]
diff --git a/posts/sharing-wifi-connection-with-network-manager-hotspot.mdwn b/posts/sharing-wifi-connection-with-network-manager-hotspot.mdwn
index e307e52..82585ac 100644
--- a/posts/sharing-wifi-connection-with-network-manager-hotspot.mdwn
+++ b/posts/sharing-wifi-connection-with-network-manager-hotspot.mdwn
@@ -93,4 +93,4 @@ on my machine's local firewall:
     -A INPUT -d 10.42.0.255 -s 10.42.0.1 -j ACCEPT
     -A INPUT -d 10.42.0.1 -s 10.42.0.0/24 -j ACCEPT
 
-[[!tag nzoss]] [[!tag gnome]] [[!tag debian]]
+[[!tag nzoss]] [[!tag gnome]] [[!tag debian]] [[!tag wifi]]
diff --git a/posts/using-all-5ghz-wifi-frequencies-in-gargoyle-router.mdwn b/posts/using-all-5ghz-wifi-frequencies-in-gargoyle-router.mdwn
index c65cae1..5590f51 100644
--- a/posts/using-all-5ghz-wifi-frequencies-in-gargoyle-router.mdwn
+++ b/posts/using-all-5ghz-wifi-frequencies-in-gargoyle-router.mdwn
@@ -67,4 +67,4 @@ If you are interested, there is a lot more information about how all of this
 works in the
 [kernel documentation for the wireless stack](https://wireless.wiki.kernel.org/en/developers/regulatory/processing_rules#country_definition).
 
-[[!tag debian]] [[!tag nzoss]] [[!tag openwrt]] [[!tag gargoyle]]
+[[!tag debian]] [[!tag nzoss]] [[!tag openwrt]] [[!tag gargoyle]] [[!tag wifi]]
diff --git a/posts/using-gogo-wifi-linux.mdwn b/posts/using-gogo-wifi-linux.mdwn
index 8dfe92f..4fdb969 100644
--- a/posts/using-gogo-wifi-linux.mdwn
+++ b/posts/using-gogo-wifi-linux.mdwn
@@ -40,4 +40,4 @@ You may want to create an email template for this so that you can fire off a
 quick email to them as soon as you connect. I will probably write a script
 for it next time I use this service.
 
-[[!tag debian]] [[!tag firefox]]
+[[!tag debian]] [[!tag firefox]] [[!tag wifi]]

Increased backup frequency.
diff --git a/posts/automated-mythtv-maintenance-tasks.mdwn b/posts/automated-mythtv-maintenance-tasks.mdwn
index 43cce7c..3ae6566 100644
--- a/posts/automated-mythtv-maintenance-tasks.mdwn
+++ b/posts/automated-mythtv-maintenance-tasks.mdwn
@@ -8,11 +8,12 @@ server.
 
 The first part performs a [database backup](https://www.mythtv.org/wiki/User_Manual:Periodic_Maintenance#The_database):
 
-    5 1 * * *  mythtv  /usr/share/mythtv/mythconverg_backup.pl
+    5 1,13 * * *  mythtv  /usr/share/mythtv/mythconverg_backup.pl
 
 which I previously configured by putting the following in `/home/mythtv/.mythtv/backuprc`:
 
     DBBackupDirectory=/var/backups/mythtv
+    rotate=15
 
 and creating a new directory for it:
 

Fix typo.
diff --git a/posts/recovering-from-corrupt-mariadb-index-page.mdwn b/posts/recovering-from-corrupt-mariadb-index-page.mdwn
index 650c89f..ec909ce 100644
--- a/posts/recovering-from-corrupt-mariadb-index-page.mdwn
+++ b/posts/recovering-from-corrupt-mariadb-index-page.mdwn
@@ -61,7 +61,7 @@ restoring from backup.
 
 First of all, I shut down MythTV as `root`:
 
-    kilall mythfrontend
+    killall mythfrontend
     systemctl stop mythtv-status.service
     systemctl stop mythtv-backend.service
 

Comment moderation
diff --git a/posts/fixing-turris-omnia-wifi-quality/comment_1_bceb9a70d0604063d2ce138a668e309a._comment b/posts/fixing-turris-omnia-wifi-quality/comment_1_bceb9a70d0604063d2ce138a668e309a._comment
new file mode 100644
index 0000000..3a3cc16
--- /dev/null
+++ b/posts/fixing-turris-omnia-wifi-quality/comment_1_bceb9a70d0604063d2ce138a668e309a._comment
@@ -0,0 +1,12 @@
+[[!comment format=mdwn
+ ip="2a01:c22:886a:1200:ccf4:eec2:481:46ae"
+ claimedauthor="Till"
+ subject="Thanks a lot!"
+ date="2021-01-31T00:53:55Z"
+ content="""
+Thank you so much for sharing your findings about the swapped pigtail cables in your Turris Omnia. I bought a TO just recently and set it up tonight. I noticed that I got signal levels of just -79 dBm in 2.4 GHz even when I was just 1 m away from the device. At the same time & place, I received 5 GHz signal with about -34 dBm.
+
+Without your post and detailed description it would probably have taken me days to find out whats going on...
+
+
+"""]]
diff --git a/posts/ipv6-and-openvpn-on-linode/comment_1_dae3978e13f51bdd6654e5cfafb7d53e._comment b/posts/ipv6-and-openvpn-on-linode/comment_1_dae3978e13f51bdd6654e5cfafb7d53e._comment
new file mode 100644
index 0000000..15ca82a
--- /dev/null
+++ b/posts/ipv6-and-openvpn-on-linode/comment_1_dae3978e13f51bdd6654e5cfafb7d53e._comment
@@ -0,0 +1,15 @@
+[[!comment format=mdwn
+ ip="2406:1e00:b910:7704::1006"
+ subject="Multiple Prefixes"
+ date="2021-02-01T00:48:09Z"
+ content="""
+This works well, and clients will be assigned an address in the prefix.
+
+However, some applications want clients to talk to each other through the tunnel.
+
+For IPv4, they could just use each other's 10.8.0.x addresses, but with this configuration, the prefix is not a private value and therefore subject to (unlikely but possible) change.
+
+A solution is to use IPv6 ULAs, but then clients can't reach the IPv6 internet without prefix translation. In reality, we want to be able to give each client an IPv4 10.8.0.x address, an IPv6 GUA, and an IPv6 ULA. 
+
+How do we extend this configuration to achieve that? 
+"""]]

Comment moderation
diff --git a/posts/programming-anytone-d878uv-on-linux-using-windows10-and-virtualbox/comment_9_b06ee049460f9a4d182a670523892490._comment b/posts/programming-anytone-d878uv-on-linux-using-windows10-and-virtualbox/comment_9_b06ee049460f9a4d182a670523892490._comment
new file mode 100644
index 0000000..d58a7f4
--- /dev/null
+++ b/posts/programming-anytone-d878uv-on-linux-using-windows10-and-virtualbox/comment_9_b06ee049460f9a4d182a670523892490._comment
@@ -0,0 +1,8 @@
+[[!comment format=mdwn
+ ip="67.61.210.79"
+ claimedauthor="Ben"
+ subject="USB/Com Port"
+ date="2021-01-25T03:36:43Z"
+ content="""
+Did you do anything to configure the USB or COM port? I've tried both but couldn't read from the device. 
+"""]]

Fix filename.
diff --git a/posts/programming-dmr-radio-with-cps/ve7rag_bc2.png b/posts/programming-dmr-radio-with-cps/ve7rag-bc2.png
similarity index 100%
rename from posts/programming-dmr-radio-with-cps/ve7rag_bc2.png
rename to posts/programming-dmr-radio-with-cps/ve7rag-bc2.png

Add DMR post on AnyTone.
diff --git a/posts/programming-dmr-radio-with-cps.mdwn b/posts/programming-dmr-radio-with-cps.mdwn
new file mode 100644
index 0000000..d23cf8d
--- /dev/null
+++ b/posts/programming-dmr-radio-with-cps.mdwn
@@ -0,0 +1,184 @@
+[[!meta title="Programming a DMR radio with its CPS"]]
+[[!meta date="2021-01-04T23:40:00.000-08:00"]]
+[[!meta license="[Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/)"]]
+
+Here are some notes I took around programming my [AnyTone AT-D878UV
+radio](https://www.bridgecomsystems.com/collections/amateur-handheld-radios/products/anytone-at-d878uv-dual-band-dmr-handheld-radio-w-gps-programming-cable)
+to operate on [DMR](https://en.wikipedia.org/wiki/Digital_mobile_radio)
+using the [CPS
+software](https://feeding.cloud.geek.nz/posts/programming-anytone-d878uv-on-linux-using-windows10-and-virtualbox/)
+that comes with it.
+
+Note that you can always [tune in to a VFO channel by
+hand](https://www.youtube.com/watch?v=VHapE2wqSMI) if you haven't had time
+to add it to your codeplug yet.
+
+# DMR terminology
+
+First of all, the [terminology of
+DMR](https://www.jeffreykopcak.com/2017/05/10/dmr-in-amateur-radio-terminology/)
+is quite different from that of the regular analog FM world.
+
+Here are the basic terms:
+
+- **Frequency**: *same meaning as in the analog world*
+- **Repeater**: *same meaning as in the analog world*
+- **Timeslot**: Each frequency is split into two timeslots (1 and 2) and what
+  that means that there can be two simultaneous transmissions on each frequency.
+- **Color code**: This is the digital equivalent of a CTCSS tone (sometimes
+  called privacy tone) in that using the incorrect code means that you will
+  tie up one of the timeslots on the frequency, but nobody else will hear
+  you. These are [not actually named after
+  colors](https://amateurradionotes.com/dmr.htm#colorcodes), but are instead
+  just numerical IDs from 0 to 15.
+
+There are two different identification mechanisms (both are required):
+
+- **Callsign**: This is the same identifier issued to you by your country's
+  amateur radio authority. Mine is [VA7GPL](https://www.qrz.com/lookup?tquery=VA7GPL&mode=callsign).
+- **Radio ID**: This is a unique numerical ID tied to your callsign which
+  you must [register for](https://radioid.net/register) ahead of time and
+  program into your radio. Mine is
+  [3027260](https://database.radioid.net/database/view?id=3027260#!).
+
+The following is where this digital mode becomes most interesting:
+
+- **Talkgroup**: a "chat room" where everything you say will be heard by
+  anybody listening to that talkgroup
+- **Network**: a group of repeaters connected together over the Internet
+  (typically) and sharing a common list of talkgroups
+- **Hotspot**: a personal simplex device which allows you to connect to a
+  network with your handheld and access all of the talkgroups available on
+  that network
+
+The most [active](https://app.brandmeisteractivity.live/) network these days
+is [Brandmeister](https://brandmeister.network/), but there are several others.
+
+- **Access**: This can either be *Always on* which means that a talkgroup
+  will be permanently broadcasting on a timeslot and frequency, or *PTT*
+  which means a talkgroup will not be broadcast until it is first "woken up"
+  by pressing the push-to-talk button and then will broadcast for a certain
+  amount of time before going to sleep again.
+- **Channel**: As in the analog world, this is what you select on your radio
+  when you want to talk to a group of people. In the digital world however, it
+  is tied not only to a frequency (and timeslot) and tone (color code), but
+  also to a specific talkgroup.
+
+Ultimately what you want to do when you program your radio is to find the
+talkgroups you are interested in (from the list offered by your local
+repeater) and then assign them to specific channel numbers on your radio.
+More on that later.
+
+# Callsign and Radio IDs
+
+Before we get to talkgroups, let's set your callsign and [Radio
+ID](https://radioid.net/database/search):
+
+![](/posts/programming-dmr-radio-with-cps/radioid.png)
+
+Then you need to download the latest list of Radio IDs so that your radio
+can display people's names and callsigns instead of just their numerical IDs.
+
+One approach is to only [download the list of users who recently
+talked](https://brandmeister.network/?page=contactsexport) on talkgroups you
+are interested in. For example, I used to download the contacts for the
+following talkgroups:
+```
+91,93,95,913,937,3026,3027,302,30271,30272,530,5301,5302,5303,5304,3100,3153,31330
+```
+but these days, what I normally do is to just download the [entire worldwide
+database](https://www.radioid.net/database/dumps) (`user.csv`) since my
+radio still has enough storage (200k entries) for it.
+
+In order for the `user.csv` file to work with the AnyTone CPS, it needs to
+have particular columns and use the DOS end-of-line characters (`apt install
+dos2unix` if you want to do it manually). I wrote a
+[script](https://github.com/fmarier/user-scripts/blob/master/dmr-users) to
+do all of the work for me.
+
+If you use [dmrconfig](https://github.com/sergev/dmrconfig) to program this
+radio instead, then the conversion is unnecessary. The `user.csv` file can
+be used directly, however it will be truncated due to an [incorrect limit
+hard-coded in the software](https://github.com/sergev/dmrconfig/issues/50).
+
+# Talkgroups
+
+Next, you need to pick the talkgroups you would like to allocate to specific
+channels on your radio.
+
+Start by looking at the documentation for your local repeaters (e.g.
+[VE7RAG](http://www.bcfmca.bc.ca/dmr.php) and
+[VE7NWR](http://www.nwarc.org/cms/?q=node/330) in the Vancouver area).
+
+In addition to telling you the listen and transmit *frequencies* of the
+repeater (again, this works the same way as with analog FM), these will tell
+you which *talkgroups* are available and what *timeslots* and *color codes*
+they have been set to. It will also tell you the type of *access* for each
+of these talkgroups.
+
+This is how I programmed a channel:
+
+![](/posts/programming-dmr-radio-with-cps/ve7rag-bc2.png)
+
+and a talkgroup on the VE7RAG repeater in my radio:
+
+![](/posts/programming-dmr-radio-with-cps/tg-bc2.png)
+
+If you don't have a local repeater with DMR capability, or if you want to
+access talkgroups available on a different network, then you will need to
+get a DMR hotspot such as one that's compatible with the
+[Pi-Star](https://www.pistar.uk/) software.
+
+This is an excerpt from the programming I created for the talkgroups
+I made available through my hotspot:
+
+![](/posts/programming-dmr-radio-with-cps/brandmeister-nz-national.png)
+![](/posts/programming-dmr-radio-with-cps/tg-zl-national.png)
+
+One of the unfortunate limitations of the CPS software for the AnyTone 878
+is that talkgroup numbers are globally unique identifiers. This means that
+if TG1234 (hypothetical example) is *Ragchew 3000* on DMR-MARC but
+*Iceland-wide chat* on Brandmeister, then you can't have two copies of it
+with different names. The solution I found for this was to give that
+talkgroup the name "TG1234" instead of "Ragchew3k" or "Iceland". I use a
+more memorable name for non-conflicting talkgroups, but for the problematic
+ones, I simply repeat the talkgroup number.
+
+# Simplex
+
+Talkgroups are not required to operate on DMR. Just like analog FM, you can
+talk to another person point-to-point using a simplex channel.
+
+The convention for all simplex channels is the following:
+
+- Talkgroup: `99`
+- Color code: `1`
+- Timeslot: `1`
+- Admit criteria: `Always`
+- In Call Criteria: `TX` or `Always`
+
+![](/posts/programming-dmr-radio-with-cps/tg-simplex.png)
+
+After talking to the [British Columbia Amateur Radio Coordination
+Council](https://bcarcc.org/), I found that the following frequency ranges are
+most suitable for DMR simplex:
+
+- 145.710-145.790 MHz (simplex digital transmissions)
+- 446.000-446.975 MHz (all simplex modes)
+
+The [VECTOR
+list](https://docs.google.com/spreadsheets/d/1XNRLnQWY1AJiQQy1umiEBTf4oBMyhrjOc0CIhlLm4pI/edit#gid=0)
+identifies two frequencies in particular:
+
+- 446.075 MHz
+- 446.500 MHz
+
+![](/posts/programming-dmr-radio-with-cps/vector-dmr-simplex2.png)
+
+# Learn more
+
+If you'd like to learn more about DMR, I would suggest you start with [this
+excellent guide](http://www.k4usd.org/guide.pdf) (also [mirrored
+here](https://www.raqi.ca/~ve2rae/dmr/Amateur_Radio_Guide_to_DMR.pdf)).
+
+[[!tag ham]] [[!tag anytone]]
diff --git a/posts/programming-dmr-radio-with-cps/brandmeister-nz-national.png b/posts/programming-dmr-radio-with-cps/brandmeister-nz-national.png
new file mode 100644
index 0000000..f00db55
Binary files /dev/null and b/posts/programming-dmr-radio-with-cps/brandmeister-nz-national.png differ
diff --git a/posts/programming-dmr-radio-with-cps/radioid.png b/posts/programming-dmr-radio-with-cps/radioid.png
new file mode 100644
index 0000000..69cee8f
Binary files /dev/null and b/posts/programming-dmr-radio-with-cps/radioid.png differ
diff --git a/posts/programming-dmr-radio-with-cps/tg-bc2.png b/posts/programming-dmr-radio-with-cps/tg-bc2.png
new file mode 100644

(Diff truncated)
calendar update
diff --git a/archives/2021.mdwn b/archives/2021.mdwn
new file mode 100644
index 0000000..325ac2d
--- /dev/null
+++ b/archives/2021.mdwn
@@ -0,0 +1 @@
+[[!calendar type=year year=2021 pages="page(posts/*) and !*/Discussion"]]
diff --git a/archives/2021/01.mdwn b/archives/2021/01.mdwn
new file mode 100644
index 0000000..9b5bb5d
--- /dev/null
+++ b/archives/2021/01.mdwn
@@ -0,0 +1,5 @@
+[[!sidebar content="""
+[[!calendar type=month month=01 year=2021 pages="page(posts/*) and !*/Discussion"]]
+"""]]
+
+[[!inline pages="creation_month(01) and creation_year(2021) and page(posts/*) and !*/Discussion" show=0 feeds=no reverse=yes]]
diff --git a/archives/2021/02.mdwn b/archives/2021/02.mdwn
new file mode 100644
index 0000000..3903fe8
--- /dev/null
+++ b/archives/2021/02.mdwn
@@ -0,0 +1,5 @@
+[[!sidebar content="""
+[[!calendar type=month month=02 year=2021 pages="page(posts/*) and !*/Discussion"]]
+"""]]
+
+[[!inline pages="creation_month(02) and creation_year(2021) and page(posts/*) and !*/Discussion" show=0 feeds=no reverse=yes]]
diff --git a/archives/2021/03.mdwn b/archives/2021/03.mdwn
new file mode 100644
index 0000000..0c7f1e7
--- /dev/null
+++ b/archives/2021/03.mdwn
@@ -0,0 +1,5 @@
+[[!sidebar content="""
+[[!calendar type=month month=03 year=2021 pages="page(posts/*) and !*/Discussion"]]
+"""]]
+
+[[!inline pages="creation_month(03) and creation_year(2021) and page(posts/*) and !*/Discussion" show=0 feeds=no reverse=yes]]
diff --git a/archives/2021/04.mdwn b/archives/2021/04.mdwn
new file mode 100644
index 0000000..5fb95cb
--- /dev/null
+++ b/archives/2021/04.mdwn
@@ -0,0 +1,5 @@
+[[!sidebar content="""
+[[!calendar type=month month=04 year=2021 pages="page(posts/*) and !*/Discussion"]]
+"""]]
+
+[[!inline pages="creation_month(04) and creation_year(2021) and page(posts/*) and !*/Discussion" show=0 feeds=no reverse=yes]]
diff --git a/archives/2021/05.mdwn b/archives/2021/05.mdwn
new file mode 100644
index 0000000..ec47bca
--- /dev/null
+++ b/archives/2021/05.mdwn
@@ -0,0 +1,5 @@
+[[!sidebar content="""
+[[!calendar type=month month=05 year=2021 pages="page(posts/*) and !*/Discussion"]]
+"""]]
+
+[[!inline pages="creation_month(05) and creation_year(2021) and page(posts/*) and !*/Discussion" show=0 feeds=no reverse=yes]]
diff --git a/archives/2021/06.mdwn b/archives/2021/06.mdwn
new file mode 100644
index 0000000..8af1077
--- /dev/null
+++ b/archives/2021/06.mdwn
@@ -0,0 +1,5 @@
+[[!sidebar content="""
+[[!calendar type=month month=06 year=2021 pages="page(posts/*) and !*/Discussion"]]
+"""]]
+
+[[!inline pages="creation_month(06) and creation_year(2021) and page(posts/*) and !*/Discussion" show=0 feeds=no reverse=yes]]
diff --git a/archives/2021/07.mdwn b/archives/2021/07.mdwn
new file mode 100644
index 0000000..54ccb3d
--- /dev/null
+++ b/archives/2021/07.mdwn
@@ -0,0 +1,5 @@
+[[!sidebar content="""
+[[!calendar type=month month=07 year=2021 pages="page(posts/*) and !*/Discussion"]]
+"""]]
+
+[[!inline pages="creation_month(07) and creation_year(2021) and page(posts/*) and !*/Discussion" show=0 feeds=no reverse=yes]]
diff --git a/archives/2021/08.mdwn b/archives/2021/08.mdwn
new file mode 100644
index 0000000..d6242ff
--- /dev/null
+++ b/archives/2021/08.mdwn
@@ -0,0 +1,5 @@
+[[!sidebar content="""
+[[!calendar type=month month=08 year=2021 pages="page(posts/*) and !*/Discussion"]]
+"""]]
+
+[[!inline pages="creation_month(08) and creation_year(2021) and page(posts/*) and !*/Discussion" show=0 feeds=no reverse=yes]]
diff --git a/archives/2021/09.mdwn b/archives/2021/09.mdwn
new file mode 100644
index 0000000..a8d92cb
--- /dev/null
+++ b/archives/2021/09.mdwn
@@ -0,0 +1,5 @@
+[[!sidebar content="""
+[[!calendar type=month month=09 year=2021 pages="page(posts/*) and !*/Discussion"]]
+"""]]
+
+[[!inline pages="creation_month(09) and creation_year(2021) and page(posts/*) and !*/Discussion" show=0 feeds=no reverse=yes]]
diff --git a/archives/2021/10.mdwn b/archives/2021/10.mdwn
new file mode 100644
index 0000000..2514206
--- /dev/null
+++ b/archives/2021/10.mdwn
@@ -0,0 +1,5 @@
+[[!sidebar content="""
+[[!calendar type=month month=10 year=2021 pages="page(posts/*) and !*/Discussion"]]
+"""]]
+
+[[!inline pages="creation_month(10) and creation_year(2021) and page(posts/*) and !*/Discussion" show=0 feeds=no reverse=yes]]
diff --git a/archives/2021/11.mdwn b/archives/2021/11.mdwn
new file mode 100644
index 0000000..9c5f9fd
--- /dev/null
+++ b/archives/2021/11.mdwn
@@ -0,0 +1,5 @@
+[[!sidebar content="""
+[[!calendar type=month month=11 year=2021 pages="page(posts/*) and !*/Discussion"]]
+"""]]
+
+[[!inline pages="creation_month(11) and creation_year(2021) and page(posts/*) and !*/Discussion" show=0 feeds=no reverse=yes]]
diff --git a/archives/2021/12.mdwn b/archives/2021/12.mdwn
new file mode 100644
index 0000000..4132294
--- /dev/null
+++ b/archives/2021/12.mdwn
@@ -0,0 +1,5 @@
+[[!sidebar content="""
+[[!calendar type=month month=12 year=2021 pages="page(posts/*) and !*/Discussion"]]
+"""]]
+
+[[!inline pages="creation_month(12) and creation_year(2021) and page(posts/*) and !*/Discussion" show=0 feeds=no reverse=yes]]

Replace dead link with archive.org copy.
diff --git a/posts/looking-back-on-starting-libravatar.mdwn b/posts/looking-back-on-starting-libravatar.mdwn
index fe6a5e7..7641386 100644
--- a/posts/looking-back-on-starting-libravatar.mdwn
+++ b/posts/looking-back-on-starting-libravatar.mdwn
@@ -110,7 +110,7 @@ When I started the project in 2011, I had a few goals:
   [DebConf](http://penta.debconf.org/dc10_schedule///////events/682.en.html)
   [twice](https://summit.debconf.org/debconf14/meeting/16/outsourcing-your-webapp-maintenance-to-debian/),
   [linux.conf.au](https://www.youtube.com/watch?v=ufkYjt9HV64) and
-  [OSCON](https://conferences.oreilly.com/oscon/oscon2011/public/schedule/detail/18773).
+  [OSCON](https://web.archive.org/web/20161005202936/http://conferences.oreilly.com/oscon/oscon2011/public/schedule/detail/18773).
 
 - Career-wise, I wanted to move beyond PHP development, which I successfully
   achieved by working for a [new client](https://logger.paua.org.nz/) while

Fix typo.
diff --git a/posts/looking-back-on-starting-libravatar.mdwn b/posts/looking-back-on-starting-libravatar.mdwn
index cb10af1..fe6a5e7 100644
--- a/posts/looking-back-on-starting-libravatar.mdwn
+++ b/posts/looking-back-on-starting-libravatar.mdwn
@@ -161,7 +161,7 @@ project over the years:
 - The Wellington Perl Mongers for their invaluable feedback on an early prototype.
 - The `#equifoss` group for their ongoing suppport and numerous ideas.
 - Nigel Babu and Melissa Draper for producing the first (and only) project
-  stikers, as well as Chris Cormack for spreading so effectively.
+  stickers, as well as Chris Cormack for spreading them so effectively.
 - Adolfo Jayme, Alfredo Hernández, Anthony Harrington, Asier Iturralde
   Sarasola, Besnik, Beto1917, Daniel Neis, Eduardo Battaglia, Fernando P
   Silveira, Gabriele Castagneti, Heimen Stoffels, Iñaki Arenaza, Jakob

Remove blog that's no longer updated.
diff --git a/posts/list-planet-linux-australia-blogs/linuxaustralia.opml b/posts/list-planet-linux-australia-blogs/linuxaustralia.opml
index 715ed99..72ad571 100644
--- a/posts/list-planet-linux-australia-blogs/linuxaustralia.opml
+++ b/posts/list-planet-linux-australia-blogs/linuxaustralia.opml
@@ -19,7 +19,6 @@
    <outline version="RSS" archiveMode="globalDefault" useCustomFetchInterval="false" maxArticleNumber="1000" xmlUrl="https://www.csamuel.org/feed" htmlUrl="https://www.csamuel.org" fetchInterval="30" id="1432669172" description="Computers, science, archaeology and other random burblings" title="Chris Samuel" maxArticleAge="60" comment="" copyright="" type="rss" text="Chris Samuel"/>
    <outline maxArticleAge="60" copyright="" xmlUrl="https://blog.christophersmart.com/feed/" fetchInterval="30" useCustomFetchInterval="false" logoHeight="32" description="Fortiter Et Recte" version="RSS" text="Chris Smart" logoWidth="32" logoUrl="https://blog.christophersmart.com/wp-content/uploads/2008/08/cropped-csmart-32x32.jpg" archiveMode="globalDefault" maxArticleNumber="1000" comment="" type="rss" title="Chris Smart" htmlUrl="https://blog.christophersmart.com" id="1309301898"/>
    <outline version="RSS" archiveMode="globalDefault" useCustomFetchInterval="false" maxArticleNumber="1000" xmlUrl="https://chrisjrn.com/feed.xml" htmlUrl="https://chrisjrn.com/" fetchInterval="30" id="2199337231" description="Christopher Neugebauer&amp;apos;s site" title="Chris Neugebauer" maxArticleAge="60" comment="" copyright="" type="rss" text="Chris Neugebauer"/>
-   <outline version="RSS" archiveMode="globalDefault" useCustomFetchInterval="false" maxArticleNumber="1000" xmlUrl="http://chris.yeoh.info/feed/" htmlUrl="http://chris.yeoh.info" fetchInterval="30" id="2446739425" description="" title="Chris Yeoh" maxArticleAge="60" comment="" copyright="" type="rss" text="Chris Yeoh"/>
    <outline maxArticleAge="60" copyright="" xmlUrl="https://clintonroy.wordpress.com/feed/" fetchInterval="30" useCustomFetchInterval="false" logoHeight="31" description="An Open Source Software Engineer" version="RSS" text="Clinton Roy" logoWidth="88" logoUrl="https://s0.wp.com/i/buttonw-com.png" archiveMode="globalDefault" maxArticleNumber="1000" comment="" type="rss" title="Clinton Roy" htmlUrl="https://clintonroy.wordpress.com" id="165453513"/>
    <outline version="RSS" archiveMode="globalDefault" useCustomFetchInterval="false" maxArticleNumber="1000" xmlUrl="https://feeds.feedburner.com/ColinCharlesLA" htmlUrl="http://www.bytebot.net/blog" fetchInterval="30" id="306929162" description="&amp;quot;Imagine. Create. Execute.&amp;quot; Rough notes on technology, media, travel, business, work, and my other interests." title="Colin Charles" maxArticleAge="60" comment="" copyright="" type="rss" text="Colin Charles"/>
    <outline version="RSS" archiveMode="globalDefault" useCustomFetchInterval="false" maxArticleNumber="1000" xmlUrl="http://blog.kfish.org/feeds/posts/default" htmlUrl="http://blog.kfish.org/" fetchInterval="30" id="2458778784" description="&lt;strong>Making free software for embedded/mobile multimedia, web video and computer music; in C and Haskell, using algebra, type theory and git!&lt;/strong>" title="Conrad Parker" maxArticleAge="60" comment="" copyright="" type="rss" text="Conrad Parker"/>

Add Planet LA post.
diff --git a/posts/list-planet-linux-australia-blogs.mdwn b/posts/list-planet-linux-australia-blogs.mdwn
new file mode 100644
index 0000000..bf4c2a9
--- /dev/null
+++ b/posts/list-planet-linux-australia-blogs.mdwn
@@ -0,0 +1,81 @@
+[[!meta title="List of Planet Linux Australia blogs"]]
+[[!meta date="2020-12-10T23:30:00.000-08:00"]]
+[[!meta license="[Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/)"]]
+
+I've been following [Planet Linux Australia](https://planet.linux.org.au/)
+for many years and discovered many interesting FOSS blogs through it. I was
+sad to see that it got [shut down a few weeks
+ago](https://linux.org.au/saying-farewell-to-planet-linux-australia/) and so
+I decided to manually add all of the feeds to my RSS reader to avoid missing
+posts from people I have been indirectly following for years.
+
+Since all feeds have been removed from the site, I recovered the list of
+blogs available from an [old copy of the
+site](https://web.archive.org/web/20200228054023/https://planet.linux.org.au/)
+preserved by the [Internet Archive](https://archive.org).
+
+Here is the [resulting `.opml`
+file](/posts/list-planet-linux-australia-blogs/linuxaustralia.opml) if you'd
+like to subscribe.
+
+## Changes
+
+Once I had the full list, I removed all blogs that are gone, empty or broken
+(e.g. domain not resolving, returning a 404, various database or server
+errors).
+
+I updated the URLs of a few blogs which had moved but hadn't updated their
+feeds on the planet. I also updated the name of a blogger who was still
+listed under a previous last name.
+
+Finally, I removed LA-specific tags from feeds since these are unlikely to
+be used again.
+
+## Work-arounds
+
+The following LiveJournal feeds didn't work in my RSS reader but opened fine
+in a browser:
+
+- <https://ninjafoo.livejournal.com/data/rss>
+- <https://certifiedwaif.livejournal.com/data/rss>
+- <https://tau-iota-mu-c.livejournal.com/data/rss>
+- <https://lathiat.livejournal.com/data/rss>
+
+However since none of them have them updated in the last 7 years, I just
+left them out.
+
+A couple appear to be impossible to [fetch over
+Tor](https://feeding.cloud.geek.nz/posts/things-that-work-well-with-tor/),
+presumably due to a [Cloudflare
+setting](https://community.cloudflare.com/t/block-tor-and-vpns-from-accessing-my-website/134730/2):
+
+- <https://www.jamver.id.au/cgi-bin/blosxom.cgi/index.rss>
+- <https://www.rwhitby.net/feed>
+- <https://blog.khax.net/feed/>
+- <https://dalts.com/index.xml>
+- <https://pento.net/feed/>
+
+Since only the last two have been updated in the last 9 years, I added these
+to Feedburner and added the following "proxied" URLs to my reader:
+
+- <https://feeds.feedburner.com/SteveDaltonLA>
+- <https://feeds.feedburner.com/GaryPendergastLA>
+
+Similarly, I couldn't fetch the following over Tor for some other reasons:
+
+- <http://algorithm.com.au/blog/files/rss.xml>
+- <http://soundadvice.id.au/blog/index.atom>
+- <http://www.bytebot.net/blog/archives/category/databases/feed>
+- <http://levlafayette.com/blog/feed>
+- <http://www.rowetel.com/?feed=rss2>
+- <https://hamishtaylor.com.au/photekgraddft-hamish-taylors-blog?format=rss>
+
+I excluded the first two which haven't been updated in 6 years and proxied
+the other ones:
+
+- <https://feeds.feedburner.com/ColinCharlesLA>
+- <https://feeds.feedburner.com/LevLafayetteLA>
+- <https://feeds.feedburner.com/DavidRoweLA>
+- <https://feeds.feedburner.com/HamishTaylorLA>
+
+[[!tag tor]]
diff --git a/posts/list-planet-linux-australia-blogs/linuxaustralia.opml b/posts/list-planet-linux-australia-blogs/linuxaustralia.opml
new file mode 100644
index 0000000..715ed99
--- /dev/null
+++ b/posts/list-planet-linux-australia-blogs/linuxaustralia.opml
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<opml version="1.0">
+ <head>
+  <text/>
+ </head>
+ <body>
+  <outline isOpen="true" id="2064586897" text="Planet Linux Australia">
+   <outline version="RSS" archiveMode="globalDefault" useCustomFetchInterval="false" maxArticleNumber="1000" xmlUrl="https://adrianchadd.blogspot.com/feeds/posts/default" htmlUrl="https://adrianchadd.blogspot.com/" fetchInterval="30" id="2136324713" description="" title="Adrian Chadd" maxArticleAge="60" comment="" copyright="" type="rss" text="Adrian Chadd"/>
+   <outline maxArticleAge="60" copyright="" xmlUrl="https://feeds2.feedburner.com/symphonious" fetchInterval="30" useCustomFetchInterval="false" logoHeight="0" description="Living in a state of accord." version="RSS" text="Adrian Sutton" logoWidth="0" logoUrl="https://www.symphonious.net/wp-atom.php" archiveMode="globalDefault" maxArticleNumber="1000" comment="" type="rss" title="Adrian Sutton" htmlUrl="https://www.symphonious.net" id="3352462773"/>
+   <outline version="RSS" archiveMode="globalDefault" useCustomFetchInterval="false" maxArticleNumber="1000" xmlUrl="https://etymon.blogspot.com/atom.xml" htmlUrl="https://etymon.blogspot.com/" fetchInterval="30" id="1279987017" description="\Et&quot;y*mon\, n.; pl. E. Etymons, Gr. Etyma. &#xd;&#xa;&lt;br>&#xd;&#xa;[L., fr. Gr. 'e`tymon the true literal sense of a word according to its derivation, an etymon]" title="Andrae Muys" maxArticleAge="60" comment="" copyright="" type="rss" text="Andrae Muys"/>
+   <outline maxArticleAge="60" copyright="" xmlUrl="https://ajdlinux.wordpress.com/feed/atom/" fetchInterval="30" useCustomFetchInterval="false" logoHeight="0" description="...one PC at a time. Just another person&amp;apos;s views on Linux, life and other things..." version="RSS" text="Andrew Donnellan" logoWidth="0" logoUrl="https://ajdlinux.wordpress.com/wp-atom.php" archiveMode="globalDefault" maxArticleNumber="1000" comment="" type="rss" title="Andrew Donnellan" htmlUrl="https://ajdlinux.wordpress.com" id="3265789158"/>
+   <outline version="RSS" archiveMode="globalDefault" useCustomFetchInterval="false" maxArticleNumber="1000" xmlUrl="http://blog.andrew.net.au/index.rss" htmlUrl="http://blog.andrew.net.au" fetchInterval="30" id="364764844" description="Andrew Pollock's blog." title="Andrew Pollock" maxArticleAge="60" comment="" copyright="" type="rss" text="Andrew Pollock"/>
+   <outline maxArticleAge="60" copyright="" xmlUrl="http://blog.etc.gen.nz/feeds/index.rss2" fetchInterval="30" useCustomFetchInterval="false" logoHeight="21" description="This is a blog, it is it is." version="RSS" text="Andrew Ruthven" logoWidth="100" logoUrl="http://blog.etc.gen.nz/templates/2k11/img/s9y_banner_small.png" archiveMode="globalDefault" maxArticleNumber="1000" comment="" type="rss" title="Andrew Ruthven" htmlUrl="http://blog.etc.gen.nz/" id="1575153193"/>
+   <outline version="RSS" archiveMode="globalDefault" useCustomFetchInterval="false" maxArticleNumber="1000" xmlUrl="https://lentz.com.au/feed" htmlUrl="https://lentz.com.au" fetchInterval="30" id="3044188428" description="The Lentz tribe in Brisbane" title="Arjen Lentz" maxArticleAge="60" comment="" copyright="" type="rss" text="Arjen Lentz"/>
+   <outline version="RSS" archiveMode="globalDefault" useCustomFetchInterval="false" maxArticleNumber="1000" xmlUrl="http://benno.id.au/blog/feed/" htmlUrl="http://benno.id.au/blog/" fetchInterval="30" id="3923796910" description="Systems design and other random tech stuff" title="Ben Leslie" maxArticleAge="60" comment="" copyright="Ben Leslie 2006-09" type="rss" text="Ben Leslie"/>
+   <outline version="RSS" archiveMode="globalDefault" useCustomFetchInterval="false" maxArticleNumber="1000" xmlUrl="https://monkeyiq.blogspot.com/feeds/posts/default" htmlUrl="https://monkeyiq.blogspot.com/" fetchInterval="30" id="220149261" description="C++, Linux, libferris and embedded development. Yet another blog from yet another NARG." title="Ben Martin" maxArticleAge="60" comment="" copyright="" type="rss" text="Ben Martin"/>
+   <outline maxArticleAge="60" copyright="" xmlUrl="https://bluehackers.org/feed/atom" fetchInterval="30" useCustomFetchInterval="false" logoHeight="0" description="because we&amp;apos;re not just geeks - we&amp;apos;re humans." version="RSS" text="BlueHackers" logoWidth="0" logoUrl="https://bluehackers.org/wp-atom.php" archiveMode="globalDefault" maxArticleNumber="1000" comment="" type="rss" title="BlueHackers" htmlUrl="https://bluehackers.org" id="3703814559"/>
+   <outline maxArticleAge="60" copyright="" xmlUrl="https://brendanscott.wordpress.com/feed/" fetchInterval="30" useCustomFetchInterval="false" logoHeight="31" description="Comments on Life and Law, Free Software, Open Source and Intellectual Monopolies" version="RSS" text="Brendan Scott" logoWidth="88" logoUrl="https://s0.wp.com/i/buttonw-com.png" archiveMode="globalDefault" maxArticleNumber="1000" comment="" type="rss" title="Brendan Scott" htmlUrl="https://brendanscott.wordpress.com" id="2903001798"/>
+   <outline version="RSS" archiveMode="globalDefault" useCustomFetchInterval="false" maxArticleNumber="1000" xmlUrl="https://www.csamuel.org/feed" htmlUrl="https://www.csamuel.org" fetchInterval="30" id="1432669172" description="Computers, science, archaeology and other random burblings" title="Chris Samuel" maxArticleAge="60" comment="" copyright="" type="rss" text="Chris Samuel"/>
+   <outline maxArticleAge="60" copyright="" xmlUrl="https://blog.christophersmart.com/feed/" fetchInterval="30" useCustomFetchInterval="false" logoHeight="32" description="Fortiter Et Recte" version="RSS" text="Chris Smart" logoWidth="32" logoUrl="https://blog.christophersmart.com/wp-content/uploads/2008/08/cropped-csmart-32x32.jpg" archiveMode="globalDefault" maxArticleNumber="1000" comment="" type="rss" title="Chris Smart" htmlUrl="https://blog.christophersmart.com" id="1309301898"/>
+   <outline version="RSS" archiveMode="globalDefault" useCustomFetchInterval="false" maxArticleNumber="1000" xmlUrl="https://chrisjrn.com/feed.xml" htmlUrl="https://chrisjrn.com/" fetchInterval="30" id="2199337231" description="Christopher Neugebauer&amp;apos;s site" title="Chris Neugebauer" maxArticleAge="60" comment="" copyright="" type="rss" text="Chris Neugebauer"/>
+   <outline version="RSS" archiveMode="globalDefault" useCustomFetchInterval="false" maxArticleNumber="1000" xmlUrl="http://chris.yeoh.info/feed/" htmlUrl="http://chris.yeoh.info" fetchInterval="30" id="2446739425" description="" title="Chris Yeoh" maxArticleAge="60" comment="" copyright="" type="rss" text="Chris Yeoh"/>
+   <outline maxArticleAge="60" copyright="" xmlUrl="https://clintonroy.wordpress.com/feed/" fetchInterval="30" useCustomFetchInterval="false" logoHeight="31" description="An Open Source Software Engineer" version="RSS" text="Clinton Roy" logoWidth="88" logoUrl="https://s0.wp.com/i/buttonw-com.png" archiveMode="globalDefault" maxArticleNumber="1000" comment="" type="rss" title="Clinton Roy" htmlUrl="https://clintonroy.wordpress.com" id="165453513"/>
+   <outline version="RSS" archiveMode="globalDefault" useCustomFetchInterval="false" maxArticleNumber="1000" xmlUrl="https://feeds.feedburner.com/ColinCharlesLA" htmlUrl="http://www.bytebot.net/blog" fetchInterval="30" id="306929162" description="&amp;quot;Imagine. Create. Execute.&amp;quot; Rough notes on technology, media, travel, business, work, and my other interests." title="Colin Charles" maxArticleAge="60" comment="" copyright="" type="rss" text="Colin Charles"/>
+   <outline version="RSS" archiveMode="globalDefault" useCustomFetchInterval="false" maxArticleNumber="1000" xmlUrl="http://blog.kfish.org/feeds/posts/default" htmlUrl="http://blog.kfish.org/" fetchInterval="30" id="2458778784" description="&lt;strong>Making free software for embedded/mobile multimedia, web video and computer music; in C and Haskell, using algebra, type theory and git!&lt;/strong>" title="Conrad Parker" maxArticleAge="60" comment="" copyright="" type="rss" text="Conrad Parker"/>
+   <outline version="RSS" archiveMode="globalDefault" useCustomFetchInterval="false" maxArticleNumber="1000" xmlUrl="http://blog.taz.net.au/feed/" htmlUrl="http://blog.taz.net.au" fetchInterval="30" id="2716947403" description="Tech Notes And Miscellaneous Thoughts" title="Craig Sanders" maxArticleAge="60" comment="" copyright="" type="rss" text="Craig Sanders"/>
+   <outline version="RSS" archiveMode="globalDefault" useCustomFetchInterval="false" maxArticleNumber="1000" xmlUrl="https://mcwhirter.com.au/index.rss" htmlUrl="http://mcwhirter.com.au//" fetchInterval="30" id="1608228020" description="mcwhirter.com.au" title="Craige McWhirter" maxArticleAge="60" comment="" copyright="" type="rss" text="Craige McWhirter"/>
+   <outline version="RSS" archiveMode="globalDefault" useCustomFetchInterval="false" maxArticleNumber="1000" xmlUrl="https://blogs.gnome.org/danni/feed/" htmlUrl="https://blogs.gnome.org/danni" fetchInterval="30" id="3530083293" description="Danielle's technical stuffs" title="Danielle Madeley" maxArticleAge="60" comment="" copyright="" type="rss" text="Danielle Madeley"/>
+   <outline version="RSS" archiveMode="globalDefault" useCustomFetchInterval="false" maxArticleNumber="1000" xmlUrl="https://feeds.feedburner.com/skwashd" htmlUrl="https://davehall.com.au/blog/dave" fetchInterval="30" id="1677023889" description="" title="Dave Hall" maxArticleAge="60" comment="" copyright="" type="rss" text="Dave Hall"/>
+   <outline version="RSS" archiveMode="globalDefault" useCustomFetchInterval="false" maxArticleNumber="1000" xmlUrl="https://feeds.feedburner.com/DavidRoweLA" htmlUrl="https://www.rowetel.com" fetchInterval="30" id="2518525852" description="open telephony software, hardware, critical thinking" title="David Rowe" maxArticleAge="60" comment="" copyright="" type="rss" text="David Rowe"/>
+   <outline version="RSS" archiveMode="globalDefault" useCustomFetchInterval="false" maxArticleNumber="1000" xmlUrl="http://www.mega-nerd.com/erikd/Blog/index.rss" htmlUrl="http://www.mega-nerd.com/erikd/Blog" fetchInterval="30" id="3531593969" description="An ocassional rant" title="Erik de Castro Lopo" maxArticleAge="60" comment="" copyright="Copyright 2006-2010 Erik de Castro Lopo" type="rss" text="Erik de Castro Lopo"/>
+   <outline version="RSS" archiveMode="globalDefault" useCustomFetchInterval="false" maxArticleNumber="1000" xmlUrl="https://feeding.cloud.geek.nz/index.rss" htmlUrl="http://feeding.cloud.geek.nz/" fetchInterval="30" id="1229046837" description="Feeding the Cloud" title="Francois Marier" maxArticleAge="60" comment="" copyright="" type="rss" text="Francois Marier"/>
+   <outline version="RSS" archiveMode="globalDefault" useCustomFetchInterval="false" maxArticleNumber="1000" xmlUrl="http://noronha.id.au/feed/" htmlUrl="https://noronha.id.au" fetchInterval="30" id="595433577" description="Life is boring if things &amp;quot;Just Work&amp;quot; !" title="Gabriel Noronha" maxArticleAge="60" comment="" copyright="" type="rss" text="Gabriel Noronha"/>
+   <outline maxArticleAge="60" copyright="" xmlUrl="https://feeds.feedburner.com/GaryPendergastLA" fetchInterval="30" useCustomFetchInterval="false" logoHeight="32" description="I'm on the Internet" version="RSS" text="Gary Pendergast" logoWidth="32" logoUrl="https://pento.net/wp-content/uploads/2014/10/1ad9e5c98d81c6815a65dab5b6e1f669-54515cee_site_icon-32x32.png" archiveMode="globalDefault" maxArticleNumber="1000" comment="" type="rss" title="Gary Pendergast" htmlUrl="https://pento.net" id="1806746700"/>
+   <outline maxArticleAge="60" copyright="" xmlUrl="https://gdt.dreamwidth.org/data/rss" fetchInterval="30" useCustomFetchInterval="false" logoHeight="100" description="Postcards from Semaphore - Dreamwidth Studios" version="RSS" text="Glen Turner" logoWidth="100" logoUrl="https://v.dreamwidth.org/12224960/2733095" archiveMode="globalDefault" maxArticleNumber="1000" comment="" type="rss" title="Glen Turner" htmlUrl="https://gdt.dreamwidth.org/" id="727997966"/>
+   <outline version="RSS" archiveMode="globalDefault" useCustomFetchInterval="false" maxArticleNumber="1000" xmlUrl="https://teapartiesandunicorns.blogspot.com/feeds/posts/default" htmlUrl="https://teapartiesandunicorns.blogspot.com/" fetchInterval="30" id="1788590842" description="Creating awesome software" title="Greg Black" maxArticleAge="60" comment="" copyright="" type="rss" text="Greg Black"/>
+   <outline version="RSS" archiveMode="globalDefault" useCustomFetchInterval="false" maxArticleNumber="1000" xmlUrl="https://feeds.feedburner.com/HamishTaylorLA" htmlUrl="https://hamishtaylor.com.au/photekgraddft-hamish-taylors-blog/" fetchInterval="30" id="1478909113" description="" title="Hamish Taylor" maxArticleAge="60" comment="" copyright="" type="rss" text="Hamish Taylor"/>
+   <outline version="RSS" archiveMode="globalDefault" useCustomFetchInterval="false" maxArticleNumber="1000" xmlUrl="http://blemings.org/hugh/blog/blosxom.cgi/index.rss" htmlUrl="http://blemings.org/hugh/blog/blosxom.cgi/" fetchInterval="30" id="801275812" description="A Diary and Miscellaneous ramblings" title="Hugh Blemings" maxArticleAge="60" comment="" copyright="" type="rss" text="Hugh Blemings"/>
+   <outline version="RSS" archiveMode="globalDefault" useCustomFetchInterval="false" maxArticleNumber="1000" xmlUrl="https://feeds.feedburner.com/technovelty" htmlUrl="https://www.technovelty.org/" fetchInterval="30" id="3154827406" description="" title="Ian Wienand" maxArticleAge="60" comment="" copyright="" type="rss" text="Ian Wienand"/>
+   <outline maxArticleAge="60" copyright="" xmlUrl="https://blog.james.rcpt.to/category/computing/linux/feed/" fetchInterval="30" useCustomFetchInterval="false" logoHeight="32" description="Scribblings of a Techie" version="RSS" text="James Bromberger" logoWidth="32" logoUrl="/wp-content/uploads/2016/01/cropped-james-bromberger-02-32x32.jpg" archiveMode="globalDefault" maxArticleNumber="1000" comment="" type="rss" title="James Bromberger" htmlUrl="" id="92157731"/>
+   <outline version="RSS" archiveMode="globalDefault" useCustomFetchInterval="false" maxArticleNumber="1000" xmlUrl="https://spacepants.org/blog/index.xml" htmlUrl="https://spacepants.org/blog/" fetchInterval="30" id="336648069" description="Recent content in spaceblog on spacepants.org" title="Jamie Wilkinson" maxArticleAge="60" comment="" copyright="All content Copyright © 2002-2020 Jamie Wilkinson.&#xa;Entries in this blog are licensed under the Creative Commons Attribution-Sharealike v2 License." type="rss" text="Jamie Wilkinson"/>
+   <outline version="RSS" archiveMode="globalDefault" useCustomFetchInterval="false" maxArticleNumber="1000" xmlUrl="https://noraisin.net/diary/?feed=rss2" htmlUrl="https://noraisin.net/diary" fetchInterval="30" id="3209333186" description="Coding blog - GStreamer, OpenHMD mostly" title="Jan Schmidt" maxArticleAge="60" comment="" copyright="" type="rss" text="Jan Schmidt"/>
+   <outline maxArticleAge="60" copyright="" xmlUrl="https://nooks.livejournal.com/data/rss" fetchInterval="30" useCustomFetchInterval="false" logoHeight="100" description="Jason Parker-Burlingham - LiveJournal.com" version="RSS" text="Jason Parker-Burlingham" logoWidth="100" logoUrl="https://l-userpic.livejournal.com/44632593/1388669" archiveMode="globalDefault" maxArticleNumber="1000" comment="" type="rss" title="Jason Parker-Burlingham" htmlUrl="https://nooks.livejournal.com/" id="1800194004"/>
+   <outline maxArticleAge="60" copyright="" xmlUrl="https://bethesignal.org/feed/atom/" fetchInterval="30" useCustomFetchInterval="false" logoHeight="0" description="" version="RSS" text="Jeff Waugh" logoWidth="0" logoUrl="https://bethesignal.org/wp-atom.php" archiveMode="globalDefault" maxArticleNumber="1000" comment="" type="rss" title="Jeff Waugh" htmlUrl="https://bethesignal.org" id="4057446467"/>
+   <outline version="RSS" archiveMode="globalDefault" useCustomFetchInterval="false" maxArticleNumber="1000" xmlUrl="http://jk.ozlabs.org/feeds/rss/" htmlUrl="http://jk.ozlabs.org/" fetchInterval="30" id="3040733194" description="Updates to blog, photos and code from jk.ozlabs.org" title="Jeremy Kerr" maxArticleAge="60" comment="" copyright="" type="rss" text="Jeremy Kerr"/>
+   <outline version="RSS" archiveMode="globalDefault" useCustomFetchInterval="false" maxArticleNumber="1000" xmlUrl="https://jeremy.visser.name/index.xml" htmlUrl="https://jeremy.visser.name/" fetchInterval="30" id="2659616076" description="Recent content in Jeremy Visser — Hacker, Sysadmin, Bassoonist on Jeremy Visser" title="Jeremy Visser" maxArticleAge="60" comment="" copyright="Licensed under a Creative Commons Attribution-Share Alike 4.0 License. http://creativecommons.org/licenses/by-sa/4.0/" type="rss" text="Jeremy Visser"/>
+   <outline version="RSS" archiveMode="globalDefault" useCustomFetchInterval="false" maxArticleNumber="1000" xmlUrl="http://brnz.org/hbr/?feed=rss2" htmlUrl="http://brnz.org/hbr" fetchInterval="30" id="986775896" description="hint for Branch (r-form)" title="Jonathan Adamczewski" maxArticleAge="60" comment="" copyright="" type="rss" text="Jonathan Adamczewski"/>
+   <outline version="RSS" archiveMode="globalDefault" useCustomFetchInterval="false" maxArticleNumber="1000" xmlUrl="http://noisymime.org/blog/feed/" htmlUrl="http://noisymime.org/blog" fetchInterval="30" id="3235908358" description="My ancient blog archive" title="Josh Stewart" maxArticleAge="60" comment="" copyright="" type="rss" text="Josh Stewart"/>
+   <outline version="RSS" archiveMode="globalDefault" useCustomFetchInterval="false" maxArticleNumber="1000" xmlUrl="https://josh.people.rcbops.com/feed/" htmlUrl="https://josh.hesketh.net.au" fetchInterval="30" id="1049289463" description="Open Source, Linux, OpenStack, Mechatronics, Blog(?)" title="Joshua Hesketh" maxArticleAge="60" comment="" copyright="" type="rss" text="Joshua Hesketh"/>
+   <outline maxArticleAge="60" copyright="" xmlUrl="https://laptop006.livejournal.com/data/rss" fetchInterval="30" useCustomFetchInterval="false" logoHeight="100" description="Julien Goodwin - LiveJournal.com" version="RSS" text="Julien Goodwin" logoWidth="84" logoUrl="https://l-userpic.livejournal.com/37672818/8254271" archiveMode="globalDefault" maxArticleNumber="1000" comment="" type="rss" title="Julien Goodwin" htmlUrl="https://laptop006.livejournal.com/" id="4031182726"/>
+   <outline version="RSS" archiveMode="globalDefault" useCustomFetchInterval="false" maxArticleNumber="1000" xmlUrl="https://infinitedigits.blogspot.com/feeds/posts/default" htmlUrl="https://infinitedigits.blogspot.com/" fetchInterval="30" id="694601602" description="" title="Leah Duncan" maxArticleAge="60" comment="" copyright="" type="rss" text="Leah Duncan"/>
+   <outline version="RSS" archiveMode="globalDefault" useCustomFetchInterval="false" maxArticleNumber="1000" xmlUrl="https://leonbrooks.blogspot.com/atom.xml" htmlUrl="https://leonbrooks.blogspot.com/" fetchInterval="30" id="1627579536" description="Linux Advocate in Western Australia" title="Leon Brooks" maxArticleAge="60" comment="" copyright="" type="rss" text="Leon Brooks"/>
+   <outline version="RSS" archiveMode="globalDefault" useCustomFetchInterval="false" maxArticleNumber="1000" xmlUrl="https://feeds.feedburner.com/LevLafayetteLA" htmlUrl="http://levlafayette.com/blog" fetchInterval="30" id="2267109132" description="" title="Lev Lafayette" maxArticleAge="60" comment="" copyright="" type="rss" text="Lev Lafayette"/>
+   <outline version="RSS" archiveMode="globalDefault" useCustomFetchInterval="false" maxArticleNumber="1000" xmlUrl="https://linux.org.au/feed/" htmlUrl="https://linux.org.au" fetchInterval="30" id="2846462728" description="Representing Free Software and Open Source Communities" title="Linux Australia" maxArticleAge="60" comment="" copyright="" type="rss" text="Linux Australia"/>
+   <outline version="RSS" archiveMode="globalDefault" useCustomFetchInterval="false" maxArticleNumber="1000" xmlUrl="https://www.hezmatt.org/~mpalmer/blog/rss.xml" htmlUrl="//www.hezmatt.org/~mpalmer/blog/" fetchInterval="30" id="3161894866" description="The Thoughts of Matt Palmer" title="Matt Palmer" maxArticleAge="60" comment="" copyright="" type="rss" text="Matt Palmer"/>
+   <outline version="RSS" archiveMode="globalDefault" useCustomFetchInterval="false" maxArticleNumber="1000" xmlUrl="https://oliver.net.au/?feed=rss2" htmlUrl="https://oliver.net.au" fetchInterval="30" id="1903446338" description="Linux, life and programming" title="Matthew Oliver" maxArticleAge="60" comment="" copyright="" type="rss" text="Matthew Oliver"/>
+   <outline version="RSS" archiveMode="globalDefault" useCustomFetchInterval="false" maxArticleNumber="1000" xmlUrl="https://feeds.feedburner.com/Founds" htmlUrl="http://blog.dataparksearch.org" fetchInterval="30" id="1647832837" description="Just DataparkSearch weblog" title="Maxim Zakharov" maxArticleAge="60" comment="" copyright="" type="rss" text="Maxim Zakharov"/>
+   <outline version="RSS" archiveMode="globalDefault" useCustomFetchInterval="false" maxArticleNumber="1000" xmlUrl="https://lifelog.michaeldavies.org/feeds/posts/default" htmlUrl="https://lifelog.michaeldavies.org/" fetchInterval="30" id="1634131735" description="" title="Michael Davies" maxArticleAge="60" comment="" copyright="" type="rss" text="Michael Davies"/>
+   <outline version="RSS" archiveMode="globalDefault" useCustomFetchInterval="false" maxArticleNumber="1000" xmlUrl="http://neuling.org/mikey/index.cgi/index.rss" htmlUrl="http://neuling.org/mikey/index.cgi" fetchInterval="30" id="1093304945" description="Look at me, Look at me, Look at me!!!" title="Michael Neuling" maxArticleAge="60" comment="" copyright="" type="rss" text="Michael Neuling"/>
+   <outline version="RSS" archiveMode="globalDefault" useCustomFetchInterval="false" maxArticleNumber="1000" xmlUrl="https://www.madebymikal.com/feed/" htmlUrl="https://www.madebymikal.com" fetchInterval="30" id="1291538766" description="Content here is by Michael Still, mikal@stillhq.com." title="Michael Still" maxArticleAge="60" comment="" copyright="" type="rss" text="Michael Still"/>
+   <outline version="RSS" archiveMode="globalDefault" useCustomFetchInterval="false" maxArticleNumber="1000" xmlUrl="http://www.debianslashrules.org/index.rss" htmlUrl="http://www.debianslashrules.org/" fetchInterval="30" id="502043467" description="Thoughts from no-one important" title="Mike Beattie" maxArticleAge="60" comment="" copyright="" type="rss" text="Mike Beattie"/>
+   <outline maxArticleAge="60" copyright="" xmlUrl="https://openstem.com.au/feed" fetchInterval="30" useCustomFetchInterval="false" logoHeight="32" description="Helping students engage." version="RSS" text="OpenSTEM" logoWidth="32" logoUrl="https://openstem.com.au/wp-content/uploads/2015/04/owl-icon-512x512-552b5a4dv1_site_icon-100x100.png" archiveMode="globalDefault" maxArticleNumber="1000" comment="" type="rss" title="OpenSTEM" htmlUrl="https://openstem.com.au" id="1858510813"/>
+   <outline version="RSS" archiveMode="globalDefault" useCustomFetchInterval="false" maxArticleNumber="1000" xmlUrl="https://cafuego.net/rss.xml" htmlUrl="https://cafuego.net/" fetchInterval="30" id="3436238507" description="" title="Peter Lieverdink" maxArticleAge="60" comment="" copyright="" type="rss" text="Peter Lieverdink"/>
+   <outline version="RSS" archiveMode="globalDefault" useCustomFetchInterval="false" maxArticleNumber="1000" xmlUrl="http://pipka.org/feed/" htmlUrl="http://pipka.org" fetchInterval="30" id="1312671929" description="What are we doing today Brain? Taking over the world like we always do." title="Pia Andrews" maxArticleAge="60" comment="" copyright="" type="rss" text="Pia Andrews"/>
+   <outline version="RSS" archiveMode="globalDefault" useCustomFetchInterval="false" maxArticleNumber="1000" xmlUrl="http://www.mechanicalcat.net/richard/log/Python/rss" htmlUrl="http://www.mechanicalcat.net/richard/log/Python" fetchInterval="30" id="2028029603" description="Richard Jones' Log: Python" title="Richard Jones" maxArticleAge="60" comment="" copyright="" type="rss" text="Richard Jones"/>
+   <outline maxArticleAge="60" copyright="" xmlUrl="https://rbtcollins.wordpress.com/feed/" fetchInterval="30" useCustomFetchInterval="false" logoHeight="31" description="Someone near you is coding" version="RSS" text="Robert Collins" logoWidth="88" logoUrl="https://s0.wp.com/i/buttonw-com.png" archiveMode="globalDefault" maxArticleNumber="1000" comment="" type="rss" title="Robert Collins" htmlUrl="https://rbtcollins.wordpress.com" id="1719739607"/>
+   <outline version="RSS" archiveMode="globalDefault" useCustomFetchInterval="false" maxArticleNumber="1000" xmlUrl="https://etbe.coker.com.au/feed/" htmlUrl="https://etbe.coker.com.au" fetchInterval="30" id="2377874160" description="Linux, politics, and other interesting things" title="Russell Coker" maxArticleAge="60" comment="" copyright="" type="rss" text="Russell Coker"/>
+   <outline version="RSS" archiveMode="globalDefault" useCustomFetchInterval="false" maxArticleNumber="1000" xmlUrl="https://rusty.ozlabs.org/?feed=rss2" htmlUrl="https://rusty.ozlabs.org" fetchInterval="30" id="3091376666" description="Stealing From Smart People" title="Rusty Russell" maxArticleAge="60" comment="" copyright="" type="rss" text="Rusty Russell"/>
+   <outline maxArticleAge="60" copyright="" xmlUrl="https://sswam.wordpress.com/feed/" fetchInterval="30" useCustomFetchInterval="false" logoHeight="31" description="" version="RSS" text="Sam Watkins" logoWidth="88" logoUrl="https://s0.wp.com/i/buttonw-com.png" archiveMode="globalDefault" maxArticleNumber="1000" comment="" type="rss" title="Sam Watkins" htmlUrl="https://sswam.wordpress.com" id="2958449255"/>
+   <outline maxArticleAge="60" copyright="" xmlUrl="https://gingertech.net/feed/atom/" fetchInterval="30" useCustomFetchInterval="false" logoHeight="0" description="Silvia&amp;apos;s blog" version="RSS" text="Silvia Pfeiffer" logoWidth="0" logoUrl="https://gingertech.net/wp-atom.php" archiveMode="globalDefault" maxArticleNumber="1000" comment="" type="rss" title="Silvia Pfeiffer" htmlUrl="https://gingertech.net" id="3688564650"/>
+   <outline version="RSS" archiveMode="globalDefault" useCustomFetchInterval="false" maxArticleNumber="1000" xmlUrl="https://horms.net/blog/rss.xml" htmlUrl="http://horms.net/" fetchInterval="30" id="2116203352" description="Horms Solutions - Free and Open Source Softwrae Development" title="Simon Horms" maxArticleAge="60" comment="" copyright="" type="rss" text="Simon Horms"/>
+   <outline maxArticleAge="60" copyright="" xmlUrl="https://blog.darkmere.gen.nz/feed/" fetchInterval="30" useCustomFetchInterval="false" logoHeight="32" description="New Zealand, Sysadmin, Linux, Curry, Chess" version="RSS" text="Simon Lyall" logoWidth="32" logoUrl="https://blog.darkmere.gen.nz/wp-content/uploads/2015/04/mrlyall-thin-60x60.png" archiveMode="globalDefault" maxArticleNumber="1000" comment="" type="rss" title="Simon Lyall" htmlUrl="https://blog.darkmere.gen.nz" id="2569062528"/>
+   <outline version="RSS" archiveMode="globalDefault" useCustomFetchInterval="false" maxArticleNumber="1000" xmlUrl="https://feeds.feedburner.com/SteveDaltonLA" htmlUrl="https://dalts.com/" fetchInterval="30" id="2513729190" description="Recent content on Kind Acts of Randomness" title="Steve Dalton" maxArticleAge="60" comment="" copyright="" type="rss" text="Steve Dalton"/>
+   <outline version="RSS" archiveMode="globalDefault" useCustomFetchInterval="false" maxArticleNumber="1000" xmlUrl="http://svana.org/sjh/diary//index.rss" htmlUrl="http://svana.org/sjh/diary" fetchInterval="30" id="712173389" description="mtb / vegan / running / linux / canberra / cycling / etc" title="Steven Hanley" maxArticleAge="60" comment="" copyright="" type="rss" text="Steven Hanley"/>
+   <outline version="RSS" archiveMode="globalDefault" useCustomFetchInterval="false" maxArticleNumber="1000" xmlUrl="https://www.flamingspork.com/blog/feed/" htmlUrl="https://www.flamingspork.com/blog" fetchInterval="30" id="1079836154" description="Ramblings which occasionally resemble reality. This is the blog of Stewart Smith." title="Stewart Smith" maxArticleAge="60" comment="" copyright="" type="rss" text="Stewart Smith"/>
+   <outline version="RSS" archiveMode="globalDefault" useCustomFetchInterval="false" maxArticleNumber="1000" xmlUrl="https://feeds.feedburner.com/BlahBlahWoofWoof" htmlUrl="https://timriley.info" fetchInterval="30" id="3941210372" description="" title="Tim Riley" maxArticleAge="60" comment="" copyright="" type="rss" text="Tim Riley"/>
+   <outline version="RSS" archiveMode="globalDefault" useCustomFetchInterval="false" maxArticleNumber="1000" xmlUrl="https://ourobengr.com/feed/" htmlUrl="https://ourobengr.com" fetchInterval="30" id="1545419881" description="An engineer eating his own tail" title="Tim Serong" maxArticleAge="60" comment="" copyright="" type="rss" text="Tim Serong"/>
+   <outline version="RSS" archiveMode="globalDefault" useCustomFetchInterval="false" maxArticleNumber="1000" xmlUrl="https://sthbrx.github.io/atom.xml" htmlUrl="https://sthbrx.github.io/" fetchInterval="30" id="2212733042" description="A Power Technical Blog" title="sthbrx - a POWER technical blog" maxArticleAge="60" comment="" copyright="" type="rss" text="sthbrx - a POWER technical blog"/>
+  </outline>
+ </body>
+</opml>
+

Add ads.txt post.
diff --git a/posts/opting-your-domain-out-of-programmatic-advertising.mdwn b/posts/opting-your-domain-out-of-programmatic-advertising.mdwn
new file mode 100644
index 0000000..c8483e3
--- /dev/null
+++ b/posts/opting-your-domain-out-of-programmatic-advertising.mdwn
@@ -0,0 +1,64 @@
+[[!meta title="Opting your domain out of programmatic advertising"]]
+[[!meta date="2020-12-08T00:10:00.000-08:00"]]
+[[!meta license="[Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/)"]]
+
+A few years ago, the advertising industry introduced the [`ads.txt`
+project](https://iabtechlab.com/ads-txt-about) in order to defend against
+widespread *domain spoofing* vulnerabilities in [programmatic
+advertising](https://en.wikipedia.org/wiki/Programmatic_advertising).
+
+I decided to use this technology to opt out of having ads sold for my
+domains, at least through ad exchanges which perform this check, by hosting
+a text file containing this:
+
+    contact=ads@fmarier.org
+
+at the following locations:
+
+- <https://feeding.cloud.geek.nz/ads.txt>
+- <https://fmarier.org/ads.txt>
+
+(In order to get this to work on my blog, running
+[Ikiwiki](https://ikiwiki.info/) on
+[Branchable](https://www.branchable.com/), I had to disable the [txt
+plugin](https://ikiwiki.info/plugins/txt/) in order to get `ads.txt` to be
+served as a plain text file instead of being automatically rendered as
+HTML.)
+
+## Specification
+
+The key parts of the [specification](https://iabtechlab.com/ads-txt/) for our purposes are:
+
+> [3.1] If the server response indicates the resource does not exist (HTTP
+Status Code 404), the advertising system can assume no declarations exist
+and that no advertising system is unauthorized to buy and sell ads on the
+website.
+
+> [3.2.1] Some publishers may choose to not authorize any advertising system
+by publishing an empty ads.txt file, indicating that no advertising system
+is authorized to buy and sell ads on the website. So that consuming systems
+properly read and interpret the empty file (differentiating between web
+servers returning error pages for the /ads.txt URL), at least one properly
+formatted line must be included which adheres to the format specification
+described above.
+
+As you can see, the specification sadly ignores
+[RFC8615](https://tools.ietf.org/html/rfc8615) and requires that the
+`ads.txt` file be present directly in the root of your web server, like the
+venerable [`robots.txt`](http://www.robotstxt.org/) file, but unlike the
+newer [`security.txt`](https://securitytxt.org/) standard.
+
+If you don't want to provide an email address in your `ads.txt` file, the
+specification recommends using the following line verbatim:
+
+    placeholder.example.com, placeholder, DIRECT, placeholder
+
+## Validation
+
+A number of online validators exist, but I used the following to
+double-check my setup:
+
+- <https://adstxt.guru/validator/url/?url=https%3A%2F%2Ffeeding.cloud.geek.nz%2Fads.txt>
+- <https://www.adstxtgenerator.com/validator/results/?site=feeding.cloud.geek.nz>
+
+[[!tag web]] [[!tag debian]]

Attempt to fix the tag cloud.
https://ikiwiki.info/ikiwiki/directive/pagestats/
diff --git a/sidebar.mdwn b/sidebar.mdwn
index 52a5d66..5c09f50 100644
--- a/sidebar.mdwn
+++ b/sidebar.mdwn
@@ -23,4 +23,4 @@
 [[Recent Comments|comments]]
 
 [[Tags]]:
-[[!pagestats pages="./tags/* and !tags/debian and !tags/catalyst and !tags/mozilla and !tags/ubuntu and !tags/nzoss and !tags/sysadmin and !tags/mahara" among="./posts/*"]]
+[[!pagestats pages="tags/* and !tags/debian and !tags/catalyst and !tags/mozilla and !tags/ubuntu and !tags/nzoss and !tags/sysadmin and !tags/mahara" among="posts/*"]]

Add ads.txt file.
https://iabtechlab.com/ads-txt/
diff --git a/ads.txt b/ads.txt
new file mode 100644
index 0000000..a4035fd
--- /dev/null
+++ b/ads.txt
@@ -0,0 +1 @@
+contact=ads@fmarier.org

Add pack corruption post for Restic.
diff --git a/posts/removing-corrupted-data-pack-restic-backup.mdwn b/posts/removing-corrupted-data-pack-restic-backup.mdwn
new file mode 100644
index 0000000..a1fd591
--- /dev/null
+++ b/posts/removing-corrupted-data-pack-restic-backup.mdwn
@@ -0,0 +1,89 @@
+[[!meta title="Removing a corrupted data pack in a Restic backup"]]
+[[!meta date="2020-11-22T11:30:00.000-08:00"]]
+[[!meta license="[Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/)"]]
+
+I recently ran into a corrupted data pack in a [Restic](https://restic.net/)
+backup on my
+[GnuBee](https://feeding.cloud.geek.nz/posts/backing-up-to-gnubee2/). It led
+to consistent failures during the `prune` operation:
+
+    incomplete pack file (will be removed): b45afb51749c0778de6a54942d62d361acf87b513c02c27fd2d32b730e174f2e
+    incomplete pack file (will be removed): c71452fa91413b49ea67e228c1afdc8d9343164d3c989ab48f3dd868641db113
+    incomplete pack file (will be removed): 10bf128be565a5dc4a46fc2fc5c18b12ed2e77899e7043b28ce6604e575d1463
+    incomplete pack file (will be removed): df282c9e64b225c2664dc6d89d1859af94f35936e87e5941cee99b8fbefd7620
+    incomplete pack file (will be removed): 1de20e74aac7ac239489e6767ec29822ffe52e1f2d7f61c3ec86e64e31984919
+    hash does not match id: want 8fac6efe99f2a103b0c9c57293a245f25aeac4146d0e07c2ab540d91f23d3bb5, got 2818331716e8a5dd64a610d1a4f85c970fd8ae92f891d64625beaaa6072e1b84
+    github.com/restic/restic/internal/repository.Repack
+            github.com/restic/restic/internal/repository/repack.go:37
+    main.pruneRepository
+            github.com/restic/restic/cmd/restic/cmd_prune.go:242
+    main.runPrune
+            github.com/restic/restic/cmd/restic/cmd_prune.go:62
+    main.glob..func19
+            github.com/restic/restic/cmd/restic/cmd_prune.go:27
+    github.com/spf13/cobra.(*Command).execute
+            github.com/spf13/cobra/command.go:838
+    github.com/spf13/cobra.(*Command).ExecuteC
+            github.com/spf13/cobra/command.go:943
+    github.com/spf13/cobra.(*Command).Execute
+            github.com/spf13/cobra/command.go:883
+    main.main
+            github.com/restic/restic/cmd/restic/main.go:86
+    runtime.main
+            runtime/proc.go:204
+    runtime.goexit
+            runtime/asm_amd64.s:1374
+
+Thanks to the [excellent support
+forum](https://forum.restic.net/t/restic-prune-issue/3098/2), I was able to
+resolve this issue by dropping a single snapshot.
+
+First, I identified the snapshot which contained the offending pack:
+
+    $ restic -r sftp:hostname.local: find --pack 8fac6efe99f2a103b0c9c57293a245f25aeac4146d0e07c2ab540d91f23d3bb5
+    repository b0b0516c opened successfully, password is correct
+    Found blob 2beffa460d4e8ca4ee6bf56df279d1a858824f5cf6edc41a394499510aa5af9e
+     ... in file /home/francois/.local/share/akregator/Archive/http___udd.debian.org_dmd_feed_
+         (tree 602b373abedca01f0b007fea17aa5ad2c8f4d11f1786dd06574068bf41e32020)
+     ... in snapshot 5535dc9d (2020-06-30 08:34:41)
+
+Then, I could simply drop that snapshot:
+
+    $ restic -r sftp:hostname.local: forget 5535dc9d
+    repository b0b0516c opened successfully, password is correct
+    [0:00] 100.00%  1 / 1 files deleted
+
+and run the `prune` command to remove the snapshot, as well as the incomplete
+packs that were also mentioned in the above output but could never be
+removed due to the other error:
+
+    $ restic -r sftp:hostname.local: prune
+    repository b0b0516c opened successfully, password is correct
+    counting files in repo
+    building new index for repo
+    [20:11] 100.00%  77439 / 77439 packs
+    incomplete pack file (will be removed): b45afb51749c0778de6a54942d62d361acf87b513c02c27fd2d32b730e174f2e
+    incomplete pack file (will be removed): c71452fa91413b49ea67e228c1afdc8d9343164d3c989ab48f3dd868641db113
+    incomplete pack file (will be removed): 10bf128be565a5dc4a46fc2fc5c18b12ed2e77899e7043b28ce6604e575d1463
+    incomplete pack file (will be removed): df282c9e64b225c2664dc6d89d1859af94f35936e87e5941cee99b8fbefd7620
+    incomplete pack file (will be removed): 1de20e74aac7ac239489e6767ec29822ffe52e1f2d7f61c3ec86e64e31984919
+    repository contains 77434 packs (2384522 blobs) with 367.648 GiB
+    processed 2384522 blobs: 1165510 duplicate blobs, 47.331 GiB duplicate
+    load all snapshots
+    find data that is still in use for 15 snapshots
+    [1:11] 100.00%  15 / 15 snapshots
+    found 1006062 of 2384522 data blobs still in use, removing 1378460 blobs
+    will remove 5 invalid files
+    will delete 13728 packs and rewrite 15140 packs, this frees 142.285 GiB
+    [4:58:20] 100.00%  15140 / 15140 packs rewritten
+    counting files in repo
+    [18:58] 100.00%  50164 / 50164 packs
+    finding old index files
+    saved new indexes as [340cb68f 91ff77ef ee21a086 3e5fa853 084b5d4b 3b8d5b7a d5c385b4 5eff0be3 2cebb212 5e0d9244 29a36849 8251dcee 85db6fa2 29ed23f6 fb306aba 6ee289eb 0a74829d]
+    remove 190 old index files
+    [0:00] 100.00%  190 / 190 files deleted
+    remove 28868 old packs
+    [1:23] 100.00%  28868 / 28868 files deleted
+    done
+
+[[!tag restic]] [[!tag debian]]

Comment moderation
diff --git a/posts/time-synchronization-with-ntp-and-systemd/comment_7_8bb06960594c910c4b8d70cbe3f94490._comment b/posts/time-synchronization-with-ntp-and-systemd/comment_7_8bb06960594c910c4b8d70cbe3f94490._comment
new file mode 100644
index 0000000..5176515
--- /dev/null
+++ b/posts/time-synchronization-with-ntp-and-systemd/comment_7_8bb06960594c910c4b8d70cbe3f94490._comment
@@ -0,0 +1,8 @@
+[[!comment format=mdwn
+ ip="78.12.2.79"
+ claimedauthor="Alfio M."
+ subject="It worked on Raspberry PI 3 "
+ date="2020-11-11T08:58:23Z"
+ content="""
+Thank you François, your guide helped me to solve this issue on a Raspberry PI 3 running Raspbian Buster.
+"""]]

creating tag page tags/mysql
diff --git a/tags/mysql.mdwn b/tags/mysql.mdwn
new file mode 100644
index 0000000..0e3e3dc
--- /dev/null
+++ b/tags/mysql.mdwn
@@ -0,0 +1,4 @@
+[[!meta title="pages tagged mysql"]]
+
+[[!inline pages="tagged(mysql)" actions="no" archive="yes"
+feedshow=10]]

Add post about recovering from corrupt MariaDB index page.
Also create a new MySQL tag.
diff --git a/posts/fixing-mariadb-innodb-errors-mythtv30.mdwn b/posts/fixing-mariadb-innodb-errors-mythtv30.mdwn
index 303ce46..fa670dc 100644
--- a/posts/fixing-mariadb-innodb-errors-mythtv30.mdwn
+++ b/posts/fixing-mariadb-innodb-errors-mythtv30.mdwn
@@ -132,4 +132,4 @@ and then ran it like this:
 
     mysql -umythtv -pPassword1 mythconverg < alter_tables.sql
 
-[[!tag mythtv]] [[!tag debian]]
+[[!tag mythtv]] [[!tag debian]] [[!tag mysql]]
diff --git a/posts/recovering-from-corrupt-mariadb-index-page.mdwn b/posts/recovering-from-corrupt-mariadb-index-page.mdwn
new file mode 100644
index 0000000..650c89f
--- /dev/null
+++ b/posts/recovering-from-corrupt-mariadb-index-page.mdwn
@@ -0,0 +1,113 @@
+[[!meta title="Recovering from a corrupt MariaDB index page"]]
+[[!meta date="2020-11-01T15:10:00.000-08:00"]]
+[[!meta license="[Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/)"]]
+
+I ran into a corrupt [MariaDB](https://mariadb.org/) index page the other day and had to
+restore my [MythTV database](https://www.mythtv.org/wiki/Database) from the
+automatic backups I make as part of my [regular maintainance tasks](https://feeding.cloud.geek.nz/posts/automated-mythtv-maintenance-tasks/).
+
+## Signs of trouble
+
+My troubles started when my daily backup failed on this line:
+
+    mysqldump --opt mythconverg -umythtv -pPASSWORD > mythconverg-200200923T1117.sql
+
+with this error message:
+
+    mysqldump: Error 1034: Index for table 'recordedseek' is corrupt; try to repair it when dumping table `recordedseek` at row: 4059895
+
+Comparing the dump that was just created to the database dumps in
+`/var/backups/mythtv/`, it was clear that it was incomplete since it was
+about 100 MB smaller.
+
+I first tried a gentle `OPTIMIZE TABLE recordedseek` as suggested in [this
+StackExchange
+answer](https://dba.stackexchange.com/questions/31701/finding-and-fixing-innodb-index-corruption/35190#35190)
+but that caused the database to segfault:
+
+    mysqld[9141]: 2020-09-23 15:02:46 0 [ERROR] InnoDB: Database page corruption on disk or a failed file read of tablespace mythconverg/recordedseek page [page id: space=115871, page number=11373]. You may have to recover from a backup.
+    mysqld[9141]: 2020-09-23 15:02:46 0 [Note] InnoDB: Page dump in ascii and hex (16384 bytes):
+    mysqld[9141]:  len 16384; hex 06177fa70000...
+    mysqld[9141]:  C     K     c      {\;
+    mysqld[9141]: InnoDB: End of page dump
+    mysqld[9141]: 2020-09-23 15:02:46 0 [Note] InnoDB: Uncompressed page, stored checksum in field1 102203303, calculated checksums for field1: crc32 806650270, innodb 1139779342,  page type 17855 == INDEX.none 3735928559, stored checksum in field2 102203303, calculated checksums for field2: crc32 806650270, innodb 3322209073, none 3735928559,  page LSN 148 2450029404, low 4 bytes of LSN at page end 2450029404, page number (if stored to page already) 11373, space id (if created with >= MySQL-4.1.1 and stored already) 115871
+    mysqld[9141]: 2020-09-23 15:02:46 0 [Note] InnoDB: Page may be an index page where index id is 697207
+    mysqld[9141]: 2020-09-23 15:02:46 0 [Note] InnoDB: Index 697207 is `PRIMARY` in table `mythconverg`.`recordedseek`
+    mysqld[9141]: 2020-09-23 15:02:46 0 [Note] InnoDB: It is also possible that your operating system has corrupted its own file cache and rebooting your computer removes the error. If the corrupt page is an index page. You can also try to fix the corruption by dumping, dropping, and reimporting the corrupt table. You can use CHECK TABLE to scan your table for corruption. Please refer to https://mariadb.com/kb/en/library/innodb-recovery-modes/ for information about forcing recovery.
+    mysqld[9141]: 200923 15:02:46 2020-09-23 15:02:46 0 [ERROR] InnoDB: Failed to read file './mythconverg/recordedseek.ibd' at offset 11373: Page read from tablespace is corrupted.
+    mysqld[9141]: [ERROR] mysqld got signal 11 ;
+    mysqld[9141]: Core pattern: |/lib/systemd/systemd-coredump %P %u %g %s %t 9223372036854775808 %h ...
+    kernel: [820233.893658] mysqld[9186]: segfault at 90 ip 0000557a229f6d90 sp 00007f69e82e2dc0 error 4 in mysqld[557a224ef000+803000]
+    kernel: [820233.893665] Code: c4 20 83 bd e4 eb ff ff 44 48 89 ...
+    systemd[1]: mariadb.service: Main process exited, code=killed, status=11/SEGV
+    systemd[1]: mariadb.service: Failed with result 'signal'.
+    systemd-coredump[9240]: Process 9141 (mysqld) of user 107 dumped core.#012#012Stack trace of thread 9186: ...
+    systemd[1]: mariadb.service: Service RestartSec=5s expired, scheduling restart.
+    systemd[1]: mariadb.service: Scheduled restart job, restart counter is at 1.
+    mysqld[9260]: 2020-09-23 15:02:52 0 [Warning] Could not increase number of max_open_files to more than 16364 (request: 32186)
+    mysqld[9260]: 2020-09-23 15:02:53 0 [Note] InnoDB: Starting crash recovery from checkpoint LSN=638234502026
+    ...
+    mysqld[9260]: 2020-09-23 15:02:53 0 [Note] InnoDB: Recovered page [page id: space=115875, page number=5363] from the doublewrite buffer.
+    mysqld[9260]: 2020-09-23 15:02:53 0 [Note] InnoDB: Starting final batch to recover 2 pages from redo log.
+    mysqld[9260]: 2020-09-23 15:02:53 0 [Note] InnoDB: Waiting for purge to start
+    mysqld[9260]: 2020-09-23 15:02:53 0 [Note] Recovering after a crash using tc.log
+    mysqld[9260]: 2020-09-23 15:02:53 0 [Note] Starting crash recovery...
+    mysqld[9260]: 2020-09-23 15:02:53 0 [Note] Crash recovery finished.
+
+and so I went with the nuclear option of dropping the MythTV database and
+restoring from backup.
+
+## Dropping the corrupt database
+
+First of all, I shut down MythTV as `root`:
+
+    kilall mythfrontend
+    systemctl stop mythtv-status.service
+    systemctl stop mythtv-backend.service
+
+and took a full copy of my MariaDB databases just in case:
+
+    systemctl stop mariadb.service
+    cd /var/lib
+    apack /root/var-lib-mysql-20200923T1215.tgz mysql/
+    systemctl start mariadb.service
+
+before dropping the MythTV databse (`mythconverg`):
+
+    $ mysql -pPASSWORD
+    
+    MariaDB [(none)]> show databases;
+    +--------------------+
+    | Database           |
+    +--------------------+
+    | information_schema |
+    | mysql              |
+    | mythconverg        |
+    | performance_schema |
+    +--------------------+
+    4 rows in set (0.000 sec)
+    
+    MariaDB [(none)]> drop database mythconverg;
+    Query OK, 114 rows affected (25.564 sec)
+    
+    MariaDB [(none)]> quit
+    Bye
+
+## Restoring from backup
+
+Then I [re-created an empty database](https://www.mythtv.org/wiki/Database_Setup#Debian.2FUbuntu.2FMint):
+
+    mysql -pPASSWORD < /usr/share/mythtv/sql/mc.sql
+
+and [restored the last DB
+dump](https://www.mythtv.org/wiki/Database_Backup_and_Restore#Database_Restore)
+prior to the detection of the corruption:
+
+    sudo -i -u mythtv
+    /usr/share/mythtv/mythconverg_restore.pl --directory /var/backups/mythtv --filename mythconverg-1350-20200923010502.sql.gz
+
+In order to restart everything properly, I simply rebooted the machine:
+
+    systemctl reboot
+
+[[!tag mythtv]] [[!tag mysql]]

Add daily reboot to prevent the network from dropping.
diff --git a/posts/installing-debian-buster-on-gnubee2.mdwn b/posts/installing-debian-buster-on-gnubee2.mdwn
index 7773915..a268f9f 100644
--- a/posts/installing-debian-buster-on-gnubee2.mdwn
+++ b/posts/installing-debian-buster-on-gnubee2.mdwn
@@ -286,4 +286,16 @@ I also added the following to `/etc/.gitignore` to make
 since `fake-hwclock` unfortunately [keeps its data file in
 `/etc/`](https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=782314).
 
+## Fixing network drops
+
+I regularly see the GnuBee drop its network connections and nothing short of
+a reboot will fix it in my experience. Not sure whether that's a
+driver/kernel problem, but I decided to try to prevent it by scheduling
+a daily reboot in `/etc/cron.d/reboot-daily`:
+
+    30 18 * * *	root	/bin/grep --quiet finish= /proc/mdstat || /bin/systemctl reboot
+
+The extra check is there to ensure that the machine doesn't reboot if it's
+busy re-synchronizing the RAID array.
+
 [[!tag debian]] [[!tag gnubee]]

Let avahi-daemon handle hostname registration.
diff --git a/posts/upgrading-from-ubuntu-bionic-to-focal.mdwn b/posts/upgrading-from-ubuntu-bionic-to-focal.mdwn
index 4179bf1..b828492 100644
--- a/posts/upgrading-from-ubuntu-bionic-to-focal.mdwn
+++ b/posts/upgrading-from-ubuntu-bionic-to-focal.mdwn
@@ -60,10 +60,10 @@ systemd-resolved and its `/etc/resolv.conf` symlink to `/run/systemd/resolve/stu
 I added the following in a new `/etc/NetworkManager/conf.d/mdns.conf` file:
 
     [connection]
-    connection.mdns=2
+    connection.mdns=1
 
-which instructs NetworkManager to [register the hostname and resolve mDNS](https://developer.gnome.org/NetworkManager/stable/settings-connection.html)
-on all network interfaces it manages.
+which instructs NetworkManager to [resolve mDNS](https://developer.gnome.org/NetworkManager/stable/settings-connection.html)
+on all network interfaces it manages but not register a hostname since that's done by [avahi-daemon](https://launchpad.net/ubuntu/+source/avahi).
 
 Then I enabled mDNS globally in systemd-resolved by setting the following
 in `/etc/systemd/resolved.conf`:

Mention the `/etc/resolv.conf` symlink for systemd-resolved.
diff --git a/posts/upgrading-from-ubuntu-bionic-to-focal.mdwn b/posts/upgrading-from-ubuntu-bionic-to-focal.mdwn
index 2e33582..4179bf1 100644
--- a/posts/upgrading-from-ubuntu-bionic-to-focal.mdwn
+++ b/posts/upgrading-from-ubuntu-bionic-to-focal.mdwn
@@ -54,7 +54,8 @@ but not with systemd:
     Global: no
     Link 2 (enp4s0): no
 
-The [best solution I found](https://askubuntu.com/questions/1279792/local-hostname-resolution-is-slow-on-20-04) didn't involve disabling systemd-resolved.
+The [best solution I found](https://askubuntu.com/questions/1279792/local-hostname-resolution-is-slow-on-20-04) involves keeping
+systemd-resolved and its `/etc/resolv.conf` symlink to `/run/systemd/resolve/stub-resolv.conf`.
 
 I added the following in a new `/etc/NetworkManager/conf.d/mdns.conf` file:
 

Improve network setup instructions.
diff --git a/posts/upgrading-from-ubuntu-bionic-to-focal.mdwn b/posts/upgrading-from-ubuntu-bionic-to-focal.mdwn
index f488668..2e33582 100644
--- a/posts/upgrading-from-ubuntu-bionic-to-focal.mdwn
+++ b/posts/upgrading-from-ubuntu-bionic-to-focal.mdwn
@@ -26,7 +26,7 @@ same time:
 
     apt remove safe-rm
 
-## Network problems (netplan.io)
+## Network problems
 
 After the upgrade, my network settings weren't really working properly and
 so I started by switching from
@@ -35,9 +35,10 @@ so I started by switching from
 be the preferred way of configuring the network on Ubuntu now.
 
 Then I found out that netplan.io is
-[incompatible](https://bugs.launchpad.net/netplan/+bug/1830507) with
-[systemd-resolved's handling of `.local`
+[not automatically enabling](https://bugs.launchpad.net/netplan/+bug/1830507) the
+[systemd-resolved handling of `.local`
 hostnames](https://unix.stackexchange.com/questions/459991/how-to-configure-systemd-resolved-for-mdns-multicast-dns-on-local-network/574006#574006).
+
 I would be able to resolve a hostname using
 [avahi](https://launchpad.net/ubuntu/+source/avahi):
 
@@ -49,24 +50,13 @@ but not with systemd:
     $ systemd-resolve machine.local
     machine.local: resolve call failed: 'machine.local' not found
 
-The best work-around I found was to [disable systemd-resolved](https://askubuntu.com/questions/907246/how-to-disable-systemd-resolved-in-ubuntu/907249#907249):
-
-    systemctl stop systemd-resolved.service
-    systemctl disable systemd-resolved.service
-    rm /etc/resolv.conf
-
-## Network problems (NetworkManager)
-
-On a different machine, running NetworkManager instead of netplan.io,
-I used a [different solution](https://askubuntu.com/questions/1279792/local-hostname-resolution-is-slow-on-20-04) which didn't involve disabling systemd-resolved.
-
-Before making these changes, I could see that mdns was disabled in systemd-resolved:
-
     $ resolvectl mdns
     Global: no
     Link 2 (enp4s0): no
 
-To fix it, I added the following in a new `/etc/NetworkManager/conf.d/mdns.conf` file:
+The [best solution I found](https://askubuntu.com/questions/1279792/local-hostname-resolution-is-slow-on-20-04) didn't involve disabling systemd-resolved.
+
+I added the following in a new `/etc/NetworkManager/conf.d/mdns.conf` file:
 
     [connection]
     connection.mdns=2

Add mDNS solution for NetworkManager.
diff --git a/posts/upgrading-from-ubuntu-bionic-to-focal.mdwn b/posts/upgrading-from-ubuntu-bionic-to-focal.mdwn
index db1e646..f488668 100644
--- a/posts/upgrading-from-ubuntu-bionic-to-focal.mdwn
+++ b/posts/upgrading-from-ubuntu-bionic-to-focal.mdwn
@@ -26,7 +26,7 @@ same time:
 
     apt remove safe-rm
 
-## Network problems
+## Network problems (netplan.io)
 
 After the upgrade, my network settings weren't really working properly and
 so I started by switching from
@@ -55,6 +55,41 @@ The best work-around I found was to [disable systemd-resolved](https://askubuntu
     systemctl disable systemd-resolved.service
     rm /etc/resolv.conf
 
+## Network problems (NetworkManager)
+
+On a different machine, running NetworkManager instead of netplan.io,
+I used a [different solution](https://askubuntu.com/questions/1279792/local-hostname-resolution-is-slow-on-20-04) which didn't involve disabling systemd-resolved.
+
+Before making these changes, I could see that mdns was disabled in systemd-resolved:
+
+    $ resolvectl mdns
+    Global: no
+    Link 2 (enp4s0): no
+
+To fix it, I added the following in a new `/etc/NetworkManager/conf.d/mdns.conf` file:
+
+    [connection]
+    connection.mdns=2
+
+which instructs NetworkManager to [register the hostname and resolve mDNS](https://developer.gnome.org/NetworkManager/stable/settings-connection.html)
+on all network interfaces it manages.
+
+Then I enabled mDNS globally in systemd-resolved by setting the following
+in `/etc/systemd/resolved.conf`:
+
+    MulticastDNS=yes
+
+before restarting both services:
+
+    systemctl restart NetworkManager.service systemd-resolved.service
+
+With that in place, `.local` hostnames are resolved properly and I can
+see that mDNS is fully enabled:
+
+    $ resolvectl mdns
+    Global: yes
+    Link 2 (enp4s0): yes
+
 ## Boot problems
 
 For some reason I was able to boot with the kernel I got as part of the

Comment moderation
diff --git a/posts/encrypting-your-home-directory-using/comment_12_f97b9af438b6098eaeefed07c93965ca._comment b/posts/encrypting-your-home-directory-using/comment_12_f97b9af438b6098eaeefed07c93965ca._comment
new file mode 100644
index 0000000..5a39658
--- /dev/null
+++ b/posts/encrypting-your-home-directory-using/comment_12_f97b9af438b6098eaeefed07c93965ca._comment
@@ -0,0 +1,10 @@
+[[!comment format=mdwn
+ username="francois@665656f0ba400877c9b12e8fbb086e45aa01f7c0"
+ nickname="francois"
+ subject="Re: comment 14"
+ date="2020-10-30T21:58:19Z"
+ content="""
+> Thanks for the info, but where do you enter the password when using this method?
+
+As long as your home directory is mounted automatically via `/etc/fstab`, you should be prompted for the password at boot time.
+"""]]

Comment moderation
diff --git a/posts/encrypting-your-home-directory-using/comment_11_faa30b967d333be1b081f48059431007._comment b/posts/encrypting-your-home-directory-using/comment_11_faa30b967d333be1b081f48059431007._comment
new file mode 100644
index 0000000..abe75c5
--- /dev/null
+++ b/posts/encrypting-your-home-directory-using/comment_11_faa30b967d333be1b081f48059431007._comment
@@ -0,0 +1,7 @@
+[[!comment format=mdwn
+ ip="95.86.237.24"
+ subject="comment 14"
+ date="2020-10-30T20:38:10Z"
+ content="""
+Thanks for the info, but where do you enter the password when using this method?
+"""]]

Remove redundant comments.
diff --git a/posts/encrypting-your-home-directory-using/comment_3_e763425321329cabb2a3a82d8d234cbd._comment b/posts/encrypting-your-home-directory-using/comment_3_e763425321329cabb2a3a82d8d234cbd._comment
deleted file mode 100644
index 1d3bd7c..0000000
--- a/posts/encrypting-your-home-directory-using/comment_3_e763425321329cabb2a3a82d8d234cbd._comment
+++ /dev/null
@@ -1,10 +0,0 @@
-[[!comment format=mdwn
- username="http://www.blogger.com/profile/17642321912779274665"
- nickname="jak"
- subject=""
- date="2008-05-24T21:34:00.000+12:00"
- content="""
-You might want to use cp -a /homebackup/{.*,*} /home to also copy dot-files.
-
-
-"""]]
diff --git a/posts/encrypting-your-home-directory-using/comment_5_226684dfe4bdd17438dbde62977e5abb._comment b/posts/encrypting-your-home-directory-using/comment_5_226684dfe4bdd17438dbde62977e5abb._comment
deleted file mode 100644
index dd171bb..0000000
--- a/posts/encrypting-your-home-directory-using/comment_5_226684dfe4bdd17438dbde62977e5abb._comment
+++ /dev/null
@@ -1,9 +0,0 @@
-[[!comment format=mdwn
- claimedauthor="Mike"
- subject=""
- date="2008-05-24T23:10:00.000+12:00"
- content="""
-Nice idea in principle but you might want to change the instructions slightly so that the user doesn't lose all their dotfiles in the process.
-
-
-"""]]
diff --git a/posts/encrypting-your-home-directory-using/comment_9_db3e029e4c901ef109a1fc29df4b4e4b._comment b/posts/encrypting-your-home-directory-using/comment_9_db3e029e4c901ef109a1fc29df4b4e4b._comment
deleted file mode 100644
index c215b5f..0000000
--- a/posts/encrypting-your-home-directory-using/comment_9_db3e029e4c901ef109a1fc29df4b4e4b._comment
+++ /dev/null
@@ -1,9 +0,0 @@
-[[!comment format=mdwn
- claimedauthor="Anonymous"
- subject=""
- date="2008-05-25T02:46:00.000+12:00"
- content="""
-The first step could be simpler: 'cp -a /home /homebackup'. Also, in response to the post that you should use 'cp -a /homebackup/{.*,*} /home' to get back dotfiles -- not only is this usually unnecessary, since dotfiles are usually in /home/USERNAME/, not directly in /home/, but because cp -a is recursive, '/homebackup/.*' includes '/homebackup/..'. Don't do it; it will copy the entire contents of your filesystem into '/home'.
-
-
-"""]]

Mention mouse cursor problem on Pop!_OS 20.04.
diff --git a/posts/creating-a-modern-tiling-desktop-environment-using-i3.mdwn b/posts/creating-a-modern-tiling-desktop-environment-using-i3.mdwn
index 9ebdaa7..0c88861 100644
--- a/posts/creating-a-modern-tiling-desktop-environment-using-i3.mdwn
+++ b/posts/creating-a-modern-tiling-desktop-environment-using-i3.mdwn
@@ -162,4 +162,14 @@ If you find your systray on the wrong display after plugging an external monitor
 
 and then restarting i3.
 
+# Mouse cursor
+
+On [Pop!\_OS](https://pop.system76.com/) 20.04, I ran into a problem where only the mouse was using the
+default Xorg cursor instead of a styled and scalable one.
+
+This was fixed using [this work-around](https://www.reddit.com/r/i3wm/comments/edz4a5/mouse_cursor_size/):
+
+    mkdir -p ~/.icons/default
+    cp -r /usr/share/icons/Pop/* ~/.icons/default/
+
 [[!tag debian]] [[!tag i3]] [[!tag gnome]] [[!tag nzoss]] [[!tag systemd]]

Add GnuBee root partition copy post.
diff --git a/posts/copying-gnubee-root-partition-to-another-drive.mdwn b/posts/copying-gnubee-root-partition-to-another-drive.mdwn
new file mode 100644
index 0000000..6ad9432
--- /dev/null
+++ b/posts/copying-gnubee-root-partition-to-another-drive.mdwn
@@ -0,0 +1,138 @@
+[[!meta title="Copying a GnuBee's root partition onto a new drive"]]
+[[!meta date="2020-10-24T16:30:00.000-07:00"]]
+[[!meta license="[Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/)"]]
+
+Here is the process I followed when I moved my GnuBee's root partition from
+one flaky Kingston SSD drive to a brand new Samsung SSD.
+
+It was relatively straightforward, but there are two key points:
+
+1. Make sure you label the root partition `GNUBEE-ROOT`.
+2. Make sure you copy the network configuration from the SSD, not the
+   `tmpfs` mount.
+
+# Copy the partition table
+
+First, with both drives plugged in, I replicated the partition table of the
+first drive (`/dev/sda`):
+
+    # fdisk -l /dev/sda
+    Disk /dev/sda: 111.8 GiB, 120034123776 bytes, 234441648 sectors
+    Disk model: KINGSTON SA400S3
+    Units: sectors of 1 * 512 = 512 bytes
+    Sector size (logical/physical): 512 bytes / 512 bytes
+    I/O size (minimum/optimal): 512 bytes / 512 bytes
+    Disklabel type: gpt
+    Disk identifier: 799CD830-526B-42CE-8EE7-8C94EF098D46
+    
+    Device       Start       End   Sectors   Size Type
+    /dev/sda1     2048   8390655   8388608     4G Linux swap
+    /dev/sda2  8390656 234441614 226050959 107.8G Linux filesystem
+
+onto the second drive (`/dev/sde`):
+
+    # fdisk /dev/sde
+    
+    Welcome to fdisk (util-linux 2.33.1).
+    Changes will remain in memory only, until you decide to write them.
+    Be careful before using the write command.
+    
+    Device does not contain a recognized partition table.
+    Created a new DOS disklabel with disk identifier 0xd011eaba.
+    
+    Command (m for help): g
+    Created a new GPT disklabel (GUID: 83F70325-5BE0-034E-A9E1-1965FEFD8E9F).
+    
+    Command (m for help): n
+    Partition number (1-128, default 1): 
+    First sector (2048-488397134, default 2048): 
+    Last sector, +/-sectors or +/-size{K,M,G,T,P} (2048-488397134, default 488397134): +4G
+    
+    Created a new partition 1 of type 'Linux filesystem' and of size 4 GiB.
+    
+    Command (m for help): t
+    Selected partition 1
+    Partition type (type L to list all types): 19
+    Changed type of partition 'Linux filesystem' to 'Linux swap'.
+    
+    Command (m for help): n
+    Partition number (2-128, default 2): 
+    First sector (8390656-488397134, default 8390656): 
+    Last sector, +/-sectors or +/-size{K,M,G,T,P} (8390656-488397134, default 488397134): 234441614
+    
+    Created a new partition 2 of type 'Linux filesystem' and of size 107.8 GiB.
+    
+    Command (m for help): p
+    Disk /dev/sde: 232.9 GiB, 250059350016 bytes, 488397168 sectors
+    Disk model: Samsung SSD 860 
+    Units: sectors of 1 * 512 = 512 bytes
+    Sector size (logical/physical): 512 bytes / 512 bytes
+    I/O size (minimum/optimal): 512 bytes / 512 bytes
+    Disklabel type: gpt
+    Disk identifier: 83F70325-5BE0-034E-A9E1-1965FEFD8E9F
+    
+    Device       Start       End   Sectors   Size Type
+    /dev/sde1     2048   8390655   8388608     4G Linux swap
+    /dev/sde2  8390656 234441614 226050959 107.8G Linux filesystem
+    
+    Command (m for help): w
+    The partition table has been altered.
+    Calling ioctl() to re-read partition table.
+    Syncing disks.
+
+I wasted a large amount of space on the second drive, but that was on
+purpose in case I decide to later on move to a
+[RAID-1](https://en.wikipedia.org/wiki/Standard_RAID_levels#RAID_1) root
+partition with the Kingston SSD.
+
+# Format the partitions
+
+Second, I formated the new partitions:
+
+    # mkswap /dev/sde1 
+    Setting up swapspace version 1, size = 4 GiB (4294963200 bytes)
+    no label, UUID=7a85fbce-2493-45c1-a548-4ec6e827ec29
+
+    # mkfs.ext4 /dev/sde2 
+    mke2fs 1.44.5 (15-Dec-2018)
+    Discarding device blocks: done                            
+    Creating filesystem with 28256369 4k blocks and 7069696 inodes
+    Filesystem UUID: 732a76df-d369-4e7b-857a-dd55fd461bbc
+    Superblock backups stored on blocks: 
+        32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208, 
+        4096000, 7962624, 11239424, 20480000, 23887872
+    
+    Allocating group tables: done                            
+    Writing inode tables: done                            
+    Creating journal (131072 blocks): done
+    Writing superblocks and filesystem accounting information: done   
+
+and labeled the root partition so that the GnuBee can pick it up as it boots
+up:
+
+    e2label /dev/sde2 GNUBEE-ROOT
+
+since `GNUBEE-ROOT` is what [`uboot` will be looking
+for](http://gnubee.org/background_info.html#using-debian-or-openmediavault).
+
+# Copy the data over
+
+Finally, I copied the data over from the original drive to the new one:
+
+    # umount /etc/network
+    # mkdir /mnt/root
+    # mount /dev/sde2 /mnt/root
+    # rsync -aHx --delete --exclude=/dev/* --exclude=/proc/* --exclude=/sys/* --exclude=/tmp/* --exclude=/mnt/* --exclude=/lost+found/* --exclude=/media/* --exclude=/rom/* /* /mnt/root/
+    # sync
+
+Note that if you don't unmount `/etc/network/`, you'll be copying the
+override provided at boot time instead of the underlying config that's on
+the root partition. The reason that this matters is that the [script that
+renames the network interfaces to `ethblack` and
+`ethblue`](https://github.com/neilbrown/gnubee-tools/blob/64e98ce8352d799b22967bbd2681f325e683b70d/initramfs/init#L77-L88)
+expects specific files in order to produce a working network configuration.
+If you copy the final *modified* config files then you end up with an
+bind-mounted empty directory as `/etc/network`, and the network interfaces
+can't be brought up successfully.
+
+[[!tag gnubee]]

Comment moderation
diff --git a/posts/using-letsencrypt-cert-with-asterisk/comment_1_7d1810b82ce0d3f0e0904e5f0c62d535._comment b/posts/using-letsencrypt-cert-with-asterisk/comment_1_7d1810b82ce0d3f0e0904e5f0c62d535._comment
new file mode 100644
index 0000000..bda51ba
--- /dev/null
+++ b/posts/using-letsencrypt-cert-with-asterisk/comment_1_7d1810b82ce0d3f0e0904e5f0c62d535._comment
@@ -0,0 +1,11 @@
+[[!comment format=mdwn
+ ip="2001:2003:f04b:7800:fd0f:a515:b7e0:67e5"
+ claimedauthor="Steve Kemp"
+ url="https://steve.kemp.fi/"
+ subject="Alternative .."
+ date="2020-10-18T14:26:50Z"
+ content="""
+That's not a bad solution, but it might be simpler to use a client which handles a DNS-challenge.
+
+With DNS-challenges you don't have to worry about webservers, or internal/external firewalling.
+"""]]

Add post about Let's Encrypt cert with Asterisk.
diff --git a/posts/sip-encryption-on-voip-ms.mdwn b/posts/sip-encryption-on-voip-ms.mdwn
index 37697ae..f7ca9c2 100644
--- a/posts/sip-encryption-on-voip-ms.mdwn
+++ b/posts/sip-encryption-on-voip-ms.mdwn
@@ -56,19 +56,26 @@ The dialplan didn't change and so I still have the following in
 
 ## Server certificate
 
-The only thing I still need to fix is to make this error message go away in
-my logs:
+After setting everything up, I saw the following error in my logs:
 
     asterisk[8691]: ERROR[8691]: tcptls.c:966 in __ssl_setup: TLS/SSL error loading cert file. <asterisk.pem>
 
-It appears to be related to the fact that I didn't set `tlscertfile` in
+due to the fact that I didn't set `tlscertfile` in
 `/etc/asterisk/sip.conf` and that it's using its default value of
 `asterisk.pem`, a non-existent file.
 
-Since my Asterisk server is only acting as a TLS *client*, and not a TLS
-*server*, there's probably no harm in not having a certificate. That said,
-it looks pretty easy to [use a Let's Encrypt cert with
-Asterisk](https://community.asterisk.org/t/has-anyone-used-letsencrypt-to-setup-ssl-for-asterisk/67145/6).
+I initially thought that since my Asterisk server is only acting as a TLS
+*client*, and not a TLS *server*, there's probably no harm in not having a
+certificate. In practice however, my TLS connection was a little unreliable
+and it would regularly fail with the following TLS errors:
+
+    asterisk[534775]: ERROR[534879]: iostream.c:538 in ast_iostream_close: SSL_shutdown() failed: error:00000005:lib(0):func(0):DH lib, Underlying BIO error: Broken pipe
+    asterisk[534775]: ERROR[610289]: tcptls.c:553 in ast_tcptls_client_start: Unable to connect SIP socket to w.x.y.z:5061: Connection refused
+    asterisk[534775]: ERROR[610289]: tcptls.c:553 in ast_tcptls_client_start: Unable to connect SIP socket to w.x.y.z:5061: Connection reset by peer
+
+I therefore decided to [setup a Let's Encrypt certificate in
+Asterisk](https://feeding.cloud.geek.nz/posts/using-letsencrypt-cert-with-asterisk/)
+to eliminate the original error.
 
 ## Firewall
 
@@ -88,4 +95,4 @@ recommendations](https://wiki.voip.ms/article/Firewall) in
 where `w.x.y.z` is the IP address of `servername.voip.ms` as returned by
 `dig +short servername.voip.ms`.
 
-[[!tag debian]] [[!tag asterisk]] [[!tag letsencrypt]] [[!tag voipms]]
+[[!tag debian]] [[!tag asterisk]] [[!tag ssl]] [[!tag voipms]]
diff --git a/posts/using-letsencrypt-cert-with-asterisk.mdwn b/posts/using-letsencrypt-cert-with-asterisk.mdwn
new file mode 100644
index 0000000..fcd3350
--- /dev/null
+++ b/posts/using-letsencrypt-cert-with-asterisk.mdwn
@@ -0,0 +1,86 @@
+[[!meta title="Using a Let's Encrypt TLS certificate with Asterisk 16.2"]]
+[[!meta date="2020-10-17T17:45:00.000-07:00"]]
+[[!meta license="[Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/)"]]
+
+In order to fix the following error after setting up [SIP
+TLS](https://feeding.cloud.geek.nz/posts/sip-encryption-on-voip-ms/) in
+[Asterisk](https://www.asterisk.org/) 16.2:
+
+    asterisk[8691]: ERROR[8691]: tcptls.c:966 in __ssl_setup: TLS/SSL error loading cert file. <asterisk.pem>
+
+I created a [Let's Encrypt](https://letsencrypt.org/) certificate using
+[certbot](https://certbot.eff.org/):
+
+    apt install certbot
+    certbot certonly --standalone -d hostname.example.com
+
+To enable the `asterisk` user to load the certificate successfuly (it
+doesn't permission to access to the certificates under `/etc/letsencrypt/`),
+I copied it to the right directory:
+
+    cp /etc/letsencrypt/live/hostname.example.com/privkey.pem /etc/asterisk/asterisk.key
+    cp /etc/letsencrypt/live/hostname.example.com/fullchain.pem /etc/asterisk/asterisk.cert
+    chown asterisk:asterisk /etc/asterisk/asterisk.cert /etc/asterisk/asterisk.key
+    chmod go-rwx /etc/asterisk/asterisk.cert /etc/asterisk/asterisk.key
+
+Then I set the following variables in `/etc/asterisk/sip.conf`:
+
+    tlscertfile=/etc/asterisk/asterisk.cert
+    tlsprivatekey=/etc/asterisk/asterisk.key
+
+## Automatic renewal
+
+The machine on which I run asterisk has a tricky Apache setup:
+
+- a webserver is running on port 80
+- port 80 is restricted to the local network
+
+This meant that the certbot domain ownership checks would get blocked by the
+firewall, and I couldn't open that port without exposing the private
+webserver to the Internet.
+
+So I ended up disabling the built-in certbot renewal mechanism:
+
+    systemctl disable certbot.timer certbot.service
+    systemctl stop certbot.timer certbot.service
+
+and then writing my own script in `/etc/cron.daily/certbot-francois`:
+
+    #!/bin/bash
+    TEMPFILE=`mktemp`
+    
+    # Stop Apache and backup firewall.
+    /bin/systemctl stop apache2.service
+    /usr/sbin/iptables-save > $TEMPFILE
+    
+    # Open up port 80 to the whole world.
+    /usr/sbin/iptables -D INPUT -j LOGDROP
+    /usr/sbin/iptables -A INPUT -p tcp --dport 80 -j ACCEPT
+    /usr/sbin/iptables -A INPUT -j LOGDROP
+    
+    # Renew all certs.
+    /usr/bin/certbot renew --quiet
+    
+    # Restore firewall and restart Apache.
+    /usr/sbin/iptables -D INPUT -p tcp --dport 80 -j ACCEPT
+    /usr/sbin/iptables-restore < $TEMPFILE
+    /bin/systemctl start apache2.service
+    
+    # Copy certificate into asterisk.
+    cp /etc/letsencrypt/live/hostname.example.com/privkey.pem /etc/asterisk/asterisk.key
+    cp /etc/letsencrypt/live/hostname.example.com/fullchain.pem /etc/asterisk/asterisk.cert
+    chown asterisk:asterisk /etc/asterisk/asterisk.cert /etc/asterisk/asterisk.key
+    chmod go-rwx /etc/asterisk/asterisk.cert /etc/asterisk/asterisk.key
+    /bin/systemctl restart asterisk.service
+    
+    # Commit changes to etckeeper.
+    pushd /etc/ > /dev/null
+    /usr/bin/git add letsencrypt asterisk
+    DIFFSTAT="$(/usr/bin/git diff --cached --stat)"
+    if [ -n "$DIFFSTAT" ] ; then
+        /usr/bin/git commit --quiet -m "Renewed letsencrypt certs."
+        echo "$DIFFSTAT"
+    fi
+    popd > /dev/null
+
+[[!tag letsencrypt]] [[!tag asterisk]] [[!tag debian]] [[!tag ssl]]

Remove now erroneous word.
This should have been part of 1860c102c323e5f0c5085e1ca6a3d36ea4024d89.
diff --git a/posts/making-apache-website-available-tor-onion-service.mdwn b/posts/making-apache-website-available-tor-onion-service.mdwn
index 70a985e..bd21dd8 100644
--- a/posts/making-apache-website-available-tor-onion-service.mdwn
+++ b/posts/making-apache-website-available-tor-onion-service.mdwn
@@ -109,7 +109,7 @@ and available in [Brave Nightly](https://brave.com/download-nightly/)):
 
 ![](/posts/making-apache-website-available-tor-onion-service/onion-location.png)
 
-Testing that the `Alt-Svc` is working also required using the [Tor Browser](https://www.torproject.org/download/)
+Testing that the `Alt-Svc` is working required using the [Tor Browser](https://www.torproject.org/download/)
 since that's [not yet supported in
 Brave](https://github.com/brave/brave-browser/issues/1121):
 

Use Brave Nightly to test Onion-Location header.
diff --git a/posts/making-apache-website-available-tor-onion-service.mdwn b/posts/making-apache-website-available-tor-onion-service.mdwn
index 88d7f71..70a985e 100644
--- a/posts/making-apache-website-available-tor-onion-service.mdwn
+++ b/posts/making-apache-website-available-tor-onion-service.mdwn
@@ -102,14 +102,14 @@ window](https://support.brave.com/hc/en-us/articles/360018121491-What-is-a-Priva
 - <http://ixrdj3iwwhkuau5tby5jh3a536a2rdhpbdbu6ldhng43r47kim7a3lid.onion/>
 - <https://ixrdj3iwwhkuau5tby5jh3a536a2rdhpbdbu6ldhng43r47kim7a3lid.onion/> (a TLS certificate error is expected)
 
-I also checked using the [Tor Browser](https://www.torproject.org/download/)
-that the [`Onion-Location`
-header](https://community.torproject.org/onion-services/advanced/onion-location/)
-is correctly recognized and triggers the display of a button in the URL bar:
+I also checked that the main URL (<https://fmarier.org/>) exposes a working
+[`Onion-Location` header](https://community.torproject.org/onion-services/advanced/onion-location/)
+which triggers the display of a button in the URL bar (recently [merged](https://github.com/brave/brave-core/pull/6762)
+and available in [Brave Nightly](https://brave.com/download-nightly/)):
 
 ![](/posts/making-apache-website-available-tor-onion-service/onion-location.png)
 
-Testing that the `Alt-Svc` is working also required using the Tor Browser
+Testing that the `Alt-Svc` is working also required using the [Tor Browser](https://www.torproject.org/download/)
 since that's [not yet supported in
 Brave](https://github.com/brave/brave-browser/issues/1121):
 
diff --git a/posts/making-apache-website-available-tor-onion-service/onion-location.png b/posts/making-apache-website-available-tor-onion-service/onion-location.png
index f735bcf..04f28a6 100644
Binary files a/posts/making-apache-website-available-tor-onion-service/onion-location.png and b/posts/making-apache-website-available-tor-onion-service/onion-location.png differ

Add Tor Onion Service post.
diff --git a/posts/making-apache-website-available-tor-onion-service.mdwn b/posts/making-apache-website-available-tor-onion-service.mdwn
new file mode 100644
index 0000000..88d7f71
--- /dev/null
+++ b/posts/making-apache-website-available-tor-onion-service.mdwn
@@ -0,0 +1,148 @@
+[[!meta title="Making an Apache website available as a Tor Onion Service"]]
+[[!meta date="2020-10-13T20:45:00.000-07:00"]]
+[[!meta license="[Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/)"]]
+
+As part of the [#MoreOnionsPorFavor
+campaign](https://blog.torproject.org/more-onions-porfavor), I decided to
+follow [`brave.com`'s lead](https://brave.com/new-onion-service/) and make
+my [homepage](https://fmarier.org) available as a [Tor onion
+service](https://2019.www.torproject.org/docs/tor-onion-service.html.en).
+
+## Tor daemon setup
+
+I started by installing the Tor daemon locally:
+
+    apt install tor
+
+and then setting the following in `/etc/tor/torrc`:
+
+    SocksPort 0
+    SocksPolicy reject *
+    HiddenServiceDir /var/lib/tor/hidden_service/
+    HiddenServicePort 80 [2600:3c04::f03c:91ff:fe8c:61ac]:80
+    HiddenServicePort 443 [2600:3c04::f03c:91ff:fe8c:61ac]:443
+    HiddenServiceVersion 3
+    HiddenServiceNonAnonymousMode 1
+    HiddenServiceSingleHopMode 1
+
+in order to create a [version 3 onion
+service](https://blog.torproject.org/v2-deprecation-timeline) without
+actually running a [Tor relay](https://community.torproject.org/relay/).
+
+Note that since I am making a public website available over Tor, I do not
+need the location of the website to be hidden and so I used the same
+settings as
+[Cloudflare](https://blog.cloudflare.com/cloudflare-onion-service/) in their
+public Tor proxy.
+
+Also, I explicitly used the **external IPv6 address** of my server in the
+configuration in order to prevent [localhost
+bypasses](https://riseup.net/en/security/network-security/tor/onionservices-best-practices#be-careful-of-localhost-bypasses).
+
+After restarting the Tor daemon to reload the configuration file:
+
+    systemctl restart tor.service
+
+I looked for the address of my onion service:
+
+    $ cat /var/lib/tor/hidden_service/hostname 
+    ixrdj3iwwhkuau5tby5jh3a536a2rdhpbdbu6ldhng43r47kim7a3lid.onion
+
+## Apache configuration
+
+Next, I enabled a few required Apache modules:
+
+    a2enmod mpm_event
+    a2enmod http2
+    a2enmod headers
+
+and configured my Apache vhosts in `/etc/apache2/sites-enabled/www.conf`:
+
+    <VirtualHost *:443>
+        ServerName fmarier.org
+        ServerAlias ixrdj3iwwhkuau5tby5jh3a536a2rdhpbdbu6ldhng43r47kim7a3lid.onion
+    
+        Protocols h2, http/1.1
+        Header set Onion-Location "http://ixrdj3iwwhkuau5tby5jh3a536a2rdhpbdbu6ldhng43r47kim7a3lid.onion%{REQUEST_URI}s"
+        Header set alt-svc 'h2="ixrdj3iwwhkuau5tby5jh3a536a2rdhpbdbu6ldhng43r47kim7a3lid.onion:443"; ma=315360000; persist=1'
+        Header add Strict-Transport-Security: "max-age=63072000"
+    
+        Include /etc/fmarier-org/www-common.include
+    
+        SSLEngine On
+        SSLCertificateFile /etc/letsencrypt/live/fmarier.org/fullchain.pem
+        SSLCertificateKeyFile /etc/letsencrypt/live/fmarier.org/privkey.pem
+    </VirtualHost>
+    
+    <VirtualHost *:80>
+        ServerName fmarier.org
+        Redirect permanent / https://fmarier.org/
+    </VirtualHost>
+         
+    <VirtualHost *:80>
+        ServerName ixrdj3iwwhkuau5tby5jh3a536a2rdhpbdbu6ldhng43r47kim7a3lid.onion
+        Include /etc/fmarier-org/www-common.include
+    </VirtualHost>
+
+Note that `/etc/fmarier-org/www-common.include` contains all of the
+configuration options that are common to both the HTTP and the HTTPS sites
+(e.g. document root, caching headers, aliases, etc.).
+
+Finally, I restarted Apache:
+
+    apache2ctl configtest
+    systemctl restart apache2.service
+
+## Testing
+
+In order to test that my website is correctly available at its `.onion`
+address, I opened the following URLs in a [Brave Tor
+window](https://support.brave.com/hc/en-us/articles/360018121491-What-is-a-Private-Window-with-Tor-):
+
+- <http://ixrdj3iwwhkuau5tby5jh3a536a2rdhpbdbu6ldhng43r47kim7a3lid.onion/>
+- <https://ixrdj3iwwhkuau5tby5jh3a536a2rdhpbdbu6ldhng43r47kim7a3lid.onion/> (a TLS certificate error is expected)
+
+I also checked using the [Tor Browser](https://www.torproject.org/download/)
+that the [`Onion-Location`
+header](https://community.torproject.org/onion-services/advanced/onion-location/)
+is correctly recognized and triggers the display of a button in the URL bar:
+
+![](/posts/making-apache-website-available-tor-onion-service/onion-location.png)
+
+Testing that the `Alt-Svc` is working also required using the Tor Browser
+since that's [not yet supported in
+Brave](https://github.com/brave/brave-browser/issues/1121):
+
+1. Open <https://fmarier.org>.
+2. Wait 30 seconds.
+3. Reload the page.
+
+On the server side, I saw the following:
+
+    2a0b:f4c2:2::1 - - [14/Oct/2020:02:42:20 +0000] "GET / HTTP/2.0" 200 2696 "-" "Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0"
+    2600:3c04::f03c:91ff:fe8c:61ac - - [14/Oct/2020:02:42:53 +0000] "GET / HTTP/2.0" 200 2696 "-" "Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0"
+
+That first IP address is from a Tor exit node:
+
+    $ whois 2a0b:f4c2:2::1
+    ...
+    inet6num:       2a0b:f4c2::/40
+    netname:        MK-TOR-EXIT
+    remarks:        -----------------------------------
+    remarks:        This network is used for Tor Exits.
+    remarks:        We do not have any logs at all.
+    remarks:        For more information please visit:
+    remarks:        https://www.torproject.org
+
+which indicates that the first request was **not using** the `.onion`
+address.
+
+The second IP address is the one for my server:
+
+    $ dig +short -x 2600:3c04::f03c:91ff:fe8c:61ac
+    hafnarfjordur.fmarier.org.
+
+which indicates that the second request to Apache came from the Tor relay
+running on my server, hence **using** the `.onion` address.
+
+[[!tag debian]] [[!tag tor]] [[!tag apache]] [[!tag brave]]
diff --git a/posts/making-apache-website-available-tor-onion-service/onion-location.png b/posts/making-apache-website-available-tor-onion-service/onion-location.png
new file mode 100644
index 0000000..f735bcf
Binary files /dev/null and b/posts/making-apache-website-available-tor-onion-service/onion-location.png differ

Add post on focal upgrade.
diff --git a/posts/upgrading-from-ubuntu-bionic-to-focal.mdwn b/posts/upgrading-from-ubuntu-bionic-to-focal.mdwn
new file mode 100644
index 0000000..db1e646
--- /dev/null
+++ b/posts/upgrading-from-ubuntu-bionic-to-focal.mdwn
@@ -0,0 +1,76 @@
+[[!meta title="Upgrading from Ubuntu 18.04 bionic to 20.04 focal"]]
+[[!meta date="2020-10-11T12:30:00.000-07:00"]]
+[[!meta license="[Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/)"]]
+
+I recently upgraded from Ubuntu 18.04.5 (bionic) to 20.04.1 (focal) and it
+was one of the roughest Ubuntu upgrades I've gone through in a while. Here
+are the notes I took on avoiding or fixing the problems I ran into.
+
+## Preparation
+
+Before going through the upgrade, I disabled the configurations which I know
+interfere with the process:
+
+- Enable [etckeeper](https://launchpad.net/ubuntu/+source/etckeeper) *auto-commits before install* by putting the
+  following in `/etc/etckeeper/etckeeper.conf`:
+
+        AVOID_COMMIT_BEFORE_INSTALL=0
+
+- Remount `/tmp` as exectuable:
+
+        mount -o remount,exec /tmp
+
+Another step I should have taken but didn't, was to temporarily remove [safe-rm](https://launchpad.net/ubuntu/+source/safe-rm)
+since it [caused some problems](https://bugs.launchpad.net/ubuntu/+source/ubuntu-release-upgrader/+bug/1893724) related to a Perl upgrade happening at the
+same time:
+
+    apt remove safe-rm
+
+## Network problems
+
+After the upgrade, my network settings weren't really working properly and
+so I started by switching from
+[ifupdown](https://launchpad.net/ubuntu/+source/ifupdown) to
+[netplan.io](https://launchpad.net/ubuntu/+source/netplan.io) which seems to
+be the preferred way of configuring the network on Ubuntu now.
+
+Then I found out that netplan.io is
+[incompatible](https://bugs.launchpad.net/netplan/+bug/1830507) with
+[systemd-resolved's handling of `.local`
+hostnames](https://unix.stackexchange.com/questions/459991/how-to-configure-systemd-resolved-for-mdns-multicast-dns-on-local-network/574006#574006).
+I would be able to resolve a hostname using
+[avahi](https://launchpad.net/ubuntu/+source/avahi):
+
+    $ avahi-resolve --name machine.local
+    machine.local	192.168.1.5
+
+but not with systemd:
+
+    $ systemd-resolve machine.local
+    machine.local: resolve call failed: 'machine.local' not found
+
+The best work-around I found was to [disable systemd-resolved](https://askubuntu.com/questions/907246/how-to-disable-systemd-resolved-in-ubuntu/907249#907249):
+
+    systemctl stop systemd-resolved.service
+    systemctl disable systemd-resolved.service
+    rm /etc/resolv.conf
+
+## Boot problems
+
+For some reason I was able to boot with the kernel I got as part of the
+focal update, but a later kernel update rendered my machine unbootable.
+
+Adding some missing [RAID](https://en.wikipedia.org/wiki/RAID)-related modules to
+`/etc/initramfs-tools/modules`:
+
+    raid1
+    dmraid
+    md-raid1
+
+and then re-creating all initramfs:
+
+    update-initramfs -u -k all
+
+seemed to do the trick.
+
+[[!tag ubuntu]] [[!tag systemd]] [[!tag raid]]

Link to the right upstream homepage.
The `iperf` package is IPerf2 (https://iperf2.sourceforge.io/) whereas `iperf3`
is iPerf (https://iperf.fr/).
diff --git a/posts/npr-modem-setup-testing-linux.mdwn b/posts/npr-modem-setup-testing-linux.mdwn
index c2b1318..c4fa330 100644
--- a/posts/npr-modem-setup-testing-linux.mdwn
+++ b/posts/npr-modem-setup-testing-linux.mdwn
@@ -128,7 +128,7 @@ two machines, I decided to run a quick test to measure the available
 bandwidth in an ideal setting (i.e. the two antennas very close to each
 other).
 
-On both computers, I installed [iperf](https://iperf.fr/):
+On both computers, I installed [iperf](https://iperf2.sourceforge.io/):
 
     apt install iperf
 

Comment moderation
diff --git a/posts/repairing-corrupt-ext4-root-partition/comment_1_212a8a6ad1ed4a94c76d530f00631934._comment b/posts/repairing-corrupt-ext4-root-partition/comment_1_212a8a6ad1ed4a94c76d530f00631934._comment
new file mode 100644
index 0000000..e769999
--- /dev/null
+++ b/posts/repairing-corrupt-ext4-root-partition/comment_1_212a8a6ad1ed4a94c76d530f00631934._comment
@@ -0,0 +1,13 @@
+[[!comment format=mdwn
+ ip="2a01:261:355:1d00:6ef0:49ff:fe08:ef01"
+ claimedauthor="Tomaž"
+ url="https://www.tablix.org/~avian/blog/"
+ subject="Suggestions about filesystem corruption"
+ date="2020-09-27T11:19:20Z"
+ content="""
+Hi. Two things you might want to check, if you haven't already.
+
+See if the \"UDMA_CRC_Error_Count\" or \"CRC_Error_Count\" attribute (199) reported by your smartctl is >0 and slowly increasing over time. It's not marked as an error by smartctl and it's easy to miss. It's an indication of a flaky SATA bus connection and I've seen this cause filesystem corruption (I'm guessing because every once in a while CRC will randomly end up OK for a corrupted command).
+
+The other thing is to check if you're running \"fstrim\". Some SSDs are known to have bugs with that and you might be running a kernel that doesn't yet have a workaround or blacklist for your particular model or SSD firmware version. See [[https://github.com/torvalds/linux/blob/master/drivers/ata/libata-core.c#L3774]].
+"""]]

Add necessary firewall rules.
diff --git a/posts/sip-encryption-on-voip-ms.mdwn b/posts/sip-encryption-on-voip-ms.mdwn
index 0d99118..37697ae 100644
--- a/posts/sip-encryption-on-voip-ms.mdwn
+++ b/posts/sip-encryption-on-voip-ms.mdwn
@@ -70,4 +70,22 @@ Since my Asterisk server is only acting as a TLS *client*, and not a TLS
 it looks pretty easy to [use a Let's Encrypt cert with
 Asterisk](https://community.asterisk.org/t/has-anyone-used-letsencrypt-to-setup-ssl-for-asterisk/67145/6).
 
-[[!tag debian]] [[!tag asterisk]] [[!tag nzoss]] [[!tag letsencrypt]] [[!tag voipms]]
+## Firewall
+
+This originally appeared not to be necessary, but I found that I ran into a
+number of intermittent connection errors such as:
+
+    asterisk[1280841]: ERROR[1537920]: tcptls.c:553 in ast_tcptls_client_start: Unable to connect SIP socket to w.x.y.z:5061: Connection reset by peer
+
+and so I put the [official firewall
+recommendations](https://wiki.voip.ms/article/Firewall) in
+`/etc/network/iptables.up.rules`:
+
+    # SIP and RTP on TCP/UDP (servername.voip.ms)
+    -A INPUT -s w.x.y.z/32 -p tcp --dport 5061 -j ACCEPT
+    -A INPUT -s w.x.y.z/32 -p udp --sport 5004:5005 --dport 10001:20000 -j ACCEPT
+
+where `w.x.y.z` is the IP address of `servername.voip.ms` as returned by
+`dig +short servername.voip.ms`.
+
+[[!tag debian]] [[!tag asterisk]] [[!tag letsencrypt]] [[!tag voipms]]

creating tag page tags/ext4
diff --git a/tags/ext4.mdwn b/tags/ext4.mdwn
new file mode 100644
index 0000000..57c407f
--- /dev/null
+++ b/tags/ext4.mdwn
@@ -0,0 +1,4 @@
+[[!meta title="pages tagged ext4"]]
+
+[[!inline pages="tagged(ext4)" actions="no" archive="yes"
+feedshow=10]]

Add post about ext4 root partition corruption.
diff --git a/posts/repairing-corrupt-ext4-root-partition.mdwn b/posts/repairing-corrupt-ext4-root-partition.mdwn
new file mode 100644
index 0000000..00d3ef7
--- /dev/null
+++ b/posts/repairing-corrupt-ext4-root-partition.mdwn
@@ -0,0 +1,112 @@
+[[!meta title="Repairing a corrupt ext4 root partition"]]
+[[!meta date="2020-09-26T12:45:00.000-07:00"]]
+[[!meta license="[Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/)"]]
+
+I ran into filesystem corruption
+([ext4](https://en.wikipedia.org/wiki/Ext4)) on the root partition of my
+[backup server](https://feeding.cloud.geek.nz/posts/backing-up-to-gnubee2/)
+which caused it to go into read-only mode. Since it's the root partition,
+it's not possible to unmount it and repair it while it's running. Normally I
+would boot from an [Ubuntu live CD / USB
+stick](https://ubuntu.com/download/alternative-downloads), but in this case
+the machine is using the
+[`mipsel`](https://en.wikipedia.org/wiki/MIPS_architecture) architecture and
+so that's not an option.
+
+# Repair using a USB enclosure
+
+I had to pull the shutdown the server and then pull the SSD drive out. I
+then moved it to an external USB enclosure and connected it to my laptop.
+
+I started with an automatic filesystem repair:
+
+    fsck.ext4 -pf /dev/sde2
+
+which failed for some reason and so I moved to an interactive repair:
+
+    fsck.ext4 -f /dev/sde2
+
+Once all of the errors were fixed, I ran a full surface scan to update the
+list of bad blocks:
+
+    fsck.ext4 -c /dev/sde2
+
+Finally, I forced another check to make sure that everything was fixed at
+the filesystem level:
+
+    fsck.ext4 -f /dev/sde2
+
+# Fix invalid alternate GPT
+
+The other thing I noticed is this messge in my `dmesg` log:
+
+    scsi 8:0:0:0: Direct-Access     KINGSTON  SA400S37120     SBFK PQ: 0 ANSI: 6
+    sd 8:0:0:0: Attached scsi generic sg4 type 0
+    sd 8:0:0:0: [sde] 234441644 512-byte logical blocks: (120 GB/112 GiB)
+    sd 8:0:0:0: [sde] Write Protect is off
+    sd 8:0:0:0: [sde] Mode Sense: 31 00 00 00
+    sd 8:0:0:0: [sde] Write cache: enabled, read cache: enabled, doesn't support DPO or FUA
+    sd 8:0:0:0: [sde] Optimal transfer size 33553920 bytes
+    Alternate GPT is invalid, using primary GPT.
+     sde: sde1 sde2
+
+I therefore checked to see if the partition table looked fine and got the
+following:
+
+    $ fdisk -l /dev/sde
+    GPT PMBR size mismatch (234441643 != 234441647) will be corrected by write.
+    The backup GPT table is not on the end of the device. This problem will be corrected by write.
+    Disk /dev/sde: 111.8 GiB, 120034123776 bytes, 234441648 sectors
+    Disk model: KINGSTON SA400S3
+    Units: sectors of 1 * 512 = 512 bytes
+    Sector size (logical/physical): 512 bytes / 512 bytes
+    I/O size (minimum/optimal): 512 bytes / 512 bytes
+    Disklabel type: gpt
+    Disk identifier: 799CD830-526B-42CE-8EE7-8C94EF098D46
+    
+    Device       Start       End   Sectors   Size Type
+    /dev/sde1     2048   8390655   8388608     4G Linux swap
+    /dev/sde2  8390656 234441614 226050959 107.8G Linux filesystem
+
+It turns out that all I had to do, since only the backup / alternate GPT
+partition table was corrupt and the primary one was fine, was to re-write
+the partition table:
+
+    $ fdisk /dev/sde
+    
+    Welcome to fdisk (util-linux 2.33.1).
+    Changes will remain in memory only, until you decide to write them.
+    Be careful before using the write command.
+    
+    GPT PMBR size mismatch (234441643 != 234441647) will be corrected by write.
+    The backup GPT table is not on the end of the device. This problem will be corrected by write.
+    
+    Command (m for help): w
+    
+    The partition table has been altered.
+    Syncing disks.
+
+# Run SMART checks
+
+Since I still didn't know what caused the filesystem corruption in the first
+place, I decided to do one last check:
+[SMART](https://en.wikipedia.org/wiki/S.M.A.R.T.) errors.
+
+I couldn't do this via the USB enclosure since the SMART commands aren't
+forwarded to the drive and so I popped the drive back into the backup
+server and booted it up.
+
+First, I checked whether any SMART errors had been reported using
+[smartmontools](https://www.smartmontools.org/):
+
+    smartctl -a /dev/sda
+
+That didn't show any errors and so I kicked off an extended test:
+
+    smartctl -t long /dev/sda
+
+which ran for 30 minutes and then passed without any errors.
+
+The mystery remains unsolved.
+
+[[!tag gnubee]] [[!tag smart]] [[!tag ext4]] [[!tag debian]]

Create a new ext4 tag.
diff --git a/posts/encrypting-your-home-directory-using.mdwn b/posts/encrypting-your-home-directory-using.mdwn
index 9f2e21e..44e3f99 100644
--- a/posts/encrypting-your-home-directory-using.mdwn
+++ b/posts/encrypting-your-home-directory-using.mdwn
@@ -33,4 +33,4 @@ If you happen to have `/home` on a separate partition already (`/dev/sda5` in th
 
 That's it. Now to fully secure your laptop against theft, you should think about an [encrypted backup strategy](http://packages.debian.org/sid/duplicity) for your data...
 
-[[!tag debian]] [[!tag sysadmin]] [[!tag ubuntu]] [[!tag luks]]
+[[!tag debian]] [[!tag sysadmin]] [[!tag ubuntu]] [[!tag luks]] [[!tag ext4]]
diff --git a/posts/manually-expanding-raid1-array-ubuntu.mdwn b/posts/manually-expanding-raid1-array-ubuntu.mdwn
index 97712c6..58df32d 100644
--- a/posts/manually-expanding-raid1-array-ubuntu.mdwn
+++ b/posts/manually-expanding-raid1-array-ubuntu.mdwn
@@ -148,4 +148,4 @@ The last step was to regenerate the initramfs:
 before rebooting into something that looks exactly like the original RAID1
 array but with twice the size.
 
-[[!tag nzoss]] [[!tag sysadmin]] [[!tag debian]] [[!tag raid]] [[!tag ubuntu]] [[!tag luks]]
+[[!tag ext4]] [[!tag sysadmin]] [[!tag debian]] [[!tag raid]] [[!tag ubuntu]] [[!tag luks]]
diff --git a/posts/raid1-alternative-for-ssd-drives.mdwn b/posts/raid1-alternative-for-ssd-drives.mdwn
index 2176781..a29371b 100644
--- a/posts/raid1-alternative-for-ssd-drives.mdwn
+++ b/posts/raid1-alternative-for-ssd-drives.mdwn
@@ -56,4 +56,4 @@ Finally, after reading this [excellent LWN article](http://lwn.net/Articles/4084
   
 Is there anything else I should be doing to make sure I get the most out of my SSD?
 
-[[!tag grub]] [[!tag debian]] [[!tag sysadmin]] [[!tag ubuntu]] [[!tag nzoss]] [[!tag raid]]
+[[!tag grub]] [[!tag debian]] [[!tag sysadmin]] [[!tag ubuntu]] [[!tag raid]] [[!tag ext4]]
diff --git a/posts/setting-up-raid-on-existing.mdwn b/posts/setting-up-raid-on-existing.mdwn
index a1a40d5..e02a640 100644
--- a/posts/setting-up-raid-on-existing.mdwn
+++ b/posts/setting-up-raid-on-existing.mdwn
@@ -217,4 +217,4 @@ Something else you should seriously consider is to install the `smartmontools` p
 
 These checks, performed by the hard disk controllers directly, could warn you of imminent failures ahead of time. Personally, when I start seeing errors in the SMART log (`smartctl -a /dev/sda`), I order a new drive straight away.
 
-[[!tag grub]] [[!tag raid]] [[!tag debian]] [[!tag sysadmin]] [[!tag ubuntu]] [[!tag nzoss]]
+[[!tag grub]] [[!tag raid]] [[!tag debian]] [[!tag sysadmin]] [[!tag ubuntu]] [[!tag ext4]]
diff --git a/posts/two-tier-encryption-strategy-archiving.mdwn b/posts/two-tier-encryption-strategy-archiving.mdwn
index 7502669..7f68ae0 100644
--- a/posts/two-tier-encryption-strategy-archiving.mdwn
+++ b/posts/two-tier-encryption-strategy-archiving.mdwn
@@ -47,4 +47,4 @@ and:
 
     cryptmount -u archives
 
-[[!tag catalyst]] [[!tag debian]] [[!tag sysadmin]] [[!tag security]] [[!tag ubuntu]] [[!tag cryptmount]]
+[[!tag ext4]] [[!tag debian]] [[!tag sysadmin]] [[!tag security]] [[!tag ubuntu]] [[!tag cryptmount]]

Poor man's RAID-1 on the GnuBee.
diff --git a/posts/backing-up-to-gnubee2.mdwn b/posts/backing-up-to-gnubee2.mdwn
index 2226a34..3fcac7c 100644
--- a/posts/backing-up-to-gnubee2.mdwn
+++ b/posts/backing-up-to-gnubee2.mdwn
@@ -62,6 +62,28 @@ and added the following to `/etc/fstab`:
 
     /dev/md127 /mnt/data/ ext4 noatime,nodiratime 0 2
 
+### Keeping a copy of the root partition
+
+In order to survive a failing SSD drive, I could have bought a second SSD
+and gone for a
+[RAID-1](https://en.wikipedia.org/wiki/Standard_RAID_levels#RAID_1) setup.
+Instead, I went for a cheaper option, a [poor man's
+RAID-1](https://feeding.cloud.geek.nz/posts/poor-mans-raid1-between-ssd-and-hard-drive/),
+where I will have to reinstall the machine but it will be very quick and I
+won't lose any of my configuration.
+
+The way that it works is that I periodically sync the contents of the root
+partition onto the RAID-5 array using a cronjob in `/etc/cron.d/hdd-sync`:
+
+    0 10 * * *     root    /usr/local/sbin/ssd_root_backup
+
+which runs the `/usr/local/sbin/ssd_root_backup` script:
+
+    #!/bin/sh
+    nocache nice ionice -c3 rsync -aHx --delete --exclude=/dev/* --exclude=/proc/* --exclude=/sys/* --exclude=/tmp/* --exclude=/mnt/* --exclude=/lost+found/* --exclude=/media/* --exclude=/var/tmp/* /* /mnt/data/root/
+
+### Drive spin down
+
 To reduce unnecessary noise and reduce power consumption, I also installed
 [hdparm](https://sourceforge.net/projects/hdparm/):
 
@@ -86,6 +108,8 @@ and then reloaded the configuration:
 
      /usr/lib/pm-utils/power.d/95hdparm-apm resume
 
+### Monitoring drive health
+
 Finally I setup [smartmontools](https://www.smartmontools.org/) by putting
 the following in `/etc/smartd.conf`:
 

Remove superfluous words.
diff --git a/posts/npr-modem-setup-testing-linux.mdwn b/posts/npr-modem-setup-testing-linux.mdwn
index 8ef6ca2..c2b1318 100644
--- a/posts/npr-modem-setup-testing-linux.mdwn
+++ b/posts/npr-modem-setup-testing-linux.mdwn
@@ -76,7 +76,7 @@ and confirmed that they were able to successfully connect to each other:
 
 # Monitoring RF
 
-To monitor what is happening on the air and check and quickly determine
+To monitor what is happening on the air and quickly determine
 whether or not the modems are chatting, you can use a [software-defined
 radio](https://www.nooelec.com/store/sdr/sdr-receivers/nesdr/nesdr-mini.html)
 along with [gqrx](https://gqrx.dk/) with the following settings:

Small fixes to NPR post.
diff --git a/posts/npr-modem-setup-testing-linux.mdwn b/posts/npr-modem-setup-testing-linux.mdwn
index e9f4088..8ef6ca2 100644
--- a/posts/npr-modem-setup-testing-linux.mdwn
+++ b/posts/npr-modem-setup-testing-linux.mdwn
@@ -1,9 +1,9 @@
-[[!meta title="Setting and testing an NPR modem on Linux"]]
-[[!meta date="2020-09-17T23:20:00.000-07:00"]]
+[[!meta title="Setting up and testing an NPR modem on Linux"]]
+[[!meta date="2020-09-17T23:35:00.000-07:00"]]
 [[!meta license="[Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/)"]]
 
-After acquiring a [New Packet Radio
-modem](https://hackaday.io/project/164092-npr-new-packet-radio) on behalf of
+After acquiring a pair of [New Packet Radio
+modems](https://hackaday.io/project/164092-npr-new-packet-radio) on behalf of
 [VECTOR](https://vectorradio.ca), I set it up on my Linux machine and ran
 some basic tests to check whether it could achieve the advertised 500 kbps
 transfer rates, which are much higher than

creating tag page tags/iperf
diff --git a/tags/iperf.mdwn b/tags/iperf.mdwn
new file mode 100644
index 0000000..6ab9b02
--- /dev/null
+++ b/tags/iperf.mdwn
@@ -0,0 +1,4 @@
+[[!meta title="pages tagged iperf"]]
+
+[[!inline pages="tagged(iperf)" actions="no" archive="yes"
+feedshow=10]]

creating tag page tags/npr
diff --git a/tags/npr.mdwn b/tags/npr.mdwn
new file mode 100644
index 0000000..ac08df6
--- /dev/null
+++ b/tags/npr.mdwn
@@ -0,0 +1,4 @@
+[[!meta title="pages tagged npr"]]
+
+[[!inline pages="tagged(npr)" actions="no" archive="yes"
+feedshow=10]]

Add NPR setup post.
diff --git a/posts/npr-modem-setup-testing-linux.mdwn b/posts/npr-modem-setup-testing-linux.mdwn
new file mode 100644
index 0000000..e9f4088
--- /dev/null
+++ b/posts/npr-modem-setup-testing-linux.mdwn
@@ -0,0 +1,245 @@
+[[!meta title="Setting and testing an NPR modem on Linux"]]
+[[!meta date="2020-09-17T23:20:00.000-07:00"]]
+[[!meta license="[Creative Commons Attribution-ShareAlike 4.0 International License](http://creativecommons.org/licenses/by-sa/4.0/)"]]
+
+After acquiring a [New Packet Radio
+modem](https://hackaday.io/project/164092-npr-new-packet-radio) on behalf of
+[VECTOR](https://vectorradio.ca), I set it up on my Linux machine and ran
+some basic tests to check whether it could achieve the advertised 500 kbps
+transfer rates, which are much higher than
+[AX25](https://en.wikipedia.org/wiki/AX.25)) packet radio.
+
+The exact equipment I used was:
+
+- [NPR-70 v05 modems](https://elekitsorparts.com/product/npr-70-modem-by-f4hdk-new-packet-radio-over-70cm-band-amateur-radio-packet-radio)
+- [Bingfu Dual Band antennas](https://www.amazon.ca/gp/product/B07WPWK5JK/)
+- [Alinco DM-330MV power supply](https://www.radioworld.ca/ali-dm330mvt)
+
+![](/posts/npr-modem-setup-testing-linux/physical_setup.jpg)
+
+# Radio setup
+
+After connecting the modems to the power supply and their respective
+antennas, I connected both modems to my laptop via micro-USB cables and used
+[minicom](https://salsa.debian.org/minicom-team/minicom) to connect to their
+console on `/dev/ttyACM[01]`:
+
+    minicom -8 -b 921600 -D /dev/ttyACM0
+    minicom -8 -b 921600 -D /dev/ttyACM1
+
+To confirm that the firmware was the latest one, I used the following command:
+
+    ready> version
+    firmware: 2020_02_23
+    freq band: 70cm
+
+then I immediately turned off the radio:
+
+    radio off
+
+which can be verified with:
+
+    status
+
+Following the [British Columbia 70 cm band
+plan](http://bcarcc.org/440planA.pdf), I picked the following frequency,
+modulation (bandwidth of 360 kHz), and power (0.05 W):
+
+    set frequency 433.500
+    set modulation 22
+    set RF_power 7
+
+and then did the rest of the configuration for the master:
+
+    set callsign VA7GPL_0
+    set is_master yes
+    set DHCP_active no
+    set telnet_active no
+
+and the client:
+
+    set callsign VA7GPL_1
+    set is_master no
+    set DHCP_active yes
+    set telnet_active no
+
+and that was enough to get the two modems to talk to one another.
+
+On both of them, I ran the following:
+
+    save
+    reboot
+
+and confirmed that they were able to successfully connect to each other:
+
+    who
+
+# Monitoring RF
+
+To monitor what is happening on the air and check and quickly determine
+whether or not the modems are chatting, you can use a [software-defined
+radio](https://www.nooelec.com/store/sdr/sdr-receivers/nesdr/nesdr-mini.html)
+along with [gqrx](https://gqrx.dk/) with the following settings:
+
+    frequency: 433.500 MHz
+    filter width: user (80k)
+    filter shape: normal
+    mode: Raw I/Q
+
+I found it quite helpful to keep this running the whole time I was working
+with these modems. The background "keep alive" sounds are quite distinct
+from the heavy traffic sounds.
+
+# IP setup
+
+The radio bits out of the way, I turned to the networking configuration.
+
+On the master, I set the following so that I could connect the master to my
+home network (`192.168.1.0/24`) without conflicts: 
+
+    set def_route_active yes
+    set DNS_active no
+    set modem_IP 192.168.1.254
+    set IP_begin 192.168.1.225
+    set master_IP_size 29
+    set netmask 255.255.255.0
+
+(My router's DHCP server is configured to allocate dynamic IP addresses from
+`192.168.1.100` to `192.168.1.224`.)
+
+At this point, I connected my laptop to the client using a
+[CAT-5](https://en.wikipedia.org/wiki/Category_5_cable) network cable and
+the master to the ethernet switch, essentially following *Annex 5* of the
+[Advanced User
+Guide](https://cdn.hackaday.io/files/1640927020512128/NPR_advanced_guide_v2.14.pdf).
+
+My laptop got assigned IP address `192.168.1.225` and so I used another
+computer on the same network to ping my laptop via the NPR modems:
+
+    ping 192.168.1.225
+
+This gave me a round-trip time of around 150-250 ms.
+
+# Performance test
+
+Having successfully established an
+[IP](https://en.wikipedia.org/wiki/Internet_Protocol) connection between the
+two machines, I decided to run a quick test to measure the available
+bandwidth in an ideal setting (i.e. the two antennas very close to each
+other).
+
+On both computers, I installed [iperf](https://iperf.fr/):
+
+    apt install iperf
+
+and then setup the iperf server on my desktop computer:
+
+    sudo iptables -A INPUT -s 192.168.1.0/24 -p TCP --dport 5001 -j ACCEPT
+    sudo iptables -A INPUT -s 192.168.1.0/24 -u UDP --dport 5001 -j ACCEPT
+    iperf --server
+
+On the laptop, I set the MTU to `750` in NetworkManager:
+
+![](/posts/npr-modem-setup-testing-linux/mtu-750-networkmanager.png)
+
+and restarted the network.
+
+Then I created a new user account (`npr` with a uid of `1001`):
+
+    sudo adduser npr
+
+and made sure that only that account could access the network by running the
+following as `root`:
+
+    # Flush all chains.
+    iptables -F
+    
+    # Set defaults policies.
+    iptables -P INPUT DROP
+    iptables -P OUTPUT DROP
+    iptables -P FORWARD DROP
+    
+    # Don't block localhost and ICMP traffic.
+    iptables -A INPUT -i lo -j ACCEPT
+    iptables -A INPUT -p icmp -j ACCEPT
+    iptables -A OUTPUT -o lo -j ACCEPT
+    
+    # Don't re-evaluate already accepted connections.
+    iptables -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
+    iptables -A OUTPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
+    
+    # Allow connections to/from the test user.
+    iptables -A OUTPUT -m owner --uid-owner 1001 -m conntrack --ctstate NEW -j ACCEPT
+    
+    # Log anything that gets blocked.
+    iptables -A INPUT -j LOG
+    iptables -A OUTPUT -j LOG
+    iptables -A FORWARD -j LOG
+
+then I started the test as the `npr` user:
+
+    sudo -i -u npr
+    iperf --client 192.168.1.8
+
+# Results
+
+The results were as good as advertised both with modulation 22 (360 kHz
+bandwidth):
+
+    $ iperf --client 192.168.1.8 --time 30
+    ------------------------------------------------------------
+    Client connecting to 192.168.1.8, TCP port 5001
+    TCP window size: 85.0 KByte (default)
+    ------------------------------------------------------------
+    [  3] local 192.168.1.225 port 58462 connected with 192.168.1.8 port 5001

(Diff truncated)
Comment moderation
diff --git a/posts/recovering-from-unbootable-ubuntu-encrypted-lvm-root-partition/comment_10_88b2fb718e4fb1b9b1f2c4f6ff9b0128._comment b/posts/recovering-from-unbootable-ubuntu-encrypted-lvm-root-partition/comment_10_88b2fb718e4fb1b9b1f2c4f6ff9b0128._comment
new file mode 100644
index 0000000..9354a9c
--- /dev/null
+++ b/posts/recovering-from-unbootable-ubuntu-encrypted-lvm-root-partition/comment_10_88b2fb718e4fb1b9b1f2c4f6ff9b0128._comment
@@ -0,0 +1,13 @@
+[[!comment format=mdwn
+ ip="80.123.19.32"
+ claimedauthor="FlascheLeer"
+ subject="comment 10"
+ date="2020-09-08T09:48:42Z"
+ content="""
+This helped a lot. Thanks!
+For a younger Ubuntu (20.04), I also had to mount /sys:
+
+    mount --rbind /sys /mnt/sys/
+
+I didn't try the mount -o bind method with sys.
+"""]]

Link to the Debian bug report for user services.
diff --git a/posts/home-music-server-with-mpd.mdwn b/posts/home-music-server-with-mpd.mdwn
index 2682895..38fba01 100644
--- a/posts/home-music-server-with-mpd.mdwn
+++ b/posts/home-music-server-with-mpd.mdwn
@@ -29,8 +29,7 @@ then open `/etc/mpd.conf` and set these:
 
 Note that you can find the right sound device on your machine using the `aplay -L` command.
 
-Since this is a headless system setup, it may be necessary to disable the
-user service:
+Since this is a headless system setup, it may be necessary to [disable the user service](https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=959693):
 
     rm /etc/xdg/autostart/mpd.desktop
     systemctl --global disable mpd.service

Use the correct sound device in mpd.conf.
diff --git a/posts/home-music-server-with-mpd.mdwn b/posts/home-music-server-with-mpd.mdwn
index d46a7ea..2682895 100644
--- a/posts/home-music-server-with-mpd.mdwn
+++ b/posts/home-music-server-with-mpd.mdwn
@@ -23,10 +23,12 @@ then open `/etc/mpd.conf` and set these:
     audio_output {
        type       "alsa"
        name       "My ALSA Device"
-       device     "hw:0,0"
+       device     "hw:CARD=DAC,DEV=0"
        mixer_type "software"
     }
 
+Note that you can find the right sound device on your machine using the `aplay -L` command.
+
 Since this is a headless system setup, it may be necessary to disable the
 user service:
 

Remove zeroconf since it doesn't work with systemd sockets
Sep 06 11:40 : zeroconf: No global port, disabling zeroconf
diff --git a/posts/home-music-server-with-mpd.mdwn b/posts/home-music-server-with-mpd.mdwn
index cdac277..d46a7ea 100644
--- a/posts/home-music-server-with-mpd.mdwn
+++ b/posts/home-music-server-with-mpd.mdwn
@@ -18,7 +18,6 @@ then open `/etc/mpd.conf` and set these:
     music_directory    "/path/to/music/"
     bind_to_address    "0.0.0.0"
     bind_to_address    "/run/mpd/socket"
-    zeroconf_enabled   "yes"
     password           "Password1"
     
     audio_output {

Disable user service interfering with main mpd service.
diff --git a/posts/home-music-server-with-mpd.mdwn b/posts/home-music-server-with-mpd.mdwn
index 3f03efd..cdac277 100644
--- a/posts/home-music-server-with-mpd.mdwn
+++ b/posts/home-music-server-with-mpd.mdwn
@@ -28,6 +28,24 @@ then open `/etc/mpd.conf` and set these:
        mixer_type "software"
     }
 
+Since this is a headless system setup, it may be necessary to disable the
+user service:
+
+    rm /etc/xdg/autostart/mpd.desktop
+    systemctl --global disable mpd.service
+
+in order to prevent systemd from launching the mpd service whenever a user
+logs in, leading to error messages like:
+
+    systemd[324808]: mpd.socket: Failed to create listening socket ([::]:6600): Address already in use
+    systemd[324808]: mpd.socket: Failed to listen on sockets: Address already in use
+    systemd[324808]: mpd.socket: Failed with result 'resources'.
+    systemd[324808]: Failed to listen on mpd.socket.
+    mpd[324823]: exception: failed to open log file "/var/log/mpd/mpd.log" (config line 39): Permission denied
+    systemd[324808]: mpd.service: Main process exited, code=exited, status=1/FAILURE
+    systemd[324808]: mpd.service: Failed with result 'exit-code'.
+    systemd[324808]: Failed to start Music Player Daemon.
+
 Once all of that is in place, restart the mpd daemon:
 
     systemctl restart mpd.service

Simplify and fix the Apache configuration.
diff --git a/posts/home-music-server-with-mpd.mdwn b/posts/home-music-server-with-mpd.mdwn
index 949dd5a..3f03efd 100644
--- a/posts/home-music-server-with-mpd.mdwn
+++ b/posts/home-music-server-with-mpd.mdwn
@@ -72,22 +72,21 @@ from a local web server I have installed
 
     apt install apache2
 
-and configured it to serve the covers by putting the following in
-`/etc/apache2/conf-available/mpd.conf`:
+and configured it to serve the covers by putting the following in the
+default vhost section of `/etc/apache2/sites-available/000-default.conf`:
 
+    Alias /music /path/to/music
+    
     <Directory /path/to/music>
+        Options -MultiViews -Indexes
         AllowOverride None
-        Require all granted
+        Order allow,deny
+        allow from all
     </Directory>
 
-and then the following line in the default vhost section of
-`/etc/apache2/sites-available/000-default.conf`:
-
-    Alias /music /path/to/music
-
-Finally, I enabled the new configuration and restarted Apache:
+Finally, I enabled the new vhost and restarted Apache:
 
-    a2enconf mpd.conf
+    a2ensite 000-default
     systemctl restart apache2.service
 
 # Clients

Switch to alsa to simplify headless operation
diff --git a/posts/home-music-server-with-mpd.mdwn b/posts/home-music-server-with-mpd.mdwn
index 6f443bb..949dd5a 100644
--- a/posts/home-music-server-with-mpd.mdwn
+++ b/posts/home-music-server-with-mpd.mdwn
@@ -16,47 +16,21 @@ Start by installing the server and the client package:
 then open `/etc/mpd.conf` and set these:
 
     music_directory    "/path/to/music/"
-    bind_to_address    "192.168.1.2"
+    bind_to_address    "0.0.0.0"
     bind_to_address    "/run/mpd/socket"
     zeroconf_enabled   "yes"
     password           "Password1"
-
-before replacing the alsa output:
-
-    audio_output {
-       type    "alsa"
-       name    "My ALSA Device"
-    }
-
-with a pulseaudio one:
-
+    
     audio_output {
-       type    "pulse"
-       name    "Pulseaudio Output"
-       server  "127.0.0.1"
+       type       "alsa"
+       name       "My ALSA Device"
+       device     "hw:0,0"
+       mixer_type "software"
     }
 
-and exposing pulseaudio to localhost via `/etc/pulse/default.pa`:
-
-    ### Network access (may be configured with paprefs, so leave this commented
-    ### here if you plan to use paprefs)
-    load-module module-native-protocol-tcp auth-ip-acl=127.0.0.1
-
-In order for the automatic detection (zeroconf) of your music server
-to work, you need to [prevent systemd from creating the network
-socket](https://www.mail-archive.com/mpd-devel@musicpd.org/msg00239.html):
-
-    systemctl stop mpd.service
-    systemctl stop mpd.socket
-    systemctl disable mpd.socket
-
-otherwise you'll see this in `/var/log/mpd/mpd.log`:
-
-    zeroconf: No global port, disabling zeroconf
-
-Once all of that is in place, start the mpd daemon:
+Once all of that is in place, restart the mpd daemon:
 
-    systemctl start mpd.service
+    systemctl restart mpd.service
 
 and create an index of your music files:
 

Comment moderation
diff --git a/posts/programming-anytone-d878uv-on-linux-using-windows10-and-virtualbox/comment_8_c6382c5a5eb077a8992dbeffb9dc6f6e._comment b/posts/programming-anytone-d878uv-on-linux-using-windows10-and-virtualbox/comment_8_c6382c5a5eb077a8992dbeffb9dc6f6e._comment
new file mode 100644
index 0000000..7e08d41
--- /dev/null
+++ b/posts/programming-anytone-d878uv-on-linux-using-windows10-and-virtualbox/comment_8_c6382c5a5eb077a8992dbeffb9dc6f6e._comment
@@ -0,0 +1,14 @@
+[[!comment format=mdwn
+ username="francois@665656f0ba400877c9b12e8fbb086e45aa01f7c0"
+ nickname="francois"
+ subject="Re: Wine for Anytone 878"
+ date="2020-09-01T16:15:33Z"
+ content="""
+> If I understand you correctly, Wine does not let CPS read or write to the Anytone 878.
+
+I have not tried Wine so I can't comment on this.
+
+> Does that mean we need to purchase a Windows 10 license and run it from VirtualBox?
+
+You could probably use one of the [Windows 10 IE / Legacy Edge testing VMs](https://developer.microsoft.com/en-us/microsoft-edge/tools/vms/) that Microsoft offers for free for 90 days.
+"""]]

Comment moderation
diff --git a/posts/programming-anytone-d878uv-on-linux-using-windows10-and-virtualbox/comment_7_d9d686bb1c13a519639d76276da07451._comment b/posts/programming-anytone-d878uv-on-linux-using-windows10-and-virtualbox/comment_7_d9d686bb1c13a519639d76276da07451._comment
new file mode 100644
index 0000000..99d5d93
--- /dev/null
+++ b/posts/programming-anytone-d878uv-on-linux-using-windows10-and-virtualbox/comment_7_d9d686bb1c13a519639d76276da07451._comment
@@ -0,0 +1,9 @@
+[[!comment format=mdwn
+ ip="134.223.230.152"
+ claimedauthor="Glen Flint"
+ url="GlenFlint@aol.com"
+ subject="Wine for Anytone 878"
+ date="2020-09-01T14:53:09Z"
+ content="""
+If I understand you correctly, Wine does not let CPS read or write to the Anytone 878.  Does that mean we need to purchase a Windows 10 license and run it from VirtualBox?  There is a different version of CPS for Windows 7.  Does that work better with Wine?
+"""]]

Comment moderation
diff --git a/posts/restricting-outgoing-webapp-requests-using-squid-proxy/comment_1_66de753ab892687677eb8740d4913f74._comment b/posts/restricting-outgoing-webapp-requests-using-squid-proxy/comment_1_66de753ab892687677eb8740d4913f74._comment
new file mode 100644
index 0000000..82dee38
--- /dev/null
+++ b/posts/restricting-outgoing-webapp-requests-using-squid-proxy/comment_1_66de753ab892687677eb8740d4913f74._comment
@@ -0,0 +1,8 @@
+[[!comment format=mdwn
+ ip="167.123.240.150"
+ claimedauthor="Thrawn"
+ subject="How to minimise Squid overhead?"
+ date="2020-08-27T23:28:48Z"
+ content="""
+This type of filtering could be very useful for one of our applications, but there are concerns about the overhead of running an extra process on our servers, and I notice that Squid's FAQ says it uses memory fairly aggressively to improve caching. How would we configure it to discard all of the caching (and associated memory usage) and just do IP filtering?
+"""]]