summaryrefslogtreecommitdiff
path: root/www/posts/arch/2020-05-25/installing-arch-linux-on-a-thinkpad-x220/index.html
blob: 2139e523c3d60ecacca45380abdcbdfb3a170e5a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
<!doctype html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Installing Arch Linux on a Thinkpad X220</title>
        <link rel="shortcut icon" href="/images/favicon.png">
        <link rel="stylesheet" href="/css/site.css">
        <link href="/posts/atom.xml" type="application/atom+xml" rel="alternate" title="The Home of David T. Sadler - All Posts"/>
        <link href="/posts/arch/atom.xml" type="application/atom+xml" rel="alternate" title="The Home of David T. Sadler - All Posts About Arch"/>
        <link href="/posts/gemini/atom.xml" type="application/atom+xml" rel="alternate" title="The Home of David T. Sadler - All Posts About Gemini"/>
        <link href="/posts/jigsaw/atom.xml" type="application/atom+xml" rel="alternate" title="The Home of David T. Sadler - All Posts About Jigsaw"/>
        <link href="/posts/laravel/atom.xml" type="application/atom+xml" rel="alternate" title="The Home of David T. Sadler - All Posts About Laravel"/>
        <link href="/posts/linux/atom.xml" type="application/atom+xml" rel="alternate" title="The Home of David T. Sadler - All Posts About Linux"/>
        <link href="/posts/markdown/atom.xml" type="application/atom+xml" rel="alternate" title="The Home of David T. Sadler - All Posts About Markdown"/>
        <link href="/posts/netlify/atom.xml" type="application/atom+xml" rel="alternate" title="The Home of David T. Sadler - All Posts About Netlify"/>
        <link href="/posts/nextcloud/atom.xml" type="application/atom+xml" rel="alternate" title="The Home of David T. Sadler - All Posts About Nextcloud"/>
        <link href="/posts/php/atom.xml" type="application/atom+xml" rel="alternate" title="The Home of David T. Sadler - All Posts About PHP"/>
    </head>
    <body>
        <section><h1>Installing Arch Linux on a Thinkpad X220</h1><blockquote>Mon 25th May 2020 By David T. Sadler.</blockquote><h2>Introduction</h2><p>So I purchased a used Thinkpad X220 for about £90 on eBay and decided to install Arch Linux onto it. This guide is the steps that I took to achieve this.</p><p>Now when it comes to Linux there is always more than one way of doing things and this guide is just my preferred way. Feel free to follow it for your own installation, just keep in mind that you may have to change some of the steps to suit your own circumstances. Also there is every chance that the information presented here will be out of date so I recommend that you at least read the through the official installation guide for the most up-to-date information.</p><h2>Download the Arch Linux ISO Image</h2><p>The Arch Linux download page provides direct download and torrent links. You should also download the PGP signature to the same location and use the gpg command to verify that the ISO has not been compromised.</p><pre><code class="shell">$ gpg --keyserver-options auto-key-retrieve --verify archlinux-2020.05.01-x86_64.iso.sig</code></pre><h2>Create a Live USB of Arch Linux</h2><p>Flash the image to a USB drive using Etcher. Alternatively you can use the dd command. Just ensure that /path/to/archlinux.iso is to where you have downloaded the image and that /dev/sdx is your USB drive.</p><pre><code class="shell">dd bs=4M if=/path/to/archlinux.iso of=/dev/sdx status=progress &amp;&amp; sync</code></pre><h2>Boot the Live Environment</h2><p>I wanted to make sure that the Thinkpad was using UEFI as I would be using EFISTUB to load the Linux kernel as an EFI executable. This is done through the BIOS which can be gotten to by pressing the ThinkVantage button as the machine is booting before pressing F1 to get to the BIOS settings. From there navigate to Startup and changed the UEFI/Legacy Boot option to be UEFI Only. Press F10 to save and exit the BIOS and then power down the machine.</p><p>With the USB drive plugged in power the machine back on, all the while pressing F12 until the boot menu appears and select USB HDD: Mass Storage Device and wait for the installation image to boot. When prompted select Arch Linux archiso X86_64 UEFI CD where you will be take to the live environment&#039;s terminal. </p><h2>Set the Keyboard Layout</h2><p>The default console map is US which meant that for me pressing Shift+3 was displaying the hash symbol (#) instead of the pound symbol (£). So the UK keyboard layout needed to be loaded.</p><pre><code class="shell">$ loadkeys uk</code></pre><p>You can get a list of supported keyboard layouts if you need to load a different one.</p><pre><code class="shell">ls /usr/share/kbd/keymaps/**/*.map.gz</code></pre><h2>Verify the Boot Mode</h2><p>To verify that the Thinkpad has UEFI enabled check that the efivars directory exists.</p><pre><code class="shell">$ ls /sys/firmware/efi/efivars</code></pre><h2>Connect to the Internet</h2><p>Verify that the machine can connect to the internet with the ping command.</p><pre><code class="shell">$ ping -c3 davidtsadler.com</code></pre><p>Before booting the machine I plugged in an Ethernet cable that was connected directly to my home network&#039;s router. The installation environment detected the network connection and obtained an IP address via DHCP. </p><h2>Update the System Clock</h2><p>Ensure the system clock is correct.</p><pre><code class="shell">$ timedatectl set-ntp true</code></pre><h2>Partition the Disks</h2><p>Use the lsblk command to determine which disks and partitions exist on the system.</p><pre><code class="shell">$ lsblk

