<?xml version="1.0" encoding="utf-8" ?>
<?xml-stylesheet type="text/xsl" href="https://codeofconnor.com/xml/base.min.xml" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Code of Connor</title>
    <link>https://codeofconnor.com/</link>
    <description>Recent content on Code of Connor</description>
    <generator>Hugo</generator>
    <language>en-us</language>
    <lastBuildDate>Wed, 04 Mar 2026 17:53:13 -0800</lastBuildDate>
    <atom:link href="https://codeofconnor.com/feed.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>go-nbd release 0.3.0</title>
      <link>https://codeofconnor.com/go-nbd-0.3.0/</link>
      <pubDate>Wed, 04 Mar 2026 17:53:13 -0800</pubDate>
      <guid>https://codeofconnor.com/go-nbd-0.3.0/</guid>
      <description>&lt;p&gt;I&amp;rsquo;m happy to announce that a new release of the github.com/digitalocean/go-nbd&#xA;module, version 0.3.0, is available here:&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://github.com/digitalocean/go-nbd/releases/tag/v0.3.0&#34;&gt;https://github.com/digitalocean/go-nbd/releases/tag/v0.3.0&lt;/a&gt;&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Getting Started With Kubevirt</title>
      <link>https://codeofconnor.com/getting-started-with-kubevirt/</link>
      <pubDate>Mon, 25 Aug 2025 18:55:40 -0700</pubDate>
      <guid>https://codeofconnor.com/getting-started-with-kubevirt/</guid>
      <description>&lt;p&gt;When I &lt;a href=&#34;https://codeofconnor.com/my-new-virtualization-homelab&#34;&gt;first got into homelab&lt;/a&gt; as a hobby, I had a single server.&#xA;Of course, that&amp;rsquo;s how it usually starts for those of us in the homelab community; but for most of us, it doesn&amp;rsquo;t end there.&#xA;I know this, because I am most of us.&lt;/p&gt;&#xA;&lt;p&gt;Instead of a single server, my homelab now consists of 1-3 Minisforum UM480XT mini PCs.&#xA;I say &amp;ldquo;1-3&amp;rdquo; because I switch things up a lot depending on my mood and what I feel like maintaining.&lt;/p&gt;&#xA;&lt;p&gt;My interests eventually led me to learning about containerization and orchestration, the latter being an essential concept in providing highly available infrastructure for applications in spite of hardware failure.&lt;/p&gt;&#xA;&lt;p&gt;I opted to use my homelab to get hands on experience with the orchestration platform I wanted to learn, Kubernetes.&#xA;Maybe you&amp;rsquo;ve already heard of it.&lt;/p&gt;&#xA;&lt;p&gt;Almost everything I currently run in my homelab is well-suited to containerization and is a good fit for deploying with Kubernetes.&#xA;There are a couple of exceptions to this.&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;My managed switches are UniFi switches, so I run the UniFi controller application on the network management VLAN.&#xA;I use &lt;a href=&#34;https://community.ui.com/questions/UniFi-Installation-Scripts-or-UniFi-Easy-Update-Script-or-UniFi-Lets-Encrypt-or-UniFi-Easy-Encrypt-/ccbc7530-dd61-40a7-82ec-22b17f027776&#34;&gt;Glenn R&amp;rsquo;s UniFi installation scripts&lt;/a&gt; for this, since the UniFi controller application is all packaged up for Ubuntu.&lt;/li&gt;&#xA;&lt;li&gt;I occasionally spin up VMs for the traditional VPS experience, for learning in a sandbox, experimenting with operating systems, or for running stateful components like databases (or anything that comes packaged &lt;em&gt;with&lt;/em&gt; a database.)&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;Those two exceptions don&amp;rsquo;t make up the bulk of my software deployments, so I&amp;rsquo;d prefer to optimize for the common case which is deploying stateless applications on Kubernetes.&#xA;To that end, I&amp;rsquo;ve chosen to deploy Kubernetes on one of my mini PCs, and I figured I&amp;rsquo;d bring the virtual machines along for the ride in case I settle on Kubernetes for the longer term in my homelab.&lt;/p&gt;&#xA;&lt;p&gt;Up until now, I&amp;rsquo;ve relied on the traditional Linux KVM virtualization stack, which is &lt;a href=&#34;https://linux-kvm.org/page/Main_Page&#34;&gt;KVM&lt;/a&gt;, &lt;a href=&#34;https://www.qemu.org/&#34;&gt;QEMU&lt;/a&gt;, &lt;a href=&#34;https://libvirt.org/&#34;&gt;libvirt&lt;/a&gt;, and &lt;a href=&#34;https://www.virt-tools.org/&#34;&gt;Virt Tools&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Luckily, I don&amp;rsquo;t have to leave it behind! &lt;a href=&#34;https://kubevirt.io/&#34;&gt;KubeVirt&lt;/a&gt; bridges the traditional Linux KVM stack to an orchestration-based paradigm using Kubernetes.&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;h2 id=&#34;requirements&#34;&gt;&#xA;  &lt;a class=&#34;Heading-link u-clickable&#34; href=&#34;https://codeofconnor.com/getting-started-with-kubevirt/#requirements&#34;&gt;Requirements&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;Virtual machines need to be on their designated VLANs (especially the UniFi controller, as mentioned above.)&lt;/li&gt;&#xA;&lt;li&gt;There shouldn&amp;rsquo;t be a network stack inbetween the virtual machine and its storage devices.&#xA;I don&amp;rsquo;t want the CPU, memory, or network overhead of highly-available CSI implementations and if any migrations need to happen, offline migrations are perfectly acceptable (KubeVirt only supports live migration if the PersistentVolume&amp;rsquo;s access mode is ReadWriteMany, which local NVMe storage won&amp;rsquo;t be.)&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;hr&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;h2 id=&#34;host-networking&#34;&gt;&#xA;  &lt;a class=&#34;Heading-link u-clickable&#34; href=&#34;https://codeofconnor.com/getting-started-with-kubevirt/#host-networking&#34;&gt;Host networking&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;To start, I did a fresh installation of Ubuntu 24.04 with LVM on one of my idle mini PCs.&lt;/p&gt;&#xA;&lt;p&gt;For the VLAN requirement, let&amp;rsquo;s start with setting up networking on the host.&#xA;I always do this step first because I don&amp;rsquo;t have a remote management interface for these mini PCs and messing up the networking over SSH means I have to get up and plug the computer into a monitor and keyboard anyway.&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# /etc/netplan/net.yaml&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;network&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;version&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;ethernets&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;eno1&lt;/span&gt;: {}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;vlans&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;eno1.10&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;id&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;link&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;eno1&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;bridges&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;br1&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;interfaces&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - &lt;span style=&#34;color:#ae81ff&#34;&gt;eno1&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;parameters&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;stp&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;forward-delay&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;br10&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;interfaces&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - &lt;span style=&#34;color:#ae81ff&#34;&gt;eno1.10&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;parameters&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;stp&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;forward-delay&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;addresses&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - &lt;span style=&#34;color:#ae81ff&#34;&gt;192.168.88.147&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;/24&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;routes&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - &lt;span style=&#34;color:#f92672&#34;&gt;to&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;default&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f92672&#34;&gt;via&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;192.168.88.1&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;nameservers&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;addresses&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          - &lt;span style=&#34;color:#ae81ff&#34;&gt;192.168.88.1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;This is fairly straightforward.&#xA;&amp;ldquo;vlan1&amp;rdquo; is my network management VLAN.&#xA;&amp;ldquo;vlan10&amp;rdquo; is my regular LAN.&#xA;br1 is only there for the UniFi controller VM to be on the network management VLAN, which is the untagged, default VLAN.&lt;/p&gt;&#xA;&lt;p&gt;At this point, I was able to do the rest of the setup from the comfort of my couch over SSH.&lt;/p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;h2 id=&#34;host-storage&#34;&gt;&#xA;  &lt;a class=&#34;Heading-link u-clickable&#34; href=&#34;https://codeofconnor.com/getting-started-with-kubevirt/#host-storage&#34;&gt;Host storage&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Now for the storage requirement.&#xA;LVM doesn&amp;rsquo;t consume the entire VG for the initial installation of Ubuntu, so it offers a lot of flexibility for what I want to do with the rest of the space even though I just have the single NVMe drive installed.&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ sudo lvcreate --type thin-pool -L 250G ubuntu-vg -n workload&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;It&amp;rsquo;s not necessary to create an LV for each base image, especially if you use the &lt;a href=&#34;https://kubevirt.io/user-guide/storage/containerized_data_importer/&#34;&gt;KubeVirt Containerized Data Importer&lt;/a&gt;, but I&amp;rsquo;m not currently using it.&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ sudo lvcreate -V 5G -T ubuntu-vg/workload -n ubuntu-24.04_amd64&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ curl -LkO https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ sudo qemu-img convert -O raw noble-server-cloudimg-amd64.img /dev/ubuntu-vg/ubuntu-24.04_amd64&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;The &lt;code&gt;qemu-img&lt;/code&gt; step is just because Ubuntu offers the image as a QCOW2 image, but the LV is a raw block device and so QCOW2 won&amp;rsquo;t work here.&lt;/p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;h2 id=&#34;kubernetes&#34;&gt;&#xA;  &lt;a class=&#34;Heading-link u-clickable&#34; href=&#34;https://codeofconnor.com/getting-started-with-kubevirt/#kubernetes&#34;&gt;Kubernetes&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;At this point, I&amp;rsquo;m ready to install Kubernetes.&#xA;I&amp;rsquo;ll be installing &lt;a href=&#34;https://k3s.io/&#34;&gt;K3s&lt;/a&gt;, which is a lightweight Kubernetes distribution.&#xA;I might move onto something like kubeadm or something more manual in the future, but I was excited to get started on this project so K3s it is!&lt;/p&gt;&#xA;&lt;p&gt;I won&amp;rsquo;t be overly opinionated here, the only exception is disabling the local path provisioner and passing an argument to the Kubelet so that it&amp;rsquo;s possible to deploy privileged pods (which is necessary for the KubeVirt pods to have access to &lt;code&gt;/dev/kvm&lt;/code&gt; for hardware accelerated virtualization.)&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# /etc/rancher/k3s/config.yaml&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;---&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;write-kubeconfig-mode&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;0644&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;tls-san&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;metalk8s.local.connorkuehl.net&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;disable&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#ae81ff&#34;&gt;local-storage&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;kubelet-arg&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;allow-privileged=true&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;node-ip&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;192.168.88.147&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;bind-address&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;192.168.88.147&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;cluster-init&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;Note that the mode I chose for the &lt;code&gt;write-kubeconfig-mode&lt;/code&gt; is overly permissive and is not acceptable for production.&lt;/p&gt;&#xA;&lt;p&gt;I chose to use &lt;a href=&#34;https://github.com/k8snetworkplumbingwg/multus-cni&#34;&gt;Multus&lt;/a&gt; to connect the virtual machine pods to their respective bridge device on the host.&lt;/p&gt;&#xA;&lt;p&gt;Something I really like about K3s is that if you lay down Kubernetes manifests under a well-known directory, a K3s controller will apply them.&#xA;Even Helm charts:&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# /var/lib/rancher/k3s/server/manifests/multus-cni.yaml&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;---&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;apiVersion&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;helm.cattle.io/v1&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;kind&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;HelmChart&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;metadata&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;multus&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;namespace&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;kube-system&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;spec&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;repo&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;https://rke2-charts.rancher.io&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;chart&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;rke2-multus&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;targetNamespace&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;kube-system&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;valuesContent&lt;/span&gt;: |-&lt;span style=&#34;color:#e6db74&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    config:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;      fullnameOverride: multus&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;      cni_conf:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        confDir: /var/lib/rancher/k3s/agent/etc/cni/net.d&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        binDir: /var/lib/rancher/k3s/data/cni/&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        kubeconfig: /var/lib/rancher/k3s/agent/etc/cni/net.d/multus.d/multus.kubeconfig&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        multusAutoconfigDir: /var/lib/rancher/k3s/agent/etc/cni/net.d&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;With Multus installed, let&amp;rsquo;s teach it about the host bridges:&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# /var/lib/rancher/k3s/server/manifests/vlan1.yaml&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;---&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;apiVersion&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;k8s.cni.cncf.io/v1&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;kind&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;NetworkAttachmentDefinition&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;metadata&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;vlan1&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;spec&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;config&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;{&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    &amp;#34;cniVersion&amp;#34;: &amp;#34;0.3.1&amp;#34;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    &amp;#34;type&amp;#34;: &amp;#34;bridge&amp;#34;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    &amp;#34;bridge&amp;#34;: &amp;#34;br1&amp;#34;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    &amp;#34;ipam&amp;#34;: {}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  }&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&#xA;  &#xA;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# /var/lib/rancher/k3s/server/manifests/vlan10.yaml&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;---&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;apiVersion&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;k8s.cni.cncf.io/v1&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;kind&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;NetworkAttachmentDefinition&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;metadata&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;vlan10&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;spec&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;config&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;{&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    &amp;#34;cniVersion&amp;#34;: &amp;#34;0.3.1&amp;#34;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    &amp;#34;type&amp;#34;: &amp;#34;bridge&amp;#34;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    &amp;#34;bridge&amp;#34;: &amp;#34;br10&amp;#34;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    &amp;#34;ipam&amp;#34;: {}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  }&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;Note 1: on a multiple node setup, this will not work unless the host networking is configured identically.&#xA;For example, if br10 bridges to VLAN 10 on node A, but VLAN 11 (or doesn&amp;rsquo;t exist) on node B, that would be a problem.&lt;/p&gt;&#xA;&lt;p&gt;Note 2: the IPAM block is disabled because I will only use these NetworkAttachmentDefinitions with KubeVirt VMs whose guest operating systems will DHCP to get an IP address from my router.&lt;/p&gt;&#xA;&lt;p&gt;Alright, what about the LVM thin pool I set up?&lt;/p&gt;&#xA;&lt;p&gt;I chose to use &lt;a href=&#34;https://github.com/kubernetes-sigs/sig-storage-local-static-provisioner&#34;&gt;local-static-provisioner&lt;/a&gt; to expose the LVs that I precreate on the node automatically.&#xA;All I have to do is create the LV and then drop a symlink to it in a discovery directory and the controller will automatically create the PersistentVolume object in the kube-apiserver for me.&#xA;The alternative would be writing a bunch of YAML like this after setting up the LV:&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;apiVersion&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;v1&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;kind&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;PersistentVolume&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;metadata&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;local-pv-9c25bcdb&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;spec&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;accessModes&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#ae81ff&#34;&gt;ReadWriteOnce&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;capacity&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;storage&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;10Gi&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;local&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;path&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;/mnt/disks/nvme/hellokubevirt&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;persistentVolumeReclaimPolicy&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;Retain&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;storageClassName&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;local-nvme&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;volumeMode&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;Block&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;I&amp;rsquo;d rather not do so much typing.&lt;/p&gt;&#xA;&lt;p&gt;Let&amp;rsquo;s set up the discovery directory:&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ sudo mkdir -p /mnt/disks/nvme&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;I&amp;rsquo;ll install local-static-provisioner with Helm, so here are the values for the chart:&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# local-static-provisioner.values.yaml&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;classes&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;local-nvme&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;hostDir&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;/mnt/disks/nvme&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;volumeMode&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;Block&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;storageClass&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;reclaimPolicy&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;Retain&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&#xA;  &#xA;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ helm template &lt;span style=&#34;color:#ae81ff&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;--debug local-static-provisioner/local-static-provisioner \&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;--version v2.8.0 \&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;--namespace local-static-provisioner \&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;-f local-static-provisioner.values.yaml | sudo tee /var/lib/rancher/k3s/server/manifests/local-static-provisioner.generated.yaml&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;For some reason, I couldn&amp;rsquo;t get this working as a HelmChart Addon like I did with Multus above.&lt;/p&gt;&#xA;&lt;p&gt;Lastly, I followed the &lt;a href=&#34;https://kubevirt.io/user-guide/cluster_admin/installation/&#34;&gt;KubeVirt installation guide&lt;/a&gt;.&lt;/p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;h2 id=&#34;creating-a-test-vm&#34;&gt;&#xA;  &lt;a class=&#34;Heading-link u-clickable&#34; href=&#34;https://codeofconnor.com/getting-started-with-kubevirt/#creating-a-test-vm&#34;&gt;Creating a test VM&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;First, I&amp;rsquo;ll create a copy of the base image:&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ sudo lvcreate –V 10G -T ubuntu-vg/workload -n hellokubevirt&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ sudo qemu-img convert -O raw /dev/ubuntu-vg/ubuntu-24.04_amd64 /dev/ubuntu-vg/hellokubevirt&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;And expose it to the &lt;code&gt;local-static-provisioner&lt;/code&gt;&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ sudo ln -s /dev/ubuntu-vg/hellokubevirt /mnt/disks/nvme/&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;And finally&amp;hellip; a VM.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;ll concede it&amp;rsquo;s a bit of a shock in its final form all at once, but I&amp;rsquo;m too lazy to type up the progression that resulted from running &lt;code&gt;kubectl explain ...&lt;/code&gt; for just about everything.&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# hellokubevirt.yaml&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;---&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;apiVersion&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;v1&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;kind&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;PersistentVolumeClaim&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;metadata&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;hellokubevirt&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;namespace&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;default&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;spec&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;volumeName&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;local-pv-9c25bcdb&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;volumeMode&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;Block&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;accessModes&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    - &lt;span style=&#34;color:#ae81ff&#34;&gt;ReadWriteOnce&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;storageClassName&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;local-nvme&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;resources&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;requests&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;storage&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;10G&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;---&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;apiVersion&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;kubevirt.io/v1&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;kind&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;VirtualMachine&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;metadata&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;hellokubevirt&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;namespace&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;default&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;spec&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;runStrategy&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;Always&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;template&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;spec&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;domain&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;resources&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f92672&#34;&gt;requests&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;memory&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;1024M&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;cpu&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f92672&#34;&gt;cores&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f92672&#34;&gt;devices&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f92672&#34;&gt;disks&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            - &lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;disk0&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              &lt;span style=&#34;color:#f92672&#34;&gt;disk&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#f92672&#34;&gt;bus&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;virtio&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            - &lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;cloudinitdisk&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              &lt;span style=&#34;color:#f92672&#34;&gt;disk&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#f92672&#34;&gt;bus&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;virtio&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f92672&#34;&gt;interfaces&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            - &lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;net0&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              &lt;span style=&#34;color:#f92672&#34;&gt;bridge&lt;/span&gt;: {}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;networks&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - &lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;net0&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f92672&#34;&gt;multus&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;networkName&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;default/vlan10&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;volumes&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - &lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;disk0&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f92672&#34;&gt;persistentVolumeClaim&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;claimName&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;hellokubevirt&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - &lt;span style=&#34;color:#f92672&#34;&gt;name&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;cloudinitdisk&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f92672&#34;&gt;cloudInitNoCloud&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f92672&#34;&gt;userData&lt;/span&gt;: |-&lt;span style=&#34;color:#e6db74&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;              #cloud-config&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;              hostname: hellokubevirt&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;              users:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;              - name: root&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;                ssh_import_id: [&amp;#39;gh:connorkuehl&amp;#39;]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&#xA;  &#xA;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ kubectl create -f hellokubevirt.yaml&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ virtctl console hellokubevirt&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;# ...&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;# Output omitted &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; brevity.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[  OK  ] Reached target cloud-init.target - Cloud-init target.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Ubuntu 24.04.3 LTS hellokubevirt ttyS0&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;hellokubevirt login:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;h2 id=&#34;one-last-wrinkle&#34;&gt;&#xA;  &lt;a class=&#34;Heading-link u-clickable&#34; href=&#34;https://codeofconnor.com/getting-started-with-kubevirt/#one-last-wrinkle&#34;&gt;One last wrinkle&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;If I stop then start the VM, networking breaks.&#xA;Astute readers might have noticed I did not specify a MAC address in the VirtualMachine spec.&#xA;Each time I stop then start the VM, KubeVirt generates a new VMI from the VirtualMachine spec, including a MAC address if one was omitted.&#xA;This makes netplan very upset in the guest OS.&lt;/p&gt;&#xA;&lt;p&gt;To fix, I just include a MAC address (or use the one that was generated first):&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ kubectl get vmi hellokubevirt -o&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;jsonpath&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;{.spec.domain.devices.interfaces[0].macAddress}&amp;#39;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;b6:12:29:88:50:59&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ kubectl edit vm hellokubevirt&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;         interfaces:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;         - bridge: {}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;           macAddress: b6:12:29:88:50:59&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;           name: net0&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;That&amp;rsquo;s sufficient for me.&lt;/p&gt;&#xA;&lt;p&gt;I did find an alternative workaround on &lt;a href=&#34;https://xeiaso.net/notes/2024/kubevirt-ubuntu-networking/&#34;&gt;Xe Iaso&amp;rsquo;s blog post&lt;/a&gt;, but I chose not to use it because I need a stable MAC address anyway as I prefer to configure static DHCP leases at my router.&lt;/p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;h2 id=&#34;conclusion&#34;&gt;&#xA;  &lt;a class=&#34;Heading-link u-clickable&#34; href=&#34;https://codeofconnor.com/getting-started-with-kubevirt/#conclusion&#34;&gt;Conclusion&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Overall, I&amp;rsquo;m quite pleased with how easy it was to get started with KubeVirt.&lt;/p&gt;&#xA;&lt;p&gt;Will I stick with it? Only time will tell. I do miss &lt;code&gt;virt-install&lt;/code&gt; and I&amp;rsquo;m a big fan of how simple plain old Linux with libvirt is.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Effortless Static Sites With Terraform and Caddy</title>
      <link>https://codeofconnor.com/effortless-static-sites-with-terraform-and-caddy/</link>
      <pubDate>Sat, 22 Oct 2022 19:29:32 -0500</pubDate>
      <guid>https://codeofconnor.com/effortless-static-sites-with-terraform-and-caddy/</guid>
      <description>&lt;p&gt;My blog has moved around several times since its inception. I first&#xA;started it in college using Automattic&amp;rsquo;s hosted wordpress.com services,&#xA;then I eventually moved it to a $5 VPS where I self-hosted my own copy&#xA;of WordPress. After some time of that, its final form has taken the&#xA;shape of a Hugo-generated static site on a $5 Linux VPS.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;ll concede that it doesn&amp;rsquo;t take a substantial amount of toil to deploy&#xA;a server and configure it to run a basic WordPress blog or static HTML&#xA;pages for that matter. I&amp;rsquo;ve developed some sentimental feelings toward&#xA;this blog though, and taking the proper steps to ensure that it is&#xA;properly backed up or easily restored in case of tragedy adds a bit more&#xA;complexity.&lt;/p&gt;&#xA;&lt;p&gt;There are a couple of well-understood solutions that constitute a valid&#xA;backup/restore/recovery plan.&lt;/p&gt;&#xA;&lt;p&gt;The first would be to simply implement a traditional backup solution.&#xA;Many cloud providers, including the one I use for my VPS, have&#xA;disk-based snapshot and backup solutions. I could also integrate a&#xA;file-based backup solution from inside the VPS where the site could be&#xA;backed up to other locations.&lt;/p&gt;&#xA;&lt;p&gt;Both of these scenarios would likely require me to spend more money on&#xA;my blog, which I am not strictly averse to because of its importance to&#xA;me. The file-based backup strategy would also require me to implement&#xA;a restore strategy.&lt;/p&gt;&#xA;&lt;p&gt;Obviously, I&amp;rsquo;d need to come up with automation for either strategy,&#xA;because it&amp;rsquo;d be foolish not to. And well, that&amp;rsquo;s just more work.&lt;/p&gt;&#xA;&lt;p&gt;I decided to go a different route. I chose to apply some of the&#xA;immutable infrastructure as code principles that I&amp;rsquo;ve been learning&#xA;to implement a turnkey solution that is entirely reproducible from&#xA;source, no matter what. It also doesn&amp;rsquo;t require paying for my cloud&#xA;provider to host snapshots or blob storage buckets.&lt;/p&gt;&#xA;&lt;p&gt;At the heart of it, is a &lt;a href=&#34;https://github.com/connorkuehl/infra-codeofconnor&#34;&gt;Terraform module that I&amp;rsquo;ve written&lt;/a&gt;. Terraform allows you&#xA;to declare the infrastructure that you want. It will read this&#xA;declaration and examine the state of your infrastructure, and it will&#xA;carry out whatever actions are necessary to ensure that your&#xA;infrastructure has taken the shape that you want.&lt;/p&gt;&#xA;&lt;p&gt;In this case, I started off by declaring that I want a single&#xA;DigitalOcean droplet&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-hcl&#34; data-lang=&#34;hcl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;resource&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;digitalocean_droplet&amp;#34; &amp;#34;www&amp;#34;&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  image  &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;ubuntu-20-04-x64&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  name   &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;www&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  region &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;sfo3&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  size   &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;s-1vcpu-1gb&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  ssh_keys &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt;.&lt;span style=&#34;color:#66d9ef&#34;&gt;ssh_keys&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;Now, this hasn&amp;rsquo;t really saved me much work at all. If anything, it&#xA;saves me the effort of clicking around on the cloud console to make&#xA;a droplet and/or typing the droplet create commands at the command line.&lt;/p&gt;&#xA;&lt;p&gt;I still have to SSH in, configure a web server, and rsync the static&#xA;site over to the served directory.&lt;/p&gt;&#xA;&lt;p&gt;Luckily, I can also specify a cloud-init script that the droplet will&#xA;run after it is created and powered on.&lt;/p&gt;&#xA;&lt;p&gt;For example, I can now automate the installation of a web server, all&#xA;from the comfort of Terraform&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-hcl&#34; data-lang=&#34;hcl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;resource&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;digitalocean_droplet&amp;#34; &amp;#34;www&amp;#34;&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  image  &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;ubuntu-20-04-x64&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  name   &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;www&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  region &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;sfo3&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  size   &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;s-1vcpu-1gb&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  ssh_keys  &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt;.&lt;span style=&#34;color:#66d9ef&#34;&gt;ssh_keys&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  user_data &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;EOF&lt;/span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#!/usr/bin/env bash&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;mkdir&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;p&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;www&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;data&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;html&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;export DEBIAN_FRONTEND&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;noninteractive&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;apt&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;install&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;y&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;debian&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;keyring&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;debian&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;archive&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;keyring&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;apt&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;transport&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;https&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;curl&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;sLf&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;https&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;://&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;dl&lt;/span&gt;.&lt;span style=&#34;color:#66d9ef&#34;&gt;cloudsmith&lt;/span&gt;.&lt;span style=&#34;color:#66d9ef&#34;&gt;io&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;caddy&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;stable&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;gpg&lt;/span&gt;.&lt;span style=&#34;color:#66d9ef&#34;&gt;key&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;sudo&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;gpg&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;--&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;dearmor&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;o&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;usr&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;share&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;keyrings&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;caddy&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;stable&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;archive&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;keyring&lt;/span&gt;.&lt;span style=&#34;color:#66d9ef&#34;&gt;gpg&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;curl&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;sLf&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;https&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;://&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;dl&lt;/span&gt;.&lt;span style=&#34;color:#66d9ef&#34;&gt;cloudsmith&lt;/span&gt;.&lt;span style=&#34;color:#66d9ef&#34;&gt;io&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;caddy&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;stable&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;debian&lt;/span&gt;.&lt;span style=&#34;color:#66d9ef&#34;&gt;deb&lt;/span&gt;.&lt;span style=&#34;color:#66d9ef&#34;&gt;txt&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;sudo&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;tee&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;etc&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;apt&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;sources&lt;/span&gt;.&lt;span style=&#34;color:#66d9ef&#34;&gt;list&lt;/span&gt;.&lt;span style=&#34;color:#66d9ef&#34;&gt;d&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;caddy&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;stable&lt;/span&gt;.&lt;span style=&#34;color:#66d9ef&#34;&gt;list&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;apt&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;update&lt;/span&gt;            &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;apt&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;get&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;install&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;y&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;caddy&lt;/span&gt;  &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;systemctl&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;enable&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;--&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;now&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;caddy&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;cat&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;etc&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;caddy&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;Caddyfile&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;EOM&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;codeofconnor&lt;/span&gt;.&lt;span style=&#34;color:#66d9ef&#34;&gt;com&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;root&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;www&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;data&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;html&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;file_server&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;EOM&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;chown&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;R&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;caddy&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;caddy&lt;/span&gt; &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;www&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;data&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;html&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;systemctl&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;reload&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;caddy&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;EOF&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;Ah, that&amp;rsquo;s much nicer. Now all that&amp;rsquo;s required is rsync&amp;rsquo;ing the files&#xA;over for Caddy to serve, which is no big deal, because I already have&#xA;a simple Makefile target for that in the git repo that has all of the&#xA;source markdown for my blog.&lt;/p&gt;&#xA;&lt;p&gt;&lt;em&gt;Egads!&lt;/em&gt; This won&amp;rsquo;t work as is, because when I rsync my files over,&#xA;the owner and group is set to my user and group, and so Caddy can&amp;rsquo;t&#xA;actually display them because it does not have permission to.&lt;/p&gt;&#xA;&lt;p&gt;I can just make a quick tweak to the cloud-init script so that the&#xA;caddy group is automatically applied to files added to the html&#xA;directory and caddy will be able to read them:&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-diff&#34; data-lang=&#34;diff&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;chown -R caddy:caddy /var/www-data/html&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;+chmod g+s /var/www-data/html&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;Alas, now Terraform will have to re-create this droplet so it can&#xA;create one that will have the updated cloud-init script run as part&#xA;of the provisioning process. This means that the version of the site&#xA;I uploaded earlier will be lost, and I&amp;rsquo;ll have to upload it again.&lt;/p&gt;&#xA;&lt;p&gt;It&amp;rsquo;s not the end of the world, but still requires me to remember to&#xA;run the deploy target from my site&amp;rsquo;s Makefile again. Not a great&#xA;experience.&lt;/p&gt;&#xA;&lt;p&gt;In most cases, this is the beauty of immutable infrastructure as code.&#xA;There is almost always no doubt at all what the state of the system is&#xA;in because it&amp;rsquo;s written here as code and Terraform will alert you to&#xA;any configuration drift and show what&amp;rsquo;s needed to correct it.&lt;/p&gt;&#xA;&lt;p&gt;This is also easily fixed while still practicing immutable IaC&#xA;techniques.&lt;/p&gt;&#xA;&lt;p&gt;In this case, we just have to isolate the mutable part of the&#xA;infrastructure. We can just attach a block volume and populate it with&#xA;the contents of the site.&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-hcl&#34; data-lang=&#34;hcl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;resource&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;digitalocean_volume&amp;#34; &amp;#34;www_data&amp;#34;&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  region                  &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;sfo3&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  name                    &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;www-data&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  size                    &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  initial_filesystem_type &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;ext4&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  description             &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Content for the webserver to serve&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&#xA;  &#xA;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-hcl&#34; data-lang=&#34;hcl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;resource&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;digitalocean_volume_attachment&amp;#34; &amp;#34;attach_www_data_to_www&amp;#34;&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  droplet_id &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;digitalocean_droplet&lt;/span&gt;.&lt;span style=&#34;color:#66d9ef&#34;&gt;www&lt;/span&gt;.&lt;span style=&#34;color:#66d9ef&#34;&gt;id&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  volume_id  &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;digitalocean_volume&lt;/span&gt;.&lt;span style=&#34;color:#66d9ef&#34;&gt;www_data&lt;/span&gt;.&lt;span style=&#34;color:#66d9ef&#34;&gt;id&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;And before we forget, we want to make sure our cloud-init data mounts&#xA;it to the right place so Caddy can serve files from it:&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-diff&#34; data-lang=&#34;diff&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;#!/usr/bin/env bash&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;export DEBIAN_FRONTEND=noninteractive&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;mkdir -p /var/www-data&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;+mount -o discard,defaults,noatime /dev/disk/by-id/scsi-0DO_Volume_${volume_name} /var/www-data&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;+echo &amp;#39;/dev/disk/by-id/scsi-0DO_Volume_${volume_name} /var/www-data ext4 defaults,nofail,discard 0 0&amp;#39; &amp;gt;&amp;gt; /etc/fstab&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;+&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;+mkdir -p /var/www-data/html&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;apt-get install -y debian-keyring debian-archive-keyring apt-transport-https&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;curl -1sLf &amp;#39;https://dl.cloudsmith.io/public/caddy/stable/gpg.key&amp;#39; | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;curl -1sLf &amp;#39;https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt&amp;#39; | sudo tee /etc/apt/sources.list.d/caddy-stable.list&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;apt-get update            &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;apt-get install -y caddy  &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;systemctl enable --now caddy&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cat &amp;gt; /etc/caddy/Caddyfile &amp;lt;&amp;lt;EOF&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;${domain_name} {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    root * /var/www-data/html&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    file_server&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;} &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;EOF&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;chown -R caddy:caddy /var/www-data/html&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;chmod g+s /var/www-data/html&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;systemctl restart caddy&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;Astute readers may have noticed I moved the cloud-init script out of&#xA;the heredoc in the Terraform module. For completeness, the Terraform&#xA;module is updated to look like this now:&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-hcl&#34; data-lang=&#34;hcl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  image  &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;ubuntu-20-04-x64&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  name   &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;www&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  region &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;sfo3&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  size   &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;s-1vcpu-1gb&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  ssh_keys  &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt;.&lt;span style=&#34;color:#66d9ef&#34;&gt;ssh_keys&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  user_data &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;templatefile&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;${path.module}/user_data.sh&amp;#34;&lt;/span&gt;, {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    domain_name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt;.&lt;span style=&#34;color:#66d9ef&#34;&gt;domain_name&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    volume_name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;digitalocean_volume&lt;/span&gt;.&lt;span style=&#34;color:#66d9ef&#34;&gt;www_data&lt;/span&gt;.&lt;span style=&#34;color:#66d9ef&#34;&gt;name&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  })&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;Nice! Now I just have to run &lt;code&gt;terraform apply&lt;/code&gt; and then deploy the site&#xA;one last time since the old droplet with the site on it was blown away.&lt;/p&gt;&#xA;&lt;p&gt;This time, the deploy will deposit the files into the block volume since&#xA;I mounted it at the same place that the deploy script is used to copying&#xA;files to.&lt;/p&gt;&#xA;&lt;p&gt;Now I can happily tear down and recreate the droplet, and I won&amp;rsquo;t have&#xA;to run the deploy to refurnish it with the site data every time.&lt;/p&gt;&#xA;&lt;p&gt;The finishing touch, of course, is DNS. I can easily point&#xA;codeofconnor.com at the correct droplet, no matter how many times I&#xA;destroy and recreate it, like so:&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-hcl&#34; data-lang=&#34;hcl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;resource&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;digitalocean_domain&amp;#34; &amp;#34;a_record&amp;#34;&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  name       &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt;.&lt;span style=&#34;color:#66d9ef&#34;&gt;domain_name&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  ip_address &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;digitalocean_droplet&lt;/span&gt;.&lt;span style=&#34;color:#66d9ef&#34;&gt;www&lt;/span&gt;.&lt;span style=&#34;color:#66d9ef&#34;&gt;ipv4_address&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;Now, this basic configuration does result in downtime if the base&#xA;droplet is destroyed. At least, until DNS propagates to point at the&#xA;new droplet.&lt;/p&gt;&#xA;&lt;p&gt;If the day ever comes that I want a zero-downtime redeploy of the base&#xA;droplet, I&amp;rsquo;ll be sure to write about it here!&lt;/p&gt;&#xA;&lt;p&gt;Now anything can happen to my infrastructure, and I can be back up and&#xA;running with exactly these steps:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;Make sure I have a valid DigitalOcean API token&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;terraform apply&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;make deploy&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;Wait for DNS to propagate&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;&#xA;&lt;hr&gt;&#xA;&lt;ol&gt;&#xA;&lt;li id=&#34;fn:1&#34;&gt;&#xA;&lt;p&gt;Disclaimer: as of this writing, I am a DigitalOcean employee.&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li id=&#34;fn:2&#34;&gt;&#xA;&lt;p&gt;It&amp;rsquo;s unlikely that every code snippet here can be copy-and-pasted&#xA;into your own Terraform configuration and &amp;ldquo;just work.&amp;rdquo; I ended up&#xA;copying and pasting snippets from the final product, and simplifying&#xA;them down to their earlier renditions because I did not have a git&#xA;history with many intermediate steps. The final form has more details in&#xA;it that I feel would distract from the main goals of this post.&amp;#160;&lt;a href=&#34;#fnref:2&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;/div&gt;&#xA;</description>
    </item>
    <item>
      <title>Applying Cloud Native Patterns to My Discord Bot</title>
      <link>https://codeofconnor.com/applying-cloud-native-patterns-to-my-discord-bot/</link>
      <pubDate>Sun, 31 Jul 2022 12:05:46 -0500</pubDate>
      <guid>https://codeofconnor.com/applying-cloud-native-patterns-to-my-discord-bot/</guid>
      <description>&lt;p&gt;I recently read through &lt;a href=&#34;https://www.manning.com/books/cloud-native-patterns&#34;&gt;&lt;em&gt;Cloud Native Patterns&lt;/em&gt; by Cornelia Davis&lt;/a&gt;. I thought it was&#xA;a great book. This post is not a review of &lt;em&gt;Cloud Native Patterns&lt;/em&gt;, but&#xA;instead it&amp;rsquo;s a journal of how I&amp;rsquo;ve applied some of what I&amp;rsquo;ve learned.&lt;/p&gt;&#xA;&lt;p&gt;There have been many times that I&amp;rsquo;ve declared &lt;a href=&#34;https://github.com/connorkuehl/popple&#34;&gt;Popple&lt;/a&gt; to be a completed project, but&#xA;it&amp;rsquo;s continued to prove itself a valuable playground for experimenting&#xA;and learning new-to-me ideas in both software development and ops. So,&#xA;naturally, I chose to hack on Popple to get some hands-on experience&#xA;with some of the concepts described in the book.&lt;/p&gt;&#xA;&lt;p&gt;At the risk of oversimplifying things, I&amp;rsquo;ve found these two guiding&#xA;questions to be a good rule of thumb for what needs to change:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;What prevents me from running this as multiple processes?&lt;/li&gt;&#xA;&lt;li&gt;What happens if &lt;code&gt;$dependency&lt;/code&gt; is malfunctioning?&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;The first question has to do with identifying which parts of the&#xA;application would prevent horizontal scaling to eliminate single&#xA;points of failure. The second question is a barometer for deciding&#xA;what kinds of reliability patterns I should bake in and where to&#xA;meet any kinds of desired consistency or availability goals.&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;p&gt;&lt;strong&gt;DISCLAIMER:&lt;/strong&gt; I realize this rearchitecting was completely overkill&#xA;for workloads that my current Discord bot deployment handles. This was&#xA;just for academic purposes.&lt;/p&gt;&#xA;&lt;p&gt;I had no issues with the reliability or performance of a single&#xA;process with a SQLite database.&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;h1 id=&#34;previous-architecture&#34;&gt;&#xA;  &lt;a class=&#34;Heading-link u-clickable&#34; href=&#34;https://codeofconnor.com/applying-cloud-native-patterns-to-my-discord-bot/#previous-architecture&#34;&gt;Previous Architecture&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://codeofconnor.com/images/popple-deploy0.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;A quick look at this might uncover some answers to the guiding questions&#xA;above.&lt;/p&gt;&#xA;&lt;p&gt;The following aspects of this deployment prevents horizontal scaling:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;This deployment assumes a SQLite database on the local filesystem.&#xA;Deploying an additional instance to a separate node would need access&#xA;to that database somehow. Pulling it out to a remote filesystem is&#xA;possible I suppose, but there may be concurrency issues and this would&#xA;require additional provisioning on nodes where it&amp;rsquo;s deployed.&lt;/li&gt;&#xA;&lt;li&gt;It&amp;rsquo;s not obvious from looking at the diagram, but the process identifies&#xA;itself to Discord using a special token. The Discord API &lt;em&gt;does&lt;/em&gt; allow&#xA;for bots to introduce themselves as a &lt;em&gt;shard&lt;/em&gt; of that token, but my bot&#xA;does not currently do that. So I&amp;rsquo;d only want one of these deployed until&#xA;the bot supports sharding.&lt;/li&gt;&#xA;&lt;li&gt;Another thing that needs to change is how Popple is configured. Previously,&#xA;it would read a config file from the filesystem. Rather than adding those&#xA;to nodes that are provisioned, I&amp;rsquo;ll change it to be configured purely through&#xA;environment variables. There will be a couple of secrets that need to be configured,&#xA;but it&amp;rsquo;s not going to be in scope for this blog post since I only deploy&#xA;Popple to my homelab. I&amp;rsquo;ll address this at a later date.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;Because of this, my Discord bot is a stateful service, which inherently makes&#xA;horizontal scaling much harder. The first step is going to be isolating the&#xA;stateful parts of the application.&lt;/p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;h1 id=&#34;isolating-state&#34;&gt;&#xA;  &lt;a class=&#34;Heading-link u-clickable&#34; href=&#34;https://codeofconnor.com/applying-cloud-native-patterns-to-my-discord-bot/#isolating-state&#34;&gt;Isolating state&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;Let&amp;rsquo;s deal with the SQLite issue first. There are a couple of things that&#xA;immediately come to mind:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;Deploy a relational database (E.g., MariaDB, Postgres, MySQL, etc).&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;It would look like this:&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://codeofconnor.com/images/popple-deploy1.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;&#xA;&lt;ol start=&#34;2&#34;&gt;&#xA;&lt;li&gt;Alternatively, put a CRUD service &lt;em&gt;in front&lt;/em&gt; of the SQLite database.&#xA;Like this:&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://codeofconnor.com/images/popple-deploy2.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;d rather do the first one, since it simplifies development because I won&amp;rsquo;t&#xA;have to maintain the CRUD API that stands in front of the SQLite database.&lt;/p&gt;&#xA;&lt;p&gt;The second issue is Discord sharding.&lt;/p&gt;&#xA;&lt;p&gt;It looks like the Discord library I am using has a way to shard, but it seems&#xA;to require a shard ID &lt;em&gt;and&lt;/em&gt; the total number of shards a priori. Off the top&#xA;of my head, I&amp;rsquo;m not sure how I could horizontally scale the processes up and&#xA;down without having to coordinate across all of them to make sure their shard&#xA;ID and max shard counts are correct, so until I have an idea of how to approach&#xA;that, I will work around it by splitting the actual Discord presence out into its&#xA;own process, like so:&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://codeofconnor.com/images/popple-deploy3.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;I drew an arrow between &lt;code&gt;bot&lt;/code&gt; and &lt;code&gt;popple&lt;/code&gt; because the above diagram implies there&#xA;will have to be some sort of client-server communication between those two processes.&lt;/p&gt;&#xA;&lt;p&gt;However, dear reader, I am writing this after the fact, and while I don&amp;rsquo;t mean&#xA;to spoil the rest of the post, I can tell you for sure that client-server is&#xA;not the direction I&amp;rsquo;m taking this, because it couples the two services.&lt;/p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;h1 id=&#34;keeping-things-decoupled&#34;&gt;&#xA;  &lt;a class=&#34;Heading-link u-clickable&#34; href=&#34;https://codeofconnor.com/applying-cloud-native-patterns-to-my-discord-bot/#keeping-things-decoupled&#34;&gt;Keeping things decoupled&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;Now that I&amp;rsquo;ve factored out the stateful services (the Discord bot presence and&#xA;the MySQL database), the business-logic component can now scale horizontally.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://codeofconnor.com/images/popple-deploy4.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;A client-server relationship between the bot and the business-logic service&#xA;would work, but in my opinion, it would add some unnecessary complexity.&lt;/p&gt;&#xA;&lt;p&gt;First, I&amp;rsquo;d need to expose an API for the &lt;code&gt;popple&lt;/code&gt; service so that the bot can&#xA;hit those endpoints when something happens.&lt;/p&gt;&#xA;&lt;p&gt;Second, from a development perspective, I would probably have to scale the bot&#xA;internally with concurrency for multiple operations from from any number of&#xA;servers that the bot is overseeing.&lt;/p&gt;&#xA;&lt;p&gt;Third, there&amp;rsquo;s another model that fits nicely with how a chat bot operates. I&amp;rsquo;ve&#xA;already alluded to this in a previous paragraph:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;First, I&amp;rsquo;d need to expose an API for the &lt;code&gt;popple&lt;/code&gt; service so that the bot can&#xA;hit those endpoints &lt;strong&gt;when something happens.&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;Emphasis mine.&lt;/p&gt;&#xA;&lt;p&gt;Yep. We&amp;rsquo;re making this bad bear event-driven.&lt;/p&gt;&#xA;&lt;p&gt;All the bot has to do is read incoming messages, determine if it&amp;rsquo;s a control&#xA;command or if the message has anything that indicates someone or something&amp;rsquo;s&#xA;karma is incremented or decremented.&lt;/p&gt;&#xA;&lt;p&gt;For example, if a control message comes in changing a bot value, the bot could&#xA;emit an &lt;code&gt;setting-changed&lt;/code&gt; event. Or if a message comes in that changes karma,&#xA;it could emit a &lt;code&gt;karma-changed&lt;/code&gt; event.&lt;/p&gt;&#xA;&lt;p&gt;I&amp;rsquo;m not going to write a message broker, so I&amp;rsquo;ll be recruiting some help:&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://codeofconnor.com/images/popple-deploy5.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;I like this model a lot, and not just because a chat bot lends itself well to&#xA;an event-driven paradigm. It also allows for the &lt;code&gt;bot&lt;/code&gt; and business service&#xA;components to be developed independently of one another.&lt;/p&gt;&#xA;&lt;p&gt;For example, someone could contribute to the project without needing a Discord&#xA;bot token if the area of the codebase they&amp;rsquo;re concerned with only exists in the&#xA;service component.&lt;/p&gt;&#xA;&lt;p&gt;Lastly, something I noticed is that the top-level driver functions for each of&#xA;these services become very simple. They wait for an event and process it.&lt;/p&gt;&#xA;&lt;p&gt;If you&amp;rsquo;re into reading Go code, then you can see for yourself what I&amp;rsquo;m talking&#xA;about:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;popplebot&lt;/code&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;The &lt;a href=&#34;https://github.com/connorkuehl/popple/blob/43e3cf4750b0d892d269174097c6301765d0e9da/cmd/popplebot/main.go#L170&#34;&gt;&lt;code&gt;requestLoop&lt;/code&gt;&lt;/a&gt;&#xA;that reads Discord messages and emits &amp;ldquo;request&amp;rdquo; events that are handled by &lt;code&gt;popplesvc&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;The &lt;a href=&#34;https://github.com/connorkuehl/popple/blob/43e3cf4750b0d892d269174097c6301765d0e9da/cmd/popplebot/main.go#L198&#34;&gt;&lt;code&gt;eventLoop&lt;/code&gt;&lt;/a&gt;&#xA;that processes events that &lt;code&gt;popplesvc&lt;/code&gt; emits (such as &amp;ldquo;karma changed&amp;rdquo; or &amp;ldquo;control message&#xA;processed&amp;rdquo;)&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;popplesvc&lt;/code&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;The &lt;a href=&#34;https://github.com/connorkuehl/popple/blob/43e3cf4750b0d892d269174097c6301765d0e9da/cmd/popplesvc/main.go#L161&#34;&gt;&lt;code&gt;eventLoop&lt;/code&gt;&lt;/a&gt;&#xA;that processes request events that &lt;code&gt;popplebot&lt;/code&gt; submitted to a work queue and&#xA;emits events when it processes a control message or a &amp;ldquo;karma change&amp;rdquo; request.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;h1 id=&#34;conclusion&#34;&gt;&#xA;  &lt;a class=&#34;Heading-link u-clickable&#34; href=&#34;https://codeofconnor.com/applying-cloud-native-patterns-to-my-discord-bot/#conclusion&#34;&gt;Conclusion&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;I&amp;rsquo;ll be the first to admit that what I&amp;rsquo;ve done here is make the&#xA;deployment much more complicated.&lt;/p&gt;&#xA;&lt;p&gt;The previous architecture essentially allowed me to copy a binary&#xA;over and start it. If Discord&amp;rsquo;s up and I have a path through the&#xA;Internet to Discord then we&amp;rsquo;re good to go.&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;p&gt;Aside: to be fair, all it takes to deploy to my homelab is to push&#xA;my git repo there and run &lt;code&gt;docker-compose up -d&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;p&gt;Now I have two binaries to deploy, and I need to make sure that they&#xA;have a RabbitMQ instance &lt;em&gt;and&lt;/em&gt; a MySQL database up for them to function&#xA;properly.&lt;/p&gt;&#xA;&lt;p&gt;Despite this, I like the spot that the project is in a lot more than&#xA;I did previously.&lt;/p&gt;&#xA;&lt;p&gt;Now,&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;It&amp;rsquo;s easier to develop components in isolation. For example, drive-by&#xA;contributors no longer need their own Discord bot token to run the application&#xA;since they can just develop and test the &lt;code&gt;popplesvc&lt;/code&gt; component.&lt;/li&gt;&#xA;&lt;li&gt;It&amp;rsquo;s more suitable for a cloud-native deployment. The &lt;code&gt;popplesvc&lt;/code&gt; component&#xA;could be added to a Kubernetes replica set that can expand and contract freely&#xA;because it no longer has a file system dependency on the SQLite database.&lt;/li&gt;&#xA;&lt;li&gt;Either component could now go down briefly and when it comes back up, RabbitMQ&#xA;will be able to furnish it with any events that it missed.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;</description>
    </item>
    <item>
      <title>Monitoring My Discord Bot</title>
      <link>https://codeofconnor.com/monitoring-my-discord-bot/</link>
      <pubDate>Thu, 14 Jul 2022 20:34:49 -0500</pubDate>
      <guid>https://codeofconnor.com/monitoring-my-discord-bot/</guid>
      <description>&lt;p&gt;I decided to implement a health check capability into my Discord bot.&#xA;Its uptime is totally unimportant, but it was still fun to do.&lt;/p&gt;&#xA;&lt;p&gt;In terms of monitoring the Discord bot, there were a few options:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;Build my own with cron, bash, and sendmail; install it on a separate host&lt;/li&gt;&#xA;&lt;li&gt;Use cron to send pings to &lt;a href=&#34;https://healthchecks.io&#34;&gt;healthchecks.io&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;Use either &lt;a href=&#34;https://updown.io&#34;&gt;updown.io&lt;/a&gt; or &lt;a href=&#34;https://uptimerobot.com&#34;&gt;uptimerobot.com&lt;/a&gt;.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;I chose the second option. I found it compelling because the model of pushing&#xA;a ping from my host to the monitoring API didn&amp;rsquo;t require me to punch a hole&#xA;in my host&amp;rsquo;s firewall so that the monitoring API could scrape the health check&#xA;endpoint. It&amp;rsquo;s also free, but it&amp;rsquo;s worth noting that when I played around with&#xA;&lt;a href=&#34;https://updown.io&#34;&gt;updown.io&amp;rsquo;s&lt;/a&gt; price estimators that I found it to be very&#xA;inexpensive (on the order of &lt;em&gt;cents&lt;/em&gt; per year for my use case).&lt;/p&gt;&#xA;&lt;p&gt;Instead, I have a cron job that executes a bash script, like this:&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#!/usr/bin/env bash&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;details&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;$(&lt;/span&gt;curl -sSf http://localhost:8000/health&lt;span style=&#34;color:#66d9ef&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;curl -m &lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt; --retry &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt; --data-raw &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$details&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt; https://hc-ping.com/&amp;lt;redacted&amp;gt;/$?&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;The way it works is the cron job runs on the same host that my Discord bot runs on.&#xA;It hits the bot&amp;rsquo;s health check endpoint and stores the output in the &lt;code&gt;details&lt;/code&gt;&#xA;variable. The output is just JSON that describes the health of the bot, like this:&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;discord_connection&amp;#34;&lt;/span&gt;:&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;connected&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;discord_heartbeat_latency&amp;#34;&lt;/span&gt;:&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;63 ms&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://healthchecks.io&#34;&gt;healthchecks.io&lt;/a&gt; can persist information that you send&#xA;in your ping, and I wanted it to persist the health details with each check-in&#xA;because I figure there&amp;rsquo;d be some forensic value in having that information.&lt;/p&gt;&#xA;&lt;p&gt;The second &lt;code&gt;curl&lt;/code&gt; invocation is the actual &lt;a href=&#34;https://healthchecks.io&#34;&gt;healthchecks.io&lt;/a&gt;&#xA;ping. I include the exit status of the first &lt;code&gt;curl&lt;/code&gt; invocation because the&#xA;&lt;a href=&#34;https://healthchecks.io&#34;&gt;healthchecks.io&lt;/a&gt; API allows you to specify that the monitored&#xA;service was able to check-in but is experiencing failures.&lt;/p&gt;&#xA;&lt;p&gt;To take advantage of that, my &lt;code&gt;curl&lt;/code&gt; invocation uses the &lt;code&gt;-f&lt;/code&gt; flag so that its exit&#xA;code will be non-zero if the healthcheck endpoint doesn&amp;rsquo;t return an HTTP 200 code.&lt;/p&gt;&#xA;&lt;p&gt;The end result of this monitoring goes a little something like this:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;My cronjob runs every 5 minutes. I configured the &lt;a href=&#34;https://healthchecks.io&#34;&gt;healthchecks.io&lt;/a&gt;&#xA;monitor to expect a ping every 5 minutes.&lt;/li&gt;&#xA;&lt;li&gt;If &lt;a href=&#34;https://healthchecks.io&#34;&gt;healthchecks.io&lt;/a&gt; does not receive a ping within&#xA;that 5 minute interval, I configured it to have a 15 minute grace period. If&#xA;the grace period elapses and &lt;a href=&#34;https://healthchecks.io&#34;&gt;healthchecks.io&lt;/a&gt; does&#xA;not receive a ping, then &lt;a href=&#34;https://healthchecks.io&#34;&gt;healthchecks.io&lt;/a&gt; sends me&#xA;an email.&lt;/li&gt;&#xA;&lt;li&gt;If &lt;a href=&#34;https://healthchecks.io&#34;&gt;healthchecks.io&lt;/a&gt; receives a ping with the non-zero&#xA;exit code supplied from my cronjob, then it sends me an email.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;Otherwise, no news is good news! Setting all of this up took approximately 5 minutes.&lt;/p&gt;&#xA;&lt;p&gt;So what happens if the bot goes down and I get an email? Probably nothing, heh. It&amp;rsquo;ll&#xA;be cool though. If anything, I&amp;rsquo;m guessing this will end up inadvertently tracking my&#xA;ISP&amp;rsquo;s uptime since my Discord bot runs in my homelab.&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;p&gt;I&amp;rsquo;m not in any way affiliated with &lt;a href=&#34;https://healthchecks.io&#34;&gt;healthchecks.io&lt;/a&gt;, and&#xA;I&amp;rsquo;ve never spoken with them.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Type Safe Enums in Go</title>
      <link>https://codeofconnor.com/type-safe-enums-in-go/</link>
      <pubDate>Wed, 11 May 2022 17:13:19 -0500</pubDate>
      <guid>https://codeofconnor.com/type-safe-enums-in-go/</guid>
      <description>&lt;p&gt;I&amp;rsquo;m willing to bet that many Go programmers have seen or used this strategy&#xA;for enumerating things in code before:&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;type&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Profession&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; (&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#a6e22e&#34;&gt;Unknown&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Profession&lt;/span&gt; = &lt;span style=&#34;color:#66d9ef&#34;&gt;iota&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#a6e22e&#34;&gt;Warrior&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#a6e22e&#34;&gt;Cleric&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#a6e22e&#34;&gt;Hunter&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#a6e22e&#34;&gt;Mage&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;In practice, this is probably fine. In fact, I can&amp;rsquo;t think of a time this has&#xA;caused a (known) bug in one of my programs (yet.) Famous last words.&lt;/p&gt;&#xA;&lt;p&gt;However, there are times where I wish the solution was a little more&#xA;bulletproof at compile-time.&lt;/p&gt;&#xA;&lt;p&gt;For example, I dislike having an Unknown variant. This is mainly to avoid&#xA;ambiguity when client code introduces a zero-value for this enum and perhaps&#xA;forgets to set it, like so:&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;prof&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Profession&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;_&lt;/span&gt; = &lt;span style=&#34;color:#a6e22e&#34;&gt;whatIsYourProfession&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;prof&lt;/span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;This means that most code that cares about the enum type will have to account&#xA;for the Unknown variant, like so:&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;whatIsYourProfession&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;p&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Profession&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;error&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;switch&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;p&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Unknown&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;errors&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;New&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;unknown profession&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Warrior&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;&lt;span style=&#34;color:#a6e22e&#34;&gt;fmt&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Couldn&amp;#39;t beat you in an arm-wrestling match.&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Cleric&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;&lt;span style=&#34;color:#a6e22e&#34;&gt;fmt&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Divine!&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Hunter&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;&lt;span style=&#34;color:#a6e22e&#34;&gt;fmt&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Shoot an arrow over them mountains.&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Mage&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;&lt;span style=&#34;color:#a6e22e&#34;&gt;fmt&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;I&amp;#39;ll pick a card, any card.&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;Even though I accounted for all of the professions, there&amp;rsquo;s still a bug here.&lt;/p&gt;&#xA;&lt;p&gt;Technically, client code could do this:&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;whatIsYourProfession&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;Profession&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;)); &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#a6e22e&#34;&gt;fmt&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;fooled ya!&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;Again, in practice, it&amp;rsquo;s unlikely that client code would pull the pin on that&#xA;grenade and hand it to us, but I wish the compiler would stop them from doing&#xA;so.&lt;/p&gt;&#xA;&lt;p&gt;So I guess we could add a default case:&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;whatIsYourProfession&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;p&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Profession&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;error&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;switch&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;p&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Unknown&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;errors&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;New&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;unknown profession&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Warrior&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;&lt;span style=&#34;color:#a6e22e&#34;&gt;fmt&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Couldn&amp;#39;t beat you in an arm-wrestling match.&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Cleric&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;&lt;span style=&#34;color:#a6e22e&#34;&gt;fmt&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Divine!&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Hunter&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;&lt;span style=&#34;color:#a6e22e&#34;&gt;fmt&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Shoot an arrow over them mountains.&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Mage&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;&lt;span style=&#34;color:#a6e22e&#34;&gt;fmt&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;I&amp;#39;ll pick a card, any card.&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;default&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;errors&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;New&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;how could you betray me like this?&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;We could prevent the zero-value situation by sealing the type within our&#xA;package so client code can&amp;rsquo;t zero-value construct it without using specific&#xA;constructor functions:&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;package&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;profession&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; (&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;errors&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; (&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#a6e22e&#34;&gt;ErrUnknown&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;error&lt;/span&gt; = &lt;span style=&#34;color:#a6e22e&#34;&gt;errors&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;New&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;unknown profession&amp;#34;&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;type&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Profession&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt;() &lt;span style=&#34;color:#a6e22e&#34;&gt;profession&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;p&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Profession&lt;/span&gt;) &lt;span style=&#34;color:#a6e22e&#34;&gt;String&lt;/span&gt;() &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;s&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;switch&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Is&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;p&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;Warrior&lt;/span&gt;):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;&lt;span style=&#34;color:#a6e22e&#34;&gt;s&lt;/span&gt; = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;warrior&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Is&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;p&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;Cleric&lt;/span&gt;):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;&lt;span style=&#34;color:#a6e22e&#34;&gt;s&lt;/span&gt; = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;cleric&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Is&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;p&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;Hunter&lt;/span&gt;):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;&lt;span style=&#34;color:#a6e22e&#34;&gt;s&lt;/span&gt; = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;hunter&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Is&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;p&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;Mage&lt;/span&gt;):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;&lt;span style=&#34;color:#a6e22e&#34;&gt;s&lt;/span&gt; = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;mage&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;s&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;type&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;profession&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; (&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#a6e22e&#34;&gt;warrior&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;profession&lt;/span&gt; = &lt;span style=&#34;color:#66d9ef&#34;&gt;iota&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#a6e22e&#34;&gt;cleric&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#a6e22e&#34;&gt;hunter&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#a6e22e&#34;&gt;mage&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;FromString&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;s&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;) (&lt;span style=&#34;color:#a6e22e&#34;&gt;Profession&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;error&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;switch&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;s&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;warrior&amp;#34;&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Warrior&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;cleric&amp;#34;&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Cleric&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;hunter&amp;#34;&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Hunter&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;mage&amp;#34;&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Mage&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;ErrUnknown&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Warrior&lt;/span&gt;() &lt;span style=&#34;color:#a6e22e&#34;&gt;profession&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;warrior&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Cleric&lt;/span&gt;() &lt;span style=&#34;color:#a6e22e&#34;&gt;profession&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;cleric&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Hunter&lt;/span&gt;() &lt;span style=&#34;color:#a6e22e&#34;&gt;profession&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;hunter&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Mage&lt;/span&gt;() &lt;span style=&#34;color:#a6e22e&#34;&gt;profession&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;mage&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Is&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;r&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;target&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Profession&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;r&lt;/span&gt;() &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;target&lt;/span&gt;()&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;Now there&amp;rsquo;s no such thing as an unknown zero-value profession. Furthermore,&#xA;because the underlying type is sealed in the package and only accessible via&#xA;constructor functions, the worst thing client code can do is pass a nil function&#xA;pointer through if they create a zero-value (nil pointer) profession.Profession:&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;prof&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;profession&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Profession&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;// nil&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;Whether or not that offends you, is up to you. Representing an invalid state&#xA;is a bug, and I&amp;rsquo;d rather segfault than limp along and hope for the best&#xA;despite garbage state flowing through my program.&lt;/p&gt;&#xA;&lt;p&gt;Let&amp;rsquo;s tie it all together with a complete example program, where we can now&#xA;have peace of mind that garbage enum values won&amp;rsquo;t be able to quietly sneak&#xA;through.&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;package&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; (&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;bufio&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;errors&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;fmt&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;os&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;strings&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;codeofconnor.com/profession/profession&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;() {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#a6e22e&#34;&gt;scanner&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;bufio&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;NewScanner&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;os&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Stdin&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;scanner&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Scan&lt;/span&gt;() {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;&lt;span style=&#34;color:#a6e22e&#34;&gt;line&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;strings&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;TrimSpace&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;scanner&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Text&lt;/span&gt;())&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;&lt;span style=&#34;color:#a6e22e&#34;&gt;prof&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;profession&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;FromString&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;line&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;errors&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Is&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;profession&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;ErrUnknown&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;&#x9;&lt;span style=&#34;color:#a6e22e&#34;&gt;fmt&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;I&amp;#39;ve never heard of a&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;line&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;continue&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;&lt;span style=&#34;color:#a6e22e&#34;&gt;whatIsYourProfession&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;prof&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;whatIsYourProfession&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;prof&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;profession&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Profession&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;message&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;switch&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;profession&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Is&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;prof&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;profession&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Warrior&lt;/span&gt;):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;&lt;span style=&#34;color:#a6e22e&#34;&gt;message&lt;/span&gt; = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Couldn&amp;#39;t beat you in an arm-wrestling match.&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;profession&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Is&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;prof&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;profession&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Cleric&lt;/span&gt;):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;&lt;span style=&#34;color:#a6e22e&#34;&gt;message&lt;/span&gt; = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Divine!&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;profession&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Is&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;prof&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;profession&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Hunter&lt;/span&gt;):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;&lt;span style=&#34;color:#a6e22e&#34;&gt;message&lt;/span&gt; = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Shoot an arrow over them mountains.&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;profession&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Is&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;prof&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;profession&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Mage&lt;/span&gt;):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;&lt;span style=&#34;color:#a6e22e&#34;&gt;message&lt;/span&gt; = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;I&amp;#39;ll pick a card, any card.&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#66d9ef&#34;&gt;default&lt;/span&gt;:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&#x9;&lt;span style=&#34;color:#a6e22e&#34;&gt;message&lt;/span&gt; = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;I&amp;#39;ve never met a &amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;prof&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;String&lt;/span&gt;() &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34; before.&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#x9;&lt;span style=&#34;color:#a6e22e&#34;&gt;fmt&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Println&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;message&lt;/span&gt;)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&#xA;  &#xA;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;% ./p&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;warrior&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Couldn&amp;#39;t beat you in an arm-wrestling match.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;hunter&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Shoot an arrow over them mountains.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;mage&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;I&amp;#39;ll pick a card, any card.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cleric&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Divine!&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;potato&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;I&amp;#39;ve never heard of a potato&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;</description>
    </item>
    <item>
      <title>My New Virtualization Homelab</title>
      <link>https://codeofconnor.com/my-new-virtualization-homelab/</link>
      <pubDate>Fri, 12 Nov 2021 22:03:41 -0600</pubDate>
      <guid>https://codeofconnor.com/my-new-virtualization-homelab/</guid>
      <description>&#xA;&#xA;&#xA;&#xA;&lt;h1 id=&#34;behold&#34;&gt;&#xA;  &lt;a class=&#34;Heading-link u-clickable&#34; href=&#34;https://codeofconnor.com/my-new-virtualization-homelab/#behold&#34;&gt;Behold!&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;&lt;img src=&#34;https://codeofconnor.com/images/virtlab.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;h1 id=&#34;the-specs&#34;&gt;&#xA;  &lt;a class=&#34;Heading-link u-clickable&#34; href=&#34;https://codeofconnor.com/my-new-virtualization-homelab/#the-specs&#34;&gt;The specs&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Processor: Intel Core i5 11400 6C/12T&lt;/li&gt;&#xA;&lt;li&gt;Motherboard: ASUS Prime 560M-A&lt;/li&gt;&#xA;&lt;li&gt;RAM: 32GiB DDR4 @ 3200MHz (Corsair Vengeance)&lt;/li&gt;&#xA;&lt;li&gt;SSD0: Samsung Pro 970 256GB&lt;/li&gt;&#xA;&lt;li&gt;SSD1: Tcsungbow 1TB ¯\_(ツ)_/¯&lt;/li&gt;&#xA;&lt;li&gt;Case: CoolerMaster N200 MicroATX&lt;/li&gt;&#xA;&lt;li&gt;Hypervisor/host OS: Debian 11 Bullseye&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;h1 id=&#34;why&#34;&gt;&#xA;  &lt;a class=&#34;Heading-link u-clickable&#34; href=&#34;https://codeofconnor.com/my-new-virtualization-homelab/#why&#34;&gt;Why?&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;I like virtualization and I want an always-on server to continue to&#xA;learn and experiment with the KVM virtualization ecosystem.&lt;/p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;h1 id=&#34;why-not-used-enterprise-gear&#34;&gt;&#xA;  &lt;a class=&#34;Heading-link u-clickable&#34; href=&#34;https://codeofconnor.com/my-new-virtualization-homelab/#why-not-used-enterprise-gear&#34;&gt;Why not used enterprise gear?&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;Looks big and loud. Most of the used gear at my price point seemed kind&#xA;of old, too. And besides, if this ever stops being a hobby for me, I can&#xA;just throw a video card in this (if such a mythical thing can ever be&#xA;found again) and find it a new home.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Running an ARM64 OpenBSD Virtual Machine on Apple Silicon</title>
      <link>https://codeofconnor.com/running-an-arm64-openbsd-virtual-machine-on-apple-silicon-with-qemu/</link>
      <pubDate>Sun, 07 Nov 2021 11:08:48 -0600</pubDate>
      <guid>https://codeofconnor.com/running-an-arm64-openbsd-virtual-machine-on-apple-silicon-with-qemu/</guid>
      <description>&lt;ol&gt;&#xA;&lt;li&gt;&lt;a href=&#34;#install-an-hvf-equipped-build-of-qemu&#34;&gt;Install an HVF-equipped build of QEMU&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;#download-the-openbsd-7-install-image&#34;&gt;Download the OpenBSD 7 Install Image&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;#create-a-new-virtual-disk&#34;&gt;Create a new virtual disk&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;#set-up-the-launch-script&#34;&gt;Set up the launch script&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;#install-openbsd&#34;&gt;Install OpenBSD&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;#set-up-host-to-guest-ssh-access&#34;&gt;Set up host to guest SSH access&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;h1 id=&#34;install-an-hvf-equipped-build-of-qemu&#34;&gt;&#xA;  &lt;a class=&#34;Heading-link u-clickable&#34; href=&#34;https://codeofconnor.com/running-an-arm64-openbsd-virtual-machine-on-apple-silicon-with-qemu/#install-an-hvf-equipped-build-of-qemu&#34;&gt;Install an HVF-equipped build of QEMU&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;The QEMU developers recently merged Apple Silicon support for Apple&amp;rsquo;s&#xA;Hypervisor.Framework virtualization layer. This means that barring&#xA;any complications or removals, the next release tag for QEMU should&#xA;include this support.&lt;/p&gt;&#xA;&lt;p&gt;For more information, see the &lt;a href=&#34;https://wiki.qemu.org/Planning/6.2&#34;&gt;6.2 planning page on the QEMU wiki&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Alas, if you are reading this before the next release of QEMU that&#xA;contains this code is generally available, you will need to build&#xA;QEMU from source.&lt;/p&gt;&#xA;&lt;p&gt;If you use the Homebrew package manager, then this means you already&#xA;have the Xcode build tools installed, so this can be as easy as:&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ git clone https://gitlab.com/qemu-project/qemu.git&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ cd qemu&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ brew install meson&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ brew deps qemu | xargs brew install&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ mkdir builddir&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ cd builddir&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ ../configure --target-list&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;aarch64-softmmu&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ meson compile&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ sudo meson install&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;If the idea of building and installing the bleeding edge of QEMU is&#xA;offensive to you, then you may wish to check out the latest tag and&#xA;apply the Apple Silicon Hypervisor.Framework patches on top of it.&lt;/p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;h1 id=&#34;download-the-openbsd-7-install-image&#34;&gt;&#xA;  &lt;a class=&#34;Heading-link u-clickable&#34; href=&#34;https://codeofconnor.com/running-an-arm64-openbsd-virtual-machine-on-apple-silicon-with-qemu/#download-the-openbsd-7-install-image&#34;&gt;Download the OpenBSD 7 Install Image&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;Create a directory for stashing install media:&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ mkdir -p $HOME/virt/images/base&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;Download the install image and the SHA256 info:&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ cd $HOME/virt/images/base&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ curl -LkO https://cdn.openbsd.org/pub/OpenBSD/7.0/arm64/SHA256&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ curl -LkO https://cdn.openbsd.org/pub/OpenBSD/7.0/arm64/install70.img&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;Always check the checksums:&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ shasum -a &lt;span style=&#34;color:#ae81ff&#34;&gt;256&lt;/span&gt; -c SHA256 --ignore-missing&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;install70.img: OK&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;h1 id=&#34;create-a-new-virtual-disk&#34;&gt;&#xA;  &lt;a class=&#34;Heading-link u-clickable&#34; href=&#34;https://codeofconnor.com/running-an-arm64-openbsd-virtual-machine-on-apple-silicon-with-qemu/#create-a-new-virtual-disk&#34;&gt;Create a new virtual disk&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&#xA;  &#xA;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ qemu-img create -f qcow2 openbsd.qcow2 15G&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;h1 id=&#34;set-up-the-launch-script&#34;&gt;&#xA;  &lt;a class=&#34;Heading-link u-clickable&#34; href=&#34;https://codeofconnor.com/running-an-arm64-openbsd-virtual-machine-on-apple-silicon-with-qemu/#set-up-the-launch-script&#34;&gt;Set up the launch script&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&#xA;  &#xA;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cat &amp;gt; run_openbsd.sh &amp;lt;&amp;lt;EOF&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;#!/usr/bin/env sh&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;qemu-system-aarch64 \&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    -M virt \&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    -accel hvf \&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    -m 2048 \&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    -cpu cortex-a57 -M virt,highmem=off \&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    -drive file=/usr/local/share/qemu/edk2-aarch64-code.fd,if=pflash,format=raw,readonly=on \&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    -drive file=install70.img \&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    -drive file=openbsd.qcow2,format=qcow2 \&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    -nographic \&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    -serial tcp::4444,server,telnet,wait&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;EOF&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;Don&amp;rsquo;t forget to replace the path to your &lt;code&gt;openbsd.qcow2&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Mark it as executable:&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ chmod u+x run_openbsd.sh&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;In the next step you will launch the VM, but won&amp;rsquo;t see anything. That is&#xA;because QEMU is waiting for you to connect over telnet before it proceeds.&lt;/p&gt;&#xA;&lt;p&gt;So, in one window:&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ ./run_openbsd.sh&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;And in another window:&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ telnet localhost &lt;span style=&#34;color:#ae81ff&#34;&gt;4444&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;Wait for the early boot messages to finish and eventually you should see&#xA;something resembling an OpenBSD install prompt.&lt;/p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;h1 id=&#34;install-openbsd&#34;&gt;&#xA;  &lt;a class=&#34;Heading-link u-clickable&#34; href=&#34;https://codeofconnor.com/running-an-arm64-openbsd-virtual-machine-on-apple-silicon-with-qemu/#install-openbsd&#34;&gt;Install OpenBSD&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;This step is up to you! Just remember that once it&amp;rsquo;s finished installing,&#xA;remove the &lt;code&gt;-drive&lt;/code&gt; line in your &lt;code&gt;run_openbsd.sh&lt;/code&gt; that contains the&#xA;&lt;code&gt;install70.img&lt;/code&gt;. You don&amp;rsquo;t need it anymore!&lt;/p&gt;&#xA;&lt;p&gt;Enjoy your super-fast OpenBSD VM on your super-fast Apple Silicon machine!&lt;/p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;h1 id=&#34;set-up-host-to-guest-ssh-access&#34;&gt;&#xA;  &lt;a class=&#34;Heading-link u-clickable&#34; href=&#34;https://codeofconnor.com/running-an-arm64-openbsd-virtual-machine-on-apple-silicon-with-qemu/#set-up-host-to-guest-ssh-access&#34;&gt;Set up host to guest SSH access&lt;/a&gt;&#xA;&lt;/h1&gt;&#xA;&lt;p&gt;You don&amp;rsquo;t have to live your life exclusively over telnet. If you&amp;rsquo;d&#xA;prefer to SSH in at this point, go ahead and add these lines to&#xA;your &lt;code&gt;run_openbsd.sh&lt;/code&gt; launcher script:&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    -net user,hostfwd=tcp::2222-:22 \&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    -net nic \&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;This will forward port 2222 on your host to port 22 in the guest (which&#xA;is the default port that sshd will listen on).&lt;/p&gt;&#xA;&lt;p&gt;In the guest, enable sshd:&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;# rcctl enable sshd&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;# rcctl start sshd&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;On the host, copy your public key over (unless you&amp;rsquo;d rather type the&#xA;password for the user in your guest)&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ ssh-copy-id -i ~/.ssh/id_ed25519 -p &lt;span style=&#34;color:#ae81ff&#34;&gt;2222&lt;/span&gt; ckuehl@localhost&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;Finally, now you can simply SSH into your guest whenever you want:&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-console&#34; data-lang=&#34;console&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ ssh -p &lt;span style=&#34;color:#ae81ff&#34;&gt;2222&lt;/span&gt; ckuehl@localhost&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;</description>
    </item>
    <item>
      <title>A Faster Way to Create Virtual Machines with Cloud Images and virt-manager</title>
      <link>https://codeofconnor.com/a-faster-way-to-create-virtual-machines-with-cloud-images-and-virt-manager/</link>
      <pubDate>Fri, 09 Jul 2021 10:30:05 +0000</pubDate>
      <guid>https://codeofconnor.com/a-faster-way-to-create-virtual-machines-with-cloud-images-and-virt-manager/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve written previously about &lt;a href=&#34;https://codeofconnor.com/booting-cloud-images-with-qemu/&#34;&gt;Booting Cloud Images with QEMU&lt;/a&gt;. However, I&amp;rsquo;ve since graduated to a more convenient method of spawning virtual machines. This method is also much faster and is more cohesive with the rest of the virtualization stack that you&amp;rsquo;ll find on your Linux distribution. As someone who creates and tears down tons of virtual machines for testing things, this method appeals to me more than the previous. Let&amp;rsquo;s get into it.&lt;/p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;h2 id=&#34;download-a-cloud-image&#34;&gt;&#xA;  &lt;a class=&#34;Heading-link u-clickable&#34; href=&#34;https://codeofconnor.com/a-faster-way-to-create-virtual-machines-with-cloud-images-and-virt-manager/#download-a-cloud-image&#34;&gt;Download a Cloud Image&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Ideally, you&amp;rsquo;ll find a download link for a QCOW2 VM image. Otherwise, you&amp;rsquo;ll need to convert it with &lt;code&gt;qemu-img convert&lt;/code&gt;. As before, I will use a Fedora cloud image.&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# mkdir /var/lib/libvirt/images/base&#xA;# cd /var/lib/libvirt/images/base&#xA;# curl -LkO https://download.fedoraproject.org/pub/fedora/linux/releases/34/Cloud/x86_64/images/Fedora-Cloud-Base-34-1.2.x86_64.qcow2&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;h2 id=&#34;generate-a-cloud-init-bootstrap-image&#34;&gt;&#xA;  &lt;a class=&#34;Heading-link u-clickable&#34; href=&#34;https://codeofconnor.com/a-faster-way-to-create-virtual-machines-with-cloud-images-and-virt-manager/#generate-a-cloud-init-bootstrap-image&#34;&gt;Generate a cloud-init bootstrap image&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;This can be a one-time thing, if you don&amp;rsquo;t mind each of your new VMs taking on these settings.&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# mkdir cloud-init-bootstrap&#xA;# cd cloud-init-bootstrap&#xA;# touch meta-data&#xA;# cat &amp;gt; user-data &amp;lt;&amp;lt;EOF&#xA;#cloud-config&#xA;&#xA;system_info:&#xA;  default_user:&#xA;    name: fedora&#xA;&#xA;chpasswd:&#xA;  list: |&#xA;    fedora:password&#xA;  expire: False&#xA;&#xA;resize_rootfs: True&#xA;EOF&#xA;# genisoimage -output seedci.iso -volid cidata -joliet -rock user-data meta-data&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Obviously, if this machine is going to be accessible via the Internet, you&amp;rsquo;ll want to follow some better security practices. For testing kernel changes, it&amp;rsquo;s probably fine.&lt;/p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;h2 id=&#34;create-a-disk-for-the-new-vm&#34;&gt;&#xA;  &lt;a class=&#34;Heading-link u-clickable&#34; href=&#34;https://codeofconnor.com/a-faster-way-to-create-virtual-machines-with-cloud-images-and-virt-manager/#create-a-disk-for-the-new-vm&#34;&gt;Create a disk for the new VM&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;You can just use exactly what was downloaded, however, I like to create a new image which is based on the original download. That way I can create new VM disks without having to re-download or remember to copy and paste the original disks. It&amp;rsquo;s also convenient to make a larger disk than the base image, as cloud-init will take care of resizing the file system for you.&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# qemu-img create -f qcow2 \&#xA;  -b /var/lib/libvirt/images/base/Fedora-Cloud-Base-34-1.2.x86_64.qcow2 \&#xA;  -F qcow2 \&#xA;  /var/lib/libvirt/images/fedora-34.qcow2 15G&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;I tend to name the disk what I intend to name the virtual machine for identification purposes.&lt;/p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;h2 id=&#34;create-the-new-domain-vm&#34;&gt;&#xA;  &lt;a class=&#34;Heading-link u-clickable&#34; href=&#34;https://codeofconnor.com/a-faster-way-to-create-virtual-machines-with-cloud-images-and-virt-manager/#create-the-new-domain-vm&#34;&gt;Create the new domain (VM)&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;&lt;code&gt;virt-manager&lt;/code&gt; comes with a helpful tool called &lt;code&gt;virt-install&lt;/code&gt; to provision a new virtual machine. Note that the shell prompt has switched from root to a user session.&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ virt-install --name fedora-34 --os-variant fedora --vcpus 2 --memory 2048 \&#xA;  --graphics vnc --virt-type kvm --disk /var/lib/libvirt/images/fedora-34.qcow2 \&#xA;  --cdrom /var/lib/libvirt/images/base/cloud-init-bootstrap/seedci.iso&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;h2 id=&#34;enjoy-your-new-virtual-machine&#34;&gt;&#xA;  &lt;a class=&#34;Heading-link u-clickable&#34; href=&#34;https://codeofconnor.com/a-faster-way-to-create-virtual-machines-with-cloud-images-and-virt-manager/#enjoy-your-new-virtual-machine&#34;&gt;Enjoy your new virtual machine&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;The VM may now be managed on the command line with &lt;code&gt;virsh&lt;/code&gt; or with the &lt;code&gt;virt-manager&lt;/code&gt; GUI. You could leave this one in its current state and simply clone new VMs from it whenever you&amp;rsquo;d like a brand-new one, or follow these same steps again for new VMs.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Implementing a Continuous Delivery Pipeline for my Discord Bot with GitHub Actions, podman, and systemd</title>
      <link>https://codeofconnor.com/implementing-a-continuous-delivery-pipeline-for-my-discord-bot-with-github-actions-podman-and-systemd/</link>
      <pubDate>Sun, 04 Jul 2021 15:04:19 +0000</pubDate>
      <guid>https://codeofconnor.com/implementing-a-continuous-delivery-pipeline-for-my-discord-bot-with-github-actions-podman-and-systemd/</guid>
      <description>&lt;p&gt;I&amp;rsquo;ve been having a lot of fun lately refining a weekend project I started a few months ago. I basically threw this bot over the wall back in early April. About a month ago, I started getting serious about learning the Go programming language, so I thought I&amp;rsquo;d just revisit my Discord bot with a more &amp;ldquo;learned&amp;rdquo; eye and find ways to polish it up a bit.&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://github.com/connorkuehl/popple&#34;&gt;Popple&lt;/a&gt; is a Discord bot that I made for myself and my friends, and it has been my playground for practicing everything I was learning in a project with an extremely small blast radius. Actually, the blast radius is both small and sympathetic, since most of my friends in that server are software developers too; so it was easy to laugh about whatever bugs that had made it into the running version of the bot.&lt;/p&gt;&#xA;&lt;p&gt;In any case, I&amp;rsquo;ve been pushing commits to Popple consistently each week (actually, much to my pleasant surprise, a few developers have contributed to closing some of my &lt;a href=&#34;https://github.com/connorkuehl/popple/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22&#34;&gt;&amp;ldquo;good first issues&amp;rdquo;&lt;/a&gt; on the repo too!) This rate of change started to become cumbersome, especially since early on, I didn&amp;rsquo;t have any automation in place for this.&lt;/p&gt;&#xA;&lt;p&gt;My deployments were all manual, and looked like this:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Push changes to the branch I wanted them on&lt;/li&gt;&#xA;&lt;li&gt;SSH into my VPS&lt;/li&gt;&#xA;&lt;li&gt;Run &lt;code&gt;go install github.com/connorkuehl/popple@latest&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;Get annoyed that &lt;code&gt;go-install&lt;/code&gt; seems to be caching the &lt;code&gt;@latest&lt;/code&gt;, and re-run the command pasting in the latest SHA instead of the &lt;code&gt;latest&lt;/code&gt; keyword&amp;hellip;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Yuck!&lt;/p&gt;&#xA;&lt;p&gt;Lucky for me, I was learning about Ansible, so I thought it&amp;rsquo;d be fun to write a playbook. The playbook would fetch the latest changes for the Git ref I passed in as a variable, it would build &amp;amp; install the binary to my PATH, and restart a systemd unit so that the new binary could &amp;ldquo;go live.&amp;rdquo;&lt;/p&gt;&#xA;&lt;p&gt;This playbook was actually an enormous improvement to the manual steps; but there was still a lot to be desired here.&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;I didn&amp;rsquo;t want to keep the Go toolchain or git installed on my server&lt;/li&gt;&#xA;&lt;li&gt;I didn&amp;rsquo;t want the server to have to build the code from source&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;h2 id=&#34;containers-to-the-rescue&#34;&gt;&#xA;  &lt;a class=&#34;Heading-link u-clickable&#34; href=&#34;https://codeofconnor.com/implementing-a-continuous-delivery-pipeline-for-my-discord-bot-with-github-actions-podman-and-systemd/#containers-to-the-rescue&#34;&gt;Containers to the rescue&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Long story short, I wrote a Dockerfile, I made an account on hub.docker.com, and I built and pushed images of the tip of my &lt;code&gt;master&lt;/code&gt; branch as well as the commits I had already tagged as releases.&lt;/p&gt;&#xA;&lt;p&gt;My Dockerfile looks like this:&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;FROM golang:1.15-alpine AS build&#xA;RUN apk add build-base      # for gcc&#xA;RUN mkdir /popple&#xA;ADD . /popple&#xA;WORKDIR /popple&#xA;RUN go build .&#xA;&#xA;FROM alpine:latest&#xA;WORKDIR /root/&#xA;COPY --from=build /popple/popple .&#xA;&#xA;# docker image run --rm -v path/to/db:/root/popple.sqlite \&#xA;#                       -v path/to/token:/root/bot.token \&#xA;#                       image_name&#xA;ENTRYPOINT [&amp;#34;/root/popple&amp;#34;, &amp;#34;-db&amp;#34;, &amp;#34;popple.sqlite&amp;#34;, &amp;#34;-token&amp;#34;, &amp;#34;bot.token&amp;#34;]&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;This Dockerfile takes advantage of what are known as &amp;ldquo;multi-stage builds.&amp;rdquo; The first image can balloon up with all of your build dependencies, but can hand off the final binary to a much slimmer image for actual use.&lt;/p&gt;&#xA;&lt;p&gt;You can see my first image is called &amp;ldquo;build&amp;rdquo; and the second image has a &lt;code&gt;COPY --from=build ...&lt;/code&gt; statement which copies the binary over. Go&amp;rsquo;s static linking is what buys us this convenient one-line copy. The binary is all we need.&lt;/p&gt;&#xA;&lt;p&gt;Then, on my VPS, I could simply: &lt;code&gt;podman run --name popple_bot -d ... docker.io/conkue/popple&lt;/code&gt;&lt;/p&gt;&#xA;&lt;p&gt;It was great! Still rather manual, but this removed the need to keep a Go toolchain and git installed on my VPS. Furthermore, we haven&amp;rsquo;t exactly removed me from the equation here. I still have to build the new image, push it to Docker Hub, SSH in, pull the latest image and bounce the container.&lt;/p&gt;&#xA;&lt;p&gt;Actually, this all sounds &lt;strong&gt;more&lt;/strong&gt; involved than what the process was like before&amp;hellip; but not for long! (And yes, I probably could have just updated the Ansible playbook but I knew we could automate even that step and that&amp;rsquo;s where I was moving towards&amp;hellip; can&amp;rsquo;t stop now!)&lt;/p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;h2 id=&#34;github-actions-wasnt-far-behind&#34;&gt;&#xA;  &lt;a class=&#34;Heading-link u-clickable&#34; href=&#34;https://codeofconnor.com/implementing-a-continuous-delivery-pipeline-for-my-discord-bot-with-github-actions-podman-and-systemd/#github-actions-wasnt-far-behind&#34;&gt;GitHub Actions wasn&amp;rsquo;t far behind&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Lucky for me, automatically pushing a container image from GitHub Actions is already a solved problem.&lt;/p&gt;&#xA;&lt;p&gt;I found a &lt;a href=&#34;https://docs.docker.com/ci-cd/github-actions/&#34;&gt;tutorial on Docker&amp;rsquo;s website for configuring a GitHub Action&lt;/a&gt; that will build and push a container image from the Dockerfile in your git repo.&lt;/p&gt;&#xA;&lt;p&gt;My action looks exactly like this (at least, at the time of this writing):&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;name: Deploy to Docker Hub&#xA;&#xA;on:&#xA;  push:&#xA;    branches: [ master ]&#xA;&#xA;jobs:&#xA;&#xA;  build:&#xA;    runs-on: ubuntu-latest&#xA;    steps:&#xA;    - name: Set up caching&#xA;      uses: actions/cache@v2&#xA;      with:&#xA;        path: /tmp/.buildx-cache&#xA;        key: ${{ runner.os }}-buildx-${{ github.sha }}&#xA;        restore-keys: |&#xA;          ${{ runner.os }}-buildx-&#xA;&#xA;    - name: Check out source code&#xA;      uses: actions/checkout@v2&#xA;&#xA;    - name: Login to Docker Hub&#xA;      uses: docker/login-action@v1&#xA;      with:&#xA;        username: ${{ secrets.DOCKER_HUB_USERNAME }}&#xA;        password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}&#xA;&#xA;    - name: Set up Docker BuildX&#xA;      id: buildx&#xA;      uses: docker/setup-buildx-action@v1&#xA;&#xA;    - name: Ship it&#xA;      id: docker_build&#xA;      uses: docker/build-push-action@v2&#xA;      with:&#xA;        context: ./&#xA;        file: ./Dockerfile&#xA;        push: true&#xA;        tags: ${{ secrets.DOCKER_HUB_USERNAME }}/popple:latest&#xA;        platforms: linux/amd64,linux/arm64&#xA;        cache-from: type=local,src=https://codeofconnor.com/tmp/.buildx-cache&#xA;        cache-to: type=local,dest=/tmp/.buildx-cache&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Okay, so far, this is fantastic, but I&amp;rsquo;ve only removed the need for me to build and push the container images from my local machine, but that&amp;rsquo;s a step in the right direction.&lt;/p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;h2 id=&#34;podman-auto-update-would-like-to-join-the-party&#34;&gt;&#xA;  &lt;a class=&#34;Heading-link u-clickable&#34; href=&#34;https://codeofconnor.com/implementing-a-continuous-delivery-pipeline-for-my-discord-bot-with-github-actions-podman-and-systemd/#podman-auto-update-would-like-to-join-the-party&#34;&gt;podman-auto-update would like to join the party&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Actually, before I even knew about &lt;code&gt;podman-auto-update(1)&lt;/code&gt;, I had generated a systemd unit for my Podman container, but I am going to reorder the events so that I look like a capable and highly observant blog author.&lt;/p&gt;&#xA;&lt;p&gt;Turns out, &lt;code&gt;podman&lt;/code&gt; will check to see if there&amp;rsquo;s a new version of your container image for you. All you have to do is ask. The &lt;code&gt;podman&lt;/code&gt; version shipped in the Enterprise Linux distribution that runs on my VPS doesn&amp;rsquo;t seem new enough to support the &lt;code&gt;registry&lt;/code&gt; value for the &lt;code&gt;io.containers.autoupdate&lt;/code&gt; label; which appears to be favored by the documentation. I would have used that if it did. It &lt;strong&gt;does&lt;/strong&gt; support the &lt;code&gt;image&lt;/code&gt; value, so:&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ podman run --label=&amp;#34;io.containers.autoupdate=image&amp;#34; -d --name popple_bot \&#xA;  ...snipped \&#xA;  docker.io/conkue/popple:latest&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;&lt;code&gt;podman-auto-update(1)&lt;/code&gt; is aware of the fact that many might choose to have &lt;code&gt;systemd&lt;/code&gt; start or stop their containers, and so it will behave nicely, especially once you see the next step where we generate a &lt;code&gt;systemd&lt;/code&gt; unit for this bot.&lt;/p&gt;&#xA;&lt;p&gt;But first, let&amp;rsquo;s enable and start the &lt;code&gt;systemd&lt;/code&gt; timer that &lt;code&gt;podman&lt;/code&gt; ships, so that auto-updates are indeed automatic:&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ systemctl enable podman-auto-update&#xA;$ systemctl start podman-auto-update&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;The timer will automatically check for updates once per day at midnight, but this can be tweaked according to the &lt;code&gt;podman-auto-update(1)&lt;/code&gt; man page. I&amp;rsquo;ll probably leave it at the default setting to see how it feels, especially since I&amp;rsquo;ve pretty much added everything I want to add to the bot.&lt;/p&gt;&#xA;&lt;p&gt;You can also just SSH in and run &lt;code&gt;podman auto-update&lt;/code&gt; if you want to trigger this early.&lt;/p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;h2 id=&#34;giving-a-daemon-the-reigns&#34;&gt;&#xA;  &lt;a class=&#34;Heading-link u-clickable&#34; href=&#34;https://codeofconnor.com/implementing-a-continuous-delivery-pipeline-for-my-discord-bot-with-github-actions-podman-and-systemd/#giving-a-daemon-the-reigns&#34;&gt;Giving a daemon the reigns&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;I like setting up &lt;code&gt;systemd&lt;/code&gt; units so that when my VPS is restarted everything comes back up nicely. &lt;code&gt;podman&lt;/code&gt; makes this easy too:&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ podman generate systemd --new --name popple_bot &amp;gt; /etc/systemd/system/popple.service&#xA;$ systemctl daemon-reload&#xA;$ systemctl enable popple&#xA;$ systemctl start popple&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;h2 id=&#34;conclusion&#34;&gt;&#xA;  &lt;a class=&#34;Heading-link u-clickable&#34; href=&#34;https://codeofconnor.com/implementing-a-continuous-delivery-pipeline-for-my-discord-bot-with-github-actions-podman-and-systemd/#conclusion&#34;&gt;Conclusion&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Let&amp;rsquo;s take a look at my to do list:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;del&gt;I don&amp;rsquo;t want to manually SSH in to the VPS to deploy&lt;/del&gt;* &lt;code&gt;podman-auto-update&lt;/code&gt; will automatically pull the latest bot container image&lt;/li&gt;&#xA;&lt;li&gt;&lt;del&gt;I don&amp;rsquo;t want to manually build and push container images&lt;/del&gt;* GitHub Actions will automatically push a new image of the latest commit to &lt;code&gt;master&lt;/code&gt; when I push&lt;/li&gt;&#xA;&lt;li&gt;&lt;del&gt;I don&amp;rsquo;t want my VPS to have git or the Go toolchain installed&lt;/del&gt;* Just needs &lt;code&gt;podman&lt;/code&gt;!&lt;/li&gt;&#xA;&lt;li&gt;&lt;del&gt;I want my bot to automatically come back up when the server restarts&lt;/del&gt;* &lt;code&gt;podman&lt;/code&gt; generated a &lt;code&gt;systemd&lt;/code&gt; unit for me!&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Sweet! All in a morning&amp;rsquo;s work. Now I can just kick back, push some commits, and watch the changes roll out automatically.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>How I use Thunderbird to Write Emails and Review Patches</title>
      <link>https://codeofconnor.com/how-i-use-thunderbird-to-write-emails-and-review-patches/</link>
      <pubDate>Wed, 21 Apr 2021 18:17:29 +0000</pubDate>
      <guid>https://codeofconnor.com/how-i-use-thunderbird-to-write-emails-and-review-patches/</guid>
      <description>&lt;p&gt;Regardless of how one might feel about patches-over-email software development, the reality is that a lot of exciting open source projects are developed on mailing lists. Configuring a pleasant plaintext-oriented e-mail environment may not be obvious for those of us who come from a primarily git forge style background. At least, it wasn&amp;rsquo;t for me. In any case, I&amp;rsquo;ve finally arrived at a productive setup and I&amp;rsquo;d like to write it down here for posterity.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Note: This is not a guide to sending git patches with Thunderbird. I use &lt;a href=&#34;https://git-send-email.io/&#34;&gt;git-send-email(1)&lt;/a&gt; exclusively for that.&lt;/strong&gt;&lt;/p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;h2 id=&#34;initial-configuration&#34;&gt;&#xA;  &lt;a class=&#34;Heading-link u-clickable&#34; href=&#34;https://codeofconnor.com/how-i-use-thunderbird-to-write-emails-and-review-patches/#initial-configuration&#34;&gt;Initial Configuration&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;The first thing I do after installing Thunderbird is apply &lt;a href=&#34;https://www.kernel.org/doc/html/latest/process/email-clients.html#thunderbird-gui&#34;&gt;Linux kernel&amp;rsquo;s recommended settings&lt;/a&gt;. After that, I select &amp;ldquo;Edit&amp;rdquo; &amp;gt; &amp;ldquo;Account Settings&amp;rdquo; and modify the following:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;em&gt;&amp;ldquo;Copies and Folders&amp;rdquo; tab&lt;/em&gt;: Select &amp;ldquo;Place replies in the folder of the message being replied to&amp;rdquo;&lt;/li&gt;&#xA;&lt;li&gt;&lt;em&gt;&amp;ldquo;Composition &amp;amp; Addressing&amp;rdquo; tab&lt;/em&gt;: De-select &amp;ldquo;compose messages in HTML format; and for the &amp;ldquo;When quoting,&amp;rdquo; dropdown menu, select &amp;ldquo;start my reply below the quote&amp;rdquo;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;&lt;strong&gt;Note: As of this writing, the recommended &amp;ldquo;exteditor&amp;rdquo; Thunderbird addon is not compatible with the latest version of Thunderbird. I describe how I cope with this later on in the post.&lt;/strong&gt;&lt;/p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;h2 id=&#34;reviewing-patches&#34;&gt;&#xA;  &lt;a class=&#34;Heading-link u-clickable&#34; href=&#34;https://codeofconnor.com/how-i-use-thunderbird-to-write-emails-and-review-patches/#reviewing-patches&#34;&gt;Reviewing Patches&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;For a one-time setup, install the &lt;a href=&#34;https://addons.thunderbird.net/en-US/thunderbird/addon/colored-diffs/&#34;&gt;Colored Diffs&lt;/a&gt; Thunderbird addon. It will apply git-diff coloring to email messages if it detects a diff in the body of the message.&lt;/p&gt;&#xA;&lt;p&gt;Depending on the scope of the change, I may choose to review the patch exclusively within Thunderbird by reading the diff, or I may choose to apply the patches locally to my git tree to play around with them. The aforementioned Colored Diffs addon makes the former much more pleasant.&lt;/p&gt;&#xA;&lt;p&gt;If there&amp;rsquo;s just a few patches, I might open a terminal window and navigate to my git tree. Once I&amp;rsquo;ve done that, I&amp;rsquo;ll right-click the first patch &amp;gt; Select &amp;ldquo;Copy to Clipboard&amp;rdquo; &amp;gt; Select &amp;ldquo;Message&amp;rdquo;. Then I can apply the patch:&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ git am -&#xA;&amp;lt;PASTE&amp;gt;&#xA;^D&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;I&amp;rsquo;ll repeat that for each patch.&lt;/p&gt;&#xA;&lt;p&gt;This doesn&amp;rsquo;t scale well for more than 2 to 3 patches though. If there are multiple patches, I will select each patch and then right-click &amp;gt; &amp;ldquo;Save selected messages&amp;rdquo; &amp;gt; &amp;ldquo;As mbox file (new)&amp;rdquo; &amp;gt; Save. Then, in a terminal window, I can navigate to my git tree and just &lt;code&gt;git am /tmp/patch/to/patches.mbox&lt;/code&gt;.&lt;/p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;h3 id=&#34;late-to-the-party&#34;&gt;&#xA;  &lt;a class=&#34;Heading-link u-clickable&#34; href=&#34;https://codeofconnor.com/how-i-use-thunderbird-to-write-emails-and-review-patches/#late-to-the-party&#34;&gt;Late to the party?&lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;If you subscribed to a mailing list and you stumble upon some patches or messages that occurred prior to your subscription but you still want to participate in, not all is lost. Many mailing list archives let you download previous emails, usually bundled by the month they were sent.&lt;/p&gt;&#xA;&lt;p&gt;I use the &lt;a href=&#34;https://addons.thunderbird.net/en-US/thunderbird/addon/importexporttools-ng/&#34;&gt;ImportExportTools NG&lt;/a&gt; Thunderbird addon to import emails from the archives into Thunderbird. Once they&amp;rsquo;ve been imported, you can reply or read them at your leisure. I&amp;rsquo;ve found this to be a good way to get caught up on mailing lists. For reading a short thread or two, the HTML archive view is fine; for anything more involved than that, it&amp;rsquo;s more comfortable to read inside of Thunderbird, in my opinion.&lt;/p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;h2 id=&#34;composing-emails&#34;&gt;&#xA;  &lt;a class=&#34;Heading-link u-clickable&#34; href=&#34;https://codeofconnor.com/how-i-use-thunderbird-to-write-emails-and-review-patches/#composing-emails&#34;&gt;Composing Emails&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;For a while, I would use the regular editor that comes with Thunderbird. However, it can be hard to consistently indent things or keep the width of my paragraphs to a consistent level in the editor since it doesn&amp;rsquo;t include line or column information. I also prefer using Vim or Vim keybindings to edit text.&lt;/p&gt;&#xA;&lt;p&gt;Since the exteditor addon is incompatible with newer versions of Thunderbird, I just open a terminal emulator and type my messages up in Vim. If I am replying to an email and I want to quote parts of it, I will just cut and paste the contents of the Thunderbird editor into Vim, then I put Vim into email mode:&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;:set filetype=mail colorcolumn=72&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Or, to spend less time typing:&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;:set ft=mail cc=72&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;This way the syntax highlighting is much nicer with quoted snippets and I can keep the widths of my paragraphs consistent with the color column or column information that Vim shows me. Furthermore, with the filetype set to &amp;ldquo;mail&amp;rdquo;, Vim will automatically break to a new line when I threaten to exceed the conventional 72 character width of plaintext emails.&lt;/p&gt;&#xA;&lt;p&gt;When I am ready to send my email, I just copy the contents of my Vim buffer and paste it into the Thunderbird editor and hit &amp;ldquo;Send&amp;rdquo;.&lt;/p&gt;&#xA;&lt;p&gt;The xclip(1) package is very useful for this. To copy the entirety of my Vim buffer, all I have to do is type:&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;:w !xclip -sel clip&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Then select the Thunderbird editor and hit ctrl+V!&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Note: I don&amp;rsquo;t have a suggestion for a Windows or macOS equivalent to the xclip(1) tool. Sorry! If you know of one, let me know and I can update this post.&lt;/strong&gt;&lt;/p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;h2 id=&#34;conclusion&#34;&gt;&#xA;  &lt;a class=&#34;Heading-link u-clickable&#34; href=&#34;https://codeofconnor.com/how-i-use-thunderbird-to-write-emails-and-review-patches/#conclusion&#34;&gt;Conclusion&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;That just about sums up everything about my Thunderbird workflow that makes me feel productive.&lt;/p&gt;&#xA;&lt;p&gt;There is no reward for reading a blog post as banal as this. 🙂&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Tips for Improving the Quality of Your Code Reviews</title>
      <link>https://codeofconnor.com/tips-for-improving-the-quality-of-your-code-reviews/</link>
      <pubDate>Mon, 19 Apr 2021 08:16:00 +0000</pubDate>
      <guid>https://codeofconnor.com/tips-for-improving-the-quality-of-your-code-reviews/</guid>
      <description>&#xA;&#xA;&#xA;&#xA;&lt;h2 id=&#34;check-out-the-changes-locally&#34;&gt;&#xA;  &lt;a class=&#34;Heading-link u-clickable&#34; href=&#34;https://codeofconnor.com/tips-for-improving-the-quality-of-your-code-reviews/#check-out-the-changes-locally&#34;&gt;Check out the changes locally&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;For everything but the most trivial of patches, check change out locally. Not only is this a technical prerequisite for some of the other tips in this article, but I&amp;rsquo;ve found it is easier to remain focused on the review when it takes place outside of my email inbox/GitHub/Gitlab/etc.&lt;/p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;h2 id=&#34;use-more-context-when-viewing-changes&#34;&gt;&#xA;  &lt;a class=&#34;Heading-link u-clickable&#34; href=&#34;https://codeofconnor.com/tips-for-improving-the-quality-of-your-code-reviews/#use-more-context-when-viewing-changes&#34;&gt;Use more context when viewing changes&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;The default context for a diff is rather narrow. It will show lines added and removed next to only a few other lines of the code. This can reduce the efficacy of the review unless you would consider yourself already intimately familiar with the surrounding codebase without having it in front of you.&lt;/p&gt;&#xA;&lt;p&gt;If you&amp;rsquo;re on the command line, the &lt;code&gt;--unified=&amp;lt;n&amp;gt;&lt;/code&gt; or &lt;code&gt;-U&amp;lt;n&amp;gt;&lt;/code&gt; flag can be passed to &lt;code&gt;git-diff(1)&lt;/code&gt; and &lt;code&gt;git-show(1)&lt;/code&gt; to show more context for the diff. Some graphical git tools show the entire context by default. I do almost everything git-related on the command line, but lately I have been using the &lt;code&gt;gitk&lt;/code&gt; GUI client when reviewing patches because it shows the entire context for each patch and makes it easy for me to jump around from patch to patch.&lt;/p&gt;&#xA;&lt;p&gt;This lets me see the entire contents of the files that the diff modifies. Expanding the context makes it particularly easy to see:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;if the change is stylistically consistent with the rest of the code;&lt;/li&gt;&#xA;&lt;li&gt;if there are any existing abstractions (types, functions, constants, etc) that could have been leveraged to better accomplish this change, but weren&amp;rsquo;t;&lt;/li&gt;&#xA;&lt;li&gt;if some other part of the code should have been updated to take advantage of this change, but wasn&amp;rsquo;t;&lt;/li&gt;&#xA;&lt;li&gt;if there&amp;rsquo;s a better home for this change&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;The list above is a great way to better familiarize yourself with the code while performing code reviews.&lt;/p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;h2 id=&#34;exercise-it-yourself&#34;&gt;&#xA;  &lt;a class=&#34;Heading-link u-clickable&#34; href=&#34;https://codeofconnor.com/tips-for-improving-the-quality-of-your-code-reviews/#exercise-it-yourself&#34;&gt;Exercise it yourself&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Hitting the code paths that the patches modify is especially critical if the change didn&amp;rsquo;t come without any automated unit, integration, or end-to-end tests. If this is the case, should it have come with automated tests? Probably. This is very dependent on the thing you&amp;rsquo;re working on.&lt;/p&gt;&#xA;&lt;p&gt;There are a number of benefits to figuring out how to exercise these code paths:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;It allows you to learn or verify what you know about the &amp;ldquo;flow&amp;rdquo; of the code. What modules/types/subsystems are crossed from start to finish to reach this code path? How do they communicate?&lt;/li&gt;&#xA;&lt;li&gt;It may help you spot potential improvements to the patch or confirm its correctness.&lt;/li&gt;&#xA;&lt;li&gt;If it is a customer/user visible change, try to empathize with them. What is the experience like for a non-technical user compared to a technical user?&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;h2 id=&#34;determine-the-changes-discoverability-if-applicable&#34;&gt;&#xA;  &lt;a class=&#34;Heading-link u-clickable&#34; href=&#34;https://codeofconnor.com/tips-for-improving-the-quality-of-your-code-reviews/#determine-the-changes-discoverability-if-applicable&#34;&gt;Determine the change&amp;rsquo;s discoverability (if applicable)&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Should this change include documentation? If so, does it already? If it&amp;rsquo;s a new subcommand to a command line tool, is it added to the tool&amp;rsquo;s usage or &lt;code&gt;--help&lt;/code&gt; output? Is this an addition to some API that is meant to be consumed by other services or developers? Where will they learn about its existence?&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>sevctl available soon in Fedora 34</title>
      <link>https://codeofconnor.com/sevctl-available-soon-in-fedora-34/</link>
      <pubDate>Thu, 08 Apr 2021 08:25:00 +0000</pubDate>
      <guid>https://codeofconnor.com/sevctl-available-soon-in-fedora-34/</guid>
      <description>&lt;p&gt;I am pleased to announce that &lt;a href=&#34;https://github.com/enarx/sevctl&#34;&gt;sevctl&lt;/a&gt; will be available in the Fedora repositories starting with Fedora 34. Fedora is the first distribution to include sevctl in its repositories 🎉.&lt;/p&gt;&#xA;&lt;p&gt;sevctl is an administrative utility for managing the AMD Secure Encrypted Virtualization (SEV) platform, which is available on AMD&amp;rsquo;s EPYC processors. It makes many routine AMD SEV tasks quite easy, such as:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Generating, exporting, and verifying a certificate chain&lt;/li&gt;&#xA;&lt;li&gt;Displaying information about the SEV platform&lt;/li&gt;&#xA;&lt;li&gt;Resetting the platform&amp;rsquo;s persistent state&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;As of this writing, Fedora 34 is &lt;a href=&#34;https://fedorapeople.org/groups/schedule/f-34/f-34-key-tasks.html&#34;&gt;entering its final freeze&lt;/a&gt;, but sevctl is &lt;a href=&#34;https://bodhi.fedoraproject.org/updates/FEDORA-2021-dbfe1805fb&#34;&gt;queued for inclusion&lt;/a&gt; once Fedora 34 thaws. sevctl is already available in Fedora Rawhide for immediate use.&lt;/p&gt;&#xA;&lt;p&gt;Please submit all bug reports, patches, and feature requests to &lt;a href=&#34;https://github.com/enarx/sevctl&#34;&gt;sevctl&amp;rsquo;s upstream repository on GitHub&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Binary RPMs can be found in the &lt;a href=&#34;https://src.fedoraproject.org/rpms/rust-sevctl&#34;&gt;rust-sevctl packaging repo&lt;/a&gt;.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Booting Cloud Images with QEMU</title>
      <link>https://codeofconnor.com/booting-cloud-images-with-qemu/</link>
      <pubDate>Sun, 24 Jan 2021 10:31:46 +0000</pubDate>
      <guid>https://codeofconnor.com/booting-cloud-images-with-qemu/</guid>
      <description>&lt;p&gt;Do you ever get frustrated with waiting for a heavy VM image to download or with installing operating systems onto virtual machines manually? It can start to feel cumbersome after a while, especially if you bring up and tear down lots of virtual machines as part of your workflow. It&amp;rsquo;d be nice if spawning a ready-to-use VM was as quick and as easy as it is when using a public cloud.&lt;/p&gt;&#xA;&lt;p&gt;Good news! It can be. Many Linux distributions (and BSDs) publish what are known as &amp;ldquo;cloud images&amp;rdquo; for use in virtual machines. These images are purposefully tiny and contain the bare minimum for what is required to provision a virtual machine.&lt;/p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;h2 id=&#34;create-a-directory-structure-for-your-vms&#34;&gt;&#xA;  &lt;a class=&#34;Heading-link u-clickable&#34; href=&#34;https://codeofconnor.com/booting-cloud-images-with-qemu/#create-a-directory-structure-for-your-vms&#34;&gt;Create a directory structure for your VMs&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;This is up to you, however, here&amp;rsquo;s mine (I like to keep it simple):&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ mkdir -p $HOME/VM/images/base&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;h2 id=&#34;download-a-cloud-base-image&#34;&gt;&#xA;  &lt;a class=&#34;Heading-link u-clickable&#34; href=&#34;https://codeofconnor.com/booting-cloud-images-with-qemu/#download-a-cloud-base-image&#34;&gt;Download a cloud base image&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;I&amp;rsquo;ll use a &lt;a href=&#34;https://alt.fedoraproject.org/cloud/&#34;&gt;Fedora Cloud Base Image&lt;/a&gt; for the virtual machines, but like the previous step, you can find your own cloud image from your favorite OS distribution. Distributions typically do a good job of explicitly pointing out which download is a cloud image, so I don&amp;rsquo;t think you&amp;rsquo;ll have much trouble finding one.&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ curl -Lk https://download.fedoraproject.org/pub/fedora/linux/releases/33/Cloud/x86_64/images/Fedora-Cloud-Base-33-1.2.x86_64.qcow2 &amp;gt; $HOME/VM/images/base/fedora_cloud_base_33.qcow2&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;The important thing here is that the cloud image is a &lt;strong&gt;qcow2&lt;/strong&gt; image. If it&amp;rsquo;s a &lt;strong&gt;raw&lt;/strong&gt; image, you&amp;rsquo;ll have to convert it like so:&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ qemu-img convert -f raw -O qcow2 path_to_raw_image.raw new_filename_for_qcow2_image.qcow2&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;h2 id=&#34;create-a-new-image-from-the-cloud-base-image&#34;&gt;&#xA;  &lt;a class=&#34;Heading-link u-clickable&#34; href=&#34;https://codeofconnor.com/booting-cloud-images-with-qemu/#create-a-new-image-from-the-cloud-base-image&#34;&gt;Create a new image from the cloud base image&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;I usually make a new folder where I can put artifacts related to my individual VMs as needed:&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ mkdir $HOME/VM/fedora_33&#xA;$ qemu-img create -f qcow2 -b $HOME/VM/images/base/fedora_cloud_base_33.qcow2 $HOME/VM/fedora_33/hdd.qcow2 8G&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Deriving a new image from the base image is a neat trick because it allows us to create new VM images from the base image without having to download a new image again.&lt;/p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;h2 id=&#34;create-a-cloud-init-bootstrap-image&#34;&gt;&#xA;  &lt;a class=&#34;Heading-link u-clickable&#34; href=&#34;https://codeofconnor.com/booting-cloud-images-with-qemu/#create-a-cloud-init-bootstrap-image&#34;&gt;Create a cloud-init bootstrap image&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;The cloud image that we&amp;rsquo;re using requires &lt;code&gt;cloud-init&lt;/code&gt; to perform some first-time setup. I&amp;rsquo;m trying to use only the bare minimum configuration to bring up a virtual machine here, but curious readers should head over to &lt;code&gt;cloud-init&lt;/code&gt;&amp;rsquo;s documentation if they&amp;rsquo;d like to learn other ways that it can be used to customize their virtual machine.&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ cd $HOME/VM/fedora_33&#xA;$ touch meta-data&#xA;$ cat &amp;gt; user-data &amp;lt;&amp;lt; EOF&#xA;#cloud-config&#xA;&#xA;system_info:&#xA;   default_user:&#xA;     name: fedora&#xA;&#xA;chpasswd:&#xA;  list: |&#xA;    fedora:password&#xA;  expire: False&#xA; &#xA;&#xA;resize_rootfs: True&#xA;ssh_authorized_keys:&#xA;   - ssh-rsa AAAAB3Nza...&#xA;EOF&#xA;$ genisoimage -output seedci.iso -volid cidata -joliet -rock user-data meta-data&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;h2 id=&#34;launch-your-virtual-machine&#34;&gt;&#xA;  &lt;a class=&#34;Heading-link u-clickable&#34; href=&#34;https://codeofconnor.com/booting-cloud-images-with-qemu/#launch-your-virtual-machine&#34;&gt;Launch your virtual machine&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Here is a super minimal QEMU command line for launching your new VM! You&amp;rsquo;ll want to supplement this with more arguments to fit your needs (i.e., &lt;code&gt;-accel&lt;/code&gt;, &lt;code&gt;-cpu&lt;/code&gt;, etc).&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ qemu-system-x86_64 -m 512 -drive file=hdd.qcow2 -cdrom seedci.iso&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Log in with the user account that was described in the &lt;code&gt;user-data&lt;/code&gt; file. In the case of this article, the username is &lt;code&gt;fedora&lt;/code&gt; and the password is &lt;code&gt;password&lt;/code&gt;. Obviously this configuration is mainly for cheap, disposable VMs that will be used for local development/testing. This configuration is unsuitable for a production environment.&lt;/p&gt;&#xA;&lt;p&gt;You can remove the &lt;code&gt;-cdrom&lt;/code&gt; argument from the command line for subsequent launches because cloud-init will only use it for the instance&amp;rsquo;s first boot to configure things.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>How Rust&#39;s Type Checker Helped Find a Bug in a Linux Kernel ioctl Definition</title>
      <link>https://codeofconnor.com/how-rusts-type-checker-helped-find-a-bug-in-a-linux-kernel-ioctl-definition/</link>
      <pubDate>Sat, 05 Dec 2020 11:45:27 +0000</pubDate>
      <guid>https://codeofconnor.com/how-rusts-type-checker-helped-find-a-bug-in-a-linux-kernel-ioctl-definition/</guid>
      <description>&lt;p&gt;Don&amp;rsquo;t you love it when your compiler thinks hard so you don&amp;rsquo;t have to? Rust&amp;rsquo;s built-in static analysis is praised for providing all kinds of safety guarantees for your code. Today, it&amp;rsquo;s not about your code, or even my code; it&amp;rsquo;s about how calling Linux &lt;code&gt;ioctl&lt;/code&gt;s through a type-safe abstraction layer exposed a bug in an &lt;code&gt;ioctl&lt;/code&gt; definition and Rust&amp;rsquo;s type-checker was the first one to bark about it!&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://github.com/enarx/iocuddle/&#34;&gt;&lt;code&gt;iocuddle&lt;/code&gt;&lt;/a&gt; is a library for improving the safety of &lt;code&gt;ioctl&lt;/code&gt; calls from Rust. But what&amp;rsquo;s so unsafe about &lt;code&gt;ioctl&lt;/code&gt;s that we need a crate for it in the first place? The Linux kernel&amp;rsquo;s &lt;code&gt;ioctl&lt;/code&gt; mechanism is a minimal interface that allows Linux module developers to provide APIs to userspace that don&amp;rsquo;t necessarily fit the mold of the primary module classes: &lt;code&gt;char&lt;/code&gt;, &lt;code&gt;block&lt;/code&gt;, and &lt;code&gt;net&lt;/code&gt;. To this end, the &lt;code&gt;ioctl&lt;/code&gt; function definition must be broad enough to avoid constraining the interfaces that module authors can expose.&lt;/p&gt;&#xA;&lt;p&gt;Let&amp;rsquo;s take a look:&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;#include &amp;lt;sys/ioctl.h&amp;gt;&#xA;&#xA;int ioctl(int fd, unsigned long request, ...);&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Most readers will probably agree that this minimal interface allows for a large number of valid inputs. In fact, there&amp;rsquo;s not much out there that this function &lt;strong&gt;won&amp;rsquo;t accept&lt;/strong&gt; at compile-time or at run-time. As a result, the kernel only needs one syscall instead of allowing driver developers to add zillions for their specific module.&lt;/p&gt;&#xA;&lt;p&gt;Unfortunately for userspace programmers, this means a great deal of care must be taken for each &lt;code&gt;ioctl&lt;/code&gt; call site to ensure the correct data are passed in. If a mistake is made, it won&amp;rsquo;t be discovered until run-time.&lt;/p&gt;&#xA;&lt;p&gt;Readers who are already familiar with the Rust programming language may already know that, among other things, Rust ensures type-safety with the ferocity of one thousand suns. Furthermore, most programmers in general would probably agree that they&amp;rsquo;d rather be notified of an error at compile-time rather than run-time.&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;iocuddle&lt;/code&gt; exists to help provide these guarantees to Rust applications and libraries. It shifts the care that must be taken from each call site and funnels it into one place: the initial &lt;code&gt;iocuddle::Ioctl&lt;/code&gt; definition. It is here that the crate author must ensure they&amp;rsquo;ve found the correct &lt;code&gt;ioctl&lt;/code&gt; type from the Linux headers and that they understand the &lt;code&gt;ioctl&lt;/code&gt;&amp;rsquo;s &amp;ldquo;direction&amp;rdquo; (though this is also found in the Linux headers). After that, the Rust definition of the &lt;code&gt;ioctl&lt;/code&gt; is complete, and client code can enjoy compile-time type-checking of what they&amp;rsquo;re sending in to an &lt;code&gt;ioctl&lt;/code&gt; call.&lt;/p&gt;&#xA;&lt;p&gt;And yet&amp;hellip; it wasn&amp;rsquo;t working for me! The internet lied to me! Rust is no panacea!1!1 (joking, joking&amp;hellip;)&lt;/p&gt;&#xA;&lt;p&gt;Despite quadruple checking that I had the right &lt;code&gt;ioctl&lt;/code&gt; type:&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;const KVM: Group = Group::new(0xAE);&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;against the Linux kernel&amp;rsquo;s:&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;uapi/linux/kvm.h:#define KVMIO_0xAE&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;as well as checking that I had the right &lt;code&gt;ioctl&lt;/code&gt; number:&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;pub const PIN: Ioctl&amp;lt;Write, &amp;amp;Command&amp;lt;Pin&amp;gt;&amp;gt; = unsafe { ENC_OP.write(0xbb) };&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;against the Linux kernel&amp;rsquo;s:&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;uapi/linux/kvm.h:KVM_MEMORY_ENCRYPT_REG_REGION   _IOR(KVMIO, 0xbb, struct kvm_enc_region)&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;My &lt;code&gt;ioctl&lt;/code&gt; call was failing with &lt;code&gt;ENOTTY&lt;/code&gt;, no appropriate &lt;code&gt;ioctl&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Have you spotted the bug? Oops. I expressed the &lt;code&gt;iocuddle::Ioctl&lt;/code&gt; as a &lt;code&gt;Write&lt;/code&gt; direction (&lt;code&gt;_IOW&lt;/code&gt;) &lt;code&gt;ioctl&lt;/code&gt;, not &lt;code&gt;_IOR&lt;/code&gt; as it&amp;rsquo;s shown here in the Linux kernel source. Silly me! Under the covers, &lt;code&gt;iocuddle&lt;/code&gt; performs the same &lt;code&gt;ioctl&lt;/code&gt; construction as the &lt;code&gt;ioctl&lt;/code&gt; constructor macros. So indeed, mislabeling the &lt;code&gt;ioctl&lt;/code&gt; direction will result in an &lt;code&gt;ioctl&lt;/code&gt; with &lt;code&gt;ENOTTY&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;I had been debugging for a while, and was getting tired, so I just quickly changed the direction in my &lt;code&gt;iocuddle::Ioctl&lt;/code&gt; definition and found my program fails to compile. More specifically, I&amp;rsquo;m unable to define this &lt;code&gt;Ioctl&lt;/code&gt; such that I can send in my own data that the kernel needs to fulfill my request! That can&amp;rsquo;t be right, according to the documentation for this &lt;code&gt;ioctl&lt;/code&gt;, I&amp;rsquo;m supposed to supply information:&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;4.111 KVM_MEMORY_ENCRYPT_REG_REGION&#xA;&#xA;Capability: basic&#xA;Architectures: x86&#xA;Type: system&#xA;Parameters: struct kvm_enc_region (in)&#xA;Returns: 0 on success; -1 on error&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Readers who are already familiar with Linux &lt;code&gt;ioctls&lt;/code&gt; may already agree that &lt;code&gt;iocuddle&lt;/code&gt;&amp;rsquo;s types weren&amp;rsquo;t wrong, they just weren&amp;rsquo;t letting me express an illegal state. Why is this an illegal state? Let&amp;rsquo;s look into &lt;code&gt;ioctl&lt;/code&gt; directions a little more closely:&lt;/p&gt;&#xA;&lt;p&gt;The Linux kernel &lt;code&gt;ioctl&lt;/code&gt; definitions are built using macros that provide hints as to the direction that data flows during the course of the &lt;code&gt;ioctl&lt;/code&gt; call:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;_IOR&lt;/code&gt; indicates that userspace will read data &lt;strong&gt;from&lt;/strong&gt; the kernel. Generally, this means that userspace provides some buffer or struct for the kernel to write to, and if the &lt;code&gt;ioctl&lt;/code&gt; succeeded, then userspace can inspect the contents of that buffer or struct for whatever information the &lt;code&gt;ioctl&lt;/code&gt; promised to give them.&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;_IOW&lt;/code&gt; indicates that userspace will &lt;strong&gt;supply data to&lt;/strong&gt; the kernel. If the &lt;code&gt;ioctl&lt;/code&gt; succeeds, then userspace can assume that the kernel took whatever actions were described in the &lt;code&gt;ioctl&lt;/code&gt; and its relevant buffers.&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;_IOWR&lt;/code&gt; indicates that data can flow in both directions for this &lt;code&gt;ioctl&lt;/code&gt;. This is quite common, as one &lt;code&gt;ioctl&lt;/code&gt; may be used to multiplex different commands. Often times, the data passed in to the kernel is a struct that describes what subcommand to execute during the course of the &lt;code&gt;ioctl&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;So really, &lt;code&gt;iocuddle&lt;/code&gt;&amp;rsquo;s types were forcing me to define types that make sense for the semantics of the &lt;code&gt;ioctl&lt;/code&gt;. A &lt;code&gt;Read&lt;/code&gt; direction &lt;code&gt;iocuddle::Ioctl&lt;/code&gt; has no business allowing the caller to send in its own buffer. The &lt;code&gt;iocuddle::Ioctl&lt;/code&gt; will zero out &lt;strong&gt;its own buffer&lt;/strong&gt; and let the kernel scribble on it before handing that to the caller.&lt;/p&gt;&#xA;&lt;p&gt;Indeed, everything about the documentation for this &lt;code&gt;ioctl&lt;/code&gt; suggests that data flows from userspace into the kernel, and while Rust&amp;rsquo;s type-checker didn&amp;rsquo;t point that out in so many words, it&amp;rsquo;s the only thing at compile-time that pointed out something is wrong with this &lt;code&gt;ioctl&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;While it is a great emotional victory to know that this &lt;code&gt;ioctl&lt;/code&gt; should have been constructed with &lt;code&gt;_IOW&lt;/code&gt;, it is a futile one, for the &lt;code&gt;ioctl&lt;/code&gt; has already been etched into the stone tablet that is the Linux syscall ABI and it so it shall be&amp;hellip; forever.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Hacktoberfest 2020 Was Not All Bad</title>
      <link>https://codeofconnor.com/hacktoberfest-2020-was-not-all-bad/</link>
      <pubDate>Sat, 31 Oct 2020 18:02:53 +0000</pubDate>
      <guid>https://codeofconnor.com/hacktoberfest-2020-was-not-all-bad/</guid>
      <description>&lt;p&gt;Hacktoberfest 2020 &lt;a href=&#34;https://lobste.rs/s/epbcho/digitalocean_s_hacktoberfest_is_hurting&#34;&gt;had a rocky start&lt;/a&gt;. I’m not here to argue against any of the criticisms brought up by other members of the community. Their feedback is not unfounded. However, I don’t believe it was all bad.&lt;/p&gt;&#xA;&lt;p&gt;I signed &lt;a href=&#34;https://github.com/connorkuehl/tftp&#34;&gt;one of my weekend projects&lt;/a&gt; up for Hacktoberfest to gain some more experience in a maintainer role rather than an individual contributor role. In this regard, I believe Hacktoberfest 2020 was a successful experience for myself and for the contributors who spent their time and energy submitting patches to my project.&lt;/p&gt;&#xA;&lt;p&gt;What follows are some of the lessons I’ve learned through this experience.&lt;/p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;h2 id=&#34;disclaimer&#34;&gt;&#xA;  &lt;a class=&#34;Heading-link u-clickable&#34; href=&#34;https://codeofconnor.com/hacktoberfest-2020-was-not-all-bad/#disclaimer&#34;&gt;Disclaimer&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Before getting into it, I’ll just throw a disclaimer up. I’m perfectly aware that my sample size of 1 does not reflect the state of all open source projects. Furthermore, this experiment was entirely fueled on an event that offers contributors a chance at getting a free T-shirt.&lt;/p&gt;&#xA;&lt;p&gt;However, a maintainer can’t &lt;strong&gt;control&lt;/strong&gt; the reasons why contributors to contribute. There are a number of reasons why people might contribute:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;They are paid to contribute&lt;/li&gt;&#xA;&lt;li&gt;They want to fix something they’re experiencing&lt;/li&gt;&#xA;&lt;li&gt;They want to sharpen their own skills on a project that uses a language or tools or some underlying concept they want to learn&lt;/li&gt;&#xA;&lt;li&gt;They want to win a free T-shirt&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;To a maintainer, the experience is more or less the same regardless of what motivates a contributor to contribute. At least, for legitimate contributions.&lt;/p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;h2 id=&#34;more-momentum--more-contributions&#34;&gt;&#xA;  &lt;a class=&#34;Heading-link u-clickable&#34; href=&#34;https://codeofconnor.com/hacktoberfest-2020-was-not-all-bad/#more-momentum--more-contributions&#34;&gt;More momentum = more contributions&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;What I mean by momentum is the activity levels that an open source project produces. For very large projects, it almost looks like this bus drives itself. Ubiquitous projects, either through popularity or practical use, see hundreds if not thousands of issues filed against them. Patches/pull requests/merge requests may meet or exceed even the number of issues filed against them.&lt;/p&gt;&#xA;&lt;p&gt;Regardless, the more popular or ubiquitous a project is, the more work it is generating. These are all opportunities for contributions.&lt;/p&gt;&#xA;&lt;p&gt;For a smaller project–much less a project that no one uses or cares about (not trying to diss myself, but really, who uses Trivial File Transfer Protocol in 2020?)–contribution opportunities are not as visible.&lt;/p&gt;&#xA;&lt;p&gt;The maintainer must make these tasks obvious by filing issues themselves.&lt;/p&gt;&#xA;&lt;p&gt;I found that the more issues I filed, the more pull requests I would get. In fact, a quick look through &lt;a href=&#34;https://github.com/connorkuehl/tftp/pulls?q=is%3Apr+-author%3Aconnorkuehl&#34;&gt;all pull requests &lt;strong&gt;not&lt;/strong&gt; authored by me&lt;/a&gt; reveals that 100% of the pull requests from other people are associated with an issue that has already been filed.&lt;/p&gt;&#xA;&lt;p&gt;Furthermore, I’ve noticed that when I stopped filing issues, the new contributor count started waning.&lt;/p&gt;&#xA;&lt;p&gt;My takeaway from this is that it is encouraging to new contributors to see visible work as a jumping-off point for them to get started with making contributions.&lt;/p&gt;&#xA;&lt;p&gt;Generally, this will be issues/bugs filed in some kind of project tracker. I also consider reviewing pull requests as contributing; however, during my experiment, only one contributor reviewed someone else’s work on the repo and they only did so after having some of their own patches merged.&lt;/p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;h2 id=&#34;removing-ambiguity-increases-contributions-from-newinexperienced-contributors&#34;&gt;&#xA;  &lt;a class=&#34;Heading-link u-clickable&#34; href=&#34;https://codeofconnor.com/hacktoberfest-2020-was-not-all-bad/#removing-ambiguity-increases-contributions-from-newinexperienced-contributors&#34;&gt;Removing ambiguity increases contributions from new/inexperienced contributors&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Speaking of visible work, I’ve found it is helpful to remove ambiguity from bugs/issues filed in the tracker. Some might argue this is almost to the point of &lt;strong&gt;spoonfeeding&lt;/strong&gt;. I don’t think so.&lt;/p&gt;&#xA;&lt;p&gt;Clearly stating the acceptance criteria and providing a loose outline of hints for how one might orient themselves to the task and the part of the codebase that it pertains to resulted in a large number of well-defined issues being closed on my repo during Hacktoberfest.&lt;/p&gt;&#xA;&lt;p&gt;It can be tempting to write the bare minimum that you believe will jog your memory of what must be done while working on a project with a close-knit team of people who are responsible for different parts of the codebase (or just by working by yourself).&lt;/p&gt;&#xA;&lt;p&gt;Fight that urge. If it will take a significant amount of time to decompose the task in this way when you’re filing the issue, then the issue can be marked as a meta or a large-scoped issue. I’d encourage you to revisit the issue and break it down further when your schedule allows. The advanatages of this are two-fold:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;A little bit of time designing can save a lot of time coding&lt;/li&gt;&#xA;&lt;li&gt;Someone else might do the work for you if they have some clue what a way forward might look like&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;h2 id=&#34;it-takes-up-a-lot-of-your-time&#34;&gt;&#xA;  &lt;a class=&#34;Heading-link u-clickable&#34; href=&#34;https://codeofconnor.com/hacktoberfest-2020-was-not-all-bad/#it-takes-up-a-lot-of-your-time&#34;&gt;It takes up a lot of your time&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;I get it now. Before, I was sympathizing. Now, I’m empathizing. I know from my day job that reviewing code, especially for larger changes, takes a lot of time and energy. I maintained a much smaller project during Hacktoberfest than I do for work. I’m also paid to do those things at work; but no one is paying me for my toy TFTP project :-)&lt;/p&gt;&#xA;&lt;p&gt;Encouraging contributions, reviewing them, and shepherding them all the way through to be merged can take a lot of time. Some pull requests might take 5, 10, 15 minutes to review. Others might take 30 minutes or an hour. They might also require more than one pass over while you cogitate over it!&lt;/p&gt;&#xA;&lt;p&gt;Sometimes this time investment just isn’t palatable if you’ve had a tough week at work, or if you want to invest your spare time in some of your other hobbies, or spend time with people you care about, etc.&lt;/p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;h2 id=&#34;overall&#34;&gt;&#xA;  &lt;a class=&#34;Heading-link u-clickable&#34; href=&#34;https://codeofconnor.com/hacktoberfest-2020-was-not-all-bad/#overall&#34;&gt;Overall&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;To a lot of the internet, it seems like Hacktoberfest as a net negative. It was worth it to me, though. I got to learn a lot about maintaining a project in a controlled environment.&lt;/p&gt;&#xA;&lt;p&gt;In hindsight, much of this article reads like common sense. I’ve found that after spending too much time in the weeds with one workflow or another, sometimes we lose sight of the fundamentals. A little reflection is always a breath of fresh air.&lt;/p&gt;&#xA;&lt;p&gt;My hope is that some of the contributors, especially those that struck me as newer to the software field, took something away from this process, too.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Test Driven Development in the Clang Compiler</title>
      <link>https://codeofconnor.com/test-driven-development-in-the-clang-compiler/</link>
      <pubDate>Thu, 25 Jul 2019 15:48:13 +0000</pubDate>
      <guid>https://codeofconnor.com/test-driven-development-in-the-clang-compiler/</guid>
      <description>&lt;p&gt;A while back, I participated in a software engineering capstone with a group of other computer science students to complete my degree. Our project was to create a from-scratch implementation of grsecurity’s “randstruct” GCC plugin for the Clang compiler. Long story short, we ended up sending out a request for comments (RFC) on the initial draft that we produced during the capstone. A number of Clang/LLVM contributors took the time to review what we made and kindly suggested some changes for a future revision.&lt;/p&gt;&#xA;&lt;p&gt;Since then, not much has happened with the Clang Randstruct RFC. We (the members of the capstone team) have all gone our separate ways and have been busy settling into post-university life.&lt;/p&gt;&#xA;&lt;p&gt;Over the past week or so I’ve been re-familiarizing myself with the project and working on a new revision that addresses all of the comments from the RFC. The primary concern with the RFC was test coverage — something that we kind of scratched our heads over during the implementation of our RFC draft.&lt;/p&gt;&#xA;&lt;p&gt;The entire time we had been compiling source code with our feature and without the feature and manually inspecting structure layouts with either &lt;code&gt;pahole&lt;/code&gt;, &lt;code&gt;gdb&lt;/code&gt;, or even just a test program that calculated and printed the offset of a structure’s field(s). The expectation of that last manual test method being that it is–of course–a different offset than the unmodified compiler produces.&lt;/p&gt;&#xA;&lt;p&gt;Can we even automate that? What do we do? Do we compile something and compare a diff of the assembly? A diff of the pahole output? Is that even accurate? What kinds of inconsistencies or “noise” would throw that off? I mean, we could certainly compare the outputs of the offset calculation test programs, but what kind of coverage would you call that? Toy programs don’t even scratch the surface of complexity of many “real” software projects.&lt;/p&gt;&#xA;&lt;p&gt;&lt;em&gt;It turns out we were overcomplicating it.&lt;/em&gt; I don’t mean that in a rude way. Often times when I’m introduced to a problem area for the first time I’ve found that my first solutions often do overcomplicate things. This is a normal learning experience.&lt;/p&gt;&#xA;&lt;p&gt;We felt slightly lost in the massive, complex project that is the Clang compiler. We weren’t completely in the dark, but we were holding a candle, not a lighthouse (granted, one could never hold a lighthouse, the analogy here is about illumination). Clang is huge and complex, but it also exposes powerful interfaces into its internal machinery.&lt;/p&gt;&#xA;&lt;p&gt;Last week I started poking around the &lt;code&gt;unittests/&lt;/code&gt; folder to get a feeling for how the tests are written in the first place. After a little bit of searching, I found this:&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;AST0 = tooling::buildASTFromCodeWithArgs(Code0, Args, InputFileName);&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;You can’t see me, but I’m wiping a tear from my eye. It’s beautiful. This function will compile the code that lives in the string called &lt;code&gt;Code0&lt;/code&gt; with the compiler arguments stored in &lt;code&gt;Args&lt;/code&gt; (the &lt;code&gt;InputFileName&lt;/code&gt; is unused for my purposes, since the code is in the string). It returns the abstract syntax tree that results from compiling the code. It is the star of this blog post.&lt;/p&gt;&#xA;&lt;p&gt;Our entire project is predicated around manipulating the representation of a &lt;code&gt;RecordDecl&lt;/code&gt; (Structure) in Clang’s Abstract Syntax Tree. This is a huge win.&lt;/p&gt;&#xA;&lt;p&gt;I like test driven development. I also know that test coverage is primarily what needs to be addressed for the next revision of this if there’s any hope of getting this code upstream. So I’m thinking: &lt;strong&gt;why not outline all of the tests that were suggested by the reviewers and try to “rewrite” Clang Randstruct following the principles of test driven development?&lt;/strong&gt; This way I can more easily address and iterate on all of the concerns outlined for this next revision while increasing test coverage the whole time! (And maybe fix a bug or two along the way.)&lt;/p&gt;&#xA;&lt;p&gt;So, without further ado, I made a file in the &lt;code&gt;unittests/AST/&lt;/code&gt; directory and wrote these two functions:&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;static std::unique_ptr&amp;lt;ASTUnit&amp;gt; MakeAST(const std::string&amp;amp; SourceCode, Language Lang)&#xA;{&#xA;    auto Args = getBasicRunOptionsForLanguage(Lang);&#xA;    auto AST = tooling::buildASTFromCodeWithArgs(SourceCode, Args, &amp;#34;input.cc&amp;#34;);&#xA;    return AST;&#xA;}&#xA;&#xA;static RecordDecl* GetRecordDeclFromAST(const ASTContext&amp;amp; C, const std::string&amp;amp; Name)&#xA;{&#xA;    return FirstDeclMatcher&amp;lt;RecordDecl&amp;gt;().match(C.getTranslationUnitDecl(), recordDecl(hasName(Name)));&#xA;}&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;The first function taps into Clang’s tooling library to produce an AST for some code. The second function simply fetches the first RecordDecl from the AST with the matching name.&lt;/p&gt;&#xA;&lt;p&gt;Here’s an example. This is one of the first tests I wrote when I was ready to begin porting over the randomization code from our RFC (since this is a TDD rewrite, after all):&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;TEST(StructureLayoutRandomization,&#xA;StructuresLayoutFieldLocationsCanBeRandomized)&#xA;{&#xA;    std::string Code =&#xA;        R&amp;#34;(&#xA;        struct test_struct {&#xA;            int a;&#xA;            int b;&#xA;            int c;&#xA;            int d;&#xA;            int e;&#xA;            int f;&#xA;        };&#xA;        )&amp;#34;;&#xA;&#xA;    auto AST = MakeAST(Code, Lang_C);&#xA;    auto RD = GetRecordDeclFromAST(AST-&amp;gt;getASTContext(), &amp;#34;test_struct&amp;#34;);&#xA;    RandomizeStructureLayout(AST-&amp;gt;getASTContext(), RD);&#xA;    std::vector&amp;lt;std::string&amp;gt; before = {&amp;#34;a&amp;#34;, &amp;#34;b&amp;#34;, &amp;#34;c&amp;#34;, &amp;#34;d&amp;#34;, &amp;#34;e&amp;#34;, &amp;#34;f&amp;#34;};&#xA;    std::vector&amp;lt;std::string&amp;gt; after = GetFieldNamesFromRecord(RD);&#xA;&#xA;    ASSERT_NE(before, after);&#xA;}&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Obviously, this won’t compile. I don’t want to belabor the principles of test driven development here, but the test is written first, then we write just enough implementation code to get the test to compile and pass.&lt;/p&gt;&#xA;&lt;p&gt;To build enough of Clang just for our unit tests to run, &lt;code&gt;make -j4 ASTTests&lt;/code&gt; will suffice.&lt;/p&gt;&#xA;&lt;p&gt;The Clang test suite is massive, so running make &lt;code&gt;clang-test&lt;/code&gt; is a great way to lose an afternoon to compilation times.&lt;/p&gt;&#xA;&lt;p&gt;Similarly, running all of ASTTests will age you. We can run just our tests by passing a &lt;code&gt;gtest&lt;/code&gt; filter:&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ ./tools/clang/unittests/AST/ASTTests --gtest_filter=StructureLayoutRandomization*&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Now, rinse and repeat.&lt;/p&gt;&#xA;&lt;p&gt;This has resulted in what I consider to be a much tighter and more responsive development cycle than before. It is much faster to compile the ASTTests than it is to compile Clang and point it at a test program manually. It has eliminated manual structure layout inspection. Failing an assertion results in a more human-friendly “this does not equal that” type message. Since each change was motivated by the appearance of a unit test it’s much easier to track down where a bug was introduced.&lt;/p&gt;&#xA;&lt;p&gt;Practicing test driven development inside Clang has been a really motivating experience. I’ve rewritten most of the functionality with automated test coverage executing every code path (which is a sharp increase from 0%). Not only that, but adding some of the unit tests suggested by the upstream contributors has helped guide the implementation and verify correctness. I was able to refactor some of the randomization algorithm without breaking a sweat since I already had test coverage to help verify my assumptions about how everything should work!&lt;/p&gt;&#xA;&lt;p&gt;Not to mention, the test suite provides a much shorter feedback time. It’s also no longer required to maintain small test programs and compile one with the feature enabled and one version with Randstruct disabled. Since it’s automated, there is no manual structure layout inspection required, which has helped me iterate on new features much quicker. This is an important factor that helps me stay motivated.&lt;/p&gt;&#xA;&lt;p&gt;Every large, unfamiliar code base seems nebulous at first and many of its helpful internal features are hard to discover sometimes. This post is an ode to Clang’s internals. I’m &lt;strong&gt;that happy&lt;/strong&gt; to have stumbled upon this foothold for helping me work on this next revision.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>How the Linux Kernel Detects PCI Devices and Pairs Them With Their Drivers</title>
      <link>https://codeofconnor.com/how-the-linux-kernel-detects-pci-devices-and-pairs-them-with-their-drivers/</link>
      <pubDate>Sat, 01 Jun 2019 18:45:42 +0000</pubDate>
      <guid>https://codeofconnor.com/how-the-linux-kernel-detects-pci-devices-and-pairs-them-with-their-drivers/</guid>
      <description>&lt;p&gt;Have you ever wondered how Linux knows what PCI devices are plugged in? How does Linux know what driver to associate with the device when it detects it?&lt;/p&gt;&#xA;&lt;p&gt;In short, here’s what happens:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;During the kernel’s init process (&lt;code&gt;init/main.c&lt;/code&gt;), various subsystems are brought up according to their “init levels.” Among these early subsystems are the ACPI subsystem and the PCI bus driver.&lt;/li&gt;&#xA;&lt;li&gt;The ACPI subsystem probes the system bus. This “probe” is actually a recursive scan since there can be other devices that act as “bridges” from that main system bus.&lt;/li&gt;&#xA;&lt;li&gt;Each bus is probed, that is, asked to enumerate the devices that are connected to them. It’s at this point we’ll start seeing their sysfs entries.&lt;/li&gt;&#xA;&lt;li&gt;For each device that the bus sees, it will attempt to associate a device driver to it. How does it know to do this? Well, it’s actually up to the device driver. It’s the driver’s responsibility to export a table of devices that it will support when it registers itself to the PCI subsystem. This is used by the hotplug system to map modules to the PCI devices they support. It’s basically a phone book of who we need to call when dealing with a given PCI device.&lt;/li&gt;&#xA;&lt;li&gt;Assuming a match, the kernel will (eventually) call the driver’s &lt;code&gt;probe()&lt;/code&gt; function, and the device driver can decide whether or not it claims the device. Yes, the kernel basically takes the device and walks up to the driver(s) that claim they can handle a certain device and then asks, “is this your kid?”&lt;/li&gt;&#xA;&lt;li&gt;Remember, a device driver whose &lt;code&gt;module_init&lt;/code&gt; equivalent has been called is included in this roll-call (built-in or module) so long as this device is compatible with their supported module device table. Built-in modules are asked first (&lt;a href=&#34;https://lwn.net/Articles/260856/&#34;&gt;according to the order that they’re linked into the kernel image&lt;/a&gt;).&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;Finally, let’s take a look at a stack trace from a kernel running in QEMU. We’ll start from the bottom, and work our way up. (I’ve removed the function addresses in the stack trace to reduce clutter)&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;#28 ?? ()&#xA;        at arch/x86/entry/entry_64.S:352&#xA;#27 ret_from_fork () &#xA;        at init/main.c:1087&#xA;#26 kernel_init (unused=&amp;lt;optimized out&amp;gt;)&#xA;        at init/main.c:1169&#xA;#25 kernel_init_freeable ()&#xA;        at init/main.c:1009&#xA;#24 do_basic_setup () &#xA;        at init/main.c:991&#xA;#23 do_initcalls () &#xA;        at ./include/linux/compiler.h:305&#xA;#22 do_initcall_level (level=&amp;lt;optimized out&amp;gt;)&#xA;        at init/main.c:915&#xA;#21 do_one_initcall (fn=0xffffffff828d6101 &amp;lt;piix_init&amp;gt;)&#xA;        at drivers/ata/ata_piix.c:1773&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;As part of initialization, the kernel brings itself up gradually. This gradual bring-up is described by the kernel’s init levels. Here’s the order defined in &lt;code&gt;init/main.c&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;pure&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;core&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;postcore&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;arch&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;subsys&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;fs&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;device&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;late&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;On our &lt;code&gt;qemu-system-x86_64&lt;/code&gt; system, it looks like &lt;code&gt;piix_init&lt;/code&gt; is being invoked. This driver’s entry point is declared using the module_init macro, meaning that it belongs to the device init level. So at this point, the ACPI subsystem has already been brought up and the PCI bus has been scanned for devices.&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;#20 piix_init () &#xA;        at drivers/pci/pci-driver.c:1402&#xA;#19 __pci_register_driver (drv=&amp;lt;optimized out&amp;gt;, owner=&amp;lt;optimized out&amp;gt;, mod_name=&amp;lt;optimized out&amp;gt;)&#xA;    at drivers/base/driver.c:170&#xA;#18 driver_register (drv=0xffffffff827a33b0 &amp;lt;piix_pci_driver+112&amp;gt;)&#xA;        at drivers/base/bus.c:645&#xA;#17 bus_add_driver (drv=0xffffffff827a33b0 &amp;lt;piix_pci_driver+112&amp;gt;)&#xA;        at drivers/base/dd.c:1037&#xA;#16 driver_attach (drv=&amp;lt;optimized out&amp;gt;)&#xA;        at drivers/base/bus.c:304&#xA;#15 bus_for_each_dev (bus=&amp;lt;optimized out&amp;gt;, start=&amp;lt;optimized out&amp;gt;, data=0x0 &amp;lt;fixed_percpu_data&amp;gt;, fn=0x0 &amp;lt;fixed_percpu_data&amp;gt;) &#xA;        at drivers/base/dd.c:1021&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;  &#xA;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;This module registers itself as a PCI driver in its init function. This means the driver is associated with the PCI bus and is now a valid candidate for driving PCI devices. Since it’s being registered now, we can carry on and see if we can find the physical device that it should be paired with. So, we iterate through all of the eligible devices connected to the bus in &lt;code&gt;bus_for_each_dev&lt;/code&gt; and begin searching.&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;#14 __driver_attach (dev=0xffff88801b14f0b0, data=0xffffffff827a33b0 &#xA;&amp;lt;piix_pci_driver+112&amp;gt;)&#xA;        at drivers/base/dd.c:944&#xA;#13 device_driver_attach ( drv=0xffffffff827a33b0 &amp;lt;piix_pci_driver+112&amp;gt;, &#xA;dev=0xffff88801b14f0b0)&#xA;        at drivers/base/dd.c:670&#xA;#12 driver_probe_device ( drv=0xffffffff827a33b0 &amp;lt;piix_pci_driver+112&amp;gt;, &#xA;dev=0xffff88801b14f0b0)&#xA;        at drivers/base/dd.c:509&#xA;#11 really_probe (dev=0xffff88801b14f000, drv=0x1f &amp;lt;fixed_percpu_data+31&amp;gt;)&#xA;        at drivers/pci/pci-driver.c:425&#xA;#10 pci_device_probe (dev=0xffff88801b14f0b0)&#xA;        at drivers/pci/pci-driver.c:385&#xA;#9  __pci_device_probe (pci_dev=&amp;lt;optimized out&amp;gt;, drv=&amp;lt;optimized out&amp;gt;)&#xA;        at drivers/pci/pci-driver.c:360&#xA;#8  pci_call_probe (id=&amp;lt;optimized out&amp;gt;, dev=&amp;lt;optimized out&amp;gt;, drv=&amp;lt;optimized &#xA;out&amp;gt;) &#xA;        at drivers/pci/pci-driver.c:306&#xA;#7  local_pci_probe (_ddi=0xffffc900000dbc50)&#xA;        at drivers/ata/ata_piix.c:1672&#xA;#6  piix_init_one (pdev=0xffff88801b14f000, ent=0x1f &amp;lt;fixed_percpu_data+31&amp;gt;)&#xA;        at drivers/pci/pci.c:1806&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Wow, that looks like a mouthful at first glance. Rest assured, all that’s happening here is that for each eligible device connected to the PCI bus, the bus attempts to attach the driver to the device. This ultimately ends up calling the driver’s &lt;code&gt;probe()&lt;/code&gt; callback, which in this case is &lt;code&gt;piix_init_one()&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;This callback is registered &lt;code&gt;drivers/ata/ata_piix.c&lt;/code&gt; line 1760. We also see in &lt;code&gt;piix_init_one()&lt;/code&gt; the driver checks the PCI device ID information to determine if it should reject the device. The device is not claimed if this probe function returns an error.&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;#5  pcim_enable_device (pdev=0xffff88801b14f000)&#xA;        at drivers/pci/pci.c:1806&#xA;#4  pci_enable_device (dev=&amp;lt;optimized out&amp;gt;)&#xA;        at drivers/pci/pci.c:1677&#xA;#3  pci_enable_device_flags (dev=0xffff88801b14f000, flags=768)&#xA;        at drivers/pci/pci.c:1588&#xA;#2  do_pci_enable_device (dev=0xffff88801b14f000, bars=31)&#xA;        at arch/x86/pci/common.c:709&#xA;#1  pcibios_enable_device (dev=0xffff88801b14f000, mask=&amp;lt;optimized out&amp;gt;) &#xA;        at drivers/pci/setup-res.c:456&#xA;#0  pci_enable_resources (dev=0xffff88801b14f000, mask=31)&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;However, if the device driver remains happy during its &lt;code&gt;probe()&lt;/code&gt; function, it will ultimately enable the PCI device and return success.&lt;/p&gt;&#xA;&lt;p&gt;And that’s how the Linux kernel detects PCI devices and pairs them with their device driver!&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Macromancer&#39;s Spellbook: Using Macros to Stay Dry</title>
      <link>https://codeofconnor.com/macromancers-spellbook-using-macros-to-stay-dry/</link>
      <pubDate>Thu, 31 May 2018 03:42:59 +0000</pubDate>
      <guid>https://codeofconnor.com/macromancers-spellbook-using-macros-to-stay-dry/</guid>
      <description>&lt;p&gt;It is sometimes hard to see past the thick haze of caution surrounding the use of C Preprocessor macros in your code. Indeed, &lt;a href=&#34;https://stackoverflow.com/questions/14041453/why-are-preprocessor-macros-evil-and-what-are-the-alternatives/14041847#14041847&#34;&gt;the dangers of Macromancy&lt;/a&gt; are well-stated in many corners of the internet. You would do well to heed them.&lt;/p&gt;&#xA;&lt;p&gt;However, there are times where a judicious use of macros can help reduce duplication in your code and make it tidier and easier to reason with. Every time a block of code is duplicated by hand, it is likely to become yet another maintenance burden to contend with. If it just so happens that some of these blocks of code contains a bug… well… you get to go fix it in more than one place.&lt;/p&gt;&#xA;&lt;p&gt;I know! Why don’t we just extract the duplicated code into a function? Good instinct. In many cases, this may be the perfect solution to your problem. In my case, it wasn’t.&lt;/p&gt;&#xA;&lt;p&gt;I recently upgraded the scheduling algorithm for my distribution of xv6 (a learning OS). I abstracted away the process table (an array of processes) by building multiple circular-linked state lists on top of it. Now, we have the infinitely preferable solution of not having to iterate over the entire process table when we want to find and manipulate some active processes.&lt;/p&gt;&#xA;&lt;p&gt;Shortly into the implementation, I met my DRY monster for the project. There are many areas in the scheduling and process-handling routines where I need to traverse these state lists the exact same way, but interact with the processes differently while I’m traversing.&lt;/p&gt;&#xA;&lt;p&gt;In my situation, the redundant code was the traversal of one or more state lists. However, embedded in those traversals were routine-specific blocks of code that operated on one or more unique local variables to that routine. As you can see, I don’t have a one-size fits all operation that could be neatly extracted into a function. I have a control structure and context-specific behavior to do within.&lt;/p&gt;&#xA;&lt;p&gt;I could have certainly just copied and pasted my state list traversal logic into the four different routines where I needed it, but it felt so painful. What if I made a mistake? If four system-critical process routines were compromised with some stupid bug it would be was an absolutely hellish nightmare to debug. Trust me.&lt;/p&gt;&#xA;&lt;p&gt;Here’s what it looked like at first:&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;struct proc **lists[] = {&#xA;  &amp;amp;ptable.plists.ready,&#xA;  &amp;amp;ptable.plists.run,&#xA;  &amp;amp;ptable.plists.sleep,&#xA;  // ... snipped..&#xA;};&#xA;&#xA;for (int i = 0; i &amp;lt; NELEM(lists); ++i) {&#xA;  struct proc *rear = *(lists[i]);&#xA;  if (rear) {&#xA;  p = rear-&amp;gt;next;&#xA;  do {&#xA;&#xA;    // routine specific stuff goes here!&#xA;&#xA;    p = p-&amp;gt;next;&#xA;  } while (p != rear);&#xA; }&#xA;}&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;It seems simple enough. It takes an array of circular-linked lists for it to traverse, and then inside the inner do-while is the routine-specific logic.&lt;/p&gt;&#xA;&lt;p&gt;Copying and pasting it four times is too painful. C Preprocessor macros are just token expansions. Let’s have the compiler copy and paste it for us. The control structure for looping over an array and traversing circular-linked lists therein is the same for all four functions.&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;#define STATELIST_TRAVERSE(lists, cursor, code) for (int i = 0; i &amp;lt; NELEM(lists); ++i) { \&#xA;  if (*(lists[i])) { \&#xA;    cursor = (*(lists[i])); \&#xA;    struct proc *next = cursor-&amp;gt;next; \&#xA;    do { \&#xA;      { \&#xA;        cursor = next; \&#xA;        next = cursor-&amp;gt;next; \&#xA;        code \&#xA;      } \&#xA;    } while (*(lists[i]) &amp;amp;&amp;amp; cursor != *(lists[i])); \&#xA;  } \&#xA;} \&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;It’s just a little bit hideous at first, but I got used to it as soon as I simply used that macro four times and everything just worked.&lt;/p&gt;&#xA;&lt;p&gt;Let’s dissect this a little bit.&lt;/p&gt;&#xA;&lt;p&gt;The backward slashes on the end of each line are so that the C Preprocessor knows that the macro substitution I’m defining will span multiple lines.&lt;/p&gt;&#xA;&lt;p&gt;The macro takes three arguments, lists, cursor, and code. I designed it this way because I want the contract to be very clear regarding the macro’s usage. Whoever wants to use the macro must provide an array of circular-linked lists for me to traverse, they must provide their pointer by which the lists will be traversed (I call this the “cursor”), and they must supply the code they want executed with each iteration. The macro requires the caller to supply the “cursor” to the macro so that they may interact directly with whichever process the loop is currently visiting through that cursor. Basically, they can use the cursor like a really simple iterator for access to the element.&lt;/p&gt;&#xA;&lt;p&gt;By doing this, I believe that the macro is transparent and does not obfuscate or try to take control from whoever is using it. It does not heavily pollute the routine with variable declarations within. It is a reliable scaffolding for its one purpose: traversing through a number of circular linked lists while exposing the elements to the programmer for whatever purposes they need.&lt;/p&gt;&#xA;&lt;p&gt;Let’s see it in action. Here’s a part of the &lt;code&gt;exit()&lt;/code&gt; routine for when a process is terminating:&lt;/p&gt;&#xA;&#xA;  &#xA;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;// ...snipped&#xA;  struct proc **lists[] = {&#xA;    &amp;amp;ptable.plists.ready,&#xA;    &amp;amp;ptable.plists.run,&#xA;    &amp;amp;ptable.plists.zombie,&#xA;    &amp;amp;ptable.plists.sleep,&#xA;  };&#xA;&#xA;  // Pass abandoned children to init.&#xA;  STATELIST_TRAVERSE(lists, p, {&#xA;      if (p-&amp;gt;parent == curproc) {&#xA;        p-&amp;gt;parent = initproc;&#xA;        if (p-&amp;gt;state == ZOMBIE) {&#xA;          wakeup1(initproc);&#xA;        }&#xA;      }&#xA;  });&#xA;// ...snipped&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Granted, it does look a little weird at first. As you can see, I create an array of circular-linked lists that I want the macro to traverse. Different routines will need to visit different numbers of lists, so this set-up is required for each call-site.&lt;/p&gt;&#xA;&lt;p&gt;I pass in my pointer that I declared earlier in the routine. This will be used as my “cursor” to traverse the circular-linked list of processes.&lt;/p&gt;&#xA;&lt;p&gt;The third argument, which I wrap in curly braces, is the routine-specific code block that I want executed at each process in the list. As you can see, I use the pointer that I passed in as the “cursor” so that I may interact directly with the element within the list.&lt;/p&gt;&#xA;&lt;p&gt;I did it the hard way without the macro and it was painful. I abstracted it away into a macro and it trimmed down the redundant lines of code significantly across my process management routines.&lt;/p&gt;&#xA;&lt;p&gt;Using a macro shouldn’t be your first choice for everything. If function extraction is not possible, or you are working in an environment that cannot support the overhead of function calls, etc. a macro can certainly help.&lt;/p&gt;&#xA;&lt;p&gt;Remember to be cautious about what your macros are doing, what variables they introduce, and how they operate on arguments passed in to them.&lt;/p&gt;&#xA;&lt;p&gt;Bonus story: I also created control commands for printing the contents of my various state lists with different formatting. Guess what really came in handy for that? The macro you just read about.&lt;/p&gt;&#xA;</description>
    </item>
  </channel>
</rss>
