<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>avojak | blog</title>
    <description>programming &amp; projects, breakfast cereal &amp; coffee</description>
    <link>https://avojak.com/blog/</link>
    <atom:link href="https://avojak.com/blog/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Sun, 04 Feb 2024 05:17:10 +0000</pubDate>
    <lastBuildDate>Sun, 04 Feb 2024 05:17:10 +0000</lastBuildDate>
    <generator>Jekyll v3.9.3</generator>
    
      
        <item>
          <title>Installing ESXi with a USB NIC</title>
          <description>&lt;p&gt;Several years ago I wrote about how to create a custom ISO for installing ESXi with a Realtek 8111 NIC. Unfortunately that process was only valid for ESXi versions up to 6.7, which has &lt;a href=&quot;https://endoflife.date/esxi&quot;&gt;reached its end of life&lt;/a&gt;.&lt;/p&gt;

&lt;aside&gt;
  &lt;a class=&quot;featured with-image 1&quot; href=&quot;https://avojak.com/blog/2020/12/19/installing-esxi-for-realtek-8111-nic/&quot;&gt;
    &lt;div class=&quot;featured-image&quot; alt=&quot;Featured image&quot; style=&quot;background-image: url(https://images.unsplash.com/photo-1560732488-6b0df240254a);&quot;&gt;&lt;/div&gt;
    &lt;header&gt;
      &lt;h2&gt;Installing ESXi for Realtek 8111 NIC&lt;/h2&gt;
      &lt;h3&gt;A classic tale of struggling for hours and hours before finally stumbling across the perfect blog post&lt;/h3&gt;
      &lt;div class=&quot;byline&quot;&gt;
        &lt;div class=&quot;avatar&quot;&gt;
          &lt;img srcset=&quot;https://www.gravatar.com/avatar/4dfa9066e794647fdedf323ecab85333?s=96&amp;amp;d=blank 2x&quot; src=&quot;https://www.gravatar.com/avatar/4dfa9066e794647fdedf323ecab85333?s=48&amp;amp;d=blank&quot; alt=&quot;Avatar for Andrew Vojak&quot; /&gt;
        &lt;/div&gt;
        &lt;div class=&quot;author&quot;&gt;
          &lt;span class=&quot;name&quot;&gt;Andrew Vojak&lt;/span&gt;
        &lt;/div&gt;
        &lt;time class=&quot;post-date&quot; datetime=&quot;2020-12-19&quot;&gt;Sat, Dec 19, 2020&lt;/time&gt;
        &lt;span class=&quot;read-time&quot; title=&quot;Estimated read time&quot;&gt;3 min read&lt;/span&gt;
      &lt;/div&gt;
    &lt;/header&gt;
  &lt;/a&gt;
&lt;/aside&gt;

&lt;p&gt;To upgrade the host that I previously wrote about, I looked into the &lt;a href=&quot;https://communities.vmware.com/t5/Flings-Docs/USB-Network-Native-Driver-for-ESXi-Documentation/ta-p/2998944&quot;&gt;USB Network Native Driver for ESXi&lt;/a&gt; Fling. This had listed support for a handful of inexpensive USB NICs, so I purchased a cheap RTL8153 USB NIC to try out.&lt;/p&gt;

&lt;p&gt;Because of the incompatibility of the existing NIC with ESXi 7, I was previously unable to even complete the install on this hardware. This meant I would have to create a new installer ISO with the Fling bundled in it, rather than upgrade in place or upgrade to 7 and then install the Fling later.&lt;/p&gt;

&lt;p&gt;The process was largely the same as before, but to make things easy to follow, these are the exact steps that I performed:&lt;/p&gt;

&lt;h2 id=&quot;1-retrieve-the-esxi-70-offline-bundle&quot;&gt;1. Retrieve the ESXi 7.0 Offline Bundle&lt;/h2&gt;

&lt;p&gt;To obtain the prerequisite tools, refer to the prior blog post. At the time of writing, the current latest release of ESXi 7 is 7.0.3, so I downloaded the offline bundle with the following command:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;PS C:\Users\Andrew\Downloads&amp;gt; .\ESXi-Customizer-PS.ps1 -v70 -ozip
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We now have an archive named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ESXi-7.0U3so-22348808-standard.zip&lt;/code&gt; (or something similar if a newer version is released).&lt;/p&gt;

&lt;h2 id=&quot;2-download-the-usb-nic-fling&quot;&gt;2. Download the USB NIC Fling&lt;/h2&gt;

&lt;p&gt;Download the latest version of the Fling for ESXi 7 from this page: &lt;a href=&quot;https://communities.vmware.com/t5/Flings/ct-p/77&quot;&gt;https://communities.vmware.com/t5/Flings/ct-p/77&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Currently the latest supported version is &lt;a href=&quot;https://download3.vmware.com/software/vmw-tools/USBNND/ESXi703-VMKUSB-NIC-FLING-55634242-component-19849370.zip&quot;&gt;1.10&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;3-build-the-new-iso&quot;&gt;3. Build the new ISO&lt;/h2&gt;

&lt;p&gt;With both of the offline bundles (ESXi and the Realtek driver) in the same directory, we can now run some more commands:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;Create the software depot:&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; PS C:\Users\Andrew\Downloads&amp;gt; Add-EsxSoftwareDepot &quot;.\ESXi703-VMKUSB-NIC-FLING-55634242-component-19849370.zip&quot;, `
    &quot;ESXi-7.0U3so-22348808-standard.zip&quot; 
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Create an image profile by cloning an existing one:&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; PS C:\Users\Andrew\Downloads&amp;gt; Get-EsxImageProfile
 PS C:\Users\Andrew\Downloads&amp;gt; New-EsxImageProfile -CloneProfile ESXi-7.0U3so-22348808-standard `
    -name ESXi-7.0U3so-22348808-usb-nic -Vendor Razz
 PS C:\Users\Andrew\Downloads&amp;gt; Set-EsxImageProfile ESXi-7.0U3so-22348808-usb-nic `
    -AcceptanceLevel CommunitySupported
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Add the driver to the new image profile:&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; PS C:\Users\Andrew\Downloads&amp;gt; Get-EsxSoftwarePackage
 PS C:\Users\Andrew\Downloads&amp;gt; Add-EsxSoftwarePackage -ImageProfile ESXi-7.0U3so-22348808-usb-nic `
    -SoftwarePackage vmkusb-nic-fling
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Create the ISO:&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; PS C:\Users\Andrew\Downloads&amp;gt; Export-EsxImageProfile -ImageProfile ESXi-7.0U3so-22348808-usb-nic `
    -ExportToIso -filepath .\ESXi-7.0U3so-22348808-usb-nic.iso
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;4-create-a-bootable-usb-installer&quot;&gt;4. Create a Bootable USB Installer&lt;/h2&gt;

&lt;p&gt;I used &lt;a href=&quot;https://rufus.akeo.ie/&quot;&gt;Rufus&lt;/a&gt; as well to create the bootable USB drive from the ISO, but you can use any tool that will get the job done.&lt;/p&gt;

&lt;h2 id=&quot;5-install-and-expect-a-failure&quot;&gt;5. Install and Expect a Failure!&lt;/h2&gt;

&lt;p&gt;Boot the installer and go through the prompts until you reach an error screen at 81% installed. Never fear! This is expected!&lt;/p&gt;

&lt;p&gt;Thankfully I found this blog post (&lt;a href=&quot;https://www.virten.net/2020/07/solution-esxi-installation-with-usb-nic-only-fails-at-81/&quot;&gt;Solution: ESXi Installation with USB NIC only fails at 81%&lt;/a&gt;) that explained how technically ESXi was installed at this point, but not fully confiugred. There is a simple fix to force ESXi to recognize the USB NIC for the management interface.&lt;/p&gt;

&lt;p&gt;In case that site goes down, I’ll quote the steps below:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;ol&gt;
    &lt;li&gt;… Create ISO …&lt;/li&gt;
    &lt;li&gt;… Install until 81% failure …&lt;/li&gt;
    &lt;li&gt;At this point, ESXi is already &lt;strong&gt;installed&lt;/strong&gt;, but not &lt;strong&gt;configured&lt;/strong&gt;.&lt;/li&gt;
    &lt;li&gt;Remove the installation media and reboot the system&lt;/li&gt;
    &lt;li&gt;When ESXi is loaded, press &lt;strong&gt;F2&lt;/strong&gt; and login as &lt;strong&gt;“root” without password&lt;/strong&gt;. (The password entered during the installation has not been saved because the configuration failed)&lt;/li&gt;
    &lt;li&gt;You should notice that all Network Options are greyed out. Select &lt;strong&gt;Network Restore Options&lt;/strong&gt;.&lt;/li&gt;
    &lt;li&gt;Select &lt;strong&gt;Restore Network Settings&lt;/strong&gt;&lt;/li&gt;
    &lt;li&gt;Log out&lt;/li&gt;
    &lt;li&gt;Log back in&lt;/li&gt;
    &lt;li&gt;Network options are no longer greyed out and the vusb0 adapter has been detected&lt;/li&gt;
  &lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once the steps are followed, you’ll be up and running with ESXi 7.0 fully leveraging your new USB NIC! Even better, the default VM Network will be configured to use this NIC as well, so no extra steps are needed to hit the ground running with new VMs, or to update existing VMs.&lt;/p&gt;
</description>
          <pubDate>Wed, 17 Jan 2024 00:00:00 +0000</pubDate>
          <link>https://avojak.com/blog/2024/01/17/installing-esxi-with-usb-nic/</link>
          <guid isPermaLink="true">https://avojak.com/blog/2024/01/17/installing-esxi-with-usb-nic/</guid>
          
              
              
          <enclosure url="https://avojak.com/blog" />
          
          
          <category>hardware</category>
          
          
        </item>
      
    
      
        <item>
          <title>Welcome to My Homelab!</title>
          <description>&lt;p&gt;This will be the first in an series of posts about my homelab setup. But first, what exactly &lt;em&gt;is&lt;/em&gt; a homelab?&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;A homelab, in the simplest terms is a sandbox that you can learn and play with new or unfamiliar technologies. They can be as simple as a set of VM’s on an old PC or laptop… &lt;sup id=&quot;fnref:1&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The complete source mentioned in this post can be found over in the GitHub repository:&lt;/p&gt;

&lt;div class=&quot;github-card&quot;&gt;
  &lt;header&gt;
    &lt;a class=&quot;account&quot; href=&quot;https://github.com/avojak/homelab&quot;&gt;
      &lt;img class=&quot;avatar&quot; src=&quot;https://github.com/avojak.png&quot; /&gt;
      &lt;p&gt;avojak/&lt;b&gt;homelab&lt;/b&gt;&lt;/p&gt;
    &lt;/a&gt;
    &lt;a href=&quot;https://github.com/avojak/homelab&quot;&gt;
      &lt;img class=&quot;logo&quot; src=&quot;/blog/images/github.svg&quot; /&gt;
    &lt;/a&gt;
  &lt;/header&gt;

  

  &lt;p id=&quot;avojak/homelab-description&quot;&gt;&lt;/p&gt;

  &lt;footer&gt;
    &lt;span id=&quot;avojak/homelab-issues&quot; style=&quot;white-space:nowrap;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 512 512&quot;&gt;&lt;!--! Font Awesome Pro 6.1.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --&gt;&lt;path d=&quot;M160 256C160 202.1 202.1 160 256 160C309 160 352 202.1 352 256C352 309 309 352 256 352C202.1 352 160 309 160 256zM512 256C512 397.4 397.4 512 256 512C114.6 512 0 397.4 0 256C0 114.6 114.6 0 256 0C397.4 0 512 114.6 512 256zM256 48C141.1 48 48 141.1 48 256C48 370.9 141.1 464 256 464C370.9 464 464 370.9 464 256C464 141.1 370.9 48 256 48z&quot; /&gt;&lt;/svg&gt;&lt;/span&gt;
    &lt;span id=&quot;avojak/homelab-stargazers&quot; style=&quot;white-space:nowrap;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 576 512&quot;&gt;&lt;!--! Font Awesome Pro 6.1.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --&gt;&lt;path d=&quot;M287.9 0C297.1 0 305.5 5.25 309.5 13.52L378.1 154.8L531.4 177.5C540.4 178.8 547.8 185.1 550.7 193.7C553.5 202.4 551.2 211.9 544.8 218.2L433.6 328.4L459.9 483.9C461.4 492.9 457.7 502.1 450.2 507.4C442.8 512.7 432.1 513.4 424.9 509.1L287.9 435.9L150.1 509.1C142.9 513.4 133.1 512.7 125.6 507.4C118.2 502.1 114.5 492.9 115.1 483.9L142.2 328.4L31.11 218.2C24.65 211.9 22.36 202.4 25.2 193.7C28.03 185.1 35.5 178.8 44.49 177.5L197.7 154.8L266.3 13.52C270.4 5.249 278.7 0 287.9 0L287.9 0zM287.9 78.95L235.4 187.2C231.9 194.3 225.1 199.3 217.3 200.5L98.98 217.9L184.9 303C190.4 308.5 192.9 316.4 191.6 324.1L171.4 443.7L276.6 387.5C283.7 383.7 292.2 383.7 299.2 387.5L404.4 443.7L384.2 324.1C382.9 316.4 385.5 308.5 391 303L476.9 217.9L358.6 200.5C350.7 199.3 343.9 194.3 340.5 187.2L287.9 78.95z&quot; /&gt;&lt;/svg&gt;&lt;/span&gt;
    &lt;span id=&quot;avojak/homelab-forks&quot; style=&quot;white-space:nowrap;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 448 512&quot;&gt;&lt;!--! Font Awesome Pro 6.1.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --&gt;&lt;path d=&quot;M160 80C160 112.8 140.3 140.1 112 153.3V192C112 209.7 126.3 224 144 224H304C321.7 224 336 209.7 336 192V153.3C307.7 140.1 288 112.8 288 80C288 35.82 323.8 0 368 0C412.2 0 448 35.82 448 80C448 112.8 428.3 140.1 400 153.3V192C400 245 357 288 304 288H256V358.7C284.3 371 304 399.2 304 432C304 476.2 268.2 512 224 512C179.8 512 144 476.2 144 432C144 399.2 163.7 371 192 358.7V288H144C90.98 288 48 245 48 192V153.3C19.75 140.1 0 112.8 0 80C0 35.82 35.82 0 80 0C124.2 0 160 35.82 160 80V80zM80 104C93.25 104 104 93.25 104 80C104 66.75 93.25 56 80 56C66.75 56 56 66.75 56 80C56 93.25 66.75 104 80 104zM368 104C381.3 104 392 93.25 392 80C392 66.75 381.3 56 368 56C354.7 56 344 66.75 344 80C344 93.25 354.7 104 368 104zM224 408C210.7 408 200 418.7 200 432C200 445.3 210.7 456 224 456C237.3 456 248 445.3 248 432C248 418.7 237.3 408 224 408z&quot; /&gt;&lt;/svg&gt;&lt;/span&gt;
  &lt;/footer&gt;
  &lt;footer&gt;
    &lt;a href=&quot;https://github.com/avojak/homelab&quot;&gt;
      &lt;span&gt;View on GitHub →&lt;/span&gt;
    &lt;/a&gt;
  &lt;/footer&gt;

  &lt;script defer=&quot;&quot;&gt;
    // Data for testing without actually hitting the GitHub API
    // out = {&quot;stargazers_count&quot;:123456, &quot;forks_count&quot;:123, &quot;open_issues_count&quot;:60, &quot;description&quot;:&quot;Super cool project with absolutely zero bugs&quot;};
    // document.getElementById(&quot;avojak/homelab-stargazers&quot;).innerHTML += numeral(out['stargazers_count']).format('0.[0]a') + &quot; Stars&quot;;
    // document.getElementById(&quot;avojak/homelab-issues&quot;).innerHTML += numeral(out['open_issues_count']).format('0.[0]a') + &quot; Issues&quot;;
    // document.getElementById(&quot;avojak/homelab-description&quot;).innerHTML = out['description'];
    // document.getElementById(&quot;avojak/homelab-forks&quot;).innerHTML += numeral(out['forks_count']).format('0.[0]a') + &quot; Forks&quot;;

    fetch('https://api.github.com/repos/avojak/homelab')
      .then(res =&gt; {
        if (!res.ok) {
          throw new Error(&quot;Bad network response&quot;);
        }
        return res.json();
      })
      .then((out) =&gt; {
        var stargazers = out['stargazers_count']
        var issues = out['open_issues_count']
        var forks = out['forks_count']
        
        // Populate the elements with the data from the response
        document.getElementById(&quot;avojak/homelab-stargazers&quot;).innerHTML += numeral(stargazers).format('0.[0]a') + &quot; Star&quot; + (stargazers != 1 ? &quot;s&quot; : &quot;&quot;);
        document.getElementById(&quot;avojak/homelab-issues&quot;).innerHTML += numeral(issues).format('0.[0]a') + &quot; Issue&quot; + (issues != 1 ? &quot;s&quot; : &quot;&quot;);
        document.getElementById(&quot;avojak/homelab-forks&quot;).innerHTML += numeral(forks).format('0.[0]a') + &quot; Fork&quot; + (forks != 1 ? &quot;s&quot; : &quot;&quot;);
        document.getElementById(&quot;avojak/homelab-description&quot;).innerHTML = out['description'];
    }).catch((err) =&gt; {
      console.error(err);
      // Clear out the fields in case of error rather than showing 0 values
      document.getElementById(&quot;avojak/homelab-stargazers&quot;).innerHTML = &quot;&quot;;
      document.getElementById(&quot;avojak/homelab-issues&quot;).innerHTML = &quot;&quot;;
      document.getElementById(&quot;avojak/homelab-forks&quot;).innerHTML = &quot;&quot;;
    });
  &lt;/script&gt;
&lt;/div&gt;

&lt;p&gt;Please note that this is not intended to be an exhaustive tutorial for any of the tools that I use, but simply an overview of how I’m using them.&lt;/p&gt;

&lt;h1 id=&quot;overview&quot;&gt;Overview&lt;/h1&gt;

&lt;p&gt;If all you’re curious about is what services I’m running, I’ll put that up front!&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://hub.docker.com/r/oznu/cloudflare-ddns/&quot;&gt;Cloudflare DDNS&lt;/a&gt; - Dynamic DNS for domains which are managed by Cloudflare&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://grafana.com&quot;&gt;Grafana&lt;/a&gt; - Dashboards&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://homebridge.io&quot;&gt;Homebridge&lt;/a&gt; - Apple HomeKit compatibility for smart home devices which don’t offer native compatibility&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://gethomepage.dev&quot;&gt;Homepage&lt;/a&gt; - Application dashboard and landing page for my homelab with bookmarks to most of my services&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.influxdata.com/home/&quot;&gt;InfluxDB&lt;/a&gt; - Time series database that stores homelab metrics&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://pi-hole.net&quot;&gt;Pi-hole&lt;/a&gt; - DNS-level ad-blocker&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/avojak/pihole-influxdb-monitor&quot;&gt;Pi-hole InfluxDB Monitor&lt;/a&gt; - Export Pi-hole metrics to InfluxDB&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://plausible.io&quot;&gt;Plausible&lt;/a&gt; - Privacy-respecting analytics&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.plex.tv&quot;&gt;Plex&lt;/a&gt; - Movie and TV show library&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.portainer.io&quot;&gt;Portainer&lt;/a&gt; - Container management platform that I use simply for monitoring logs&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://prometheus.io&quot;&gt;Prometheus&lt;/a&gt; - Another time series database for storing metrics&lt;/li&gt;
  &lt;li&gt;Prometheus VMware Exporter - Export VMware stats to Prometheus (I had forked this repo from somewhere and pushed it as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;avojak/prometheus-vmware-exporter&lt;/code&gt; a long time ago, but it has since disappeared…)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.scrypted.app&quot;&gt;Scrypted&lt;/a&gt; - Home video integration platform that I use for integrating my UniFi security cameras with Apple HomeKit&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://hub.docker.com/r/loganmarchione/docker-speedtest-influxdbv2&quot;&gt;Speedtest&lt;/a&gt; - Runs periodic network speed tests and records the results in InfluxDB&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.influxdata.com/time-series-platform/telegraf/&quot;&gt;Telegraf&lt;/a&gt; - Agent for collecting realtime metrics from clients that I use to monitor my VMs&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://traefik.io&quot;&gt;Traefik&lt;/a&gt; - Application proxy that I use for routing traffic coming into my homelab, as well as within&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://uptime.kuma.pet&quot;&gt;Uptime Kuma&lt;/a&gt; - Simple uptime monitoring for servers, applications, and more&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can check out my Pi-hole InfluxDB Monitor over on GitHub:&lt;/p&gt;

&lt;div class=&quot;github-card&quot;&gt;
  &lt;header&gt;
    &lt;a class=&quot;account&quot; href=&quot;https://github.com/avojak/pihole-influxdb-monitor&quot;&gt;
      &lt;img class=&quot;avatar&quot; src=&quot;https://github.com/avojak.png&quot; /&gt;
      &lt;p&gt;avojak/&lt;b&gt;pihole-influxdb-monitor&lt;/b&gt;&lt;/p&gt;
    &lt;/a&gt;
    &lt;a href=&quot;https://github.com/avojak/pihole-influxdb-monitor&quot;&gt;
      &lt;img class=&quot;logo&quot; src=&quot;/blog/images/github.svg&quot; /&gt;
    &lt;/a&gt;
  &lt;/header&gt;

  

  &lt;p id=&quot;avojak/pihole-influxdb-monitor-description&quot;&gt;&lt;/p&gt;

  &lt;footer&gt;
    &lt;span id=&quot;avojak/pihole-influxdb-monitor-issues&quot; style=&quot;white-space:nowrap;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 512 512&quot;&gt;&lt;!--! Font Awesome Pro 6.1.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --&gt;&lt;path d=&quot;M160 256C160 202.1 202.1 160 256 160C309 160 352 202.1 352 256C352 309 309 352 256 352C202.1 352 160 309 160 256zM512 256C512 397.4 397.4 512 256 512C114.6 512 0 397.4 0 256C0 114.6 114.6 0 256 0C397.4 0 512 114.6 512 256zM256 48C141.1 48 48 141.1 48 256C48 370.9 141.1 464 256 464C370.9 464 464 370.9 464 256C464 141.1 370.9 48 256 48z&quot; /&gt;&lt;/svg&gt;&lt;/span&gt;
    &lt;span id=&quot;avojak/pihole-influxdb-monitor-stargazers&quot; style=&quot;white-space:nowrap;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 576 512&quot;&gt;&lt;!--! Font Awesome Pro 6.1.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --&gt;&lt;path d=&quot;M287.9 0C297.1 0 305.5 5.25 309.5 13.52L378.1 154.8L531.4 177.5C540.4 178.8 547.8 185.1 550.7 193.7C553.5 202.4 551.2 211.9 544.8 218.2L433.6 328.4L459.9 483.9C461.4 492.9 457.7 502.1 450.2 507.4C442.8 512.7 432.1 513.4 424.9 509.1L287.9 435.9L150.1 509.1C142.9 513.4 133.1 512.7 125.6 507.4C118.2 502.1 114.5 492.9 115.1 483.9L142.2 328.4L31.11 218.2C24.65 211.9 22.36 202.4 25.2 193.7C28.03 185.1 35.5 178.8 44.49 177.5L197.7 154.8L266.3 13.52C270.4 5.249 278.7 0 287.9 0L287.9 0zM287.9 78.95L235.4 187.2C231.9 194.3 225.1 199.3 217.3 200.5L98.98 217.9L184.9 303C190.4 308.5 192.9 316.4 191.6 324.1L171.4 443.7L276.6 387.5C283.7 383.7 292.2 383.7 299.2 387.5L404.4 443.7L384.2 324.1C382.9 316.4 385.5 308.5 391 303L476.9 217.9L358.6 200.5C350.7 199.3 343.9 194.3 340.5 187.2L287.9 78.95z&quot; /&gt;&lt;/svg&gt;&lt;/span&gt;
    &lt;span id=&quot;avojak/pihole-influxdb-monitor-forks&quot; style=&quot;white-space:nowrap;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 448 512&quot;&gt;&lt;!--! Font Awesome Pro 6.1.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --&gt;&lt;path d=&quot;M160 80C160 112.8 140.3 140.1 112 153.3V192C112 209.7 126.3 224 144 224H304C321.7 224 336 209.7 336 192V153.3C307.7 140.1 288 112.8 288 80C288 35.82 323.8 0 368 0C412.2 0 448 35.82 448 80C448 112.8 428.3 140.1 400 153.3V192C400 245 357 288 304 288H256V358.7C284.3 371 304 399.2 304 432C304 476.2 268.2 512 224 512C179.8 512 144 476.2 144 432C144 399.2 163.7 371 192 358.7V288H144C90.98 288 48 245 48 192V153.3C19.75 140.1 0 112.8 0 80C0 35.82 35.82 0 80 0C124.2 0 160 35.82 160 80V80zM80 104C93.25 104 104 93.25 104 80C104 66.75 93.25 56 80 56C66.75 56 56 66.75 56 80C56 93.25 66.75 104 80 104zM368 104C381.3 104 392 93.25 392 80C392 66.75 381.3 56 368 56C354.7 56 344 66.75 344 80C344 93.25 354.7 104 368 104zM224 408C210.7 408 200 418.7 200 432C200 445.3 210.7 456 224 456C237.3 456 248 445.3 248 432C248 418.7 237.3 408 224 408z&quot; /&gt;&lt;/svg&gt;&lt;/span&gt;
  &lt;/footer&gt;
  &lt;footer&gt;
    &lt;a href=&quot;https://github.com/avojak/pihole-influxdb-monitor&quot;&gt;
      &lt;span&gt;View on GitHub →&lt;/span&gt;
    &lt;/a&gt;
  &lt;/footer&gt;

  &lt;script defer=&quot;&quot;&gt;
    // Data for testing without actually hitting the GitHub API
    // out = {&quot;stargazers_count&quot;:123456, &quot;forks_count&quot;:123, &quot;open_issues_count&quot;:60, &quot;description&quot;:&quot;Super cool project with absolutely zero bugs&quot;};
    // document.getElementById(&quot;avojak/pihole-influxdb-monitor-stargazers&quot;).innerHTML += numeral(out['stargazers_count']).format('0.[0]a') + &quot; Stars&quot;;
    // document.getElementById(&quot;avojak/pihole-influxdb-monitor-issues&quot;).innerHTML += numeral(out['open_issues_count']).format('0.[0]a') + &quot; Issues&quot;;
    // document.getElementById(&quot;avojak/pihole-influxdb-monitor-description&quot;).innerHTML = out['description'];
    // document.getElementById(&quot;avojak/pihole-influxdb-monitor-forks&quot;).innerHTML += numeral(out['forks_count']).format('0.[0]a') + &quot; Forks&quot;;

    fetch('https://api.github.com/repos/avojak/pihole-influxdb-monitor')
      .then(res =&gt; {
        if (!res.ok) {
          throw new Error(&quot;Bad network response&quot;);
        }
        return res.json();
      })
      .then((out) =&gt; {
        var stargazers = out['stargazers_count']
        var issues = out['open_issues_count']
        var forks = out['forks_count']
        
        // Populate the elements with the data from the response
        document.getElementById(&quot;avojak/pihole-influxdb-monitor-stargazers&quot;).innerHTML += numeral(stargazers).format('0.[0]a') + &quot; Star&quot; + (stargazers != 1 ? &quot;s&quot; : &quot;&quot;);
        document.getElementById(&quot;avojak/pihole-influxdb-monitor-issues&quot;).innerHTML += numeral(issues).format('0.[0]a') + &quot; Issue&quot; + (issues != 1 ? &quot;s&quot; : &quot;&quot;);
        document.getElementById(&quot;avojak/pihole-influxdb-monitor-forks&quot;).innerHTML += numeral(forks).format('0.[0]a') + &quot; Fork&quot; + (forks != 1 ? &quot;s&quot; : &quot;&quot;);
        document.getElementById(&quot;avojak/pihole-influxdb-monitor-description&quot;).innerHTML = out['description'];
    }).catch((err) =&gt; {
      console.error(err);
      // Clear out the fields in case of error rather than showing 0 values
      document.getElementById(&quot;avojak/pihole-influxdb-monitor-stargazers&quot;).innerHTML = &quot;&quot;;
      document.getElementById(&quot;avojak/pihole-influxdb-monitor-issues&quot;).innerHTML = &quot;&quot;;
      document.getElementById(&quot;avojak/pihole-influxdb-monitor-forks&quot;).innerHTML = &quot;&quot;;
    });
  &lt;/script&gt;
&lt;/div&gt;

&lt;p&gt;I also have a blog post about how I created a Grafana dashboard for all the metrics:&lt;/p&gt;

&lt;aside&gt;
  &lt;a class=&quot;featured with-image 1&quot; href=&quot;https://avojak.com/blog/2022/12/15/pihole-grafana-dashboard/&quot;&gt;
    &lt;div class=&quot;featured-image&quot; alt=&quot;Featured image&quot; style=&quot;background-image: url(https://s3.amazonaws.com/avojak.com/2022-12-15-pihole-influxdb-monitor/dashboard.png);&quot;&gt;&lt;/div&gt;
    &lt;header&gt;
      &lt;h2&gt;Grafana Dashboard for Pi-hole Stats&lt;/h2&gt;
      &lt;h3&gt;Excessive Pi-hole monitoring with pretty graphs!&lt;/h3&gt;
      &lt;div class=&quot;byline&quot;&gt;
        &lt;div class=&quot;avatar&quot;&gt;
          &lt;img srcset=&quot;https://www.gravatar.com/avatar/4dfa9066e794647fdedf323ecab85333?s=96&amp;amp;d=blank 2x&quot; src=&quot;https://www.gravatar.com/avatar/4dfa9066e794647fdedf323ecab85333?s=48&amp;amp;d=blank&quot; alt=&quot;Avatar for Andrew Vojak&quot; /&gt;
        &lt;/div&gt;
        &lt;div class=&quot;author&quot;&gt;
          &lt;span class=&quot;name&quot;&gt;Andrew Vojak&lt;/span&gt;
        &lt;/div&gt;
        &lt;time class=&quot;post-date&quot; datetime=&quot;2022-12-15&quot;&gt;Thu, Dec 15, 2022&lt;/time&gt;
        &lt;span class=&quot;read-time&quot; title=&quot;Estimated read time&quot;&gt;3 min read&lt;/span&gt;
      &lt;/div&gt;
    &lt;/header&gt;
  &lt;/a&gt;
&lt;/aside&gt;

&lt;h2 id=&quot;the-hardware&quot;&gt;The Hardware&lt;/h2&gt;

&lt;p&gt;My setup is always evolving, but for now I am using a total of six physical servers: five Mac minis and one custom-build rack-mount server. Each server will be running VMware ESXi as the hypervisor. In a nutshell, a hypervisor is what is installed on the bare metal machine (host) to run and manage virtual machines (guests). The hypervisor provides the guests with isolated, &lt;a href=&quot;https://en.wikipedia.org/wiki/Hardware_virtualization&quot;&gt;virtual hardware&lt;/a&gt; on which to run.&lt;/p&gt;

&lt;p&gt;I chose ESXi because I’m familiar with it, although I would like to give Proxmox a try at some point in the future.&lt;/p&gt;

&lt;h2 id=&quot;automation-and-configuration-as-code&quot;&gt;Automation and Configuration-as-Code&lt;/h2&gt;

&lt;p&gt;Using virtual machines is not a necessity, however there are many benefits. At scale, virtual machines can be spun up or down as demand ebbs and flows. In a small homelab setting, using virtual machines allows me to easily move between hardware hosts, create backups, and quickly test and rollback changes.&lt;/p&gt;

&lt;p&gt;When I first set out building my homelab a couple of years ago, my goal was to learn some industry standard tools. Sure, it might be overkill, but part of the fun of a homelab is having an environment to learn in! I have since been able to bring all of this experience into my day job to deploy applications enterprise-wide!&lt;/p&gt;

&lt;p&gt;The three main pieces of the puzzle that I’m going to tackle in this Part 1 post are:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Creating the guest OS image using &lt;a href=&quot;https://www.packer.io&quot;&gt;Packer&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Deploying the guest VM using &lt;a href=&quot;https://www.terraform.io&quot;&gt;Terraform&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Installing software VM using &lt;a href=&quot;https://www.ansible.com&quot;&gt;Ansible&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There isn’t a particular reason I chose each of these tools over others that are available. I already knew &lt;em&gt;about&lt;/em&gt; them, so I decided to give it a go!&lt;/p&gt;

&lt;h3 id=&quot;packer---creating-the-os-image&quot;&gt;Packer - Creating the OS Image&lt;/h3&gt;

&lt;h4 id=&quot;what-is-packer&quot;&gt;What is Packer?&lt;/h4&gt;

&lt;blockquote&gt;
  &lt;p&gt;Packer is an open source tool for creating identical machine images for multiple platforms from a single source configuration.
…
A machine image is a single static unit that contains a pre-configured operating system and installed software which is used to quickly create new running machines. Machine image formats change for each platform. Some examples include AMIs for EC2, VMDK/VMX files for VMware, OVF exports for VirtualBox, etc. &lt;sup id=&quot;fnref:2&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4 id=&quot;why-use-packer&quot;&gt;Why use Packer?&lt;/h4&gt;

&lt;p&gt;There are already images available for server operating systems, so why create your own?&lt;/p&gt;

&lt;p&gt;Pre-built images a great, but think about what happens when you install it for the first time: you click through the installer, pick some date/time/keyboard/location options, create a user, etc. etc… Definitely not something I want to repeat every time I deploy a new VM!&lt;/p&gt;

&lt;p&gt;With Packer, I took the Ubuntu 22.04 LTS Server ISO and created an OVA image file to deploy to ESXi. The OVA has Ubuntu Server already pre-installed, pre-configured, and an SSH key is setup for use later on with Ansible!&lt;/p&gt;

&lt;h4 id=&quot;how&quot;&gt;How?&lt;/h4&gt;

&lt;p&gt;Packer uses JSON template files to define how to build and provision images. My template file was largely influenced by this post: &lt;a href=&quot;https://gmusumeci.medium.com/how-to-use-packer-to-build-an-ubuntu-18-04-template-for-vmware-vsphere-da5bbf1be92f&quot;&gt;How to use Packer to Build an Ubuntu 18.04 Template for VMware vSphere&lt;/a&gt;. Even though I’m ultimatel not using vSphere, the process is nearly identical.&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;builders&lt;/code&gt; block defines basic information about the ISO to use, the commands to run on boot to automate the installation, and where to place the output image upon completion. Note that some details, such as the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;memory&lt;/code&gt;, can be modified once the image is actual used to provision a VM in ESXi.&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;builders&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;vmware-iso&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;guest_os_type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;ubuntu-64&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;memory&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1024&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;{{user `vm_name`}}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;iso_url&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;{{user `iso_url`}}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;iso_checksum&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;{{user `iso_checksum`}}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;http_directory&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;{{user `vm_name`}}/http&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;boot_wait&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;10s&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;boot_command&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;c&amp;lt;wait&amp;gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;linux /casper/vmlinuz --- autoinstall ds=&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;nocloud-net;seedfrom=http://{{.HTTPIP}}:{{.HTTPPort}}/&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;enter&amp;gt;&amp;lt;wait&amp;gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;initrd /casper/initrd&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;enter&amp;gt;&amp;lt;wait&amp;gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;boot&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;enter&amp;gt;&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;shutdown_command&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;echo '{{user `password`}}' | sudo -S shutdown -P now&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;ssh_username&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;{{user `username`}}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;ssh_password&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;{{user `password`}}&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;ssh_port&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;22&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;ssh_pty&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;ssh_timeout&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;1800s&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;ssh_handshake_attempts&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;20&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;output_directory&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;{{user `vm_name`}}/build&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;user-data&lt;/code&gt; file provides &lt;a href=&quot;https://cloudinit.readthedocs.io/en/latest/&quot;&gt;cloud-init&lt;/a&gt; configuration for the language, keyboard configuration, user creation, etc.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;em&gt;In a real-world setting you would not want to use a static password here. In my case I’m only using that password during the Packer build - when it comes time to deploy, the Ansible will prevent password-based authentication for SSH.&lt;/em&gt;&lt;/p&gt;

&lt;div class=&quot;language-yml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;c1&quot;&gt;#cloud-config&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;autoinstall&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;packages_update&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;packages_upgrade&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;identity&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;hostname&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ubuntu-server-22.04&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;provisioner&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;REDACTED&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;early-commands&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# otherwise packer tries to connect and exceed max attempts:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;systemctl stop ssh&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;systemctl restart systemd-timesyncd&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;keyboard&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;layout&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;us&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;locale&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;en_US&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;timezone&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;UTC&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;ssh&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;install-server&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;allow-pw&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;authorized-keys&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;ssh-rsa&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;...&quot;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;late-commands&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;sed -i -e 's/^#\?PasswordAuthentication.*/PasswordAuthentication yes/g' /target/etc/ssh/sshd_config&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;echo 'provisioner ALL=(ALL) NOPASSWD:ALL' &amp;gt; /target/etc/sudoers.d/provisioner&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;storage&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;layout&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;direct&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Within the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;provisioners&lt;/code&gt; block, we can define commands to run against the installed operating system. This allows you to install software and make other changes before the template is finalized.&lt;/p&gt;

&lt;p&gt;There are other types of provisioners, but in my case I only wanted to accmoplish two tasks:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Install software updates&lt;/li&gt;
  &lt;li&gt;Install an SSH key&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;provisioners&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;shell&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;inline&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;sudo apt-get install -y curl cloud-init python3-distutils&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;curl -sSL https://raw.githubusercontent.com/vmware/cloud-init-vmware-guestinfo/master/install.sh | sudo sh -&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;shell&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;inline&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;sudo rm /etc/machine-id /var/lib/dbus/machine-id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;sudo touch /etc/machine-id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;sudo chmod 444 /etc/machine-id&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;file&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;source&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;{{user `vm_name`}}/files/00-installer-config.yaml&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;destination&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;/tmp/00-installer-config.yaml&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;shell&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;inline&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;sudo mv /tmp/00-installer-config.yaml /etc/netplan/00-installer-config.yaml&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;sudo chmod 644 /etc/netplan/00-installer-config.yaml&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;sudo chown root:root /etc/netplan/00-installer-config.yaml&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;sudo netplan apply&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The SSH key is provided at build time using a variable:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;variables&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;ssh_key&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The final piece of the template file is a post-processor to create the OVA file from the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.vmx&lt;/code&gt; file:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;post-processors&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;shell-local&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;inline&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;ovftool {{user `vm_name`}}/build/packer-{{user `vm_name`}}.vmx output/{{user `vm_name`}}.ova&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;With the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vmware-iso&lt;/code&gt; type builder we get VMDK/VMX files for free, but I wanted to deploy using an OVA image (I had better luck deploying OVAs with Terraform). To do this I simply leveraged the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ovftool&lt;/code&gt; from VMware &lt;sup id=&quot;fnref:3&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:3&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;h3 id=&quot;terraform---deploying-the-vms&quot;&gt;Terraform - Deploying the VMs&lt;/h3&gt;

&lt;h4 id=&quot;what-is-terraform&quot;&gt;What is Terraform?&lt;/h4&gt;

&lt;p&gt;Terraform is a tool to enable Infrastructure as Code.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Provisioning infrastructure through point-and-click GUIs or custom scripts is slow, error-prone, inefficient, and doesn’t scale.
…
With HashiCorp Terraform, provisioning and security can be automated with declarative infrastructure and policy as code. Infrastructure and policies are codified, shared, versioned, and executed within a workflow that is consistent across all infrastructure. &lt;sup id=&quot;fnref:4&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:4&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;4&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4 id=&quot;why-use-terraform&quot;&gt;Why use Terraform?&lt;/h4&gt;

&lt;p&gt;Even though I’m working with a relatively small environment, Terraform allows me to quickly spin up, tear down, and replace VMs.&lt;/p&gt;

&lt;h4 id=&quot;how-1&quot;&gt;How?&lt;/h4&gt;

&lt;p&gt;I created an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;appserver&lt;/code&gt; module that allowed me to reduce a lot of duplication in my code:&lt;/p&gt;

&lt;div class=&quot;language-hcl highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nx&quot;&gt;resource&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;esxi_guest&quot;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;appserver&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;guest_name&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;guest_name&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;disk_store&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;datastore1&quot;&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;network_interfaces&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;virtual_network&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;VM Network&quot;&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;nic_type&lt;/span&gt;        &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;vmxnet3&quot;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;guest_startup_timeout&lt;/span&gt;  &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;45&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# Seconds&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;guest_shutdown_timeout&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;30&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# Seconds&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;boot_firmware&lt;/span&gt;  &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;bios&quot;&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;boot_disk_size&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;boot_disk_size&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;memsize&lt;/span&gt;        &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;memsize&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1024&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;numvcpus&lt;/span&gt;       &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;numvcpus&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;power&lt;/span&gt;          &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;on&quot;&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;ovf_source&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ova_file&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then for each VM that I want to create, I simply invoke the module with slightly different settings. For example, for one of my Pi-hole servers:&lt;/p&gt;

&lt;div class=&quot;language-hcl highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;pihole01&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;source&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;./modules/appserver&quot;&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;providers&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;esxi&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;esxi&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;esx_mac_03&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;guest_name&lt;/span&gt;     &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;pihole01&quot;&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;boot_disk_size&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;25&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;memsize&lt;/span&gt;        &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;numvcpus&lt;/span&gt;       &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;ova_file&lt;/span&gt;       &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;var&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ova_files&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;ubuntu&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;amd64&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;ansible---installing-software&quot;&gt;Ansible - Installing Software&lt;/h3&gt;

&lt;h4 id=&quot;what-is-ansible&quot;&gt;What is Ansible?&lt;/h4&gt;

&lt;p&gt;Ansible is a tool that enables automation of infrastructure. You write files called “playbooks” which define a series a idempotent tasks that configure infrastructure, applications, systems, and more.&lt;/p&gt;

&lt;h4 id=&quot;why-use-ansible&quot;&gt;Why use Ansible?&lt;/h4&gt;

&lt;p&gt;Idempotency is a key concept within Ansible, and this means that a well-crafted playbook can be run repeatedly and always have the same result. If something starts acting strangely on a server, I can re-run my playbooks to get everything back in alignemnt. Similarly, if I need to rebuild a VM, I simply have to point the playbook to a different server and it will setup all the services and applications just like they were before.&lt;/p&gt;

&lt;h4 id=&quot;how-2&quot;&gt;How?&lt;/h4&gt;

&lt;p&gt;I decided early on that I wanted as many applications as possible to be installed via Docker. This has a few advantages:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Software is installed, managed, and monitored consistently&lt;/li&gt;
  &lt;li&gt;I can easily update/replace the underlying VM with little to no effect&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As a result, most of the VMs that I have in my lab are really just Docker hosts, and most of my Ansible roles simply drop in Docker compose files.&lt;/p&gt;

&lt;h2 id=&quot;end-result&quot;&gt;End Result&lt;/h2&gt;

&lt;p&gt;After all of that, I finally spent a bit of time putting together a diagram of all the services that I’m running:&lt;/p&gt;

&lt;figure class=&quot;constrained&quot;&gt;
  &lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/avojak/homelab/master/documentation/Homelab%20Architecture.png&quot; alt=&quot;Homelab Architecture&quot; /&gt;&lt;/p&gt;
  &lt;figcaption&gt;All the services and hosts in one picture!&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://www.epmmarshall.com/homelab-intro/&quot;&gt;https://www.epmmarshall.com/homelab-intro/&lt;/a&gt; &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:2&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://www.packer.io/intro&quot;&gt;https://www.packer.io/intro&lt;/a&gt; &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:3&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://code.vmware.com/web/tool/4.4.0/ovf&quot;&gt;https://code.vmware.com/web/tool/4.4.0/ovf&lt;/a&gt; &lt;a href=&quot;#fnref:3&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:4&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://www.terraform.io/use-cases/infrastructure-as-code&quot;&gt;https://www.terraform.io/use-cases/infrastructure-as-code&lt;/a&gt; &lt;a href=&quot;#fnref:4&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</description>
          <pubDate>Sat, 30 Sep 2023 00:00:00 +0000</pubDate>
          <link>https://avojak.com/blog/2023/09/30/homelab-introduction/</link>
          <guid isPermaLink="true">https://avojak.com/blog/2023/09/30/homelab-introduction/</guid>
          
              
              
          <enclosure url="https://avojak.com/blog" />
          
          
          <category>homelab</category>
          
          <category>software</category>
          
          <category>evergreen</category>
          
          
        </item>
      
    
      
        <item>
          <title>DIY Built-in Bathroom Cabinet</title>
          <description>&lt;p&gt;Our primary bathroom used to have an awkard nook next to our shower, so I decided to dive in and learn about building cabinets! What
was once wasted space is now efficient storage. And, true to form, it took me far longer than it should have.&lt;/p&gt;

&lt;h2 id=&quot;the-space&quot;&gt;The Space&lt;/h2&gt;

&lt;p&gt;This empty nook next to our shower has been empty since we moved into the house. We suspect that when the house was originally built,
the builder offered an upgrade to do some sort of cabinet or built-in vanity here. But to us, it was wasted space!&lt;/p&gt;

&lt;figure class=&quot;constrained&quot;&gt;
  &lt;p&gt;&lt;img src=&quot;https://s3.amazonaws.com/avojak.com/2023-05-21-diy-builtin-bathroom-cabinet/IMG_3541.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
  &lt;figcaption&gt;This empty nook&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&quot;design&quot;&gt;Design&lt;/h2&gt;

&lt;p&gt;The driving factor in the design was to maximize storage space. Our linen closet is pretty small and we had the opportunity here to
essentially double our linen closet space.&lt;/p&gt;

&lt;p&gt;The appearance was inspired by a post that my wife found: &lt;a href=&quot;https://www.jennasuedesign.com/diy-fluted-cabinet-doors/&quot;&gt;DIY Fluted Cabinet Doors&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;My first step was to model the project using Fusion 360:&lt;/p&gt;

&lt;figure class=&quot;half&quot;&gt;
  &lt;p&gt;&lt;img src=&quot;https://s3.amazonaws.com/avojak.com/2023-05-21-diy-builtin-bathroom-cabinet/IMG_3599.JPG&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;https://s3.amazonaws.com/avojak.com/2023-05-21-diy-builtin-bathroom-cabinet/IMG_3600.JPG&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;/figure&gt;

&lt;p&gt;At this point we hadn’t yet settled on a final color or what handles we wanted to use on the doors.&lt;/p&gt;

&lt;h2 id=&quot;materials&quot;&gt;Materials&lt;/h2&gt;

&lt;p&gt;With the core design all settled, it was off to the hardware store! For the plywood, I opted for &lt;a href=&quot;https://www.obsessedwoodworking.com/what-is-sande-plywood/&quot;&gt;sandeply&lt;/a&gt;. This was a new type of plywood to me, but it had a major advantage over the birch: lower cost! I also really loved the grain pattern, and since the interior of the cabinets wouldn’t be painted, I really wanted that to pop.&lt;/p&gt;

&lt;p&gt;I decided early on that I would try to use 1/2” plywood for the cabinet carcasses. Most people use 3/4” when building cabinets, but since
this project wouldn’t be supporting a lot of weight, and would be secured to the wall, I felt comfortable using the 1/2” plywood. For the back panel
I went with 1/4” sheets of sande plywood.&lt;/p&gt;

&lt;p&gt;For the face frame, I picked up a bunch of 1”x2” poplar boards because poplar is great for painting. The doors are also made out of poplar, but are 1”x3” boards
with a 1/4” sheet of sande ply for the back panel.&lt;/p&gt;

&lt;figure class=&quot;half&quot;&gt;
  &lt;p&gt;&lt;img src=&quot;https://s3.amazonaws.com/avojak.com/2023-05-21-diy-builtin-bathroom-cabinet/IMG_3593.jpeg&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;https://s3.amazonaws.com/avojak.com/2023-05-21-diy-builtin-bathroom-cabinet/IMG_3619.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
  &lt;figcaption&gt;&lt;b&gt;Left:&lt;/b&gt; All the sheets of plywood | &lt;b&gt;Right:&lt;/b&gt; Poplar boards for the face frame&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&quot;cabinet-carcass&quot;&gt;Cabinet Carcass&lt;/h2&gt;

&lt;p&gt;Time to start building! The first step was to cut the boards down to size for the cabinet carcasses. Next, I cut horizontal dadoes
along the bottom and middle of the side panels where the bottom and middle shelf panels sit. I also cut a long veritcal dado for
the back panel to slide into. Then I drilled a LOT of pocket holes for all the brace pieces!&lt;/p&gt;

&lt;p&gt;To cut the dadoes I did repeated passes on my table saw. For the veritcal dadoes this was relatively easy, however for the horizontal
dadoes I probably should’ve used a router, as that would’ve been a bit safer. For the dadoes on the very edge, I used a sacrificial
piece of scrap would to sneak all the way up on the edge of the plywood.&lt;/p&gt;

&lt;figure class=&quot;quarter&quot;&gt;
  &lt;p&gt;&lt;img src=&quot;https://s3.amazonaws.com/avojak.com/2023-05-21-diy-builtin-bathroom-cabinet/IMG_3603.jpeg&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;https://s3.amazonaws.com/avojak.com/2023-05-21-diy-builtin-bathroom-cabinet/IMG_3602.jpeg&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;https://s3.amazonaws.com/avojak.com/2023-05-21-diy-builtin-bathroom-cabinet/IMG_3596.jpeg&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;https://s3.amazonaws.com/avojak.com/2023-05-21-diy-builtin-bathroom-cabinet/IMG_3604.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
  &lt;figcaption&gt;&lt;b&gt;Left:&lt;/b&gt; Side panels | &lt;b&gt;Center-left:&lt;/b&gt; Test fit of the dado | &lt;b&gt;Center-right:&lt;/b&gt; Dadoes on the braces | &lt;b&gt;Right:&lt;/b&gt; Pocket holes!&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Then on to assembly! I picked up some cheap corner clamps to help keep the side panels square with the shelves, although they don’t do much in the way of clamping pressure.
There were multiple times in this project where I should’ve picked up a nail gun, and this was the first.&lt;/p&gt;

&lt;figure class=&quot;quarter&quot;&gt;
  &lt;p&gt;&lt;img src=&quot;https://s3.amazonaws.com/avojak.com/2023-05-21-diy-builtin-bathroom-cabinet/IMG_3605.jpeg&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;https://s3.amazonaws.com/avojak.com/2023-05-21-diy-builtin-bathroom-cabinet/IMG_3606.jpeg&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;https://s3.amazonaws.com/avojak.com/2023-05-21-diy-builtin-bathroom-cabinet/IMG_3607.jpeg&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;https://s3.amazonaws.com/avojak.com/2023-05-21-diy-builtin-bathroom-cabinet/IMG_3608.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
  &lt;figcaption&gt;&lt;b&gt;Left:&lt;/b&gt; Glue-up of shelves with side panels | &lt;b&gt;Center-left:&lt;/b&gt; Back panel and top braces | &lt;b&gt;Center-right:&lt;/b&gt; Finished front | &lt;b&gt;Right:&lt;/b&gt; Finished rear&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;With the carcasses complete, I set about sanding, and sanding, and sanding… Finally I was able to stack them for the first time to get a feel for just how large
this cabinet was going to be.&lt;/p&gt;

&lt;figure class=&quot;third&quot;&gt;
  &lt;p&gt;&lt;img src=&quot;https://s3.amazonaws.com/avojak.com/2023-05-21-diy-builtin-bathroom-cabinet/IMG_3609.jpeg&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;https://s3.amazonaws.com/avojak.com/2023-05-21-diy-builtin-bathroom-cabinet/IMG_3611.jpeg&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;https://s3.amazonaws.com/avojak.com/2023-05-21-diy-builtin-bathroom-cabinet/IMG_3672.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
  &lt;figcaption&gt;&lt;b&gt;Left:&lt;/b&gt; Completed carcasses | &lt;b&gt;Center:&lt;/b&gt; First stack | &lt;b&gt;Right:&lt;/b&gt; Test fit in the nook&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;After multiple coats of water-based polyurethane alternated with a light sanding, I added all 176 shelf pin holes. Thankfully there’s a Kreg jig for that, so it took no time at all.&lt;/p&gt;

&lt;figure class=&quot;half&quot;&gt;
  &lt;p&gt;&lt;img src=&quot;https://s3.amazonaws.com/avojak.com/2023-05-21-diy-builtin-bathroom-cabinet/IMG_3623.jpeg&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;https://s3.amazonaws.com/avojak.com/2023-05-21-diy-builtin-bathroom-cabinet/IMG_3630.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
  &lt;figcaption&gt;&lt;b&gt;Left:&lt;/b&gt; Post-polyurethane | &lt;b&gt;Right:&lt;/b&gt; Shelf pin holes&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Around this time I also put together four adjustable shelves. These were made with the same 1/2” ply and a strip of poplar across the front that would eventually
be painted to match the face frame. I added some edge-banding to the other three sides to help protect the plywood when the shelves are adjusted.&lt;/p&gt;

&lt;figure class=&quot;half&quot;&gt;
  &lt;p&gt;&lt;img src=&quot;https://s3.amazonaws.com/avojak.com/2023-05-21-diy-builtin-bathroom-cabinet/IMG_3622.jpeg&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;https://s3.amazonaws.com/avojak.com/2023-05-21-diy-builtin-bathroom-cabinet/IMG_3629.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
  &lt;figcaption&gt;&lt;b&gt;Left:&lt;/b&gt; Adjustable shelf glue-up | &lt;b&gt;Right:&lt;/b&gt; Adjustable shelf after polyurethane&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&quot;face-frame&quot;&gt;Face Frame&lt;/h2&gt;

&lt;p&gt;The face frame went together pretty quickly, and is held together with pocket holes on the back side.&lt;/p&gt;

&lt;figure class=&quot;half&quot;&gt;
  &lt;p&gt;&lt;img src=&quot;https://s3.amazonaws.com/avojak.com/2023-05-21-diy-builtin-bathroom-cabinet/IMG_3673.jpeg&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;https://s3.amazonaws.com/avojak.com/2023-05-21-diy-builtin-bathroom-cabinet/68581816484__F472E4B2-D588-45EF-B2DE-B364E81B85DE.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
  &lt;figcaption&gt;&lt;b&gt;Left:&lt;/b&gt; Test fit of the face frame | &lt;b&gt;Right:&lt;/b&gt; I have some strong words for whoever decided that stickers belong on wood&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;To attach the face frame, the plan was to use biscuits for alignment followed by pocket holes. Rather than buy a biscuit joiner, I picked up
a router bit that matched the thickness of the biscuits. With the face frame clamped to the carcasses, I used some painter tape across the joints
and drew a straight line. Then I cut the tape and had perfect markers for where the biscuit joints should be cut on both the cabinet carcasses and
the back of the face frame.&lt;/p&gt;

&lt;figure class=&quot;constrained&quot;&gt;
  &lt;p&gt;&lt;img src=&quot;https://s3.amazonaws.com/avojak.com/2023-05-21-diy-builtin-bathroom-cabinet/68581552568__0156BB25-E6AB-4DFD-B960-1849376756F2.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;/figure&gt;

&lt;p&gt;I forgot to take a picture of the process of lining up the biscuit joints, but here’s a picture of me showing off proper PPE with the markings in the background!&lt;/p&gt;

&lt;figure class=&quot;constrained&quot;&gt;
  &lt;p&gt;&lt;img src=&quot;https://s3.amazonaws.com/avojak.com/2023-05-21-diy-builtin-bathroom-cabinet/IMG_3788.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;/figure&gt;

&lt;h2 id=&quot;doors&quot;&gt;Doors&lt;/h2&gt;

&lt;p&gt;To assemble the doors, I cut down 1x3 pieces of poplar and cut a 1/4” dado for the plywood panel. Then I cut biscuit joints on the ends
of the four side pieces using the same method that I used for the face frame. Finally I gingerly applied the clue, slid in the side panel and
used a special clamp that would hold all four corners at once and apply even pressure.&lt;/p&gt;

&lt;p&gt;Now you might be asking yourself why I decided to do miter joints on something that would ultimately be painted, and you would never
know that it was a miter joint. And you would be 100% correct. This was not something that I thought through very well. A simple tongue
and groove door would’ve been far simpler. But hey, I learned some new techniques!&lt;/p&gt;

&lt;figure class=&quot;third&quot;&gt;
  &lt;p&gt;&lt;img src=&quot;https://s3.amazonaws.com/avojak.com/2023-05-21-diy-builtin-bathroom-cabinet/IMG_3707.jpeg&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;https://s3.amazonaws.com/avojak.com/2023-05-21-diy-builtin-bathroom-cabinet/IMG_3731.jpeg&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;https://s3.amazonaws.com/avojak.com/2023-05-21-diy-builtin-bathroom-cabinet/IMG_3729.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
  &lt;figcaption&gt;&lt;b&gt;Left:&lt;/b&gt; Dadoes on the side pieces | &lt;b&gt;Center:&lt;/b&gt; Biscuit joints cut using the router | &lt;b&gt;Right:&lt;/b&gt; Final assembly&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;To attach the doors, I picked up some Blum soft close hinges. As always, there’s a Kreg jig for that! I’ve never used these hinges before, so I made a small replica of the face frame and door to test and adjust the alignment settings on the jig.&lt;/p&gt;

&lt;figure class=&quot;constrained&quot;&gt;
  &lt;p&gt;&lt;img src=&quot;https://s3.amazonaws.com/avojak.com/2023-05-21-diy-builtin-bathroom-cabinet/IMG_4593.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;/figure&gt;

&lt;h2 id=&quot;installation&quot;&gt;Installation&lt;/h2&gt;

&lt;p&gt;Time to start installing! It was a little tight trying to stack cabinets that go 98% of the way to the ceiling, but once I got them aligned I clamped them together and slid them into place.&lt;/p&gt;

&lt;figure class=&quot;half&quot;&gt;
  &lt;p&gt;&lt;img src=&quot;https://s3.amazonaws.com/avojak.com/2023-05-21-diy-builtin-bathroom-cabinet/IMG_3817.jpeg&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;https://s3.amazonaws.com/avojak.com/2023-05-21-diy-builtin-bathroom-cabinet/IMG_3825.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
  &lt;figcaption&gt;&lt;b&gt;Left:&lt;/b&gt; Stacked in the bathroom | &lt;b&gt;Right:&lt;/b&gt; Clamps ready to go&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Attaching the face frame was a massive pain. My original plan was to use pocket holes coming in from the outside of the cabinet carcass into the face frame. I got the biscuits glued, applied some glue to the edges of the cabinet carcasses, got the face frame on and clamped, screwed in the first pocket screw, and… it broke through the face frame.&lt;/p&gt;

&lt;p&gt;At this point I started panicking. Although I had applied some glue, I was relying on the screws to pull the face frame tight. In my panic, I grabbed some finish nails (I didn’t have a nail gun at this point) and started nailing the face frame to the cabinets. It wasn’t pretty, but I would later be able to patch over the nails and touch up the paint.&lt;/p&gt;

&lt;figure class=&quot;half&quot;&gt;
  &lt;p&gt;&lt;img src=&quot;https://s3.amazonaws.com/avojak.com/2023-05-21-diy-builtin-bathroom-cabinet/IMG_3823.jpeg&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;https://s3.amazonaws.com/avojak.com/2023-05-21-diy-builtin-bathroom-cabinet/IMG_3826.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
  &lt;figcaption&gt;&lt;b&gt;Left:&lt;/b&gt; Pocket hole damage | &lt;b&gt;Right:&lt;/b&gt; face frame installed&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Finally I added some trim pieces to match the curvature of the wall. I wasn’t confident enough to attemt to scribe the face frame to match the wall, so this allowed my some flexibility to bend the trim to match. For this step I went and bought a nail gun - so much easier!&lt;/p&gt;

&lt;figure class=&quot;constrained&quot;&gt;
  &lt;p&gt;&lt;img src=&quot;https://s3.amazonaws.com/avojak.com/2023-05-21-diy-builtin-bathroom-cabinet/IMG_4587.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
  &lt;figcaption&gt;Trim added and caulked&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;All that was left to do was add the hardware on the doors and install the doors!&lt;/p&gt;

&lt;figure class=&quot;half&quot;&gt;
  &lt;p&gt;&lt;img src=&quot;https://s3.amazonaws.com/avojak.com/2023-05-21-diy-builtin-bathroom-cabinet/IMG_4678.jpeg&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;https://s3.amazonaws.com/avojak.com/2023-05-21-diy-builtin-bathroom-cabinet/IMG_4679.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;/figure&gt;

&lt;h2 id=&quot;finished-product&quot;&gt;Finished Product&lt;/h2&gt;

&lt;p&gt;All done!&lt;/p&gt;

&lt;figure class=&quot;half&quot;&gt;
  &lt;p&gt;&lt;img src=&quot;https://s3.amazonaws.com/avojak.com/2023-05-21-diy-builtin-bathroom-cabinet/IMG_4683.jpeg&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;https://s3.amazonaws.com/avojak.com/2023-05-21-diy-builtin-bathroom-cabinet/IMG_4684.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;/figure&gt;

&lt;h2 id=&quot;lessons-learned&quot;&gt;Lessons Learned&lt;/h2&gt;

&lt;p&gt;As always, there were lots of lessons to be learned. Here’s a few of the ones that stuck with me the most:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;I shouldn’t have used miter joints for the doors&lt;/strong&gt; - I learned something new, but really I just made my life more difficult with this one!&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Pocket hole fail for the face frame&lt;/strong&gt; - This really falls under the umbrella of “measure twice”. If I had taken a little bit more time to measure where the screws would be going at that angle, it would’ve been obvious that there would be problems. Similarly, I could’ve made a small mockup of the face frame and cabinet edge to test this.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Alignment on the face frame&lt;/strong&gt; - The biscuits worked well to align the face frame, but it wasn’t perfect. If I had some more clamps I could’ve more easily kept it in place. Ultimately it slid slightly when I was panicking and putting in the nails by hand.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Test, test, test. For miters, painting, door hinges, anything new!&lt;/strong&gt; - Making small mockups of joints to test before doing the real thing is very helpful. I also should’ve tested the paint sprayer on a test piece of wood before jumping in and painting the doors.&lt;/li&gt;
&lt;/ol&gt;
</description>
          <pubDate>Sat, 01 Jul 2023 00:00:00 +0000</pubDate>
          <link>https://avojak.com/blog/2023/07/01/diy-builtin-bathroom-cabinet/</link>
          <guid isPermaLink="true">https://avojak.com/blog/2023/07/01/diy-builtin-bathroom-cabinet/</guid>
          
              
              
          <enclosure url="https://avojak.com/blog" />
          
          
          <category>diy</category>
          
          <category>woodworking</category>
          
          <category>evergreen</category>
          
          
        </item>
      
    
      
        <item>
          <title>Grafana Dashboard for Pi-hole Stats</title>
          <description>&lt;p&gt;One of the most important parts of my home network and homelab is my &lt;a href=&quot;https://pi-hole.net&quot;&gt;Pi-hole&lt;/a&gt;, because who likes internet ads?
I also enjoy pretty graphs, so I recently started looking for a way to add Pi-hole stats to my Grafana dashboard. Unfortunately all
the other monitoring tools that I came across had a fatal flaw in how they recorded the number of queries throughout the day:
they simply plotted the “queries today” value at each poll interval. However, Pi-hole tracks queries in 10-minute rolling windows.
Each value represents the number of queries for the past 23 hours and 50 minutes, plus the number of queries in the current 10-minute
window. Charting this over time looks like a sawtooth, and isn’t what we want:&lt;/p&gt;

&lt;figure class=&quot;constrained&quot;&gt;
  &lt;p&gt;&lt;img src=&quot;https://s3.amazonaws.com/avojak.com/2022-12-15-pihole-influxdb-monitor/IMG_4107.JPG&quot; alt=&quot;Not ideal plot&quot; /&gt;&lt;/p&gt;
  &lt;figcaption&gt;This plot over time is somewhat misleading&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;My goal was to replicate the same chart that the Pi-hole dashboard shows.&lt;/p&gt;

&lt;p&gt;Using the &lt;a href=&quot;https://discourse.pi-hole.net/t/pi-hole-api/1863&quot;&gt;REST API&lt;/a&gt;, I simply poll the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;overTimeData10mins&lt;/code&gt; endpoint and record those
datapoints in InfluxDB. Sure, there will be a lot of duplicate data, but those datapoints will simply be overwritten in the database.
Once I got this working, I made quick work of populating all the other data that the API provides.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;c1&quot;&gt;# Ads over time
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ads_over_time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;points&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Point&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;from_dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;&quot;measurement&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;over_time_data&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;&quot;tags&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tags&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;&quot;fields&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;s&quot;&gt;&quot;ads_over_time&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;&quot;time&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;timestamp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;WritePrecision&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;S&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;figure class=&quot;constrained&quot;&gt;
  &lt;p&gt;&lt;img src=&quot;https://s3.amazonaws.com/avojak.com/2022-12-15-pihole-influxdb-monitor/queries.png&quot; alt=&quot;Queries over time&quot; /&gt;&lt;/p&gt;
  &lt;figcaption&gt;Bingo!&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The last major challenge was displaying the tables of Top Queries, Top Ads, and Top Sources. For most other datapoints I was able to simply
take the JSON data from the API and drop it in as the field data for the database record. However, attempting to group multiple records by time,
and sort them turned into a massive headache, and failed in several edge cases. 
To make querying the data easier, I converted the JSON data from the REST API into a comma-separated 
string. This way there would always be a single, latest record in the database with the top items.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;n&quot;&gt;points&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Point&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;from_dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;measurement&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;top_queries&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;tags&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tags&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;fields&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;&quot;top_10&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_json_to_csv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stats&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'top_queries'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
        &lt;span class=&quot;s&quot;&gt;&quot;time&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;now_seconds&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;WritePrecision&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;S&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In the end I tossed together a little Grafana dashboard to display all the data I collected:&lt;/p&gt;

&lt;figure class=&quot;constrained&quot;&gt;
  &lt;p&gt;&lt;img src=&quot;https://s3.amazonaws.com/avojak.com/2022-12-15-pihole-influxdb-monitor/dashboard-full.png&quot; alt=&quot;Grafana dashboard&quot; /&gt;&lt;/p&gt;
&lt;/figure&gt;

&lt;p&gt;The script is available as a Docker image on DockerHub, and can be run easily:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;docker run &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;PIHOLE_ALIAS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;pihole&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;PIHOLE_ADDRESS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;http://pi.hole&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;PIHOLE_TOKEN&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;pihole_api_token&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;INFLUXDB_ADDRESS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;http://influxdb:8086&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;INFLUXDB_ORG&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;my-org&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;INFLUXDB_TOKEN&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;super_secret_token&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;INFLUXDB_BUCKET&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;pihole&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    avojak/pihole-influxdb:latest
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Alternatively you can run the Python application directly from the command-line:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;python3 pihole-influxdb.py &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--pihole-alias&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;pihole&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--pihole-address&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;http://pi.hole&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--pihole-token&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;pihole_api_token&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--influxdb-address&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;http://influxdb:8096&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--influxdb-org&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;my-org&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--influxdb-token&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;super_secret_token&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--influxdb-bucket&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;pihole&quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Check out the full source over on my GitHub:&lt;/p&gt;

&lt;div class=&quot;github-card&quot;&gt;
  &lt;header&gt;
    &lt;a class=&quot;account&quot; href=&quot;https://github.com/avojak/pihole-influxdb-monitor&quot;&gt;
      &lt;img class=&quot;avatar&quot; src=&quot;https://github.com/avojak.png&quot; /&gt;
      &lt;p&gt;avojak/&lt;b&gt;pihole-influxdb-monitor&lt;/b&gt;&lt;/p&gt;
    &lt;/a&gt;
    &lt;a href=&quot;https://github.com/avojak/pihole-influxdb-monitor&quot;&gt;
      &lt;img class=&quot;logo&quot; src=&quot;/blog/images/github.svg&quot; /&gt;
    &lt;/a&gt;
  &lt;/header&gt;

  

  &lt;p id=&quot;avojak/pihole-influxdb-monitor-description&quot;&gt;&lt;/p&gt;

  &lt;footer&gt;
    &lt;span id=&quot;avojak/pihole-influxdb-monitor-issues&quot; style=&quot;white-space:nowrap;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 512 512&quot;&gt;&lt;!--! Font Awesome Pro 6.1.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --&gt;&lt;path d=&quot;M160 256C160 202.1 202.1 160 256 160C309 160 352 202.1 352 256C352 309 309 352 256 352C202.1 352 160 309 160 256zM512 256C512 397.4 397.4 512 256 512C114.6 512 0 397.4 0 256C0 114.6 114.6 0 256 0C397.4 0 512 114.6 512 256zM256 48C141.1 48 48 141.1 48 256C48 370.9 141.1 464 256 464C370.9 464 464 370.9 464 256C464 141.1 370.9 48 256 48z&quot; /&gt;&lt;/svg&gt;&lt;/span&gt;
    &lt;span id=&quot;avojak/pihole-influxdb-monitor-stargazers&quot; style=&quot;white-space:nowrap;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 576 512&quot;&gt;&lt;!--! Font Awesome Pro 6.1.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --&gt;&lt;path d=&quot;M287.9 0C297.1 0 305.5 5.25 309.5 13.52L378.1 154.8L531.4 177.5C540.4 178.8 547.8 185.1 550.7 193.7C553.5 202.4 551.2 211.9 544.8 218.2L433.6 328.4L459.9 483.9C461.4 492.9 457.7 502.1 450.2 507.4C442.8 512.7 432.1 513.4 424.9 509.1L287.9 435.9L150.1 509.1C142.9 513.4 133.1 512.7 125.6 507.4C118.2 502.1 114.5 492.9 115.1 483.9L142.2 328.4L31.11 218.2C24.65 211.9 22.36 202.4 25.2 193.7C28.03 185.1 35.5 178.8 44.49 177.5L197.7 154.8L266.3 13.52C270.4 5.249 278.7 0 287.9 0L287.9 0zM287.9 78.95L235.4 187.2C231.9 194.3 225.1 199.3 217.3 200.5L98.98 217.9L184.9 303C190.4 308.5 192.9 316.4 191.6 324.1L171.4 443.7L276.6 387.5C283.7 383.7 292.2 383.7 299.2 387.5L404.4 443.7L384.2 324.1C382.9 316.4 385.5 308.5 391 303L476.9 217.9L358.6 200.5C350.7 199.3 343.9 194.3 340.5 187.2L287.9 78.95z&quot; /&gt;&lt;/svg&gt;&lt;/span&gt;
    &lt;span id=&quot;avojak/pihole-influxdb-monitor-forks&quot; style=&quot;white-space:nowrap;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 448 512&quot;&gt;&lt;!--! Font Awesome Pro 6.1.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --&gt;&lt;path d=&quot;M160 80C160 112.8 140.3 140.1 112 153.3V192C112 209.7 126.3 224 144 224H304C321.7 224 336 209.7 336 192V153.3C307.7 140.1 288 112.8 288 80C288 35.82 323.8 0 368 0C412.2 0 448 35.82 448 80C448 112.8 428.3 140.1 400 153.3V192C400 245 357 288 304 288H256V358.7C284.3 371 304 399.2 304 432C304 476.2 268.2 512 224 512C179.8 512 144 476.2 144 432C144 399.2 163.7 371 192 358.7V288H144C90.98 288 48 245 48 192V153.3C19.75 140.1 0 112.8 0 80C0 35.82 35.82 0 80 0C124.2 0 160 35.82 160 80V80zM80 104C93.25 104 104 93.25 104 80C104 66.75 93.25 56 80 56C66.75 56 56 66.75 56 80C56 93.25 66.75 104 80 104zM368 104C381.3 104 392 93.25 392 80C392 66.75 381.3 56 368 56C354.7 56 344 66.75 344 80C344 93.25 354.7 104 368 104zM224 408C210.7 408 200 418.7 200 432C200 445.3 210.7 456 224 456C237.3 456 248 445.3 248 432C248 418.7 237.3 408 224 408z&quot; /&gt;&lt;/svg&gt;&lt;/span&gt;
  &lt;/footer&gt;
  &lt;footer&gt;
    &lt;a href=&quot;https://github.com/avojak/pihole-influxdb-monitor&quot;&gt;
      &lt;span&gt;View on GitHub →&lt;/span&gt;
    &lt;/a&gt;
  &lt;/footer&gt;

  &lt;script defer=&quot;&quot;&gt;
    // Data for testing without actually hitting the GitHub API
    // out = {&quot;stargazers_count&quot;:123456, &quot;forks_count&quot;:123, &quot;open_issues_count&quot;:60, &quot;description&quot;:&quot;Super cool project with absolutely zero bugs&quot;};
    // document.getElementById(&quot;avojak/pihole-influxdb-monitor-stargazers&quot;).innerHTML += numeral(out['stargazers_count']).format('0.[0]a') + &quot; Stars&quot;;
    // document.getElementById(&quot;avojak/pihole-influxdb-monitor-issues&quot;).innerHTML += numeral(out['open_issues_count']).format('0.[0]a') + &quot; Issues&quot;;
    // document.getElementById(&quot;avojak/pihole-influxdb-monitor-description&quot;).innerHTML = out['description'];
    // document.getElementById(&quot;avojak/pihole-influxdb-monitor-forks&quot;).innerHTML += numeral(out['forks_count']).format('0.[0]a') + &quot; Forks&quot;;

    fetch('https://api.github.com/repos/avojak/pihole-influxdb-monitor')
      .then(res =&gt; {
        if (!res.ok) {
          throw new Error(&quot;Bad network response&quot;);
        }
        return res.json();
      })
      .then((out) =&gt; {
        var stargazers = out['stargazers_count']
        var issues = out['open_issues_count']
        var forks = out['forks_count']
        
        // Populate the elements with the data from the response
        document.getElementById(&quot;avojak/pihole-influxdb-monitor-stargazers&quot;).innerHTML += numeral(stargazers).format('0.[0]a') + &quot; Star&quot; + (stargazers != 1 ? &quot;s&quot; : &quot;&quot;);
        document.getElementById(&quot;avojak/pihole-influxdb-monitor-issues&quot;).innerHTML += numeral(issues).format('0.[0]a') + &quot; Issue&quot; + (issues != 1 ? &quot;s&quot; : &quot;&quot;);
        document.getElementById(&quot;avojak/pihole-influxdb-monitor-forks&quot;).innerHTML += numeral(forks).format('0.[0]a') + &quot; Fork&quot; + (forks != 1 ? &quot;s&quot; : &quot;&quot;);
        document.getElementById(&quot;avojak/pihole-influxdb-monitor-description&quot;).innerHTML = out['description'];
    }).catch((err) =&gt; {
      console.error(err);
      // Clear out the fields in case of error rather than showing 0 values
      document.getElementById(&quot;avojak/pihole-influxdb-monitor-stargazers&quot;).innerHTML = &quot;&quot;;
      document.getElementById(&quot;avojak/pihole-influxdb-monitor-issues&quot;).innerHTML = &quot;&quot;;
      document.getElementById(&quot;avojak/pihole-influxdb-monitor-forks&quot;).innerHTML = &quot;&quot;;
    });
  &lt;/script&gt;
&lt;/div&gt;
</description>
          <pubDate>Thu, 15 Dec 2022 00:00:00 +0000</pubDate>
          <link>https://avojak.com/blog/2022/12/15/pihole-grafana-dashboard/</link>
          <guid isPermaLink="true">https://avojak.com/blog/2022/12/15/pihole-grafana-dashboard/</guid>
          
              
              
          <enclosure url="https://avojak.com/blog" />
          
          
          <category>software</category>
          
          <category>app</category>
          
          <category>evergreen</category>
          
          
        </item>
      
    
      
        <item>
          <title>Updating Warble to GTK4</title>
          <description>&lt;p&gt;Warble is officially at version 2.0.0, which means it’s now using GTK4!&lt;/p&gt;

&lt;div class=&quot;twitter-card&quot;&gt;
  &lt;header&gt;
    &lt;a class=&quot;account&quot; href=&quot;https://twitter.com/avojak&quot;&gt;
      
      
      &lt;img class=&quot;avatar&quot; src=&quot;/blog/images/avojak.jpeg&quot; /&gt;
      &lt;strong&gt;Andrew Vojak&lt;/strong&gt;
      &lt;p&gt;@avojak&lt;/p&gt;
    &lt;/a&gt;
    &lt;a href=&quot;https://twitter.com/avojak/status/1565773075336966144&quot;&gt;
      &lt;img class=&quot;logo&quot; src=&quot;/blog/images/twitter.svg&quot; /&gt;
    &lt;/a&gt;
  &lt;/header&gt;

  &lt;p&gt;Warble v2.0.0 is here! 🥳&lt;br /&gt;
&lt;br /&gt;
✨Updated to GTK4&lt;br /&gt;
✨Cleaner UI&lt;br /&gt;
✨New (barely modified) icon&lt;br /&gt;
✨Same fun gameplay!&lt;br /&gt;
&lt;br /&gt;
Get the update soon on 
@elementary
 #AppCenter, or on #Flathub!&lt;br /&gt;
&lt;br /&gt;
#vala #gtk #gtk4&lt;/p&gt;

  

  &lt;footer&gt;
    &lt;a href=&quot;https://twitter.com/avojak/status/1565773075336966144&quot;&gt;
      &lt;span&gt;View on Twitter →&lt;/span&gt;
      &lt;time datetime=&quot;2022-09-02&quot;&gt;Sep  2, 2022&lt;/time&gt;
    &lt;/a&gt;
  &lt;/footer&gt;
&lt;/div&gt;

&lt;p&gt;It was a fun challenge to update Warble from GTK3 to GTK4, although a bit frustrating at times. I hope that by walking through some of the specific changes I made, you might not struggle quite as much as I did! Wherever possible I will include links to the diff in GitHub so you can see the exact change that I made.&lt;/p&gt;

&lt;p&gt;You can view the full diff of changes here: &lt;a href=&quot;https://github.com/avojak/warble/pull/39/files&quot;&gt;avojak/warble/pull/39&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;updating-the-dependencies&quot;&gt;Updating the Dependencies&lt;/h2&gt;

&lt;p&gt;The obvious first step is to, well, pull in GTK4 as a dependency!&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;dependency('gtk4', version: '&amp;gt;= 4.6.3')
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And if you’re using an IDE, you’ll immediately be met with a giant slap in the face. Compile errors out the wazoo! If you’re like me, you had no idea that Libhandy was going away (and I was just getting used to it…). Turns out, &lt;a href=&quot;https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/migrating-libhandy-1-4-to-libadwaita.html&quot;&gt;Libadwaita is the successor&lt;/a&gt;. Not to worry, this is an easy substitution!&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;dependency('libadwaita-1', version: '&amp;gt;= 1.1.0'),
# I'm using Granite as well, so you need to grab the new version of it as well!
dependency('granite-7', version: '&amp;gt;= 7.0.0')
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now all you have to do is quickly rush through your code, take a quick guess at how to fix the compile errors, rebuild, run, and…&lt;/p&gt;

&lt;div class=&quot;twitter-card&quot;&gt;
  &lt;header&gt;
    &lt;a class=&quot;account&quot; href=&quot;https://twitter.com/avojak&quot;&gt;
      
      
      &lt;img class=&quot;avatar&quot; src=&quot;/blog/images/avojak.jpeg&quot; /&gt;
      &lt;strong&gt;Andrew Vojak&lt;/strong&gt;
      &lt;p&gt;@avojak&lt;/p&gt;
    &lt;/a&gt;
    &lt;a href=&quot;https://twitter.com/avojak/status/1555739691760197632&quot;&gt;
      &lt;img class=&quot;logo&quot; src=&quot;/blog/images/twitter.svg&quot; /&gt;
    &lt;/a&gt;
  &lt;/header&gt;

  &lt;p&gt;Me: *Updates Warble to GTK4 and spends a couple hours resolving all the compile and runtime errors*&lt;br /&gt;&lt;br /&gt;Warble: 🙃&lt;/p&gt;

  
    &lt;a href=&quot;https://twitter.com/avojak/status/1555739691760197632&quot;&gt;
      
      
      &lt;img src=&quot;https://pbs.twimg.com/media/FZcZAQ5XkAEKDo6?format=jpg&amp;amp;name=small&quot; /&gt;
    &lt;/a&gt;
  

  &lt;footer&gt;
    &lt;a href=&quot;https://twitter.com/avojak/status/1555739691760197632&quot;&gt;
      &lt;span&gt;View on Twitter →&lt;/span&gt;
      &lt;time datetime=&quot;2022-08-05&quot;&gt;Aug  5, 2022&lt;/time&gt;
    &lt;/a&gt;
  &lt;/footer&gt;
&lt;/div&gt;

&lt;p&gt;Welp. This was when I realized that it wasn’t going to be a quick update!&lt;/p&gt;

&lt;h2 id=&quot;helpful-resources&quot;&gt;Helpful Resources&lt;/h2&gt;

&lt;p&gt;I’m going to pause briefly here to list some resources that I found helpful along the way:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.gtk.org/gtk4/migrating-3to4.html&quot;&gt;GTK - Migrating 3 to 4&lt;/a&gt; - This guide was very helpful at identifying specific API changes, and the replacements. When in doubt, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl+f&lt;/code&gt; on this page first!&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://wiki.gnome.org/Projects/GTK/Inspector&quot;&gt;GTK Inspector&lt;/a&gt; - The GTK inspector application is incredibly useful in general, but specifically when you’re making UI changes. If you’re using Flatpak, simply add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--env=GTK_DEBUG=interactive&lt;/code&gt; to your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;flatpak run&lt;/code&gt; command and the Inspector will open alongside your app!&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://valadoc.org/&quot;&gt;Valadoc.org&lt;/a&gt; - Another probably obvious resource, but nothing beats reading the docs.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;input-handling&quot;&gt;Input Handling&lt;/h2&gt;

&lt;p&gt;There are two main input methods in Warble: using the physical keyboard attached to the computer, and the on-screen keyboard.&lt;/p&gt;

&lt;h3 id=&quot;keyboard-input&quot;&gt;Keyboard Input&lt;/h3&gt;

&lt;p&gt;With GTK3 and Libhandy, I used the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;key_press_event&lt;/code&gt; signal on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Hdy.Window&lt;/code&gt;, but GTK4 introduced a new &lt;a href=&quot;https://docs.gtk.org/gtk4/migrating-3to4.html#stop-using-gtkwidget-event-signals&quot;&gt;event controller system&lt;/a&gt;.&lt;/p&gt;

&lt;div class=&quot;language-vala highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key_event_controller&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Gtk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;EventControllerKey&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;key_event_controller&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key_pressed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;connect&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;on_key_pressed_event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Gtk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Widget&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;add_controller&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key_event_controller&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;A relatively small difference in this case, but worth noting.&lt;/p&gt;

&lt;h3 id=&quot;mouse-input&quot;&gt;Mouse Input&lt;/h3&gt;

&lt;p&gt;Mouse input is more significantly different. Instead of needing to use widgets such as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;EventBox&lt;/code&gt;, and using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gdk.EventMask&lt;/code&gt; for the various event types you care about, we now use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gtk.GestureClick&lt;/code&gt; (or other similar controllers depending on the desired event type):&lt;/p&gt;

&lt;div class=&quot;language-vala highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gesture_event_controller&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Gtk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;GestureClick&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;gesture_event_controller&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pressed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;connect&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n_press&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n_press&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;get_style_context&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;add_class&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;key-pressed&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;clicked&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;letter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;gesture_event_controller&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;released&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;connect&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n_press&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n_press&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;get_style_context&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;remove_class&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;key-pressed&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;add_controller&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;gesture_event_controller&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;(&lt;a href=&quot;https://github.com/avojak/warble/pull/39/files#diff-e9ec1e63eb9175d660c1b054f23cc7a3a59ebaace77d5190a379ab490cebd79dR35-R38&quot;&gt;Diff&lt;/a&gt;)&lt;/p&gt;

&lt;h2 id=&quot;final-classes&quot;&gt;Final Classes&lt;/h2&gt;

&lt;p&gt;One error you will most certainly encounter is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;field 'parent_instance' has incomplete type&lt;/code&gt;. This is because many Gtk classes are now final and &lt;a href=&quot;https://docs.gtk.org/gtk4/migrating-3to4.html#subclassing&quot;&gt;cannot be subclassed&lt;/a&gt;. I ran into this because I had subclassed &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gtk.HeaderBar&lt;/code&gt;. In my case I simply created the header bar directly in a different class, but in other cases I had to use a different container widget.&lt;/p&gt;

&lt;h2 id=&quot;drawing-with-cairo&quot;&gt;Drawing with Cairo&lt;/h2&gt;

&lt;p&gt;Under GTK3, the method for doing custom drawing on a widget was to override the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;draw(Cairo.Context)&lt;/code&gt; method:&lt;/p&gt;

&lt;div class=&quot;language-vala highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;override&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;bool&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;draw&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Cairo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Context&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;draw&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// Draw the widget as normal&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;save&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;custom_drawing&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// Do some custom drawing after the base widget is drawn&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;restore&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;For GTK4, there’s a different approach that I can use. To draw the squares on the game board with letters, the squares are now &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gtk.DrawingArea&lt;/code&gt; widgets. This class exposes a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;set_draw_func(Gtk.DrawingArea da, Cairo.Context ctx, int width, int height)&lt;/code&gt; method with some new convenience parameters for width and height.&lt;/p&gt;

&lt;div class=&quot;language-vala highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;draw_func&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Gtk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DrawingArea&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;drawing_area&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Cairo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Context&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;color&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Gdk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;RGBA&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;parse&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Warble&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ColorPalette&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get_text_color&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;set_source_rgb&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;red&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;green&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;blue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;select_font_face&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Inter&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Cairo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;FontSlant&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NORMAL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Cairo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;FontWeight&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BOLD&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;set_font_size&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;FONT_SIZE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;Cairo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TextExtents&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;extents&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;text_extents&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;letter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_string&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;out&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;extents&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;double&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;width&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;extents&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;width&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;extents&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x_bearing&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;double&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;height&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;extents&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;height&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;extents&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y_bearing&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;move_to&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;show_text&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;letter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_string&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;());&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;(&lt;a href=&quot;https://github.com/avojak/warble/pull/39/files#diff-e9ec1e63eb9175d660c1b054f23cc7a3a59ebaace77d5190a379ab490cebd79d&quot;&gt;Diff&lt;/a&gt;)&lt;/p&gt;

&lt;h2 id=&quot;window-positioning&quot;&gt;Window Positioning&lt;/h2&gt;

&lt;p&gt;Gone are the days of saving the position of the application window to the GSettings - now the window manager handles that for you!&lt;/p&gt;

&lt;p&gt;(&lt;a href=&quot;https://github.com/avojak/warble/pull/39/files#diff-9af23ba992f6eda45848cc8442543801a8c1bae99e960f6ac02b44887695f3df&quot;&gt;Diff&lt;/a&gt;)&lt;/p&gt;

&lt;h2 id=&quot;css-blur&quot;&gt;CSS Blur&lt;/h2&gt;

&lt;p&gt;This isn’t a change, but rather a new feature in GTK4 that I love. You can now use a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;blur()&lt;/code&gt; filter. In Warble, I combine blur with transparency on certain overlay views as a nice replacement for using a dialog:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;.faded {
    transition-property: opacity, filter;
    transition-duration: 0.5s;
    filter: blur(5px);
    opacity: 0.3;
}
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I’m a big fan of how this turned out:&lt;/p&gt;

&lt;figure class=&quot;third&quot;&gt;
  &lt;p&gt;&lt;img src=&quot;&quot; alt=&quot;&quot; /&gt; &lt;!-- placeholder to force the real picture to be in the middle and zoom properly --&gt;
&lt;img src=&quot;https://raw.githubusercontent.com/avojak/warble/2.0.0/data/assets/screenshots/warble-screenshot-03.png&quot; alt=&quot;Warble screenshot with blur&quot; /&gt;
&lt;img src=&quot;&quot; alt=&quot;&quot; /&gt; &lt;!-- placeholder to force the real picture to be in the middle and zoom properly --&gt;&lt;/p&gt;
&lt;/figure&gt;

&lt;h2 id=&quot;granite-modebutton-no-more&quot;&gt;Granite &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ModeButton&lt;/code&gt; No More&lt;/h2&gt;

&lt;p&gt;Not necessarily GTK4 related, but the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ModeButton&lt;/code&gt; widget was &lt;a href=&quot;https://github.com/elementary/granite/issues/619&quot;&gt;removed from Granite&lt;/a&gt;. This is really a “shame on me” moment for using this widget incorrectly, but it might pose a “gotcha” to anyone else using it in a similar way.&lt;/p&gt;

&lt;p&gt;The replacement is to use grouped &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Gtk.CheckButton&lt;/code&gt; widgets. For Warble, I created a simple wrapper widget (&lt;a href=&quot;https://github.com/avojak/warble/pull/39/files#diff-4e8d6a9aecab109cdceab4ad2f0d06868a628c24747f8bf1fecfe58f6c4a7cbb&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CheckButtonGroup&lt;/code&gt;&lt;/a&gt;) to handle the grouping.&lt;/p&gt;

&lt;p&gt;I think the GTK method of grouping widgets in this case is rather clumsy. One common theme in GTK4 is the use of proper container widgets, yet here we create a “group” by assigning a sibling widget to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;group&lt;/code&gt; property. Huh?? Now you end up with a widget that’s pulling double-duty as both an individual button, but also the group. In my opinion, a better design would be to create a specific group container widget that you add buttons to.&lt;/p&gt;

&lt;h2 id=&quot;css-changes&quot;&gt;CSS Changes&lt;/h2&gt;

&lt;p&gt;While not directly related to GTK4, I took this opportunity to leverage CSS for styling the game area and on-screen keyboard instead of using custom icons for each possible color scenario.&lt;/p&gt;

&lt;p&gt;The icons for the squares were replaced with two lines of code to apply some CSS classes:&lt;/p&gt;

&lt;div class=&quot;language-vala highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nf&quot;&gt;get_style_context&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;add_class&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Granite&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;STYLE_CLASS_CARD&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nf&quot;&gt;get_style_context&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;add_class&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Granite&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;STYLE_CLASS_ROUNDED&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And the various states for the squares and keyboard keys were also represented with CSS classes:&lt;/p&gt;

&lt;div class=&quot;language-css highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nc&quot;&gt;.guess-correct&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;LIME_300&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;border&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1px&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;solid&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;LIME_700&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nc&quot;&gt;.guess-incorrect&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SILVER_500&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;border&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1px&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;solid&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SILVER_900&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nc&quot;&gt;.guess-close&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BANANA_300&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;border&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1px&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;solid&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BANANA_700&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nc&quot;&gt;.key-pressed&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;translateY&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;2px&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;nc&quot;&gt;.tile-active&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;border&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1px&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;solid&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;BLACK_700&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When switching to high-contrast mode, all I had to do was swap out the stylesheet - no more changing ALL the icons!&lt;/p&gt;

&lt;div class=&quot;language-vala highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;load_theme_stylesheet&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gtk_settings&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Gtk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Settings&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get_default&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;bool&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dark_mode&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gtk_settings&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;gtk_application_prefer_dark_theme&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;bool&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;high_contrast_mode&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;settings&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get_boolean&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;high-contrast-mode&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dark_mode&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;high_contrast_mode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;theme_provider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;load_from_resource&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;APP_ID&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;replace&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;/warble-dark-hicontrast.css&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dark_mode&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;high_contrast_mode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;theme_provider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;load_from_resource&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;APP_ID&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;replace&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;/warble-dark.css&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dark_mode&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;high_contrast_mode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;theme_provider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;load_from_resource&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;APP_ID&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;replace&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;/warble-light-hicontrast.css&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;theme_provider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;load_from_resource&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Constants&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;APP_ID&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;replace&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;/warble-light.css&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As an added bonus, I was able to very easily add a subtle outline for the square on the game board that’s currently waiting for input. Previously this would’ve required a whole new icon.&lt;/p&gt;

&lt;h2 id=&quot;end-result&quot;&gt;End Result&lt;/h2&gt;

&lt;p&gt;Check out the end result over on GitHub!&lt;/p&gt;

&lt;div class=&quot;github-card&quot;&gt;
  &lt;header&gt;
    &lt;a class=&quot;account&quot; href=&quot;https://github.com/avojak/warble&quot;&gt;
      &lt;img class=&quot;avatar&quot; src=&quot;https://github.com/avojak.png&quot; /&gt;
      &lt;p&gt;avojak/&lt;b&gt;warble&lt;/b&gt;&lt;/p&gt;
    &lt;/a&gt;
    &lt;a href=&quot;https://github.com/avojak/warble&quot;&gt;
      &lt;img class=&quot;logo&quot; src=&quot;/blog/images/github.svg&quot; /&gt;
    &lt;/a&gt;
  &lt;/header&gt;

  

  &lt;p id=&quot;avojak/warble-description&quot;&gt;&lt;/p&gt;

  &lt;footer&gt;
    &lt;span id=&quot;avojak/warble-issues&quot; style=&quot;white-space:nowrap;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 512 512&quot;&gt;&lt;!--! Font Awesome Pro 6.1.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --&gt;&lt;path d=&quot;M160 256C160 202.1 202.1 160 256 160C309 160 352 202.1 352 256C352 309 309 352 256 352C202.1 352 160 309 160 256zM512 256C512 397.4 397.4 512 256 512C114.6 512 0 397.4 0 256C0 114.6 114.6 0 256 0C397.4 0 512 114.6 512 256zM256 48C141.1 48 48 141.1 48 256C48 370.9 141.1 464 256 464C370.9 464 464 370.9 464 256C464 141.1 370.9 48 256 48z&quot; /&gt;&lt;/svg&gt;&lt;/span&gt;
    &lt;span id=&quot;avojak/warble-stargazers&quot; style=&quot;white-space:nowrap;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 576 512&quot;&gt;&lt;!--! Font Awesome Pro 6.1.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --&gt;&lt;path d=&quot;M287.9 0C297.1 0 305.5 5.25 309.5 13.52L378.1 154.8L531.4 177.5C540.4 178.8 547.8 185.1 550.7 193.7C553.5 202.4 551.2 211.9 544.8 218.2L433.6 328.4L459.9 483.9C461.4 492.9 457.7 502.1 450.2 507.4C442.8 512.7 432.1 513.4 424.9 509.1L287.9 435.9L150.1 509.1C142.9 513.4 133.1 512.7 125.6 507.4C118.2 502.1 114.5 492.9 115.1 483.9L142.2 328.4L31.11 218.2C24.65 211.9 22.36 202.4 25.2 193.7C28.03 185.1 35.5 178.8 44.49 177.5L197.7 154.8L266.3 13.52C270.4 5.249 278.7 0 287.9 0L287.9 0zM287.9 78.95L235.4 187.2C231.9 194.3 225.1 199.3 217.3 200.5L98.98 217.9L184.9 303C190.4 308.5 192.9 316.4 191.6 324.1L171.4 443.7L276.6 387.5C283.7 383.7 292.2 383.7 299.2 387.5L404.4 443.7L384.2 324.1C382.9 316.4 385.5 308.5 391 303L476.9 217.9L358.6 200.5C350.7 199.3 343.9 194.3 340.5 187.2L287.9 78.95z&quot; /&gt;&lt;/svg&gt;&lt;/span&gt;
    &lt;span id=&quot;avojak/warble-forks&quot; style=&quot;white-space:nowrap;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 448 512&quot;&gt;&lt;!--! Font Awesome Pro 6.1.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --&gt;&lt;path d=&quot;M160 80C160 112.8 140.3 140.1 112 153.3V192C112 209.7 126.3 224 144 224H304C321.7 224 336 209.7 336 192V153.3C307.7 140.1 288 112.8 288 80C288 35.82 323.8 0 368 0C412.2 0 448 35.82 448 80C448 112.8 428.3 140.1 400 153.3V192C400 245 357 288 304 288H256V358.7C284.3 371 304 399.2 304 432C304 476.2 268.2 512 224 512C179.8 512 144 476.2 144 432C144 399.2 163.7 371 192 358.7V288H144C90.98 288 48 245 48 192V153.3C19.75 140.1 0 112.8 0 80C0 35.82 35.82 0 80 0C124.2 0 160 35.82 160 80V80zM80 104C93.25 104 104 93.25 104 80C104 66.75 93.25 56 80 56C66.75 56 56 66.75 56 80C56 93.25 66.75 104 80 104zM368 104C381.3 104 392 93.25 392 80C392 66.75 381.3 56 368 56C354.7 56 344 66.75 344 80C344 93.25 354.7 104 368 104zM224 408C210.7 408 200 418.7 200 432C200 445.3 210.7 456 224 456C237.3 456 248 445.3 248 432C248 418.7 237.3 408 224 408z&quot; /&gt;&lt;/svg&gt;&lt;/span&gt;
  &lt;/footer&gt;
  &lt;footer&gt;
    &lt;a href=&quot;https://github.com/avojak/warble&quot;&gt;
      &lt;span&gt;View on GitHub →&lt;/span&gt;
    &lt;/a&gt;
  &lt;/footer&gt;

  &lt;script defer=&quot;&quot;&gt;
    // Data for testing without actually hitting the GitHub API
    // out = {&quot;stargazers_count&quot;:123456, &quot;forks_count&quot;:123, &quot;open_issues_count&quot;:60, &quot;description&quot;:&quot;Super cool project with absolutely zero bugs&quot;};
    // document.getElementById(&quot;avojak/warble-stargazers&quot;).innerHTML += numeral(out['stargazers_count']).format('0.[0]a') + &quot; Stars&quot;;
    // document.getElementById(&quot;avojak/warble-issues&quot;).innerHTML += numeral(out['open_issues_count']).format('0.[0]a') + &quot; Issues&quot;;
    // document.getElementById(&quot;avojak/warble-description&quot;).innerHTML = out['description'];
    // document.getElementById(&quot;avojak/warble-forks&quot;).innerHTML += numeral(out['forks_count']).format('0.[0]a') + &quot; Forks&quot;;

    fetch('https://api.github.com/repos/avojak/warble')
      .then(res =&gt; {
        if (!res.ok) {
          throw new Error(&quot;Bad network response&quot;);
        }
        return res.json();
      })
      .then((out) =&gt; {
        var stargazers = out['stargazers_count']
        var issues = out['open_issues_count']
        var forks = out['forks_count']
        
        // Populate the elements with the data from the response
        document.getElementById(&quot;avojak/warble-stargazers&quot;).innerHTML += numeral(stargazers).format('0.[0]a') + &quot; Star&quot; + (stargazers != 1 ? &quot;s&quot; : &quot;&quot;);
        document.getElementById(&quot;avojak/warble-issues&quot;).innerHTML += numeral(issues).format('0.[0]a') + &quot; Issue&quot; + (issues != 1 ? &quot;s&quot; : &quot;&quot;);
        document.getElementById(&quot;avojak/warble-forks&quot;).innerHTML += numeral(forks).format('0.[0]a') + &quot; Fork&quot; + (forks != 1 ? &quot;s&quot; : &quot;&quot;);
        document.getElementById(&quot;avojak/warble-description&quot;).innerHTML = out['description'];
    }).catch((err) =&gt; {
      console.error(err);
      // Clear out the fields in case of error rather than showing 0 values
      document.getElementById(&quot;avojak/warble-stargazers&quot;).innerHTML = &quot;&quot;;
      document.getElementById(&quot;avojak/warble-issues&quot;).innerHTML = &quot;&quot;;
      document.getElementById(&quot;avojak/warble-forks&quot;).innerHTML = &quot;&quot;;
    });
  &lt;/script&gt;
&lt;/div&gt;
</description>
          <pubDate>Fri, 02 Sep 2022 00:00:00 +0000</pubDate>
          <link>https://avojak.com/blog/2022/09/02/updating-warble-to-gtk4/</link>
          <guid isPermaLink="true">https://avojak.com/blog/2022/09/02/updating-warble-to-gtk4/</guid>
          
              
              
          <enclosure url="https://avojak.com/blog" />
          
          
          <category>software</category>
          
          <category>app</category>
          
          <category>elementary-os</category>
          
          <category>evergreen</category>
          
          
        </item>
      
    
      
        <item>
          <title>Installing VMware ESXi on a Raspberry Pi</title>
          <description>&lt;p&gt;I’ve had a pair of Raspberry Pi 4’s sitting around collecting dust and figured I could put them to use in my homelab.
I currently run &lt;a href=&quot;https://pi-hole.net&quot;&gt;Pi-hole&lt;/a&gt; (a network-wide ad blocker) on a pair of very old Raspberry Pi 2’s.
Although they’re working just fine for now, they’re not ideal for a few reasons:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;They run from the SD card vs. an SSD (SD card notoriously wear out quickly)&lt;/li&gt;
  &lt;li&gt;They require both a power cable &lt;em&gt;AND&lt;/em&gt; network cable vs. PoE&lt;/li&gt;
  &lt;li&gt;They are manually managed vs. the Infrastructure-as-Code powering the rest of my homelab&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There’s more than one way to crack an egg, but since I run ESXi on other hosts as well, it seemed like a good way
to have some consistency.&lt;/p&gt;

&lt;p&gt;There are a number of guides out there for how to get ESXi (technically &lt;a href=&quot;https://flings.vmware.com/esxi-arm-edition&quot;&gt;ESXi-Arm&lt;/a&gt;)
running on a Raspberry Pi, but I constantly ran into boot issues. I finally came across &lt;a href=&quot;https://vmattroman.com/esxi-on-raspberry-pi-4-how-to-install/&quot;&gt;this blog post by Mateusz Romaniuk&lt;/a&gt;
and it worked like a charm. I’ll summarize how I did everything below, as there are a few points in the blog post
that give you options I didn’t feel were necessary. Be sure to check out the original post for guided pictures
that are very helpful as well!&lt;/p&gt;

&lt;h2 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;Raspberry Pi 4 Model B (4 or 8 GB RAM)&lt;/li&gt;
  &lt;li&gt;USB keyboard&lt;/li&gt;
  &lt;li&gt;Power suply (bonus points for using PoE!)&lt;/li&gt;
  &lt;li&gt;microHDMI to HDMI adapter + HDMI cable (or just a microHDMI to HDMI cable)&lt;/li&gt;
  &lt;li&gt;1x microSD card (for UEFI firmware, so it doesn’t need to have a very large capacity)&lt;/li&gt;
  &lt;li&gt;1x USB flash drive (for the ESXi installer, which is only ~140MB)&lt;/li&gt;
  &lt;li&gt;1x USB flash drive or SSD (for the full ESXi installation)&lt;/li&gt;
  &lt;li&gt;The &lt;a href=&quot;https://www.raspberrypi.org/downloads/&quot;&gt;Raspberry Pi Imager tool&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;The &lt;a href=&quot;https://flings.vmware.com/esxi-arm-edition&quot;&gt;ESXi-Arm ISO&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/raspberrypi/firmware&quot;&gt;https://github.com/raspberrypi/firmware&lt;/a&gt; (Click “Code”, then “Download ZIP”)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/pftf/RPi4/releases/&quot;&gt;https://github.com/pftf/RPi4&lt;/a&gt; (Download the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RPi4_UEFI_Firmware_vX.XX.zip&lt;/code&gt; file from the most recent release)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;preparation&quot;&gt;Preparation&lt;/h2&gt;

&lt;h3 id=&quot;prepare-the-sd-card&quot;&gt;Prepare the SD Card&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;Insert the SD card into your computer and open the Raspberry Pi Imager tool.&lt;/li&gt;
  &lt;li&gt;Select “CHOOSE OS”, and scroll down to “Misc utility images”. Select “Bootloader”, and then “SD Card Boot”.&lt;/li&gt;
  &lt;li&gt;Select “CHOOSE STORAGE”, and select the option for your SD card.&lt;/li&gt;
  &lt;li&gt;Select “WRITE”.&lt;/li&gt;
  &lt;li&gt;Wait until the imager is done, and then safely remove the SD card.&lt;/li&gt;
  &lt;li&gt;Insert the SD card into the Raspberry Pi (without the SSD and USB flash drive connected), and power on the Pi.&lt;/li&gt;
  &lt;li&gt;You should see a green screen and the green LED on the Raspberry Pi blinking. If so, power off the Pi. If not, there should be an error message displayed.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;copy-the-uefi-firmware&quot;&gt;Copy the UEFI Firmware&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;Insert the SD card back into your computer and re-open the Raspberry Pi Image tool.&lt;/li&gt;
  &lt;li&gt;Select “CHOOSE OS”, and scroll to “Erase”.&lt;/li&gt;
  &lt;li&gt;Select “CHOOSE STORAGE”, and select the option for your SD card.&lt;/li&gt;
  &lt;li&gt;Select “WRITE”.&lt;/li&gt;
  &lt;li&gt;When done, it may automatically unmount the SD card. If it does, simply unplug it and plug it back in.&lt;/li&gt;
  &lt;li&gt;Extract the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;firmware-master.zip&lt;/code&gt; file downloaded previously, and delete all of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kernel*.img&lt;/code&gt; files within the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;firmware-master/boot/&lt;/code&gt; directory (there should be 4 of them).&lt;/li&gt;
  &lt;li&gt;Copy all files from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;firmware-master/boot/&lt;/code&gt; onto the SD card.&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Extract the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RPi4_UEFI_Firmware_vX.XX.zip&lt;/code&gt; file downloaded previously, and copy all contents onto the SD card (you can select the option to replace files in case there are matches).&lt;/p&gt;

    &lt;p&gt;&lt;em&gt;If the Raspberry Pi that you’re using is the &lt;strong&gt;4 GB&lt;/strong&gt; model (meaning it has 4 GB of RAM, &lt;strong&gt;not&lt;/strong&gt; 8 GB), you will need to open &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;config.txt&lt;/code&gt; on the SD card and add the following line to the end of the file:&lt;/em&gt;&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; gpu_mem=16
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;Safely eject the SD card from your computer and insert it into the Raspberry Pi.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;configure-the-uefi-firmware&quot;&gt;Configure the UEFI Firmware&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;With the SD card inserted, power on the Raspberry Pi, and when you see the Raspberry Pi logo hit the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Esc&lt;/code&gt; key.&lt;/li&gt;
  &lt;li&gt;Use the arrow keys to select “Device Manager”.&lt;/li&gt;
  &lt;li&gt;Select “Raspberry Pi Configuration”, and then “Advanced Configuration”.&lt;/li&gt;
  &lt;li&gt;Change “Limit RAM to 3 GB” to “Disabled”.&lt;/li&gt;
  &lt;li&gt;Press &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;F10&lt;/code&gt; to save, then &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Y&lt;/code&gt; to confirm.&lt;/li&gt;
  &lt;li&gt;Press &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Esc&lt;/code&gt; three times until you are back on the main screen, and then select &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Continue&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;You will be prompted that the “Configuration changed. Reset to apply it Now. Press ENTER to reset”. Press &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Enter&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;When the Raspberry Pi reboots, once again press &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Esc&lt;/code&gt; when the logo appears on the boot screen, and you should be back at the UEFI boot screen.&lt;/li&gt;
  &lt;li&gt;Leave the Raspberry Pi alone for a minute while we prepare the ESXi installer!&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&quot;prepare-the-esxi-installer&quot;&gt;Prepare the ESXi Installer&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;Create a bootable USB flash drive from the ESXi-Arm ISO downloaded previously (I highly recommend using &lt;a href=&quot;https://www.balena.io/etcher/&quot;&gt;balenaEtcher&lt;/a&gt; for this).&lt;/li&gt;
  &lt;li&gt;Once the drive is done being flashed, safely eject it from your computer and plug it into the Raspberry Pi.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;installation&quot;&gt;Installation&lt;/h2&gt;

&lt;p&gt;At this point, the Raspberry Pi should be powered on to the UEFI boot screen. If not already, power on the Raspberry Pi and hit &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Esc&lt;/code&gt; when the boot logo appears.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Make sure that the USB flash drive with the ESXi installer is inserted.&lt;/li&gt;
  &lt;li&gt;Connect the SSD (be sure to use the one of the USB 3.0 ports!).&lt;/li&gt;
  &lt;li&gt;From the UEFI boot screen, select “Boot Manager”.&lt;/li&gt;
  &lt;li&gt;Scroll down to your bootable USB flash drive from the menu &lt;strong&gt;but don’t select it yet!&lt;/strong&gt;
You will have a short window of time to press &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Shift+O&lt;/code&gt; &lt;strong&gt;once&lt;/strong&gt; (that’s an ‘O’ as in octopus) before ESXi starts booting. If you miss it, it’s ok, you’ll soon be able to press &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Esc&lt;/code&gt; to cancel the installer and reboot your device to try again.&lt;/li&gt;
  &lt;li&gt;Now, select your device from the boot menu and press &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Shift+O&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;At the bottom of the screen you should see a small prompt that says &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;runweasel cdromBoot&lt;/code&gt;. Add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;autoPartitionOSDataSize=8192&lt;/code&gt; to the end so that the full command reads:&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; runweasel cdromBoot autoPartitionOSDataSize=8192
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;Press &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Enter&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;The ESXi installer will start, and when prompted, press &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Enter&lt;/code&gt; to continue.&lt;/li&gt;
  &lt;li&gt;Press &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;F11&lt;/code&gt; to accept the EULA.&lt;/li&gt;
  &lt;li&gt;Select your SSD drive from the Storage Device menu.&lt;/li&gt;
  &lt;li&gt;Press &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Enter&lt;/code&gt; to confirm the disk selection.&lt;/li&gt;
  &lt;li&gt;Choose your keyboard layout.&lt;/li&gt;
  &lt;li&gt;Set a root password, and then press &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Enter&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Press &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;F11&lt;/code&gt; (yet again) to confirm the installation.&lt;/li&gt;
  &lt;li&gt;When the installation is complete, remove the USB flash drive with the installer, and press &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Enter&lt;/code&gt; to reboot the Raspberry Pi.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;set-default-boot-device&quot;&gt;Set Default Boot Device&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;When the Raspberry Pi boot logo reappears, press &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Esc&lt;/code&gt; to enter the UEFI boot screen.&lt;/li&gt;
  &lt;li&gt;Select “Boot Maintenance Manager”, then “Boot Options”, and then “Change Boot Order”.&lt;/li&gt;
  &lt;li&gt;Move your SSD device to the top of the list.&lt;/li&gt;
  &lt;li&gt;Press &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;F10&lt;/code&gt; to save, then &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Y&lt;/code&gt; to confirm.&lt;/li&gt;
  &lt;li&gt;Press &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Esc&lt;/code&gt; three times until you are back on the main screen, and then select &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Continue&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;esxi-configuration&quot;&gt;ESXi Configuration&lt;/h2&gt;

&lt;p&gt;Before you start creating VMs, grab a free license for ESXi 7 from VMware: &lt;a href=&quot;https://my.vmware.com/en/group/vmware/evalcenter?p=free-esxi7&quot;&gt;https://my.vmware.com/en/group/vmware/evalcenter?p=free-esxi7&lt;/a&gt;.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Open a web browser on your computer and go to the address shown by ESXi on the screen (be sure to use HTTPS).&lt;/li&gt;
  &lt;li&gt;Log in using the username &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;root&lt;/code&gt; and the password you set previously.&lt;/li&gt;
  &lt;li&gt;Select “Manage” from the Navigator panel on the left side of the screen, and go to the “Licensing” tab. Click “Assign license”, and paste in your new ESXi 7 license. Click Assign.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Badabing bada-boom, you’ve got yourself ESXi running on a Raspberry Pi!&lt;/p&gt;

&lt;figure class=&quot;constrained&quot;&gt;
  &lt;p&gt;&lt;img src=&quot;https://i.imgur.com/lI7xRc5.jpg&quot; alt=&quot;ESXi homepage&quot; /&gt;&lt;/p&gt;
&lt;/figure&gt;

&lt;hr /&gt;

&lt;p&gt;References:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://vmattroman.com/esxi-on-raspberry-pi-4-how-to-install/&quot;&gt;https://vmattroman.com/esxi-on-raspberry-pi-4-how-to-install/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
          <pubDate>Mon, 29 Aug 2022 00:00:00 +0000</pubDate>
          <link>https://avojak.com/blog/2022/08/29/esxi-on-raspberry-pi/</link>
          <guid isPermaLink="true">https://avojak.com/blog/2022/08/29/esxi-on-raspberry-pi/</guid>
          
              
              
          <enclosure url="https://avojak.com/blog" />
          
          
          <category>hardware</category>
          
          <category>homelab</category>
          
          <category>raspberry-pi</category>
          
          <category>evergreen</category>
          
          
        </item>
      
    
      
        <item>
          <title>Adding Disk Space to an CentOS VM</title>
          <description>&lt;p&gt;This post is mostly a reminder to my future self how to do this!&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;Update the virtual disk&lt;/p&gt;

    &lt;p&gt;This is the easy part - shut down the VM and expand the disk size in the virtual machine settings. Easy peasy.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Install growpart&lt;/p&gt;

    &lt;p&gt;If not already installed, grab growpart.&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; $ sudo yum install cloud-utils-growpart
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Expand the partition&lt;/p&gt;

    &lt;p&gt;I found the name of my device using the Disks appliction. In my case the root partition was on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/dev/nvme0n1p3&lt;/code&gt;, so the device is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/dev/nvme0n1&lt;/code&gt; and the partition is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;3&lt;/code&gt;.&lt;/p&gt;

    &lt;p&gt;Using growpart (first with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-N&lt;/code&gt; to do a dry-run):&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; $ sudo growpart /dev/nvme0n1 3 -N
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;p&gt;If no errors, run again without the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-N&lt;/code&gt; option.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Expand the physical volume&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; $ sudo pvresize /dev/nvme0n1p3
 Physical volume &quot;/dev/nvme0n1p3&quot; changed
 1 physical volume(s) resized or updated / 0 physical volume(s) not resized
 $ sudo pvs
 PV             VG Fmt  Attr PSize  PFree 
 /dev/nvme0n1p3 cs lvm2 a--  48.41g 30.00g
 $ sudo vgs
 VG #PV #LV #SN Attr   VSize  VFree 
 cs   1   2   0 wz--n- 48.41g 30.00g
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Expand the logical volume&lt;/p&gt;

    &lt;p&gt;Using the output from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vgs&lt;/code&gt; above (you can also find the name from the Disks application), we can now expand &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/root&lt;/code&gt;:&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; $ sudo lvextend -r -l +100%FREE /dev/cs/root
   Size of logical volume cs/root changed from 16.41 GiB (4201 extents) to 46.41 GiB (11881 extents).
   Logical volume cs/root successfully resized.
   meta-data=/dev/mapper/cs-root    isize=512    agcount=4, agsize=1075456 blks
            =                       sectsz=512   attr=2, projid32bit=1
            =                       crc=1        finobt=1, sparse=1, rmapbt=0
            =                       reflink=1    bigtime=0 inobtcount=0
   data     =                       bsize=4096   blocks=4301824, imaxpct=25
            =                       sunit=0      swidth=0 blks
   naming   =version 2              bsize=4096   ascii-ci=0, ftype=1
   log      =internal log           bsize=4096   blocks=2560, version=2
            =                       sectsz=512   sunit=0 blks, lazy-count=1
   realtime =none                   extsz=4096   blocks=0, rtextents=0
   data blocks changed from 4301824 to 12166144
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;p&gt;Verify:&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt; $ df -h
   Filesystem           Size  Used Avail Use% Mounted on
   devtmpfs             3.8G     0  3.8G   0% /dev
   tmpfs                3.8G     0  3.8G   0% /dev/shm
   tmpfs                3.8G  9.9M  3.8G   1% /run
   tmpfs                3.8G     0  3.8G   0% /sys/fs/cgroup
   /dev/mapper/cs-root   47G   13G   34G  28% /
   /dev/nvme0n1p2      1014M  251M  764M  25% /boot
   /dev/nvme0n1p1       599M  7.3M  592M   2% /boot/efi
   tmpfs                774M     0  774M   0% /run/user/976
   tmpfs                774M   28K  774M   1% /run/user/1000
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Fill up the disk and repeat!&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;hr /&gt;

&lt;p&gt;Helpful resources:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.unixarena.com/2022/01/how-to-extend-the-root-filesystem-in-rhel-8-centos-8.html/&quot;&gt;https://www.unixarena.com/2022/01/how-to-extend-the-root-filesystem-in-rhel-8-centos-8.html/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
          <pubDate>Fri, 26 Aug 2022 00:00:00 +0000</pubDate>
          <link>https://avojak.com/blog/2022/08/26/expand-centos-vm-root-partition/</link>
          <guid isPermaLink="true">https://avojak.com/blog/2022/08/26/expand-centos-vm-root-partition/</guid>
          
              
              
          <enclosure url="https://avojak.com/blog" />
          
          
          <category>evergreen</category>
          
          <category>centos</category>
          
          <category>virtual-machine</category>
          
          
        </item>
      
    
      
    
      
        <item>
          <title>Deploying Valadoc to GitHub Pages</title>
          <description>&lt;p&gt;I recently wrote a few libraries in Vala and was struggling to make the &lt;a href=&quot;https://wiki.gnome.org/Projects/Valadoc&quot;&gt;Valadoc&lt;/a&gt;
easily accessible to users. I found several examples of developers generating the docs locally and simply checking them
into version control in a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docs/&lt;/code&gt; directory. This doesn’t appeal to me, as documentation like this - in my opinion - is a 
product of a successful build and should be hosted separately.&lt;/p&gt;

&lt;p&gt;After some digging I came across a GitHub Action for deploying to GitHub Pages. This seemed like a great fit because it will
easily integrate with my existing CI process.&lt;/p&gt;

&lt;h2 id=&quot;preparation&quot;&gt;Preparation&lt;/h2&gt;

&lt;h3 id=&quot;generate-valadoc&quot;&gt;Generate Valadoc&lt;/h3&gt;

&lt;p&gt;The first thing to do is ensure that your library will generate Valadoc documentation. I’m not going to go into the details
of that here, but at the bottom of the post you will find a link to an example repository of mine that you can use for
reference.&lt;/p&gt;

&lt;p&gt;In most cases you will end up with a pair of commands that look something like this:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;meson build &lt;span class=&quot;nt&quot;&gt;-Ddocumentation&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;true
&lt;/span&gt;ninja &lt;span class=&quot;nt&quot;&gt;-C&lt;/span&gt; build
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Et voilà! There’s documentation in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;build/doc/&lt;/code&gt; directory!&lt;/p&gt;

&lt;h3 id=&quot;enable-github-pages&quot;&gt;Enable GitHub Pages&lt;/h3&gt;

&lt;p&gt;The next step is to create a separate &lt;em&gt;empty&lt;/em&gt; branch on your repository named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gh-pages&lt;/code&gt;. Simply create a new
branch, delete all the files, and push.&lt;/p&gt;

&lt;p&gt;Now you’ll want to enable GitHub Pages for your repository. For this, you can refer to GitHub’s documentation: &lt;a href=&quot;https://docs.github.com/en/pages/getting-started-with-github-pages/configuring-a-publishing-source-for-your-github-pages-site&quot;&gt;Configuring a publishing source for your GitHub Pages site&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;setup-the-action&quot;&gt;Setup the Action&lt;/h2&gt;

&lt;p&gt;Now it’s time to create the GitHub action! If you don’t already, create a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.github/workflows/&lt;/code&gt; directory in the root of
your repository. Then, create a YAML file (e.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ci.yml&lt;/code&gt;, or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;valadoc.yml&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;I already have a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ci.yml&lt;/code&gt; file here with jobs to build and execute unit tests, so I’m omitting the irrelevant jobs below. I wanted
to only publish the documentation if the build and tests were successful, so I put everything in a single file. You don’t need
to do this if you don’t want to.&lt;/p&gt;

&lt;p&gt;My &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ci.yml&lt;/code&gt; file looks like this:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;CI&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;pull_request&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;permissions&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;contents&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;write&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# ... other jobs omitted...&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;Valadoc&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Deploy Valadoc&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;needs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Test&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;container&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; 
      &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;elementary/docker:unstable&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Checkout&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/checkout@v3&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Install dependencies&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;apt-get update &amp;amp;&amp;amp; apt-get -y install libvala-dev valac meson git&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Build&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;meson build -Ddocumentation=true &amp;amp;&amp;amp; ninja -C build&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Deploy&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${{ github.ref == 'refs/heads/master' }}&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;peaceiris/actions-gh-pages@v3&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;github_token&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;publish_dir&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;./build/doc/catalyst-1-vala&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The first three steps simply checkout the source code, install some dependencies, and build the documentation using the
same command described previously.&lt;/p&gt;

&lt;p&gt;The fourth step is the real magic. I have an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;if&lt;/code&gt; check to only publish documentation from the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;master&lt;/code&gt; branch. You can
remove this or change the branch name to another branch if you’d like. Then we include the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;peaceiris/actions-gh-pages@v3&lt;/code&gt;
action. We don’t need to re-invent the wheel!&lt;/p&gt;

&lt;p&gt;We provide two variables - &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;github_token&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;publish_dir&lt;/code&gt;. The token will allow the build to have write access to publish
to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gh-pages&lt;/code&gt; branch. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;publish_dir&lt;/code&gt; points to the specific directory that will be published. In my case I have a
specific subdirectory within &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;build/doc/&lt;/code&gt; that I want to publish.&lt;/p&gt;

&lt;p&gt;And that’s it!&lt;/p&gt;

&lt;p&gt;You can find complete documentation for the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;peaceiris/actions-gh-pages&lt;/code&gt; action on GitHub: &lt;a href=&quot;https://github.com/peaceiris/actions-gh-pages&quot;&gt;peaceiris/actions-gh-pages&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;end-result&quot;&gt;End Result&lt;/h2&gt;

&lt;p&gt;When it’s all said and done you’ll have publically-accessible documentation hosted for free via GitHub Pages. I have
a custom domain setup for my GitHub pages (the same one you’re looking at right now!) so you can view an example over at:
&lt;a href=&quot;https://avojak.com/libcatalyst/catalyst-1/&quot;&gt;https://avojak.com/libcatalyst/catalyst-1/&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And no project like this would be completely without a shiny new badge for your README:&lt;/p&gt;

&lt;div class=&quot;language-markdown highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;![Documentation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;](&lt;/span&gt;&lt;span class=&quot;sx&quot;&gt;https://img.shields.io/badge/documentation-valadoc-a56de2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;](https://avojak.com/libcatalyst/catalyst-1/)
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href=&quot;https://avojak.com/libcatalyst/catalyst-1/&quot;&gt;&lt;img src=&quot;https://img.shields.io/badge/documentation-valadoc-a56de2&quot; alt=&quot;Documentation&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can see a full example project using this exact process over on my GitHub:&lt;/p&gt;

&lt;div class=&quot;github-card&quot;&gt;
  &lt;header&gt;
    &lt;a class=&quot;account&quot; href=&quot;https://github.com/avojak/libcatalyst&quot;&gt;
      &lt;img class=&quot;avatar&quot; src=&quot;https://github.com/avojak.png&quot; /&gt;
      &lt;p&gt;avojak/&lt;b&gt;libcatalyst&lt;/b&gt;&lt;/p&gt;
    &lt;/a&gt;
    &lt;a href=&quot;https://github.com/avojak/libcatalyst&quot;&gt;
      &lt;img class=&quot;logo&quot; src=&quot;/blog/images/github.svg&quot; /&gt;
    &lt;/a&gt;
  &lt;/header&gt;

  

  &lt;p id=&quot;avojak/libcatalyst-description&quot;&gt;&lt;/p&gt;

  &lt;footer&gt;
    &lt;span id=&quot;avojak/libcatalyst-issues&quot; style=&quot;white-space:nowrap;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 512 512&quot;&gt;&lt;!--! Font Awesome Pro 6.1.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --&gt;&lt;path d=&quot;M160 256C160 202.1 202.1 160 256 160C309 160 352 202.1 352 256C352 309 309 352 256 352C202.1 352 160 309 160 256zM512 256C512 397.4 397.4 512 256 512C114.6 512 0 397.4 0 256C0 114.6 114.6 0 256 0C397.4 0 512 114.6 512 256zM256 48C141.1 48 48 141.1 48 256C48 370.9 141.1 464 256 464C370.9 464 464 370.9 464 256C464 141.1 370.9 48 256 48z&quot; /&gt;&lt;/svg&gt;&lt;/span&gt;
    &lt;span id=&quot;avojak/libcatalyst-stargazers&quot; style=&quot;white-space:nowrap;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 576 512&quot;&gt;&lt;!--! Font Awesome Pro 6.1.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --&gt;&lt;path d=&quot;M287.9 0C297.1 0 305.5 5.25 309.5 13.52L378.1 154.8L531.4 177.5C540.4 178.8 547.8 185.1 550.7 193.7C553.5 202.4 551.2 211.9 544.8 218.2L433.6 328.4L459.9 483.9C461.4 492.9 457.7 502.1 450.2 507.4C442.8 512.7 432.1 513.4 424.9 509.1L287.9 435.9L150.1 509.1C142.9 513.4 133.1 512.7 125.6 507.4C118.2 502.1 114.5 492.9 115.1 483.9L142.2 328.4L31.11 218.2C24.65 211.9 22.36 202.4 25.2 193.7C28.03 185.1 35.5 178.8 44.49 177.5L197.7 154.8L266.3 13.52C270.4 5.249 278.7 0 287.9 0L287.9 0zM287.9 78.95L235.4 187.2C231.9 194.3 225.1 199.3 217.3 200.5L98.98 217.9L184.9 303C190.4 308.5 192.9 316.4 191.6 324.1L171.4 443.7L276.6 387.5C283.7 383.7 292.2 383.7 299.2 387.5L404.4 443.7L384.2 324.1C382.9 316.4 385.5 308.5 391 303L476.9 217.9L358.6 200.5C350.7 199.3 343.9 194.3 340.5 187.2L287.9 78.95z&quot; /&gt;&lt;/svg&gt;&lt;/span&gt;
    &lt;span id=&quot;avojak/libcatalyst-forks&quot; style=&quot;white-space:nowrap;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 448 512&quot;&gt;&lt;!--! Font Awesome Pro 6.1.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --&gt;&lt;path d=&quot;M160 80C160 112.8 140.3 140.1 112 153.3V192C112 209.7 126.3 224 144 224H304C321.7 224 336 209.7 336 192V153.3C307.7 140.1 288 112.8 288 80C288 35.82 323.8 0 368 0C412.2 0 448 35.82 448 80C448 112.8 428.3 140.1 400 153.3V192C400 245 357 288 304 288H256V358.7C284.3 371 304 399.2 304 432C304 476.2 268.2 512 224 512C179.8 512 144 476.2 144 432C144 399.2 163.7 371 192 358.7V288H144C90.98 288 48 245 48 192V153.3C19.75 140.1 0 112.8 0 80C0 35.82 35.82 0 80 0C124.2 0 160 35.82 160 80V80zM80 104C93.25 104 104 93.25 104 80C104 66.75 93.25 56 80 56C66.75 56 56 66.75 56 80C56 93.25 66.75 104 80 104zM368 104C381.3 104 392 93.25 392 80C392 66.75 381.3 56 368 56C354.7 56 344 66.75 344 80C344 93.25 354.7 104 368 104zM224 408C210.7 408 200 418.7 200 432C200 445.3 210.7 456 224 456C237.3 456 248 445.3 248 432C248 418.7 237.3 408 224 408z&quot; /&gt;&lt;/svg&gt;&lt;/span&gt;
  &lt;/footer&gt;
  &lt;footer&gt;
    &lt;a href=&quot;https://github.com/avojak/libcatalyst&quot;&gt;
      &lt;span&gt;View on GitHub →&lt;/span&gt;
    &lt;/a&gt;
  &lt;/footer&gt;

  &lt;script defer=&quot;&quot;&gt;
    // Data for testing without actually hitting the GitHub API
    // out = {&quot;stargazers_count&quot;:123456, &quot;forks_count&quot;:123, &quot;open_issues_count&quot;:60, &quot;description&quot;:&quot;Super cool project with absolutely zero bugs&quot;};
    // document.getElementById(&quot;avojak/libcatalyst-stargazers&quot;).innerHTML += numeral(out['stargazers_count']).format('0.[0]a') + &quot; Stars&quot;;
    // document.getElementById(&quot;avojak/libcatalyst-issues&quot;).innerHTML += numeral(out['open_issues_count']).format('0.[0]a') + &quot; Issues&quot;;
    // document.getElementById(&quot;avojak/libcatalyst-description&quot;).innerHTML = out['description'];
    // document.getElementById(&quot;avojak/libcatalyst-forks&quot;).innerHTML += numeral(out['forks_count']).format('0.[0]a') + &quot; Forks&quot;;

    fetch('https://api.github.com/repos/avojak/libcatalyst')
      .then(res =&gt; {
        if (!res.ok) {
          throw new Error(&quot;Bad network response&quot;);
        }
        return res.json();
      })
      .then((out) =&gt; {
        var stargazers = out['stargazers_count']
        var issues = out['open_issues_count']
        var forks = out['forks_count']
        
        // Populate the elements with the data from the response
        document.getElementById(&quot;avojak/libcatalyst-stargazers&quot;).innerHTML += numeral(stargazers).format('0.[0]a') + &quot; Star&quot; + (stargazers != 1 ? &quot;s&quot; : &quot;&quot;);
        document.getElementById(&quot;avojak/libcatalyst-issues&quot;).innerHTML += numeral(issues).format('0.[0]a') + &quot; Issue&quot; + (issues != 1 ? &quot;s&quot; : &quot;&quot;);
        document.getElementById(&quot;avojak/libcatalyst-forks&quot;).innerHTML += numeral(forks).format('0.[0]a') + &quot; Fork&quot; + (forks != 1 ? &quot;s&quot; : &quot;&quot;);
        document.getElementById(&quot;avojak/libcatalyst-description&quot;).innerHTML = out['description'];
    }).catch((err) =&gt; {
      console.error(err);
      // Clear out the fields in case of error rather than showing 0 values
      document.getElementById(&quot;avojak/libcatalyst-stargazers&quot;).innerHTML = &quot;&quot;;
      document.getElementById(&quot;avojak/libcatalyst-issues&quot;).innerHTML = &quot;&quot;;
      document.getElementById(&quot;avojak/libcatalyst-forks&quot;).innerHTML = &quot;&quot;;
    });
  &lt;/script&gt;
&lt;/div&gt;
</description>
          <pubDate>Fri, 05 Aug 2022 00:00:00 +0000</pubDate>
          <link>https://avojak.com/blog/2022/08/05/deploying-valadoc-to-github-pages/</link>
          <guid isPermaLink="true">https://avojak.com/blog/2022/08/05/deploying-valadoc-to-github-pages/</guid>
          
              
              
          <enclosure url="https://avojak.com/blog" />
          
          
          <category>evergreen</category>
          
          <category>software</category>
          
          
        </item>
      
    
      
        <item>
          <title>Libsemver</title>
          <description>&lt;p&gt;As I’ve been developing apps in Vala and GTK for a few years now, there have been two questions nagging at me the entire time:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;How libraries are written in Vala?&lt;/li&gt;
  &lt;li&gt;How do you properly unit test code written in Vala?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Frankly the second question is one that I &lt;em&gt;should&lt;/em&gt; have investigated from the start, but better late than never, no time like the present to learn, and other adages.&lt;/p&gt;

&lt;p&gt;To learn by doing, I settled on writing a library to handle Semantic Versions that I aptly named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;libsemver&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;github-card&quot;&gt;
  &lt;header&gt;
    &lt;a class=&quot;account&quot; href=&quot;https://github.com/avojak/libsemver&quot;&gt;
      &lt;img class=&quot;avatar&quot; src=&quot;https://github.com/avojak.png&quot; /&gt;
      &lt;p&gt;avojak/&lt;b&gt;libsemver&lt;/b&gt;&lt;/p&gt;
    &lt;/a&gt;
    &lt;a href=&quot;https://github.com/avojak/libsemver&quot;&gt;
      &lt;img class=&quot;logo&quot; src=&quot;/blog/images/github.svg&quot; /&gt;
    &lt;/a&gt;
  &lt;/header&gt;

  

  &lt;p id=&quot;avojak/libsemver-description&quot;&gt;&lt;/p&gt;

  &lt;footer&gt;
    &lt;span id=&quot;avojak/libsemver-issues&quot; style=&quot;white-space:nowrap;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 512 512&quot;&gt;&lt;!--! Font Awesome Pro 6.1.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --&gt;&lt;path d=&quot;M160 256C160 202.1 202.1 160 256 160C309 160 352 202.1 352 256C352 309 309 352 256 352C202.1 352 160 309 160 256zM512 256C512 397.4 397.4 512 256 512C114.6 512 0 397.4 0 256C0 114.6 114.6 0 256 0C397.4 0 512 114.6 512 256zM256 48C141.1 48 48 141.1 48 256C48 370.9 141.1 464 256 464C370.9 464 464 370.9 464 256C464 141.1 370.9 48 256 48z&quot; /&gt;&lt;/svg&gt;&lt;/span&gt;
    &lt;span id=&quot;avojak/libsemver-stargazers&quot; style=&quot;white-space:nowrap;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 576 512&quot;&gt;&lt;!--! Font Awesome Pro 6.1.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --&gt;&lt;path d=&quot;M287.9 0C297.1 0 305.5 5.25 309.5 13.52L378.1 154.8L531.4 177.5C540.4 178.8 547.8 185.1 550.7 193.7C553.5 202.4 551.2 211.9 544.8 218.2L433.6 328.4L459.9 483.9C461.4 492.9 457.7 502.1 450.2 507.4C442.8 512.7 432.1 513.4 424.9 509.1L287.9 435.9L150.1 509.1C142.9 513.4 133.1 512.7 125.6 507.4C118.2 502.1 114.5 492.9 115.1 483.9L142.2 328.4L31.11 218.2C24.65 211.9 22.36 202.4 25.2 193.7C28.03 185.1 35.5 178.8 44.49 177.5L197.7 154.8L266.3 13.52C270.4 5.249 278.7 0 287.9 0L287.9 0zM287.9 78.95L235.4 187.2C231.9 194.3 225.1 199.3 217.3 200.5L98.98 217.9L184.9 303C190.4 308.5 192.9 316.4 191.6 324.1L171.4 443.7L276.6 387.5C283.7 383.7 292.2 383.7 299.2 387.5L404.4 443.7L384.2 324.1C382.9 316.4 385.5 308.5 391 303L476.9 217.9L358.6 200.5C350.7 199.3 343.9 194.3 340.5 187.2L287.9 78.95z&quot; /&gt;&lt;/svg&gt;&lt;/span&gt;
    &lt;span id=&quot;avojak/libsemver-forks&quot; style=&quot;white-space:nowrap;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 448 512&quot;&gt;&lt;!--! Font Awesome Pro 6.1.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --&gt;&lt;path d=&quot;M160 80C160 112.8 140.3 140.1 112 153.3V192C112 209.7 126.3 224 144 224H304C321.7 224 336 209.7 336 192V153.3C307.7 140.1 288 112.8 288 80C288 35.82 323.8 0 368 0C412.2 0 448 35.82 448 80C448 112.8 428.3 140.1 400 153.3V192C400 245 357 288 304 288H256V358.7C284.3 371 304 399.2 304 432C304 476.2 268.2 512 224 512C179.8 512 144 476.2 144 432C144 399.2 163.7 371 192 358.7V288H144C90.98 288 48 245 48 192V153.3C19.75 140.1 0 112.8 0 80C0 35.82 35.82 0 80 0C124.2 0 160 35.82 160 80V80zM80 104C93.25 104 104 93.25 104 80C104 66.75 93.25 56 80 56C66.75 56 56 66.75 56 80C56 93.25 66.75 104 80 104zM368 104C381.3 104 392 93.25 392 80C392 66.75 381.3 56 368 56C354.7 56 344 66.75 344 80C344 93.25 354.7 104 368 104zM224 408C210.7 408 200 418.7 200 432C200 445.3 210.7 456 224 456C237.3 456 248 445.3 248 432C248 418.7 237.3 408 224 408z&quot; /&gt;&lt;/svg&gt;&lt;/span&gt;
  &lt;/footer&gt;
  &lt;footer&gt;
    &lt;a href=&quot;https://github.com/avojak/libsemver&quot;&gt;
      &lt;span&gt;View on GitHub →&lt;/span&gt;
    &lt;/a&gt;
  &lt;/footer&gt;

  &lt;script defer=&quot;&quot;&gt;
    // Data for testing without actually hitting the GitHub API
    // out = {&quot;stargazers_count&quot;:123456, &quot;forks_count&quot;:123, &quot;open_issues_count&quot;:60, &quot;description&quot;:&quot;Super cool project with absolutely zero bugs&quot;};
    // document.getElementById(&quot;avojak/libsemver-stargazers&quot;).innerHTML += numeral(out['stargazers_count']).format('0.[0]a') + &quot; Stars&quot;;
    // document.getElementById(&quot;avojak/libsemver-issues&quot;).innerHTML += numeral(out['open_issues_count']).format('0.[0]a') + &quot; Issues&quot;;
    // document.getElementById(&quot;avojak/libsemver-description&quot;).innerHTML = out['description'];
    // document.getElementById(&quot;avojak/libsemver-forks&quot;).innerHTML += numeral(out['forks_count']).format('0.[0]a') + &quot; Forks&quot;;

    fetch('https://api.github.com/repos/avojak/libsemver')
      .then(res =&gt; {
        if (!res.ok) {
          throw new Error(&quot;Bad network response&quot;);
        }
        return res.json();
      })
      .then((out) =&gt; {
        var stargazers = out['stargazers_count']
        var issues = out['open_issues_count']
        var forks = out['forks_count']
        
        // Populate the elements with the data from the response
        document.getElementById(&quot;avojak/libsemver-stargazers&quot;).innerHTML += numeral(stargazers).format('0.[0]a') + &quot; Star&quot; + (stargazers != 1 ? &quot;s&quot; : &quot;&quot;);
        document.getElementById(&quot;avojak/libsemver-issues&quot;).innerHTML += numeral(issues).format('0.[0]a') + &quot; Issue&quot; + (issues != 1 ? &quot;s&quot; : &quot;&quot;);
        document.getElementById(&quot;avojak/libsemver-forks&quot;).innerHTML += numeral(forks).format('0.[0]a') + &quot; Fork&quot; + (forks != 1 ? &quot;s&quot; : &quot;&quot;);
        document.getElementById(&quot;avojak/libsemver-description&quot;).innerHTML = out['description'];
    }).catch((err) =&gt; {
      console.error(err);
      // Clear out the fields in case of error rather than showing 0 values
      document.getElementById(&quot;avojak/libsemver-stargazers&quot;).innerHTML = &quot;&quot;;
      document.getElementById(&quot;avojak/libsemver-issues&quot;).innerHTML = &quot;&quot;;
      document.getElementById(&quot;avojak/libsemver-forks&quot;).innerHTML = &quot;&quot;;
    });
  &lt;/script&gt;
&lt;/div&gt;

&lt;h2 id=&quot;how-are-libraries-written-in-vala&quot;&gt;How Are Libraries Written in Vala?&lt;/h2&gt;

&lt;p&gt;Well the real meat and potatoes is just like writing a library in any other language, but the project setup for a library is incredibly confusing if you’re not sure what you’re doing. Spoiler: I wasn’t sure what I was doing, and frankly I still don’t.
The amount of boilerplate code that goes into a library so that it’s properly picked up by the system when you try to use it is a bit overwhelming. I can’t really write much about this process because like I said, I still don’t fully understand
all the ins and outs. I used &lt;a href=&quot;https://github.com/elementary/granite&quot;&gt;Granite&lt;/a&gt; heavily as a reference since I had used it before, and in the end I was able to successfully pull my library into a test project and use it.&lt;/p&gt;

&lt;p&gt;Another great reference that I came across was &lt;a href=&quot;https://github.com/vala-lang/valdo&quot;&gt;Valdo&lt;/a&gt;, which contains a handful of templates for creating various types of projects in Vala.&lt;/p&gt;

&lt;h2 id=&quot;how-do-i-properly-write-unit-tests-in-vala&quot;&gt;How Do I Properly Write Unit Tests in Vala?&lt;/h2&gt;

&lt;p&gt;GLib Test was the natural starting place, and after poking around on how other people did testing, I landed on the following:&lt;/p&gt;

&lt;h3 id=&quot;directory-structure&quot;&gt;Directory structure&lt;/h3&gt;

&lt;p&gt;The root directory for all testing code is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;libsemver/tests/&lt;/code&gt;. That directory is included in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;meson.build&lt;/code&gt; file in the root of the project.&lt;/p&gt;

&lt;p&gt;In &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;libsemver/tests/&lt;/code&gt;, we have all of our files containing the test cases. I chose to break it down such that there’s a 1:1 relationship between
library sources files and test source files (e.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Version.vala&lt;/code&gt; corresponds to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;VersionTest.vala&lt;/code&gt;). In my research I found some GNOME projects
that broke out test cases into their own file, but this seemed to have the potential to get out of control very quickly.&lt;/p&gt;

&lt;p&gt;In the tests &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;meson.build&lt;/code&gt;, each test source file is listed and a loop is used to create separate test executables for each file:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;unit_test_files &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;s1&quot;&gt;'Specification'&lt;/span&gt;,
    &lt;span class=&quot;s1&quot;&gt;'Util'&lt;/span&gt;,
    &lt;span class=&quot;s1&quot;&gt;'Version'&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;

foreach name : unit_test_files
    &lt;span class=&quot;nb&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;name, executable&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;name, name + &lt;span class=&quot;s1&quot;&gt;'Test.vala'&lt;/span&gt;, libsemver_sources, dependencies: libsemver_deps, &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt;: &lt;span class=&quot;nb&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;
endforeach
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Notice that in reality just part of the test source file name is enumerated, and the full name is build later. This lets us have more user-friendly
test case names when we go to run the tests.&lt;/p&gt;

&lt;h3 id=&quot;test-source-files&quot;&gt;Test Source Files&lt;/h3&gt;

&lt;p&gt;Lets take a quick look at the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UtilTest.vala&lt;/code&gt; file which contains unit tests for a large-number arithmetic utility class I wrote to safely handle
adding, subtracting, and comparison of numbers larger than what can be held in a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uint64&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-vala highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;UtilTest&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;GLib&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Object&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

    &lt;span class=&quot;cm&quot;&gt;/**
      * Tests large number addition.
      */&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;test_addition&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;assert_true&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Util&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;large_number_addition&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;0&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;0&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;0&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;assert_true&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Util&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;large_number_addition&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;0&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;assert_true&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Util&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;large_number_addition&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;0&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;assert_true&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Util&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;large_number_addition&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;2&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;assert_true&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Util&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;large_number_addition&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;123&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;58&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;181&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;assert_true&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Util&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;large_number_addition&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;999&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;1000&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;assert_true&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Util&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;large_number_addition&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;999&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;1000&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;assert_true&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Util&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;large_number_addition&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;99999999999999999999998&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;99999999999999999999999&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;assert_true&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Util&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;large_number_addition&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;99999999999999999999999&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;100000000000000000000000&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;cm&quot;&gt;/**
      * Tests large number subtraction.
      */&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;test_subtraction&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;assert_true&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Util&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;large_number_subtraction&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;0&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;0&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;0&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;assert_true&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Util&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;large_number_subtraction&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;0&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;assert_true&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Util&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;large_number_subtraction&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;0&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;assert_true&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Util&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;large_number_subtraction&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;2&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;assert_true&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Util&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;large_number_subtraction&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;181&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;58&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;123&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;assert_true&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Util&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;large_number_subtraction&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;121&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;58&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;63&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;assert_true&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Util&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;large_number_subtraction&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;58&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;121&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;-63&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;assert_true&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Util&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;large_number_subtraction&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;0&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;-1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;assert_true&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Util&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;large_number_subtraction&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;99999999999999999999999&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;99999999999999999999998&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;GLib&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Test&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;init&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;ref&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;GLib&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Test&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;add_func&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/util/addition&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;test_addition&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;GLib&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Test&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;add_func&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/util/subtraction&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;test_subtraction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;GLib&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Test&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;run&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main&lt;/code&gt; method we initialize GLib Test and then queue up our test functions. It’s all pretty straightforward, using assertions to verify
that your code behaves as expected. However when we go to run this test, we get the following output:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;ninja &lt;span class=&quot;nt&quot;&gt;-C&lt;/span&gt; build &lt;span class=&quot;nb&quot;&gt;test&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;0/1] Running all tests.
1/1 Util                                    OK       0.01 s 

Ok:                    1
Expected Fail:         0
Fail:                  0
Unexpected Pass:       0
Skipped:               0
Timeout:               0
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Well, ok, we can see everything passed, but even though we wrote 18 different assertions across two different test functions, it was all globbed
into a single test entry. Not awful, but not ideal.&lt;/p&gt;

&lt;p&gt;Now let’s see what happens if we have a failing test:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nv&quot;&gt;$ninja&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-C&lt;/span&gt; build &lt;span class=&quot;nb&quot;&gt;test&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;3/4] Running all tests.
1/1 Util                                    FAIL     0.01 s &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;killed by signal 6 SIGABRT&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;

Ok:                    0
Expected Fail:         0
Fail:                  1
Unexpected Pass:       0
Skipped:               0
Timeout:               0


The output from the failed tests:

1/1 Util                                    FAIL     0.01 s &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;killed by signal 6 SIGABRT&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;---&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;command&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;---&lt;/span&gt;
16:07:52 /home/avojak/git/libsemver/build/tests/Util
&lt;span class=&quot;nt&quot;&gt;---&lt;/span&gt; stdout &lt;span class=&quot;nt&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# random seed: R02S9ff2e81c6ab0e5ea9e06492ec046b612&lt;/span&gt;
1..2
&lt;span class=&quot;c&quot;&gt;# Start of util tests&lt;/span&gt;
Bail out! ERROR:../tests/UtilTest.vala:14:sem_ver_util_test_test_addition: &lt;span class=&quot;s1&quot;&gt;'g_strcmp0 (_tmp1_, &quot;1&quot;) == 0'&lt;/span&gt; should be TRUE
&lt;span class=&quot;nt&quot;&gt;---&lt;/span&gt; stderr &lt;span class=&quot;nt&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;**&lt;/span&gt;
ERROR:../tests/UtilTest.vala:14:sem_ver_util_test_test_addition: &lt;span class=&quot;s1&quot;&gt;'g_strcmp0 (_tmp1_, &quot;1&quot;) == 0'&lt;/span&gt; should be TRUE
&lt;span class=&quot;nt&quot;&gt;-------&lt;/span&gt;

Full log written to /home/avojak/git/libsemver/build/meson-logs/testlog.txt
FAILED: meson-test 
/usr/bin/meson &lt;span class=&quot;nb&quot;&gt;test&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--no-rebuild&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--print-errorlogs&lt;/span&gt;
ninja: build stopped: subcommand failed.
make: &lt;span class=&quot;k&quot;&gt;***&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;Makefile:19: &lt;span class=&quot;nb&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt; Error 1
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Ok… we can conclude that we failed on the assertion on line 14, but the output is not exactly friendly… Our assertion was:&lt;/p&gt;

&lt;div class=&quot;language-vala highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;rouge-gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;rouge-code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nf&quot;&gt;assert_true&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Util&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;large_number_addition&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;0&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;0&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// Will fail intentionally&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It’s easy to see how in a slightly more complicated test this would not be as simple to figure out. Additionally, if you use any
sort of helper function in your testing to do assertions, there’s no trace. That means any tests using that helper function would
show the same line number in case of a failure.&lt;/p&gt;

&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;

&lt;p&gt;Writing unit tests with GLib Test feels a bit cludgy.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Poor feedback from failed assertions (need better messaging)&lt;/li&gt;
  &lt;li&gt;Poor visibility as to which test case failed (output is only on a class level, so if a class has multiple assertions they all get globbed together)&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;whats-next&quot;&gt;What’s Next?&lt;/h2&gt;

&lt;h3 id=&quot;better-unit-testing&quot;&gt;Better Unit Testing&lt;/h3&gt;

&lt;p&gt;As a next step, I would really like to spend some time digging into a project that I recently discovered called Valadate, which is a testing framework built on top of GLib Test:&lt;/p&gt;

&lt;div class=&quot;github-card&quot;&gt;
  &lt;header&gt;
    &lt;a class=&quot;account&quot; href=&quot;https://github.com/chebizarro/valadate&quot;&gt;
      &lt;img class=&quot;avatar&quot; src=&quot;https://github.com/chebizarro.png&quot; /&gt;
      &lt;p&gt;chebizarro/&lt;b&gt;valadate&lt;/b&gt;&lt;/p&gt;
    &lt;/a&gt;
    &lt;a href=&quot;https://github.com/chebizarro/valadate&quot;&gt;
      &lt;img class=&quot;logo&quot; src=&quot;/blog/images/github.svg&quot; /&gt;
    &lt;/a&gt;
  &lt;/header&gt;

  

  &lt;p id=&quot;chebizarro/valadate-description&quot;&gt;&lt;/p&gt;

  &lt;footer&gt;
    &lt;span id=&quot;chebizarro/valadate-issues&quot; style=&quot;white-space:nowrap;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 512 512&quot;&gt;&lt;!--! Font Awesome Pro 6.1.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --&gt;&lt;path d=&quot;M160 256C160 202.1 202.1 160 256 160C309 160 352 202.1 352 256C352 309 309 352 256 352C202.1 352 160 309 160 256zM512 256C512 397.4 397.4 512 256 512C114.6 512 0 397.4 0 256C0 114.6 114.6 0 256 0C397.4 0 512 114.6 512 256zM256 48C141.1 48 48 141.1 48 256C48 370.9 141.1 464 256 464C370.9 464 464 370.9 464 256C464 141.1 370.9 48 256 48z&quot; /&gt;&lt;/svg&gt;&lt;/span&gt;
    &lt;span id=&quot;chebizarro/valadate-stargazers&quot; style=&quot;white-space:nowrap;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 576 512&quot;&gt;&lt;!--! Font Awesome Pro 6.1.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --&gt;&lt;path d=&quot;M287.9 0C297.1 0 305.5 5.25 309.5 13.52L378.1 154.8L531.4 177.5C540.4 178.8 547.8 185.1 550.7 193.7C553.5 202.4 551.2 211.9 544.8 218.2L433.6 328.4L459.9 483.9C461.4 492.9 457.7 502.1 450.2 507.4C442.8 512.7 432.1 513.4 424.9 509.1L287.9 435.9L150.1 509.1C142.9 513.4 133.1 512.7 125.6 507.4C118.2 502.1 114.5 492.9 115.1 483.9L142.2 328.4L31.11 218.2C24.65 211.9 22.36 202.4 25.2 193.7C28.03 185.1 35.5 178.8 44.49 177.5L197.7 154.8L266.3 13.52C270.4 5.249 278.7 0 287.9 0L287.9 0zM287.9 78.95L235.4 187.2C231.9 194.3 225.1 199.3 217.3 200.5L98.98 217.9L184.9 303C190.4 308.5 192.9 316.4 191.6 324.1L171.4 443.7L276.6 387.5C283.7 383.7 292.2 383.7 299.2 387.5L404.4 443.7L384.2 324.1C382.9 316.4 385.5 308.5 391 303L476.9 217.9L358.6 200.5C350.7 199.3 343.9 194.3 340.5 187.2L287.9 78.95z&quot; /&gt;&lt;/svg&gt;&lt;/span&gt;
    &lt;span id=&quot;chebizarro/valadate-forks&quot; style=&quot;white-space:nowrap;&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 448 512&quot;&gt;&lt;!--! Font Awesome Pro 6.1.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --&gt;&lt;path d=&quot;M160 80C160 112.8 140.3 140.1 112 153.3V192C112 209.7 126.3 224 144 224H304C321.7 224 336 209.7 336 192V153.3C307.7 140.1 288 112.8 288 80C288 35.82 323.8 0 368 0C412.2 0 448 35.82 448 80C448 112.8 428.3 140.1 400 153.3V192C400 245 357 288 304 288H256V358.7C284.3 371 304 399.2 304 432C304 476.2 268.2 512 224 512C179.8 512 144 476.2 144 432C144 399.2 163.7 371 192 358.7V288H144C90.98 288 48 245 48 192V153.3C19.75 140.1 0 112.8 0 80C0 35.82 35.82 0 80 0C124.2 0 160 35.82 160 80V80zM80 104C93.25 104 104 93.25 104 80C104 66.75 93.25 56 80 56C66.75 56 56 66.75 56 80C56 93.25 66.75 104 80 104zM368 104C381.3 104 392 93.25 392 80C392 66.75 381.3 56 368 56C354.7 56 344 66.75 344 80C344 93.25 354.7 104 368 104zM224 408C210.7 408 200 418.7 200 432C200 445.3 210.7 456 224 456C237.3 456 248 445.3 248 432C248 418.7 237.3 408 224 408z&quot; /&gt;&lt;/svg&gt;&lt;/span&gt;
  &lt;/footer&gt;
  &lt;footer&gt;
    &lt;a href=&quot;https://github.com/chebizarro/valadate&quot;&gt;
      &lt;span&gt;View on GitHub →&lt;/span&gt;
    &lt;/a&gt;
  &lt;/footer&gt;

  &lt;script defer=&quot;&quot;&gt;
    // Data for testing without actually hitting the GitHub API
    // out = {&quot;stargazers_count&quot;:123456, &quot;forks_count&quot;:123, &quot;open_issues_count&quot;:60, &quot;description&quot;:&quot;Super cool project with absolutely zero bugs&quot;};
    // document.getElementById(&quot;chebizarro/valadate-stargazers&quot;).innerHTML += numeral(out['stargazers_count']).format('0.[0]a') + &quot; Stars&quot;;
    // document.getElementById(&quot;chebizarro/valadate-issues&quot;).innerHTML += numeral(out['open_issues_count']).format('0.[0]a') + &quot; Issues&quot;;
    // document.getElementById(&quot;chebizarro/valadate-description&quot;).innerHTML = out['description'];
    // document.getElementById(&quot;chebizarro/valadate-forks&quot;).innerHTML += numeral(out['forks_count']).format('0.[0]a') + &quot; Forks&quot;;

    fetch('https://api.github.com/repos/chebizarro/valadate')
      .then(res =&gt; {
        if (!res.ok) {
          throw new Error(&quot;Bad network response&quot;);
        }
        return res.json();
      })
      .then((out) =&gt; {
        var stargazers = out['stargazers_count']
        var issues = out['open_issues_count']
        var forks = out['forks_count']
        
        // Populate the elements with the data from the response
        document.getElementById(&quot;chebizarro/valadate-stargazers&quot;).innerHTML += numeral(stargazers).format('0.[0]a') + &quot; Star&quot; + (stargazers != 1 ? &quot;s&quot; : &quot;&quot;);
        document.getElementById(&quot;chebizarro/valadate-issues&quot;).innerHTML += numeral(issues).format('0.[0]a') + &quot; Issue&quot; + (issues != 1 ? &quot;s&quot; : &quot;&quot;);
        document.getElementById(&quot;chebizarro/valadate-forks&quot;).innerHTML += numeral(forks).format('0.[0]a') + &quot; Fork&quot; + (forks != 1 ? &quot;s&quot; : &quot;&quot;);
        document.getElementById(&quot;chebizarro/valadate-description&quot;).innerHTML = out['description'];
    }).catch((err) =&gt; {
      console.error(err);
      // Clear out the fields in case of error rather than showing 0 values
      document.getElementById(&quot;chebizarro/valadate-stargazers&quot;).innerHTML = &quot;&quot;;
      document.getElementById(&quot;chebizarro/valadate-issues&quot;).innerHTML = &quot;&quot;;
      document.getElementById(&quot;chebizarro/valadate-forks&quot;).innerHTML = &quot;&quot;;
    });
  &lt;/script&gt;
&lt;/div&gt;

&lt;p&gt;Although it is no longer maintained, I’ve forked the project and hope that it can still be of use.&lt;/p&gt;

&lt;p&gt;I would also like to explore testing of Gtk widgets.&lt;/p&gt;

&lt;h3 id=&quot;deployment-of-documentation&quot;&gt;Deployment of Documentation&lt;/h3&gt;

&lt;p&gt;Although the library is currently configured to generate a documentation site using Valadoc, it’s not deployed anywhere. I would like to explore some options, including leveraging GitHub Pages for static hosting. One challenge here will be managing multiple versions of the documentation (e.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1.0.0&lt;/code&gt; vs. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;latest&lt;/code&gt;).&lt;/p&gt;
</description>
          <pubDate>Fri, 10 Jun 2022 00:00:00 +0000</pubDate>
          <link>https://avojak.com/blog/2022/06/10/libsemver/</link>
          <guid isPermaLink="true">https://avojak.com/blog/2022/06/10/libsemver/</guid>
          
              
              
          <enclosure url="https://avojak.com/blog" />
          
          
          <category>software</category>
          
          <category>evergreen</category>
          
          
        </item>
      
    
  </channel>
</rss>