NAME   MAJ:MIN RM   SIZE RO TYPE MOUNTPOINT
loop0    7:0    0   535M  1 loop /run/archiso/sfs/airootfs
sda      8:0    0 298.1G  0 disk
sdb      8:16   1   7.4G  0 disk
├─sdb1   8:17   1   652M  0 part /run/archiso/bootmnt
└─sdb2   8:18   1    64M  0 part</code></pre><p>From the above output I could see that my hard drive was sda as sdb was the USB drive and loop0 could just be ignored.</p><p>I knew that I wanted to have an encrypted partition and make use of LVM on it so my disk layout would be.</p><pre><code class="shell">+-------------------+ +--------------------------------------------------------+
| Boot partition    | | Logical volume 1 | Logical volume 2 | Logical volume 3 |
|                   | |                  |                  |                  |
| /boot             | | /                | [SWAP]           | /home            |
|                   | |                  |                  |                  |
|                   | | 50G              | 16G              | 200G             |
|                   | |                  |                  |                  |
|                   | | /dev/vg0/root    | /dev/vg0/swap    | /dev/vg0/home    |
|                   | |_ _ _ _ _ _ _ _ _ |_ _ _ _ _ _ _ _ _ |_ _ _ _ _ _ _ _ _ |
|                   | |                                                        |
|                   | |              LUKS2 encrypted partition                 |
| /dev/sda1 (512MB) | |                    /dev/sda2                           |
+-------------------+ +--------------------------------------------------------+</code></pre><p>The hard drive would be split into two partitions. The first, sdb1 would be 512MB in size and mounted in the file system at /boot. This would be the EFI system partition. The reminder of the disk space would be given to the partition sda2 and encrypted using LUKS2. LVM would then be used to create the volume group vg0 that would be divided into three partitions as logical volumes.</p><ul><li>/dev/vg0/root 50G root partition.</li><li>/dev/vg0/swap 16G swap partition.</li><li>/dev/vg0/home 200G home partition.</li></ul><p>Use fdisk to create the partitions.</p><pre><code class="shell">$ fdisk /dev/sda</code></pre><p>Enter g to create a new empty GPT partition table</p><pre><code class="shell">Command (m for help): g
Created a new GPT disklabel (GUID: 6987D065-936E-1547-9F02-F78145025A96).</code></pre><p>Since this is a UEFI system there must be a EFI partition at the beginning of the disk. Enter n to add a new partition and enter 1 to assign it as the first partition. Use the default value for the first sector but enter +512M for the last sector.</p><pre><code class="shell">Command (m for help): n
Partition number (1-128, default 1): 1
First sector (2048-625142414, default 2048): 
Last sector, +/-sectors or +/-size{K,M,G,T,P} (2048-625142414, default 625142414): +512M

Created a new partition 1 of type &#039;Linux filesystem&#039; and of size 512 MiB.</code></pre><p>Enter t to change the partition type and enter 1 to make it an EFI System. You can also get a list of partition types by pressing L.</p><pre><code class="shell">Command (m for help): t
Selected partition 1
Partition type (type L to list all types): 1
Changed type of partition &#039;Linux filesystem&#039; to &#039;EFI System&#039;.</code></pre><p>To create the second partition enter n again to add another partition, and then enter 2 to assign it as the second partition. Use the default values for both first and last sectors to allocate the remainder of the drive.</p><pre><code class="shell">Command (m for help): n
Partition number (2-128, default 2): 2
First sector (1050624-625142414, default 1050624): 
Last sector, +/-sectors or +/-size{K,M,G,T,P} (1050624-625142414, default 625142414): 

Created a new partition 2 of type &#039;Linux filesystem&#039; and of size 297.6 GiB.</code></pre><p>Enter w to write the changes and quit.</p><pre><code class="shell">Command (m for help): w
The partition table has been altered.
Calling ioctl() to re-read partition table.
Syncing disks.</code></pre><p>Use lsblk to confirm that two partitions have been created.</p><pre><code class="shell">$ lsblk /dev/sda

NAME   MAJ:MIN RM   SIZE RO TYPE MOUNTPOINT
sda      8:0    0 298.1G  0 disk
├─sda1   8:1    0   512M  0 part
└─sda2   8:2    0 297.6G  0 part</code></pre><h2>LUKS</h2><p>Encrypt the second partition with the cryptsetup command. </p><pre><code class="shell">$ cryptsetup luksFormat /dev/sda2</code></pre><p>When prompted enter YES in capitals to overwrite any data that is currently on the partition. </p><pre><code class="shell">WARNING!
========
This will overwrite data on /dev/sda2 irrevocably.

Are you sure? (Type &#039;yes&#039; in capital letters): YES</code></pre><p>Enter and verify a passphrase. Whenever the machine is now booted you will be prompted to enter this passphrase in order for the partition to be decrypted.</p><pre><code class="shell">Enter passphrase for /dev/sda2: 
Verify passphrase: 
cryptsetup luksFormat /dev/sda2  17.01s user 1.05s system 105% cpu 17.106 total</code></pre><h2>LVM on LUKS</h2><p>Before setting up LVM decrypt the partition.</p><pre><code class="shell">$ cryptsetup open /dev/sda2 cryptlvm</code></pre><p>You will be prompted to enter the passphrase that you set up earlier.</p><pre><code class="shell">Enter passphrase for /dev/sda2: 
cryptsetup open /dev/sda2 cryptlvm  6.48s user 0.36s system 92% cpu 7.436 total</code></pre><p>Create a physical volume.</p><pre><code class="shell">$ pvcreate /dev/mapper/cryptlvm</code></pre><p>Create a volume group called vg0.</p><pre><code class="shell">$ vgcreate vg0 /dev/mapper/cryptlvm</code></pre><p>Create three logical volumes for the root, swap and home partitions.</p><pre><code class="shell">$ lvcreate -L 50G vg0 -n root
$ lvcreate -L 16G vg0 -n swap
$ lvcreate -L 200G vg0 -n home</code></pre><p>Make use of lsblk again to verify that LVM has been setup as expected.</p><pre><code class="shell">$ lsblk /dev/sda

NAME           MAJ:MIN RM   SIZE RO TYPE  MOUNTPOINT
sda              8:0    0 298.1G  0 disk  
├─sda1           8:1    0   512M  0 part  
└─sda2           8:2    0 297.6G  0 part  
  └─cryptlvm   254:0    0 297.6G  0 crypt 
    ├─vg0-root 254:1    0    50G  0 lvm   
    ├─vg0-swap 254:2    0    16G  0 lvm   
    └─vg0-home 254:3    0   200G  0 lvm</code></pre><h2>Format the Partitions</h2><p>Format the boot partition at /dev/sda1 with a FAT32 file system as the UEFI specification requires the usage of it.</p><pre><code class="shell">$ mkfs.fat -F32 /dev/sda1</code></pre><p>The root and home partitions can be formatted with ext4.</p><pre><code class="shell">$ mkfs.ext4 /dev/vg0/root
$ mkfs.ext4 /dev/vg0/home</code></pre><p>Initialise the swap partition.</p><pre><code class="shell">$ mkswap /dev/vg0/swap
$ swapon /dev/vg0/swap</code></pre><h2>Mount the File Systems</h2><p>Mount the root partition into /mnt.</p><pre><code class="shell">$ mount /dev/vg0/root /mnt</code></pre><p>Mount the boot partition into /mnt/boot. </p><pre><code class="shell">$ mkdir /mnt/boot
$ mount /dev/sda1 /mnt/boot</code></pre><p>Finally mount the home partition into /mnt/home.</p><pre><code class="shell">$ mkdir /mnt/home
$ mount /dev/vg0/home /mnt/home</code></pre><h2>Select the Mirrors</h2><p>All mirror servers defined in /etc/pacman.d/mirrorlist where done at the time the installation image was built. Since it&#039;s ideal to try and use servers that are close to your location you can rebuild the list using the rankmirrors utility. This is not included by default on the live environment so you will need to download it.</p><p>First sync the pacman repository.</p><pre><code class="shell">pacman -Syy</code></pre><p>Then download the pacmain-contrib package which contains the rankmirrors utility.</p><pre><code class="shell">$ pacman -S pacman-contrib</code></pre><p>The official Pacman Mirrorlist Generator can be used to get an up-to-date list of servers for your country. The below command obtains a list of UK servers that support https and pass it to rankmirrors to obtain the 5 fastest.</p><pre><code class="shell">$ curl -s &quot;https://www.archlinux.org/mirrorlist/?country=GB&amp;protocol=https&amp;use_mirror_status=on&quot; | sed -e &#039;s/^#Server/Server/&#039; -e &#039;/^#/d&#039; | rankmirrors -n 5 - &gt; /etc/pacman.d/mirrorlist</code></pre><h2>Install Essential Packages</h2><p>The pacstrap script is used to install the base package, Linux kernel and firmware.</p><pre><code class="shell">$ pacstrap /mnt base linux linux-firmware neovim wpa_supplicant dhcpcd cryptsetup lvm2 efibootmgr intel-ucode</code></pre><p>I also installed a few other packages that I knew I was going to need.</p><ul><li>neovim. Allows you to edit files instead of using nano.</li><li>wpa_supplicant. Provides tools for connecting to a WPA2 protected wireless network.</li><li>dhcpcd. Needed so that you machine can obtain an IP address from your home router via dhcp.</li><li>cryptsetup. Since the partition is encrypted this package is required in order for it to be decrypted during booting. </li><li>lvm2. Provides the LVM tools to manage the LVM partition.</li><li>efibootmgr. Needed to configure the system to boot via UEFI.</li><li>intel-ucode. Enables microcode updates during boot.</li></ul><h2>Fstab</h2><p>Create a fstab file on the new system.</p><pre><code class="shell">$ genfstab -U /mnt &gt;&gt; /mnt/etc/fstab</code></pre><h2>Chroot</h2><p>Use arch-chroot to enter the new system as the root user. From now on you will be configuring the new system.</p><pre><code class="shell">$ arch-chroot /mnt</code></pre><h2>Time Zone</h2><p>Setup the timezone. Replace Europe/London with your timezone.</p><pre><code class="shell">$ ln -sf /usr/share/zoneinfo/Europe/London /etc/localtime</code></pre><p>Update the hardware clock.</p><pre><code class="shell">$ hwclock --systohc</code></pre><h2>Localization</h2><p>Use nvim to edit /etc/locale.gen.</p><pre><code class="shell">$ nvim /etc/locale.gen</code></pre><p>Uncomment your preferred language. For me this meant en_GB.UTF-8 UTF-8. Save the file and exit before generating the locales.</p><pre><code class="shell">$ locale-gen</code></pre><p>Edit /etc/locale.conf.</p><pre><code class="shell">$ nvim /etc/locale.conf</code></pre><p>Add the below line. Replace en_GB.UTF-8 with the language that you chose earlier.</p><pre>LANG=en_GB.UTF-8</pre><p>If you used loadkeys earlier you will need to edit /etc/vconsole.conf and add your chosen keymap.</p><pre><code class="shell">$ nvim /etc/vconsole.conf</code></pre><p>For me this meant adding the UK keymap.</p><pre>KEYMAP=uk</pre><h2>Network Configuration</h2><p>Create the file /etc/hostname and add an entry to it. This hostname will be the name of the machine on your network. I tend to name by devices after characters from the book Howl&#039;s Moving Castle https://en.wikipedia.org/wiki/Howl%27s_Moving_Castle).</p><pre><code class="shell">$ echo suliman &gt; /etc/hostname</code></pre><p>You then need to edit the /etc/hosts file.</p><pre><code class="shell">$ nvim /etc/hosts</code></pre><p>Add the following lines to this file. Replace suliman with the hostname you set up earlier.</p><pre><code class="shell">127.0.0.1	localhost
::1		    localhost
127.0.0.1	suliman.localdomain suliman</code></pre><h2>Wireless</h2><p>Use the ip command to determine the name of the wireless network interface.</p><pre><code class="shell">$ip link

1: lo: &lt;LOOPBACK,UP,LOWER_UP&gt; mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: enp0s25: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
    link/ether f0:de:f1:86:e1:75 brd ff:ff:ff:ff:ff:ff
3: wwp0s29u1u4: &lt;BROADCAST,MULTICAST&gt; mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether 86:06:37:c4:9b:41 brd ff:ff:ff:ff:ff:ff
4: wlan0: &lt;BROADCAST,MULTICAST&gt; mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether 08:11:96:02:10:ac brd ff:ff:ff:ff:ff:ff</code></pre><p>Looking at the output of the ip command this is wlan0. This name however will not be the name of the interface once the installation has been completed. You see the Arch installation environment does not use predictable names for interfaces. This is due to it using iwd which is unable to cope with interface renaming and so it is disabled for wireless interfaces. When the system boots into the installed system predictable names for interfaces will be enabled and wlan0 will be assigned a different name.</p><p>In order to find out what name will be assigned use the udevadm command.</p><pre><code class="shell">$ udevadm test-builtin net_id /sys/class/net/wlan0

Load module index
Parsed configuration file /usr/lib/systemd/network/99-default.link
Parsed configuration file /usr/lib/systemd/network/80-iwd.link
Created link configuration context.
Using default interface naming scheme &#039;v245&#039;.
ID_NET_NAMING_SCHEME=v245
ID_NET_NAME_MAC=wlx0811960210ac
ID_OUI_FROM_DATABASE=Intel Corporate
ID_NET_NAME_PATH=wlp3s0
Unload module index
Unloaded link configuration context.</code></pre><p>What we are interested in is the value of ID_NET_NAME_PATH which is wlp3s0.</p><p>My wireless network is a WPA2 protected network with a hidden SSID. Since wpa_supplicant has been installed when running pacstrap it is possible to use wpa_passphrase to generate the configuration file that wpa_supplicant will use to connect to the wireless network. Replace &amp;lt;SSID&amp;gt; and &amp;lt;PASSWORD&amp;gt; with your details. Note that the name of the configuration file contains the name of the network interface wlp3s0. Replace this with the name of your network interface.</p><pre><code class="shell">$ wpa_passphrase &lt;SSID&gt; &lt;PASSWORD&gt; &gt; /etc/wpa_supplicant/wpa_supplicant-wlp3s0.conf</code></pre><p>If your wireless network uses a hidden SSID you will need to edit the configuration file.</p><pre><code class="shell">$ nvim /etc/wpa_supplicant/wpa_supplicant-wlp3s0.conf</code></pre><p>And add the below line. </p><pre><code class="shell">scan_ssid=1</code></pre><p>Make sure that wpa_supplicant starts at boot.</p><pre><code class="shell">$ systemctl enable wpa_supplicant@wlp3s0.service</code></pre><p>Have an IP address assigned via DHCP during booting.</p><pre><code class="shell">$ systemctl enable dhcpcd@wlp3s0.service</code></pre><h2>Initramfs</h2><p>You will need to rebuild the initial ramdisk and the current one is not aware that the filesystem will be encrypted. Before rebuilding it some configuration changes need to be made.</p><pre><code class="shell">$ nvim /etc/mkinitcpio.conf</code></pre><p>Locate the section where the HOOKS are configured and replace it with the line below.</p><pre>HOOKS=(base udev autodetect keyboard keymap modconf block encrypt lvm2 filesystems fsck)</pre><p>This ensures that the keyboard is present before the filesystem is detected so that you are enter the passphrase to decrypt the partition. It also ensures that the decryption is done before the LVM is handled.</p><p>Save the changes and exist before rebuilding with the mkinitcpio command. </p><pre><code class="shell">mkinitcpio -P</code></pre><h2>EFISTUB Booting and Microcode</h2><p>The Thinkpad X220 UEFI implementation allows an operating system to be booted without the need for an intermediate bootloader such as GRUB. It is possible to add a UEFI boot entry to the motherboard itself and have Arch booted directly.</p><p>Modifying the motherboard boot entries is done using efibootmgr. However usage of this command can be quite verbose so it is recommended to create a shell script instead. </p><pre><code class="shell">nvim /usr/local/sbin/mkefibootentry</code></pre><p>The shell script will call efibootmgr with the required arguments. </p><pre>#!/bin/sh

# Determine the UUID of the partition that is encrypted
PARTUUID=`blkid /dev/sda2 -s PARTUUID -o value`

efibootmgr \
  --disk /dev/sda --part 1 \
  --create --label &quot;Arch Linux&quot; \
  --loader /vmlinuz-linux \
  --unicode &#039;cryptdevice=PARTUUID=&#039;$PARTUUID&#039;:cryptlvm root=/dev/vg0/root rw initrd=\intel-ucode.img initrd=\initramfs-linux.img&#039; \
  --verbose </pre><p>The --unicode argument is where the kernel parameters are specified. This tells the system that the partition identified by PARTUUID is encrypted and that the root filesystem to mount is the logical volume called root that is part of the volume group vg0. The microcode is also loaded with initrd=\intel-ucode.img.</p><p>Make this script executable.</p><pre><code class="shell">chmod u+x /usr/local/sbin/mkefibootentry</code></pre><p>Run the script to add to the motherboard boot entries.</p><pre><code class="shell">$ mkefibootentry</code></pre><h2>Root Password</h2><p>Create a secure password for the root user.</p><pre><code class="shell">$ passwd</code></pre><h2>Reboot</h2><p>Return to the Arch live installation environment.</p><pre><code class="shell">$ exit</code></pre><p>Unmount the partitions.</p><pre><code class="shell">$ umount -R /mnt</code></pre><p>Restart the machine with reboot. Remember to remove any installation media such as a USB drive.</p><pre><code class="shell">$ reboot</code></pre><p>Provided nothing has gone wrong your machine will boot into a fresh installation of Arch Linux. Don&#039;t forget that during the boot you will be prompted to enter the passphrase to decrypt the system partition. </p><p>Following this guide will leave you with a very minimal system where you can login as the root user. From this point how you configure the system is up to you as it will be very different to how I configure my own. If you interested in seeing how I do it then see my other posts on the subject. </p><h3>Links</h3><a href="https://www.archlinux.org/Installation_guide/">Offical Arch Installation Guide.</a><a href="https://www.archlinux.org/download/">Arch Linux Download Page.</a><a href="https://www.balena.io/etcher/">Etcher.</a><a href="https://www.archlinux.org/mirrorlist/">Pacman Mirrorlist Generator.</a><a href="https://en.wikipedia.org/wiki/Howl%27s_Moving_Castle/">Wikipedia Entry for Howl&#039;s Moving Castle.</a><a href="/posts/arch/">Arch - Read More Posts.</a><p>I don&#039;t have comments as I don&#039;t want to manage them. You can however contact me at the below address if you want to.</p><a href="mailto:david@davidtsadler.com">Email david@davidtsadler.com</a><h3>License</h3><a href="https://creativecommons.org/licenses/by-sa/4.0/">The contents of this site is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.</a><p>Copyright © 2021 David T. Sadler.</p><a href="/">Return to Homepage.</a></section>
        <script defer src="/js/highlight.min.js"></script>
        <script defer src="/js/site.js"></script>
    </body>
</html>